@oh-my-pi/pi-coding-agent 17.0.9 → 17.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (349) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/README.md +1 -1
  3. package/dist/cli.js +4685 -4614
  4. package/dist/types/async/job-manager.d.ts +37 -1
  5. package/dist/types/cli/args.d.ts +2 -0
  6. package/dist/types/cli/bench-cli.d.ts +41 -0
  7. package/dist/types/commands/bench.d.ts +15 -3
  8. package/dist/types/commands/launch.d.ts +4 -0
  9. package/dist/types/config/model-registry.d.ts +3 -1
  10. package/dist/types/config/models-config-schema.d.ts +18 -6
  11. package/dist/types/config/models-config.d.ts +14 -5
  12. package/dist/types/config/provider-globals.d.ts +2 -2
  13. package/dist/types/config/service-tier.d.ts +5 -1
  14. package/dist/types/config/settings-schema.d.ts +374 -47
  15. package/dist/types/config/settings.d.ts +7 -0
  16. package/dist/types/dap/client.d.ts +20 -0
  17. package/dist/types/exec/bash-executor.d.ts +38 -0
  18. package/dist/types/exec/direnv.d.ts +33 -0
  19. package/dist/types/export/html/args.d.ts +15 -0
  20. package/dist/types/export/html/index.d.ts +11 -26
  21. package/dist/types/export/html/web-palette.d.ts +55 -116
  22. package/dist/types/extensibility/extensions/loader.d.ts +3 -0
  23. package/dist/types/extensibility/extensions/types.d.ts +17 -2
  24. package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +3 -0
  25. package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +13 -0
  26. package/dist/types/live/attestation.d.ts +2 -0
  27. package/dist/types/live/controller.d.ts +48 -0
  28. package/dist/types/live/protocol.d.ts +85 -0
  29. package/dist/types/live/protocol.test.d.ts +1 -0
  30. package/dist/types/live/transport.d.ts +35 -0
  31. package/dist/types/live/visualizer.d.ts +30 -0
  32. package/dist/types/lsp/client.d.ts +16 -1
  33. package/dist/types/mcp/config-writer.test.d.ts +1 -0
  34. package/dist/types/mcp/oauth-discovery.d.ts +2 -0
  35. package/dist/types/mcp/smithery-auth.d.ts +1 -1
  36. package/dist/types/mcp/smithery-auth.test.d.ts +1 -0
  37. package/dist/types/memory-backend/tool-names.d.ts +2 -0
  38. package/dist/types/modes/components/assistant-message.d.ts +1 -0
  39. package/dist/types/modes/components/custom-message.d.ts +1 -1
  40. package/dist/types/modes/components/login-dialog.d.ts +2 -2
  41. package/dist/types/modes/components/message-frame.d.ts +8 -4
  42. package/dist/types/modes/components/plan-review-overlay.d.ts +26 -0
  43. package/dist/types/modes/components/session-account-selector.d.ts +11 -0
  44. package/dist/types/modes/components/settings-defs.d.ts +7 -1
  45. package/dist/types/modes/components/status-line/component.jj-cache.test.d.ts +1 -0
  46. package/dist/types/modes/components/tool-execution.d.ts +9 -1
  47. package/dist/types/modes/controllers/command-controller.d.ts +1 -1
  48. package/dist/types/modes/controllers/event-controller.d.ts +6 -1
  49. package/dist/types/modes/controllers/live-command-controller.d.ts +14 -0
  50. package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
  51. package/dist/types/modes/interactive-mode.d.ts +6 -0
  52. package/dist/types/modes/theme/theme.d.ts +1 -1
  53. package/dist/types/modes/types.d.ts +4 -1
  54. package/dist/types/modes/utils/context-usage.d.ts +12 -2
  55. package/dist/types/plan-mode/plan-files.d.ts +10 -0
  56. package/dist/types/registry/agent-lifecycle.d.ts +21 -9
  57. package/dist/types/registry/agent-registry.d.ts +11 -4
  58. package/dist/types/sdk.d.ts +15 -1
  59. package/dist/types/secrets/index.d.ts +15 -2
  60. package/dist/types/secrets/obfuscator.d.ts +113 -15
  61. package/dist/types/session/acp-permission-gate.d.ts +19 -0
  62. package/dist/types/session/agent-session-events.d.ts +84 -0
  63. package/dist/types/session/agent-session-types.d.ts +321 -0
  64. package/dist/types/session/agent-session.d.ts +135 -753
  65. package/dist/types/session/async-job-delivery.d.ts +28 -0
  66. package/dist/types/session/auth-storage.d.ts +1 -1
  67. package/dist/types/session/bash-runner.d.ts +75 -0
  68. package/dist/types/session/blob-store.d.ts +11 -1
  69. package/dist/types/session/blob-store.test.d.ts +1 -0
  70. package/dist/types/session/checkpoint-entries.d.ts +28 -0
  71. package/dist/types/session/eval-runner.d.ts +51 -0
  72. package/dist/types/session/irc-bridge.d.ts +46 -0
  73. package/dist/types/session/messages.d.ts +24 -0
  74. package/dist/types/session/model-controls.d.ts +180 -0
  75. package/dist/types/session/prewalk.d.ts +46 -0
  76. package/dist/types/session/queued-messages.d.ts +22 -0
  77. package/dist/types/session/redis-session-storage.d.ts +1 -0
  78. package/dist/types/session/retry-fallback-chains.d.ts +74 -0
  79. package/dist/types/session/role-models.d.ts +15 -0
  80. package/dist/types/session/session-advisors.d.ts +228 -0
  81. package/dist/types/session/session-entries.d.ts +8 -0
  82. package/dist/types/session/session-handoff.d.ts +70 -0
  83. package/dist/types/session/session-history-format.d.ts +6 -1
  84. package/dist/types/session/session-maintenance.d.ts +246 -0
  85. package/dist/types/session/session-manager.d.ts +41 -0
  86. package/dist/types/session/session-memory.d.ts +56 -0
  87. package/dist/types/session/session-provider-boundary.d.ts +62 -0
  88. package/dist/types/session/session-stats.d.ts +48 -0
  89. package/dist/types/session/session-tools.d.ts +158 -0
  90. package/dist/types/session/session-workspace.d.ts +32 -0
  91. package/dist/types/session/stream-guards.d.ts +44 -0
  92. package/dist/types/session/streaming-output.d.ts +8 -0
  93. package/dist/types/session/todo-tracker.d.ts +56 -0
  94. package/dist/types/session/ttsr-coordinator.d.ts +51 -0
  95. package/dist/types/session/turn-recovery.d.ts +225 -0
  96. package/dist/types/slash-commands/helpers/session-pin.d.ts +9 -0
  97. package/dist/types/stt/index.d.ts +0 -2
  98. package/dist/types/stt/stt-controller.d.ts +7 -0
  99. package/dist/types/system-prompt.d.ts +2 -0
  100. package/dist/types/task/executor.d.ts +4 -1
  101. package/dist/types/task/output-manager.d.ts +3 -1
  102. package/dist/types/tiny/title-client.d.ts +10 -0
  103. package/dist/types/tools/approval.d.ts +1 -0
  104. package/dist/types/tools/browser/launch.d.ts +5 -0
  105. package/dist/types/tools/builtin-names.d.ts +1 -1
  106. package/dist/types/tools/computer/protocol.d.ts +43 -0
  107. package/dist/types/tools/computer/supervisor.d.ts +32 -0
  108. package/dist/types/tools/computer/worker-entry.d.ts +1 -0
  109. package/dist/types/tools/computer/worker.d.ts +15 -0
  110. package/dist/types/tools/computer-renderer.d.ts +22 -0
  111. package/dist/types/tools/computer.d.ts +71 -0
  112. package/dist/types/tools/context.d.ts +2 -0
  113. package/dist/types/tools/default-renderer.d.ts +21 -0
  114. package/dist/types/tools/image-gen.d.ts +4 -4
  115. package/dist/types/tools/image-providers.d.ts +38 -0
  116. package/dist/types/tools/index.d.ts +7 -0
  117. package/dist/types/tools/path-utils.d.ts +8 -0
  118. package/dist/types/tools/todo.d.ts +10 -4
  119. package/dist/types/tools/xdev.d.ts +5 -1
  120. package/dist/types/tts/streaming-player.d.ts +10 -29
  121. package/dist/types/tts/vocalizer.d.ts +5 -0
  122. package/dist/types/utils/jj.d.ts +29 -0
  123. package/dist/types/utils/lang-from-path.d.ts +1 -0
  124. package/dist/types/utils/title-generator.d.ts +30 -0
  125. package/dist/types/utils/tools-manager.d.ts +1 -2
  126. package/dist/types/vibe/runtime.d.ts +36 -8
  127. package/dist/types/web/search/index.d.ts +1 -1
  128. package/dist/types/web/search/provider.d.ts +15 -5
  129. package/dist/types/web/search/types.d.ts +94 -0
  130. package/package.json +12 -12
  131. package/scripts/generate-share-viewer.ts +4 -6
  132. package/src/advisor/__tests__/advisor.test.ts +643 -0
  133. package/src/advisor/runtime.ts +144 -43
  134. package/src/async/job-manager.ts +90 -2
  135. package/src/cli/args.ts +3 -0
  136. package/src/cli/auth-gateway-cli.ts +18 -3
  137. package/src/cli/bench-cli.ts +395 -29
  138. package/src/cli/flag-tables.ts +3 -0
  139. package/src/cli/gc-cli.ts +2 -2
  140. package/src/cli/setup-cli.ts +2 -14
  141. package/src/cli/usage-cli.ts +10 -1
  142. package/src/cli.ts +8 -0
  143. package/src/collab/host.ts +3 -2
  144. package/src/commands/bench.ts +18 -4
  145. package/src/commands/launch.ts +4 -0
  146. package/src/config/model-registry.ts +16 -2
  147. package/src/config/models-config-schema.ts +15 -4
  148. package/src/config/provider-globals.ts +9 -9
  149. package/src/config/service-tier.ts +26 -1
  150. package/src/config/settings-schema.ts +270 -51
  151. package/src/config/settings.ts +81 -2
  152. package/src/dap/client.ts +37 -4
  153. package/src/edit/index.ts +49 -12
  154. package/src/eval/executor-base.ts +1 -0
  155. package/src/eval/js/executor.ts +2 -0
  156. package/src/eval/py/executor.ts +103 -33
  157. package/src/exec/bash-executor.ts +90 -6
  158. package/src/exec/direnv.ts +145 -0
  159. package/src/export/html/args.ts +20 -0
  160. package/src/export/html/index.ts +45 -47
  161. package/src/export/html/template.css +19 -1
  162. package/src/export/html/template.html +6 -0
  163. package/src/export/html/template.js +21 -0
  164. package/src/export/html/tool-views.generated.js +31 -31
  165. package/src/export/html/web-palette.ts +116 -132
  166. package/src/export/share.ts +249 -49
  167. package/src/extensibility/extensions/loader.ts +29 -1
  168. package/src/extensibility/extensions/runner.ts +6 -0
  169. package/src/extensibility/extensions/types.ts +33 -2
  170. package/src/extensibility/extensions/wrapper.ts +68 -12
  171. package/src/extensibility/legacy-pi-coding-agent-shim.ts +7 -1
  172. package/src/extensibility/plugins/legacy-pi-compat.ts +19 -1
  173. package/src/hindsight/state.ts +64 -6
  174. package/src/internal-urls/local-protocol.ts +2 -1
  175. package/src/internal-urls/memory-protocol.ts +2 -2
  176. package/src/internal-urls/skill-protocol.ts +2 -2
  177. package/src/internal-urls/ssh-protocol.ts +2 -1
  178. package/src/internal-urls/vault-protocol.ts +2 -1
  179. package/src/live/attestation.ts +91 -0
  180. package/src/live/controller.ts +517 -0
  181. package/src/live/prompts/agent-final-message.md +3 -0
  182. package/src/live/prompts/live-instructions.md +23 -0
  183. package/src/live/protocol.test.ts +140 -0
  184. package/src/live/protocol.ts +233 -0
  185. package/src/live/transport.ts +422 -0
  186. package/src/live/visualizer.ts +214 -0
  187. package/src/lsp/client.ts +29 -9
  188. package/src/lsp/index.ts +19 -4
  189. package/src/main.ts +8 -1
  190. package/src/mcp/config-writer.test.ts +43 -0
  191. package/src/mcp/config-writer.ts +109 -82
  192. package/src/mcp/oauth-discovery.ts +10 -2
  193. package/src/mcp/smithery-auth.test.ts +29 -0
  194. package/src/mcp/smithery-auth.ts +3 -2
  195. package/src/memories/index.ts +40 -20
  196. package/src/memory-backend/tool-names.ts +2 -0
  197. package/src/mnemopi/backend.ts +24 -7
  198. package/src/modes/acp/acp-agent.ts +56 -10
  199. package/src/modes/acp/acp-event-mapper.ts +8 -1
  200. package/src/modes/components/agent-hub.ts +1 -1
  201. package/src/modes/components/assistant-message.ts +7 -2
  202. package/src/modes/components/custom-message.ts +4 -1
  203. package/src/modes/components/login-dialog.ts +14 -4
  204. package/src/modes/components/message-frame.ts +14 -8
  205. package/src/modes/components/plan-review-overlay.ts +350 -35
  206. package/src/modes/components/session-account-selector.ts +62 -0
  207. package/src/modes/components/settings-defs.ts +23 -1
  208. package/src/modes/components/settings-selector.ts +169 -1
  209. package/src/modes/components/status-line/component.jj-cache.test.ts +229 -0
  210. package/src/modes/components/status-line/component.ts +123 -2
  211. package/src/modes/components/tool-execution.ts +28 -112
  212. package/src/modes/controllers/command-controller.ts +56 -15
  213. package/src/modes/controllers/event-controller.ts +111 -16
  214. package/src/modes/controllers/extension-ui-controller.test.ts +1 -0
  215. package/src/modes/controllers/extension-ui-controller.ts +20 -4
  216. package/src/modes/controllers/input-controller.ts +47 -39
  217. package/src/modes/controllers/live-command-controller.ts +255 -0
  218. package/src/modes/controllers/mcp-command-controller.ts +10 -1
  219. package/src/modes/controllers/omfg-controller.ts +44 -40
  220. package/src/modes/controllers/selector-controller.ts +80 -10
  221. package/src/modes/controllers/session-focus-controller.ts +6 -1
  222. package/src/modes/interactive-mode.ts +176 -38
  223. package/src/modes/runtime-init.ts +2 -0
  224. package/src/modes/setup-wizard/scenes/web-search.ts +17 -9
  225. package/src/modes/theme/theme.ts +1 -1
  226. package/src/modes/types.ts +4 -1
  227. package/src/modes/utils/context-usage.ts +23 -7
  228. package/src/modes/utils/ui-helpers.ts +10 -5
  229. package/src/plan-mode/plan-files.ts +40 -0
  230. package/src/prompts/bench/cache-prefix-chunk.md +1 -0
  231. package/src/prompts/bench/cache-prefix.md +3 -0
  232. package/src/prompts/bench/cache-suffix.md +1 -0
  233. package/src/prompts/system/computer-safety.md +14 -0
  234. package/src/prompts/system/custom-system-prompt.md +1 -1
  235. package/src/prompts/system/project-prompt.md +8 -1
  236. package/src/prompts/system/subagent-async-pending.md +6 -0
  237. package/src/prompts/system/system-prompt.md +1 -1
  238. package/src/prompts/system/workflow-notice.md +48 -40
  239. package/src/prompts/system/xdev-mount-notice.md +4 -0
  240. package/src/prompts/tools/computer.md +26 -0
  241. package/src/prompts/tools/todo.md +3 -1
  242. package/src/registry/agent-lifecycle.ts +116 -46
  243. package/src/registry/agent-registry.ts +35 -8
  244. package/src/registry/persisted-agents.ts +26 -2
  245. package/src/sdk.ts +215 -136
  246. package/src/secrets/index.ts +127 -4
  247. package/src/secrets/obfuscator.ts +2328 -105
  248. package/src/session/acp-permission-gate.ts +165 -0
  249. package/src/session/agent-session-error-log.test.ts +1 -1
  250. package/src/session/agent-session-events.ts +66 -0
  251. package/src/session/agent-session-types.ts +343 -0
  252. package/src/session/agent-session.ts +1940 -12218
  253. package/src/session/artifacts.ts +6 -5
  254. package/src/session/async-job-delivery.ts +74 -0
  255. package/src/session/auth-storage.ts +1 -0
  256. package/src/session/bash-runner.ts +326 -0
  257. package/src/session/blob-store.test.ts +56 -0
  258. package/src/session/blob-store.ts +18 -2
  259. package/src/session/checkpoint-entries.ts +81 -0
  260. package/src/session/eval-runner.ts +217 -0
  261. package/src/session/indexed-session-storage.ts +9 -2
  262. package/src/session/irc-bridge.ts +203 -0
  263. package/src/session/messages.ts +266 -1
  264. package/src/session/model-controls.ts +714 -0
  265. package/src/session/prewalk.ts +252 -0
  266. package/src/session/queued-messages.ts +93 -0
  267. package/src/session/redis-session-storage.ts +51 -11
  268. package/src/session/retry-fallback-chains.ts +455 -0
  269. package/src/session/role-models.ts +85 -0
  270. package/src/session/session-advisors.ts +1679 -0
  271. package/src/session/session-context.ts +6 -3
  272. package/src/session/session-entries.ts +8 -0
  273. package/src/session/session-handoff.ts +308 -0
  274. package/src/session/session-history-format.ts +20 -9
  275. package/src/session/session-loader.ts +0 -46
  276. package/src/session/session-maintenance.ts +2983 -0
  277. package/src/session/session-manager.ts +390 -30
  278. package/src/session/session-memory.ts +222 -0
  279. package/src/session/session-provider-boundary.ts +307 -0
  280. package/src/session/session-stats.ts +293 -0
  281. package/src/session/session-tools.ts +980 -0
  282. package/src/session/session-workspace.ts +53 -0
  283. package/src/session/stream-guards.ts +417 -0
  284. package/src/session/streaming-output.ts +52 -5
  285. package/src/session/todo-tracker.ts +380 -0
  286. package/src/session/ttsr-coordinator.ts +496 -0
  287. package/src/session/turn-recovery.ts +1629 -0
  288. package/src/slash-commands/builtin-registry.ts +257 -20
  289. package/src/slash-commands/helpers/session-pin.ts +44 -0
  290. package/src/slash-commands/helpers/usage-report.ts +22 -3
  291. package/src/stt/downloader.ts +0 -2
  292. package/src/stt/index.ts +0 -2
  293. package/src/stt/stt-controller.ts +57 -146
  294. package/src/system-prompt.ts +45 -17
  295. package/src/task/executor.ts +285 -70
  296. package/src/task/index.ts +98 -105
  297. package/src/task/output-manager.ts +25 -3
  298. package/src/task/persisted-revive.ts +4 -3
  299. package/src/task/structured-subagent.ts +1 -0
  300. package/src/tiny/title-client.ts +22 -0
  301. package/src/tools/approval.ts +55 -10
  302. package/src/tools/bash-interactive.ts +116 -86
  303. package/src/tools/bash-skill-urls.ts +28 -2
  304. package/src/tools/bash.ts +339 -123
  305. package/src/tools/browser/launch.ts +9 -1
  306. package/src/tools/browser/tab-supervisor.ts +8 -5
  307. package/src/tools/browser.ts +94 -47
  308. package/src/tools/builtin-names.ts +1 -0
  309. package/src/tools/computer/protocol.ts +28 -0
  310. package/src/tools/computer/supervisor.ts +258 -0
  311. package/src/tools/computer/worker-entry.ts +25 -0
  312. package/src/tools/computer/worker.ts +135 -0
  313. package/src/tools/computer-renderer.ts +108 -0
  314. package/src/tools/computer.ts +433 -0
  315. package/src/tools/context.ts +2 -0
  316. package/src/tools/default-renderer.ts +139 -0
  317. package/src/tools/essential-tools.ts +1 -0
  318. package/src/tools/image-gen.ts +20 -28
  319. package/src/tools/image-providers.ts +50 -0
  320. package/src/tools/index.ts +14 -0
  321. package/src/tools/path-utils.ts +49 -2
  322. package/src/tools/read.ts +156 -88
  323. package/src/tools/renderers.ts +9 -1
  324. package/src/tools/todo.ts +101 -11
  325. package/src/tools/vibe.ts +1 -2
  326. package/src/tools/write.ts +51 -1
  327. package/src/tools/xdev.ts +121 -29
  328. package/src/tts/streaming-player.ts +82 -283
  329. package/src/tts/vocalizer.ts +18 -1
  330. package/src/utils/clipboard.ts +1 -30
  331. package/src/utils/jj.ts +125 -4
  332. package/src/utils/lang-from-path.ts +7 -0
  333. package/src/utils/mac-file-urls.applescript +37 -0
  334. package/src/utils/title-generator.ts +137 -1
  335. package/src/utils/tool-choice.ts +14 -0
  336. package/src/utils/tools-manager.ts +1 -19
  337. package/src/vibe/runtime.ts +852 -72
  338. package/src/web/search/index.ts +3 -6
  339. package/src/web/search/provider.ts +31 -16
  340. package/src/web/search/providers/xai.ts +7 -1
  341. package/src/web/search/types.ts +3 -0
  342. package/dist/types/stt/recorder.d.ts +0 -30
  343. package/dist/types/stt/transcriber.d.ts +0 -14
  344. package/dist/types/stt/wav.d.ts +0 -29
  345. package/dist/types/tts/player.d.ts +0 -32
  346. package/src/stt/recorder.ts +0 -551
  347. package/src/stt/transcriber.ts +0 -60
  348. package/src/stt/wav.ts +0 -173
  349. package/src/tts/player.ts +0 -137
