@stigmer/runner 3.0.9-dev.20260615153829 → 3.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/dist/.build-fingerprint +1 -1
- package/dist/__test-utils__/approval-contract/types.d.ts +174 -0
- package/dist/__test-utils__/approval-contract/types.js +24 -0
- package/dist/__test-utils__/approval-contract/types.js.map +1 -0
- package/dist/activities/call-agent-status.d.ts +19 -1
- package/dist/activities/call-agent-status.js +64 -5
- package/dist/activities/call-agent-status.js.map +1 -1
- package/dist/activities/call-llm.js +19 -53
- package/dist/activities/call-llm.js.map +1 -1
- package/dist/activities/classify-tool-approvals.d.ts +24 -0
- package/dist/activities/classify-tool-approvals.js +69 -17
- package/dist/activities/classify-tool-approvals.js.map +1 -1
- package/dist/activities/discover-mcp-server.d.ts +7 -0
- package/dist/activities/discover-mcp-server.js +11 -1
- package/dist/activities/discover-mcp-server.js.map +1 -1
- package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.d.ts +140 -0
- package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.js +130 -0
- package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.js.map +1 -0
- package/dist/activities/execute-cursor/__test-utils__/gateway-substrate.d.ts +18 -0
- package/dist/activities/execute-cursor/__test-utils__/gateway-substrate.js +123 -0
- package/dist/activities/execute-cursor/__test-utils__/gateway-substrate.js.map +1 -0
- package/dist/activities/execute-cursor/approval-policy.d.ts +17 -61
- package/dist/activities/execute-cursor/approval-policy.js +8 -120
- package/dist/activities/execute-cursor/approval-policy.js.map +1 -1
- package/dist/activities/execute-cursor/approval-state.d.ts +219 -9
- package/dist/activities/execute-cursor/approval-state.js +233 -17
- package/dist/activities/execute-cursor/approval-state.js.map +1 -1
- package/dist/activities/execute-cursor/capture-flow.d.ts +127 -0
- package/dist/activities/execute-cursor/capture-flow.js +234 -0
- package/dist/activities/execute-cursor/capture-flow.js.map +1 -0
- package/dist/activities/execute-cursor/cas-observations.d.ts +96 -0
- package/dist/activities/execute-cursor/cas-observations.js +184 -0
- package/dist/activities/execute-cursor/cas-observations.js.map +1 -0
- package/dist/activities/execute-cursor/command-provenance.d.ts +62 -0
- package/dist/activities/execute-cursor/command-provenance.js +128 -0
- package/dist/activities/execute-cursor/command-provenance.js.map +1 -0
- package/dist/activities/execute-cursor/exact-apply.d.ts +110 -0
- package/dist/activities/execute-cursor/exact-apply.js +204 -0
- package/dist/activities/execute-cursor/exact-apply.js.map +1 -0
- package/dist/activities/execute-cursor/hook-script.d.ts +53 -24
- package/dist/activities/execute-cursor/hook-script.js +310 -47
- package/dist/activities/execute-cursor/hook-script.js.map +1 -1
- package/dist/activities/execute-cursor/index.d.ts +8 -1
- package/dist/activities/execute-cursor/index.js +500 -64
- package/dist/activities/execute-cursor/index.js.map +1 -1
- package/dist/activities/execute-cursor/message-translator.d.ts +258 -11
- package/dist/activities/execute-cursor/message-translator.js +836 -90
- package/dist/activities/execute-cursor/message-translator.js.map +1 -1
- package/dist/activities/execute-cursor/prompt-builder.d.ts +20 -23
- package/dist/activities/execute-cursor/prompt-builder.js +74 -15
- package/dist/activities/execute-cursor/prompt-builder.js.map +1 -1
- package/dist/activities/execute-cursor/workspace-setup.d.ts +17 -0
- package/dist/activities/execute-cursor/workspace-setup.js +212 -33
- package/dist/activities/execute-cursor/workspace-setup.js.map +1 -1
- package/dist/activities/execute-deep-agent/__test-utils__/gateway-substrate.d.ts +19 -0
- package/dist/activities/execute-deep-agent/__test-utils__/gateway-substrate.js +143 -0
- package/dist/activities/execute-deep-agent/__test-utils__/gateway-substrate.js.map +1 -0
- package/dist/activities/execute-deep-agent/__test-utils__/scripted-model.d.ts +88 -0
- package/dist/activities/execute-deep-agent/__test-utils__/scripted-model.js +81 -0
- package/dist/activities/execute-deep-agent/__test-utils__/scripted-model.js.map +1 -0
- package/dist/activities/execute-deep-agent/approval-file-change.d.ts +47 -0
- package/dist/activities/execute-deep-agent/approval-file-change.js +68 -0
- package/dist/activities/execute-deep-agent/approval-file-change.js.map +1 -0
- package/dist/activities/execute-deep-agent/attachment-injector.d.ts +8 -1
- package/dist/activities/execute-deep-agent/attachment-injector.js +7 -7
- package/dist/activities/execute-deep-agent/attachment-injector.js.map +1 -1
- package/dist/activities/execute-deep-agent/cas-capture-backend.d.ts +42 -0
- package/dist/activities/execute-deep-agent/cas-capture-backend.js +47 -0
- package/dist/activities/execute-deep-agent/cas-capture-backend.js.map +1 -0
- package/dist/activities/execute-deep-agent/cas-capture-observer.d.ts +79 -0
- package/dist/activities/execute-deep-agent/cas-capture-observer.js +112 -0
- package/dist/activities/execute-deep-agent/cas-capture-observer.js.map +1 -0
- package/dist/activities/execute-deep-agent/hitl.d.ts +16 -3
- package/dist/activities/execute-deep-agent/hitl.js +11 -3
- package/dist/activities/execute-deep-agent/hitl.js.map +1 -1
- package/dist/activities/execute-deep-agent/index.d.ts +2 -1
- package/dist/activities/execute-deep-agent/index.js +393 -33
- package/dist/activities/execute-deep-agent/index.js.map +1 -1
- package/dist/activities/execute-deep-agent/inline-publisher.d.ts +7 -1
- package/dist/activities/execute-deep-agent/inline-publisher.js +23 -2
- package/dist/activities/execute-deep-agent/inline-publisher.js.map +1 -1
- package/dist/activities/execute-deep-agent/setup.d.ts +53 -2
- package/dist/activities/execute-deep-agent/setup.js +149 -92
- package/dist/activities/execute-deep-agent/setup.js.map +1 -1
- package/dist/activities/execute-deep-agent/stamp-flowed-rows.d.ts +36 -0
- package/dist/activities/execute-deep-agent/stamp-flowed-rows.js +56 -0
- package/dist/activities/execute-deep-agent/stamp-flowed-rows.js.map +1 -0
- package/dist/activities/execute-deep-agent/status-builder-shared.d.ts +34 -1
- package/dist/activities/execute-deep-agent/status-builder-shared.js +26 -25
- package/dist/activities/execute-deep-agent/status-builder-shared.js.map +1 -1
- package/dist/activities/execute-deep-agent/status-builder.d.ts +17 -5
- package/dist/activities/execute-deep-agent/status-builder.js +44 -3
- package/dist/activities/execute-deep-agent/status-builder.js.map +1 -1
- package/dist/activities/execute-deep-agent/streaming-side-effects.js +2 -19
- package/dist/activities/execute-deep-agent/streaming-side-effects.js.map +1 -1
- package/dist/activities/execute-deep-agent/streaming.js +3 -15
- package/dist/activities/execute-deep-agent/streaming.js.map +1 -1
- package/dist/activities/execute-deep-agent/subagent-transformer.d.ts +25 -7
- package/dist/activities/execute-deep-agent/subagent-transformer.js +23 -7
- package/dist/activities/execute-deep-agent/subagent-transformer.js.map +1 -1
- package/dist/activities/execute-deep-agent/subagent-wiring.d.ts +30 -3
- package/dist/activities/execute-deep-agent/subagent-wiring.js +29 -3
- package/dist/activities/execute-deep-agent/subagent-wiring.js.map +1 -1
- package/dist/activities/execute-deep-agent/v3-status-builder.js +32 -2
- package/dist/activities/execute-deep-agent/v3-status-builder.js.map +1 -1
- package/dist/claimcheck/payload-codec.js +9 -5
- package/dist/claimcheck/payload-codec.js.map +1 -1
- package/dist/client/stigmer-client.d.ts +2 -0
- package/dist/client/stigmer-client.js +2 -0
- package/dist/client/stigmer-client.js.map +1 -1
- package/dist/middleware/approval-gate.d.ts +85 -4
- package/dist/middleware/approval-gate.js +165 -38
- package/dist/middleware/approval-gate.js.map +1 -1
- package/dist/middleware/types.d.ts +2 -5
- package/dist/shared/activity-input.d.ts +43 -0
- package/dist/shared/activity-input.js +17 -0
- package/dist/shared/activity-input.js.map +1 -0
- package/dist/shared/approval-canonicalize.d.ts +19 -0
- package/dist/shared/approval-canonicalize.js +119 -0
- package/dist/shared/approval-canonicalize.js.map +1 -0
- package/dist/shared/approval-fingerprint.d.ts +106 -0
- package/dist/shared/approval-fingerprint.js +113 -0
- package/dist/shared/approval-fingerprint.js.map +1 -0
- package/dist/shared/approval-policy.d.ts +182 -12
- package/dist/shared/approval-policy.js +213 -27
- package/dist/shared/approval-policy.js.map +1 -1
- package/dist/shared/args-preview.d.ts +52 -0
- package/dist/shared/args-preview.js +93 -0
- package/dist/shared/args-preview.js.map +1 -0
- package/dist/shared/artifact-storage.d.ts +19 -1
- package/dist/shared/artifact-storage.js +48 -11
- package/dist/shared/artifact-storage.js.map +1 -1
- package/dist/shared/file-change.d.ts +44 -0
- package/dist/shared/file-change.js +57 -0
- package/dist/shared/file-change.js.map +1 -0
- package/dist/shared/file-tools.d.ts +107 -0
- package/dist/shared/file-tools.js +168 -0
- package/dist/shared/file-tools.js.map +1 -0
- package/dist/shared/filereview/capture.d.ts +202 -0
- package/dist/shared/filereview/capture.js +498 -0
- package/dist/shared/filereview/capture.js.map +1 -0
- package/dist/shared/filereview/cas-substrate.d.ts +190 -0
- package/dist/shared/filereview/cas-substrate.js +284 -0
- package/dist/shared/filereview/cas-substrate.js.map +1 -0
- package/dist/shared/filereview/digest.d.ts +40 -0
- package/dist/shared/filereview/digest.js +66 -0
- package/dist/shared/filereview/digest.js.map +1 -0
- package/dist/shared/filereview/events.d.ts +170 -0
- package/dist/shared/filereview/events.js +298 -0
- package/dist/shared/filereview/events.js.map +1 -0
- package/dist/shared/filereview/git-substrate.d.ts +175 -0
- package/dist/shared/filereview/git-substrate.js +439 -0
- package/dist/shared/filereview/git-substrate.js.map +1 -0
- package/dist/shared/filereview/index.d.ts +11 -0
- package/dist/shared/filereview/index.js +12 -0
- package/dist/shared/filereview/index.js.map +1 -0
- package/dist/shared/filereview/secret-paths.d.ts +63 -0
- package/dist/shared/filereview/secret-paths.js +105 -0
- package/dist/shared/filereview/secret-paths.js.map +1 -0
- package/dist/shared/fingerprint-secret.d.ts +26 -0
- package/dist/shared/fingerprint-secret.js +47 -0
- package/dist/shared/fingerprint-secret.js.map +1 -0
- package/dist/shared/model-client.d.ts +51 -0
- package/dist/shared/model-client.js +77 -0
- package/dist/shared/model-client.js.map +1 -0
- package/dist/shared/plan-artifact.js +0 -2
- package/dist/shared/plan-artifact.js.map +1 -1
- package/dist/shared/status-offload.d.ts +83 -9
- package/dist/shared/status-offload.js +399 -79
- package/dist/shared/status-offload.js.map +1 -1
- package/dist/shared/status.js +14 -1
- package/dist/shared/status.js.map +1 -1
- package/dist/shared/tool-kind.d.ts +19 -0
- package/dist/shared/tool-kind.js +13 -0
- package/dist/shared/tool-kind.js.map +1 -1
- package/dist/shared/tool-row.d.ts +88 -0
- package/dist/shared/tool-row.js +127 -0
- package/dist/shared/tool-row.js.map +1 -0
- package/dist/shared/workspace/platform-dir.d.ts +25 -0
- package/dist/shared/workspace/platform-dir.js +38 -2
- package/dist/shared/workspace/platform-dir.js.map +1 -1
- package/dist/workflows/call-agent-orchestrator.js +56 -7
- package/dist/workflows/call-agent-orchestrator.js.map +1 -1
- package/dist/workflows/connect-mcp-server.d.ts +50 -0
- package/dist/workflows/connect-mcp-server.js +136 -15
- package/dist/workflows/connect-mcp-server.js.map +1 -1
- package/dist/workflows/types.d.ts +8 -0
- package/package.json +2 -2
- package/src/__test-utils__/approval-contract/contract.ts +224 -0
- package/src/__test-utils__/approval-contract/types.ts +179 -0
- package/src/__test-utils__/fake-artifact-storage.ts +72 -0
- package/src/__tests__/approval-gateway-contract.test.ts +29 -0
- package/src/__tests__/claimcheck-codec.test.ts +16 -53
- package/src/__tests__/golden-e2e.test.ts +2 -0
- package/src/__tests__/runner-token-coordinator.test.ts +3 -3
- package/src/activities/__tests__/call-agent-status.test.ts +135 -0
- package/src/activities/__tests__/call-llm.test.ts +1 -1
- package/src/activities/__tests__/classify-tool-approvals.test.ts +208 -1
- package/src/activities/__tests__/discover-mcp-server.test.ts +30 -0
- package/src/activities/__tests__/workflow-event-activities.test.ts +2 -1
- package/src/activities/call-agent-status.ts +74 -4
- package/src/activities/call-llm.ts +18 -63
- package/src/activities/classify-tool-approvals.ts +101 -19
- package/src/activities/discover-mcp-server.ts +29 -1
- package/src/activities/execute-cursor/__test-utils__/cursor-hook-harness.ts +216 -0
- package/src/activities/execute-cursor/__test-utils__/gateway-substrate.ts +148 -0
- package/src/activities/execute-cursor/__tests__/approval-gate.test.ts +41 -9
- package/src/activities/execute-cursor/__tests__/approval-state.test.ts +292 -0
- package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +68 -1
- package/src/activities/execute-cursor/__tests__/capture-flow.test.ts +1005 -0
- package/src/activities/execute-cursor/__tests__/cas-observations.test.ts +187 -0
- package/src/activities/execute-cursor/__tests__/coarse-fingerprint.test.ts +97 -0
- package/src/activities/execute-cursor/__tests__/command-provenance.test.ts +240 -0
- package/src/activities/execute-cursor/__tests__/deny-gate-exact-apply.test.ts +203 -0
- package/src/activities/execute-cursor/__tests__/exact-apply.test.ts +375 -0
- package/src/activities/execute-cursor/__tests__/hitl-ledger.test.ts +1294 -24
- package/src/activities/execute-cursor/__tests__/hitl-resume-history.test.ts +446 -0
- package/src/activities/execute-cursor/__tests__/hook-script.test.ts +384 -110
- package/src/activities/execute-cursor/__tests__/message-translator.test.ts +171 -25
- package/src/activities/execute-cursor/__tests__/sequential-gate-resume.test.ts +189 -0
- package/src/activities/execute-cursor/__tests__/tool-result-image.test.ts +44 -23
- package/src/activities/execute-cursor/__tests__/workspace-setup.test.ts +190 -10
- package/src/activities/execute-cursor/approval-policy.ts +28 -159
- package/src/activities/execute-cursor/approval-state.ts +366 -18
- package/src/activities/execute-cursor/capture-flow.ts +323 -0
- package/src/activities/execute-cursor/cas-observations.ts +204 -0
- package/src/activities/execute-cursor/command-provenance.ts +168 -0
- package/src/activities/execute-cursor/exact-apply.ts +253 -0
- package/src/activities/execute-cursor/hook-script.ts +317 -51
- package/src/activities/execute-cursor/index.ts +575 -67
- package/src/activities/execute-cursor/message-translator.ts +963 -89
- package/src/activities/execute-cursor/prompt-builder.ts +80 -14
- package/src/activities/execute-cursor/workspace-setup.ts +257 -42
- package/src/activities/execute-deep-agent/__test-utils__/gateway-substrate.ts +180 -0
- package/src/activities/execute-deep-agent/__test-utils__/scripted-model.ts +134 -0
- package/src/activities/execute-deep-agent/__tests__/approval-file-change.test.ts +84 -0
- package/src/activities/execute-deep-agent/__tests__/attachment-injector.test.ts +11 -24
- package/src/activities/execute-deep-agent/__tests__/cas-capture-backend.test.ts +64 -0
- package/src/activities/execute-deep-agent/__tests__/cas-capture-observer.test.ts +163 -0
- package/src/activities/execute-deep-agent/__tests__/hitl-integration.test.ts +8 -5
- package/src/activities/execute-deep-agent/__tests__/hitl-resume-approve-all.test.ts +342 -0
- package/src/activities/execute-deep-agent/__tests__/hitl-resume-history.test.ts +284 -0
- package/src/activities/execute-deep-agent/__tests__/hitl.test.ts +14 -36
- package/src/activities/execute-deep-agent/__tests__/inline-publisher.test.ts +31 -13
- package/src/activities/execute-deep-agent/__tests__/sequential-gate-resume.test.ts +349 -0
- package/src/activities/execute-deep-agent/__tests__/stamp-flowed-rows.test.ts +119 -0
- package/src/activities/execute-deep-agent/__tests__/status-builder.test.ts +91 -10
- package/src/activities/execute-deep-agent/__tests__/streaming-v3.test.ts +9 -9
- package/src/activities/execute-deep-agent/__tests__/subagent-approval-propagation.test.ts +160 -0
- package/src/activities/execute-deep-agent/__tests__/subagent-gitignored-capture.test.ts +213 -0
- package/src/activities/execute-deep-agent/__tests__/subagent-transformer.test.ts +3 -6
- package/src/activities/execute-deep-agent/__tests__/subagent-wiring.test.ts +84 -1
- package/src/activities/execute-deep-agent/__tests__/v3-status-builder.test.ts +4 -1
- package/src/activities/execute-deep-agent/approval-file-change.ts +80 -0
- package/src/activities/execute-deep-agent/attachment-injector.ts +20 -11
- package/src/activities/execute-deep-agent/cas-capture-backend.ts +66 -0
- package/src/activities/execute-deep-agent/cas-capture-observer.ts +125 -0
- package/src/activities/execute-deep-agent/hitl.ts +23 -7
- package/src/activities/execute-deep-agent/index.ts +463 -40
- package/src/activities/execute-deep-agent/inline-publisher.ts +27 -4
- package/src/activities/execute-deep-agent/setup.ts +223 -125
- package/src/activities/execute-deep-agent/stamp-flowed-rows.ts +64 -0
- package/src/activities/execute-deep-agent/status-builder-shared.ts +62 -23
- package/src/activities/execute-deep-agent/status-builder.ts +63 -8
- package/src/activities/execute-deep-agent/streaming-side-effects.ts +2 -16
- package/src/activities/execute-deep-agent/streaming.ts +3 -13
- package/src/activities/execute-deep-agent/subagent-transformer.ts +53 -13
- package/src/activities/execute-deep-agent/subagent-wiring.ts +50 -3
- package/src/activities/execute-deep-agent/v3-status-builder.ts +36 -2
- package/src/claimcheck/payload-codec.ts +8 -8
- package/src/client/stigmer-client.ts +9 -1
- package/src/middleware/__tests__/approval-gate.test.ts +488 -4
- package/src/middleware/approval-gate.ts +247 -38
- package/src/middleware/types.ts +5 -5
- package/src/shared/__tests__/activity-input.test.ts +78 -0
- package/src/shared/__tests__/approval-canonicalize.test.ts +106 -0
- package/src/shared/__tests__/approval-fingerprint.test.ts +115 -0
- package/src/shared/__tests__/approval-policy.test.ts +274 -40
- package/src/shared/__tests__/args-preview.test.ts +78 -0
- package/src/shared/__tests__/artifact-storage-extended.test.ts +62 -10
- package/src/shared/__tests__/artifact-storage.test.ts +123 -11
- package/src/shared/__tests__/file-change.test.ts +85 -0
- package/src/shared/__tests__/file-tools.test.ts +90 -0
- package/src/shared/__tests__/fingerprint-secret.test.ts +51 -0
- package/src/shared/__tests__/lease-scope-corpus.test.ts +56 -0
- package/src/shared/__tests__/model-client.test.ts +162 -0
- package/src/shared/__tests__/plan-artifact.test.ts +11 -26
- package/src/shared/__tests__/policy-source-corpus.test.ts +58 -0
- package/src/shared/__tests__/status-offload.test.ts +573 -16
- package/src/shared/__tests__/status.test.ts +4 -5
- package/src/shared/__tests__/tool-kind.test.ts +24 -1
- package/src/shared/__tests__/tool-row.test.ts +221 -0
- package/src/shared/activity-input.ts +57 -0
- package/src/shared/approval-canonicalize.ts +159 -0
- package/src/shared/approval-fingerprint.ts +148 -0
- package/src/shared/approval-policy.ts +303 -27
- package/src/shared/args-preview.ts +98 -0
- package/src/shared/artifact-storage.ts +62 -11
- package/src/shared/checkpointer/__tests__/http-saver.test.ts +1 -2
- package/src/shared/file-change.ts +64 -0
- package/src/shared/file-tools.ts +169 -0
- package/src/shared/filereview/__tests__/capture.test.ts +856 -0
- package/src/shared/filereview/__tests__/cas-substrate.test.ts +404 -0
- package/src/shared/filereview/__tests__/digest.test.ts +100 -0
- package/src/shared/filereview/__tests__/events.test.ts +245 -0
- package/src/shared/filereview/__tests__/git-substrate.test.ts +362 -0
- package/src/shared/filereview/__tests__/proxy-reconcile.test.ts +286 -0
- package/src/shared/filereview/__tests__/secret-paths.test.ts +121 -0
- package/src/shared/filereview/capture.ts +727 -0
- package/src/shared/filereview/cas-substrate.ts +401 -0
- package/src/shared/filereview/digest.ts +83 -0
- package/src/shared/filereview/events.ts +449 -0
- package/src/shared/filereview/git-substrate.ts +555 -0
- package/src/shared/filereview/index.ts +60 -0
- package/src/shared/filereview/secret-paths.ts +121 -0
- package/src/shared/fingerprint-secret.ts +53 -0
- package/src/shared/model-client.ts +122 -0
- package/src/shared/plan-artifact.ts +0 -2
- package/src/shared/status-offload.ts +433 -77
- package/src/shared/status.ts +13 -0
- package/src/shared/tool-kind.ts +33 -0
- package/src/shared/tool-row.ts +135 -0
- package/src/shared/workspace/platform-dir.ts +41 -2
- package/src/workflow-engine/__tests__/golden-execution.test.ts +35 -18
- package/src/workflow-engine/__tests__/tasks/try.test.ts +1 -1
- package/src/workflows/__tests__/connect-mcp-server.test.ts +304 -29
- package/src/workflows/call-agent-orchestrator.ts +53 -6
- package/src/workflows/connect-mcp-server.ts +179 -24
- package/src/workflows/types.ts +8 -0
|
@@ -168,6 +168,33 @@ describe("DiscoverMcpServer activity", () => {
|
|
|
168
168
|
);
|
|
169
169
|
});
|
|
170
170
|
|
|
171
|
+
it("returns previous tool definitions for incremental diffing", async () => {
|
|
172
|
+
const { extractPreviousState } = await import("../discover-mcp-server.js");
|
|
173
|
+
const server = makeMcpServer({
|
|
174
|
+
status: {
|
|
175
|
+
discoveredCapabilities: {
|
|
176
|
+
tools: [
|
|
177
|
+
{ name: "search", description: "Search things", inputSchema: { type: "object" } },
|
|
178
|
+
{ name: "delete_repo", description: "Delete a repo", inputSchema: null },
|
|
179
|
+
],
|
|
180
|
+
resourceTemplates: [],
|
|
181
|
+
},
|
|
182
|
+
toolApprovals: [{ toolName: "delete_repo", message: "Delete" }],
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
const state = extractPreviousState(server);
|
|
186
|
+
expect(state.tools).toEqual([
|
|
187
|
+
{ name: "search", description: "Search things", inputSchema: { type: "object" } },
|
|
188
|
+
{ name: "delete_repo", description: "Delete a repo", inputSchema: null },
|
|
189
|
+
]);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it("returns empty previous tools when server has no status", async () => {
|
|
193
|
+
const { extractPreviousState } = await import("../discover-mcp-server.js");
|
|
194
|
+
const state = extractPreviousState(makeMcpServer({ status: undefined }));
|
|
195
|
+
expect(state.tools).toEqual([]);
|
|
196
|
+
});
|
|
197
|
+
|
|
171
198
|
it("extracts tool approvals from status", async () => {
|
|
172
199
|
const { extractPreviousState } = await import("../discover-mcp-server.js");
|
|
173
200
|
const server = makeMcpServer({
|
|
@@ -444,6 +471,9 @@ describe("DiscoverMcpServer activity", () => {
|
|
|
444
471
|
expect(result.previousToolApprovals).toEqual([
|
|
445
472
|
{ toolName: "old_tool", requiresApproval: true, message: "Approve old_tool" },
|
|
446
473
|
]);
|
|
474
|
+
expect(result.previousTools).toEqual([
|
|
475
|
+
{ name: "old_tool", description: "Old", inputSchema: null },
|
|
476
|
+
]);
|
|
447
477
|
expect(result.tools[0].name).toBe("new_tool");
|
|
448
478
|
expect(result.newToolsFingerprint).toHaveLength(64);
|
|
449
479
|
expect(result.newToolsFingerprint).not.toBe(result.previousToolsFingerprint);
|
|
@@ -329,7 +329,8 @@ describe("toProtoEvent", () => {
|
|
|
329
329
|
attemptNumber: 1,
|
|
330
330
|
};
|
|
331
331
|
const proto = toProtoEvent(desc);
|
|
332
|
-
|
|
332
|
+
if (proto.payload.case !== "taskStarted") throw new Error("unexpected");
|
|
333
|
+
expect(proto.payload.value.taskKind).toBe(WorkflowTaskKind.agent_call);
|
|
333
334
|
});
|
|
334
335
|
});
|
|
335
336
|
|
|
@@ -15,9 +15,9 @@
|
|
|
15
15
|
import { StigmerClient } from "../client/stigmer-client.js";
|
|
16
16
|
import { loadConfig } from "../config.js";
|
|
17
17
|
import { create } from "@bufbuild/protobuf";
|
|
18
|
-
import { WorkflowExecutionStatusSchema, WorkflowPendingApprovalSchema } from "@stigmer/protos/ai/stigmer/agentic/workflowexecution/v1/api_pb";
|
|
18
|
+
import { WorkflowExecutionStatusSchema, WorkflowPendingApprovalSchema, WorkflowPendingFileReviewSchema } from "@stigmer/protos/ai/stigmer/agentic/workflowexecution/v1/api_pb";
|
|
19
19
|
import type { ChildApprovalNotification } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/approval_pb";
|
|
20
|
-
import { ToolCallStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
20
|
+
import { ToolCallStatus, FileChangeSetStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
21
21
|
|
|
22
22
|
function buildClient(): StigmerClient {
|
|
23
23
|
const config = loadConfig();
|
|
@@ -34,12 +34,13 @@ export async function updateWorkflowTaskApprovalStatus(
|
|
|
34
34
|
): Promise<void> {
|
|
35
35
|
if (!executionId) return;
|
|
36
36
|
|
|
37
|
+
const childExecId = notification.executionId;
|
|
37
38
|
const client = buildClient();
|
|
38
39
|
const pendingApprovals = (notification.pendingApprovals ?? []).map(
|
|
39
40
|
(approval) =>
|
|
40
41
|
create(WorkflowPendingApprovalSchema, {
|
|
41
42
|
approval,
|
|
42
|
-
childAgentExecutionId:
|
|
43
|
+
childAgentExecutionId: childExecId,
|
|
43
44
|
}),
|
|
44
45
|
);
|
|
45
46
|
|
|
@@ -47,24 +48,91 @@ export async function updateWorkflowTaskApprovalStatus(
|
|
|
47
48
|
pendingApprovals,
|
|
48
49
|
});
|
|
49
50
|
|
|
51
|
+
// Per-child merge scoped to this child: the write replaces only this child's
|
|
52
|
+
// approvals and preserves every parallel sibling's entries.
|
|
50
53
|
await client.updateWorkflowExecutionStatus(executionId, status, {
|
|
51
54
|
updatePendingApprovals: true,
|
|
55
|
+
pendingUpdateChildAgentExecutionId: childExecId,
|
|
52
56
|
});
|
|
53
57
|
}
|
|
54
58
|
|
|
55
59
|
export async function clearWorkflowApprovalStatus(
|
|
56
60
|
executionId: string,
|
|
61
|
+
childExecutionId: string,
|
|
57
62
|
): Promise<void> {
|
|
58
|
-
if (!executionId) return;
|
|
63
|
+
if (!executionId || !childExecutionId) return;
|
|
59
64
|
|
|
60
65
|
const client = buildClient();
|
|
61
66
|
const status = create(WorkflowExecutionStatusSchema, {
|
|
62
67
|
pendingApprovals: [],
|
|
63
68
|
});
|
|
64
69
|
|
|
70
|
+
// Scoped clear: empty list for this child clears only its approvals; sibling
|
|
71
|
+
// children still awaiting approval are untouched.
|
|
65
72
|
await client.updateWorkflowExecutionStatus(executionId, status, {
|
|
66
73
|
updatePendingApprovals: true,
|
|
74
|
+
pendingUpdateChildAgentExecutionId: childExecutionId,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Surfaces (or clears) a child agent's file-review gate on the parent workflow.
|
|
80
|
+
*
|
|
81
|
+
* Reference-only: writes a single WorkflowPendingFileReview naming the child and
|
|
82
|
+
* the change_set ids it currently has AWAITING_REVIEW — never the diffs, which
|
|
83
|
+
* stay single-sourced on the child. Passing an empty changeSetIds clears this
|
|
84
|
+
* child's entry (per-child merge, so parallel siblings are never disturbed).
|
|
85
|
+
*/
|
|
86
|
+
export async function updateWorkflowFileReviewStatus(
|
|
87
|
+
executionId: string,
|
|
88
|
+
childExecutionId: string,
|
|
89
|
+
changeSetIds: string[],
|
|
90
|
+
): Promise<void> {
|
|
91
|
+
if (!executionId || !childExecutionId) return;
|
|
92
|
+
|
|
93
|
+
const client = buildClient();
|
|
94
|
+
const pendingFileReviews =
|
|
95
|
+
changeSetIds.length > 0
|
|
96
|
+
? [
|
|
97
|
+
create(WorkflowPendingFileReviewSchema, {
|
|
98
|
+
childAgentExecutionId: childExecutionId,
|
|
99
|
+
changeSetId: changeSetIds,
|
|
100
|
+
}),
|
|
101
|
+
]
|
|
102
|
+
: [];
|
|
103
|
+
|
|
104
|
+
const status = create(WorkflowExecutionStatusSchema, {
|
|
105
|
+
pendingFileReviews,
|
|
67
106
|
});
|
|
107
|
+
|
|
108
|
+
await client.updateWorkflowExecutionStatus(executionId, status, {
|
|
109
|
+
updatePendingFileReviews: true,
|
|
110
|
+
pendingUpdateChildAgentExecutionId: childExecutionId,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Reads the child agent execution and returns the ids of its change sets that
|
|
116
|
+
* are currently AWAITING_REVIEW. The gated child is non-terminal, so its last
|
|
117
|
+
* status write stored a populated file_change_sets projection; GET returns it
|
|
118
|
+
* (the same reason getAgentExecutionProgress reads status.messages).
|
|
119
|
+
*/
|
|
120
|
+
export async function getAwaitingFileReviewChangeSetIds(
|
|
121
|
+
childExecutionId: string,
|
|
122
|
+
): Promise<string[]> {
|
|
123
|
+
if (!childExecutionId) return [];
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
const client = buildClient();
|
|
127
|
+
const execution = await client.getExecution(childExecutionId);
|
|
128
|
+
const changeSets = execution?.status?.fileChangeSets ?? [];
|
|
129
|
+
return changeSets
|
|
130
|
+
.filter((cs) => cs.status === FileChangeSetStatus.AWAITING_REVIEW)
|
|
131
|
+
.map((cs) => cs.id);
|
|
132
|
+
} catch (err) {
|
|
133
|
+
console.warn("Failed to fetch child file-review state (non-fatal):", String(err));
|
|
134
|
+
return [];
|
|
135
|
+
}
|
|
68
136
|
}
|
|
69
137
|
|
|
70
138
|
/**
|
|
@@ -125,6 +193,8 @@ export function createCallAgentStatusActivities() {
|
|
|
125
193
|
return {
|
|
126
194
|
UpdateWorkflowTaskApprovalStatus: updateWorkflowTaskApprovalStatus,
|
|
127
195
|
ClearWorkflowApprovalStatus: clearWorkflowApprovalStatus,
|
|
196
|
+
UpdateWorkflowFileReviewStatus: updateWorkflowFileReviewStatus,
|
|
197
|
+
GetAwaitingFileReviewChangeSetIds: getAwaitingFileReviewChangeSetIds,
|
|
128
198
|
GetAgentExecutionProgress: getAgentExecutionProgress,
|
|
129
199
|
};
|
|
130
200
|
}
|
|
@@ -16,8 +16,6 @@
|
|
|
16
16
|
* Output: LlmCallResult
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import { ChatAnthropic } from "@langchain/anthropic";
|
|
20
|
-
import { ChatOpenAI } from "@langchain/openai";
|
|
21
19
|
import { HumanMessage, SystemMessage } from "@langchain/core/messages";
|
|
22
20
|
import type { BaseChatModel } from "@langchain/core/language_models/chat_models";
|
|
23
21
|
import type { BaseMessageChunk } from "@langchain/core/messages";
|
|
@@ -28,12 +26,11 @@ import { jsonSchemaToZod } from "../shared/json-schema-to-zod.js";
|
|
|
28
26
|
import {
|
|
29
27
|
inferProvider,
|
|
30
28
|
stripProviderPrefix,
|
|
31
|
-
resolveProxyBaseUrl,
|
|
32
|
-
buildProxyHeaders,
|
|
33
29
|
type LlmProvider,
|
|
34
30
|
} from "../shared/llm-proxy.js";
|
|
35
31
|
import { computeLlmCostMicros, ensureLoaded as ensurePricingLoaded } from "../shared/model-pricing.js";
|
|
36
32
|
import { resolveToApiModelId } from "../shared/model-registry.js";
|
|
33
|
+
import { buildChatModel } from "../shared/model-client.js";
|
|
37
34
|
|
|
38
35
|
export interface LlmCallConfig {
|
|
39
36
|
readonly model: string;
|
|
@@ -54,54 +51,6 @@ export interface LlmCallResult {
|
|
|
54
51
|
readonly parse_error?: string;
|
|
55
52
|
}
|
|
56
53
|
|
|
57
|
-
function constructModel(
|
|
58
|
-
provider: LlmProvider,
|
|
59
|
-
modelId: string,
|
|
60
|
-
config: LlmCallConfig,
|
|
61
|
-
baseUrl?: string,
|
|
62
|
-
headers?: Record<string, string>,
|
|
63
|
-
): BaseChatModel {
|
|
64
|
-
if (provider === "openai") {
|
|
65
|
-
const apiKey = baseUrl
|
|
66
|
-
? (headers?.Authorization?.replace("Bearer ", "") ?? "proxy-managed")
|
|
67
|
-
: (process.env.OPENAI_API_KEY ?? "");
|
|
68
|
-
|
|
69
|
-
return new ChatOpenAI({
|
|
70
|
-
model: modelId,
|
|
71
|
-
temperature: config.temperature ?? 0,
|
|
72
|
-
maxTokens: config.max_tokens,
|
|
73
|
-
...(baseUrl || headers
|
|
74
|
-
? {
|
|
75
|
-
configuration: {
|
|
76
|
-
...(baseUrl ? { baseURL: baseUrl } : {}),
|
|
77
|
-
...(headers ? { defaultHeaders: headers } : {}),
|
|
78
|
-
},
|
|
79
|
-
}
|
|
80
|
-
: {}),
|
|
81
|
-
apiKey,
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const apiKey = baseUrl
|
|
86
|
-
? (headers?.Authorization?.replace("Bearer ", "") ?? "proxy-managed")
|
|
87
|
-
: (process.env.ANTHROPIC_API_KEY ?? "");
|
|
88
|
-
|
|
89
|
-
return new ChatAnthropic({
|
|
90
|
-
model: modelId,
|
|
91
|
-
temperature: config.temperature ?? 0,
|
|
92
|
-
maxTokens: config.max_tokens ?? 4096,
|
|
93
|
-
...(baseUrl || headers
|
|
94
|
-
? {
|
|
95
|
-
clientOptions: {
|
|
96
|
-
...(baseUrl ? { baseURL: baseUrl } : {}),
|
|
97
|
-
...(headers ? { defaultHeaders: headers } : {}),
|
|
98
|
-
},
|
|
99
|
-
}
|
|
100
|
-
: {}),
|
|
101
|
-
apiKey,
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
|
|
105
54
|
/**
|
|
106
55
|
* Stream a chat model invocation and collect the response.
|
|
107
56
|
*
|
|
@@ -274,22 +223,18 @@ export async function callLlmAction(
|
|
|
274
223
|
`structured=${!!config.response_schema} execution=${executionId}`,
|
|
275
224
|
);
|
|
276
225
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
if (proxyActive) {
|
|
281
|
-
|
|
282
|
-
headers = buildProxyHeaders(stigmerToken!, { workflowExecutionId: executionId });
|
|
283
|
-
} else if (provider === "openai") {
|
|
284
|
-
if (!process.env.OPENAI_API_KEY) {
|
|
226
|
+
// Direct mode (no proxy) requires the provider's own API key. Validated here
|
|
227
|
+
// rather than in buildChatModel so the shared module stays free of Temporal
|
|
228
|
+
// failure types.
|
|
229
|
+
if (!proxyActive) {
|
|
230
|
+
if (provider === "openai" && !process.env.OPENAI_API_KEY) {
|
|
285
231
|
throw ApplicationFailure.nonRetryable(
|
|
286
232
|
`OPENAI_API_KEY is not set and no proxy is configured. ` +
|
|
287
233
|
`Set the API key in your environment or connect to a Stigmer Cloud deployment.`,
|
|
288
234
|
"LLM_MISSING_API_KEY",
|
|
289
235
|
);
|
|
290
236
|
}
|
|
291
|
-
|
|
292
|
-
if (!process.env.ANTHROPIC_API_KEY) {
|
|
237
|
+
if (provider === "anthropic" && !process.env.ANTHROPIC_API_KEY) {
|
|
293
238
|
throw ApplicationFailure.nonRetryable(
|
|
294
239
|
`ANTHROPIC_API_KEY is not set and no proxy is configured. ` +
|
|
295
240
|
`Set the API key in your environment or connect to a Stigmer Cloud deployment.`,
|
|
@@ -298,7 +243,17 @@ export async function callLlmAction(
|
|
|
298
243
|
}
|
|
299
244
|
}
|
|
300
245
|
|
|
301
|
-
|
|
246
|
+
// Anthropic requires an explicit maxTokens; preserve the 4096 default here
|
|
247
|
+
// (buildChatModel intentionally imposes none). resolvedModel is already an
|
|
248
|
+
// API id, so buildChatModel's resolve step is a no-op for it.
|
|
249
|
+
const { model } = await buildChatModel({
|
|
250
|
+
modelName: resolvedModel,
|
|
251
|
+
proxyEndpoint: proxyActive ? proxyEndpoint : undefined,
|
|
252
|
+
stigmerToken: proxyActive ? stigmerToken : undefined,
|
|
253
|
+
headerScope: { workflowExecutionId: executionId },
|
|
254
|
+
temperature: config.temperature,
|
|
255
|
+
maxTokens: provider === "anthropic" ? (config.max_tokens ?? 4096) : config.max_tokens,
|
|
256
|
+
});
|
|
302
257
|
|
|
303
258
|
const messages: (HumanMessage | SystemMessage)[] = [];
|
|
304
259
|
if (config.system_prompt) {
|
|
@@ -19,11 +19,10 @@
|
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
import { z } from "zod";
|
|
22
|
-
import { ChatOpenAI } from "@langchain/openai";
|
|
23
22
|
import { SystemMessage, HumanMessage } from "@langchain/core/messages";
|
|
24
23
|
import { activityStarted, activityFinished } from "../idle-watchdog.js";
|
|
25
24
|
import { getSummarizationModel } from "../shared/model-registry.js";
|
|
26
|
-
import {
|
|
25
|
+
import { buildChatModel } from "../shared/model-client.js";
|
|
27
26
|
import type { Config } from "../config.js";
|
|
28
27
|
|
|
29
28
|
const BATCH_SIZE = 40;
|
|
@@ -51,6 +50,15 @@ export interface ToolApprovalResult {
|
|
|
51
50
|
tool_name: string;
|
|
52
51
|
requires_approval: boolean;
|
|
53
52
|
message: string;
|
|
53
|
+
/**
|
|
54
|
+
* True when the connect-time fail-closed tightener force-gated this tool from
|
|
55
|
+
* its destructiveHint annotation (see applyDestructiveHintTightener), not the
|
|
56
|
+
* classifier. Persisted on ToolApprovalPolicy.from_destructive_hint so the
|
|
57
|
+
* runner attributes the gate to the annotation
|
|
58
|
+
* (ApprovalPolicySource.ANNOTATION_DESTRUCTIVE_TIGHTEN) instead of the
|
|
59
|
+
* classifier default. Absent/false on classifier and human-pinned entries.
|
|
60
|
+
*/
|
|
61
|
+
from_destructive_hint?: boolean;
|
|
54
62
|
}
|
|
55
63
|
|
|
56
64
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -89,9 +97,12 @@ safe (auto-approve) or sensitive (requires human approval before execution).
|
|
|
89
97
|
|
|
90
98
|
Classification rules:
|
|
91
99
|
|
|
92
|
-
1. READ-ONLY operations → requires_approval: false
|
|
93
|
-
Examples: search, list, get, query, read, fetch, describe, count
|
|
94
|
-
|
|
100
|
+
1. READ-ONLY / OBSERVATION operations → requires_approval: false
|
|
101
|
+
Examples: search, list, get, query, read, fetch, describe, count, view,
|
|
102
|
+
inspect, status, screenshot, snapshot, and "get state" style tools.
|
|
103
|
+
These only retrieve or observe data and have no side effects. When a tool
|
|
104
|
+
name begins with one of these verbs (e.g. get_app_state, list_apps), prefer
|
|
105
|
+
requires_approval: false unless its description clearly says it mutates.
|
|
95
106
|
|
|
96
107
|
2. CREATE or MODIFY operations → requires_approval: true
|
|
97
108
|
Examples: create, update, put, set, add, edit, modify, write, post, send
|
|
@@ -120,6 +131,19 @@ For tools that do NOT require approval, leave message empty.
|
|
|
120
131
|
|
|
121
132
|
Output one classification per tool, maintaining the input order.`;
|
|
122
133
|
|
|
134
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
135
|
+
// Read-only authority
|
|
136
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
137
|
+
//
|
|
138
|
+
// Read-only auto-approval is owned SOLELY by the trusted LLM classifier above.
|
|
139
|
+
// There is deliberately no deterministic name-based relax here: a tool name is
|
|
140
|
+
// an untrusted, server-supplied signal, and relaxing a gate on it is the unsafe
|
|
141
|
+
// direction (e.g. `get_and_delete_stale_records` leads with `get` yet deletes).
|
|
142
|
+
// A prior name-prefix heuristic was removed for exactly this reason. Annotations
|
|
143
|
+
// are likewise never trusted to relax — the connect workflow uses `destructiveHint`
|
|
144
|
+
// only to TIGHTEN (see applyDestructiveHintTightener). Anything the classifier
|
|
145
|
+
// does not affirmatively clear stays gated (fail closed).
|
|
146
|
+
|
|
123
147
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
124
148
|
// Core Classification Logic (no Temporal coupling)
|
|
125
149
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -168,7 +192,17 @@ export async function classifyTools(
|
|
|
168
192
|
batchIdx,
|
|
169
193
|
totalBatches: batches.length,
|
|
170
194
|
});
|
|
171
|
-
|
|
195
|
+
// Reconcile against the input batch: a tool the model omitted must fail
|
|
196
|
+
// closed, never slip through un-gated. Partial output is as dangerous as
|
|
197
|
+
// an outage for the missing tools.
|
|
198
|
+
const { reconciled, failedClosedCount } = reconcileBatchClassifications(batch, batchResult);
|
|
199
|
+
if (failedClosedCount > 0) {
|
|
200
|
+
console.warn(
|
|
201
|
+
`[ClassifyToolApprovals] Batch ${batchIdx + 1}/${batches.length} for '${serverName}': ` +
|
|
202
|
+
`${failedClosedCount} tool(s) missing from classifier output — failing closed (requires_approval=true)`,
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
allApprovals.push(...reconciled);
|
|
172
206
|
} catch (err) {
|
|
173
207
|
console.error(
|
|
174
208
|
`[ClassifyToolApprovals] Batch ${batchIdx + 1}/${batches.length} failed ` +
|
|
@@ -179,6 +213,10 @@ export async function classifyTools(
|
|
|
179
213
|
}
|
|
180
214
|
}
|
|
181
215
|
|
|
216
|
+
// The LLM classifier is the sole read-only authority: only tools it
|
|
217
|
+
// affirmatively cleared (requires_approval=false) are auto-approved. Tools it
|
|
218
|
+
// gated, omitted (reconciled to fail-closed), or that fell back on an outage
|
|
219
|
+
// all remain gated. No name-based relax runs here by design.
|
|
182
220
|
const approved = allApprovals.filter((a) => a.requires_approval);
|
|
183
221
|
|
|
184
222
|
console.log(
|
|
@@ -214,22 +252,22 @@ async function classifyBatch(params: ClassifyBatchParams): Promise<ToolApprovalR
|
|
|
214
252
|
|
|
215
253
|
const maxTokens = Math.max(MIN_MAX_TOKENS, batch.length * MAX_TOKENS_PER_TOOL);
|
|
216
254
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
:
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
temperature: 0,
|
|
255
|
+
// Provider is inferred from the resolved economy model — for an Anthropic
|
|
256
|
+
// primary this routes to Claude, not the hardcoded OpenAI path it used to.
|
|
257
|
+
const { model: llm } = await buildChatModel({
|
|
258
|
+
modelName: model,
|
|
259
|
+
proxyEndpoint,
|
|
260
|
+
stigmerToken: stigmerToken ?? undefined,
|
|
261
|
+
headerScope: { mcpServerId: mcpServerId ?? undefined },
|
|
225
262
|
maxTokens,
|
|
226
|
-
configuration: {
|
|
227
|
-
baseURL: resolvedBaseUrl,
|
|
228
|
-
defaultHeaders: headers,
|
|
229
|
-
},
|
|
230
263
|
});
|
|
231
264
|
|
|
232
|
-
|
|
265
|
+
// Explicit type param: BaseChatModel.withStructuredOutput widens to
|
|
266
|
+
// Record<string, any>, unlike the concrete SDK overloads, so pin the schema's
|
|
267
|
+
// output type here.
|
|
268
|
+
const structuredLlm = llm.withStructuredOutput<ClassifyToolApprovalsOutput>(
|
|
269
|
+
ClassifyToolApprovalsOutputSchema,
|
|
270
|
+
);
|
|
233
271
|
|
|
234
272
|
const toolsPayload = buildToolsPayload(batch);
|
|
235
273
|
const userPrompt =
|
|
@@ -264,7 +302,51 @@ async function classifyBatch(params: ClassifyBatchParams): Promise<ToolApprovalR
|
|
|
264
302
|
// Helpers
|
|
265
303
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
266
304
|
|
|
305
|
+
/**
|
|
306
|
+
* Reconcile a batch's LLM classifications against the tools that were actually
|
|
307
|
+
* sent. The canonical result is built from the INPUT batch, never the raw model
|
|
308
|
+
* output, so the classifier can never silently disarm a gate by omission:
|
|
309
|
+
*
|
|
310
|
+
* - A tool the model classified is kept as-is (its requires_approval + message).
|
|
311
|
+
* - A tool the model OMITTED fails closed (requires_approval=true) — a missing
|
|
312
|
+
* decision is treated exactly like an outage for that one tool.
|
|
313
|
+
* - A name the model returned that was never in the batch (a hallucinated or
|
|
314
|
+
* duplicated entry) is dropped — only real tools earn a policy.
|
|
315
|
+
*/
|
|
316
|
+
export function reconcileBatchClassifications(
|
|
317
|
+
batch: ToolDescriptor[],
|
|
318
|
+
llmResults: ToolApprovalResult[],
|
|
319
|
+
): { reconciled: ToolApprovalResult[]; failedClosedCount: number } {
|
|
320
|
+
const byName = new Map<string, ToolApprovalResult>();
|
|
321
|
+
for (const r of llmResults) {
|
|
322
|
+
if (r.tool_name) byName.set(r.tool_name, r);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const reconciled: ToolApprovalResult[] = [];
|
|
326
|
+
let failedClosedCount = 0;
|
|
327
|
+
for (const tool of batch) {
|
|
328
|
+
const classified = byName.get(tool.name);
|
|
329
|
+
if (classified) {
|
|
330
|
+
reconciled.push(classified);
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
reconciled.push({
|
|
334
|
+
tool_name: tool.name,
|
|
335
|
+
requires_approval: true,
|
|
336
|
+
message: `Execute ${tool.name}`,
|
|
337
|
+
});
|
|
338
|
+
failedClosedCount++;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return { reconciled, failedClosedCount };
|
|
342
|
+
}
|
|
343
|
+
|
|
267
344
|
export function fallbackApprovals(tools: ToolDescriptor[]): ToolApprovalResult[] {
|
|
345
|
+
// Full fail-closed: when the classifier batch throws, every tool in it is
|
|
346
|
+
// gated. With no trusted classification we cannot safely auto-approve anything
|
|
347
|
+
// — a name is an untrusted signal — so the entire batch requires approval
|
|
348
|
+
// until a reconnect re-classifies it. The accepted tradeoff is that, during a
|
|
349
|
+
// rare classifier outage, benign read tools briefly prompt for approval.
|
|
268
350
|
return tools.map((tool) => ({
|
|
269
351
|
tool_name: tool.name,
|
|
270
352
|
requires_approval: true,
|
|
@@ -47,6 +47,18 @@ export interface DiscoveredToolResult {
|
|
|
47
47
|
name: string;
|
|
48
48
|
description: string;
|
|
49
49
|
inputSchema?: Record<string, unknown> | null;
|
|
50
|
+
// MCP server-supplied behaviour hints. UNTRUSTED — the MCP spec is explicit
|
|
51
|
+
// that clients must never make tool-use decisions on annotations from
|
|
52
|
+
// untrusted servers. We therefore consume them only to TIGHTEN gating
|
|
53
|
+
// (destructiveHint → force-gate), never to relax it. Kept in-memory for the
|
|
54
|
+
// connect workflow's tightener; deliberately excluded from `toolsFingerprint`
|
|
55
|
+
// / `toolSignature` so incremental-classification reuse stays content-stable.
|
|
56
|
+
annotations?: DiscoveredToolAnnotations | null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface DiscoveredToolAnnotations {
|
|
60
|
+
readOnlyHint?: boolean;
|
|
61
|
+
destructiveHint?: boolean;
|
|
50
62
|
}
|
|
51
63
|
|
|
52
64
|
export interface DiscoveredResourceTemplateResult {
|
|
@@ -62,6 +74,11 @@ export interface DiscoverMcpServerOutput {
|
|
|
62
74
|
previousToolsFingerprint: string;
|
|
63
75
|
previousToolApprovals: ToolApprovalDict[];
|
|
64
76
|
newToolsFingerprint: string;
|
|
77
|
+
// Full definitions of the tools discovered on the previous connect, read from
|
|
78
|
+
// status.discovered_capabilities. The connect workflow diffs these against the
|
|
79
|
+
// freshly discovered tools to reuse prior approval decisions for unchanged
|
|
80
|
+
// tools and (re)classify only the new or changed ones. Empty on first connect.
|
|
81
|
+
previousTools: DiscoveredToolResult[];
|
|
65
82
|
}
|
|
66
83
|
|
|
67
84
|
export interface ToolApprovalDict {
|
|
@@ -97,11 +114,12 @@ export function toolsFingerprint(tools: DiscoveredToolResult[]): string {
|
|
|
97
114
|
interface PreviousState {
|
|
98
115
|
fingerprint: string;
|
|
99
116
|
toolApprovals: ToolApprovalDict[];
|
|
117
|
+
tools: DiscoveredToolResult[];
|
|
100
118
|
}
|
|
101
119
|
|
|
102
120
|
export function extractPreviousState(mcpServer: McpServer): PreviousState {
|
|
103
121
|
const status = mcpServer.status;
|
|
104
|
-
if (!status) return { fingerprint: "", toolApprovals: [] };
|
|
122
|
+
if (!status) return { fingerprint: "", toolApprovals: [], tools: [] };
|
|
105
123
|
|
|
106
124
|
const caps = status.discoveredCapabilities;
|
|
107
125
|
const prevTools: DiscoveredToolResult[] = [];
|
|
@@ -131,6 +149,7 @@ export function extractPreviousState(mcpServer: McpServer): PreviousState {
|
|
|
131
149
|
return {
|
|
132
150
|
fingerprint: toolsFingerprint(prevTools),
|
|
133
151
|
toolApprovals,
|
|
152
|
+
tools: prevTools,
|
|
134
153
|
};
|
|
135
154
|
}
|
|
136
155
|
|
|
@@ -244,6 +263,7 @@ export async function discoverMcpServer(
|
|
|
244
263
|
previousToolsFingerprint: previousState.fingerprint,
|
|
245
264
|
previousToolApprovals: previousState.toolApprovals,
|
|
246
265
|
newToolsFingerprint: newFp,
|
|
266
|
+
previousTools: previousState.tools,
|
|
247
267
|
};
|
|
248
268
|
}
|
|
249
269
|
|
|
@@ -323,6 +343,14 @@ async function connectAndDiscover(
|
|
|
323
343
|
inputSchema: tool.inputSchema
|
|
324
344
|
? (tool.inputSchema as Record<string, unknown>)
|
|
325
345
|
: null,
|
|
346
|
+
// Capture only the two hints the tightener uses. Stored verbatim from
|
|
347
|
+
// the server (untrusted): used to tighten, never to relax.
|
|
348
|
+
annotations: tool.annotations
|
|
349
|
+
? {
|
|
350
|
+
readOnlyHint: tool.annotations.readOnlyHint,
|
|
351
|
+
destructiveHint: tool.annotations.destructiveHint,
|
|
352
|
+
}
|
|
353
|
+
: null,
|
|
326
354
|
});
|
|
327
355
|
}
|
|
328
356
|
|