lsd-pi 1.1.3 → 1.1.5

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 (306) hide show
  1. package/README.md +2 -1
  2. package/dist/cli.js +14 -0
  3. package/dist/headless.d.ts +2 -0
  4. package/dist/headless.js +23 -0
  5. package/dist/resources/extensions/async-jobs/async-bash-tool.js +14 -0
  6. package/dist/resources/extensions/async-jobs/await-tool.js +14 -0
  7. package/dist/resources/extensions/async-jobs/cancel-job-tool.js +7 -0
  8. package/dist/resources/extensions/cache-timer/index.js +4 -0
  9. package/dist/resources/extensions/codex-rotate/IMPLEMENTATION.md +18 -13
  10. package/dist/resources/extensions/codex-rotate/README.md +9 -3
  11. package/dist/resources/extensions/codex-rotate/commands.js +19 -9
  12. package/dist/resources/extensions/codex-rotate/index.js +22 -37
  13. package/dist/resources/extensions/codex-rotate/oauth.js +70 -13
  14. package/dist/resources/extensions/codex-rotate/quota.js +7 -6
  15. package/dist/resources/extensions/codex-rotate/sync.js +6 -0
  16. package/dist/resources/extensions/memory/auto-extract.js +160 -77
  17. package/dist/resources/extensions/shared/interview-ui.js +2 -1
  18. package/dist/resources/extensions/shared/rtk.js +89 -87
  19. package/dist/resources/extensions/slash-commands/plan.js +12 -0
  20. package/dist/resources/extensions/subagent/background-job-manager.js +170 -0
  21. package/dist/resources/extensions/subagent/background-runner.js +36 -0
  22. package/dist/resources/extensions/subagent/background-types.js +8 -0
  23. package/dist/resources/extensions/subagent/index.js +459 -13
  24. package/dist/startup-model-validation.js +12 -2
  25. package/dist/update-check.js +2 -2
  26. package/dist/update-cmd.js +3 -3
  27. package/package.json +2 -1
  28. package/packages/native/dist/stream-process/index.js +110 -1
  29. package/packages/native/src/stream-process/index.ts +115 -1
  30. package/packages/pi-ai/dist/providers/openai-codex-responses.d.ts.map +1 -1
  31. package/packages/pi-ai/dist/providers/openai-codex-responses.js +9 -1
  32. package/packages/pi-ai/dist/providers/openai-codex-responses.js.map +1 -1
  33. package/packages/pi-ai/dist/providers/openai-codex-responses.test.d.ts +2 -0
  34. package/packages/pi-ai/dist/providers/openai-codex-responses.test.d.ts.map +1 -0
  35. package/packages/pi-ai/dist/providers/openai-codex-responses.test.js +52 -0
  36. package/packages/pi-ai/dist/providers/openai-codex-responses.test.js.map +1 -0
  37. package/packages/pi-ai/src/providers/openai-codex-responses.test.ts +66 -0
  38. package/packages/pi-ai/src/providers/openai-codex-responses.ts +9 -1
  39. package/packages/pi-coding-agent/dist/core/agent-session.d.ts +13 -0
  40. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  41. package/packages/pi-coding-agent/dist/core/agent-session.js +36 -2
  42. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  43. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +7 -0
  44. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
  45. package/packages/pi-coding-agent/dist/core/auth-storage.js +38 -5
  46. package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
  47. package/packages/pi-coding-agent/dist/core/auth-storage.test.js +27 -0
  48. package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
  49. package/packages/pi-coding-agent/dist/core/bash-executor.d.ts +5 -0
  50. package/packages/pi-coding-agent/dist/core/bash-executor.d.ts.map +1 -1
  51. package/packages/pi-coding-agent/dist/core/bash-executor.js +131 -101
  52. package/packages/pi-coding-agent/dist/core/bash-executor.js.map +1 -1
  53. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +3 -1
  54. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  55. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  56. package/packages/pi-coding-agent/dist/core/footer-data-provider.d.ts +4 -1
  57. package/packages/pi-coding-agent/dist/core/footer-data-provider.d.ts.map +1 -1
  58. package/packages/pi-coding-agent/dist/core/footer-data-provider.js +7 -0
  59. package/packages/pi-coding-agent/dist/core/footer-data-provider.js.map +1 -1
  60. package/packages/pi-coding-agent/dist/core/keybindings.d.ts +1 -1
  61. package/packages/pi-coding-agent/dist/core/keybindings.d.ts.map +1 -1
  62. package/packages/pi-coding-agent/dist/core/keybindings.js +2 -0
  63. package/packages/pi-coding-agent/dist/core/keybindings.js.map +1 -1
  64. package/packages/pi-coding-agent/dist/core/lsp/lsp.md +2 -0
  65. package/packages/pi-coding-agent/dist/core/messages.d.ts +1 -0
  66. package/packages/pi-coding-agent/dist/core/messages.d.ts.map +1 -1
  67. package/packages/pi-coding-agent/dist/core/messages.js +1 -1
  68. package/packages/pi-coding-agent/dist/core/messages.js.map +1 -1
  69. package/packages/pi-coding-agent/dist/core/pty-executor.d.ts +48 -0
  70. package/packages/pi-coding-agent/dist/core/pty-executor.d.ts.map +1 -0
  71. package/packages/pi-coding-agent/dist/core/pty-executor.js +173 -0
  72. package/packages/pi-coding-agent/dist/core/pty-executor.js.map +1 -0
  73. package/packages/pi-coding-agent/dist/core/retry-handler.d.ts +1 -0
  74. package/packages/pi-coding-agent/dist/core/retry-handler.d.ts.map +1 -1
  75. package/packages/pi-coding-agent/dist/core/retry-handler.js +24 -6
  76. package/packages/pi-coding-agent/dist/core/retry-handler.js.map +1 -1
  77. package/packages/pi-coding-agent/dist/core/retry-handler.test.js +58 -0
  78. package/packages/pi-coding-agent/dist/core/retry-handler.test.js.map +1 -1
  79. package/packages/pi-coding-agent/dist/core/sandbox/index.d.ts +5 -0
  80. package/packages/pi-coding-agent/dist/core/sandbox/index.d.ts.map +1 -0
  81. package/packages/pi-coding-agent/dist/core/sandbox/index.js +5 -0
  82. package/packages/pi-coding-agent/dist/core/sandbox/index.js.map +1 -0
  83. package/packages/pi-coding-agent/dist/core/sandbox/linux-sandbox.d.ts +10 -0
  84. package/packages/pi-coding-agent/dist/core/sandbox/linux-sandbox.d.ts.map +1 -0
  85. package/packages/pi-coding-agent/dist/core/sandbox/linux-sandbox.js +73 -0
  86. package/packages/pi-coding-agent/dist/core/sandbox/linux-sandbox.js.map +1 -0
  87. package/packages/pi-coding-agent/dist/core/sandbox/macos-sandbox.d.ts +6 -0
  88. package/packages/pi-coding-agent/dist/core/sandbox/macos-sandbox.d.ts.map +1 -0
  89. package/packages/pi-coding-agent/dist/core/sandbox/macos-sandbox.js +73 -0
  90. package/packages/pi-coding-agent/dist/core/sandbox/macos-sandbox.js.map +1 -0
  91. package/packages/pi-coding-agent/dist/core/sandbox/sandbox-manager.d.ts +37 -0
  92. package/packages/pi-coding-agent/dist/core/sandbox/sandbox-manager.d.ts.map +1 -0
  93. package/packages/pi-coding-agent/dist/core/sandbox/sandbox-manager.js +200 -0
  94. package/packages/pi-coding-agent/dist/core/sandbox/sandbox-manager.js.map +1 -0
  95. package/packages/pi-coding-agent/dist/core/sandbox/sandbox-policy.d.ts +19 -0
  96. package/packages/pi-coding-agent/dist/core/sandbox/sandbox-policy.d.ts.map +1 -0
  97. package/packages/pi-coding-agent/dist/core/sandbox/sandbox-policy.js +19 -0
  98. package/packages/pi-coding-agent/dist/core/sandbox/sandbox-policy.js.map +1 -0
  99. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  100. package/packages/pi-coding-agent/dist/core/sdk.js +18 -3
  101. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  102. package/packages/pi-coding-agent/dist/core/session-manager.d.ts +6 -0
  103. package/packages/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
  104. package/packages/pi-coding-agent/dist/core/session-manager.js +18 -0
  105. package/packages/pi-coding-agent/dist/core/session-manager.js.map +1 -1
  106. package/packages/pi-coding-agent/dist/core/session-manager.test.js +20 -0
  107. package/packages/pi-coding-agent/dist/core/session-manager.test.js.map +1 -1
  108. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +19 -0
  109. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  110. package/packages/pi-coding-agent/dist/core/settings-manager.js +33 -0
  111. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  112. package/packages/pi-coding-agent/dist/core/slash-commands.d.ts.map +1 -1
  113. package/packages/pi-coding-agent/dist/core/slash-commands.js +1 -0
  114. package/packages/pi-coding-agent/dist/core/slash-commands.js.map +1 -1
  115. package/packages/pi-coding-agent/dist/core/tool-approval.d.ts +8 -0
  116. package/packages/pi-coding-agent/dist/core/tool-approval.d.ts.map +1 -1
  117. package/packages/pi-coding-agent/dist/core/tool-approval.js +12 -2
  118. package/packages/pi-coding-agent/dist/core/tool-approval.js.map +1 -1
  119. package/packages/pi-coding-agent/dist/core/tools/bash.d.ts +6 -0
  120. package/packages/pi-coding-agent/dist/core/tools/bash.d.ts.map +1 -1
  121. package/packages/pi-coding-agent/dist/core/tools/bash.js +100 -65
  122. package/packages/pi-coding-agent/dist/core/tools/bash.js.map +1 -1
  123. package/packages/pi-coding-agent/dist/core/tools/grep.js +1 -1
  124. package/packages/pi-coding-agent/dist/core/tools/grep.js.map +1 -1
  125. package/packages/pi-coding-agent/dist/core/tools/index.d.ts +7 -0
  126. package/packages/pi-coding-agent/dist/core/tools/index.d.ts.map +1 -1
  127. package/packages/pi-coding-agent/dist/core/tools/index.js +23 -2
  128. package/packages/pi-coding-agent/dist/core/tools/index.js.map +1 -1
  129. package/packages/pi-coding-agent/dist/core/tools/pty.d.ts +50 -0
  130. package/packages/pi-coding-agent/dist/core/tools/pty.d.ts.map +1 -0
  131. package/packages/pi-coding-agent/dist/core/tools/pty.js +289 -0
  132. package/packages/pi-coding-agent/dist/core/tools/pty.js.map +1 -0
  133. package/packages/pi-coding-agent/dist/index.d.ts +2 -1
  134. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  135. package/packages/pi-coding-agent/dist/index.js +1 -0
  136. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  137. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/timestamp.test.js +8 -8
  138. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/timestamp.test.js.map +1 -1
  139. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts +3 -1
  140. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  141. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js +13 -6
  142. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js.map +1 -1
  143. package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.d.ts +5 -6
  144. package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
  145. package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.js +31 -56
  146. package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.js.map +1 -1
  147. package/packages/pi-coding-agent/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
  148. package/packages/pi-coding-agent/dist/modes/interactive/components/branch-summary-message.js +1 -4
  149. package/packages/pi-coding-agent/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
  150. package/packages/pi-coding-agent/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
  151. package/packages/pi-coding-agent/dist/modes/interactive/components/compaction-summary-message.js +1 -4
  152. package/packages/pi-coding-agent/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
  153. package/packages/pi-coding-agent/dist/modes/interactive/components/embedded-terminal.d.ts +39 -0
  154. package/packages/pi-coding-agent/dist/modes/interactive/components/embedded-terminal.d.ts.map +1 -0
  155. package/packages/pi-coding-agent/dist/modes/interactive/components/embedded-terminal.js +182 -0
  156. package/packages/pi-coding-agent/dist/modes/interactive/components/embedded-terminal.js.map +1 -0
  157. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
  158. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +7 -0
  159. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
  160. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts +8 -0
  161. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  162. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js +45 -1
  163. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js.map +1 -1
  164. package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
  165. package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.js +2 -4
  166. package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
  167. package/packages/pi-coding-agent/dist/modes/interactive/components/timestamp.d.ts +2 -4
  168. package/packages/pi-coding-agent/dist/modes/interactive/components/timestamp.d.ts.map +1 -1
  169. package/packages/pi-coding-agent/dist/modes/interactive/components/timestamp.js +4 -12
  170. package/packages/pi-coding-agent/dist/modes/interactive/components/timestamp.js.map +1 -1
  171. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +6 -2
  172. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  173. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +196 -98
  174. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  175. package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.js +8 -9
  176. package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.js.map +1 -1
  177. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts +11 -0
  178. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  179. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +52 -11
  180. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  181. package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.d.ts.map +1 -1
  182. package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.js +1 -0
  183. package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.js.map +1 -1
  184. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
  185. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +2 -0
  186. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
  187. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts +3 -0
  188. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -1
  189. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js.map +1 -1
  190. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +30 -0
  191. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  192. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +362 -37
  193. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  194. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.d.ts +1 -0
  195. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.d.ts.map +1 -1
  196. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js +5 -0
  197. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js.map +1 -1
  198. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  199. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js +22 -0
  200. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js.map +1 -1
  201. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js +8 -8
  202. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js.map +1 -1
  203. package/packages/pi-coding-agent/dist/modes/interactive/utils/editor-link.d.ts +28 -0
  204. package/packages/pi-coding-agent/dist/modes/interactive/utils/editor-link.d.ts.map +1 -0
  205. package/packages/pi-coding-agent/dist/modes/interactive/utils/editor-link.js +78 -0
  206. package/packages/pi-coding-agent/dist/modes/interactive/utils/editor-link.js.map +1 -0
  207. package/packages/pi-coding-agent/dist/utils/terminal-screen.d.ts +10 -0
  208. package/packages/pi-coding-agent/dist/utils/terminal-screen.d.ts.map +1 -0
  209. package/packages/pi-coding-agent/dist/utils/terminal-screen.js +67 -0
  210. package/packages/pi-coding-agent/dist/utils/terminal-screen.js.map +1 -0
  211. package/packages/pi-coding-agent/dist/utils/terminal-serializer.d.ts +7 -0
  212. package/packages/pi-coding-agent/dist/utils/terminal-serializer.d.ts.map +1 -0
  213. package/packages/pi-coding-agent/dist/utils/terminal-serializer.js +67 -0
  214. package/packages/pi-coding-agent/dist/utils/terminal-serializer.js.map +1 -0
  215. package/packages/pi-coding-agent/package.json +9 -4
  216. package/packages/pi-coding-agent/src/core/agent-session.ts +48 -2
  217. package/packages/pi-coding-agent/src/core/auth-storage.test.ts +33 -0
  218. package/packages/pi-coding-agent/src/core/auth-storage.ts +36 -5
  219. package/packages/pi-coding-agent/src/core/bash-executor.ts +142 -106
  220. package/packages/pi-coding-agent/src/core/extensions/types.ts +1 -1
  221. package/packages/pi-coding-agent/src/core/footer-data-provider.ts +10 -1
  222. package/packages/pi-coding-agent/src/core/keybindings.ts +4 -1
  223. package/packages/pi-coding-agent/src/core/lsp/lsp.md +2 -0
  224. package/packages/pi-coding-agent/src/core/messages.ts +2 -1
  225. package/packages/pi-coding-agent/src/core/pty-executor.ts +229 -0
  226. package/packages/pi-coding-agent/src/core/retry-handler.test.ts +73 -0
  227. package/packages/pi-coding-agent/src/core/retry-handler.ts +25 -6
  228. package/packages/pi-coding-agent/src/core/sandbox/index.ts +10 -0
  229. package/packages/pi-coding-agent/src/core/sandbox/linux-sandbox.ts +96 -0
  230. package/packages/pi-coding-agent/src/core/sandbox/macos-sandbox.ts +83 -0
  231. package/packages/pi-coding-agent/src/core/sandbox/sandbox-manager.ts +240 -0
  232. package/packages/pi-coding-agent/src/core/sandbox/sandbox-policy.ts +39 -0
  233. package/packages/pi-coding-agent/src/core/sdk.ts +18 -3
  234. package/packages/pi-coding-agent/src/core/session-manager.test.ts +22 -0
  235. package/packages/pi-coding-agent/src/core/session-manager.ts +20 -0
  236. package/packages/pi-coding-agent/src/core/settings-manager.ts +53 -0
  237. package/packages/pi-coding-agent/src/core/slash-commands.ts +1 -0
  238. package/packages/pi-coding-agent/src/core/tool-approval.ts +22 -2
  239. package/packages/pi-coding-agent/src/core/tools/bash.ts +107 -65
  240. package/packages/pi-coding-agent/src/core/tools/grep.ts +1 -1
  241. package/packages/pi-coding-agent/src/core/tools/index.ts +35 -2
  242. package/packages/pi-coding-agent/src/core/tools/pty.ts +354 -0
  243. package/packages/pi-coding-agent/src/index.ts +9 -0
  244. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/timestamp.test.ts +8 -8
  245. package/packages/pi-coding-agent/src/modes/interactive/components/assistant-message.ts +14 -4
  246. package/packages/pi-coding-agent/src/modes/interactive/components/bash-execution.ts +43 -62
  247. package/packages/pi-coding-agent/src/modes/interactive/components/branch-summary-message.ts +1 -3
  248. package/packages/pi-coding-agent/src/modes/interactive/components/compaction-summary-message.ts +1 -3
  249. package/packages/pi-coding-agent/src/modes/interactive/components/embedded-terminal.ts +224 -0
  250. package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +7 -0
  251. package/packages/pi-coding-agent/src/modes/interactive/components/settings-selector.ts +57 -1
  252. package/packages/pi-coding-agent/src/modes/interactive/components/skill-invocation-message.ts +2 -3
  253. package/packages/pi-coding-agent/src/modes/interactive/components/timestamp.ts +4 -14
  254. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +197 -101
  255. package/packages/pi-coding-agent/src/modes/interactive/components/user-message.ts +8 -8
  256. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +57 -10
  257. package/packages/pi-coding-agent/src/modes/interactive/controllers/extension-ui-controller.ts +1 -0
  258. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +2 -0
  259. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-state.ts +3 -0
  260. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +410 -42
  261. package/packages/pi-coding-agent/src/modes/interactive/slash-command-handlers.ts +6 -0
  262. package/packages/pi-coding-agent/src/modes/interactive/theme/theme.ts +24 -0
  263. package/packages/pi-coding-agent/src/modes/interactive/theme/themes.ts +8 -8
  264. package/packages/pi-coding-agent/src/modes/interactive/utils/editor-link.ts +101 -0
  265. package/packages/pi-coding-agent/src/utils/terminal-screen.ts +77 -0
  266. package/packages/pi-coding-agent/src/utils/terminal-serializer.ts +72 -0
  267. package/packages/pi-tui/dist/components/animated-status.d.ts +66 -0
  268. package/packages/pi-tui/dist/components/animated-status.d.ts.map +1 -0
  269. package/packages/pi-tui/dist/components/animated-status.js +168 -0
  270. package/packages/pi-tui/dist/components/animated-status.js.map +1 -0
  271. package/packages/pi-tui/dist/components/loader.d.ts +26 -6
  272. package/packages/pi-tui/dist/components/loader.d.ts.map +1 -1
  273. package/packages/pi-tui/dist/components/loader.js +178 -18
  274. package/packages/pi-tui/dist/components/loader.js.map +1 -1
  275. package/packages/pi-tui/dist/components/markdown.d.ts +7 -0
  276. package/packages/pi-tui/dist/components/markdown.d.ts.map +1 -1
  277. package/packages/pi-tui/dist/components/markdown.js +11 -4
  278. package/packages/pi-tui/dist/components/markdown.js.map +1 -1
  279. package/packages/pi-tui/src/components/loader.ts +196 -19
  280. package/packages/pi-tui/src/components/markdown.ts +19 -4
  281. package/pkg/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  282. package/pkg/dist/modes/interactive/theme/theme.js +22 -0
  283. package/pkg/dist/modes/interactive/theme/theme.js.map +1 -1
  284. package/pkg/dist/modes/interactive/theme/themes.js +8 -8
  285. package/pkg/dist/modes/interactive/theme/themes.js.map +1 -1
  286. package/pkg/package.json +1 -1
  287. package/src/resources/extensions/async-jobs/async-bash-tool.ts +13 -0
  288. package/src/resources/extensions/async-jobs/await-tool.ts +13 -0
  289. package/src/resources/extensions/async-jobs/cancel-job-tool.ts +8 -0
  290. package/src/resources/extensions/cache-timer/index.ts +101 -96
  291. package/src/resources/extensions/codex-rotate/IMPLEMENTATION.md +18 -13
  292. package/src/resources/extensions/codex-rotate/README.md +9 -3
  293. package/src/resources/extensions/codex-rotate/commands.ts +335 -322
  294. package/src/resources/extensions/codex-rotate/index.ts +94 -107
  295. package/src/resources/extensions/codex-rotate/oauth.ts +76 -12
  296. package/src/resources/extensions/codex-rotate/quota.ts +8 -7
  297. package/src/resources/extensions/codex-rotate/sync.ts +5 -0
  298. package/src/resources/extensions/memory/auto-extract.ts +297 -203
  299. package/src/resources/extensions/memory/tests/auto-extract.test.ts +191 -144
  300. package/src/resources/extensions/shared/interview-ui.ts +2 -1
  301. package/src/resources/extensions/shared/rtk.js +112 -0
  302. package/src/resources/extensions/slash-commands/plan.ts +11 -0
  303. package/src/resources/extensions/subagent/background-job-manager.ts +217 -0
  304. package/src/resources/extensions/subagent/background-runner.ts +69 -0
  305. package/src/resources/extensions/subagent/background-types.ts +54 -0
  306. package/src/resources/extensions/subagent/index.ts +1653 -1123
