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,255 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LLM - Multi-Provider Large Language Model Integration
|
|
3
|
+
*
|
|
4
|
+
* Generic LLM calling infrastructure supporting multiple providers.
|
|
5
|
+
* This is the foundation layer - provider configs and raw inference.
|
|
6
|
+
*
|
|
7
|
+
* Supported Providers:
|
|
8
|
+
* - Gemini (Google) - GEMINI_API_KEY (uses @google/genai SDK)
|
|
9
|
+
* - OpenAI (GPT) - OPENAI_API_KEY
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { existsSync, readFileSync } from 'fs';
|
|
13
|
+
import { GoogleGenAI } from '@google/genai';
|
|
14
|
+
import { loadProjectSettings } from '../hooks/shared.js';
|
|
15
|
+
|
|
16
|
+
// ============================================================================
|
|
17
|
+
// Types
|
|
18
|
+
// ============================================================================
|
|
19
|
+
|
|
20
|
+
export type ProviderName = 'gemini' | 'openai';
|
|
21
|
+
|
|
22
|
+
export interface ProviderConfig {
|
|
23
|
+
name: ProviderName;
|
|
24
|
+
apiKeyEnvVar: string;
|
|
25
|
+
defaultModel: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface LLMResult {
|
|
29
|
+
text: string;
|
|
30
|
+
model: string;
|
|
31
|
+
provider: ProviderName;
|
|
32
|
+
durationMs: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface AskOptions {
|
|
36
|
+
provider?: ProviderName;
|
|
37
|
+
model?: string;
|
|
38
|
+
files?: string[];
|
|
39
|
+
context?: string;
|
|
40
|
+
timeout?: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ============================================================================
|
|
44
|
+
// Configuration
|
|
45
|
+
// ============================================================================
|
|
46
|
+
|
|
47
|
+
export const PROVIDERS: Record<ProviderName, ProviderConfig> = {
|
|
48
|
+
gemini: {
|
|
49
|
+
name: 'gemini',
|
|
50
|
+
apiKeyEnvVar: 'GEMINI_API_KEY',
|
|
51
|
+
defaultModel: 'gemini-3-pro-preview',
|
|
52
|
+
},
|
|
53
|
+
openai: {
|
|
54
|
+
name: 'openai',
|
|
55
|
+
apiKeyEnvVar: 'OPENAI_API_KEY',
|
|
56
|
+
defaultModel: 'gpt-5.2',
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const DEFAULT_TIMEOUT = 120000;
|
|
61
|
+
|
|
62
|
+
// ============================================================================
|
|
63
|
+
// Public API
|
|
64
|
+
// ============================================================================
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Get the default provider from settings or fallback
|
|
68
|
+
*/
|
|
69
|
+
export function getDefaultProvider(): ProviderName {
|
|
70
|
+
const settings = loadProjectSettings();
|
|
71
|
+
const provider = settings?.oracle?.defaultProvider;
|
|
72
|
+
if (provider === 'openai' || provider === 'gemini') {
|
|
73
|
+
return provider;
|
|
74
|
+
}
|
|
75
|
+
return 'gemini';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Get the compaction provider from settings or fallback to gemini.
|
|
80
|
+
* Compaction uses gemini by default due to its large context window (1M+ tokens).
|
|
81
|
+
*/
|
|
82
|
+
export function getCompactionProvider(): ProviderName {
|
|
83
|
+
const settings = loadProjectSettings();
|
|
84
|
+
const provider = settings?.oracle?.compactionProvider;
|
|
85
|
+
if (provider === 'openai' || provider === 'gemini') {
|
|
86
|
+
return provider;
|
|
87
|
+
}
|
|
88
|
+
return 'gemini'; // Default to gemini for large context handling
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Generic LLM inference
|
|
93
|
+
*
|
|
94
|
+
* @param query - The prompt/question to send
|
|
95
|
+
* @param options - Provider, model, file context, etc.
|
|
96
|
+
* @returns LLM response with metadata
|
|
97
|
+
*/
|
|
98
|
+
export async function ask(query: string, options: AskOptions = {}): Promise<LLMResult> {
|
|
99
|
+
const providerName = options.provider ?? getDefaultProvider();
|
|
100
|
+
const provider = PROVIDERS[providerName];
|
|
101
|
+
|
|
102
|
+
if (!provider) {
|
|
103
|
+
throw new Error(`Invalid provider: ${providerName}. Use: gemini, openai`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const apiKey = process.env[provider.apiKeyEnvVar];
|
|
107
|
+
if (!apiKey) {
|
|
108
|
+
throw new Error(`${provider.apiKeyEnvVar} not set in environment`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Build prompt with context
|
|
112
|
+
const parts: string[] = [];
|
|
113
|
+
|
|
114
|
+
if (options.context) {
|
|
115
|
+
parts.push(options.context);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (options.files && options.files.length > 0) {
|
|
119
|
+
const fileContents = readFiles(options.files);
|
|
120
|
+
if (Object.keys(fileContents).length > 0) {
|
|
121
|
+
const fileContext = Object.entries(fileContents)
|
|
122
|
+
.map(([path, content]) => `### ${path}\n\`\`\`\n${content}\n\`\`\``)
|
|
123
|
+
.join('\n\n');
|
|
124
|
+
parts.push(fileContext);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
parts.push(query);
|
|
129
|
+
const prompt = parts.join('\n\n');
|
|
130
|
+
|
|
131
|
+
const model = options.model ?? provider.defaultModel;
|
|
132
|
+
const timeout = options.timeout ?? DEFAULT_TIMEOUT;
|
|
133
|
+
|
|
134
|
+
const start = performance.now();
|
|
135
|
+
const result = await callProvider(providerName, apiKey, prompt, model, timeout);
|
|
136
|
+
const durationMs = Math.round(performance.now() - start);
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
text: result.text,
|
|
140
|
+
model: result.model,
|
|
141
|
+
provider: providerName,
|
|
142
|
+
durationMs,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ============================================================================
|
|
147
|
+
// Provider Implementations
|
|
148
|
+
// ============================================================================
|
|
149
|
+
|
|
150
|
+
interface ProviderResult {
|
|
151
|
+
text: string;
|
|
152
|
+
model: string;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async function callProvider(
|
|
156
|
+
provider: ProviderName,
|
|
157
|
+
apiKey: string,
|
|
158
|
+
prompt: string,
|
|
159
|
+
model: string,
|
|
160
|
+
timeout: number
|
|
161
|
+
): Promise<ProviderResult> {
|
|
162
|
+
switch (provider) {
|
|
163
|
+
case 'gemini':
|
|
164
|
+
return callGemini(apiKey, prompt, model, timeout);
|
|
165
|
+
case 'openai':
|
|
166
|
+
return callOpenAI(apiKey, prompt, model, timeout);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Call Gemini API using the official @google/genai SDK.
|
|
172
|
+
* Uses API key authentication (Gemini Developer API, not Vertex AI).
|
|
173
|
+
*/
|
|
174
|
+
async function callGemini(
|
|
175
|
+
apiKey: string,
|
|
176
|
+
prompt: string,
|
|
177
|
+
model: string,
|
|
178
|
+
timeout: number
|
|
179
|
+
): Promise<ProviderResult> {
|
|
180
|
+
// Initialize with API key only - this uses Gemini Developer API, not Vertex AI
|
|
181
|
+
const ai = new GoogleGenAI({ apiKey });
|
|
182
|
+
|
|
183
|
+
// Create abort controller for timeout
|
|
184
|
+
const controller = new AbortController();
|
|
185
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
const response = await ai.models.generateContent({
|
|
189
|
+
model,
|
|
190
|
+
contents: prompt,
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
const text = response.text ?? '';
|
|
194
|
+
return { text, model };
|
|
195
|
+
} finally {
|
|
196
|
+
clearTimeout(timeoutId);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async function callOpenAI(
|
|
201
|
+
apiKey: string,
|
|
202
|
+
prompt: string,
|
|
203
|
+
model: string,
|
|
204
|
+
timeout: number
|
|
205
|
+
): Promise<ProviderResult> {
|
|
206
|
+
const controller = new AbortController();
|
|
207
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
208
|
+
|
|
209
|
+
try {
|
|
210
|
+
const response = await fetch('https://api.openai.com/v1/chat/completions', {
|
|
211
|
+
method: 'POST',
|
|
212
|
+
headers: {
|
|
213
|
+
Authorization: `Bearer ${apiKey}`,
|
|
214
|
+
'Content-Type': 'application/json',
|
|
215
|
+
},
|
|
216
|
+
body: JSON.stringify({
|
|
217
|
+
model,
|
|
218
|
+
messages: [{ role: 'user', content: prompt }],
|
|
219
|
+
}),
|
|
220
|
+
signal: controller.signal,
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
if (!response.ok) {
|
|
224
|
+
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const data = (await response.json()) as {
|
|
228
|
+
choices?: Array<{ message?: { content?: string } }>;
|
|
229
|
+
model?: string;
|
|
230
|
+
};
|
|
231
|
+
const text = data.choices?.[0]?.message?.content ?? '';
|
|
232
|
+
|
|
233
|
+
return { text, model: data.model ?? model };
|
|
234
|
+
} finally {
|
|
235
|
+
clearTimeout(timeoutId);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// ============================================================================
|
|
240
|
+
// Utilities
|
|
241
|
+
// ============================================================================
|
|
242
|
+
|
|
243
|
+
function readFiles(paths: string[]): Record<string, string> {
|
|
244
|
+
const result: Record<string, string> = {};
|
|
245
|
+
for (const path of paths) {
|
|
246
|
+
if (existsSync(path)) {
|
|
247
|
+
try {
|
|
248
|
+
result[path] = readFileSync(path, 'utf-8');
|
|
249
|
+
} catch {
|
|
250
|
+
// Skip unreadable files
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return result;
|
|
255
|
+
}
|
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Client - Connects to session daemon for stateful servers.
|
|
3
|
+
*
|
|
4
|
+
* For stateful servers (playwright, xcodebuild, etc.), connects to a
|
|
5
|
+
* per-agent daemon that manages persistent MCP sessions.
|
|
6
|
+
*
|
|
7
|
+
* For stateless servers, uses direct one-shot connections.
|
|
8
|
+
*
|
|
9
|
+
* Daemon socket path: .allhands/harness/.cache/sessions/{AGENT_ID}.sock
|
|
10
|
+
*
|
|
11
|
+
* Session Lifecycle (stateful servers):
|
|
12
|
+
* - Sessions are auto-started on first tool call.
|
|
13
|
+
* - Sessions timeout after inactivity (configurable per-server).
|
|
14
|
+
* - When all sessions timeout, daemon exits.
|
|
15
|
+
* - Use --restart flag to recover from bad state.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { connect, type Socket } from 'net';
|
|
19
|
+
import { spawn } from 'child_process';
|
|
20
|
+
import { existsSync, readFileSync, mkdirSync } from 'fs';
|
|
21
|
+
import { dirname, join } from 'path';
|
|
22
|
+
import { fileURLToPath } from 'url';
|
|
23
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
24
|
+
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
25
|
+
import type { McpServerConfig, McpToolSchema } from './mcp-runtime.js';
|
|
26
|
+
import { resolveEnvVars } from './mcp-runtime.js';
|
|
27
|
+
|
|
28
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
29
|
+
// Path: harness/src/lib/ -> harness/src/ -> harness/
|
|
30
|
+
const HARNESS_ROOT = join(__dirname, '..', '..');
|
|
31
|
+
const SESSIONS_DIR = join(HARNESS_ROOT, '.cache', 'sessions');
|
|
32
|
+
const DAEMON_SCRIPT = join(__dirname, 'mcp-daemon.ts');
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Get the agent ID from environment.
|
|
36
|
+
*/
|
|
37
|
+
export function getAgentId(): string {
|
|
38
|
+
return process.env.AGENT_ID ?? 'default';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Get the socket path for an agent.
|
|
43
|
+
*/
|
|
44
|
+
function getSocketPath(agentId: string): string {
|
|
45
|
+
return join(SESSIONS_DIR, `${agentId}.sock`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Get the PID file path for an agent.
|
|
50
|
+
*/
|
|
51
|
+
function getPidPath(agentId: string): string {
|
|
52
|
+
return join(SESSIONS_DIR, `${agentId}.pid`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Check if the daemon is running for an agent.
|
|
57
|
+
*/
|
|
58
|
+
export function isDaemonRunning(agentId?: string): boolean {
|
|
59
|
+
const aid = agentId ?? getAgentId();
|
|
60
|
+
const pidPath = getPidPath(aid);
|
|
61
|
+
const socketPath = getSocketPath(aid);
|
|
62
|
+
|
|
63
|
+
if (!existsSync(pidPath) || !existsSync(socketPath)) {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
const pid = parseInt(readFileSync(pidPath, 'utf-8').trim(), 10);
|
|
69
|
+
// Check if process is running
|
|
70
|
+
process.kill(pid, 0);
|
|
71
|
+
return true;
|
|
72
|
+
} catch {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Sleep helper for async waiting.
|
|
79
|
+
*/
|
|
80
|
+
function sleep(ms: number): Promise<void> {
|
|
81
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Start the daemon for an agent (if not already running).
|
|
86
|
+
*/
|
|
87
|
+
export async function startDaemon(agentId?: string): Promise<void> {
|
|
88
|
+
const aid = agentId ?? getAgentId();
|
|
89
|
+
|
|
90
|
+
if (isDaemonRunning(aid)) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Ensure sessions directory exists
|
|
95
|
+
if (!existsSync(SESSIONS_DIR)) {
|
|
96
|
+
mkdirSync(SESSIONS_DIR, { recursive: true });
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Spawn daemon as detached process
|
|
100
|
+
const child = spawn('npx', ['tsx', DAEMON_SCRIPT, aid], {
|
|
101
|
+
cwd: HARNESS_ROOT,
|
|
102
|
+
detached: true,
|
|
103
|
+
stdio: 'ignore',
|
|
104
|
+
env: { ...process.env, AGENT_ID: aid },
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
child.unref();
|
|
108
|
+
|
|
109
|
+
// Wait for socket to be created (with timeout)
|
|
110
|
+
const socketPath = getSocketPath(aid);
|
|
111
|
+
const startTime = Date.now();
|
|
112
|
+
const timeout = 10000; // 10 seconds
|
|
113
|
+
|
|
114
|
+
while (!existsSync(socketPath)) {
|
|
115
|
+
if (Date.now() - startTime > timeout) {
|
|
116
|
+
throw new Error(`Daemon failed to start for agent ${aid}`);
|
|
117
|
+
}
|
|
118
|
+
await sleep(100);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Send a command to the daemon and get response.
|
|
124
|
+
*/
|
|
125
|
+
async function sendToDaemon<T>(agentId: string, command: unknown, timeoutMs = 30000): Promise<T> {
|
|
126
|
+
return new Promise((resolve, reject) => {
|
|
127
|
+
const socketPath = getSocketPath(agentId);
|
|
128
|
+
|
|
129
|
+
if (!existsSync(socketPath)) {
|
|
130
|
+
reject(new Error(`Daemon not running for agent ${agentId}`));
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const socket: Socket = connect(socketPath);
|
|
135
|
+
let buffer = '';
|
|
136
|
+
|
|
137
|
+
socket.on('connect', () => {
|
|
138
|
+
socket.write(JSON.stringify(command) + '\n');
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
socket.on('data', (data) => {
|
|
142
|
+
buffer += data.toString();
|
|
143
|
+
const newlineIdx = buffer.indexOf('\n');
|
|
144
|
+
if (newlineIdx !== -1) {
|
|
145
|
+
const response = buffer.slice(0, newlineIdx);
|
|
146
|
+
socket.end();
|
|
147
|
+
try {
|
|
148
|
+
resolve(JSON.parse(response) as T);
|
|
149
|
+
} catch (e) {
|
|
150
|
+
reject(new Error(`Invalid response from daemon: ${response}`));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
socket.on('error', (err) => {
|
|
156
|
+
reject(err);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
socket.on('timeout', () => {
|
|
160
|
+
socket.destroy();
|
|
161
|
+
reject(new Error('Daemon connection timeout'));
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
socket.setTimeout(timeoutMs);
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Create and connect a new MCP client for a one-shot call.
|
|
170
|
+
*/
|
|
171
|
+
async function createOneShot(config: McpServerConfig): Promise<{ client: Client; transport: StdioClientTransport }> {
|
|
172
|
+
if (!config.command) {
|
|
173
|
+
throw new Error(`Server ${config.name} requires 'command' for stdio transport`);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const env = resolveEnvVars(config.env);
|
|
177
|
+
|
|
178
|
+
const transport = new StdioClientTransport({
|
|
179
|
+
command: config.command,
|
|
180
|
+
args: config.args,
|
|
181
|
+
env: { ...process.env, ...env } as Record<string, string>,
|
|
182
|
+
stderr: 'pipe',
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
const client = new Client(
|
|
186
|
+
{ name: 'allhands-cli', version: '0.1.0' },
|
|
187
|
+
{ capabilities: {} }
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
await client.connect(transport);
|
|
191
|
+
|
|
192
|
+
return { client, transport };
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ============================================================================
|
|
196
|
+
// Public API
|
|
197
|
+
// ============================================================================
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Restart a server session (for recovery from bad state).
|
|
201
|
+
*/
|
|
202
|
+
export async function restartServer(
|
|
203
|
+
config: McpServerConfig,
|
|
204
|
+
agentId?: string
|
|
205
|
+
): Promise<{ success: boolean; pid?: number; error?: string }> {
|
|
206
|
+
const aid = agentId ?? getAgentId();
|
|
207
|
+
|
|
208
|
+
// Ensure daemon is running
|
|
209
|
+
await startDaemon(aid);
|
|
210
|
+
|
|
211
|
+
return sendToDaemon(aid, {
|
|
212
|
+
cmd: 'restart',
|
|
213
|
+
server: config.name,
|
|
214
|
+
config,
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* List all active sessions.
|
|
220
|
+
*/
|
|
221
|
+
export async function listSessions(agentId?: string): Promise<Array<{
|
|
222
|
+
serverName: string;
|
|
223
|
+
startedAt: Date;
|
|
224
|
+
lastUsedAt: Date;
|
|
225
|
+
timeoutMs: number;
|
|
226
|
+
pid?: number;
|
|
227
|
+
}>> {
|
|
228
|
+
const aid = agentId ?? getAgentId();
|
|
229
|
+
|
|
230
|
+
if (!isDaemonRunning(aid)) {
|
|
231
|
+
return [];
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const result = await sendToDaemon<{
|
|
235
|
+
success: boolean;
|
|
236
|
+
sessions: Array<{ server: string; startedAt: string; lastUsedAt: string; timeoutMs: number; pid?: number }>;
|
|
237
|
+
}>(aid, { cmd: 'list' });
|
|
238
|
+
|
|
239
|
+
return result.sessions.map((s) => ({
|
|
240
|
+
serverName: s.server,
|
|
241
|
+
startedAt: new Date(s.startedAt),
|
|
242
|
+
lastUsedAt: new Date(s.lastUsedAt),
|
|
243
|
+
timeoutMs: s.timeoutMs,
|
|
244
|
+
pid: s.pid,
|
|
245
|
+
}));
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Discover tools from an MCP server.
|
|
250
|
+
*
|
|
251
|
+
* For stateful servers, uses daemon (auto-starts session).
|
|
252
|
+
* For stateless servers, uses one-shot connection.
|
|
253
|
+
*/
|
|
254
|
+
export async function discoverTools(
|
|
255
|
+
config: McpServerConfig,
|
|
256
|
+
agentId?: string
|
|
257
|
+
): Promise<McpToolSchema[]> {
|
|
258
|
+
const aid = agentId ?? getAgentId();
|
|
259
|
+
|
|
260
|
+
if (config.stateful) {
|
|
261
|
+
// Ensure daemon is running, then discover via daemon
|
|
262
|
+
await startDaemon(aid);
|
|
263
|
+
|
|
264
|
+
const result = await sendToDaemon<{
|
|
265
|
+
success: boolean;
|
|
266
|
+
tools?: McpToolSchema[];
|
|
267
|
+
error?: string;
|
|
268
|
+
}>(aid, {
|
|
269
|
+
cmd: 'discover',
|
|
270
|
+
server: config.name,
|
|
271
|
+
config,
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
if (!result.success) {
|
|
275
|
+
throw new Error(result.error ?? 'Failed to discover tools');
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return result.tools ?? [];
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Stateless: one-shot discovery
|
|
282
|
+
const { client, transport } = await createOneShot(config);
|
|
283
|
+
|
|
284
|
+
try {
|
|
285
|
+
const result = await client.listTools();
|
|
286
|
+
const tools = result.tools.map((t) => ({
|
|
287
|
+
name: t.name,
|
|
288
|
+
description: t.description,
|
|
289
|
+
inputSchema: t.inputSchema as McpToolSchema['inputSchema'],
|
|
290
|
+
}));
|
|
291
|
+
|
|
292
|
+
return config.hiddenTools?.length
|
|
293
|
+
? tools.filter((t) => !config.hiddenTools!.includes(t.name))
|
|
294
|
+
: tools;
|
|
295
|
+
} finally {
|
|
296
|
+
await transport.close();
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Call a tool on an MCP server.
|
|
302
|
+
*
|
|
303
|
+
* For stateful servers, uses daemon (auto-starts session).
|
|
304
|
+
* For stateless servers, uses one-shot connection.
|
|
305
|
+
*/
|
|
306
|
+
export async function callTool(
|
|
307
|
+
config: McpServerConfig,
|
|
308
|
+
toolName: string,
|
|
309
|
+
params: Record<string, unknown>,
|
|
310
|
+
agentId?: string
|
|
311
|
+
): Promise<unknown> {
|
|
312
|
+
const aid = agentId ?? getAgentId();
|
|
313
|
+
|
|
314
|
+
if (config.stateful) {
|
|
315
|
+
// Ensure daemon is running, then call via daemon
|
|
316
|
+
await startDaemon(aid);
|
|
317
|
+
|
|
318
|
+
const callTimeout = config.stateful_session_timeout ?? 60000;
|
|
319
|
+
const result = await sendToDaemon<{
|
|
320
|
+
success: boolean;
|
|
321
|
+
result?: unknown;
|
|
322
|
+
error?: string;
|
|
323
|
+
}>(aid, {
|
|
324
|
+
cmd: 'call',
|
|
325
|
+
server: config.name,
|
|
326
|
+
tool: toolName,
|
|
327
|
+
params,
|
|
328
|
+
config,
|
|
329
|
+
}, callTimeout);
|
|
330
|
+
|
|
331
|
+
if (!result.success) {
|
|
332
|
+
throw new Error(result.error ?? 'Tool call failed');
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return result.result;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Stateless: one-shot connection
|
|
339
|
+
const { client, transport } = await createOneShot(config);
|
|
340
|
+
|
|
341
|
+
try {
|
|
342
|
+
const result = await client.callTool({ name: toolName, arguments: params });
|
|
343
|
+
|
|
344
|
+
if ('content' in result && Array.isArray(result.content)) {
|
|
345
|
+
const textContent = result.content.find((c) => c.type === 'text');
|
|
346
|
+
if (textContent && 'text' in textContent) {
|
|
347
|
+
try {
|
|
348
|
+
return JSON.parse(textContent.text);
|
|
349
|
+
} catch {
|
|
350
|
+
return textContent.text;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
return result.content;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return result;
|
|
357
|
+
} finally {
|
|
358
|
+
await transport.close();
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Shutdown the daemon for an agent.
|
|
364
|
+
* Called by tmux cleanup or manual shutdown.
|
|
365
|
+
*/
|
|
366
|
+
export async function shutdownDaemon(agentId?: string): Promise<void> {
|
|
367
|
+
const aid = agentId ?? getAgentId();
|
|
368
|
+
|
|
369
|
+
if (!isDaemonRunning(aid)) {
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
try {
|
|
374
|
+
await sendToDaemon(aid, { cmd: 'shutdown' });
|
|
375
|
+
} catch {
|
|
376
|
+
// Daemon may have already exited
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Get daemon info.
|
|
382
|
+
*/
|
|
383
|
+
export async function getDaemonInfo(agentId?: string): Promise<{
|
|
384
|
+
running: boolean;
|
|
385
|
+
pid?: number;
|
|
386
|
+
socketPath?: string;
|
|
387
|
+
sessionCount?: number;
|
|
388
|
+
sessions?: string[];
|
|
389
|
+
}> {
|
|
390
|
+
const aid = agentId ?? getAgentId();
|
|
391
|
+
const pidPath = getPidPath(aid);
|
|
392
|
+
const socketPath = getSocketPath(aid);
|
|
393
|
+
|
|
394
|
+
if (!isDaemonRunning(aid)) {
|
|
395
|
+
return { running: false };
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
try {
|
|
399
|
+
const pid = parseInt(readFileSync(pidPath, 'utf-8').trim(), 10);
|
|
400
|
+
|
|
401
|
+
// Get live info from daemon
|
|
402
|
+
const info = await sendToDaemon<{
|
|
403
|
+
success: boolean;
|
|
404
|
+
pid: number;
|
|
405
|
+
sessionCount: number;
|
|
406
|
+
sessions: string[];
|
|
407
|
+
}>(aid, { cmd: 'info' });
|
|
408
|
+
|
|
409
|
+
return {
|
|
410
|
+
running: true,
|
|
411
|
+
pid,
|
|
412
|
+
socketPath,
|
|
413
|
+
sessionCount: info.sessionCount,
|
|
414
|
+
sessions: info.sessions,
|
|
415
|
+
};
|
|
416
|
+
} catch {
|
|
417
|
+
return { running: false };
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Send ping to daemon to keep sessions alive.
|
|
423
|
+
*/
|
|
424
|
+
export async function pingDaemon(agentId?: string): Promise<{ success: boolean; sessionsRefreshed: number }> {
|
|
425
|
+
const aid = agentId ?? getAgentId();
|
|
426
|
+
|
|
427
|
+
if (!isDaemonRunning(aid)) {
|
|
428
|
+
return { success: false, sessionsRefreshed: 0 };
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
return sendToDaemon(aid, { cmd: 'ping' });
|
|
432
|
+
}
|