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,318 @@
|
|
|
1
|
+
# Billing System
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The Billing System provides persistent tracking of API usage and costs across all
|
|
6
|
+
sessions. It records every LLM API call with token counts and calculated costs,
|
|
7
|
+
storing them in monthly JSONL files for easy querying and analysis.
|
|
8
|
+
|
|
9
|
+
## Design Principles
|
|
10
|
+
|
|
11
|
+
- **Non-blocking** — Billing persistence is fire-and-forget; failures never interrupt agent flow
|
|
12
|
+
- **Minimal footprint** — JSONL format, one file per month, no database dependency
|
|
13
|
+
- **Privacy-first** — Data stored locally in `~/.octo/billing/`, never uploaded
|
|
14
|
+
- **Accurate costing** — Uses the same `ModelPricing` module as real-time display
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Architecture
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
22
|
+
│ Agent │
|
|
23
|
+
│ CostTracker module │
|
|
24
|
+
│ └── track_cost() │
|
|
25
|
+
│ ├── Calculate cost (ModelPricing) │
|
|
26
|
+
│ ├── Update UI (real-time) │
|
|
27
|
+
│ └── persist_billing_record() ──────┐ │
|
|
28
|
+
└─────────────────────────────────────────────┼───────────────────┘
|
|
29
|
+
│
|
|
30
|
+
▼
|
|
31
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
32
|
+
│ Billing Module │
|
|
33
|
+
│ lib/octo/billing/ │
|
|
34
|
+
│ ├── billing_record.rb (data structure) │
|
|
35
|
+
│ └── billing_store.rb (JSONL persistence) │
|
|
36
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
37
|
+
│
|
|
38
|
+
▼
|
|
39
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
40
|
+
│ Storage │
|
|
41
|
+
│ ~/.octo/billing/ │
|
|
42
|
+
│ ├── 2026-05.jsonl │
|
|
43
|
+
│ ├── 2026-04.jsonl │
|
|
44
|
+
│ └── ... │
|
|
45
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Components
|
|
51
|
+
|
|
52
|
+
### BillingRecord (`lib/octo/billing/billing_record.rb`)
|
|
53
|
+
|
|
54
|
+
A Struct representing a single API call:
|
|
55
|
+
|
|
56
|
+
| Field | Type | Description |
|
|
57
|
+
|-------|------|-------------|
|
|
58
|
+
| `id` | String | UUID, auto-generated |
|
|
59
|
+
| `session_id` | String | Associated session |
|
|
60
|
+
| `timestamp` | Time | When the call was made |
|
|
61
|
+
| `model` | String | Model name (e.g., "claude-sonnet-4.5") |
|
|
62
|
+
| `prompt_tokens` | Integer | Input tokens |
|
|
63
|
+
| `completion_tokens` | Integer | Output tokens |
|
|
64
|
+
| `cache_read_tokens` | Integer | Tokens read from cache |
|
|
65
|
+
| `cache_write_tokens` | Integer | Tokens written to cache |
|
|
66
|
+
| `cost_usd` | Float | Calculated cost in USD |
|
|
67
|
+
| `cost_source` | Symbol | `:api`, `:price`, or `:estimated` |
|
|
68
|
+
|
|
69
|
+
### BillingStore (`lib/octo/billing/billing_store.rb`)
|
|
70
|
+
|
|
71
|
+
Handles persistence and querying:
|
|
72
|
+
|
|
73
|
+
```ruby
|
|
74
|
+
store = Octo::Billing::BillingStore.new
|
|
75
|
+
|
|
76
|
+
# Append a record
|
|
77
|
+
store.append(record)
|
|
78
|
+
|
|
79
|
+
# Query with filters
|
|
80
|
+
records = store.query(from: 1.week.ago, model: "claude-sonnet-4.5", limit: 100)
|
|
81
|
+
|
|
82
|
+
# Get summary statistics
|
|
83
|
+
summary = store.summary(period: :month)
|
|
84
|
+
# => { total_cost: 12.34, total_tokens: 500000, by_model: {...}, ... }
|
|
85
|
+
|
|
86
|
+
# Daily breakdown for charts
|
|
87
|
+
daily = store.daily_breakdown(days: 30)
|
|
88
|
+
# => [{ date: "2026-05-01", cost: 1.23, tokens: 50000, requests: 42 }, ...]
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Storage Format
|
|
94
|
+
|
|
95
|
+
Records are stored as JSON Lines (one JSON object per line):
|
|
96
|
+
|
|
97
|
+
```jsonl
|
|
98
|
+
{"id":"abc123","session_id":"def456","timestamp":"2026-05-22T15:30:00+08:00","model":"claude-sonnet-4.5","prompt_tokens":1500,"completion_tokens":500,"cache_read_tokens":1000,"cache_write_tokens":0,"cost_usd":0.0045,"cost_source":"price"}
|
|
99
|
+
{"id":"abc124","session_id":"def456","timestamp":"2026-05-22T15:31:00+08:00","model":"claude-sonnet-4.5","prompt_tokens":2000,"completion_tokens":800,"cache_read_tokens":1500,"cache_write_tokens":0,"cost_usd":0.0052,"cost_source":"price"}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**Why JSONL?**
|
|
103
|
+
- Append-only writes (no file locking needed)
|
|
104
|
+
- Easy to parse line-by-line (memory efficient)
|
|
105
|
+
- Human-readable for debugging
|
|
106
|
+
- Simple monthly rotation
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## API Endpoints
|
|
111
|
+
|
|
112
|
+
### GET /api/billing/summary
|
|
113
|
+
|
|
114
|
+
Returns aggregated statistics for a time period.
|
|
115
|
+
|
|
116
|
+
**Query Parameters:**
|
|
117
|
+
- `period` — `day`, `week`, `month`, `year`, or `all` (default: `month`)
|
|
118
|
+
|
|
119
|
+
**Response:**
|
|
120
|
+
```json
|
|
121
|
+
{
|
|
122
|
+
"period": "month",
|
|
123
|
+
"from": "2026-05-01T00:00:00+08:00",
|
|
124
|
+
"to": "2026-05-22T15:30:00+08:00",
|
|
125
|
+
"total_cost": 12.3456,
|
|
126
|
+
"total_tokens": 500000,
|
|
127
|
+
"prompt_tokens": 350000,
|
|
128
|
+
"completion_tokens": 150000,
|
|
129
|
+
"cache_read_tokens": 200000,
|
|
130
|
+
"cache_write_tokens": 50000,
|
|
131
|
+
"by_model": {
|
|
132
|
+
"claude-sonnet-4.5": { "cost": 10.00, "requests": 100 },
|
|
133
|
+
"deepseek-v4-flash": { "cost": 2.34, "requests": 50 }
|
|
134
|
+
},
|
|
135
|
+
"by_day": {
|
|
136
|
+
"2026-05-22": 1.23,
|
|
137
|
+
"2026-05-21": 2.34
|
|
138
|
+
},
|
|
139
|
+
"record_count": 150
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### GET /api/billing/daily
|
|
144
|
+
|
|
145
|
+
Returns daily cost breakdown for charting.
|
|
146
|
+
|
|
147
|
+
**Query Parameters:**
|
|
148
|
+
- `days` — Number of days (default: 30, max: 90)
|
|
149
|
+
|
|
150
|
+
**Response:**
|
|
151
|
+
```json
|
|
152
|
+
{
|
|
153
|
+
"days": [
|
|
154
|
+
{ "date": "2026-05-22", "cost": 1.2345, "tokens": 50000, "requests": 42 },
|
|
155
|
+
{ "date": "2026-05-21", "cost": 2.3456, "tokens": 80000, "requests": 65 }
|
|
156
|
+
]
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### GET /api/billing/records
|
|
161
|
+
|
|
162
|
+
Returns raw billing records.
|
|
163
|
+
|
|
164
|
+
**Query Parameters:**
|
|
165
|
+
- `limit` — Max records (default: 100, max: 500)
|
|
166
|
+
- `model` — Filter by model name
|
|
167
|
+
- `session_id` — Filter by session ID
|
|
168
|
+
|
|
169
|
+
**Response:**
|
|
170
|
+
```json
|
|
171
|
+
{
|
|
172
|
+
"records": [
|
|
173
|
+
{ "id": "...", "timestamp": "...", "model": "...", "cost_usd": 0.01, ... }
|
|
174
|
+
],
|
|
175
|
+
"count": 100
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## CLI Command
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
# Show current month's billing
|
|
185
|
+
octo billing
|
|
186
|
+
|
|
187
|
+
# Show specific period
|
|
188
|
+
octo billing --period week
|
|
189
|
+
octo billing --period day
|
|
190
|
+
octo billing --period all
|
|
191
|
+
|
|
192
|
+
# Output as JSON (for scripting)
|
|
193
|
+
octo billing --json
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
**Sample Output:**
|
|
197
|
+
```
|
|
198
|
+
📊 Billing Summary (month)
|
|
199
|
+
──────────────────────────────────────────────────
|
|
200
|
+
|
|
201
|
+
💰 Total Cost: $12.3456
|
|
202
|
+
📝 Total Tokens: 500,000
|
|
203
|
+
📥 Prompt Tokens: 350,000
|
|
204
|
+
📤 Completion: 150,000
|
|
205
|
+
🗄️ Cache Read: 200,000
|
|
206
|
+
📝 Cache Write: 50,000
|
|
207
|
+
🔢 API Requests: 150
|
|
208
|
+
|
|
209
|
+
📈 By Model:
|
|
210
|
+
──────────────────────────────────────────────────
|
|
211
|
+
claude-sonnet-4.5
|
|
212
|
+
Cost: $10.0000 | Requests: 100
|
|
213
|
+
deepseek-v4-flash
|
|
214
|
+
Cost: $2.3456 | Requests: 50
|
|
215
|
+
|
|
216
|
+
📅 Recent Daily Usage:
|
|
217
|
+
──────────────────────────────────────────────────
|
|
218
|
+
2026-05-22 $1.2345 ████████████
|
|
219
|
+
2026-05-21 $2.3456 ████████████████████████
|
|
220
|
+
|
|
221
|
+
──────────────────────────────────────────────────
|
|
222
|
+
Data stored in: ~/.octo/billing/
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## Web UI
|
|
228
|
+
|
|
229
|
+
The Billing panel is accessible from the sidebar under "My Data":
|
|
230
|
+
|
|
231
|
+
- **Summary cards** — Total cost, tokens, API requests
|
|
232
|
+
- **Token breakdown** — Prompt, completion, cache read/write
|
|
233
|
+
- **By Model table** — Cost and request count per model
|
|
234
|
+
- **Daily chart** — Visual bar chart of recent usage
|
|
235
|
+
- **Period selector** — Filter by day/week/month/year/all
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## Currency Settings
|
|
240
|
+
|
|
241
|
+
The Web UI supports multiple currencies for cost display:
|
|
242
|
+
|
|
243
|
+
| Currency | Symbol | Exchange Rate |
|
|
244
|
+
|----------|--------|---------------|
|
|
245
|
+
| USD | $ | 1.0 (base) |
|
|
246
|
+
| CNY | ¥ | 6.7944 |
|
|
247
|
+
|
|
248
|
+
### Configuration
|
|
249
|
+
|
|
250
|
+
1. Go to **Settings** page
|
|
251
|
+
2. Find the **Currency** section
|
|
252
|
+
3. Select `$ USD` or `¥ CNY`
|
|
253
|
+
|
|
254
|
+
### Scope
|
|
255
|
+
|
|
256
|
+
Currency settings apply to:
|
|
257
|
+
- Billing panel (total cost, model costs, daily chart)
|
|
258
|
+
- Session info bar (top cost display)
|
|
259
|
+
- Token usage lines (per-API-call cost)
|
|
260
|
+
- Task completion messages
|
|
261
|
+
|
|
262
|
+
**Note:** CLI always displays costs in USD (API's native currency).
|
|
263
|
+
|
|
264
|
+
### Implementation
|
|
265
|
+
|
|
266
|
+
Currency preference is stored in browser `localStorage` under key `octo-currency`.
|
|
267
|
+
|
|
268
|
+
```javascript
|
|
269
|
+
// Access currency utilities from Billing module
|
|
270
|
+
Billing.getCurrency() // "USD" or "CNY"
|
|
271
|
+
Billing.getCurrencySymbol() // "$" or "¥"
|
|
272
|
+
Billing.convertCost(usd) // Convert USD to selected currency
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
## Integration with CostTracker
|
|
278
|
+
|
|
279
|
+
The billing system hooks into `Agent::CostTracker#track_cost`:
|
|
280
|
+
|
|
281
|
+
```ruby
|
|
282
|
+
def track_cost(usage, raw_api_usage: nil)
|
|
283
|
+
# ... existing cost calculation ...
|
|
284
|
+
|
|
285
|
+
# Persist billing record (skip for subagents to avoid double-counting)
|
|
286
|
+
unless @is_subagent
|
|
287
|
+
persist_billing_record(usage, iteration_cost)
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
token_data
|
|
291
|
+
end
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
**Key behaviors:**
|
|
295
|
+
- Subagent costs are NOT recorded separately (parent agent merges them)
|
|
296
|
+
- Unknown model costs (nil) are skipped
|
|
297
|
+
- Persistence failures are logged but never raise
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
## Data Retention
|
|
302
|
+
|
|
303
|
+
- Records are stored indefinitely by default
|
|
304
|
+
- Monthly files can be manually deleted from `~/.octo/billing/`
|
|
305
|
+
- Future: `BillingStore#cleanup(before: 1.year.ago)` for automated retention
|
|
306
|
+
|
|
307
|
+
---
|
|
308
|
+
|
|
309
|
+
## Future Enhancements
|
|
310
|
+
|
|
311
|
+
- [ ] Export to CSV/JSON
|
|
312
|
+
- [ ] Budget alerts (daily/monthly limits)
|
|
313
|
+
- [ ] Cost comparison across models
|
|
314
|
+
- [ ] Session-level cost breakdown in UI
|
|
315
|
+
- [x] i18n support for billing labels (English/Chinese)
|
|
316
|
+
- [x] Currency settings (USD/CNY)
|
|
317
|
+
- [ ] Dynamic exchange rate updates
|
|
318
|
+
- [ ] More currency options (EUR, JPY, etc.)
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# Channel Architecture
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
Channel is a feature that bridges Octo's Server Sessions to IM platforms
|
|
6
|
+
(Feishu, WeCom, DingTalk, etc.). It reuses the existing Agent + SessionRegistry
|
|
7
|
+
infrastructure — the Agent knows nothing about IM; the Channel layer is purely
|
|
8
|
+
a transport adapter.
|
|
9
|
+
|
|
10
|
+
## Design Principles
|
|
11
|
+
|
|
12
|
+
- **Zero Agent intrusion** — Agent only speaks `UIInterface`; swap the controller, get IM output
|
|
13
|
+
- **Reuse SessionRegistry** — IM chats resolve to the same `SessionRegistry` sessions as Web UI
|
|
14
|
+
- **WebSocket long connection** — No public domain required; adapters hold a persistent WSS connection to the IM platform
|
|
15
|
+
- **One platform = 2 threads** — read loop thread + ping/heartbeat thread (constant, small footprint)
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Layer Diagram
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
IM Platforms (Feishu / WeCom / DingTalk)
|
|
23
|
+
│ WebSocket long connection (wss://)
|
|
24
|
+
▼
|
|
25
|
+
┌─────────────────────────────────────┐
|
|
26
|
+
│ Channel Adapter Layer │
|
|
27
|
+
│ Feishu::Adapter │
|
|
28
|
+
│ ├── WSClient (read loop + ping) │
|
|
29
|
+
│ ├── Bot (send API) │
|
|
30
|
+
│ └── MessageParser │
|
|
31
|
+
│ Wecom::Adapter │
|
|
32
|
+
│ └── WSClient (read loop + ping) │
|
|
33
|
+
│ (future) Dingtalk::Adapter │
|
|
34
|
+
└──────────────┬──────────────────────┘
|
|
35
|
+
│ standardized event Hash
|
|
36
|
+
▼
|
|
37
|
+
┌─────────────────────────────────────┐
|
|
38
|
+
│ ChannelManager │
|
|
39
|
+
│ • Owns adapter threads │
|
|
40
|
+
│ • Routes inbound event → │
|
|
41
|
+
│ ChannelBinding → session_id │
|
|
42
|
+
│ • Calls agent.run in Thread.new │
|
|
43
|
+
└──────────────┬──────────────────────┘
|
|
44
|
+
│
|
|
45
|
+
┌───────┴────────┐
|
|
46
|
+
▼ ▼
|
|
47
|
+
SessionRegistry ChannelUIController
|
|
48
|
+
(existing) (implements UIInterface)
|
|
49
|
+
│ │
|
|
50
|
+
▼ ▼
|
|
51
|
+
Agent IM Platform reply
|
|
52
|
+
(unchanged) via adapter.send_text
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## File Structure
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
lib/octo/channel/
|
|
61
|
+
├── adapters/
|
|
62
|
+
│ ├── base.rb # Adapter abstract base + registry
|
|
63
|
+
│ ├── feishu/
|
|
64
|
+
│ │ ├── adapter.rb # Feishu::Adapter < Base
|
|
65
|
+
│ │ ├── bot.rb # HTTP send API (token cache, Markdown/card)
|
|
66
|
+
│ │ ├── message_parser.rb # Raw WS event → standardized Hash
|
|
67
|
+
│ │ └── ws_client.rb # Feishu protobuf WS long connection
|
|
68
|
+
│ └── wecom/
|
|
69
|
+
│ ├── adapter.rb # Wecom::Adapter < Base
|
|
70
|
+
│ └── ws_client.rb # WeCom JSON WS long connection
|
|
71
|
+
├── channel_message.rb # Struct: standardized inbound message
|
|
72
|
+
├── channel_binding.rb # (platform, user_id) → session_id mapping
|
|
73
|
+
├── channel_ui_controller.rb # UIInterface impl — pushes events to IM
|
|
74
|
+
└── channel_manager.rb # Lifecycle: start/stop adapters, route messages
|
|
75
|
+
lib/octo/channel.rb # Top-level require entry point
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Standardized Inbound Event
|
|
81
|
+
|
|
82
|
+
All adapters yield the same Hash shape to `ChannelManager`:
|
|
83
|
+
|
|
84
|
+
```ruby
|
|
85
|
+
{
|
|
86
|
+
platform: :feishu, # Symbol
|
|
87
|
+
chat_id: "oc_xxx", # String — IM chat/group identifier
|
|
88
|
+
user_id: "ou_xxx", # String — IM user identifier
|
|
89
|
+
text: "deploy now", # String — cleaned user text
|
|
90
|
+
message_id: "om_xxx", # String — for threading / update
|
|
91
|
+
timestamp: Time, # Time object
|
|
92
|
+
chat_type: :direct | :group, # Symbol
|
|
93
|
+
raw: { ... } # Original platform payload
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Adapter Interface (Base)
|
|
100
|
+
|
|
101
|
+
```ruby
|
|
102
|
+
class Adapters::Base
|
|
103
|
+
def self.platform_id → Symbol
|
|
104
|
+
def self.platform_config(raw_config) → Hash # symbol-keyed
|
|
105
|
+
def self.env_keys → Array<String> # for config serialization
|
|
106
|
+
|
|
107
|
+
def start(&on_message) # blocks; yields event Hash per inbound message
|
|
108
|
+
def stop # graceful shutdown
|
|
109
|
+
def send_text(chat_id, text, reply_to: nil) → Hash
|
|
110
|
+
def update_message(chat_id, message_id, text) → Boolean
|
|
111
|
+
def supports_message_updates? → Boolean
|
|
112
|
+
def validate_config(config) → Array<String> # error messages
|
|
113
|
+
end
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## ChannelManager
|
|
119
|
+
|
|
120
|
+
```ruby
|
|
121
|
+
class ChannelManager
|
|
122
|
+
def initialize(session_registry:, session_builder:, channel_config:, agent_config:)
|
|
123
|
+
|
|
124
|
+
def start # Thread.new per enabled platform adapter
|
|
125
|
+
def stop # kills all adapter threads gracefully
|
|
126
|
+
|
|
127
|
+
private
|
|
128
|
+
|
|
129
|
+
def route_message(adapter, event)
|
|
130
|
+
session_id = @binding.resolve_or_create(event, session_builder: @session_builder)
|
|
131
|
+
ui = ChannelUIController.new(event, adapter)
|
|
132
|
+
Thread.new { run_agent(session_id, event[:text], ui) }
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## ChannelBinding
|
|
140
|
+
|
|
141
|
+
Maps `(platform, user_id)` → `session_id`. Persisted to `~/.octo/channel_bindings.yml`.
|
|
142
|
+
|
|
143
|
+
Binding modes (configurable per platform):
|
|
144
|
+
|
|
145
|
+
| Mode | Key | Description |
|
|
146
|
+
|------|-----|-------------|
|
|
147
|
+
| `user` | `(platform, user_id)` | Each IM user gets their own session (default) |
|
|
148
|
+
| `chat` | `(platform, chat_id)` | Whole group shares one session |
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## ChannelUIController
|
|
153
|
+
|
|
154
|
+
Implements `UIInterface`. Key behaviours:
|
|
155
|
+
|
|
156
|
+
- `show_assistant_message` → `adapter.send_text(chat_id, content)`
|
|
157
|
+
- `show_tool_call` → buffers as `⚙️ \`tool summary\`` (flushed on next message)
|
|
158
|
+
- `show_progress` → `adapter.update_message(...)` if `supports_message_updates?`
|
|
159
|
+
- `show_complete` → sends `✅ Complete • N iterations • $cost`
|
|
160
|
+
- `request_confirmation` → **not supported in IM** (returns auto-approved / raises)
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Thread Model
|
|
165
|
+
|
|
166
|
+
```
|
|
167
|
+
Main thread (WEBrick server.start — blocks)
|
|
168
|
+
├── WEBrick request threads (existing)
|
|
169
|
+
├── Agent task threads (existing, per task)
|
|
170
|
+
├── Scheduler thread (existing, octo-scheduler)
|
|
171
|
+
└── ChannelManager
|
|
172
|
+
├── feishu-adapter thread (WSClient read loop, constant)
|
|
173
|
+
│ └── feishu-ping thread (heartbeat, 90s)
|
|
174
|
+
└── wecom-adapter thread (WSClient read loop, constant)
|
|
175
|
+
└── wecom-ping thread (heartbeat, 30s)
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Per enabled platform: **2 constant threads**. Agent task threads are spawned
|
|
179
|
+
on demand (same as Web UI path) and exit when done.
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## Configuration
|
|
184
|
+
|
|
185
|
+
Channel credentials live in `~/.octo/channels.yml` (managed by `ChannelConfig`
|
|
186
|
+
which already exists in main branch):
|
|
187
|
+
|
|
188
|
+
```yaml
|
|
189
|
+
channels:
|
|
190
|
+
feishu:
|
|
191
|
+
enabled: true
|
|
192
|
+
app_id: cli_xxx
|
|
193
|
+
app_secret: xxx
|
|
194
|
+
allowed_users:
|
|
195
|
+
- ou_xxx
|
|
196
|
+
wecom:
|
|
197
|
+
enabled: false
|
|
198
|
+
bot_id: xxx
|
|
199
|
+
secret: xxx
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
`ChannelManager` reads this via `ChannelConfig#platform_config(platform)`.
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## Integration with HttpServer
|
|
207
|
+
|
|
208
|
+
```ruby
|
|
209
|
+
# HttpServer#initialize
|
|
210
|
+
@channel_manager = ChannelManager.new(
|
|
211
|
+
session_registry: @registry,
|
|
212
|
+
session_builder: method(:build_session),
|
|
213
|
+
channel_config: Octo::ChannelConfig.load,
|
|
214
|
+
agent_config: @agent_config
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
# HttpServer#start (after scheduler.start)
|
|
218
|
+
@channel_manager.start
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
`ChannelManager#start` is non-blocking (spawns threads internally),
|
|
222
|
+
mirroring `Scheduler#start` behaviour.
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## Future: DingTalk
|
|
227
|
+
|
|
228
|
+
DingTalk also supports a WebSocket Stream mode. Adding it means:
|
|
229
|
+
|
|
230
|
+
1. `lib/octo/channel/adapters/dingtalk/adapter.rb` inheriting `Base`
|
|
231
|
+
2. `lib/octo/channel/adapters/dingtalk/ws_client.rb`
|
|
232
|
+
3. Register: `Adapters.register(:dingtalk, Adapter)`
|
|
233
|
+
4. Add credentials to `ChannelConfig`
|
|
234
|
+
|
|
235
|
+
No changes needed to `ChannelManager`, `ChannelUIController`, or `ChannelBinding`.
|