package/README.md CHANGED
@@ -242,8 +242,9 @@ A few quality-of-life touches in the TUI:
242
242
 
243
243
  - the footer can show a live cache timer for the current prompt-cache window
244
244
  - `/hotkeys` gives you a full shortcut reference on demand
245
- - `/settings` now includes toggles for Codex rotate, the cache timer, RTK shell-command compression, and a configurable **Main accent** preset
245
+ - `/settings` now includes toggles for Codex rotate, the cache timer, **Pin last prompt**, RTK shell-command compression, and a configurable **Main accent** preset
246
246
  - changing the main accent also updates accent-driven UI elements and the text input border across thinking levels
247
+ - when **Pin last prompt** is enabled, LSD keeps your most recent non-command prompt visible above the editor as a lightweight reminder
247
248
 
248
249
  Some workflow/automation commands still use the legacy namespace:
249
250
 
package/dist/cli.js CHANGED
@@ -58,6 +58,14 @@ function parseCliArgs(argv) {
58
58
  else if (arg === '--model' && i + 1 < args.length) {
59
59
  flags.model = args[++i];
60
60
  }
61
+ else if (arg === '--sandbox' && i + 1 < args.length) {
62
+ const value = args[++i];
63
+ if (value === 'none' || value === 'workspace-write' || value === 'auto')
64
+ flags.sandbox = value;
65
+ }
66
+ else if (arg === '--no-sandbox') {
67
+ flags.noSandbox = true;
68
+ }
61
69
  else if (arg === '--extension' && i + 1 < args.length) {
62
70
  flags.extensions.push(args[++i]);
63
71
  }
@@ -249,6 +257,12 @@ const modelRegistry = new ModelRegistry(authStorage, modelsJsonPath);
249
257
  markStartup('ModelRegistry');
250
258
  const settingsManager = SettingsManager.create(agentDir);
251
259
  markStartup('SettingsManager.create');
260
+ if (cliFlags.noSandbox) {
261
+ process.env.PI_NO_SANDBOX = '1';
262
+ }
263
+ else if (cliFlags.sandbox) {
264
+ process.env.PI_SANDBOX = cliFlags.sandbox;
265
+ }
252
266
  process.env.LUCENT_CODE_PERMISSION_MODE = settingsManager.getPermissionMode();
253
267
  const configuredClassifierModel = settingsManager.getClassifierModel();
254
268
  if (configuredClassifierModel) {
@@ -18,6 +18,8 @@ export interface HeadlessOptions {
18
18
  json: boolean;
19
19
  outputFormat: OutputFormat;
20
20
  model?: string;
21
+ sandbox?: 'none' | 'workspace-write' | 'auto';
22
+ noSandbox?: boolean;
21
23
  command: string;
22
24
  commandArgs: string[];
23
25
  context?: string;
package/dist/headless.js CHANGED
@@ -86,6 +86,15 @@ export function parseHeadlessArgs(argv) {
86
86
  // --model can also be passed from the main CLI; headless-specific takes precedence
87
87
  options.model = args[++i];
88
88
  }
89
+ else if (arg === '--sandbox' && i + 1 < args.length) {
90
+ const value = args[++i];
91
+ if (value === 'none' || value === 'workspace-write' || value === 'auto') {
92
+ options.sandbox = value;
93
+ }
94
+ }
95
+ else if (arg === '--no-sandbox') {
96
+ options.noSandbox = true;
97
+ }
89
98
  else if (arg === '--context' && i + 1 < args.length) {
90
99
  options.context = args[++i];
91
100
  }
@@ -189,6 +198,14 @@ async function runHeadlessOnce(options, restartCount) {
189
198
  if (isAutoMode && options.timeout === 300_000) {
190
199
  options.timeout = 0;
191
200
  }
201
+ if (!options.json) {
202
+ if (options.noSandbox) {
203
+ process.stderr.write('[headless] Sandbox: disabled by flag\n');
204
+ }
205
+ else if (options.sandbox) {
206
+ process.stderr.write(`[headless] Sandbox: ${options.sandbox}\n`);
207
+ }
208
+ }
192
209
  // Supervised mode cannot share stdin with --context -
193
210
  if (options.supervised && options.context === '-') {
194
211
  process.stderr.write('[headless] Error: --supervised cannot be used with --context - (both require stdin)\n');
@@ -271,6 +288,12 @@ async function runHeadlessOnce(options, restartCount) {
271
288
  if (injector) {
272
289
  clientOptions.env = injector.getSecretEnvVars();
273
290
  }
291
+ if (options.noSandbox) {
292
+ clientOptions.env = { ...(clientOptions.env || {}), PI_NO_SANDBOX: '1' };
293
+ }
294
+ else if (options.sandbox) {
295
+ clientOptions.env = { ...(clientOptions.env || {}), PI_SANDBOX: options.sandbox };
296
+ }
274
297
  // Signal headless mode to the GSD extension (skips UAT human pause, etc.)
275
298
  clientOptions.env = { ...(clientOptions.env || {}), GSD_HEADLESS: '1' };
276
299
  // Propagate --bare to the child process
@@ -6,6 +6,7 @@
6
6
  * with await_job.
7
7
  */
8
8
  import { getShellConfig, sanitizeCommand, DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, } from "@gsd/pi-coding-agent";
9
+ import { Text } from "@gsd/pi-tui";
9
10
  import { Type } from "@sinclair/typebox";
10
11
  import { spawn, spawnSync } from "node:child_process";
11
12
  import { createWriteStream } from "node:fs";
@@ -70,6 +71,19 @@ export function createAsyncBashTool(getManager, getCwd) {
70
71
  "Check /jobs to see all running and recent background jobs.",
71
72
  ],
72
73
  parameters: schema,
74
+ renderCall(args, theme, options) {
75
+ const cmd = args.command ?? "";
76
+ const display = cmd.length > 80 ? cmd.slice(0, 77) + "..." : cmd;
77
+ const indicator = options?.statusIndicator ? `${options.statusIndicator} ` : "";
78
+ let text = indicator + theme.fg("toolTitle", theme.bold("async_bash "));
79
+ text += theme.fg("bashMode", "$ ");
80
+ text += theme.fg("accent", display || theme.fg("muted", "..."));
81
+ if (args.label)
82
+ text += theme.fg("muted", ` (${args.label})`);
83
+ if (args.timeout)
84
+ text += theme.fg("dim", ` timeout:${args.timeout}s`);
85
+ return new Text(text, 0, 0);
86
+ },
73
87
  async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
74
88
  const manager = getManager();
75
89
  const cwd = getCwd();
@@ -4,6 +4,7 @@
4
4
  * If specific job IDs are provided, waits for those jobs.
5
5
  * If omitted, waits for any running job to complete.
6
6
  */
7
+ import { Text } from "@gsd/pi-tui";
7
8
  import { Type } from "@sinclair/typebox";
8
9
  const DEFAULT_TIMEOUT_SECONDS = 120;
9
10
  const schema = Type.Object({
@@ -21,6 +22,19 @@ export function createAwaitTool(getManager) {
21
22
  label: "Await Background Job",
22
23
  description: "Wait for background jobs to complete. Provide specific job IDs or omit to wait for the next job that finishes. Returns results of completed jobs.",
23
24
  parameters: schema,
25
+ renderCall(args, theme, options) {
26
+ const indicator = options?.statusIndicator ? `${options.statusIndicator} ` : "";
27
+ let text = indicator + theme.fg("toolTitle", theme.bold("await_job"));
28
+ if (args.jobs && args.jobs.length > 0) {
29
+ text += " " + theme.fg("accent", args.jobs.join(", "));
30
+ }
31
+ else {
32
+ text += theme.fg("muted", " (any)");
33
+ }
34
+ if (args.timeout)
35
+ text += theme.fg("dim", ` timeout:${args.timeout}s`);
36
+ return new Text(text, 0, 0);
37
+ },
24
38
  async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
25
39
  const manager = getManager();
26
40
  const { jobs: jobIds, timeout } = params;
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * cancel_job tool — cancel a running background job.
3
3
  */
4
+ import { Text } from "@gsd/pi-tui";
4
5
  import { Type } from "@sinclair/typebox";
5
6
  const schema = Type.Object({
6
7
  job_id: Type.String({ description: "The background job ID to cancel (e.g. bg_a1b2c3d4)" }),
@@ -11,6 +12,12 @@ export function createCancelJobTool(getManager) {
11
12
  label: "Cancel Background Job",
12
13
  description: "Cancel a running background job by its ID.",
13
14
  parameters: schema,
15
+ renderCall(args, theme, options) {
16
+ const indicator = options?.statusIndicator ? `${options.statusIndicator} ` : "";
17
+ let text = indicator + theme.fg("toolTitle", theme.bold("cancel_job "));
18
+ text += theme.fg("accent", args.job_id);
19
+ return new Text(text, 0, 0);
20
+ },
14
21
  async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
15
22
  const manager = getManager();
16
23
  const result = manager.cancel(params.job_id);
@@ -14,6 +14,7 @@ import { existsSync, readFileSync, writeFileSync } from "node:fs";
14
14
  import { join } from "node:path";
15
15
  import { getAgentDir } from "@gsd/pi-coding-agent";
16
16
  const STATUS_KEY = "cache-timer";
17
+ const IS_MEMORY_MAINTENANCE_WORKER = process.env.LSD_MEMORY_EXTRACT === "1" || process.env.LSD_MEMORY_DREAM === "1";
17
18
  // ANSI color codes for timer display
18
19
  const ANSI_RESET = "\x1b[0m";
19
20
  const ANSI_YELLOW = "\x1b[33m";
@@ -65,6 +66,9 @@ function formatElapsed(ms) {
65
66
  return `⏱ ${time}`;
66
67
  }
67
68
  export default function cacheTimerExtension(pi) {
69
+ if (IS_MEMORY_MAINTENANCE_WORKER) {
70
+ return;
71
+ }
68
72
  let timer = null;
69
73
  let startTime = null;
70
74
  let enabled = readEnabled();
@@ -18,6 +18,7 @@ Implemented a Codex OAuth rotation extension for LSD that manages multiple ChatG
18
18
  - Writes `openai-codex` credential array with `api_key` type
19
19
  - Avoids the `set()` method (which appends duplicates)
20
20
  - Maintains stable credential order for index-based backoff
21
+ - Followed by a live `AuthStorage.reload()` in the extension so the running process sees account changes immediately
21
22
 
22
23
  3. **Background Refresh Timer** (`index.ts`)
23
24
  - Runs every 10 minutes
@@ -42,11 +43,10 @@ Implemented a Codex OAuth rotation extension for LSD that manages multiple ChatG
42
43
  - `/codex import-cockpit` - Import from Cockpit
43
44
  - `/codex sync` - Force refresh all tokens
44
45
 
45
- 6. **Error Detection** (`quota.ts`)
46
- - Detects quota/rate limit errors
47
- - Detects auth errors (401, expired tokens)
48
- - Classifies errors by type
49
- - Integrates with `AuthStorage.markUsageLimitReached()`
46
+ 6. **Error Detection / Rotation Path**
47
+ - Quota/rate-limit/auth classification lives in `quota.ts`
48
+ - Same-turn retry and per-credential backoff are performed by core `RetryHandler`
49
+ - The extension's responsibility is to keep `auth.json` and the live in-memory auth state in sync
50
50
 
51
51
  ## File Structure
52
52
 
@@ -87,6 +87,7 @@ Using `FileAuthStorageBackend.withLockAsync()`:
87
87
  - Prevents race conditions with multiple LSD instances
88
88
  - Replaces entire credential array (not just updating keys)
89
89
  - Maintains stable order for index-based backoff
90
+ - Requires a follow-up live auth reload in the current process so retry rotation uses the updated credential set
90
91
 
91
92
  ### 4. Session-Sticky Credential Selection
92
93
 
@@ -105,15 +106,15 @@ LSD already uses session-sticky selection by default:
105
106
 
106
107
  ## Next Steps: Phase 2 (Resilience)
107
108
 
108
- The following features were planned but not yet implemented:
109
+ Remaining improvements that could still be added:
109
110
 
110
- 1. **Enhanced error detection in `agent_end`**
111
- - Already implemented basic error detection
112
- - Could add more sophisticated quota pattern matching
111
+ 1. **Richer `/codex status` diagnostics**
112
+ - Show live backoff / credential-availability state from `AuthStorage`
113
+ - Make it easier to verify when an account was rotated away after a usage-limit hit
113
114
 
114
- 2. **Better credential change listener**
115
- - Could listen for external auth.json modifications
116
- - Would re-sync on change detection
115
+ 2. **External change detection**
116
+ - Could listen for external auth/account file modifications
117
+ - Would re-sync / reload on change detection
117
118
 
118
119
  3. **Migration helpers**
119
120
  - Could add `/codex migrate` to assist users moving from Cockpit
@@ -121,8 +122,12 @@ The following features were planned but not yet implemented:
121
122
 
122
123
  ## Testing
123
124
 
124
- To test the extension:
125
+ Implemented regression coverage now includes:
126
+ - `src/tests/codex-rotate-auth-reload.test.ts`
127
+ - Verifies that codex-rotate syncs require a live auth reload for the running process to see the latest credentials
128
+ - Verifies `quota_exhausted` backoff on one `openai-codex` credential causes the next retry to select the next credential
125
129
 
130
+ Manual smoke test:
126
131
  1. Add an account: `/codex add`
127
132
  2. Check status: `/codex status`
128
133
  3. Add another account: `/codex add`
@@ -97,14 +97,20 @@ Codex access tokens work identically to API keys:
97
97
  - Background timer runs every 10 minutes
98
98
  - Tokens are refreshed when expiring within 5 minutes
99
99
  - Refreshed tokens are atomically synced to auth.json
100
+ - After every successful sync, the extension reloads live `AuthStorage` so retries and rotation see the latest credentials immediately
100
101
  - Failed refreshes disable the account with a reason
101
102
 
102
103
  ### Error Handling
103
104
 
104
- The extension hooks into `agent_end` events to detect:
105
+ The extension now relies on LSD core retry/backoff handling rather than its own `agent_end` hook:
106
+ - The Codex provider surfaces friendly usage-limit / rate-limit / auth errors
107
+ - Core `RetryHandler` classifies those failures and calls `markUsageLimitReached(...)`
108
+ - If another `openai-codex` credential is available, LSD automatically rotates and retries the same prompt
105
109
  - Rate limit errors (429) → 30s backoff
106
- - Quota exhausted errors → 30min backoff
107
- - Auth errors (401) → treated as rate limits for immediate rotation
110
+ - Quota exhausted / usage limit errors → 30min backoff
111
+ - Auth errors (401) → treated as immediate credential-rotation failures
112
+
113
+ To make that work reliably, the extension reloads the live auth state after every successful sync so the retry handler sees the current credential pool immediately.
108
114
 
109
115
  ## Security
110
116
 
@@ -63,6 +63,13 @@ function displayAccounts(ctx, accounts) {
63
63
  });
64
64
  ctx.ui.notify(lines.join("\n"), "info");
65
65
  }
66
+ async function syncAccountsToAuthAndReload(ctx) {
67
+ const synced = await syncAccountsToAuth(getAllAccounts());
68
+ if (synced) {
69
+ ctx.modelRegistry.authStorage.reload();
70
+ }
71
+ return synced;
72
+ }
66
73
  /**
67
74
  * Register the /codex command
68
75
  */
@@ -104,7 +111,10 @@ export function registerCodexCommand(pi) {
104
111
  switch (sub) {
105
112
  case "add": {
106
113
  ctx.ui.notify("Starting OAuth login flow...", "info");
107
- const accountData = await performOAuthLogin();
114
+ const accountData = await performOAuthLogin(undefined, {
115
+ onStatus: (msg) => ctx.ui.notify(msg, "info"),
116
+ onManualCodeInput: async () => (await ctx.ui.input("Paste the redirect URL from your browser:", "http://localhost:...")) ?? "",
117
+ });
108
118
  // Prompt for email (optional)
109
119
  const emailInput = await ctx.ui.input("Email for this account (optional, press Enter to skip)", "");
110
120
  const email = emailInput || undefined;
@@ -115,7 +125,7 @@ export function registerCodexCommand(pi) {
115
125
  disabled: false,
116
126
  });
117
127
  // Sync to auth.json
118
- const success = await syncAccountsToAuth(getAllAccounts());
128
+ const success = await syncAccountsToAuthAndReload(ctx);
119
129
  if (success) {
120
130
  ctx.ui.notify(`Added account: ${email || account.accountId}. Synced to auth.json.`, "success");
121
131
  }
@@ -173,7 +183,7 @@ export function registerCodexCommand(pi) {
173
183
  const confirmed = await ctx.ui.select(`Remove account: ${account.email || account.accountId}?`, ["Yes, remove", "Cancel"], { signal: AbortSignal.timeout(30000) });
174
184
  if (confirmed === "Yes, remove") {
175
185
  removeAccount(account.id);
176
- await syncAccountsToAuth(getAllAccounts());
186
+ await syncAccountsToAuthAndReload(ctx);
177
187
  ctx.ui.notify(`Removed account: ${account.email || account.accountId}`, "success");
178
188
  }
179
189
  return;
@@ -197,7 +207,7 @@ export function registerCodexCommand(pi) {
197
207
  return;
198
208
  }
199
209
  updateAccount(account.id, { disabled: false, disabledReason: undefined });
200
- await syncAccountsToAuth(getAllAccounts());
210
+ await syncAccountsToAuthAndReload(ctx);
201
211
  ctx.ui.notify(`Enabled account: ${account.email || account.accountId}`, "success");
202
212
  return;
203
213
  }
@@ -220,7 +230,7 @@ export function registerCodexCommand(pi) {
220
230
  return;
221
231
  }
222
232
  updateAccount(account.id, { disabled: true, disabledReason: "manually disabled" });
223
- await syncAccountsToAuth(getAllAccounts());
233
+ await syncAccountsToAuthAndReload(ctx);
224
234
  ctx.ui.notify(`Disabled account: ${account.email || account.accountId}`, "success");
225
235
  return;
226
236
  }
@@ -231,7 +241,7 @@ export function registerCodexCommand(pi) {
231
241
  ctx.ui.notify("No account found to import.", "warning");
232
242
  return;
233
243
  }
234
- const account = addAccount({
244
+ addAccount({
235
245
  email: imported.email,
236
246
  accountId: imported.accountId,
237
247
  refreshToken: imported.refreshToken,
@@ -240,7 +250,7 @@ export function registerCodexCommand(pi) {
240
250
  lastUsed: undefined,
241
251
  disabled: false,
242
252
  });
243
- await syncAccountsToAuth(getAllAccounts());
253
+ await syncAccountsToAuthAndReload(ctx);
244
254
  ctx.ui.notify(`Imported account: ${imported.email || imported.accountId}`, "success");
245
255
  return;
246
256
  }
@@ -254,7 +264,7 @@ export function registerCodexCommand(pi) {
254
264
  for (const acc of imported) {
255
265
  addAccount(acc);
256
266
  }
257
- await syncAccountsToAuth(getAllAccounts());
267
+ await syncAccountsToAuthAndReload(ctx);
258
268
  ctx.ui.notify(`Imported ${imported.length} account(s) from Cockpit Tools`, "success");
259
269
  return;
260
270
  }
@@ -275,7 +285,7 @@ export function registerCodexCommand(pi) {
275
285
  results.failed++;
276
286
  }
277
287
  }
278
- await syncAccountsToAuth(getAllAccounts());
288
+ await syncAccountsToAuthAndReload(ctx);
279
289
  if (results.failed === 0) {
280
290
  ctx.ui.notify(`Synced ${results.success} account(s) to auth.json`, "success");
281
291
  }
@@ -7,14 +7,16 @@
7
7
  import { getAllAccounts, updateAccount, getAccountsNeedingRefresh } from "./accounts.js";
8
8
  import { syncAccountsToAuth } from "./sync.js";
9
9
  import { registerCodexCommand } from "./commands.js";
10
- import { classifyError, markCredentialBackoff, shouldBackoffCredential } from "./quota.js";
11
- import { REFRESH_INTERVAL_MS, PROVIDER_NAME } from "./config.js";
12
- import { logCodexRotateError, logCodexRotateSwitch } from "./logger.js";
10
+ import { REFRESH_INTERVAL_MS } from "./config.js";
11
+ import { logCodexRotateError } from "./logger.js";
13
12
  let refreshTimer = null;
13
+ async function reloadLiveAuthState(ctx) {
14
+ ctx.modelRegistry.authStorage.reload();
15
+ }
14
16
  /**
15
17
  * Refresh all accounts that need it
16
18
  */
17
- async function refreshExpiringAccounts() {
19
+ async function refreshExpiringAccounts(ctx) {
18
20
  try {
19
21
  const accountsNeedingRefresh = getAccountsNeedingRefresh();
20
22
  if (accountsNeedingRefresh.length === 0) {
@@ -42,7 +44,10 @@ async function refreshExpiringAccounts() {
42
44
  if (successCount > 0) {
43
45
  // Sync refreshed accounts to auth.json
44
46
  const allAccounts = getAllAccounts();
45
- await syncAccountsToAuth(allAccounts);
47
+ const synced = await syncAccountsToAuth(allAccounts);
48
+ if (synced) {
49
+ await reloadLiveAuthState(ctx);
50
+ }
46
51
  }
47
52
  if (failCount > 0 && successCount === 0) {
48
53
  logCodexRotateError(`Failed to refresh ${failCount} account(s)`);
@@ -55,12 +60,12 @@ async function refreshExpiringAccounts() {
55
60
  /**
56
61
  * Start the background refresh timer
57
62
  */
58
- function startRefreshTimer() {
63
+ function startRefreshTimer(ctx) {
59
64
  if (refreshTimer) {
60
65
  clearInterval(refreshTimer);
61
66
  }
62
67
  refreshTimer = setInterval(() => {
63
- void refreshExpiringAccounts();
68
+ void refreshExpiringAccounts(ctx);
64
69
  }, REFRESH_INTERVAL_MS);
65
70
  }
66
71
  /**
@@ -79,46 +84,26 @@ export default function CodexRotateExtension(pi) {
79
84
  // Register commands
80
85
  registerCodexCommand(pi);
81
86
  // Session start hook
82
- pi.on("session_start", async (_event) => {
87
+ pi.on("session_start", async (_event, ctx) => {
83
88
  const accounts = getAllAccounts();
84
89
  if (accounts.length === 0) {
85
90
  return;
86
91
  }
87
92
  // Refresh any expiring accounts immediately
88
- await refreshExpiringAccounts();
93
+ await refreshExpiringAccounts(ctx);
89
94
  // Sync to auth.json
90
- await syncAccountsToAuth(getAllAccounts());
95
+ const synced = await syncAccountsToAuth(getAllAccounts());
96
+ if (synced) {
97
+ await reloadLiveAuthState(ctx);
98
+ }
91
99
  // Start background refresh timer
92
- startRefreshTimer();
100
+ startRefreshTimer(ctx);
93
101
  });
94
102
  // Session shutdown hook
95
103
  pi.on("session_shutdown", () => {
96
104
  stopRefreshTimer();
97
105
  });
98
- // Agent end hook - detect quota/auth errors and backoff credentials
99
- pi.on("agent_end", async (event, ctx) => {
100
- try {
101
- const messages = event.messages;
102
- const lastAssistant = [...messages].reverse().find((message) => message.role === "assistant");
103
- if (!lastAssistant || !("errorMessage" in lastAssistant) || !lastAssistant.errorMessage)
104
- return;
105
- const errorMessage = lastAssistant.errorMessage;
106
- if (!shouldBackoffCredential(errorMessage))
107
- return;
108
- const errorType = classifyError(errorMessage);
109
- const sessionId = ctx.sessionManager.getSessionId();
110
- const anotherAvailable = markCredentialBackoff(PROVIDER_NAME, sessionId, errorType);
111
- if (anotherAvailable) {
112
- logCodexRotateSwitch(`Auto-switched account after ${errorType} error`);
113
- ctx.ui.notify("Codex credential backed off, rotating to next account", "info");
114
- }
115
- else {
116
- logCodexRotateError(`All accounts backed off after ${errorType} error`);
117
- ctx.ui.notify("All Codex credentials are backed off. Please wait before retrying.", "warning");
118
- }
119
- }
120
- catch (error) {
121
- logCodexRotateError("Error in agent_end handler:", error);
122
- }
123
- });
106
+ // Agent end hook intentionally omitted.
107
+ // Credential backoff + same-turn retry now lives in the core RetryHandler,
108
+ // which knows the actual credential index used for the current session.
124
109
  }
@@ -1,7 +1,11 @@
1
1
  /**
2
- * OAuth flow wrapper for Codex account management
2
+ * OAuth flow wrapper for Codex account management.
3
+ *
4
+ * Uses authStorage.login('openai-codex', callbacks) — the exact same code
5
+ * path as the onboarding wizard — so the browser auto-redirect flow, PKCE
6
+ * exchange, and manual-paste fallback all work identically.
3
7
  */
4
- import { loginOpenAICodex, refreshOpenAICodexToken } from "@gsd/pi-ai/oauth";
8
+ import { refreshOpenAICodexToken } from "@gsd/pi-ai/oauth";
5
9
  import { logCodexRotateError } from "./logger.js";
6
10
  function getAccountId(credentials) {
7
11
  if (typeof credentials.accountId !== "string" || credentials.accountId.length === 0) {
@@ -9,6 +13,14 @@ function getAccountId(credentials) {
9
13
  }
10
14
  return credentials.accountId;
11
15
  }
16
+ /**
17
+ * Get the AuthStorage instance (same pattern as quota.ts)
18
+ */
19
+ async function getAuthStorage() {
20
+ const specifier = "@gsd/pi-coding-agent/dist/core/auth-storage.js";
21
+ const mod = await import(/* webpackIgnore: true */ specifier);
22
+ return new mod.AuthStorage();
23
+ }
12
24
  function asObject(value) {
13
25
  return value !== null && typeof value === "object" ? value : null;
14
26
  }
@@ -19,23 +31,68 @@ function getRequiredRefreshToken(data) {
19
31
  const refreshToken = data.refreshToken ?? data.refresh_token;
20
32
  return typeof refreshToken === "string" && refreshToken.length > 0 ? refreshToken : null;
21
33
  }
34
+ /** Open a URL in the system browser (best-effort, non-blocking) */
35
+ async function openBrowser(url) {
36
+ try {
37
+ const { execFile } = await import("node:child_process");
38
+ if (process.platform === "win32") {
39
+ execFile("powershell", ["-c", `Start-Process '${url.replace(/'/g, "''")}'`], () => { });
40
+ }
41
+ else {
42
+ const cmd = process.platform === "darwin" ? "open" : "xdg-open";
43
+ execFile(cmd, [url], () => { });
44
+ }
45
+ }
46
+ catch {
47
+ // Browser open failed — URL still shown via onStatus
48
+ }
49
+ }
22
50
  /**
23
- * Perform OAuth login and return a new Codex account
51
+ * Perform OAuth login and return a new Codex account.
52
+ *
53
+ * Delegates to authStorage.login('openai-codex', callbacks) — the exact same
54
+ * code path used by the onboarding wizard. After login completes, reads back
55
+ * the stored OAuth credential to extract the tokens for the account store.
24
56
  */
25
- export async function performOAuthLogin(email) {
26
- const credentials = await loginOpenAICodex({
27
- onAuth: () => { },
28
- onPrompt: async () => {
29
- throw new Error("OAuth browser flow failed. Please try again.");
57
+ export async function performOAuthLogin(email, callbacks) {
58
+ const { onStatus, onManualCodeInput } = callbacks ?? {};
59
+ const authStorage = await getAuthStorage();
60
+ // Use authStorage.login() the exact same path as onboarding
61
+ await authStorage.login("openai-codex", {
62
+ onAuth: (info) => {
63
+ onStatus?.(`Opening browser for Codex OAuth...\nURL: ${info.url}`);
64
+ openBrowser(info.url);
65
+ if (info.instructions) {
66
+ onStatus?.(info.instructions);
67
+ }
68
+ },
69
+ onPrompt: async (prompt) => {
70
+ // Fallback: if onManualCodeInput is available, use it for the prompt too
71
+ if (onManualCodeInput) {
72
+ return onManualCodeInput();
73
+ }
74
+ throw new Error(`OAuth browser flow failed: ${prompt.message}`);
30
75
  },
31
- onProgress: () => { },
76
+ onProgress: (message) => {
77
+ onStatus?.(message);
78
+ },
79
+ onManualCodeInput,
32
80
  });
81
+ // Read back the stored credential
82
+ const credential = authStorage.get("openai-codex");
83
+ if (!credential || credential.type !== "oauth") {
84
+ throw new Error("OAuth login succeeded but no credential was stored");
85
+ }
86
+ const { access, refresh, expires, accountId } = credential;
87
+ if (!access || !refresh || !accountId) {
88
+ throw new Error("Stored OAuth credential is missing required fields");
89
+ }
33
90
  return {
34
91
  email,
35
- accountId: getAccountId(credentials),
36
- refreshToken: credentials.refresh,
37
- accessToken: credentials.access,
38
- expiresAt: credentials.expires,
92
+ accountId: accountId,
93
+ refreshToken: refresh,
94
+ accessToken: access,
95
+ expiresAt: expires,
39
96
  };
40
97
  }
41
98
  /**
@@ -66,10 +66,11 @@ export function extractErrorFromResponse(response) {
66
66
  /**
67
67
  * Get the AuthStorage instance for marking usage limits
68
68
  */
69
- function getAuthStorage() {
70
- // Dynamic import to avoid top-level dependencies
71
- const { AuthStorage } = require("@gsd/pi-coding-agent/dist/core/auth-storage.js");
72
- return new AuthStorage();
69
+ async function getAuthStorage() {
70
+ // Dynamic import to avoid top-level dependencies (require not available in ESM context)
71
+ const specifier = "@gsd/pi-coding-agent/dist/core/auth-storage.js";
72
+ const mod = await import(/* webpackIgnore: true */ specifier);
73
+ return new mod.AuthStorage();
73
74
  }
74
75
  /**
75
76
  * Mark a credential as rate-limited/quota exhausted
@@ -77,9 +78,9 @@ function getAuthStorage() {
77
78
  * This should be called when we detect a quota/rate limit error in the agent response.
78
79
  * It will back off the credential and LSD will automatically rotate to the next one.
79
80
  */
80
- export function markCredentialBackoff(provider, sessionId, errorType) {
81
+ export async function markCredentialBackoff(provider, sessionId, errorType) {
81
82
  try {
82
- const authStorage = getAuthStorage();
83
+ const authStorage = await getAuthStorage();
83
84
  // Map CodexErrorType to AuthStorage error type
84
85
  const authErrorType = errorType === "rate_limit"
85
86
  ? "rate_limit"
@@ -30,6 +30,12 @@ export async function syncAccountsToAuth(accounts) {
30
30
  }
31
31
  const credentials = accounts
32
32
  .filter((acc) => !acc.disabled)
33
+ .sort((a, b) => {
34
+ const lastUsedDiff = (b.lastUsed ?? 0) - (a.lastUsed ?? 0);
35
+ if (lastUsedDiff !== 0)
36
+ return lastUsedDiff;
37
+ return b.addedAt - a.addedAt;
38
+ })
33
39
  .map((acc) => ({
34
40
  type: "api_key",
35
41
  key: acc.accessToken,