@manyoars/oars 0.2.0 → 0.4.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 (320) hide show
  1. package/README.md +147 -28
  2. package/dist/api.js +1 -1
  3. package/dist/bin.js +6 -29
  4. package/dist/cli.d.ts +10 -4
  5. package/dist/cli.js +440 -91
  6. package/dist/commands/keys.d.ts +31 -0
  7. package/dist/commands/keys.js +65 -0
  8. package/dist/commands/login.d.ts +1 -1
  9. package/dist/commands/login.js +3 -3
  10. package/dist/errors.d.ts +13 -13
  11. package/dist/errors.js +28 -11
  12. package/dist/gate/checks.d.ts +68 -0
  13. package/dist/gate/checks.js +584 -0
  14. package/dist/gate/command.d.ts +14 -0
  15. package/dist/gate/command.js +61 -0
  16. package/dist/gate/hook-fill-main.d.ts +1 -0
  17. package/dist/gate/hook-fill-main.js +16 -0
  18. package/dist/gate/hook-fill.d.ts +38 -0
  19. package/dist/gate/hook-fill.js +94 -0
  20. package/dist/gate/hook-main.d.ts +1 -0
  21. package/dist/gate/hook-main.js +47 -0
  22. package/dist/gate/hook-process.d.ts +24 -0
  23. package/dist/gate/hook-process.js +112 -0
  24. package/dist/gate/hook-record.d.ts +3 -0
  25. package/dist/gate/hook-record.js +8 -0
  26. package/dist/gate/hook.d.ts +48 -0
  27. package/dist/gate/hook.js +259 -0
  28. package/dist/gate/install.d.ts +25 -0
  29. package/dist/gate/install.js +126 -0
  30. package/dist/gate/local-bin.d.ts +8 -0
  31. package/dist/gate/local-bin.js +36 -0
  32. package/dist/gate/notes.d.ts +19 -0
  33. package/dist/gate/notes.js +27 -0
  34. package/dist/gate/novelty.d.ts +35 -0
  35. package/dist/gate/novelty.js +99 -0
  36. package/dist/gate/policy.d.ts +25 -0
  37. package/dist/gate/policy.js +73 -0
  38. package/dist/gate/record.d.ts +120 -0
  39. package/dist/gate/record.js +125 -0
  40. package/dist/gate/registry-state.d.ts +10 -0
  41. package/dist/gate/registry-state.js +30 -0
  42. package/dist/gate/registry.d.ts +56 -0
  43. package/dist/gate/registry.js +94 -0
  44. package/dist/gate/replay.d.ts +20 -0
  45. package/dist/gate/replay.js +254 -0
  46. package/dist/gate/report.d.ts +50 -0
  47. package/dist/gate/report.js +89 -0
  48. package/dist/gate/scope.d.ts +7 -0
  49. package/dist/gate/scope.js +27 -0
  50. package/dist/gate/transcripts.d.ts +68 -0
  51. package/dist/gate/transcripts.js +232 -0
  52. package/dist/hook-bin.d.ts +1 -0
  53. package/dist/hook-bin.js +50 -0
  54. package/dist/issuer.d.ts +10 -6
  55. package/dist/issuer.js +19 -24
  56. package/dist/main.d.ts +1 -0
  57. package/dist/main.js +67 -0
  58. package/dist/oidc.js +1 -1
  59. package/dist/sessions/actions.d.ts +35 -0
  60. package/dist/sessions/actions.js +429 -0
  61. package/dist/sessions/allow.d.ts +23 -0
  62. package/dist/sessions/allow.js +71 -0
  63. package/dist/sessions/archive.d.ts +11 -0
  64. package/dist/sessions/archive.js +38 -0
  65. package/dist/sessions/audit.d.ts +42 -0
  66. package/dist/sessions/audit.js +104 -0
  67. package/dist/sessions/baseline.d.ts +62 -0
  68. package/dist/sessions/baseline.js +190 -0
  69. package/dist/sessions/call-shape.d.ts +19 -0
  70. package/dist/sessions/call-shape.js +85 -0
  71. package/dist/sessions/capture-runtime.d.ts +10 -0
  72. package/dist/sessions/capture-runtime.js +12 -0
  73. package/dist/sessions/cell-increments.d.ts +47 -0
  74. package/dist/sessions/cell-increments.js +243 -0
  75. package/dist/sessions/cells.d.ts +109 -0
  76. package/dist/sessions/cells.js +261 -0
  77. package/dist/sessions/codex.d.ts +53 -0
  78. package/dist/sessions/codex.js +745 -0
  79. package/dist/sessions/command.d.ts +127 -0
  80. package/dist/sessions/command.js +419 -0
  81. package/dist/sessions/copilot.d.ts +40 -0
  82. package/dist/sessions/copilot.js +432 -0
  83. package/dist/sessions/coverage.d.ts +19 -0
  84. package/dist/sessions/coverage.js +56 -0
  85. package/dist/sessions/cursor.d.ts +24 -0
  86. package/dist/sessions/cursor.js +324 -0
  87. package/dist/sessions/derive.d.ts +57 -0
  88. package/dist/sessions/derive.js +386 -0
  89. package/dist/sessions/exclusion.d.ts +87 -0
  90. package/dist/sessions/exclusion.js +361 -0
  91. package/dist/sessions/extract.d.ts +181 -0
  92. package/dist/sessions/extract.js +1806 -0
  93. package/dist/sessions/fold.d.ts +17 -0
  94. package/dist/sessions/fold.js +71 -0
  95. package/dist/sessions/frame.d.ts +24 -0
  96. package/dist/sessions/frame.js +95 -0
  97. package/dist/sessions/gemini.d.ts +37 -0
  98. package/dist/sessions/gemini.js +613 -0
  99. package/dist/sessions/gold-arms.d.ts +67 -0
  100. package/dist/sessions/gold-arms.js +172 -0
  101. package/dist/sessions/gold.d.ts +197 -0
  102. package/dist/sessions/gold.js +463 -0
  103. package/dist/sessions/graph.d.ts +12 -0
  104. package/dist/sessions/graph.js +34 -0
  105. package/dist/sessions/grok.d.ts +36 -0
  106. package/dist/sessions/grok.js +421 -0
  107. package/dist/sessions/hook-calls.d.ts +3 -0
  108. package/dist/sessions/hook-calls.js +47 -0
  109. package/dist/sessions/hook-index.d.ts +66 -0
  110. package/dist/sessions/hook-index.js +164 -0
  111. package/dist/sessions/hooks.d.ts +17 -0
  112. package/dist/sessions/hooks.js +281 -0
  113. package/dist/sessions/identity.d.ts +27 -0
  114. package/dist/sessions/identity.js +131 -0
  115. package/dist/sessions/install.d.ts +70 -0
  116. package/dist/sessions/install.js +246 -0
  117. package/dist/sessions/lineage.d.ts +19 -0
  118. package/dist/sessions/lineage.js +118 -0
  119. package/dist/sessions/mcp.d.ts +7 -0
  120. package/dist/sessions/mcp.js +157 -0
  121. package/dist/sessions/merkle.d.ts +7 -0
  122. package/dist/sessions/merkle.js +28 -0
  123. package/dist/sessions/motif.d.ts +44 -0
  124. package/dist/sessions/motif.js +59 -0
  125. package/dist/sessions/opencode.d.ts +33 -0
  126. package/dist/sessions/opencode.js +319 -0
  127. package/dist/sessions/otel.d.ts +60 -0
  128. package/dist/sessions/otel.js +494 -0
  129. package/dist/sessions/package-books.d.ts +48 -0
  130. package/dist/sessions/package-books.js +120 -0
  131. package/dist/sessions/package-resolve.d.ts +32 -0
  132. package/dist/sessions/package-resolve.js +185 -0
  133. package/dist/sessions/pending.d.ts +34 -0
  134. package/dist/sessions/pending.js +142 -0
  135. package/dist/sessions/pi.d.ts +36 -0
  136. package/dist/sessions/pi.js +353 -0
  137. package/dist/sessions/pool-books.d.ts +21 -0
  138. package/dist/sessions/pool-books.js +75 -0
  139. package/dist/sessions/pool-choices.d.ts +5 -0
  140. package/dist/sessions/pool-choices.js +11 -0
  141. package/dist/sessions/pool-command.d.ts +115 -0
  142. package/dist/sessions/pool-command.js +669 -0
  143. package/dist/sessions/pool-delta.d.ts +54 -0
  144. package/dist/sessions/pool-delta.js +190 -0
  145. package/dist/sessions/pool-nightly-deps.d.ts +3 -0
  146. package/dist/sessions/pool-nightly-deps.js +11 -0
  147. package/dist/sessions/pool-nightly.d.ts +10 -0
  148. package/dist/sessions/pool-nightly.js +85 -0
  149. package/dist/sessions/pool-outbox.d.ts +76 -0
  150. package/dist/sessions/pool-outbox.js +313 -0
  151. package/dist/sessions/pool-print.d.ts +26 -0
  152. package/dist/sessions/pool-print.js +68 -0
  153. package/dist/sessions/pool-raw.d.ts +27 -0
  154. package/dist/sessions/pool-raw.js +68 -0
  155. package/dist/sessions/pool-replica.d.ts +35 -0
  156. package/dist/sessions/pool-replica.js +91 -0
  157. package/dist/sessions/pool-send-lock.d.ts +20 -0
  158. package/dist/sessions/pool-send-lock.js +87 -0
  159. package/dist/sessions/pool-sent.d.ts +4 -0
  160. package/dist/sessions/pool-sent.js +26 -0
  161. package/dist/sessions/pool-totals.d.ts +10 -0
  162. package/dist/sessions/pool-totals.js +21 -0
  163. package/dist/sessions/pool-wire.d.ts +435 -0
  164. package/dist/sessions/pool-wire.js +1100 -0
  165. package/dist/sessions/pool.d.ts +32 -0
  166. package/dist/sessions/pool.js +164 -0
  167. package/dist/sessions/projection.d.ts +75 -0
  168. package/dist/sessions/projection.js +165 -0
  169. package/dist/sessions/public-vocabulary.d.ts +72 -0
  170. package/dist/sessions/public-vocabulary.js +295 -0
  171. package/dist/sessions/query.d.ts +123 -0
  172. package/dist/sessions/query.js +268 -0
  173. package/dist/sessions/read-adoption.d.ts +2 -0
  174. package/dist/sessions/read-adoption.js +27 -0
  175. package/dist/sessions/read-call-shape.d.ts +2 -0
  176. package/dist/sessions/read-call-shape.js +25 -0
  177. package/dist/sessions/read-change.d.ts +2 -0
  178. package/dist/sessions/read-change.js +30 -0
  179. package/dist/sessions/read-cost.d.ts +4 -0
  180. package/dist/sessions/read-cost.js +48 -0
  181. package/dist/sessions/read-day-move.d.ts +3 -0
  182. package/dist/sessions/read-day-move.js +65 -0
  183. package/dist/sessions/read-fit.d.ts +7 -0
  184. package/dist/sessions/read-fit.js +41 -0
  185. package/dist/sessions/read-interaction.d.ts +5 -0
  186. package/dist/sessions/read-interaction.js +62 -0
  187. package/dist/sessions/read-lead-lag.d.ts +15 -0
  188. package/dist/sessions/read-lead-lag.js +81 -0
  189. package/dist/sessions/read-live.d.ts +6 -0
  190. package/dist/sessions/read-live.js +38 -0
  191. package/dist/sessions/read-method.d.ts +2 -0
  192. package/dist/sessions/read-method.js +29 -0
  193. package/dist/sessions/read-next-step.d.ts +2 -0
  194. package/dist/sessions/read-next-step.js +35 -0
  195. package/dist/sessions/read-order.d.ts +54 -0
  196. package/dist/sessions/read-order.js +62 -0
  197. package/dist/sessions/read-readable.d.ts +8 -0
  198. package/dist/sessions/read-readable.js +24 -0
  199. package/dist/sessions/read-recovery-edge.d.ts +2 -0
  200. package/dist/sessions/read-recovery-edge.js +26 -0
  201. package/dist/sessions/read-recovery.d.ts +20 -0
  202. package/dist/sessions/read-recovery.js +97 -0
  203. package/dist/sessions/read-rows.d.ts +13 -0
  204. package/dist/sessions/read-rows.js +47 -0
  205. package/dist/sessions/read-substitution.d.ts +5 -0
  206. package/dist/sessions/read-substitution.js +51 -0
  207. package/dist/sessions/read-support.d.ts +48 -0
  208. package/dist/sessions/read-support.js +73 -0
  209. package/dist/sessions/read-tool-day.d.ts +2 -0
  210. package/dist/sessions/read-tool-day.js +26 -0
  211. package/dist/sessions/read-version-boundary.d.ts +31 -0
  212. package/dist/sessions/read-version-boundary.js +95 -0
  213. package/dist/sessions/read.d.ts +86 -0
  214. package/dist/sessions/read.js +87 -0
  215. package/dist/sessions/recall-bundle.d.ts +12 -0
  216. package/dist/sessions/recall-bundle.js +33 -0
  217. package/dist/sessions/recall-consequence.d.ts +45 -0
  218. package/dist/sessions/recall-consequence.js +179 -0
  219. package/dist/sessions/recall-frames.d.ts +18 -0
  220. package/dist/sessions/recall-frames.js +44 -0
  221. package/dist/sessions/recall-next.d.ts +145 -0
  222. package/dist/sessions/recall-next.js +241 -0
  223. package/dist/sessions/recall-object.d.ts +139 -0
  224. package/dist/sessions/recall-object.js +78 -0
  225. package/dist/sessions/recall-pool.d.ts +19 -0
  226. package/dist/sessions/recall-pool.js +96 -0
  227. package/dist/sessions/recall-push.d.ts +26 -0
  228. package/dist/sessions/recall-push.js +47 -0
  229. package/dist/sessions/recall-reads.d.ts +124 -0
  230. package/dist/sessions/recall-reads.js +417 -0
  231. package/dist/sessions/recall-render.d.ts +19 -0
  232. package/dist/sessions/recall-render.js +168 -0
  233. package/dist/sessions/recovery-calls.d.ts +21 -0
  234. package/dist/sessions/recovery-calls.js +25 -0
  235. package/dist/sessions/redact.d.ts +17 -0
  236. package/dist/sessions/redact.js +113 -0
  237. package/dist/sessions/repo-root.d.ts +10 -0
  238. package/dist/sessions/repo-root.js +101 -0
  239. package/dist/sessions/schema.d.ts +694 -0
  240. package/dist/sessions/schema.js +167 -0
  241. package/dist/sessions/session-shape.d.ts +76 -0
  242. package/dist/sessions/session-shape.js +355 -0
  243. package/dist/sessions/sources.d.ts +50 -0
  244. package/dist/sessions/sources.js +233 -0
  245. package/dist/sessions/steps.d.ts +105 -0
  246. package/dist/sessions/steps.js +250 -0
  247. package/dist/sessions/store.d.ts +309 -0
  248. package/dist/sessions/store.js +1498 -0
  249. package/dist/sessions/strata.d.ts +26 -0
  250. package/dist/sessions/strata.js +77 -0
  251. package/dist/sessions/survival.d.ts +27 -0
  252. package/dist/sessions/survival.js +230 -0
  253. package/dist/sessions/transcript-tail.d.ts +14 -0
  254. package/dist/sessions/transcript-tail.js +64 -0
  255. package/dist/sessions/verify.d.ts +53 -0
  256. package/dist/sessions/verify.js +286 -0
  257. package/dist/sessions/vocab-load.d.ts +26 -0
  258. package/dist/sessions/vocab-load.js +61 -0
  259. package/dist/sessions/vscode-copilot.d.ts +38 -0
  260. package/dist/sessions/vscode-copilot.js +494 -0
  261. package/dist/telemetry/command.d.ts +8 -0
  262. package/dist/telemetry/command.js +85 -0
  263. package/dist/telemetry/consent.d.ts +17 -0
  264. package/dist/telemetry/consent.js +63 -0
  265. package/dist/telemetry/crash.d.ts +8 -0
  266. package/dist/telemetry/crash.js +52 -0
  267. package/dist/telemetry/emit.d.ts +31 -0
  268. package/dist/telemetry/emit.js +42 -0
  269. package/dist/telemetry/fields.d.ts +16 -0
  270. package/dist/telemetry/fields.js +26 -0
  271. package/dist/telemetry/health.d.ts +21 -0
  272. package/dist/telemetry/health.js +108 -0
  273. package/dist/telemetry/hook-emit.d.ts +4 -0
  274. package/dist/telemetry/hook-emit.js +18 -0
  275. package/dist/telemetry/measures.d.ts +32 -0
  276. package/dist/telemetry/measures.js +128 -0
  277. package/dist/telemetry/observe.d.ts +13 -0
  278. package/dist/telemetry/observe.js +51 -0
  279. package/dist/telemetry/read-events.d.ts +9 -0
  280. package/dist/telemetry/read-events.js +17 -0
  281. package/dist/telemetry/record.d.ts +17 -0
  282. package/dist/telemetry/record.js +26 -0
  283. package/dist/telemetry/report.d.ts +77 -0
  284. package/dist/telemetry/report.js +188 -0
  285. package/dist/telemetry/schema.d.ts +40 -0
  286. package/dist/telemetry/schema.js +187 -0
  287. package/dist/telemetry/send-main.d.ts +1 -0
  288. package/dist/telemetry/send-main.js +11 -0
  289. package/dist/telemetry/send.d.ts +36 -0
  290. package/dist/telemetry/send.js +172 -0
  291. package/dist/telemetry/spool.d.ts +17 -0
  292. package/dist/telemetry/spool.js +70 -0
  293. package/dist/telemetry/wire.d.ts +25 -0
  294. package/dist/telemetry/wire.js +76 -0
  295. package/package.json +4 -5
  296. package/vocab/crates.json +1 -0
  297. package/vocab/go.json +1 -0
  298. package/vocab/harness.json +1 -0
  299. package/vocab/homebrew.json +1 -0
  300. package/vocab/manifest.json +1 -0
  301. package/vocab/maven.json +1 -0
  302. package/vocab/models.json +1 -0
  303. package/vocab/npm.json +1 -0
  304. package/vocab/programs.json +1 -0
  305. package/vocab/pypi.json +1 -0
  306. package/vocab/rubygems.json +1 -0
  307. package/dist/commands/deploy-input.d.ts +0 -38
  308. package/dist/commands/deploy-input.js +0 -150
  309. package/dist/commands/deploy.d.ts +0 -22
  310. package/dist/commands/deploy.js +0 -124
  311. package/dist/commands/status.d.ts +0 -7
  312. package/dist/commands/status.js +0 -18
  313. package/dist/commands/templates.d.ts +0 -11
  314. package/dist/commands/templates.js +0 -32
  315. package/dist/commands/test-charge.d.ts +0 -11
  316. package/dist/commands/test-charge.js +0 -34
  317. package/dist/journal.d.ts +0 -26
  318. package/dist/journal.js +0 -58
  319. package/parity/deploy-plans/paid-api.json +0 -34
  320. package/parity/deploy-plans/per-task-agent.json +0 -34
