all-hands-cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.allhands/README.md +75 -0
- package/.allhands/agents/compounder.yaml +15 -0
- package/.allhands/agents/coordinator.yaml +17 -0
- package/.allhands/agents/documentor.yaml +15 -0
- package/.allhands/agents/e2e-test-planner.yaml +17 -0
- package/.allhands/agents/emergent.yaml +22 -0
- package/.allhands/agents/executor.yaml +14 -0
- package/.allhands/agents/ideation.yaml +11 -0
- package/.allhands/agents/initiative-steering.yaml +19 -0
- package/.allhands/agents/judge.yaml +13 -0
- package/.allhands/agents/planner.yaml +19 -0
- package/.allhands/agents/pr-reviewer.yaml +15 -0
- package/.allhands/docs.json +5 -0
- package/.allhands/docs.local.json +26 -0
- package/.allhands/flows/COMPOUNDING.md +203 -0
- package/.allhands/flows/COORDINATION.md +89 -0
- package/.allhands/flows/CORE.md +87 -0
- package/.allhands/flows/DOCUMENTATION.md +218 -0
- package/.allhands/flows/E2E_TEST_PLAN_BUILDING.md +140 -0
- package/.allhands/flows/EMERGENT_PLANNING.md +57 -0
- package/.allhands/flows/IDEATION_SCOPING.md +154 -0
- package/.allhands/flows/INITIATIVE_STEERING.md +110 -0
- package/.allhands/flows/JUDGE_REVIEWING.md +79 -0
- package/.allhands/flows/PROMPT_TASK_EXECUTION.md +68 -0
- package/.allhands/flows/PR_REVIEWING.md +43 -0
- package/.allhands/flows/SPEC_PLANNING.md +216 -0
- package/.allhands/flows/harness/WRITING_HARNESS_FLOWS.md +27 -0
- package/.allhands/flows/harness/WRITING_HARNESS_KNOWLEDGE.md +27 -0
- package/.allhands/flows/harness/WRITING_HARNESS_ORCHESTRATION.md +27 -0
- package/.allhands/flows/harness/WRITING_HARNESS_SKILLS.md +27 -0
- package/.allhands/flows/harness/WRITING_HARNESS_TOOLS.md +27 -0
- package/.allhands/flows/harness/WRITING_HARNESS_VALIDATION_TOOLING.md +27 -0
- package/.allhands/flows/shared/CODEBASE_UNDERSTANDING.md +72 -0
- package/.allhands/flows/shared/CREATE_HARNESS_SPEC.md +48 -0
- package/.allhands/flows/shared/CREATE_SPEC.md +41 -0
- package/.allhands/flows/shared/CREATE_VALIDATION_TOOLING_SPEC.md +70 -0
- package/.allhands/flows/shared/DOCUMENTATION_DISCOVERY.md +123 -0
- package/.allhands/flows/shared/DOCUMENTATION_WRITER.md +101 -0
- package/.allhands/flows/shared/EMERGENT_REFINEMENT_ANALYSIS.md +76 -0
- package/.allhands/flows/shared/EXTERNAL_TECH_GUIDANCE.md +97 -0
- package/.allhands/flows/shared/IDEATION_CODEBASE_GROUNDING.md +49 -0
- package/.allhands/flows/shared/PLAN_DEEPENING.md +152 -0
- package/.allhands/flows/shared/PROMPT_TASKS_CURATION.md +113 -0
- package/.allhands/flows/shared/PROMPT_VALIDATION_REVIEW.MD +99 -0
- package/.allhands/flows/shared/QUICK_PREMORTEM.md +70 -0
- package/.allhands/flows/shared/RESEARCH_GUIDANCE.md +38 -0
- package/.allhands/flows/shared/REVIEW_OPTIONS_BREAKDOWN.md +68 -0
- package/.allhands/flows/shared/SKILL_EXTRACTION.md +84 -0
- package/.allhands/flows/shared/SPEC_FLOW_ANALYSIS.md +119 -0
- package/.allhands/flows/shared/TDD_WORKFLOW.md +109 -0
- package/.allhands/flows/shared/UTILIZE_VALIDATION_TOOLING.md +84 -0
- package/.allhands/flows/shared/WRITING_HARNESS_FLOWS.md +11 -0
- package/.allhands/flows/shared/WRITING_HARNESS_MCP_TOOLS.md +84 -0
- package/.allhands/flows/shared/jury/ARCHITECTURE_REVIEW.md +91 -0
- package/.allhands/flows/shared/jury/BEST_PRACTICES_REVIEW.md +80 -0
- package/.allhands/flows/shared/jury/CLAIM_VERIFICATION_REVIEW.md +101 -0
- package/.allhands/flows/shared/jury/EXPECTATIONS_FIT_REVIEW.md +78 -0
- package/.allhands/flows/shared/jury/MAINTAINABILITY_REVIEW.md +110 -0
- package/.allhands/flows/shared/jury/PROMPTS_EXPECTATIONS_FIT.md +74 -0
- package/.allhands/flows/shared/jury/PROMPTS_FLOW_ANALYSIS.md +92 -0
- package/.allhands/flows/shared/jury/PROMPTS_YAGNI.md +78 -0
- package/.allhands/flows/shared/jury/PROMPT_PREMORTEM.md +125 -0
- package/.allhands/flows/shared/jury/SECURITY_REVIEW.md +86 -0
- package/.allhands/flows/shared/jury/YAGNI_REVIEW.md +82 -0
- package/.allhands/flows/wip/DEBUG_INVESTIGATION.md +162 -0
- package/.allhands/flows/wip/MEMORY_RECALL.md +62 -0
- package/.allhands/harness/ah +131 -0
- package/.allhands/harness/package-lock.json +5292 -0
- package/.allhands/harness/package.json +52 -0
- package/.allhands/harness/src/__tests__/e2e/commands.test.ts +307 -0
- package/.allhands/harness/src/__tests__/e2e/event-loop.test.ts +539 -0
- package/.allhands/harness/src/__tests__/e2e/hooks.test.ts +427 -0
- package/.allhands/harness/src/__tests__/e2e/new-initiative-routing.test.ts +137 -0
- package/.allhands/harness/src/__tests__/e2e/run-e2e.ts +109 -0
- package/.allhands/harness/src/__tests__/e2e/specs-type.test.ts +210 -0
- package/.allhands/harness/src/__tests__/e2e/validation-hooks.test.ts +669 -0
- package/.allhands/harness/src/__tests__/e2e/validation-path-consistency.test.ts +354 -0
- package/.allhands/harness/src/__tests__/e2e/validation.test.ts +528 -0
- package/.allhands/harness/src/__tests__/harness/assertions.ts +318 -0
- package/.allhands/harness/src/__tests__/harness/cli-runner.ts +359 -0
- package/.allhands/harness/src/__tests__/harness/fixture.ts +384 -0
- package/.allhands/harness/src/__tests__/harness/hook-runner.ts +411 -0
- package/.allhands/harness/src/__tests__/harness/index.ts +122 -0
- package/.allhands/harness/src/cli.ts +36 -0
- package/.allhands/harness/src/commands/complexity.ts +177 -0
- package/.allhands/harness/src/commands/context7.ts +202 -0
- package/.allhands/harness/src/commands/docs.ts +557 -0
- package/.allhands/harness/src/commands/hooks.ts +24 -0
- package/.allhands/harness/src/commands/index.ts +51 -0
- package/.allhands/harness/src/commands/knowledge.ts +382 -0
- package/.allhands/harness/src/commands/memories.ts +302 -0
- package/.allhands/harness/src/commands/notify.ts +61 -0
- package/.allhands/harness/src/commands/oracle.ts +158 -0
- package/.allhands/harness/src/commands/perplexity.ts +220 -0
- package/.allhands/harness/src/commands/planning.ts +245 -0
- package/.allhands/harness/src/commands/schema.ts +73 -0
- package/.allhands/harness/src/commands/skills.ts +128 -0
- package/.allhands/harness/src/commands/solutions.ts +353 -0
- package/.allhands/harness/src/commands/spawn.ts +158 -0
- package/.allhands/harness/src/commands/specs.ts +532 -0
- package/.allhands/harness/src/commands/tavily.ts +226 -0
- package/.allhands/harness/src/commands/tools.ts +579 -0
- package/.allhands/harness/src/commands/trace.ts +327 -0
- package/.allhands/harness/src/commands/tui.ts +960 -0
- package/.allhands/harness/src/commands/validate.ts +143 -0
- package/.allhands/harness/src/commands/validation-tools.ts +108 -0
- package/.allhands/harness/src/hooks/context.ts +1442 -0
- package/.allhands/harness/src/hooks/enforcement.ts +170 -0
- package/.allhands/harness/src/hooks/index.ts +54 -0
- package/.allhands/harness/src/hooks/lifecycle.ts +229 -0
- package/.allhands/harness/src/hooks/notification.ts +104 -0
- package/.allhands/harness/src/hooks/observability.ts +551 -0
- package/.allhands/harness/src/hooks/session.ts +88 -0
- package/.allhands/harness/src/hooks/shared.ts +815 -0
- package/.allhands/harness/src/hooks/transcript-parser.ts +208 -0
- package/.allhands/harness/src/hooks/validation.ts +617 -0
- package/.allhands/harness/src/lib/__tests__/ctags.test.ts +244 -0
- package/.allhands/harness/src/lib/__tests__/docs-validation.test.ts +344 -0
- package/.allhands/harness/src/lib/__tests__/mcp-runtime.test.ts +190 -0
- package/.allhands/harness/src/lib/__tests__/schema.test.ts +861 -0
- package/.allhands/harness/src/lib/base-command.ts +198 -0
- package/.allhands/harness/src/lib/cli-daemon.ts +343 -0
- package/.allhands/harness/src/lib/compaction.ts +313 -0
- package/.allhands/harness/src/lib/ctags.ts +497 -0
- package/.allhands/harness/src/lib/docs-validation.ts +907 -0
- package/.allhands/harness/src/lib/event-loop.ts +662 -0
- package/.allhands/harness/src/lib/flows.ts +155 -0
- package/.allhands/harness/src/lib/git.ts +276 -0
- package/.allhands/harness/src/lib/knowledge-worker.ts +72 -0
- package/.allhands/harness/src/lib/knowledge.ts +810 -0
- package/.allhands/harness/src/lib/llm.ts +255 -0
- package/.allhands/harness/src/lib/mcp-client.ts +432 -0
- package/.allhands/harness/src/lib/mcp-daemon.ts +486 -0
- package/.allhands/harness/src/lib/mcp-runtime.ts +418 -0
- package/.allhands/harness/src/lib/notification.ts +115 -0
- package/.allhands/harness/src/lib/opencode/index.ts +70 -0
- package/.allhands/harness/src/lib/opencode/profiles.ts +300 -0
- package/.allhands/harness/src/lib/opencode/prompts/codesearch.md +98 -0
- package/.allhands/harness/src/lib/opencode/prompts/knowledge-aggregator.md +67 -0
- package/.allhands/harness/src/lib/opencode/runner.ts +281 -0
- package/.allhands/harness/src/lib/oracle.ts +926 -0
- package/.allhands/harness/src/lib/planning-utils.ts +150 -0
- package/.allhands/harness/src/lib/planning.ts +605 -0
- package/.allhands/harness/src/lib/pr-review.ts +225 -0
- package/.allhands/harness/src/lib/prompts.ts +522 -0
- package/.allhands/harness/src/lib/schema.ts +418 -0
- package/.allhands/harness/src/lib/schemas/agent-profile.ts +141 -0
- package/.allhands/harness/src/lib/schemas/template-vars.ts +138 -0
- package/.allhands/harness/src/lib/session.ts +164 -0
- package/.allhands/harness/src/lib/specs.ts +348 -0
- package/.allhands/harness/src/lib/tldr.ts +829 -0
- package/.allhands/harness/src/lib/tmux.ts +1051 -0
- package/.allhands/harness/src/lib/trace-store.ts +714 -0
- package/.allhands/harness/src/mcp/__tests__/index.test.ts +46 -0
- package/.allhands/harness/src/mcp/_template.ts +47 -0
- package/.allhands/harness/src/mcp/filesystem.ts +33 -0
- package/.allhands/harness/src/mcp/index.ts +69 -0
- package/.allhands/harness/src/mcp/playwright.ts +34 -0
- package/.allhands/harness/src/mcp/xcodebuild.ts +29 -0
- package/.allhands/harness/src/schemas/docs.schema.json +44 -0
- package/.allhands/harness/src/schemas/settings.schema.json +214 -0
- package/.allhands/harness/src/tui/actions.ts +227 -0
- package/.allhands/harness/src/tui/file-viewer-modal.ts +270 -0
- package/.allhands/harness/src/tui/index.ts +1574 -0
- package/.allhands/harness/src/tui/modal.ts +232 -0
- package/.allhands/harness/src/tui/prompts-pane.ts +186 -0
- package/.allhands/harness/src/tui/status-pane.ts +434 -0
- package/.allhands/harness/tsconfig.json +22 -0
- package/.allhands/harness/vitest.config.ts +13 -0
- package/.allhands/pillars.md +33 -0
- package/.allhands/principles.md +88 -0
- package/.allhands/schemas/alignment.yaml +51 -0
- package/.allhands/schemas/documentation.yaml +10 -0
- package/.allhands/schemas/prompt.yaml +92 -0
- package/.allhands/schemas/skill.yaml +34 -0
- package/.allhands/schemas/solution.yaml +131 -0
- package/.allhands/schemas/spec.yaml +67 -0
- package/.allhands/schemas/validation-suite.yaml +49 -0
- package/.allhands/schemas/workflow.yaml +51 -0
- package/.allhands/settings.json +57 -0
- package/.allhands/skills/claude-code-patterns/SKILL.md +60 -0
- package/.allhands/skills/claude-code-patterns/docs/context-hygiene.md +19 -0
- package/.allhands/skills/harness-maintenance/SKILL.md +449 -0
- package/.allhands/skills/harness-maintenance/references/core-architecture.md +187 -0
- package/.allhands/skills/harness-maintenance/references/harness-skills.md +87 -0
- package/.allhands/skills/harness-maintenance/references/knowledge-compounding.md +78 -0
- package/.allhands/skills/harness-maintenance/references/tools-commands-mcp-hooks.md +115 -0
- package/.allhands/skills/harness-maintenance/references/validation-tooling.md +77 -0
- package/.allhands/skills/harness-maintenance/references/writing-flows.md +84 -0
- package/.allhands/validation/browser-automation.md +109 -0
- package/.allhands/validation/xcode-automation.md +195 -0
- package/.allhands/workflows/documentation.md +86 -0
- package/.allhands/workflows/investigation.md +81 -0
- package/.allhands/workflows/milestone.md +91 -0
- package/.allhands/workflows/optimization.md +85 -0
- package/.allhands/workflows/refactor.md +99 -0
- package/.allhands/workflows/triage.md +81 -0
- package/.claude/README.md +1 -0
- package/.claude/agents/explorer.md +10 -0
- package/.claude/agents/researcher.md +11 -0
- package/.claude/agents/task-runner.md +8 -0
- package/.claude/settings.json +231 -0
- package/.env.ai.example +7 -0
- package/.github/workflows/npm-publish.yml +69 -0
- package/.internal.json +45 -0
- package/.tldr/config.json +11 -0
- package/.tldrignore +90 -0
- package/CLAUDE.md +6 -0
- package/README.md +98 -0
- package/bin/sync-cli.js +7552 -0
- package/concerns.md +7 -0
- package/docs/README.md +41 -0
- package/docs/agents/README.md +24 -0
- package/docs/agents/agent-configuration-system.md +86 -0
- package/docs/agents/execution-agents.md +50 -0
- package/docs/agents/knowledge-agents.md +61 -0
- package/docs/agents/orchestration-agent.md +57 -0
- package/docs/agents/planning-agents.md +84 -0
- package/docs/agents/quality-review-agents.md +67 -0
- package/docs/agents/workflow-agent-orchestration.md +69 -0
- package/docs/flows/README.md +44 -0
- package/docs/flows/compounding.md +126 -0
- package/docs/flows/coordination.md +72 -0
- package/docs/flows/core-harness-integration.md +63 -0
- package/docs/flows/documentation-orchestration.md +98 -0
- package/docs/flows/e2e-test-plan-building.md +83 -0
- package/docs/flows/emergent-refinement.md +104 -0
- package/docs/flows/flow-authoring-and-mcp-tools.md +89 -0
- package/docs/flows/judge-reviewing.md +112 -0
- package/docs/flows/plan-deepening-and-research.md +107 -0
- package/docs/flows/plan-review-jury.md +114 -0
- package/docs/flows/pr-reviewing.md +54 -0
- package/docs/flows/prompt-task-execution.md +119 -0
- package/docs/flows/spec-planning.md +162 -0
- package/docs/flows/type-specific-scoping-flows.md +49 -0
- package/docs/flows/validation-and-skills-integration.md +145 -0
- package/docs/flows/wip/wip-flows.md +102 -0
- package/docs/harness/README.md +23 -0
- package/docs/harness/agent-profiles.md +84 -0
- package/docs/harness/cli/README.md +24 -0
- package/docs/harness/cli/cli-entry-and-command-discovery.md +91 -0
- package/docs/harness/cli/docs-command.md +87 -0
- package/docs/harness/cli/knowledge-command.md +91 -0
- package/docs/harness/cli/minor-cli-commands.md +65 -0
- package/docs/harness/cli/oracle-command.md +113 -0
- package/docs/harness/cli/planning-command.md +95 -0
- package/docs/harness/cli/schema-and-validation-commands.md +154 -0
- package/docs/harness/cli/search-commands.md +97 -0
- package/docs/harness/cli/spawn-command.md +136 -0
- package/docs/harness/cli/specs-command.md +102 -0
- package/docs/harness/cli/tools-command.md +122 -0
- package/docs/harness/cli/trace-command.md +122 -0
- package/docs/harness/cli-daemon.md +92 -0
- package/docs/harness/event-loop.md +184 -0
- package/docs/harness/hooks/README.md +15 -0
- package/docs/harness/hooks/context-hooks.md +96 -0
- package/docs/harness/hooks/lifecycle-and-observability-hooks.md +135 -0
- package/docs/harness/hooks/validation-hooks.md +97 -0
- package/docs/harness/test-harness.md +149 -0
- package/docs/harness/tui.md +176 -0
- package/docs/memories.md +20 -0
- package/docs/solutions/agentic-issues/premature-agent-deletion-tui-action-dependency-20260130.md +49 -0
- package/docs/solutions/agentic-issues/ref-anchor-scope-mismatch-skill-references-20260131.md +55 -0
- package/docs/solutions/agentic-issues/tautological-tests-routing-20260131.md +52 -0
- package/docs/solutions/integration_issue/blocktool-output-format-mismatch-hook-runner-20260130.md +52 -0
- package/docs/solutions/integration_issue/dual-validation-path-divergence-schema-20260130.md +66 -0
- package/docs/solutions/security-issues/unsanitized-domain-path-join-20260131.md +52 -0
- package/docs/solutions/test-failures/event-loop-mock-ordering-checkAgentWindows-20260130.md +63 -0
- package/docs/sync-cli/README.md +19 -0
- package/docs/sync-cli/cli-entrypoint-and-commands.md +39 -0
- package/docs/sync-cli/commands/README.md +11 -0
- package/docs/sync-cli/commands/pull-manifest-command.md +36 -0
- package/docs/sync-cli/commands/push-command.md +84 -0
- package/docs/sync-cli/commands/sync-command.md +71 -0
- package/docs/sync-cli/systems/README.md +14 -0
- package/docs/sync-cli/systems/git-and-github-integration.md +49 -0
- package/docs/sync-cli/systems/interactive-ui.md +43 -0
- package/docs/sync-cli/systems/manifest-and-distribution.md +51 -0
- package/docs/sync-cli/systems/path-resolution.md +42 -0
- package/package.json +46 -0
- package/scripts/install-shim.sh +40 -0
- package/scripts/pre-pack.sh +25 -0
- package/specs/harness-maintenance-skill.spec.md +138 -0
- package/specs/roadmap/git-spec-lifecycle-management.spec.md +113 -0
- package/specs/sync-init-flag.spec.md +117 -0
- package/specs/unified-workflow-orchestration.spec.md +250 -0
- package/specs/validation-tooling-practice.spec.md +98 -0
- package/specs/workflow-domain-configuration.spec.md +265 -0
- package/src/commands/pull-manifest.ts +31 -0
- package/src/commands/push.ts +344 -0
- package/src/commands/sync.ts +289 -0
- package/src/lib/constants.ts +10 -0
- package/src/lib/dotfiles.ts +36 -0
- package/src/lib/fs-utils.ts +18 -0
- package/src/lib/gh.ts +40 -0
- package/src/lib/git.ts +63 -0
- package/src/lib/gitignore.ts +167 -0
- package/src/lib/manifest.ts +121 -0
- package/src/lib/marker-sync.ts +39 -0
- package/src/lib/paths.ts +38 -0
- package/src/lib/target-lines.ts +66 -0
- package/src/lib/ui.ts +78 -0
- package/src/sync-cli.ts +120 -0
- package/target-lines.json +23 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Session Daemon - Persistent MCP session manager.
|
|
3
|
+
*
|
|
4
|
+
* This daemon runs as a background process and manages MCP client sessions
|
|
5
|
+
* for stateful servers. It listens on a Unix socket and accepts JSON commands.
|
|
6
|
+
*
|
|
7
|
+
* Each AGENT_ID gets its own daemon instance, enabling parallel sessions.
|
|
8
|
+
*
|
|
9
|
+
* Socket path: .allhands/harness/.cache/sessions/{AGENT_ID}.sock
|
|
10
|
+
*
|
|
11
|
+
* Commands:
|
|
12
|
+
* - { cmd: "call", server: string, tool: string, params: object, config: McpServerConfig }
|
|
13
|
+
* Auto-starts server session if needed, then calls the tool.
|
|
14
|
+
* - { cmd: "discover", server: string, config: McpServerConfig }
|
|
15
|
+
* Auto-starts server session if needed, returns available tools.
|
|
16
|
+
* - { cmd: "restart", server: string, config: McpServerConfig }
|
|
17
|
+
* Restarts a server session (for recovery from bad state).
|
|
18
|
+
* - { cmd: "list" }
|
|
19
|
+
* Lists all active server sessions.
|
|
20
|
+
* - { cmd: "info" }
|
|
21
|
+
* Returns daemon info (pid, session count, etc.)
|
|
22
|
+
* - { cmd: "ping" }
|
|
23
|
+
* Keep-alive, resets activity timer for all sessions.
|
|
24
|
+
* - { cmd: "shutdown" }
|
|
25
|
+
* Graceful shutdown - closes all sessions and exits.
|
|
26
|
+
*
|
|
27
|
+
* Session Lifecycle:
|
|
28
|
+
* - Sessions are auto-started on first call/discover.
|
|
29
|
+
* - Each session has its own inactivity timeout (from config or default).
|
|
30
|
+
* - When a session times out, it's closed and removed.
|
|
31
|
+
* - When no sessions remain, the daemon exits immediately.
|
|
32
|
+
* - Daemon can also be killed externally (e.g., tmux cleanup).
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
import { createServer, type Server, type Socket } from 'net';
|
|
36
|
+
import { existsSync, mkdirSync, unlinkSync, writeFileSync } from 'fs';
|
|
37
|
+
import { dirname, join } from 'path';
|
|
38
|
+
import { fileURLToPath } from 'url';
|
|
39
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
40
|
+
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
41
|
+
import type { McpServerConfig, McpToolSchema } from './mcp-runtime.js';
|
|
42
|
+
import { resolveEnvVars, DAEMON_DEFAULT_MCP_TIMEOUT } from './mcp-runtime.js';
|
|
43
|
+
|
|
44
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
45
|
+
// Path: harness/src/lib/ -> harness/src/ -> harness/
|
|
46
|
+
const HARNESS_ROOT = join(__dirname, '..', '..');
|
|
47
|
+
const SESSIONS_DIR = join(HARNESS_ROOT, '.cache', 'sessions');
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* How often to check for session timeouts (30 seconds).
|
|
51
|
+
*/
|
|
52
|
+
const TIMEOUT_CHECK_INTERVAL_MS = 30000;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Active session state.
|
|
56
|
+
*/
|
|
57
|
+
interface McpSession {
|
|
58
|
+
client: Client;
|
|
59
|
+
transport: StdioClientTransport;
|
|
60
|
+
config: McpServerConfig;
|
|
61
|
+
startedAt: Date;
|
|
62
|
+
lastUsedAt: Date;
|
|
63
|
+
timeoutMs: number;
|
|
64
|
+
tools?: McpToolSchema[];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Daemon command types.
|
|
69
|
+
*/
|
|
70
|
+
type DaemonCommand =
|
|
71
|
+
| { cmd: 'call'; server: string; tool: string; params: Record<string, unknown>; config: McpServerConfig }
|
|
72
|
+
| { cmd: 'discover'; server: string; config: McpServerConfig }
|
|
73
|
+
| { cmd: 'restart'; server: string; config: McpServerConfig }
|
|
74
|
+
| { cmd: 'list' }
|
|
75
|
+
| { cmd: 'info' }
|
|
76
|
+
| { cmd: 'ping' }
|
|
77
|
+
| { cmd: 'shutdown' };
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Session manager - tracks active MCP client sessions.
|
|
81
|
+
*/
|
|
82
|
+
const sessions = new Map<string, McpSession>();
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Reference to the server for shutdown.
|
|
86
|
+
*/
|
|
87
|
+
let serverInstance: Server | null = null;
|
|
88
|
+
let cleanupFn: (() => void) | null = null;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Create and connect a new MCP client for a server.
|
|
92
|
+
*/
|
|
93
|
+
async function createClient(config: McpServerConfig): Promise<{ client: Client; transport: StdioClientTransport }> {
|
|
94
|
+
if (!config.command) {
|
|
95
|
+
throw new Error(`Server ${config.name} requires 'command' for stdio transport`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const env = resolveEnvVars(config.env);
|
|
99
|
+
|
|
100
|
+
const transport = new StdioClientTransport({
|
|
101
|
+
command: config.command,
|
|
102
|
+
args: config.args,
|
|
103
|
+
env: { ...process.env, ...env } as Record<string, string>,
|
|
104
|
+
stderr: 'pipe',
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const client = new Client(
|
|
108
|
+
{ name: 'allhands-daemon', version: '0.1.0' },
|
|
109
|
+
{ capabilities: {} }
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
await client.connect(transport);
|
|
113
|
+
|
|
114
|
+
return { client, transport };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Ensure a session exists for a server, creating one if needed.
|
|
119
|
+
* Returns the session.
|
|
120
|
+
*/
|
|
121
|
+
async function ensureSession(server: string, config: McpServerConfig): Promise<McpSession> {
|
|
122
|
+
const existing = sessions.get(server);
|
|
123
|
+
if (existing) {
|
|
124
|
+
existing.lastUsedAt = new Date();
|
|
125
|
+
return existing;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Create new session
|
|
129
|
+
const { client, transport } = await createClient(config);
|
|
130
|
+
const timeoutMs = config.stateful_session_timeout ?? DAEMON_DEFAULT_MCP_TIMEOUT;
|
|
131
|
+
|
|
132
|
+
const session: McpSession = {
|
|
133
|
+
client,
|
|
134
|
+
transport,
|
|
135
|
+
config,
|
|
136
|
+
startedAt: new Date(),
|
|
137
|
+
lastUsedAt: new Date(),
|
|
138
|
+
timeoutMs,
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
sessions.set(server, session);
|
|
142
|
+
console.log(`Session started: ${server} (timeout: ${timeoutMs}ms)`);
|
|
143
|
+
|
|
144
|
+
return session;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Close and remove a session.
|
|
149
|
+
*/
|
|
150
|
+
async function closeSession(server: string): Promise<void> {
|
|
151
|
+
const session = sessions.get(server);
|
|
152
|
+
if (!session) return;
|
|
153
|
+
|
|
154
|
+
try {
|
|
155
|
+
await session.transport.close();
|
|
156
|
+
} catch {
|
|
157
|
+
// Ignore close errors
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
sessions.delete(server);
|
|
161
|
+
console.log(`Session closed: ${server}`);
|
|
162
|
+
|
|
163
|
+
// If no sessions remain, exit immediately
|
|
164
|
+
if (sessions.size === 0) {
|
|
165
|
+
console.log('No sessions remaining, daemon exiting.');
|
|
166
|
+
if (serverInstance && cleanupFn) {
|
|
167
|
+
serverInstance.close();
|
|
168
|
+
cleanupFn();
|
|
169
|
+
}
|
|
170
|
+
process.exit(0);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Handle tool call command.
|
|
176
|
+
* Auto-starts session if needed.
|
|
177
|
+
*/
|
|
178
|
+
async function handleCall(
|
|
179
|
+
server: string,
|
|
180
|
+
tool: string,
|
|
181
|
+
params: Record<string, unknown>,
|
|
182
|
+
config: McpServerConfig
|
|
183
|
+
): Promise<{ success: boolean; result?: unknown; error?: string }> {
|
|
184
|
+
try {
|
|
185
|
+
const session = await ensureSession(server, config);
|
|
186
|
+
const callTimeout = config.stateful_session_timeout ?? 60000;
|
|
187
|
+
const result = await session.client.callTool(
|
|
188
|
+
{ name: tool, arguments: params },
|
|
189
|
+
undefined,
|
|
190
|
+
{ timeout: callTimeout }
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
// Extract content from result
|
|
194
|
+
if ('content' in result && Array.isArray(result.content)) {
|
|
195
|
+
const textContent = result.content.find((c) => c.type === 'text');
|
|
196
|
+
if (textContent && 'text' in textContent) {
|
|
197
|
+
try {
|
|
198
|
+
return { success: true, result: JSON.parse(textContent.text) };
|
|
199
|
+
} catch {
|
|
200
|
+
return { success: true, result: textContent.text };
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return { success: true, result: result.content };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return { success: true, result };
|
|
207
|
+
} catch (e) {
|
|
208
|
+
return { success: false, error: e instanceof Error ? e.message : String(e) };
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Handle discover tools command.
|
|
214
|
+
* Auto-starts session if needed for stateful servers.
|
|
215
|
+
*/
|
|
216
|
+
async function handleDiscover(
|
|
217
|
+
server: string,
|
|
218
|
+
config: McpServerConfig
|
|
219
|
+
): Promise<{ success: boolean; tools?: McpToolSchema[]; error?: string }> {
|
|
220
|
+
try {
|
|
221
|
+
const session = await ensureSession(server, config);
|
|
222
|
+
|
|
223
|
+
if (!session.tools) {
|
|
224
|
+
const result = await session.client.listTools();
|
|
225
|
+
session.tools = result.tools.map((t) => ({
|
|
226
|
+
name: t.name,
|
|
227
|
+
description: t.description,
|
|
228
|
+
inputSchema: t.inputSchema as McpToolSchema['inputSchema'],
|
|
229
|
+
}));
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const tools = config.hiddenTools?.length
|
|
233
|
+
? session.tools.filter((t) => !config.hiddenTools!.includes(t.name))
|
|
234
|
+
: session.tools;
|
|
235
|
+
|
|
236
|
+
return { success: true, tools };
|
|
237
|
+
} catch (e) {
|
|
238
|
+
return { success: false, error: e instanceof Error ? e.message : String(e) };
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Handle restart command.
|
|
244
|
+
* Closes existing session and creates a new one.
|
|
245
|
+
*/
|
|
246
|
+
async function handleRestart(
|
|
247
|
+
server: string,
|
|
248
|
+
config: McpServerConfig
|
|
249
|
+
): Promise<{ success: boolean; pid?: number; error?: string }> {
|
|
250
|
+
// Close existing session if any
|
|
251
|
+
const existing = sessions.get(server);
|
|
252
|
+
if (existing) {
|
|
253
|
+
try {
|
|
254
|
+
await existing.transport.close();
|
|
255
|
+
} catch {
|
|
256
|
+
// Ignore
|
|
257
|
+
}
|
|
258
|
+
sessions.delete(server);
|
|
259
|
+
console.log(`Session stopped for restart: ${server}`);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Create new session
|
|
263
|
+
try {
|
|
264
|
+
const session = await ensureSession(server, config);
|
|
265
|
+
return { success: true, pid: session.transport.pid ?? undefined };
|
|
266
|
+
} catch (e) {
|
|
267
|
+
return { success: false, error: e instanceof Error ? e.message : String(e) };
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Handle list sessions command.
|
|
273
|
+
*/
|
|
274
|
+
function handleList(): { success: boolean; sessions: Array<{ server: string; startedAt: string; lastUsedAt: string; timeoutMs: number; pid?: number }> } {
|
|
275
|
+
const result: Array<{ server: string; startedAt: string; lastUsedAt: string; timeoutMs: number; pid?: number }> = [];
|
|
276
|
+
|
|
277
|
+
for (const [server, session] of sessions) {
|
|
278
|
+
result.push({
|
|
279
|
+
server,
|
|
280
|
+
startedAt: session.startedAt.toISOString(),
|
|
281
|
+
lastUsedAt: session.lastUsedAt.toISOString(),
|
|
282
|
+
timeoutMs: session.timeoutMs,
|
|
283
|
+
pid: session.transport.pid ?? undefined,
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return { success: true, sessions: result };
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Handle ping command - refreshes all session timestamps.
|
|
292
|
+
*/
|
|
293
|
+
function handlePing(): { success: boolean; pong: true; sessionsRefreshed: number } {
|
|
294
|
+
const now = new Date();
|
|
295
|
+
for (const session of sessions.values()) {
|
|
296
|
+
session.lastUsedAt = now;
|
|
297
|
+
}
|
|
298
|
+
return { success: true, pong: true, sessionsRefreshed: sessions.size };
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Process a command from client.
|
|
303
|
+
*/
|
|
304
|
+
async function processCommand(command: DaemonCommand): Promise<unknown> {
|
|
305
|
+
switch (command.cmd) {
|
|
306
|
+
case 'call':
|
|
307
|
+
return handleCall(command.server, command.tool, command.params, command.config);
|
|
308
|
+
case 'discover':
|
|
309
|
+
return handleDiscover(command.server, command.config);
|
|
310
|
+
case 'restart':
|
|
311
|
+
return handleRestart(command.server, command.config);
|
|
312
|
+
case 'list':
|
|
313
|
+
return handleList();
|
|
314
|
+
case 'info':
|
|
315
|
+
return {
|
|
316
|
+
success: true,
|
|
317
|
+
pid: process.pid,
|
|
318
|
+
sessionCount: sessions.size,
|
|
319
|
+
sessions: Array.from(sessions.keys()),
|
|
320
|
+
};
|
|
321
|
+
case 'ping':
|
|
322
|
+
return handlePing();
|
|
323
|
+
case 'shutdown':
|
|
324
|
+
// Graceful shutdown
|
|
325
|
+
console.log('Shutdown requested');
|
|
326
|
+
for (const session of sessions.values()) {
|
|
327
|
+
try {
|
|
328
|
+
await session.transport.close();
|
|
329
|
+
} catch {
|
|
330
|
+
// Ignore
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
sessions.clear();
|
|
334
|
+
process.exit(0);
|
|
335
|
+
default:
|
|
336
|
+
return { success: false, error: 'Unknown command' };
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Handle client connection.
|
|
342
|
+
*/
|
|
343
|
+
function handleConnection(socket: Socket): void {
|
|
344
|
+
let buffer = '';
|
|
345
|
+
|
|
346
|
+
socket.on('data', async (data) => {
|
|
347
|
+
buffer += data.toString();
|
|
348
|
+
|
|
349
|
+
// Process complete JSON messages (newline-delimited)
|
|
350
|
+
const lines = buffer.split('\n');
|
|
351
|
+
buffer = lines.pop() ?? '';
|
|
352
|
+
|
|
353
|
+
for (const line of lines) {
|
|
354
|
+
if (!line.trim()) continue;
|
|
355
|
+
|
|
356
|
+
try {
|
|
357
|
+
const command = JSON.parse(line) as DaemonCommand;
|
|
358
|
+
const result = await processCommand(command);
|
|
359
|
+
socket.write(JSON.stringify(result) + '\n');
|
|
360
|
+
} catch (e) {
|
|
361
|
+
socket.write(JSON.stringify({
|
|
362
|
+
success: false,
|
|
363
|
+
error: e instanceof Error ? e.message : String(e),
|
|
364
|
+
}) + '\n');
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
socket.on('error', () => {
|
|
370
|
+
// Client disconnected, ignore
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Check for timed-out sessions and close them.
|
|
376
|
+
*/
|
|
377
|
+
async function checkSessionTimeouts(): Promise<void> {
|
|
378
|
+
const now = Date.now();
|
|
379
|
+
const toClose: string[] = [];
|
|
380
|
+
|
|
381
|
+
for (const [server, session] of sessions) {
|
|
382
|
+
const idleMs = now - session.lastUsedAt.getTime();
|
|
383
|
+
if (idleMs >= session.timeoutMs) {
|
|
384
|
+
console.log(`Session timed out: ${server} (idle ${Math.round(idleMs / 1000)}s)`);
|
|
385
|
+
toClose.push(server);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
for (const server of toClose) {
|
|
390
|
+
await closeSession(server);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Graceful shutdown - close all sessions and exit.
|
|
396
|
+
*/
|
|
397
|
+
async function gracefulShutdown(server: Server, cleanup: () => void, reason: string): Promise<void> {
|
|
398
|
+
console.log(`Daemon shutting down: ${reason}`);
|
|
399
|
+
|
|
400
|
+
// Close all MCP sessions
|
|
401
|
+
for (const session of sessions.values()) {
|
|
402
|
+
try {
|
|
403
|
+
await session.transport.close();
|
|
404
|
+
} catch {
|
|
405
|
+
// Ignore
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
sessions.clear();
|
|
409
|
+
|
|
410
|
+
// Close the server
|
|
411
|
+
server.close();
|
|
412
|
+
|
|
413
|
+
// Cleanup files
|
|
414
|
+
cleanup();
|
|
415
|
+
|
|
416
|
+
process.exit(0);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Start the daemon.
|
|
421
|
+
*/
|
|
422
|
+
function startDaemon(agentId: string): void {
|
|
423
|
+
// Ensure sessions directory exists
|
|
424
|
+
if (!existsSync(SESSIONS_DIR)) {
|
|
425
|
+
mkdirSync(SESSIONS_DIR, { recursive: true });
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
const socketPath = join(SESSIONS_DIR, `${agentId}.sock`);
|
|
429
|
+
const pidPath = join(SESSIONS_DIR, `${agentId}.pid`);
|
|
430
|
+
|
|
431
|
+
// Clean up stale socket
|
|
432
|
+
if (existsSync(socketPath)) {
|
|
433
|
+
try {
|
|
434
|
+
unlinkSync(socketPath);
|
|
435
|
+
} catch {
|
|
436
|
+
// Ignore
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const server = createServer(handleConnection);
|
|
441
|
+
serverInstance = server;
|
|
442
|
+
|
|
443
|
+
// Cleanup function for files
|
|
444
|
+
const cleanup = () => {
|
|
445
|
+
try {
|
|
446
|
+
if (existsSync(socketPath)) unlinkSync(socketPath);
|
|
447
|
+
if (existsSync(pidPath)) unlinkSync(pidPath);
|
|
448
|
+
} catch {
|
|
449
|
+
// Ignore
|
|
450
|
+
}
|
|
451
|
+
};
|
|
452
|
+
cleanupFn = cleanup;
|
|
453
|
+
|
|
454
|
+
server.listen(socketPath, () => {
|
|
455
|
+
// Write PID file
|
|
456
|
+
writeFileSync(pidPath, String(process.pid));
|
|
457
|
+
console.log(`Daemon started for agent ${agentId} (PID: ${process.pid})`);
|
|
458
|
+
console.log(`Socket: ${socketPath}`);
|
|
459
|
+
console.log(`Default session timeout: ${DAEMON_DEFAULT_MCP_TIMEOUT}ms`);
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
server.on('error', (err) => {
|
|
463
|
+
console.error('Daemon error:', err);
|
|
464
|
+
process.exit(1);
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
// Start session timeout checker
|
|
468
|
+
const timeoutChecker = setInterval(async () => {
|
|
469
|
+
await checkSessionTimeouts();
|
|
470
|
+
}, TIMEOUT_CHECK_INTERVAL_MS);
|
|
471
|
+
|
|
472
|
+
// Don't let the interval keep the process alive if everything else is done
|
|
473
|
+
timeoutChecker.unref();
|
|
474
|
+
|
|
475
|
+
process.on('exit', cleanup);
|
|
476
|
+
process.on('SIGINT', () => {
|
|
477
|
+
gracefulShutdown(server, cleanup, 'SIGINT');
|
|
478
|
+
});
|
|
479
|
+
process.on('SIGTERM', () => {
|
|
480
|
+
gracefulShutdown(server, cleanup, 'SIGTERM');
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// Main - start daemon with AGENT_ID from args or env
|
|
485
|
+
const agentId = process.argv[2] || process.env.AGENT_ID || 'default';
|
|
486
|
+
startDaemon(agentId);
|