@sapiom/harness 0.0.0 → 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 (246) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +44 -43
  3. package/dist/cli/auth.d.ts +26 -0
  4. package/dist/cli/auth.d.ts.map +1 -0
  5. package/dist/cli/auth.js +39 -0
  6. package/dist/cli/auth.js.map +1 -0
  7. package/dist/cli/bin.d.ts +3 -0
  8. package/dist/cli/bin.d.ts.map +1 -0
  9. package/dist/cli/bin.js +163 -0
  10. package/dist/cli/bin.js.map +1 -0
  11. package/dist/cli/consent.d.ts +15 -0
  12. package/dist/cli/consent.d.ts.map +1 -0
  13. package/dist/cli/consent.js +81 -0
  14. package/dist/cli/consent.js.map +1 -0
  15. package/dist/cli/doctor.d.ts +27 -0
  16. package/dist/cli/doctor.d.ts.map +1 -0
  17. package/dist/cli/doctor.js +74 -0
  18. package/dist/cli/doctor.js.map +1 -0
  19. package/dist/cli/machine-id.d.ts +19 -0
  20. package/dist/cli/machine-id.d.ts.map +1 -0
  21. package/dist/cli/machine-id.js +75 -0
  22. package/dist/cli/machine-id.js.map +1 -0
  23. package/dist/cli/paths.d.ts +3 -0
  24. package/dist/cli/paths.d.ts.map +1 -0
  25. package/dist/cli/paths.js +11 -0
  26. package/dist/cli/paths.js.map +1 -0
  27. package/dist/cli/settings.d.ts +26 -0
  28. package/dist/cli/settings.d.ts.map +1 -0
  29. package/dist/cli/settings.js +123 -0
  30. package/dist/cli/settings.js.map +1 -0
  31. package/dist/core/adapters/claude-code.d.ts +41 -0
  32. package/dist/core/adapters/claude-code.d.ts.map +1 -0
  33. package/dist/core/adapters/claude-code.js +238 -0
  34. package/dist/core/adapters/claude-code.js.map +1 -0
  35. package/dist/core/adapters/codex.d.ts +59 -0
  36. package/dist/core/adapters/codex.d.ts.map +1 -0
  37. package/dist/core/adapters/codex.js +319 -0
  38. package/dist/core/adapters/codex.js.map +1 -0
  39. package/dist/core/canvas-body.d.ts +45 -0
  40. package/dist/core/canvas-body.d.ts.map +1 -0
  41. package/dist/core/canvas-body.js +94 -0
  42. package/dist/core/canvas-body.js.map +1 -0
  43. package/dist/core/canvas-cache.d.ts +23 -0
  44. package/dist/core/canvas-cache.d.ts.map +1 -0
  45. package/dist/core/canvas-cache.js +64 -0
  46. package/dist/core/canvas-cache.js.map +1 -0
  47. package/dist/core/canvas-enrich.d.ts +106 -0
  48. package/dist/core/canvas-enrich.d.ts.map +1 -0
  49. package/dist/core/canvas-enrich.js +178 -0
  50. package/dist/core/canvas-enrich.js.map +1 -0
  51. package/dist/core/canvas-enrichment.d.ts +144 -0
  52. package/dist/core/canvas-enrichment.d.ts.map +1 -0
  53. package/dist/core/canvas-enrichment.js +192 -0
  54. package/dist/core/canvas-enrichment.js.map +1 -0
  55. package/dist/core/canvas-graph.d.ts +71 -0
  56. package/dist/core/canvas-graph.d.ts.map +1 -0
  57. package/dist/core/canvas-graph.js +111 -0
  58. package/dist/core/canvas-graph.js.map +1 -0
  59. package/dist/core/canvas-index-classify.d.ts +41 -0
  60. package/dist/core/canvas-index-classify.d.ts.map +1 -0
  61. package/dist/core/canvas-index-classify.js +63 -0
  62. package/dist/core/canvas-index-classify.js.map +1 -0
  63. package/dist/core/canvas-interconnections.d.ts +24 -0
  64. package/dist/core/canvas-interconnections.d.ts.map +1 -0
  65. package/dist/core/canvas-interconnections.js +96 -0
  66. package/dist/core/canvas-interconnections.js.map +1 -0
  67. package/dist/core/canvas-manifest-check.d.ts +15 -0
  68. package/dist/core/canvas-manifest-check.d.ts.map +1 -0
  69. package/dist/core/canvas-manifest-check.js +90 -0
  70. package/dist/core/canvas-manifest-check.js.map +1 -0
  71. package/dist/core/canvas-render.d.ts +73 -0
  72. package/dist/core/canvas-render.d.ts.map +1 -0
  73. package/dist/core/canvas-render.js +145 -0
  74. package/dist/core/canvas-render.js.map +1 -0
  75. package/dist/core/canvas-svg.d.ts +50 -0
  76. package/dist/core/canvas-svg.d.ts.map +1 -0
  77. package/dist/core/canvas-svg.js +240 -0
  78. package/dist/core/canvas-svg.js.map +1 -0
  79. package/dist/core/canvas-template.d.ts +43 -0
  80. package/dist/core/canvas-template.d.ts.map +1 -0
  81. package/dist/core/canvas-template.js +365 -0
  82. package/dist/core/canvas-template.js.map +1 -0
  83. package/dist/core/canvas-watcher.d.ts +25 -0
  84. package/dist/core/canvas-watcher.d.ts.map +1 -0
  85. package/dist/core/canvas-watcher.js +144 -0
  86. package/dist/core/canvas-watcher.js.map +1 -0
  87. package/dist/core/collector/batcher.d.ts +57 -0
  88. package/dist/core/collector/batcher.d.ts.map +1 -0
  89. package/dist/core/collector/batcher.js +143 -0
  90. package/dist/core/collector/batcher.js.map +1 -0
  91. package/dist/core/collector/codex-tailer.d.ts +85 -0
  92. package/dist/core/collector/codex-tailer.d.ts.map +1 -0
  93. package/dist/core/collector/codex-tailer.js +270 -0
  94. package/dist/core/collector/codex-tailer.js.map +1 -0
  95. package/dist/core/collector/normalizer.d.ts +33 -0
  96. package/dist/core/collector/normalizer.d.ts.map +1 -0
  97. package/dist/core/collector/normalizer.js +94 -0
  98. package/dist/core/collector/normalizer.js.map +1 -0
  99. package/dist/core/collector/seq.d.ts +14 -0
  100. package/dist/core/collector/seq.d.ts.map +1 -0
  101. package/dist/core/collector/seq.js +20 -0
  102. package/dist/core/collector/seq.js.map +1 -0
  103. package/dist/core/collector/store.d.ts +16 -0
  104. package/dist/core/collector/store.d.ts.map +1 -0
  105. package/dist/core/collector/store.js +30 -0
  106. package/dist/core/collector/store.js.map +1 -0
  107. package/dist/core/collector/transcript.d.ts +38 -0
  108. package/dist/core/collector/transcript.d.ts.map +1 -0
  109. package/dist/core/collector/transcript.js +110 -0
  110. package/dist/core/collector/transcript.js.map +1 -0
  111. package/dist/core/event-bus.d.ts +9 -0
  112. package/dist/core/event-bus.d.ts.map +1 -0
  113. package/dist/core/event-bus.js +25 -0
  114. package/dist/core/event-bus.js.map +1 -0
  115. package/dist/core/example-seed.d.ts +34 -0
  116. package/dist/core/example-seed.d.ts.map +1 -0
  117. package/dist/core/example-seed.js +277 -0
  118. package/dist/core/example-seed.js.map +1 -0
  119. package/dist/core/inject/claude-settings.d.ts +34 -0
  120. package/dist/core/inject/claude-settings.d.ts.map +1 -0
  121. package/dist/core/inject/claude-settings.js +152 -0
  122. package/dist/core/inject/claude-settings.js.map +1 -0
  123. package/dist/core/inject/mcp-config.d.ts +26 -0
  124. package/dist/core/inject/mcp-config.d.ts.map +1 -0
  125. package/dist/core/inject/mcp-config.js +39 -0
  126. package/dist/core/inject/mcp-config.js.map +1 -0
  127. package/dist/core/inject/retention.d.ts +66 -0
  128. package/dist/core/inject/retention.d.ts.map +1 -0
  129. package/dist/core/inject/retention.js +119 -0
  130. package/dist/core/inject/retention.js.map +1 -0
  131. package/dist/core/inject/system-prompt.d.ts +11 -0
  132. package/dist/core/inject/system-prompt.d.ts.map +1 -0
  133. package/dist/core/inject/system-prompt.js +22 -0
  134. package/dist/core/inject/system-prompt.js.map +1 -0
  135. package/dist/core/macro-runner.d.ts +36 -0
  136. package/dist/core/macro-runner.d.ts.map +1 -0
  137. package/dist/core/macro-runner.js +66 -0
  138. package/dist/core/macro-runner.js.map +1 -0
  139. package/dist/core/macros.d.ts +10 -0
  140. package/dist/core/macros.d.ts.map +1 -0
  141. package/dist/core/macros.js +59 -0
  142. package/dist/core/macros.js.map +1 -0
  143. package/dist/core/path-safety.d.ts +33 -0
  144. package/dist/core/path-safety.d.ts.map +1 -0
  145. package/dist/core/path-safety.js +63 -0
  146. package/dist/core/path-safety.js.map +1 -0
  147. package/dist/core/paths.d.ts +27 -0
  148. package/dist/core/paths.d.ts.map +1 -0
  149. package/dist/core/paths.js +43 -0
  150. package/dist/core/paths.js.map +1 -0
  151. package/dist/core/port-detector.d.ts +69 -0
  152. package/dist/core/port-detector.d.ts.map +1 -0
  153. package/dist/core/port-detector.js +121 -0
  154. package/dist/core/port-detector.js.map +1 -0
  155. package/dist/core/session-manager.d.ts +275 -0
  156. package/dist/core/session-manager.d.ts.map +1 -0
  157. package/dist/core/session-manager.js +729 -0
  158. package/dist/core/session-manager.js.map +1 -0
  159. package/dist/core/task-manager.d.ts +138 -0
  160. package/dist/core/task-manager.d.ts.map +1 -0
  161. package/dist/core/task-manager.js +286 -0
  162. package/dist/core/task-manager.js.map +1 -0
  163. package/dist/core/task-stream.d.ts +32 -0
  164. package/dist/core/task-stream.d.ts.map +1 -0
  165. package/dist/core/task-stream.js +87 -0
  166. package/dist/core/task-stream.js.map +1 -0
  167. package/dist/core/workflow-registry.d.ts +36 -0
  168. package/dist/core/workflow-registry.d.ts.map +1 -0
  169. package/dist/core/workflow-registry.js +220 -0
  170. package/dist/core/workflow-registry.js.map +1 -0
  171. package/dist/core/workspace-context.d.ts +43 -0
  172. package/dist/core/workspace-context.d.ts.map +1 -0
  173. package/dist/core/workspace-context.js +108 -0
  174. package/dist/core/workspace-context.js.map +1 -0
  175. package/dist/core/workspace-watcher.d.ts +26 -0
  176. package/dist/core/workspace-watcher.d.ts.map +1 -0
  177. package/dist/core/workspace-watcher.js +174 -0
  178. package/dist/core/workspace-watcher.js.map +1 -0
  179. package/dist/index.d.ts +6 -0
  180. package/dist/index.d.ts.map +1 -0
  181. package/dist/index.js +6 -0
  182. package/dist/index.js.map +1 -0
  183. package/dist/profiles/canvas-guidelines.d.ts +20 -0
  184. package/dist/profiles/canvas-guidelines.d.ts.map +1 -0
  185. package/dist/profiles/canvas-guidelines.js +48 -0
  186. package/dist/profiles/canvas-guidelines.js.map +1 -0
  187. package/dist/profiles/default.d.ts +10 -0
  188. package/dist/profiles/default.d.ts.map +1 -0
  189. package/dist/profiles/default.js +70 -0
  190. package/dist/profiles/default.js.map +1 -0
  191. package/dist/server/auth.d.ts +9 -0
  192. package/dist/server/auth.d.ts.map +1 -0
  193. package/dist/server/auth.js +28 -0
  194. package/dist/server/auth.js.map +1 -0
  195. package/dist/server/canvas-render.d.ts +18 -0
  196. package/dist/server/canvas-render.d.ts.map +1 -0
  197. package/dist/server/canvas-render.js +24 -0
  198. package/dist/server/canvas-render.js.map +1 -0
  199. package/dist/server/canvas.d.ts +12 -0
  200. package/dist/server/canvas.d.ts.map +1 -0
  201. package/dist/server/canvas.js +174 -0
  202. package/dist/server/canvas.js.map +1 -0
  203. package/dist/server/events-ws.d.ts +11 -0
  204. package/dist/server/events-ws.d.ts.map +1 -0
  205. package/dist/server/events-ws.js +24 -0
  206. package/dist/server/events-ws.js.map +1 -0
  207. package/dist/server/fs.d.ts +4 -0
  208. package/dist/server/fs.d.ts.map +1 -0
  209. package/dist/server/fs.js +88 -0
  210. package/dist/server/fs.js.map +1 -0
  211. package/dist/server/index.d.ts +83 -0
  212. package/dist/server/index.d.ts.map +1 -0
  213. package/dist/server/index.js +0 -0
  214. package/dist/server/index.js.map +1 -0
  215. package/dist/server/ingest.d.ts +74 -0
  216. package/dist/server/ingest.d.ts.map +1 -0
  217. package/dist/server/ingest.js +74 -0
  218. package/dist/server/ingest.js.map +1 -0
  219. package/dist/server/macros.d.ts +32 -0
  220. package/dist/server/macros.d.ts.map +1 -0
  221. package/dist/server/macros.js +76 -0
  222. package/dist/server/macros.js.map +1 -0
  223. package/dist/server/rest.d.ts +75 -0
  224. package/dist/server/rest.d.ts.map +1 -0
  225. package/dist/server/rest.js +235 -0
  226. package/dist/server/rest.js.map +1 -0
  227. package/dist/server/static.d.ts +8 -0
  228. package/dist/server/static.d.ts.map +1 -0
  229. package/dist/server/static.js +36 -0
  230. package/dist/server/static.js.map +1 -0
  231. package/dist/server/terminal-ws.d.ts +9 -0
  232. package/dist/server/terminal-ws.d.ts.map +1 -0
  233. package/dist/server/terminal-ws.js +59 -0
  234. package/dist/server/terminal-ws.js.map +1 -0
  235. package/dist/server/ws-router.d.ts +16 -0
  236. package/dist/server/ws-router.d.ts.map +1 -0
  237. package/dist/server/ws-router.js +26 -0
  238. package/dist/server/ws-router.js.map +1 -0
  239. package/dist/shared/types.d.ts +485 -0
  240. package/dist/shared/types.d.ts.map +1 -0
  241. package/dist/shared/types.js +80 -0
  242. package/dist/shared/types.js.map +1 -0
  243. package/dist/web/assets/index-B9R4TzH7.css +32 -0
  244. package/dist/web/assets/index-Dh97QAgq.js +192 -0
  245. package/dist/web/index.html +27 -0
  246. package/package.json +84 -7
