peaks-loop 4.1.2 → 4.1.3
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/.claude-plugin/marketplace.json +136 -47
- package/CHANGELOG.md +56 -0
- package/README-en.md +1 -1
- package/README.md +1 -1
- package/contracts/readonly-argv-whitelist.json +211 -0
- package/dist/cli/commands/audit-artifact-runners.d.ts +16 -0
- package/dist/cli/commands/audit-artifact-runners.js +170 -0
- package/dist/cli/commands/audit-command-shared.d.ts +97 -0
- package/dist/cli/commands/audit-command-shared.js +60 -0
- package/dist/cli/commands/audit-commands.d.ts +7 -31
- package/dist/cli/commands/audit-commands.js +64 -473
- package/dist/cli/commands/audit-goal-runners.d.ts +17 -0
- package/dist/cli/commands/audit-goal-runners.js +114 -0
- package/dist/cli/commands/audit-red-lines-runners.d.ts +11 -0
- package/dist/cli/commands/audit-red-lines-runners.js +43 -0
- package/dist/cli/commands/audit-refusal-data.d.ts +23 -0
- package/dist/cli/commands/audit-refusal-data.js +69 -0
- package/dist/cli/commands/audit-static-runners.d.ts +13 -0
- package/dist/cli/commands/audit-static-runners.js +139 -0
- package/dist/cli/commands/await-runners.d.ts +11 -0
- package/dist/cli/commands/await-runners.js +148 -0
- package/dist/cli/commands/code-auto-compact-command.d.ts +3 -0
- package/dist/cli/commands/code-auto-compact-command.js +124 -0
- package/dist/cli/commands/code-auto-compact-envelope.d.ts +22 -0
- package/dist/cli/commands/code-auto-compact-envelope.js +39 -0
- package/dist/cli/commands/code-context-audit-command.d.ts +3 -0
- package/dist/cli/commands/code-context-audit-command.js +68 -0
- package/dist/cli/commands/code-context-now-command.d.ts +3 -0
- package/dist/cli/commands/code-context-now-command.js +124 -0
- package/dist/cli/commands/code-context-now-envelope.d.ts +66 -0
- package/dist/cli/commands/code-context-now-envelope.js +136 -0
- package/dist/cli/commands/code-context-now-mapping.d.ts +27 -0
- package/dist/cli/commands/code-context-now-mapping.js +43 -0
- package/dist/cli/commands/code-emit-handoff-command.d.ts +3 -0
- package/dist/cli/commands/code-emit-handoff-command.js +99 -0
- package/dist/cli/commands/code-gate-step-08-command.d.ts +3 -0
- package/dist/cli/commands/code-gate-step-08-command.js +139 -0
- package/dist/cli/commands/code-post-compact-detect-command.d.ts +3 -0
- package/dist/cli/commands/code-post-compact-detect-command.js +50 -0
- package/dist/cli/commands/code-runtime-commands.d.ts +9 -43
- package/dist/cli/commands/code-runtime-commands.js +20 -672
- package/dist/cli/commands/code-runtime-session.d.ts +1 -0
- package/dist/cli/commands/code-runtime-session.js +15 -0
- package/dist/cli/commands/compact-command.d.ts +6 -2
- package/dist/cli/commands/compact-command.js +12 -559
- package/dist/cli/commands/compact-dry-run-command.d.ts +3 -0
- package/dist/cli/commands/compact-dry-run-command.js +87 -0
- package/dist/cli/commands/compact-force-command.d.ts +3 -0
- package/dist/cli/commands/compact-force-command.js +98 -0
- package/dist/cli/commands/compact-harness-window-command.d.ts +3 -0
- package/dist/cli/commands/compact-harness-window-command.js +174 -0
- package/dist/cli/commands/compact-history-command.d.ts +3 -0
- package/dist/cli/commands/compact-history-command.js +89 -0
- package/dist/cli/commands/compact-read-commands.d.ts +3 -0
- package/dist/cli/commands/compact-read-commands.js +123 -0
- package/dist/cli/commands/compact-settle-command.d.ts +3 -0
- package/dist/cli/commands/compact-settle-command.js +130 -0
- package/dist/cli/commands/compact-shared.d.ts +9 -0
- package/dist/cli/commands/compact-shared.js +26 -0
- package/dist/cli/commands/core/skill-command.d.ts +1 -44
- package/dist/cli/commands/core/skill-command.js +13 -541
- package/dist/cli/commands/core/skill-context-verdict.d.ts +38 -0
- package/dist/cli/commands/core/skill-context-verdict.js +63 -0
- package/dist/cli/commands/core/skill-generated-config-notice.d.ts +41 -0
- package/dist/cli/commands/core/skill-generated-config-notice.js +75 -0
- package/dist/cli/commands/core/skill-heartbeat-commands.d.ts +3 -0
- package/dist/cli/commands/core/skill-heartbeat-commands.js +50 -0
- package/dist/cli/commands/core/skill-presence-commands.d.ts +3 -0
- package/dist/cli/commands/core/skill-presence-commands.js +123 -0
- package/dist/cli/commands/core/skill-presence-housekeeping-commands.d.ts +3 -0
- package/dist/cli/commands/core/skill-presence-housekeeping-commands.js +165 -0
- package/dist/cli/commands/core/skill-registry-commands.d.ts +3 -0
- package/dist/cli/commands/core/skill-registry-commands.js +117 -0
- package/dist/cli/commands/core/skill-runbook-command.d.ts +3 -0
- package/dist/cli/commands/core/skill-runbook-command.js +29 -0
- package/dist/cli/commands/dispatch-command-spec.d.ts +2 -0
- package/dist/cli/commands/dispatch-command-spec.js +62 -0
- package/dist/cli/commands/dispatch-commands.js +159 -775
- package/dist/cli/commands/dispatch-detached-branch.d.ts +3 -0
- package/dist/cli/commands/dispatch-detached-branch.js +58 -0
- package/dist/cli/commands/dispatch-envelope.d.ts +36 -0
- package/dist/cli/commands/dispatch-envelope.js +104 -0
- package/dist/cli/commands/dispatch-isolation-lease.d.ts +17 -0
- package/dist/cli/commands/dispatch-isolation-lease.js +149 -0
- package/dist/cli/commands/dispatch-preflight.d.ts +7 -0
- package/dist/cli/commands/dispatch-preflight.js +105 -0
- package/dist/cli/commands/dispatch-prompt-blocks.d.ts +10 -0
- package/dist/cli/commands/dispatch-prompt-blocks.js +56 -0
- package/dist/cli/commands/dispatch-prompt-composition.d.ts +30 -0
- package/dist/cli/commands/dispatch-prompt-composition.js +171 -0
- package/dist/cli/commands/dispatch-record-write.d.ts +24 -0
- package/dist/cli/commands/dispatch-record-write.js +96 -0
- package/dist/cli/commands/dispatch-tool-call.d.ts +15 -0
- package/dist/cli/commands/dispatch-tool-call.js +128 -0
- package/dist/cli/commands/evolution-command-shared.d.ts +78 -0
- package/dist/cli/commands/evolution-command-shared.js +107 -0
- package/dist/cli/commands/evolution-commands.d.ts +11 -1
- package/dist/cli/commands/evolution-commands.js +19 -350
- package/dist/cli/commands/evolution-evaluate-command.d.ts +24 -0
- package/dist/cli/commands/evolution-evaluate-command.js +138 -0
- package/dist/cli/commands/evolution-propose-command.d.ts +23 -0
- package/dist/cli/commands/evolution-propose-command.js +145 -0
- package/dist/cli/commands/evolution-status-command.d.ts +17 -0
- package/dist/cli/commands/evolution-status-command.js +41 -0
- package/dist/cli/commands/evolution-verdict-commands.d.ts +30 -0
- package/dist/cli/commands/evolution-verdict-commands.js +100 -0
- package/dist/cli/commands/finalize-accumulator.d.ts +44 -0
- package/dist/cli/commands/finalize-accumulator.js +55 -0
- package/dist/cli/commands/finalize-branches.d.ts +34 -0
- package/dist/cli/commands/finalize-branches.js +119 -0
- package/dist/cli/commands/finalize-runners.d.ts +3 -0
- package/dist/cli/commands/finalize-runners.js +92 -0
- package/dist/cli/commands/finalize-selection.d.ts +65 -0
- package/dist/cli/commands/finalize-selection.js +35 -0
- package/dist/cli/commands/gate-commands.js +26 -8
- package/dist/cli/commands/job-checkpoint-commands.d.ts +3 -0
- package/dist/cli/commands/job-checkpoint-commands.js +157 -0
- package/dist/cli/commands/job-command-shared.d.ts +109 -0
- package/dist/cli/commands/job-command-shared.js +72 -0
- package/dist/cli/commands/job-commands.d.ts +4 -64
- package/dist/cli/commands/job-commands.js +42 -552
- package/dist/cli/commands/job-cost-check-command.d.ts +3 -0
- package/dist/cli/commands/job-cost-check-command.js +41 -0
- package/dist/cli/commands/job-init-status-commands.d.ts +3 -0
- package/dist/cli/commands/job-init-status-commands.js +149 -0
- package/dist/cli/commands/job-run-commands.d.ts +3 -0
- package/dist/cli/commands/job-run-commands.js +97 -0
- package/dist/cli/commands/job-state-root.d.ts +35 -0
- package/dist/cli/commands/job-state-root.js +76 -0
- package/dist/cli/commands/loop-eval-capture-index.d.ts +4 -0
- package/dist/cli/commands/loop-eval-capture-index.js +44 -0
- package/dist/cli/commands/loop-eval-commands.d.ts +8 -5
- package/dist/cli/commands/loop-eval-commands.js +12 -626
- package/dist/cli/commands/loop-eval-eval-command.d.ts +3 -0
- package/dist/cli/commands/loop-eval-eval-command.js +168 -0
- package/dist/cli/commands/loop-eval-monotonic-command.d.ts +3 -0
- package/dist/cli/commands/loop-eval-monotonic-command.js +90 -0
- package/dist/cli/commands/loop-eval-parents.d.ts +7 -0
- package/dist/cli/commands/loop-eval-parents.js +20 -0
- package/dist/cli/commands/loop-eval-run-code.d.ts +7 -0
- package/dist/cli/commands/loop-eval-run-code.js +40 -0
- package/dist/cli/commands/loop-eval-run-command.d.ts +3 -0
- package/dist/cli/commands/loop-eval-run-command.js +113 -0
- package/dist/cli/commands/loop-eval-spec-commands.d.ts +3 -0
- package/dist/cli/commands/loop-eval-spec-commands.js +165 -0
- package/dist/cli/commands/loop-eval-spec-lint-command.d.ts +3 -0
- package/dist/cli/commands/loop-eval-spec-lint-command.js +42 -0
- package/dist/cli/commands/loop-eval-workflow-commands.d.ts +3 -0
- package/dist/cli/commands/loop-eval-workflow-commands.js +141 -0
- package/dist/cli/commands/mcp-commands.d.ts +10 -0
- package/dist/cli/commands/mcp-commands.js +157 -0
- package/dist/cli/commands/request-command-options.d.ts +49 -0
- package/dist/cli/commands/request-command-options.js +1 -0
- package/dist/cli/commands/request-commands.d.ts +3 -9
- package/dist/cli/commands/request-commands.js +17 -522
- package/dist/cli/commands/request-init-command.d.ts +3 -0
- package/dist/cli/commands/request-init-command.js +100 -0
- package/dist/cli/commands/request-lint-command.d.ts +3 -0
- package/dist/cli/commands/request-lint-command.js +50 -0
- package/dist/cli/commands/request-list-command.d.ts +10 -0
- package/dist/cli/commands/request-list-command.js +53 -0
- package/dist/cli/commands/request-repair-status-command.d.ts +3 -0
- package/dist/cli/commands/request-repair-status-command.js +61 -0
- package/dist/cli/commands/request-show-command.d.ts +3 -0
- package/dist/cli/commands/request-show-command.js +56 -0
- package/dist/cli/commands/request-transition-command.d.ts +3 -0
- package/dist/cli/commands/request-transition-command.js +122 -0
- package/dist/cli/commands/request-transition-failures.d.ts +8 -0
- package/dist/cli/commands/request-transition-failures.js +90 -0
- package/dist/cli/commands/request-transition-hooks.d.ts +50 -0
- package/dist/cli/commands/request-transition-hooks.js +115 -0
- package/dist/cli/commands/request-transition-preflight.d.ts +10 -0
- package/dist/cli/commands/request-transition-preflight.js +107 -0
- package/dist/cli/commands/scan-command-shared.d.ts +96 -0
- package/dist/cli/commands/scan-command-shared.js +83 -0
- package/dist/cli/commands/scan-commands.d.ts +3 -2
- package/dist/cli/commands/scan-commands.js +23 -463
- package/dist/cli/commands/scan-inventory-commands.d.ts +3 -0
- package/dist/cli/commands/scan-inventory-commands.js +96 -0
- package/dist/cli/commands/scan-project-commands.d.ts +3 -0
- package/dist/cli/commands/scan-project-commands.js +65 -0
- package/dist/cli/commands/scan-scope-commands.d.ts +3 -0
- package/dist/cli/commands/scan-scope-commands.js +149 -0
- package/dist/cli/commands/scan-scope-helpers.d.ts +8 -0
- package/dist/cli/commands/scan-scope-helpers.js +54 -0
- package/dist/cli/commands/scan-structural-commands.d.ts +3 -0
- package/dist/cli/commands/scan-structural-commands.js +113 -0
- package/dist/cli/commands/share-commands.d.ts +12 -68
- package/dist/cli/commands/share-commands.js +47 -556
- package/dist/cli/commands/share-error-hints.d.ts +14 -0
- package/dist/cli/commands/share-error-hints.js +38 -0
- package/dist/cli/commands/share-identity.d.ts +13 -0
- package/dist/cli/commands/share-identity.js +22 -0
- package/dist/cli/commands/share-record-paths.d.ts +37 -0
- package/dist/cli/commands/share-record-paths.js +68 -0
- package/dist/cli/commands/share-runners.d.ts +3 -0
- package/dist/cli/commands/share-runners.js +158 -0
- package/dist/cli/commands/shared-read-runners.d.ts +3 -0
- package/dist/cli/commands/shared-read-runners.js +51 -0
- package/dist/cli/commands/slice-check-command.d.ts +3 -0
- package/dist/cli/commands/slice-check-command.js +54 -0
- package/dist/cli/commands/slice-commands.d.ts +2 -22
- package/dist/cli/commands/slice-commands.js +18 -517
- package/dist/cli/commands/slice-decompose-artifacts.d.ts +18 -0
- package/dist/cli/commands/slice-decompose-artifacts.js +79 -0
- package/dist/cli/commands/slice-decompose-command.d.ts +3 -0
- package/dist/cli/commands/slice-decompose-command.js +123 -0
- package/dist/cli/commands/slice-list-commands.d.ts +3 -0
- package/dist/cli/commands/slice-list-commands.js +182 -0
- package/dist/cli/commands/slice-pick-command.d.ts +3 -0
- package/dist/cli/commands/slice-pick-command.js +60 -0
- package/dist/cli/commands/slice-plan-command.d.ts +24 -0
- package/dist/cli/commands/slice-plan-command.js +111 -0
- package/dist/cli/commands/workflow-commands.d.ts +10 -1
- package/dist/cli/commands/workflow-commands.js +6 -594
- package/dist/cli/commands/workflow-gate-commands.d.ts +3 -0
- package/dist/cli/commands/workflow-gate-commands.js +116 -0
- package/dist/cli/commands/workflow-plan-helpers.d.ts +48 -0
- package/dist/cli/commands/workflow-plan-helpers.js +103 -0
- package/dist/cli/commands/workflow-planning-commands.d.ts +3 -0
- package/dist/cli/commands/workflow-planning-commands.js +56 -0
- package/dist/cli/commands/workflow-swarm-actions.d.ts +7 -0
- package/dist/cli/commands/workflow-swarm-actions.js +109 -0
- package/dist/cli/commands/workflow-swarm-commands.d.ts +3 -0
- package/dist/cli/commands/workflow-swarm-commands.js +138 -0
- package/dist/cli/commands/workflow-tech-actions.d.ts +10 -0
- package/dist/cli/commands/workflow-tech-actions.js +150 -0
- package/dist/cli/commands/workspace/init-actions.d.ts +10 -0
- package/dist/cli/commands/workspace/init-actions.js +199 -0
- package/dist/cli/commands/workspace/init-codegraph-stake.d.ts +10 -0
- package/dist/cli/commands/workspace/init-codegraph-stake.js +54 -0
- package/dist/cli/commands/workspace/init-command.d.ts +8 -124
- package/dist/cli/commands/workspace/init-command.js +59 -623
- package/dist/cli/commands/workspace/init-context.d.ts +58 -0
- package/dist/cli/commands/workspace/init-context.js +121 -0
- package/dist/cli/commands/workspace/init-envelope.d.ts +3 -0
- package/dist/cli/commands/workspace/init-envelope.js +34 -0
- package/dist/cli/commands/workspace/init-failures.d.ts +3 -0
- package/dist/cli/commands/workspace/init-failures.js +84 -0
- package/dist/cli/commands/workspace/init-hooks-decision.d.ts +24 -0
- package/dist/cli/commands/workspace/init-hooks-decision.js +189 -0
- package/dist/cli/commands/workspace/init-options.d.ts +102 -0
- package/dist/cli/commands/workspace/init-options.js +1 -0
- package/dist/cli/commands/worktree-lease-commands.d.ts +10 -6
- package/dist/cli/commands/worktree-lease-commands.js +42 -512
- package/dist/cli/commands/worktree-lease-gc-command.d.ts +12 -0
- package/dist/cli/commands/worktree-lease-gc-command.js +117 -0
- package/dist/cli/commands/worktree-lease-git.d.ts +22 -0
- package/dist/cli/commands/worktree-lease-git.js +48 -0
- package/dist/cli/commands/worktree-lease-list-command.d.ts +12 -0
- package/dist/cli/commands/worktree-lease-list-command.js +100 -0
- package/dist/cli/commands/worktree-lease-load.d.ts +37 -0
- package/dist/cli/commands/worktree-lease-load.js +28 -0
- package/dist/cli/commands/worktree-lease-release-command.d.ts +13 -0
- package/dist/cli/commands/worktree-lease-release-command.js +105 -0
- package/dist/cli/commands/worktree-lease-renew-command.d.ts +11 -0
- package/dist/cli/commands/worktree-lease-renew-command.js +99 -0
- package/dist/cli/commands/worktree-lease-session.d.ts +22 -0
- package/dist/cli/commands/worktree-lease-session.js +29 -0
- package/dist/cli/commands/worktree-lease-spawn-command.d.ts +11 -0
- package/dist/cli/commands/worktree-lease-spawn-command.js +119 -0
- package/dist/cli/commands/worktree-lease-status-command.d.ts +11 -0
- package/dist/cli/commands/worktree-lease-status-command.js +86 -0
- package/dist/cli/commands/worktree-lease-store.d.ts +19 -0
- package/dist/cli/commands/worktree-lease-store.js +24 -0
- package/dist/cli/program.js +5 -0
- package/dist/services/distribution/mcp-install-runner.d.ts +21 -0
- package/dist/services/distribution/mcp-install-runner.js +59 -0
- package/dist/services/distribution/mcp-install.d.ts +95 -0
- package/dist/services/distribution/mcp-install.js +179 -0
- package/dist/services/distribution/plugin-manifest.d.ts +97 -0
- package/dist/services/distribution/plugin-manifest.js +126 -0
- package/dist/services/hooks/mcp-surface-gate.d.ts +19 -0
- package/dist/services/hooks/mcp-surface-gate.js +87 -0
- package/dist/services/ide/adapters/claude-code-adapter.d.ts +4 -35
- package/dist/services/ide/adapters/claude-code-adapter.js +48 -171
- package/dist/services/ide/adapters/claude-code-local-state.d.ts +28 -0
- package/dist/services/ide/adapters/claude-code-local-state.js +112 -0
- package/dist/services/ide/adapters/claude-code-model-window.d.ts +37 -0
- package/dist/services/ide/adapters/claude-code-model-window.js +113 -0
- package/dist/services/ide/ide-mcp-install-types.d.ts +78 -0
- package/dist/services/ide/ide-mcp-install-types.js +104 -0
- package/dist/services/ide/ide-types.d.ts +45 -1
- package/dist/services/ide/mcp-tool-matcher.d.ts +34 -0
- package/dist/services/ide/mcp-tool-matcher.js +84 -0
- package/dist/services/lint/npx-resolver.js +7 -0
- package/dist/services/mcp/cli-executor.d.ts +36 -0
- package/dist/services/mcp/cli-executor.js +97 -0
- package/dist/services/mcp/json-rpc.d.ts +81 -0
- package/dist/services/mcp/json-rpc.js +116 -0
- package/dist/services/mcp/memory-tool.d.ts +3 -0
- package/dist/services/mcp/memory-tool.js +62 -0
- package/dist/services/mcp/server-main.d.ts +1 -0
- package/dist/services/mcp/server-main.js +66 -0
- package/dist/services/mcp/server.d.ts +22 -0
- package/dist/services/mcp/server.js +152 -0
- package/dist/services/mcp/surface.d.ts +62 -0
- package/dist/services/mcp/surface.js +198 -0
- package/dist/services/mcp/tool-core.d.ts +42 -0
- package/dist/services/mcp/tool-core.js +137 -0
- package/dist/services/mcp/tools.d.ts +13 -0
- package/dist/services/mcp/tools.js +58 -0
- package/dist/services/readonly-surface/argv-guard.d.ts +32 -0
- package/dist/services/readonly-surface/argv-guard.js +164 -0
- package/dist/services/readonly-surface/readonly-whitelist.d.ts +147 -0
- package/dist/services/readonly-surface/readonly-whitelist.js +107 -0
- package/dist/services/readonly-surface/whitelist-generator.d.ts +64 -0
- package/dist/services/readonly-surface/whitelist-generator.js +212 -0
- package/dist/services/skills/hooks-codegate-superpowers.d.ts +1 -0
- package/dist/services/skills/hooks-codegate-superpowers.js +26 -0
- package/dist/services/skills/hooks-settings-service.js +10 -2
- package/package.json +17 -6
- package/scripts/install-skills-bundles.mjs +233 -0
- package/scripts/install-skills-fs.mjs +148 -0
- package/scripts/install-skills-ide.mjs +279 -0
- package/scripts/install-skills-output-style-default.mjs +196 -0
- package/scripts/install-skills-output-styles.mjs +121 -0
- package/scripts/install-skills-report.mjs +42 -0
- package/scripts/install-skills-user-config.mjs +198 -0
- package/scripts/install-skills.mjs +36 -1066
- package/scripts/watch-tree.mjs +140 -0
- package/scripts/watch.mjs +4 -126
- package/skills/peaks-status/SKILL.md +31 -13
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { type ReadOnlyWhitelist, type ReadOnlyWhitelistEntry } from '../readonly-surface/readonly-whitelist.js';
|
|
2
|
+
/** One JSON Schema property, as far as this surface needs one. */
|
|
3
|
+
export interface JsonSchemaProperty {
|
|
4
|
+
readonly type: 'string' | 'integer';
|
|
5
|
+
readonly description?: string;
|
|
6
|
+
readonly pattern?: string;
|
|
7
|
+
readonly enum?: readonly string[];
|
|
8
|
+
readonly minimum?: number;
|
|
9
|
+
readonly maximum?: number;
|
|
10
|
+
readonly default?: number;
|
|
11
|
+
}
|
|
12
|
+
export interface JsonSchema {
|
|
13
|
+
readonly type: 'object';
|
|
14
|
+
readonly properties: Readonly<Record<string, JsonSchemaProperty>>;
|
|
15
|
+
readonly required: readonly string[];
|
|
16
|
+
/** The LLM must not send anything else; an unknown key is a refused call. */
|
|
17
|
+
readonly additionalProperties: false;
|
|
18
|
+
}
|
|
19
|
+
export interface McpToolDefinition {
|
|
20
|
+
readonly name: string;
|
|
21
|
+
readonly description: string;
|
|
22
|
+
readonly inputSchema: JsonSchema;
|
|
23
|
+
/** The whitelist entries this tool runs, in whitelist order. Kept off the wire. */
|
|
24
|
+
readonly entries: readonly ReadOnlyWhitelistEntry[];
|
|
25
|
+
}
|
|
26
|
+
/** Group the whitelist's entries into tools, preserving first-appearance order. */
|
|
27
|
+
export declare function buildToolDefinitions(whitelist: ReadOnlyWhitelist): readonly McpToolDefinition[];
|
|
28
|
+
/** Load the whitelist artifact and build the surface from it. */
|
|
29
|
+
export declare function loadToolDefinitions(): readonly McpToolDefinition[];
|
|
30
|
+
/** Why a surface was refused. Named so the server can print it and exit non-zero. */
|
|
31
|
+
export declare class McpSurfaceError extends Error {
|
|
32
|
+
constructor(message: string);
|
|
33
|
+
}
|
|
34
|
+
/** An entry's argv as a comparable template key: literals literal, placeholders `{name}`. */
|
|
35
|
+
export declare function argvTemplateOf(entry: ReadOnlyWhitelistEntry): string;
|
|
36
|
+
/**
|
|
37
|
+
* Refuse a whitelist artifact that is not internally coherent.
|
|
38
|
+
*
|
|
39
|
+
* WHO THIS IS FOR. The artifact is generated and CI re-generates it, so the
|
|
40
|
+
* shape below holds on a clean checkout. The failure it exists for is the one
|
|
41
|
+
* that leaves no other trace: the file is edited by hand, or an entry is
|
|
42
|
+
* half-written, and a server starts up happily serving a surface nothing proved.
|
|
43
|
+
* Every rule here is a property the generator establishes and a hand-edit
|
|
44
|
+
* breaks, so a violated rule means "this is not the generated artifact".
|
|
45
|
+
*/
|
|
46
|
+
export declare function validateWhitelist(whitelist: ReadOnlyWhitelist): void;
|
|
47
|
+
/**
|
|
48
|
+
* L4 (spec §6): refuse to start when the tool surface and the proven whitelist do
|
|
49
|
+
* not describe the same thing.
|
|
50
|
+
*
|
|
51
|
+
* Two properties, each meant to fail loudly rather than degrade:
|
|
52
|
+
*
|
|
53
|
+
* 1. Every argv a tool can run is a whitelisted argv, by template. The surface
|
|
54
|
+
* is derived from the same artifact today, so on the production path this
|
|
55
|
+
* reads as "nothing unproven got in" — but it is the boundary that has to
|
|
56
|
+
* hold the moment a tool is authored rather than derived, and the test
|
|
57
|
+
* proves it can fail by handing it a surface that names an unproven argv.
|
|
58
|
+
* 2. Every whitelisted entry is reachable from some tool. This one DOES have a
|
|
59
|
+
* live trigger: an entry added to `readonly-surface.json` whose tool name is
|
|
60
|
+
* mistyped is proven read-only and then never served.
|
|
61
|
+
*/
|
|
62
|
+
export declare function validateSurface(whitelist: ReadOnlyWhitelist, tools: readonly McpToolDefinition[]): void;
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
// src/services/mcp/surface.ts
|
|
2
|
+
//
|
|
3
|
+
// The tool surface, DERIVED from the generated read-only whitelist rather than
|
|
4
|
+
// restated beside it.
|
|
5
|
+
//
|
|
6
|
+
// WHY DERIVED. "Every argv a tool can run is whitelisted" is only checkable if
|
|
7
|
+
// there is no second list to disagree with. So the tools ARE the whitelist's
|
|
8
|
+
// entries grouped by their `tool` field: adding a tool means editing
|
|
9
|
+
// `contracts/readonly-surface.json` and regenerating, and a tool whose argv was
|
|
10
|
+
// never proven read-only cannot be declared here at all.
|
|
11
|
+
//
|
|
12
|
+
// `validateSurface` is spec §6's L4 — the startup check. L1 ("the server only
|
|
13
|
+
// runs whitelisted argv") is a property of the code; L4 turns "someone put a
|
|
14
|
+
// write argv into the surface" from a silent hole into a refusal to start. Its
|
|
15
|
+
// unit is the ARGV, not the tool: a composite tool carries several, and checking
|
|
16
|
+
// per tool would wave through all but one of them.
|
|
17
|
+
//
|
|
18
|
+
// JSON SCHEMA, NOT COMMANDER. The parameter constraints below come from the
|
|
19
|
+
// whitelist's `params`, which the generator introspected out of the live
|
|
20
|
+
// Commander registry when it wrote the artifact (`constraintSource` marks the
|
|
21
|
+
// few that were hand-written). One build of latency is the price of not loading
|
|
22
|
+
// the whole CLI command registry into a read-only server process; the artifact
|
|
23
|
+
// is regenerated in the build and CI fails on a diff, so it cannot drift.
|
|
24
|
+
import { loadReadOnlyWhitelist } from '../readonly-surface/readonly-whitelist.js';
|
|
25
|
+
/**
|
|
26
|
+
* LLM-facing prose, per tool name. Every tool the whitelist declares must have
|
|
27
|
+
* an entry here or `validateSurface` refuses to start — spec §8.4 rule 2 asks
|
|
28
|
+
* the description to state the precondition, and a tool that silently has none
|
|
29
|
+
* is how an LLM ends up calling it outside a peaks project.
|
|
30
|
+
*/
|
|
31
|
+
const TOOL_DESCRIPTIONS = {
|
|
32
|
+
peaks_status: 'Where a Peaks-Loop workflow currently is. Read-only. Composes `peaks skill presence --json`, ' +
|
|
33
|
+
'`peaks session list --json` and, when both `rid` and `role` are given, `peaks request show ' +
|
|
34
|
+
'<rid> --role <role> --json --project <project>`. Each CLI result is returned verbatim under ' +
|
|
35
|
+
'its own key; a key is absent when that call was not run. Requires a Peaks-Loop project ' +
|
|
36
|
+
'(a `.peaks/` directory) as the working directory.',
|
|
37
|
+
peaks_memory_search: 'Search this project’s durable memory for a term, and get back NAMES and short excerpts — ' +
|
|
38
|
+
'never note bodies. Read-only. Use it to answer "has this project settled anything about X?" ' +
|
|
39
|
+
'Requires a Peaks-Loop project (a `.peaks/` directory) as the working directory.'
|
|
40
|
+
};
|
|
41
|
+
/** Per-parameter prose, by placeholder name. Absent names simply carry no description. */
|
|
42
|
+
const PARAM_DESCRIPTIONS = {
|
|
43
|
+
rid: 'The id of the request to read.',
|
|
44
|
+
role: 'Role whose artifact to read. One of the declared enum values.',
|
|
45
|
+
project: 'Absolute path of the Peaks-Loop project. Defaults to the server’s working directory.',
|
|
46
|
+
query: 'The term to search project memory for.',
|
|
47
|
+
limit: 'Maximum number of matching notes to return.'
|
|
48
|
+
};
|
|
49
|
+
const TYPE_SHAPES = {
|
|
50
|
+
slug: (spec) => spec.pattern === undefined ? { type: 'string' } : { type: 'string', pattern: spec.pattern },
|
|
51
|
+
enum: (spec) => ({ type: 'string', enum: spec.values ?? [] }),
|
|
52
|
+
integer: (spec) => ({
|
|
53
|
+
type: 'integer',
|
|
54
|
+
minimum: spec.min ?? 0,
|
|
55
|
+
maximum: spec.max ?? Number.MAX_SAFE_INTEGER,
|
|
56
|
+
...(spec.default === undefined ? {} : { default: spec.default })
|
|
57
|
+
}),
|
|
58
|
+
token: () => ({ type: 'string' }),
|
|
59
|
+
path: () => ({ type: 'string' })
|
|
60
|
+
};
|
|
61
|
+
function propertyFor(name, spec) {
|
|
62
|
+
const description = PARAM_DESCRIPTIONS[name];
|
|
63
|
+
const described = description === undefined ? {} : { description };
|
|
64
|
+
return { ...TYPE_SHAPES[spec.type](spec), ...described };
|
|
65
|
+
}
|
|
66
|
+
function inputSchemaOf(entries) {
|
|
67
|
+
const properties = {};
|
|
68
|
+
for (const entry of entries) {
|
|
69
|
+
for (const [name, spec] of Object.entries(entry.params)) {
|
|
70
|
+
properties[name] = propertyFor(name, spec);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// Nothing is `required` at the tool level. A tool whose entries all take no
|
|
74
|
+
// parameter is callable with `{}`, and a tool with several entries (three
|
|
75
|
+
// argv behind `peaks_status`) runs the subset whose parameters the caller
|
|
76
|
+
// actually supplied. Requiring the union would make the two parameterless
|
|
77
|
+
// argv unreachable.
|
|
78
|
+
return { type: 'object', properties, required: [], additionalProperties: false };
|
|
79
|
+
}
|
|
80
|
+
/** Group the whitelist's entries into tools, preserving first-appearance order. */
|
|
81
|
+
export function buildToolDefinitions(whitelist) {
|
|
82
|
+
const byTool = new Map();
|
|
83
|
+
for (const entry of whitelist.entries) {
|
|
84
|
+
const bucket = byTool.get(entry.tool);
|
|
85
|
+
if (bucket === undefined) {
|
|
86
|
+
byTool.set(entry.tool, [entry]);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
bucket.push(entry);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return [...byTool].map(([name, entries]) => ({
|
|
93
|
+
name,
|
|
94
|
+
description: TOOL_DESCRIPTIONS[name] ?? '',
|
|
95
|
+
inputSchema: inputSchemaOf(entries),
|
|
96
|
+
entries
|
|
97
|
+
}));
|
|
98
|
+
}
|
|
99
|
+
/** Load the whitelist artifact and build the surface from it. */
|
|
100
|
+
export function loadToolDefinitions() {
|
|
101
|
+
return buildToolDefinitions(loadReadOnlyWhitelist());
|
|
102
|
+
}
|
|
103
|
+
/** Why a surface was refused. Named so the server can print it and exit non-zero. */
|
|
104
|
+
export class McpSurfaceError extends Error {
|
|
105
|
+
constructor(message) {
|
|
106
|
+
super(message);
|
|
107
|
+
this.name = 'McpSurfaceError';
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/** An entry's argv as a comparable template key: literals literal, placeholders `{name}`. */
|
|
111
|
+
export function argvTemplateOf(entry) {
|
|
112
|
+
return entry.argv
|
|
113
|
+
.map((segment) => (segment.kind === 'literal' ? segment.value : `{${segment.name}}`))
|
|
114
|
+
.join(' ');
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Refuse a whitelist artifact that is not internally coherent.
|
|
118
|
+
*
|
|
119
|
+
* WHO THIS IS FOR. The artifact is generated and CI re-generates it, so the
|
|
120
|
+
* shape below holds on a clean checkout. The failure it exists for is the one
|
|
121
|
+
* that leaves no other trace: the file is edited by hand, or an entry is
|
|
122
|
+
* half-written, and a server starts up happily serving a surface nothing proved.
|
|
123
|
+
* Every rule here is a property the generator establishes and a hand-edit
|
|
124
|
+
* breaks, so a violated rule means "this is not the generated artifact".
|
|
125
|
+
*/
|
|
126
|
+
export function validateWhitelist(whitelist) {
|
|
127
|
+
const seenIds = new Set();
|
|
128
|
+
for (const entry of whitelist.entries) {
|
|
129
|
+
if (seenIds.has(entry.id)) {
|
|
130
|
+
throw new McpSurfaceError(`Whitelist entry id '${entry.id}' is declared twice; refusing to start.`);
|
|
131
|
+
}
|
|
132
|
+
seenIds.add(entry.id);
|
|
133
|
+
const literals = entry.argv
|
|
134
|
+
.filter((segment) => segment.kind === 'literal')
|
|
135
|
+
.map((segment) => (segment.kind === 'literal' ? segment.value : ''));
|
|
136
|
+
// The command path must open the argv: an entry whose template does not
|
|
137
|
+
// begin with the command it claims to be is an entry about something else.
|
|
138
|
+
const prefix = literals.slice(0, entry.commandPath.length).join(' ');
|
|
139
|
+
if (prefix !== entry.commandPath.join(' ')) {
|
|
140
|
+
throw new McpSurfaceError(`Whitelist entry '${entry.id}' declares commandPath '${entry.commandPath.join(' ')}' but its argv starts '${prefix}'; refusing to start.`);
|
|
141
|
+
}
|
|
142
|
+
const used = entry.argv.flatMap((segment) => (segment.kind === 'param' ? [segment.name] : []));
|
|
143
|
+
for (const name of used) {
|
|
144
|
+
if (!(name in entry.params)) {
|
|
145
|
+
throw new McpSurfaceError(`Whitelist entry '${entry.id}' uses placeholder '${name}' that it does not declare; refusing to start.`);
|
|
146
|
+
}
|
|
147
|
+
if (used.filter((candidate) => candidate === name).length !== 1) {
|
|
148
|
+
throw new McpSurfaceError(`Whitelist entry '${entry.id}' uses placeholder '${name}' more than once; refusing to start.`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
for (const name of Object.keys(entry.params)) {
|
|
152
|
+
if (!used.includes(name)) {
|
|
153
|
+
throw new McpSurfaceError(`Whitelist entry '${entry.id}' declares parameter '${name}' that its argv never uses; refusing to start.`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* L4 (spec §6): refuse to start when the tool surface and the proven whitelist do
|
|
160
|
+
* not describe the same thing.
|
|
161
|
+
*
|
|
162
|
+
* Two properties, each meant to fail loudly rather than degrade:
|
|
163
|
+
*
|
|
164
|
+
* 1. Every argv a tool can run is a whitelisted argv, by template. The surface
|
|
165
|
+
* is derived from the same artifact today, so on the production path this
|
|
166
|
+
* reads as "nothing unproven got in" — but it is the boundary that has to
|
|
167
|
+
* hold the moment a tool is authored rather than derived, and the test
|
|
168
|
+
* proves it can fail by handing it a surface that names an unproven argv.
|
|
169
|
+
* 2. Every whitelisted entry is reachable from some tool. This one DOES have a
|
|
170
|
+
* live trigger: an entry added to `readonly-surface.json` whose tool name is
|
|
171
|
+
* mistyped is proven read-only and then never served.
|
|
172
|
+
*/
|
|
173
|
+
export function validateSurface(whitelist, tools) {
|
|
174
|
+
validateWhitelist(whitelist);
|
|
175
|
+
if (tools.length === 0) {
|
|
176
|
+
throw new McpSurfaceError('MCP surface declares no tools; refusing to start.');
|
|
177
|
+
}
|
|
178
|
+
const whitelistedTemplates = new Set(whitelist.entries.map((entry) => argvTemplateOf(entry)));
|
|
179
|
+
const servedTemplates = new Set();
|
|
180
|
+
for (const tool of tools) {
|
|
181
|
+
if (tool.description.trim().length === 0) {
|
|
182
|
+
throw new McpSurfaceError(`MCP tool '${tool.name}' has no description; refusing to start (spec §8.4 rule 2).`);
|
|
183
|
+
}
|
|
184
|
+
for (const entry of tool.entries) {
|
|
185
|
+
const template = argvTemplateOf(entry);
|
|
186
|
+
if (!whitelistedTemplates.has(template)) {
|
|
187
|
+
throw new McpSurfaceError(`MCP tool '${tool.name}' carries argv '${template}' that is not in the read-only whitelist; refusing to start.`);
|
|
188
|
+
}
|
|
189
|
+
servedTemplates.add(template);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
for (const entry of whitelist.entries) {
|
|
193
|
+
const template = argvTemplateOf(entry);
|
|
194
|
+
if (!servedTemplates.has(template)) {
|
|
195
|
+
throw new McpSurfaceError(`Whitelist entry '${entry.id}' ('${template}') is reachable from no MCP tool; refusing to start.`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { ReadOnlyWhitelistEntry } from '../readonly-surface/readonly-whitelist.js';
|
|
2
|
+
import { type CliExecution } from './cli-executor.js';
|
|
3
|
+
/** How much of one memory note's description travels back. */
|
|
4
|
+
export declare const MEMORY_EXCERPT_CHARS = 120;
|
|
5
|
+
/** Hard ceiling on matches returned, independent of what the CLI was asked for. */
|
|
6
|
+
export declare const MEMORY_MAX_RETURNED = 50;
|
|
7
|
+
export interface ToolTextContent {
|
|
8
|
+
readonly type: 'text';
|
|
9
|
+
readonly text: string;
|
|
10
|
+
}
|
|
11
|
+
export interface ToolCallResult {
|
|
12
|
+
readonly content: readonly ToolTextContent[];
|
|
13
|
+
/** Present and `true` only when something failed. Absent means success. */
|
|
14
|
+
readonly isError?: boolean;
|
|
15
|
+
}
|
|
16
|
+
/** Injectable execution, so a test can drive a handler without a CLI. */
|
|
17
|
+
export type CliExecutor = (argv: readonly string[]) => Promise<CliExecution>;
|
|
18
|
+
export interface ToolCallContext {
|
|
19
|
+
readonly execute?: CliExecutor;
|
|
20
|
+
/** Directory the CLI runs in. Defaults to the server process's own. */
|
|
21
|
+
readonly cwd?: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The values to render one entry with, or `undefined` when the caller did not
|
|
25
|
+
* supply enough for this entry to run. A value the caller DID supply is always
|
|
26
|
+
* present — it is handed to the guard, which is what decides whether it is
|
|
27
|
+
* allowed, so nothing supplied can be silently skipped.
|
|
28
|
+
*/
|
|
29
|
+
export declare function valuesForEntry(entry: ReadOnlyWhitelistEntry, args: Readonly<Record<string, unknown>>, context: ToolCallContext): Record<string, string> | undefined;
|
|
30
|
+
/** One argv's outcome, kept whole so the caller can see every envelope. */
|
|
31
|
+
export interface EntryOutcome {
|
|
32
|
+
readonly key: string;
|
|
33
|
+
readonly execution: CliExecution;
|
|
34
|
+
readonly envelope: unknown;
|
|
35
|
+
readonly failure?: string;
|
|
36
|
+
}
|
|
37
|
+
/** Render the outcomes as a tool result, failing the call when any argv failed. */
|
|
38
|
+
export declare function resultOf(outcomes: readonly EntryOutcome[]): ToolCallResult;
|
|
39
|
+
/** A refused call: the parameter guard said no, before anything was executed. */
|
|
40
|
+
export declare function rejectedResult(entry: ReadOnlyWhitelistEntry, reason: string): ToolCallResult;
|
|
41
|
+
/** Execute one already-validated argv and keep its whole outcome. */
|
|
42
|
+
export declare function runArgv(key: string, argv: readonly string[], context: ToolCallContext): Promise<EntryOutcome>;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// src/services/mcp/tool-core.ts
|
|
2
|
+
//
|
|
3
|
+
// What both tool handlers are made of: the result shape, the argument-to-argv
|
|
4
|
+
// step, and the one place a CLI execution becomes an outcome. Split out of
|
|
5
|
+
// `tools.ts` because that file had grown past the repo's 300-line cap, and the
|
|
6
|
+
// cap is measured per file rather than per concept.
|
|
7
|
+
//
|
|
8
|
+
// ERRORS ARE NEVER FLATTENED HERE, either. A CLI failure becomes a `failure`
|
|
9
|
+
// string on the outcome, never an absent value: a read tool that answers
|
|
10
|
+
// "nothing there" when it actually failed is worse than one that errors, because
|
|
11
|
+
// the reader believes the empty answer (spec §9 rule 3).
|
|
12
|
+
import { executeCliArgv } from './cli-executor.js';
|
|
13
|
+
/** How much of one memory note's description travels back. */
|
|
14
|
+
export const MEMORY_EXCERPT_CHARS = 120;
|
|
15
|
+
/** Hard ceiling on matches returned, independent of what the CLI was asked for. */
|
|
16
|
+
export const MEMORY_MAX_RETURNED = 50;
|
|
17
|
+
/**
|
|
18
|
+
* Hand-written parameter defaults — the one place this module can drift from the
|
|
19
|
+
* whitelist (spec §8.1 asks such places to be visible). `project` is a MANDATORY
|
|
20
|
+
* option of `peaks request show`, and a status caller should not have to repeat
|
|
21
|
+
* the directory the server already runs in. Nothing else is defaulted: every
|
|
22
|
+
* other placeholder is either required by the caller or defaulted in the data.
|
|
23
|
+
*/
|
|
24
|
+
function defaultFor(param, context) {
|
|
25
|
+
return param === 'project' ? (context.cwd ?? process.cwd()) : undefined;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* A transported argument as the guard's input. The JSON transport carries
|
|
29
|
+
* numbers for integer parameters and the guard can only judge a string, so
|
|
30
|
+
* coercion happens here, at the boundary — and a wrong JSON type is NOT
|
|
31
|
+
* stringified into something that looks like data: it becomes the empty string,
|
|
32
|
+
* which the guard already refuses by name (`EMPTY`).
|
|
33
|
+
*/
|
|
34
|
+
function asGuardInput(supplied) {
|
|
35
|
+
if (typeof supplied === 'string')
|
|
36
|
+
return supplied;
|
|
37
|
+
if (typeof supplied === 'number')
|
|
38
|
+
return String(supplied);
|
|
39
|
+
return '';
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The values to render one entry with, or `undefined` when the caller did not
|
|
43
|
+
* supply enough for this entry to run. A value the caller DID supply is always
|
|
44
|
+
* present — it is handed to the guard, which is what decides whether it is
|
|
45
|
+
* allowed, so nothing supplied can be silently skipped.
|
|
46
|
+
*/
|
|
47
|
+
export function valuesForEntry(entry, args, context) {
|
|
48
|
+
const values = {};
|
|
49
|
+
for (const [name, spec] of Object.entries(entry.params)) {
|
|
50
|
+
const supplied = args[name];
|
|
51
|
+
if (supplied !== undefined) {
|
|
52
|
+
values[name] = asGuardInput(supplied);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
const fallback = spec.default === undefined ? defaultFor(name, context) : String(spec.default);
|
|
56
|
+
if (fallback === undefined)
|
|
57
|
+
return undefined;
|
|
58
|
+
values[name] = fallback;
|
|
59
|
+
}
|
|
60
|
+
return values;
|
|
61
|
+
}
|
|
62
|
+
/** True when the CLI process itself failed, or its stdout was not a JSON envelope. */
|
|
63
|
+
function failureReasonOf(execution, envelope) {
|
|
64
|
+
const called = `peaks ${execution.argv.join(' ')}`;
|
|
65
|
+
if (execution.launchError !== undefined) {
|
|
66
|
+
return `The peaks CLI could not be started: ${execution.launchError}`;
|
|
67
|
+
}
|
|
68
|
+
if (execution.timedOut) {
|
|
69
|
+
return `The peaks CLI did not finish in time and was killed: ${called}`;
|
|
70
|
+
}
|
|
71
|
+
if (execution.exitCode !== 0) {
|
|
72
|
+
return `The peaks CLI exited with code ${String(execution.exitCode)}: ${called}`;
|
|
73
|
+
}
|
|
74
|
+
if (envelope === undefined) {
|
|
75
|
+
return `The peaks CLI printed no JSON on stdout: ${called}`;
|
|
76
|
+
}
|
|
77
|
+
if (isFailedEnvelope(envelope)) {
|
|
78
|
+
return `The peaks CLI reported failure: ${called}`;
|
|
79
|
+
}
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
function isFailedEnvelope(value) {
|
|
83
|
+
return typeof value === 'object' && value !== null && 'ok' in value && value.ok === false;
|
|
84
|
+
}
|
|
85
|
+
function parseStdout(stdout) {
|
|
86
|
+
try {
|
|
87
|
+
return { parsed: true, value: JSON.parse(stdout) };
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return { parsed: false };
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/** Render the outcomes as a tool result, failing the call when any argv failed. */
|
|
94
|
+
export function resultOf(outcomes) {
|
|
95
|
+
const payload = {};
|
|
96
|
+
for (const outcome of outcomes) {
|
|
97
|
+
payload[outcome.key] =
|
|
98
|
+
outcome.envelope === undefined
|
|
99
|
+
? { ok: false, message: outcome.failure ?? 'no output', argv: outcome.execution.argv }
|
|
100
|
+
: outcome.envelope;
|
|
101
|
+
}
|
|
102
|
+
const failures = outcomes.filter((outcome) => outcome.failure !== undefined);
|
|
103
|
+
if (failures.length === 0) {
|
|
104
|
+
return { content: [{ type: 'text', text: JSON.stringify(payload) }] };
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
content: [
|
|
108
|
+
{
|
|
109
|
+
type: 'text',
|
|
110
|
+
text: JSON.stringify({
|
|
111
|
+
...payload,
|
|
112
|
+
failures: failures.map((outcome) => ({ key: outcome.key, reason: outcome.failure }))
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
],
|
|
116
|
+
isError: true
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
/** A refused call: the parameter guard said no, before anything was executed. */
|
|
120
|
+
export function rejectedResult(entry, reason) {
|
|
121
|
+
return {
|
|
122
|
+
content: [{ type: 'text', text: JSON.stringify({ entry: entry.id, reason }) }],
|
|
123
|
+
isError: true
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/** Execute one already-validated argv and keep its whole outcome. */
|
|
127
|
+
export async function runArgv(key, argv, context) {
|
|
128
|
+
const execute = context.execute ??
|
|
129
|
+
((args) => executeCliArgv(args, context.cwd === undefined ? {} : { cwd: context.cwd }));
|
|
130
|
+
const execution = await execute(argv);
|
|
131
|
+
const parsed = parseStdout(execution.stdout);
|
|
132
|
+
const envelope = parsed.parsed ? parsed.value : undefined;
|
|
133
|
+
const failure = failureReasonOf(execution, envelope);
|
|
134
|
+
return failure === undefined
|
|
135
|
+
? { key, execution, envelope }
|
|
136
|
+
: { key, execution, envelope, failure };
|
|
137
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { McpToolDefinition } from './surface.js';
|
|
2
|
+
import { type ToolCallContext, type ToolCallResult } from './tool-core.js';
|
|
3
|
+
/**
|
|
4
|
+
* `peaks_status`: run every argv the caller supplied enough for, and return each
|
|
5
|
+
* CLI envelope verbatim under the id of the whitelist entry that produced it.
|
|
6
|
+
*/
|
|
7
|
+
export declare function callStatusTool(tool: McpToolDefinition, args: Readonly<Record<string, unknown>>, context: ToolCallContext): Promise<ToolCallResult>;
|
|
8
|
+
type ToolHandler = (tool: McpToolDefinition, args: Readonly<Record<string, unknown>>, context: ToolCallContext) => Promise<ToolCallResult>;
|
|
9
|
+
/** Tools the surface declares that no handler can serve. Non-empty = refuse to start. */
|
|
10
|
+
export declare function missingHandlers(tools: readonly McpToolDefinition[]): readonly string[];
|
|
11
|
+
/** Route a `tools/call` to its handler, or `undefined` for a tool nobody serves. */
|
|
12
|
+
export declare function handlerFor(name: string): ToolHandler | undefined;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// src/services/mcp/tools.ts
|
|
2
|
+
//
|
|
3
|
+
// `peaks_status`, and the table that routes a `tools/call` to its handler. The
|
|
4
|
+
// result shape, the argument-to-argv step and the outcome rendering live in
|
|
5
|
+
// `tool-core.ts`; the bounded memory tool lives in `memory-tool.ts`.
|
|
6
|
+
//
|
|
7
|
+
// The composite is a pipe with a shape, never a judge (spec §3.2): "a composite
|
|
8
|
+
// tool may arrange and reshape; it may not CONCLUDE." Each CLI envelope is
|
|
9
|
+
// embedded VERBATIM under the whitelist entry id that produced it, so the whole
|
|
10
|
+
// result is an identity transform of the calls it ran. A key is absent when its
|
|
11
|
+
// argv did not run — exactly "subset"; no branch fabricates a value no argv
|
|
12
|
+
// returned.
|
|
13
|
+
import { buildArgv } from '../readonly-surface/argv-guard.js';
|
|
14
|
+
import { callMemorySearchTool } from './memory-tool.js';
|
|
15
|
+
import { rejectedResult, resultOf, runArgv, valuesForEntry } from './tool-core.js';
|
|
16
|
+
/**
|
|
17
|
+
* `peaks_status`: run every argv the caller supplied enough for, and return each
|
|
18
|
+
* CLI envelope verbatim under the id of the whitelist entry that produced it.
|
|
19
|
+
*/
|
|
20
|
+
export async function callStatusTool(tool, args, context) {
|
|
21
|
+
// Every argv is BUILT before any is run. Executing as we go meant a refused
|
|
22
|
+
// placeholder left the earlier argv of the same call already executed — a
|
|
23
|
+
// caller's malformed input would have reached the CLI's read path anyway.
|
|
24
|
+
const plan = [];
|
|
25
|
+
for (const entry of tool.entries) {
|
|
26
|
+
const values = valuesForEntry(entry, args, context);
|
|
27
|
+
if (values === undefined)
|
|
28
|
+
continue;
|
|
29
|
+
const built = buildArgv(entry, values);
|
|
30
|
+
if (!built.ok) {
|
|
31
|
+
return rejectedResult(entry, `Parameter ${built.param} was refused: ${built.message}`);
|
|
32
|
+
}
|
|
33
|
+
plan.push({ id: entry.id, argv: built.argv });
|
|
34
|
+
}
|
|
35
|
+
const outcomes = [];
|
|
36
|
+
for (const step of plan) {
|
|
37
|
+
outcomes.push(await runArgv(step.id, step.argv, context));
|
|
38
|
+
}
|
|
39
|
+
return resultOf(outcomes);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Tool name -> handler, spelled out rather than derived. A tool the whitelist
|
|
43
|
+
* declares but this table does not name cannot be served, and `missingHandlers`
|
|
44
|
+
* below turns that into a refusal to start instead of a call that answers with
|
|
45
|
+
* whatever the fallback branch happened to do.
|
|
46
|
+
*/
|
|
47
|
+
const TOOL_HANDLERS = {
|
|
48
|
+
peaks_status: callStatusTool,
|
|
49
|
+
peaks_memory_search: callMemorySearchTool
|
|
50
|
+
};
|
|
51
|
+
/** Tools the surface declares that no handler can serve. Non-empty = refuse to start. */
|
|
52
|
+
export function missingHandlers(tools) {
|
|
53
|
+
return tools.map((tool) => tool.name).filter((name) => !(name in TOOL_HANDLERS));
|
|
54
|
+
}
|
|
55
|
+
/** Route a `tools/call` to its handler, or `undefined` for a tool nobody serves. */
|
|
56
|
+
export function handlerFor(name) {
|
|
57
|
+
return TOOL_HANDLERS[name];
|
|
58
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { PlaceholderSpec, ReadOnlyWhitelistEntry } from './readonly-whitelist.js';
|
|
2
|
+
/** Upper bound on any single placeholder value, whatever its type. */
|
|
3
|
+
export declare const READONLY_VALUE_MAX_LENGTH = 200;
|
|
4
|
+
export type GuardRejection = {
|
|
5
|
+
readonly ok: false;
|
|
6
|
+
readonly param: string;
|
|
7
|
+
readonly code: GuardRejectionCode;
|
|
8
|
+
readonly message: string;
|
|
9
|
+
};
|
|
10
|
+
export type GuardRejectionCode = 'NOT_A_STRING' | 'EMPTY' | 'TOO_LONG' | 'FLAG_SHAPED' | 'CONTROL_CHARACTER' | 'WHITESPACE' | 'PATTERN_MISMATCH' | 'NOT_IN_ENUM' | 'NOT_AN_INTEGER' | 'OUT_OF_RANGE' | 'NOT_ABSOLUTE';
|
|
11
|
+
export type GuardResult = {
|
|
12
|
+
readonly ok: true;
|
|
13
|
+
readonly value: string;
|
|
14
|
+
} | GuardRejection;
|
|
15
|
+
/** The process options that keep the array from being re-parsed by a shell. */
|
|
16
|
+
export declare function readonlySpawnOptions(): {
|
|
17
|
+
readonly shell: false;
|
|
18
|
+
};
|
|
19
|
+
/** Validate one placeholder value against its spec. */
|
|
20
|
+
export declare function validatePlaceholderValue(param: string, spec: PlaceholderSpec, raw: unknown): GuardResult;
|
|
21
|
+
export type BuildArgvResult = {
|
|
22
|
+
readonly ok: true;
|
|
23
|
+
readonly argv: string[];
|
|
24
|
+
} | GuardRejection;
|
|
25
|
+
/**
|
|
26
|
+
* Render a whitelist entry into a concrete argv ARRAY.
|
|
27
|
+
*
|
|
28
|
+
* The output is never a string: literals and validated values are pushed as
|
|
29
|
+
* separate elements, so no value can contribute token boundaries. A parameter the
|
|
30
|
+
* entry does not declare is refused rather than appended.
|
|
31
|
+
*/
|
|
32
|
+
export declare function buildArgv(entry: ReadOnlyWhitelistEntry, values: Readonly<Record<string, string>>): BuildArgvResult;
|