@coralai/claude-code-agent 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/README.md +111 -0
  2. package/dist/agent.d.ts +240 -0
  3. package/dist/agent.d.ts.map +1 -0
  4. package/dist/agent.js +592 -0
  5. package/dist/agent.js.map +1 -0
  6. package/dist/errors.d.ts +27 -0
  7. package/dist/errors.d.ts.map +1 -0
  8. package/dist/errors.js +33 -0
  9. package/dist/errors.js.map +1 -0
  10. package/dist/hooks/fs.d.ts +26 -0
  11. package/dist/hooks/fs.d.ts.map +1 -0
  12. package/dist/hooks/fs.js +61 -0
  13. package/dist/hooks/fs.js.map +1 -0
  14. package/dist/hooks/index.d.ts +18 -0
  15. package/dist/hooks/index.d.ts.map +1 -0
  16. package/dist/hooks/index.js +2 -0
  17. package/dist/hooks/index.js.map +1 -0
  18. package/dist/hooks/permissions.d.ts +42 -0
  19. package/dist/hooks/permissions.d.ts.map +1 -0
  20. package/dist/hooks/permissions.js +79 -0
  21. package/dist/hooks/permissions.js.map +1 -0
  22. package/dist/hooks/terminal.d.ts +56 -0
  23. package/dist/hooks/terminal.d.ts.map +1 -0
  24. package/dist/hooks/terminal.js +127 -0
  25. package/dist/hooks/terminal.js.map +1 -0
  26. package/dist/index.d.ts +32 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +31 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/internal/acp-client-interface.d.ts +107 -0
  31. package/dist/internal/acp-client-interface.d.ts.map +1 -0
  32. package/dist/internal/acp-client-interface.js +17 -0
  33. package/dist/internal/acp-client-interface.js.map +1 -0
  34. package/dist/internal/acp-types.d.ts +64 -0
  35. package/dist/internal/acp-types.d.ts.map +1 -0
  36. package/dist/internal/acp-types.js +17 -0
  37. package/dist/internal/acp-types.js.map +1 -0
  38. package/dist/internal/agent-runtime-interface.d.ts +40 -0
  39. package/dist/internal/agent-runtime-interface.d.ts.map +1 -0
  40. package/dist/internal/agent-runtime-interface.js +17 -0
  41. package/dist/internal/agent-runtime-interface.js.map +1 -0
  42. package/dist/internal/local-acp-client.d.ts +27 -0
  43. package/dist/internal/local-acp-client.d.ts.map +1 -0
  44. package/dist/internal/local-acp-client.js +26 -0
  45. package/dist/internal/local-acp-client.js.map +1 -0
  46. package/dist/internal/sdk-adapter.d.ts +24 -0
  47. package/dist/internal/sdk-adapter.d.ts.map +1 -0
  48. package/dist/internal/sdk-adapter.js +453 -0
  49. package/dist/internal/sdk-adapter.js.map +1 -0
  50. package/dist/internal/session-accumulator.d.ts +55 -0
  51. package/dist/internal/session-accumulator.d.ts.map +1 -0
  52. package/dist/internal/session-accumulator.js +133 -0
  53. package/dist/internal/session-accumulator.js.map +1 -0
  54. package/dist/prompt-handle.d.ts +70 -0
  55. package/dist/prompt-handle.d.ts.map +1 -0
  56. package/dist/prompt-handle.js +137 -0
  57. package/dist/prompt-handle.js.map +1 -0
  58. package/dist/recording/index.d.ts +10 -0
  59. package/dist/recording/index.d.ts.map +1 -0
  60. package/dist/recording/index.js +10 -0
  61. package/dist/recording/index.js.map +1 -0
  62. package/dist/recording/replayer.d.ts +17 -0
  63. package/dist/recording/replayer.d.ts.map +1 -0
  64. package/dist/recording/replayer.js +68 -0
  65. package/dist/recording/replayer.js.map +1 -0
  66. package/dist/recording/writer.d.ts +32 -0
  67. package/dist/recording/writer.d.ts.map +1 -0
  68. package/dist/recording/writer.js +122 -0
  69. package/dist/recording/writer.js.map +1 -0
  70. package/dist/telemetry/index.d.ts +8 -0
  71. package/dist/telemetry/index.d.ts.map +1 -0
  72. package/dist/telemetry/index.js +8 -0
  73. package/dist/telemetry/index.js.map +1 -0
  74. package/dist/telemetry/otel-adapter.d.ts +105 -0
  75. package/dist/telemetry/otel-adapter.d.ts.map +1 -0
  76. package/dist/telemetry/otel-adapter.js +153 -0
  77. package/dist/telemetry/otel-adapter.js.map +1 -0
  78. package/package.json +45 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-accumulator.js","sourceRoot":"","sources":["../../src/internal/session-accumulator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * PromptHandle — the value returned by `agent.prompt()`.