@@ -0,0 +1,143 @@
1
+ /**
2
+ * In-memory queue -> batched POST to the remote collector, at
3
+ * `${SAPIOM_COLLECTOR_URL}/v1/harness/events`.
4
+ *
5
+ * Gated by telemetry opt-in: when off (or when no collector URL is
6
+ * configured at all), `enqueue` is a no-op — events still land in the local
7
+ * ndjson store via a separate path, this is only the remote fan-out.
8
+ *
9
+ * Response contract: any 2xx = accepted, drop the batch from the queue.
10
+ * 4xx = permanent failure, drop without retry (a malformed batch will never
11
+ * succeed by resending it). 5xx or a network error = transient, retry with
12
+ * exponential backoff, then drop.
13
+ */
14
+ import * as crypto from "node:crypto";
15
+ import { ANALYTICS_SCHEMA_VERSION, ENV, } from "../../shared/types.js";
16
+ const EVENTS_PATH = "/v1/harness/events";
17
+ const DEFAULT_FLUSH_INTERVAL_MS = 5000;
18
+ const DEFAULT_MAX_BATCH_SIZE = 50;
19
+ const RETRY_BACKOFF_MS = [500, 1000, 2000];
20
+ /** Hard ceiling so a long collector outage can't grow memory unbounded. */
21
+ const MAX_QUEUE_SIZE = 1000;
22
+ function sleep(ms) {
23
+ return new Promise((resolve) => setTimeout(resolve, ms));
24
+ }
25
+ export class CollectorBatcher {
26
+ constructor(options) {
27
+ this.queue = [];
28
+ this.flushing = false;
29
+ this.warnedNoCollectorUrl = false;
30
+ this.machineId = options.machineId;
31
+ this.context = options.context;
32
+ this.telemetryOptIn = options.telemetryOptIn;
33
+ this.apiKey = options.apiKey ?? null;
34
+ const collectorUrl = options.collectorUrl ?? process.env[ENV.collectorUrl];
35
+ this.eventsUrl = collectorUrl ? `${collectorUrl.replace(/\/$/, "")}${EVENTS_PATH}` : undefined;
36
+ this.maxBatchSize = options.maxBatchSize ?? DEFAULT_MAX_BATCH_SIZE;
37
+ this.fetchImpl = options.fetchImpl ?? fetch;
38
+ this.onDebug = options.onDebug ?? (() => { });
39
+ const flushIntervalMs = options.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS;
40
+ if (this.eventsUrl) {
41
+ this.timer = setInterval(() => {
42
+ void this.flush();
43
+ }, flushIntervalMs);
44
+ this.timer.unref?.();
45
+ }
46
+ else {
47
+ this.timer = null;
48
+ }
49
+ }
50
+ setTelemetryOptIn(optIn) {
51
+ this.telemetryOptIn = optIn;
52
+ if (!optIn)
53
+ this.queue = [];
54
+ }
55
+ /** Update the auth key used on subsequent flushes (e.g. after login/logout). */
56
+ setApiKey(apiKey) {
57
+ this.apiKey = apiKey;
58
+ }
59
+ /** Queue an event for the next batch. No-op unless opted in and configured. */
60
+ enqueue(event) {
61
+ if (!this.telemetryOptIn)
62
+ return;
63
+ if (!this.eventsUrl) {
64
+ if (!this.warnedNoCollectorUrl) {
65
+ this.warnedNoCollectorUrl = true;
66
+ this.onDebug(`${ENV.collectorUrl} not set — remote analytics batching disabled`);
67
+ }
68
+ return;
69
+ }
70
+ this.queue.push(event);
71
+ if (this.queue.length > MAX_QUEUE_SIZE) {
72
+ const dropped = this.queue.length - MAX_QUEUE_SIZE;
73
+ this.queue = this.queue.slice(dropped);
74
+ this.onDebug(`dropped ${dropped} queued analytics events (queue over capacity)`);
75
+ }
76
+ if (this.queue.length >= this.maxBatchSize) {
77
+ void this.flush();
78
+ }
79
+ }
80
+ /** Send whatever is queued now, with retry/backoff, dropping on final failure. */
81
+ async flush() {
82
+ if (this.flushing || !this.eventsUrl || this.queue.length === 0)
83
+ return;
84
+ this.flushing = true;
85
+ try {
86
+ const events = this.queue.splice(0, this.maxBatchSize);
87
+ const batch = {
88
+ batchId: crypto.randomUUID(),
89
+ schemaVersion: ANALYTICS_SCHEMA_VERSION,
90
+ machineId: this.machineId,
91
+ sentAt: new Date().toISOString(),
92
+ context: this.context,
93
+ events,
94
+ };
95
+ await this.sendWithRetry(this.eventsUrl, batch);
96
+ }
97
+ finally {
98
+ this.flushing = false;
99
+ }
100
+ }
101
+ async sendWithRetry(url, batch) {
102
+ const body = JSON.stringify(batch);
103
+ const headers = { "content-type": "application/json" };
104
+ // Anonymous traffic is valid (identity then rests on machineId); the
105
+ // header is only sent once a Sapiom user is actually logged in.
106
+ if (this.apiKey)
107
+ headers.authorization = `Bearer ${this.apiKey}`;
108
+ for (let attempt = 0; attempt <= RETRY_BACKOFF_MS.length; attempt++) {
109
+ try {
110
+ const res = await this.fetchImpl(url, {
111
+ method: "POST",
112
+ headers,
113
+ body,
114
+ });
115
+ if (res.ok)
116
+ return; // any 2xx = accepted
117
+ if (res.status >= 400 && res.status < 500) {
118
+ // Permanent failure — resending an unprocessable batch never helps.
119
+ this.onDebug(`dropping batch of ${batch.events.length} events: collector rejected with ${res.status}`);
120
+ return;
121
+ }
122
+ throw new Error(`collector responded ${res.status}`);
123
+ }
124
+ catch (err) {
125
+ const isLastAttempt = attempt === RETRY_BACKOFF_MS.length;
126
+ if (isLastAttempt) {
127
+ this.onDebug(`dropping batch of ${batch.events.length} events after ${attempt + 1} attempts: ${String(err)}`);
128
+ return;
129
+ }
130
+ await sleep(RETRY_BACKOFF_MS[attempt]);
131
+ }
132
+ }
133
+ }
134
+ /** Flush everything queued, then stop the periodic timer. */
135
+ async close() {
136
+ if (this.timer)
137
+ clearInterval(this.timer);
138
+ while (this.queue.length > 0) {
139
+ await this.flush();
140
+ }
141
+ }
142
+ }
143
+ //# sourceMappingURL=batcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"batcher.js","sourceRoot":"","sources":["../../../src/core/collector/batcher.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Codex transcript tailer — Codex has no hook system, so this file *is* its
3
+ * entire analytics eventSource (see HarnessAdapter.eventSource ===
4
+ * "transcript-tail" on CodexAdapter). It incrementally reads a rollout JSONL
5
+ * file as Codex appends to it and translates each entry into the same
6
+ * `{hookEvent, payload}` shape Claude Code's hooks would have produced for
7
+ * equivalent activity — so the existing ingest pipeline
8
+ * (core/collector/normalizer.ts's `normalizeHookEvent`) can consume both
9
+ * harnesses unchanged, with zero Codex-specific branching in the normalizer.
10
+ *
11
+ * Self-contained by design: no imports from server/, session-manager.ts, or
12
+ * the rest of core/collector/ (only types, imported for shape-compatibility
13
+ * with the normalizer this feeds). Not wired into the server — see the
14
+ * "Wiring this in" section of the PR description for the integration
15
+ * contract (what to call, and when).
16
+ */
17
+ import type { ClaudeHookEvent, RawHookPayload } from "./normalizer.js";
18
+ export type CodexEventListener = (hookEvent: ClaudeHookEvent, payload: RawHookPayload) => void;
19
+ export interface CodexTailerOptions {
20
+ /** Path to the rollout JSONL file. It's fine if this doesn't exist yet —
21
+ * the tailer polls until Codex creates it (there's an unavoidable race
22
+ * between spawning `codex` and it creating its rollout file). */
23
+ rolloutPath: string;
24
+ onEvent: CodexEventListener;
25
+ onError?: (err: unknown) => void;
26
+ pollIntervalMs?: number;
27
+ /**
28
+ * Tail from byte 0 regardless of the file's current size — for a fresh
29
+ * launch, where the caller discovered this rollout file *because* it
30
+ * already contains the session_meta line the caller cares about (a
31
+ * find-then-tail race: by the time a poll-based discovery step returns a
32
+ * path, Codex has necessarily already written to it). Without this, the
33
+ * default "start from current size" baseline (correct for a genuine
34
+ * resume, where prior content really is history to skip) would silently
35
+ * swallow that already-written SessionStart. Defaults to false (resume
36
+ * semantics: skip whatever's already there, only emit new activity).
37
+ */
38
+ startFromBeginning?: boolean;
39
+ }
40
+ export interface CodexTailerHandle {
41
+ /** Stop polling without emitting anything further. */
42
+ stop(): void;
43
+ /**
44
+ * Synthesize a SessionEnd hook-shaped event and stop polling. Codex's
45
+ * rollout format has no "session ended" line of its own — call this when
46
+ * the harness detects the underlying pty has exited.
47
+ */
48
+ emitSessionEnd(reason?: string): void;
49
+ }
50
+ /**
51
+ * Incrementally tail a Codex rollout file, translating each new line into a
52
+ * Claude-hook-shaped `(hookEvent, payload)` pair via `onEvent`. Only emits
53
+ * for content appended *after* the tailer starts (matching hook semantics —
54
+ * hooks only fire for new activity, never backfill); use
55
+ * `CodexAdapter.listPastSessions` for history.
56
+ */
57
+ export declare function tailCodexRollout(options: CodexTailerOptions): CodexTailerHandle;
58
+ export interface FindRolloutFileOptions {
59
+ /** Match a rollout file whose session_meta.cwd equals this exactly. */
60
+ cwd: string;
61
+ /** Only consider sessions created at/after this time (ms epoch) —
62
+ * disambiguates a freshly-launched session from older ones sharing a cwd.
63
+ * Ignored when `agentSessionId` is given (an exact id match is a strictly
64
+ * stronger signal than a time-window heuristic). */
65
+ sinceMs?: number;
66
+ /**
67
+ * Match a rollout file by its exact session_meta.id (the rollout/agent
68
+ * session id) instead of by recency — needed when resuming a session,
69
+ * where `sinceMs` can't disambiguate it from other sessions sharing the
70
+ * same cwd that happen to have been touched more recently.
71
+ */
72
+ agentSessionId?: string;
73
+ /** Overridable for tests. */
74
+ homeDir?: string;
75
+ }
76
+ /**
77
+ * Finds a rollout file matching `cwd`. With `agentSessionId`, matches that
78
+ * exact session (for resume — cwd alone is ambiguous when multiple sessions
79
+ * share a directory). Otherwise returns the most-recently-modified match,
80
+ * optionally bounded by `sinceMs`. Intended to be polled by the integrator
81
+ * right after spawning a fresh `codex` process — there's no way to know the
82
+ * exact rollout path in advance (it's a timestamp+UUID Codex generates itself).
83
+ */
84
+ export declare function findRolloutFile(options: FindRolloutFileOptions): Promise<string | null>;
85
+ //# sourceMappingURL=codex-tailer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codex-tailer.d.ts","sourceRoot":"","sources":["../../../src/core/collector/codex-tailer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAMH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AASvE,MAAM,MAAM,kBAAkB,GAAG,CAAC,SAAS,EAAE,eAAe,EAAE,OAAO,EAAE,cAAc,KAAK,IAAI,CAAC;AAE/F,MAAM,WAAW,kBAAkB;IACjC;;qEAEiE;IACjE,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,kBAAkB,CAAC;IAC5B,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;IACjC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;;;;;;OAUG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED,MAAM,WAAW,iBAAiB;IAChC,sDAAsD;IACtD,IAAI,IAAI,IAAI,CAAC;IACb;;;;OAIG;IACH,cAAc,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACvC;AAYD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,kBAAkB,GAAG,iBAAiB,CA0I/E;AAMD,MAAM,WAAW,sBAAsB;IACrC,uEAAuE;IACvE,GAAG,EAAE,MAAM,CAAC;IACZ;;;wDAGoD;IACpD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,6BAA6B;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAgED;;;;;;;GAOG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAgC7F"}
@@ -0,0 +1,270 @@
1
+ /**
2
+ * Codex transcript tailer — Codex has no hook system, so this file *is* its
3
+ * entire analytics eventSource (see HarnessAdapter.eventSource ===
4
+ * "transcript-tail" on CodexAdapter). It incrementally reads a rollout JSONL
5
+ * file as Codex appends to it and translates each entry into the same
6
+ * `{hookEvent, payload}` shape Claude Code's hooks would have produced for
7
+ * equivalent activity — so the existing ingest pipeline
8
+ * (core/collector/normalizer.ts's `normalizeHookEvent`) can consume both
9
+ * harnesses unchanged, with zero Codex-specific branching in the normalizer.
10
+ *
11
+ * Self-contained by design: no imports from server/, session-manager.ts, or
12
+ * the rest of core/collector/ (only types, imported for shape-compatibility
13
+ * with the normalizer this feeds). Not wired into the server — see the
14
+ * "Wiring this in" section of the PR description for the integration
15
+ * contract (what to call, and when).
16
+ */
17
+ import { open, readdir, realpath, stat } from "node:fs/promises";
18
+ import { homedir } from "node:os";
19
+ import { join } from "node:path";
20
+ const DEFAULT_POLL_INTERVAL_MS = 300;
21
+ const MAX_SCAN_DEPTH = 4; // ~/.codex/sessions/YYYY/MM/DD/*.jsonl
22
+ /**
23
+ * Incrementally tail a Codex rollout file, translating each new line into a
24
+ * Claude-hook-shaped `(hookEvent, payload)` pair via `onEvent`. Only emits
25
+ * for content appended *after* the tailer starts (matching hook semantics —
26
+ * hooks only fire for new activity, never backfill); use
27
+ * `CodexAdapter.listPastSessions` for history.
28
+ */
29
+ export function tailCodexRollout(options) {
30
+ const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
31
+ let offset = 0;
32
+ let carry = "";
33
+ let sessionStartEmitted = false;
34
+ let stopped = false;
35
+ let polling = false;
36
+ const pendingCalls = new Map();
37
+ // Resolve the resume-vs-fresh baseline exactly once, immediately, before
38
+ // any poll reads content. `startFromBeginning` (see CodexTailerOptions)
39
+ // always wins when set. Otherwise: if the file already exists right now,
40
+ // its current size becomes the "don't backfill past this" offset (resume
41
+ // — only new activity should be emitted, matching hook semantics); if it
42
+ // doesn't exist yet, the offset stays 0, so once Codex creates the file
43
+ // the very first bytes it writes are read as new.
44
+ let baselineResolved = false;
45
+ if (options.startFromBeginning) {
46
+ baselineResolved = true;
47
+ }
48
+ else {
49
+ void stat(options.rolloutPath).then((s) => {
50
+ offset = s.size;
51
+ baselineResolved = true;
52
+ }, () => {
53
+ baselineResolved = true;
54
+ });
55
+ }
56
+ const timer = setInterval(() => {
57
+ void poll();
58
+ }, pollIntervalMs);
59
+ timer.unref?.();
60
+ async function poll() {
61
+ if (stopped || polling || !baselineResolved)
62
+ return;
63
+ polling = true;
64
+ try {
65
+ const fileStat = await stat(options.rolloutPath);
66
+ if (fileStat.size <= offset)
67
+ return;
68
+ const handle = await open(options.rolloutPath, "r");
69
+ try {
70
+ const length = fileStat.size - offset;
71
+ const buffer = Buffer.allocUnsafe(length);
72
+ await handle.read(buffer, 0, length, offset);
73
+ offset = fileStat.size;
74
+ const chunk = carry + buffer.toString("utf8");
75
+ const lines = chunk.split("\n");
76
+ carry = lines.pop() ?? "";
77
+ for (const line of lines)
78
+ processLine(line);
79
+ }
80
+ finally {
81
+ await handle.close();
82
+ }
83
+ }
84
+ catch (err) {
85
+ // ENOENT is the expected steady-state before Codex has created the
86
+ // file yet — anything else is a real read/parse failure worth surfacing.
87
+ if (err?.code !== "ENOENT") {
88
+ options.onError?.(err);
89
+ }
90
+ }
91
+ finally {
92
+ polling = false;
93
+ }
94
+ }
95
+ function processLine(line) {
96
+ const trimmed = line.trim();
97
+ if (!trimmed)
98
+ return;
99
+ let entry;
100
+ try {
101
+ entry = JSON.parse(trimmed);
102
+ }
103
+ catch (err) {
104
+ options.onError?.(err);
105
+ return;
106
+ }
107
+ translate(entry);
108
+ }
109
+ function translate(entry) {
110
+ const payload = entry.payload;
111
+ if (entry.type === "session_meta") {
112
+ if (sessionStartEmitted)
113
+ return;
114
+ sessionStartEmitted = true;
115
+ options.onEvent("SessionStart", {
116
+ source: "codex",
117
+ cwd: typeof payload?.cwd === "string" ? payload.cwd : null,
118
+ // Claude's normalizer resolves agentSessionId from a `session_id`
119
+ // field on the hook payload — matching that field name here is what
120
+ // lets normalizeHookEvent() pick up the rollout id unchanged.
121
+ session_id: typeof payload?.id === "string" ? payload.id : undefined,
122
+ });
123
+ return;
124
+ }
125
+ if (entry.type === "event_msg") {
126
+ if (payload?.type === "user_message" && typeof payload.message === "string") {
127
+ options.onEvent("UserPromptSubmit", { prompt: payload.message });
128
+ }
129
+ else if (payload?.type === "task_complete") {
130
+ options.onEvent("Stop", { stop_hook_active: false });
131
+ }
132
+ return;
133
+ }
134
+ if (entry.type === "response_item") {
135
+ if (payload?.type === "function_call" && typeof payload.call_id === "string") {
136
+ pendingCalls.set(payload.call_id, {
137
+ name: typeof payload.name === "string" ? payload.name : "unknown",
138
+ argumentsRaw: typeof payload.arguments === "string" ? payload.arguments : "",
139
+ });
140
+ }
141
+ else if (payload?.type === "function_call_output" && typeof payload.call_id === "string") {
142
+ const call = pendingCalls.get(payload.call_id);
143
+ pendingCalls.delete(payload.call_id);
144
+ options.onEvent("PostToolUse", {
145
+ tool_name: call?.name ?? "unknown",
146
+ tool_input: call?.argumentsRaw ?? "",
147
+ tool_response: payload.output ?? "",
148
+ });
149
+ }
150
+ return;
151
+ }
152
+ }
153
+ return {
154
+ stop() {
155
+ stopped = true;
156
+ clearInterval(timer);
157
+ },
158
+ emitSessionEnd(reason) {
159
+ if (stopped)
160
+ return;
161
+ options.onEvent("SessionEnd", { reason: reason ?? null });
162
+ stopped = true;
163
+ clearInterval(timer);
164
+ },
165
+ };
166
+ }
167
+ async function readSessionMetaHead(filePath, maxBytes = 65536) {
168
+ let content;
169
+ try {
170
+ const handle = await open(filePath, "r");
171
+ try {
172
+ const { size } = await handle.stat();
173
+ const length = Math.min(size, maxBytes);
174
+ const buffer = Buffer.allocUnsafe(length);
175
+ await handle.read(buffer, 0, length, 0);
176
+ content = buffer.toString("utf8");
177
+ }
178
+ finally {
179
+ await handle.close();
180
+ }
181
+ }
182
+ catch {
183
+ return null;
184
+ }
185
+ for (const line of content.split("\n").slice(0, 20)) {
186
+ const trimmed = line.trim();
187
+ if (!trimmed)
188
+ continue;
189
+ let parsed;
190
+ try {
191
+ parsed = JSON.parse(trimmed);
192
+ }
193
+ catch {
194
+ continue;
195
+ }
196
+ if (parsed.type !== "session_meta")
197
+ continue;
198
+ const cwd = typeof parsed.payload?.cwd === "string" ? parsed.payload.cwd : undefined;
199
+ if (!cwd)
200
+ return null;
201
+ const id = typeof parsed.payload?.id === "string" ? parsed.payload.id : null;
202
+ const timestamp = typeof parsed.payload?.timestamp === "string" ? Date.parse(parsed.payload.timestamp) : NaN;
203
+ return { id, cwd, timestampMs: Number.isNaN(timestamp) ? null : timestamp };
204
+ }
205
+ return null;
206
+ }
207
+ async function collectRolloutFiles(dir, depth = 0) {
208
+ if (depth > MAX_SCAN_DEPTH)
209
+ return [];
210
+ let entries;
211
+ try {
212
+ entries = await readdir(dir, { withFileTypes: true });
213
+ }
214
+ catch {
215
+ return [];
216
+ }
217
+ const results = [];
218
+ for (const entry of entries) {
219
+ const fullPath = join(dir, entry.name);
220
+ if (entry.isDirectory()) {
221
+ results.push(...(await collectRolloutFiles(fullPath, depth + 1)));
222
+ }
223
+ else if (entry.name.endsWith(".jsonl")) {
224
+ results.push(fullPath);
225
+ }
226
+ }
227
+ return results;
228
+ }
229
+ /**
230
+ * Finds a rollout file matching `cwd`. With `agentSessionId`, matches that
231
+ * exact session (for resume — cwd alone is ambiguous when multiple sessions
232
+ * share a directory). Otherwise returns the most-recently-modified match,
233
+ * optionally bounded by `sinceMs`. Intended to be polled by the integrator
234
+ * right after spawning a fresh `codex` process — there's no way to know the
235
+ * exact rollout path in advance (it's a timestamp+UUID Codex generates itself).
236
+ */
237
+ export async function findRolloutFile(options) {
238
+ const homeDir = options.homeDir ?? homedir();
239
+ const root = join(homeDir, ".codex", "sessions");
240
+ const files = await collectRolloutFiles(root);
241
+ // Codex records session_meta.cwd as the OS-canonicalized path (symlinks
242
+ // resolved) — on macOS in particular, `/tmp` and `/var` are themselves
243
+ // symlinks into `/private`, so a cwd like `/var/folders/.../T/foo` (what
244
+ // `os.tmpdir()` and most callers hand us) never string-equals what Codex
245
+ // wrote (`/private/var/folders/.../T/foo`), even though it's the same
246
+ // directory. Resolve our side the same way before comparing. Falls back
247
+ // to the raw value if the directory is gone (a session that already
248
+ // exited, or a test double that never touches the real filesystem).
249
+ const resolvedCwd = await realpath(options.cwd).catch(() => options.cwd);
250
+ let best = null;
251
+ for (const filePath of files) {
252
+ const meta = await readSessionMetaHead(filePath);
253
+ if (!meta || meta.cwd !== resolvedCwd)
254
+ continue;
255
+ if (options.agentSessionId !== undefined) {
256
+ if (meta.id !== options.agentSessionId)
257
+ continue;
258
+ return filePath;
259
+ }
260
+ if (options.sinceMs !== undefined && meta.timestampMs !== null && meta.timestampMs < options.sinceMs)
261
+ continue;
262
+ const fileStat = await stat(filePath).catch(() => null);
263
+ if (!fileStat)
264
+ continue;
265
+ if (!best || fileStat.mtimeMs > best.mtimeMs)
266
+ best = { path: filePath, mtimeMs: fileStat.mtimeMs };
267
+ }
268
+ return best?.path ?? null;
269
+ }
270
+ //# sourceMappingURL=codex-tailer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codex-tailer.js","sourceRoot":"","sources":["../../../src/core/collector/codex-tailer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Raw Claude Code hook payload -> normalized AnalyticsEvent.
3
+ *
4
+ * `userId` / `machineId` / `harness` are never present on the hook payload
5
+ * itself (Claude Code has no idea it's running inside the harness) — they're
6
+ * injected from server-side context resolved by the caller (the session
7
+ * registry, auth state, machine-id file).
8
+ */
9
+ import type { AnalyticsEvent, HarnessKind } from "../../shared/types.js";
10
+ /** The six hook names Claude Code invokes; see claude-settings.ts. */
11
+ export type ClaudeHookEvent = "SessionStart" | "UserPromptSubmit" | "PreToolUse" | "PostToolUse" | "Stop" | "SessionEnd";
12
+ /** Loose on purpose — hook payloads vary by event and Claude Code version. */
13
+ export type RawHookPayload = Record<string, unknown>;
14
+ export interface NormalizeContext {
15
+ userId: string | null;
16
+ tenantId: string | null;
17
+ machineId: string;
18
+ harnessSessionId: string;
19
+ harness: HarnessKind;
20
+ /** Already-known agent session id, if the registry has resolved one yet. */
21
+ agentSessionId: string | null;
22
+ /** Per-harnessSessionId monotonic counter, assigned by the caller (see core/collector/seq.ts). */
23
+ seq: number;
24
+ }
25
+ /** Stringify + truncate a value so a giant tool payload can't blow up storage. */
26
+ export declare function truncateForPayload(value: unknown, maxLength?: number): string;
27
+ /**
28
+ * Normalize one Claude Code hook invocation into an AnalyticsEvent, or
29
+ * `null` when the hook doesn't map to an analytics event (PreToolUse, or an
30
+ * unrecognized hook name).
31
+ */
32
+ export declare function normalizeHookEvent(hookEvent: string, hookPayload: RawHookPayload, context: NormalizeContext): AnalyticsEvent | null;
33
+ //# sourceMappingURL=normalizer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalizer.d.ts","sourceRoot":"","sources":["../../../src/core/collector/normalizer.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAsB,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAE7F,sEAAsE;AACtE,MAAM,MAAM,eAAe,GACvB,cAAc,GACd,kBAAkB,GAClB,YAAY,GACZ,aAAa,GACb,MAAM,GACN,YAAY,CAAC;AAEjB,8EAA8E;AAC9E,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAErD,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,MAAM,CAAC;IACzB,OAAO,EAAE,WAAW,CAAC;IACrB,4EAA4E;IAC5E,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,kGAAkG;IAClG,GAAG,EAAE,MAAM,CAAC;CACb;AAiBD,kFAAkF;AAClF,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,EAAE,SAAS,SAAmB,GAAG,MAAM,CAIvF;AA+CD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,cAAc,EAC3B,OAAO,EAAE,gBAAgB,GACxB,cAAc,GAAG,IAAI,CAmBvB"}
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Raw Claude Code hook payload -> normalized AnalyticsEvent.
3
+ *
4
+ * `userId` / `machineId` / `harness` are never present on the hook payload
5
+ * itself (Claude Code has no idea it's running inside the harness) — they're
6
+ * injected from server-side context resolved by the caller (the session
7
+ * registry, auth state, machine-id file).
8
+ */
9
+ import * as crypto from "node:crypto";
10
+ const HOOK_TO_TYPE = {
11
+ SessionStart: "session.start",
12
+ UserPromptSubmit: "prompt.submitted",
13
+ PostToolUse: "tool.call",
14
+ Stop: "turn.completed",
15
+ SessionEnd: "session.end",
16
+ // PreToolUse has no dedicated analytics event today; it's still registered
17
+ // as a hook (see claude-settings.ts) so a future correctness check can
18
+ // compare pre/post firing without a settings-file change.
19
+ };
20
+ const MAX_FIELD_LENGTH = 4000;
21
+ /** Tool output can be much larger than other fields; cap it separately. */
22
+ const MAX_TOOL_RESPONSE_LENGTH = 16 * 1024;
23
+ /** Stringify + truncate a value so a giant tool payload can't blow up storage. */
24
+ export function truncateForPayload(value, maxLength = MAX_FIELD_LENGTH) {
25
+ const str = typeof value === "string" ? value : JSON.stringify(value ?? null);
26
+ if (str.length <= maxLength)
27
+ return str;
28
+ return `${str.slice(0, maxLength)}…[truncated ${str.length - maxLength} chars]`;
29
+ }
30
+ function stringField(payload, key) {
31
+ const value = payload[key];
32
+ return typeof value === "string" ? value : undefined;
33
+ }
34
+ function buildEventPayload(hookEvent, hookPayload) {
35
+ switch (hookEvent) {
36
+ case "SessionStart":
37
+ return {
38
+ source: stringField(hookPayload, "source") ?? null,
39
+ cwd: stringField(hookPayload, "cwd") ?? null,
40
+ };
41
+ case "UserPromptSubmit":
42
+ return {
43
+ prompt: stringField(hookPayload, "prompt") ?? "",
44
+ };
45
+ case "PostToolUse":
46
+ return {
47
+ toolName: stringField(hookPayload, "tool_name") ?? null,
48
+ toolInput: truncateForPayload(hookPayload.tool_input),
49
+ toolResponseSummary: truncateForPayload(hookPayload.tool_response, MAX_TOOL_RESPONSE_LENGTH),
50
+ };
51
+ case "Stop":
52
+ // Claude Code's own Stop payload already carries the final assistant
53
+ // message — no need to wait on the transcript file for this field (it
54
+ // may not exist on disk yet, or at all, when Stop fires; see
55
+ // enrichTurnCompleted for the model/usage backfill that DOES need it).
56
+ return {
57
+ stopHookActive: hookPayload.stop_hook_active === true,
58
+ assistantText: stringField(hookPayload, "last_assistant_message") ?? null,
59
+ };
60
+ case "SessionEnd":
61
+ return {
62
+ reason: stringField(hookPayload, "reason") ?? null,
63
+ };
64
+ case "PreToolUse":
65
+ return {};
66
+ default:
67
+ return {};
68
+ }
69
+ }
70
+ /**
71
+ * Normalize one Claude Code hook invocation into an AnalyticsEvent, or
72
+ * `null` when the hook doesn't map to an analytics event (PreToolUse, or an
73
+ * unrecognized hook name).
74
+ */
75
+ export function normalizeHookEvent(hookEvent, hookPayload, context) {
76
+ const type = HOOK_TO_TYPE[hookEvent];
77
+ if (!type)
78
+ return null;
79
+ const agentSessionId = stringField(hookPayload, "session_id") ?? context.agentSessionId;
80
+ return {
81
+ eventId: crypto.randomUUID(),
82
+ seq: context.seq,
83
+ ts: new Date().toISOString(),
84
+ userId: context.userId,
85
+ tenantId: context.tenantId,
86
+ machineId: context.machineId,
87
+ harnessSessionId: context.harnessSessionId,
88
+ agentSessionId,
89
+ harness: context.harness,
90
+ type,
91
+ payload: buildEventPayload(hookEvent, hookPayload),
92
+ };
93
+ }
94
+ //# sourceMappingURL=normalizer.js.map