@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,852 @@
1
+ import { z } from 'zod';
2
+
3
+ /**
4
+ * Canonical agent event payload (§10.2) as zod schemas + inferred types — the typed
5
+ * contract every adapter, the normalizer, and the sender share.
6
+ *
7
+ * LOCKSTEP (§16.4): field-for-field identical to the product `packages/schemas`
8
+ * `event.ts` (same names, same optionality, same enums, same constraints). This is
9
+ * the SHAPE contract validated at ingestion (`POST /v1/agent-events`); any change
10
+ * to the product schema is a coordinated change here. This package validates SHAPE
11
+ * only — it never stores or logs payload content (§15.2). Redaction/truncation/path
12
+ * hashing is CORE-NORMALIZE's job, not this file's.
13
+ */
14
+
15
+ /** Machine identity carried on each event (§10.2). `os` is free-form (the adapter reports it). */
16
+ declare const machineSchema: z.ZodObject<{
17
+ label: z.ZodString;
18
+ os: z.ZodString;
19
+ }, z.core.$strip>;
20
+ /** Workspace context (§10.2). `repo_name`/`branch` are optional ("if available", §8.6). */
21
+ declare const workspaceSchema: z.ZodObject<{
22
+ cwd: z.ZodString;
23
+ repo_name: z.ZodOptional<z.ZodString>;
24
+ branch: z.ZodOptional<z.ZodString>;
25
+ }, z.core.$strip>;
26
+ /** Open-ended event metadata (§10.2): known fields plus any adapter-specific extras. */
27
+ declare const eventMetadataSchema: z.ZodObject<{
28
+ tool: z.ZodOptional<z.ZodString>;
29
+ command_summary: z.ZodOptional<z.ZodString>;
30
+ }, z.core.$catchall<z.ZodUnknown>>;
31
+ /** The canonical agent event payload (§10.2). */
32
+ declare const birdyBeepAgentEventSchema: z.ZodObject<{
33
+ event_id: z.ZodString;
34
+ event_type: z.ZodEnum<{
35
+ session_started: "session_started";
36
+ session_resumed: "session_resumed";
37
+ session_active: "session_active";
38
+ needs_input: "needs_input";
39
+ approval_required: "approval_required";
40
+ agent_idle: "agent_idle";
41
+ agent_completed: "agent_completed";
42
+ agent_failed: "agent_failed";
43
+ test_failed: "test_failed";
44
+ tool_started: "tool_started";
45
+ tool_finished: "tool_finished";
46
+ subagent_started: "subagent_started";
47
+ subagent_completed: "subagent_completed";
48
+ custom: "custom";
49
+ test: "test";
50
+ }>;
51
+ occurred_at: z.ZodISODateTime;
52
+ harness: z.ZodEnum<{
53
+ claude_code: "claude_code";
54
+ codex: "codex";
55
+ opencode: "opencode";
56
+ }>;
57
+ harness_version: z.ZodOptional<z.ZodString>;
58
+ source_session_id: z.ZodString;
59
+ machine: z.ZodObject<{
60
+ label: z.ZodString;
61
+ os: z.ZodString;
62
+ }, z.core.$strip>;
63
+ workspace: z.ZodObject<{
64
+ cwd: z.ZodString;
65
+ repo_name: z.ZodOptional<z.ZodString>;
66
+ branch: z.ZodOptional<z.ZodString>;
67
+ }, z.core.$strip>;
68
+ status: z.ZodEnum<{
69
+ starting: "starting";
70
+ running: "running";
71
+ waiting_for_input: "waiting_for_input";
72
+ waiting_for_approval: "waiting_for_approval";
73
+ idle: "idle";
74
+ completed: "completed";
75
+ failed: "failed";
76
+ unknown: "unknown";
77
+ }>;
78
+ title: z.ZodString;
79
+ body: z.ZodString;
80
+ metadata: z.ZodOptional<z.ZodObject<{
81
+ tool: z.ZodOptional<z.ZodString>;
82
+ command_summary: z.ZodOptional<z.ZodString>;
83
+ }, z.core.$catchall<z.ZodUnknown>>>;
84
+ }, z.core.$strip>;
85
+ type Machine = z.infer<typeof machineSchema>;
86
+ type Workspace = z.infer<typeof workspaceSchema>;
87
+ type EventMetadata = z.infer<typeof eventMetadataSchema>;
88
+ type BirdyBeepAgentEvent = z.infer<typeof birdyBeepAgentEventSchema>;
89
+ /**
90
+ * The request body for `POST /v1/agent-events` is exactly the canonical event
91
+ * payload (§13.4/§13.5). Aliased so the sender (CORE-SENDER) reads intent clearly.
92
+ */
93
+ declare const agentEventsRequestSchema: z.ZodObject<{
94
+ event_id: z.ZodString;
95
+ event_type: z.ZodEnum<{
96
+ session_started: "session_started";
97
+ session_resumed: "session_resumed";
98
+ session_active: "session_active";
99
+ needs_input: "needs_input";
100
+ approval_required: "approval_required";
101
+ agent_idle: "agent_idle";
102
+ agent_completed: "agent_completed";
103
+ agent_failed: "agent_failed";
104
+ test_failed: "test_failed";
105
+ tool_started: "tool_started";
106
+ tool_finished: "tool_finished";
107
+ subagent_started: "subagent_started";
108
+ subagent_completed: "subagent_completed";
109
+ custom: "custom";
110
+ test: "test";
111
+ }>;
112
+ occurred_at: z.ZodISODateTime;
113
+ harness: z.ZodEnum<{
114
+ claude_code: "claude_code";
115
+ codex: "codex";
116
+ opencode: "opencode";
117
+ }>;
118
+ harness_version: z.ZodOptional<z.ZodString>;
119
+ source_session_id: z.ZodString;
120
+ machine: z.ZodObject<{
121
+ label: z.ZodString;
122
+ os: z.ZodString;
123
+ }, z.core.$strip>;
124
+ workspace: z.ZodObject<{
125
+ cwd: z.ZodString;
126
+ repo_name: z.ZodOptional<z.ZodString>;
127
+ branch: z.ZodOptional<z.ZodString>;
128
+ }, z.core.$strip>;
129
+ status: z.ZodEnum<{
130
+ starting: "starting";
131
+ running: "running";
132
+ waiting_for_input: "waiting_for_input";
133
+ waiting_for_approval: "waiting_for_approval";
134
+ idle: "idle";
135
+ completed: "completed";
136
+ failed: "failed";
137
+ unknown: "unknown";
138
+ }>;
139
+ title: z.ZodString;
140
+ body: z.ZodString;
141
+ metadata: z.ZodOptional<z.ZodObject<{
142
+ tool: z.ZodOptional<z.ZodString>;
143
+ command_summary: z.ZodOptional<z.ZodString>;
144
+ }, z.core.$catchall<z.ZodUnknown>>>;
145
+ }, z.core.$strip>;
146
+ type AgentEventsRequest = BirdyBeepAgentEvent;
147
+
148
+ /**
149
+ * Primitive enums + guards for the canonical agent event (§10.1, §10.4, §13.5).
150
+ *
151
+ * LOCKSTEP (§16.4): these unions MIRROR the product repo's `@birdybeep/shared`
152
+ * (HARNESS_IDS / BIRDYBEEP_EVENT_TYPES / AGENT_SESSION_STATUSES) and the zod
153
+ * validators in its `packages/schemas`. The agent repo cannot import the private
154
+ * `@birdybeep/shared`, so the values are vendored here — any change there MUST be
155
+ * mirrored here (the schema parity test fails if the agent side drifts).
156
+ */
157
+
158
+ /** Every agent event type, in PRD §10.1 order. Mirrors @birdybeep/shared BIRDYBEEP_EVENT_TYPES. */
159
+ declare const BIRDYBEEP_EVENT_TYPES: readonly ["session_started", "session_resumed", "session_active", "needs_input", "approval_required", "agent_idle", "agent_completed", "agent_failed", "test_failed", "tool_started", "tool_finished", "subagent_started", "subagent_completed", "custom", "test"];
160
+ type BirdyBeepEventType = (typeof BIRDYBEEP_EVENT_TYPES)[number];
161
+ /** Every session status, in PRD §10.4 order. Mirrors @birdybeep/shared AGENT_SESSION_STATUSES. */
162
+ declare const AGENT_SESSION_STATUSES: readonly ["starting", "running", "waiting_for_input", "waiting_for_approval", "idle", "completed", "failed", "unknown"];
163
+ type AgentSessionStatus = (typeof AGENT_SESSION_STATUSES)[number];
164
+ /** Supported harness ids (§9.5–9.7). Mirrors @birdybeep/shared HARNESS_IDS. */
165
+ declare const HARNESS_IDS: readonly ["claude_code", "codex", "opencode"];
166
+ type HarnessId = (typeof HARNESS_IDS)[number];
167
+ /** Enum validators derived from the vendored tuples so the validator can't drift from the type. */
168
+ declare const eventTypeSchema: z.ZodEnum<{
169
+ session_started: "session_started";
170
+ session_resumed: "session_resumed";
171
+ session_active: "session_active";
172
+ needs_input: "needs_input";
173
+ approval_required: "approval_required";
174
+ agent_idle: "agent_idle";
175
+ agent_completed: "agent_completed";
176
+ agent_failed: "agent_failed";
177
+ test_failed: "test_failed";
178
+ tool_started: "tool_started";
179
+ tool_finished: "tool_finished";
180
+ subagent_started: "subagent_started";
181
+ subagent_completed: "subagent_completed";
182
+ custom: "custom";
183
+ test: "test";
184
+ }>;
185
+ declare const sessionStatusSchema: z.ZodEnum<{
186
+ starting: "starting";
187
+ running: "running";
188
+ waiting_for_input: "waiting_for_input";
189
+ waiting_for_approval: "waiting_for_approval";
190
+ idle: "idle";
191
+ completed: "completed";
192
+ failed: "failed";
193
+ unknown: "unknown";
194
+ }>;
195
+ declare const harnessSchema: z.ZodEnum<{
196
+ claude_code: "claude_code";
197
+ codex: "codex";
198
+ opencode: "opencode";
199
+ }>;
200
+ /** ISO 8601 timestamp, e.g. "2026-06-11T12:34:56.000Z" (trailing Z or numeric offset). */
201
+ declare const isoDateTimeSchema: z.ZodISODateTime;
202
+ /** Max accepted agent-event request body, in bytes (§13.5). Mirrors the product MAX_AGENT_EVENT_BYTES. */
203
+ declare const MAX_AGENT_EVENT_BYTES: number;
204
+ /**
205
+ * Whether a raw agent-event body is within the size cap (§13.5). Platform-neutral
206
+ * (no TextEncoder / Buffer): pass the body's byte length, or the raw bytes.
207
+ */
208
+ declare function isWithinMaxAgentEventSize(body: number | ArrayBuffer | ArrayBufferView): boolean;
209
+
210
+ /**
211
+ * The `AgentAdapter` interface (§9.1): the one uniform contract the Claude Code,
212
+ * Codex, and OpenCode adapters all implement, so the CLI can detect/install/
213
+ * uninstall/check/doctor/normalize any harness without special-casing. New
214
+ * harnesses plug in by implementing this — nothing in the CLI changes.
215
+ *
216
+ * Contract every adapter MUST honor (§7.3), enforced by the shared E2E harness:
217
+ * - install is IDEMPOTENT (a second install is a no-op);
218
+ * - existing config is BACKED UP before modification;
219
+ * - only BirdyBeep-MANAGED entries are added (existing config preserved);
220
+ * - tokens are NEVER written into harness config or repo files;
221
+ * - install/uninstall report changed files + any required user actions;
222
+ * - uninstall removes exactly the managed entries (restores the original).
223
+ */
224
+
225
+ /** Integration status values (§8.8). */
226
+ declare const INTEGRATION_STATUSES: readonly ["installed", "not_detected", "needs_restart", "needs_trust", "error", "revoked", "unknown"];
227
+ type IntegrationStatus = (typeof INTEGRATION_STATUSES)[number];
228
+ /** Result of probing whether a harness is present on the machine. */
229
+ interface DetectionResult {
230
+ detected: boolean;
231
+ version?: string;
232
+ /** Path to the harness config the adapter would manage, if found. */
233
+ configPath?: string;
234
+ detail?: string;
235
+ }
236
+ interface InstallOptions {
237
+ /** Compute + report changes without writing anything. */
238
+ dryRun?: boolean;
239
+ }
240
+ interface InstallResult {
241
+ /** False when the install was a no-op (already installed / idempotent re-run). */
242
+ changed: boolean;
243
+ /** Config files created or modified by this install. */
244
+ changedFiles: string[];
245
+ /** Backups written before modifying pre-existing config. */
246
+ backupFiles: string[];
247
+ /** Actions the user must still take (e.g. trust Codex hooks, restart OpenCode). */
248
+ requiredActions: string[];
249
+ /** Resulting integration status (e.g. installed / needs_trust / needs_restart). */
250
+ status: IntegrationStatus;
251
+ }
252
+ interface UninstallOptions {
253
+ dryRun?: boolean;
254
+ }
255
+ interface UninstallResult {
256
+ changed: boolean;
257
+ /** Files removed (created by BirdyBeep). */
258
+ removedFiles: string[];
259
+ /** Files restored from backup to their pre-install contents. */
260
+ restoredFiles: string[];
261
+ }
262
+ interface DoctorCheck {
263
+ name: string;
264
+ ok: boolean;
265
+ status?: IntegrationStatus;
266
+ detail?: string;
267
+ /** Suggested fix when `ok` is false. */
268
+ remedy?: string;
269
+ }
270
+ interface DoctorResult {
271
+ ok: boolean;
272
+ checks: DoctorCheck[];
273
+ }
274
+ /** The contract implemented by every harness adapter (§9.1). */
275
+ interface AgentAdapter {
276
+ /** Stable harness id (also the event `harness` value). */
277
+ readonly id: HarnessId;
278
+ /** Human-facing harness name, e.g. "Claude Code". */
279
+ readonly displayName: string;
280
+ /** Is this harness installed/usable on the machine? */
281
+ detect(): Promise<DetectionResult>;
282
+ /** Idempotently install BirdyBeep into the harness config (backs up first). */
283
+ install(options?: InstallOptions): Promise<InstallResult>;
284
+ /** Remove BirdyBeep's managed entries, restoring the original config. */
285
+ uninstall(options?: UninstallOptions): Promise<UninstallResult>;
286
+ /** Current integration status (§8.8). */
287
+ status(): Promise<IntegrationStatus>;
288
+ /** Diagnose integration health and suggest remedies. */
289
+ doctor(): Promise<DoctorResult>;
290
+ /** Map a raw harness payload to a redacted, validated canonical event. */
291
+ normalizeEvent(input: unknown): Promise<BirdyBeepAgentEvent>;
292
+ }
293
+
294
+ /**
295
+ * API error/response contract (§13.4) — MIRRORED from the product
296
+ * `packages/schemas/api.ts` (their ticket 95e). The single wire-facing error shape
297
+ * the Worker emits and this CLI parses; agent-core's sender keys retry-vs-terminal
298
+ * off these codes. LOCKSTEP (§16.4): keep ERROR_CODES / the envelope / ERROR_STATUS
299
+ * identical to the product. Additive to and independent of the §10.2 event payload.
300
+ *
301
+ * Messages are human-readable text only — never notification title/body or request
302
+ * content (§15.2); `details` is for safe structured hints (e.g. which field failed).
303
+ */
304
+
305
+ /** Stable, machine-readable error codes the client keys off. */
306
+ declare const ERROR_CODES: readonly ["validation_failed", "unauthorized", "forbidden", "token_revoked", "not_found", "payload_too_large", "rate_limited", "quota_exceeded", "internal_error"];
307
+ declare const errorCodeSchema: z.ZodEnum<{
308
+ validation_failed: "validation_failed";
309
+ unauthorized: "unauthorized";
310
+ forbidden: "forbidden";
311
+ token_revoked: "token_revoked";
312
+ not_found: "not_found";
313
+ payload_too_large: "payload_too_large";
314
+ rate_limited: "rate_limited";
315
+ quota_exceeded: "quota_exceeded";
316
+ internal_error: "internal_error";
317
+ }>;
318
+ type ErrorCode = (typeof ERROR_CODES)[number];
319
+ /** The error response envelope: `{ error: { code, message, details? }, requestId? }`. */
320
+ declare const errorEnvelopeSchema: z.ZodObject<{
321
+ error: z.ZodObject<{
322
+ code: z.ZodEnum<{
323
+ validation_failed: "validation_failed";
324
+ unauthorized: "unauthorized";
325
+ forbidden: "forbidden";
326
+ token_revoked: "token_revoked";
327
+ not_found: "not_found";
328
+ payload_too_large: "payload_too_large";
329
+ rate_limited: "rate_limited";
330
+ quota_exceeded: "quota_exceeded";
331
+ internal_error: "internal_error";
332
+ }>;
333
+ message: z.ZodString;
334
+ details: z.ZodOptional<z.ZodUnknown>;
335
+ }, z.core.$strip>;
336
+ requestId: z.ZodOptional<z.ZodString>;
337
+ }, z.core.$strip>;
338
+ type ErrorEnvelope = z.infer<typeof errorEnvelopeSchema>;
339
+ /** Generic success envelope (`{ data }`) for endpoints that opt into wrapping. */
340
+ declare const successEnvelopeSchema: <T extends z.ZodTypeAny>(data: T) => z.ZodObject<{
341
+ data: T;
342
+ }, z.core.$strip>;
343
+ type SuccessEnvelope<T> = {
344
+ data: T;
345
+ };
346
+ /**
347
+ * Canonical HTTP status per error code — part of the contract: a given code always
348
+ * maps to this status, so the client can branch on either.
349
+ */
350
+ declare const ERROR_STATUS: {
351
+ readonly validation_failed: 400;
352
+ readonly unauthorized: 401;
353
+ readonly forbidden: 403;
354
+ readonly token_revoked: 403;
355
+ readonly not_found: 404;
356
+ readonly payload_too_large: 413;
357
+ readonly rate_limited: 429;
358
+ readonly quota_exceeded: 429;
359
+ readonly internal_error: 500;
360
+ };
361
+
362
+ /** Default dedup window: collapse identical events seen within 10s. */
363
+ declare const DEFAULT_DEDUP_WINDOW_MS = 10000;
364
+ /**
365
+ * Window for collapsing the SAME-approval double-fire (same session + type, different
366
+ * body — e.g. "Claude Code needs your permission…" vs "Approve Bash?"). DELIBERATE
367
+ * TRADEOFF: within this window a genuinely DISTINCT second approval is also collapsed
368
+ * (dropped!), inverting the module's better-double-beep-than-drop bias — so the window
369
+ * is as short as the double-fire allows. The double-fire is one harness action emitting
370
+ * two hooks back-to-back (typically <100ms apart); two REAL approvals are separated by
371
+ * the human answering the first, or by prompts that Claude Code serializes in its UI —
372
+ * sub-second spacing is not a realistic distinct-approval pattern. Keep this ≤ the
373
+ * ledger's default window (markAndCheck clamps a wider value down) and NEVER raise it
374
+ * casually: every added millisecond widens the drop window for real approvals.
375
+ * Best-effort under concurrency: two SIMULTANEOUS hook processes can both pass the
376
+ * read-check-write ledger race and double-beep — that direction is fail-open (annoying,
377
+ * not lossy) and accepted.
378
+ */
379
+ declare const APPROVAL_COLLAPSE_WINDOW_MS = 1000;
380
+ interface RecentEventLedgerOptions {
381
+ /** Ledger file path (default `<dataDir>/recent-events.json`). */
382
+ path?: string;
383
+ /** Dedup window in ms (default 10s). Also the retention horizon for pruning. */
384
+ windowMs?: number;
385
+ /** Injectable clock (ms) for deterministic tests. */
386
+ now?: () => number;
387
+ }
388
+ /**
389
+ * A canonical identity for an event: same harness + session + type + CONTENT = the
390
+ * "same beep". Content rides as a truncated hash so the ledger file never stores
391
+ * notification text (§15.2).
392
+ */
393
+ declare function eventIdentity(event: {
394
+ harness: string;
395
+ source_session_id: string;
396
+ event_type: string;
397
+ title: string;
398
+ body: string;
399
+ }): string;
400
+ /**
401
+ * The content-BLIND identity used ONLY for the approval double-fire collapse (see the
402
+ * module doc): one physical approval emits two payload shapes whose bodies differ, so
403
+ * the content-aware identity cannot pair them.
404
+ */
405
+ declare function approvalCollapseIdentity(event: {
406
+ harness: string;
407
+ source_session_id: string;
408
+ }): string;
409
+ declare class RecentEventLedger {
410
+ #private;
411
+ readonly path: string;
412
+ constructor(options?: RecentEventLedgerOptions);
413
+ /**
414
+ * If `identity` was recorded within the window, return true (it's a duplicate —
415
+ * caller should skip). Otherwise record it (pruning expired entries) and return
416
+ * false. `windowMs` narrows the duplicate check for THIS identity (e.g. the short
417
+ * approval-collapse window) — pruning always uses the ledger's own (max) window so
418
+ * a narrower check never evicts entries other checks still need. Fail-open: any
419
+ * I/O error returns false (allow the send).
420
+ */
421
+ markAndCheck(identity: string, windowMs?: number): boolean;
422
+ clear(): void;
423
+ /** Whether the ledger file has secure (0600) perms. True on Windows (ACL-based). */
424
+ isSecure(): boolean;
425
+ }
426
+
427
+ /** Durable host signals combined into the fingerprint. Injectable for deterministic tests. */
428
+ interface MachineSignals {
429
+ hostname: string;
430
+ platform: string;
431
+ arch: string;
432
+ cpuModel: string;
433
+ totalmem: number;
434
+ /** First stable non-internal MAC, or null. */
435
+ mac: string | null;
436
+ }
437
+ /** Gather durable signals from the host. */
438
+ declare function collectMachineSignals(): MachineSignals;
439
+ /** Derive the non-reversible fingerprint hash from signals (raw values never returned). */
440
+ declare function fingerprintFromSignals(signals: MachineSignals): string;
441
+ /** Stable, hashed machine fingerprint (`machine_installations.machine_fingerprint_hash`, §14.3). */
442
+ declare function getMachineFingerprintHash(signals?: MachineSignals): string;
443
+ /** Normalized OS value for `machine.os` (§10.2) / `machine_installations.os` (§14.3). */
444
+ declare function getOS(platform?: NodeJS.Platform): string;
445
+ /** Best-effort human label for `machine.label` (§10.2); auto-detected, editable in-app (§8.7). */
446
+ declare function getMachineLabel(rawHostname?: string): string;
447
+ /** Convenience: the full machine identity used by pairing + the event `machine` block. */
448
+ interface MachineIdentity {
449
+ label: string;
450
+ os: string;
451
+ fingerprintHash: string;
452
+ }
453
+ declare function getMachineIdentity(): MachineIdentity;
454
+
455
+ /** 24h default retention (§15.3). */
456
+ declare const QUEUE_RETENTION_MS: number;
457
+ /** Default max entries drained per call so a drain never blocks the harness (§9.3). */
458
+ declare const DEFAULT_DRAIN_MAX = 50;
459
+ /**
460
+ * Age after which a `.claim` file is considered ORPHANED and returned to the queue
461
+ * (erm): a drain claims an entry via rename before sending; if that process is killed
462
+ * mid-send the claim would otherwise strand the event forever (claims are invisible
463
+ * to normal reads). Far above any legitimate in-flight send (bounded to seconds).
464
+ */
465
+ declare const CLAIM_RECLAIM_MS = 60000;
466
+ /** What the sender decided for one queued event. delivered/drop → remove; retry → keep. */
467
+ type DrainOutcome = "delivered" | "drop" | "retry";
468
+ interface DrainResult {
469
+ delivered: number;
470
+ dropped: number;
471
+ kept: number;
472
+ pruned: number;
473
+ }
474
+ interface LocalEventQueueOptions {
475
+ /** Queue directory (default `<dataDir>/queue`). Tests pass a sandbox path. */
476
+ dir?: string;
477
+ /** Retention window in ms (default 24h). */
478
+ retentionMs?: number;
479
+ /** Injectable clock (ms since epoch) for deterministic tests. */
480
+ now?: () => number;
481
+ }
482
+ /**
483
+ * Best-effort on-disk queue. Every public method swallows I/O errors (returning a
484
+ * safe default) so a full/locked/corrupt queue degrades gracefully and never
485
+ * throws into the calling hook.
486
+ */
487
+ declare class LocalEventQueue {
488
+ #private;
489
+ readonly dir: string;
490
+ constructor(options?: LocalEventQueueOptions);
491
+ /** Park a normalized event on disk (atomic write, 0600). Never throws. */
492
+ enqueue(event: BirdyBeepAgentEvent): boolean;
493
+ /** Count of fresh (non-expired) queued events. Prunes expired entries as a side effect. */
494
+ size(): number;
495
+ /**
496
+ * Drain up to `max` fresh entries through `send`. Each entry is CLAIMED via an
497
+ * atomic rename before sending, so two concurrent drains never double-send the
498
+ * same event. delivered/drop remove the entry; retry keeps it for next time.
499
+ * Bounded + best-effort: never throws into the caller.
500
+ */
501
+ drain(send: (event: BirdyBeepAgentEvent) => Promise<DrainOutcome> | DrainOutcome, options?: {
502
+ max?: number;
503
+ stopWhen?: () => boolean;
504
+ }): Promise<DrainResult>;
505
+ /** Remove every queued entry (used by `doctor` / debug tooling). Never throws. */
506
+ clear(): number;
507
+ /** Whether the queue dir has secure (0700) perms. Returns true on Windows (ACL-based). */
508
+ isSecure(): boolean;
509
+ }
510
+
511
+ type TokenStoreKind = "keychain" | "file";
512
+ interface TokenStore {
513
+ readonly kind: TokenStoreKind;
514
+ get(): Promise<string | null>;
515
+ set(token: string): Promise<void>;
516
+ clear(): Promise<void>;
517
+ }
518
+ /** Pluggable OS-keychain backend. Real impls shell out; tests inject a fake. */
519
+ interface KeychainBackend {
520
+ /** Whether this backend can be used on the current machine. */
521
+ readonly available: boolean;
522
+ get(service: string, account: string): Promise<string | null>;
523
+ set(service: string, account: string, secret: string): Promise<void>;
524
+ delete(service: string, account: string): Promise<void>;
525
+ }
526
+ interface FileTokenStoreOptions {
527
+ /** Override the token file path (tests). Default `<dataDir>/token`. */
528
+ path?: string;
529
+ }
530
+ declare class FileTokenStore implements TokenStore {
531
+ #private;
532
+ readonly kind = "file";
533
+ readonly path: string;
534
+ constructor(options?: FileTokenStoreOptions);
535
+ get(): Promise<string | null>;
536
+ set(token: string): Promise<void>;
537
+ clear(): Promise<void>;
538
+ }
539
+ declare class KeychainTokenStore implements TokenStore {
540
+ #private;
541
+ readonly kind = "keychain";
542
+ constructor(backend: KeychainBackend);
543
+ get(): Promise<string | null>;
544
+ set(token: string): Promise<void>;
545
+ clear(): Promise<void>;
546
+ }
547
+ /** A backend that reports itself unavailable (Linux without secret service / Windows fallback). */
548
+ declare const unavailableKeychainBackend: KeychainBackend;
549
+ /** macOS Keychain via the built-in `security` CLI. */
550
+ declare function macosKeychainBackend(): KeychainBackend;
551
+ /** The default keychain backend for the current OS (best-effort; file fallback otherwise). */
552
+ declare function defaultKeychainBackend(): KeychainBackend;
553
+ interface TokenStoreOptions {
554
+ /** Inject a keychain backend (tests / custom). Defaults to the OS backend. */
555
+ backend?: KeychainBackend;
556
+ /** Override the fallback file path (tests). */
557
+ filePath?: string;
558
+ }
559
+ /** Resolve the PRIMARY store: keychain when available, else the strict-perm file. */
560
+ declare function resolveTokenStore(options?: TokenStoreOptions): TokenStore;
561
+ /** Store the machine token in the primary store (keychain if available, else file). */
562
+ declare function setToken(token: string, options?: TokenStoreOptions): Promise<TokenStoreKind>;
563
+ /** Read the machine token: keychain first if available, then the file fallback. */
564
+ declare function getToken(options?: TokenStoreOptions): Promise<string | null>;
565
+ /** Remove the token from BOTH keychain and file fallback (logout / revoke). */
566
+ declare function clearToken(options?: TokenStoreOptions): Promise<void>;
567
+
568
+ /**
569
+ * Event sender (§9.2–9.3): POST a normalized event to `/v1/agent-events` with a
570
+ * SHORT hard timeout; on timeout/network/transient failure, queue it and return
571
+ * fast — never blocking or throwing into the harness. The retry-vs-terminal
572
+ * decision keys off the product error-envelope code (mirrored in `api.ts`), so the
573
+ * queue never fills with un-deliverable events. Each send also opportunistically
574
+ * drains the backlog (bounded by count AND by a TOTAL time budget — Claude Code
575
+ * installs its hooks with a 10s timeout, and an unbounded 50-entry drain at 3s per
576
+ * attempt could blow well past it, erm). The token is read from secure storage at
577
+ * send time and never logged; request bodies/title/body are never logged.
578
+ */
579
+
580
+ declare const DEFAULT_SEND_TIMEOUT_MS = 3000;
581
+ /**
582
+ * Total wall-clock budget for one send() (first attempt + opportunistic drain).
583
+ * Comfortably under the 10s hook timeout the adapters install, with headroom for
584
+ * process spawn + stdin read around it.
585
+ */
586
+ declare const DEFAULT_TOTAL_BUDGET_MS = 5000;
587
+ type SendOutcome = "delivered" | "queued" | "dropped";
588
+ interface SendResult {
589
+ outcome: SendOutcome;
590
+ status?: number;
591
+ /** Error code from the response envelope, when the server returned one. */
592
+ code?: ErrorCode;
593
+ /**
594
+ * The backend's delivery decision from the 202 body (`notified` / `suppressed` /
595
+ * `deduped`), when parseable. Lets callers (CLI `test`, 9fh) report what actually
596
+ * happened instead of claiming a beep that the backend decided not to push.
597
+ */
598
+ decision?: string;
599
+ /** Result of the opportunistic queue drain performed on this send. */
600
+ drained?: DrainResult;
601
+ }
602
+ interface SenderConfig {
603
+ /** API base URL, e.g. `https://api.birdybeep.com` (or a `wrangler dev` URL). */
604
+ baseUrl: string;
605
+ /** Hard per-request timeout (default 3s) — the harness must not wait longer. */
606
+ timeoutMs?: number;
607
+ /** Total budget for one send()+drain (default 5s) — see module doc (erm). */
608
+ totalBudgetMs?: number;
609
+ /** Queue instance (default a LocalEventQueue at the user data dir). */
610
+ queue?: LocalEventQueue;
611
+ /** Token-store options (inject backend/path in tests). */
612
+ tokenOptions?: TokenStoreOptions;
613
+ /** fetch implementation (injected in tests). */
614
+ fetchImpl?: typeof fetch;
615
+ /** Max queued events drained per send (bounded so the hook returns fast). */
616
+ drainMax?: number;
617
+ /** Injectable clock (ms) for deterministic budget tests. */
618
+ now?: () => number;
619
+ }
620
+ interface Sender {
621
+ send(event: BirdyBeepAgentEvent): Promise<SendResult>;
622
+ drainNow(): Promise<DrainResult>;
623
+ }
624
+ declare function createSender(config: SenderConfig): Sender;
625
+
626
+ /**
627
+ * The shared hook pipeline (§9.2–9.3): the core of `birdybeep hook <harness>`.
628
+ * A harness fires a hook → this runs adapter.normalizeEvent (redact/hash/validate)
629
+ * → dedup (collapse a repeat of the same beep) → sender.send (short timeout, queue
630
+ * on failure) → return fast. It must NEVER throw into or block the harness: an
631
+ * unmappable payload is skipped, a duplicate is dropped, and delivery failures queue.
632
+ * The CLI `hook` command (CLI-HOOK) wires stdin + adapter selection around this.
633
+ */
634
+
635
+ type HookOutcome = "delivered" | "queued" | "dropped" | "deduped" | "skipped";
636
+ interface HookResult {
637
+ outcome: HookOutcome;
638
+ /** The normalized event type, when the payload was mappable. */
639
+ eventType?: string;
640
+ /** The sender's result, when a send was attempted. */
641
+ send?: SendResult;
642
+ }
643
+ interface RunHookOptions {
644
+ sender: Sender;
645
+ /** Dedup ledger (default a RecentEventLedger at the user data dir). */
646
+ ledger?: RecentEventLedger;
647
+ }
648
+ /**
649
+ * Run one harness hook fire end-to-end. Returns a {@link HookResult}; never throws.
650
+ */
651
+ declare function runAgentHook(adapter: AgentAdapter, rawInput: unknown, options: RunHookOptions): Promise<HookResult>;
652
+
653
+ /**
654
+ * Integration-status report wire contract (§8.8/§13.4) — MIRRORED from the product
655
+ * `packages/schemas/integrations.ts`. `birdybeep agent install` / `doctor` report each
656
+ * harness's install/health state in ONE batched `POST /v1/integrations/status` call.
657
+ * LOCKSTEP (§16.4): additive to and independent of the §10.2 event payload.
658
+ *
659
+ * Identity (machine + user) is derived server-side from the machine token, never from this
660
+ * body. `last_status_payload` is diagnostic-only JSON (metadata only — never notification
661
+ * title/body or plaintext paths, §15.2), size-capped so an oversized blob fails validation.
662
+ */
663
+
664
+ /** §8.8 integration status, as a validator (mirrors the product's `integrationStatusSchema`). */
665
+ declare const integrationStatusSchema: z.ZodEnum<{
666
+ unknown: "unknown";
667
+ error: "error";
668
+ installed: "installed";
669
+ not_detected: "not_detected";
670
+ needs_restart: "needs_restart";
671
+ needs_trust: "needs_trust";
672
+ revoked: "revoked";
673
+ }>;
674
+ /** A machine reports a handful of harnesses per call (§8.8). */
675
+ declare const STATUS_REPORT_MAX_ITEMS = 16;
676
+ /** Diagnostic payload cap (§13.5): an oversized `last_status_payload` → 400. */
677
+ declare const STATUS_REPORT_MAX_PAYLOAD_BYTES = 2048;
678
+ /** One harness's reported install/health state. */
679
+ declare const integrationStatusItemSchema: z.ZodObject<{
680
+ harness: z.ZodEnum<{
681
+ claude_code: "claude_code";
682
+ codex: "codex";
683
+ opencode: "opencode";
684
+ }>;
685
+ status: z.ZodEnum<{
686
+ unknown: "unknown";
687
+ error: "error";
688
+ installed: "installed";
689
+ not_detected: "not_detected";
690
+ needs_restart: "needs_restart";
691
+ needs_trust: "needs_trust";
692
+ revoked: "revoked";
693
+ }>;
694
+ harness_version: z.ZodOptional<z.ZodString>;
695
+ adapter_version: z.ZodOptional<z.ZodString>;
696
+ last_status_payload: z.ZodOptional<z.ZodUnknown>;
697
+ }, z.core.$strip>;
698
+ /** The batched `POST /v1/integrations/status` request body. */
699
+ declare const integrationStatusReportSchema: z.ZodObject<{
700
+ integrations: z.ZodArray<z.ZodObject<{
701
+ harness: z.ZodEnum<{
702
+ claude_code: "claude_code";
703
+ codex: "codex";
704
+ opencode: "opencode";
705
+ }>;
706
+ status: z.ZodEnum<{
707
+ unknown: "unknown";
708
+ error: "error";
709
+ installed: "installed";
710
+ not_detected: "not_detected";
711
+ needs_restart: "needs_restart";
712
+ needs_trust: "needs_trust";
713
+ revoked: "revoked";
714
+ }>;
715
+ harness_version: z.ZodOptional<z.ZodString>;
716
+ adapter_version: z.ZodOptional<z.ZodString>;
717
+ last_status_payload: z.ZodOptional<z.ZodUnknown>;
718
+ }, z.core.$strip>>;
719
+ }, z.core.$strip>;
720
+ /**
721
+ * The response: the server echoes each harness's EFFECTIVE status (e.g. it forces
722
+ * Codex → `needs_trust` until the first event regardless of what the CLI reported).
723
+ * Tolerant of extra fields so a server addition won't break parsing.
724
+ */
725
+ declare const integrationStatusResponseSchema: z.ZodObject<{
726
+ integrations: z.ZodArray<z.ZodObject<{
727
+ harness: z.ZodEnum<{
728
+ claude_code: "claude_code";
729
+ codex: "codex";
730
+ opencode: "opencode";
731
+ }>;
732
+ status: z.ZodEnum<{
733
+ unknown: "unknown";
734
+ error: "error";
735
+ installed: "installed";
736
+ not_detected: "not_detected";
737
+ needs_restart: "needs_restart";
738
+ needs_trust: "needs_trust";
739
+ revoked: "revoked";
740
+ }>;
741
+ }, z.core.$catchall<z.ZodUnknown>>>;
742
+ }, z.core.$strip>;
743
+ type IntegrationStatusItem = z.infer<typeof integrationStatusItemSchema>;
744
+ type IntegrationStatusReport = z.infer<typeof integrationStatusReportSchema>;
745
+ type IntegrationStatusResponse = z.infer<typeof integrationStatusResponseSchema>;
746
+
747
+ /** Per-field truncation caps (agent-local privacy choice; the 16 KB total is the lockstep cap). */
748
+ declare const TITLE_MAX_CHARS = 200;
749
+ declare const BODY_MAX_CHARS = 2000;
750
+ declare const METADATA_VALUE_MAX_CHARS = 500;
751
+ declare const METADATA_MAX_KEYS = 64;
752
+ declare const METADATA_MAX_DEPTH = 4;
753
+ declare const LABEL_MAX_CHARS = 120;
754
+ /** Thrown when input cannot be coerced into a valid, under-cap event. */
755
+ declare class NormalizeError extends Error {
756
+ readonly issues?: readonly unknown[] | undefined;
757
+ constructor(message: string, issues?: readonly unknown[] | undefined);
758
+ }
759
+ interface NormalizeOptions {
760
+ /** Injectable clock for deterministic tests. Default: real wall clock (ISO). */
761
+ now?: () => string;
762
+ /** Injectable id generator for deterministic tests. Default: `evt_local_<uuid>`. */
763
+ generateId?: () => string;
764
+ }
765
+ /** Replace every absolute path in a string with a stable hash (no raw path survives). */
766
+ declare function scrubAbsolutePaths(text: string): string;
767
+ /** Replace secret-looking substrings with a redaction marker. */
768
+ declare function redactSecrets(text: string): string;
769
+ /** Truncate to `max` chars with an ellipsis marker. */
770
+ declare function truncate(text: string, max: number): string;
771
+ /** Hash an absolute path so it never leaves the machine raw (§14.5). */
772
+ declare function hashPath(path: string): string;
773
+ /**
774
+ * Coerce raw adapter input into a privacy-safe, size-bounded, validated
775
+ * {@link BirdyBeepAgentEvent}. Fills `event_id`/`occurred_at` defaults, hashes
776
+ * absolute paths, redacts secrets, truncates strings, and enforces the size cap.
777
+ * Throws {@link NormalizeError} if the result cannot be made valid + under-cap.
778
+ */
779
+ declare function normalizeEvent(input: unknown, opts?: NormalizeOptions): BirdyBeepAgentEvent;
780
+
781
+ /**
782
+ * Pairing handshake wire contract (§7.2/§13.4) — MIRRORED from the product
783
+ * `packages/schemas/pairing.ts`. The CLI builds the `POST /v1/pair/start` + `/v1/pair/token`
784
+ * requests and parses these (BARE, not `{ data }`-wrapped) responses. LOCKSTEP (§16.4):
785
+ * additive to and independent of the §10.2 event payload — a change here is coordinated.
786
+ *
787
+ * Privacy (§15.1): codes are short-lived single-use, stored HASHED server-side; `qr_payload`
788
+ * carries only the short `user_code`, NEVER a durable token; the `machine_token` from
789
+ * `/pair/token` is shown once and only its peppered hash is persisted. Optional advisory
790
+ * fields use `.catch(undefined)` to match the routes' accept-and-ignore leniency.
791
+ *
792
+ * (`/v1/pair/approve` is the signed-in MOBILE side — not mirrored here; the CLI never calls it.)
793
+ */
794
+
795
+ /** `POST /v1/pair/start` — UNAUTHENTICATED; the CLI opens a pairing session. */
796
+ declare const pairStartRequestSchema: z.ZodObject<{
797
+ machine_label: z.ZodString;
798
+ os: z.ZodCatch<z.ZodOptional<z.ZodString>>;
799
+ cli_version: z.ZodCatch<z.ZodOptional<z.ZodString>>;
800
+ requested_scopes: z.ZodCatch<z.ZodOptional<z.ZodArray<z.ZodString>>>;
801
+ }, z.core.$strip>;
802
+ /** `POST /v1/pair/token` — UNAUTHENTICATED; the CLI exchanges its device code (polled). */
803
+ declare const pairTokenRequestSchema: z.ZodObject<{
804
+ device_code: z.ZodString;
805
+ machine_fingerprint: z.ZodCatch<z.ZodOptional<z.ZodString>>;
806
+ }, z.core.$strip>;
807
+ /** `POST /v1/pair/start` response (bare). `qr_payload` encodes only the short `user_code`. */
808
+ declare const pairStartResponseSchema: z.ZodObject<{
809
+ device_code: z.ZodString;
810
+ user_code: z.ZodString;
811
+ qr_payload: z.ZodString;
812
+ expires_at: z.ZodISODateTime;
813
+ }, z.core.$strip>;
814
+ /** `POST /v1/pair/token` 201 response (bare). The durable token is issued exactly once. */
815
+ declare const pairTokenResponseSchema: z.ZodObject<{
816
+ machine_token: z.ZodString;
817
+ machine_id: z.ZodString;
818
+ }, z.core.$strip>;
819
+ type PairStartRequest = z.infer<typeof pairStartRequestSchema>;
820
+ type PairTokenRequest = z.infer<typeof pairTokenRequestSchema>;
821
+ type PairStartResponse = z.infer<typeof pairStartResponseSchema>;
822
+ type PairTokenResponse = z.infer<typeof pairTokenResponseSchema>;
823
+
824
+ /** Absolute path to the BirdyBeep user DATA dir (queue, caches). */
825
+ declare function birdyBeepDataDir(): string;
826
+ /** Absolute path to the BirdyBeep user CONFIG dir (token store fallback). */
827
+ declare function birdyBeepConfigDir(): string;
828
+
829
+ /**
830
+ * @birdybeep/agent-core — the shared runtime every adapter and the CLI build on:
831
+ * the canonical event schema (kept in lockstep with the private product repo's
832
+ * `packages/schemas`), the normalizer/redaction layer, the 24h local queue, the
833
+ * sender, the machine token store, and the `AgentAdapter` interface.
834
+ *
835
+ * The canonical event schema + enums (CORE-SCHEMA) are exported below. The
836
+ * normalizer/queue/sender/token-store/adapter-interface land in the remaining
837
+ * agent-core epic (CORE-*) tickets — see `bd ready`.
838
+ */
839
+
840
+ /** Package version marker — replaced by the real build/version pipeline (REL-*). */
841
+ declare const AGENT_CORE_VERSION = "0.0.0";
842
+ /**
843
+ * Minimal identity an adapter reports about the harness it integrates with.
844
+ * The full {@link https://birdybeep.dev | AgentAdapter} interface (detect /
845
+ * install / uninstall / status / doctor / normalizeEvent) is defined in CORE-ADAPTER.
846
+ */
847
+ interface AdapterMeta {
848
+ /** Stable harness id, e.g. `"claude_code"`, `"codex"`, `"opencode"`. */
849
+ readonly harness: string;
850
+ }
851
+
852
+ export { AGENT_CORE_VERSION, AGENT_SESSION_STATUSES, APPROVAL_COLLAPSE_WINDOW_MS, type AdapterMeta, type AgentAdapter, type AgentEventsRequest, type AgentSessionStatus, BIRDYBEEP_EVENT_TYPES, BODY_MAX_CHARS, type BirdyBeepAgentEvent, type BirdyBeepEventType, CLAIM_RECLAIM_MS, DEFAULT_DEDUP_WINDOW_MS, DEFAULT_DRAIN_MAX, DEFAULT_SEND_TIMEOUT_MS, DEFAULT_TOTAL_BUDGET_MS, type DetectionResult, type DoctorCheck, type DoctorResult, type DrainOutcome, type DrainResult, ERROR_CODES, ERROR_STATUS, type ErrorCode, type ErrorEnvelope, type EventMetadata, FileTokenStore, type FileTokenStoreOptions, HARNESS_IDS, type HarnessId, type HookOutcome, type HookResult, INTEGRATION_STATUSES, type InstallOptions, type InstallResult, type IntegrationStatus, type IntegrationStatusItem, type IntegrationStatusReport, type IntegrationStatusResponse, type KeychainBackend, KeychainTokenStore, LABEL_MAX_CHARS, LocalEventQueue, type LocalEventQueueOptions, MAX_AGENT_EVENT_BYTES, METADATA_MAX_DEPTH, METADATA_MAX_KEYS, METADATA_VALUE_MAX_CHARS, type Machine, type MachineIdentity, type MachineSignals, NormalizeError, type NormalizeOptions, type PairStartRequest, type PairStartResponse, type PairTokenRequest, type PairTokenResponse, QUEUE_RETENTION_MS, RecentEventLedger, type RecentEventLedgerOptions, type RunHookOptions, STATUS_REPORT_MAX_ITEMS, STATUS_REPORT_MAX_PAYLOAD_BYTES, type SendOutcome, type SendResult, type Sender, type SenderConfig, type SuccessEnvelope, TITLE_MAX_CHARS, type TokenStore, type TokenStoreKind, type TokenStoreOptions, type UninstallOptions, type UninstallResult, type Workspace, agentEventsRequestSchema, approvalCollapseIdentity, birdyBeepAgentEventSchema, birdyBeepConfigDir, birdyBeepDataDir, clearToken, collectMachineSignals, createSender, defaultKeychainBackend, errorCodeSchema, errorEnvelopeSchema, eventIdentity, eventMetadataSchema, eventTypeSchema, fingerprintFromSignals, getMachineFingerprintHash, getMachineIdentity, getMachineLabel, getOS, getToken, harnessSchema, hashPath, integrationStatusItemSchema, integrationStatusReportSchema, integrationStatusResponseSchema, integrationStatusSchema, isWithinMaxAgentEventSize, isoDateTimeSchema, machineSchema, macosKeychainBackend, normalizeEvent, pairStartRequestSchema, pairStartResponseSchema, pairTokenRequestSchema, pairTokenResponseSchema, redactSecrets, resolveTokenStore, runAgentHook, scrubAbsolutePaths, sessionStatusSchema, setToken, successEnvelopeSchema, truncate, unavailableKeychainBackend, workspaceSchema };