painapple-code 1.0.0__py3-none-any.whl

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 (436) hide show
  1. painapple_code/__init__.py +30 -0
  2. painapple_code/__main__.py +18 -0
  3. painapple_code/_version.py +24 -0
  4. painapple_code/auth_middleware.py +930 -0
  5. painapple_code/cli/__init__.py +204 -0
  6. painapple_code/cli/deploy/__init__.py +15 -0
  7. painapple_code/cli/deploy/claude_seed.py +41 -0
  8. painapple_code/cli/deploy/config.py +260 -0
  9. painapple_code/cli/deploy/container.py +715 -0
  10. painapple_code/cli/deploy/launch.py +163 -0
  11. painapple_code/cli/deploy/runtime.py +223 -0
  12. painapple_code/cli/lifecycle_cmd.py +443 -0
  13. painapple_code/cli/list_cmd.py +572 -0
  14. painapple_code/cli/manage_cmd.py +670 -0
  15. painapple_code/cli/netinfo.py +125 -0
  16. painapple_code/cli/profiles.py +390 -0
  17. painapple_code/cli/serve_args.py +163 -0
  18. painapple_code/cli/serve_config.py +256 -0
  19. painapple_code/cli/setup_cmd.py +664 -0
  20. painapple_code/cli/ui.py +438 -0
  21. painapple_code/cost_analytics.py +879 -0
  22. painapple_code/data/models.defaults.yaml +18 -0
  23. painapple_code/data/models.yaml +18 -0
  24. painapple_code/data/presets.defaults.json +32 -0
  25. painapple_code/data/strings.yaml +2269 -0
  26. painapple_code/helpers.py +406 -0
  27. painapple_code/paths.py +1514 -0
  28. painapple_code/prompt_explorer.py +984 -0
  29. painapple_code/providers/__init__.py +224 -0
  30. painapple_code/providers/base.py +710 -0
  31. painapple_code/providers/claude/__init__.py +74 -0
  32. painapple_code/providers/claude/capabilities.py +218 -0
  33. painapple_code/providers/claude/errors.py +79 -0
  34. painapple_code/providers/claude/launch.py +50 -0
  35. painapple_code/providers/claude/summary.py +89 -0
  36. painapple_code/providers/claude/translate.py +74 -0
  37. painapple_code/providers/claude_sdk/__init__.py +139 -0
  38. painapple_code/providers/claude_sdk/driver.py +412 -0
  39. painapple_code/providers/codex/__init__.py +87 -0
  40. painapple_code/providers/codex/capabilities.py +321 -0
  41. painapple_code/providers/codex/errors.py +70 -0
  42. painapple_code/providers/codex/launch.py +142 -0
  43. painapple_code/providers/codex/summary.py +196 -0
  44. painapple_code/providers/codex/translate.py +204 -0
  45. painapple_code/providers/codex_app_server/__init__.py +100 -0
  46. painapple_code/providers/codex_app_server/capabilities.py +41 -0
  47. painapple_code/providers/codex_app_server/launch.py +202 -0
  48. painapple_code/providers/codex_app_server/summary.py +291 -0
  49. painapple_code/providers/codex_app_server/translate.py +263 -0
  50. painapple_code/providers/codex_app_server/transport.py +238 -0
  51. painapple_code/routes/__init__.py +0 -0
  52. painapple_code/routes/api_agents.py +441 -0
  53. painapple_code/routes/api_app.py +368 -0
  54. painapple_code/routes/api_app_commit_sections.py +127 -0
  55. painapple_code/routes/api_app_config.py +997 -0
  56. painapple_code/routes/api_app_session_prefs.py +217 -0
  57. painapple_code/routes/api_browser.py +690 -0
  58. painapple_code/routes/api_commands.py +374 -0
  59. painapple_code/routes/api_costs.py +128 -0
  60. painapple_code/routes/api_drafts.py +139 -0
  61. painapple_code/routes/api_exec.py +149 -0
  62. painapple_code/routes/api_files.py +542 -0
  63. painapple_code/routes/api_git.py +657 -0
  64. painapple_code/routes/api_logs.py +649 -0
  65. painapple_code/routes/api_plugins.py +275 -0
  66. painapple_code/routes/api_project_config.py +418 -0
  67. painapple_code/routes/api_prompts.py +231 -0
  68. painapple_code/routes/api_providers.py +53 -0
  69. painapple_code/routes/api_search.py +411 -0
  70. painapple_code/routes/api_session_stash.py +234 -0
  71. painapple_code/routes/api_session_welcome.py +151 -0
  72. painapple_code/routes/api_sessions.py +742 -0
  73. painapple_code/routes/api_shadow.py +323 -0
  74. painapple_code/routes/api_shadow_db.py +512 -0
  75. painapple_code/routes/api_shadow_files.py +332 -0
  76. painapple_code/routes/api_shadow_search.py +476 -0
  77. painapple_code/routes/api_skills.py +610 -0
  78. painapple_code/routes/api_tasks.py +147 -0
  79. painapple_code/routes/api_terminal.py +540 -0
  80. painapple_code/routes/api_upload.py +293 -0
  81. painapple_code/routes/api_viewer.py +229 -0
  82. painapple_code/routes/dependencies.py +109 -0
  83. painapple_code/routes/ws_chat.py +837 -0
  84. painapple_code/server.py +2208 -0
  85. painapple_code/server_logging.py +263 -0
  86. painapple_code/services/__init__.py +0 -0
  87. painapple_code/services/agent_session.py +2738 -0
  88. painapple_code/session_store.py +253 -0
  89. painapple_code/session_store_core.py +879 -0
  90. painapple_code/shadow_db.py +830 -0
  91. painapple_code/shadow_db_plans.py +369 -0
  92. painapple_code/shadow_db_queries.py +595 -0
  93. painapple_code/shadow_db_schema.py +676 -0
  94. painapple_code/shadow_git.py +1296 -0
  95. painapple_code/shadow_git_frontmatter.py +314 -0
  96. painapple_code/shadow_git_sections.py +520 -0
  97. painapple_code/shadow_git_summary.py +462 -0
  98. painapple_code/shadow_parser.py +560 -0
  99. painapple_code/static/css/00-variables.css +276 -0
  100. painapple_code/static/css/01-base.css +245 -0
  101. painapple_code/static/css/02-tooltips.css +36 -0
  102. painapple_code/static/css/09-left-rail.css +448 -0
  103. painapple_code/static/css/10-header.css +777 -0
  104. painapple_code/static/css/11-connection-bar.css +120 -0
  105. painapple_code/static/css/20-messages.css +750 -0
  106. painapple_code/static/css/21-tool-blocks.css +2340 -0
  107. painapple_code/static/css/22-edit-diff.css +619 -0
  108. painapple_code/static/css/23-thinking.css +1584 -0
  109. painapple_code/static/css/24-background-tasks.css +359 -0
  110. painapple_code/static/css/25-context-block.css +252 -0
  111. painapple_code/static/css/26-session-setup.css +144 -0
  112. painapple_code/static/css/30-markdown.css +854 -0
  113. painapple_code/static/css/40-input.css +2418 -0
  114. painapple_code/static/css/41-autocomplete.css +127 -0
  115. painapple_code/static/css/42-activity-strip.css +97 -0
  116. painapple_code/static/css/43-file-autocomplete.css +121 -0
  117. painapple_code/static/css/44-snippets-autocomplete.css +224 -0
  118. painapple_code/static/css/45-selection-bar.css +433 -0
  119. painapple_code/static/css/46-keyboard-bar.css +119 -0
  120. painapple_code/static/css/47-keyboard-longpress.css +116 -0
  121. painapple_code/static/css/47-token-profile.css +106 -0
  122. painapple_code/static/css/50-welcome.css +3095 -0
  123. painapple_code/static/css/51-modals.css +301 -0
  124. painapple_code/static/css/52-utilities.css +77 -0
  125. painapple_code/static/css/53-image-upload.css +456 -0
  126. painapple_code/static/css/54-todo-list.css +102 -0
  127. painapple_code/static/css/55-file-upload.css +113 -0
  128. painapple_code/static/css/56-chat-navigator.css +218 -0
  129. painapple_code/static/css/56-permission-card.css +332 -0
  130. painapple_code/static/css/56-plan-approval.css +204 -0
  131. painapple_code/static/css/56-question-form.css +602 -0
  132. painapple_code/static/css/57-swipe-indicator.css +95 -0
  133. painapple_code/static/css/58-session-families.css +627 -0
  134. painapple_code/static/css/59-about.css +196 -0
  135. painapple_code/static/css/61-log-explorer.css +723 -0
  136. painapple_code/static/css/62-json-tree.css +489 -0
  137. painapple_code/static/css/62-skills-widget.css +650 -0
  138. painapple_code/static/css/63-commands-widget.css +30 -0
  139. painapple_code/static/css/63-plugins-widget.css +174 -0
  140. painapple_code/static/css/63-terminal.css +516 -0
  141. painapple_code/static/css/64-search-files-widget.css +448 -0
  142. painapple_code/static/css/65-snippets-widget.css +426 -0
  143. painapple_code/static/css/66-active-sessions.css +979 -0
  144. painapple_code/static/css/66-agents-widget.css +265 -0
  145. painapple_code/static/css/66-file-explorer-widget.css +855 -0
  146. painapple_code/static/css/66-git-panel.css +791 -0
  147. painapple_code/static/css/66-history-explorer.css +1496 -0
  148. painapple_code/static/css/67-diff-viewer.css +602 -0
  149. painapple_code/static/css/67-editor-view.css +748 -0
  150. painapple_code/static/css/67-file-preview-widget.css +1873 -0
  151. painapple_code/static/css/68-browser-widget.css +337 -0
  152. painapple_code/static/css/68-compare-wizard.css +355 -0
  153. painapple_code/static/css/68-csv-preview.css +106 -0
  154. painapple_code/static/css/68-diff-preview.css +103 -0
  155. painapple_code/static/css/68-helpers-install-widget.css +491 -0
  156. painapple_code/static/css/68-image-annotate.css +346 -0
  157. painapple_code/static/css/68-terminal-view.css +98 -0
  158. painapple_code/static/css/68-uploads-widget.css +229 -0
  159. painapple_code/static/css/69-cost-analytics.css +537 -0
  160. painapple_code/static/css/70-config-panel.css +3736 -0
  161. painapple_code/static/css/70-excalidraw.css +136 -0
  162. painapple_code/static/css/70-prompt-explorer.css +890 -0
  163. painapple_code/static/css/71-chart.css +111 -0
  164. painapple_code/static/css/72-discussion-sidebar.css +678 -0
  165. painapple_code/static/css/74-debug-logs.css +656 -0
  166. painapple_code/static/css/80-open-dialog.css +146 -0
  167. painapple_code/static/css/80-quick-switcher.css +383 -0
  168. painapple_code/static/css/80-widget-system.css +1763 -0
  169. painapple_code/static/css/81-quick-actions.css +1020 -0
  170. painapple_code/static/css/82-context-popover.css +1233 -0
  171. painapple_code/static/css/83-context-menu.css +339 -0
  172. painapple_code/static/css/85-lazy-loading.css +102 -0
  173. painapple_code/static/css/86-zen-mode.css +708 -0
  174. painapple_code/static/css/87-grid-switcher.css +346 -0
  175. painapple_code/static/css/login.css +198 -0
  176. painapple_code/static/dist/app.bundle.js +7177 -0
  177. painapple_code/static/feature-triage.html +446 -0
  178. painapple_code/static/icons/apple-touch-icon.png +0 -0
  179. painapple_code/static/icons/favicon-32.png +0 -0
  180. painapple_code/static/icons/favicon.ico +0 -0
  181. painapple_code/static/icons/icon-128.png +0 -0
  182. painapple_code/static/icons/icon-144.png +0 -0
  183. painapple_code/static/icons/icon-152.png +0 -0
  184. painapple_code/static/icons/icon-180.png +0 -0
  185. painapple_code/static/icons/icon-192.png +0 -0
  186. painapple_code/static/icons/icon-384.png +0 -0
  187. painapple_code/static/icons/icon-512.png +0 -0
  188. painapple_code/static/icons/icon-72.png +0 -0
  189. painapple_code/static/icons/icon-96.png +0 -0
  190. painapple_code/static/js/action-delegate.js +187 -0
  191. painapple_code/static/js/activity-strip.js +193 -0
  192. painapple_code/static/js/app/context-menus.js +619 -0
  193. painapple_code/static/js/app/file-preview.js +163 -0
  194. painapple_code/static/js/app/init-ui.js +574 -0
  195. painapple_code/static/js/app/input-events.js +561 -0
  196. painapple_code/static/js/app/input-resize.js +313 -0
  197. painapple_code/static/js/app/message-actions.js +419 -0
  198. painapple_code/static/js/app/panels.js +210 -0
  199. painapple_code/static/js/app/rendering-delegators.js +246 -0
  200. painapple_code/static/js/app/server-loading.js +365 -0
  201. painapple_code/static/js/app/session-ops.js +287 -0
  202. painapple_code/static/js/app/session-switch.js +443 -0
  203. painapple_code/static/js/app/settings-helpers.js +280 -0
  204. painapple_code/static/js/app/startup-welcome.js +290 -0
  205. painapple_code/static/js/app-context.js +157 -0
  206. painapple_code/static/js/app.js +2134 -0
  207. painapple_code/static/js/auth-fetch.js +46 -0
  208. painapple_code/static/js/background-tasks.js +274 -0
  209. painapple_code/static/js/boot.js +73 -0
  210. painapple_code/static/js/caret-position.js +171 -0
  211. painapple_code/static/js/chat-navigator.js +710 -0
  212. painapple_code/static/js/chat-search.js +323 -0
  213. painapple_code/static/js/command-executor.js +176 -0
  214. painapple_code/static/js/command-store.js +497 -0
  215. painapple_code/static/js/components.js +1414 -0
  216. painapple_code/static/js/config.js +218 -0
  217. painapple_code/static/js/context-menu.js +740 -0
  218. painapple_code/static/js/controllers/chat-controller.js +4252 -0
  219. painapple_code/static/js/controllers/dialog-controller.js +217 -0
  220. painapple_code/static/js/controllers/tab-controller.js +1828 -0
  221. painapple_code/static/js/controllers/thinking-controller.js +915 -0
  222. painapple_code/static/js/diff-utils.js +486 -0
  223. painapple_code/static/js/drafts-pill.js +95 -0
  224. painapple_code/static/js/editor-view.js +815 -0
  225. painapple_code/static/js/effort-settings.js +430 -0
  226. painapple_code/static/js/feature-triage.js +811 -0
  227. painapple_code/static/js/file-autocomplete.js +516 -0
  228. painapple_code/static/js/file-tabs.js +123 -0
  229. painapple_code/static/js/gestures.js +448 -0
  230. painapple_code/static/js/grid-switcher.js +334 -0
  231. painapple_code/static/js/input-handler.js +1472 -0
  232. painapple_code/static/js/keyboard-bar.js +495 -0
  233. painapple_code/static/js/layout-switcher.js +109 -0
  234. painapple_code/static/js/linkify-utils.js +200 -0
  235. painapple_code/static/js/login.js +141 -0
  236. painapple_code/static/js/open-dialog.js +879 -0
  237. painapple_code/static/js/orphan-terminals.js +373 -0
  238. painapple_code/static/js/path-utils.js +179 -0
  239. painapple_code/static/js/perf-marks.js +102 -0
  240. painapple_code/static/js/permission-settings.js +475 -0
  241. painapple_code/static/js/pointer-input.js +59 -0
  242. painapple_code/static/js/preview/json-tree-menu.js +226 -0
  243. painapple_code/static/js/preview/json-tree-toolbar.js +291 -0
  244. painapple_code/static/js/preview/json-tree.js +457 -0
  245. painapple_code/static/js/preview/preview-breadcrumb.js +387 -0
  246. painapple_code/static/js/preview/preview-edit.js +367 -0
  247. painapple_code/static/js/preview/preview-events.js +361 -0
  248. painapple_code/static/js/preview/preview-history.js +567 -0
  249. painapple_code/static/js/preview/preview-inline-edit.js +869 -0
  250. painapple_code/static/js/preview/preview-poll.js +108 -0
  251. painapple_code/static/js/preview/preview-render.js +572 -0
  252. painapple_code/static/js/preview/preview-search.js +421 -0
  253. painapple_code/static/js/preview/preview-state.js +256 -0
  254. painapple_code/static/js/preview/preview-utils.js +251 -0
  255. painapple_code/static/js/preview-plugins/chart-plugin.js +22 -0
  256. painapple_code/static/js/preview-plugins/csv-plugin.js +188 -0
  257. painapple_code/static/js/preview-plugins/diff-plugin.js +258 -0
  258. painapple_code/static/js/preview-plugins/excalidraw-plugin.js +25 -0
  259. painapple_code/static/js/preview-plugins/html-plugin.js +104 -0
  260. painapple_code/static/js/preview-plugins/image-plugin.js +20 -0
  261. painapple_code/static/js/preview-plugins/index.js +34 -0
  262. painapple_code/static/js/preview-plugins/json-plugin.js +99 -0
  263. painapple_code/static/js/preview-plugins/jsonl-plugin.js +152 -0
  264. painapple_code/static/js/preview-plugins/markdown-plugin.js +266 -0
  265. painapple_code/static/js/preview-plugins/panzoom-plugin.js +63 -0
  266. painapple_code/static/js/preview-plugins/plugin-helpers.js +282 -0
  267. painapple_code/static/js/project-color-picker.js +110 -0
  268. painapple_code/static/js/project-colors.js +163 -0
  269. painapple_code/static/js/prompt-favorites.js +187 -0
  270. painapple_code/static/js/quick-actions-menu.js +1736 -0
  271. painapple_code/static/js/quick-actions-registry.js +1239 -0
  272. painapple_code/static/js/quick-switcher/controller.js +294 -0
  273. painapple_code/static/js/quick-switcher/fuzzy-scorer.js +123 -0
  274. painapple_code/static/js/quick-switcher/index.js +30 -0
  275. painapple_code/static/js/quick-switcher/providers/base-provider.js +51 -0
  276. painapple_code/static/js/quick-switcher/providers/command-provider.js +56 -0
  277. painapple_code/static/js/quick-switcher/providers/file-provider.js +291 -0
  278. painapple_code/static/js/quick-switcher/providers/panel-provider.js +132 -0
  279. painapple_code/static/js/quick-switcher/providers/project-provider.js +361 -0
  280. painapple_code/static/js/quick-switcher/providers/read-files-provider.js +104 -0
  281. painapple_code/static/js/quick-switcher/providers/skills-provider.js +129 -0
  282. painapple_code/static/js/quick-switcher/registry.js +69 -0
  283. painapple_code/static/js/quick-switcher/ui/item.js +54 -0
  284. painapple_code/static/js/quick-switcher/ui/picker.js +464 -0
  285. painapple_code/static/js/recent-opens.js +40 -0
  286. painapple_code/static/js/scroll-manager.js +530 -0
  287. painapple_code/static/js/scroll-state-machine.js +359 -0
  288. painapple_code/static/js/selection/action-bar.js +793 -0
  289. painapple_code/static/js/selection/index.js +15 -0
  290. painapple_code/static/js/selection/selection-handler.js +877 -0
  291. painapple_code/static/js/selection/state.js +181 -0
  292. painapple_code/static/js/session/agent-progress.js +202 -0
  293. painapple_code/static/js/session/handle-agent-message.js +481 -0
  294. painapple_code/static/js/session/handle-message.js +550 -0
  295. painapple_code/static/js/session/interactive.js +587 -0
  296. painapple_code/static/js/session/message-store.js +271 -0
  297. painapple_code/static/js/session/messages.js +253 -0
  298. painapple_code/static/js/session/persistence.js +179 -0
  299. painapple_code/static/js/session/restore.js +136 -0
  300. painapple_code/static/js/session/sync.js +333 -0
  301. painapple_code/static/js/session-container-pool.js +371 -0
  302. painapple_code/static/js/session-setup-panel.js +274 -0
  303. painapple_code/static/js/session.js +1047 -0
  304. painapple_code/static/js/shortcut-hints.js +272 -0
  305. painapple_code/static/js/shortcuts.js +1146 -0
  306. painapple_code/static/js/skills-autocomplete.js +262 -0
  307. painapple_code/static/js/snippets-autocomplete.js +738 -0
  308. painapple_code/static/js/stash-refs-view.js +83 -0
  309. painapple_code/static/js/stash-ui.js +768 -0
  310. painapple_code/static/js/stash.js +785 -0
  311. painapple_code/static/js/status-bar.js +1184 -0
  312. painapple_code/static/js/token-profile.js +274 -0
  313. painapple_code/static/js/tool-renderer-blocks.js +1626 -0
  314. painapple_code/static/js/tool-renderer-thinking.js +813 -0
  315. painapple_code/static/js/tool-renderer.js +1500 -0
  316. painapple_code/static/js/tooltips.js +197 -0
  317. painapple_code/static/js/upload-manager.js +1019 -0
  318. painapple_code/static/js/utils.js +521 -0
  319. painapple_code/static/js/welcome/api.js +363 -0
  320. painapple_code/static/js/welcome/cards.js +626 -0
  321. painapple_code/static/js/welcome/context-menu.js +585 -0
  322. painapple_code/static/js/welcome/families.js +801 -0
  323. painapple_code/static/js/welcome/preview.js +234 -0
  324. painapple_code/static/js/welcome/state.js +129 -0
  325. painapple_code/static/js/welcome.js +1874 -0
  326. painapple_code/static/js/widget-system/base-widget.js +717 -0
  327. painapple_code/static/js/widget-system/device-manager.js +178 -0
  328. painapple_code/static/js/widget-system/event-bus.js +152 -0
  329. painapple_code/static/js/widget-system/icons.js +120 -0
  330. painapple_code/static/js/widget-system/index.js +49 -0
  331. painapple_code/static/js/widget-system/init.js +54 -0
  332. painapple_code/static/js/widget-system/types/bottom-sheet.js +307 -0
  333. painapple_code/static/js/widget-system/types/floating.js +915 -0
  334. painapple_code/static/js/widget-system/types/index.js +40 -0
  335. painapple_code/static/js/widget-system/types/modal.js +147 -0
  336. painapple_code/static/js/widget-system/types/sidebar.js +158 -0
  337. painapple_code/static/js/widget-system/types/tab.js +146 -0
  338. painapple_code/static/js/widget-system/types/top-sheet.js +203 -0
  339. painapple_code/static/js/widget-system/widget-manager.js +1187 -0
  340. painapple_code/static/js/widgets/about-widget.js +260 -0
  341. painapple_code/static/js/widgets/active-sessions-widget.js +760 -0
  342. painapple_code/static/js/widgets/agents-widget.js +897 -0
  343. painapple_code/static/js/widgets/browser-widget.js +579 -0
  344. painapple_code/static/js/widgets/commands-widget.js +694 -0
  345. painapple_code/static/js/widgets/config/commit-sections.js +449 -0
  346. painapple_code/static/js/widgets/config/dir-autocomplete.js +224 -0
  347. painapple_code/static/js/widgets/config/gestures.js +161 -0
  348. painapple_code/static/js/widgets/config/models-tab.js +988 -0
  349. painapple_code/static/js/widgets/config/quick-actions-tab.js +882 -0
  350. painapple_code/static/js/widgets/config/shortcut-editor.js +587 -0
  351. painapple_code/static/js/widgets/config/state.js +517 -0
  352. painapple_code/static/js/widgets/config/system-controls.js +90 -0
  353. painapple_code/static/js/widgets/config-widget.js +1535 -0
  354. painapple_code/static/js/widgets/cost-analytics-widget.js +621 -0
  355. painapple_code/static/js/widgets/debug-widget.js +693 -0
  356. painapple_code/static/js/widgets/diff-viewer-widget.js +1420 -0
  357. painapple_code/static/js/widgets/discussion-widget.js +1107 -0
  358. painapple_code/static/js/widgets/file-explorer-widget.js +2166 -0
  359. painapple_code/static/js/widgets/file-preview-widget.js +861 -0
  360. painapple_code/static/js/widgets/git-widget.js +794 -0
  361. painapple_code/static/js/widgets/helpers-install-widget.js +720 -0
  362. painapple_code/static/js/widgets/history-explorer-widget.js +1804 -0
  363. painapple_code/static/js/widgets/image-annotate-widget.js +951 -0
  364. painapple_code/static/js/widgets/image-preview-widget.js +692 -0
  365. painapple_code/static/js/widgets/index.js +176 -0
  366. painapple_code/static/js/widgets/log-explorer-widget.js +1057 -0
  367. painapple_code/static/js/widgets/plugins-widget.js +305 -0
  368. painapple_code/static/js/widgets/prompt-explorer-widget.js +1119 -0
  369. painapple_code/static/js/widgets/search-files-widget.js +529 -0
  370. painapple_code/static/js/widgets/skills-widget.js +843 -0
  371. painapple_code/static/js/widgets/snippets-widget.js +479 -0
  372. painapple_code/static/js/widgets/sub-agents-widget.js +333 -0
  373. painapple_code/static/js/widgets/tasks-widget.js +379 -0
  374. painapple_code/static/js/widgets/terminal/connection.js +283 -0
  375. painapple_code/static/js/widgets/terminal/gestures.js +357 -0
  376. painapple_code/static/js/widgets/terminal/init.js +642 -0
  377. painapple_code/static/js/widgets/terminal/link-providers.js +532 -0
  378. painapple_code/static/js/widgets/terminal/osc52.js +213 -0
  379. painapple_code/static/js/widgets/terminal/render.js +159 -0
  380. painapple_code/static/js/widgets/terminal/size.js +54 -0
  381. painapple_code/static/js/widgets/terminal/state.js +211 -0
  382. painapple_code/static/js/widgets/terminal/wrap-utils.js +101 -0
  383. painapple_code/static/js/widgets/terminal-widget.js +793 -0
  384. painapple_code/static/js/widgets/uploads-widget.js +461 -0
  385. painapple_code/static/js/widgets/zen-widget.js +1001 -0
  386. painapple_code/static/login.html +51 -0
  387. painapple_code/static/manifest.json +73 -0
  388. painapple_code/static/sw.js +293 -0
  389. painapple_code/static/vendor/LICENSES.txt +769 -0
  390. painapple_code/static/vendor/README.md +103 -0
  391. painapple_code/static/vendor/codemirror.js +35 -0
  392. painapple_code/static/vendor/codemirror.js.LEGAL.txt +304 -0
  393. painapple_code/static/vendor/github-markdown-dark.min.css +1124 -0
  394. painapple_code/static/vendor/highlight-github-dark.min.css +10 -0
  395. painapple_code/static/vendor/highlight-lang-dockerfile.min.js +8 -0
  396. painapple_code/static/vendor/highlight-lang-nginx.min.js +21 -0
  397. painapple_code/static/vendor/highlight-lang-properties.min.js +10 -0
  398. painapple_code/static/vendor/highlight-lang-scala.min.js +28 -0
  399. painapple_code/static/vendor/highlight.min.js +1213 -0
  400. painapple_code/static/vendor/marked.min.js +6 -0
  401. painapple_code/static/vendor/purify.min.js +3 -0
  402. painapple_code/static/vendor/xterm-addon-fit.js +2 -0
  403. painapple_code/static/vendor/xterm.css +209 -0
  404. painapple_code/static/vendor/xterm.js +2 -0
  405. painapple_code/static/web-client.html +635 -0
  406. painapple_code/subprocess_registry.py +535 -0
  407. painapple_code/tls_cert.py +79 -0
  408. painapple_code/tools/agents/shadow-git-helper.md +400 -0
  409. painapple_code/tools/excalidraw-to-svg.js +143 -0
  410. painapple_code/tools/install-helpers.sh +148 -0
  411. painapple_code/tools/shadow-git +320 -0
  412. painapple_code/tools/shadow-query +120 -0
  413. painapple_code/tools/vegalite-to-svg.js +79 -0
  414. painapple_code/turn_query.py +766 -0
  415. painapple_code/turn_tracker.py +221 -0
  416. painapple_code/utils/__init__.py +0 -0
  417. painapple_code/utils/agent_cli.py +306 -0
  418. painapple_code/utils/chart.py +103 -0
  419. painapple_code/utils/excalidraw.py +107 -0
  420. painapple_code/utils/file_lock.py +162 -0
  421. painapple_code/utils/file_paths.py +648 -0
  422. painapple_code/utils/generate_icons.py +251 -0
  423. painapple_code/utils/proc.py +205 -0
  424. painapple_code/utils/pty_backend.py +59 -0
  425. painapple_code/utils/pty_posix.py +160 -0
  426. painapple_code/utils/pty_windows.py +245 -0
  427. painapple_code/utils/token_profiles.py +91 -0
  428. painapple_code/viewer_templates.py +657 -0
  429. painapple_code/welcome_search.py +879 -0
  430. painapple_code-1.0.0.dist-info/METADATA +292 -0
  431. painapple_code-1.0.0.dist-info/RECORD +436 -0
  432. painapple_code-1.0.0.dist-info/WHEEL +5 -0
  433. painapple_code-1.0.0.dist-info/entry_points.txt +2 -0
  434. painapple_code-1.0.0.dist-info/licenses/LICENSE +661 -0
  435. painapple_code-1.0.0.dist-info/licenses/THIRD_PARTY_NOTICES.md +261 -0
  436. painapple_code-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,202 @@
