octo-agent 0.11.2

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 (319) hide show
  1. checksums.yaml +7 -0
  2. data/.clacky/skills/commit/SKILL.md +423 -0
  3. data/.clacky/skills/gem-release/SKILL.md +199 -0
  4. data/.clacky/skills/gem-release/scripts/release.sh +304 -0
  5. data/.clacky/skills/oss-upload/SKILL.md +47 -0
  6. data/.octorules +106 -0
  7. data/.rspec +3 -0
  8. data/.rubocop.yml +8 -0
  9. data/CHANGELOG.md +76 -0
  10. data/CODE_OF_CONDUCT.md +132 -0
  11. data/CONTRIBUTING.md +92 -0
  12. data/Dockerfile +28 -0
  13. data/LICENSE.txt +22 -0
  14. data/POSITIONING.md +46 -0
  15. data/README.md +134 -0
  16. data/README_CN.md +134 -0
  17. data/Rakefile +34 -0
  18. data/benchmark/fixtures/sample_project/Gemfile +3 -0
  19. data/benchmark/fixtures/sample_project/lib/api_handler.rb +32 -0
  20. data/benchmark/fixtures/sample_project/lib/order_calculator.rb +23 -0
  21. data/benchmark/fixtures/sample_project/lib/user_renderer.rb +20 -0
  22. data/benchmark/fixtures/sample_project/spec/order_calculator_spec.rb +20 -0
  23. data/benchmark/results/EVALUATION_REPORT.md +165 -0
  24. data/benchmark/results/baseline_20260511_174424.json +128 -0
  25. data/benchmark/results/report_20260511_175256.json +271 -0
  26. data/benchmark/results/report_20260511_175444.json +271 -0
  27. data/benchmark/results/treatment_20260511_175103.json +130 -0
  28. data/benchmark/runner.rb +441 -0
  29. data/bin/octo +7 -0
  30. data/docs/agent-first-ui-design.md +77 -0
  31. data/docs/billing-system.md +318 -0
  32. data/docs/channel-architecture.md +235 -0
  33. data/docs/engineering-article.md +343 -0
  34. data/docs/session-skill-invocation.md +69 -0
  35. data/docs/time_machine_design.md +247 -0
  36. data/docs/ui2-architecture.md +124 -0
  37. data/homebrew/README.md +96 -0
  38. data/homebrew/openocto.rb +24 -0
  39. data/lib/octo/agent/hook_manager.rb +61 -0
  40. data/lib/octo/agent/llm_caller.rb +800 -0
  41. data/lib/octo/agent/memory_updater.rb +246 -0
  42. data/lib/octo/agent/message_compressor.rb +225 -0
  43. data/lib/octo/agent/message_compressor_helper.rb +869 -0
  44. data/lib/octo/agent/next_message_suggester.rb +215 -0
  45. data/lib/octo/agent/session_serializer.rb +685 -0
  46. data/lib/octo/agent/skill_auto_creator.rb +114 -0
  47. data/lib/octo/agent/skill_evolution.rb +61 -0
  48. data/lib/octo/agent/skill_manager.rb +466 -0
  49. data/lib/octo/agent/skill_reflector.rb +89 -0
  50. data/lib/octo/agent/system_prompt_builder.rb +101 -0
  51. data/lib/octo/agent/time_machine.rb +214 -0
  52. data/lib/octo/agent/tool_executor.rb +454 -0
  53. data/lib/octo/agent/tool_registry.rb +150 -0
  54. data/lib/octo/agent.rb +2180 -0
  55. data/lib/octo/agent_config.rb +989 -0
  56. data/lib/octo/agent_profile.rb +112 -0
  57. data/lib/octo/anthropic_stream_aggregator.rb +137 -0
  58. data/lib/octo/background_task_registry.rb +324 -0
  59. data/lib/octo/banner.rb +34 -0
  60. data/lib/octo/bedrock_stream_aggregator.rb +137 -0
  61. data/lib/octo/block_font.rb +331 -0
  62. data/lib/octo/cli.rb +968 -0
  63. data/lib/octo/client.rb +623 -0
  64. data/lib/octo/default_agents/SOUL.md +3 -0
  65. data/lib/octo/default_agents/USER.md +1 -0
  66. data/lib/octo/default_agents/base_prompt.md +66 -0
  67. data/lib/octo/default_agents/coding/profile.yml +2 -0
  68. data/lib/octo/default_agents/coding/system_prompt.md +67 -0
  69. data/lib/octo/default_agents/general/profile.yml +2 -0
  70. data/lib/octo/default_agents/general/system_prompt.md +16 -0
  71. data/lib/octo/default_parsers/doc_parser.rb +69 -0
  72. data/lib/octo/default_parsers/docx_parser.rb +188 -0
  73. data/lib/octo/default_parsers/pdf_parser.rb +120 -0
  74. data/lib/octo/default_parsers/pdf_parser_ocr.py +103 -0
  75. data/lib/octo/default_parsers/pdf_parser_plumber.py +62 -0
  76. data/lib/octo/default_parsers/pptx_parser.rb +140 -0
  77. data/lib/octo/default_parsers/xlsx_parser.rb +121 -0
  78. data/lib/octo/default_skills/browser-setup/SKILL.md +426 -0
  79. data/lib/octo/default_skills/channel-manager/SKILL.md +623 -0
  80. data/lib/octo/default_skills/channel-manager/dingtalk_setup.rb +191 -0
  81. data/lib/octo/default_skills/channel-manager/discord_setup.rb +199 -0
  82. data/lib/octo/default_skills/channel-manager/feishu_setup.rb +574 -0
  83. data/lib/octo/default_skills/channel-manager/import_lark_skills.rb +97 -0
  84. data/lib/octo/default_skills/channel-manager/install_feishu_skills.rb +105 -0
  85. data/lib/octo/default_skills/channel-manager/weixin_setup.rb +274 -0
  86. data/lib/octo/default_skills/code-explorer/SKILL.md +36 -0
  87. data/lib/octo/default_skills/cron-task-creator/SKILL.md +257 -0
  88. data/lib/octo/default_skills/cron-task-creator/evals/evals.json +38 -0
  89. data/lib/octo/default_skills/onboard/SKILL.md +578 -0
  90. data/lib/octo/default_skills/onboard/scripts/import_external_skills.rb +413 -0
  91. data/lib/octo/default_skills/onboard/scripts/install_builtin_skills.rb +97 -0
  92. data/lib/octo/default_skills/persist-memory/SKILL.md +59 -0
  93. data/lib/octo/default_skills/personal-website/SKILL.md +113 -0
  94. data/lib/octo/default_skills/personal-website/publish.rb +235 -0
  95. data/lib/octo/default_skills/product-help/SKILL.md +123 -0
  96. data/lib/octo/default_skills/product-help/docs/agent-config.md +74 -0
  97. data/lib/octo/default_skills/product-help/docs/best-practices.md +49 -0
  98. data/lib/octo/default_skills/product-help/docs/browser-tool.md +53 -0
  99. data/lib/octo/default_skills/product-help/docs/built-in-skills.md +43 -0
  100. data/lib/octo/default_skills/product-help/docs/cli-reference.md +82 -0
  101. data/lib/octo/default_skills/product-help/docs/create-your-first-skill.md +47 -0
  102. data/lib/octo/default_skills/product-help/docs/faq.md +98 -0
  103. data/lib/octo/default_skills/product-help/docs/how-to-use-a-skill.md +58 -0
  104. data/lib/octo/default_skills/product-help/docs/installation.md +59 -0
  105. data/lib/octo/default_skills/product-help/docs/memory-system.md +61 -0
  106. data/lib/octo/default_skills/product-help/docs/octorules.md +62 -0
  107. data/lib/octo/default_skills/product-help/docs/session-management.md +63 -0
  108. data/lib/octo/default_skills/product-help/docs/skill-basics.md +55 -0
  109. data/lib/octo/default_skills/product-help/docs/skill-frontmatter.md +61 -0
  110. data/lib/octo/default_skills/product-help/docs/web-server.md +49 -0
  111. data/lib/octo/default_skills/product-help/docs/what-is-octo.md +37 -0
  112. data/lib/octo/default_skills/product-help/docs/windows-installation.md +36 -0
  113. data/lib/octo/default_skills/product-help/docs/writing-tips.md +53 -0
  114. data/lib/octo/default_skills/recall-memory/SKILL.md +65 -0
  115. data/lib/octo/default_skills/skill-add/SKILL.md +59 -0
  116. data/lib/octo/default_skills/skill-add/scripts/install_from_zip.rb +295 -0
  117. data/lib/octo/default_skills/skill-creator/SKILL.md +602 -0
  118. data/lib/octo/default_skills/skill-creator/agents/analyzer.md +274 -0
  119. data/lib/octo/default_skills/skill-creator/agents/comparator.md +202 -0
  120. data/lib/octo/default_skills/skill-creator/agents/grader.md +223 -0
  121. data/lib/octo/default_skills/skill-creator/eval-viewer/generate_review.py +471 -0
  122. data/lib/octo/default_skills/skill-creator/eval-viewer/viewer.html +1325 -0
  123. data/lib/octo/default_skills/skill-creator/references/schemas.md +430 -0
  124. data/lib/octo/default_skills/skill-creator/scripts/__init__.py +0 -0
  125. data/lib/octo/default_skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
  126. data/lib/octo/default_skills/skill-creator/scripts/generate_report.py +326 -0
  127. data/lib/octo/default_skills/skill-creator/scripts/improve_description.py +310 -0
  128. data/lib/octo/default_skills/skill-creator/scripts/quick_validate.py +103 -0
  129. data/lib/octo/default_skills/skill-creator/scripts/run_eval.py +317 -0
  130. data/lib/octo/default_skills/skill-creator/scripts/run_loop.py +331 -0
  131. data/lib/octo/default_skills/skill-creator/scripts/utils.py +47 -0
  132. data/lib/octo/default_skills/skill-creator/scripts/validate_skill_frontmatter.rb +143 -0
  133. data/lib/octo/idle_compression_timer.rb +115 -0
  134. data/lib/octo/json_ui_controller.rb +204 -0
  135. data/lib/octo/message_format/anthropic.rb +409 -0
  136. data/lib/octo/message_format/bedrock.rb +361 -0
  137. data/lib/octo/message_format/open_ai.rb +222 -0
  138. data/lib/octo/message_history.rb +373 -0
  139. data/lib/octo/openai_stream_aggregator.rb +130 -0
  140. data/lib/octo/plain_ui_controller.rb +166 -0
  141. data/lib/octo/providers.rb +534 -0
  142. data/lib/octo/server/browser_manager.rb +397 -0
  143. data/lib/octo/server/channel/adapters/base.rb +82 -0
  144. data/lib/octo/server/channel/adapters/dingtalk/adapter.rb +314 -0
  145. data/lib/octo/server/channel/adapters/dingtalk/api_client.rb +391 -0
  146. data/lib/octo/server/channel/adapters/dingtalk/stream_client.rb +203 -0
  147. data/lib/octo/server/channel/adapters/discord/adapter.rb +229 -0
  148. data/lib/octo/server/channel/adapters/discord/api_client.rb +107 -0
  149. data/lib/octo/server/channel/adapters/discord/gateway_client.rb +270 -0
  150. data/lib/octo/server/channel/adapters/feishu/adapter.rb +320 -0
  151. data/lib/octo/server/channel/adapters/feishu/bot.rb +478 -0
  152. data/lib/octo/server/channel/adapters/feishu/file_processor.rb +36 -0
  153. data/lib/octo/server/channel/adapters/feishu/message_parser.rb +129 -0
  154. data/lib/octo/server/channel/adapters/feishu/ws_client.rb +423 -0
  155. data/lib/octo/server/channel/adapters/telegram/adapter.rb +375 -0
  156. data/lib/octo/server/channel/adapters/telegram/api_client.rb +205 -0
  157. data/lib/octo/server/channel/adapters/wecom/adapter.rb +148 -0
  158. data/lib/octo/server/channel/adapters/wecom/media_downloader.rb +115 -0
  159. data/lib/octo/server/channel/adapters/wecom/ws_client.rb +395 -0
  160. data/lib/octo/server/channel/adapters/weixin/adapter.rb +692 -0
  161. data/lib/octo/server/channel/adapters/weixin/api_client.rb +402 -0
  162. data/lib/octo/server/channel/channel_config.rb +178 -0
  163. data/lib/octo/server/channel/channel_manager.rb +468 -0
  164. data/lib/octo/server/channel/channel_ui_controller.rb +224 -0
  165. data/lib/octo/server/channel.rb +33 -0
  166. data/lib/octo/server/discover.rb +77 -0
  167. data/lib/octo/server/epipe_safe_io.rb +105 -0
  168. data/lib/octo/server/http_server.rb +3554 -0
  169. data/lib/octo/server/scheduler.rb +317 -0
  170. data/lib/octo/server/server_master.rb +325 -0
  171. data/lib/octo/server/session_registry.rb +431 -0
  172. data/lib/octo/server/web_ui_controller.rb +487 -0
  173. data/lib/octo/session_manager.rb +385 -0
  174. data/lib/octo/skill.rb +466 -0
  175. data/lib/octo/skill_loader.rb +328 -0
  176. data/lib/octo/tools/base.rb +118 -0
  177. data/lib/octo/tools/browser.rb +625 -0
  178. data/lib/octo/tools/edit.rb +165 -0
  179. data/lib/octo/tools/file_reader.rb +549 -0
  180. data/lib/octo/tools/glob.rb +162 -0
  181. data/lib/octo/tools/grep.rb +356 -0
  182. data/lib/octo/tools/invoke_skill.rb +96 -0
  183. data/lib/octo/tools/list_tasks.rb +54 -0
  184. data/lib/octo/tools/redo_task.rb +41 -0
  185. data/lib/octo/tools/request_user_feedback.rb +84 -0
  186. data/lib/octo/tools/security.rb +333 -0
  187. data/lib/octo/tools/terminal/output_cleaner.rb +63 -0
  188. data/lib/octo/tools/terminal/persistent_session.rb +268 -0
  189. data/lib/octo/tools/terminal/safe_rm.sh +106 -0
  190. data/lib/octo/tools/terminal/session_manager.rb +213 -0
  191. data/lib/octo/tools/terminal.rb +1828 -0
  192. data/lib/octo/tools/todo_manager.rb +374 -0
  193. data/lib/octo/tools/trash_manager.rb +388 -0
  194. data/lib/octo/tools/undo_task.rb +35 -0
  195. data/lib/octo/tools/web_fetch.rb +242 -0
  196. data/lib/octo/tools/web_search.rb +260 -0
  197. data/lib/octo/tools/write.rb +77 -0
  198. data/lib/octo/ui2/block_font.rb +10 -0
  199. data/lib/octo/ui2/components/base_component.rb +163 -0
  200. data/lib/octo/ui2/components/command_suggestions.rb +290 -0
  201. data/lib/octo/ui2/components/common_component.rb +96 -0
  202. data/lib/octo/ui2/components/inline_input.rb +226 -0
  203. data/lib/octo/ui2/components/input_area.rb +1338 -0
  204. data/lib/octo/ui2/components/message_component.rb +99 -0
  205. data/lib/octo/ui2/components/modal_component.rb +419 -0
  206. data/lib/octo/ui2/components/todo_area.rb +149 -0
  207. data/lib/octo/ui2/components/tool_component.rb +107 -0
  208. data/lib/octo/ui2/components/welcome_banner.rb +139 -0
  209. data/lib/octo/ui2/layout_manager.rb +807 -0
  210. data/lib/octo/ui2/line_editor.rb +363 -0
  211. data/lib/octo/ui2/markdown_renderer.rb +100 -0
  212. data/lib/octo/ui2/output_buffer.rb +370 -0
  213. data/lib/octo/ui2/progress_handle.rb +362 -0
  214. data/lib/octo/ui2/progress_indicator.rb +55 -0
  215. data/lib/octo/ui2/screen_buffer.rb +273 -0
  216. data/lib/octo/ui2/terminal_detector.rb +119 -0
  217. data/lib/octo/ui2/theme_manager.rb +85 -0
  218. data/lib/octo/ui2/themes/base_theme.rb +105 -0
  219. data/lib/octo/ui2/themes/hacker_theme.rb +62 -0
  220. data/lib/octo/ui2/themes/minimal_theme.rb +56 -0
  221. data/lib/octo/ui2/thinking_verbs.rb +26 -0
  222. data/lib/octo/ui2/ui_controller.rb +1625 -0
  223. data/lib/octo/ui2/view_renderer.rb +177 -0
  224. data/lib/octo/ui2.rb +40 -0
  225. data/lib/octo/ui_interface.rb +154 -0
  226. data/lib/octo/utils/arguments_parser.rb +191 -0
  227. data/lib/octo/utils/browser_detector.rb +195 -0
  228. data/lib/octo/utils/encoding.rb +92 -0
  229. data/lib/octo/utils/environment_detector.rb +140 -0
  230. data/lib/octo/utils/file_ignore_helper.rb +170 -0
  231. data/lib/octo/utils/file_processor.rb +601 -0
  232. data/lib/octo/utils/gitignore_parser.rb +154 -0
  233. data/lib/octo/utils/limit_stack.rb +152 -0
  234. data/lib/octo/utils/logger.rb +124 -0
  235. data/lib/octo/utils/login_shell.rb +72 -0
  236. data/lib/octo/utils/model_pricing.rb +646 -0
  237. data/lib/octo/utils/parser_manager.rb +165 -0
  238. data/lib/octo/utils/path_helper.rb +15 -0
  239. data/lib/octo/utils/scripts_manager.rb +59 -0
  240. data/lib/octo/utils/string_matcher.rb +158 -0
  241. data/lib/octo/utils/trash_directory.rb +112 -0
  242. data/lib/octo/utils/workspace_rules.rb +46 -0
  243. data/lib/octo/version.rb +5 -0
  244. data/lib/octo/web/app.css +7141 -0
  245. data/lib/octo/web/app.js +543 -0
  246. data/lib/octo/web/apple-touch-icon.png +0 -0
  247. data/lib/octo/web/auth.js +150 -0
  248. data/lib/octo/web/channels.js +276 -0
  249. data/lib/octo/web/datepicker.js +205 -0
  250. data/lib/octo/web/favicon.png +0 -0
  251. data/lib/octo/web/i18n.js +1073 -0
  252. data/lib/octo/web/icon-512.png +0 -0
  253. data/lib/octo/web/icon-dark.svg +25 -0
  254. data/lib/octo/web/icon.svg +29 -0
  255. data/lib/octo/web/index.html +871 -0
  256. data/lib/octo/web/marked.min.js +69 -0
  257. data/lib/octo/web/onboard.js +491 -0
  258. data/lib/octo/web/profile.js +442 -0
  259. data/lib/octo/web/sessions.js +4421 -0
  260. data/lib/octo/web/settings.js +913 -0
  261. data/lib/octo/web/sidebar.js +32 -0
  262. data/lib/octo/web/skills.js +885 -0
  263. data/lib/octo/web/tasks.js +297 -0
  264. data/lib/octo/web/theme.js +105 -0
  265. data/lib/octo/web/trash.js +343 -0
  266. data/lib/octo/web/vendor/hljs/highlight.min.js +1244 -0
  267. data/lib/octo/web/vendor/hljs/hljs-theme.css +95 -0
  268. data/lib/octo/web/vendor/katex/auto-render.min.js +1 -0
  269. data/lib/octo/web/vendor/katex/fonts/KaTeX_AMS-Regular.woff2 +0 -0
  270. data/lib/octo/web/vendor/katex/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
  271. data/lib/octo/web/vendor/katex/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
  272. data/lib/octo/web/vendor/katex/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
  273. data/lib/octo/web/vendor/katex/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
  274. data/lib/octo/web/vendor/katex/fonts/KaTeX_Main-Bold.woff2 +0 -0
  275. data/lib/octo/web/vendor/katex/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
  276. data/lib/octo/web/vendor/katex/fonts/KaTeX_Main-Italic.woff2 +0 -0
  277. data/lib/octo/web/vendor/katex/fonts/KaTeX_Main-Regular.woff2 +0 -0
  278. data/lib/octo/web/vendor/katex/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
  279. data/lib/octo/web/vendor/katex/fonts/KaTeX_Math-Italic.woff2 +0 -0
  280. data/lib/octo/web/vendor/katex/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
  281. data/lib/octo/web/vendor/katex/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
  282. data/lib/octo/web/vendor/katex/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
  283. data/lib/octo/web/vendor/katex/fonts/KaTeX_Script-Regular.woff2 +0 -0
  284. data/lib/octo/web/vendor/katex/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  285. data/lib/octo/web/vendor/katex/fonts/KaTeX_Size2-Regular.woff2 +0 -0
  286. data/lib/octo/web/vendor/katex/fonts/KaTeX_Size3-Regular.woff2 +0 -0
  287. data/lib/octo/web/vendor/katex/fonts/KaTeX_Size4-Regular.woff2 +0 -0
  288. data/lib/octo/web/vendor/katex/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
  289. data/lib/octo/web/vendor/katex/katex.min.css +1 -0
  290. data/lib/octo/web/vendor/katex/katex.min.js +1 -0
  291. data/lib/octo/web/version.js +449 -0
  292. data/lib/octo/web/weixin-qr.html +209 -0
  293. data/lib/octo/web/ws-dispatcher.js +357 -0
  294. data/lib/octo/web/ws.js +128 -0
  295. data/lib/octo.rb +145 -0
  296. data/scripts/build/build.sh +329 -0
  297. data/scripts/build/lib/apt.sh +56 -0
  298. data/scripts/build/lib/brew.sh +89 -0
  299. data/scripts/build/lib/colors.sh +17 -0
  300. data/scripts/build/lib/gem.sh +95 -0
  301. data/scripts/build/lib/mise.sh +125 -0
  302. data/scripts/build/lib/network.sh +157 -0
  303. data/scripts/build/lib/os.sh +57 -0
  304. data/scripts/build/lib/shell.sh +37 -0
  305. data/scripts/build/src/install.sh.cc +174 -0
  306. data/scripts/build/src/install_browser.sh.cc +101 -0
  307. data/scripts/build/src/install_full.sh.cc +290 -0
  308. data/scripts/build/src/install_rails_deps.sh.cc +145 -0
  309. data/scripts/build/src/install_system_deps.sh.cc +123 -0
  310. data/scripts/build/src/uninstall.sh.cc +101 -0
  311. data/scripts/install.ps1 +532 -0
  312. data/scripts/install.sh +567 -0
  313. data/scripts/install_browser.sh +479 -0
  314. data/scripts/install_full.sh +838 -0
  315. data/scripts/install_rails_deps.sh +746 -0
  316. data/scripts/install_system_deps.sh +518 -0
  317. data/scripts/uninstall.sh +287 -0
  318. data/sig/octo.rbs +4 -0
  319. metadata +614 -0
