@birdybeep/agent-core 0.0.1

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 @@
1
+ {"version":3,"sources":["../src/adapter.ts","../src/api.ts","../src/dedup.ts","../src/paths.ts","../src/event.ts","../src/primitives.ts","../src/fingerprint.ts","../src/hook.ts","../src/integrations.ts","../src/normalize.ts","../src/pairing.ts","../src/queue.ts","../src/token-store.ts","../src/sender.ts","../src/index.ts"],"sourcesContent":["/**\n * The `AgentAdapter` interface (§9.1): the one uniform contract the Claude Code,\n * Codex, and OpenCode adapters all implement, so the CLI can detect/install/\n * uninstall/check/doctor/normalize any harness without special-casing. New\n * harnesses plug in by implementing this — nothing in the CLI changes.\n *\n * Contract every adapter MUST honor (§7.3), enforced by the shared E2E harness:\n * - install is IDEMPOTENT (a second install is a no-op);\n * - existing config is BACKED UP before modification;\n * - only BirdyBeep-MANAGED entries are added (existing config preserved);\n * - tokens are NEVER written into harness config or repo files;\n * - install/uninstall report changed files + any required user actions;\n * - uninstall removes exactly the managed entries (restores the original).\n */\nimport type { BirdyBeepAgentEvent } from \"./event\";\nimport type { HarnessId } from \"./primitives\";\n\n/** Integration status values (§8.8). */\nexport const INTEGRATION_STATUSES = [\n \"installed\",\n \"not_detected\",\n \"needs_restart\",\n \"needs_trust\",\n \"error\",\n \"revoked\",\n \"unknown\",\n] as const;\nexport type IntegrationStatus = (typeof INTEGRATION_STATUSES)[number];\n\n/** Result of probing whether a harness is present on the machine. */\nexport interface DetectionResult {\n detected: boolean;\n version?: string;\n /** Path to the harness config the adapter would manage, if found. */\n configPath?: string;\n detail?: string;\n}\n\nexport interface InstallOptions {\n /** Compute + report changes without writing anything. */\n dryRun?: boolean;\n}\n\nexport interface InstallResult {\n /** False when the install was a no-op (already installed / idempotent re-run). */\n changed: boolean;\n /** Config files created or modified by this install. */\n changedFiles: string[];\n /** Backups written before modifying pre-existing config. */\n backupFiles: string[];\n /** Actions the user must still take (e.g. trust Codex hooks, restart OpenCode). */\n requiredActions: string[];\n /** Resulting integration status (e.g. installed / needs_trust / needs_restart). */\n status: IntegrationStatus;\n}\n\nexport interface UninstallOptions {\n dryRun?: boolean;\n}\n\nexport interface UninstallResult {\n changed: boolean;\n /** Files removed (created by BirdyBeep). */\n removedFiles: string[];\n /** Files restored from backup to their pre-install contents. */\n restoredFiles: string[];\n}\n\nexport interface DoctorCheck {\n name: string;\n ok: boolean;\n status?: IntegrationStatus;\n detail?: string;\n /** Suggested fix when `ok` is false. */\n remedy?: string;\n}\n\nexport interface DoctorResult {\n ok: boolean;\n checks: DoctorCheck[];\n}\n\n/** The contract implemented by every harness adapter (§9.1). */\nexport interface AgentAdapter {\n /** Stable harness id (also the event `harness` value). */\n readonly id: HarnessId;\n /** Human-facing harness name, e.g. \"Claude Code\". */\n readonly displayName: string;\n\n /** Is this harness installed/usable on the machine? */\n detect(): Promise<DetectionResult>;\n /** Idempotently install BirdyBeep into the harness config (backs up first). */\n install(options?: InstallOptions): Promise<InstallResult>;\n /** Remove BirdyBeep's managed entries, restoring the original config. */\n uninstall(options?: UninstallOptions): Promise<UninstallResult>;\n /** Current integration status (§8.8). */\n status(): Promise<IntegrationStatus>;\n /** Diagnose integration health and suggest remedies. */\n doctor(): Promise<DoctorResult>;\n /** Map a raw harness payload to a redacted, validated canonical event. */\n normalizeEvent(input: unknown): Promise<BirdyBeepAgentEvent>;\n}\n","/**\n * API error/response contract (§13.4) — MIRRORED from the product\n * `packages/schemas/api.ts` (their ticket 95e). The single wire-facing error shape\n * the Worker emits and this CLI parses; agent-core's sender keys retry-vs-terminal\n * off these codes. LOCKSTEP (§16.4): keep ERROR_CODES / the envelope / ERROR_STATUS\n * identical to the product. Additive to and independent of the §10.2 event payload.\n *\n * Messages are human-readable text only — never notification title/body or request\n * content (§15.2); `details` is for safe structured hints (e.g. which field failed).\n */\nimport { z } from \"zod\";\n\n/** Stable, machine-readable error codes the client keys off. */\nexport const ERROR_CODES = [\n \"validation_failed\",\n \"unauthorized\",\n \"forbidden\",\n \"token_revoked\",\n \"not_found\",\n \"payload_too_large\",\n \"rate_limited\",\n \"quota_exceeded\",\n \"internal_error\",\n] as const;\n\nexport const errorCodeSchema = z.enum(ERROR_CODES);\nexport type ErrorCode = (typeof ERROR_CODES)[number];\n\n/** The error response envelope: `{ error: { code, message, details? }, requestId? }`. */\nexport const errorEnvelopeSchema = z.object({\n error: z.object({\n code: errorCodeSchema,\n message: z.string(),\n details: z.unknown().optional(),\n }),\n requestId: z.string().optional(),\n});\nexport type ErrorEnvelope = z.infer<typeof errorEnvelopeSchema>;\n\n/** Generic success envelope (`{ data }`) for endpoints that opt into wrapping. */\nexport const successEnvelopeSchema = <T extends z.ZodTypeAny>(data: T) => z.object({ data });\nexport type SuccessEnvelope<T> = { data: T };\n\n/**\n * Canonical HTTP status per error code — part of the contract: a given code always\n * maps to this status, so the client can branch on either.\n */\nexport const ERROR_STATUS = {\n validation_failed: 400,\n unauthorized: 401,\n forbidden: 403,\n token_revoked: 403,\n not_found: 404,\n payload_too_large: 413,\n rate_limited: 429,\n quota_exceeded: 429,\n internal_error: 500,\n} as const satisfies Record<ErrorCode, number>;\n","/**\n * Recent-event dedup ledger. Each harness hook fires in its OWN process (no daemon),\n * and a single user action can trigger more than one hook (e.g. Claude Code may emit\n * both PermissionRequest and Notification{permission_prompt} for one approval). To\n * avoid a double-beep, the ledger records recently-delivered event identities on disk\n * (strict perms, in the user data dir) and collapses a repeat of the same identity\n * within a short window. Best-effort + FAIL-OPEN: if the ledger can't be read/written\n * it allows the send (better a rare double-beep than a dropped notification).\n *\n * Identity is CONTENT-AWARE (birdybeep-agent-erm): it includes a hash of title+body,\n * so two DIFFERENT notifications of the same type inside the window both beep (the old\n * type-only identity silently dropped the second one). The one case that must still\n * collapse across DIFFERENT content — Claude Code's permission double-fire, whose two\n * shapes share a type but not a body — is handled by the separate short\n * {@link approvalCollapseIdentity} window checked by the hook pipeline.\n */\nimport { createHash } from \"node:crypto\";\nimport {\n chmodSync,\n existsSync,\n mkdirSync,\n readFileSync,\n renameSync,\n rmSync,\n statSync,\n writeFileSync,\n} from \"node:fs\";\nimport { dirname, join } from \"node:path\";\n\nimport { birdyBeepDataDir } from \"./paths\";\n\n/** Default dedup window: collapse identical events seen within 10s. */\nexport const DEFAULT_DEDUP_WINDOW_MS = 10_000;\n\n/**\n * Window for collapsing the SAME-approval double-fire (same session + type, different\n * body — e.g. \"Claude Code needs your permission…\" vs \"Approve Bash?\"). DELIBERATE\n * TRADEOFF: within this window a genuinely DISTINCT second approval is also collapsed\n * (dropped!), inverting the module's better-double-beep-than-drop bias — so the window\n * is as short as the double-fire allows. The double-fire is one harness action emitting\n * two hooks back-to-back (typically <100ms apart); two REAL approvals are separated by\n * the human answering the first, or by prompts that Claude Code serializes in its UI —\n * sub-second spacing is not a realistic distinct-approval pattern. Keep this ≤ the\n * ledger's default window (markAndCheck clamps a wider value down) and NEVER raise it\n * casually: every added millisecond widens the drop window for real approvals.\n * Best-effort under concurrency: two SIMULTANEOUS hook processes can both pass the\n * read-check-write ledger race and double-beep — that direction is fail-open (annoying,\n * not lossy) and accepted.\n */\nexport const APPROVAL_COLLAPSE_WINDOW_MS = 1_000;\n\nexport interface RecentEventLedgerOptions {\n /** Ledger file path (default `<dataDir>/recent-events.json`). */\n path?: string;\n /** Dedup window in ms (default 10s). Also the retention horizon for pruning. */\n windowMs?: number;\n /** Injectable clock (ms) for deterministic tests. */\n now?: () => number;\n}\n\ninterface LedgerEntry {\n id: string;\n ts: number;\n}\nfunction isLedgerEntry(v: unknown): v is LedgerEntry {\n return (\n typeof v === \"object\" &&\n v !== null &&\n typeof (v as Record<string, unknown>)[\"id\"] === \"string\" &&\n typeof (v as Record<string, unknown>)[\"ts\"] === \"number\"\n );\n}\n\n/** Short content digest for the identity — a hash, never the content itself (§15.2). */\nfunction contentHash(title: string, body: string): string {\n return createHash(\"sha256\").update(`${title}\\n${body}`).digest(\"hex\").slice(0, 16);\n}\n\n/**\n * A canonical identity for an event: same harness + session + type + CONTENT = the\n * \"same beep\". Content rides as a truncated hash so the ledger file never stores\n * notification text (§15.2).\n */\nexport function eventIdentity(event: {\n harness: string;\n source_session_id: string;\n event_type: string;\n title: string;\n body: string;\n}): string {\n return `${event.harness}:${event.source_session_id}:${event.event_type}:${contentHash(\n event.title,\n event.body,\n )}`;\n}\n\n/**\n * The content-BLIND identity used ONLY for the approval double-fire collapse (see the\n * module doc): one physical approval emits two payload shapes whose bodies differ, so\n * the content-aware identity cannot pair them.\n */\nexport function approvalCollapseIdentity(event: {\n harness: string;\n source_session_id: string;\n}): string {\n return `${event.harness}:${event.source_session_id}:approval_required:any`;\n}\n\nexport class RecentEventLedger {\n readonly path: string;\n readonly #windowMs: number;\n readonly #now: () => number;\n\n constructor(options: RecentEventLedgerOptions = {}) {\n this.path = options.path ?? join(birdyBeepDataDir(), \"recent-events.json\");\n this.#windowMs = options.windowMs ?? DEFAULT_DEDUP_WINDOW_MS;\n this.#now = options.now ?? (() => Date.now());\n }\n\n #read(): LedgerEntry[] {\n if (!existsSync(this.path)) return [];\n try {\n const parsed: unknown = JSON.parse(readFileSync(this.path, \"utf8\"));\n return Array.isArray(parsed) ? parsed.filter(isLedgerEntry) : [];\n } catch {\n return [];\n }\n }\n\n #write(entries: LedgerEntry[]): void {\n const dir = dirname(this.path);\n mkdirSync(dir, { recursive: true, mode: 0o700 });\n if (process.platform !== \"win32\") chmodSync(dir, 0o700);\n const tmp = `${this.path}.tmp`;\n writeFileSync(tmp, JSON.stringify(entries), { mode: 0o600 });\n renameSync(tmp, this.path);\n if (process.platform !== \"win32\") chmodSync(this.path, 0o600);\n }\n\n /**\n * If `identity` was recorded within the window, return true (it's a duplicate —\n * caller should skip). Otherwise record it (pruning expired entries) and return\n * false. `windowMs` narrows the duplicate check for THIS identity (e.g. the short\n * approval-collapse window) — pruning always uses the ledger's own (max) window so\n * a narrower check never evicts entries other checks still need. Fail-open: any\n * I/O error returns false (allow the send).\n */\n markAndCheck(identity: string, windowMs: number = this.#windowMs): boolean {\n try {\n const now = this.#now();\n const pruneCutoff = now - this.#windowMs;\n const dupCutoff = now - Math.min(windowMs, this.#windowMs);\n const fresh = this.#read().filter((e) => e.ts >= pruneCutoff);\n if (fresh.some((e) => e.id === identity && e.ts >= dupCutoff)) return true; // duplicate\n fresh.push({ id: identity, ts: now });\n this.#write(fresh);\n return false;\n } catch {\n return false; // fail open — never drop a notification due to a ledger error\n }\n }\n\n clear(): void {\n try {\n if (existsSync(this.path)) rmSync(this.path, { force: true });\n } catch {\n /* ignore */\n }\n }\n\n /** Whether the ledger file has secure (0600) perms. True on Windows (ACL-based). */\n isSecure(): boolean {\n if (process.platform === \"win32\") return true;\n try {\n return (statSync(this.path).mode & 0o077) === 0;\n } catch {\n return true; // absent file → nothing insecure\n }\n }\n}\n","/**\n * Cross-OS resolution of the BirdyBeep user data / config directories. These live\n * in the user's profile (NEVER repo-local), honoring the platform conventions and\n * the env vars the E2E sandbox redirects (HOME, XDG_*, LOCALAPPDATA) so tests stay\n * hermetic:\n * - Windows: %LOCALAPPDATA%\\birdybeep\n * - macOS: ~/Library/Application Support/birdybeep\n * - Linux: $XDG_DATA_HOME|$XDG_CONFIG_HOME or ~/.local/share | ~/.config /birdybeep\n */\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nfunction baseDir(kind: \"data\" | \"config\"): string {\n const home = homedir();\n if (process.platform === \"win32\") {\n const local = process.env[\"LOCALAPPDATA\"];\n return local && local.length > 0 ? local : join(home, \"AppData\", \"Local\");\n }\n if (process.platform === \"darwin\") {\n return join(home, \"Library\", \"Application Support\");\n }\n const xdg = kind === \"data\" ? process.env[\"XDG_DATA_HOME\"] : process.env[\"XDG_CONFIG_HOME\"];\n if (xdg && xdg.length > 0) return xdg;\n return join(home, kind === \"data\" ? join(\".local\", \"share\") : \".config\");\n}\n\n/** Absolute path to the BirdyBeep user DATA dir (queue, caches). */\nexport function birdyBeepDataDir(): string {\n return join(baseDir(\"data\"), \"birdybeep\");\n}\n\n/** Absolute path to the BirdyBeep user CONFIG dir (token store fallback). */\nexport function birdyBeepConfigDir(): string {\n return join(baseDir(\"config\"), \"birdybeep\");\n}\n","/**\n * Canonical agent event payload (§10.2) as zod schemas + inferred types — the typed\n * contract every adapter, the normalizer, and the sender share.\n *\n * LOCKSTEP (§16.4): field-for-field identical to the product `packages/schemas`\n * `event.ts` (same names, same optionality, same enums, same constraints). This is\n * the SHAPE contract validated at ingestion (`POST /v1/agent-events`); any change\n * to the product schema is a coordinated change here. This package validates SHAPE\n * only — it never stores or logs payload content (§15.2). Redaction/truncation/path\n * hashing is CORE-NORMALIZE's job, not this file's.\n */\nimport { z } from \"zod\";\n\nimport {\n eventTypeSchema,\n harnessSchema,\n isoDateTimeSchema,\n sessionStatusSchema,\n} from \"./primitives\";\n\n/** Machine identity carried on each event (§10.2). `os` is free-form (the adapter reports it). */\nexport const machineSchema = z.object({\n label: z.string().min(1),\n os: z.string().min(1),\n});\n\n/** Workspace context (§10.2). `repo_name`/`branch` are optional (\"if available\", §8.6). */\nexport const workspaceSchema = z.object({\n cwd: z.string().min(1),\n repo_name: z.string().optional(),\n branch: z.string().optional(),\n});\n\n/** Open-ended event metadata (§10.2): known fields plus any adapter-specific extras. */\nexport const eventMetadataSchema = z\n .object({\n tool: z.string().optional(),\n command_summary: z.string().optional(),\n })\n .catchall(z.unknown());\n\n/** The canonical agent event payload (§10.2). */\nexport const birdyBeepAgentEventSchema = z.object({\n event_id: z.string().min(1),\n event_type: eventTypeSchema,\n occurred_at: isoDateTimeSchema,\n harness: harnessSchema,\n harness_version: z.string().optional(),\n source_session_id: z.string().min(1),\n machine: machineSchema,\n workspace: workspaceSchema,\n status: sessionStatusSchema,\n title: z.string(),\n body: z.string(),\n metadata: eventMetadataSchema.optional(),\n});\n\nexport type Machine = z.infer<typeof machineSchema>;\nexport type Workspace = z.infer<typeof workspaceSchema>;\nexport type EventMetadata = z.infer<typeof eventMetadataSchema>;\nexport type BirdyBeepAgentEvent = z.infer<typeof birdyBeepAgentEventSchema>;\n\n/**\n * The request body for `POST /v1/agent-events` is exactly the canonical event\n * payload (§13.4/§13.5). Aliased so the sender (CORE-SENDER) reads intent clearly.\n */\nexport const agentEventsRequestSchema = birdyBeepAgentEventSchema;\nexport type AgentEventsRequest = BirdyBeepAgentEvent;\n","/**\n * Primitive enums + guards for the canonical agent event (§10.1, §10.4, §13.5).\n *\n * LOCKSTEP (§16.4): these unions MIRROR the product repo's `@birdybeep/shared`\n * (HARNESS_IDS / BIRDYBEEP_EVENT_TYPES / AGENT_SESSION_STATUSES) and the zod\n * validators in its `packages/schemas`. The agent repo cannot import the private\n * `@birdybeep/shared`, so the values are vendored here — any change there MUST be\n * mirrored here (the schema parity test fails if the agent side drifts).\n */\nimport { z } from \"zod\";\n\n/** Every agent event type, in PRD §10.1 order. Mirrors @birdybeep/shared BIRDYBEEP_EVENT_TYPES. */\nexport const BIRDYBEEP_EVENT_TYPES = [\n \"session_started\",\n \"session_resumed\",\n \"session_active\",\n \"needs_input\",\n \"approval_required\",\n \"agent_idle\",\n \"agent_completed\",\n \"agent_failed\",\n \"test_failed\",\n \"tool_started\",\n \"tool_finished\",\n \"subagent_started\",\n \"subagent_completed\",\n \"custom\",\n // \"test\" (9fh): the `birdybeep test` diagnostic. Notifies by default server-side\n // (unlike \"custom\", which the §10.5 matrix suppresses unconditionally) and is\n // quota-exempt — the product's DEFAULT_NOTIFY/gauntlet carry the other half.\n \"test\",\n] as const;\nexport type BirdyBeepEventType = (typeof BIRDYBEEP_EVENT_TYPES)[number];\n\n/** Every session status, in PRD §10.4 order. Mirrors @birdybeep/shared AGENT_SESSION_STATUSES. */\nexport const AGENT_SESSION_STATUSES = [\n \"starting\",\n \"running\",\n \"waiting_for_input\",\n \"waiting_for_approval\",\n \"idle\",\n \"completed\",\n \"failed\",\n \"unknown\",\n] as const;\nexport type AgentSessionStatus = (typeof AGENT_SESSION_STATUSES)[number];\n\n/** Supported harness ids (§9.5–9.7). Mirrors @birdybeep/shared HARNESS_IDS. */\nexport const HARNESS_IDS = [\"claude_code\", \"codex\", \"opencode\"] as const;\nexport type HarnessId = (typeof HARNESS_IDS)[number];\n\n/** Enum validators derived from the vendored tuples so the validator can't drift from the type. */\nexport const eventTypeSchema = z.enum(BIRDYBEEP_EVENT_TYPES); // §10.1\nexport const sessionStatusSchema = z.enum(AGENT_SESSION_STATUSES); // §10.4\nexport const harnessSchema = z.enum(HARNESS_IDS); // §9.5–9.7\n\n/** ISO 8601 timestamp, e.g. \"2026-06-11T12:34:56.000Z\" (trailing Z or numeric offset). */\nexport const isoDateTimeSchema = z.iso.datetime({ offset: true });\n\n/** Max accepted agent-event request body, in bytes (§13.5). Mirrors the product MAX_AGENT_EVENT_BYTES. */\nexport const MAX_AGENT_EVENT_BYTES = 16 * 1024;\n\n/**\n * Whether a raw agent-event body is within the size cap (§13.5). Platform-neutral\n * (no TextEncoder / Buffer): pass the body's byte length, or the raw bytes.\n */\nexport function isWithinMaxAgentEventSize(body: number | ArrayBuffer | ArrayBufferView): boolean {\n const bytes = typeof body === \"number\" ? body : body.byteLength;\n return bytes <= MAX_AGENT_EVENT_BYTES;\n}\n","/**\n * Machine identity (§10.3, §14.3, §10.2): a STABLE, non-reversible fingerprint hash\n * plus an auto-detected human label and normalized OS. Raw host signals (hostname,\n * MAC, …) are combined and hashed locally — only the hash leaves this module, never\n * a raw hardware identifier. Same machine → same hash across runs/reinstalls, so it\n * keys idempotent machine-installation upserts.\n */\nimport { createHash } from \"node:crypto\";\nimport { cpus, hostname, networkInterfaces, totalmem } from \"node:os\";\n\n/** Durable host signals combined into the fingerprint. Injectable for deterministic tests. */\nexport interface MachineSignals {\n hostname: string;\n platform: string;\n arch: string;\n cpuModel: string;\n totalmem: number;\n /** First stable non-internal MAC, or null. */\n mac: string | null;\n}\n\n/** First non-internal, non-zero MAC address, or null. */\nfunction primaryMac(): string | null {\n const ifaces = networkInterfaces();\n for (const list of Object.values(ifaces)) {\n for (const entry of list ?? []) {\n if (!entry.internal && entry.mac && entry.mac !== \"00:00:00:00:00:00\") return entry.mac;\n }\n }\n return null;\n}\n\n/** Gather durable signals from the host. */\nexport function collectMachineSignals(): MachineSignals {\n return {\n hostname: hostname(),\n platform: process.platform,\n arch: process.arch,\n cpuModel: cpus()[0]?.model ?? \"unknown\",\n totalmem: totalmem(),\n mac: primaryMac(),\n };\n}\n\n/** Derive the non-reversible fingerprint hash from signals (raw values never returned). */\nexport function fingerprintFromSignals(signals: MachineSignals): string {\n const material = [\n signals.platform,\n signals.arch,\n signals.hostname,\n signals.cpuModel,\n String(signals.totalmem),\n signals.mac ?? \"\",\n ].join(\"\u0000\");\n return createHash(\"sha256\").update(material).digest(\"hex\");\n}\n\n/** Stable, hashed machine fingerprint (`machine_installations.machine_fingerprint_hash`, §14.3). */\nexport function getMachineFingerprintHash(\n signals: MachineSignals = collectMachineSignals(),\n): string {\n return fingerprintFromSignals(signals);\n}\n\n/** Normalized OS value for `machine.os` (§10.2) / `machine_installations.os` (§14.3). */\nexport function getOS(platform: NodeJS.Platform = process.platform): string {\n switch (platform) {\n case \"darwin\":\n return \"macos\";\n case \"win32\":\n return \"windows\";\n case \"linux\":\n return \"linux\";\n default:\n return platform;\n }\n}\n\n/** Best-effort human label for `machine.label` (§10.2); auto-detected, editable in-app (§8.7). */\nexport function getMachineLabel(rawHostname: string = hostname()): string {\n const label = rawHostname.replace(/\\.local$/i, \"\").trim();\n return label.length > 0 ? label : `${getOS()}-machine`;\n}\n\n/** Convenience: the full machine identity used by pairing + the event `machine` block. */\nexport interface MachineIdentity {\n label: string;\n os: string;\n fingerprintHash: string;\n}\n\nexport function getMachineIdentity(): MachineIdentity {\n return {\n label: getMachineLabel(),\n os: getOS(),\n fingerprintHash: getMachineFingerprintHash(),\n };\n}\n","/**\n * The shared hook pipeline (§9.2–9.3): the core of `birdybeep hook <harness>`.\n * A harness fires a hook → this runs adapter.normalizeEvent (redact/hash/validate)\n * → dedup (collapse a repeat of the same beep) → sender.send (short timeout, queue\n * on failure) → return fast. It must NEVER throw into or block the harness: an\n * unmappable payload is skipped, a duplicate is dropped, and delivery failures queue.\n * The CLI `hook` command (CLI-HOOK) wires stdin + adapter selection around this.\n */\nimport type { AgentAdapter } from \"./adapter\";\nimport {\n APPROVAL_COLLAPSE_WINDOW_MS,\n approvalCollapseIdentity,\n eventIdentity,\n RecentEventLedger,\n} from \"./dedup\";\nimport type { Sender, SendResult } from \"./sender\";\n\nexport type HookOutcome = \"delivered\" | \"queued\" | \"dropped\" | \"deduped\" | \"skipped\";\n\nexport interface HookResult {\n outcome: HookOutcome;\n /** The normalized event type, when the payload was mappable. */\n eventType?: string;\n /** The sender's result, when a send was attempted. */\n send?: SendResult;\n}\n\nexport interface RunHookOptions {\n sender: Sender;\n /** Dedup ledger (default a RecentEventLedger at the user data dir). */\n ledger?: RecentEventLedger;\n}\n\n/**\n * Run one harness hook fire end-to-end. Returns a {@link HookResult}; never throws.\n */\nexport async function runAgentHook(\n adapter: AgentAdapter,\n rawInput: unknown,\n options: RunHookOptions,\n): Promise<HookResult> {\n let event;\n try {\n event = await adapter.normalizeEvent(rawInput);\n } catch {\n return { outcome: \"skipped\" }; // unmappable/garbled hook payload → ignore, don't disturb the harness\n }\n\n const ledger = options.ledger ?? new RecentEventLedger();\n // Content-aware identity (erm): identical repeats collapse; DIFFERENT notifications\n // of the same type both beep. approval_required ALSO collapses across content within\n // a short window, because one physical approval double-fires two payload shapes with\n // different bodies (Notification{permission_prompt} + PermissionRequest).\n const contentDup = ledger.markAndCheck(eventIdentity(event));\n const approvalDup =\n event.event_type === \"approval_required\" &&\n ledger.markAndCheck(approvalCollapseIdentity(event), APPROVAL_COLLAPSE_WINDOW_MS);\n if (contentDup || approvalDup) {\n return { outcome: \"deduped\", eventType: event.event_type }; // same beep already sent → no double-beep\n }\n\n const send = await options.sender.send(event);\n return { outcome: send.outcome, eventType: event.event_type, send };\n}\n","/**\n * Integration-status report wire contract (§8.8/§13.4) — MIRRORED from the product\n * `packages/schemas/integrations.ts`. `birdybeep agent install` / `doctor` report each\n * harness's install/health state in ONE batched `POST /v1/integrations/status` call.\n * LOCKSTEP (§16.4): additive to and independent of the §10.2 event payload.\n *\n * Identity (machine + user) is derived server-side from the machine token, never from this\n * body. `last_status_payload` is diagnostic-only JSON (metadata only — never notification\n * title/body or plaintext paths, §15.2), size-capped so an oversized blob fails validation.\n */\nimport { z } from \"zod\";\n\nimport { INTEGRATION_STATUSES } from \"./adapter\";\nimport { harnessSchema } from \"./primitives\";\n\n/** §8.8 integration status, as a validator (mirrors the product's `integrationStatusSchema`). */\nexport const integrationStatusSchema = z.enum(INTEGRATION_STATUSES);\n\n/** A machine reports a handful of harnesses per call (§8.8). */\nexport const STATUS_REPORT_MAX_ITEMS = 16;\n/** Diagnostic payload cap (§13.5): an oversized `last_status_payload` → 400. */\nexport const STATUS_REPORT_MAX_PAYLOAD_BYTES = 2048;\n\n/** One harness's reported install/health state. */\nexport const integrationStatusItemSchema = z.object({\n harness: harnessSchema,\n status: integrationStatusSchema,\n harness_version: z.string().max(64).optional(),\n adapter_version: z.string().max(64).optional(),\n last_status_payload: z\n .unknown()\n .optional()\n .refine((v) => v === undefined || JSON.stringify(v).length <= STATUS_REPORT_MAX_PAYLOAD_BYTES, {\n message: \"last_status_payload too large\",\n }),\n});\n\n/** The batched `POST /v1/integrations/status` request body. */\nexport const integrationStatusReportSchema = z.object({\n integrations: z.array(integrationStatusItemSchema).min(1).max(STATUS_REPORT_MAX_ITEMS),\n});\n\n/**\n * The response: the server echoes each harness's EFFECTIVE status (e.g. it forces\n * Codex → `needs_trust` until the first event regardless of what the CLI reported).\n * Tolerant of extra fields so a server addition won't break parsing.\n */\nexport const integrationStatusResponseSchema = z.object({\n integrations: z.array(\n z.object({ harness: harnessSchema, status: integrationStatusSchema }).catchall(z.unknown()),\n ),\n});\n\nexport type IntegrationStatusItem = z.infer<typeof integrationStatusItemSchema>;\nexport type IntegrationStatusReport = z.infer<typeof integrationStatusReportSchema>;\nexport type IntegrationStatusResponse = z.infer<typeof integrationStatusResponseSchema>;\n","/**\n * normalizeEvent + redaction/truncation/path-hashing (§9.2, §15.1–15.3).\n *\n * This is where the LOCAL privacy rules run before anything leaves the machine:\n * absolute paths are hashed, secret-looking strings are redacted, every string is\n * truncated to a bounded length, and the serialized payload is forced under the\n * §13.5 size cap. The output always validates as a {@link BirdyBeepAgentEvent} or\n * a {@link NormalizeError} is thrown — never a partially-valid event. Pure: no I/O,\n * no logging of raw content (§15.2).\n */\nimport { createHash, randomUUID } from \"node:crypto\";\n\nimport { type BirdyBeepAgentEvent, birdyBeepAgentEventSchema } from \"./event\";\nimport { MAX_AGENT_EVENT_BYTES } from \"./primitives\";\n\n/** Per-field truncation caps (agent-local privacy choice; the 16 KB total is the lockstep cap). */\nexport const TITLE_MAX_CHARS = 200;\nexport const BODY_MAX_CHARS = 2000;\nexport const METADATA_VALUE_MAX_CHARS = 500;\nexport const METADATA_MAX_KEYS = 64;\nexport const METADATA_MAX_DEPTH = 4;\nexport const LABEL_MAX_CHARS = 120;\n\n/** Thrown when input cannot be coerced into a valid, under-cap event. */\nexport class NormalizeError extends Error {\n constructor(\n message: string,\n readonly issues?: readonly unknown[],\n ) {\n super(message);\n this.name = \"NormalizeError\";\n }\n}\n\nexport interface NormalizeOptions {\n /** Injectable clock for deterministic tests. Default: real wall clock (ISO). */\n now?: () => string;\n /** Injectable id generator for deterministic tests. Default: `evt_local_<uuid>`. */\n generateId?: () => string;\n}\n\nfunction isString(v: unknown): v is string {\n return typeof v === \"string\";\n}\n\nfunction asRecord(v: unknown): Record<string, unknown> {\n return typeof v === \"object\" && v !== null && !Array.isArray(v)\n ? (v as Record<string, unknown>)\n : {};\n}\n\n/** Stable, irreversible token for a hashed value. Same input → same hash across runs. */\nfunction hashToken(value: string): string {\n return `h_${createHash(\"sha256\").update(value).digest(\"hex\").slice(0, 16)}`;\n}\n\n/** A POSIX (`/a/b…`) or Windows (`C:\\a…`) absolute path with ≥2 segments. */\nconst ABSOLUTE_PATH_RE = /(?:\\/[A-Za-z0-9_.-]+){2,}|[A-Za-z]:(?:[\\\\/][A-Za-z0-9_. -]+)+/g;\n\n/** Secret-shaped substrings → replaced wholesale (best-effort; truncation is the backstop). */\nconst SECRET_RES: readonly RegExp[] = [\n /\\bAKIA[0-9A-Z]{16}\\b/g, // AWS access key id\n /\\bgh[pousr]_[A-Za-z0-9]{20,}\\b/g, // GitHub tokens\n /\\bsk-[A-Za-z0-9_-]{16,}\\b/g, // OpenAI-style keys\n /\\bxox[baprs]-[A-Za-z0-9-]{10,}\\b/g, // Slack tokens\n /\\beyJ[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}\\b/g, // JWT\n /\\b(?:bearer|token|secret|password|passwd|api[_-]?key)\\b\\s*[:=]\\s*\\S+/gi, // key=value secrets\n];\n\n/** Replace every absolute path in a string with a stable hash (no raw path survives). */\nexport function scrubAbsolutePaths(text: string): string {\n return text.replace(ABSOLUTE_PATH_RE, (match) => hashToken(match));\n}\n\n/** Replace secret-looking substrings with a redaction marker. */\nexport function redactSecrets(text: string): string {\n let out = text;\n for (const re of SECRET_RES) out = out.replace(re, \"[redacted]\");\n return out;\n}\n\n/** Truncate to `max` chars with an ellipsis marker. */\nexport function truncate(text: string, max: number): string {\n return text.length <= max ? text : `${text.slice(0, Math.max(0, max - 1))}…`;\n}\n\n/** Hash an absolute path so it never leaves the machine raw (§14.5). */\nexport function hashPath(path: string): string {\n return hashToken(path);\n}\n\n/** Full string-cleaning pipeline: scrub paths → redact secrets → truncate. */\nfunction cleanString(text: string, max: number): string {\n return truncate(redactSecrets(scrubAbsolutePaths(text)), max);\n}\n\n/** Recursively sanitize an arbitrary metadata value, bounding depth/size and scrubbing strings. */\nfunction sanitizeValue(value: unknown, depth: number): unknown {\n if (isString(value)) return cleanString(value, METADATA_VALUE_MAX_CHARS);\n if (typeof value === \"number\" || typeof value === \"boolean\" || value === null) return value;\n if (depth >= METADATA_MAX_DEPTH) return undefined; // collapse over-deep structures\n if (Array.isArray(value)) {\n return value.slice(0, METADATA_MAX_KEYS).map((v) => sanitizeValue(v, depth + 1));\n }\n if (typeof value === \"object\") {\n const out: Record<string, unknown> = {};\n let n = 0;\n for (const [k, v] of Object.entries(value)) {\n if (n >= METADATA_MAX_KEYS) break;\n const cleaned = sanitizeValue(v, depth + 1);\n if (cleaned !== undefined) {\n out[cleanString(k, LABEL_MAX_CHARS)] = cleaned;\n n++;\n }\n }\n return out;\n }\n return undefined; // functions/symbols/etc. are dropped\n}\n\nfunction serializedBytes(event: BirdyBeepAgentEvent): number {\n return Buffer.byteLength(JSON.stringify(event), \"utf8\");\n}\n\n/** Deterministically shrink an over-cap event: drop metadata → harden body → harden title. */\nfunction enforceSize(event: BirdyBeepAgentEvent): BirdyBeepAgentEvent {\n if (serializedBytes(event) <= MAX_AGENT_EVENT_BYTES) return event;\n const steps: BirdyBeepAgentEvent[] = [\n { ...event, metadata: undefined },\n { ...event, metadata: undefined, body: truncate(event.body, 256) },\n {\n ...event,\n metadata: undefined,\n body: truncate(event.body, 256),\n title: truncate(event.title, 120),\n },\n ];\n for (const candidate of steps) {\n if (serializedBytes(candidate) <= MAX_AGENT_EVENT_BYTES) return candidate;\n }\n throw new NormalizeError(\"event exceeds max payload size even after shrinking\");\n}\n\n/**\n * Coerce raw adapter input into a privacy-safe, size-bounded, validated\n * {@link BirdyBeepAgentEvent}. Fills `event_id`/`occurred_at` defaults, hashes\n * absolute paths, redacts secrets, truncates strings, and enforces the size cap.\n * Throws {@link NormalizeError} if the result cannot be made valid + under-cap.\n */\nexport function normalizeEvent(input: unknown, opts: NormalizeOptions = {}): BirdyBeepAgentEvent {\n const rec = asRecord(input);\n const ws = asRecord(rec[\"workspace\"]);\n const machine = asRecord(rec[\"machine\"]);\n\n const candidate: Record<string, unknown> = {\n event_id:\n isString(rec[\"event_id\"]) && rec[\"event_id\"].length > 0\n ? rec[\"event_id\"]\n : (opts.generateId?.() ?? `evt_local_${randomUUID()}`),\n event_type: rec[\"event_type\"],\n occurred_at:\n isString(rec[\"occurred_at\"]) && rec[\"occurred_at\"].length > 0\n ? rec[\"occurred_at\"]\n : (opts.now?.() ?? new Date().toISOString()),\n harness: rec[\"harness\"],\n // source_session_id is a key (§10.3) — keep it stable, but never let a raw path through.\n source_session_id: isString(rec[\"source_session_id\"])\n ? scrubAbsolutePaths(rec[\"source_session_id\"])\n : rec[\"source_session_id\"],\n machine: {\n label: isString(machine[\"label\"])\n ? cleanString(machine[\"label\"], LABEL_MAX_CHARS)\n : machine[\"label\"],\n os: isString(machine[\"os\"]) ? cleanString(machine[\"os\"], LABEL_MAX_CHARS) : machine[\"os\"],\n },\n workspace: {\n // cwd is an absolute path → always hashed (§15: no absolute path leaves the machine).\n cwd: isString(ws[\"cwd\"]) ? hashPath(ws[\"cwd\"]) : ws[\"cwd\"],\n },\n status: rec[\"status\"],\n title: isString(rec[\"title\"]) ? cleanString(rec[\"title\"], TITLE_MAX_CHARS) : rec[\"title\"],\n body: isString(rec[\"body\"]) ? cleanString(rec[\"body\"], BODY_MAX_CHARS) : rec[\"body\"],\n };\n\n if (isString(rec[\"harness_version\"])) {\n candidate[\"harness_version\"] = cleanString(rec[\"harness_version\"], LABEL_MAX_CHARS);\n }\n // Safe workspace labels are kept (cleaned), per §14.5.\n const wsOut = candidate[\"workspace\"] as Record<string, unknown>;\n if (isString(ws[\"repo_name\"])) wsOut[\"repo_name\"] = cleanString(ws[\"repo_name\"], LABEL_MAX_CHARS);\n if (isString(ws[\"branch\"])) wsOut[\"branch\"] = cleanString(ws[\"branch\"], LABEL_MAX_CHARS);\n if (rec[\"metadata\"] !== undefined) {\n candidate[\"metadata\"] = sanitizeValue(rec[\"metadata\"], 0);\n }\n\n const parsed = birdyBeepAgentEventSchema.safeParse(candidate);\n if (!parsed.success) {\n throw new NormalizeError(\"normalized event failed schema validation\", parsed.error.issues);\n }\n return enforceSize(parsed.data);\n}\n","/**\n * Pairing handshake wire contract (§7.2/§13.4) — MIRRORED from the product\n * `packages/schemas/pairing.ts`. The CLI builds the `POST /v1/pair/start` + `/v1/pair/token`\n * requests and parses these (BARE, not `{ data }`-wrapped) responses. LOCKSTEP (§16.4):\n * additive to and independent of the §10.2 event payload — a change here is coordinated.\n *\n * Privacy (§15.1): codes are short-lived single-use, stored HASHED server-side; `qr_payload`\n * carries only the short `user_code`, NEVER a durable token; the `machine_token` from\n * `/pair/token` is shown once and only its peppered hash is persisted. Optional advisory\n * fields use `.catch(undefined)` to match the routes' accept-and-ignore leniency.\n *\n * (`/v1/pair/approve` is the signed-in MOBILE side — not mirrored here; the CLI never calls it.)\n */\nimport { z } from \"zod\";\n\nimport { isoDateTimeSchema } from \"./primitives\";\n\n/** `POST /v1/pair/start` — UNAUTHENTICATED; the CLI opens a pairing session. */\nexport const pairStartRequestSchema = z.object({\n machine_label: z.string().min(1),\n os: z.string().optional().catch(undefined),\n cli_version: z.string().optional().catch(undefined),\n requested_scopes: z.array(z.string()).optional().catch(undefined),\n});\n\n/** `POST /v1/pair/token` — UNAUTHENTICATED; the CLI exchanges its device code (polled). */\nexport const pairTokenRequestSchema = z.object({\n device_code: z.string().min(1),\n machine_fingerprint: z.string().optional().catch(undefined),\n});\n\n/** `POST /v1/pair/start` response (bare). `qr_payload` encodes only the short `user_code`. */\nexport const pairStartResponseSchema = z.object({\n device_code: z.string(),\n user_code: z.string(),\n qr_payload: z.string(),\n expires_at: isoDateTimeSchema,\n});\n\n/** `POST /v1/pair/token` 201 response (bare). The durable token is issued exactly once. */\nexport const pairTokenResponseSchema = z.object({\n machine_token: z.string(),\n machine_id: z.string(),\n});\n\nexport type PairStartRequest = z.infer<typeof pairStartRequestSchema>;\nexport type PairTokenRequest = z.infer<typeof pairTokenRequestSchema>;\nexport type PairStartResponse = z.infer<typeof pairStartResponseSchema>;\nexport type PairTokenResponse = z.infer<typeof pairTokenResponseSchema>;\n","/**\n * Local retry queue (§9.3, §15.3). When delivery fails or the machine is offline,\n * a normalized event is parked on disk and drained opportunistically on the next\n * hook/CLI invocation — there is NO background daemon. The queue is best-effort\n * (≤24h retention, not a durable audit log), strict-permissioned (dir 0700, files\n * 0600), lives OUTSIDE the repo, and must never throw into or block the harness.\n *\n * This module owns enqueue/drain/clear primitives; the HTTP POST + timeout logic\n * lives in CORE-SENDER, which drives `drain` with a send callback.\n */\nimport { randomUUID } from \"node:crypto\";\nimport {\n chmodSync,\n existsSync,\n mkdirSync,\n readdirSync,\n readFileSync,\n renameSync,\n rmSync,\n statSync,\n writeFileSync,\n} from \"node:fs\";\nimport { join } from \"node:path\";\n\nimport type { BirdyBeepAgentEvent } from \"./event\";\nimport { birdyBeepDataDir } from \"./paths\";\n\n/** 24h default retention (§15.3). */\nexport const QUEUE_RETENTION_MS = 24 * 60 * 60 * 1000;\n/** Default max entries drained per call so a drain never blocks the harness (§9.3). */\nexport const DEFAULT_DRAIN_MAX = 50;\n/**\n * Age after which a `.claim` file is considered ORPHANED and returned to the queue\n * (erm): a drain claims an entry via rename before sending; if that process is killed\n * mid-send the claim would otherwise strand the event forever (claims are invisible\n * to normal reads). Far above any legitimate in-flight send (bounded to seconds).\n */\nexport const CLAIM_RECLAIM_MS = 60_000;\n\n/** What the sender decided for one queued event. delivered/drop → remove; retry → keep. */\nexport type DrainOutcome = \"delivered\" | \"drop\" | \"retry\";\n\nexport interface DrainResult {\n delivered: number;\n dropped: number;\n kept: number;\n pruned: number;\n}\n\ninterface QueueEntry {\n path: string;\n enqueuedAt: number;\n event: BirdyBeepAgentEvent;\n}\n\nexport interface LocalEventQueueOptions {\n /** Queue directory (default `<dataDir>/queue`). Tests pass a sandbox path. */\n dir?: string;\n /** Retention window in ms (default 24h). */\n retentionMs?: number;\n /** Injectable clock (ms since epoch) for deterministic tests. */\n now?: () => number;\n}\n\nfunction isRecord(v: unknown): v is Record<string, unknown> {\n return typeof v === \"object\" && v !== null;\n}\n\n/**\n * Best-effort on-disk queue. Every public method swallows I/O errors (returning a\n * safe default) so a full/locked/corrupt queue degrades gracefully and never\n * throws into the calling hook.\n */\nexport class LocalEventQueue {\n readonly dir: string;\n readonly #retentionMs: number;\n readonly #now: () => number;\n\n constructor(options: LocalEventQueueOptions = {}) {\n this.dir = options.dir ?? join(birdyBeepDataDir(), \"queue\");\n this.#retentionMs = options.retentionMs ?? QUEUE_RETENTION_MS;\n this.#now = options.now ?? (() => Date.now());\n }\n\n /** Ensure the dir exists with 0700 perms; repair a too-permissive existing dir. */\n #ensureDir(): void {\n mkdirSync(this.dir, { recursive: true, mode: 0o700 });\n if (process.platform !== \"win32\") chmodSync(this.dir, 0o700); // repair perms\n }\n\n /** Park a normalized event on disk (atomic write, 0600). Never throws. */\n enqueue(event: BirdyBeepAgentEvent): boolean {\n try {\n this.#ensureDir();\n const enqueuedAt = this.#now();\n const name = `${enqueuedAt}-${randomUUID()}.json`;\n const finalPath = join(this.dir, name);\n const tmpPath = `${finalPath}.tmp`;\n writeFileSync(tmpPath, JSON.stringify({ enqueuedAt, event }), { mode: 0o600 });\n renameSync(tmpPath, finalPath);\n if (process.platform !== \"win32\") chmodSync(finalPath, 0o600);\n return true;\n } catch {\n return false; // best-effort: a failed enqueue must never break the harness\n }\n }\n\n /**\n * Return orphaned `.claim` files (claims older than {@link CLAIM_RECLAIM_MS}) to the\n * queue by renaming them back to their original `.json` name. The claim timestamp is\n * embedded in the claim FILENAME (rename preserves mtime, so fs times would report\n * the enqueue age, not the claim age — an active drain of an old entry must not be\n * \"reclaimed\" out from under it). Racing reclaims are safe: rename losers ENOENT.\n * Known window: a drainer SUSPENDED (not dead) >60s can resume after its claim was\n * reclaimed and double-send — accepted, since 60s ≫ the 5s send budget (a claim that\n * old almost always is a dead process) and the backend dedupe absorbs the repeat.\n */\n #reclaimOrphanedClaims(names: string[]): void {\n const cutoff = this.#now() - CLAIM_RECLAIM_MS;\n for (const name of names) {\n const m = /^(.+\\.json)\\.(\\d+)-[0-9a-f-]+\\.claim$/.exec(name);\n if (!m || Number(m[2]) > cutoff) continue; // not a claim, or still legitimately in flight\n try {\n renameSync(join(this.dir, name), join(this.dir, m[1]!)); // back into the queue\n } catch {\n /* another process won the reclaim (or the fs refused) — never throw */\n }\n }\n }\n\n /** Read all non-expired entries (FIFO by enqueue time); prune expired/corrupt ones. */\n #readFresh(): { fresh: QueueEntry[]; pruned: number } {\n if (!existsSync(this.dir)) return { fresh: [], pruned: 0 };\n if (process.platform !== \"win32\") {\n try {\n chmodSync(this.dir, 0o700); // repair a too-permissive dir on any access\n } catch {\n /* ignore */\n }\n }\n let pruned = 0;\n const fresh: QueueEntry[] = [];\n const cutoff = this.#now() - this.#retentionMs;\n let names: string[];\n try {\n names = readdirSync(this.dir);\n this.#reclaimOrphanedClaims(names); // orphaned claims rejoin the queue first (erm)\n names = readdirSync(this.dir); // re-list so reclaimed entries are visible this pass\n } catch {\n return { fresh: [], pruned: 0 };\n }\n for (const name of names) {\n if (!name.endsWith(\".json\")) continue; // skip .tmp / .claim\n const path = join(this.dir, name);\n try {\n const parsed: unknown = JSON.parse(readFileSync(path, \"utf8\"));\n const enqueuedAt = isRecord(parsed) ? parsed[\"enqueuedAt\"] : undefined;\n const event = isRecord(parsed) ? parsed[\"event\"] : undefined;\n if (typeof enqueuedAt !== \"number\" || !isRecord(event)) {\n rmSync(path, { force: true }); // corrupt → drop\n pruned++;\n continue;\n }\n if (enqueuedAt < cutoff) {\n rmSync(path, { force: true }); // expired → prune, never deliver\n pruned++;\n continue;\n }\n fresh.push({ path, enqueuedAt, event: event as unknown as BirdyBeepAgentEvent });\n } catch {\n try {\n rmSync(path, { force: true });\n } catch {\n /* ignore */\n }\n pruned++;\n }\n }\n fresh.sort((a, b) => a.enqueuedAt - b.enqueuedAt);\n return { fresh, pruned };\n }\n\n /** Count of fresh (non-expired) queued events. Prunes expired entries as a side effect. */\n size(): number {\n return this.#readFresh().fresh.length;\n }\n\n /**\n * Drain up to `max` fresh entries through `send`. Each entry is CLAIMED via an\n * atomic rename before sending, so two concurrent drains never double-send the\n * same event. delivered/drop remove the entry; retry keeps it for next time.\n * Bounded + best-effort: never throws into the caller.\n */\n async drain(\n send: (event: BirdyBeepAgentEvent) => Promise<DrainOutcome> | DrainOutcome,\n options: { max?: number; stopWhen?: () => boolean } = {},\n ): Promise<DrainResult> {\n const max = options.max ?? DEFAULT_DRAIN_MAX;\n const result: DrainResult = { delivered: 0, dropped: 0, kept: 0, pruned: 0 };\n let fresh: QueueEntry[];\n try {\n this.#ensureDir();\n const read = this.#readFresh();\n fresh = read.fresh;\n result.pruned = read.pruned;\n } catch {\n return result;\n }\n for (const entry of fresh.slice(0, max)) {\n // Budget bound (erm): the sender stops the drain when the hook's total time\n // budget is spent — unclaimed entries simply stay queued for the next drain.\n if (options.stopWhen?.() === true) {\n result.kept += 1;\n continue;\n }\n const claim = `${entry.path}.${this.#now()}-${randomUUID()}.claim`;\n try {\n renameSync(entry.path, claim); // atomic claim; loser gets ENOENT → skip\n } catch {\n continue; // another drain already owns this entry\n }\n let outcome: DrainOutcome;\n try {\n outcome = await send(entry.event);\n } catch {\n outcome = \"retry\"; // sender threw → keep for next drain\n }\n if (outcome === \"retry\") {\n try {\n renameSync(claim, entry.path); // release the claim\n } catch {\n /* ignore */\n }\n result.kept++;\n } else {\n try {\n rmSync(claim, { force: true });\n } catch {\n /* ignore */\n }\n if (outcome === \"delivered\") result.delivered++;\n else result.dropped++;\n }\n }\n return result;\n }\n\n /** Remove every queued entry (used by `doctor` / debug tooling). Never throws. */\n clear(): number {\n if (!existsSync(this.dir)) return 0;\n let removed = 0;\n try {\n for (const name of readdirSync(this.dir)) {\n try {\n rmSync(join(this.dir, name), { force: true });\n removed++;\n } catch {\n /* ignore */\n }\n }\n } catch {\n /* ignore */\n }\n return removed;\n }\n\n /** Whether the queue dir has secure (0700) perms. Returns true on Windows (ACL-based). */\n isSecure(): boolean {\n if (process.platform === \"win32\") return true;\n try {\n return (statSync(this.dir).mode & 0o077) === 0;\n } catch {\n return false;\n }\n }\n}\n","/**\n * Machine installation token storage (§7.2, §15.1): the OS keychain where usable,\n * a strict-permission file fallback (file 0600, dir 0700, under the user DATA dir)\n * otherwise — and NEVER a repo-local file or harness config. The sender reads the\n * token at send time; `logout`/revoke clears it; rotation overwrites it.\n *\n * The keychain is behind an injectable {@link KeychainBackend} so the store logic\n * is unit-tested with a fake backend (the real OS keychain is never touched by the\n * suite). On a headless/SSH machine with no secret service, the file fallback is\n * the working path — which is exactly what CI Linux/Windows exercise.\n */\nimport { execFile } from \"node:child_process\";\nimport {\n chmodSync,\n existsSync,\n mkdirSync,\n readFileSync,\n renameSync,\n rmSync,\n statSync,\n writeFileSync,\n} from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { promisify } from \"node:util\";\n\nimport { birdyBeepDataDir } from \"./paths\";\n\nconst execFileAsync = promisify(execFile);\n\n/** Keychain namespacing for the single machine installation token. */\nconst SERVICE = \"birdybeep\";\nconst ACCOUNT = \"machine-token\";\n\nexport type TokenStoreKind = \"keychain\" | \"file\";\n\nexport interface TokenStore {\n readonly kind: TokenStoreKind;\n get(): Promise<string | null>;\n set(token: string): Promise<void>;\n clear(): Promise<void>;\n}\n\n/** Pluggable OS-keychain backend. Real impls shell out; tests inject a fake. */\nexport interface KeychainBackend {\n /** Whether this backend can be used on the current machine. */\n readonly available: boolean;\n get(service: string, account: string): Promise<string | null>;\n set(service: string, account: string, secret: string): Promise<void>;\n delete(service: string, account: string): Promise<void>;\n}\n\n// ── File fallback (the always-available, fully-tested path) ──────────────────\n\nexport interface FileTokenStoreOptions {\n /** Override the token file path (tests). Default `<dataDir>/token`. */\n path?: string;\n}\n\nexport class FileTokenStore implements TokenStore {\n readonly kind = \"file\";\n readonly path: string;\n\n constructor(options: FileTokenStoreOptions = {}) {\n this.path = options.path ?? join(birdyBeepDataDir(), \"token\");\n }\n\n // Sync internals, Promise-returning to satisfy the TokenStore interface.\n get(): Promise<string | null> {\n if (!existsSync(this.path)) return Promise.resolve(null);\n this.#repairPerms();\n const raw = readFileSync(this.path, \"utf8\").trim();\n return Promise.resolve(raw.length > 0 ? raw : null);\n }\n\n set(token: string): Promise<void> {\n const dir = dirname(this.path);\n mkdirSync(dir, { recursive: true, mode: 0o700 });\n if (process.platform !== \"win32\") chmodSync(dir, 0o700);\n const tmp = `${this.path}.tmp`;\n writeFileSync(tmp, token, { mode: 0o600 });\n renameSync(tmp, this.path);\n if (process.platform !== \"win32\") chmodSync(this.path, 0o600);\n return Promise.resolve();\n }\n\n clear(): Promise<void> {\n rmSync(this.path, { force: true });\n return Promise.resolve();\n }\n\n /** Repair a too-permissive token file (§15.1: strict perms). POSIX only. */\n #repairPerms(): void {\n if (process.platform === \"win32\") return;\n try {\n if ((statSync(this.path).mode & 0o077) !== 0) chmodSync(this.path, 0o600);\n } catch {\n /* ignore */\n }\n }\n}\n\n// ── Keychain store (wraps a backend) ─────────────────────────────────────────\n\nexport class KeychainTokenStore implements TokenStore {\n readonly kind = \"keychain\";\n readonly #backend: KeychainBackend;\n\n constructor(backend: KeychainBackend) {\n this.#backend = backend;\n }\n\n get(): Promise<string | null> {\n return this.#backend.get(SERVICE, ACCOUNT);\n }\n\n set(token: string): Promise<void> {\n return this.#backend.set(SERVICE, ACCOUNT, token);\n }\n\n clear(): Promise<void> {\n return this.#backend.delete(SERVICE, ACCOUNT);\n }\n}\n\n// ── Real macOS keychain backend (`security`). Not exercised by the unit suite. ──\n\n/** A backend that reports itself unavailable (Linux without secret service / Windows fallback). */\nexport const unavailableKeychainBackend: KeychainBackend = {\n available: false,\n get: () => Promise.resolve(null),\n set: () => Promise.reject(new Error(\"keychain unavailable\")),\n delete: () => Promise.resolve(),\n};\n\n/** macOS Keychain via the built-in `security` CLI. */\nexport function macosKeychainBackend(): KeychainBackend {\n return {\n available: process.platform === \"darwin\",\n async get(service, account) {\n try {\n const { stdout } = await execFileAsync(\"security\", [\n \"find-generic-password\",\n \"-s\",\n service,\n \"-a\",\n account,\n \"-w\",\n ]);\n const value = stdout.replace(/\\n$/, \"\");\n return value.length > 0 ? value : null;\n } catch {\n return null; // not found / locked → treat as absent\n }\n },\n async set(service, account, secret) {\n // -U updates an existing item; namespaced to BirdyBeep's service/account.\n await execFileAsync(\"security\", [\n \"add-generic-password\",\n \"-U\",\n \"-s\",\n service,\n \"-a\",\n account,\n \"-w\",\n secret,\n ]);\n },\n async delete(service, account) {\n try {\n await execFileAsync(\"security\", [\"delete-generic-password\", \"-s\", service, \"-a\", account]);\n } catch {\n /* already absent → fine */\n }\n },\n };\n}\n\n/** The default keychain backend for the current OS (best-effort; file fallback otherwise). */\nexport function defaultKeychainBackend(): KeychainBackend {\n if (process.platform === \"darwin\") return macosKeychainBackend();\n // Linux Secret Service / Windows Credential Manager backends can be added later;\n // until then those platforms use the strict-perm file fallback (§7.2 headless path).\n return unavailableKeychainBackend;\n}\n\nexport interface TokenStoreOptions {\n /** Inject a keychain backend (tests / custom). Defaults to the OS backend. */\n backend?: KeychainBackend;\n /** Override the fallback file path (tests). */\n filePath?: string;\n}\n\n/** Resolve the PRIMARY store: keychain when available, else the strict-perm file. */\nexport function resolveTokenStore(options: TokenStoreOptions = {}): TokenStore {\n const backend = options.backend ?? defaultKeychainBackend();\n if (backend.available) return new KeychainTokenStore(backend);\n return new FileTokenStore(options.filePath !== undefined ? { path: options.filePath } : {});\n}\n\n// ── High-level API used by the CLI + sender ──────────────────────────────────\n\n/** Store the machine token in the primary store (keychain if available, else file). */\nexport async function setToken(\n token: string,\n options: TokenStoreOptions = {},\n): Promise<TokenStoreKind> {\n const store = resolveTokenStore(options);\n await store.set(token);\n return store.kind;\n}\n\n/** Read the machine token: keychain first if available, then the file fallback. */\nexport async function getToken(options: TokenStoreOptions = {}): Promise<string | null> {\n const backend = options.backend ?? defaultKeychainBackend();\n if (backend.available) {\n const fromKeychain = await new KeychainTokenStore(backend).get();\n if (fromKeychain !== null) return fromKeychain;\n }\n const file = new FileTokenStore(options.filePath !== undefined ? { path: options.filePath } : {});\n return file.get();\n}\n\n/** Remove the token from BOTH keychain and file fallback (logout / revoke). */\nexport async function clearToken(options: TokenStoreOptions = {}): Promise<void> {\n const backend = options.backend ?? defaultKeychainBackend();\n if (backend.available) await new KeychainTokenStore(backend).clear();\n await new FileTokenStore(\n options.filePath !== undefined ? { path: options.filePath } : {},\n ).clear();\n}\n","/**\n * Event sender (§9.2–9.3): POST a normalized event to `/v1/agent-events` with a\n * SHORT hard timeout; on timeout/network/transient failure, queue it and return\n * fast — never blocking or throwing into the harness. The retry-vs-terminal\n * decision keys off the product error-envelope code (mirrored in `api.ts`), so the\n * queue never fills with un-deliverable events. Each send also opportunistically\n * drains the backlog (bounded by count AND by a TOTAL time budget — Claude Code\n * installs its hooks with a 10s timeout, and an unbounded 50-entry drain at 3s per\n * attempt could blow well past it, erm). The token is read from secure storage at\n * send time and never logged; request bodies/title/body are never logged.\n */\nimport { type ErrorCode, errorEnvelopeSchema } from \"./api\";\nimport type { BirdyBeepAgentEvent } from \"./event\";\nimport { DEFAULT_DRAIN_MAX, type DrainOutcome, type DrainResult, LocalEventQueue } from \"./queue\";\nimport { getToken, type TokenStoreOptions } from \"./token-store\";\n\nexport const DEFAULT_SEND_TIMEOUT_MS = 3000;\n/**\n * Total wall-clock budget for one send() (first attempt + opportunistic drain).\n * Comfortably under the 10s hook timeout the adapters install, with headroom for\n * process spawn + stdin read around it.\n */\nexport const DEFAULT_TOTAL_BUDGET_MS = 5000;\n/** Stop draining when less than this remains — a send that can't finish shouldn't start. */\nconst MIN_DRAIN_ATTEMPT_MS = 250;\nconst AGENT_EVENTS_PATH = \"/v1/agent-events\";\n\nexport type SendOutcome = \"delivered\" | \"queued\" | \"dropped\";\n\nexport interface SendResult {\n outcome: SendOutcome;\n status?: number;\n /** Error code from the response envelope, when the server returned one. */\n code?: ErrorCode;\n /**\n * The backend's delivery decision from the 202 body (`notified` / `suppressed` /\n * `deduped`), when parseable. Lets callers (CLI `test`, 9fh) report what actually\n * happened instead of claiming a beep that the backend decided not to push.\n */\n decision?: string;\n /** Result of the opportunistic queue drain performed on this send. */\n drained?: DrainResult;\n}\n\nexport interface SenderConfig {\n /** API base URL, e.g. `https://api.birdybeep.com` (or a `wrangler dev` URL). */\n baseUrl: string;\n /** Hard per-request timeout (default 3s) — the harness must not wait longer. */\n timeoutMs?: number;\n /** Total budget for one send()+drain (default 5s) — see module doc (erm). */\n totalBudgetMs?: number;\n /** Queue instance (default a LocalEventQueue at the user data dir). */\n queue?: LocalEventQueue;\n /** Token-store options (inject backend/path in tests). */\n tokenOptions?: TokenStoreOptions;\n /** fetch implementation (injected in tests). */\n fetchImpl?: typeof fetch;\n /** Max queued events drained per send (bounded so the hook returns fast). */\n drainMax?: number;\n /** Injectable clock (ms) for deterministic budget tests. */\n now?: () => number;\n}\n\nexport interface Sender {\n send(event: BirdyBeepAgentEvent): Promise<SendResult>;\n drainNow(): Promise<DrainResult>;\n}\n\ninterface Attempt {\n result: DrainOutcome;\n status?: number | undefined;\n code?: ErrorCode | undefined;\n decision?: string | undefined;\n}\n\nconst EMPTY_DRAIN: DrainResult = { delivered: 0, dropped: 0, kept: 0, pruned: 0 };\n\n/** Decide whether a non-2xx response is worth retrying (queue) or terminal (drop). */\nfunction classify(status: number, code: ErrorCode | undefined): \"retry\" | \"drop\" {\n if (code === \"rate_limited\" || code === \"internal_error\") return \"retry\";\n if (code !== undefined) return \"drop\"; // unauthorized / forbidden / token_revoked / validation_failed / payload_too_large / not_found / quota_exceeded\n if (status >= 500 || status === 429) return \"retry\"; // transient, no parseable envelope\n return \"drop\"; // other 4xx\n}\n\n/** Extract the ingest decision from a 2xx body, tolerating any shape. */\nfunction parseDecision(body: unknown): string | undefined {\n if (typeof body !== \"object\" || body === null) return undefined;\n const decision = (body as Record<string, unknown>)[\"decision\"];\n return typeof decision === \"string\" ? decision : undefined;\n}\n\nexport function createSender(config: SenderConfig): Sender {\n const baseUrl = config.baseUrl.replace(/\\/$/, \"\");\n const timeoutMs = config.timeoutMs ?? DEFAULT_SEND_TIMEOUT_MS;\n const totalBudgetMs = config.totalBudgetMs ?? DEFAULT_TOTAL_BUDGET_MS;\n const queue = config.queue ?? new LocalEventQueue();\n const fetchImpl = config.fetchImpl ?? fetch;\n const drainMax = config.drainMax ?? DEFAULT_DRAIN_MAX;\n const clock = config.now ?? (() => Date.now());\n\n async function attempt(\n event: BirdyBeepAgentEvent,\n token: string,\n attemptTimeoutMs: number = timeoutMs,\n ): Promise<Attempt> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), attemptTimeoutMs);\n try {\n const res = await fetchImpl(`${baseUrl}${AGENT_EVENTS_PATH}`, {\n method: \"POST\",\n headers: { authorization: `Bearer ${token}`, \"content-type\": \"application/json\" },\n body: JSON.stringify(event),\n signal: controller.signal,\n });\n if (res.status >= 200 && res.status < 300) {\n // The 202 body carries {accepted, decision} — surface the decision so callers\n // can tell \"push enqueued\" from \"accepted but suppressed/deduped\" (9fh).\n const decision = parseDecision(await res.json().catch(() => undefined));\n return { result: \"delivered\", status: res.status, decision };\n }\n let code: ErrorCode | undefined;\n try {\n const body: unknown = await res.json();\n const parsed = errorEnvelopeSchema.safeParse(body);\n if (parsed.success) code = parsed.data.error.code;\n } catch {\n /* non-JSON error body → fall back to status */\n }\n return { result: classify(res.status, code), status: res.status, code };\n } catch {\n return { result: \"retry\" }; // timeout / transport error → queue\n } finally {\n clearTimeout(timer);\n }\n }\n\n /** Drain the backlog until drainMax entries OR the deadline is reached. */\n function drainQueue(token: string, deadline: number): Promise<DrainResult> {\n return queue.drain(\n (e) => {\n const remaining = deadline - clock();\n return attempt(e, token, Math.max(1, Math.min(timeoutMs, remaining))).then((a) => a.result);\n },\n { max: drainMax, stopWhen: () => deadline - clock() < MIN_DRAIN_ATTEMPT_MS },\n );\n }\n\n return {\n async send(event: BirdyBeepAgentEvent): Promise<SendResult> {\n const deadline = clock() + totalBudgetMs;\n const token = await getToken(config.tokenOptions);\n if (token === null) {\n queue.enqueue(event); // not paired yet → retry after `birdybeep login`\n return { outcome: \"queued\" };\n }\n const a = await attempt(event, token, Math.min(timeoutMs, totalBudgetMs));\n let outcome: SendOutcome;\n if (a.result === \"delivered\") {\n outcome = \"delivered\";\n } else if (a.result === \"retry\") {\n queue.enqueue(event);\n outcome = \"queued\";\n } else {\n outcome = \"dropped\"; // terminal reject → do not re-queue\n }\n const drained = await drainQueue(token, deadline);\n const result: SendResult = { outcome, drained };\n if (a.status !== undefined) result.status = a.status;\n if (a.code !== undefined) result.code = a.code;\n if (a.decision !== undefined) result.decision = a.decision;\n return result;\n },\n\n async drainNow(): Promise<DrainResult> {\n const token = await getToken(config.tokenOptions);\n if (token === null) return EMPTY_DRAIN;\n return drainQueue(token, clock() + totalBudgetMs);\n },\n };\n}\n","/**\n * @birdybeep/agent-core — the shared runtime every adapter and the CLI build on:\n * the canonical event schema (kept in lockstep with the private product repo's\n * `packages/schemas`), the normalizer/redaction layer, the 24h local queue, the\n * sender, the machine token store, and the `AgentAdapter` interface.\n *\n * The canonical event schema + enums (CORE-SCHEMA) are exported below. The\n * normalizer/queue/sender/token-store/adapter-interface land in the remaining\n * agent-core epic (CORE-*) tickets — see `bd ready`.\n */\nexport * from \"./adapter\";\nexport * from \"./api\";\nexport * from \"./dedup\";\nexport * from \"./event\";\nexport * from \"./fingerprint\";\nexport * from \"./hook\";\nexport * from \"./integrations\";\nexport * from \"./normalize\";\nexport * from \"./pairing\";\nexport * from \"./paths\";\nexport * from \"./primitives\";\nexport * from \"./queue\";\nexport * from \"./sender\";\nexport * from \"./token-store\";\n\n/** Package version marker — replaced by the real build/version pipeline (REL-*). */\nexport const AGENT_CORE_VERSION = \"0.0.0\";\n\n/**\n * Minimal identity an adapter reports about the harness it integrates with.\n * The full {@link https://birdybeep.dev | AgentAdapter} interface (detect /\n * install / uninstall / status / doctor / normalizeEvent) is defined in CORE-ADAPTER.\n */\nexport interface AdapterMeta {\n /** Stable harness id, e.g. `\"claude_code\"`, `\"codex\"`, `\"opencode\"`. */\n readonly harness: string;\n}\n"],"mappings":"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","names":["join","join","z","z","z","createHash","z","z","createHash","createHash","z","z","randomUUID","chmodSync","existsSync","mkdirSync","readFileSync","renameSync","rmSync","statSync","writeFileSync","join","join","mkdirSync","chmodSync","randomUUID","writeFileSync","renameSync","existsSync","readFileSync","rmSync","statSync","chmodSync","existsSync","mkdirSync","readFileSync","renameSync","rmSync","statSync","writeFileSync","dirname","join","join","existsSync","readFileSync","dirname","mkdirSync","chmodSync","writeFileSync","renameSync","rmSync","statSync"]}
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@birdybeep/agent-core",
3
+ "version": "0.0.1",
4
+ "description": "BirdyBeep agent core: event schema, normalizer/redaction, local queue, sender, token store, and the AgentAdapter interface.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/Mojave-Labs/birdybeep-agent.git",
9
+ "directory": "packages/agent-core"
10
+ },
11
+ "type": "module",
12
+ "main": "./dist/index.cjs",
13
+ "module": "./dist/index.js",
14
+ "types": "./dist/index.d.ts",
15
+ "exports": {
16
+ ".": {
17
+ "import": {
18
+ "types": "./dist/index.d.ts",
19
+ "default": "./dist/index.js"
20
+ },
21
+ "require": {
22
+ "types": "./dist/index.d.cts",
23
+ "default": "./dist/index.cjs"
24
+ }
25
+ }
26
+ },
27
+ "files": [
28
+ "dist"
29
+ ],
30
+ "dependencies": {
31
+ "zod": "^4"
32
+ },
33
+ "devDependencies": {
34
+ "@birdybeep/test-harness": "0.0.0"
35
+ },
36
+ "engines": {
37
+ "node": ">=20.11.0"
38
+ },
39
+ "scripts": {
40
+ "build": "tsup src/index.ts --format esm,cjs --dts --sourcemap --clean",
41
+ "typecheck": "tsc --noEmit -p tsconfig.json",
42
+ "lint": "eslint .",
43
+ "test": "vitest run"
44
+ }
45
+ }