@a1hvdy/cc-openclaw 0.3.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.
- package/LICENSE +22 -0
- package/README.md +207 -0
- package/configs/.gitkeep +0 -0
- package/configs/council-reviewer-prompt.md +82 -0
- package/configs/council-system-prompt.md +141 -0
- package/dist/scripts/bench/ab-harness.d.ts +58 -0
- package/dist/scripts/bench/ab-harness.d.ts.map +1 -0
- package/dist/scripts/bench/ab-harness.js +78 -0
- package/dist/scripts/bench/ab-harness.js.map +1 -0
- package/dist/src/channels/adapter.d.ts +103 -0
- package/dist/src/channels/adapter.d.ts.map +1 -0
- package/dist/src/channels/adapter.js +38 -0
- package/dist/src/channels/adapter.js.map +1 -0
- package/dist/src/channels/telegram/completion-summary.d.ts +22 -0
- package/dist/src/channels/telegram/completion-summary.d.ts.map +1 -0
- package/dist/src/channels/telegram/completion-summary.js +186 -0
- package/dist/src/channels/telegram/completion-summary.js.map +1 -0
- package/dist/src/channels/telegram/error-renderer.d.ts +30 -0
- package/dist/src/channels/telegram/error-renderer.d.ts.map +1 -0
- package/dist/src/channels/telegram/error-renderer.js +133 -0
- package/dist/src/channels/telegram/error-renderer.js.map +1 -0
- package/dist/src/channels/telegram/event-reducer.d.ts +34 -0
- package/dist/src/channels/telegram/event-reducer.d.ts.map +1 -0
- package/dist/src/channels/telegram/event-reducer.js +579 -0
- package/dist/src/channels/telegram/event-reducer.js.map +1 -0
- package/dist/src/channels/telegram/index.d.ts +14 -0
- package/dist/src/channels/telegram/index.d.ts.map +1 -0
- package/dist/src/channels/telegram/index.js +14 -0
- package/dist/src/channels/telegram/index.js.map +1 -0
- package/dist/src/channels/telegram/injector.d.ts +54 -0
- package/dist/src/channels/telegram/injector.d.ts.map +1 -0
- package/dist/src/channels/telegram/injector.js +200 -0
- package/dist/src/channels/telegram/injector.js.map +1 -0
- package/dist/src/channels/telegram/live-card.d.ts +230 -0
- package/dist/src/channels/telegram/live-card.d.ts.map +1 -0
- package/dist/src/channels/telegram/live-card.js +916 -0
- package/dist/src/channels/telegram/live-card.js.map +1 -0
- package/dist/src/channels/telegram/state-machine.d.ts +23 -0
- package/dist/src/channels/telegram/state-machine.d.ts.map +1 -0
- package/dist/src/channels/telegram/state-machine.js +72 -0
- package/dist/src/channels/telegram/state-machine.js.map +1 -0
- package/dist/src/channels/telegram/tool-tracker.d.ts +147 -0
- package/dist/src/channels/telegram/tool-tracker.d.ts.map +1 -0
- package/dist/src/channels/telegram/tool-tracker.js +520 -0
- package/dist/src/channels/telegram/tool-tracker.js.map +1 -0
- package/dist/src/circuit-breaker.d.ts +22 -0
- package/dist/src/circuit-breaker.d.ts.map +1 -0
- package/dist/src/circuit-breaker.js +47 -0
- package/dist/src/circuit-breaker.js.map +1 -0
- package/dist/src/command-router/cc-handler.d.ts +67 -0
- package/dist/src/command-router/cc-handler.d.ts.map +1 -0
- package/dist/src/command-router/cc-handler.js +980 -0
- package/dist/src/command-router/cc-handler.js.map +1 -0
- package/dist/src/command-router/index.d.ts +3 -0
- package/dist/src/command-router/index.d.ts.map +1 -0
- package/dist/src/command-router/index.js +2 -0
- package/dist/src/command-router/index.js.map +1 -0
- package/dist/src/constants.d.ts +132 -0
- package/dist/src/constants.d.ts.map +1 -0
- package/dist/src/constants.js +140 -0
- package/dist/src/constants.js.map +1 -0
- package/dist/src/council/consensus.d.ts +21 -0
- package/dist/src/council/consensus.d.ts.map +1 -0
- package/dist/src/council/consensus.js +52 -0
- package/dist/src/council/consensus.js.map +1 -0
- package/dist/src/council/council.d.ts +68 -0
- package/dist/src/council/council.d.ts.map +1 -0
- package/dist/src/council/council.js +914 -0
- package/dist/src/council/council.js.map +1 -0
- package/dist/src/council/index.d.ts +3 -0
- package/dist/src/council/index.d.ts.map +1 -0
- package/dist/src/council/index.js +3 -0
- package/dist/src/council/index.js.map +1 -0
- package/dist/src/engines/base-oneshot-session.d.ts +88 -0
- package/dist/src/engines/base-oneshot-session.d.ts.map +1 -0
- package/dist/src/engines/base-oneshot-session.js +228 -0
- package/dist/src/engines/base-oneshot-session.js.map +1 -0
- package/dist/src/engines/index.d.ts +7 -0
- package/dist/src/engines/index.d.ts.map +1 -0
- package/dist/src/engines/index.js +7 -0
- package/dist/src/engines/index.js.map +1 -0
- package/dist/src/engines/persistent-codex-session.d.ts +17 -0
- package/dist/src/engines/persistent-codex-session.d.ts.map +1 -0
- package/dist/src/engines/persistent-codex-session.js +106 -0
- package/dist/src/engines/persistent-codex-session.js.map +1 -0
- package/dist/src/engines/persistent-cursor-session.d.ts +22 -0
- package/dist/src/engines/persistent-cursor-session.d.ts.map +1 -0
- package/dist/src/engines/persistent-cursor-session.js +242 -0
- package/dist/src/engines/persistent-cursor-session.js.map +1 -0
- package/dist/src/engines/persistent-custom-session.d.ts +79 -0
- package/dist/src/engines/persistent-custom-session.d.ts.map +1 -0
- package/dist/src/engines/persistent-custom-session.js +939 -0
- package/dist/src/engines/persistent-custom-session.js.map +1 -0
- package/dist/src/engines/persistent-gemini-session.d.ts +22 -0
- package/dist/src/engines/persistent-gemini-session.d.ts.map +1 -0
- package/dist/src/engines/persistent-gemini-session.js +217 -0
- package/dist/src/engines/persistent-gemini-session.js.map +1 -0
- package/dist/src/engines/persistent-session.d.ts +77 -0
- package/dist/src/engines/persistent-session.d.ts.map +1 -0
- package/dist/src/engines/persistent-session.js +730 -0
- package/dist/src/engines/persistent-session.js.map +1 -0
- package/dist/src/health/handler.d.ts +40 -0
- package/dist/src/health/handler.d.ts.map +1 -0
- package/dist/src/health/handler.js +70 -0
- package/dist/src/health/handler.js.map +1 -0
- package/dist/src/health/index.d.ts +2 -0
- package/dist/src/health/index.d.ts.map +1 -0
- package/dist/src/health/index.js +2 -0
- package/dist/src/health/index.js.map +1 -0
- package/dist/src/index.d.ts +49 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +84 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/lib/auto-recovery.d.ts +45 -0
- package/dist/src/lib/auto-recovery.d.ts.map +1 -0
- package/dist/src/lib/auto-recovery.js +217 -0
- package/dist/src/lib/auto-recovery.js.map +1 -0
- package/dist/src/lib/cache-parity.d.ts +39 -0
- package/dist/src/lib/cache-parity.d.ts.map +1 -0
- package/dist/src/lib/cache-parity.js +92 -0
- package/dist/src/lib/cache-parity.js.map +1 -0
- package/dist/src/lib/circuit-breaker.d.ts +22 -0
- package/dist/src/lib/circuit-breaker.d.ts.map +1 -0
- package/dist/src/lib/circuit-breaker.js +47 -0
- package/dist/src/lib/circuit-breaker.js.map +1 -0
- package/dist/src/lib/config.d.ts +74 -0
- package/dist/src/lib/config.d.ts.map +1 -0
- package/dist/src/lib/config.js +244 -0
- package/dist/src/lib/config.js.map +1 -0
- package/dist/src/lib/drift-detector.d.ts +47 -0
- package/dist/src/lib/drift-detector.d.ts.map +1 -0
- package/dist/src/lib/drift-detector.js +192 -0
- package/dist/src/lib/drift-detector.js.map +1 -0
- package/dist/src/lib/error-formatter.d.ts +78 -0
- package/dist/src/lib/error-formatter.d.ts.map +1 -0
- package/dist/src/lib/error-formatter.js +149 -0
- package/dist/src/lib/error-formatter.js.map +1 -0
- package/dist/src/lib/heartbeat-workaround.d.ts +45 -0
- package/dist/src/lib/heartbeat-workaround.d.ts.map +1 -0
- package/dist/src/lib/heartbeat-workaround.js +61 -0
- package/dist/src/lib/heartbeat-workaround.js.map +1 -0
- package/dist/src/lib/index.d.ts +8 -0
- package/dist/src/lib/index.d.ts.map +1 -0
- package/dist/src/lib/index.js +8 -0
- package/dist/src/lib/index.js.map +1 -0
- package/dist/src/lib/register-guard.d.ts +49 -0
- package/dist/src/lib/register-guard.d.ts.map +1 -0
- package/dist/src/lib/register-guard.js +73 -0
- package/dist/src/lib/register-guard.js.map +1 -0
- package/dist/src/lib/route-flag.d.ts +50 -0
- package/dist/src/lib/route-flag.d.ts.map +1 -0
- package/dist/src/lib/route-flag.js +52 -0
- package/dist/src/lib/route-flag.js.map +1 -0
- package/dist/src/lib/sysprompt-strip.d.ts +54 -0
- package/dist/src/lib/sysprompt-strip.d.ts.map +1 -0
- package/dist/src/lib/sysprompt-strip.js +75 -0
- package/dist/src/lib/sysprompt-strip.js.map +1 -0
- package/dist/src/lib/telemetry.d.ts +39 -0
- package/dist/src/lib/telemetry.d.ts.map +1 -0
- package/dist/src/lib/telemetry.js +73 -0
- package/dist/src/lib/telemetry.js.map +1 -0
- package/dist/src/lib/test-mode.d.ts +27 -0
- package/dist/src/lib/test-mode.d.ts.map +1 -0
- package/dist/src/lib/test-mode.js +38 -0
- package/dist/src/lib/test-mode.js.map +1 -0
- package/dist/src/lib/vendor-paths.d.ts +15 -0
- package/dist/src/lib/vendor-paths.d.ts.map +1 -0
- package/dist/src/lib/vendor-paths.js +32 -0
- package/dist/src/lib/vendor-paths.js.map +1 -0
- package/dist/src/logger.d.ts +17 -0
- package/dist/src/logger.d.ts.map +1 -0
- package/dist/src/logger.js +46 -0
- package/dist/src/logger.js.map +1 -0
- package/dist/src/mcp/bridge.d.ts +22 -0
- package/dist/src/mcp/bridge.d.ts.map +1 -0
- package/dist/src/mcp/bridge.js +78 -0
- package/dist/src/mcp/bridge.js.map +1 -0
- package/dist/src/mcp/index.d.ts +3 -0
- package/dist/src/mcp/index.d.ts.map +1 -0
- package/dist/src/mcp/index.js +2 -0
- package/dist/src/mcp/index.js.map +1 -0
- package/dist/src/models.d.ts +70 -0
- package/dist/src/models.d.ts.map +1 -0
- package/dist/src/models.js +289 -0
- package/dist/src/models.js.map +1 -0
- package/dist/src/openai-compat/cli-stream-parser.d.ts +135 -0
- package/dist/src/openai-compat/cli-stream-parser.d.ts.map +1 -0
- package/dist/src/openai-compat/cli-stream-parser.js +195 -0
- package/dist/src/openai-compat/cli-stream-parser.js.map +1 -0
- package/dist/src/openai-compat/index.d.ts +2 -0
- package/dist/src/openai-compat/index.d.ts.map +1 -0
- package/dist/src/openai-compat/index.js +2 -0
- package/dist/src/openai-compat/index.js.map +1 -0
- package/dist/src/openai-compat/openai-compat.d.ts +281 -0
- package/dist/src/openai-compat/openai-compat.d.ts.map +1 -0
- package/dist/src/openai-compat/openai-compat.js +939 -0
- package/dist/src/openai-compat/openai-compat.js.map +1 -0
- package/dist/src/openai-compat/skill-resolver.d.ts +36 -0
- package/dist/src/openai-compat/skill-resolver.d.ts.map +1 -0
- package/dist/src/openai-compat/skill-resolver.js +134 -0
- package/dist/src/openai-compat/skill-resolver.js.map +1 -0
- package/dist/src/openai-compat/sse-translator.d.ts +32 -0
- package/dist/src/openai-compat/sse-translator.d.ts.map +1 -0
- package/dist/src/openai-compat/sse-translator.js +155 -0
- package/dist/src/openai-compat/sse-translator.js.map +1 -0
- package/dist/src/proxy/anthropic-adapter.d.ts +137 -0
- package/dist/src/proxy/anthropic-adapter.d.ts.map +1 -0
- package/dist/src/proxy/anthropic-adapter.js +392 -0
- package/dist/src/proxy/anthropic-adapter.js.map +1 -0
- package/dist/src/proxy/handler.d.ts +40 -0
- package/dist/src/proxy/handler.d.ts.map +1 -0
- package/dist/src/proxy/handler.js +378 -0
- package/dist/src/proxy/handler.js.map +1 -0
- package/dist/src/proxy/index.d.ts +5 -0
- package/dist/src/proxy/index.d.ts.map +1 -0
- package/dist/src/proxy/index.js +5 -0
- package/dist/src/proxy/index.js.map +1 -0
- package/dist/src/proxy/schema-cleaner.d.ts +12 -0
- package/dist/src/proxy/schema-cleaner.d.ts.map +1 -0
- package/dist/src/proxy/schema-cleaner.js +34 -0
- package/dist/src/proxy/schema-cleaner.js.map +1 -0
- package/dist/src/proxy/thought-cache.d.ts +20 -0
- package/dist/src/proxy/thought-cache.d.ts.map +1 -0
- package/dist/src/proxy/thought-cache.js +53 -0
- package/dist/src/proxy/thought-cache.js.map +1 -0
- package/dist/src/session/embedded-server.d.ts +26 -0
- package/dist/src/session/embedded-server.d.ts.map +1 -0
- package/dist/src/session/embedded-server.js +367 -0
- package/dist/src/session/embedded-server.js.map +1 -0
- package/dist/src/session/inbox-manager.d.ts +39 -0
- package/dist/src/session/inbox-manager.d.ts.map +1 -0
- package/dist/src/session/inbox-manager.js +111 -0
- package/dist/src/session/inbox-manager.js.map +1 -0
- package/dist/src/session/index.d.ts +4 -0
- package/dist/src/session/index.d.ts.map +1 -0
- package/dist/src/session/index.js +4 -0
- package/dist/src/session/index.js.map +1 -0
- package/dist/src/session/session-manager.d.ts +212 -0
- package/dist/src/session/session-manager.d.ts.map +1 -0
- package/dist/src/session/session-manager.js +1351 -0
- package/dist/src/session/session-manager.js.map +1 -0
- package/dist/src/session-bootstrap/cwd-patch.d.ts +51 -0
- package/dist/src/session-bootstrap/cwd-patch.d.ts.map +1 -0
- package/dist/src/session-bootstrap/cwd-patch.js +955 -0
- package/dist/src/session-bootstrap/cwd-patch.js.map +1 -0
- package/dist/src/session-bootstrap/index.d.ts +4 -0
- package/dist/src/session-bootstrap/index.d.ts.map +1 -0
- package/dist/src/session-bootstrap/index.js +4 -0
- package/dist/src/session-bootstrap/index.js.map +1 -0
- package/dist/src/session-bootstrap/sysprompt-strip.d.ts +26 -0
- package/dist/src/session-bootstrap/sysprompt-strip.d.ts.map +1 -0
- package/dist/src/session-bootstrap/sysprompt-strip.js +57 -0
- package/dist/src/session-bootstrap/sysprompt-strip.js.map +1 -0
- package/dist/src/session-bootstrap/think-conflict-resolver.d.ts +33 -0
- package/dist/src/session-bootstrap/think-conflict-resolver.d.ts.map +1 -0
- package/dist/src/session-bootstrap/think-conflict-resolver.js +234 -0
- package/dist/src/session-bootstrap/think-conflict-resolver.js.map +1 -0
- package/dist/src/types.d.ts +489 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +8 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/validation.d.ts +32 -0
- package/dist/src/validation.d.ts.map +1 -0
- package/dist/src/validation.js +104 -0
- package/dist/src/validation.js.map +1 -0
- package/dist/tests/_helpers/subprocess-mock.d.ts +35 -0
- package/dist/tests/_helpers/subprocess-mock.d.ts.map +1 -0
- package/dist/tests/_helpers/subprocess-mock.js +136 -0
- package/dist/tests/_helpers/subprocess-mock.js.map +1 -0
- package/dist/tests/auto-recovery.test.d.ts +2 -0
- package/dist/tests/auto-recovery.test.d.ts.map +1 -0
- package/dist/tests/auto-recovery.test.js +189 -0
- package/dist/tests/auto-recovery.test.js.map +1 -0
- package/dist/tests/bench-harness.test.d.ts +2 -0
- package/dist/tests/bench-harness.test.d.ts.map +1 -0
- package/dist/tests/bench-harness.test.js +21 -0
- package/dist/tests/bench-harness.test.js.map +1 -0
- package/dist/tests/cache-parity.test.d.ts +2 -0
- package/dist/tests/cache-parity.test.d.ts.map +1 -0
- package/dist/tests/cache-parity.test.js +401 -0
- package/dist/tests/cache-parity.test.js.map +1 -0
- package/dist/tests/command-router.test.d.ts +2 -0
- package/dist/tests/command-router.test.d.ts.map +1 -0
- package/dist/tests/command-router.test.js +60 -0
- package/dist/tests/command-router.test.js.map +1 -0
- package/dist/tests/council.test.d.ts +2 -0
- package/dist/tests/council.test.d.ts.map +1 -0
- package/dist/tests/council.test.js +20 -0
- package/dist/tests/council.test.js.map +1 -0
- package/dist/tests/drift-detector.test.d.ts +2 -0
- package/dist/tests/drift-detector.test.d.ts.map +1 -0
- package/dist/tests/drift-detector.test.js +268 -0
- package/dist/tests/drift-detector.test.js.map +1 -0
- package/dist/tests/eager-bootstrap-gating.test.d.ts +9 -0
- package/dist/tests/eager-bootstrap-gating.test.d.ts.map +1 -0
- package/dist/tests/eager-bootstrap-gating.test.js +97 -0
- package/dist/tests/eager-bootstrap-gating.test.js.map +1 -0
- package/dist/tests/engines.test.d.ts +2 -0
- package/dist/tests/engines.test.d.ts.map +1 -0
- package/dist/tests/engines.test.js +8 -0
- package/dist/tests/engines.test.js.map +1 -0
- package/dist/tests/error-formatter.test.d.ts +2 -0
- package/dist/tests/error-formatter.test.d.ts.map +1 -0
- package/dist/tests/error-formatter.test.js +220 -0
- package/dist/tests/error-formatter.test.js.map +1 -0
- package/dist/tests/health.test.d.ts +2 -0
- package/dist/tests/health.test.d.ts.map +1 -0
- package/dist/tests/health.test.js +110 -0
- package/dist/tests/health.test.js.map +1 -0
- package/dist/tests/heartbeat-workaround.test.d.ts +2 -0
- package/dist/tests/heartbeat-workaround.test.d.ts.map +1 -0
- package/dist/tests/heartbeat-workaround.test.js +90 -0
- package/dist/tests/heartbeat-workaround.test.js.map +1 -0
- package/dist/tests/index.test.d.ts +2 -0
- package/dist/tests/index.test.d.ts.map +1 -0
- package/dist/tests/index.test.js +7 -0
- package/dist/tests/index.test.js.map +1 -0
- package/dist/tests/lib-sysprompt-strip.test.d.ts +2 -0
- package/dist/tests/lib-sysprompt-strip.test.d.ts.map +1 -0
- package/dist/tests/lib-sysprompt-strip.test.js +145 -0
- package/dist/tests/lib-sysprompt-strip.test.js.map +1 -0
- package/dist/tests/listener-activation.test.d.ts +2 -0
- package/dist/tests/listener-activation.test.d.ts.map +1 -0
- package/dist/tests/listener-activation.test.js +87 -0
- package/dist/tests/listener-activation.test.js.map +1 -0
- package/dist/tests/mcp-bridge.test.d.ts +2 -0
- package/dist/tests/mcp-bridge.test.d.ts.map +1 -0
- package/dist/tests/mcp-bridge.test.js +137 -0
- package/dist/tests/mcp-bridge.test.js.map +1 -0
- package/dist/tests/openai-compat.test.d.ts +2 -0
- package/dist/tests/openai-compat.test.d.ts.map +1 -0
- package/dist/tests/openai-compat.test.js +8 -0
- package/dist/tests/openai-compat.test.js.map +1 -0
- package/dist/tests/proxy-heartbeat-integration.test.d.ts +15 -0
- package/dist/tests/proxy-heartbeat-integration.test.d.ts.map +1 -0
- package/dist/tests/proxy-heartbeat-integration.test.js +122 -0
- package/dist/tests/proxy-heartbeat-integration.test.js.map +1 -0
- package/dist/tests/proxy.test.d.ts +2 -0
- package/dist/tests/proxy.test.d.ts.map +1 -0
- package/dist/tests/proxy.test.js +8 -0
- package/dist/tests/proxy.test.js.map +1 -0
- package/dist/tests/register-guard-stacking.test.d.ts +2 -0
- package/dist/tests/register-guard-stacking.test.d.ts.map +1 -0
- package/dist/tests/register-guard-stacking.test.js +61 -0
- package/dist/tests/register-guard-stacking.test.js.map +1 -0
- package/dist/tests/register-guard.test.d.ts +2 -0
- package/dist/tests/register-guard.test.d.ts.map +1 -0
- package/dist/tests/register-guard.test.js +129 -0
- package/dist/tests/register-guard.test.js.map +1 -0
- package/dist/tests/route-flag-rollback.test.d.ts +2 -0
- package/dist/tests/route-flag-rollback.test.d.ts.map +1 -0
- package/dist/tests/route-flag-rollback.test.js +70 -0
- package/dist/tests/route-flag-rollback.test.js.map +1 -0
- package/dist/tests/route-flag.test.d.ts +2 -0
- package/dist/tests/route-flag.test.d.ts.map +1 -0
- package/dist/tests/route-flag.test.js +101 -0
- package/dist/tests/route-flag.test.js.map +1 -0
- package/dist/tests/session-bootstrap.test.d.ts +2 -0
- package/dist/tests/session-bootstrap.test.d.ts.map +1 -0
- package/dist/tests/session-bootstrap.test.js +183 -0
- package/dist/tests/session-bootstrap.test.js.map +1 -0
- package/dist/tests/session.test.d.ts +2 -0
- package/dist/tests/session.test.d.ts.map +1 -0
- package/dist/tests/session.test.js +17 -0
- package/dist/tests/session.test.js.map +1 -0
- package/dist/tests/state-machine.test.d.ts +2 -0
- package/dist/tests/state-machine.test.d.ts.map +1 -0
- package/dist/tests/state-machine.test.js +133 -0
- package/dist/tests/state-machine.test.js.map +1 -0
- package/dist/tests/streaming/cli-stream-parser.test.d.ts +2 -0
- package/dist/tests/streaming/cli-stream-parser.test.d.ts.map +1 -0
- package/dist/tests/streaming/cli-stream-parser.test.js +233 -0
- package/dist/tests/streaming/cli-stream-parser.test.js.map +1 -0
- package/dist/tests/streaming/feature-flag.test.d.ts +14 -0
- package/dist/tests/streaming/feature-flag.test.d.ts.map +1 -0
- package/dist/tests/streaming/feature-flag.test.js +163 -0
- package/dist/tests/streaming/feature-flag.test.js.map +1 -0
- package/dist/tests/streaming/no-tools-prompt.test.d.ts +17 -0
- package/dist/tests/streaming/no-tools-prompt.test.d.ts.map +1 -0
- package/dist/tests/streaming/no-tools-prompt.test.js +229 -0
- package/dist/tests/streaming/no-tools-prompt.test.js.map +1 -0
- package/dist/tests/streaming/skill-plus-tools.test.d.ts +14 -0
- package/dist/tests/streaming/skill-plus-tools.test.d.ts.map +1 -0
- package/dist/tests/streaming/skill-plus-tools.test.js +234 -0
- package/dist/tests/streaming/skill-plus-tools.test.js.map +1 -0
- package/dist/tests/streaming/sse-translator.test.d.ts +2 -0
- package/dist/tests/streaming/sse-translator.test.d.ts.map +1 -0
- package/dist/tests/streaming/sse-translator.test.js +227 -0
- package/dist/tests/streaming/sse-translator.test.js.map +1 -0
- package/dist/tests/streaming/tool-result-roundtrip.test.d.ts +11 -0
- package/dist/tests/streaming/tool-result-roundtrip.test.d.ts.map +1 -0
- package/dist/tests/streaming/tool-result-roundtrip.test.js +215 -0
- package/dist/tests/streaming/tool-result-roundtrip.test.js.map +1 -0
- package/dist/tests/streaming/tool-use-translation.test.d.ts +10 -0
- package/dist/tests/streaming/tool-use-translation.test.d.ts.map +1 -0
- package/dist/tests/streaming/tool-use-translation.test.js +251 -0
- package/dist/tests/streaming/tool-use-translation.test.js.map +1 -0
- package/dist/tests/telegram-bridge.test.d.ts +2 -0
- package/dist/tests/telegram-bridge.test.d.ts.map +1 -0
- package/dist/tests/telegram-bridge.test.js +17 -0
- package/dist/tests/telegram-bridge.test.js.map +1 -0
- package/dist/tests/telegram-injector.test.d.ts +2 -0
- package/dist/tests/telegram-injector.test.d.ts.map +1 -0
- package/dist/tests/telegram-injector.test.js +74 -0
- package/dist/tests/telegram-injector.test.js.map +1 -0
- package/dist/tests/telemetry.test.d.ts +2 -0
- package/dist/tests/telemetry.test.d.ts.map +1 -0
- package/dist/tests/telemetry.test.js +405 -0
- package/dist/tests/telemetry.test.js.map +1 -0
- package/dist/tests/test-mode.test.d.ts +2 -0
- package/dist/tests/test-mode.test.d.ts.map +1 -0
- package/dist/tests/test-mode.test.js +39 -0
- package/dist/tests/test-mode.test.js.map +1 -0
- package/mcp-config.template.json +13 -0
- package/mcp-tools.json +1 -0
- package/openclaw-mcp-bridge.cjs +152 -0
- package/openclaw.plugin.json +30 -0
- package/package.json +45 -0
- package/skills/.gitkeep +0 -0
- package/stubs/commands-status-deps.runtime.js +10 -0
- package/stubs/status.runtime.js +149 -0
- package/vendor/base-oneshot-session.d.ts +87 -0
- package/vendor/base-oneshot-session.js +227 -0
- package/vendor/base-oneshot-session.js.map +1 -0
- package/vendor/circuit-breaker.d.ts +21 -0
- package/vendor/circuit-breaker.js +47 -0
- package/vendor/circuit-breaker.js.map +1 -0
- package/vendor/consensus.d.ts +20 -0
- package/vendor/consensus.js +52 -0
- package/vendor/consensus.js.map +1 -0
- package/vendor/constants.d.ts +130 -0
- package/vendor/constants.js +139 -0
- package/vendor/constants.js.map +1 -0
- package/vendor/council.d.ts +67 -0
- package/vendor/council.js +913 -0
- package/vendor/council.js.map +1 -0
- package/vendor/embedded-server.d.ts +25 -0
- package/vendor/embedded-server.js +360 -0
- package/vendor/embedded-server.js.map +1 -0
- package/vendor/inbox-manager.d.ts +38 -0
- package/vendor/inbox-manager.js +111 -0
- package/vendor/inbox-manager.js.map +1 -0
- package/vendor/index.d.ts +63 -0
- package/vendor/index.js +705 -0
- package/vendor/index.js.map +1 -0
- package/vendor/logger.d.ts +16 -0
- package/vendor/logger.js +44 -0
- package/vendor/logger.js.map +1 -0
- package/vendor/models.d.ts +69 -0
- package/vendor/models.js +289 -0
- package/vendor/models.js.map +1 -0
- package/vendor/openai-compat.d.ts +197 -0
- package/vendor/openai-compat.js +721 -0
- package/vendor/openai-compat.js.map +1 -0
- package/vendor/persistent-codex-session.d.ts +16 -0
- package/vendor/persistent-codex-session.js +105 -0
- package/vendor/persistent-codex-session.js.map +1 -0
- package/vendor/persistent-cursor-session.d.ts +21 -0
- package/vendor/persistent-cursor-session.js +241 -0
- package/vendor/persistent-cursor-session.js.map +1 -0
- package/vendor/persistent-custom-session.d.ts +78 -0
- package/vendor/persistent-custom-session.js +937 -0
- package/vendor/persistent-custom-session.js.map +1 -0
- package/vendor/persistent-gemini-session.d.ts +21 -0
- package/vendor/persistent-gemini-session.js +216 -0
- package/vendor/persistent-gemini-session.js.map +1 -0
- package/vendor/persistent-session.d.ts +74 -0
- package/vendor/persistent-session.js +684 -0
- package/vendor/persistent-session.js.map +1 -0
- package/vendor/proxy/anthropic-adapter.d.ts +136 -0
- package/vendor/proxy/anthropic-adapter.js +392 -0
- package/vendor/proxy/anthropic-adapter.js.map +1 -0
- package/vendor/proxy/handler.d.ts +39 -0
- package/vendor/proxy/handler.js +323 -0
- package/vendor/proxy/handler.js.map +1 -0
- package/vendor/proxy/schema-cleaner.d.ts +11 -0
- package/vendor/proxy/schema-cleaner.js +34 -0
- package/vendor/proxy/schema-cleaner.js.map +1 -0
- package/vendor/proxy/thought-cache.d.ts +19 -0
- package/vendor/proxy/thought-cache.js +53 -0
- package/vendor/proxy/thought-cache.js.map +1 -0
- package/vendor/session-manager.d.ts +211 -0
- package/vendor/session-manager.js +1345 -0
- package/vendor/session-manager.js.map +1 -0
- package/vendor/skill-resolver.js +107 -0
- package/vendor/types.d.ts +466 -0
- package/vendor/types.js +8 -0
- package/vendor/types.js.map +1 -0
- package/vendor/validation.d.ts +31 -0
- package/vendor/validation.js +104 -0
- package/vendor/validation.js.map +1 -0
|
@@ -0,0 +1,980 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cc-handler — OpenClaw plugin (TypeScript port of savvy-claude-code/cc-handler.js)
|
|
3
|
+
*
|
|
4
|
+
* Intercepts /cc <prompt> messages from Telegram via the before_dispatch hook,
|
|
5
|
+
* launches a Claude Code session through SessionManager, and lets the existing
|
|
6
|
+
* telegram-ux-bridge handle live card delivery.
|
|
7
|
+
*
|
|
8
|
+
* Supports per-session chat targeting and continuation:
|
|
9
|
+
* /cc <prompt> — start a new Claude Code session
|
|
10
|
+
* /cc+ <prompt> — continue the last session in this chat
|
|
11
|
+
* /cc continue <prompt> — alternative continuation syntax
|
|
12
|
+
* /cc status — check active session for this chat
|
|
13
|
+
* /cc stop — stop the active session for this chat
|
|
14
|
+
* /cc resume [id|slug] — resume by session ID (8-char hex) or slug
|
|
15
|
+
* /cc list — show resumable sessions for this chat
|
|
16
|
+
*/
|
|
17
|
+
import * as fs from 'node:fs';
|
|
18
|
+
import * as path from 'node:path';
|
|
19
|
+
import * as crypto from 'node:crypto';
|
|
20
|
+
import { request as httpsRequest } from 'node:https';
|
|
21
|
+
import { defaultRegisterGuard } from '../lib/register-guard.js';
|
|
22
|
+
import { VENDOR_FILES } from '../lib/vendor-paths.js';
|
|
23
|
+
// ── Constants ─────────────────────────────────────────────────────────────
|
|
24
|
+
const PLUGIN_TAG = '[cc-openclaw/command-router]';
|
|
25
|
+
const CC_PLUGIN_PATH = VENDOR_FILES.sessionManager;
|
|
26
|
+
const SESSIONS_DIR = '/home/a1xai/.openclaw/sessions';
|
|
27
|
+
const SESSIONS_INDEX_PATH = path.join(SESSIONS_DIR, 'index.json');
|
|
28
|
+
const DEFAULT_CWD = '/home/a1xai/.openclaw';
|
|
29
|
+
const IDLE_TIMEOUT_MS = 15 * 60 * 1000; // 15 min
|
|
30
|
+
const ACK_TIMEOUT_MS = 5_000;
|
|
31
|
+
// Max age for session recovery on warm restart.
|
|
32
|
+
const RECOVERY_MAX_AGE_MS = 24 * 60 * 60 * 1000; // 24h
|
|
33
|
+
const OPENCLAW_CONFIG_PATH = '/home/a1xai/.openclaw/openclaw.json';
|
|
34
|
+
// ── Telegram Direct Send ──────────────────────────────────────────────────
|
|
35
|
+
let BOT_TOKEN = '';
|
|
36
|
+
function loadBotToken() {
|
|
37
|
+
if (BOT_TOKEN)
|
|
38
|
+
return;
|
|
39
|
+
try {
|
|
40
|
+
const raw = fs.readFileSync(OPENCLAW_CONFIG_PATH, 'utf8');
|
|
41
|
+
const cfg = JSON.parse(raw);
|
|
42
|
+
const tg = cfg.channels?.telegram;
|
|
43
|
+
if (tg?.accounts) {
|
|
44
|
+
const acctKey = tg.defaultAccount || 'default';
|
|
45
|
+
const acct = tg.accounts[acctKey] || {};
|
|
46
|
+
BOT_TOKEN = acct.botToken || '';
|
|
47
|
+
}
|
|
48
|
+
if (BOT_TOKEN)
|
|
49
|
+
logger.info(`${PLUGIN_TAG} Bot token loaded for direct replies`);
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
logger.warn(`${PLUGIN_TAG} Failed to load bot token: ${err instanceof Error ? err.message : String(err)}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function telegramApiDirect(method, params) {
|
|
56
|
+
return new Promise((resolve, reject) => {
|
|
57
|
+
const body = JSON.stringify(params);
|
|
58
|
+
const options = {
|
|
59
|
+
hostname: 'api.telegram.org',
|
|
60
|
+
path: `/bot${BOT_TOKEN}/${method}`,
|
|
61
|
+
method: 'POST',
|
|
62
|
+
headers: {
|
|
63
|
+
'Content-Type': 'application/json',
|
|
64
|
+
'Content-Length': Buffer.byteLength(body),
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
const req = httpsRequest(options, (res) => {
|
|
68
|
+
let data = '';
|
|
69
|
+
res.on('data', (chunk) => (data += chunk));
|
|
70
|
+
res.on('end', () => {
|
|
71
|
+
try {
|
|
72
|
+
resolve(JSON.parse(data));
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
resolve({ ok: false });
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
req.on('error', reject);
|
|
80
|
+
req.setTimeout(10_000, () => req.destroy(new Error('Telegram API timeout')));
|
|
81
|
+
req.write(body);
|
|
82
|
+
req.end();
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
async function sendDirectReply(chatId, threadId, text) {
|
|
86
|
+
if (!BOT_TOKEN || !chatId)
|
|
87
|
+
return null;
|
|
88
|
+
try {
|
|
89
|
+
const params = {
|
|
90
|
+
chat_id: chatId,
|
|
91
|
+
text,
|
|
92
|
+
disable_web_page_preview: true,
|
|
93
|
+
};
|
|
94
|
+
if (threadId)
|
|
95
|
+
params.message_thread_id = Number(threadId);
|
|
96
|
+
const res = await telegramApiDirect('sendMessage', params);
|
|
97
|
+
if (!res.ok) {
|
|
98
|
+
logger.warn(`${PLUGIN_TAG} Direct reply failed: ${JSON.stringify(res.description || res)}`);
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
return res.result?.message_id ?? null;
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
logger.warn(`${PLUGIN_TAG} Direct reply error: ${err instanceof Error ? err.message : String(err)}`);
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// ── Slug Generator ────────────────────────────────────────────────────────
|
|
109
|
+
const FILLER_WORDS = new Set([
|
|
110
|
+
'the', 'a', 'an', 'is', 'are', 'was', 'were', 'be', 'been', 'being',
|
|
111
|
+
'have', 'has', 'had', 'do', 'does', 'did', 'will', 'would', 'could',
|
|
112
|
+
'should', 'may', 'might', 'shall', 'can', 'to', 'of', 'in', 'for',
|
|
113
|
+
'on', 'with', 'at', 'by', 'from', 'it', 'its', 'this', 'that', 'and',
|
|
114
|
+
'or', 'but', 'not', 'so', 'if', 'then', 'than', 'just', 'also',
|
|
115
|
+
'very', 'really', 'please', 'me', 'my', 'i',
|
|
116
|
+
]);
|
|
117
|
+
function makeSlug(instruction) {
|
|
118
|
+
const tokens = instruction
|
|
119
|
+
.toLowerCase()
|
|
120
|
+
.replace(/[^a-z0-9\s-]/g, '')
|
|
121
|
+
.split(/\s+/)
|
|
122
|
+
.filter((t) => t.length > 0 && !FILLER_WORDS.has(t));
|
|
123
|
+
const suffix = Date.now().toString(36).slice(-4);
|
|
124
|
+
if (tokens.length === 0) {
|
|
125
|
+
return `task-${suffix}`;
|
|
126
|
+
}
|
|
127
|
+
let slug = tokens.slice(0, 4).join('-');
|
|
128
|
+
if (slug.length > 24) {
|
|
129
|
+
slug = slug.slice(0, 24).replace(/-$/, '');
|
|
130
|
+
}
|
|
131
|
+
return `${slug}-${suffix}`;
|
|
132
|
+
}
|
|
133
|
+
// ── Session ID Generator ──────────────────────────────────────────────────
|
|
134
|
+
function generateSessionId() {
|
|
135
|
+
return crypto.randomBytes(4).toString('hex');
|
|
136
|
+
}
|
|
137
|
+
// ── Session Index ─────────────────────────────────────────────────────────
|
|
138
|
+
let sessionIndex = {};
|
|
139
|
+
function loadSessionIndex() {
|
|
140
|
+
try {
|
|
141
|
+
if (fs.existsSync(SESSIONS_INDEX_PATH)) {
|
|
142
|
+
sessionIndex = JSON.parse(fs.readFileSync(SESSIONS_INDEX_PATH, 'utf8'));
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
sessionIndex = {};
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
function saveSessionIndex() {
|
|
150
|
+
try {
|
|
151
|
+
fs.mkdirSync(SESSIONS_DIR, { recursive: true });
|
|
152
|
+
const tmp = SESSIONS_INDEX_PATH + '.tmp';
|
|
153
|
+
fs.writeFileSync(tmp, JSON.stringify(sessionIndex, null, 2));
|
|
154
|
+
fs.renameSync(tmp, SESSIONS_INDEX_PATH);
|
|
155
|
+
}
|
|
156
|
+
catch { /* best effort */ }
|
|
157
|
+
}
|
|
158
|
+
// ── Session Store ─────────────────────────────────────────────────────────
|
|
159
|
+
function saveSession(meta) {
|
|
160
|
+
const dir = path.join(SESSIONS_DIR, meta.slug);
|
|
161
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
162
|
+
const filePath = path.join(dir, 'session.json');
|
|
163
|
+
const tmp = filePath + '.tmp';
|
|
164
|
+
fs.writeFileSync(tmp, JSON.stringify(meta, null, 2));
|
|
165
|
+
fs.renameSync(tmp, filePath);
|
|
166
|
+
if (meta.id) {
|
|
167
|
+
sessionIndex[meta.id] = meta.slug;
|
|
168
|
+
saveSessionIndex();
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
function loadSession(slug) {
|
|
172
|
+
const filePath = path.join(SESSIONS_DIR, slug, 'session.json');
|
|
173
|
+
try {
|
|
174
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
function loadSessionById(id) {
|
|
181
|
+
const slug = sessionIndex[id];
|
|
182
|
+
if (slug)
|
|
183
|
+
return loadSession(slug);
|
|
184
|
+
const all = scanAllSessions();
|
|
185
|
+
const match = all.find((s) => s.id === id);
|
|
186
|
+
if (match) {
|
|
187
|
+
sessionIndex[id] = match.slug;
|
|
188
|
+
saveSessionIndex();
|
|
189
|
+
}
|
|
190
|
+
return match ?? null;
|
|
191
|
+
}
|
|
192
|
+
function scanAllSessions() {
|
|
193
|
+
const results = [];
|
|
194
|
+
try {
|
|
195
|
+
if (!fs.existsSync(SESSIONS_DIR))
|
|
196
|
+
return results;
|
|
197
|
+
const entries = fs.readdirSync(SESSIONS_DIR, { withFileTypes: true });
|
|
198
|
+
for (const entry of entries) {
|
|
199
|
+
if (!entry.isDirectory())
|
|
200
|
+
continue;
|
|
201
|
+
const meta = loadSession(entry.name);
|
|
202
|
+
if (meta && meta.slug && meta.chatId) {
|
|
203
|
+
results.push(meta);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
// Best effort — if sessions dir is unreadable, return empty
|
|
209
|
+
}
|
|
210
|
+
return results;
|
|
211
|
+
}
|
|
212
|
+
// ── Plugin Entry ──────────────────────────────────────────────────────────
|
|
213
|
+
let logger = console;
|
|
214
|
+
let sessionManager = null;
|
|
215
|
+
const activeSessions = new Map();
|
|
216
|
+
function sessionMapKey(chatId, threadId) {
|
|
217
|
+
return threadId ? `${chatId}:${threadId}` : String(chatId);
|
|
218
|
+
}
|
|
219
|
+
// ── parseCcCommand — pure exported parser ─────────────────────────────────
|
|
220
|
+
/**
|
|
221
|
+
* Parse a raw input string into a CcCommand.
|
|
222
|
+
* Returns null if the input is not a /cc or /cc+ command.
|
|
223
|
+
*
|
|
224
|
+
* Recognised forms:
|
|
225
|
+
* /cc+ <prompt> → { subcommand: 'continue', prompt }
|
|
226
|
+
* /cc <prompt> → { subcommand: 'new', prompt }
|
|
227
|
+
* /cc continue <prompt> → { subcommand: 'continue', prompt }
|
|
228
|
+
* /cc stop → { subcommand: 'stop' }
|
|
229
|
+
* /cc resume [id|slug] → { subcommand: 'resume', target? }
|
|
230
|
+
* /cc status → { subcommand: 'status' }
|
|
231
|
+
* /cc list → { subcommand: 'list' }
|
|
232
|
+
*/
|
|
233
|
+
export function parseCcCommand(input) {
|
|
234
|
+
if (!input)
|
|
235
|
+
return null;
|
|
236
|
+
const content = input.trim();
|
|
237
|
+
if (!content)
|
|
238
|
+
return null;
|
|
239
|
+
// /cc+ <prompt>
|
|
240
|
+
const ccPlusMatch = content.match(/^\/cc\+\s*([\s\S]+)/i);
|
|
241
|
+
if (ccPlusMatch) {
|
|
242
|
+
const prompt = ccPlusMatch[1].trim();
|
|
243
|
+
if (!prompt)
|
|
244
|
+
return null;
|
|
245
|
+
return { subcommand: 'continue', prompt };
|
|
246
|
+
}
|
|
247
|
+
// Must start with /cc
|
|
248
|
+
if (!/^\/cc(\s|$)/i.test(content))
|
|
249
|
+
return null;
|
|
250
|
+
const afterCc = content.slice(3).trim();
|
|
251
|
+
// /cc continue <prompt>
|
|
252
|
+
const continueMatch = afterCc.match(/^continue\s+([\s\S]+)/i);
|
|
253
|
+
if (continueMatch) {
|
|
254
|
+
const prompt = continueMatch[1].trim();
|
|
255
|
+
if (!prompt)
|
|
256
|
+
return null;
|
|
257
|
+
return { subcommand: 'continue', prompt };
|
|
258
|
+
}
|
|
259
|
+
// /cc stop
|
|
260
|
+
if (afterCc.toLowerCase() === 'stop') {
|
|
261
|
+
return { subcommand: 'stop' };
|
|
262
|
+
}
|
|
263
|
+
// /cc resume [id|slug]
|
|
264
|
+
const resumeMatch = afterCc.match(/^resume(?:\s+(.+))?$/i);
|
|
265
|
+
if (resumeMatch) {
|
|
266
|
+
const target = (resumeMatch[1] || '').trim() || undefined;
|
|
267
|
+
return { subcommand: 'resume', target };
|
|
268
|
+
}
|
|
269
|
+
// /cc status
|
|
270
|
+
if (afterCc.toLowerCase() === 'status') {
|
|
271
|
+
return { subcommand: 'status' };
|
|
272
|
+
}
|
|
273
|
+
// /cc list
|
|
274
|
+
if (afterCc.toLowerCase() === 'list') {
|
|
275
|
+
return { subcommand: 'list' };
|
|
276
|
+
}
|
|
277
|
+
// /cc <prompt> — new session (may be empty)
|
|
278
|
+
if (!afterCc)
|
|
279
|
+
return null;
|
|
280
|
+
return { subcommand: 'new', prompt: afterCc };
|
|
281
|
+
}
|
|
282
|
+
// ── Status Handler ────────────────────────────────────────────────────────
|
|
283
|
+
function buildPhase2FlagsLine() {
|
|
284
|
+
const allowBuiltins = process.env.CC_OPENCLAW_ALLOW_BUILTINS === '1';
|
|
285
|
+
const toolStream = process.env.CC_OPENCLAW_TOOL_STREAM === '1';
|
|
286
|
+
return `Flags: builtins=${allowBuiltins ? 'on' : 'off'} | toolstream=${toolStream ? 'on' : 'off'}`;
|
|
287
|
+
}
|
|
288
|
+
function handleStatus(chatId, threadId) {
|
|
289
|
+
const key = sessionMapKey(chatId, threadId);
|
|
290
|
+
const active = activeSessions.get(key);
|
|
291
|
+
const flagsLine = buildPhase2FlagsLine();
|
|
292
|
+
if (!active) {
|
|
293
|
+
const diskSessions = scanAllSessions()
|
|
294
|
+
.filter((s) => s.chatId === chatId &&
|
|
295
|
+
(s.threadId || undefined) === (threadId || undefined) &&
|
|
296
|
+
s.state !== 'failed')
|
|
297
|
+
.sort((a, b) => new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime());
|
|
298
|
+
if (diskSessions.length > 0) {
|
|
299
|
+
const last = diskSessions[0];
|
|
300
|
+
const id = last.id || '?';
|
|
301
|
+
return {
|
|
302
|
+
handled: true,
|
|
303
|
+
text: `No active session.\nLast: [${id}] ${last.slug} (${last.state}, ${last.turns || 0} turns)\nResume: /cc resume ${id}\n${flagsLine}`,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
return { handled: true, text: `No active session in this chat.\n${flagsLine}` };
|
|
307
|
+
}
|
|
308
|
+
const elapsed = Math.round((Date.now() - new Date(active.meta.startedAt).getTime()) / 1000);
|
|
309
|
+
const turns = active.meta.turns || 1;
|
|
310
|
+
const id = active.meta.id || '?';
|
|
311
|
+
return {
|
|
312
|
+
handled: true,
|
|
313
|
+
text: `Active: [${id}] ${active.slug} (${turns} turn${turns > 1 ? 's' : ''}, ${elapsed}s)\n${flagsLine}`,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
// ── List Handler ──────────────────────────────────────────────────────────
|
|
317
|
+
function handleList(chatId, threadId) {
|
|
318
|
+
const sessions = scanAllSessions()
|
|
319
|
+
.filter((s) => s.chatId === chatId &&
|
|
320
|
+
(s.threadId || undefined) === (threadId || undefined))
|
|
321
|
+
.sort((a, b) => {
|
|
322
|
+
const timeA = new Date(a.lastContinuedAt || a.completedAt || a.startedAt).getTime();
|
|
323
|
+
const timeB = new Date(b.lastContinuedAt || b.completedAt || b.startedAt).getTime();
|
|
324
|
+
return timeB - timeA;
|
|
325
|
+
})
|
|
326
|
+
.slice(0, 10);
|
|
327
|
+
if (sessions.length === 0) {
|
|
328
|
+
return { handled: true, text: 'No sessions found for this chat.' };
|
|
329
|
+
}
|
|
330
|
+
const lines = sessions.map((s) => {
|
|
331
|
+
const id = s.id || '--------';
|
|
332
|
+
const state = s.state || '?';
|
|
333
|
+
const turns = s.turns || 0;
|
|
334
|
+
const resumable = !!s.claudeSessionId;
|
|
335
|
+
const marker = resumable ? '✅' : '❌';
|
|
336
|
+
const prompt = (s.instruction || '').slice(0, 30);
|
|
337
|
+
return `${marker} ${id} ${s.slug} (${state}, ${turns}t) ${prompt}`;
|
|
338
|
+
});
|
|
339
|
+
return {
|
|
340
|
+
handled: true,
|
|
341
|
+
text: `Sessions (last 10):\n${lines.join('\n')}\n\nResume: /cc resume <id>`,
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
// ── Continuation Handler ──────────────────────────────────────────────────
|
|
345
|
+
function handleContinuation(prompt, chatId, threadId) {
|
|
346
|
+
if (!sessionManager) {
|
|
347
|
+
return {
|
|
348
|
+
handled: true,
|
|
349
|
+
text: 'Claude Code handler not ready — try again in a few seconds.',
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
const key = sessionMapKey(chatId, threadId);
|
|
353
|
+
const active = activeSessions.get(key);
|
|
354
|
+
if (!active) {
|
|
355
|
+
return {
|
|
356
|
+
handled: true,
|
|
357
|
+
text: 'No active session to continue. Start one with /cc <prompt> or /cc resume',
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
if (active.needsRehydration) {
|
|
361
|
+
if (!active.meta.claudeSessionId) {
|
|
362
|
+
activeSessions.delete(key);
|
|
363
|
+
return {
|
|
364
|
+
handled: true,
|
|
365
|
+
text: 'Recovered session has no Claude ID — start fresh with /cc <prompt>',
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
logger.info(`${PLUGIN_TAG} /cc+ rehydrating recovered session: slug=${active.slug} chat=${chatId} thread=${threadId || 'none'}`);
|
|
369
|
+
let resolveAckId;
|
|
370
|
+
const ackIdPromise = new Promise((r) => {
|
|
371
|
+
resolveAckId = r;
|
|
372
|
+
});
|
|
373
|
+
(async () => {
|
|
374
|
+
try {
|
|
375
|
+
await rehydrateSession(active.meta, chatId, threadId);
|
|
376
|
+
active.needsRehydration = false;
|
|
377
|
+
await continueTurn(active, prompt, chatId, threadId, ackIdPromise);
|
|
378
|
+
}
|
|
379
|
+
catch (err) {
|
|
380
|
+
logger.error(`${PLUGIN_TAG} Rehydrate+continue ${active.slug} failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
381
|
+
scheduleIdle(key, active);
|
|
382
|
+
}
|
|
383
|
+
})();
|
|
384
|
+
return {
|
|
385
|
+
handled: true,
|
|
386
|
+
text: `⏳ [${active.meta.id || '?'}] Resuming & continuing ${active.slug}...`,
|
|
387
|
+
_resolveAckId: resolveAckId,
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
try {
|
|
391
|
+
sessionManager.getStatus(active.sessionName);
|
|
392
|
+
}
|
|
393
|
+
catch {
|
|
394
|
+
activeSessions.delete(key);
|
|
395
|
+
return {
|
|
396
|
+
handled: true,
|
|
397
|
+
text: 'Previous session expired. Use /cc resume or start fresh with /cc <prompt>',
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
if (active.idleTimer)
|
|
401
|
+
clearTimeout(active.idleTimer);
|
|
402
|
+
logger.info(`${PLUGIN_TAG} /cc+ continuation: slug=${active.slug} chat=${chatId} thread=${threadId || 'none'}`);
|
|
403
|
+
let resolveAckId;
|
|
404
|
+
const ackIdPromise = new Promise((r) => {
|
|
405
|
+
resolveAckId = r;
|
|
406
|
+
});
|
|
407
|
+
continueTurn(active, prompt, chatId, threadId, ackIdPromise).catch((err) => {
|
|
408
|
+
logger.error(`${PLUGIN_TAG} Continuation ${active.slug} failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
409
|
+
scheduleIdle(key, active);
|
|
410
|
+
});
|
|
411
|
+
return {
|
|
412
|
+
handled: true,
|
|
413
|
+
text: `⏳ [${active.meta.id || '?'}] Continuing ${active.slug}...`,
|
|
414
|
+
_resolveAckId: resolveAckId,
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
// ── Stop Handler ──────────────────────────────────────────────────────────
|
|
418
|
+
function handleStop(chatId, threadId) {
|
|
419
|
+
if (!sessionManager) {
|
|
420
|
+
return { handled: true, text: 'Claude Code handler not ready.' };
|
|
421
|
+
}
|
|
422
|
+
const key = sessionMapKey(chatId, threadId);
|
|
423
|
+
const active = activeSessions.get(key);
|
|
424
|
+
if (!active) {
|
|
425
|
+
return { handled: true, text: 'No active session to stop.' };
|
|
426
|
+
}
|
|
427
|
+
const { sessionName, slug, meta } = active;
|
|
428
|
+
if (active.idleTimer)
|
|
429
|
+
clearTimeout(active.idleTimer);
|
|
430
|
+
activeSessions.delete(key);
|
|
431
|
+
(async () => {
|
|
432
|
+
try {
|
|
433
|
+
await sessionManager.stopSession(sessionName);
|
|
434
|
+
logger.info(`${PLUGIN_TAG} Session ${sessionName} stopped by /cc stop`);
|
|
435
|
+
}
|
|
436
|
+
catch (err) {
|
|
437
|
+
logger.warn(`${PLUGIN_TAG} stopSession(${sessionName}) error: ${err instanceof Error ? err.message : String(err)}`);
|
|
438
|
+
}
|
|
439
|
+
})();
|
|
440
|
+
meta.state = 'stopped';
|
|
441
|
+
meta.completedAt = new Date().toISOString();
|
|
442
|
+
try {
|
|
443
|
+
saveSession(meta);
|
|
444
|
+
}
|
|
445
|
+
catch { /* best effort */ }
|
|
446
|
+
const sessionId = meta.id || slug;
|
|
447
|
+
return {
|
|
448
|
+
handled: true,
|
|
449
|
+
text: `⏹ Stopped [${sessionId}] ${slug}.\nResume: /cc resume ${sessionId}`,
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
// ── Resume Handler ────────────────────────────────────────────────────────
|
|
453
|
+
function handleResume(chatId, threadId, targetSlug) {
|
|
454
|
+
if (!sessionManager) {
|
|
455
|
+
return { handled: true, text: 'Claude Code handler not ready.' };
|
|
456
|
+
}
|
|
457
|
+
const key = sessionMapKey(chatId, threadId);
|
|
458
|
+
const currentActive = activeSessions.get(key);
|
|
459
|
+
if (currentActive) {
|
|
460
|
+
return {
|
|
461
|
+
handled: true,
|
|
462
|
+
text: `Already have active session: ${currentActive.slug}\nUse /cc stop first, or /cc+ to continue it.`,
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
let meta = null;
|
|
466
|
+
if (targetSlug) {
|
|
467
|
+
const isSessionId = /^[0-9a-f]{8}$/i.test(targetSlug);
|
|
468
|
+
if (isSessionId) {
|
|
469
|
+
meta = loadSessionById(targetSlug.toLowerCase());
|
|
470
|
+
}
|
|
471
|
+
if (!meta) {
|
|
472
|
+
meta = loadSession(targetSlug);
|
|
473
|
+
}
|
|
474
|
+
if (!meta) {
|
|
475
|
+
return { handled: true, text: `Session "${targetSlug}" not found.` };
|
|
476
|
+
}
|
|
477
|
+
if (meta.chatId !== chatId) {
|
|
478
|
+
return {
|
|
479
|
+
handled: true,
|
|
480
|
+
text: `Session "${targetSlug}" belongs to a different chat.`,
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
else {
|
|
485
|
+
const candidates = scanAllSessions()
|
|
486
|
+
.filter((s) => s.chatId === chatId &&
|
|
487
|
+
(s.threadId || undefined) === (threadId || undefined) &&
|
|
488
|
+
!!s.claudeSessionId)
|
|
489
|
+
.sort((a, b) => {
|
|
490
|
+
const timeA = new Date(a.lastContinuedAt || a.completedAt || a.startedAt).getTime();
|
|
491
|
+
const timeB = new Date(b.lastContinuedAt || b.completedAt || b.startedAt).getTime();
|
|
492
|
+
return timeB - timeA;
|
|
493
|
+
});
|
|
494
|
+
if (candidates.length === 0) {
|
|
495
|
+
return {
|
|
496
|
+
handled: true,
|
|
497
|
+
text: 'No resumable session found. Start fresh with /cc <prompt>',
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
meta = candidates[0];
|
|
501
|
+
}
|
|
502
|
+
if (!meta.claudeSessionId) {
|
|
503
|
+
return {
|
|
504
|
+
handled: true,
|
|
505
|
+
text: `Session ${meta.slug} has no Claude session ID — cannot resume.\nStart fresh with /cc <prompt>`,
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
const { slug, id: resumeId } = meta;
|
|
509
|
+
logger.info(`${PLUGIN_TAG} /cc resume: id=${resumeId} slug=${slug} claudeId=${meta.claudeSessionId} chat=${chatId} thread=${threadId || 'none'}`);
|
|
510
|
+
rehydrateSession(meta, chatId, threadId).catch((err) => {
|
|
511
|
+
logger.error(`${PLUGIN_TAG} Resume ${slug} failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
512
|
+
meta.state = 'failed';
|
|
513
|
+
meta.error = `Resume failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
514
|
+
try {
|
|
515
|
+
saveSession(meta);
|
|
516
|
+
}
|
|
517
|
+
catch { /* best effort */ }
|
|
518
|
+
const active = activeSessions.get(key);
|
|
519
|
+
if (active?.slug === slug) {
|
|
520
|
+
if (active.idleTimer)
|
|
521
|
+
clearTimeout(active.idleTimer);
|
|
522
|
+
activeSessions.delete(key);
|
|
523
|
+
}
|
|
524
|
+
});
|
|
525
|
+
return { handled: true, text: `⏳ [${resumeId || '?'}] Resuming ${slug}...` };
|
|
526
|
+
}
|
|
527
|
+
// ── Session Launcher ──────────────────────────────────────────────────────
|
|
528
|
+
async function launchSession(sessionName, prompt, slug, meta, chatId, threadId, ackIdPromise) {
|
|
529
|
+
logger.info(`${PLUGIN_TAG} Starting session ${sessionName}...`);
|
|
530
|
+
const key = sessionMapKey(chatId, threadId);
|
|
531
|
+
const preAssignedId = crypto.randomUUID();
|
|
532
|
+
meta.claudeSessionId = preAssignedId;
|
|
533
|
+
meta.state = 'starting';
|
|
534
|
+
try {
|
|
535
|
+
saveSession(meta);
|
|
536
|
+
}
|
|
537
|
+
catch { /* best effort */ }
|
|
538
|
+
await sessionManager.startSession({
|
|
539
|
+
name: sessionName,
|
|
540
|
+
cwd: DEFAULT_CWD,
|
|
541
|
+
engine: 'claude',
|
|
542
|
+
permissionMode: 'bypassPermissions',
|
|
543
|
+
effort: 'high',
|
|
544
|
+
customSessionId: preAssignedId,
|
|
545
|
+
});
|
|
546
|
+
meta.state = 'running';
|
|
547
|
+
try {
|
|
548
|
+
saveSession(meta);
|
|
549
|
+
}
|
|
550
|
+
catch { /* best effort */ }
|
|
551
|
+
const prevActive = activeSessions.get(key);
|
|
552
|
+
if (prevActive && prevActive.sessionName !== sessionName) {
|
|
553
|
+
if (prevActive.idleTimer)
|
|
554
|
+
clearTimeout(prevActive.idleTimer);
|
|
555
|
+
try {
|
|
556
|
+
await sessionManager.stopSession(prevActive.sessionName);
|
|
557
|
+
}
|
|
558
|
+
catch { /* may already be gone */ }
|
|
559
|
+
}
|
|
560
|
+
const activeEntry = { sessionName, slug, meta, idleTimer: null };
|
|
561
|
+
activeSessions.set(key, activeEntry);
|
|
562
|
+
logger.info(`${PLUGIN_TAG} Session ${sessionName} started — sending prompt...`);
|
|
563
|
+
const opts = { telegramChatId: chatId };
|
|
564
|
+
if (threadId)
|
|
565
|
+
opts.telegramThreadId = threadId;
|
|
566
|
+
const ackMsgId = await Promise.race([
|
|
567
|
+
ackIdPromise,
|
|
568
|
+
new Promise((r) => setTimeout(() => r(null), ACK_TIMEOUT_MS)),
|
|
569
|
+
]);
|
|
570
|
+
if (ackMsgId)
|
|
571
|
+
opts.telegramAckMessageId = ackMsgId;
|
|
572
|
+
const result = await sessionManager.sendMessage(sessionName, prompt, opts);
|
|
573
|
+
meta.state = 'idle';
|
|
574
|
+
meta.turns = 1;
|
|
575
|
+
meta.completedAt = new Date().toISOString();
|
|
576
|
+
meta.output = (result?.output || '').slice(0, 2000);
|
|
577
|
+
meta.claudeSessionId = result?.sessionId ?? meta.claudeSessionId;
|
|
578
|
+
try {
|
|
579
|
+
saveSession(meta);
|
|
580
|
+
}
|
|
581
|
+
catch { /* best effort */ }
|
|
582
|
+
logger.info(`${PLUGIN_TAG} Session ${sessionName} turn 1 complete — idle for ${IDLE_TIMEOUT_MS / 1000}s`);
|
|
583
|
+
scheduleIdle(key, activeEntry);
|
|
584
|
+
}
|
|
585
|
+
// ── Rehydrate Session ─────────────────────────────────────────────────────
|
|
586
|
+
async function rehydrateSession(meta, chatId, threadId) {
|
|
587
|
+
const { slug, sessionName, claudeSessionId } = meta;
|
|
588
|
+
const key = sessionMapKey(chatId, threadId);
|
|
589
|
+
logger.info(`${PLUGIN_TAG} Rehydrating ${sessionName} with claudeSessionId=${claudeSessionId}...`);
|
|
590
|
+
await sessionManager.startSession({
|
|
591
|
+
name: sessionName,
|
|
592
|
+
cwd: meta.cwd || DEFAULT_CWD,
|
|
593
|
+
engine: 'claude',
|
|
594
|
+
permissionMode: 'bypassPermissions',
|
|
595
|
+
effort: 'high',
|
|
596
|
+
resumeSessionId: claudeSessionId,
|
|
597
|
+
});
|
|
598
|
+
const prevActive = activeSessions.get(key);
|
|
599
|
+
if (prevActive && prevActive.sessionName !== sessionName) {
|
|
600
|
+
if (prevActive.idleTimer)
|
|
601
|
+
clearTimeout(prevActive.idleTimer);
|
|
602
|
+
try {
|
|
603
|
+
await sessionManager.stopSession(prevActive.sessionName);
|
|
604
|
+
}
|
|
605
|
+
catch { /* ignore */ }
|
|
606
|
+
}
|
|
607
|
+
meta.state = 'idle';
|
|
608
|
+
meta.threadId = threadId;
|
|
609
|
+
meta.lastContinuedAt = new Date().toISOString();
|
|
610
|
+
try {
|
|
611
|
+
saveSession(meta);
|
|
612
|
+
}
|
|
613
|
+
catch { /* best effort */ }
|
|
614
|
+
const activeEntry = { sessionName, slug, meta, idleTimer: null };
|
|
615
|
+
activeSessions.set(key, activeEntry);
|
|
616
|
+
logger.info(`${PLUGIN_TAG} Session ${sessionName} resumed — idle for ${IDLE_TIMEOUT_MS / 1000}s`);
|
|
617
|
+
scheduleIdle(key, activeEntry);
|
|
618
|
+
}
|
|
619
|
+
// ── Continuation Turn ─────────────────────────────────────────────────────
|
|
620
|
+
async function continueTurn(active, prompt, chatId, threadId, ackIdPromise) {
|
|
621
|
+
const { sessionName, meta } = active;
|
|
622
|
+
const key = sessionMapKey(chatId, threadId);
|
|
623
|
+
meta.state = 'running';
|
|
624
|
+
try {
|
|
625
|
+
saveSession(meta);
|
|
626
|
+
}
|
|
627
|
+
catch { /* best effort */ }
|
|
628
|
+
const opts = { telegramChatId: chatId };
|
|
629
|
+
if (threadId)
|
|
630
|
+
opts.telegramThreadId = threadId;
|
|
631
|
+
const ackMsgId = await Promise.race([
|
|
632
|
+
ackIdPromise,
|
|
633
|
+
new Promise((r) => setTimeout(() => r(null), ACK_TIMEOUT_MS)),
|
|
634
|
+
]);
|
|
635
|
+
if (ackMsgId)
|
|
636
|
+
opts.telegramAckMessageId = ackMsgId;
|
|
637
|
+
const result = await sessionManager.sendMessage(sessionName, prompt, opts);
|
|
638
|
+
meta.state = 'idle';
|
|
639
|
+
meta.turns = (meta.turns || 1) + 1;
|
|
640
|
+
meta.lastContinuedAt = new Date().toISOString();
|
|
641
|
+
meta.output = (result?.output || '').slice(0, 2000);
|
|
642
|
+
meta.claudeSessionId = result?.sessionId ?? meta.claudeSessionId;
|
|
643
|
+
try {
|
|
644
|
+
saveSession(meta);
|
|
645
|
+
}
|
|
646
|
+
catch { /* best effort */ }
|
|
647
|
+
logger.info(`${PLUGIN_TAG} Session ${sessionName} turn ${meta.turns} complete`);
|
|
648
|
+
scheduleIdle(key, active);
|
|
649
|
+
}
|
|
650
|
+
// ── Idle Timer ────────────────────────────────────────────────────────────
|
|
651
|
+
function scheduleIdle(key, active) {
|
|
652
|
+
if (active.idleTimer)
|
|
653
|
+
clearTimeout(active.idleTimer);
|
|
654
|
+
active.idleTimer = setTimeout(() => {
|
|
655
|
+
logger.info(`${PLUGIN_TAG} Session ${active.sessionName} idle timeout — stopping`);
|
|
656
|
+
try {
|
|
657
|
+
sessionManager
|
|
658
|
+
?.stopSession(active.sessionName)
|
|
659
|
+
.catch((e) => logger.warn(`${PLUGIN_TAG} idle stop error: ${e.message}`));
|
|
660
|
+
}
|
|
661
|
+
catch { /* may already be gone */ }
|
|
662
|
+
activeSessions.delete(key);
|
|
663
|
+
active.meta.state = 'idle';
|
|
664
|
+
active.meta.completedAt = new Date().toISOString();
|
|
665
|
+
try {
|
|
666
|
+
saveSession(active.meta);
|
|
667
|
+
}
|
|
668
|
+
catch { /* best effort */ }
|
|
669
|
+
}, IDLE_TIMEOUT_MS);
|
|
670
|
+
}
|
|
671
|
+
// ── Warm-Restart Recovery ─────────────────────────────────────────────────
|
|
672
|
+
function recoverSessions() {
|
|
673
|
+
const now = Date.now();
|
|
674
|
+
const allSessions = scanAllSessions();
|
|
675
|
+
let indexDirty = false;
|
|
676
|
+
for (const meta of allSessions) {
|
|
677
|
+
if (!meta.id) {
|
|
678
|
+
meta.id = generateSessionId();
|
|
679
|
+
sessionIndex[meta.id] = meta.slug;
|
|
680
|
+
indexDirty = true;
|
|
681
|
+
try {
|
|
682
|
+
saveSession(meta);
|
|
683
|
+
}
|
|
684
|
+
catch { /* best effort */ }
|
|
685
|
+
logger.info(`${PLUGIN_TAG} Backfilled session ID ${meta.id} for ${meta.slug}`);
|
|
686
|
+
}
|
|
687
|
+
else if (!sessionIndex[meta.id]) {
|
|
688
|
+
sessionIndex[meta.id] = meta.slug;
|
|
689
|
+
indexDirty = true;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
if (indexDirty)
|
|
693
|
+
saveSessionIndex();
|
|
694
|
+
let recovered = 0;
|
|
695
|
+
for (const meta of allSessions) {
|
|
696
|
+
if (!['running', 'idle', 'timed_out'].includes(meta.state))
|
|
697
|
+
continue;
|
|
698
|
+
const lastActivity = new Date(meta.lastContinuedAt || meta.completedAt || meta.startedAt).getTime();
|
|
699
|
+
if (now - lastActivity > RECOVERY_MAX_AGE_MS)
|
|
700
|
+
continue;
|
|
701
|
+
if (!meta.claudeSessionId)
|
|
702
|
+
continue;
|
|
703
|
+
const recoverKey = sessionMapKey(meta.chatId, meta.threadId);
|
|
704
|
+
if (activeSessions.has(recoverKey))
|
|
705
|
+
continue;
|
|
706
|
+
if (meta.state === 'running') {
|
|
707
|
+
meta.state = 'interrupted';
|
|
708
|
+
try {
|
|
709
|
+
saveSession(meta);
|
|
710
|
+
}
|
|
711
|
+
catch { /* best effort */ }
|
|
712
|
+
}
|
|
713
|
+
const activeEntry = {
|
|
714
|
+
sessionName: meta.sessionName,
|
|
715
|
+
slug: meta.slug,
|
|
716
|
+
meta,
|
|
717
|
+
idleTimer: null,
|
|
718
|
+
needsRehydration: true,
|
|
719
|
+
};
|
|
720
|
+
activeSessions.set(recoverKey, activeEntry);
|
|
721
|
+
recovered++;
|
|
722
|
+
logger.info(`${PLUGIN_TAG} Recovered session ${meta.slug} for chat ${meta.chatId} thread=${meta.threadId || 'none'} (was ${meta.state === 'interrupted' ? 'running' : 'idle'})`);
|
|
723
|
+
}
|
|
724
|
+
if (recovered > 0) {
|
|
725
|
+
logger.info(`${PLUGIN_TAG} Warm-restart recovery: ${recovered} session(s) restored`);
|
|
726
|
+
}
|
|
727
|
+
else {
|
|
728
|
+
logger.info(`${PLUGIN_TAG} Warm-restart recovery: no recent sessions to restore`);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
// ── Plugin Object & register() ────────────────────────────────────────────
|
|
732
|
+
/**
|
|
733
|
+
* Register the cc-handler plugin with the OpenClaw API.
|
|
734
|
+
* Idempotent: subsequent calls are no-ops (prevents listener stacking).
|
|
735
|
+
*/
|
|
736
|
+
export function register(api) {
|
|
737
|
+
logger = api.logger || console;
|
|
738
|
+
defaultRegisterGuard.guard('command-router/cc-handler', api, () => {
|
|
739
|
+
// ── Read Claude Code plugin config from gateway ──────────────────────
|
|
740
|
+
const ccConfig = {
|
|
741
|
+
claudeBin: 'claude',
|
|
742
|
+
defaultPermissionMode: 'bypassPermissions',
|
|
743
|
+
defaultEffort: 'high',
|
|
744
|
+
maxConcurrentSessions: 5,
|
|
745
|
+
sessionTtlMinutes: 120,
|
|
746
|
+
};
|
|
747
|
+
try {
|
|
748
|
+
const pluginConfigs = api.config?.plugins?.configs ?? {};
|
|
749
|
+
const ccPluginConfig = pluginConfigs['openclaw-claude-code'] ?? {};
|
|
750
|
+
if (ccPluginConfig.claudeBin)
|
|
751
|
+
ccConfig.claudeBin = ccPluginConfig.claudeBin;
|
|
752
|
+
if (ccPluginConfig.defaultPermissionMode)
|
|
753
|
+
ccConfig.defaultPermissionMode = ccPluginConfig.defaultPermissionMode;
|
|
754
|
+
if (ccPluginConfig.defaultEffort)
|
|
755
|
+
ccConfig.defaultEffort = ccPluginConfig.defaultEffort;
|
|
756
|
+
if (ccPluginConfig.maxConcurrentSessions)
|
|
757
|
+
ccConfig.maxConcurrentSessions = ccPluginConfig.maxConcurrentSessions;
|
|
758
|
+
if (ccPluginConfig.sessionTtlMinutes)
|
|
759
|
+
ccConfig.sessionTtlMinutes = ccPluginConfig.sessionTtlMinutes;
|
|
760
|
+
}
|
|
761
|
+
catch {
|
|
762
|
+
logger.warn(`${PLUGIN_TAG} Could not read openclaw-claude-code config — using defaults`);
|
|
763
|
+
}
|
|
764
|
+
loadBotToken();
|
|
765
|
+
// ── Lifecycle service ────────────────────────────────────────────────
|
|
766
|
+
api.registerService({
|
|
767
|
+
id: 'telegram-cc-handler',
|
|
768
|
+
start: async () => {
|
|
769
|
+
logger.info(`${PLUGIN_TAG} Initialising SessionManager...`);
|
|
770
|
+
loadSessionIndex();
|
|
771
|
+
try {
|
|
772
|
+
// TODO(P2): refine import type when full plugin types land
|
|
773
|
+
const mod = await import(CC_PLUGIN_PATH);
|
|
774
|
+
const SessionManager = mod.SessionManager ??
|
|
775
|
+
mod.default?.SessionManager;
|
|
776
|
+
if (!SessionManager) {
|
|
777
|
+
logger.error(`${PLUGIN_TAG} SessionManager class not found — plugin inactive`);
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
// ── Prevent orphan-cleanup race ───────────────────────────────────
|
|
781
|
+
// The vendored SessionManager runs `_cleanupOrphanedPids` in its
|
|
782
|
+
// constructor — reads the shared session-pids.json and kills any
|
|
783
|
+
// alive PID listed there. In our gateway, TWO SessionManagers are
|
|
784
|
+
// constructed: the eagerManager in cwd-patch.ts (for openai-compat)
|
|
785
|
+
// and this one (for /cc commands). The second construction's
|
|
786
|
+
// cleanup mistakes the first manager's prewarm session for an
|
|
787
|
+
// orphan and kills it, defeating the cold-start prewarm.
|
|
788
|
+
//
|
|
789
|
+
// Fix: make this second SessionManager's cleanup a no-op. The
|
|
790
|
+
// eagerManager (first to construct) already did the legitimate
|
|
791
|
+
// post-crash cleanup at boot. Any further cleanup would only kill
|
|
792
|
+
// peer-manager processes in the same gateway lifetime.
|
|
793
|
+
const _ProtoSM = SessionManager.prototype;
|
|
794
|
+
if (_ProtoSM._cleanupOrphanedPids && !_ProtoSM._cleanupOrphanedPids_orig) {
|
|
795
|
+
_ProtoSM._cleanupOrphanedPids_orig = _ProtoSM._cleanupOrphanedPids;
|
|
796
|
+
_ProtoSM._cleanupOrphanedPids = function () {
|
|
797
|
+
// no-op; eagerManager handles cleanup at boot
|
|
798
|
+
};
|
|
799
|
+
logger.info(`${PLUGIN_TAG} SessionManager._cleanupOrphanedPids no-oped (peer-manager protection)`);
|
|
800
|
+
}
|
|
801
|
+
sessionManager = new SessionManager(ccConfig);
|
|
802
|
+
logger.info(`${PLUGIN_TAG} SessionManager ready — /cc commands active`);
|
|
803
|
+
recoverSessions();
|
|
804
|
+
}
|
|
805
|
+
catch (err) {
|
|
806
|
+
logger.error(`${PLUGIN_TAG} Failed to init SessionManager: ${err instanceof Error ? err.message : String(err)}`);
|
|
807
|
+
}
|
|
808
|
+
},
|
|
809
|
+
stop: () => {
|
|
810
|
+
for (const [, active] of activeSessions) {
|
|
811
|
+
if (active.idleTimer)
|
|
812
|
+
clearTimeout(active.idleTimer);
|
|
813
|
+
}
|
|
814
|
+
activeSessions.clear();
|
|
815
|
+
if (sessionManager) {
|
|
816
|
+
sessionManager
|
|
817
|
+
.shutdown()
|
|
818
|
+
.catch((e) => logger.warn(`${PLUGIN_TAG} shutdown error: ${e.message}`));
|
|
819
|
+
sessionManager = null;
|
|
820
|
+
}
|
|
821
|
+
logger.info(`${PLUGIN_TAG} Stopped`);
|
|
822
|
+
},
|
|
823
|
+
});
|
|
824
|
+
// ── before_dispatch hook ─────────────────────────────────────────────
|
|
825
|
+
const ccInnerHandler = async (event, ctx) => {
|
|
826
|
+
if (event.channel !== 'telegram')
|
|
827
|
+
return undefined;
|
|
828
|
+
const content = (typeof event.content === 'string' ? event.content : '').trim();
|
|
829
|
+
const senderId = typeof event.senderId === 'string' ? event.senderId : '';
|
|
830
|
+
const rawConvId = (ctx.conversationId || '').replace(/^telegram:/i, '');
|
|
831
|
+
const topicMatch = rawConvId.match(/^(-?\d+):topic:(\d+)$/);
|
|
832
|
+
const chatId = topicMatch
|
|
833
|
+
? topicMatch[1]
|
|
834
|
+
: /^-?\d+$/.test(rawConvId)
|
|
835
|
+
? rawConvId
|
|
836
|
+
: senderId;
|
|
837
|
+
const threadId = topicMatch ? topicMatch[2] : undefined;
|
|
838
|
+
logger.info(`${PLUGIN_TAG} target: conv=${ctx.conversationId || 'empty'} chat=${chatId} thread=${threadId || 'none'} sender=${senderId} group=${event.isGroup}`);
|
|
839
|
+
// /cc+ <prompt>
|
|
840
|
+
const ccPlusMatch = content.match(/^\/cc\+\s*([\s\S]+)/i);
|
|
841
|
+
if (ccPlusMatch) {
|
|
842
|
+
const prompt = ccPlusMatch[1].trim();
|
|
843
|
+
if (!prompt)
|
|
844
|
+
return { handled: true, text: 'Usage: /cc+ <prompt>' };
|
|
845
|
+
return handleContinuation(prompt, chatId, threadId);
|
|
846
|
+
}
|
|
847
|
+
if (!content.match(/^\/cc(\s|$)/i))
|
|
848
|
+
return undefined;
|
|
849
|
+
const afterCc = content.slice(3).trim();
|
|
850
|
+
const continueMatch = afterCc.match(/^continue\s+([\s\S]+)/i);
|
|
851
|
+
if (continueMatch) {
|
|
852
|
+
const prompt = continueMatch[1].trim();
|
|
853
|
+
if (!prompt)
|
|
854
|
+
return { handled: true, text: 'Usage: /cc continue <prompt>' };
|
|
855
|
+
return handleContinuation(prompt, chatId, threadId);
|
|
856
|
+
}
|
|
857
|
+
if (afterCc.toLowerCase() === 'stop') {
|
|
858
|
+
return handleStop(chatId, threadId);
|
|
859
|
+
}
|
|
860
|
+
const resumeMatch = afterCc.match(/^resume(?:\s+(.+))?$/i);
|
|
861
|
+
if (resumeMatch) {
|
|
862
|
+
const targetSlug = (resumeMatch[1] || '').trim() || null;
|
|
863
|
+
return handleResume(chatId, threadId, targetSlug);
|
|
864
|
+
}
|
|
865
|
+
if (afterCc.toLowerCase() === 'status') {
|
|
866
|
+
return handleStatus(chatId, threadId);
|
|
867
|
+
}
|
|
868
|
+
if (afterCc.toLowerCase() === 'list') {
|
|
869
|
+
return handleList(chatId, threadId);
|
|
870
|
+
}
|
|
871
|
+
const prompt = afterCc;
|
|
872
|
+
if (!prompt) {
|
|
873
|
+
return {
|
|
874
|
+
handled: true,
|
|
875
|
+
text: [
|
|
876
|
+
'Usage:',
|
|
877
|
+
' /cc <prompt> — new session',
|
|
878
|
+
' /cc+ <prompt> — continue',
|
|
879
|
+
' /cc status — check active',
|
|
880
|
+
' /cc stop — halt session',
|
|
881
|
+
' /cc resume [id|slug] — resume previous',
|
|
882
|
+
' /cc list — show resumable sessions',
|
|
883
|
+
].join('\n'),
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
if (!sessionManager) {
|
|
887
|
+
return {
|
|
888
|
+
handled: true,
|
|
889
|
+
text: 'Claude Code handler not ready — try again in a few seconds.',
|
|
890
|
+
};
|
|
891
|
+
}
|
|
892
|
+
const slug = makeSlug(prompt);
|
|
893
|
+
const sessionId = generateSessionId();
|
|
894
|
+
const sessionName = `cc-${slug}`;
|
|
895
|
+
logger.info(`${PLUGIN_TAG} /cc claimed: id=${sessionId} slug=${slug} sender=${senderId} chat=${chatId}`);
|
|
896
|
+
const meta = {
|
|
897
|
+
id: sessionId,
|
|
898
|
+
slug,
|
|
899
|
+
sessionName,
|
|
900
|
+
chatId,
|
|
901
|
+
threadId,
|
|
902
|
+
senderId,
|
|
903
|
+
state: 'starting',
|
|
904
|
+
instruction: prompt,
|
|
905
|
+
turns: 0,
|
|
906
|
+
startedAt: new Date().toISOString(),
|
|
907
|
+
completedAt: null,
|
|
908
|
+
lastContinuedAt: null,
|
|
909
|
+
claudeSessionId: null,
|
|
910
|
+
output: null,
|
|
911
|
+
error: null,
|
|
912
|
+
};
|
|
913
|
+
try {
|
|
914
|
+
saveSession(meta);
|
|
915
|
+
}
|
|
916
|
+
catch (err) {
|
|
917
|
+
logger.error(`${PLUGIN_TAG} Failed to save session meta: ${err instanceof Error ? err.message : String(err)}`);
|
|
918
|
+
}
|
|
919
|
+
let resolveAckId;
|
|
920
|
+
const ackIdPromise = new Promise((r) => {
|
|
921
|
+
resolveAckId = r;
|
|
922
|
+
});
|
|
923
|
+
const launchKey = sessionMapKey(chatId, threadId);
|
|
924
|
+
launchSession(sessionName, prompt, slug, meta, chatId, threadId, ackIdPromise).catch((err) => {
|
|
925
|
+
logger.error(`${PLUGIN_TAG} Session ${sessionName} failed: ${err.message}`);
|
|
926
|
+
try {
|
|
927
|
+
const isTimeout = /timeout/i.test(err.message);
|
|
928
|
+
meta.state = isTimeout ? 'timed_out' : 'failed';
|
|
929
|
+
meta.error = err.message;
|
|
930
|
+
meta.completedAt = new Date().toISOString();
|
|
931
|
+
saveSession(meta);
|
|
932
|
+
}
|
|
933
|
+
catch { /* best effort */ }
|
|
934
|
+
const active = activeSessions.get(launchKey);
|
|
935
|
+
if (active?.sessionName === sessionName) {
|
|
936
|
+
if (active.idleTimer)
|
|
937
|
+
clearTimeout(active.idleTimer);
|
|
938
|
+
activeSessions.delete(launchKey);
|
|
939
|
+
}
|
|
940
|
+
});
|
|
941
|
+
return {
|
|
942
|
+
handled: true,
|
|
943
|
+
text: `⏳ [${sessionId}] ${slug} — starting Claude Code session...`,
|
|
944
|
+
_resolveAckId: resolveAckId,
|
|
945
|
+
};
|
|
946
|
+
};
|
|
947
|
+
api.on('before_dispatch', async (...args) => {
|
|
948
|
+
const event = args[0];
|
|
949
|
+
const ctx = args[1];
|
|
950
|
+
const result = await ccInnerHandler(event, ctx);
|
|
951
|
+
if (!result?.handled)
|
|
952
|
+
return result;
|
|
953
|
+
if (event && typeof event === 'object')
|
|
954
|
+
event.content = '';
|
|
955
|
+
if (result.text && BOT_TOKEN) {
|
|
956
|
+
const convId = (ctx.conversationId || '').replace(/^telegram:/i, '');
|
|
957
|
+
const tm = convId.match(/^(-?\d+):topic:(\d+)$/);
|
|
958
|
+
const ackChat = tm
|
|
959
|
+
? tm[1]
|
|
960
|
+
: /^-?\d+$/.test(convId)
|
|
961
|
+
? convId
|
|
962
|
+
: event.senderId || '';
|
|
963
|
+
const ackThread = tm ? tm[2] : undefined;
|
|
964
|
+
logger.info(`${PLUGIN_TAG} direct-send ack to chat=${ackChat} thread=${ackThread || 'none'}`);
|
|
965
|
+
const ackMessageId = await sendDirectReply(ackChat, ackThread, result.text);
|
|
966
|
+
if (result._resolveAckId) {
|
|
967
|
+
result._resolveAckId(ackMessageId ?? null);
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
else if (result._resolveAckId) {
|
|
971
|
+
result._resolveAckId(null);
|
|
972
|
+
}
|
|
973
|
+
return { handled: true, text: '' };
|
|
974
|
+
});
|
|
975
|
+
logger.info(`${PLUGIN_TAG} before_dispatch hook registered (/cc, /cc+, /cc stop, /cc resume, /cc status, /cc list)`);
|
|
976
|
+
}); // end defaultRegisterGuard.guard
|
|
977
|
+
}
|
|
978
|
+
// Default export compatible with the original plugin object shape
|
|
979
|
+
export default { id: 'telegram-cc-handler', name: 'Telegram /cc Handler', register };
|
|
980
|
+
//# sourceMappingURL=cc-handler.js.map
|