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.
Files changed (305) hide show
  1. package/.allhands/README.md +75 -0
  2. package/.allhands/agents/compounder.yaml +15 -0
  3. package/.allhands/agents/coordinator.yaml +17 -0
  4. package/.allhands/agents/documentor.yaml +15 -0
  5. package/.allhands/agents/e2e-test-planner.yaml +17 -0
  6. package/.allhands/agents/emergent.yaml +22 -0
  7. package/.allhands/agents/executor.yaml +14 -0
  8. package/.allhands/agents/ideation.yaml +11 -0
  9. package/.allhands/agents/initiative-steering.yaml +19 -0
  10. package/.allhands/agents/judge.yaml +13 -0
  11. package/.allhands/agents/planner.yaml +19 -0
  12. package/.allhands/agents/pr-reviewer.yaml +15 -0
  13. package/.allhands/docs.json +5 -0
  14. package/.allhands/docs.local.json +26 -0
  15. package/.allhands/flows/COMPOUNDING.md +203 -0
  16. package/.allhands/flows/COORDINATION.md +89 -0
  17. package/.allhands/flows/CORE.md +87 -0
  18. package/.allhands/flows/DOCUMENTATION.md +218 -0
  19. package/.allhands/flows/E2E_TEST_PLAN_BUILDING.md +140 -0
  20. package/.allhands/flows/EMERGENT_PLANNING.md +57 -0
  21. package/.allhands/flows/IDEATION_SCOPING.md +154 -0
  22. package/.allhands/flows/INITIATIVE_STEERING.md +110 -0
  23. package/.allhands/flows/JUDGE_REVIEWING.md +79 -0
  24. package/.allhands/flows/PROMPT_TASK_EXECUTION.md +68 -0
  25. package/.allhands/flows/PR_REVIEWING.md +43 -0
  26. package/.allhands/flows/SPEC_PLANNING.md +216 -0
  27. package/.allhands/flows/harness/WRITING_HARNESS_FLOWS.md +27 -0
  28. package/.allhands/flows/harness/WRITING_HARNESS_KNOWLEDGE.md +27 -0
  29. package/.allhands/flows/harness/WRITING_HARNESS_ORCHESTRATION.md +27 -0
  30. package/.allhands/flows/harness/WRITING_HARNESS_SKILLS.md +27 -0
  31. package/.allhands/flows/harness/WRITING_HARNESS_TOOLS.md +27 -0
  32. package/.allhands/flows/harness/WRITING_HARNESS_VALIDATION_TOOLING.md +27 -0
  33. package/.allhands/flows/shared/CODEBASE_UNDERSTANDING.md +72 -0
  34. package/.allhands/flows/shared/CREATE_HARNESS_SPEC.md +48 -0
  35. package/.allhands/flows/shared/CREATE_SPEC.md +41 -0
  36. package/.allhands/flows/shared/CREATE_VALIDATION_TOOLING_SPEC.md +70 -0
  37. package/.allhands/flows/shared/DOCUMENTATION_DISCOVERY.md +123 -0
  38. package/.allhands/flows/shared/DOCUMENTATION_WRITER.md +101 -0
  39. package/.allhands/flows/shared/EMERGENT_REFINEMENT_ANALYSIS.md +76 -0
  40. package/.allhands/flows/shared/EXTERNAL_TECH_GUIDANCE.md +97 -0
  41. package/.allhands/flows/shared/IDEATION_CODEBASE_GROUNDING.md +49 -0
  42. package/.allhands/flows/shared/PLAN_DEEPENING.md +152 -0
  43. package/.allhands/flows/shared/PROMPT_TASKS_CURATION.md +113 -0
  44. package/.allhands/flows/shared/PROMPT_VALIDATION_REVIEW.MD +99 -0
  45. package/.allhands/flows/shared/QUICK_PREMORTEM.md +70 -0
  46. package/.allhands/flows/shared/RESEARCH_GUIDANCE.md +38 -0
  47. package/.allhands/flows/shared/REVIEW_OPTIONS_BREAKDOWN.md +68 -0
  48. package/.allhands/flows/shared/SKILL_EXTRACTION.md +84 -0
  49. package/.allhands/flows/shared/SPEC_FLOW_ANALYSIS.md +119 -0
  50. package/.allhands/flows/shared/TDD_WORKFLOW.md +109 -0
  51. package/.allhands/flows/shared/UTILIZE_VALIDATION_TOOLING.md +84 -0
  52. package/.allhands/flows/shared/WRITING_HARNESS_FLOWS.md +11 -0
  53. package/.allhands/flows/shared/WRITING_HARNESS_MCP_TOOLS.md +84 -0
  54. package/.allhands/flows/shared/jury/ARCHITECTURE_REVIEW.md +91 -0
  55. package/.allhands/flows/shared/jury/BEST_PRACTICES_REVIEW.md +80 -0
  56. package/.allhands/flows/shared/jury/CLAIM_VERIFICATION_REVIEW.md +101 -0
  57. package/.allhands/flows/shared/jury/EXPECTATIONS_FIT_REVIEW.md +78 -0
  58. package/.allhands/flows/shared/jury/MAINTAINABILITY_REVIEW.md +110 -0
  59. package/.allhands/flows/shared/jury/PROMPTS_EXPECTATIONS_FIT.md +74 -0
  60. package/.allhands/flows/shared/jury/PROMPTS_FLOW_ANALYSIS.md +92 -0
  61. package/.allhands/flows/shared/jury/PROMPTS_YAGNI.md +78 -0
  62. package/.allhands/flows/shared/jury/PROMPT_PREMORTEM.md +125 -0
  63. package/.allhands/flows/shared/jury/SECURITY_REVIEW.md +86 -0
  64. package/.allhands/flows/shared/jury/YAGNI_REVIEW.md +82 -0
  65. package/.allhands/flows/wip/DEBUG_INVESTIGATION.md +162 -0
  66. package/.allhands/flows/wip/MEMORY_RECALL.md +62 -0
  67. package/.allhands/harness/ah +131 -0
  68. package/.allhands/harness/package-lock.json +5292 -0
  69. package/.allhands/harness/package.json +52 -0
  70. package/.allhands/harness/src/__tests__/e2e/commands.test.ts +307 -0
  71. package/.allhands/harness/src/__tests__/e2e/event-loop.test.ts +539 -0
  72. package/.allhands/harness/src/__tests__/e2e/hooks.test.ts +427 -0
  73. package/.allhands/harness/src/__tests__/e2e/new-initiative-routing.test.ts +137 -0
  74. package/.allhands/harness/src/__tests__/e2e/run-e2e.ts +109 -0
  75. package/.allhands/harness/src/__tests__/e2e/specs-type.test.ts +210 -0
  76. package/.allhands/harness/src/__tests__/e2e/validation-hooks.test.ts +669 -0
  77. package/.allhands/harness/src/__tests__/e2e/validation-path-consistency.test.ts +354 -0
  78. package/.allhands/harness/src/__tests__/e2e/validation.test.ts +528 -0
  79. package/.allhands/harness/src/__tests__/harness/assertions.ts +318 -0
  80. package/.allhands/harness/src/__tests__/harness/cli-runner.ts +359 -0
  81. package/.allhands/harness/src/__tests__/harness/fixture.ts +384 -0
  82. package/.allhands/harness/src/__tests__/harness/hook-runner.ts +411 -0
  83. package/.allhands/harness/src/__tests__/harness/index.ts +122 -0
  84. package/.allhands/harness/src/cli.ts +36 -0
  85. package/.allhands/harness/src/commands/complexity.ts +177 -0
  86. package/.allhands/harness/src/commands/context7.ts +202 -0
  87. package/.allhands/harness/src/commands/docs.ts +557 -0
  88. package/.allhands/harness/src/commands/hooks.ts +24 -0
  89. package/.allhands/harness/src/commands/index.ts +51 -0
  90. package/.allhands/harness/src/commands/knowledge.ts +382 -0
  91. package/.allhands/harness/src/commands/memories.ts +302 -0
  92. package/.allhands/harness/src/commands/notify.ts +61 -0
  93. package/.allhands/harness/src/commands/oracle.ts +158 -0
  94. package/.allhands/harness/src/commands/perplexity.ts +220 -0
  95. package/.allhands/harness/src/commands/planning.ts +245 -0
  96. package/.allhands/harness/src/commands/schema.ts +73 -0
  97. package/.allhands/harness/src/commands/skills.ts +128 -0
  98. package/.allhands/harness/src/commands/solutions.ts +353 -0
  99. package/.allhands/harness/src/commands/spawn.ts +158 -0
  100. package/.allhands/harness/src/commands/specs.ts +532 -0
  101. package/.allhands/harness/src/commands/tavily.ts +226 -0
  102. package/.allhands/harness/src/commands/tools.ts +579 -0
  103. package/.allhands/harness/src/commands/trace.ts +327 -0
  104. package/.allhands/harness/src/commands/tui.ts +960 -0
  105. package/.allhands/harness/src/commands/validate.ts +143 -0
  106. package/.allhands/harness/src/commands/validation-tools.ts +108 -0
  107. package/.allhands/harness/src/hooks/context.ts +1442 -0
  108. package/.allhands/harness/src/hooks/enforcement.ts +170 -0
  109. package/.allhands/harness/src/hooks/index.ts +54 -0
  110. package/.allhands/harness/src/hooks/lifecycle.ts +229 -0
  111. package/.allhands/harness/src/hooks/notification.ts +104 -0
  112. package/.allhands/harness/src/hooks/observability.ts +551 -0
  113. package/.allhands/harness/src/hooks/session.ts +88 -0
  114. package/.allhands/harness/src/hooks/shared.ts +815 -0
  115. package/.allhands/harness/src/hooks/transcript-parser.ts +208 -0
  116. package/.allhands/harness/src/hooks/validation.ts +617 -0
  117. package/.allhands/harness/src/lib/__tests__/ctags.test.ts +244 -0
  118. package/.allhands/harness/src/lib/__tests__/docs-validation.test.ts +344 -0
  119. package/.allhands/harness/src/lib/__tests__/mcp-runtime.test.ts +190 -0
  120. package/.allhands/harness/src/lib/__tests__/schema.test.ts +861 -0
  121. package/.allhands/harness/src/lib/base-command.ts +198 -0
  122. package/.allhands/harness/src/lib/cli-daemon.ts +343 -0
  123. package/.allhands/harness/src/lib/compaction.ts +313 -0
  124. package/.allhands/harness/src/lib/ctags.ts +497 -0
  125. package/.allhands/harness/src/lib/docs-validation.ts +907 -0
  126. package/.allhands/harness/src/lib/event-loop.ts +662 -0
  127. package/.allhands/harness/src/lib/flows.ts +155 -0
  128. package/.allhands/harness/src/lib/git.ts +276 -0
  129. package/.allhands/harness/src/lib/knowledge-worker.ts +72 -0
  130. package/.allhands/harness/src/lib/knowledge.ts +810 -0
  131. package/.allhands/harness/src/lib/llm.ts +255 -0
  132. package/.allhands/harness/src/lib/mcp-client.ts +432 -0
  133. package/.allhands/harness/src/lib/mcp-daemon.ts +486 -0
  134. package/.allhands/harness/src/lib/mcp-runtime.ts +418 -0
  135. package/.allhands/harness/src/lib/notification.ts +115 -0
  136. package/.allhands/harness/src/lib/opencode/index.ts +70 -0
  137. package/.allhands/harness/src/lib/opencode/profiles.ts +300 -0
  138. package/.allhands/harness/src/lib/opencode/prompts/codesearch.md +98 -0
  139. package/.allhands/harness/src/lib/opencode/prompts/knowledge-aggregator.md +67 -0
  140. package/.allhands/harness/src/lib/opencode/runner.ts +281 -0
  141. package/.allhands/harness/src/lib/oracle.ts +926 -0
  142. package/.allhands/harness/src/lib/planning-utils.ts +150 -0
  143. package/.allhands/harness/src/lib/planning.ts +605 -0
  144. package/.allhands/harness/src/lib/pr-review.ts +225 -0
  145. package/.allhands/harness/src/lib/prompts.ts +522 -0
  146. package/.allhands/harness/src/lib/schema.ts +418 -0
  147. package/.allhands/harness/src/lib/schemas/agent-profile.ts +141 -0
  148. package/.allhands/harness/src/lib/schemas/template-vars.ts +138 -0
  149. package/.allhands/harness/src/lib/session.ts +164 -0
  150. package/.allhands/harness/src/lib/specs.ts +348 -0
  151. package/.allhands/harness/src/lib/tldr.ts +829 -0
  152. package/.allhands/harness/src/lib/tmux.ts +1051 -0
  153. package/.allhands/harness/src/lib/trace-store.ts +714 -0
  154. package/.allhands/harness/src/mcp/__tests__/index.test.ts +46 -0
  155. package/.allhands/harness/src/mcp/_template.ts +47 -0
  156. package/.allhands/harness/src/mcp/filesystem.ts +33 -0
  157. package/.allhands/harness/src/mcp/index.ts +69 -0
  158. package/.allhands/harness/src/mcp/playwright.ts +34 -0
  159. package/.allhands/harness/src/mcp/xcodebuild.ts +29 -0
  160. package/.allhands/harness/src/schemas/docs.schema.json +44 -0
  161. package/.allhands/harness/src/schemas/settings.schema.json +214 -0
  162. package/.allhands/harness/src/tui/actions.ts +227 -0
  163. package/.allhands/harness/src/tui/file-viewer-modal.ts +270 -0
  164. package/.allhands/harness/src/tui/index.ts +1574 -0
  165. package/.allhands/harness/src/tui/modal.ts +232 -0
  166. package/.allhands/harness/src/tui/prompts-pane.ts +186 -0
  167. package/.allhands/harness/src/tui/status-pane.ts +434 -0
  168. package/.allhands/harness/tsconfig.json +22 -0
  169. package/.allhands/harness/vitest.config.ts +13 -0
  170. package/.allhands/pillars.md +33 -0
  171. package/.allhands/principles.md +88 -0
  172. package/.allhands/schemas/alignment.yaml +51 -0
  173. package/.allhands/schemas/documentation.yaml +10 -0
  174. package/.allhands/schemas/prompt.yaml +92 -0
  175. package/.allhands/schemas/skill.yaml +34 -0
  176. package/.allhands/schemas/solution.yaml +131 -0
  177. package/.allhands/schemas/spec.yaml +67 -0
  178. package/.allhands/schemas/validation-suite.yaml +49 -0
  179. package/.allhands/schemas/workflow.yaml +51 -0
  180. package/.allhands/settings.json +57 -0
  181. package/.allhands/skills/claude-code-patterns/SKILL.md +60 -0
  182. package/.allhands/skills/claude-code-patterns/docs/context-hygiene.md +19 -0
  183. package/.allhands/skills/harness-maintenance/SKILL.md +449 -0
  184. package/.allhands/skills/harness-maintenance/references/core-architecture.md +187 -0
  185. package/.allhands/skills/harness-maintenance/references/harness-skills.md +87 -0
  186. package/.allhands/skills/harness-maintenance/references/knowledge-compounding.md +78 -0
  187. package/.allhands/skills/harness-maintenance/references/tools-commands-mcp-hooks.md +115 -0
  188. package/.allhands/skills/harness-maintenance/references/validation-tooling.md +77 -0
  189. package/.allhands/skills/harness-maintenance/references/writing-flows.md +84 -0
  190. package/.allhands/validation/browser-automation.md +109 -0
  191. package/.allhands/validation/xcode-automation.md +195 -0
  192. package/.allhands/workflows/documentation.md +86 -0
  193. package/.allhands/workflows/investigation.md +81 -0
  194. package/.allhands/workflows/milestone.md +91 -0
  195. package/.allhands/workflows/optimization.md +85 -0
  196. package/.allhands/workflows/refactor.md +99 -0
  197. package/.allhands/workflows/triage.md +81 -0
  198. package/.claude/README.md +1 -0
  199. package/.claude/agents/explorer.md +10 -0
  200. package/.claude/agents/researcher.md +11 -0
  201. package/.claude/agents/task-runner.md +8 -0
  202. package/.claude/settings.json +231 -0
  203. package/.env.ai.example +7 -0
  204. package/.github/workflows/npm-publish.yml +69 -0
  205. package/.internal.json +45 -0
  206. package/.tldr/config.json +11 -0
  207. package/.tldrignore +90 -0
  208. package/CLAUDE.md +6 -0
  209. package/README.md +98 -0
  210. package/bin/sync-cli.js +7552 -0
  211. package/concerns.md +7 -0
  212. package/docs/README.md +41 -0
  213. package/docs/agents/README.md +24 -0
  214. package/docs/agents/agent-configuration-system.md +86 -0
  215. package/docs/agents/execution-agents.md +50 -0
  216. package/docs/agents/knowledge-agents.md +61 -0
  217. package/docs/agents/orchestration-agent.md +57 -0
  218. package/docs/agents/planning-agents.md +84 -0
  219. package/docs/agents/quality-review-agents.md +67 -0
  220. package/docs/agents/workflow-agent-orchestration.md +69 -0
  221. package/docs/flows/README.md +44 -0
  222. package/docs/flows/compounding.md +126 -0
  223. package/docs/flows/coordination.md +72 -0
  224. package/docs/flows/core-harness-integration.md +63 -0
  225. package/docs/flows/documentation-orchestration.md +98 -0
  226. package/docs/flows/e2e-test-plan-building.md +83 -0
  227. package/docs/flows/emergent-refinement.md +104 -0
  228. package/docs/flows/flow-authoring-and-mcp-tools.md +89 -0
  229. package/docs/flows/judge-reviewing.md +112 -0
  230. package/docs/flows/plan-deepening-and-research.md +107 -0
  231. package/docs/flows/plan-review-jury.md +114 -0
  232. package/docs/flows/pr-reviewing.md +54 -0
  233. package/docs/flows/prompt-task-execution.md +119 -0
  234. package/docs/flows/spec-planning.md +162 -0
  235. package/docs/flows/type-specific-scoping-flows.md +49 -0
  236. package/docs/flows/validation-and-skills-integration.md +145 -0
  237. package/docs/flows/wip/wip-flows.md +102 -0
  238. package/docs/harness/README.md +23 -0
  239. package/docs/harness/agent-profiles.md +84 -0
  240. package/docs/harness/cli/README.md +24 -0
  241. package/docs/harness/cli/cli-entry-and-command-discovery.md +91 -0
  242. package/docs/harness/cli/docs-command.md +87 -0
  243. package/docs/harness/cli/knowledge-command.md +91 -0
  244. package/docs/harness/cli/minor-cli-commands.md +65 -0
  245. package/docs/harness/cli/oracle-command.md +113 -0
  246. package/docs/harness/cli/planning-command.md +95 -0
  247. package/docs/harness/cli/schema-and-validation-commands.md +154 -0
  248. package/docs/harness/cli/search-commands.md +97 -0
  249. package/docs/harness/cli/spawn-command.md +136 -0
  250. package/docs/harness/cli/specs-command.md +102 -0
  251. package/docs/harness/cli/tools-command.md +122 -0
  252. package/docs/harness/cli/trace-command.md +122 -0
  253. package/docs/harness/cli-daemon.md +92 -0
  254. package/docs/harness/event-loop.md +184 -0
  255. package/docs/harness/hooks/README.md +15 -0
  256. package/docs/harness/hooks/context-hooks.md +96 -0
  257. package/docs/harness/hooks/lifecycle-and-observability-hooks.md +135 -0
  258. package/docs/harness/hooks/validation-hooks.md +97 -0
  259. package/docs/harness/test-harness.md +149 -0
  260. package/docs/harness/tui.md +176 -0
  261. package/docs/memories.md +20 -0
  262. package/docs/solutions/agentic-issues/premature-agent-deletion-tui-action-dependency-20260130.md +49 -0
  263. package/docs/solutions/agentic-issues/ref-anchor-scope-mismatch-skill-references-20260131.md +55 -0
  264. package/docs/solutions/agentic-issues/tautological-tests-routing-20260131.md +52 -0
  265. package/docs/solutions/integration_issue/blocktool-output-format-mismatch-hook-runner-20260130.md +52 -0
  266. package/docs/solutions/integration_issue/dual-validation-path-divergence-schema-20260130.md +66 -0
  267. package/docs/solutions/security-issues/unsanitized-domain-path-join-20260131.md +52 -0
  268. package/docs/solutions/test-failures/event-loop-mock-ordering-checkAgentWindows-20260130.md +63 -0
  269. package/docs/sync-cli/README.md +19 -0
  270. package/docs/sync-cli/cli-entrypoint-and-commands.md +39 -0
  271. package/docs/sync-cli/commands/README.md +11 -0
  272. package/docs/sync-cli/commands/pull-manifest-command.md +36 -0
  273. package/docs/sync-cli/commands/push-command.md +84 -0
  274. package/docs/sync-cli/commands/sync-command.md +71 -0
  275. package/docs/sync-cli/systems/README.md +14 -0
  276. package/docs/sync-cli/systems/git-and-github-integration.md +49 -0
  277. package/docs/sync-cli/systems/interactive-ui.md +43 -0
  278. package/docs/sync-cli/systems/manifest-and-distribution.md +51 -0
  279. package/docs/sync-cli/systems/path-resolution.md +42 -0
  280. package/package.json +46 -0
  281. package/scripts/install-shim.sh +40 -0
  282. package/scripts/pre-pack.sh +25 -0
  283. package/specs/harness-maintenance-skill.spec.md +138 -0
  284. package/specs/roadmap/git-spec-lifecycle-management.spec.md +113 -0
  285. package/specs/sync-init-flag.spec.md +117 -0
  286. package/specs/unified-workflow-orchestration.spec.md +250 -0
  287. package/specs/validation-tooling-practice.spec.md +98 -0
  288. package/specs/workflow-domain-configuration.spec.md +265 -0
  289. package/src/commands/pull-manifest.ts +31 -0
  290. package/src/commands/push.ts +344 -0
  291. package/src/commands/sync.ts +289 -0
  292. package/src/lib/constants.ts +10 -0
  293. package/src/lib/dotfiles.ts +36 -0
  294. package/src/lib/fs-utils.ts +18 -0
  295. package/src/lib/gh.ts +40 -0
  296. package/src/lib/git.ts +63 -0
  297. package/src/lib/gitignore.ts +167 -0
  298. package/src/lib/manifest.ts +121 -0
  299. package/src/lib/marker-sync.ts +39 -0
  300. package/src/lib/paths.ts +38 -0
  301. package/src/lib/target-lines.ts +66 -0
  302. package/src/lib/ui.ts +78 -0
  303. package/src/sync-cli.ts +120 -0
  304. package/target-lines.json +23 -0
  305. 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);