@manyoars/oars 0.2.0 → 0.3.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 (241) hide show
  1. package/README.md +132 -28
  2. package/dist/api.js +1 -1
  3. package/dist/bin.js +6 -29
  4. package/dist/cli.d.ts +6 -3
  5. package/dist/cli.js +367 -88
  6. package/dist/commands/keys.d.ts +31 -0
  7. package/dist/commands/keys.js +65 -0
  8. package/dist/commands/login.js +2 -2
  9. package/dist/errors.d.ts +13 -13
  10. package/dist/errors.js +28 -11
  11. package/dist/gate/checks.d.ts +68 -0
  12. package/dist/gate/checks.js +584 -0
  13. package/dist/gate/command.d.ts +14 -0
  14. package/dist/gate/command.js +61 -0
  15. package/dist/gate/hook-fill-main.d.ts +1 -0
  16. package/dist/gate/hook-fill-main.js +16 -0
  17. package/dist/gate/hook-fill.d.ts +38 -0
  18. package/dist/gate/hook-fill.js +94 -0
  19. package/dist/gate/hook-main.d.ts +1 -0
  20. package/dist/gate/hook-main.js +35 -0
  21. package/dist/gate/hook-process.d.ts +14 -0
  22. package/dist/gate/hook-process.js +64 -0
  23. package/dist/gate/hook.d.ts +46 -0
  24. package/dist/gate/hook.js +256 -0
  25. package/dist/gate/install.d.ts +25 -0
  26. package/dist/gate/install.js +126 -0
  27. package/dist/gate/local-bin.d.ts +8 -0
  28. package/dist/gate/local-bin.js +36 -0
  29. package/dist/gate/notes.d.ts +19 -0
  30. package/dist/gate/notes.js +27 -0
  31. package/dist/gate/novelty.d.ts +35 -0
  32. package/dist/gate/novelty.js +99 -0
  33. package/dist/gate/policy.d.ts +25 -0
  34. package/dist/gate/policy.js +73 -0
  35. package/dist/gate/record.d.ts +120 -0
  36. package/dist/gate/record.js +125 -0
  37. package/dist/gate/registry-state.d.ts +10 -0
  38. package/dist/gate/registry-state.js +30 -0
  39. package/dist/gate/registry.d.ts +56 -0
  40. package/dist/gate/registry.js +94 -0
  41. package/dist/gate/replay.d.ts +20 -0
  42. package/dist/gate/replay.js +257 -0
  43. package/dist/gate/report.d.ts +50 -0
  44. package/dist/gate/report.js +89 -0
  45. package/dist/gate/scope.d.ts +7 -0
  46. package/dist/gate/scope.js +27 -0
  47. package/dist/gate/transcripts.d.ts +68 -0
  48. package/dist/gate/transcripts.js +232 -0
  49. package/dist/hook-bin.d.ts +1 -0
  50. package/dist/hook-bin.js +25 -0
  51. package/dist/issuer.d.ts +7 -1
  52. package/dist/issuer.js +18 -1
  53. package/dist/main.d.ts +1 -0
  54. package/dist/main.js +64 -0
  55. package/dist/oidc.js +1 -1
  56. package/dist/sessions/actions.d.ts +35 -0
  57. package/dist/sessions/actions.js +351 -0
  58. package/dist/sessions/allow.d.ts +14 -0
  59. package/dist/sessions/allow.js +51 -0
  60. package/dist/sessions/archive.d.ts +11 -0
  61. package/dist/sessions/archive.js +38 -0
  62. package/dist/sessions/audit.d.ts +42 -0
  63. package/dist/sessions/audit.js +104 -0
  64. package/dist/sessions/baseline.d.ts +62 -0
  65. package/dist/sessions/baseline.js +188 -0
  66. package/dist/sessions/capture-runtime.d.ts +10 -0
  67. package/dist/sessions/capture-runtime.js +12 -0
  68. package/dist/sessions/cell-increments.d.ts +43 -0
  69. package/dist/sessions/cell-increments.js +209 -0
  70. package/dist/sessions/cells.d.ts +79 -0
  71. package/dist/sessions/cells.js +181 -0
  72. package/dist/sessions/codex.d.ts +53 -0
  73. package/dist/sessions/codex.js +679 -0
  74. package/dist/sessions/command.d.ts +117 -0
  75. package/dist/sessions/command.js +373 -0
  76. package/dist/sessions/copilot.d.ts +40 -0
  77. package/dist/sessions/copilot.js +432 -0
  78. package/dist/sessions/cursor.d.ts +24 -0
  79. package/dist/sessions/cursor.js +324 -0
  80. package/dist/sessions/derive.d.ts +57 -0
  81. package/dist/sessions/derive.js +320 -0
  82. package/dist/sessions/exclusion.d.ts +81 -0
  83. package/dist/sessions/exclusion.js +353 -0
  84. package/dist/sessions/extract.d.ts +179 -0
  85. package/dist/sessions/extract.js +1774 -0
  86. package/dist/sessions/fold.d.ts +17 -0
  87. package/dist/sessions/fold.js +71 -0
  88. package/dist/sessions/frame.d.ts +24 -0
  89. package/dist/sessions/frame.js +95 -0
  90. package/dist/sessions/gemini.d.ts +37 -0
  91. package/dist/sessions/gemini.js +613 -0
  92. package/dist/sessions/gold-arms.d.ts +67 -0
  93. package/dist/sessions/gold-arms.js +172 -0
  94. package/dist/sessions/gold.d.ts +197 -0
  95. package/dist/sessions/gold.js +463 -0
  96. package/dist/sessions/graph.d.ts +12 -0
  97. package/dist/sessions/graph.js +34 -0
  98. package/dist/sessions/grok.d.ts +36 -0
  99. package/dist/sessions/grok.js +421 -0
  100. package/dist/sessions/hook-calls.d.ts +3 -0
  101. package/dist/sessions/hook-calls.js +47 -0
  102. package/dist/sessions/hook-index.d.ts +53 -0
  103. package/dist/sessions/hook-index.js +157 -0
  104. package/dist/sessions/hooks.d.ts +13 -0
  105. package/dist/sessions/hooks.js +170 -0
  106. package/dist/sessions/identity.d.ts +22 -0
  107. package/dist/sessions/identity.js +101 -0
  108. package/dist/sessions/install.d.ts +54 -0
  109. package/dist/sessions/install.js +201 -0
  110. package/dist/sessions/lineage.d.ts +19 -0
  111. package/dist/sessions/lineage.js +118 -0
  112. package/dist/sessions/mcp.d.ts +6 -0
  113. package/dist/sessions/mcp.js +153 -0
  114. package/dist/sessions/merkle.d.ts +7 -0
  115. package/dist/sessions/merkle.js +28 -0
  116. package/dist/sessions/motif.d.ts +43 -0
  117. package/dist/sessions/motif.js +59 -0
  118. package/dist/sessions/package-resolve.d.ts +30 -0
  119. package/dist/sessions/package-resolve.js +183 -0
  120. package/dist/sessions/pending.d.ts +34 -0
  121. package/dist/sessions/pending.js +142 -0
  122. package/dist/sessions/pool-command.d.ts +44 -0
  123. package/dist/sessions/pool-command.js +542 -0
  124. package/dist/sessions/pool-delta.d.ts +54 -0
  125. package/dist/sessions/pool-delta.js +190 -0
  126. package/dist/sessions/pool-outbox.d.ts +63 -0
  127. package/dist/sessions/pool-outbox.js +260 -0
  128. package/dist/sessions/pool-print.d.ts +26 -0
  129. package/dist/sessions/pool-print.js +68 -0
  130. package/dist/sessions/pool-wire.d.ts +225 -0
  131. package/dist/sessions/pool-wire.js +620 -0
  132. package/dist/sessions/pool.d.ts +32 -0
  133. package/dist/sessions/pool.js +164 -0
  134. package/dist/sessions/projection.d.ts +75 -0
  135. package/dist/sessions/projection.js +165 -0
  136. package/dist/sessions/public-vocabulary.d.ts +73 -0
  137. package/dist/sessions/public-vocabulary.js +302 -0
  138. package/dist/sessions/query.d.ts +111 -0
  139. package/dist/sessions/query.js +246 -0
  140. package/dist/sessions/read-adoption.d.ts +2 -0
  141. package/dist/sessions/read-adoption.js +27 -0
  142. package/dist/sessions/read-call-shape.d.ts +2 -0
  143. package/dist/sessions/read-call-shape.js +25 -0
  144. package/dist/sessions/read-change.d.ts +2 -0
  145. package/dist/sessions/read-change.js +30 -0
  146. package/dist/sessions/read-cost.d.ts +4 -0
  147. package/dist/sessions/read-cost.js +37 -0
  148. package/dist/sessions/read-day-move.d.ts +3 -0
  149. package/dist/sessions/read-day-move.js +65 -0
  150. package/dist/sessions/read-fit.d.ts +7 -0
  151. package/dist/sessions/read-fit.js +41 -0
  152. package/dist/sessions/read-interaction.d.ts +5 -0
  153. package/dist/sessions/read-interaction.js +62 -0
  154. package/dist/sessions/read-lead-lag.d.ts +15 -0
  155. package/dist/sessions/read-lead-lag.js +81 -0
  156. package/dist/sessions/read-live.d.ts +6 -0
  157. package/dist/sessions/read-live.js +38 -0
  158. package/dist/sessions/read-method.d.ts +2 -0
  159. package/dist/sessions/read-method.js +29 -0
  160. package/dist/sessions/read-next-step.d.ts +2 -0
  161. package/dist/sessions/read-next-step.js +35 -0
  162. package/dist/sessions/read-order.d.ts +54 -0
  163. package/dist/sessions/read-order.js +62 -0
  164. package/dist/sessions/read-readable.d.ts +8 -0
  165. package/dist/sessions/read-readable.js +24 -0
  166. package/dist/sessions/read-recovery.d.ts +20 -0
  167. package/dist/sessions/read-recovery.js +97 -0
  168. package/dist/sessions/read-rows.d.ts +13 -0
  169. package/dist/sessions/read-rows.js +47 -0
  170. package/dist/sessions/read-substitution.d.ts +5 -0
  171. package/dist/sessions/read-substitution.js +51 -0
  172. package/dist/sessions/read-support.d.ts +48 -0
  173. package/dist/sessions/read-support.js +73 -0
  174. package/dist/sessions/read-tool-day.d.ts +2 -0
  175. package/dist/sessions/read-tool-day.js +26 -0
  176. package/dist/sessions/read-version-boundary.d.ts +31 -0
  177. package/dist/sessions/read-version-boundary.js +95 -0
  178. package/dist/sessions/read.d.ts +79 -0
  179. package/dist/sessions/read.js +84 -0
  180. package/dist/sessions/recall-consequence.d.ts +45 -0
  181. package/dist/sessions/recall-consequence.js +179 -0
  182. package/dist/sessions/recall-object.d.ts +137 -0
  183. package/dist/sessions/recall-object.js +77 -0
  184. package/dist/sessions/recall-pool.d.ts +19 -0
  185. package/dist/sessions/recall-pool.js +96 -0
  186. package/dist/sessions/recall-push.d.ts +26 -0
  187. package/dist/sessions/recall-push.js +95 -0
  188. package/dist/sessions/recall-reads.d.ts +117 -0
  189. package/dist/sessions/recall-reads.js +410 -0
  190. package/dist/sessions/recall-render.d.ts +19 -0
  191. package/dist/sessions/recall-render.js +168 -0
  192. package/dist/sessions/redact.d.ts +6 -0
  193. package/dist/sessions/redact.js +54 -0
  194. package/dist/sessions/repo-root.d.ts +10 -0
  195. package/dist/sessions/repo-root.js +101 -0
  196. package/dist/sessions/schema.d.ts +644 -0
  197. package/dist/sessions/schema.js +148 -0
  198. package/dist/sessions/session-shape.d.ts +73 -0
  199. package/dist/sessions/session-shape.js +342 -0
  200. package/dist/sessions/sources.d.ts +50 -0
  201. package/dist/sessions/sources.js +159 -0
  202. package/dist/sessions/steps.d.ts +105 -0
  203. package/dist/sessions/steps.js +250 -0
  204. package/dist/sessions/store.d.ts +297 -0
  205. package/dist/sessions/store.js +1478 -0
  206. package/dist/sessions/strata.d.ts +26 -0
  207. package/dist/sessions/strata.js +77 -0
  208. package/dist/sessions/survival.d.ts +25 -0
  209. package/dist/sessions/survival.js +230 -0
  210. package/dist/sessions/verify.d.ts +53 -0
  211. package/dist/sessions/verify.js +286 -0
  212. package/dist/sessions/vocab-load.d.ts +26 -0
  213. package/dist/sessions/vocab-load.js +61 -0
  214. package/dist/sessions/vscode-copilot.d.ts +38 -0
  215. package/dist/sessions/vscode-copilot.js +477 -0
  216. package/package.json +4 -5
  217. package/vocab/crates.json +1 -0
  218. package/vocab/go.json +1 -0
  219. package/vocab/harness.json +1 -0
  220. package/vocab/homebrew.json +1 -0
  221. package/vocab/manifest.json +1 -0
  222. package/vocab/maven.json +1 -0
  223. package/vocab/models.json +1 -0
  224. package/vocab/npm.json +1 -0
  225. package/vocab/programs.json +1 -0
  226. package/vocab/pypi.json +1 -0
  227. package/vocab/rubygems.json +1 -0
  228. package/dist/commands/deploy-input.d.ts +0 -38
  229. package/dist/commands/deploy-input.js +0 -150
  230. package/dist/commands/deploy.d.ts +0 -22
  231. package/dist/commands/deploy.js +0 -124
  232. package/dist/commands/status.d.ts +0 -7
  233. package/dist/commands/status.js +0 -18
  234. package/dist/commands/templates.d.ts +0 -11
  235. package/dist/commands/templates.js +0 -32
  236. package/dist/commands/test-charge.d.ts +0 -11
  237. package/dist/commands/test-charge.js +0 -34
  238. package/dist/journal.d.ts +0 -26
  239. package/dist/journal.js +0 -58
  240. package/parity/deploy-plans/paid-api.json +0 -34
  241. package/parity/deploy-plans/per-task-agent.json +0 -34
