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.
- checksums.yaml +7 -0
- data/.clacky/skills/commit/SKILL.md +423 -0
- data/.clacky/skills/gem-release/SKILL.md +199 -0
- data/.clacky/skills/gem-release/scripts/release.sh +304 -0
- data/.clacky/skills/oss-upload/SKILL.md +47 -0
- data/.octorules +106 -0
- data/.rspec +3 -0
- data/.rubocop.yml +8 -0
- data/CHANGELOG.md +76 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/CONTRIBUTING.md +92 -0
- data/Dockerfile +28 -0
- data/LICENSE.txt +22 -0
- data/POSITIONING.md +46 -0
- data/README.md +134 -0
- data/README_CN.md +134 -0
- data/Rakefile +34 -0
- data/benchmark/fixtures/sample_project/Gemfile +3 -0
- data/benchmark/fixtures/sample_project/lib/api_handler.rb +32 -0
- data/benchmark/fixtures/sample_project/lib/order_calculator.rb +23 -0
- data/benchmark/fixtures/sample_project/lib/user_renderer.rb +20 -0
- data/benchmark/fixtures/sample_project/spec/order_calculator_spec.rb +20 -0
- data/benchmark/results/EVALUATION_REPORT.md +165 -0
- data/benchmark/results/baseline_20260511_174424.json +128 -0
- data/benchmark/results/report_20260511_175256.json +271 -0
- data/benchmark/results/report_20260511_175444.json +271 -0
- data/benchmark/results/treatment_20260511_175103.json +130 -0
- data/benchmark/runner.rb +441 -0
- data/bin/octo +7 -0
- data/docs/agent-first-ui-design.md +77 -0
- data/docs/billing-system.md +318 -0
- data/docs/channel-architecture.md +235 -0
- data/docs/engineering-article.md +343 -0
- data/docs/session-skill-invocation.md +69 -0
- data/docs/time_machine_design.md +247 -0
- data/docs/ui2-architecture.md +124 -0
- data/homebrew/README.md +96 -0
- data/homebrew/openocto.rb +24 -0
- data/lib/octo/agent/hook_manager.rb +61 -0
- data/lib/octo/agent/llm_caller.rb +800 -0
- data/lib/octo/agent/memory_updater.rb +246 -0
- data/lib/octo/agent/message_compressor.rb +225 -0
- data/lib/octo/agent/message_compressor_helper.rb +869 -0
- data/lib/octo/agent/next_message_suggester.rb +215 -0
- data/lib/octo/agent/session_serializer.rb +685 -0
- data/lib/octo/agent/skill_auto_creator.rb +114 -0
- data/lib/octo/agent/skill_evolution.rb +61 -0
- data/lib/octo/agent/skill_manager.rb +466 -0
- data/lib/octo/agent/skill_reflector.rb +89 -0
- data/lib/octo/agent/system_prompt_builder.rb +101 -0
- data/lib/octo/agent/time_machine.rb +214 -0
- data/lib/octo/agent/tool_executor.rb +454 -0
- data/lib/octo/agent/tool_registry.rb +150 -0
- data/lib/octo/agent.rb +2180 -0
- data/lib/octo/agent_config.rb +989 -0
- data/lib/octo/agent_profile.rb +112 -0
- data/lib/octo/anthropic_stream_aggregator.rb +137 -0
- data/lib/octo/background_task_registry.rb +324 -0
- data/lib/octo/banner.rb +34 -0
- data/lib/octo/bedrock_stream_aggregator.rb +137 -0
- data/lib/octo/block_font.rb +331 -0
- data/lib/octo/cli.rb +968 -0
- data/lib/octo/client.rb +623 -0
- data/lib/octo/default_agents/SOUL.md +3 -0
- data/lib/octo/default_agents/USER.md +1 -0
- data/lib/octo/default_agents/base_prompt.md +66 -0
- data/lib/octo/default_agents/coding/profile.yml +2 -0
- data/lib/octo/default_agents/coding/system_prompt.md +67 -0
- data/lib/octo/default_agents/general/profile.yml +2 -0
- data/lib/octo/default_agents/general/system_prompt.md +16 -0
- data/lib/octo/default_parsers/doc_parser.rb +69 -0
- data/lib/octo/default_parsers/docx_parser.rb +188 -0
- data/lib/octo/default_parsers/pdf_parser.rb +120 -0
- data/lib/octo/default_parsers/pdf_parser_ocr.py +103 -0
- data/lib/octo/default_parsers/pdf_parser_plumber.py +62 -0
- data/lib/octo/default_parsers/pptx_parser.rb +140 -0
- data/lib/octo/default_parsers/xlsx_parser.rb +121 -0
- data/lib/octo/default_skills/browser-setup/SKILL.md +426 -0
- data/lib/octo/default_skills/channel-manager/SKILL.md +623 -0
- data/lib/octo/default_skills/channel-manager/dingtalk_setup.rb +191 -0
- data/lib/octo/default_skills/channel-manager/discord_setup.rb +199 -0
- data/lib/octo/default_skills/channel-manager/feishu_setup.rb +574 -0
- data/lib/octo/default_skills/channel-manager/import_lark_skills.rb +97 -0
- data/lib/octo/default_skills/channel-manager/install_feishu_skills.rb +105 -0
- data/lib/octo/default_skills/channel-manager/weixin_setup.rb +274 -0
- data/lib/octo/default_skills/code-explorer/SKILL.md +36 -0
- data/lib/octo/default_skills/cron-task-creator/SKILL.md +257 -0
- data/lib/octo/default_skills/cron-task-creator/evals/evals.json +38 -0
- data/lib/octo/default_skills/onboard/SKILL.md +578 -0
- data/lib/octo/default_skills/onboard/scripts/import_external_skills.rb +413 -0
- data/lib/octo/default_skills/onboard/scripts/install_builtin_skills.rb +97 -0
- data/lib/octo/default_skills/persist-memory/SKILL.md +59 -0
- data/lib/octo/default_skills/personal-website/SKILL.md +113 -0
- data/lib/octo/default_skills/personal-website/publish.rb +235 -0
- data/lib/octo/default_skills/product-help/SKILL.md +123 -0
- data/lib/octo/default_skills/product-help/docs/agent-config.md +74 -0
- data/lib/octo/default_skills/product-help/docs/best-practices.md +49 -0
- data/lib/octo/default_skills/product-help/docs/browser-tool.md +53 -0
- data/lib/octo/default_skills/product-help/docs/built-in-skills.md +43 -0
- data/lib/octo/default_skills/product-help/docs/cli-reference.md +82 -0
- data/lib/octo/default_skills/product-help/docs/create-your-first-skill.md +47 -0
- data/lib/octo/default_skills/product-help/docs/faq.md +98 -0
- data/lib/octo/default_skills/product-help/docs/how-to-use-a-skill.md +58 -0
- data/lib/octo/default_skills/product-help/docs/installation.md +59 -0
- data/lib/octo/default_skills/product-help/docs/memory-system.md +61 -0
- data/lib/octo/default_skills/product-help/docs/octorules.md +62 -0
- data/lib/octo/default_skills/product-help/docs/session-management.md +63 -0
- data/lib/octo/default_skills/product-help/docs/skill-basics.md +55 -0
- data/lib/octo/default_skills/product-help/docs/skill-frontmatter.md +61 -0
- data/lib/octo/default_skills/product-help/docs/web-server.md +49 -0
- data/lib/octo/default_skills/product-help/docs/what-is-octo.md +37 -0
- data/lib/octo/default_skills/product-help/docs/windows-installation.md +36 -0
- data/lib/octo/default_skills/product-help/docs/writing-tips.md +53 -0
- data/lib/octo/default_skills/recall-memory/SKILL.md +65 -0
- data/lib/octo/default_skills/skill-add/SKILL.md +59 -0
- data/lib/octo/default_skills/skill-add/scripts/install_from_zip.rb +295 -0
- data/lib/octo/default_skills/skill-creator/SKILL.md +602 -0
- data/lib/octo/default_skills/skill-creator/agents/analyzer.md +274 -0
- data/lib/octo/default_skills/skill-creator/agents/comparator.md +202 -0
- data/lib/octo/default_skills/skill-creator/agents/grader.md +223 -0
- data/lib/octo/default_skills/skill-creator/eval-viewer/generate_review.py +471 -0
- data/lib/octo/default_skills/skill-creator/eval-viewer/viewer.html +1325 -0
- data/lib/octo/default_skills/skill-creator/references/schemas.md +430 -0
- data/lib/octo/default_skills/skill-creator/scripts/__init__.py +0 -0
- data/lib/octo/default_skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
- data/lib/octo/default_skills/skill-creator/scripts/generate_report.py +326 -0
- data/lib/octo/default_skills/skill-creator/scripts/improve_description.py +310 -0
- data/lib/octo/default_skills/skill-creator/scripts/quick_validate.py +103 -0
- data/lib/octo/default_skills/skill-creator/scripts/run_eval.py +317 -0
- data/lib/octo/default_skills/skill-creator/scripts/run_loop.py +331 -0
- data/lib/octo/default_skills/skill-creator/scripts/utils.py +47 -0
- data/lib/octo/default_skills/skill-creator/scripts/validate_skill_frontmatter.rb +143 -0
- data/lib/octo/idle_compression_timer.rb +115 -0
- data/lib/octo/json_ui_controller.rb +204 -0
- data/lib/octo/message_format/anthropic.rb +409 -0
- data/lib/octo/message_format/bedrock.rb +361 -0
- data/lib/octo/message_format/open_ai.rb +222 -0
- data/lib/octo/message_history.rb +373 -0
- data/lib/octo/openai_stream_aggregator.rb +130 -0
- data/lib/octo/plain_ui_controller.rb +166 -0
- data/lib/octo/providers.rb +534 -0
- data/lib/octo/server/browser_manager.rb +397 -0
- data/lib/octo/server/channel/adapters/base.rb +82 -0
- data/lib/octo/server/channel/adapters/dingtalk/adapter.rb +314 -0
- data/lib/octo/server/channel/adapters/dingtalk/api_client.rb +391 -0
- data/lib/octo/server/channel/adapters/dingtalk/stream_client.rb +203 -0
- data/lib/octo/server/channel/adapters/discord/adapter.rb +229 -0
- data/lib/octo/server/channel/adapters/discord/api_client.rb +107 -0
- data/lib/octo/server/channel/adapters/discord/gateway_client.rb +270 -0
- data/lib/octo/server/channel/adapters/feishu/adapter.rb +320 -0
- data/lib/octo/server/channel/adapters/feishu/bot.rb +478 -0
- data/lib/octo/server/channel/adapters/feishu/file_processor.rb +36 -0
- data/lib/octo/server/channel/adapters/feishu/message_parser.rb +129 -0
- data/lib/octo/server/channel/adapters/feishu/ws_client.rb +423 -0
- data/lib/octo/server/channel/adapters/telegram/adapter.rb +375 -0
- data/lib/octo/server/channel/adapters/telegram/api_client.rb +205 -0
- data/lib/octo/server/channel/adapters/wecom/adapter.rb +148 -0
- data/lib/octo/server/channel/adapters/wecom/media_downloader.rb +115 -0
- data/lib/octo/server/channel/adapters/wecom/ws_client.rb +395 -0
- data/lib/octo/server/channel/adapters/weixin/adapter.rb +692 -0
- data/lib/octo/server/channel/adapters/weixin/api_client.rb +402 -0
- data/lib/octo/server/channel/channel_config.rb +178 -0
- data/lib/octo/server/channel/channel_manager.rb +468 -0
- data/lib/octo/server/channel/channel_ui_controller.rb +224 -0
- data/lib/octo/server/channel.rb +33 -0
- data/lib/octo/server/discover.rb +77 -0
- data/lib/octo/server/epipe_safe_io.rb +105 -0
- data/lib/octo/server/http_server.rb +3554 -0
- data/lib/octo/server/scheduler.rb +317 -0
- data/lib/octo/server/server_master.rb +325 -0
- data/lib/octo/server/session_registry.rb +431 -0
- data/lib/octo/server/web_ui_controller.rb +487 -0
- data/lib/octo/session_manager.rb +385 -0
- data/lib/octo/skill.rb +466 -0
- data/lib/octo/skill_loader.rb +328 -0
- data/lib/octo/tools/base.rb +118 -0
- data/lib/octo/tools/browser.rb +625 -0
- data/lib/octo/tools/edit.rb +165 -0
- data/lib/octo/tools/file_reader.rb +549 -0
- data/lib/octo/tools/glob.rb +162 -0
- data/lib/octo/tools/grep.rb +356 -0
- data/lib/octo/tools/invoke_skill.rb +96 -0
- data/lib/octo/tools/list_tasks.rb +54 -0
- data/lib/octo/tools/redo_task.rb +41 -0
- data/lib/octo/tools/request_user_feedback.rb +84 -0
- data/lib/octo/tools/security.rb +333 -0
- data/lib/octo/tools/terminal/output_cleaner.rb +63 -0
- data/lib/octo/tools/terminal/persistent_session.rb +268 -0
- data/lib/octo/tools/terminal/safe_rm.sh +106 -0
- data/lib/octo/tools/terminal/session_manager.rb +213 -0
- data/lib/octo/tools/terminal.rb +1828 -0
- data/lib/octo/tools/todo_manager.rb +374 -0
- data/lib/octo/tools/trash_manager.rb +388 -0
- data/lib/octo/tools/undo_task.rb +35 -0
- data/lib/octo/tools/web_fetch.rb +242 -0
- data/lib/octo/tools/web_search.rb +260 -0
- data/lib/octo/tools/write.rb +77 -0
- data/lib/octo/ui2/block_font.rb +10 -0
- data/lib/octo/ui2/components/base_component.rb +163 -0
- data/lib/octo/ui2/components/command_suggestions.rb +290 -0
- data/lib/octo/ui2/components/common_component.rb +96 -0
- data/lib/octo/ui2/components/inline_input.rb +226 -0
- data/lib/octo/ui2/components/input_area.rb +1338 -0
- data/lib/octo/ui2/components/message_component.rb +99 -0
- data/lib/octo/ui2/components/modal_component.rb +419 -0
- data/lib/octo/ui2/components/todo_area.rb +149 -0
- data/lib/octo/ui2/components/tool_component.rb +107 -0
- data/lib/octo/ui2/components/welcome_banner.rb +139 -0
- data/lib/octo/ui2/layout_manager.rb +807 -0
- data/lib/octo/ui2/line_editor.rb +363 -0
- data/lib/octo/ui2/markdown_renderer.rb +100 -0
- data/lib/octo/ui2/output_buffer.rb +370 -0
- data/lib/octo/ui2/progress_handle.rb +362 -0
- data/lib/octo/ui2/progress_indicator.rb +55 -0
- data/lib/octo/ui2/screen_buffer.rb +273 -0
- data/lib/octo/ui2/terminal_detector.rb +119 -0
- data/lib/octo/ui2/theme_manager.rb +85 -0
- data/lib/octo/ui2/themes/base_theme.rb +105 -0
- data/lib/octo/ui2/themes/hacker_theme.rb +62 -0
- data/lib/octo/ui2/themes/minimal_theme.rb +56 -0
- data/lib/octo/ui2/thinking_verbs.rb +26 -0
- data/lib/octo/ui2/ui_controller.rb +1625 -0
- data/lib/octo/ui2/view_renderer.rb +177 -0
- data/lib/octo/ui2.rb +40 -0
- data/lib/octo/ui_interface.rb +154 -0
- data/lib/octo/utils/arguments_parser.rb +191 -0
- data/lib/octo/utils/browser_detector.rb +195 -0
- data/lib/octo/utils/encoding.rb +92 -0
- data/lib/octo/utils/environment_detector.rb +140 -0
- data/lib/octo/utils/file_ignore_helper.rb +170 -0
- data/lib/octo/utils/file_processor.rb +601 -0
- data/lib/octo/utils/gitignore_parser.rb +154 -0
- data/lib/octo/utils/limit_stack.rb +152 -0
- data/lib/octo/utils/logger.rb +124 -0
- data/lib/octo/utils/login_shell.rb +72 -0
- data/lib/octo/utils/model_pricing.rb +646 -0
- data/lib/octo/utils/parser_manager.rb +165 -0
- data/lib/octo/utils/path_helper.rb +15 -0
- data/lib/octo/utils/scripts_manager.rb +59 -0
- data/lib/octo/utils/string_matcher.rb +158 -0
- data/lib/octo/utils/trash_directory.rb +112 -0
- data/lib/octo/utils/workspace_rules.rb +46 -0
- data/lib/octo/version.rb +5 -0
- data/lib/octo/web/app.css +7141 -0
- data/lib/octo/web/app.js +543 -0
- data/lib/octo/web/apple-touch-icon.png +0 -0
- data/lib/octo/web/auth.js +150 -0
- data/lib/octo/web/channels.js +276 -0
- data/lib/octo/web/datepicker.js +205 -0
- data/lib/octo/web/favicon.png +0 -0
- data/lib/octo/web/i18n.js +1073 -0
- data/lib/octo/web/icon-512.png +0 -0
- data/lib/octo/web/icon-dark.svg +25 -0
- data/lib/octo/web/icon.svg +29 -0
- data/lib/octo/web/index.html +871 -0
- data/lib/octo/web/marked.min.js +69 -0
- data/lib/octo/web/onboard.js +491 -0
- data/lib/octo/web/profile.js +442 -0
- data/lib/octo/web/sessions.js +4421 -0
- data/lib/octo/web/settings.js +913 -0
- data/lib/octo/web/sidebar.js +32 -0
- data/lib/octo/web/skills.js +885 -0
- data/lib/octo/web/tasks.js +297 -0
- data/lib/octo/web/theme.js +105 -0
- data/lib/octo/web/trash.js +343 -0
- data/lib/octo/web/vendor/hljs/highlight.min.js +1244 -0
- data/lib/octo/web/vendor/hljs/hljs-theme.css +95 -0
- data/lib/octo/web/vendor/katex/auto-render.min.js +1 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_AMS-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Main-Bold.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Main-Italic.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Main-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Math-Italic.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Script-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Size1-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Size2-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Size3-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Size4-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
- data/lib/octo/web/vendor/katex/katex.min.css +1 -0
- data/lib/octo/web/vendor/katex/katex.min.js +1 -0
- data/lib/octo/web/version.js +449 -0
- data/lib/octo/web/weixin-qr.html +209 -0
- data/lib/octo/web/ws-dispatcher.js +357 -0
- data/lib/octo/web/ws.js +128 -0
- data/lib/octo.rb +145 -0
- data/scripts/build/build.sh +329 -0
- data/scripts/build/lib/apt.sh +56 -0
- data/scripts/build/lib/brew.sh +89 -0
- data/scripts/build/lib/colors.sh +17 -0
- data/scripts/build/lib/gem.sh +95 -0
- data/scripts/build/lib/mise.sh +125 -0
- data/scripts/build/lib/network.sh +157 -0
- data/scripts/build/lib/os.sh +57 -0
- data/scripts/build/lib/shell.sh +37 -0
- data/scripts/build/src/install.sh.cc +174 -0
- data/scripts/build/src/install_browser.sh.cc +101 -0
- data/scripts/build/src/install_full.sh.cc +290 -0
- data/scripts/build/src/install_rails_deps.sh.cc +145 -0
- data/scripts/build/src/install_system_deps.sh.cc +123 -0
- data/scripts/build/src/uninstall.sh.cc +101 -0
- data/scripts/install.ps1 +532 -0
- data/scripts/install.sh +567 -0
- data/scripts/install_browser.sh +479 -0
- data/scripts/install_full.sh +838 -0
- data/scripts/install_rails_deps.sh +746 -0
- data/scripts/install_system_deps.sh +518 -0
- data/scripts/uninstall.sh +287 -0
- data/sig/octo.rbs +4 -0
- metadata +614 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Channel layer — bridges Server Sessions to IM platforms (Feishu, WeCom, etc.)
|
|
4
|
+
#
|
|
5
|
+
# Load order:
|
|
6
|
+
# 1. Adapter base & registry
|
|
7
|
+
# 2. Platform adapters (each self-registers via Adapters.register)
|
|
8
|
+
# 3. ChannelConfig (IM platform credentials)
|
|
9
|
+
# 4. ChannelUIController (UIInterface impl that sends to IM)
|
|
10
|
+
# 5. ChannelManager (orchestrates adapters + sessions, in-memory session map)
|
|
11
|
+
#
|
|
12
|
+
# Usage in HttpServer:
|
|
13
|
+
# require_relative "channel"
|
|
14
|
+
# @channel_manager = Octo::Channel::ChannelManager.new(
|
|
15
|
+
# session_registry: @registry,
|
|
16
|
+
# session_builder: method(:build_session),
|
|
17
|
+
# channel_config: Octo::ChannelConfig.load
|
|
18
|
+
# )
|
|
19
|
+
# @channel_manager.start
|
|
20
|
+
|
|
21
|
+
require_relative "channel/adapters/base"
|
|
22
|
+
|
|
23
|
+
# Load platform adapters (each registers itself)
|
|
24
|
+
require_relative "channel/adapters/feishu/adapter"
|
|
25
|
+
require_relative "channel/adapters/wecom/adapter"
|
|
26
|
+
require_relative "channel/adapters/weixin/adapter"
|
|
27
|
+
require_relative "channel/adapters/discord/adapter"
|
|
28
|
+
require_relative "channel/adapters/telegram/adapter"
|
|
29
|
+
require_relative "channel/adapters/dingtalk/adapter"
|
|
30
|
+
|
|
31
|
+
require_relative "channel/channel_config"
|
|
32
|
+
require_relative "channel/channel_ui_controller"
|
|
33
|
+
require_relative "channel/channel_manager"
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "tmpdir"
|
|
4
|
+
require "socket"
|
|
5
|
+
|
|
6
|
+
module Octo
|
|
7
|
+
module Server
|
|
8
|
+
# Discover locally-running Octo server(s) by scanning PID files
|
|
9
|
+
# written by Master at /tmp/octo-master-<port>.pid.
|
|
10
|
+
#
|
|
11
|
+
# Used by the CLI (bare `octo agent` mode) to auto-detect a sibling
|
|
12
|
+
# server process, so skills that call back into the server (channels,
|
|
13
|
+
# browser, scheduler, etc.) can work without the user manually setting
|
|
14
|
+
# OCTO_SERVER_HOST / OCTO_SERVER_PORT.
|
|
15
|
+
#
|
|
16
|
+
# Fast and side-effect free: only reads files and sends signal 0.
|
|
17
|
+
# Does NOT probe the TCP port (avoids false positives from stale files
|
|
18
|
+
# but also avoids noisy connection attempts).
|
|
19
|
+
module Discover
|
|
20
|
+
PID_FILE_GLOB = File.join(Dir.tmpdir, "octo-master-*.pid").freeze
|
|
21
|
+
PID_FILE_REGEX = /octo-master-(\d+)\.pid\z/.freeze
|
|
22
|
+
|
|
23
|
+
module_function
|
|
24
|
+
|
|
25
|
+
# Find the first live Octo server on this machine.
|
|
26
|
+
#
|
|
27
|
+
# @return [Hash, nil] { host: "127.0.0.1", port: Integer, pid: Integer } or nil
|
|
28
|
+
def find_local
|
|
29
|
+
find_all_local.first
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Find all live Octo servers on this machine.
|
|
33
|
+
#
|
|
34
|
+
# A PID file is considered "live" when:
|
|
35
|
+
# 1. The filename matches octo-master-<port>.pid
|
|
36
|
+
# 2. Its contents parse as a positive integer
|
|
37
|
+
# 3. Process.kill(0, pid) confirms the PID is alive
|
|
38
|
+
#
|
|
39
|
+
# Stale PID files (process gone) are silently ignored. We do NOT
|
|
40
|
+
# delete them here — that's the owning server's responsibility.
|
|
41
|
+
#
|
|
42
|
+
# @return [Array<Hash>] sorted by port ascending
|
|
43
|
+
def find_all_local
|
|
44
|
+
Dir.glob(PID_FILE_GLOB).filter_map do |path|
|
|
45
|
+
m = path.match(PID_FILE_REGEX)
|
|
46
|
+
next nil unless m
|
|
47
|
+
|
|
48
|
+
port = m[1].to_i
|
|
49
|
+
next nil if port <= 0
|
|
50
|
+
|
|
51
|
+
pid_str = File.read(path).strip rescue nil
|
|
52
|
+
next nil if pid_str.nil? || pid_str.empty?
|
|
53
|
+
|
|
54
|
+
pid = pid_str.to_i
|
|
55
|
+
next nil if pid <= 0
|
|
56
|
+
|
|
57
|
+
next nil unless process_alive?(pid)
|
|
58
|
+
|
|
59
|
+
{ host: "127.0.0.1", port: port, pid: pid }
|
|
60
|
+
end.sort_by { |e| e[:port] }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# @param pid [Integer]
|
|
64
|
+
# @return [Boolean]
|
|
65
|
+
def process_alive?(pid)
|
|
66
|
+
Process.kill(0, pid)
|
|
67
|
+
true
|
|
68
|
+
rescue Errno::ESRCH, Errno::EPERM
|
|
69
|
+
# ESRCH — no such process; EPERM — process exists but owned by someone else
|
|
70
|
+
# (still technically alive, but we can't safely assume it's "our" server)
|
|
71
|
+
false
|
|
72
|
+
rescue StandardError
|
|
73
|
+
false
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "delegate"
|
|
4
|
+
|
|
5
|
+
module Octo
|
|
6
|
+
module Server
|
|
7
|
+
# EPIPESafeIO — wraps an IO ($stdout / $stderr) so that writes never raise
|
|
8
|
+
# Errno::EPIPE / IOError to the calling code.
|
|
9
|
+
#
|
|
10
|
+
# Why this exists:
|
|
11
|
+
# The server worker process inherits fd 0/1/2 from the Master. If the
|
|
12
|
+
# Master itself was launched in a way where its stdout/stderr eventually
|
|
13
|
+
# becomes a broken pipe (e.g. launched by an installer that exits, or by
|
|
14
|
+
# a GUI/IDE process that closes its end), the worker's first `puts` after
|
|
15
|
+
# that pipe breaks raises Errno::EPIPE. Unhandled, this kills the worker
|
|
16
|
+
# — taking all in-memory sessions, agent loops, and SSE connections down
|
|
17
|
+
# with it, and triggering a crash loop because the new worker inherits
|
|
18
|
+
# the same broken fd.
|
|
19
|
+
#
|
|
20
|
+
# Behavior:
|
|
21
|
+
# - Healthy state: delegates every method to the underlying IO. Users
|
|
22
|
+
# see normal terminal output (banner, request logs, etc.).
|
|
23
|
+
# - First broken-pipe failure: catches Errno::EPIPE / IOError, swaps
|
|
24
|
+
# the underlying IO to /dev/null permanently, and returns silently.
|
|
25
|
+
# Subsequent writes succeed (into /dev/null) so the worker stays alive.
|
|
26
|
+
# - Session state, agent loops, SSE connections all preserved.
|
|
27
|
+
#
|
|
28
|
+
# Scope:
|
|
29
|
+
# We only wrap $stdout / $stderr (the global variables that Kernel#puts,
|
|
30
|
+
# Kernel#print, Kernel#warn, etc. use under the hood). We do NOT touch
|
|
31
|
+
# the STDOUT / STDERR constants — a codebase audit confirmed nothing in
|
|
32
|
+
# Octo writes to those constants directly (only `STDOUT.flush` which
|
|
33
|
+
# cannot raise EPIPE).
|
|
34
|
+
class EPIPESafeIO < SimpleDelegator
|
|
35
|
+
# Methods that perform writes and may raise Errno::EPIPE.
|
|
36
|
+
# We override each one to rescue and degrade gracefully.
|
|
37
|
+
WRITE_METHODS = %i[write write_nonblock syswrite puts print printf << putc].freeze
|
|
38
|
+
|
|
39
|
+
WRITE_METHODS.each do |m|
|
|
40
|
+
define_method(m) do |*args, **kwargs, &blk|
|
|
41
|
+
if kwargs.empty?
|
|
42
|
+
__getobj__.public_send(m, *args, &blk)
|
|
43
|
+
else
|
|
44
|
+
__getobj__.public_send(m, *args, **kwargs, &blk)
|
|
45
|
+
end
|
|
46
|
+
rescue Errno::EPIPE, IOError => e
|
|
47
|
+
fall_back_to_null!(e)
|
|
48
|
+
# Retry the write into /dev/null so semantics (return value type) stay
|
|
49
|
+
# close to what the caller expects. If even this fails, swallow it —
|
|
50
|
+
# we must not raise from inside a write to $stdout/$stderr.
|
|
51
|
+
begin
|
|
52
|
+
if kwargs.empty?
|
|
53
|
+
__getobj__.public_send(m, *args, &blk)
|
|
54
|
+
else
|
|
55
|
+
__getobj__.public_send(m, *args, **kwargs, &blk)
|
|
56
|
+
end
|
|
57
|
+
rescue StandardError
|
|
58
|
+
nil
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Some callers do `$stdout.flush`. Make it safe too.
|
|
64
|
+
def flush
|
|
65
|
+
__getobj__.flush
|
|
66
|
+
rescue Errno::EPIPE, IOError => e
|
|
67
|
+
fall_back_to_null!(e)
|
|
68
|
+
nil
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Whether this wrapper has already fallen back to /dev/null.
|
|
72
|
+
# Useful for tests and diagnostics.
|
|
73
|
+
def fell_back?
|
|
74
|
+
@fell_back == true
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
private def fall_back_to_null!(error)
|
|
78
|
+
return if @fell_back
|
|
79
|
+
|
|
80
|
+
@fell_back = true
|
|
81
|
+
begin
|
|
82
|
+
# Best-effort: try to log once via Octo::Logger if available.
|
|
83
|
+
# Wrapped in rescue because Logger itself might be mid-init.
|
|
84
|
+
if defined?(Octo::Logger)
|
|
85
|
+
Octo::Logger.warn(
|
|
86
|
+
"[EPIPESafeIO] Underlying IO broken (#{error.class}: #{error.message}); " \
|
|
87
|
+
"falling back to /dev/null. Worker stays alive."
|
|
88
|
+
)
|
|
89
|
+
end
|
|
90
|
+
rescue StandardError
|
|
91
|
+
# ignore
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
begin
|
|
95
|
+
null = File.open(File::NULL, "w")
|
|
96
|
+
null.sync = true
|
|
97
|
+
__setobj__(null)
|
|
98
|
+
rescue StandardError
|
|
99
|
+
# If even opening /dev/null fails, leave the original object — at
|
|
100
|
+
# worst the next write raises again and we rescue again.
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|