@@ -0,0 +1,494 @@
1
+ // The VS Code Copilot Chat source: the chat sessions VS Code itself writes become the same capture records a Claude Code transcript does. This is
2
+ // the editor's own serialized chat model, not the Copilot CLI's event log (`copilot.ts`): a different surface with a different shape.
3
+ //
4
+ // FIXTURE-DERIVED: written from the public source of microsoft/vscode @ f92b8f3adcd9bc034e6b9ade05c093dd59b99c5f (MIT; the Copilot Chat extension lives
5
+ // in-tree at `extensions/copilot/`), and never run against a live store. Nothing was copied. Paths below are under `src/vs/workbench/contrib/chat/common/`.
6
+ // No vendor text promises the format; the log form is behind `chat.useLogSessionStorage` with a `remove after stabilizing` note (model/chatSessionStore.ts:739),
7
+ // and the schema is versioned (`version: 3`, with 1 and 2 migrated, model/chatModel.ts:2593).
8
+ //
9
+ // Where it reads from (`vscodeAppData`): the folder that holds one folder per VS Code build, named by the build's `nameShort`: `%APPDATA%` on Windows,
10
+ // `~/Library/Application Support` on macOS, `$XDG_CONFIG_HOME` (else `~/.config`) on Linux; `VSCODE_APPDATA` replaces it
11
+ // (platform/environment/node/userDataPath.ts:57-109). The builds read are `Code` (Stable), `Code - Insiders`, and the pinned source's own `Code - OSS`; the first two names
12
+ // come from Microsoft's release `product.json`, not from the pin. A build with another `nameShort`, a portable install, or `--user-data-dir` is not discovered.
13
+ // Inside a build's `User` folder:
14
+ // workspaceStorage/<hash>/chatSessions/<id>.json|.jsonl a workspace's sessions (model/chatSessionStore.ts:72-76, :738-741)
15
+ // workspaceStorage/<hash>/workspace.json `{folder}` or `{workspace}`, a uri: how the hash leads to a working directory
16
+ // (platform/storage/electron-main/storageMain.ts:425,481-492)
17
+ // globalStorage/emptyWindowChatSessions/<id>.json|.jsonl sessions from a window with no folder (no working directory)
18
+ // workspaceStorage/no-workspace/chatSessions the older spelling of the last one, read the same way
19
+ // globalStorage/transferredChatSessions NOT READ: a hand-off written when a session moves to another workspace, removed once read and expired after five
20
+ // minutes (chatSessionStore.ts:79,275,316); the destination workspace then holds its own copy
21
+ // A session moved between workspaces is copied and the original kept (chatSessionStore.ts:150-175), so one id can sit in two folders: the newest copy is read.
22
+ //
23
+ // `.json` is one `JSON.stringify(ISerializableChatData)`. `.jsonl` is an append log, one entry per line (model/objectMutationLog.ts:195-215, 345-390):
24
+ // `{kind:0,v}` the initial value, `{kind:1,k,v}` a set at path `k`, `{kind:2,k,v,i}` a push (the array is cut to `i` first), `{kind:3,k}` a delete. VS Code reads the
25
+ // `.jsonl` first and the `.json` only when that is unreadable (chatSessionStore.ts:655-672), and so does this. VS Code throws on a malformed log line; this skips it and
26
+ // counts it, so the deposit line reports it. Like codex.ts, a session is written out as the transcript lines `captureSession` reads, so redaction, the
27
+ // 1,000-character line cap, the salted hashes, and the skeleton are the Claude Code path's own code.
28
+ //
29
+ // One request is one prompt and one response (ISerializableChatRequestData, model/chatModel.ts:2230-2259):
30
+ // message `message.text`, or the string itself (old format): the prompt. A request with `isSystemInitiated`, `requestHiddenFromTranscript`, or
31
+ // `hiddenFromTranscript` is the harness writing, not the operator, and its prompt is not read; its response still is
32
+ // timestamp epoch milliseconds, `responseTimestamp` for the response. A missing or implausible one is never made up: the time carries forward
33
+ // modelId the model
34
+ // agent `agent.extensionVersion` (participants/chatAgents.ts:71) is the Copilot Chat extension's version, the harness version, read only when `agent.extensionId.value` is `GitHub.copilot-chat` (a third-party participant carries its own): the newest request that names one
35
+ // response[] a markdown part is a bare `{value}` (a string in the old format); `kind:'thinking'` is skipped;
36
+ // `kind:'toolInvocationSerialized'` is a tool call (chatService/chatService.ts:1246-1270); `kind:'textEditGroup'` or `'notebookEditGroup'` `{uri}` is a file edited
37
+ // usage `completionTokens` on the request, the whole turn's output (model/chatModel.ts:1929-1934, 2086), and `modelTotals[{model, inputTokens, cachedTokens, outputTokens}]`
38
+ // on the request too (chatService.ts:176-188; chatModel.ts:2093), the only per-model figure. Neither is under a `usage` key. `promptTokens` is the last model call alone, so it is not read as consumption. The source does not
39
+ // say whether `inputTokens` includes `cachedTokens`, so it is not subtracted: that is a stated limit (a store that includes it counts the cache twice).
40
+ // `modelTotals` is rare: the extension API bridge hands the editor only promptTokens, completionTokens, outputBuffer, copilotCredits, and promptTokenDetails
41
+ // (api/common/extHostChatAgents2.ts:463-468), and a public code search found `modelTotals` written only by the agent-host adapter
42
+ // (contrib/chat/browser/agentSessions/agentHost/stateToProgressAdapter.ts:936). So on an extension-written session, which is the surface this reader exists
43
+ // for, `input` 0 with a nonzero `output` means the input is NOT KNOWN, not that none was spent
44
+ // A tool call's `toolId` is the bare name (`run_in_terminal`) or the contributed one (`copilot_applyPatch`), both in extensions/copilot/src/extension/tools/common/toolNames.ts.
45
+ // A call with `subAgentInvocationId` is a subagent's own and is not read; the parent's `runSubagent` call is. Tool inputs are not persisted (model/chatProgressTypes/chatToolInvocation.ts:504-533),
46
+ // so only a terminal command (`toolSpecificData.commandLine`) and an edit's file (the `textEditGroup` after the call) are known. A shell call's outcome is its exit code
47
+ // alone (`terminalCommandState.exitCode`, chatService.ts:803-807), never a status field; with none and no error, it is unknown and stays `none`.
48
+ // A refusal is `isConfirmed` `{type:0}` (Denied) or `{type:5}` (Skipped) with a `source` (ToolConfirmKind, chatService.ts:901-913), or `false` (before VS Code 1.104). The Skip
49
+ // button of a confirmation is the decline button and writes `{type:5, source:'user'}` (browser/widget/chatContentParts/toolInvocationParts/abstractToolConfirmationSubPart.ts:225,
50
+ // chatTerminalToolConfirmationSubPart.ts:380,507; browser/tools/languageModelToolsService.ts:711), so a user's refusal (`source` `user`) reads as Claude Code's refusal text and
51
+ // stores `user-rejected`; a hook's or a risk assessment's (languageModelToolsService.ts:683,1051) reads as a permission denial. A `{type:5}` with no `source` is the skip after
52
+ // approval (model/chatProgressTypes/chatToolInvocation.ts:516) and has no outcome. The file holds no working-directory change;
53
+ // its one harness version is the Copilot Chat agent's `extensionVersion` (see `agent` above).
54
+ import { readdir, readFile, stat } from 'node:fs/promises';
55
+ import { homedir } from 'node:os';
56
+ import { basename, dirname, join } from 'node:path';
57
+ import { fileURLToPath } from 'node:url';
58
+ /** The folder that holds one folder per VS Code build; a test passes its own so it never reads the real one. */
59
+ export const vscodeAppData = (platform, env, home = homedir()) => {
60
+ const set = env.VSCODE_APPDATA;
61
+ if (set !== undefined && set !== '')
62
+ return set;
63
+ if (platform === 'win32')
64
+ return env.APPDATA || join(home, 'AppData', 'Roaming');
65
+ if (platform === 'darwin')
66
+ return join(home, 'Library', 'Application Support');
67
+ return env.XDG_CONFIG_HOME || join(home, '.config');
68
+ };
69
+ /** The `nameShort` of each build read. */
70
+ const SHELL_TOOLS = new Set(['run_in_terminal', 'run_task', 'create_and_run_task']);
71
+ const CHANNELS = ['Code', 'Code - Insiders', 'Code - OSS'];
72
+ // ExtensionIdentifier compares ids case-insensitively, so this is the lowercase form of `GitHub.copilot-chat`.
73
+ const COPILOT_CHAT_EXTENSION_ID = 'github.copilot-chat';
74
+ const isObj = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
75
+ const str = (value) => (typeof value === 'string' && value !== '' ? value : undefined);
76
+ const num = (value) => (typeof value === 'number' && Number.isFinite(value) ? value : undefined);
77
+ const parse = (text) => {
78
+ try {
79
+ return JSON.parse(text);
80
+ }
81
+ catch {
82
+ return undefined;
83
+ }
84
+ };
85
+ /** A `file:` uri string as a path; undefined for any other scheme or a string that is no uri. */
86
+ const fileUriPath = (value) => {
87
+ const text = str(value);
88
+ if (text === undefined || !text.startsWith('file:'))
89
+ return undefined;
90
+ try {
91
+ return fileURLToPath(text);
92
+ }
93
+ catch {
94
+ return undefined;
95
+ }
96
+ };
97
+ /** The working directory a `workspaceStorage/<hash>/workspace.json` names: a folder, or the folder that holds a `.code-workspace` file. A remote uri is none. */
98
+ const workspaceFolder = (text) => {
99
+ const entry = parse(text);
100
+ if (!isObj(entry))
101
+ return null;
102
+ const folder = fileUriPath(entry.folder);
103
+ if (folder !== undefined)
104
+ return folder;
105
+ const file = fileUriPath(entry.workspace);
106
+ return file === undefined ? null : dirname(file);
107
+ };
108
+ /** The path of a serialized uri (`{scheme, path, fsPath?}`, or a string) when it is a local file. */
109
+ const editPath = (uri) => {
110
+ if (typeof uri === 'string')
111
+ return fileUriPath(uri);
112
+ if (!isObj(uri) || (str(uri.scheme) !== undefined && uri.scheme !== 'file'))
113
+ return undefined;
114
+ const fsPath = str(uri.fsPath);
115
+ if (fsPath !== undefined)
116
+ return fsPath;
117
+ const path = str(uri.path);
118
+ // A Windows file uri's path is `/c:/dir/file`.
119
+ return path === undefined ? undefined : /^\/[A-Za-z]:/.test(path) ? path.slice(1) : path;
120
+ };
121
+ const SAFE_ID = /^(?!\.+$)[A-Za-z0-9._-]+$/;
122
+ /** An error's message with `file` cut to its base name, so a reason that is logged names no folder. */
123
+ const reasonOf = (error, file) => (error instanceof Error ? error.message : String(error)).split(file).join(basename(file));
124
+ const missing = (error) => error instanceof Error && 'code' in error && (error.code === 'ENOENT' || error.code === 'ENOTDIR');
125
+ /** The non-blank log lines that are not JSON (a torn write), kept so `captureSession` counts them and the deposit line reports them. */
126
+ export const chatBadLines = (text) => text.split('\n').filter((raw) => raw.trim() !== '' && parse(raw) === undefined);
127
+ /** One step down a mutation-log path: an object's key or an array's index; anything else leads nowhere. */
128
+ const child = (node, key) => (isObj(node) ? (Object.hasOwn(node, key) ? node[key] : undefined) : Array.isArray(node) ? node[Number(key)] : undefined);
129
+ /** A path key that would reach `Object.prototype` or a constructor; file content must never write through one. */
130
+ const UNSAFE_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
131
+ /** Replays one mutation-log entry onto `state` (objectMutationLog.ts:345-390); an entry whose path does not lead anywhere is dropped. */
132
+ const apply = (state, entry) => {
133
+ if (entry.kind === 0)
134
+ return entry.v;
135
+ const path = Array.isArray(entry.k) ? entry.k : [];
136
+ if (path.some((key) => UNSAFE_KEYS.has(String(key))))
137
+ return state;
138
+ if (path.length === 0)
139
+ return entry.kind === 1 ? entry.v : state;
140
+ let holder = state;
141
+ for (const key of path.slice(0, -1))
142
+ holder = child(holder, String(key));
143
+ const last = String(path[path.length - 1]);
144
+ if (!isObj(holder) && !Array.isArray(holder))
145
+ return state;
146
+ const write = (value) => {
147
+ if (isObj(holder))
148
+ holder[last] = value;
149
+ else if (Array.isArray(holder))
150
+ holder[Number(last)] = value;
151
+ };
152
+ if (entry.kind === 1)
153
+ write(entry.v);
154
+ else if (entry.kind === 3)
155
+ Reflect.deleteProperty(holder, last);
156
+ else if (entry.kind === 2) {
157
+ const current = child(holder, last);
158
+ const target = Array.isArray(current) ? current : [];
159
+ const cut = num(entry.i);
160
+ if (cut !== undefined)
161
+ target.length = Math.min(Math.max(cut, 0), target.length);
162
+ const added = Array.isArray(entry.v) ? entry.v : [];
163
+ for (const item of added)
164
+ target.push(item);
165
+ write(target);
166
+ }
167
+ return state;
168
+ };
169
+ /** The chat data a file holds: the `.json` form is the object itself, the `.jsonl` form is replayed. Throws when nothing parses. */
170
+ export const chatData = (text, jsonl) => {
171
+ if (!jsonl) {
172
+ const whole = parse(text);
173
+ if (!isObj(whole))
174
+ throw new Error('no parseable chat data');
175
+ return whole;
176
+ }
177
+ const entries = text.split('\n').flatMap((raw) => {
178
+ const entry = raw.trim() === '' ? undefined : parse(raw);
179
+ return isObj(entry) ? [entry] : [];
180
+ });
181
+ if (entries.length === 0 && text.trim() !== '')
182
+ throw new Error('no parseable line');
183
+ let state = {};
184
+ for (const entry of entries)
185
+ state = apply(state, entry);
186
+ if (!isObj(state))
187
+ throw new Error('no parseable chat data');
188
+ return state;
189
+ };
190
+ const MIN_MS = Date.parse('2000-01-01T00:00:00Z');
191
+ /** An absolute time only: a missing or relative one (not a date after 2000) says nothing about when. */
192
+ const isoOf = (value) => {
193
+ const ms = num(value);
194
+ return ms !== undefined && ms >= MIN_MS ? new Date(ms).toISOString() : undefined;
195
+ };
196
+ /** Tools that edit a file, under both spellings (toolNames.ts: `ToolName` and `ContributedToolName`). */
197
+ const EDIT_TOOLS = new Set(['apply_patch', 'copilot_applyPatch', 'replace_string_in_file', 'copilot_replaceString', 'multi_replace_string_in_file', 'copilot_multiReplaceString', 'insert_edit_into_file', 'copilot_insertEdit', 'edit_notebook_file', 'copilot_editNotebook']);
198
+ const WRITE_TOOLS = new Set(['create_file', 'copilot_createFile', 'create_new_jupyter_notebook', 'copilot_createNewJupyterNotebook']);
199
+ /** A failed shell call's error: the exit code leads, as a Claude Code Bash result's does, so its `error-line` joins the same signature. A negative code is no exit status. */
200
+ const failureOf = (exit, text) => (exit < 0 && text.trim() !== '' ? text : `Exit code ${String(exit)}\n${text}`);
201
+ const USER_REFUSAL = "The user doesn't want to proceed with this tool use. The tool use was rejected";
202
+ /** What a tool's own error field says: a string, or `true` for a failure with no words. */
203
+ const errorText = (part) => {
204
+ if (typeof part.resultError === 'string' && part.resultError !== '')
205
+ return part.resultError;
206
+ if (part.resultError === true)
207
+ return 'tool failed';
208
+ if (!isObj(part.resultDetails) || part.resultDetails.isError !== true)
209
+ return undefined;
210
+ const output = part.resultDetails.output;
211
+ const embeds = Array.isArray(output) ? output : [];
212
+ // `output` is an array of `{type:'embed', value, isText}` (common/tools/languageModelToolsService.ts:290-296, written as an array at contrib/mcp/common/
213
+ // mcpLanguageModelToolContribution.ts:279-280): the first text embed is the message. The guard also accepts a string (`isToolResultInputOutputDetails`, :305), though the declared type allows only the array.
214
+ const embed = embeds.find((entry) => isObj(entry) && entry.isText === true && str(entry.value) !== undefined);
215
+ return (isObj(embed) ? str(embed.value) : str(output)) ?? 'tool failed';
216
+ };
217
+ /** One chat session as the lines `captureSession` reads. */
218
+ export const chatLines = (data, fallbackId, folder) => {
219
+ const requests = Array.isArray(data.requests) ? data.requests.filter(isObj) : [];
220
+ const lines = [];
221
+ const base = { sessionId: str(data.sessionId) ?? fallbackId };
222
+ // The session's own working directory (a sessions-window field, ISerializableChatData3.workingDirectory) wins over its workspace's folder.
223
+ const cwd = fileUriPath(data.workingDirectory) ?? folder;
224
+ if (cwd !== null)
225
+ base.cwd = cwd;
226
+ let at = isoOf(data.creationDate);
227
+ let sequence = 0;
228
+ const stamp = (value) => {
229
+ const candidate = isoOf(value);
230
+ if (candidate !== undefined && (at === undefined || Date.parse(candidate) >= Date.parse(at)))
231
+ at = candidate;
232
+ return at ?? new Date(0).toISOString();
233
+ };
234
+ for (const request of requests) {
235
+ const when = stamp(request.timestamp);
236
+ // The extension's version, from the agent VS Code serialized beside the request (ISerializableChatAgentData.extensionVersion); a later request supersedes. A
237
+ // third-party chat participant is serialized the same way with its own extension's version, so only Copilot Chat's own agent is read (id compared case-insensitively).
238
+ const extensionVersion = isObj(request.agent) && isObj(request.agent.extensionId) && str(request.agent.extensionId.value)?.toLowerCase() === COPILOT_CHAT_EXTENSION_ID ? str(request.agent.extensionVersion) : undefined;
239
+ if (extensionVersion !== undefined)
240
+ base.version = extensionVersion;
241
+ const message = request.message;
242
+ const text = typeof message === 'string' ? str(message) : isObj(message) ? str(message.text) : undefined;
243
+ const hidden = request.isSystemInitiated === true || request.requestHiddenFromTranscript === true || request.hiddenFromTranscript === true;
244
+ sequence += 1;
245
+ if (text !== undefined && !hidden)
246
+ lines.push({ ...base, type: 'user', uuid: `${fallbackId}-u${String(sequence)}`, timestamp: when, message: { role: 'user', content: [{ type: 'text', text }] } });
247
+ const content = [];
248
+ // Each call's result, in call order, written after the response line: a tool call has its result in the next transcript line.
249
+ const results = [];
250
+ let pending = '';
251
+ let current;
252
+ const flush = () => {
253
+ if (pending !== '')
254
+ content.push({ type: 'text', text: pending });
255
+ pending = '';
256
+ };
257
+ for (const part of Array.isArray(request.response) ? request.response : []) {
258
+ if (typeof part === 'string')
259
+ pending += part;
260
+ else if (!isObj(part))
261
+ continue;
262
+ else if (part.kind === undefined && typeof part.value === 'string')
263
+ pending += part.value;
264
+ else if (part.kind === 'textEditGroup' || part.kind === 'notebookEditGroup') {
265
+ const path = editPath(part.uri);
266
+ if (current === undefined || path === undefined)
267
+ continue;
268
+ if (current.edits === 0)
269
+ current.block.input = { file_path: path };
270
+ else {
271
+ const id = `${current.id}:${String(current.edits + 1)}`;
272
+ content.push({ type: 'tool_use', id, name: current.block.name, native: current.block.native, input: { file_path: path } });
273
+ if (current.known)
274
+ results.push({ id, failure: current.failure, output: '', when: current.when });
275
+ }
276
+ current.edits += 1;
277
+ }
278
+ else if (part.kind === 'toolInvocationSerialized') {
279
+ // A subagent's own call is inline in the parent's response; the parent's `runSubagent` call is the record.
280
+ current = undefined;
281
+ if (str(part.subAgentInvocationId) !== undefined)
282
+ continue;
283
+ const toolId = str(part.toolId) ?? 'unknown';
284
+ const id = str(part.toolCallId) ?? `${fallbackId}-t${String(sequence)}-${String(content.length)}`;
285
+ const specific = isObj(part.toolSpecificData) ? part.toolSpecificData : {};
286
+ const terminal = specific.kind === 'terminal' && isObj(specific.commandLine) ? specific.commandLine : undefined;
287
+ const block = { type: 'tool_use', id, name: toolId, input: {} };
288
+ const isShell = terminal !== undefined || SHELL_TOOLS.has(toolId);
289
+ if (isShell) {
290
+ const command = terminal === undefined ? undefined : (str(terminal.userEdited) ?? str(terminal.toolEdited) ?? str(terminal.original));
291
+ block.native = toolId;
292
+ block.name = 'Bash';
293
+ block.input = command === undefined ? {} : { command };
294
+ }
295
+ else if (EDIT_TOOLS.has(toolId) || WRITE_TOOLS.has(toolId)) {
296
+ block.native = toolId;
297
+ block.name = WRITE_TOOLS.has(toolId) ? 'Write' : 'Edit';
298
+ }
299
+ flush();
300
+ content.push(block);
301
+ const confirmed = part.isConfirmed;
302
+ // `Denied` (0) is a refusal; `Skipped` (5) with a `source` is one too, since the Skip button of a confirmation writes `{type:5, source:'user'}` and a risk assessment's
303
+ // denial `{type:5, source:'riskAssessment'}`; a `Skipped` with no source is the skip after approval (chatToolInvocation.ts:516), which names no outcome.
304
+ const refusedBy = isObj(confirmed) && (confirmed.type === 0 || confirmed.type === 5) ? str(confirmed.source) : undefined;
305
+ const denied = confirmed === false || (isObj(confirmed) && (confirmed.type === 0 || (confirmed.type === 5 && refusedBy !== undefined)));
306
+ const skipped = isObj(confirmed) && confirmed.type === 5 && refusedBy === undefined;
307
+ const callWhen = stamp(part.completedAt);
308
+ const output = isObj(specific.terminalCommandOutput) ? (str(specific.terminalCommandOutput.text) ?? '') : '';
309
+ const exit = isObj(specific.terminalCommandState) ? num(specific.terminalCommandState.exitCode) : undefined;
310
+ const error = errorText(part);
311
+ let failure;
312
+ let known = true;
313
+ if (denied) {
314
+ // A user's refusal (`source` `user`, or the boolean `false` of VS Code before 1.104) is Claude Code's own wording; a hook's or a risk assessment's is a
315
+ // permission denial. A denial with no `source` names no actor (chatService.ts:910), so it claims neither.
316
+ failure = confirmed === false || refusedBy === 'user' ? USER_REFUSAL : refusedBy === undefined ? 'The tool call was denied' : `Permission to use ${toolId} has been denied`;
317
+ }
318
+ else if (skipped)
319
+ known = false;
320
+ else if (isShell && exit !== undefined)
321
+ failure = exit === 0 ? undefined : failureOf(exit, output === '' ? (error ?? '') : output);
322
+ else if (error !== undefined)
323
+ failure = error;
324
+ // `toJSON` always writes `isComplete: true`; a call that never finished carries `summary.kind` `incomplete` and, before confirmation, no `isConfirmed`
325
+ // (model/chatProgressTypes/chatToolInvocation.ts:508,516-517). Neither says it succeeded.
326
+ else if (isShell || confirmed === undefined || (isObj(part.summary) && part.summary.kind === 'incomplete'))
327
+ known = false;
328
+ if (known)
329
+ results.push({ id, failure, output: isShell ? output : '', when: callWhen });
330
+ if (block.name === 'Edit' || block.name === 'Write')
331
+ current = { block, id, failure, known, edits: 0, when: callWhen };
332
+ }
333
+ }
334
+ flush();
335
+ const totals = Array.isArray(request.modelTotals) ? request.modelTotals.filter(isObj) : [];
336
+ const completion = num(request.completionTokens);
337
+ const sum = (field) => totals.reduce((total, entry) => total + (num(entry[field]) ?? 0), 0);
338
+ // `inputTokens` is taken whole: the source does not say whether it includes `cachedTokens`. A zero `inputTokens` means unknown (the extension API bridge never reports one,
339
+ // so the zero is a default), so it is left out rather than written as a measured zero. `cachedTokens` and `outputTokens` are values: a zero is kept when a total carried the
340
+ // field, since a request with no cache hit is a measured zero, and only a field no total carried is left out. When no category is left, the request has no usage.
341
+ // An all-zero `outputTokens` beside a positive `completionTokens` is the bridge's default beside the real count, so the completion count wins over that zero.
342
+ const carried = (field) => totals.some((entry) => num(entry[field]) !== undefined);
343
+ const fromCompletion = completion !== undefined && completion > 0 ? { output_tokens: completion } : {};
344
+ const fromTotals = {
345
+ ...(sum('inputTokens') > 0 ? { input_tokens: sum('inputTokens') } : {}),
346
+ ...(carried('cachedTokens') ? { cache_read_input_tokens: sum('cachedTokens') } : {}),
347
+ ...(carried('outputTokens') ? { output_tokens: sum('outputTokens') } : {}),
348
+ ...(sum('outputTokens') === 0 ? fromCompletion : {}),
349
+ };
350
+ const counted = totals.length > 0 && Object.keys(fromTotals).length > 0 ? fromTotals : fromCompletion;
351
+ const usage = Object.keys(counted).length > 0 ? counted : undefined;
352
+ if (content.length > 0 || usage !== undefined) {
353
+ const model = str(request.modelId);
354
+ const answered = stamp(request.responseTimestamp);
355
+ lines.push({
356
+ ...base,
357
+ type: 'assistant',
358
+ uuid: `${fallbackId}-a${String(sequence)}`,
359
+ requestId: str(request.requestId) ?? `${fallbackId}-req${String(sequence)}`,
360
+ timestamp: answered,
361
+ ...(model === undefined ? {} : { requestedModel: model }),
362
+ message: { role: 'assistant', content: content.length > 0 ? content : [{ type: 'text', text: '' }], ...(usage === undefined ? {} : { usage }) },
363
+ });
364
+ }
365
+ for (const result of results) {
366
+ sequence += 1;
367
+ lines.push({
368
+ ...base,
369
+ type: 'user',
370
+ uuid: `${fallbackId}-r${String(sequence)}`,
371
+ timestamp: result.when,
372
+ // The call's own time is not paired to a start here, so no duration is claimed.
373
+ noDuration: true,
374
+ // The output follows an `Exit code 0` line, the shape a Claude Code Bash result has, so a `[branch sha]` line is still found for commit survival.
375
+ message: { role: 'user', content: [{ type: 'tool_result', tool_use_id: result.id, is_error: result.failure !== undefined, content: result.failure ?? (result.output === '' ? '' : `Exit code 0\n${result.output}`) }] },
376
+ });
377
+ }
378
+ }
379
+ return lines;
380
+ };
381
+ export const openVscodeCopilotStore = async (root) => {
382
+ const unreadable = [];
383
+ const found = new Map();
384
+ let rejected = 0;
385
+ let present = false;
386
+ const note = (file, error) => {
387
+ if (!missing(error))
388
+ unreadable.push({ file, reason: reasonOf(error, file) });
389
+ };
390
+ /** One `chatSessions` folder: each id once, its `.jsonl` ahead of its `.json`. */
391
+ const scan = async (dir, folder) => {
392
+ let names;
393
+ try {
394
+ names = (await readdir(dir, { withFileTypes: true })).filter((entry) => entry.isFile()).map((entry) => entry.name);
395
+ }
396
+ catch (error) {
397
+ note(dir, error);
398
+ return;
399
+ }
400
+ const byId = new Map();
401
+ for (const name of names.sort()) {
402
+ const ext = name.endsWith('.jsonl') ? '.jsonl' : name.endsWith('.json') ? '.json' : undefined;
403
+ if (ext === undefined)
404
+ continue;
405
+ const id = name.slice(0, -ext.length);
406
+ if (!SAFE_ID.test(id)) {
407
+ rejected += 1;
408
+ continue;
409
+ }
410
+ const files = byId.get(id) ?? [];
411
+ files[ext === '.jsonl' ? 'unshift' : 'push'](join(dir, name));
412
+ byId.set(id, files);
413
+ }
414
+ for (const [id, listed] of byId) {
415
+ // Every file of the session is stat'ed: with `chat.useLogSessionStorage` off VS Code writes only the `.json` (chatSessionStore.ts:353,394), so a log left from an
416
+ // earlier run is the older file. The newest is read first and sets the cursor, and a change to either file moves it.
417
+ const seen = [];
418
+ for (const file of listed) {
419
+ try {
420
+ const info = await stat(file);
421
+ seen.push({ file, size: info.size, mtimeMs: Math.floor(info.mtimeMs) });
422
+ }
423
+ catch (error) {
424
+ note(file, error);
425
+ }
426
+ }
427
+ // A stable sort keeps the `.jsonl` ahead of the `.json` when their times tie.
428
+ seen.sort((a, b) => b.mtimeMs - a.mtimeMs);
429
+ const newest = seen[0];
430
+ if (newest === undefined)
431
+ continue;
432
+ const earlier = found.get(id);
433
+ if (earlier === undefined || newest.mtimeMs > earlier.mtimeMs)
434
+ found.set(id, { id, folder, files: seen.map((entry) => entry.file), size: newest.size, mtimeMs: newest.mtimeMs });
435
+ }
436
+ };
437
+ for (const channel of CHANNELS) {
438
+ const user = join(root, channel, 'User');
439
+ try {
440
+ await stat(user);
441
+ }
442
+ catch (error) {
443
+ note(user, error);
444
+ continue;
445
+ }
446
+ present = true;
447
+ await scan(join(user, 'globalStorage', 'emptyWindowChatSessions'), null);
448
+ const storage = join(user, 'workspaceStorage');
449
+ let hashes;
450
+ try {
451
+ hashes = (await readdir(storage, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
452
+ }
453
+ catch (error) {
454
+ note(storage, error);
455
+ continue;
456
+ }
457
+ for (const hash of hashes) {
458
+ let folder = null;
459
+ try {
460
+ folder = workspaceFolder(await readFile(join(storage, hash, 'workspace.json'), 'utf8'));
461
+ }
462
+ catch {
463
+ // No `workspace.json` (the `no-workspace` folder has none) or an unreadable one: the sessions have no working directory.
464
+ }
465
+ await scan(join(storage, hash, 'chatSessions'), folder);
466
+ }
467
+ }
468
+ if (!present && unreadable.length === 0)
469
+ return undefined;
470
+ const sessions = [...found.values()].sort((a, b) => a.mtimeMs - b.mtimeMs || a.id.localeCompare(b.id));
471
+ return {
472
+ sessions,
473
+ rejected,
474
+ unreadable,
475
+ transcript: async (session) => {
476
+ let failure = new Error('no file');
477
+ for (const file of session.files) {
478
+ try {
479
+ const text = await readFile(file, 'utf8');
480
+ const jsonl = file.endsWith('.jsonl');
481
+ // VS Code throws on an empty log ("Empty log file", objectMutationLog.ts:393) and falls back to the `.json`; the last file alone may read as an empty session.
482
+ if (jsonl && text.trim() === '' && file !== session.files[session.files.length - 1])
483
+ throw new Error('empty log file');
484
+ const lines = [...chatLines(chatData(text, jsonl), session.id, session.folder).map((line) => JSON.stringify(line)), ...(jsonl ? chatBadLines(text) : [])];
485
+ return lines.join('\n') + (lines.length === 0 ? '' : '\n');
486
+ }
487
+ catch (error) {
488
+ failure = error;
489
+ }
490
+ }
491
+ throw failure;
492
+ },
493
+ };
494
+ };
@@ -0,0 +1,8 @@
1
+ import type { Outcome, RunDeps } from '../cli.js';
2
+ export declare const telemetryStatus: (deps: RunDeps) => Outcome;
3
+ export declare const telemetryOn: (deps: RunDeps) => Outcome;
4
+ export declare const telemetryOff: (deps: RunDeps) => Outcome;
5
+ /** The spool exactly as it is on disk, oldest first, as `pool outbox` prints a batch; the document carries the same events parsed. */
6
+ export declare const telemetryShow: (deps: RunDeps) => Outcome;
7
+ /** The user's own numbers from the spool, and the fleet's beside them when a pool server is configured and answers (docs/telemetry.md, "The report"). It writes nothing. */
8
+ export declare const telemetryReport: (deps: RunDeps) => Promise<Outcome>;
@@ -0,0 +1,85 @@
1
+ // `oars telemetry status|on|off|show`.
2
+ import { statSync } from 'node:fs';
3
+ import { CliError, ExitCode } from '../errors.js';
4
+ import { consentOf, markNoticeShown, NOTICE, setEnabled } from './consent.js';
5
+ import { readFleet, reportOf, textOf } from './report.js';
6
+ import { readSpool, spoolPaths } from './spool.js';
7
+ const STEP = 'telemetry';
8
+ const runtimeOf = (deps) => {
9
+ if (deps.telemetry === undefined)
10
+ throw new CliError('The telemetry folder is not configured in this run.', ExitCode.UNEXPECTED, 'unexpected');
11
+ return deps.telemetry;
12
+ };
13
+ const REASONS = {
14
+ default: 'on by default',
15
+ setting: 'by `oars telemetry on` or `off`',
16
+ OARS_TELEMETRY: 'by OARS_TELEMETRY',
17
+ DO_NOT_TRACK: 'by DO_NOT_TRACK',
18
+ };
19
+ const sizeOf = (path) => {
20
+ try {
21
+ return statSync(path).size;
22
+ }
23
+ catch {
24
+ return 0;
25
+ }
26
+ };
27
+ const describe = (consent) => `Telemetry is ${consent.enabled ? 'on' : 'off'} (${REASONS[consent.reason]}).`;
28
+ export const telemetryStatus = (deps) => {
29
+ const { dir } = runtimeOf(deps);
30
+ const consent = consentOf(deps.env, dir);
31
+ const events = readSpool(dir).length;
32
+ const bytes = sizeOf(spoolPaths(dir).current) + sizeOf(spoolPaths(dir).previous);
33
+ const lines = [
34
+ describe(consent),
35
+ `The notice has ${consent.noticeShown ? '' : 'not '}been shown.`,
36
+ `${String(events)} events (${String(bytes)} bytes) in ${dir}.`,
37
+ 'Events are sent to the pool server in OARS_POOL_URL, else to the Many Oars pool (pool.manyoars.com, not deployed yet: until it is, a send fails and the events stay on this machine), and only while telemetry is on and the notice has been shown.',
38
+ ];
39
+ return { document: { ok: true, step: STEP, enabled: consent.enabled, reason: consent.reason, noticeShown: consent.noticeShown, events, bytes, dir }, text: lines.join('\n') };
40
+ };
41
+ const save = (dir, enabled) => {
42
+ try {
43
+ setEnabled(dir, enabled);
44
+ }
45
+ catch (error) {
46
+ throw new CliError(`Could not save the telemetry setting in ${dir}: ${error instanceof Error ? error.message : String(error)}`, ExitCode.UNEXPECTED, 'telemetry-setting');
47
+ }
48
+ };
49
+ export const telemetryOn = (deps) => {
50
+ const { dir } = runtimeOf(deps);
51
+ save(dir, true);
52
+ // Running `on` is the notice: it is printed here and recorded as shown.
53
+ markNoticeShown(dir, deps.now);
54
+ const consent = consentOf(deps.env, dir);
55
+ const override = consent.enabled ? '' : `\nIt stays off in this shell: ${consent.reason} is set.`;
56
+ return { document: { ok: true, step: STEP, enabled: consent.enabled, reason: consent.reason, notice: NOTICE }, text: `Telemetry is on.\n${NOTICE}${override}` };
57
+ };
58
+ export const telemetryOff = (deps) => {
59
+ const { dir } = runtimeOf(deps);
60
+ save(dir, false);
61
+ return {
62
+ document: { ok: true, step: STEP, enabled: false, reason: 'setting' },
63
+ text: `Telemetry is off: nothing more is recorded or sent. What is already in the spool stays until you delete ${dir}.`,
64
+ };
65
+ };
66
+ const parsed = (line) => {
67
+ try {
68
+ return [JSON.parse(line)];
69
+ }
70
+ catch {
71
+ return [];
72
+ }
73
+ };
74
+ /** The spool exactly as it is on disk, oldest first, as `pool outbox` prints a batch; the document carries the same events parsed. */
75
+ export const telemetryShow = (deps) => {
76
+ const { dir } = runtimeOf(deps);
77
+ const lines = readSpool(dir);
78
+ return { document: { ok: true, step: STEP, events: lines.flatMap(parsed) }, text: lines.length === 0 ? `The spool is empty (${dir}).` : lines.join('\n') };
79
+ };
80
+ /** The user's own numbers from the spool, and the fleet's beside them when a pool server is configured and answers (docs/telemetry.md, "The report"). It writes nothing. */
81
+ export const telemetryReport = async (deps) => {
82
+ const { dir } = runtimeOf(deps);
83
+ const report = reportOf(readSpool(dir), deps.now(), await readFleet(deps.env));
84
+ return { document: { ok: true, step: STEP, ...report }, text: textOf(report, dir) };
85
+ };
@@ -0,0 +1,17 @@
1
+ /** Printed once on stderr, on the first run that is not a hook; also what `oars telemetry on` prints. */
2
+ export declare const NOTICE: string;
3
+ export type Reason = 'default' | 'setting' | 'OARS_TELEMETRY' | 'DO_NOT_TRACK';
4
+ export interface Consent {
5
+ enabled: boolean;
6
+ /** Which switch decided `enabled`. */
7
+ reason: Reason;
8
+ /** The notice was printed (or `on` was run) on this install. */
9
+ noticeShown: boolean;
10
+ }
11
+ export declare const consentOf: (env: NodeJS.ProcessEnv, dir: string) => Consent;
12
+ /** Saves `on` or `off`. Throws when the folder cannot be written: the command that asked reports it. */
13
+ export declare const setEnabled: (dir: string, enabled: boolean) => void;
14
+ /** Records that the notice was shown; `false` when it could not be recorded (nothing is recorded or sent from a folder that cannot be written). */
15
+ export declare const markNoticeShown: (dir: string, now: () => number) => boolean;
16
+ /** Whether the sender (t875) may send: telemetry on, and the notice shown. Off, or notice not yet shown, means nothing sends. */
17
+ export declare const telemetrySendAllowed: (env: NodeJS.ProcessEnv, dir: string) => boolean;