octocode-cli 1.2.6 ā 1.2.8
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 +85 -142
- package/out/octocode-cli.js +7063 -6934
- 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,363 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server Init Script
|
|
3
|
+
*
|
|
4
|
+
* Handles server initialization with mutex lock:
|
|
5
|
+
* 1. Acquires atomic lock to prevent concurrent startups
|
|
6
|
+
* 2. Checks if server is running (health check)
|
|
7
|
+
* 3. Starts server if not running
|
|
8
|
+
* 4. Waits for status "ok"
|
|
9
|
+
* 5. Outputs "ok" on success
|
|
10
|
+
*
|
|
11
|
+
* Improvements:
|
|
12
|
+
* - Atomic lock using O_CREAT | O_EXCL | O_WRONLY (no race condition)
|
|
13
|
+
* - Exponential backoff retry for lock acquisition
|
|
14
|
+
* - Environment variable configuration
|
|
15
|
+
* - Spawn error handling
|
|
16
|
+
* - Better health check error logging
|
|
17
|
+
*
|
|
18
|
+
* Usage: npm run server-init
|
|
19
|
+
* Exit codes: 0 = success, 1 = error
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { spawn } from 'child_process';
|
|
23
|
+
import {
|
|
24
|
+
existsSync,
|
|
25
|
+
mkdirSync,
|
|
26
|
+
unlinkSync,
|
|
27
|
+
readFileSync,
|
|
28
|
+
constants,
|
|
29
|
+
openSync,
|
|
30
|
+
writeSync,
|
|
31
|
+
closeSync,
|
|
32
|
+
statSync,
|
|
33
|
+
} from 'fs';
|
|
34
|
+
import { join } from 'path';
|
|
35
|
+
import { homedir } from 'os';
|
|
36
|
+
|
|
37
|
+
// =============================================================================
|
|
38
|
+
// Configuration (Environment Variables)
|
|
39
|
+
// =============================================================================
|
|
40
|
+
|
|
41
|
+
const PORT = parseInt(process.env.OCTOCODE_PORT || '1987', 10);
|
|
42
|
+
const HEALTH_URL = `http://localhost:${PORT}/health`;
|
|
43
|
+
const LOCK_DIR = join(homedir(), '.octocode');
|
|
44
|
+
const LOCK_FILE = join(LOCK_DIR, 'server-init.lock');
|
|
45
|
+
const LOCK_TIMEOUT_MS = parseInt(process.env.OCTOCODE_LOCK_TIMEOUT || '60000', 10);
|
|
46
|
+
const MAX_WAIT_MS = parseInt(process.env.OCTOCODE_INIT_TIMEOUT || '30000', 10);
|
|
47
|
+
const POLL_INTERVAL_MS = parseInt(process.env.OCTOCODE_POLL_INTERVAL || '500', 10);
|
|
48
|
+
|
|
49
|
+
interface LockData {
|
|
50
|
+
pid: number;
|
|
51
|
+
timestamp: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
interface HealthResponse {
|
|
55
|
+
status: 'ok' | 'initializing' | string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// =============================================================================
|
|
59
|
+
// Atomic Mutex Lock (Based on Snyk pattern - O_EXCL for atomic creation)
|
|
60
|
+
// =============================================================================
|
|
61
|
+
|
|
62
|
+
function ensureLockDir(): void {
|
|
63
|
+
if (!existsSync(LOCK_DIR)) {
|
|
64
|
+
mkdirSync(LOCK_DIR, { recursive: true });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function isLockStale(lockPath: string): boolean {
|
|
69
|
+
if (!existsSync(lockPath)) return true;
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const content = readFileSync(lockPath, 'utf-8');
|
|
73
|
+
if (content && content.trim().length > 0) {
|
|
74
|
+
const data: LockData = JSON.parse(content);
|
|
75
|
+
|
|
76
|
+
// Check timestamp
|
|
77
|
+
if (Date.now() - data.timestamp > LOCK_TIMEOUT_MS) {
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Check if process exists
|
|
82
|
+
try {
|
|
83
|
+
process.kill(data.pid, 0);
|
|
84
|
+
return false; // Process exists, lock is valid
|
|
85
|
+
} catch {
|
|
86
|
+
return true; // Process doesn't exist
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
} catch {
|
|
90
|
+
// Fall back to mtime check
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Fallback: check file modification time
|
|
94
|
+
try {
|
|
95
|
+
const stats = statSync(lockPath);
|
|
96
|
+
return Date.now() - stats.mtimeMs > LOCK_TIMEOUT_MS;
|
|
97
|
+
} catch {
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function safeUnlink(filePath: string): void {
|
|
103
|
+
try {
|
|
104
|
+
if (existsSync(filePath)) {
|
|
105
|
+
unlinkSync(filePath);
|
|
106
|
+
}
|
|
107
|
+
} catch {
|
|
108
|
+
// Ignore errors
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function acquireLock(): Promise<boolean> {
|
|
113
|
+
ensureLockDir();
|
|
114
|
+
|
|
115
|
+
// Only retry for stale lock removal race condition, not for valid locks
|
|
116
|
+
const STALE_RETRY_LIMIT = 3;
|
|
117
|
+
|
|
118
|
+
for (let attempt = 0; attempt < STALE_RETRY_LIMIT; attempt++) {
|
|
119
|
+
try {
|
|
120
|
+
// Atomic create - fails with EEXIST if file exists
|
|
121
|
+
const fd = openSync(LOCK_FILE, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY);
|
|
122
|
+
|
|
123
|
+
const lockData: LockData = {
|
|
124
|
+
pid: process.pid,
|
|
125
|
+
timestamp: Date.now(),
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
writeSync(fd, JSON.stringify(lockData));
|
|
129
|
+
closeSync(fd);
|
|
130
|
+
return true;
|
|
131
|
+
} catch (err: unknown) {
|
|
132
|
+
const nodeErr = err as NodeJS.ErrnoException;
|
|
133
|
+
|
|
134
|
+
if (nodeErr.code === 'EEXIST') {
|
|
135
|
+
// Lock exists - check if stale
|
|
136
|
+
if (isLockStale(LOCK_FILE)) {
|
|
137
|
+
safeUnlink(LOCK_FILE);
|
|
138
|
+
// Small delay to avoid tight loop on stale lock race
|
|
139
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
140
|
+
continue; // Retry after removing stale lock
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Lock is VALID - another process is starting server
|
|
144
|
+
// Don't retry, let caller fall back to waiting for server
|
|
145
|
+
return false;
|
|
146
|
+
} else {
|
|
147
|
+
// Unexpected error
|
|
148
|
+
console.error(`[server-init] Lock error: ${nodeErr.message}`);
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return false; // Failed after stale lock retries
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function releaseLock(): void {
|
|
158
|
+
try {
|
|
159
|
+
if (existsSync(LOCK_FILE)) {
|
|
160
|
+
const data: LockData = JSON.parse(readFileSync(LOCK_FILE, 'utf-8'));
|
|
161
|
+
// Only release if we own the lock
|
|
162
|
+
if (data.pid === process.pid) {
|
|
163
|
+
unlinkSync(LOCK_FILE);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
} catch {
|
|
167
|
+
// Ignore errors during cleanup
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// =============================================================================
|
|
172
|
+
// Health Check (Improved error handling)
|
|
173
|
+
// =============================================================================
|
|
174
|
+
|
|
175
|
+
async function checkHealth(): Promise<HealthResponse | null> {
|
|
176
|
+
try {
|
|
177
|
+
const response = await fetch(HEALTH_URL, {
|
|
178
|
+
signal: AbortSignal.timeout(5000),
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
if (!response.ok) {
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return (await response.json()) as HealthResponse;
|
|
186
|
+
} catch (error: unknown) {
|
|
187
|
+
// Only log unexpected errors, not connection refused (expected when server down)
|
|
188
|
+
if (error instanceof Error && !error.message.includes('ECONNREFUSED')) {
|
|
189
|
+
console.error(`[server-init] Health check error: ${error.message}`);
|
|
190
|
+
}
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// =============================================================================
|
|
196
|
+
// Server Start (Improved with error handling)
|
|
197
|
+
// =============================================================================
|
|
198
|
+
|
|
199
|
+
function startServer(): Promise<void> {
|
|
200
|
+
return new Promise((resolve, reject) => {
|
|
201
|
+
const scriptDir = new URL('.', import.meta.url).pathname;
|
|
202
|
+
const serverScript = join(scriptDir, 'server.js');
|
|
203
|
+
|
|
204
|
+
const child = spawn('node', [serverScript], {
|
|
205
|
+
detached: true,
|
|
206
|
+
stdio: 'ignore',
|
|
207
|
+
cwd: scriptDir,
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
// Handle spawn errors (e.g., ENOENT if node not found)
|
|
211
|
+
child.on('error', (err) => {
|
|
212
|
+
console.error(`[server-init] Failed to start server: ${err.message}`);
|
|
213
|
+
reject(err);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
// Give a small window for immediate spawn errors
|
|
217
|
+
setTimeout(() => {
|
|
218
|
+
child.unref();
|
|
219
|
+
console.log(`[server-init] Started server process (pid: ${child.pid})`);
|
|
220
|
+
resolve();
|
|
221
|
+
}, 100);
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// =============================================================================
|
|
226
|
+
// Wait for Ready (with exponential backoff)
|
|
227
|
+
// =============================================================================
|
|
228
|
+
|
|
229
|
+
async function waitForReady(): Promise<boolean> {
|
|
230
|
+
const startTime = Date.now();
|
|
231
|
+
let pollInterval = POLL_INTERVAL_MS;
|
|
232
|
+
|
|
233
|
+
while (Date.now() - startTime < MAX_WAIT_MS) {
|
|
234
|
+
const health = await checkHealth();
|
|
235
|
+
|
|
236
|
+
if (health?.status === 'ok') {
|
|
237
|
+
return true;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (health?.status === 'initializing') {
|
|
241
|
+
console.log('[server-init] Server initializing...');
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
245
|
+
// Gradual backoff up to 2s
|
|
246
|
+
pollInterval = Math.min(pollInterval * 1.5, 2000);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// =============================================================================
|
|
253
|
+
// Main
|
|
254
|
+
// =============================================================================
|
|
255
|
+
|
|
256
|
+
async function main(): Promise<void> {
|
|
257
|
+
// ==========================================================================
|
|
258
|
+
// FAST PATH: Check if server is already running (no lock needed)
|
|
259
|
+
// ==========================================================================
|
|
260
|
+
const initialHealth = await checkHealth();
|
|
261
|
+
|
|
262
|
+
if (initialHealth?.status === 'ok') {
|
|
263
|
+
// Server is running - immediate success, no lock needed
|
|
264
|
+
console.log('ok');
|
|
265
|
+
process.exit(0);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (initialHealth?.status === 'initializing') {
|
|
269
|
+
// Server is starting - just wait for it, no lock needed
|
|
270
|
+
console.log('[server-init] Server is initializing, waiting...');
|
|
271
|
+
const ready = await waitForReady();
|
|
272
|
+
if (ready) {
|
|
273
|
+
console.log('ok');
|
|
274
|
+
process.exit(0);
|
|
275
|
+
} else {
|
|
276
|
+
console.error('[server-init] ERROR: Server stuck in initializing state');
|
|
277
|
+
process.exit(1);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// ==========================================================================
|
|
282
|
+
// SLOW PATH: Server not running - need lock to start it
|
|
283
|
+
// ==========================================================================
|
|
284
|
+
const lockAcquired = await acquireLock();
|
|
285
|
+
|
|
286
|
+
if (!lockAcquired) {
|
|
287
|
+
// Another process is starting the server - wait for it
|
|
288
|
+
console.log('[server-init] Another process is starting server, waiting...');
|
|
289
|
+
const ready = await waitForReady();
|
|
290
|
+
if (ready) {
|
|
291
|
+
console.log('ok');
|
|
292
|
+
process.exit(0);
|
|
293
|
+
} else {
|
|
294
|
+
console.error('[server-init] ERROR: Timeout waiting for server');
|
|
295
|
+
process.exit(1);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
try {
|
|
300
|
+
// Double-check health after acquiring lock (another process may have started it)
|
|
301
|
+
const health = await checkHealth();
|
|
302
|
+
|
|
303
|
+
if (health?.status === 'ok') {
|
|
304
|
+
console.log('ok');
|
|
305
|
+
releaseLock();
|
|
306
|
+
process.exit(0);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (health?.status === 'initializing') {
|
|
310
|
+
console.log('[server-init] Server is initializing, waiting...');
|
|
311
|
+
const ready = await waitForReady();
|
|
312
|
+
if (ready) {
|
|
313
|
+
console.log('ok');
|
|
314
|
+
releaseLock();
|
|
315
|
+
process.exit(0);
|
|
316
|
+
} else {
|
|
317
|
+
console.error('[server-init] ERROR: Server stuck in initializing state');
|
|
318
|
+
releaseLock();
|
|
319
|
+
process.exit(1);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Server not running - start it
|
|
324
|
+
console.log('[server-init] Server not running, starting...');
|
|
325
|
+
|
|
326
|
+
try {
|
|
327
|
+
await startServer();
|
|
328
|
+
} catch {
|
|
329
|
+
console.error('[server-init] ERROR: Failed to spawn server process');
|
|
330
|
+
releaseLock();
|
|
331
|
+
process.exit(1);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Wait for server to be ready
|
|
335
|
+
const ready = await waitForReady();
|
|
336
|
+
if (ready) {
|
|
337
|
+
console.log('ok');
|
|
338
|
+
releaseLock();
|
|
339
|
+
process.exit(0);
|
|
340
|
+
} else {
|
|
341
|
+
console.error('[server-init] ERROR: Server failed to start within timeout');
|
|
342
|
+
releaseLock();
|
|
343
|
+
process.exit(1);
|
|
344
|
+
}
|
|
345
|
+
} catch (error) {
|
|
346
|
+
console.error('[server-init] ERROR:', error instanceof Error ? error.message : error);
|
|
347
|
+
releaseLock();
|
|
348
|
+
process.exit(1);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Cleanup on exit
|
|
353
|
+
process.on('SIGINT', () => {
|
|
354
|
+
releaseLock();
|
|
355
|
+
process.exit(130);
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
process.on('SIGTERM', () => {
|
|
359
|
+
releaseLock();
|
|
360
|
+
process.exit(143);
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
main();
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import express, { type Express, type Request, type Response, type NextFunction } from 'express';
|
|
2
|
+
import type { Server } from 'http';
|
|
3
|
+
import { toolsRoutes } from './routes/tools.js';
|
|
4
|
+
import { promptsRoutes } from './routes/prompts.js';
|
|
5
|
+
import { errorHandler } from './middleware/errorHandler.js';
|
|
6
|
+
import { requestLogger } from './middleware/logger.js';
|
|
7
|
+
import { initializeProviders, initializeSession, logSessionInit } from './index.js';
|
|
8
|
+
import { initializeMcpContent, isMcpInitialized } from './mcpCache.js';
|
|
9
|
+
import { getLogsPath, initializeLogger } from './utils/logger.js';
|
|
10
|
+
import { getAllCircuitStates, clearAllCircuits, stopCircuitCleanup } from './utils/circuitBreaker.js';
|
|
11
|
+
import { agentLog, successLog, errorLog, dimLog, warnLog } from './utils/colors.js';
|
|
12
|
+
import { fireAndForgetWithTimeout } from './utils/asyncTimeout.js';
|
|
13
|
+
import { errorQueue } from './utils/errorQueue.js';
|
|
14
|
+
|
|
15
|
+
const HOST = process.env.OCTOCODE_RESEARCH_HOST || 'localhost';
|
|
16
|
+
|
|
17
|
+
const getPort = (raw?: string): number => {
|
|
18
|
+
const DEFAULT_PORT = 1987;
|
|
19
|
+
if (!raw) return DEFAULT_PORT;
|
|
20
|
+
|
|
21
|
+
const port = Number(raw);
|
|
22
|
+
|
|
23
|
+
// Strictly allow only the non-privileged, user-registered range
|
|
24
|
+
if (!Number.isInteger(port) || port < 1024 || port > 65535) {
|
|
25
|
+
throw new Error(
|
|
26
|
+
`Invalid OCTOCODE_RESEARCH_PORT: "${raw}". ` +
|
|
27
|
+
`Please provide an integer between 1024 and 65535.`
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return port;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const PORT = getPort(process.env.OCTOCODE_RESEARCH_PORT);
|
|
35
|
+
|
|
36
|
+
const MAX_IDLE_TIME_MS = 30 * 60 * 1000; // 30 minutes idle before restart
|
|
37
|
+
const IDLE_CHECK_INTERVAL_MS = 120 * 1000; // Check every 2 minute
|
|
38
|
+
|
|
39
|
+
let server: Server | null = null;
|
|
40
|
+
let lastRequestTime: number = Date.now();
|
|
41
|
+
let idleCheckInterval: NodeJS.Timeout | null = null;
|
|
42
|
+
let isShuttingDown = false;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Check if server has been idle and should restart
|
|
46
|
+
*/
|
|
47
|
+
function checkIdleRestart(): void {
|
|
48
|
+
const idleTime = Date.now() - lastRequestTime;
|
|
49
|
+
const idleSeconds = Math.floor(idleTime / 1000);
|
|
50
|
+
|
|
51
|
+
if (idleTime > MAX_IDLE_TIME_MS) {
|
|
52
|
+
console.log(warnLog(`ā ļø Server idle for ${idleSeconds}s (>${MAX_IDLE_TIME_MS / 1000}s). Initiating restart...`));
|
|
53
|
+
gracefulShutdown('IDLE_TIMEOUT');
|
|
54
|
+
} else if (idleTime > MAX_IDLE_TIME_MS / 2) {
|
|
55
|
+
// Log warning at 50% threshold (30 minutes)
|
|
56
|
+
console.log(dimLog(`ā° Idle: ${idleSeconds}s / ${MAX_IDLE_TIME_MS / 1000}s`));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Start periodic idle checking
|
|
62
|
+
*/
|
|
63
|
+
function startIdleCheck(): void {
|
|
64
|
+
if (idleCheckInterval) return;
|
|
65
|
+
|
|
66
|
+
idleCheckInterval = setInterval(checkIdleRestart, IDLE_CHECK_INTERVAL_MS);
|
|
67
|
+
|
|
68
|
+
// Unref so it doesn't prevent process exit
|
|
69
|
+
idleCheckInterval.unref();
|
|
70
|
+
|
|
71
|
+
console.log(dimLog(`ā±ļø Idle check started (${IDLE_CHECK_INTERVAL_MS / 1000}s interval, ${MAX_IDLE_TIME_MS / 1000}s threshold)`));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Stop idle checking
|
|
76
|
+
*/
|
|
77
|
+
function stopIdleCheck(): void {
|
|
78
|
+
if (idleCheckInterval) {
|
|
79
|
+
clearInterval(idleCheckInterval);
|
|
80
|
+
idleCheckInterval = null;
|
|
81
|
+
console.log(successLog('ā
Idle check interval stopped'));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export async function createServer(): Promise<Express> {
|
|
86
|
+
// Initialize logger first (sync for startup, async after)
|
|
87
|
+
initializeLogger();
|
|
88
|
+
|
|
89
|
+
// Initialize session for telemetry tracking
|
|
90
|
+
initializeSession();
|
|
91
|
+
|
|
92
|
+
const app = express();
|
|
93
|
+
app.use(express.json());
|
|
94
|
+
|
|
95
|
+
// Track activity for idle restart
|
|
96
|
+
app.use((_req: Request, _res: Response, next: NextFunction) => {
|
|
97
|
+
lastRequestTime = Date.now();
|
|
98
|
+
next();
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
app.use(requestLogger);
|
|
102
|
+
|
|
103
|
+
app.get('/health', (_req: Request, res: Response) => {
|
|
104
|
+
const memoryUsage = process.memoryUsage();
|
|
105
|
+
const recentErrors = errorQueue.getRecent(5);
|
|
106
|
+
const initialized = isMcpInitialized();
|
|
107
|
+
const idleTimeMs = Date.now() - lastRequestTime;
|
|
108
|
+
|
|
109
|
+
res.json({
|
|
110
|
+
status: initialized ? 'ok' : 'initializing',
|
|
111
|
+
host: HOST,
|
|
112
|
+
port: PORT,
|
|
113
|
+
version: '2.2.0',
|
|
114
|
+
uptime: Math.floor(process.uptime()),
|
|
115
|
+
processManager: 'pm2',
|
|
116
|
+
// Idle tracking info
|
|
117
|
+
idle: {
|
|
118
|
+
currentMs: idleTimeMs,
|
|
119
|
+
thresholdMs: MAX_IDLE_TIME_MS,
|
|
120
|
+
checkIntervalMs: IDLE_CHECK_INTERVAL_MS,
|
|
121
|
+
percentToRestart: Math.round((idleTimeMs / MAX_IDLE_TIME_MS) * 100),
|
|
122
|
+
},
|
|
123
|
+
memory: {
|
|
124
|
+
heapUsed: Math.round(memoryUsage.heapUsed / 1024 / 1024),
|
|
125
|
+
heapTotal: Math.round(memoryUsage.heapTotal / 1024 / 1024),
|
|
126
|
+
rss: Math.round(memoryUsage.rss / 1024 / 1024),
|
|
127
|
+
},
|
|
128
|
+
circuits: getAllCircuitStates(),
|
|
129
|
+
errors: {
|
|
130
|
+
queueSize: errorQueue.size,
|
|
131
|
+
recentErrors: recentErrors.map((e) => ({
|
|
132
|
+
timestamp: e.timestamp.toISOString(),
|
|
133
|
+
context: e.context,
|
|
134
|
+
message: e.error.message,
|
|
135
|
+
})),
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// All tool execution via /tools/call/:toolName (readiness check applied in route files)
|
|
141
|
+
app.use('/tools', toolsRoutes);
|
|
142
|
+
app.use('/prompts', promptsRoutes);
|
|
143
|
+
|
|
144
|
+
// 404 handler for undefined routes
|
|
145
|
+
app.use((_req: Request, res: Response) => {
|
|
146
|
+
res.status(404).json({
|
|
147
|
+
success: false,
|
|
148
|
+
error: {
|
|
149
|
+
message: 'Route not found',
|
|
150
|
+
code: 'NOT_FOUND',
|
|
151
|
+
availableRoutes: [
|
|
152
|
+
'GET /health',
|
|
153
|
+
'GET /tools/list',
|
|
154
|
+
'GET /tools/info/:toolName',
|
|
155
|
+
'GET /tools/schemas',
|
|
156
|
+
'GET /tools/system',
|
|
157
|
+
'GET /tools/initContext',
|
|
158
|
+
'POST /tools/call/:toolName',
|
|
159
|
+
'GET /prompts/list',
|
|
160
|
+
'GET /prompts/info/:promptName',
|
|
161
|
+
],
|
|
162
|
+
hint: 'All tools are called via POST /tools/call/{toolName}',
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
app.use(errorHandler);
|
|
168
|
+
|
|
169
|
+
return app;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Graceful shutdown handler for PM2
|
|
174
|
+
* PM2 sends SIGINT, we clean up and exit (PM2 handles restart)
|
|
175
|
+
*/
|
|
176
|
+
function gracefulShutdown(signal: string): void {
|
|
177
|
+
// Prevent double-shutdown (e.g., SIGINT + SIGTERM in quick succession)
|
|
178
|
+
if (isShuttingDown) {
|
|
179
|
+
console.log(dimLog(`Already shutting down, ignoring ${signal}`));
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
isShuttingDown = true;
|
|
183
|
+
|
|
184
|
+
console.log(agentLog(`\nš Received ${signal}. Starting graceful shutdown...`));
|
|
185
|
+
|
|
186
|
+
// Force exit safety net (PM2 kill_timeout is 120s, we exit at 110s)
|
|
187
|
+
const FORCE_EXIT_TIMEOUT_MS = 110 * 1000;
|
|
188
|
+
setTimeout(() => {
|
|
189
|
+
console.log(warnLog('ā ļø Force exiting due to drain timeout'));
|
|
190
|
+
process.exit(1);
|
|
191
|
+
}, FORCE_EXIT_TIMEOUT_MS).unref();
|
|
192
|
+
|
|
193
|
+
// 1. Stop idle check interval first
|
|
194
|
+
stopIdleCheck();
|
|
195
|
+
|
|
196
|
+
// 2. Stop circuit cleanup interval
|
|
197
|
+
stopCircuitCleanup();
|
|
198
|
+
console.log(successLog('ā
Circuit cleanup interval stopped'));
|
|
199
|
+
|
|
200
|
+
// 3. Clear circuit breakers
|
|
201
|
+
clearAllCircuits();
|
|
202
|
+
console.log(successLog('ā
Circuit breakers cleared'));
|
|
203
|
+
|
|
204
|
+
// 4. Close HTTP server (waits for connections to drain)
|
|
205
|
+
if (server) {
|
|
206
|
+
console.log(dimLog('ā³ Waiting for connections to drain...'));
|
|
207
|
+
server.close((err) => {
|
|
208
|
+
if (err) {
|
|
209
|
+
console.error(errorLog('ā Error closing server:'), err);
|
|
210
|
+
process.exit(1);
|
|
211
|
+
}
|
|
212
|
+
console.log(successLog('ā
HTTP server closed'));
|
|
213
|
+
process.exit(0); // PM2 handles restart
|
|
214
|
+
});
|
|
215
|
+
} else {
|
|
216
|
+
process.exit(0);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export async function startServer(): Promise<void> {
|
|
221
|
+
const app = await createServer();
|
|
222
|
+
|
|
223
|
+
await new Promise<void>((resolve) => {
|
|
224
|
+
const httpServer = app.listen(PORT, HOST, () => {
|
|
225
|
+
server = httpServer;
|
|
226
|
+
console.log(agentLog(`š Octocode Research Server running on http://${HOST}:${PORT}`));
|
|
227
|
+
console.log(dimLog(`ā³ initializing context...`));
|
|
228
|
+
|
|
229
|
+
// Start background initialization (Warm Start)
|
|
230
|
+
initializeMcpContent()
|
|
231
|
+
.then(() => initializeProviders())
|
|
232
|
+
.then(() => {
|
|
233
|
+
console.log(successLog('ā
Context initialized - Server Ready'));
|
|
234
|
+
|
|
235
|
+
// Reset idle timer after init (prevents early timeout)
|
|
236
|
+
lastRequestTime = Date.now();
|
|
237
|
+
|
|
238
|
+
// Start idle check after initialization
|
|
239
|
+
startIdleCheck();
|
|
240
|
+
|
|
241
|
+
console.log(agentLog(`š Logs: ${getLogsPath()}`));
|
|
242
|
+
console.log(agentLog(`\nRoutes:`));
|
|
243
|
+
console.log(dimLog(` GET /health - Server health`));
|
|
244
|
+
console.log(dimLog(` GET /tools/initContext - System prompt + schemas (LOAD FIRST)`));
|
|
245
|
+
console.log(dimLog(` GET /tools/system - System prompt only`));
|
|
246
|
+
console.log(dimLog(` GET /tools/list - List all tools`));
|
|
247
|
+
console.log(dimLog(` GET /tools/info/:toolName - Tool schema (BEFORE calling)`));
|
|
248
|
+
console.log(dimLog(` GET /tools/schemas - All tools schemas`));
|
|
249
|
+
console.log(dimLog(` POST /tools/call/:toolName - Execute tool`));
|
|
250
|
+
console.log(dimLog(` GET /prompts/list - List prompts`));
|
|
251
|
+
console.log(dimLog(` GET /prompts/info/:name - Get prompt content`));
|
|
252
|
+
|
|
253
|
+
// Signal PM2 that we're ready
|
|
254
|
+
if (process.send) {
|
|
255
|
+
process.send('ready');
|
|
256
|
+
console.log(dimLog('š” PM2 ready signal sent'));
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Log session initialization after server is ready
|
|
260
|
+
fireAndForgetWithTimeout(
|
|
261
|
+
() => logSessionInit(),
|
|
262
|
+
5000,
|
|
263
|
+
'logSessionInit'
|
|
264
|
+
);
|
|
265
|
+
})
|
|
266
|
+
.catch((err) => {
|
|
267
|
+
console.error(errorLog('ā Initialization failed:'), err);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
resolve();
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Signal handlers - PM2 sends SIGINT for graceful shutdown
|
|
276
|
+
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
|
|
277
|
+
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
|
|
278
|
+
|
|
279
|
+
const isMainModule = import.meta.url === `file://${process.argv[1]}`;
|
|
280
|
+
if (isMainModule) {
|
|
281
|
+
startServer().catch((err) => {
|
|
282
|
+
console.error(errorLog('ā Failed to start server:'), err);
|
|
283
|
+
process.exit(1);
|
|
284
|
+
});
|
|
285
|
+
}
|