@@ -0,0 +1,148 @@
1
+ // The session store's schema, in one file.
2
+ //
3
+ // Everything here is deterministic and local: no model call, no network. `oars sessions deposit` reads the operator's own Claude Code
4
+ // logs (`~/.claude/projects/<path-slug>/*.jsonl` and each session's `subagents/` transcripts, one JSON event per line) and appends
5
+ // captured records to `<config>/oars/sessions/observations.jsonl`. The log is appended to, and a whole-store deposit drops superseded revs (`compactLog` in `store.ts`): a session whose file changed is deposited
6
+ // again under a higher `rev`, and a reader keeps only the highest `rev` of each session.
7
+ //
8
+ // EXTRACTOR 11 (pool vocabulary spec Section 2) changes no stored value. It moves raw `agentType`, `keys`, and `frameId` with the joined `frame` from class P to
9
+ // class A, so a store written at 10 re-derives on its next deposit and only the pooled projection (computed at deposit, never written) changes.
10
+ // Version 12 (CAPTURE 12) adds the optional `setup`, `funnel`, and `trust` to the session record, read from the transcript only (`session-shape.ts`;
11
+ // `docs/sessions-store.md`). It changed no derivation, so it leaves EXTRACTOR at 10.
12
+ // Version 13 (CAPTURE 13) changes three stored values: the email mask keeps a plain dotted version after `npm:` or `jsr:` (a pinned alias keeps its pick),
13
+ // a block word in another case or a wrapper in another case or by path is skipped to reach the real program, and a `segments` `run` whose program basename holds
14
+ // whitespace stores `<literal>`. It also settles the two stored forms of a `copilot` step at version 12. EXTRACTOR stays 11.
15
+ // Version 14 (CAPTURE 14) adds the optional `errorCode`, `packages`, and `manifest` to a tool call and the optional `stack` strata to the file record and the prompt
16
+ // record, all read from the transcript except `repoFiles`, which keeps its existing source. It changes no value captured before, so EXTRACTOR stays 11.
17
+ // Version 15 (CAPTURE 15) changes three stored values of version 14: a Cargo table header that ends in a comment (`[package] # x`) is seen as a header, so the next
18
+ // `version =` is no longer stored as `crates:version`; and `packageVersion` keeps only a version of three or four dotted numbers, so a bare `@1` or `==2.31` stores null; and, since calls with a null `packageVersion` share one `first`, a later short pin of the same package now marks `false`. EXTRACTOR stays 11.
19
+ // Version 16 (CAPTURE 16) stores a call the user refused as Claude Code does, `status` `error` and `errorCode` `user-rejected`: a Cursor call whose `toolFormerData.userDecision` is `rejected`; a Codex shell
20
+ // call, patch, or command item that is `declined` with the user's refusal text (each `none` before; any other `declined` stays `none`); and a Gemini call the user cancelled or denied (`errorCode` `other` before). A call that was `none` or `ok` gains an `error-line` record whose `line` is
21
+ // the refusal text and loses its `manifest`, and where a result line is new (every Codex refusal, a Cursor one whose status is neither `error` nor `completed`, or one whose terminal result ended aborted) its session's `lines` and skeleton move. Any other
22
+ // decision value, or none, changes nothing and is never stored; a Grok plan-mode refusal and a Codex `write_stdin rejected:` stay as they were. EXTRACTOR stays 11.
23
+ //
24
+ // Version 17 (CAPTURE 17) frames a Gemini CLI shell call as the shell that ran it. A session whose own `<session_context>` turn says `My operating system is: win32` (or,
25
+ // with no such turn, whose working directory is written the Windows way) stores its shell calls as `PowerShell` where it stored `Bash`: the tool record's `tool`, an
26
+ // error line's `tool`, the session's `tools` and the skeleton's tool names, each step's `native` (`run_shell_command` before), and the frame's `shell` (`bash` before), so every `frameId` of that
27
+ // session moves. Upstream picks PowerShell on every Windows machine (`tools/shell-utils` `getShellConfiguration`). A Linux or macOS session is unchanged. It also gives a
28
+ // session in a project folder named for a hash of its path (from before the slug registry) the cwd that hash matches when the path is known, which moves its `project`
29
+ // (`gemini` before) and every value read from the cwd; a folder whose path is not known keeps none. Sessions under `~/.cache/.gemini` (`SANDBOX=sandbox-exec`) are new
30
+ // reads, not moved ones. A Copilot CLI session with rotated `events.N.jsonl` archives is now read as one session, so its `prompts`, `tools`, `lines`, and skeleton move; one
31
+ // with no archives is unchanged. EXTRACTOR stays 11. A version-16 store's next deposit captures every transcript again from the harness or `raw/`.
32
+ //
33
+ // EXTRACTOR 12 gives a derived prompt the strata of its user-prompt record, so its own `explore` count reaches the pool's wire as a bucket. No transcript is read: a store at
34
+ // 11 re-derives from its log alone.
35
+ //
36
+ // Version 10 (CAPTURE 10) gives a turn opened by a slash command or a skill a `user-prompt` record, adds `opened` to that record, and moves `skills`
37
+ // from class P to class A, since a skill's name can be private. It changed no derivation, so it leaves EXTRACTOR at 9.
38
+ //
39
+ // Version 11 (CAPTURE 11) redacts the part of an MCP tool name after `mcp__` (Cursor: `mcp_`) wherever it is stored (`tool`, the session's `tools`, an error line's `tool`, the skeleton's tool names),
40
+ // reads package-manager and shell forms more exactly (so `pulls`, `segments`, `commits`, and each tool's `steps` change), and opens a typed prompt from a line that only starts with an injected tag's name without closing it.
41
+ // EXTRACTOR 10 reads more command forms as a run or a verify (package runners, `unittest`, installs that are not runs, a leading word before a name); no capture record changes.
42
+ //
43
+ // EXTRACTOR 9 adds motifs, cells, and reads, computed from the capture records and written beside them (`docs/sessions-store.md`), and carries a prompt's
44
+ // frame onto its derived record. No capture record changes.
45
+ //
46
+ // Version 9 (CAPTURE 9) adds `frame` and `frameId` to the session and prompt records and `steps` to the tool record, and moves `tool`, `tools`, and a
47
+ // shell-form `target` from class P to class A. It changed no derivation, so it left EXTRACTOR at 8.
48
+ //
49
+ // Version 8 (CAPTURE 8 and EXTRACTOR 8) writes one `tool` record per tool call. The harness writes
50
+ // `api-request-blob` lines, copies of the request body with no cwd, uuid, or timestamp, and capture used to read their blocks as conversation: a second
51
+ // record with status `none` (and, with no cwd, another path hash) for 99.8% of calls, plus a second error line, thinking count, and TLDR. Those lines are
52
+ // no longer read, and a call a resumed transcript replays under one tool-use id is written once. The skeleton still lists every line. The reader folds
53
+ // records already on disk the same way until each transcript is captured again (`fold.ts`).
54
+ //
55
+ // Store version 5 (CAPTURE 5) keeps version 4's shape and changes one stored value: each `models` entry of the skeleton's cost state names
56
+ // the usage keys it reported, which `verify` uses to class a side call. A version-4 skeleton reads with no keys until it is captured again.
57
+ //
58
+ // Store version 4 (CAPTURE 4) keeps version 3's shape and adds to it: `sid` on every capture record, `repo`, `fileHashes`, and `env` on
59
+ // the session, `targetBase` on the tool record, `promptFingerprint` in place of `contextFingerprint`, and a 1,000-character cap on an
60
+ // error-line's `line`. A version-3 store is read through the compatibility reader (`upgradeSession` and its siblings in store.ts) until each transcript is captured again.
61
+ //
62
+ // Store version 3 (register A28) splits capture from derivation. Capture writes four record kinds, each stamped `cv` with
63
+ // CAPTURE_VERSION: `session` (one per transcript), `user-prompt` (one per prompt), `tool` (one per tool call), and `error-line` (one per
64
+ // failed tool result, keyed by `toolUseId`). Derivation (`derive.ts`: signatures, prompt classes, tool-name sequences, recovery pairs,
65
+ // and recovery episodes) runs over those records alone, never over raw, and stamps its output `v` with EXTRACTOR_VERSION, so a change to
66
+ // a derivation rule re-derives the store without reading a transcript. Version-2 `prompt` and `error` lines (already derived) are still
67
+ // read for a session that has no version-3 rev.
68
+ //
69
+ // Field classes (spec Section 1.1): `L` local-only, never leaves the machine at any tier; `A` account store, the account's own store only;
70
+ // `P` pooled index, names, versions, counts, and day-resolution dates. The session, prompt, tool, error, and error-line records name
71
+ // each field's class in a doc comment; the skeleton and the two call logs carry theirs on the type, and docs/sessions-store.md lists
72
+ // them all. The log is the local derived-records tier: redacted text, never raw content, and nothing uploads.
73
+ //
74
+ // The text kept, each redacted first and then cut: the first 300 characters of a prompt, the first informative line of a tool error (cut
75
+ // to 1,000 characters in the `error-line` record, 160 characters in a derived example), TLDR lines (400), Confidence lines (120), and,
76
+ // with no length limit, WebSearch queries, fetched URLs, file paths, and git branch names. Also kept as written, not redacted: the
77
+ // project folder slug (it holds the username and directory names; the log stays local), skill names, effort values, model names,
78
+ // harness versions, and tool names (an MCP name's part after its prefix is redacted). `redact` masks secrets by pattern and entropy, emails, and the
79
+ // organization domains the operator names. The skeleton holds hashes only.
80
+ /** Stamped `cv` on every captured record. Bump it when the captured field list changes or a stored value of an existing captured record or skeleton changes: a bump re-reads every transcript. */
81
+ export const CAPTURE_VERSION = '17';
82
+ /** Capture versions the reader derives from. A version-3 record is upgraded on read; a newer one is read for the fields this code knows. */
83
+ export const OLDEST_READABLE_CAPTURE = 3;
84
+ /** Stamped `v` on every derived record. A bump re-derives from the log; no transcript is read. */
85
+ export const EXTRACTOR_VERSION = '12';
86
+ export const PROMPT_HEAD_CHARS = 300;
87
+ export const ERROR_EXAMPLE_CHARS = 160;
88
+ /** The reference scripts cut a normalized signature at 110 characters. */
89
+ export const SIGNATURE_CHARS = 110;
90
+ /** An error-line's `line` is redacted, then cut to this many characters (spec Section 1.2; version 3 stored it uncut). */
91
+ export const ERROR_LINE_CHARS = 1000;
92
+ /** Per-session cap on `files` and `fileHashes` (L3): the hand run on the copy store had p99 61 and max 209 files per session; 100 is the round number above p99. */
93
+ export const FILES_PER_SESSION_MAX = 100;
94
+ export const PROMPT_CLASSES = ['no-tools', 'orchestration', 'research', 'edit-verify', 'edit-only', 'explore-only', 'shell-only', 'mixed'];
95
+ /** The statuses agents recover from differently (pool vocabulary spec Section 7); any other status is `other`. */
96
+ export const HTTP_STATUSES = ['400', '401', '403', '404', '408', '409', '413', '429', '500', '502', '503', '504', '529'];
97
+ /** A shell exit status by what the agent does next: success flagged as failure, the two common failures, not executable, not found, a signal (129 to 192), and the rest. */
98
+ export const EXIT_CLASSES = ['0', '1', '2', '126', '127', 'signal'];
99
+ /** What a harness says when a call fails before or without a shell: it names the harness's own refusal, never the tool's input or output. */
100
+ export const HARNESS_ERROR_CLASSES = ['user-rejected', 'interrupted', 'permission-denied', 'input-validation', 'tool-use-error', 'timeout'];
101
+ /** The `errorCode` of a failed call (class P): a member of this set, never text from the result. `other` covers an exit status or HTTP status outside the lists, and a failure that names no class. */
102
+ export const ERROR_CODES = new Set([...EXIT_CLASSES, ...HTTP_STATUSES, ...HARNESS_ERROR_CLASSES, 'other']);
103
+ /** The harness a session record came from; a record that does not name one is Claude Code's. */
104
+ export const harnessOf = (session) => session.harness ?? 'claude-code';
105
+ /** A language's share of the files a transcript touched (pool vocabulary spec Section 7): none, incidental, secondary, primary. The lower edge is inclusive, so 10 percent is secondary and 50 is primary. */
106
+ export const LANGUAGE_SHARES = ['0', 'under-10', '10-50', 'over-50'];
107
+ /** A repository's tracked-file count by order of magnitude (Section 7). The lower edge is inclusive, so exactly 100 is `100-1k` and exactly 100000 is `over-100k`, as `repoSizeOf` buckets them. */
108
+ export const REPO_FILE_BUCKETS = ['under-100', '100-1k', '1k-10k', '10k-100k', 'over-100k'];
109
+ /** The pipeline steps the log names (runtime-commons pipeline spec 3.1); `label` is the local labeler step (A5) that no command runs yet; `index` is the hook-index write the nightly and manual runs log. */
110
+ export const STEPS = ['deposit', 'verify', 'derive', 'label', 'index', 'recall', 'read'];
111
+ /** The trigger names of spec 4.1 (the ones nothing local starts are in the schema only: `weekly` (A21) and the hosted `sync`, `cron`, `release`, `run-end`, and `run-start`). The SessionEnd hook is `stop`, never `session-end`. */
112
+ export const TRIGGERS = ['session-start', 'pre-tool', 'post-tool', 'stop', 'nightly', 'manual', 'weekly', 'sync', 'cron', 'release', 'run-end', 'run-start'];
113
+ /** `deferred`: a Stop deposit that found the store locked and recorded its session in the pending list. It is neither a success nor a failed run. */
114
+ export const STEP_STATUSES = ['ok', 'error', 'skipped', 'deferred'];
115
+ const isObject = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
116
+ const countsOf = (value) => {
117
+ if (!isObject(value))
118
+ return undefined;
119
+ const entries = Object.entries(value);
120
+ return entries.every(([, count]) => typeof count === 'number' && Number.isFinite(count)) ? Object.fromEntries(entries.map(([kind, count]) => [kind, Number(count)])) : undefined;
121
+ };
122
+ const oneOf = (list, value) => list.find((entry) => entry === value);
123
+ /** Reads one line of `pipeline-runs.jsonl`; undefined when any key is missing or mistyped, so a torn or foreign line is never counted. */
124
+ export const parseStepRow = (value) => {
125
+ if (!isObject(value))
126
+ return undefined;
127
+ const trigger = oneOf(TRIGGERS, value.trigger);
128
+ const step = oneOf(STEPS, value.step);
129
+ const status = oneOf(STEP_STATUSES, value.status);
130
+ const input = countsOf(value.in);
131
+ const output = countsOf(value.out);
132
+ if (trigger === undefined || step === undefined || status === undefined || input === undefined || output === undefined)
133
+ return undefined;
134
+ const { at, run, ms, cv, ev, lv, key, error } = value;
135
+ if (typeof at !== 'string' || typeof run !== 'string' || typeof ms !== 'number' || typeof cv !== 'string' || typeof ev !== 'string')
136
+ return undefined;
137
+ if (!(key === null || typeof key === 'string') || !(error === null || typeof error === 'string'))
138
+ return undefined;
139
+ // `lv` is the labeler stamp: spec 3.1 puts it on `label` rows only.
140
+ if (lv !== undefined && (typeof lv !== 'string' || step !== 'label'))
141
+ return undefined;
142
+ if (step === 'label' && lv === undefined)
143
+ return undefined;
144
+ // Spec 3.1: `error` is null when the step is `ok`.
145
+ if (status === 'ok' && error !== null)
146
+ return undefined;
147
+ return { at, run, trigger, step, status, ms, in: input, out: output, cv, ev, ...(lv === undefined ? {} : { lv }), key, error };
148
+ };
@@ -0,0 +1,73 @@
1
+ import type { Harness } from './schema.js';
2
+ type Obj = {
3
+ [key: string]: unknown;
4
+ };
5
+ /** The harness's tool-rejection results, both worded for the operator declining: a classifier or hook denial is policy, and the model carries on. */
6
+ export declare const REJECTED_RE: RegExp;
7
+ /** The harness writes this as a user line when the operator cut a turn short; a rejection is followed by it too, so it is not a second interrupt. */
8
+ export declare const INTERRUPT_MARKER = "[Request interrupted by user";
9
+ /** An adapter's neutral line naming tools: `offered` and `loaded` are arrays of tool names. */
10
+ export declare const SHAPE_TOOLS_LINE = "oars-tools";
11
+ /** An adapter's neutral line for a turn the operator cut short, for a harness whose record is not a Claude marker line. */
12
+ export declare const SHAPE_INTERRUPT_LINE = "oars-interrupt";
13
+ export type TrustEventKind = 'interrupt' | 'rejection' | 'denial';
14
+ /** One stop, with the tool in flight (null when none is known) and the prompt it fell in (null before the first). */
15
+ export interface TrustEvent {
16
+ event: TrustEventKind;
17
+ tool: string | null;
18
+ prompt: number | null;
19
+ }
20
+ /** The setup the session ran with. A field the transcript does not hold is absent. */
21
+ export interface SessionSetup {
22
+ /** The mode most of the session ran in: the harness's documented value, else `other`. */
23
+ permissionMode?: string;
24
+ /** Plugins evidenced by a tool named after them, sorted. */
25
+ plugins?: string[];
26
+ /** Distinct hooks seen to fire, by the name the harness gives each, a floor; never the command. */
27
+ hookCount?: number;
28
+ /** Bytes of instruction files the harness loaded mid-session, a floor: the opening instruction files are in a blob capture skips. */
29
+ instructionBytes?: number;
30
+ }
31
+ /** The tool names at each stage a transcript shows, sorted. `called` and `worked` are the session's tool calls and their status, already captured. */
32
+ export interface SessionFunnel {
33
+ /** Offered and not since removed. */
34
+ offered?: string[];
35
+ /** Pulled into the context by a tool search. */
36
+ loaded?: string[];
37
+ /** Offered and then removed, and not offered again. */
38
+ removed?: string[];
39
+ }
40
+ /** How far the session was trusted to run alone. The exact value is kept; the buckets belong to the projection. */
41
+ export interface SessionTrust {
42
+ interrupts?: number;
43
+ /** The human declined a tool call. */
44
+ rejections?: number;
45
+ /** Policy refused it: permission rules, the classifier, or a hook. */
46
+ denials?: number;
47
+ trustEvent?: TrustEvent[];
48
+ /** Minutes the agent ran alone after each human turn: from the turn to the agent's last line before the next human turn, two decimals. */
49
+ unattendedRun?: number[];
50
+ }
51
+ export interface SessionShape {
52
+ setup?: SessionSetup;
53
+ funnel?: SessionFunnel;
54
+ trust?: SessionTrust;
55
+ }
56
+ /** Where a line sits, for the parts of the shape that depend on it. */
57
+ export interface LineContext {
58
+ /** A sidechain line in a main transcript: a subagent's own, not read here. */
59
+ side: boolean;
60
+ /** The prompt the line falls in. */
61
+ prompt: number | null;
62
+ }
63
+ export interface ShapeCollector {
64
+ read(line: Obj, at: LineContext): void;
65
+ /** A turn: `human` opens a run of the agent alone and closes the last; `agent` is the agent (or an automated turn) working; `none` is neither. */
66
+ turn(kind: 'human' | 'agent' | 'none', seconds: number | undefined, uuid?: string): void;
67
+ finish(): SessionShape;
68
+ }
69
+ /** `stored` is the stored form of a tool name (`storedToolName`): every name this keeps is a tool name, an MCP server's part redacted. */
70
+ export declare const createShape: (harness: Harness, stored: (tool: string) => string, options: {
71
+ timing: boolean;
72
+ }) => ShapeCollector;
73
+ export {};
@@ -0,0 +1,342 @@
1
+ // What a transcript shows of a session's setup, its tool funnel, and its trust shape (the capture half of the pool vocabulary). Read from the
2
+ // transcript only: never today's config or instruction files, because a file read now is not that session's setup. A field the transcript does not
3
+ // hold is absent, never guessed. Counts, names, and sizes only: no instruction text, hook command, or tool description is kept. Class A, local; nothing
4
+ // here is projected or uploaded.
5
+ //
6
+ // One collector per transcript, fed line by line by `captureSession`. It reads the Claude Code line shapes (a census of 2.1.312 to 2.1.318) and
7
+ // two neutral lines an adapter may write for a harness whose records are not Claude-shaped (`oars-tools`, `oars-interrupt`).
8
+ const isObj = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
9
+ const str = (value) => (typeof value === 'string' ? value : undefined);
10
+ const names = (value) => (Array.isArray(value) ? value.filter((item) => typeof item === 'string' && item !== '') : []);
11
+ /** The harness's tool-rejection results, both worded for the operator declining: a classifier or hook denial is policy, and the model carries on. */
12
+ export const REJECTED_RE = /^The user doesn't want to (?:proceed with this tool use|take this action)/;
13
+ /** The harness writes this as a user line when the operator cut a turn short; a rejection is followed by it too, so it is not a second interrupt. */
14
+ export const INTERRUPT_MARKER = '[Request interrupted by user';
15
+ /** A policy refusal: the permission rules or the auto-mode classifier (`Permission ... has been denied`), or a hook (`PreToolUse:Bash hook error`). Not the human's. */
16
+ const DENIED_RE = /^(?:Permission (?:to use .{1,200}?|for this action) has been denied|[A-Za-z]+:[^\s]+ hook error|Hook [A-Za-z]+:[^\s]+ denied)/;
17
+ /** A plugin's MCP server names its tools `mcp__plugin_<plugin>_<server>__<tool>`; that is the only evidence of an installed plugin read here.
18
+ * The name stops at its first `_`, since the tool name cannot say where the plugin ends and the server begins: `mcp__plugin_my_plugin_srv__t` reads as `my`. */
19
+ const PLUGIN_TOOL_RE = /^mcp__plugin_([A-Za-z0-9-]+)_/;
20
+ /** A hook of the `Stop` event, named `Stop` or `Stop:<matcher>`. */
21
+ const STOP_HOOK_RE = /^Stop(?::|$)/;
22
+ /** An adapter's neutral line naming tools: `offered` and `loaded` are arrays of tool names. */
23
+ export const SHAPE_TOOLS_LINE = 'oars-tools';
24
+ /** An adapter's neutral line for a turn the operator cut short, for a harness whose record is not a Claude marker line. */
25
+ export const SHAPE_INTERRUPT_LINE = 'oars-interrupt';
26
+ /** The permission modes each harness documents; any other value is stored as `other` (spec row `permissionMode`). Keyed by harness so a new one must add its entry. */
27
+ const PERMISSION_MODES = {
28
+ 'claude-code': new Set(['default', 'acceptEdits', 'plan', 'auto', 'dontAsk', 'bypassPermissions']),
29
+ codex: new Set(['untrusted', 'on-failure', 'on-request', 'never', 'granular']),
30
+ // Cursor's, Copilot's, and Gemini CLI's readers record no permission mode, so no value is documented to keep.
31
+ copilot: new Set(),
32
+ // VS Code Copilot Chat's reader records no permission mode either.
33
+ 'vscode-copilot': new Set(),
34
+ gemini: new Set(),
35
+ cursor: new Set(),
36
+ // Grok Build's reader reads no mode (its `current_mode_update` carries one, but this reader does not read it), so no value is documented to keep.
37
+ grok: new Set(),
38
+ };
39
+ /**
40
+ * Which trust counts a harness's transcript records completely. A zero is stored only where the harness would have shown the event; where it would not,
41
+ * the count is absent, because zero would claim a human never stopped it. Keyed by harness so a new one must decide.
42
+ */
43
+ const COMPLETE = {
44
+ 'claude-code': { interrupts: true, rejections: true, denials: true, timing: true },
45
+ codex: { interrupts: true, rejections: false, denials: false, timing: true },
46
+ // Copilot's reader writes the prompts and the agent's lines with their times (so the runs are real), and reads no `abort`, tool outcome refusal, or hook denial.
47
+ copilot: { interrupts: false, rejections: false, denials: false, timing: true },
48
+ // VS Code Copilot Chat's reader writes the prompts and the responses with their times (so the runs are real) and reads a refusal as a tool error, not as a counted one; it reads no interrupt or hook denial count.
49
+ 'vscode-copilot': { interrupts: false, rejections: false, denials: false, timing: true },
50
+ // Gemini CLI's reader writes each message with its own time (so the runs are real), and reads no interrupt, refusal, or denial: the chat file holds a cancelled call as an error and no more.
51
+ gemini: { interrupts: false, rejections: false, denials: false, timing: true },
52
+ cursor: { interrupts: false, rejections: false, denials: false, timing: false },
53
+ // Grok Build's reader writes the prompts and replies with their line times (seconds), and reads no cancel, tool refusal, or hook denial.
54
+ grok: { interrupts: false, rejections: false, denials: false, timing: true },
55
+ };
56
+ const firstText = (content) => {
57
+ if (typeof content === 'string')
58
+ return content;
59
+ if (!Array.isArray(content))
60
+ return '';
61
+ for (const block of content)
62
+ if (isObj(block) && block.type === 'text')
63
+ return str(block.text) ?? '';
64
+ return '';
65
+ };
66
+ const firstLine = (content) => firstText(content).trimStart().split('\n', 1)[0] ?? '';
67
+ const pluginOf = (tool) => PLUGIN_TOOL_RE.exec(tool)?.[1];
68
+ /** `stored` is the stored form of a tool name (`storedToolName`): every name this keeps is a tool name, an MCP server's part redacted. */
69
+ export const createShape = (harness, stored, options) => {
70
+ const complete = COMPLETE[harness];
71
+ const modes = new Map();
72
+ /** Hooks seen to fire, by the name the harness gives them (`PreToolUse:Bash`, `Stop`). */
73
+ const hooks = new Set();
74
+ /** The commands a `stop_hook_summary` lists: counted only when no line of a `Stop` hook was seen, since that line and the summary are the same hooks. */
75
+ const stopCommands = new Set();
76
+ let stopSeen = false;
77
+ const memory = new Map();
78
+ const plugins = new Set();
79
+ /** Every name offered or removed, in order: the last event on a name decides which side it ends on. */
80
+ const standing = new Map();
81
+ const loaded = new Set();
82
+ const events = [];
83
+ const open = new Map();
84
+ /** Every tool call id that has had its result, so a replayed call or result is neither re-opened nor counted twice. */
85
+ const finished = new Set();
86
+ /** Human turns already taken, by uuid: a resume replays them. */
87
+ const humans = new Set();
88
+ const markers = new Set();
89
+ let rejectedLast = false;
90
+ // The human refused a call: the run ended at the agent's last line before it, and what the agent does after the refusal waits for the next human turn.
91
+ let declined = false;
92
+ const counts = { interrupts: 0, rejections: 0, denials: 0 };
93
+ const runs = [];
94
+ let start;
95
+ let end;
96
+ const timing = options.timing && complete.timing;
97
+ const mode = (raw) => {
98
+ const value = str(raw);
99
+ if (value === undefined || value === '')
100
+ return;
101
+ const kept = PERMISSION_MODES[harness].has(value) ? value : 'other';
102
+ modes.set(kept, (modes.get(kept) ?? 0) + 1);
103
+ };
104
+ const noted = (tool) => {
105
+ // The `mcp__plugin_<plugin>_` naming is Claude Code's; another harness's tool of that shape is not evidence of a plugin.
106
+ // Read from the stored form, so a plugin named with a token reads as none rather than as the token.
107
+ const plugin = harness === 'claude-code' && tool.startsWith('mcp__plugin_') ? pluginOf(stored(tool)) : undefined;
108
+ if (plugin !== undefined)
109
+ plugins.add(plugin);
110
+ };
111
+ const offer = (list) => {
112
+ for (const name of list) {
113
+ standing.set(name, 'offered');
114
+ noted(name);
115
+ }
116
+ };
117
+ const remove = (list) => {
118
+ for (const name of list) {
119
+ standing.set(name, 'removed');
120
+ noted(name);
121
+ }
122
+ };
123
+ const load = (list) => {
124
+ for (const name of list) {
125
+ loaded.add(name);
126
+ noted(name);
127
+ }
128
+ };
129
+ const event = (kind, tool, prompt) => {
130
+ if (kind === 'interrupt')
131
+ counts.interrupts += 1;
132
+ else if (kind === 'rejection')
133
+ counts.rejections += 1;
134
+ else
135
+ counts.denials += 1;
136
+ events.push({ event: kind, tool, prompt });
137
+ };
138
+ const interrupt = (tool, prompt) => {
139
+ // The turn ended, so no call is still in flight: a later marker must not name this one.
140
+ open.clear();
141
+ if (rejectedLast) {
142
+ rejectedLast = false;
143
+ return;
144
+ }
145
+ event('interrupt', tool, prompt);
146
+ };
147
+ /** The tool the harness was still waiting on: the last call with no result. */
148
+ const inFlight = () => [...open.values()].at(-1) ?? null;
149
+ const result = (block, at) => {
150
+ const id = str(block.tool_use_id);
151
+ // A tool_reference block is the tool search's answer: those tools are now loaded.
152
+ if (Array.isArray(block.content)) {
153
+ for (const inner of block.content)
154
+ if (isObj(inner) && inner.type === 'tool_reference')
155
+ load(names([inner.tool_name]));
156
+ }
157
+ if (id === undefined || finished.has(id))
158
+ return;
159
+ finished.add(id);
160
+ const tool = open.get(id) ?? null;
161
+ open.delete(id);
162
+ if (block.is_error !== true)
163
+ return;
164
+ const line = firstLine(block.content);
165
+ if (REJECTED_RE.test(line)) {
166
+ if (complete.rejections) {
167
+ rejectedLast = true;
168
+ declined = true;
169
+ }
170
+ event('rejection', tool, at.prompt);
171
+ }
172
+ else if (DENIED_RE.test(line))
173
+ event('denial', tool, at.prompt);
174
+ };
175
+ const attachment = (a) => {
176
+ const kind = str(a.type);
177
+ if (kind === 'deferred_tools_delta') {
178
+ offer(names(a.addedNames));
179
+ remove(names(a.removedNames));
180
+ }
181
+ else if (kind === 'hook_success' || kind === 'hook_additional_context' || kind === 'hook_blocking_error') {
182
+ const name = str(a.hookName);
183
+ if (name !== undefined && name !== '') {
184
+ hooks.add(name);
185
+ if (STOP_HOOK_RE.test(name))
186
+ stopSeen = true;
187
+ }
188
+ }
189
+ else if (kind === 'nested_memory' && isObj(a.content) && typeof a.content.content === 'string') {
190
+ // The same file comes again when the harness reloads it, so one path counts once.
191
+ const key = str(a.path) ?? str(a.content.path) ?? `#${String(memory.size)}`;
192
+ if (!memory.has(key))
193
+ memory.set(key, Buffer.byteLength(a.content.content));
194
+ }
195
+ };
196
+ const close = () => {
197
+ if (start !== undefined)
198
+ runs.push(Math.round((((end ?? start) - start) / 60) * 100) / 100);
199
+ start = undefined;
200
+ end = undefined;
201
+ };
202
+ return {
203
+ read(line, at) {
204
+ // A sidechain line is a subagent's own, not the main session's setup.
205
+ if (at.side)
206
+ return;
207
+ const type = str(line.type);
208
+ if (type === 'permission-mode')
209
+ mode(line.permissionMode);
210
+ else if (type === 'api-request-shape') {
211
+ // The eager tool list the request sent: names only. `shape.system` holds the system prompt and a tool's description its text, so neither is touched.
212
+ const shape = isObj(line.shape) ? line.shape : undefined;
213
+ if (Array.isArray(shape?.tools))
214
+ offer(names(shape.tools.map((tool) => (isObj(tool) ? tool.name : undefined))));
215
+ }
216
+ else if (type === SHAPE_TOOLS_LINE) {
217
+ offer(names(line.offered));
218
+ load(names(line.loaded));
219
+ }
220
+ else if (type === SHAPE_INTERRUPT_LINE)
221
+ interrupt(inFlight(), at.prompt);
222
+ else if (type === 'attachment') {
223
+ if (isObj(line.attachment))
224
+ attachment(line.attachment);
225
+ }
226
+ else if (type === 'system' && line.subtype === 'stop_hook_summary') {
227
+ if (Array.isArray(line.hookInfos))
228
+ for (const info of line.hookInfos)
229
+ if (isObj(info) && str(info.command) !== undefined)
230
+ stopCommands.add(str(info.command) ?? '');
231
+ }
232
+ else if ((type === 'user' || type === 'assistant') && isObj(line.message)) {
233
+ if (type === 'user')
234
+ mode(line.permissionMode);
235
+ const content = line.message.content;
236
+ if (type === 'user' && firstText(content).startsWith(INTERRUPT_MARKER)) {
237
+ const uuid = str(line.uuid);
238
+ if (uuid === undefined || !markers.has(uuid))
239
+ interrupt(inFlight(), at.prompt);
240
+ if (uuid !== undefined)
241
+ markers.add(uuid);
242
+ }
243
+ if (type === 'assistant')
244
+ rejectedLast = false;
245
+ if (!Array.isArray(content))
246
+ return;
247
+ for (const block of content) {
248
+ if (!isObj(block))
249
+ continue;
250
+ if (block.type === 'tool_use') {
251
+ const id = str(block.id);
252
+ const name = str(block.name);
253
+ if (name !== undefined)
254
+ noted(name);
255
+ if (id !== undefined && name !== undefined && !open.has(id) && !finished.has(id))
256
+ open.set(id, name);
257
+ }
258
+ else if (block.type === 'tool_result')
259
+ result(block, at);
260
+ }
261
+ }
262
+ },
263
+ turn(kind, seconds, uuid) {
264
+ if (kind === 'human') {
265
+ // A resume replays earlier lines: a human turn already taken is not a new one.
266
+ if (uuid !== undefined) {
267
+ if (humans.has(uuid))
268
+ return;
269
+ humans.add(uuid);
270
+ }
271
+ // The operator spoke again: nothing is still in flight, and a rejection before this is not what a later marker follows.
272
+ open.clear();
273
+ rejectedLast = false;
274
+ }
275
+ // The line after a rejection ends the run; a human turn does that anyway, so it falls through to start the next one.
276
+ const afterRejection = declined && kind !== 'human';
277
+ declined = false;
278
+ if (!timing)
279
+ return;
280
+ if (afterRejection) {
281
+ close();
282
+ return;
283
+ }
284
+ // A human turn with no usable time still ends the run before it: the agent lines after it are not that run's.
285
+ if (kind === 'human') {
286
+ close();
287
+ start = seconds;
288
+ }
289
+ else if (kind === 'agent' && seconds !== undefined && start !== undefined && seconds >= start && (end === undefined || seconds > end))
290
+ end = seconds;
291
+ },
292
+ finish() {
293
+ close();
294
+ const out = {};
295
+ const setup = {};
296
+ let best = 0;
297
+ for (const [value, count] of modes) {
298
+ if (count > best) {
299
+ best = count;
300
+ setup.permissionMode = value;
301
+ }
302
+ }
303
+ if (plugins.size > 0)
304
+ setup.plugins = [...plugins].sort();
305
+ const hookTotal = hooks.size + (stopSeen ? 0 : stopCommands.size);
306
+ if (hookTotal > 0)
307
+ setup.hookCount = hookTotal;
308
+ if (memory.size > 0)
309
+ setup.instructionBytes = [...memory.values()].reduce((sum, bytes) => sum + bytes, 0);
310
+ if (Object.keys(setup).length > 0)
311
+ out.setup = setup;
312
+ const funnel = {};
313
+ // Two names that redact to one form are one tool, as `tools` keys it.
314
+ const sideOf = (which) => [...new Set([...standing].filter(([, side]) => side === which).map(([name]) => stored(name)))].sort();
315
+ const offered = sideOf('offered');
316
+ const removed = sideOf('removed');
317
+ if (offered.length > 0)
318
+ funnel.offered = offered;
319
+ if (loaded.size > 0)
320
+ funnel.loaded = [...new Set([...loaded].map(stored))].sort();
321
+ if (removed.length > 0)
322
+ funnel.removed = removed;
323
+ if (Object.keys(funnel).length > 0)
324
+ out.funnel = funnel;
325
+ const trust = {};
326
+ if (complete.interrupts)
327
+ trust.interrupts = counts.interrupts;
328
+ if (complete.rejections)
329
+ trust.rejections = counts.rejections;
330
+ if (complete.denials)
331
+ trust.denials = counts.denials;
332
+ const kept = events.filter((item) => (item.event === 'interrupt' ? complete.interrupts : item.event === 'rejection' ? complete.rejections : complete.denials));
333
+ if (kept.length > 0)
334
+ trust.trustEvent = kept.map((item) => ({ ...item, tool: item.tool === null ? null : stored(item.tool) }));
335
+ if (runs.length > 0)
336
+ trust.unattendedRun = runs;
337
+ if (Object.keys(trust).length > 0)
338
+ out.trust = trust;
339
+ return out;
340
+ },
341
+ };
342
+ };
@@ -0,0 +1,50 @@
1
+ import type { SubagentOrigin } from './extract.js';
2
+ import type { Harness } from './schema.js';
3
+ /** A session found in a source, before its transcript is read. */
4
+ export interface SourceSession {
5
+ /** Names the skeleton (`<source>-<id>`), so it is a safe file name. */
6
+ id: string;
7
+ /** The working folder (a path); null when the source names none. */
8
+ folder: string | null;
9
+ /** With `mtimeMs`, the cursor `state.json` keeps to tell a changed session from one already deposited. */
10
+ size: number;
11
+ mtimeMs: number;
12
+ /** Set when the session is a spawned agent's: it is deposited as a subagent of its parent, so session counts stay main sessions. */
13
+ subagent?: SubagentOrigin;
14
+ }
15
+ export interface SourceStore {
16
+ sessions: SourceSession[];
17
+ /** The session as transcript text for `captureSession`; throws when it cannot be read. */
18
+ transcript: (session: SourceSession) => string | Promise<string>;
19
+ /** Sessions skipped because their id is not a safe file name, counted so the skip is never silent. */
20
+ rejected: number;
21
+ /** Items that could not be inspected, warned about once each and never fatal; `reason` names no path. */
22
+ unreadable: readonly {
23
+ file: string;
24
+ reason: string;
25
+ }[];
26
+ /** The file to blame when `transcript` throws for a session; the store's own file when a source keeps one. */
27
+ where: (session: SourceSession) => string;
28
+ close: () => void;
29
+ }
30
+ export interface Source {
31
+ /** The `--allow` name, the first segment of its `state.json` keys, and the prefix of its skeleton names. */
32
+ name: string;
33
+ harness: Harness;
34
+ /** How the deposit lines name it ("Cursor": `Read 3 Cursor sessions.`). */
35
+ label: string;
36
+ /** Where its store lives; the caller passes the environment so a test never reads the real one. */
37
+ root: (platform: NodeJS.Platform, env: {
38
+ [name: string]: string | undefined;
39
+ }, home: string) => string;
40
+ /** The file reported when the store as a whole cannot be opened. */
41
+ storeFile: (root: string) => string;
42
+ /** A session whose transcript cannot be read ends this source's deposit (Cursor's, as it was before the table) instead of skipping to the next. */
43
+ stopOnTranscriptError?: boolean;
44
+ /** Opens the store read-only; undefined when the machine has none, so a missing store is skipped silently. */
45
+ open: (root: string) => Promise<SourceStore | undefined>;
46
+ }
47
+ /** Every non-Claude source, in the order the deposit reads them. */
48
+ export declare const SOURCES: readonly Source[];
49
+ /** How a source is named in a deposit line; a name the table does not know (a test's) is its own label. */
50
+ export declare const sourceLabel: (name: string, table?: readonly Source[]) => string;