@@ -8,10 +8,11 @@ import type {
8
8
  AgentToolUpdateCallback,
9
9
  ToolLoadMode,
10
10
  } from "@oh-my-pi/pi-agent-core";
11
- import type { ImageContent, Static, TextContent, TSchema } from "@oh-my-pi/pi-ai";
11
+ import type { ComputerSafetyCheck, ImageContent, Static, TextContent, TSchema } from "@oh-my-pi/pi-ai";
12
+ import { sanitizeText } from "@oh-my-pi/pi-utils";
12
13
  import type { Settings } from "../../config/settings";
13
14
  import type { Theme } from "../../modes/theme/theme";
14
- import { type ApprovalMode, formatApprovalPrompt, resolveApproval } from "../../tools/approval";
15
+ import { type ApprovalMode, formatApprovalPrompt, resolveApproval, truncateForPrompt } from "../../tools/approval";
15
16
  import { defaultLoadModeForToolName } from "../../tools/essential-tools";
16
17
  import { normalizeToolEventInput, resolveToolEventInput } from "../tool-event-input";
17
18
  import { applyToolProxy } from "../tool-proxy";
@@ -83,6 +84,42 @@ export function wrapRegisteredTools(registeredTools: RegisteredTool[], runner: E
83
84
  return registeredTools.map(rt => wrapRegisteredTool(rt, runner));
84
85
  }