1
+ """Codex app-server provider — launch argv + JSON-RPC param shaping.
2
+
3
+ `build_command` is trivial (`codex app-server --stdio`); the interesting part is
4
+ turning resolved `LaunchOptions` + a canonical user message into the
5
+ `thread/start` / `turn/start` JSON-RPC params. That shaping lives here (the
6
+ provider owns everything CLI-shaped) so the transport driver stays pure plumbing
7
+ — it just calls `provider.thread_start_params(...)` etc. Mixed into
8
+ `CodexAppServerProvider`.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from typing import Optional
14
+
15
+ from painapple_code import paths
16
+ from painapple_code.providers.base import LaunchOptions
17
+
18
+ # Effort → `turn/start.effort` (ReasoningEffort) goes through capabilities'
19
+ # effort_for_model(): the CLI's own models_cache declares each model's
20
+ # supported reasoning levels, and the requested level is clamped to the
21
+ # TARGET model's range — mirroring codex exec (unknown/default model → the
22
+ # range every listed model speaks; no cache → the low/medium/high triad).
23
+
24
+ # Permission value → app-server `SandboxMode` (thread/start.sandbox). The
25
+ # app-server's native vocabulary is identical to codex exec's, so new sessions
26
+ # (which store the native value) map to themselves; the Claude-vocabulary rows
27
+ # are a back-compat shim for sessions created before native modes existed.
28
+ _SANDBOX_BY_MODE = {
29
+ # Codex-native (identity)
30
+ "read-only": "read-only",
31
+ "workspace-write": "workspace-write",
32
+ "danger-full-access": "danger-full-access",
33
+ # Legacy Claude-vocabulary (pre-native sessions)
34
+ "plan": "read-only",
35
+ "dontAsk": "read-only",
36
+ "acceptEdits": "workspace-write",
37
+ "auto": "workspace-write",
38
+ "bypassPermissions": "danger-full-access",
39
+ }
40
+ _DEFAULT_SANDBOX = "workspace-write"
41
+
42
+
43
+ class _LaunchMixin:
44
+ """Builds the app-server launch argv and the JSON-RPC turn/thread params."""
45
+
46
+ def frame_input(self, message: dict) -> dict:
47
+ # Never called: turns go over the JSON-RPC transport (`send_turn`), not
48
+ # the stdin line path. Raise so a mis-wired send fails loudly.
49
+ raise NotImplementedError(
50
+ "codex app-server turns ride the JSON-RPC transport, not stdin lines")
51
+
52
+ def build_command(self, opts: LaunchOptions) -> list[str]:
53
+ # Persistent JSON-RPC process over stdio. Model/effort/sandbox are NOT
54
+ # argv here — they ride on the thread/start + turn/start params below.
55
+ return [self.binary(), "app-server", "--stdio"]
56
+
57
+ # --- JSON-RPC param shaping (called by the transport) -----------------
58
+
59
+ def thread_start_params(self, opts: LaunchOptions, cwd: str) -> dict:
60
+ """Params for a fresh `thread/start`.
61
+
62
+ P1 sets `approvalPolicy="never"`: the sandbox alone governs what
63
+ model-generated commands may do, exactly like `codex exec` (which runs
64
+ approvals-off). Interactive approvals are a later phase.
65
+ """
66
+ params: dict = {
67
+ "cwd": cwd,
68
+ "sandbox": _SANDBOX_BY_MODE.get(opts.permission_mode or "", _DEFAULT_SANDBOX),
69
+ "approvalPolicy": "never",
70
+ }
71
+ model = self._wire_model(opts.model)
72
+ if model:
73
+ params["model"] = model
74
+ return params
75
+
76
+ def thread_resume_params(self, opts: LaunchOptions, thread_id: str, cwd: str) -> dict:
77
+ """Params for `thread/resume` (continue an existing thread by id)."""
78
+ params: dict = {"threadId": thread_id, "cwd": cwd}
79
+ model = self._wire_model(opts.model)
80
+ if model:
81
+ params["model"] = model
82
+ return params
83
+
84
+ def thread_fork_params(self, opts: LaunchOptions, fork_from_id: str, cwd: str) -> dict:
85
+ """Params for `thread/fork` — branch a source thread into a fresh one.
86
+
87
+ The native equivalent of Claude's `--resume <id> --fork-session`: the
88
+ app-server loads the source thread from disk and forks it into a new
89
+ *persisted* thread (no rollout-file copy). Deliberately not `ephemeral`
90
+ — the fork becomes the new session's thread, captured from the
91
+ response and resumed on subsequent turns. Sandbox/approval/model mirror
92
+ `thread_start_params` so the branch inherits the session's resolved
93
+ settings. `sandbox` and `permissions` are mutually exclusive in the
94
+ schema; we use `sandbox` (our native vocabulary), so `permissions` is
95
+ omitted.
96
+ """
97
+ params: dict = {
98
+ "threadId": fork_from_id,
99
+ "cwd": cwd,
100
+ "sandbox": _SANDBOX_BY_MODE.get(opts.permission_mode or "", _DEFAULT_SANDBOX),
101
+ "approvalPolicy": "never",
102
+ }
103
+ model = self._wire_model(opts.model)
104
+ if model:
105
+ params["model"] = model
106
+ return params
107
+
108
+ def turn_start_params(self, opts: LaunchOptions, thread_id: str, input_items: list) -> dict:
109
+ """Params for `turn/start` — the per-turn input plus effort/model."""
110
+ # `summary` opts in to reasoning summaries — WITHOUT it the app-server
111
+ # emits reasoning items with empty summary/content arrays (thinking
112
+ # blocks would render blank). "auto" lets the backend pick the format,
113
+ # matching the codex TUI's own default.
114
+ params: dict = {"threadId": thread_id, "input": input_items,
115
+ "summary": "auto"}
116
+ model = self._wire_model(opts.model)
117
+ if opts.effort:
118
+ # Clamp to the range the model that will actually run supports
119
+ # (forwarded pick, else the conservative default-model range).
120
+ mapped = self.effort_for_model(opts.effort, model)
121
+ if mapped:
122
+ params["effort"] = mapped
123
+ if model:
124
+ params["model"] = model
125
+ return params
126
+
127
+ def build_turn_input(self, message: dict) -> list[dict]:
128
+ """Canonical user message → app-server `UserInput[]`.
129
+
130
+ The canonical shape (built in ws_chat) is
131
+ ``{"type":"user","message":{"role":"user","content": str | [blocks]}}``.
132
+ Text becomes a ``text`` input; base64 image blocks are materialized to
133
+ temp files and passed as ``localImage`` inputs.
134
+ """
135
+ content = (message or {}).get("message", {}).get("content", "")
136
+ items: list[dict] = []
137
+ if isinstance(content, str):
138
+ if content:
139
+ items.append({"type": "text", "text": content})
140
+ return items
141
+ if isinstance(content, list):
142
+ image_sources = []
143
+ for block in content:
144
+ if not isinstance(block, dict):
145
+ continue
146
+ if block.get("type") == "text" and block.get("text"):
147
+ items.append({"type": "text", "text": block["text"]})
148
+ elif block.get("type") == "image":
149
+ image_sources.append(block)
150
+ for path in self._materialize_images(image_sources):
151
+ items.append({"type": "localImage", "path": path})
152
+ return items
153
+
154
+ @staticmethod
155
+ def _wire_model(model: Optional[str]) -> Optional[str]:
156
+ """Only forward a model the app-server would accept.
157
+
158
+ The session model hierarchy defaults to a *Claude* id (which Codex would
159
+ reject); never forward that — let the app-server use the model configured
160
+ by `codex login` / config.toml. A non-Claude id is passed through.
161
+ """
162
+ if model and not model.startswith("claude"):
163
+ return model
164
+ return None
165
+
166
+ @staticmethod
167
+ def _materialize_images(images: Optional[list]) -> list[str]:
168
+ """Decode base64 image blocks to temp files; return their paths."""
169
+ if not images:
170
+ return []
171
+ import base64
172
+ import tempfile
173
+ import uuid
174
+ out_dir = paths.DATA_HOME / "tmp" / "codex-images"
175
+ try:
176
+ out_dir.mkdir(parents=True, exist_ok=True)
177
+ except OSError:
178
+ out_dir = None
179
+ paths: list[str] = []
180
+ for img in images:
181
+ source = img.get("source", {}) if isinstance(img, dict) else {}
182
+ if source.get("type") != "base64" or not source.get("data"):
183
+ continue
184
+ media_type = source.get("media_type", "image/png")
185
+ ext = "jpg" if "jpeg" in media_type else media_type.split("/")[-1]
186
+ name = f"img_{uuid.uuid4().hex[:10]}.{ext}"
187
+ try:
188
+ data = base64.b64decode(source["data"])
189
+ if out_dir is not None:
190
+ p = out_dir / name
191
+ p.write_bytes(data)
192
+ else:
193
+ fd = tempfile.NamedTemporaryFile(
194
+ prefix="codex_img_", suffix=f".{ext}", delete=False
195
+ )
196
+ fd.write(data)
197
+ fd.close()
198
+ p = fd.name
199
+ paths.append(str(p))
200
+ except (OSError, ValueError):
201
+ continue
202
+ return paths
@@ -0,0 +1,291 @@
1
+ """Codex app-server provider — native rich-commit summary over JSON-RPC.
2
+
3
+ The sibling `codex` exec provider summarizes a turn by copying the thread's
4
+ rollout JSONL under a fresh id and running `codex exec resume` on the copy
5
+ (`codex exec resume` writes in place, so it can't summarize the real thread
6
+ without polluting it). The app-server needs no such file copy: it can
7
+ `thread/fork` the real thread into an *ephemeral* branch — a throwaway thread
8
+ that never persists to disk — and run one `turn/start` with a native
9
+ `outputSchema` to get the structured sections back. No rollout copy, no temp
10
+ schema/output files, no pollution of the user's thread.
11
+
12
+ Isolation: the summary runs on its OWN short-lived `codex app-server` process
13
+ (spawned by the shadow-git layer from `build_summary_fork`'s argv), not the
14
+ user's live session transport — so its turn notifications never interleave with
15
+ the user's chat stream. `drive_summary_fork` conducts that process's JSON-RPC
16
+ conversation and returns the structured sections + token usage.
17
+
18
+ On any failure it returns `(None, None)`: the turn still gets a basic shadow
19
+ commit, just without AI sections — exactly like any other unavailable fork.
20
+ Subclasses the exec summary mixin only to reuse `_strictify_schema` (the
21
+ app-server's `outputSchema` runs in the same OpenAI strict mode as exec's
22
+ `--output-schema`). Mixed into `CodexAppServerProvider`.
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ import asyncio
28
+ import json
29
+ import logging
30
+ from typing import Optional
31
+
32
+ from painapple_code import paths
33
+ from painapple_code.providers.base import SummaryForkPlan
34
+ from painapple_code.providers.codex.summary import _SummaryMixin as _CodexExecSummaryMixin
35
+
36
+ logger = logging.getLogger("painapple_code")
37
+
38
+ # Per-request waits. The handshake/fork/turn acks return promptly; the turn's
39
+ # content streams as notifications, so awaiting an ack never blocks turn-length.
40
+ _RPC_TIMEOUT = 60.0
41
+ # Whole-turn wait for the streamed result; the shadow-git layer also caps the
42
+ # entire fork at 300s, so this stays under that.
43
+ _TURN_TIMEOUT = 240.0
44
+
45
+
46
+ class _SummaryMixin(_CodexExecSummaryMixin):
47
+ """Native app-server rich-commit fork — ephemeral thread/fork + outputSchema."""
48
+
49
+ def build_summary_fork(
50
+ self,
51
+ *,
52
+ session_id: str,
53
+ fork_prompt: str,
54
+ schema_json: str,
55
+ cwd: str,
56
+ token_profile: Optional[str] = None,
57
+ session_model: Optional[str] = None,
58
+ ) -> Optional[SummaryForkPlan]:
59
+ """Plan a native (transport-driven) summary — no rollout copy.
60
+
61
+ The plan's argv launches a throwaway `codex app-server`; its `payload`
62
+ carries what `drive_summary_fork` needs: the source thread id to fork,
63
+ the prompt, the (strict) output schema, an optional cheaper summary
64
+ model, and the cwd. Nothing is copied to disk → empty `cleanup_paths`.
65
+ """
66
+ if not session_id:
67
+ return None
68
+ try:
69
+ schema_obj = json.loads(self._strictify_schema(schema_json))
70
+ except json.JSONDecodeError:
71
+ return None
72
+ # Optional cheaper summarizer model (seam-described override);
73
+ # default inherits the thread's model.
74
+ summary_model = self.get_summary_model_override()
75
+ if summary_model and summary_model.startswith("claude"):
76
+ summary_model = None
77
+ return SummaryForkPlan(
78
+ argv=[self.binary(), "app-server", "--stdio"],
79
+ env=None, # codex uses ambient auth
80
+ model=summary_model or "codex (app-server)",
81
+ output_file=None,
82
+ cleanup_paths=[], # nothing copied — that's the win
83
+ payload={
84
+ "fork_from": session_id,
85
+ "prompt": fork_prompt,
86
+ "schema": schema_obj,
87
+ "model": summary_model,
88
+ "cwd": cwd,
89
+ },
90
+ )
91
+
92
+ async def drive_summary_fork(self, proc, plan):
93
+ """Conduct the throwaway app-server's JSON-RPC summary conversation.
94
+
95
+ initialize → thread/fork{ephemeral} → turn/start{outputSchema} → collect
96
+ the final structured agentMessage + token usage. Returns
97
+ `(structured_data, cost)` or `(None, None)` on any failure (→ no AI
98
+ sections this turn). Leaves the process terminated.
99
+ """
100
+ # Lazy import keeps the transport (and its asyncio plumbing) out of the
101
+ # package's static-description import path — same reason `make_transport`
102
+ # imports it lazily.
103
+ from painapple_code.providers.codex_app_server.transport import (
104
+ _APPROVAL_DENY, _client_version, JsonRpcTransport,
105
+ )
106
+
107
+ payload = plan.payload or {}
108
+ fork_from = payload.get("fork_from")
109
+ if not fork_from:
110
+ return None, None
111
+
112
+ client = _SummaryRpcClient(proc, deny_map=_APPROVAL_DENY)
113
+ await client.start()
114
+ try:
115
+ await client.request("initialize", {
116
+ "clientInfo": {"name": "painapple-code", "version": _client_version()},
117
+ }, timeout=_RPC_TIMEOUT)
118
+ await client.notify("initialized")
119
+
120
+ fork_params = {
121
+ "threadId": fork_from,
122
+ "ephemeral": True, # throwaway branch — never persisted
123
+ "cwd": payload.get("cwd") or ".",
124
+ "sandbox": "read-only", # the summarizer must not touch files
125
+ "approvalPolicy": "never",
126
+ }
127
+ if payload.get("model"):
128
+ fork_params["model"] = payload["model"]
129
+ res = await client.request("thread/fork", fork_params, timeout=_RPC_TIMEOUT)
130
+ fork_id = JsonRpcTransport._thread_id_from_result(res)
131
+ if not fork_id:
132
+ return None, None
133
+
134
+ turn_params = {
135
+ "threadId": fork_id,
136
+ "input": [{"type": "text", "text": payload.get("prompt") or ""}],
137
+ "outputSchema": payload.get("schema"),
138
+ "effort": "low", # summarization needs no deep reasoning
139
+ }
140
+ if payload.get("model"):
141
+ turn_params["model"] = payload["model"]
142
+ await client.request("turn/start", turn_params, timeout=_RPC_TIMEOUT)
143
+
144
+ structured, usage = await client.await_turn(timeout=_TURN_TIMEOUT)
145
+ if not isinstance(structured, dict):
146
+ return None, None
147
+ cost = {
148
+ "cost": 0.0, # app-server reports no USD cost
149
+ "input_tokens": (usage or {}).get("inputTokens", 0) or 0,
150
+ "output_tokens": (usage or {}).get("outputTokens", 0) or 0,
151
+ }
152
+ return structured, cost
153
+ except Exception as e:
154
+ logger.info(f"codex app-server summary fork failed: {e}")
155
+ return None, None
156
+ finally:
157
+ await client.close()
158
+
159
+
160
+ class _SummaryRpcClient:
161
+ """Minimal one-shot JSON-RPC client over a throwaway app-server process.
162
+
163
+ Distinct from `JsonRpcTransport` (session-coupled, driven externally by the
164
+ session's stdout reader): this owns its own read loop and collects only what
165
+ a summary needs — the final `agentMessage` text (the structured JSON produced
166
+ under `outputSchema`) and the latest token usage — resolving `await_turn` on
167
+ `turn/completed`.
168
+ """
169
+
170
+ def __init__(self, proc, deny_map=None):
171
+ self.proc = proc
172
+ self._deny = deny_map or {}
173
+ self._id = 0
174
+ self._pending: dict = {}
175
+ self._turn = None # future resolved on turn/completed|error|EOF
176
+ self._last_text = None # last agentMessage text == the structured JSON
177
+ self._usage: dict = {}
178
+ self._reader = None
179
+
180
+ async def start(self):
181
+ self._reader = asyncio.create_task(self._read_loop())
182
+
183
+ def _next_id(self) -> int:
184
+ self._id += 1
185
+ return self._id
186
+
187
+ async def _write(self, obj: dict):
188
+ self.proc.stdin.write((json.dumps(obj) + "\n").encode("utf-8"))
189
+ await self.proc.stdin.drain()
190
+
191
+ async def request(self, method: str, params: dict, timeout: float = _RPC_TIMEOUT):
192
+ rid = self._next_id()
193
+ fut = asyncio.get_running_loop().create_future()
194
+ self._pending[rid] = fut
195
+ await self._write({"jsonrpc": "2.0", "id": rid, "method": method, "params": params})
196
+ try:
197
+ return await asyncio.wait_for(fut, timeout)
198
+ finally:
199
+ self._pending.pop(rid, None)
200
+
201
+ async def notify(self, method: str, params: dict | None = None):
202
+ msg = {"jsonrpc": "2.0", "method": method}
203
+ if params is not None:
204
+ msg["params"] = params
205
+ await self._write(msg)
206
+
207
+ async def await_turn(self, timeout: float = _TURN_TIMEOUT):
208
+ """Wait for turn/completed; return (structured_or_None, usage)."""
209
+ self._turn = asyncio.get_running_loop().create_future()
210
+ try:
211
+ await asyncio.wait_for(self._turn, timeout)
212
+ except asyncio.TimeoutError:
213
+ return None, self._usage
214
+ structured = None
215
+ if self._last_text:
216
+ try:
217
+ structured = json.loads(self._last_text)
218
+ except json.JSONDecodeError:
219
+ structured = None
220
+ return structured, self._usage
221
+
222
+ async def _read_loop(self):
223
+ stdout = self.proc.stdout
224
+ while True:
225
+ line = await stdout.readline()
226
+ if not line: # EOF — process exited
227
+ self._finish_turn()
228
+ return
229
+ try:
230
+ msg = json.loads(line.decode(errors="replace").strip())
231
+ except (json.JSONDecodeError, ValueError):
232
+ continue
233
+ self._dispatch(msg)
234
+
235
+ def _dispatch(self, msg: dict):
236
+ has_id = msg.get("id") is not None
237
+ has_method = "method" in msg
238
+ if has_id and has_method: # server → client request: decline
239
+ asyncio.create_task(self._decline(msg))
240
+ return
241
+ if has_id and not has_method: # response to one of our requests
242
+ fut = self._pending.get(msg["id"])
243
+ if fut is not None and not fut.done():
244
+ if msg.get("error") is not None:
245
+ fut.set_exception(RuntimeError(str(msg["error"])))
246
+ else:
247
+ fut.set_result(msg.get("result"))
248
+ return
249
+ method = msg.get("method") # notification
250
+ params = msg.get("params") or {}
251
+ if method == "item/completed":
252
+ item = params.get("item") or {}
253
+ if item.get("type") == "agentMessage" and item.get("text"):
254
+ self._last_text = item["text"]
255
+ elif method == "thread/tokenUsage/updated":
256
+ tu = params.get("tokenUsage") or {}
257
+ self._usage = tu.get("last") or self._usage
258
+ elif method in ("turn/completed", "error"):
259
+ self._finish_turn()
260
+
261
+ def _finish_turn(self):
262
+ if self._turn is not None and not self._turn.done():
263
+ self._turn.set_result(None)
264
+
265
+ async def _decline(self, msg: dict):
266
+ rid = msg.get("id")
267
+ deny = self._deny.get(msg.get("method", ""))
268
+ try:
269
+ if deny is not None:
270
+ await self._write({"jsonrpc": "2.0", "id": rid, "result": deny})
271
+ else:
272
+ await self._write({
273
+ "jsonrpc": "2.0", "id": rid,
274
+ "error": {"code": -32601, "message": "not handled"},
275
+ })
276
+ except Exception:
277
+ pass
278
+
279
+ async def close(self):
280
+ if self._reader is not None:
281
+ self._reader.cancel()
282
+ try:
283
+ await self._reader
284
+ except (asyncio.CancelledError, Exception):
285
+ pass
286
+ if self.proc.returncode is None:
287
+ try:
288
+ self.proc.kill()
289
+ await self.proc.wait()
290
+ except Exception:
291
+ pass