3
+ *
4
+ * Per Eng D1 decision (2026-05-14): `updates` is **broadcast** — every
5
+ * SessionUpdate is buffered, every consumer (including late or repeat
6
+ * iterators) sees the full ordered stream. Memory cost: ~100KB per turn
7
+ * (typically <100 updates × <1KB each). This eliminates the race where
8
+ * awaiting `result` first drains the stream before iterating `updates`.
9
+ *
10
+ * Per codex finding #4 + #5 (2026-05-14): `cancel()` cancels only this
11
+ * turn — not the session, not queued sibling prompts. Use
12
+ * `agent.cancelAll()` for session-level cancel.
13
+ *
14
+ * Spec: vision doc §3 + §13 D1.
15
+ */
16
+ import type * as schema from '@agentclientprotocol/sdk';
17
+ import { AgentError } from './errors.js';
18
+ /** A single streamed update for one turn. Re-uses the SDK shape directly so
19
+ * this package tracks ACP protocol evolution without a translation layer. */
20
+ export type SessionUpdate = schema.SessionNotification['update'];
21
+ export type StopReason = 'end_turn' | 'cancelled' | 'error' | 'max_tokens';
22
+ export interface UsageSummary {
23
+ inputTokens: number;
24
+ outputTokens: number;
25
+ cacheReadTokens?: number;
26
+ cacheWriteTokens?: number;
27
+ /** USD; sourced from `usage_update.cost` if shim provides, undefined otherwise. */
28
+ estimatedCostUsd?: number;
29
+ }
30
+ export interface PromptResult {
31
+ /** Concatenated text from all `assistant_message_chunk` updates. */
32
+ text: string;
33
+ /** Every SessionUpdate emitted during this turn, in order. */
34
+ updates: SessionUpdate[];
35
+ usage?: UsageSummary;
36
+ stopReason: StopReason;
37
+ turnId: string;
38
+ }
39
+ export interface PromptHandle {
40
+ /** Unique per turn — useful as OTel span attribute and recording key. */
41
+ readonly turnId: string;
42
+ /** Session this turn belongs to. */
43
+ readonly sessionId: string;
44
+ /** Resolves when the turn completes; rejects on error/timeout/cancel. */
45
+ readonly result: Promise<PromptResult>;
46
+ /** Async iterable of updates. **Broadcast**: multi-consumer safe; late
47
+ * consumers see the full buffered history from the start. */
48
+ readonly updates: AsyncIterable<SessionUpdate>;
49
+ /** Cancel ONLY this turn. Returns when cancel ACK received from agent. */
50
+ cancel(): Promise<void>;
51
+ }
52
+ /** Internal handle: agent code pushes updates / settles result via the
53
+ * controller; consumers see only the PromptHandle face. */
54
+ export interface PromptHandleController {
55
+ handle: PromptHandle;
56
+ pushUpdate(update: SessionUpdate): void;
57
+ settle(result: PromptResult): void;
58
+ fail(error: AgentError): void;
59
+ /** Called by Agent when caller invokes handle.cancel(); the agent runs the
60
+ * actual ACP cancel and then calls fail() with PROMPT_CANCELLED. */
61
+ setCancelImpl(impl: () => Promise<void>): void;
62
+ }
63
+ /** Build a new handle + its controller. The agent keeps the controller and
64
+ * returns the handle to the caller. */
65
+ export declare function createPromptHandle(turnId: string, sessionId: string): PromptHandleController;
66
+ /** Walk a buffer of updates and produce a final PromptResult. The agent
67
+ * calls this when the agent-side stream emits `end_turn` (or equivalent).
68
+ * Centralized so tests can pin the aggregation behavior. */
69
+ export declare function aggregateResult(turnId: string, updates: SessionUpdate[], stopReason: StopReason): PromptResult;
70
+ //# sourceMappingURL=prompt-handle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt-handle.d.ts","sourceRoot":"","sources":["../src/prompt-handle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,KAAK,MAAM,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAIzC;8EAC8E;AAC9E,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,mBAAmB,CAAC,QAAQ,CAAC,CAAC;AAEjE,MAAM,MAAM,UAAU,GAAG,UAAU,GAAG,WAAW,GAAG,OAAO,GAAG,YAAY,CAAC;AAE3E,MAAM,WAAW,YAAY;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mFAAmF;IACnF,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,YAAY;IAC3B,oEAAoE;IACpE,IAAI,EAAE,MAAM,CAAC;IACb,8DAA8D;IAC9D,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,UAAU,EAAE,UAAU,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,YAAY;IAC3B,yEAAyE;IACzE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,oCAAoC;IACpC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,yEAAyE;IACzE,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;IACvC;kEAC8D;IAC9D,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC,aAAa,CAAC,CAAC;IAC/C,0EAA0E;IAC1E,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACzB;AAID;4DAC4D;AAC5D,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,YAAY,CAAC;IACrB,UAAU,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI,CAAC;IACxC,MAAM,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,CAAC;IACnC,IAAI,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9B;yEACqE;IACrE,aAAa,CAAC,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAChD;AAED;wCACwC;AACxC,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,GAChB,sBAAsB,CAyGxB;AAID;;6DAE6D;AAC7D,wBAAgB,eAAe,CAC7B,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,aAAa,EAAE,EACxB,UAAU,EAAE,UAAU,GACrB,YAAY,CAiCd"}
@@ -0,0 +1,137 @@
1
+ import { AgentError } from './errors.js';
2
+ /** Build a new handle + its controller. The agent keeps the controller and
3
+ * returns the handle to the caller. */
4
+ export function createPromptHandle(turnId, sessionId) {
5
+ // Buffer of all updates, in insertion order. Broadcast semantics: each
6
+ // iterator reads from index 0 independently.
7
+ const buffer = [];
8
+ // Iterator-level resolvers waiting for next update or done signal.
9
+ const waiters = new Set();
10
+ // Final state: 'pending' | 'done' (buffer is the final list).
11
+ let done = false;
12
+ // Promise for the aggregated result. Resolved via controller.settle().
13
+ let resolveResult;
14
+ let rejectResult;
15
+ const result = new Promise((res, rej) => {
16
+ resolveResult = res;
17
+ rejectResult = rej;
18
+ });
19
+ // Prevent unhandled-rejection warnings if caller never awaits result.
20
+ result.catch(() => { });
21
+ // Cancel implementation set by agent after handle construction (so the
22
+ // agent can wire it to its own ACP cancel call referencing turnId).
23
+ let cancelImpl = async () => {
24
+ // Default: just settle with PROMPT_CANCELLED — agent overrides this.
25
+ const err = new AgentError('PROMPT_CANCELLED', 'Cancel requested before agent attached', {
26
+ context: { turnId, sessionId },
27
+ });
28
+ rejectResult(err);
29
+ done = true;
30
+ notifyWaiters();
31
+ };
32
+ function notifyWaiters() {
33
+ for (const wake of waiters)
34
+ wake();
35
+ waiters.clear();
36
+ }
37
+ function pushUpdate(update) {
38
+ if (done)
39
+ return; // post-settle updates ignored
40
+ buffer.push(update);
41
+ notifyWaiters();
42
+ }
43
+ function settle(value) {
44
+ if (done)
45
+ return;
46
+ done = true;
47
+ resolveResult(value);
48
+ notifyWaiters();
49
+ }
50
+ function fail(error) {
51
+ if (done)
52
+ return;
53
+ done = true;
54
+ rejectResult(error);
55
+ notifyWaiters();
56
+ }
57
+ function setCancelImpl(impl) {
58
+ cancelImpl = impl;
59
+ }
60
+ // Broadcast async iterable. Each call to [Symbol.asyncIterator]() gets a
61
+ // fresh cursor starting at buffer[0]. Multiple consumers can iterate
62
+ // concurrently or sequentially with identical results.
63
+ const updates = {
64
+ [Symbol.asyncIterator]() {
65
+ let cursor = 0;
66
+ return {
67
+ async next() {
68
+ // Drain buffer first
69
+ while (cursor < buffer.length) {
70
+ const update = buffer[cursor];
71
+ cursor++;
72
+ if (update !== undefined) {
73
+ return { value: update, done: false };
74
+ }
75
+ }
76
+ // Caught up. If turn is done, signal end.
77
+ if (done) {
78
+ return { value: undefined, done: true };
79
+ }
80
+ // Wait for next push or settle.
81
+ await new Promise((resolve) => {
82
+ waiters.add(resolve);
83
+ });
84
+ // Recurse — drain again.
85
+ return this.next();
86
+ },
87
+ return() {
88
+ // Consumer abandoned this iterator. Don't affect other consumers
89
+ // or the underlying turn — just stop yielding from this cursor.
90
+ return Promise.resolve({ value: undefined, done: true });
91
+ },
92
+ };
93
+ },
94
+ };
95
+ const handle = {
96
+ turnId,
97
+ sessionId,
98
+ result,
99
+ updates,
100
+ cancel: () => cancelImpl(),
101
+ };
102
+ return { handle, pushUpdate, settle, fail, setCancelImpl };
103
+ }
104
+ // ─── Helper: aggregate updates into PromptResult ───────────────
105
+ /** Walk a buffer of updates and produce a final PromptResult. The agent
106
+ * calls this when the agent-side stream emits `end_turn` (or equivalent).
107
+ * Centralized so tests can pin the aggregation behavior. */
108
+ export function aggregateResult(turnId, updates, stopReason) {
109
+ let text = '';
110
+ let usage;
111
+ for (const update of updates) {
112
+ const u = update;
113
+ if (u.sessionUpdate === 'agent_message_chunk' && u.content?.type === 'text' && typeof u.content.text === 'string') {
114
+ text += u.content.text;
115
+ }
116
+ if (u.sessionUpdate === 'usage_update') {
117
+ const raw = u;
118
+ // Shim emits incremental usage; the last update wins. We accept the
119
+ // raw fields here; the agent layer maps `used` → outputTokens estimate.
120
+ // For v0.1, expose raw cost if present.
121
+ if (raw.cost && typeof raw.cost.amount === 'number') {
122
+ const existing = usage ?? { inputTokens: 0, outputTokens: 0 };
123
+ usage = { ...existing, estimatedCostUsd: raw.cost.amount };
124
+ }
125
+ }
126
+ }
127
+ const result = {
128
+ text,
129
+ updates,
130
+ stopReason,
131
+ turnId,
132
+ };
133
+ if (usage !== undefined)
134
+ result.usage = usage;
135
+ return result;
136
+ }
137
+ //# sourceMappingURL=prompt-handle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt-handle.js","sourceRoot":"","sources":["../src/prompt-handle.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Recording subsystem barrel.
3
+ *
4
+ * Step 1.4.6 ships writer + minimal replayer. Hook divergence validation
5
+ * (REPLAY_MISMATCH) lives in Step 1.4.8 when static replay() wires the
6
+ * replayer back into a fake agent.
7
+ */
8
+ export { RecordingWriter, type RecordingEvent } from './writer.js';
9
+ export { RecordingReplayer } from './replayer.js';
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/recording/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,eAAe,EAAE,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Recording subsystem barrel.
3
+ *
4
+ * Step 1.4.6 ships writer + minimal replayer. Hook divergence validation
5
+ * (REPLAY_MISMATCH) lives in Step 1.4.8 when static replay() wires the
6
+ * replayer back into a fake agent.
7
+ */
8
+ export { RecordingWriter } from './writer.js';
9
+ export { RecordingReplayer } from './replayer.js';
10
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/recording/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,eAAe,EAAuB,MAAM,aAAa,CAAC;AACnE,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC"}
@@ -0,0 +1,17 @@
1
+ import type { RecordingEvent } from './writer.js';
2
+ export declare class RecordingReplayer {
3
+ private readonly events;
4
+ private cursor;
5
+ constructor(path: string);
6
+ /** Number of parsed events. */
7
+ get length(): number;
8
+ /** All events in order. */
9
+ all(): readonly RecordingEvent[];
10
+ /** Read next event of type `kind`, advancing cursor past skipped events. */
11
+ nextOfType(kind: string): RecordingEvent | null;
12
+ /** Look at the next event without advancing. */
13
+ peek(): RecordingEvent | null;
14
+ /** Throw REPLAY_EOF if cursor reached end. */
15
+ expectMore(reason: string): void;
16
+ }
17
+ //# sourceMappingURL=replayer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replayer.d.ts","sourceRoot":"","sources":["../../src/recording/replayer.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmB;IAC1C,OAAO,CAAC,MAAM,CAAK;gBAEP,IAAI,EAAE,MAAM;IA6BxB,+BAA+B;IAC/B,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED,2BAA2B;IAC3B,GAAG,IAAI,SAAS,cAAc,EAAE;IAIhC,4EAA4E;IAC5E,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI;IAQ/C,gDAAgD;IAChD,IAAI,IAAI,cAAc,GAAG,IAAI;IAI7B,8CAA8C;IAC9C,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;CASjC"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * RecordingReplayer — read jsonl recording and yield events.
3
+ *
4
+ * Step 1.4.6 scope: minimal reader that parses jsonl + iterates events.
5
+ * Step 1.4.8 adds REPLAY_MISMATCH validation (hook IO must match recording)
6
+ * + REPLAY_EOF handling (recording ended before turn settled).
7
+ */
8
+ import { readFileSync } from 'node:fs';
9
+ import { AgentError } from '../errors.js';
10
+ export class RecordingReplayer {
11
+ events;
12
+ cursor = 0;
13
+ constructor(path) {
14
+ let raw;
15
+ try {
16
+ raw = readFileSync(path, 'utf-8');
17
+ }
18
+ catch (e) {
19
+ throw new AgentError('REPLAY_EOF', `Recording file not readable at ${path}: ${e instanceof Error ? e.message : String(e)}`, {
20
+ ...(e instanceof Error ? { cause: e } : {}),
21
+ context: { path },
22
+ });
23
+ }
24
+ this.events = [];
25
+ for (const line of raw.split('\n')) {
26
+ const trimmed = line.trim();
27
+ if (!trimmed)
28
+ continue;
29
+ try {
30
+ const parsed = JSON.parse(trimmed);
31
+ if (typeof parsed.type === 'string') {
32
+ this.events.push(parsed);
33
+ }
34
+ }
35
+ catch {
36
+ /* skip malformed lines — replay is best-effort */
37
+ }
38
+ }
39
+ }
40
+ /** Number of parsed events. */
41
+ get length() {
42
+ return this.events.length;
43
+ }
44
+ /** All events in order. */
45
+ all() {
46
+ return this.events;
47
+ }
48
+ /** Read next event of type `kind`, advancing cursor past skipped events. */
49
+ nextOfType(kind) {
50
+ while (this.cursor < this.events.length) {
51
+ const event = this.events[this.cursor++];
52
+ if (event && event.type === kind)
53
+ return event;
54
+ }
55
+ return null;
56
+ }
57
+ /** Look at the next event without advancing. */
58
+ peek() {
59
+ return this.events[this.cursor] ?? null;
60
+ }
61
+ /** Throw REPLAY_EOF if cursor reached end. */
62
+ expectMore(reason) {
63
+ if (this.cursor >= this.events.length) {
64
+ throw new AgentError('REPLAY_EOF', `Recording ended unexpectedly: ${reason}`, { context: { cursor: this.cursor, total: this.events.length } });
65
+ }
66
+ }
67
+ }
68
+ //# sourceMappingURL=replayer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replayer.js","sourceRoot":"","sources":["../../src/recording/replayer.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAG1C,MAAM,OAAO,iBAAiB;IACX,MAAM,CAAmB;IAClC,MAAM,GAAG,CAAC,CAAC;IAEnB,YAAY,IAAY;QACtB,IAAI,GAAW,CAAC;QAChB,IAAI,CAAC;YACH,GAAG,GAAG,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACpC,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,MAAM,IAAI,UAAU,CAClB,YAAY,EACZ,kCAAkC,IAAI,KAAK,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EACvF;gBACE,GAAG,CAAC,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC3C,OAAO,EAAE,EAAE,IAAI,EAAE;aAClB,CACF,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC;QACjB,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YACnC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;YAC5B,IAAI,CAAC,OAAO;gBAAE,SAAS;YACvB,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAmB,CAAC;gBACrD,IAAI,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;oBACpC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;gBAC3B,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,kDAAkD;YACpD,CAAC;QACH,CAAC;IACH,CAAC;IAED,+BAA+B;IAC/B,IAAI,MAAM;QACR,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC;IAC5B,CAAC;IAED,2BAA2B;IAC3B,GAAG;QACD,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IAED,4EAA4E;IAC5E,UAAU,CAAC,IAAY;QACrB,OAAO,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;YACxC,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;YACzC,IAAI,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,IAAI;gBAAE,OAAO,KAAK,CAAC;QACjD,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,gDAAgD;IAChD,IAAI;QACF,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC;IAC1C,CAAC;IAED,8CAA8C;IAC9C,UAAU,CAAC,MAAc;QACvB,IAAI,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;YACtC,MAAM,IAAI,UAAU,CAClB,YAAY,EACZ,iCAAiC,MAAM,EAAE,EACzC,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,EAAE,CAChE,CAAC;QACJ,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,32 @@
1
+ export interface RecordingEvent {
2
+ /** ISO-8601 timestamp; set by writer if absent. */
3
+ ts?: string;
4
+ /** Event kind; see file format docs above. */
5
+ type: string;
6
+ /** Arbitrary payload fields. */
7
+ [key: string]: unknown;
8
+ }
9
+ export declare class RecordingWriter {
10
+ private readonly path;
11
+ private readonly stream;
12
+ private buffer;
13
+ private bufferedBytes;
14
+ private flushTimer;
15
+ private closed;
16
+ /** Resolves when close() finishes flushing + closing the stream. */
17
+ private closePromise;
18
+ constructor(path: string);
19
+ /** Path being written. */
20
+ get filePath(): string;
21
+ /** Append one event. ts auto-filled if absent. */
22
+ write(event: RecordingEvent): void;
23
+ /** Drain buffer to stream now. Synchronous append; no awaiting drain. */
24
+ private flushNow;
25
+ /**
26
+ * Force flush pending bytes and close the stream. Idempotent.
27
+ * Resolves only after the FS drain completes — safe to await before
28
+ * shutting down the process.
29
+ */
30
+ close(): Promise<void>;
31
+ }
32
+ //# sourceMappingURL=writer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"writer.d.ts","sourceRoot":"","sources":["../../src/recording/writer.ts"],"names":[],"mappings":"AAiCA,MAAM,WAAW,cAAc;IAC7B,mDAAmD;IACnD,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,8CAA8C;IAC9C,IAAI,EAAE,MAAM,CAAC;IACb,gCAAgC;IAChC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;IAGrC,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,aAAa,CAAK;IAE1B,OAAO,CAAC,UAAU,CAA+B;IACjD,OAAO,CAAC,MAAM,CAAS;IACvB,oEAAoE;IACpE,OAAO,CAAC,YAAY,CAA8B;gBAEtC,IAAI,EAAE,MAAM;IAcxB,0BAA0B;IAC1B,IAAI,QAAQ,IAAI,MAAM,CAErB;IAED,kDAAkD;IAClD,KAAK,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI;IAsBlC,yEAAyE;IACzE,OAAO,CAAC,QAAQ;IAsBhB;;;;OAIG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAW7B"}
@@ -0,0 +1,122 @@
1
+ /**
2
+ * RecordingWriter — captures agent session as jsonl for replay-based testing.
3
+ *
4
+ * Per Eng D3/D4 decisions (2026-05-14):
5
+ * - Captures ACP raw frames + hook IO + turn boundaries + permissions
6
+ * + elicitation answers (D3)
7
+ * - Async batch flush: 100ms tick OR 64KB buffered, whichever first (D4)
8
+ * - close() force-flushes pending bytes
9
+ *
10
+ * Privacy: jsonl contains raw prompts/responses/tool IO. Treat as sensitive.
11
+ * The agent README emits a warning when `recording` is set; this writer
12
+ * does not encrypt or redact.
13
+ *
14
+ * File format: one JSON object per line, schema:
15
+ * { ts: ISO-8601, type: <event-kind>, ...payload }
16
+ *
17
+ * Common event kinds (v0.1):
18
+ * - "session_start" { sessionId, pid?, cwd }
19
+ * - "session_stop" { reason, code? }
20
+ * - "turn_start" { turnId, prompt }
21
+ * - "turn_end" { turnId, stopReason, usage? }
22
+ * - "session_update" { turnId, update } — every SessionUpdate
23
+ * - "hook_call" { hook, input, output? } — fs/terminal/permission/elicitation
24
+ *
25
+ * Step 1.4.6 scope: writer + batch flushing + close(). Replayer that
26
+ * validates hook divergence (REPLAY_MISMATCH) → 1.4.8.
27
+ */
28
+ import { createWriteStream, mkdirSync } from 'node:fs';
29
+ import { dirname } from 'node:path';
30
+ const FLUSH_INTERVAL_MS = 100;
31
+ const FLUSH_THRESHOLD_BYTES = 64 * 1024; // 64 KB
32
+ export class RecordingWriter {
33
+ path;
34
+ stream;
35
+ // In-memory buffer: jsonl lines waiting to flush
36
+ buffer = [];
37
+ bufferedBytes = 0;
38
+ flushTimer = null;
39
+ closed = false;
40
+ /** Resolves when close() finishes flushing + closing the stream. */
41
+ closePromise = null;
42
+ constructor(path) {
43
+ this.path = path;
44
+ try {
45
+ mkdirSync(dirname(path), { recursive: true });
46
+ }
47
+ catch {
48
+ /* parent dir may already exist */
49
+ }
50
+ this.stream = createWriteStream(path, { flags: 'a', encoding: 'utf-8' });
51
+ // Stream errors get swallowed; recording must never crash the agent.
52
+ this.stream.on('error', () => {
53
+ /* best effort */
54
+ });
55
+ }
56
+ /** Path being written. */
57
+ get filePath() {
58
+ return this.path;
59
+ }
60
+ /** Append one event. ts auto-filled if absent. */
61
+ write(event) {
62
+ if (this.closed)
63
+ return;
64
+ const line = JSON.stringify({
65
+ ts: event.ts ?? new Date().toISOString(),
66
+ ...event,
67
+ }) + '\n';
68
+ this.buffer.push(line);
69
+ this.bufferedBytes += Buffer.byteLength(line, 'utf-8');
70
+ if (this.bufferedBytes >= FLUSH_THRESHOLD_BYTES) {
71
+ // Threshold flush — fire-and-forget
72
+ this.flushNow();
73
+ return;
74
+ }
75
+ if (this.flushTimer === null) {
76
+ this.flushTimer = setTimeout(() => this.flushNow(), FLUSH_INTERVAL_MS);
77
+ // Don't keep the event loop alive just for the recording flush
78
+ this.flushTimer.unref?.();
79
+ }
80
+ }
81
+ /** Drain buffer to stream now. Synchronous append; no awaiting drain. */
82
+ flushNow() {
83
+ if (this.buffer.length === 0) {
84
+ if (this.flushTimer !== null) {
85
+ clearTimeout(this.flushTimer);
86
+ this.flushTimer = null;
87
+ }
88
+ return;
89
+ }
90
+ const chunk = this.buffer.join('');
91
+ this.buffer = [];
92
+ this.bufferedBytes = 0;
93
+ if (this.flushTimer !== null) {
94
+ clearTimeout(this.flushTimer);
95
+ this.flushTimer = null;
96
+ }
97
+ try {
98
+ this.stream.write(chunk);
99
+ }
100
+ catch {
101
+ /* best effort */
102
+ }
103
+ }
104
+ /**
105
+ * Force flush pending bytes and close the stream. Idempotent.
106
+ * Resolves only after the FS drain completes — safe to await before
107
+ * shutting down the process.
108
+ */
109
+ async close() {
110
+ if (this.closePromise)
111
+ return this.closePromise;
112
+ this.closed = true;
113
+ this.flushNow();
114
+ this.closePromise = new Promise((resolve) => {
115
+ this.stream.end(() => {
116
+ resolve();
117
+ });
118
+ });
119
+ return this.closePromise;
120
+ }
121
+ }
122
+ //# sourceMappingURL=writer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"writer.js","sourceRoot":"","sources":["../../src/recording/writer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Telemetry barrel.
3
+ *
4
+ * Public OTel adapter for caller-supplied Tracer (peer dep
5
+ * `@opentelemetry/api ≥1.7 <2`, optional). Zero cost when not used.
6
+ */
7
+ export { SpanManager, SpanStatusCode, setAttributeSafe, truncateAttributeValue, type Attributes, type AttributeValue, type Span, type Tracer, } from './otel-adapter.js';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/telemetry/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EACL,WAAW,EACX,cAAc,EACd,gBAAgB,EAChB,sBAAsB,EACtB,KAAK,UAAU,EACf,KAAK,cAAc,EACnB,KAAK,IAAI,EACT,KAAK,MAAM,GACZ,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Telemetry barrel.
3
+ *
4
+ * Public OTel adapter for caller-supplied Tracer (peer dep
5
+ * `@opentelemetry/api ≥1.7 <2`, optional). Zero cost when not used.
6
+ */
7
+ export { SpanManager, SpanStatusCode, setAttributeSafe, truncateAttributeValue, } from './otel-adapter.js';
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/telemetry/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EACL,WAAW,EACX,cAAc,EACd,gBAAgB,EAChB,sBAAsB,GAKvB,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,105 @@
1
+ /**
2
+ * OpenTelemetry adapter — wraps session/prompt/tool lifecycle in OTel spans.
3
+ *
4
+ * Per Eng D4 (2026-05-14):
5
+ * - Span hierarchy: agent.session (root) → agent.prompt → agent.tool.call /
6
+ * agent.permission.request / agent.elicitation.request
7
+ * - Attributes follow OTel GenAI semantic conventions:
8
+ * gen_ai.system: 'claude'
9
+ * gen_ai.usage.input_tokens / output_tokens / cache_read_tokens / cache_write_tokens
10
+ * gen_ai.response.finish_reason
11
+ * - Attribute payloads > 8 KB are truncated and marked `truncated: true`
12
+ *
13
+ * Peer dependency: `@opentelemetry/api` ≥1.7 <2 (OPTIONAL — only loaded when
14
+ * caller passes a tracer). The adapter never imports `@opentelemetry/api`;
15
+ * it uses a minimal structural `Tracer` interface that matches the real type.
16
+ * This keeps zero-dep cost for callers who don't use telemetry.
17
+ *
18
+ * Caller pattern:
19
+ *
20
+ * import { trace } from '@opentelemetry/api';
21
+ * const agent = new ClaudeCodeAgent({ cwd, tracer: trace.getTracer('myapp') });
22
+ *
23
+ * Step 1.4.7 ships the adapter + 8 KB truncation. Span hierarchy is wired
24
+ * for session.start, prompt, and turn-end. Tool/permission/elicitation
25
+ * spans land in Step 1.4.8 alongside their respective wirings.
26
+ */
27
+ /** Subset of OTel SpanAttributeValue we use. */
28
+ export type AttributeValue = string | number | boolean | Array<string | number | boolean>;
29
+ export interface Attributes {
30
+ [key: string]: AttributeValue;
31
+ }
32
+ /** Minimal Span structural interface. */
33
+ export interface Span {
34
+ setAttribute(key: string, value: AttributeValue): unknown;
35
+ setAttributes(attrs: Attributes): unknown;
36
+ setStatus(status: {
37
+ code: number;
38
+ message?: string;
39
+ }): unknown;
40
+ recordException(error: Error): unknown;
41
+ end(endTime?: number): unknown;
42
+ }
43
+ /** Minimal Tracer structural interface. */
44
+ export interface Tracer {
45
+ startSpan(name: string, options?: {
46
+ attributes?: Attributes;
47
+ }): Span;
48
+ }
49
+ /** Truncate a string-valued attribute if it exceeds 8 KB. Returns the
50
+ * possibly-truncated value paired with a `_truncated` flag attribute name.
51
+ * Callers should write both: setAttribute(key, value) + setAttribute(key+'.truncated', true)
52
+ * when the result is truncated. */
53
+ export declare function truncateAttributeValue(value: string): {
54
+ value: string;
55
+ truncated: boolean;
56
+ };
57
+ /** Helper: set an attribute with auto-truncation when the value is a string. */
58
+ export declare function setAttributeSafe(span: Span, key: string, value: AttributeValue): void;
59
+ /** SpanStatusCode mirrors OTel: UNSET=0, OK=1, ERROR=2. */
60
+ export declare const SpanStatusCode: {
61
+ readonly UNSET: 0;
62
+ readonly OK: 1;
63
+ readonly ERROR: 2;
64
+ };
65
+ /**
66
+ * Coordinates the span hierarchy for one ClaudeCodeAgent instance.
67
+ * The agent owns a single SpanManager that opens session-scoped spans on
68
+ * start, prompt-scoped spans during each turn, and closes them on settle/fail.
69
+ */
70
+ export declare class SpanManager {
71
+ private readonly tracer;
72
+ private sessionSpan;
73
+ private promptSpan;
74
+ constructor(tracer: Tracer);
75
+ /** Open the root span for the agent's session. Returns the span so the
76
+ * caller can stash it (typically just rely on `this.sessionSpan`). */
77
+ startSession(attrs: {
78
+ sessionId: string;
79
+ cwd: string;
80
+ pid?: number;
81
+ }): Span;
82
+ /** Mark session ended. Idempotent. */
83
+ endSession(opts?: {
84
+ error?: Error;
85
+ code?: number;
86
+ }): void;
87
+ /** Open a child span for one prompt turn. */
88
+ startPrompt(attrs: {
89
+ turnId: string;
90
+ promptPreview: string;
91
+ }): Span;
92
+ /** Close the prompt span with finish reason + usage. */
93
+ endPrompt(opts: {
94
+ stopReason: string;
95
+ usage?: {
96
+ inputTokens?: number;
97
+ outputTokens?: number;
98
+ cacheReadTokens?: number;
99
+ cacheWriteTokens?: number;
100
+ estimatedCostUsd?: number;
101
+ };
102
+ error?: Error;
103
+ }): void;
104
+ }
105
+ //# sourceMappingURL=otel-adapter.d.ts.map