85
86
 
87
+ function computerSafetyChecks(context: AgentToolContext | undefined): ComputerSafetyCheck[] {
88
+ const metadata = context?.toolCall?.providerMetadata;
89
+ return metadata?.type === "computer" ? metadata.pendingSafetyChecks : [];
90
+ }
91
+
92
+ function approvalArgs(params: unknown, context: AgentToolContext | undefined): unknown {
93
+ const metadata = context?.toolCall?.providerMetadata;
94
+ return metadata?.type === "computer" ? { actions: metadata.actions } : params;
95
+ }
96
+
97
+ function toolEventArgs(params: unknown, context: AgentToolContext | undefined): Record<string, unknown> {
98
+ const metadata = context?.toolCall?.providerMetadata;
99
+ if (metadata?.type === "computer") {
100
+ return {
101
+ actions: metadata.actions,
102
+ pendingSafetyChecks: metadata.pendingSafetyChecks,
103
+ };
104
+ }
105
+ return params as Record<string, unknown>;
106
+ }
107
+
108
+ function approvalData(value: string): string {
109
+ const sanitized = sanitizeText(value)
110
+ .replace(/[\r\n\t]+/g, " ")
111
+ .trim();
112
+ const truncated = truncateForPrompt(sanitized, 500);
113
+ return truncated.replace(/([\\`*_{}[\]()<>#+\-.!|])/g, "\\$1");
114
+ }
115
+
116
+ function safetyCheckLines(checks: readonly ComputerSafetyCheck[]): string[] {
117
+ return checks.map((check, index) => {
118
+ const value = check.message || check.code || check.id;
119
+ return `${index + 1}. ${approvalData(value)}`;
120
+ });
121
+ }
122
+
86
123
  /**
87
124
  * Wraps a tool with extension callbacks for interception.
88
125
  * - Emits tool_call event before execution (can block)
@@ -128,19 +165,25 @@ export class ExtensionToolWrapper<TParameters extends TSchema = TSchema, TDetail
128
165
  const configuredMode = (settings?.get("tools.approvalMode") ?? "yolo") as ApprovalMode;
129
166
  const approvalMode: ApprovalMode = cliAutoApprove ? "yolo" : configuredMode;
130
167
  const userPolicies = (settings?.get("tools.approval") ?? {}) as Record<string, unknown>;
131
- const resolved = resolveApproval(this.tool, params, approvalMode, userPolicies);
168
+ const resolvedArgs = approvalArgs(params, context);
169
+ const resolved = resolveApproval(this.tool, resolvedArgs, approvalMode, userPolicies);
132
170
  if (resolved.policy === "deny") {
133
171
  throw new Error(
134
172
  `Tool "${this.tool.name}" is blocked by user policy.\n` +
135
173
  `To allow: remove "tools.approval.${this.tool.name}: deny" from config.`,
136
174
  );
137
175
  }
176
+ const pendingSafetyChecks = computerSafetyChecks(context);
138
177
  // An xd:// device dispatch already cleared the write tool's outer gate at
139
178
  // this tool's tier — re-prompting would double-ask for one action. Explicit
140
179
  // per-tool "prompt" policies and tool-demanded overrides still prompt.
180
+ // Provider safety checks are stronger: yolo, per-tool allow, and xdev approval
181
+ // never acknowledge them on the user's behalf.
141
182
  const explicitPrompt = resolved.override || Object.hasOwn(userPolicies, this.tool.name);
142
183
  const approvalCheck = {
143
- required: resolved.policy === "prompt" && (explicitPrompt || context?.xdevApproved !== true),
184
+ required:
185
+ pendingSafetyChecks.length > 0 ||
186
+ (resolved.policy === "prompt" && (explicitPrompt || context?.xdevApproved !== true)),
144
187
  reason: resolved.reason,
145
188
  };
146
189
 
@@ -171,10 +214,16 @@ export class ExtensionToolWrapper<TParameters extends TSchema = TSchema, TDetail
171
214
  });
172
215
  };
173
216
 
174
- // Check if UI is available
217
+ // Provider safety checks fail closed without an interactive prompt. Unlike
218
+ // ordinary tier approval, no setting or yolo mode may bypass this gate.
175
219
  if (!this.runner.hasUI()) {
176
220
  const reason = "no interactive UI available";
177
221
  await resolveApproval(false, reason);
222
+ if (pendingSafetyChecks.length > 0) {
223
+ throw new Error(
224
+ `Tool "${this.tool.name}" has pending provider safety checks but no interactive UI is available.`,
225
+ );
226
+ }
178
227
  throw new Error(
179
228
  `Tool "${this.tool.name}" requires approval but no interactive UI available.\n` +
180
229
  `Options:\n` +
@@ -185,12 +234,14 @@ export class ExtensionToolWrapper<TParameters extends TSchema = TSchema, TDetail
185
234
  }
186
235
 
187
236
  const uiContext = this.runner.getUIContext();
237
+ const basePrompt = formatApprovalPrompt(this.tool, resolvedArgs, approvalCheck.reason);
238
+ const safetyPrompt =
239
+ pendingSafetyChecks.length > 0
240
+ ? `${basePrompt}\nProvider safety checks:\n${safetyCheckLines(pendingSafetyChecks).join("\n")}`
241
+ : basePrompt;
188
242
  let choice: string | undefined;
189
243
  try {
190
- choice = await uiContext.select(formatApprovalPrompt(this.tool, params, approvalCheck.reason), [
191
- "Approve",
192
- "Deny",
193
- ]);
244
+ choice = await uiContext.select(safetyPrompt, ["Approve", "Deny"]);
194
245
  } catch (err) {
195
246
  await resolveApproval(false, err instanceof Error ? err.message : "approval aborted");
196
247
  throw err;
@@ -200,6 +251,10 @@ export class ExtensionToolWrapper<TParameters extends TSchema = TSchema, TDetail
200
251
  if (!approved) {
201
252
  throw new Error(`Tool call denied by user: ${this.tool.name}`);
202
253
  }
254
+ if (pendingSafetyChecks.length > 0) {
255
+ if (!context) throw new Error("Provider safety approval context is unavailable");
256
+ context.providerSafetyApproved = true;
257
+ }
203
258
  }
204
259
 
205
260
  // 2. Emit tool_call event - extensions can block execution
@@ -211,7 +266,7 @@ export class ExtensionToolWrapper<TParameters extends TSchema = TSchema, TDetail
211
266
  toolCallId,
212
267
  input: normalizeToolEventInput(
213
268
  this.tool.name,
214
- resolveToolEventInput(this.tool, params as Record<string, unknown>),
269
+ resolveToolEventInput(this.tool, toolEventArgs(params, context)),
215
270
  ),
216
271
  })) as ToolCallEventResult | undefined;
217
272
 
@@ -228,7 +283,7 @@ export class ExtensionToolWrapper<TParameters extends TSchema = TSchema, TDetail
228
283
  }
229
284
 
230
285
  // Execute the actual tool
231
- let result: { content: any; details?: TDetails };
286
+ let result: AgentToolResult<TDetails, TParameters>;
232
287
  let executionError: Error | undefined;
233
288
 
234
289
  try {
@@ -249,7 +304,7 @@ export class ExtensionToolWrapper<TParameters extends TSchema = TSchema, TDetail
249
304
  toolCallId,
250
305
  input: normalizeToolEventInput(
251
306
  this.tool.name,
252
- resolveToolEventInput(this.tool, params as Record<string, unknown>),
307
+ resolveToolEventInput(this.tool, toolEventArgs(params, context)),
253
308
  ),
254
309
  content: result.content,
255
310
  details: result.details,
@@ -275,6 +330,7 @@ export class ExtensionToolWrapper<TParameters extends TSchema = TSchema, TDetail
275
330
  return {
276
331
  content: modifiedContent,
277
332
  details: modifiedDetails,
333
+ providerMetadata: result.providerMetadata,
278
334
  ...(effectiveError ? { isError: true } : {}),
279
335
  };
280
336
  }
@@ -17,7 +17,7 @@ import * as fs from "node:fs";
17
17
  import * as path from "node:path";
18
18
  import type { AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
19
19
  import { type AuthCredential, SqliteAuthCredentialStore, type TSchema } from "@oh-my-pi/pi-ai";
20
- import { Text } from "@oh-my-pi/pi-tui";
20
+ import { getKeybindings, type Keybinding, Text } from "@oh-my-pi/pi-tui";
21
21
  import {
22
22
  getAgentDbPath,
23
23
  getAgentDir,
@@ -26,6 +26,7 @@ import {
26
26
  parseFrontmatter as parseOmpFrontmatter,
27
27
  } from "@oh-my-pi/pi-utils";
28
28
  import { getPackageDir as getOmpPackageDir } from "../config";
29
+ import { formatKeyHints } from "../config/keybindings";
29
30
  import type { PromptTemplate } from "../config/prompt-templates";
30
31
  import { type SettingPath, Settings } from "../config/settings";
31
32
  import { EditTool } from "../edit";
@@ -373,6 +374,11 @@ async function executeLegacyBashOperations(
373
374
  }
374
375
  }
375
376
 
377
+ /** Format the active shortcut for legacy extensions that render keybinding hints. */
378
+ export function keyText(action: Keybinding): string {
379
+ return formatKeyHints(getKeybindings().getKeys(action));
380
+ }
381
+
376
382
  /** Parse frontmatter using the historical Pi package-root helper. */
377
383
  export interface ParsedFrontmatter<T extends Record<string, unknown> = Record<string, unknown>> {
378
384
  frontmatter: T;
@@ -1504,7 +1504,25 @@ function evaluateGraphCommonJs(modulePath: string): unknown {
1504
1504
  }
1505
1505
  }
1506
1506
 
1507
- Reflect.set(globalThis, COMMONJS_REQUIRE_GLOBAL, evaluateGraphCommonJs);
1507
+ /**
1508
+ * Register {@link evaluateGraphCommonJs} as the graph-owned CommonJS require
1509
+ * bridge on `globalThis`, first-wins.
1510
+ *
1511
+ * On source-link installs the `@(scope)/pi-coding-agent` root shim is served
1512
+ * from `src/`, so an extension import can evaluate a second instance of this
1513
+ * module with empty graph state. An unconditional set would let that empty
1514
+ * instance clobber the host bundle's populated bridge and break transitive
1515
+ * CommonJS resolution (#6449); guarding preserves the first (host-owned)
1516
+ * registration. Idempotent: a subsequent call with a value already present is a
1517
+ * no-op.
1518
+ */
1519
+ export function ensureGraphCommonJsRequireRegistered(): void {
1520
+ if (!Reflect.get(globalThis, COMMONJS_REQUIRE_GLOBAL)) {
1521
+ Reflect.set(globalThis, COMMONJS_REQUIRE_GLOBAL, evaluateGraphCommonJs);
1522
+ }
1523
+ }
1524
+
1525
+ ensureGraphCommonJsRequireRegistered();
1508
1526
 
1509
1527
  let legacyPiLoadTag = 0;
1510
1528
 
@@ -187,6 +187,17 @@ export class HindsightRetainQueue {
187
187
  }
188
188
  }
189
189
 
190
+ /** Rolling hash of messages[0, count) for retention-cache validation (see #lastRetainedPrefixKey). */
191
+ function retentionPrefixKey(messages: HindsightMessage[], count: number): string {
192
+ let key = "";
193
+ for (let i = 0; i < count; i++) {
194
+ const m = messages[i];
195
+ if (m === undefined) break;
196
+ key = Bun.hash(`${key}\u0000${m.role}\u0000${m.content}`).toString(36);
197
+ }
198
+ return key;
199
+ }
200
+
190
201
  /** Per-session Hindsight runtime state owned by its AgentSession. */
191
202
  export class HindsightSessionState {
192
203
  /** Session id used for retain-queue metadata. */
@@ -202,6 +213,17 @@ export class HindsightSessionState {
202
213
  session: AgentSession;
203
214
  banksSet: Set<string>;
204
215
  lastRetainedTurn: number;
216
+ #lastRetainedMessageIndex: number = 0;
217
+ #cachedTranscript: string = "";
218
+ // Rolling hash of ALL messages in [0, #lastRetainedMessageIndex) at cache
219
+ // time. The incremental full-session cache assumes the branch is append-only;
220
+ // a rewind, branch switch, compaction, or in-place edit rewrites the prefix
221
+ // without changing the session id. Re-hashing the current prefix at use time
222
+ // makes the cache self-healing: on ANY prefix change (not just the boundary
223
+ // message) we rebuild the full transcript instead of retaining stale content
224
+ // or silently retaining nothing forever. Hashing is orders of magnitude
225
+ // cheaper than the re-formatting this cache avoids.
226
+ #lastRetainedPrefixKey: string = "";
205
227
  hasRecalledForFirstTurn: boolean;
206
228
  lastRecallSnippet?: string;
207
229
  /** Cached `<mental_models>` block injected into developer instructions. */
@@ -236,6 +258,9 @@ export class HindsightSessionState {
236
258
  this.session = options.session;
237
259
  this.banksSet = options.banksSet;
238
260
  this.lastRetainedTurn = options.lastRetainedTurn ?? 0;
261
+ this.#lastRetainedMessageIndex = 0;
262
+ this.#cachedTranscript = "";
263
+ this.#lastRetainedPrefixKey = "";
239
264
  this.hasRecalledForFirstTurn = options.hasRecalledForFirstTurn ?? false;
240
265
  this.aliasOf = options.aliasOf;
241
266
  this.retainQueue = new HindsightRetainQueue(this);
@@ -243,12 +268,18 @@ export class HindsightSessionState {
243
268
 
244
269
  setSessionId(sessionId: string): void {
245
270
  this.sessionId = sessionId;
271
+ this.#lastRetainedMessageIndex = 0;
272
+ this.#cachedTranscript = "";
273
+ this.#lastRetainedPrefixKey = "";
246
274
  }
247
275
 
248
276
  resetConversationTracking(): void {
249
277
  this.lastRetainedTurn = 0;
250
278
  this.hasRecalledForFirstTurn = false;
251
279
  this.lastRecallSnippet = undefined;
280
+ this.#lastRetainedMessageIndex = 0;
281
+ this.#cachedTranscript = "";
282
+ this.#lastRetainedPrefixKey = "";
252
283
  }
253
284
 
254
285
  enqueueRetain(content: string, context?: string): void {
@@ -285,21 +316,35 @@ export class HindsightSessionState {
285
316
  async retainSession(messages: HindsightMessage[]): Promise<void> {
286
317
  const retainedAt = new Date();
287
318
  const retainFullWindow = this.config.retainMode === "full-session";
288
- let target: HindsightMessage[];
289
319
  let documentId: string;
320
+ let transcript: string;
321
+ let nextCachedTranscript: string | undefined;
290
322
 
291
323
  if (retainFullWindow) {
292
- target = messages;
293
324
  documentId = this.sessionId;
325
+ const boundary = this.#lastRetainedMessageIndex;
326
+ if (boundary > messages.length || retentionPrefixKey(messages, boundary) !== this.#lastRetainedPrefixKey) {
327
+ this.#lastRetainedMessageIndex = 0;
328
+ this.#cachedTranscript = "";
329
+ this.#lastRetainedPrefixKey = "";
330
+ }
331
+ const newMessages = messages.slice(this.#lastRetainedMessageIndex);
332
+ const { transcript: newPart } = prepareRetentionTranscript(newMessages, true);
333
+ if (!newPart) return;
334
+ nextCachedTranscript = this.#cachedTranscript ? `${this.#cachedTranscript}\n\n${newPart}` : newPart;
335
+ transcript = nextCachedTranscript;
294
336
  } else {
295
337
  const windowTurns = this.config.retainEveryNTurns + this.config.retainOverlapTurns;
296
- target = sliceLastTurnsByUserBoundary(messages, windowTurns);
338
+ const target = sliceLastTurnsByUserBoundary(messages, windowTurns);
297
339
  documentId = `${this.sessionId}-${retainedAt.getTime()}`;
340
+ this.#lastRetainedMessageIndex = 0;
341
+ this.#cachedTranscript = "";
342
+ this.#lastRetainedPrefixKey = "";
343
+ const { transcript: windowTranscript } = prepareRetentionTranscript(target, true);
344
+ if (!windowTranscript) return;
345
+ transcript = windowTranscript;
298
346
  }
299
347
 
300
- const { transcript } = prepareRetentionTranscript(target, true);
301
- if (!transcript) return;
302
-
303
348
  await ensureBankExists(this.client, this.bankId, this.config, this.banksSet);
304
349
  await this.client.retain(this.bankId, transcript, {
305
350
  documentId,
@@ -309,6 +354,11 @@ export class HindsightSessionState {
309
354
  timestamp: retainedAt,
310
355
  async: true,
311
356
  });
357
+ if (nextCachedTranscript !== undefined) {
358
+ this.#cachedTranscript = nextCachedTranscript;
359
+ this.#lastRetainedMessageIndex = messages.length;
360
+ this.#lastRetainedPrefixKey = retentionPrefixKey(messages, messages.length);
361
+ }
312
362
  }
313
363
 
314
364
  async maybeRetainOnAgentEnd(): Promise<void> {
@@ -341,6 +391,14 @@ export class HindsightSessionState {
341
391
  async forceRetainCurrentSession(): Promise<void> {
342
392
  const messages = extractMessages(this.session.sessionManager);
343
393
  if (messages.length === 0) return;
394
+ // Forced retains are user-initiated rebuilds (`/memory enqueue`): drop the
395
+ // incremental cache so the full transcript is reformatted and resent even
396
+ // when no new messages arrived since the last auto-retain — otherwise a
397
+ // rebuild could never recover an upstream document that was deleted or a
398
+ // previous async retain that never materialized.
399
+ this.#lastRetainedMessageIndex = 0;
400
+ this.#cachedTranscript = "";
401
+ this.#lastRetainedPrefixKey = "";
344
402
  try {
345
403
  await this.retainSession(messages);
346
404
  this.lastRetainedTurn = messages.filter(m => m.role === "user").length;
@@ -3,6 +3,7 @@ import * as os from "node:os";
3
3
  import * as path from "node:path";
4
4
  import { isEnoent } from "@oh-my-pi/pi-utils";
5
5
  import { AgentRegistry } from "../registry/agent-registry";
6
+ import { isMarkdownPath } from "../utils/lang-from-path";
6
7
  import { buildDirectoryResource } from "./filesystem-resource";
7
8
  import { parseInternalUrl } from "./parse";
8
9
  import { validateRelativePath } from "./skill-protocol";
@@ -43,8 +44,8 @@ function shortLocalRoot(options: LocalProtocolOptions): string {
43
44
  }
44
45
 
45
46
  function getContentType(filePath: string): InternalResource["contentType"] {
47
+ if (isMarkdownPath(filePath)) return "text/markdown";
46
48
  const ext = path.extname(filePath).toLowerCase();
47
- if (ext === ".md") return "text/markdown";
48
49
  if (ext === ".json") return "application/json";
49
50
  return "text/plain";
50
51
  }
@@ -4,6 +4,7 @@ import { getAgentDir, isEnoent } from "@oh-my-pi/pi-utils";
4
4
  import { getMemoryRoot } from "../memories";
5
5
  import { getMnemopiSessionState, type MnemopiScopedMemoryHit, type MnemopiSessionState } from "../mnemopi/state";
6
6
  import { AgentRegistry } from "../registry/agent-registry";
7
+ import { isMarkdownPath } from "../utils/lang-from-path";
7
8
  import { buildDirectoryResource } from "./filesystem-resource";
8
9
  import { validateRelativePath } from "./skill-protocol";
9
10
  import type { InternalResource, InternalUrl, ProtocolHandler, ResolveContext, UrlCompletion } from "./types";
@@ -117,8 +118,7 @@ async function tryResolveInRoot(url: InternalUrl, memoryRoot: string): Promise<I
117
118
  }
118
119
 
119
120
  const content = await Bun.file(realTargetPath).text();
120
- const ext = path.extname(realTargetPath).toLowerCase();
121
- const contentType: InternalResource["contentType"] = ext === ".md" ? "text/markdown" : "text/plain";
121
+ const contentType: InternalResource["contentType"] = isMarkdownPath(realTargetPath) ? "text/markdown" : "text/plain";
122
122
 
123
123
  return {
124
124
  url: url.href,
@@ -12,12 +12,12 @@ import * as fs from "node:fs/promises";
12
12
  import * as path from "node:path";
13
13
  import { isEnoent } from "@oh-my-pi/pi-utils";
14
14
  import { getActiveSkills } from "../extensibility/skills";
15
+ import { isMarkdownPath } from "../utils/lang-from-path";
15
16
  import { buildDirectoryResource } from "./filesystem-resource";
16
17
  import type { InternalResource, InternalUrl, ProtocolHandler, ResolveContext, UrlCompletion } from "./types";
17
18
 
18
19
  function getContentType(filePath: string): InternalResource["contentType"] {
19
- const ext = path.extname(filePath).toLowerCase();
20
- if (ext === ".md") return "text/markdown";
20
+ if (isMarkdownPath(filePath)) return "text/markdown";
21
21
  return "text/plain";
22
22
  }
23
23
 
@@ -30,6 +30,7 @@ import {
30
30
  statRemotePath,
31
31
  writeRemoteFile,
32
32
  } from "../ssh/file-transfer";
33
+ import { isMarkdownPath } from "../utils/lang-from-path";
33
34
  import type {
34
35
  InternalResource,
35
36
  InternalUrl,
@@ -44,11 +45,11 @@ const SSH_TEXT_MAX_BYTES = 1024 * 1024;
44
45
 
45
46
  /** POSIX-aware content type from the last path segment's extension. */
46
47
  function contentTypeFor(remotePath: string): InternalResource["contentType"] {
48
+ if (isMarkdownPath(remotePath)) return "text/markdown";
47
49
  const slash = remotePath.lastIndexOf("/");
48
50
  const base = slash === -1 ? remotePath : remotePath.slice(slash + 1);
49
51
  const dot = base.lastIndexOf(".");
50
52
  const ext = dot <= 0 ? "" : base.slice(dot).toLowerCase();
51
- if (ext === ".md") return "text/markdown";
52
53
  if (ext === ".json") return "application/json";
53
54
  return "text/plain";
54
55
  }
@@ -3,6 +3,7 @@ import * as path from "node:path";
3
3
  import { $which, isEnoent } from "@oh-my-pi/pi-utils";
4
4
  import { isSettingsInitialized, settings } from "../config/settings";
5
5
  import { getDefault } from "../config/settings-schema";
6
+ import { isMarkdownPath } from "../utils/lang-from-path";
6
7
  import { parseInternalUrl } from "./parse";
7
8
  import { validateRelativePath } from "./skill-protocol";
8
9
  import type { InternalResource, InternalUrl, ProtocolHandler, ResolveContext, WriteContext } from "./types";
@@ -114,8 +115,8 @@ function toVaultValidationError(error: unknown): Error {
114
115
  }
115
116
 
116
117
  function getContentType(filePath: string): ContentType {
118
+ if (isMarkdownPath(filePath)) return "text/markdown";
117
119
  const ext = path.extname(filePath).toLowerCase();
118
- if (ext === ".md") return "text/markdown";
119
120
  if (ext === ".json") return "application/json";
120
121
  return "text/plain";
121
122
  }
@@ -0,0 +1,91 @@
1
+ import { deviceCheckGenerateToken } from "@oh-my-pi/pi-natives";
2
+
3
+ const CHATGPT_BUNDLE_ID = "com.openai.codex";
4
+ const APP_SESSION_ID = crypto.randomUUID();
5
+
6
+ type DeviceCheckResult = {
7
+ supported: boolean;
8
+ tokenBase64?: string | null;
9
+ latencyMs?: number | null;
10
+ };
11
+
12
+ function cborHeader(major: number, value: number): Buffer {
13
+ if (!Number.isSafeInteger(value) || value < 0) throw new Error(`Invalid CBOR length: ${value}`);
14
+ if (value < 24) return Buffer.from([major + value]);
15
+ if (value <= 0xff) return Buffer.from([major + 24, value]);
16
+ if (value <= 0xffff) {
17
+ const output = Buffer.allocUnsafe(3);
18
+ output[0] = major + 25;
19
+ output.writeUInt16BE(value, 1);
20
+ return output;
21
+ }
22
+ if (value <= 0xffff_ffff) {
23
+ const output = Buffer.allocUnsafe(5);
24
+ output[0] = major + 26;
25
+ output.writeUInt32BE(value, 1);
26
+ return output;
27
+ }
28
+ throw new Error(`CBOR length is too large: ${value}`);
29
+ }
30
+
31
+ function cborUnsigned(value: number): Buffer {
32
+ return cborHeader(0, value);
33
+ }
34
+
35
+ function cborText(value: string): Buffer {
36
+ const text = Buffer.from(value, "utf8");
37
+ return Buffer.concat([cborHeader(96, text.length), text]);
38
+ }
39
+
40
+ function cborMap(entries: ReadonlyArray<readonly [Buffer, Buffer]>): Buffer {
41
+ const values: Buffer[] = [cborHeader(160, entries.length)];
42
+ for (const [key, value] of entries) values.push(key, value);
43
+ return Buffer.concat(values);
44
+ }
45
+
46
+ function attestationSignals(): Buffer {
47
+ const resolved = Intl.DateTimeFormat().resolvedOptions();
48
+ const locale = (resolved.locale || "unknown").slice(0, 64);
49
+ const timezone = (resolved.timeZone || "unknown").slice(0, 64);
50
+ const preferredLanguages = Buffer.concat([cborHeader(128, 1), cborText(locale)]);
51
+ return cborMap([
52
+ [cborUnsigned(0), cborUnsigned(1)],
53
+ [cborUnsigned(1), preferredLanguages],
54
+ [cborUnsigned(2), cborText(locale)],
55
+ [cborUnsigned(3), cborText(timezone)],
56
+ [cborUnsigned(4), cborUnsigned(0)],
57
+ [cborUnsigned(5), cborUnsigned(1)],
58
+ [cborUnsigned(6), cborText(APP_SESSION_ID.slice(0, 128))],
59
+ ]);
60
+ }
61
+
62
+ function buildClientAttestation(result: DeviceCheckResult): string {
63
+ const entries: Array<readonly [Buffer, Buffer]> = [];
64
+ if (result.supported && result.tokenBase64) {
65
+ entries.push([cborText("token"), cborText(result.tokenBase64)]);
66
+ } else {
67
+ entries.push([cborText("error_code"), cborUnsigned(result.supported ? 4 : 3)]);
68
+ }
69
+ entries.push([cborText("bundle_id"), cborText(CHATGPT_BUNDLE_ID)]);
70
+ const signals = attestationSignals();
71
+ entries.push([cborText("f"), Buffer.concat([cborHeader(64, signals.length), signals])]);
72
+ if (result.latencyMs !== undefined && result.latencyMs !== null) {
73
+ const latency = Buffer.allocUnsafe(9);
74
+ latency[0] = 0xfb;
75
+ latency.writeDoubleBE(result.latencyMs, 1);
76
+ entries.push([cborText("t"), latency]);
77
+ }
78
+ return `v1.${cborMap(entries).toString("base64url")}`;
79
+ }
80
+
81
+ /** Generates the Codex DeviceCheck attestation envelope sent as `x-oai-attestation` on ChatGPT-OAuth requests. */
82
+ export async function generateCodexAttestation(): Promise<string | undefined> {
83
+ if (process.platform !== "darwin" || process.arch !== "arm64") return undefined;
84
+ let result: DeviceCheckResult;
85
+ try {
86
+ result = await deviceCheckGenerateToken();
87
+ } catch {
88
+ return undefined;
89
+ }
90
+ return JSON.stringify({ v: 1, s: 0, t: buildClientAttestation(result) });
91
+ }