octocode-cli 1.2.5 → 1.2.7
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 +21 -63
- package/README.md +86 -109
- package/out/octocode-cli.js +7027 -7014
- package/package.json +8 -6
- package/skills/README.md +97 -120
- package/skills/octocode-code-engineer/.claude/settings.local.json +18 -0
- package/skills/octocode-code-engineer/.octocode/rfc/RFC-code-engineer-weakness-fixes.md +255 -0
- package/skills/octocode-code-engineer/.plan/VALIDATED_PLAN.md +223 -0
- package/skills/octocode-code-engineer/README.md +178 -0
- package/skills/octocode-code-engineer/SKILL.md +418 -0
- package/skills/octocode-code-engineer/coverage/architecture.ts.html +7828 -0
- package/skills/octocode-code-engineer/coverage/ast-helpers.ts.html +211 -0
- package/skills/octocode-code-engineer/coverage/ast-search.ts.html +1795 -0
- package/skills/octocode-code-engineer/coverage/base.css +224 -0
- package/skills/octocode-code-engineer/coverage/block-navigation.js +87 -0
- package/skills/octocode-code-engineer/coverage/cache.ts.html +376 -0
- package/skills/octocode-code-engineer/coverage/cli.ts.html +982 -0
- package/skills/octocode-code-engineer/coverage/clover.xml +3217 -0
- package/skills/octocode-code-engineer/coverage/collect-effects.ts.html +664 -0
- package/skills/octocode-code-engineer/coverage/collect-input-sources.ts.html +577 -0
- package/skills/octocode-code-engineer/coverage/collect-performance.ts.html +331 -0
- package/skills/octocode-code-engineer/coverage/collect-prototype-pollution.ts.html +421 -0
- package/skills/octocode-code-engineer/coverage/collect-security.ts.html +604 -0
- package/skills/octocode-code-engineer/coverage/collect-test-profile.ts.html +589 -0
- package/skills/octocode-code-engineer/coverage/coverage-final.json +30 -0
- package/skills/octocode-code-engineer/coverage/dependencies.ts.html +997 -0
- package/skills/octocode-code-engineer/coverage/dependency-summary.ts.html +688 -0
- package/skills/octocode-code-engineer/coverage/discovery.ts.html +322 -0
- package/skills/octocode-code-engineer/coverage/favicon.png +0 -0
- package/skills/octocode-code-engineer/coverage/graph-analytics.ts.html +1510 -0
- package/skills/octocode-code-engineer/coverage/index.html +536 -0
- package/skills/octocode-code-engineer/coverage/index.ts.html +826 -0
- package/skills/octocode-code-engineer/coverage/metrics.ts.html +553 -0
- package/skills/octocode-code-engineer/coverage/pipeline.ts.html +2044 -0
- package/skills/octocode-code-engineer/coverage/prettify.css +1 -0
- package/skills/octocode-code-engineer/coverage/prettify.js +2 -0
- package/skills/octocode-code-engineer/coverage/report-analysis.ts.html +1570 -0
- package/skills/octocode-code-engineer/coverage/report-writer.ts.html +1102 -0
- package/skills/octocode-code-engineer/coverage/security-detectors.ts.html +1747 -0
- package/skills/octocode-code-engineer/coverage/semantic-detectors.ts.html +2152 -0
- package/skills/octocode-code-engineer/coverage/semantic.ts.html +1897 -0
- package/skills/octocode-code-engineer/coverage/sort-arrow-sprite.png +0 -0
- package/skills/octocode-code-engineer/coverage/sorter.js +210 -0
- package/skills/octocode-code-engineer/coverage/summary-md.ts.html +1222 -0
- package/skills/octocode-code-engineer/coverage/test-quality-detectors.ts.html +1039 -0
- package/skills/octocode-code-engineer/coverage/tree-sitter-analyzer.ts.html +955 -0
- package/skills/octocode-code-engineer/coverage/ts-analyzer.ts.html +1213 -0
- package/skills/octocode-code-engineer/coverage/types.ts.html +2473 -0
- package/skills/octocode-code-engineer/coverage/utils.ts.html +820 -0
- package/skills/octocode-code-engineer/eslint.config.mjs +54 -0
- package/skills/octocode-code-engineer/minify-scripts.mjs +32 -0
- package/skills/octocode-code-engineer/package.json +54 -0
- package/skills/octocode-code-engineer/references/agent-ast-reading-rfc.md +95 -0
- package/skills/octocode-code-engineer/references/architecture-techniques.md +121 -0
- package/skills/octocode-code-engineer/references/ast-search.md +210 -0
- package/skills/octocode-code-engineer/references/ast-tree-search.md +151 -0
- package/skills/octocode-code-engineer/references/cli-reference.md +167 -0
- package/skills/octocode-code-engineer/references/concepts.md +107 -0
- package/skills/octocode-code-engineer/references/finding-categories.md +128 -0
- package/skills/octocode-code-engineer/references/improvement-roadmap.md +304 -0
- package/skills/octocode-code-engineer/references/output-files.md +144 -0
- package/skills/octocode-code-engineer/references/playbooks.md +204 -0
- package/skills/octocode-code-engineer/references/present-results.md +136 -0
- package/skills/octocode-code-engineer/references/tool-workflows.md +566 -0
- package/skills/octocode-code-engineer/references/validate-investigate.md +225 -0
- package/skills/octocode-code-engineer/scripts/analysis/dependencies.js +1 -0
- package/skills/octocode-code-engineer/scripts/analysis/dependency-summary.js +1 -0
- package/skills/octocode-code-engineer/scripts/analysis/discovery.js +1 -0
- package/skills/octocode-code-engineer/scripts/analysis/graph-analytics.js +1 -0
- package/skills/octocode-code-engineer/scripts/analysis/semantic.js +1 -0
- package/skills/octocode-code-engineer/scripts/ast/helpers.js +1 -0
- package/skills/octocode-code-engineer/scripts/ast/metrics.js +1 -0
- package/skills/octocode-code-engineer/scripts/ast/search.js +2 -0
- package/skills/octocode-code-engineer/scripts/ast/tree-search.js +2 -0
- package/skills/octocode-code-engineer/scripts/ast/tree-sitter.js +1 -0
- package/skills/octocode-code-engineer/scripts/ast/ts-analyzer.js +1 -0
- package/skills/octocode-code-engineer/scripts/collectors/chains.js +1 -0
- package/skills/octocode-code-engineer/scripts/collectors/effects.js +1 -0
- package/skills/octocode-code-engineer/scripts/collectors/input-sources.js +1 -0
- package/skills/octocode-code-engineer/scripts/collectors/performance.js +1 -0
- package/skills/octocode-code-engineer/scripts/collectors/prototype-pollution.js +1 -0
- package/skills/octocode-code-engineer/scripts/collectors/security.js +1 -0
- package/skills/octocode-code-engineer/scripts/collectors/test-profile.js +1 -0
- package/skills/octocode-code-engineer/scripts/common/is-direct-run.js +1 -0
- package/skills/octocode-code-engineer/scripts/common/utils.js +1 -0
- package/skills/octocode-code-engineer/scripts/detectors/code-quality.js +1 -0
- package/skills/octocode-code-engineer/scripts/detectors/cohesion.js +1 -0
- package/skills/octocode-code-engineer/scripts/detectors/coupling.js +1 -0
- package/skills/octocode-code-engineer/scripts/detectors/cycle.js +1 -0
- package/skills/octocode-code-engineer/scripts/detectors/dead-code.js +1 -0
- package/skills/octocode-code-engineer/scripts/detectors/import-style.js +1 -0
- package/skills/octocode-code-engineer/scripts/detectors/index.js +1 -0
- package/skills/octocode-code-engineer/scripts/detectors/security.js +1 -0
- package/skills/octocode-code-engineer/scripts/detectors/semantic.js +1 -0
- package/skills/octocode-code-engineer/scripts/detectors/shared.js +1 -0
- package/skills/octocode-code-engineer/scripts/detectors/test-quality.js +1 -0
- package/skills/octocode-code-engineer/scripts/index.js +1 -0
- package/skills/octocode-code-engineer/scripts/pipeline/cache.js +1 -0
- package/skills/octocode-code-engineer/scripts/pipeline/cli.js +1 -0
- package/skills/octocode-code-engineer/scripts/pipeline/main.js +2 -0
- package/skills/octocode-code-engineer/scripts/reporting/analysis.js +1 -0
- package/skills/octocode-code-engineer/scripts/reporting/summary-md.js +1 -0
- package/skills/octocode-code-engineer/scripts/reporting/writer.js +1 -0
- package/skills/octocode-code-engineer/scripts/types/constants.js +1 -0
- package/skills/octocode-code-engineer/scripts/types/index.js +1 -0
- package/skills/octocode-code-engineer/scripts/types/interfaces.js +1 -0
- package/skills/octocode-code-engineer/src/analysis/dependencies.test.ts +545 -0
- package/skills/octocode-code-engineer/src/analysis/dependencies.ts +406 -0
- package/skills/octocode-code-engineer/src/analysis/dependency-summary.test.ts +566 -0
- package/skills/octocode-code-engineer/src/analysis/dependency-summary.ts +257 -0
- package/skills/octocode-code-engineer/src/analysis/discovery.test.ts +420 -0
- package/skills/octocode-code-engineer/src/analysis/discovery.ts +87 -0
- package/skills/octocode-code-engineer/src/analysis/graph-analytics.test.ts +449 -0
- package/skills/octocode-code-engineer/src/analysis/graph-analytics.ts +534 -0
- package/skills/octocode-code-engineer/src/analysis/semantic.test.ts +1533 -0
- package/skills/octocode-code-engineer/src/analysis/semantic.ts +830 -0
- package/skills/octocode-code-engineer/src/ast/helpers.test.ts +185 -0
- package/skills/octocode-code-engineer/src/ast/helpers.ts +62 -0
- package/skills/octocode-code-engineer/src/ast/metrics.test.ts +304 -0
- package/skills/octocode-code-engineer/src/ast/metrics.ts +204 -0
- package/skills/octocode-code-engineer/src/ast/search.test.ts +647 -0
- package/skills/octocode-code-engineer/src/ast/search.ts +648 -0
- package/skills/octocode-code-engineer/src/ast/tree-search.test.ts +199 -0
- package/skills/octocode-code-engineer/src/ast/tree-search.ts +392 -0
- package/skills/octocode-code-engineer/src/ast/tree-sitter.test.ts +407 -0
- package/skills/octocode-code-engineer/src/ast/tree-sitter.ts +402 -0
- package/skills/octocode-code-engineer/src/ast/ts-analyzer.test.ts +1864 -0
- package/skills/octocode-code-engineer/src/ast/ts-analyzer.ts +509 -0
- package/skills/octocode-code-engineer/src/collectors/chains.ts +74 -0
- package/skills/octocode-code-engineer/src/collectors/effects.test.ts +490 -0
- package/skills/octocode-code-engineer/src/collectors/effects.ts +332 -0
- package/skills/octocode-code-engineer/src/collectors/input-sources.test.ts +144 -0
- package/skills/octocode-code-engineer/src/collectors/input-sources.ts +196 -0
- package/skills/octocode-code-engineer/src/collectors/performance.test.ts +82 -0
- package/skills/octocode-code-engineer/src/collectors/performance.ts +141 -0
- package/skills/octocode-code-engineer/src/collectors/prototype-pollution.test.ts +55 -0
- package/skills/octocode-code-engineer/src/collectors/prototype-pollution.ts +162 -0
- package/skills/octocode-code-engineer/src/collectors/security.test.ts +124 -0
- package/skills/octocode-code-engineer/src/collectors/security.ts +309 -0
- package/skills/octocode-code-engineer/src/collectors/test-profile.test.ts +97 -0
- package/skills/octocode-code-engineer/src/collectors/test-profile.ts +269 -0
- package/skills/octocode-code-engineer/src/common/is-direct-run.test.ts +32 -0
- package/skills/octocode-code-engineer/src/common/is-direct-run.ts +13 -0
- package/skills/octocode-code-engineer/src/common/utils.test.ts +463 -0
- package/skills/octocode-code-engineer/src/common/utils.ts +304 -0
- package/skills/octocode-code-engineer/src/detectors/code-quality.ts +966 -0
- package/skills/octocode-code-engineer/src/detectors/cohesion.ts +539 -0
- package/skills/octocode-code-engineer/src/detectors/coupling.ts +323 -0
- package/skills/octocode-code-engineer/src/detectors/cycle.ts +349 -0
- package/skills/octocode-code-engineer/src/detectors/dead-code.ts +320 -0
- package/skills/octocode-code-engineer/src/detectors/import-style.ts +376 -0
- package/skills/octocode-code-engineer/src/detectors/index.test.ts +3061 -0
- package/skills/octocode-code-engineer/src/detectors/index.ts +88 -0
- package/skills/octocode-code-engineer/src/detectors/security.test.ts +882 -0
- package/skills/octocode-code-engineer/src/detectors/security.ts +821 -0
- package/skills/octocode-code-engineer/src/detectors/semantic.ts +758 -0
- package/skills/octocode-code-engineer/src/detectors/shared.ts +49 -0
- package/skills/octocode-code-engineer/src/detectors/test-quality.test.ts +388 -0
- package/skills/octocode-code-engineer/src/detectors/test-quality.ts +367 -0
- package/skills/octocode-code-engineer/src/index.test.ts +4425 -0
- package/skills/octocode-code-engineer/src/index.ts +403 -0
- package/skills/octocode-code-engineer/src/pipeline/cache.test.ts +199 -0
- package/skills/octocode-code-engineer/src/pipeline/cache.ts +130 -0
- package/skills/octocode-code-engineer/src/pipeline/cli.test.ts +493 -0
- package/skills/octocode-code-engineer/src/pipeline/cli.ts +344 -0
- package/skills/octocode-code-engineer/src/pipeline/main.test.ts +174 -0
- package/skills/octocode-code-engineer/src/pipeline/main.ts +1074 -0
- package/skills/octocode-code-engineer/src/pipeline.test.ts +84 -0
- package/skills/octocode-code-engineer/src/reporting/analysis.test.ts +782 -0
- package/skills/octocode-code-engineer/src/reporting/analysis.ts +688 -0
- package/skills/octocode-code-engineer/src/reporting/output-contract.test.ts +463 -0
- package/skills/octocode-code-engineer/src/reporting/summary-md.test.ts +421 -0
- package/skills/octocode-code-engineer/src/reporting/summary-md.ts +714 -0
- package/skills/octocode-code-engineer/src/reporting/writer.ts +430 -0
- package/skills/octocode-code-engineer/src/sanity.test.ts +47 -0
- package/skills/octocode-code-engineer/src/types/constants.ts +248 -0
- package/skills/octocode-code-engineer/src/types/index.ts +80 -0
- package/skills/octocode-code-engineer/src/types/interfaces.ts +682 -0
- package/skills/octocode-code-engineer/tsconfig.json +17 -0
- package/skills/octocode-code-engineer/vitest.config.ts +8 -0
- package/skills/octocode-documentation-writer/README.md +113 -0
- package/skills/octocode-documentation-writer/SKILL.md +886 -0
- package/skills/octocode-documentation-writer/references/agent-discovery-analysis.md +453 -0
- package/skills/octocode-documentation-writer/references/agent-documentation-writer.md +255 -0
- package/skills/octocode-documentation-writer/references/agent-engineer-questions.md +247 -0
- package/skills/octocode-documentation-writer/references/agent-orchestrator.md +370 -0
- package/skills/octocode-documentation-writer/references/agent-qa-validator.md +227 -0
- package/skills/octocode-documentation-writer/references/agent-researcher.md +250 -0
- package/skills/octocode-documentation-writer/schemas/analysis-schema.json +886 -0
- package/skills/octocode-documentation-writer/schemas/discovery-tasks.json +96 -0
- package/skills/octocode-documentation-writer/schemas/documentation-structure.json +373 -0
- package/skills/octocode-documentation-writer/schemas/partial-discovery-schema.json +102 -0
- package/skills/octocode-documentation-writer/schemas/partial-research-schema.json +98 -0
- package/skills/octocode-documentation-writer/schemas/qa-results-schema.json +113 -0
- package/skills/octocode-documentation-writer/schemas/questions-schema.json +228 -0
- package/skills/octocode-documentation-writer/schemas/research-schema.json +104 -0
- package/skills/octocode-documentation-writer/schemas/state-schema.json +222 -0
- package/skills/octocode-documentation-writer/schemas/work-assignments-schema.json +74 -0
- package/skills/octocode-plan/SKILL.md +122 -116
- package/skills/octocode-prompt-optimizer/SKILL.md +617 -0
- package/skills/octocode-pull-request-reviewer/README.md +249 -0
- package/skills/octocode-pull-request-reviewer/SKILL.md +479 -0
- package/skills/octocode-pull-request-reviewer/references/dependency-check.md +74 -0
- package/skills/octocode-pull-request-reviewer/references/domain-reviewers.md +24 -0
- package/skills/octocode-pull-request-reviewer/references/execution-lifecycle.md +441 -0
- package/skills/octocode-pull-request-reviewer/references/flow-analysis-protocol.md +64 -0
- package/skills/octocode-pull-request-reviewer/references/output-template.md +174 -0
- package/skills/octocode-pull-request-reviewer/references/parallel-agent-protocol.md +182 -0
- package/skills/octocode-pull-request-reviewer/references/review-guidelines.md +26 -0
- package/skills/octocode-pull-request-reviewer/references/verification-checklist.md +40 -0
- package/skills/octocode-research/.claude/settings.local.json +46 -0
- package/skills/octocode-research/.octocode/plan/code-review-fixes/plan.md +312 -0
- package/skills/octocode-research/.octocode/plan/code-review-fixes/research.md +212 -0
- package/skills/octocode-research/.octocode/plans/NODE_SERVER_START_PLAN.md +755 -0
- package/skills/octocode-research/.octocode/research/code-review/research.md +371 -0
- package/skills/octocode-research/.octocode/review/IMPROVEMENTS.md +391 -0
- package/skills/octocode-research/.octocode/review/REVIEW_PLAN.md +289 -0
- package/skills/octocode-research/.octocode/review/REVIEW_REPORT.md +356 -0
- package/skills/octocode-research/AGENTS.md +349 -0
- package/skills/octocode-research/README.md +494 -0
- package/skills/octocode-research/SKILL.md +652 -274
- package/skills/octocode-research/docs/API_REFERENCE.md +562 -0
- package/skills/octocode-research/docs/ARCHITECTURE.md +554 -0
- package/skills/octocode-research/docs/FLOWS.md +577 -0
- package/skills/octocode-research/docs/OVERVIEW.md +564 -0
- package/skills/octocode-research/docs/SERVER_FLOWS.md +631 -0
- package/skills/octocode-research/ecosystem.config.cjs +88 -0
- package/skills/octocode-research/eslint.config.mjs +27 -0
- package/skills/octocode-research/package.json +84 -0
- package/skills/octocode-research/references/GUARDRAILS.md +40 -0
- package/skills/octocode-research/references/PARALLEL_AGENT_PROTOCOL.md +178 -0
- package/skills/octocode-research/references/roast-prompt.md +149 -0
- package/skills/octocode-research/scripts/server-init.d.ts +2 -0
- package/skills/octocode-research/scripts/server-init.js +2 -0
- package/skills/octocode-research/scripts/server.d.ts +8 -0
- package/skills/octocode-research/scripts/server.js +445 -0
- package/skills/octocode-research/src/__tests__/integration/circuitBreaker.test.ts +205 -0
- package/skills/octocode-research/src/__tests__/integration/routes.test.ts +374 -0
- package/skills/octocode-research/src/__tests__/unit/circuitBreaker.test.ts +245 -0
- package/skills/octocode-research/src/__tests__/unit/errorHandler.test.ts +183 -0
- package/skills/octocode-research/src/__tests__/unit/httpPreprocess.test.ts +157 -0
- package/skills/octocode-research/src/__tests__/unit/logger.test.ts +143 -0
- package/skills/octocode-research/src/__tests__/unit/queryParser.test.ts +130 -0
- package/skills/octocode-research/src/__tests__/unit/responseBuilder.test.ts +469 -0
- package/skills/octocode-research/src/__tests__/unit/retry.test.ts +205 -0
- package/skills/octocode-research/src/index.ts +186 -0
- package/skills/octocode-research/src/mcpCache.ts +49 -0
- package/skills/octocode-research/src/middleware/errorHandler.ts +65 -0
- package/skills/octocode-research/src/middleware/logger.ts +61 -0
- package/skills/octocode-research/src/middleware/queryParser.ts +115 -0
- package/skills/octocode-research/src/middleware/readiness.ts +17 -0
- package/skills/octocode-research/src/routes/github.ts +197 -0
- package/skills/octocode-research/src/routes/local.ts +175 -0
- package/skills/octocode-research/src/routes/lsp.ts +177 -0
- package/skills/octocode-research/src/routes/package.ts +127 -0
- package/skills/octocode-research/src/routes/prompts.ts +138 -0
- package/skills/octocode-research/src/routes/tools.ts +677 -0
- package/skills/octocode-research/src/server-init.ts +363 -0
- package/skills/octocode-research/src/server.ts +285 -0
- package/skills/octocode-research/src/types/errorGuards.ts +151 -0
- package/skills/octocode-research/src/types/express.d.ts +76 -0
- package/skills/octocode-research/src/types/guards.ts +98 -0
- package/skills/octocode-research/src/types/mcp.ts +119 -0
- package/skills/octocode-research/src/types/responses.ts +199 -0
- package/skills/octocode-research/src/types/toolTypes.ts +33 -0
- package/skills/octocode-research/src/utils/asyncTimeout.ts +116 -0
- package/skills/octocode-research/src/utils/circuitBreaker.ts +492 -0
- package/skills/octocode-research/src/utils/colors.ts +53 -0
- package/skills/octocode-research/src/utils/errorQueue.ts +71 -0
- package/skills/octocode-research/src/utils/logEmoji.ts +103 -0
- package/skills/octocode-research/src/utils/logger.ts +413 -0
- package/skills/octocode-research/src/utils/resilience.ts +169 -0
- package/skills/octocode-research/src/utils/responseBuilder.ts +495 -0
- package/skills/octocode-research/src/utils/responseFactory.ts +100 -0
- package/skills/octocode-research/src/utils/responseParser.ts +272 -0
- package/skills/octocode-research/src/utils/retry.ts +280 -0
- package/skills/octocode-research/src/utils/routeFactory.ts +117 -0
- package/skills/octocode-research/src/utils/url.ts +20 -0
- package/skills/octocode-research/src/validation/httpPreprocess.ts +155 -0
- package/skills/octocode-research/src/validation/index.ts +2 -0
- package/skills/octocode-research/src/validation/schemas.ts +578 -0
- package/skills/octocode-research/src/validation/toolCallSchema.ts +132 -0
- package/skills/octocode-research/tsconfig.json +21 -0
- package/skills/octocode-research/tsdown.config.ts +42 -0
- package/skills/octocode-research/vitest.config.ts +20 -0
- package/skills/octocode-researcher/SKILL.md +461 -0
- package/skills/octocode-researcher/references/fallbacks.md +120 -0
- package/skills/{octocode-local-search → octocode-researcher}/references/tool-reference.md +132 -49
- package/skills/{octocode-local-search → octocode-researcher}/references/workflow-patterns.md +204 -4
- package/skills/octocode-rfc-generator/SKILL.md +223 -0
- package/skills/octocode-rfc-generator/references/rfc-template.md +193 -0
- package/skills/octocode-roast/SKILL.md +63 -21
- package/skills/octocode-implement/SKILL.md +0 -293
- package/skills/octocode-implement/references/execution-phases.md +0 -317
- package/skills/octocode-implement/references/tool-reference.md +0 -403
- package/skills/octocode-implement/references/workflow-patterns.md +0 -385
- package/skills/octocode-local-search/SKILL.md +0 -449
- package/skills/octocode-pr-review/SKILL.md +0 -391
- package/skills/octocode-pr-review/references/domain-reviewers.md +0 -105
- package/skills/octocode-pr-review/references/execution-lifecycle.md +0 -116
- package/skills/octocode-pr-review/references/research-flows.md +0 -75
- package/skills/octocode-research/references/tool-reference.md +0 -304
- package/skills/octocode-research/references/workflow-patterns.md +0 -325
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { errorQueue } from './errorQueue.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Timeout wrapper for fire-and-forget async operations.
|
|
5
|
+
*
|
|
6
|
+
* Prevents unbounded async operations from hanging indefinitely.
|
|
7
|
+
* Uses Promise.race with AbortController for clean cancellation.
|
|
8
|
+
*
|
|
9
|
+
* @module utils/asyncTimeout
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Default timeout for fire-and-forget operations (5 seconds).
|
|
14
|
+
* Tuned for logging/telemetry operations that should complete quickly.
|
|
15
|
+
*/
|
|
16
|
+
const DEFAULT_TIMEOUT_MS = 5000;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Execute an async operation with timeout protection.
|
|
20
|
+
*
|
|
21
|
+
* Fire-and-forget pattern with built-in timeout to prevent:
|
|
22
|
+
* - Memory leaks from unresolved promises
|
|
23
|
+
* - Resource exhaustion from hanging operations
|
|
24
|
+
* - Cascading failures from slow logging/telemetry
|
|
25
|
+
*
|
|
26
|
+
* @param operation - Async operation to execute
|
|
27
|
+
* @param timeoutMs - Maximum time to wait (default: 5000ms)
|
|
28
|
+
* @param context - Context string for error logging
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```typescript
|
|
32
|
+
* // Instead of:
|
|
33
|
+
* logSessionError(tool, code).catch(err => errorQueue.push(err, 'logSessionError'));
|
|
34
|
+
*
|
|
35
|
+
* // Use:
|
|
36
|
+
* fireAndForgetWithTimeout(
|
|
37
|
+
* () => logSessionError(tool, code),
|
|
38
|
+
* 5000,
|
|
39
|
+
* 'logSessionError'
|
|
40
|
+
* );
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export function fireAndForgetWithTimeout(
|
|
44
|
+
operation: () => Promise<unknown>,
|
|
45
|
+
timeoutMs = DEFAULT_TIMEOUT_MS,
|
|
46
|
+
context = 'fireAndForget'
|
|
47
|
+
): void {
|
|
48
|
+
const controller = new AbortController();
|
|
49
|
+
const { signal } = controller;
|
|
50
|
+
|
|
51
|
+
// Create timeout promise
|
|
52
|
+
const timeoutPromise = new Promise<never>((_, reject) => {
|
|
53
|
+
const timeoutId = setTimeout(() => {
|
|
54
|
+
controller.abort();
|
|
55
|
+
reject(new Error(`Operation timed out after ${timeoutMs}ms`));
|
|
56
|
+
}, timeoutMs);
|
|
57
|
+
|
|
58
|
+
// Clean up timeout if operation completes first
|
|
59
|
+
signal.addEventListener('abort', () => clearTimeout(timeoutId), { once: true });
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Race operation against timeout
|
|
63
|
+
Promise.race([operation(), timeoutPromise])
|
|
64
|
+
.catch((err: unknown) => {
|
|
65
|
+
// Log to error queue - never throw from fire-and-forget
|
|
66
|
+
errorQueue.push(
|
|
67
|
+
err instanceof Error ? err : new Error(String(err)),
|
|
68
|
+
context
|
|
69
|
+
);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Execute an async operation with timeout and return result.
|
|
75
|
+
*
|
|
76
|
+
* Unlike fireAndForgetWithTimeout, this returns the result or throws on timeout.
|
|
77
|
+
* Use for operations where you need the result but want timeout protection.
|
|
78
|
+
*
|
|
79
|
+
* @param operation - Async operation to execute
|
|
80
|
+
* @param timeoutMs - Maximum time to wait
|
|
81
|
+
* @param context - Context string for timeout error message
|
|
82
|
+
* @returns Promise resolving to operation result
|
|
83
|
+
* @throws Error if operation times out
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```typescript
|
|
87
|
+
* const result = await withTimeout(
|
|
88
|
+
* () => fetchData(),
|
|
89
|
+
* 3000,
|
|
90
|
+
* 'fetchData'
|
|
91
|
+
* );
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
export async function withTimeout<T>(
|
|
95
|
+
operation: () => Promise<T>,
|
|
96
|
+
timeoutMs: number,
|
|
97
|
+
context = 'withTimeout'
|
|
98
|
+
): Promise<T> {
|
|
99
|
+
const controller = new AbortController();
|
|
100
|
+
const { signal } = controller;
|
|
101
|
+
|
|
102
|
+
const timeoutPromise = new Promise<never>((_, reject) => {
|
|
103
|
+
const timeoutId = setTimeout(() => {
|
|
104
|
+
controller.abort();
|
|
105
|
+
reject(new Error(`${context}: Operation timed out after ${timeoutMs}ms`));
|
|
106
|
+
}, timeoutMs);
|
|
107
|
+
|
|
108
|
+
signal.addEventListener('abort', () => clearTimeout(timeoutId), { once: true });
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
return await Promise.race([operation(), timeoutPromise]);
|
|
113
|
+
} finally {
|
|
114
|
+
controller.abort(); // Clean up if operation completed
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
import { agentLog, warnLog, successLog, errorLog } from './colors.js';
|
|
2
|
+
import { logRateLimit } from '../index.js';
|
|
3
|
+
import { fireAndForgetWithTimeout } from './asyncTimeout.js';
|
|
4
|
+
/**
|
|
5
|
+
* Circuit breaker pattern for LSP and external services.
|
|
6
|
+
*
|
|
7
|
+
* Prevents cascading failures by temporarily stopping calls to failing services.
|
|
8
|
+
*
|
|
9
|
+
* @module utils/circuitBreaker
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Circuit breaker states
|
|
14
|
+
*/
|
|
15
|
+
type CircuitState = 'closed' | 'open' | 'half-open';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Memory management constants
|
|
19
|
+
*/
|
|
20
|
+
const MAX_CIRCUITS = 100; // Maximum number of circuits to prevent unbounded growth
|
|
21
|
+
const CIRCUIT_TTL_MS = 3600000; // 1 hour TTL for stale circuits
|
|
22
|
+
const CLEANUP_INTERVAL_MS = 600000; // 10 minutes between cleanup runs
|
|
23
|
+
|
|
24
|
+
interface CircuitRecord {
|
|
25
|
+
failures: number;
|
|
26
|
+
successes: number;
|
|
27
|
+
lastFailure: number;
|
|
28
|
+
lastAttempt: number;
|
|
29
|
+
state: CircuitState;
|
|
30
|
+
createdAt: number; // Timestamp when circuit was created
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Circuit breaker configuration
|
|
35
|
+
*/
|
|
36
|
+
export interface CircuitBreakerConfig {
|
|
37
|
+
/** Number of failures before opening circuit */
|
|
38
|
+
failureThreshold: number;
|
|
39
|
+
/** Number of successes in half-open to close circuit */
|
|
40
|
+
successThreshold: number;
|
|
41
|
+
/** Time in ms before attempting half-open */
|
|
42
|
+
resetTimeoutMs: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Default circuit breaker configuration.
|
|
47
|
+
* Tuned for balance between fault tolerance and quick recovery.
|
|
48
|
+
*/
|
|
49
|
+
const DEFAULT_CONFIG: CircuitBreakerConfig = {
|
|
50
|
+
// 3 failures: Quick to detect persistent issues,
|
|
51
|
+
// but tolerant of occasional transient errors.
|
|
52
|
+
failureThreshold: 3,
|
|
53
|
+
|
|
54
|
+
// 2 successes: Requires service to prove stability
|
|
55
|
+
// before fully resuming (prevents flapping).
|
|
56
|
+
successThreshold: 2,
|
|
57
|
+
|
|
58
|
+
// 30s timeout: Allows services time to recover from
|
|
59
|
+
// rate limits or temporary outages before retrying.
|
|
60
|
+
resetTimeoutMs: 30000,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Circuit breaker registry
|
|
65
|
+
*/
|
|
66
|
+
const circuits = new Map<string, CircuitRecord>();
|
|
67
|
+
const configs = new Map<string, CircuitBreakerConfig>();
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Get or create circuit breaker for a named service.
|
|
71
|
+
* Enforces MAX_CIRCUITS limit by cleaning up stale circuits when limit is reached.
|
|
72
|
+
*/
|
|
73
|
+
function getCircuit(name: string): CircuitRecord {
|
|
74
|
+
if (!circuits.has(name)) {
|
|
75
|
+
// Enforce circuit limit by cleaning up stale circuits
|
|
76
|
+
if (circuits.size >= MAX_CIRCUITS) {
|
|
77
|
+
cleanupStaleCircuits();
|
|
78
|
+
// If still at limit after cleanup, remove oldest circuit
|
|
79
|
+
if (circuits.size >= MAX_CIRCUITS) {
|
|
80
|
+
const oldestKey = findOldestCircuit();
|
|
81
|
+
if (oldestKey) {
|
|
82
|
+
circuits.delete(oldestKey);
|
|
83
|
+
configs.delete(oldestKey);
|
|
84
|
+
console.log(warnLog(`⚠️ Evicted oldest circuit '${oldestKey}' to make room`));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
circuits.set(name, {
|
|
90
|
+
failures: 0,
|
|
91
|
+
successes: 0,
|
|
92
|
+
lastFailure: 0,
|
|
93
|
+
lastAttempt: 0,
|
|
94
|
+
state: 'closed',
|
|
95
|
+
createdAt: Date.now(),
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
return circuits.get(name)!;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Find the oldest circuit by lastAttempt time
|
|
103
|
+
*/
|
|
104
|
+
function findOldestCircuit(): string | null {
|
|
105
|
+
let oldest: string | null = null;
|
|
106
|
+
let oldestTime = Infinity;
|
|
107
|
+
|
|
108
|
+
for (const [name, circuit] of circuits) {
|
|
109
|
+
if (circuit.lastAttempt < oldestTime) {
|
|
110
|
+
oldestTime = circuit.lastAttempt;
|
|
111
|
+
oldest = name;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return oldest;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Get configuration for a circuit
|
|
120
|
+
*/
|
|
121
|
+
function getConfig(name: string): CircuitBreakerConfig {
|
|
122
|
+
return configs.get(name) || DEFAULT_CONFIG;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Configure a specific circuit breaker
|
|
127
|
+
*/
|
|
128
|
+
export function configureCircuit(
|
|
129
|
+
name: string,
|
|
130
|
+
config: Partial<CircuitBreakerConfig>
|
|
131
|
+
): void {
|
|
132
|
+
configs.set(name, { ...DEFAULT_CONFIG, ...config });
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Execute operation with circuit breaker protection.
|
|
137
|
+
*
|
|
138
|
+
* @param name - Circuit breaker name (e.g., 'lsp', 'github')
|
|
139
|
+
* @param operation - Async operation to execute
|
|
140
|
+
* @param fallback - Optional fallback when circuit is open
|
|
141
|
+
* @returns Operation result or fallback
|
|
142
|
+
* @throws CircuitOpenError if circuit is open and no fallback provided
|
|
143
|
+
*
|
|
144
|
+
* @example
|
|
145
|
+
* ```typescript
|
|
146
|
+
* const result = await withCircuitBreaker(
|
|
147
|
+
* 'lsp',
|
|
148
|
+
* () => lspGotoDefinition({ queries }),
|
|
149
|
+
* () => ({ fallback: true, locations: [] })
|
|
150
|
+
* );
|
|
151
|
+
* ```
|
|
152
|
+
*/
|
|
153
|
+
export async function withCircuitBreaker<T>(
|
|
154
|
+
name: string,
|
|
155
|
+
operation: () => Promise<T>,
|
|
156
|
+
fallback?: () => T | Promise<T>
|
|
157
|
+
): Promise<T> {
|
|
158
|
+
const circuit = getCircuit(name);
|
|
159
|
+
const config = getConfig(name);
|
|
160
|
+
const now = Date.now();
|
|
161
|
+
|
|
162
|
+
// Update last attempt time
|
|
163
|
+
circuit.lastAttempt = now;
|
|
164
|
+
|
|
165
|
+
// Check circuit state
|
|
166
|
+
if (circuit.state === 'open') {
|
|
167
|
+
// Check if we should try half-open
|
|
168
|
+
if (now - circuit.lastFailure > config.resetTimeoutMs) {
|
|
169
|
+
circuit.state = 'half-open';
|
|
170
|
+
console.log(warnLog(`🟡 Circuit ${name} entering half-open state`));
|
|
171
|
+
} else {
|
|
172
|
+
// Circuit is open - use fallback or throw
|
|
173
|
+
console.log(
|
|
174
|
+
`🔴 Circuit ${name} is OPEN - ${Math.ceil((circuit.lastFailure + config.resetTimeoutMs - now) / 1000)}s until retry`
|
|
175
|
+
);
|
|
176
|
+
if (fallback) {
|
|
177
|
+
return fallback();
|
|
178
|
+
}
|
|
179
|
+
throw new CircuitOpenError(name, circuit.lastFailure + config.resetTimeoutMs - now);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
try {
|
|
184
|
+
const result = await operation();
|
|
185
|
+
|
|
186
|
+
// Success - handle state transition
|
|
187
|
+
if (circuit.state === 'half-open') {
|
|
188
|
+
circuit.successes++;
|
|
189
|
+
if (circuit.successes >= config.successThreshold) {
|
|
190
|
+
circuit.state = 'closed';
|
|
191
|
+
circuit.failures = 0;
|
|
192
|
+
circuit.successes = 0;
|
|
193
|
+
console.log(successLog(`🟢 Circuit ${name} CLOSED after recovery`));
|
|
194
|
+
}
|
|
195
|
+
} else {
|
|
196
|
+
// Reset failures on success in closed state
|
|
197
|
+
circuit.failures = 0;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return result;
|
|
201
|
+
} catch (error) {
|
|
202
|
+
// Failure - handle state transition
|
|
203
|
+
circuit.failures++;
|
|
204
|
+
circuit.lastFailure = now;
|
|
205
|
+
circuit.successes = 0;
|
|
206
|
+
|
|
207
|
+
if (circuit.state === 'half-open') {
|
|
208
|
+
// Failed in half-open - back to open
|
|
209
|
+
circuit.state = 'open';
|
|
210
|
+
console.log(errorLog(`🔴 Circuit ${name} back to OPEN after half-open failure`));
|
|
211
|
+
// Log rate limit/circuit open event to session telemetry
|
|
212
|
+
fireAndForgetWithTimeout(
|
|
213
|
+
() => logRateLimit({
|
|
214
|
+
limit_type: 'secondary',
|
|
215
|
+
api_method: 'circuit_breaker',
|
|
216
|
+
retry_after_seconds: config.resetTimeoutMs / 1000,
|
|
217
|
+
details: `Circuit '${name}' back to OPEN after half-open failure`
|
|
218
|
+
}),
|
|
219
|
+
5000,
|
|
220
|
+
'logRateLimit'
|
|
221
|
+
);
|
|
222
|
+
} else if (circuit.failures >= config.failureThreshold) {
|
|
223
|
+
// Too many failures - open circuit
|
|
224
|
+
circuit.state = 'open';
|
|
225
|
+
console.log(
|
|
226
|
+
`🔴 Circuit ${name} OPENED after ${circuit.failures} failures`
|
|
227
|
+
);
|
|
228
|
+
// Log rate limit/circuit open event to session telemetry
|
|
229
|
+
fireAndForgetWithTimeout(
|
|
230
|
+
() => logRateLimit({
|
|
231
|
+
limit_type: 'secondary',
|
|
232
|
+
api_method: 'circuit_breaker',
|
|
233
|
+
retry_after_seconds: config.resetTimeoutMs / 1000,
|
|
234
|
+
details: `Circuit '${name}' OPENED after ${circuit.failures} failures`
|
|
235
|
+
}),
|
|
236
|
+
5000,
|
|
237
|
+
'logRateLimit'
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
throw error;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Get current state of a circuit breaker
|
|
247
|
+
*/
|
|
248
|
+
export function getCircuitState(name: string): {
|
|
249
|
+
state: CircuitState;
|
|
250
|
+
failures: number;
|
|
251
|
+
lastFailure: number;
|
|
252
|
+
isHealthy: boolean;
|
|
253
|
+
} {
|
|
254
|
+
const circuit = getCircuit(name);
|
|
255
|
+
return {
|
|
256
|
+
state: circuit.state,
|
|
257
|
+
failures: circuit.failures,
|
|
258
|
+
lastFailure: circuit.lastFailure,
|
|
259
|
+
isHealthy: circuit.state === 'closed',
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Reset a circuit breaker to closed state
|
|
265
|
+
*/
|
|
266
|
+
export function resetCircuit(name: string): void {
|
|
267
|
+
const circuit = getCircuit(name);
|
|
268
|
+
circuit.state = 'closed';
|
|
269
|
+
circuit.failures = 0;
|
|
270
|
+
circuit.successes = 0;
|
|
271
|
+
circuit.lastFailure = 0;
|
|
272
|
+
console.log(agentLog(`🔄 Circuit ${name} manually reset to CLOSED`));
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Get all circuit states (for health endpoint)
|
|
277
|
+
*/
|
|
278
|
+
export function getAllCircuitStates(): Record<
|
|
279
|
+
string,
|
|
280
|
+
{ state: CircuitState; failures: number; isHealthy: boolean }
|
|
281
|
+
> {
|
|
282
|
+
const states: Record<
|
|
283
|
+
string,
|
|
284
|
+
{ state: CircuitState; failures: number; isHealthy: boolean }
|
|
285
|
+
> = {};
|
|
286
|
+
|
|
287
|
+
for (const [name, circuit] of circuits) {
|
|
288
|
+
states[name] = {
|
|
289
|
+
state: circuit.state,
|
|
290
|
+
failures: circuit.failures,
|
|
291
|
+
isHealthy: circuit.state === 'closed',
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return states;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Error thrown when circuit is open
|
|
300
|
+
*/
|
|
301
|
+
export class CircuitOpenError extends Error {
|
|
302
|
+
readonly circuitName: string;
|
|
303
|
+
readonly retryAfterMs: number;
|
|
304
|
+
|
|
305
|
+
constructor(name: string, retryAfterMs: number) {
|
|
306
|
+
super(`Circuit breaker '${name}' is open. Retry after ${Math.ceil(retryAfterMs / 1000)}s`);
|
|
307
|
+
this.name = 'CircuitOpenError';
|
|
308
|
+
this.circuitName = name;
|
|
309
|
+
this.retryAfterMs = retryAfterMs;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// =============================================================================
|
|
314
|
+
// Pre-configured circuits (per-tool granularity)
|
|
315
|
+
// =============================================================================
|
|
316
|
+
|
|
317
|
+
// -----------------------------------------------------------------------------
|
|
318
|
+
// GitHub circuits - split by API endpoint (different rate limits)
|
|
319
|
+
// -----------------------------------------------------------------------------
|
|
320
|
+
|
|
321
|
+
// GitHub Search API - aggressive rate limiting (30 req/min)
|
|
322
|
+
configureCircuit('github:search', {
|
|
323
|
+
failureThreshold: 2, // 2 failures = likely rate limited
|
|
324
|
+
successThreshold: 1, // Single success proves API recovered
|
|
325
|
+
resetTimeoutMs: 60000, // 60s: Give search rate limits time to reset
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
// GitHub Content API - higher limits, separate quota
|
|
329
|
+
configureCircuit('github:content', {
|
|
330
|
+
failureThreshold: 3, // 3 failures = more tolerant
|
|
331
|
+
successThreshold: 1,
|
|
332
|
+
resetTimeoutMs: 30000, // 30s: Content API recovers faster
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
// GitHub PR API - separate quota from search
|
|
336
|
+
configureCircuit('github:pulls', {
|
|
337
|
+
failureThreshold: 2, // 2 failures = likely rate limited
|
|
338
|
+
successThreshold: 1,
|
|
339
|
+
resetTimeoutMs: 60000, // 60s: PR API has similar limits to search
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
// Legacy fallback for any uncategorized github tools
|
|
343
|
+
configureCircuit('github', {
|
|
344
|
+
failureThreshold: 2,
|
|
345
|
+
successThreshold: 1,
|
|
346
|
+
resetTimeoutMs: 60000,
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
// -----------------------------------------------------------------------------
|
|
350
|
+
// LSP circuits - split by operation weight
|
|
351
|
+
// -----------------------------------------------------------------------------
|
|
352
|
+
|
|
353
|
+
// LSP Navigation (definition, references) - fast, single lookup
|
|
354
|
+
configureCircuit('lsp:navigation', {
|
|
355
|
+
failureThreshold: 3, // 3 failures = likely LSP issue
|
|
356
|
+
successThreshold: 1, // Single success proves LSP recovered
|
|
357
|
+
resetTimeoutMs: 10000, // 10s: Quick recovery expected
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
// LSP Call Hierarchy - heavier recursive operation
|
|
361
|
+
configureCircuit('lsp:hierarchy', {
|
|
362
|
+
failureThreshold: 2, // 2 failures = operation too heavy or LSP struggling
|
|
363
|
+
successThreshold: 1,
|
|
364
|
+
resetTimeoutMs: 15000, // 15s: Slightly longer for heavy ops
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
// Legacy fallback
|
|
368
|
+
configureCircuit('lsp', {
|
|
369
|
+
failureThreshold: 3,
|
|
370
|
+
successThreshold: 1,
|
|
371
|
+
resetTimeoutMs: 10000,
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
// -----------------------------------------------------------------------------
|
|
375
|
+
// Local circuits - unified (same failure mode: filesystem)
|
|
376
|
+
// -----------------------------------------------------------------------------
|
|
377
|
+
|
|
378
|
+
configureCircuit('local', {
|
|
379
|
+
failureThreshold: 5, // 5 failures = likely persistent issue
|
|
380
|
+
successThreshold: 1, // Single success proves local ops recovered
|
|
381
|
+
resetTimeoutMs: 5000, // 5s: Local ops should recover quickly
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
// -----------------------------------------------------------------------------
|
|
385
|
+
// Package circuits - unified (npm/PyPI)
|
|
386
|
+
// -----------------------------------------------------------------------------
|
|
387
|
+
|
|
388
|
+
configureCircuit('package', {
|
|
389
|
+
failureThreshold: 3, // 3 failures = likely rate limited or down
|
|
390
|
+
successThreshold: 1, // Single success proves API recovered
|
|
391
|
+
resetTimeoutMs: 45000, // 45s: Slightly faster than GitHub
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
// =============================================================================
|
|
395
|
+
// Automatic Cleanup (Memory Leak Prevention)
|
|
396
|
+
// =============================================================================
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Cleanup interval reference (for graceful shutdown)
|
|
400
|
+
*/
|
|
401
|
+
let cleanupIntervalId: ReturnType<typeof setInterval> | null = null;
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Remove stale circuits that haven't been used recently.
|
|
405
|
+
* A circuit is stale if it hasn't been attempted in CIRCUIT_TTL_MS.
|
|
406
|
+
*/
|
|
407
|
+
function cleanupStaleCircuits(): void {
|
|
408
|
+
const now = Date.now();
|
|
409
|
+
const staleCutoff = now - CIRCUIT_TTL_MS;
|
|
410
|
+
let removedCount = 0;
|
|
411
|
+
|
|
412
|
+
for (const [name, circuit] of circuits) {
|
|
413
|
+
// Don't remove circuits that are currently open (still tracking failure)
|
|
414
|
+
if (circuit.state !== 'open' && circuit.lastAttempt < staleCutoff) {
|
|
415
|
+
circuits.delete(name);
|
|
416
|
+
configs.delete(name);
|
|
417
|
+
removedCount++;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
if (removedCount > 0) {
|
|
422
|
+
console.log(agentLog(`🧹 Cleaned up ${removedCount} stale circuit(s)`));
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Start the periodic cleanup interval.
|
|
428
|
+
* Called automatically on module load.
|
|
429
|
+
*/
|
|
430
|
+
function startPeriodicCleanup(): void {
|
|
431
|
+
if (cleanupIntervalId) return; // Already running
|
|
432
|
+
|
|
433
|
+
cleanupIntervalId = setInterval(() => {
|
|
434
|
+
cleanupStaleCircuits();
|
|
435
|
+
}, CLEANUP_INTERVAL_MS);
|
|
436
|
+
|
|
437
|
+
// Unref the interval so it doesn't prevent process exit
|
|
438
|
+
if (cleanupIntervalId.unref) {
|
|
439
|
+
cleanupIntervalId.unref();
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Stop the periodic cleanup interval.
|
|
445
|
+
* Call during graceful shutdown.
|
|
446
|
+
*/
|
|
447
|
+
export function stopCircuitCleanup(): void {
|
|
448
|
+
if (cleanupIntervalId) {
|
|
449
|
+
clearInterval(cleanupIntervalId);
|
|
450
|
+
cleanupIntervalId = null;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Start periodic cleanup on module load
|
|
455
|
+
startPeriodicCleanup();
|
|
456
|
+
|
|
457
|
+
// =============================================================================
|
|
458
|
+
// Manual Cleanup Functions
|
|
459
|
+
// =============================================================================
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Clear a specific circuit breaker.
|
|
463
|
+
* Use for testing or when a service is decommissioned.
|
|
464
|
+
* @param name - Circuit breaker name to clear
|
|
465
|
+
* @returns true if circuit existed and was cleared
|
|
466
|
+
*/
|
|
467
|
+
export function clearCircuit(name: string): boolean {
|
|
468
|
+
const existed = circuits.delete(name);
|
|
469
|
+
configs.delete(name);
|
|
470
|
+
if (existed) {
|
|
471
|
+
console.log(agentLog(`🧹 Circuit ${name} cleared`));
|
|
472
|
+
}
|
|
473
|
+
return existed;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Clear all circuit breakers.
|
|
478
|
+
* Use for testing cleanup or server shutdown.
|
|
479
|
+
*/
|
|
480
|
+
export function clearAllCircuits(): void {
|
|
481
|
+
const count = circuits.size;
|
|
482
|
+
circuits.clear();
|
|
483
|
+
configs.clear();
|
|
484
|
+
console.log(agentLog(`🧹 Cleared ${count} circuit(s)`));
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Get the number of active circuits (for monitoring).
|
|
489
|
+
*/
|
|
490
|
+
export function getCircuitCount(): number {
|
|
491
|
+
return circuits.size;
|
|
492
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ANSI color codes for terminal output
|
|
3
|
+
*
|
|
4
|
+
* Color scheme:
|
|
5
|
+
* - AGENT (purple): Server/agent messages, startup, shutdown
|
|
6
|
+
* - RESULT (blue): Tool output, skill results
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const colors = {
|
|
10
|
+
// Reset
|
|
11
|
+
reset: '\x1b[0m',
|
|
12
|
+
|
|
13
|
+
// Agent messages - Purple/Magenta
|
|
14
|
+
agent: '\x1b[35m',
|
|
15
|
+
agentBright: '\x1b[95m',
|
|
16
|
+
|
|
17
|
+
// Results/Tool output - Blue
|
|
18
|
+
result: '\x1b[34m',
|
|
19
|
+
resultBright: '\x1b[94m',
|
|
20
|
+
|
|
21
|
+
// Status colors
|
|
22
|
+
success: '\x1b[32m', // Green
|
|
23
|
+
error: '\x1b[31m', // Red
|
|
24
|
+
warn: '\x1b[33m', // Yellow
|
|
25
|
+
|
|
26
|
+
// Dim for secondary info
|
|
27
|
+
dim: '\x1b[2m',
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// Helper functions for consistent formatting
|
|
31
|
+
export function agentLog(message: string): string {
|
|
32
|
+
return `${colors.agentBright}${message}${colors.reset}`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function resultLog(message: string): string {
|
|
36
|
+
return `${colors.resultBright}${message}${colors.reset}`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function successLog(message: string): string {
|
|
40
|
+
return `${colors.success}${message}${colors.reset}`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function errorLog(message: string): string {
|
|
44
|
+
return `${colors.error}${message}${colors.reset}`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function warnLog(message: string): string {
|
|
48
|
+
return `${colors.warn}${message}${colors.reset}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function dimLog(message: string): string {
|
|
52
|
+
return `${colors.dim}${message}${colors.reset}`;
|
|
53
|
+
}
|