@@ -0,0 +1,543 @@
1
+ // ── app.js — Main entry point ──────────────────────────────────────────────
2
+ //
3
+ // Coordinates WS, Sessions, Tasks, Skills and Settings modules.
4
+ // Handles WS event dispatch and wires up all DOM event listeners.
5
+ //
6
+ // Load order (in index.html):
7
+ // ws.js → sessions.js → tasks.js → skills.js → app.js
8
+ // ─────────────────────────────────────────────────────────────────────────
9
+
10
+ // ── DOM helper (shared by all modules loaded after this) ──────────────────
11
+ const $ = id => document.getElementById(id);
12
+
13
+ // ── Utilities (shared) ────────────────────────────────────────────────────
14
+ function escapeHtml(str) {
15
+ return String(str)
16
+ .replace(/&/g, "&")
17
+ .replace(/</g, "&lt;")
18
+ .replace(/>/g, "&gt;");
19
+ }
20
+
21
+ // ── Router ────────────────────────────────────────────────────────────────
22
+ //
23
+ // Single source of truth for panel visibility and URL hash.
24
+ //
25
+ // Views:
26
+ // welcome → /#
27
+ // session/{id} → /#session/{id}
28
+ // tasks → /#tasks
29
+ // skills → /#skills
30
+ // settings → /#settings
31
+ //
32
+ // Usage:
33
+ // Router.navigate("session", { id: "abc123" })
34
+ // Router.navigate("tasks")
35
+ // Router.navigate("welcome")
36
+ //
37
+ // All panels must be listed in PANELS so they are hidden before the active
38
+ // one is shown. Modules must NOT touch panel display styles directly.
39
+ // ─────────────────────────────────────────────────────────────────────────
40
+ const PANELS = [
41
+ "setup-panel",
42
+ "onboard-panel",
43
+ "welcome",
44
+ "chat-panel",
45
+ "task-detail-panel",
46
+ "skills-panel",
47
+ "channels-panel",
48
+ "trash-panel",
49
+ "profile-panel",
50
+ "settings-panel",
51
+ ];
52
+
53
+ const Router = (() => {
54
+ let _current = null; // current view name
55
+ let _params = {}; // current params (e.g. { id: "abc" } for session view)
56
+ let _skipNextHashChange = false; // prevent echo loop when we set hash ourselves
57
+
58
+ // Hide all panels.
59
+ function _hideAll() {
60
+ PANELS.forEach(p => {
61
+ const el = $(p);
62
+ if (el) el.style.display = "none";
63
+ });
64
+ }
65
+
66
+ // Update the URL hash without triggering a hashchange handler loop.
67
+ function _setHash(hash) {
68
+ _skipNextHashChange = true;
69
+ location.hash = hash;
70
+ }
71
+
72
+ // Resolve a hash string into { view, params }.
73
+ function _parseHash(hash) {
74
+ const h = (hash || "").replace(/^#\/?/, "");
75
+ if (!h) return { view: "welcome", params: {} };
76
+ if (h === "tasks") return { view: "tasks", params: {} };
77
+ if (h === "skills") return { view: "skills", params: {} };
78
+ if (h === "channels") return { view: "channels", params: {} };
79
+ if (h === "trash") return { view: "trash", params: {} };
80
+ if (h === "profile") return { view: "profile", params: {} };
81
+ // Legacy: #memories redirects to #profile (memories are now merged into
82
+ // the profile panel). Kept so bookmarks / external links don't 404.
83
+ if (h === "memories") return { view: "profile", params: {} };
84
+ if (h === "settings") return { view: "settings", params: {} };
85
+ const m = h.match(/^session\/(.+)$/);
86
+ if (m) return { view: "session", params: { id: m[1] } };
87
+ return { view: "welcome", params: {} };
88
+ }
89
+
90
+ // Sidebar items managed by Router (keyed by view name → element id).
91
+ // Router is the single authority for active highlight — modules must NOT
92
+ // add/remove the "active" class on these elements themselves.
93
+ const SIDEBAR_ITEMS = {
94
+ tasks: "tasks-sidebar-item",
95
+ skills: "skills-sidebar-item",
96
+ channels: "channels-sidebar-item",
97
+ trash: "trash-sidebar-item",
98
+ profile: "profile-sidebar-item",
99
+ };
100
+
101
+ // Remove active highlight from all Router-managed sidebar items.
102
+ function _clearSidebarActive() {
103
+ Object.values(SIDEBAR_ITEMS).forEach(id => {
104
+ const el = $(id);
105
+ if (el) el.classList.remove("active");
106
+ });
107
+ }
108
+
109
+ // Core: apply a view change. Called both from navigate() and hashchange.
110
+ function _apply(view, params = {}) {
111
+ _current = view;
112
+ _params = params;
113
+
114
+ // Close sidebar on mobile when navigating to any view
115
+ _mobileCloseSidebar();
116
+
117
+ // ── Clean up previous state ──────────────────────────────────────────
118
+ if (Sessions.activeId) {
119
+ Sessions._cacheActiveAndDeselect();
120
+ }
121
+ Sessions.updateInfoBar(null); // hide info bar when leaving any session
122
+ // Clear all sidebar highlights and settings button active state
123
+ _clearSidebarActive();
124
+ const btnSettings = $("btn-settings");
125
+ if (btnSettings) btnSettings.classList.remove("active");
126
+
127
+ _hideAll();
128
+
129
+ // Reveal #app on first navigation — ensures the correct view (and language)
130
+ // is already in place before the user sees anything.
131
+ // #app covers sidebar + main, so data-i18n elements in the sidebar are also
132
+ // hidden until applyAll() has run (prevents flash of English sidebar labels).
133
+ const appEl = document.getElementById("app");
134
+ if (appEl && appEl.style.visibility === "hidden") {
135
+ I18n.applyAll(); // Translate all data-i18n elements before revealing
136
+ appEl.style.visibility = "";
137
+ }
138
+
139
+ // ── Activate target panel + sidebar highlight ────────────────────────
140
+ switch (view) {
141
+
142
+ case "session": {
143
+ const id = params.id;
144
+ const s = Sessions.find(id);
145
+ if (!s) {
146
+ // Session not found (e.g. deleted) — fall back to welcome
147
+ _apply("welcome");
148
+ return;
149
+ }
150
+ _setHash(`session/${id}`);
151
+ $("chat-panel").style.display = "flex";
152
+ $("chat-panel").style.flexDirection = "column";
153
+ Sessions.updateChatHeader(s);
154
+ Sessions.updateStatusBar(s.status);
155
+ Sessions.updateInfoBar(s);
156
+ Sessions._restoreMessagesPublic(id);
157
+ Sessions._setActiveId(id);
158
+ // Immediately re-attach saved progress UI (timer + spinner) so it appears
159
+ // instantly without waiting for the async history fetch or WS replay.
160
+ Sessions._attachProgressUI(id);
161
+ WS.setSubscribedSession(id);
162
+ // Only disable send button until server confirms subscription
163
+ // Input field remains usable so user can type while waiting
164
+ $("btn-send").disabled = true;
165
+ WS.send({ type: "subscribe", session_id: id });
166
+ Sessions.renderList();
167
+ $("user-input").focus();
168
+
169
+ // Load session-scoped skill list (filtered by agent profile) for slash autocomplete
170
+ SkillAC.loadForSession(id);
171
+
172
+ // Always reload history on every switch (cache is not used)
173
+ Sessions.loadHistory(id);
174
+ break;
175
+ }
176
+
177
+ case "tasks":
178
+ _setHash("tasks");
179
+ $("task-detail-panel").style.display = "flex";
180
+ Tasks.onPanelShow();
181
+ Sessions.renderList();
182
+ break;
183
+
184
+ case "skills":
185
+ _setHash("skills");
186
+ $("skills-panel").style.display = "flex";
187
+ Skills.onPanelShow();
188
+ Sessions.renderList();
189
+ break;
190
+
191
+ case "channels":
192
+ _setHash("channels");
193
+ $("channels-panel").style.display = "flex";
194
+ Channels.onPanelShow();
195
+ Sessions.renderList();
196
+ break;
197
+
198
+ case "trash":
199
+ _setHash("trash");
200
+ $("trash-panel").style.display = "flex";
201
+ Trash.onPanelShow();
202
+ Sessions.renderList();
203
+ break;
204
+
205
+ case "profile":
206
+ _setHash("profile");
207
+ $("profile-panel").style.display = "flex";
208
+ Profile.onPanelShow();
209
+ Sessions.renderList();
210
+ break;
211
+
212
+ case "settings":
213
+ _setHash("settings");
214
+ $("settings-panel").style.display = "";
215
+ if (btnSettings) btnSettings.classList.add("active");
216
+ Settings.open();
217
+ Sessions.renderList();
218
+ break;
219
+
220
+ case "setup":
221
+ // Full-screen mandatory setup (language + API key). No hash — keep URL clean.
222
+ $("setup-panel").style.display = "flex";
223
+ break;
224
+
225
+ case "onboard":
226
+ // Kept for compatibility; setup-panel is now used for first-run setup.
227
+ $("onboard-panel").style.display = "flex";
228
+ break;
229
+
230
+ default: // "welcome"
231
+ _setHash("");
232
+ $("welcome").style.display = "";
233
+ Sessions.renderList();
234
+ break;
235
+ }
236
+
237
+ // Re-apply sidebar active highlight after all rendering is done.
238
+ // renderSection() rebuilds the DOM element, so we stamp active *after*.
239
+ _clearSidebarActive();
240
+ const activeItem = SIDEBAR_ITEMS[view];
241
+ if (activeItem) $(activeItem)?.classList.add("active");
242
+ }
243
+
244
+ // Listen for browser back/forward (or manual hash edits).
245
+ window.addEventListener("hashchange", () => {
246
+ if (_skipNextHashChange) {
247
+ _skipNextHashChange = false;
248
+ return;
249
+ }
250
+ const { view, params } = _parseHash(location.hash);
251
+ _apply(view, params);
252
+ });
253
+
254
+ return {
255
+ get current() { return _current; },
256
+ get params() { return _params; },
257
+
258
+ /** Navigate to a view. This is the only way panels should change. */
259
+ navigate(view, params = {}) {
260
+ _apply(view, params);
261
+ },
262
+
263
+ /** Restore state from current URL hash (called once on boot after data loads). */
264
+ restoreFromHash() {
265
+ const { view, params } = _parseHash(location.hash);
266
+ _apply(view, params);
267
+ },
268
+ };
269
+ })();
270
+
271
+ // ── Modal utility ─────────────────────────────────────────────────────────
272
+ const Modal = (() => {
273
+ /** Show a yes/no confirmation dialog. Returns a Promise<boolean>. */
274
+ function confirm(message) {
275
+ return new Promise(resolve => {
276
+ $("modal-message").textContent = message;
277
+ $("modal-overlay").style.display = "flex";
278
+
279
+ const cleanup = (result) => {
280
+ $("modal-overlay").style.display = "none";
281
+ $("modal-yes").onclick = null;
282
+ $("modal-no").onclick = null;
283
+ resolve(result);
284
+ };
285
+ $("modal-yes").onclick = () => cleanup(true);
286
+ $("modal-no").onclick = () => cleanup(false);
287
+ });
288
+ }
289
+
290
+ /** Show a text input prompt dialog. Returns a Promise<string|null>. */
291
+ function prompt(message, defaultValue = "") {
292
+ return new Promise(resolve => {
293
+ $("prompt-modal-message").textContent = message;
294
+ const input = $("prompt-modal-input");
295
+ input.value = defaultValue;
296
+ $("prompt-modal-overlay").style.display = "flex";
297
+
298
+ // Auto-focus and select all text
299
+ setTimeout(() => {
300
+ input.focus();
301
+ input.select();
302
+ }, 50);
303
+
304
+ const cleanup = (result) => {
305
+ $("prompt-modal-overlay").style.display = "none";
306
+ $("prompt-modal-ok").onclick = null;
307
+ $("prompt-modal-cancel").onclick = null;
308
+ input.onkeydown = null;
309
+ resolve(result);
310
+ };
311
+
312
+ $("prompt-modal-ok").onclick = () => cleanup(input.value.trim() || null);
313
+ $("prompt-modal-cancel").onclick = () => cleanup(null);
314
+
315
+ // Support Enter to confirm, Escape to cancel
316
+ input.onkeydown = (e) => {
317
+ if (e.key === "Enter") cleanup(input.value.trim() || null);
318
+ if (e.key === "Escape") cleanup(null);
319
+ };
320
+ });
321
+ }
322
+
323
+ /** Show a rename dialog. Returns a Promise<string|null>. */
324
+ function rename(currentName = "") {
325
+ return new Promise(resolve => {
326
+ const input = $("rename-modal-input");
327
+ input.value = currentName;
328
+ input.classList.remove("input-error");
329
+ $("rename-modal-overlay").style.display = "flex";
330
+
331
+ setTimeout(() => {
332
+ input.focus();
333
+ input.select();
334
+ }, 50);
335
+
336
+ const cleanup = (result) => {
337
+ $("rename-modal-overlay").style.display = "none";
338
+ $("rename-modal-save").onclick = null;
339
+ $("rename-modal-cancel").onclick = null;
340
+ $("rename-modal-overlay").onclick = null;
341
+ input.onkeydown = null;
342
+ input.oninput = null;
343
+ resolve(result);
344
+ };
345
+
346
+ const saveHandler = () => {
347
+ const newName = input.value.trim();
348
+ if (!newName) {
349
+ input.classList.add("input-error");
350
+ input.focus();
351
+ return;
352
+ }
353
+ cleanup(newName === currentName ? null : newName);
354
+ };
355
+
356
+ input.oninput = () => input.classList.remove("input-error");
357
+
358
+ $("rename-modal-save").onclick = saveHandler;
359
+ $("rename-modal-cancel").onclick = () => cleanup(null);
360
+
361
+ input.onkeydown = (e) => {
362
+ if (e.key === "Enter") { e.preventDefault(); saveHandler(); }
363
+ if (e.key === "Escape") cleanup(null);
364
+ };
365
+
366
+ // Close on overlay click
367
+ $("rename-modal-overlay").onclick = (e) => {
368
+ if (e.target.id === "rename-modal-overlay") cleanup(null);
369
+ };
370
+ });
371
+ }
372
+
373
+ return { confirm, prompt, rename };
374
+ })();
375
+
376
+ // ── Confirmation modal ────────────────────────────────────────────────────
377
+ function showConfirmModal(confId, message) {
378
+ $("modal-message").textContent = message;
379
+ $("modal-overlay").style.display = "flex";
380
+
381
+ const answer = result => {
382
+ $("modal-overlay").style.display = "none";
383
+ WS.send({ type: "confirmation", session_id: Sessions.activeId, id: confId, result });
384
+ };
385
+ $("modal-yes").onclick = () => answer("yes");
386
+ $("modal-no").onclick = () => answer("no");
387
+ }
388
+
389
+
390
+ // ── WS event dispatcher ───────────────────────────────────────────────────
391
+ // Moved to ws-dispatcher.js.
392
+
393
+ // ── Image & file attachments ──────────────────────────────────────────────
394
+ // Moved to sessions.js (Composer section — _initComposer() in Sessions.init()).
395
+ // All state (_pendingImages/_pendingFiles), helpers (_addAttachmentFile/etc.),
396
+ // preview rendering, and sendMessage() now live there as private members.
397
+
398
+ // ── DOM event listeners ───────────────────────────────────────────────────
399
+ // Sidebar toggle (with mobile overlay support)
400
+ function _isMobile() { return window.innerWidth <= 768; }
401
+
402
+ function _closeSidebar() {
403
+ $("sidebar").classList.add("hidden");
404
+ $("sidebar-overlay").classList.remove("active");
405
+ }
406
+
407
+ function _openSidebar() {
408
+ $("sidebar").classList.remove("hidden");
409
+ if (_isMobile()) $("sidebar-overlay").classList.add("active");
410
+ }
411
+
412
+ function _toggleSidebar() {
413
+ const isHidden = $("sidebar").classList.contains("hidden");
414
+ isHidden ? _openSidebar() : _closeSidebar();
415
+ }
416
+
417
+ if ($("btn-toggle-sidebar")) {
418
+ $("btn-toggle-sidebar").addEventListener("click", _toggleSidebar);
419
+ }
420
+
421
+ // Tap overlay to close sidebar on mobile
422
+ $("sidebar-overlay").addEventListener("click", _closeSidebar);
423
+
424
+ // On mobile: start with sidebar hidden
425
+ if (_isMobile()) _closeSidebar();
426
+
427
+ // On mobile: auto-close sidebar when switching sessions/pages
428
+ function _mobileCloseSidebar() {
429
+ if (_isMobile()) _closeSidebar();
430
+ }
431
+ // Expose for use in sessions.js (rename/delete dialogs need to close sidebar first)
432
+ window.mobileCloseSidebar = _mobileCloseSidebar;
433
+
434
+ // ── New session controls ───────────────────────────────────────────────────
435
+ // Moved to sessions.js (_initNewSessionControls, called from Sessions.init()).
436
+
437
+ // ── Session search bar ─────────────────────────────────────────────────────
438
+ // Moved to sessions.js (_initSearch in Sessions.init()).
439
+
440
+ // ── Theme / session-scoped message panel bindings ──────────────────────────
441
+
442
+ // Theme toggle in header
443
+ if ($("theme-toggle-header")) {
444
+ $("theme-toggle-header").addEventListener("click", () => Theme.toggle());
445
+ }
446
+ // btn-delete-session, #messages scroll-to-top (load history), and btn-interrupt
447
+ // moved to sessions.js (_initMessageHistory in Sessions.init()).
448
+
449
+ // btn-send, btn-attach, image-file-input change, input-area drag/drop, and
450
+ // user-input paste handlers moved to sessions.js (_initComposer).
451
+
452
+
453
+ // ── Skill autocomplete + composer bindings ───────────────────────────────
454
+ // Moved to skills.js (SkillAC IIFE, initialized from SkillAC.init()).
455
+
456
+
457
+ // ── Boot ──────────────────────────────────────────────────────────────────
458
+ Sidebar.init();
459
+ Settings.init();
460
+ Channels.init();
461
+ Sessions.init();
462
+
463
+ // Boot sequence:
464
+ // 1. Onboard check — first-run setup (key_setup / soul_setup)
465
+ // 2. Normal UI boot — WS + sessions + tasks + skills
466
+ //
467
+ // key_setup → hard block: shows full-screen setup-panel (language + API key).
468
+ // On success, setup-panel auto-launches /onboard session then boots UI.
469
+ // soul_setup → soft: auto-launches /onboard session and boots UI immediately.
470
+ // No blocking panel shown.
471
+
472
+ window.bootUI = async function() {
473
+ const { needsOnboard, phase } = await Onboard.check();
474
+ // key_setup blocks boot entirely; onboard.js calls _bootUI() when done.
475
+ if (needsOnboard && phase === "key_setup") return;
476
+
477
+ // Initialize skill autocomplete
478
+ SkillAC.init();
479
+
480
+ // soul_setup: Onboard.check() already launched the session and called _bootUI().
481
+ // For any other state, boot normally here.
482
+ if (!needsOnboard) {
483
+ // Auth already checked at app boot — safe to make API calls
484
+ WS.connect();
485
+ Tasks.load();
486
+ Skills.load();
487
+ }
488
+ };
489
+
490
+ (async () => {
491
+ // Auth check MUST run first — all API calls depend on it
492
+ const authOk = await Auth.check();
493
+ if (!authOk) {
494
+ // User cancelled auth prompt — stop boot
495
+ return;
496
+ }
497
+
498
+ await window.bootUI();
499
+ })();
500
+
501
+ // ── Image Lightbox ────────────────────────────────────────────────────────────
502
+ // Global lightbox: click any .msg-image-thumb to open; click backdrop or ✕ or
503
+ // press ESC to close.
504
+ (function () {
505
+ let _overlay = null;
506
+
507
+ function _open(src, alt) {
508
+ if (_overlay) return;
509
+ _overlay = document.createElement("div");
510
+ _overlay.className = "img-lightbox";
511
+ _overlay.innerHTML =
512
+ `<span class="img-lightbox-close" title="Close">✕</span>` +
513
+ `<img src="${src}" alt="${alt || "image"}">`;
514
+
515
+ // Click on backdrop or ✕ → close
516
+ _overlay.addEventListener("click", function (e) {
517
+ if (e.target === _overlay || e.target.classList.contains("img-lightbox-close")) {
518
+ _close();
519
+ }
520
+ });
521
+
522
+ document.body.appendChild(_overlay);
523
+ }
524
+
525
+ function _close() {
526
+ if (_overlay) { _overlay.remove(); _overlay = null; }
527
+ }
528
+
529
+ // ESC key closes lightbox
530
+ document.addEventListener("keydown", function (e) {
531
+ if (e.key === "Escape") _close();
532
+ });
533
+
534
+ // Event delegation: any click on .msg-image-thumb anywhere in the page
535
+ document.addEventListener("click", function (e) {
536
+ if (e.target.classList.contains("msg-image-thumb")) {
537
+ _open(e.target.src, e.target.alt);
538
+ }
539
+ });
540
+ })();
541
+
542
+ // Session Info Bar (model switcher + working-directory switcher) moved to sessions.js
543
+
Binary file
@@ -0,0 +1,150 @@
1
+ const Auth = (() => {
2
+ // ── Constants ──────────────────────────────────────────────────────────
3
+ const COOKIE_NAME = 'octo_access_key';
4
+ const STORAGE_KEY = 'octo_access_key';
5
+ const PROBE_ENDPOINT = '/api/sessions?limit=1';
6
+ const MAX_PROMPT_TRIES = 3;
7
+
8
+ const PROBE = Object.freeze({
9
+ OK: 'ok',
10
+ UNAUTHORIZED: 'unauthorized',
11
+ SERVER_ERR: 'server_error',
12
+ NETWORK_ERR: 'network_error',
13
+ });
14
+
15
+ // ── Module state ───────────────────────────────────────────────────────
16
+ let _authCheckPromise = null;
17
+ let _authPassed = false;
18
+
19
+ // ── Storage helpers ────────────────────────────────────────────────────
20
+ const Cookie = {
21
+ set(key) {
22
+ const secure = location.protocol === 'https:' ? '; Secure' : '';
23
+ document.cookie =
24
+ `${COOKIE_NAME}=${encodeURIComponent(key)}; path=/; SameSite=Strict${secure}`;
25
+ },
26
+ clear() {
27
+ document.cookie =
28
+ `${COOKIE_NAME}=; path=/; max-age=0; SameSite=Strict`;
29
+ },
30
+ };
31
+
32
+ function _getStoredKey() {
33
+ return (
34
+ localStorage.getItem(STORAGE_KEY) ||
35
+ new URLSearchParams(location.search).get('access_key') ||
36
+ null
37
+ );
38
+ }
39
+
40
+ // ── Auth probe ─────────────────────────────────────────────────────────
41
+ async function _probe() {
42
+ try {
43
+ const r = await fetch(PROBE_ENDPOINT);
44
+ if (r.ok) return PROBE.OK;
45
+ if (r.status === 401) return PROBE.UNAUTHORIZED;
46
+ return PROBE.SERVER_ERR;
47
+ } catch {
48
+ return PROBE.NETWORK_ERR;
49
+ }
50
+ }
51
+
52
+ // ── Prompt helper ──────────────────────────────────────────────────────
53
+ async function _askUserForKey() {
54
+ const message = (typeof I18n !== 'undefined')
55
+ ? I18n.t('auth.accessKeyRequired')
56
+ : 'Access key required:';
57
+
58
+ const el = document.getElementById('prompt-modal-input');
59
+ if (el) el.type = 'password';
60
+
61
+ try {
62
+ const input = (typeof Modal !== 'undefined' && Modal.prompt)
63
+ ? await Modal.prompt(message)
64
+ : prompt(message);
65
+ return input?.trim() || null;
66
+ } finally {
67
+ if (el) el.type = 'text';
68
+ }
69
+ }
70
+
71
+ // ── Core flow ──────────────────────────────────────────────────────────
72
+ async function _doCheck() {
73
+ const existing = _getStoredKey();
74
+ if (existing) Cookie.set(existing); // seed cookie before probe
75
+
76
+ const result = await _probe();
77
+
78
+ if (result === PROBE.OK) {
79
+ _authPassed = true;
80
+ return true;
81
+ }
82
+
83
+ if (result === PROBE.UNAUTHORIZED) {
84
+ Cookie.clear();
85
+ localStorage.removeItem(STORAGE_KEY);
86
+ return _promptAndRetry();
87
+ }
88
+
89
+ // Server/network error — let app proceed
90
+ _authPassed = true;
91
+ return true;
92
+ }
93
+
94
+ async function _promptAndRetry() {
95
+ for (let attempt = 1; attempt <= MAX_PROMPT_TRIES; attempt++) {
96
+ const key = await _askUserForKey();
97
+ if (!key) {
98
+ _authPassed = false;
99
+ return false;
100
+ }
101
+
102
+ Cookie.set(key);
103
+ const result = await _probe();
104
+
105
+ if (result === PROBE.OK) {
106
+ localStorage.setItem(STORAGE_KEY, key); // persist only after success
107
+ _authPassed = true;
108
+ return true;
109
+ }
110
+
111
+ if (result !== PROBE.UNAUTHORIZED) {
112
+ _authPassed = true; // transient — proceed
113
+ return true;
114
+ }
115
+
116
+ Cookie.clear(); // wrong key → try again
117
+ }
118
+
119
+ _authPassed = false;
120
+ return false;
121
+ }
122
+
123
+ // ── Public API (compatible with the original ws.js/app.js usage) ───────
124
+ function check() {
125
+ if (!_authCheckPromise) _authCheckPromise = _doCheck();
126
+ return _authCheckPromise;
127
+ }
128
+
129
+ return {
130
+ check,
131
+
132
+ // Returns an Authorization header object, or {} if no key present.
133
+ getHeaders() {
134
+ const k = _getStoredKey();
135
+ return k ? { Authorization: `Bearer ${k}` } : {};
136
+ },
137
+
138
+ // Returns the raw key (or null). Used by ws.js for WebSocket URLs.
139
+ getKey: _getStoredKey,
140
+
141
+ // Clears auth state so check() will re-probe on next call.
142
+ reset() {
143
+ _authCheckPromise = null;
144
+ _authPassed = false;
145
+ },
146
+
147
+ // Read-only getter: `Auth.passed` (not a function call).
148
+ get passed() { return _authPassed; },
149
+ };
150
+ })();