@phuetz/code-buddy 1.1.0 → 1.3.0
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.
- package/README.md +211 -163
- package/dist/agent/autonomous/agentic-coding-contract.d.ts +12 -12
- package/dist/agent/base-agent.d.ts +4 -0
- package/dist/agent/base-agent.js +6 -0
- package/dist/agent/codebuddy-agent.d.ts +1 -1
- package/dist/agent/codebuddy-agent.js +69 -14
- package/dist/agent/execution/agent-executor.js +122 -12
- package/dist/agent/execution/tool-dependency-graph.js +7 -0
- package/dist/agent/execution/tool-hooks.d.ts +6 -0
- package/dist/agent/execution/tool-hooks.js +13 -5
- package/dist/agent/execution/tool-selection-strategy.d.ts +11 -2
- package/dist/agent/execution/tool-selection-strategy.js +67 -10
- package/dist/agent/extended-thinking.d.ts +8 -0
- package/dist/agent/extended-thinking.js +27 -0
- package/dist/agent/facades/infrastructure-facade.d.ts +9 -2
- package/dist/agent/facades/infrastructure-facade.js +15 -6
- package/dist/agent/hermes-browser-backends.js +151 -11
- package/dist/agent/hermes-claw-migrate.d.ts +59 -0
- package/dist/agent/hermes-claw-migrate.js +427 -26
- package/dist/agent/hermes-parity-manifest.js +78 -71
- package/dist/agent/hermes-runtime-lifecycle.d.ts +67 -0
- package/dist/agent/hermes-runtime-lifecycle.js +468 -0
- package/dist/agent/middleware/index.d.ts +1 -0
- package/dist/agent/middleware/index.js +1 -0
- package/dist/agent/middleware/visual-validation-middleware.d.ts +17 -0
- package/dist/agent/middleware/visual-validation-middleware.js +67 -0
- package/dist/agent/model-benchmark.d.ts +77 -0
- package/dist/agent/model-benchmark.js +309 -0
- package/dist/agent/model-tier.d.ts +14 -0
- package/dist/agent/model-tier.js +71 -0
- package/dist/agent/multi-agent/agents/orchestrator-agent.js +17 -6
- package/dist/agent/multi-agent/multi-agent-system.js +3 -1
- package/dist/agent/multi-agent/session-fleet-bridge.d.ts +2 -0
- package/dist/agent/multi-agent/session-fleet-bridge.js +9 -1
- package/dist/agent/multi-agent/session-tools.js +22 -5
- package/dist/agent/reasoning/mcts.js +20 -6
- package/dist/agent/self-improvement/authored-artifact-gate.d.ts +18 -0
- package/dist/agent/self-improvement/authored-artifact-gate.js +42 -0
- package/dist/agent/self-improvement/authored-tool-runtime.d.ts +27 -0
- package/dist/agent/self-improvement/authored-tool-runtime.js +57 -0
- package/dist/agent/self-improvement/authored-tool-store.d.ts +24 -0
- package/dist/agent/self-improvement/authored-tool-store.js +57 -0
- package/dist/agent/self-improvement/llm-tool-proposer.d.ts +41 -0
- package/dist/agent/self-improvement/llm-tool-proposer.js +136 -0
- package/dist/agent/self-improvement/sandbox-scorer.d.ts +17 -0
- package/dist/agent/self-improvement/sandbox-scorer.js +43 -0
- package/dist/agent/self-improvement/self-knowledge.d.ts +8 -0
- package/dist/agent/self-improvement/self-knowledge.js +24 -0
- package/dist/agent/self-improvement/skill-benchmark.d.ts +9 -0
- package/dist/agent/self-improvement/skill-benchmark.js +22 -0
- package/dist/agent/self-improvement/skill-consolidator.d.ts +71 -0
- package/dist/agent/self-improvement/skill-consolidator.js +137 -0
- package/dist/agent/self-improvement/skill-engine.d.ts +42 -0
- package/dist/agent/self-improvement/skill-engine.js +87 -0
- package/dist/agent/self-improvement/skill-gate.d.ts +19 -0
- package/dist/agent/self-improvement/skill-gate.js +62 -0
- package/dist/agent/self-improvement/skill-mutator.d.ts +74 -0
- package/dist/agent/self-improvement/skill-mutator.js +223 -0
- package/dist/agent/self-improvement/skill-proposer.d.ts +40 -0
- package/dist/agent/self-improvement/skill-proposer.js +82 -0
- package/dist/agent/self-improvement/skill-types.d.ts +41 -0
- package/dist/agent/self-improvement/skill-types.js +13 -0
- package/dist/agent/self-improvement/tool-benchmark.d.ts +10 -0
- package/dist/agent/self-improvement/tool-benchmark.js +37 -0
- package/dist/agent/self-improvement/tool-engine.d.ts +54 -0
- package/dist/agent/self-improvement/tool-engine.js +101 -0
- package/dist/agent/self-improvement/tool-gate.d.ts +20 -0
- package/dist/agent/self-improvement/tool-gate.js +78 -0
- package/dist/agent/self-improvement/tool-proposer.d.ts +31 -0
- package/dist/agent/self-improvement/tool-proposer.js +34 -0
- package/dist/agent/self-improvement/tool-skill-mutator.d.ts +40 -0
- package/dist/agent/self-improvement/tool-skill-mutator.js +79 -0
- package/dist/agent/self-improvement/tool-types.d.ts +48 -0
- package/dist/agent/self-improvement/tool-types.js +9 -0
- package/dist/agent/self-improvement/types.d.ts +3 -1
- package/dist/agent/session-end-flush.d.ts +6 -1
- package/dist/agent/session-end-flush.js +20 -4
- package/dist/agent/streaming/streaming-handler.d.ts +7 -0
- package/dist/agent/streaming/streaming-handler.js +8 -1
- package/dist/agent/tool-executor.js +1 -0
- package/dist/agent/tool-handler.d.ts +5 -0
- package/dist/agent/tool-handler.js +133 -4
- package/dist/browser-automation/browser-operator-executor.js +15 -0
- package/dist/browser-automation/browser-use-runner.d.ts +96 -0
- package/dist/browser-automation/browser-use-runner.js +492 -0
- package/dist/browser-automation/camofox-runner.d.ts +107 -0
- package/dist/browser-automation/camofox-runner.js +287 -0
- package/dist/channels/dingtalk/index.js +3 -0
- package/dist/channels/discord/client.d.ts +9 -0
- package/dist/channels/discord/client.js +12 -0
- package/dist/channels/feishu/index.d.ts +167 -1
- package/dist/channels/feishu/index.js +432 -7
- package/dist/channels/gateway-lifecycle.d.ts +132 -0
- package/dist/channels/gateway-lifecycle.js +219 -0
- package/dist/channels/google-chat/index.d.ts +5 -1
- package/dist/channels/google-chat/index.js +5 -1
- package/dist/channels/imessage/index.d.ts +14 -0
- package/dist/channels/imessage/index.js +64 -17
- package/dist/channels/index.d.ts +4 -0
- package/dist/channels/index.js +4 -0
- package/dist/channels/irc/index.d.ts +73 -3
- package/dist/channels/irc/index.js +446 -11
- package/dist/channels/line/index.js +3 -0
- package/dist/channels/mattermost/index.d.ts +51 -4
- package/dist/channels/mattermost/index.js +303 -20
- package/dist/channels/nextcloud-talk/index.d.ts +89 -8
- package/dist/channels/nextcloud-talk/index.js +367 -16
- package/dist/channels/nostr/index.d.ts +121 -1
- package/dist/channels/nostr/index.js +396 -8
- package/dist/channels/ntfy/index.js +3 -0
- package/dist/channels/qq/index.js +3 -0
- package/dist/channels/slash-parity.d.ts +117 -0
- package/dist/channels/slash-parity.js +185 -0
- package/dist/channels/synology-chat/index.js +3 -0
- package/dist/channels/teams/index.d.ts +3 -0
- package/dist/channels/teams/index.js +3 -0
- package/dist/channels/telegram/client.d.ts +9 -0
- package/dist/channels/telegram/client.js +12 -0
- package/dist/channels/twilio-voice/index.js +3 -0
- package/dist/channels/webchat/index.d.ts +7 -1
- package/dist/channels/webchat/index.js +7 -1
- package/dist/channels/wecom/index.js +3 -0
- package/dist/channels/weixin/index.js +3 -0
- package/dist/channels/zalo/index.js +3 -0
- package/dist/cli/config-loader.js +1 -1
- package/dist/codebuddy/client.d.ts +30 -1
- package/dist/codebuddy/client.js +167 -20
- package/dist/codebuddy/providers/provider-chatgpt-responses.d.ts +5 -0
- package/dist/codebuddy/providers/provider-chatgpt-responses.js +7 -1
- package/dist/codebuddy/providers/provider-gemini-native.js +1 -1
- package/dist/codebuddy/providers/provider-openai-compat.d.ts +5 -0
- package/dist/codebuddy/providers/provider-openai-compat.js +82 -2
- package/dist/codebuddy/tool-definitions/computer-control-tools.js +32 -2
- package/dist/codebuddy/tool-definitions/core-tools.js +3 -3
- package/dist/codebuddy/tool-definitions/index.d.ts +2 -1
- package/dist/codebuddy/tool-definitions/index.js +2 -1
- package/dist/codebuddy/tool-definitions/multimodal-tools.d.ts +1 -0
- package/dist/codebuddy/tool-definitions/multimodal-tools.js +44 -1
- package/dist/codebuddy/tool-definitions/windows-tools.d.ts +2 -0
- package/dist/codebuddy/tool-definitions/windows-tools.js +34 -0
- package/dist/codebuddy/tools.d.ts +8 -1
- package/dist/codebuddy/tools.js +67 -24
- package/dist/commands/cli/hermes-commands.js +108 -0
- package/dist/commands/cli/improve-command.js +123 -0
- package/dist/commands/cli/native-engine-commands.js +102 -3
- package/dist/commands/cli/utility-commands.js +45 -0
- package/dist/commands/enhanced-command-handler.js +2 -2
- package/dist/commands/goal-cli.d.ts +30 -0
- package/dist/commands/goal-cli.js +210 -27
- package/dist/commands/handlers/goal-handler.d.ts +9 -2
- package/dist/commands/handlers/goal-handler.js +36 -9
- package/dist/commands/handlers/infra-handlers.js +36 -0
- package/dist/commands/handlers/memory-handlers.js +96 -1
- package/dist/commands/handlers/missing-handlers.d.ts +1 -1
- package/dist/commands/handlers/missing-handlers.js +26 -3
- package/dist/commands/headless-slash.d.ts +2 -0
- package/dist/commands/headless-slash.js +12 -0
- package/dist/commands/llm-provider-resolution.d.ts +5 -3
- package/dist/commands/llm-provider-resolution.js +87 -33
- package/dist/commands/ollama.d.ts +25 -0
- package/dist/commands/ollama.js +100 -0
- package/dist/commands/provider.d.ts +5 -0
- package/dist/commands/provider.js +106 -55
- package/dist/commands/skills-cli/index.js +123 -0
- package/dist/commands/slash/builtin-commands.js +1 -1
- package/dist/commands/spec-next.js +2 -1
- package/dist/commands/spec-plan.js +5 -16
- package/dist/commands/tunnel.d.ts +2 -0
- package/dist/commands/tunnel.js +48 -0
- package/dist/companion/percepts.js +11 -1
- package/dist/config/config-resolver.d.ts +2 -1
- package/dist/config/config-resolver.js +54 -41
- package/dist/config/constants.d.ts +28 -0
- package/dist/config/constants.js +7 -0
- package/dist/config/env-schema.js +756 -0
- package/dist/config/model-tools.js +4 -4
- package/dist/config/toml-config.d.ts +16 -0
- package/dist/config/toml-config.js +3 -0
- package/dist/context/bootstrap-loader.js +6 -23
- package/dist/context/context-manager-v2.js +1 -0
- package/dist/context/import-directive-parser.d.ts +4 -0
- package/dist/context/import-directive-parser.js +51 -6
- package/dist/context/instruction-excludes.d.ts +30 -1
- package/dist/context/instruction-excludes.js +71 -1
- package/dist/context/jit-context.d.ts +8 -10
- package/dist/context/jit-context.js +28 -106
- package/dist/context/project-context.d.ts +90 -0
- package/dist/context/project-context.js +295 -0
- package/dist/daemon/autonomous-daemon.d.ts +1 -1
- package/dist/daemon/autonomous-daemon.js +3 -3
- package/dist/daemon/autonomous-loop.d.ts +31 -1
- package/dist/daemon/autonomous-loop.js +91 -4
- package/dist/daemon/colab-goal.js +9 -1
- package/dist/daemon/cron-agent-bridge.d.ts +12 -3
- package/dist/daemon/cron-agent-bridge.js +25 -9
- package/dist/desktop/codebuddy-engine-adapter.d.ts +32 -0
- package/dist/desktop/codebuddy-engine-adapter.js +257 -80
- package/dist/desktop/engine-adapter.d.ts +14 -0
- package/dist/desktop-automation/automation-manager.js +16 -0
- package/dist/desktop-automation/omniparser-runner.d.ts +63 -0
- package/dist/desktop-automation/omniparser-runner.js +115 -0
- package/dist/desktop-automation/smart-snapshot.d.ts +11 -0
- package/dist/desktop-automation/smart-snapshot.js +72 -5
- package/dist/doctor/index.js +40 -23
- package/dist/events/types.d.ts +11 -0
- package/dist/fleet/colab-store.js +14 -2
- package/dist/fleet/peer-session-bridge.js +50 -12
- package/dist/fleet/peer-tool-bridge.js +14 -0
- package/dist/goals/goal-decomposer.d.ts +27 -0
- package/dist/goals/goal-decomposer.js +293 -0
- package/dist/goals/goal-judge-client.d.ts +15 -0
- package/dist/goals/goal-judge-client.js +45 -0
- package/dist/goals/goal-judge.d.ts +2 -0
- package/dist/goals/goal-judge.js +35 -6
- package/dist/goals/goal-loop.d.ts +14 -0
- package/dist/goals/goal-loop.js +47 -2
- package/dist/goals/goal-manager.d.ts +6 -0
- package/dist/goals/goal-manager.js +67 -14
- package/dist/goals/goal-state.d.ts +13 -0
- package/dist/goals/goal-state.js +83 -13
- package/dist/goals/goal-store.d.ts +3 -0
- package/dist/goals/goal-store.js +31 -15
- package/dist/harness/contract.d.ts +28 -28
- package/dist/hooks/user-hooks.js +17 -3
- package/dist/identity/identity-manager.js +3 -2
- package/dist/index.js +481 -26
- package/dist/input/text-to-speech.d.ts +2 -6
- package/dist/input/text-to-speech.js +2 -27
- package/dist/integrations/screenpipe/screenpipe-client.d.ts +10 -0
- package/dist/integrations/screenpipe/screenpipe-client.js +12 -2
- package/dist/integrations/tailscale.d.ts +13 -0
- package/dist/integrations/tailscale.js +87 -34
- package/dist/knowledge/workspace-indexer.js +53 -9
- package/dist/mcp/client.js +1 -0
- package/dist/mcp/mcp-desktop-tools.d.ts +23 -0
- package/dist/mcp/mcp-desktop-tools.js +158 -0
- package/dist/mcp/mcp-resources.js +2 -3
- package/dist/mcp/mcp-server.js +2 -0
- package/dist/memory/enhanced-memory.d.ts +10 -0
- package/dist/memory/enhanced-memory.js +33 -2
- package/dist/memory/index.d.ts +3 -1
- package/dist/memory/index.js +4 -1
- package/dist/memory/memory-auto-proposer.d.ts +23 -0
- package/dist/memory/memory-auto-proposer.js +308 -0
- package/dist/memory/memory-candidate-queue.d.ts +92 -0
- package/dist/memory/memory-candidate-queue.js +261 -0
- package/dist/memory/persistent-memory.d.ts +45 -2
- package/dist/memory/persistent-memory.js +236 -40
- package/dist/ml/bayesian-qualifier.d.ts +1 -1
- package/dist/ml/bayesian-qualifier.js +35 -2
- package/dist/openclaw/gateway-bridge.js +5 -0
- package/dist/plugins/marketplace.d.ts +1 -0
- package/dist/plugins/marketplace.js +7 -0
- package/dist/prompts/execution-discipline.d.ts +14 -0
- package/dist/prompts/execution-discipline.js +29 -0
- package/dist/prompts/variation-injector.js +9 -2
- package/dist/protocols/acp/acp-agentic-runner.d.ts +7 -6
- package/dist/protocols/acp/acp-agentic-runner.js +115 -18
- package/dist/protocols/acp/acp-session-store.d.ts +23 -0
- package/dist/protocols/acp/acp-session-store.js +77 -0
- package/dist/protocols/acp/acp-stdio-server.d.ts +7 -0
- package/dist/protocols/acp/acp-stdio-server.js +71 -16
- package/dist/providers/active-llm-registry.d.ts +37 -0
- package/dist/providers/active-llm-registry.js +186 -0
- package/dist/providers/auxiliary-provider.d.ts +25 -0
- package/dist/providers/auxiliary-provider.js +192 -0
- package/dist/providers/codex-oauth.d.ts +1 -1
- package/dist/providers/codex-oauth.js +27 -4
- package/dist/providers/index.d.ts +3 -0
- package/dist/providers/index.js +4 -0
- package/dist/providers/provider-catalog.d.ts +70 -0
- package/dist/providers/provider-catalog.js +738 -0
- package/dist/providers/provider-fallback.d.ts +35 -0
- package/dist/providers/provider-fallback.js +171 -0
- package/dist/providers/xai-oauth.d.ts +128 -0
- package/dist/providers/xai-oauth.js +735 -0
- package/dist/scheduler/cron-scheduler.d.ts +15 -3
- package/dist/scheduler/cron-scheduler.js +53 -7
- package/dist/scheduler/script-runner.d.ts +2 -0
- package/dist/scheduler/script-runner.js +19 -4
- package/dist/scheduler/watchdog-handlers.js +7 -20
- package/dist/search/usearch-index.js +7 -2
- package/dist/security/tool-policy/tool-groups.js +2 -0
- package/dist/sensory/dreaming.d.ts +45 -0
- package/dist/sensory/dreaming.js +114 -0
- package/dist/sensory/heartbeat-scheduler.d.ts +38 -0
- package/dist/sensory/heartbeat-scheduler.js +72 -0
- package/dist/sensory/reactions.d.ts +24 -0
- package/dist/sensory/reactions.js +31 -0
- package/dist/sensory/screen-reaction.d.ts +23 -0
- package/dist/sensory/screen-reaction.js +59 -0
- package/dist/sensory/sensory-bridge.d.ts +23 -0
- package/dist/sensory/sensory-bridge.js +85 -0
- package/dist/sensory/sensory-memory.d.ts +20 -0
- package/dist/sensory/sensory-memory.js +39 -0
- package/dist/sensory/speech-reaction.d.ts +21 -0
- package/dist/sensory/speech-reaction.js +83 -0
- package/dist/sensory/vision-reaction.d.ts +31 -0
- package/dist/sensory/vision-reaction.js +74 -0
- package/dist/server/index.js +106 -2
- package/dist/server/routes/mobile.d.ts +12 -4
- package/dist/server/routes/mobile.js +116 -1
- package/dist/server/tls-config.d.ts +35 -0
- package/dist/server/tls-config.js +142 -0
- package/dist/server/tunnel-manager.d.ts +20 -0
- package/dist/server/tunnel-manager.js +58 -0
- package/dist/server/websocket/fleet-bridge.js +5 -0
- package/dist/services/prompt-builder.d.ts +11 -0
- package/dist/services/prompt-builder.js +124 -17
- package/dist/shared/engine-types.d.ts +15 -1
- package/dist/sidecar/sidecar-bridge.d.ts +1 -0
- package/dist/sidecar/sidecar-bridge.js +21 -10
- package/dist/skills/parser.js +3 -0
- package/dist/skills/skill-importer.d.ts +58 -0
- package/dist/skills/skill-importer.js +261 -0
- package/dist/skills/skill-sources.d.ts +20 -0
- package/dist/skills/skill-sources.js +102 -0
- package/dist/skills/types.d.ts +6 -0
- package/dist/spec/spec-store.js +8 -1
- package/dist/talk-mode/providers/audioreader-tts.d.ts +1 -1
- package/dist/talk-mode/providers/audioreader-tts.js +8 -8
- package/dist/templates/project-scaffolding.js +3 -2
- package/dist/themes/theme-schema.d.ts +10 -10
- package/dist/tools/application-profiles.js +38 -0
- package/dist/tools/bash/bash-tool.d.ts +1 -0
- package/dist/tools/bash/bash-tool.js +16 -2
- package/dist/tools/bash/command-validator.js +3 -0
- package/dist/tools/bash/rtk-rewrite.d.ts +16 -0
- package/dist/tools/bash/rtk-rewrite.js +100 -0
- package/dist/tools/bash/streaming-executor.js +14 -1
- package/dist/tools/computer-control-tool.d.ts +16 -1
- package/dist/tools/computer-control-tool.js +317 -5
- package/dist/tools/document-generator.d.ts +14 -0
- package/dist/tools/document-generator.js +79 -0
- package/dist/tools/execute-code-rpc-invoker.d.ts +26 -2
- package/dist/tools/execute-code-rpc-invoker.js +46 -5
- package/dist/tools/metadata.js +28 -0
- package/dist/tools/office-macro-tool.d.ts +10 -0
- package/dist/tools/office-macro-tool.js +93 -0
- package/dist/tools/register-tool-handler.d.ts +25 -0
- package/dist/tools/register-tool-handler.js +100 -0
- package/dist/tools/registry/index.d.ts +2 -1
- package/dist/tools/registry/index.js +2 -1
- package/dist/tools/registry/memory-tools.d.ts +20 -0
- package/dist/tools/registry/memory-tools.js +225 -3
- package/dist/tools/registry/multimodal-tools.js +2 -2
- package/dist/tools/registry/vision-tools.d.ts +33 -0
- package/dist/tools/registry/vision-tools.js +308 -6
- package/dist/tools/registry/windows-tools.d.ts +2 -0
- package/dist/tools/registry/windows-tools.js +47 -0
- package/dist/tools/registry.d.ts +6 -0
- package/dist/tools/registry.js +8 -0
- package/dist/tools/route-peer-tool.js +14 -0
- package/dist/tools/screenshot-tool.js +14 -2
- package/dist/tools/text-to-speech-tool.d.ts +1 -1
- package/dist/tools/text-to-speech-tool.js +2 -12
- package/dist/tools/tool-selector.js +3 -59
- package/dist/utils/config-validation/schema.d.ts +40 -7
- package/dist/utils/config-validation/schema.js +130 -1
- package/dist/utils/cost-tracker.js +21 -1
- package/dist/utils/disk-guard.d.ts +180 -0
- package/dist/utils/disk-guard.js +404 -0
- package/dist/utils/init-project.d.ts +7 -0
- package/dist/utils/init-project.js +37 -0
- package/dist/utils/interactive-setup.js +35 -2
- package/dist/utils/model-utils.js +11 -2
- package/dist/utils/output-sanitizer.js +11 -0
- package/dist/utils/provider-detector.d.ts +8 -7
- package/dist/utils/provider-detector.js +19 -77
- package/dist/utils/settings-manager.d.ts +19 -0
- package/dist/utils/token-display.d.ts +3 -3
- package/dist/utils/token-display.js +22 -4
- package/dist/wizard/onboarding.d.ts +39 -0
- package/dist/wizard/onboarding.js +433 -21
- package/dist/wizard/provider-onboarding.d.ts +8 -23
- package/dist/wizard/provider-onboarding.js +39 -158
- package/package.json +4 -2
- package/dist/plugins/provider-onboarding.d.ts +0 -23
- package/dist/plugins/provider-onboarding.js +0 -116
- package/dist/utils/kokoro-tts.d.ts +0 -8
- package/dist/utils/kokoro-tts.js +0 -67
|
@@ -1,25 +1,104 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* IRC Channel Adapter
|
|
3
3
|
*
|
|
4
|
-
* Connects to IRC servers for sending/receiving messages
|
|
5
|
-
*
|
|
4
|
+
* Connects to IRC servers for sending/receiving messages over a real,
|
|
5
|
+
* long-lived TCP (or TLS) socket implementing an RFC 1459/2812 subset.
|
|
6
|
+
* Supports multiple channels, password auth, TLS, and auto-reconnect with
|
|
7
|
+
* exponential backoff (shared ReconnectionManager idiom — same as
|
|
8
|
+
* imessage/discord/slack).
|
|
6
9
|
*/
|
|
10
|
+
import { EventEmitter } from 'events';
|
|
11
|
+
import * as net from 'node:net';
|
|
12
|
+
import * as tls from 'node:tls';
|
|
7
13
|
import { logger } from '../../utils/logger.js';
|
|
8
|
-
import { BaseChannel } from '../core.js';
|
|
9
|
-
|
|
14
|
+
import { BaseChannel, } from '../core.js';
|
|
15
|
+
import { ReconnectionManager } from '../reconnection-manager.js';
|
|
16
|
+
/**
|
|
17
|
+
* Parse a raw IRC line (without the trailing CRLF) into prefix/command/params.
|
|
18
|
+
*
|
|
19
|
+
* Handles both prefixed and unprefixed lines — this distinction is
|
|
20
|
+
* load-bearing: the welcome reply `001 <nick> :Welcome` has NO prefix while
|
|
21
|
+
* `:nick!u@h PRIVMSG #cb :text` does. Treating tokens[0] as always-prefix (or
|
|
22
|
+
* never-prefix) misparses one of them.
|
|
23
|
+
*/
|
|
24
|
+
export function parseIRCLine(line) {
|
|
25
|
+
let rest = line;
|
|
26
|
+
let prefix;
|
|
27
|
+
if (rest.startsWith(':')) {
|
|
28
|
+
const spaceIdx = rest.indexOf(' ');
|
|
29
|
+
if (spaceIdx === -1)
|
|
30
|
+
return null;
|
|
31
|
+
prefix = rest.slice(1, spaceIdx);
|
|
32
|
+
rest = rest.slice(spaceIdx + 1);
|
|
33
|
+
}
|
|
34
|
+
// Split off the trailing parameter (everything after " :") which may
|
|
35
|
+
// contain spaces.
|
|
36
|
+
let trailing;
|
|
37
|
+
const trailingIdx = rest.indexOf(' :');
|
|
38
|
+
if (rest.startsWith(':')) {
|
|
39
|
+
// Whole remainder is the trailing param.
|
|
40
|
+
trailing = rest.slice(1);
|
|
41
|
+
rest = '';
|
|
42
|
+
}
|
|
43
|
+
else if (trailingIdx !== -1) {
|
|
44
|
+
trailing = rest.slice(trailingIdx + 2);
|
|
45
|
+
rest = rest.slice(0, trailingIdx);
|
|
46
|
+
}
|
|
47
|
+
const tokens = rest.length > 0 ? rest.split(' ').filter((t) => t.length > 0) : [];
|
|
48
|
+
const command = tokens.shift();
|
|
49
|
+
if (!command) {
|
|
50
|
+
// Possible only if the line was entirely a trailing param — invalid.
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
const params = tokens;
|
|
54
|
+
if (trailing !== undefined)
|
|
55
|
+
params.push(trailing);
|
|
56
|
+
return { prefix, command: command.toUpperCase(), params };
|
|
57
|
+
}
|
|
58
|
+
/** Extract the nick portion of an IRC prefix (`nick!user@host` → `nick`). */
|
|
59
|
+
function nickFromPrefix(prefix) {
|
|
60
|
+
if (!prefix)
|
|
61
|
+
return '';
|
|
62
|
+
const bang = prefix.indexOf('!');
|
|
63
|
+
return bang === -1 ? prefix : prefix.slice(0, bang);
|
|
64
|
+
}
|
|
65
|
+
export class IRCAdapter extends EventEmitter {
|
|
10
66
|
config;
|
|
11
67
|
running = false;
|
|
12
68
|
joinedChannels = new Set();
|
|
69
|
+
socket = null;
|
|
70
|
+
recvBuffer = '';
|
|
71
|
+
/** True while an intentional disconnect is in progress — suppresses reconnect. */
|
|
72
|
+
closing = false;
|
|
73
|
+
/** True once a close/error for the current socket has been handled (de-dup). */
|
|
74
|
+
closeHandled = false;
|
|
75
|
+
reconnecting = false;
|
|
76
|
+
reconnectionManager;
|
|
13
77
|
constructor(config) {
|
|
78
|
+
super();
|
|
14
79
|
this.config = {
|
|
15
80
|
port: config.useTLS ? 6697 : 6667,
|
|
16
81
|
username: config.nick,
|
|
17
82
|
realname: config.nick,
|
|
18
83
|
useTLS: false,
|
|
19
84
|
sasl: false,
|
|
85
|
+
connectTimeoutMs: 15000,
|
|
86
|
+
maxRetries: 10,
|
|
87
|
+
retryDelayMs: 2000,
|
|
20
88
|
...config,
|
|
21
89
|
};
|
|
90
|
+
// IRC is a genuinely persistent protocol (one long-lived TCP socket); a
|
|
91
|
+
// dropped socket is recovered with the shared exponential-backoff manager,
|
|
92
|
+
// same idiom as discord/imessage/slack.
|
|
93
|
+
this.reconnectionManager = new ReconnectionManager('irc', {
|
|
94
|
+
maxRetries: this.config.maxRetries ?? 10,
|
|
95
|
+
initialDelayMs: this.config.retryDelayMs ?? 2000,
|
|
96
|
+
maxDelayMs: 60000,
|
|
97
|
+
});
|
|
22
98
|
}
|
|
99
|
+
// ==========================================================================
|
|
100
|
+
// Lifecycle
|
|
101
|
+
// ==========================================================================
|
|
23
102
|
async start() {
|
|
24
103
|
if (this.running) {
|
|
25
104
|
throw new Error('IRCAdapter is already running');
|
|
@@ -30,26 +109,333 @@ export class IRCAdapter {
|
|
|
30
109
|
nick: this.config.nick,
|
|
31
110
|
tls: this.config.useTLS,
|
|
32
111
|
});
|
|
112
|
+
await this.connectInternal();
|
|
113
|
+
// Connectivity confirmed for this session — reset any inherited backoff
|
|
114
|
+
// state so a fresh start() never starts pre-exhausted.
|
|
115
|
+
this.reconnectionManager.onConnected();
|
|
33
116
|
this.running = true;
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
117
|
+
this.emit('connected');
|
|
118
|
+
logger.info('IRCAdapter: connected', { server: this.config.server, nick: this.config.nick });
|
|
37
119
|
}
|
|
38
120
|
async stop() {
|
|
39
121
|
if (!this.running) {
|
|
40
122
|
throw new Error('IRCAdapter is not running');
|
|
41
123
|
}
|
|
42
124
|
logger.debug('IRCAdapter: disconnecting');
|
|
125
|
+
this.closing = true;
|
|
126
|
+
this.reconnectionManager.cancel();
|
|
127
|
+
this.reconnecting = false;
|
|
128
|
+
const sock = this.socket;
|
|
129
|
+
if (sock) {
|
|
130
|
+
try {
|
|
131
|
+
if (sock.writable) {
|
|
132
|
+
sock.write('QUIT :Code Buddy signing off\r\n');
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
// best-effort QUIT
|
|
137
|
+
}
|
|
138
|
+
sock.removeAllListeners();
|
|
139
|
+
sock.destroy();
|
|
140
|
+
}
|
|
141
|
+
this.socket = null;
|
|
142
|
+
this.recvBuffer = '';
|
|
43
143
|
this.joinedChannels.clear();
|
|
44
144
|
this.running = false;
|
|
145
|
+
this.emit('disconnected');
|
|
146
|
+
logger.info('IRCAdapter: disconnected');
|
|
45
147
|
}
|
|
46
148
|
isRunning() {
|
|
47
149
|
return this.running;
|
|
48
150
|
}
|
|
151
|
+
getConfig() {
|
|
152
|
+
return { ...this.config };
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Open the socket, perform the registration handshake, and resolve once the
|
|
156
|
+
* server sends the 001 welcome numeric. Used by both start() and the
|
|
157
|
+
* reconnect callback (so it deliberately omits the running-guard).
|
|
158
|
+
*/
|
|
159
|
+
connectInternal() {
|
|
160
|
+
return new Promise((resolve, reject) => {
|
|
161
|
+
this.closing = false;
|
|
162
|
+
this.closeHandled = false;
|
|
163
|
+
this.recvBuffer = '';
|
|
164
|
+
const host = this.config.server;
|
|
165
|
+
const port = this.config.port ?? (this.config.useTLS ? 6697 : 6667);
|
|
166
|
+
let settled = false;
|
|
167
|
+
let timeout = null;
|
|
168
|
+
const cleanupSettle = () => {
|
|
169
|
+
if (timeout) {
|
|
170
|
+
clearTimeout(timeout);
|
|
171
|
+
timeout = null;
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
const onWelcome = () => {
|
|
175
|
+
if (settled)
|
|
176
|
+
return;
|
|
177
|
+
settled = true;
|
|
178
|
+
cleanupSettle();
|
|
179
|
+
resolve();
|
|
180
|
+
};
|
|
181
|
+
const onFail = (err) => {
|
|
182
|
+
if (settled)
|
|
183
|
+
return;
|
|
184
|
+
settled = true;
|
|
185
|
+
cleanupSettle();
|
|
186
|
+
if (sock) {
|
|
187
|
+
sock.removeAllListeners();
|
|
188
|
+
sock.destroy();
|
|
189
|
+
}
|
|
190
|
+
if (this.socket === sock)
|
|
191
|
+
this.socket = null;
|
|
192
|
+
reject(err);
|
|
193
|
+
};
|
|
194
|
+
const onConnectTcp = () => {
|
|
195
|
+
// TCP is up; perform the IRC registration handshake. The connection is
|
|
196
|
+
// not "ready" until the server replies 001.
|
|
197
|
+
try {
|
|
198
|
+
if (this.config.password) {
|
|
199
|
+
this.write(`PASS ${this.config.password}`);
|
|
200
|
+
}
|
|
201
|
+
this.write(`NICK ${this.config.nick}`);
|
|
202
|
+
this.write(`USER ${this.config.username ?? this.config.nick} 0 * :${this.config.realname ?? this.config.nick}`);
|
|
203
|
+
}
|
|
204
|
+
catch (err) {
|
|
205
|
+
onFail(err instanceof Error ? err : new Error(String(err)));
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
let sock;
|
|
209
|
+
try {
|
|
210
|
+
if (this.config.useTLS) {
|
|
211
|
+
sock = tls.connect({ host, port, servername: host }, onConnectTcp);
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
sock = net.createConnection({ host, port }, onConnectTcp);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
catch (err) {
|
|
218
|
+
onFail(err instanceof Error ? err : new Error(String(err)));
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
this.socket = sock;
|
|
222
|
+
sock.setEncoding('utf8');
|
|
223
|
+
sock.on('data', (chunk) => {
|
|
224
|
+
this.handleData(chunk, onWelcome);
|
|
225
|
+
});
|
|
226
|
+
sock.on('error', (err) => {
|
|
227
|
+
if (!settled) {
|
|
228
|
+
onFail(err);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
this.handleSocketClosed(sock, err);
|
|
232
|
+
});
|
|
233
|
+
sock.on('close', () => {
|
|
234
|
+
if (!settled) {
|
|
235
|
+
onFail(new Error('IRC socket closed before registration completed'));
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
this.handleSocketClosed(sock);
|
|
239
|
+
});
|
|
240
|
+
timeout = setTimeout(() => {
|
|
241
|
+
onFail(new Error(`IRC registration timed out after ${this.config.connectTimeoutMs}ms`));
|
|
242
|
+
}, this.config.connectTimeoutMs ?? 15000);
|
|
243
|
+
timeout.unref?.();
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
// ==========================================================================
|
|
247
|
+
// Inbound handling
|
|
248
|
+
// ==========================================================================
|
|
249
|
+
/**
|
|
250
|
+
* Buffer incoming bytes and dispatch complete CRLF-terminated lines.
|
|
251
|
+
* `onWelcome` is invoked when the 001 numeric is seen (resolves connect()).
|
|
252
|
+
*/
|
|
253
|
+
handleData(chunk, onWelcome) {
|
|
254
|
+
this.recvBuffer += chunk;
|
|
255
|
+
// RFC 1459 caps a line at 512 bytes. Guard against a peer that streams bytes
|
|
256
|
+
// without ever sending a newline (unbounded buffer growth -> OOM): drop the
|
|
257
|
+
// connection, which the close handler turns into a backoff reconnect.
|
|
258
|
+
if (this.recvBuffer.length > 8192) {
|
|
259
|
+
logger.warn('IRCAdapter: line buffer overflow, dropping connection', { size: this.recvBuffer.length });
|
|
260
|
+
this.recvBuffer = '';
|
|
261
|
+
this.socket?.destroy(new Error('IRC line buffer overflow'));
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
let idx;
|
|
265
|
+
// IRC servers terminate lines with \r\n, but be lenient and split on \n.
|
|
266
|
+
while ((idx = this.recvBuffer.indexOf('\n')) !== -1) {
|
|
267
|
+
let line = this.recvBuffer.slice(0, idx);
|
|
268
|
+
this.recvBuffer = this.recvBuffer.slice(idx + 1);
|
|
269
|
+
if (line.endsWith('\r'))
|
|
270
|
+
line = line.slice(0, -1);
|
|
271
|
+
if (line.length === 0)
|
|
272
|
+
continue;
|
|
273
|
+
this.handleLine(line, onWelcome);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
handleLine(line, onWelcome) {
|
|
277
|
+
const msg = parseIRCLine(line);
|
|
278
|
+
if (!msg)
|
|
279
|
+
return;
|
|
280
|
+
switch (msg.command) {
|
|
281
|
+
case 'PING': {
|
|
282
|
+
// Reply with the same token so the server keeps the link alive.
|
|
283
|
+
const token = msg.params[msg.params.length - 1] ?? '';
|
|
284
|
+
this.write(`PONG :${token}`);
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
case '001': {
|
|
288
|
+
// Welcome numeric — registration succeeded. Join configured channels.
|
|
289
|
+
for (const ch of this.config.channels) {
|
|
290
|
+
this.write(`JOIN ${ch}`);
|
|
291
|
+
this.joinedChannels.add(ch);
|
|
292
|
+
}
|
|
293
|
+
onWelcome();
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
case 'PRIVMSG': {
|
|
297
|
+
this.handlePrivmsg(msg);
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
case 'ERROR': {
|
|
301
|
+
logger.debug('IRCAdapter: server ERROR', { text: msg.params.join(' ') });
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
default:
|
|
305
|
+
// Other numerics / commands are ignored for this subset.
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
handlePrivmsg(msg) {
|
|
310
|
+
const target = msg.params[0] ?? '';
|
|
311
|
+
const text = msg.params[1] ?? '';
|
|
312
|
+
const senderNick = nickFromPrefix(msg.prefix);
|
|
313
|
+
// CTCP ACTION (/me) arrives wrapped in \x01ACTION ... \x01.
|
|
314
|
+
let content = text;
|
|
315
|
+
let contentType = 'text';
|
|
316
|
+
if (text.startsWith('ACTION ') && text.endsWith('')) {
|
|
317
|
+
content = text.slice('ACTION '.length, -1);
|
|
318
|
+
}
|
|
319
|
+
const isCommand = content.startsWith('/');
|
|
320
|
+
if (isCommand)
|
|
321
|
+
contentType = 'command';
|
|
322
|
+
const message = {
|
|
323
|
+
id: `irc-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
324
|
+
channel: {
|
|
325
|
+
id: target,
|
|
326
|
+
type: 'irc',
|
|
327
|
+
name: target,
|
|
328
|
+
isDM: !target.startsWith('#') && !target.startsWith('&'),
|
|
329
|
+
isGroup: target.startsWith('#') || target.startsWith('&'),
|
|
330
|
+
},
|
|
331
|
+
sender: {
|
|
332
|
+
id: senderNick,
|
|
333
|
+
username: senderNick,
|
|
334
|
+
displayName: senderNick,
|
|
335
|
+
},
|
|
336
|
+
content,
|
|
337
|
+
contentType,
|
|
338
|
+
timestamp: new Date(),
|
|
339
|
+
raw: { prefix: msg.prefix, command: msg.command, params: msg.params },
|
|
340
|
+
};
|
|
341
|
+
if (isCommand) {
|
|
342
|
+
const parts = content.slice(1).split(/\s+/);
|
|
343
|
+
message.isCommand = true;
|
|
344
|
+
message.commandName = parts[0];
|
|
345
|
+
message.commandArgs = parts.slice(1);
|
|
346
|
+
}
|
|
347
|
+
this.emit('message', message);
|
|
348
|
+
if (isCommand) {
|
|
349
|
+
this.emit('command', message);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
// ==========================================================================
|
|
353
|
+
// Reconnection
|
|
354
|
+
// ==========================================================================
|
|
355
|
+
/**
|
|
356
|
+
* Handle a socket 'close' or 'error' that was NOT triggered by an
|
|
357
|
+
* intentional disconnect. De-dupes the error→close pair (ECONNRESET fires
|
|
358
|
+
* 'error' then 'close') and drives a single reconnect via the shared
|
|
359
|
+
* ReconnectionManager.
|
|
360
|
+
*/
|
|
361
|
+
handleSocketClosed(sock, err) {
|
|
362
|
+
// Only act on the socket we currently own and only once per drop.
|
|
363
|
+
if (sock !== this.socket || this.closeHandled)
|
|
364
|
+
return;
|
|
365
|
+
this.closeHandled = true;
|
|
366
|
+
if (err) {
|
|
367
|
+
logger.warn('IRCAdapter: socket error', { error: err.message });
|
|
368
|
+
}
|
|
369
|
+
sock.removeAllListeners();
|
|
370
|
+
this.socket = null;
|
|
371
|
+
// Intentional disconnect — do not reconnect.
|
|
372
|
+
if (this.closing || !this.running) {
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
logger.warn('IRCAdapter: connection dropped, scheduling reconnect');
|
|
376
|
+
this.emit('disconnected', err ?? new Error('IRC connection dropped'));
|
|
377
|
+
this.reconnect();
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Recover a dropped connection via the shared ReconnectionManager
|
|
381
|
+
* (exponential backoff + jitter + exhaustion). `scheduleReconnect` is
|
|
382
|
+
* single-shot, so a failed attempt re-drives it (deferred past the manager's
|
|
383
|
+
* internal active-guard) until recovery or exhaustion.
|
|
384
|
+
*/
|
|
385
|
+
reconnect() {
|
|
386
|
+
if (this.reconnecting)
|
|
387
|
+
return;
|
|
388
|
+
if (this.reconnectionManager.listenerCount('exhausted') === 0) {
|
|
389
|
+
this.reconnectionManager.on('exhausted', () => {
|
|
390
|
+
this.reconnecting = false;
|
|
391
|
+
this.running = false;
|
|
392
|
+
this.emit('disconnected', new Error('IRC reconnection failed after all retries'));
|
|
393
|
+
logger.error('IRCAdapter: reconnection failed permanently');
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
this.reconnecting = true;
|
|
397
|
+
this.reconnectionManager.scheduleReconnect(async () => {
|
|
398
|
+
try {
|
|
399
|
+
await this.connectInternal();
|
|
400
|
+
this.reconnecting = false;
|
|
401
|
+
this.reconnectionManager.onConnected();
|
|
402
|
+
this.emit('reconnected');
|
|
403
|
+
this.emit('connected');
|
|
404
|
+
logger.info('IRCAdapter: reconnected successfully');
|
|
405
|
+
}
|
|
406
|
+
catch (error) {
|
|
407
|
+
// Re-drive after the manager clears its internal active-flag (it does
|
|
408
|
+
// so in its own finally once this closure settles).
|
|
409
|
+
if (this.running && !this.closing) {
|
|
410
|
+
setTimeout(() => {
|
|
411
|
+
if (this.running && this.reconnecting && !this.closing) {
|
|
412
|
+
this.reconnecting = false;
|
|
413
|
+
this.reconnect();
|
|
414
|
+
}
|
|
415
|
+
}, 0).unref?.();
|
|
416
|
+
}
|
|
417
|
+
throw error instanceof Error ? error : new Error(String(error));
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
// ==========================================================================
|
|
422
|
+
// Outbound / API
|
|
423
|
+
// ==========================================================================
|
|
424
|
+
write(line) {
|
|
425
|
+
const sock = this.socket;
|
|
426
|
+
if (!sock || !sock.writable) {
|
|
427
|
+
throw new Error('IRCAdapter: socket is not writable');
|
|
428
|
+
}
|
|
429
|
+
sock.write(`${line}\r\n`);
|
|
430
|
+
}
|
|
49
431
|
async sendMessage(target, text) {
|
|
50
432
|
if (!this.running) {
|
|
51
433
|
throw new Error('IRCAdapter is not running');
|
|
52
434
|
}
|
|
435
|
+
// Split on newlines: IRC PRIVMSG is one line per message.
|
|
436
|
+
for (const line of text.split(/\r?\n/)) {
|
|
437
|
+
this.write(`PRIVMSG ${target} :${line}`);
|
|
438
|
+
}
|
|
53
439
|
logger.debug('IRCAdapter: PRIVMSG', { target, textLength: text.length });
|
|
54
440
|
return { success: true };
|
|
55
441
|
}
|
|
@@ -57,6 +443,9 @@ export class IRCAdapter {
|
|
|
57
443
|
if (!this.running) {
|
|
58
444
|
throw new Error('IRCAdapter is not running');
|
|
59
445
|
}
|
|
446
|
+
for (const line of text.split(/\r?\n/)) {
|
|
447
|
+
this.write(`NOTICE ${target} :${line}`);
|
|
448
|
+
}
|
|
60
449
|
logger.debug('IRCAdapter: NOTICE', { target, textLength: text.length });
|
|
61
450
|
return { success: true };
|
|
62
451
|
}
|
|
@@ -64,6 +453,7 @@ export class IRCAdapter {
|
|
|
64
453
|
if (!this.running) {
|
|
65
454
|
throw new Error('IRCAdapter is not running');
|
|
66
455
|
}
|
|
456
|
+
this.write(`PRIVMSG ${target} :ACTION ${text}`);
|
|
67
457
|
logger.debug('IRCAdapter: ACTION', { target, textLength: text.length });
|
|
68
458
|
return { success: true };
|
|
69
459
|
}
|
|
@@ -71,6 +461,7 @@ export class IRCAdapter {
|
|
|
71
461
|
if (!this.running) {
|
|
72
462
|
throw new Error('IRCAdapter is not running');
|
|
73
463
|
}
|
|
464
|
+
this.write(`JOIN ${channel}`);
|
|
74
465
|
this.joinedChannels.add(channel);
|
|
75
466
|
logger.debug('IRCAdapter: JOIN', { channel });
|
|
76
467
|
}
|
|
@@ -78,6 +469,7 @@ export class IRCAdapter {
|
|
|
78
469
|
if (!this.running) {
|
|
79
470
|
throw new Error('IRCAdapter is not running');
|
|
80
471
|
}
|
|
472
|
+
this.write(reason ? `PART ${channel} :${reason}` : `PART ${channel}`);
|
|
81
473
|
this.joinedChannels.delete(channel);
|
|
82
474
|
logger.debug('IRCAdapter: PART', { channel, reason });
|
|
83
475
|
}
|
|
@@ -91,6 +483,7 @@ export class IRCAdapter {
|
|
|
91
483
|
if (!this.running) {
|
|
92
484
|
throw new Error('IRCAdapter is not running');
|
|
93
485
|
}
|
|
486
|
+
this.write(`NICK ${nick}`);
|
|
94
487
|
this.config.nick = nick;
|
|
95
488
|
logger.debug('IRCAdapter: NICK', { nick });
|
|
96
489
|
}
|
|
@@ -112,24 +505,66 @@ export class IRCChannel extends BaseChannel {
|
|
|
112
505
|
channels: cfg.channels || [],
|
|
113
506
|
useTLS: cfg.useTLS,
|
|
114
507
|
sasl: cfg.sasl,
|
|
508
|
+
connectTimeoutMs: cfg.connectTimeoutMs,
|
|
509
|
+
maxRetries: cfg.maxRetries,
|
|
510
|
+
retryDelayMs: cfg.retryDelayMs,
|
|
511
|
+
});
|
|
512
|
+
// Forward adapter events onto the BaseChannel event surface.
|
|
513
|
+
this.adapter.on('message', (message) => {
|
|
514
|
+
this.status.lastActivity = new Date();
|
|
515
|
+
this.emit('message', message);
|
|
516
|
+
});
|
|
517
|
+
this.adapter.on('command', (message) => {
|
|
518
|
+
this.emit('command', message);
|
|
519
|
+
});
|
|
520
|
+
this.adapter.on('reconnected', () => {
|
|
521
|
+
this.status.connected = true;
|
|
522
|
+
this.status.lastActivity = new Date();
|
|
523
|
+
});
|
|
524
|
+
this.adapter.on('disconnected', (err) => {
|
|
525
|
+
this.status.connected = false;
|
|
526
|
+
// A mid-session 'disconnected' from the adapter means an unexpected drop
|
|
527
|
+
// (the adapter is now reconnecting); surface it without tearing down.
|
|
528
|
+
this.emit('disconnected', this.type, err);
|
|
529
|
+
});
|
|
530
|
+
this.adapter.on('error', (err) => {
|
|
531
|
+
this.status.error = err.message;
|
|
532
|
+
this.emit('error', this.type, err);
|
|
115
533
|
});
|
|
116
534
|
await this.adapter.start();
|
|
117
535
|
this.status.connected = true;
|
|
536
|
+
this.status.authenticated = true;
|
|
537
|
+
this.status.lastActivity = new Date();
|
|
538
|
+
this.emit('connected', this.type);
|
|
118
539
|
}
|
|
119
540
|
async disconnect() {
|
|
120
541
|
if (this.adapter) {
|
|
121
|
-
|
|
542
|
+
if (this.adapter.isRunning()) {
|
|
543
|
+
await this.adapter.stop();
|
|
544
|
+
}
|
|
545
|
+
this.adapter.removeAllListeners();
|
|
122
546
|
this.adapter = null;
|
|
123
547
|
}
|
|
124
548
|
this.status.connected = false;
|
|
125
549
|
}
|
|
126
550
|
async send(message) {
|
|
127
|
-
if (!this.adapter) {
|
|
551
|
+
if (!this.adapter || !this.adapter.isRunning()) {
|
|
128
552
|
return { success: false, error: 'Not connected', timestamp: new Date() };
|
|
129
553
|
}
|
|
130
554
|
const target = message.channelId || this.config.channels?.[0] || '';
|
|
131
|
-
|
|
132
|
-
|
|
555
|
+
try {
|
|
556
|
+
const result = await this.adapter.sendMessage(target, message.content);
|
|
557
|
+
this.status.lastActivity = new Date();
|
|
558
|
+
return { success: result.success, timestamp: new Date() };
|
|
559
|
+
}
|
|
560
|
+
catch (error) {
|
|
561
|
+
return {
|
|
562
|
+
success: false,
|
|
563
|
+
error: error instanceof Error ? error.message : String(error),
|
|
564
|
+
timestamp: new Date(),
|
|
565
|
+
};
|
|
566
|
+
}
|
|
133
567
|
}
|
|
134
568
|
}
|
|
569
|
+
export default IRCAdapter;
|
|
135
570
|
//# sourceMappingURL=index.js.map
|
|
@@ -88,6 +88,9 @@ export class LINEChannel extends BaseChannel {
|
|
|
88
88
|
port: config.port,
|
|
89
89
|
});
|
|
90
90
|
}
|
|
91
|
+
// REST/webhook adapter — outbound via the LINE Messaging API, inbound via
|
|
92
|
+
// webhook callbacks; no persistent connection is held open, so reconnection
|
|
93
|
+
// (ReconnectionManager) is N/A.
|
|
91
94
|
async connect() {
|
|
92
95
|
await this.adapter.start();
|
|
93
96
|
this.status.connected = true;
|
|
@@ -1,20 +1,46 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Mattermost Channel Adapter
|
|
3
3
|
*
|
|
4
|
-
* Connects to Mattermost via
|
|
5
|
-
*
|
|
4
|
+
* Connects to Mattermost via its real-time WebSocket gateway
|
|
5
|
+
* (`/api/v4/websocket`) for inbound events and the REST API
|
|
6
|
+
* (`/api/v4/posts`) for outbound messages.
|
|
7
|
+
*
|
|
8
|
+
* Transport details:
|
|
9
|
+
* - Auth: a personal-access / bot token (`config.token`) is sent both as a
|
|
10
|
+
* `Bearer` header on REST calls and via the WebSocket
|
|
11
|
+
* `authentication_challenge` action after the socket opens.
|
|
12
|
+
* - Inbound: the WS pushes JSON events; `posted` events carry the new post as
|
|
13
|
+
* a nested JSON string which we parse into an `InboundMessage`.
|
|
14
|
+
* - Outbound: `send()` POSTs `{channel_id, message}` to `/api/v4/posts`.
|
|
15
|
+
* - Resilience: unintended socket drops are recovered through the shared
|
|
16
|
+
* `ReconnectionManager` (exponential backoff + jitter), the same idiom used
|
|
17
|
+
* by the discord / slack / imessage adapters. An explicit `disconnect()`
|
|
18
|
+
* sets a `closing` flag and cancels any pending reconnect so a deliberate
|
|
19
|
+
* close never spins the backoff loop.
|
|
6
20
|
*/
|
|
7
21
|
import { BaseChannel, ChannelConfig, DeliveryResult, OutboundMessage } from '../core.js';
|
|
8
22
|
export interface MattermostConfig {
|
|
9
23
|
url: string;
|
|
10
24
|
token: string;
|
|
11
25
|
teamId?: string;
|
|
26
|
+
maxRetries?: number;
|
|
27
|
+
retryDelay?: number;
|
|
12
28
|
}
|
|
13
29
|
export interface MattermostChannelConfig extends ChannelConfig {
|
|
14
30
|
url: string;
|
|
15
31
|
token: string;
|
|
16
32
|
teamId?: string;
|
|
33
|
+
maxRetries?: number;
|
|
34
|
+
retryDelay?: number;
|
|
17
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* Legacy in-process adapter.
|
|
38
|
+
*
|
|
39
|
+
* @deprecated The real transport now lives in {@link MattermostChannel}, which
|
|
40
|
+
* opens a genuine WebSocket and POSTs through the REST API. This class is kept
|
|
41
|
+
* only for backward-compatible imports (`channels/index.ts` re-export and the
|
|
42
|
+
* `new-channels.test.ts` lifecycle suite) and performs no network I/O.
|
|
43
|
+
*/
|
|
18
44
|
export declare class MattermostAdapter {
|
|
19
45
|
private config;
|
|
20
46
|
private running;
|
|
@@ -38,10 +64,31 @@ export declare class MattermostAdapter {
|
|
|
38
64
|
getConfig(): MattermostConfig;
|
|
39
65
|
}
|
|
40
66
|
export declare class MattermostChannel extends BaseChannel {
|
|
41
|
-
private
|
|
67
|
+
private readonly mmConfig;
|
|
68
|
+
private ws;
|
|
69
|
+
private readonly reconnectionManager;
|
|
70
|
+
/** Monotonic sequence number for outbound WS actions. */
|
|
71
|
+
private seq;
|
|
72
|
+
/** True once `disconnect()` is in progress so drops aren't treated as faults. */
|
|
73
|
+
private closing;
|
|
74
|
+
/** Guards against resolving the connect() promise more than once. */
|
|
75
|
+
private connectSettled;
|
|
42
76
|
constructor(config: MattermostChannelConfig);
|
|
43
77
|
connect(): Promise<void>;
|
|
44
78
|
disconnect(): Promise<void>;
|
|
45
79
|
send(message: OutboundMessage): Promise<DeliveryResult>;
|
|
80
|
+
private openSocket;
|
|
81
|
+
private handleWsEvent;
|
|
82
|
+
private onAuthenticated;
|
|
83
|
+
private handlePostedEvent;
|
|
84
|
+
/**
|
|
85
|
+
* Handle a socket drop. If the close was intentional (`disconnect()`), do
|
|
86
|
+
* nothing. Otherwise schedule a reconnect through the shared manager.
|
|
87
|
+
*/
|
|
88
|
+
private handleDrop;
|
|
89
|
+
private sendAction;
|
|
90
|
+
/** Expose the resolved WS gateway URL (handy for diagnostics / tests). */
|
|
91
|
+
getWebSocketUrl(): string;
|
|
92
|
+
getConfig(): MattermostConfig;
|
|
46
93
|
}
|
|
47
|
-
export default
|
|
94
|
+
export default MattermostChannel;
|