@stigmer/runner 3.10.0 → 3.11.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/README.md +12 -1
- package/dist/.build-fingerprint +1 -1
- package/dist/activities/call-llm.js +9 -10
- package/dist/activities/call-llm.js.map +1 -1
- package/dist/activities/classify-tool-approvals.d.ts +2 -1
- package/dist/activities/classify-tool-approvals.js +28 -2
- package/dist/activities/classify-tool-approvals.js.map +1 -1
- package/dist/activities/discover-mcp-server.d.ts +32 -0
- package/dist/activities/discover-mcp-server.js +162 -27
- package/dist/activities/discover-mcp-server.js.map +1 -1
- package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.d.ts +8 -0
- package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.js +1 -1
- package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.js.map +1 -1
- package/dist/activities/execute-cursor/approval-state.d.ts +28 -2
- package/dist/activities/execute-cursor/approval-state.js +7 -1
- package/dist/activities/execute-cursor/approval-state.js.map +1 -1
- package/dist/activities/execute-cursor/attachment-resolver.d.ts +14 -0
- package/dist/activities/execute-cursor/attachment-resolver.js +18 -4
- package/dist/activities/execute-cursor/attachment-resolver.js.map +1 -1
- package/dist/activities/execute-cursor/blueprint-resolver.d.ts +1 -9
- package/dist/activities/execute-cursor/blueprint-resolver.js +6 -22
- package/dist/activities/execute-cursor/blueprint-resolver.js.map +1 -1
- package/dist/activities/execute-cursor/env-resolver.js +3 -1
- package/dist/activities/execute-cursor/env-resolver.js.map +1 -1
- package/dist/activities/execute-cursor/error-classifier.d.ts +40 -3
- package/dist/activities/execute-cursor/error-classifier.js +81 -3
- package/dist/activities/execute-cursor/error-classifier.js.map +1 -1
- package/dist/activities/execute-cursor/extract-structured-output.d.ts +29 -0
- package/dist/activities/execute-cursor/extract-structured-output.js +58 -0
- package/dist/activities/execute-cursor/extract-structured-output.js.map +1 -0
- package/dist/activities/execute-cursor/hook-script.d.ts +14 -3
- package/dist/activities/execute-cursor/hook-script.js +72 -10
- package/dist/activities/execute-cursor/hook-script.js.map +1 -1
- package/dist/activities/execute-cursor/index.d.ts +5 -1
- package/dist/activities/execute-cursor/index.js +51 -57
- package/dist/activities/execute-cursor/index.js.map +1 -1
- package/dist/activities/execute-cursor/mcp-resolver.d.ts +24 -1
- package/dist/activities/execute-cursor/mcp-resolver.js +5 -2
- package/dist/activities/execute-cursor/mcp-resolver.js.map +1 -1
- package/dist/activities/execute-cursor/prompt-builder.d.ts +18 -4
- package/dist/activities/execute-cursor/prompt-builder.js +12 -7
- package/dist/activities/execute-cursor/prompt-builder.js.map +1 -1
- package/dist/activities/execute-cursor/turn-stream.js +4 -1
- package/dist/activities/execute-cursor/turn-stream.js.map +1 -1
- package/dist/activities/execute-deep-agent/attachment-injector.d.ts +18 -1
- package/dist/activities/execute-deep-agent/attachment-injector.js +68 -23
- package/dist/activities/execute-deep-agent/attachment-injector.js.map +1 -1
- package/dist/activities/execute-deep-agent/environment.js +3 -1
- package/dist/activities/execute-deep-agent/environment.js.map +1 -1
- package/dist/activities/execute-deep-agent/index.js +15 -0
- package/dist/activities/execute-deep-agent/index.js.map +1 -1
- package/dist/activities/execute-deep-agent/prompt-builder.d.ts +7 -7
- package/dist/activities/execute-deep-agent/prompt-builder.js +8 -2
- package/dist/activities/execute-deep-agent/prompt-builder.js.map +1 -1
- package/dist/activities/execute-deep-agent/setup.d.ts +10 -0
- package/dist/activities/execute-deep-agent/setup.js +55 -23
- package/dist/activities/execute-deep-agent/setup.js.map +1 -1
- package/dist/activities/execute-deep-agent/subagent-transformer.d.ts +18 -1
- package/dist/activities/execute-deep-agent/subagent-transformer.js +8 -1
- package/dist/activities/execute-deep-agent/subagent-transformer.js.map +1 -1
- package/dist/activities/execute-deep-agent/subagent-wiring.d.ts +11 -4
- package/dist/activities/execute-deep-agent/subagent-wiring.js +13 -4
- package/dist/activities/execute-deep-agent/subagent-wiring.js.map +1 -1
- package/dist/activities/hydrate-workflow-execution.js +3 -1
- package/dist/activities/hydrate-workflow-execution.js.map +1 -1
- package/dist/activities/workflow-event-activities.d.ts +28 -10
- package/dist/activities/workflow-event-activities.js +87 -58
- package/dist/activities/workflow-event-activities.js.map +1 -1
- package/dist/claimcheck/payload-codec.js +21 -1
- package/dist/claimcheck/payload-codec.js.map +1 -1
- package/dist/client/stigmer-client.d.ts +9 -4
- package/dist/client/stigmer-client.js +28 -15
- package/dist/client/stigmer-client.js.map +1 -1
- package/dist/encryption/config.d.ts +32 -0
- package/dist/encryption/config.js +68 -0
- package/dist/encryption/config.js.map +1 -0
- package/dist/encryption/index.d.ts +3 -0
- package/dist/encryption/index.js +3 -0
- package/dist/encryption/index.js.map +1 -0
- package/dist/encryption/payload-codec.d.ts +41 -0
- package/dist/encryption/payload-codec.js +130 -0
- package/dist/encryption/payload-codec.js.map +1 -0
- package/dist/payload-codecs.d.ts +16 -0
- package/dist/payload-codecs.js +38 -0
- package/dist/payload-codecs.js.map +1 -0
- package/dist/preflight.d.ts +31 -0
- package/dist/preflight.js +43 -0
- package/dist/preflight.js.map +1 -1
- package/dist/runner-manager.js +5 -15
- package/dist/runner-manager.js.map +1 -1
- package/dist/runner.js +5 -16
- package/dist/runner.js.map +1 -1
- package/dist/shared/approval-policy.d.ts +9 -3
- package/dist/shared/approval-policy.js +15 -6
- package/dist/shared/approval-policy.js.map +1 -1
- package/dist/shared/attachment-naming.d.ts +53 -0
- package/dist/shared/attachment-naming.js +59 -0
- package/dist/shared/attachment-naming.js.map +1 -0
- package/dist/shared/caller-identity.d.ts +23 -2
- package/dist/shared/caller-identity.js +36 -5
- package/dist/shared/caller-identity.js.map +1 -1
- package/dist/shared/channel-attachment.js +1 -0
- package/dist/shared/channel-attachment.js.map +1 -1
- package/dist/shared/checkpointer/http-saver.d.ts +42 -1
- package/dist/shared/checkpointer/http-saver.js +96 -8
- package/dist/shared/checkpointer/http-saver.js.map +1 -1
- package/dist/shared/conversation-attachment.js +1 -0
- package/dist/shared/conversation-attachment.js.map +1 -1
- package/dist/shared/datastore-attachment.d.ts +50 -7
- package/dist/shared/datastore-attachment.js +93 -11
- package/dist/shared/datastore-attachment.js.map +1 -1
- package/dist/shared/http-retry.d.ts +43 -0
- package/dist/shared/http-retry.js +50 -0
- package/dist/shared/http-retry.js.map +1 -0
- package/dist/shared/llm-backend.d.ts +275 -0
- package/dist/shared/llm-backend.js +425 -0
- package/dist/shared/llm-backend.js.map +1 -0
- package/dist/shared/llm-proxy.d.ts +8 -0
- package/dist/shared/llm-proxy.js +15 -0
- package/dist/shared/llm-proxy.js.map +1 -1
- package/dist/shared/mcp-enabled-tools.d.ts +57 -0
- package/dist/shared/mcp-enabled-tools.js +86 -0
- package/dist/shared/mcp-enabled-tools.js.map +1 -0
- package/dist/shared/mcp-manager.d.ts +3 -1
- package/dist/shared/mcp-manager.js +17 -4
- package/dist/shared/mcp-manager.js.map +1 -1
- package/dist/shared/mcp-resolver.d.ts +39 -2
- package/dist/shared/mcp-resolver.js +38 -2
- package/dist/shared/mcp-resolver.js.map +1 -1
- package/dist/shared/model-client.d.ts +12 -5
- package/dist/shared/model-client.js +138 -18
- package/dist/shared/model-client.js.map +1 -1
- package/dist/shared/model-error.js +198 -5
- package/dist/shared/model-error.js.map +1 -1
- package/dist/shared/plan-mode-permissions.d.ts +26 -0
- package/dist/shared/plan-mode-permissions.js +28 -0
- package/dist/shared/plan-mode-permissions.js.map +1 -0
- package/dist/worker.d.ts +2 -1
- package/dist/worker.js +2 -4
- package/dist/worker.js.map +1 -1
- package/dist/workflow-engine/types.d.ts +18 -0
- package/dist/workflow-engine/types.js.map +1 -1
- package/dist/workflows/call-agent-orchestrator.d.ts +9 -0
- package/dist/workflows/call-agent-orchestrator.js +1 -0
- package/dist/workflows/call-agent-orchestrator.js.map +1 -1
- package/dist/workflows/connect-mcp-server.js +7 -0
- package/dist/workflows/connect-mcp-server.js.map +1 -1
- package/dist/workflows/engine-core.js +23 -2
- package/dist/workflows/engine-core.js.map +1 -1
- package/dist/workflows/execute-from-execution.d.ts +1 -1
- package/dist/workflows/execute-from-execution.js +11 -1
- package/dist/workflows/execute-from-execution.js.map +1 -1
- package/package.json +8 -2
- package/src/__tests__/claimcheck-codec.test.ts +36 -0
- package/src/__tests__/encryption-codec.test.ts +234 -0
- package/src/__tests__/fixtures/encrypted-payload-fixture.json +15 -0
- package/src/__tests__/history-encryption-e2e.test.ts +243 -0
- package/src/__tests__/preflight.test.ts +50 -2
- package/src/activities/__tests__/call-llm.test.ts +75 -0
- package/src/activities/__tests__/classify-tool-approvals.test.ts +117 -1
- package/src/activities/__tests__/discover-mcp-server.hang.test.ts +103 -0
- package/src/activities/__tests__/discover-mcp-server.test.ts +203 -0
- package/src/activities/__tests__/workflow-event-activities.test.ts +107 -8
- package/src/activities/call-llm.ts +9 -16
- package/src/activities/classify-tool-approvals.ts +34 -4
- package/src/activities/discover-mcp-server.ts +190 -32
- package/src/activities/execute-cursor/__test-utils__/cursor-hook-harness.ts +9 -0
- package/src/activities/execute-cursor/__tests__/approval-gate.test.ts +14 -0
- package/src/activities/execute-cursor/__tests__/attachment-resolver.test.ts +53 -0
- package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +40 -14
- package/src/activities/execute-cursor/__tests__/error-classifier-extraction.test.ts +208 -0
- package/src/activities/execute-cursor/__tests__/extract-structured-output.test.ts +120 -0
- package/src/activities/execute-cursor/__tests__/hook-script.test.ts +93 -0
- package/src/activities/execute-cursor/__tests__/mcp-resolver.test.ts +125 -0
- package/src/activities/execute-cursor/__tests__/prompt-builder-delegation.test.ts +1 -1
- package/src/activities/execute-cursor/__tests__/turn-stream.test.ts +13 -0
- package/src/activities/execute-cursor/approval-state.ts +30 -1
- package/src/activities/execute-cursor/attachment-resolver.ts +31 -3
- package/src/activities/execute-cursor/blueprint-resolver.ts +7 -27
- package/src/activities/execute-cursor/env-resolver.ts +3 -1
- package/src/activities/execute-cursor/error-classifier.ts +91 -4
- package/src/activities/execute-cursor/extract-structured-output.ts +72 -0
- package/src/activities/execute-cursor/hook-script.ts +74 -10
- package/src/activities/execute-cursor/index.ts +55 -71
- package/src/activities/execute-cursor/mcp-resolver.ts +36 -2
- package/src/activities/execute-cursor/prompt-builder.ts +34 -9
- package/src/activities/execute-cursor/turn-stream.ts +5 -2
- package/src/activities/execute-deep-agent/__tests__/attachment-injector.test.ts +110 -8
- package/src/activities/execute-deep-agent/__tests__/datastore-degradation.test.ts +104 -0
- package/src/activities/execute-deep-agent/__tests__/hitl-reject.test.ts +2 -0
- package/src/activities/execute-deep-agent/__tests__/hitl-resume-approve-all.test.ts +1 -0
- package/src/activities/execute-deep-agent/__tests__/hitl-resume-history.test.ts +1 -0
- package/src/activities/execute-deep-agent/__tests__/prompt-builder.test.ts +34 -5
- package/src/activities/execute-deep-agent/__tests__/sequential-gate-resume.test.ts +1 -0
- package/src/activities/execute-deep-agent/__tests__/subagent-plan-mode-permissions.test.ts +173 -0
- package/src/activities/execute-deep-agent/__tests__/subagent-wiring.test.ts +12 -7
- package/src/activities/execute-deep-agent/attachment-injector.ts +94 -30
- package/src/activities/execute-deep-agent/environment.ts +3 -1
- package/src/activities/execute-deep-agent/index.ts +20 -0
- package/src/activities/execute-deep-agent/prompt-builder.ts +20 -10
- package/src/activities/execute-deep-agent/setup.ts +76 -28
- package/src/activities/execute-deep-agent/subagent-transformer.ts +23 -1
- package/src/activities/execute-deep-agent/subagent-wiring.ts +14 -4
- package/src/activities/hydrate-workflow-execution.ts +3 -1
- package/src/activities/workflow-event-activities.ts +96 -69
- package/src/claimcheck/payload-codec.ts +33 -1
- package/src/client/__tests__/stigmer-client.test.ts +8 -8
- package/src/client/stigmer-client.ts +32 -18
- package/src/encryption/config.ts +91 -0
- package/src/encryption/index.ts +3 -0
- package/src/encryption/payload-codec.ts +152 -0
- package/src/payload-codecs.ts +56 -0
- package/src/preflight.ts +45 -0
- package/src/runner-manager.ts +6 -24
- package/src/runner.ts +6 -25
- package/src/shared/__tests__/approval-policy.test.ts +82 -39
- package/src/shared/__tests__/attachment-naming.test.ts +159 -0
- package/src/shared/__tests__/bedrock-adapter.test.ts +213 -0
- package/src/shared/__tests__/bedrock-seam.test.ts +390 -0
- package/src/shared/__tests__/caller-identity.test.ts +25 -0
- package/src/shared/__tests__/channel-attachment.test.ts +1 -1
- package/src/shared/__tests__/connect-backfill.test.ts +1 -0
- package/src/shared/__tests__/conversation-attachment.test.ts +1 -1
- package/src/shared/__tests__/datastore-attachment.test.ts +129 -1
- package/src/shared/__tests__/foundry-adapter.test.ts +276 -0
- package/src/shared/__tests__/foundry-seam.test.ts +482 -0
- package/src/shared/__tests__/http-retry.test.ts +67 -0
- package/src/shared/__tests__/llm-backend.test.ts +616 -0
- package/src/shared/__tests__/mcp-enabled-tools.test.ts +86 -0
- package/src/shared/__tests__/mcp-manager.test.ts +84 -2
- package/src/shared/__tests__/mcp-resolver.test.ts +146 -3
- package/src/shared/__tests__/model-client.test.ts +154 -0
- package/src/shared/__tests__/model-error.test.ts +289 -1
- package/src/shared/__tests__/synthesized-attachment.test.ts +1 -0
- package/src/shared/__tests__/vertex-adapter.test.ts +169 -0
- package/src/shared/__tests__/vertex-seam.test.ts +295 -0
- package/src/shared/approval-policy.ts +14 -7
- package/src/shared/attachment-naming.ts +78 -0
- package/src/shared/caller-identity.ts +40 -5
- package/src/shared/channel-attachment.ts +1 -0
- package/src/shared/checkpointer/__tests__/http-saver.test.ts +196 -1
- package/src/shared/checkpointer/http-saver.ts +117 -9
- package/src/shared/conversation-attachment.ts +1 -0
- package/src/shared/datastore-attachment.ts +106 -11
- package/src/shared/http-retry.ts +50 -0
- package/src/shared/llm-backend.ts +544 -0
- package/src/shared/llm-proxy.ts +15 -0
- package/src/shared/mcp-enabled-tools.ts +105 -0
- package/src/shared/mcp-manager.ts +21 -4
- package/src/shared/mcp-resolver.ts +73 -2
- package/src/shared/model-client.ts +161 -19
- package/src/shared/model-error.ts +222 -4
- package/src/shared/plan-mode-permissions.ts +30 -0
- package/src/worker.ts +4 -5
- package/src/workflow-engine/types.ts +18 -0
- package/src/workflows/__tests__/execute-serverless-workflow.test.ts +68 -2
- package/src/workflows/call-agent-orchestrator.ts +10 -0
- package/src/workflows/connect-mcp-server.ts +7 -0
- package/src/workflows/engine-core.ts +23 -2
- package/src/workflows/execute-from-execution.ts +12 -2
|
@@ -32,6 +32,8 @@
|
|
|
32
32
|
* - "secret" — DD-26 secret hard-block: the agent continues, no pause.
|
|
33
33
|
* - "capture-error" — CAS staging failed, write kept on the deny-gate.
|
|
34
34
|
* - "fail-closed" — approval state file missing, everything gated denies.
|
|
35
|
+
* - "disabled" — enabled_tools manifest exclusion (issue #350): the
|
|
36
|
+
* agent continues, no pause, permanent for the run.
|
|
35
37
|
* Only approval-kind records carry the captured tool_input: a secret write's
|
|
36
38
|
* content must never be persisted (DD-26), a capture-error's content is
|
|
37
39
|
* UNCLASSIFIED (the staging error means secret classification may never have
|
|
@@ -84,8 +86,16 @@
|
|
|
84
86
|
* set, allow the rest" — matching the native harness and avoiding denial of
|
|
85
87
|
* auto-approved MCP tools (which are absent from mcpToolPolicies):
|
|
86
88
|
* 0. Scope guard: not the runner's own agent → allow (never touch the ledger)
|
|
87
|
-
* 1. Missing state file → deny (fail-closed)
|
|
88
|
-
*
|
|
89
|
+
* 1. Missing state file → deny (fail-closed)
|
|
90
|
+
* 1a. beforeMCPExecution event → tool excluded by the server's
|
|
91
|
+
* mcpServerEnabledTools allow-list → record kind "disabled", deny.
|
|
92
|
+
* Deliberately BEFORE autoApproveAll and the grant checks: enabled_tools
|
|
93
|
+
* is a capability manifest, not an approval gate (issue #350) — no bypass
|
|
94
|
+
* may resurrect a disabled tool, and no human may be offered "approve" on
|
|
95
|
+
* one. Server-scoped via the payload's mcp_server_name; an absent slug in
|
|
96
|
+
* the map means unrestricted.
|
|
97
|
+
* 1b. autoApproveAll (the pre-armed spec.auto_approve_all global bypass) →
|
|
98
|
+
* allow
|
|
89
99
|
* 2. beforeMCPExecution event → MCP tool present in mcpToolPolicies
|
|
90
100
|
* (require-approval):
|
|
91
101
|
* a. name token in approvedGrantTokens → allow (reinvocation grant)
|
|
@@ -155,6 +165,23 @@ const SECRET_BLOCKED_AGENT_MESSAGE =
|
|
|
155
165
|
"hooks. Do not retry this write or attempt a workaround; the write will not be " +
|
|
156
166
|
"applied. Continue with the rest of the task.";
|
|
157
167
|
|
|
168
|
+
// Shown to the model when an MCP tool call is denied because the agent's
|
|
169
|
+
// enabled_tools manifest excludes the tool (issue #350). Like
|
|
170
|
+
// SECRET_BLOCKED_AGENT_MESSAGE this must NOT promise a resume — the exclusion
|
|
171
|
+
// is permanent for the run and mode-independent (it is a capability manifest,
|
|
172
|
+
// not an approval, so nothing can be granted). Same embedding constraint
|
|
173
|
+
// (single-quoted bash echo of a JSON object): no double quotes, apostrophes,
|
|
174
|
+
// or backslashes.
|
|
175
|
+
const DISABLED_TOOL_AGENT_MESSAGE =
|
|
176
|
+
"This tool is not enabled for this agent: the MCP server exposes it, but the " +
|
|
177
|
+
"agent manifest (enabled_tools) excludes it. This is the platform capability " +
|
|
178
|
+
"manifest working as intended — it is not an error and not a Cursor " +
|
|
179
|
+
"misconfiguration, so never tell the user to change Cursor settings or enable " +
|
|
180
|
+
"hooks. Do not retry this tool or attempt a workaround; it will stay " +
|
|
181
|
+
"unavailable for this entire run. Use a different tool or adapt your plan, " +
|
|
182
|
+
"and if the task cannot proceed without it, tell the user plainly what was " +
|
|
183
|
+
"unavailable.";
|
|
184
|
+
|
|
158
185
|
/**
|
|
159
186
|
* Build the bash `case` arms that map an incoming hook `tool_name` to its
|
|
160
187
|
* canonical approval category. Generated from approval-policy.ts so the hook and
|
|
@@ -209,14 +236,17 @@ function buildContentDigestScript(): string {
|
|
|
209
236
|
* Build the inline Node.js identity extractor embedded in the hook script.
|
|
210
237
|
*
|
|
211
238
|
* Parses the hook's stdin JSON properly (the bash fallback's grep truncates
|
|
212
|
-
* string values at the first escaped quote) and emits
|
|
239
|
+
* string values at the first escaped quote) and emits NINE lines: tool_name,
|
|
213
240
|
* canonical category, coarse identity token, MCP name-token, hook_event_name
|
|
214
241
|
* (the event discriminator: `preToolUse` for built-ins, `beforeMCPExecution`
|
|
215
242
|
* for MCP), base64(JSON(tool_input)) — the authoritative pre-execution args the
|
|
216
|
-
* runner overlays onto the gated tool call for the approval preview —
|
|
243
|
+
* runner overlays onto the gated tool call for the approval preview — the
|
|
217
244
|
* CONTENT token (base64(category \n salient \n contentDigest), empty when the
|
|
218
|
-
* tool has no edit content)
|
|
219
|
-
*
|
|
245
|
+
* tool has no edit content), base64(salient), and mcp_server_name (the MCP
|
|
246
|
+
* server slug the beforeMCPExecution payload carries; empty for built-ins) —
|
|
247
|
+
* the server scope for the enabled_tools manifest arm. The token encodings
|
|
248
|
+
* must stay byte-identical to grantToken()/contentToken() in
|
|
249
|
+
* approval-state.ts.
|
|
220
250
|
*
|
|
221
251
|
* Authored as a single-quoted bash string, so the JS must not contain single
|
|
222
252
|
* quotes. The category map, salient field list, and edit/content field lists are
|
|
@@ -247,6 +277,7 @@ function buildNodeIdentityScript(): string {
|
|
|
247
277
|
// is "" for a non-edit tool, in which case the content token (line 7) is "".
|
|
248
278
|
buildContentDigestScript(),
|
|
249
279
|
`const ev=typeof t.hook_event_name==="string"?t.hook_event_name:"";`,
|
|
280
|
+
`const srv=typeof t.mcp_server_name==="string"?t.mcp_server_name:"";`,
|
|
250
281
|
// Line 6 is base64(JSON(tool_input)): the AUTHORITATIVE pre-execution args
|
|
251
282
|
// the runner overlays onto the gated tool call so the approval card can show
|
|
252
283
|
// the proposed change before the user approves. Base64 keeps the bash side
|
|
@@ -255,8 +286,10 @@ function buildNodeIdentityScript(): string {
|
|
|
255
286
|
// grant the runner authorizes for a file edit. Line 8 is base64(salient) —
|
|
256
287
|
// the raw resource value (file path / command) capture mode needs to run
|
|
257
288
|
// `git check-ignore` on a file path; base64 keeps newlines/quotes out of the
|
|
258
|
-
// line-oriented bash parse.
|
|
259
|
-
|
|
289
|
+
// line-oriented bash parse. Line 9 is mcp_server_name — the server scope
|
|
290
|
+
// the enabled_tools manifest arm matches against mcpServerEnabledTools
|
|
291
|
+
// (a bare slug, never quoted/escaped, so it rides as a plain line).
|
|
292
|
+
`process.stdout.write(name+"\\n"+cat+"\\n"+b(cat+"\\n"+s)+"\\n"+b(name+"\\n")+"\\n"+ev+"\\n"+b(JSON.stringify(a))+"\\n"+(dig?b(cat+"\\n"+s+"\\n"+dig):"")+"\\n"+b(s)+"\\n"+srv);`,
|
|
260
293
|
].join("");
|
|
261
294
|
}
|
|
262
295
|
|
|
@@ -279,7 +312,8 @@ function buildNodeIdentityScript(): string {
|
|
|
279
312
|
*
|
|
280
313
|
* From the pointer the script reads the current turn's approval-state file (the
|
|
281
314
|
* single source of truth for the dynamic inputs: autoApproveAll, leasedCategories,
|
|
282
|
-
* mcpToolPolicies, approvedGrantTokens), denial ledger,
|
|
315
|
+
* mcpToolPolicies, mcpServerEnabledTools, approvedGrantTokens), denial ledger,
|
|
316
|
+
* and runner PID. The
|
|
283
317
|
* static policy (which built-ins are gated, their categories, the salient arg
|
|
284
318
|
* fields) is baked at generation time from approval-policy.ts.
|
|
285
319
|
*
|
|
@@ -440,6 +474,9 @@ if [ -n "$IDENTITY" ]; then
|
|
|
440
474
|
# Raw salient (base64) — the file path / command. Capture mode decodes it to
|
|
441
475
|
# run git check-ignore on a file path.
|
|
442
476
|
SALIENT=$(printf '%s\\n' "$IDENTITY" | sed -n 8p | base64 -d 2>/dev/null || true)
|
|
477
|
+
# MCP server slug (beforeMCPExecution payloads only; empty for built-ins) —
|
|
478
|
+
# the server scope for the enabled_tools manifest arm.
|
|
479
|
+
MCP_SERVER=$(printf '%s\\n' "$IDENTITY" | sed -n 9p)
|
|
443
480
|
else
|
|
444
481
|
# Fallback when the Node binary cannot run: grep/cut extraction. Best-effort
|
|
445
482
|
# only — '"field":"[^"]*"' truncates at the first JSON-escaped quote, so the
|
|
@@ -449,6 +486,10 @@ else
|
|
|
449
486
|
# would otherwise abort the script and emit no decision.
|
|
450
487
|
TOOL_NAME=$(echo "$INPUT" | grep -o '"tool_name":"[^"]*"' | head -1 | cut -d'"' -f4 || true)
|
|
451
488
|
HOOK_EVENT=$(echo "$INPUT" | grep -o '"hook_event_name":"[^"]*"' | head -1 | cut -d'"' -f4 || true)
|
|
489
|
+
# Server slugs are plain identifiers (no JSON-escaped quotes), so the grep
|
|
490
|
+
# fallback extracts mcp_server_name reliably — the manifest arm keeps its
|
|
491
|
+
# full precision even without the Node binary.
|
|
492
|
+
MCP_SERVER=$(echo "$INPUT" | grep -o '"mcp_server_name":"[^"]*"' | head -1 | cut -d'"' -f4 || true)
|
|
452
493
|
SALIENT=""
|
|
453
494
|
for field in ${salientFields}; do
|
|
454
495
|
v=$(echo "$INPUT" | grep -o "\\"$field\\":\\"[^\\"]*\\"" | head -1 | cut -d'"' -f4 || true)
|
|
@@ -588,7 +629,30 @@ if [ "$CAPTURE_IGNORED" = "true" ] && [ "$CATEGORY" = "write" ] && [ -n "$SALIEN
|
|
|
588
629
|
fi
|
|
589
630
|
fi
|
|
590
631
|
|
|
591
|
-
# ---
|
|
632
|
+
# --- 1a. MCP capability manifest: enabled_tools (issue #350) ---
|
|
633
|
+
# Runs BEFORE the auto-approve-all shortcut and every grant check because
|
|
634
|
+
# enabled_tools is a capability manifest, not an approval gate: no bypass may
|
|
635
|
+
# resurrect a disabled tool and no human may be offered approval on one.
|
|
636
|
+
# mcpServerEnabledTools holds ONLY restricted servers (an absent slug means
|
|
637
|
+
# unrestricted, so this arm is inert for the common case). Matching is
|
|
638
|
+
# server-scoped via the payload's mcp_server_name — equal tool names on
|
|
639
|
+
# different servers cannot cross-grant — and the quoted-name membership check
|
|
640
|
+
# is exact, never a substring match. Kind "disabled": attributable,
|
|
641
|
+
# non-pausing (the model adapts, same consumer semantics as "secret"), and
|
|
642
|
+
# permanent for the run.
|
|
643
|
+
if [ "$HOOK_EVENT" = "beforeMCPExecution" ] && [ -n "$MCP_SERVER" ] && [ -n "$TOOL_NAME" ]; then
|
|
644
|
+
ENABLED_MAP=$(echo "$STATE" | grep -o '"mcpServerEnabledTools":{[^}]*}' | head -1 || true)
|
|
645
|
+
if [ -n "$ENABLED_MAP" ]; then
|
|
646
|
+
SERVER_ENABLED=$(echo "$ENABLED_MAP" | grep -o "\\"$MCP_SERVER\\":\\[[^]]*\\]" | head -1 || true)
|
|
647
|
+
if [ -n "$SERVER_ENABLED" ] && ! echo "$SERVER_ENABLED" | grep -qF "\\"$TOOL_NAME\\""; then
|
|
648
|
+
record_denial "$MCP_TOKEN" "disabled"
|
|
649
|
+
echo '{"permission":"deny","agent_message":"${DISABLED_TOOL_AGENT_MESSAGE}","user_message":"Tool not enabled for this agent: '"$TOOL_NAME"'"}'
|
|
650
|
+
exit 0
|
|
651
|
+
fi
|
|
652
|
+
fi
|
|
653
|
+
fi
|
|
654
|
+
|
|
655
|
+
# --- 1b. Auto-approve all ---
|
|
592
656
|
if echo "$STATE" | grep -q '"autoApproveAll":true'; then
|
|
593
657
|
echo '{"permission":"allow"}'
|
|
594
658
|
exit 0
|
|
@@ -84,6 +84,7 @@ import {
|
|
|
84
84
|
import { injectSynthesizedAttachment } from "../../shared/synthesized-attachment.js";
|
|
85
85
|
import { mergeApprovalPolicies } from "./approval-policy.js";
|
|
86
86
|
import { deriveActiveLeases, isUnattendedApprovalMode } from "../../shared/approval-policy.js";
|
|
87
|
+
import { enabledToolsBySlug } from "../../shared/mcp-enabled-tools.js";
|
|
87
88
|
import { backfillMcpServersIfNeeded } from "./connect-backfill.js";
|
|
88
89
|
import { resolveExecutionEnv } from "./env-resolver.js";
|
|
89
90
|
import { resolveBlueprint } from "./blueprint-resolver.js";
|
|
@@ -139,7 +140,7 @@ import { StreamingUsageSummarySchema } from "@stigmer/protos/ai/stigmer/agentic/
|
|
|
139
140
|
import { activityStarted, activityFinished } from "../../idle-watchdog.js";
|
|
140
141
|
import { normalizeActivityInput, type ExecuteActivityInput } from "../../shared/activity-input.js";
|
|
141
142
|
import { getCapturedRejection, clearCapturedRejection } from "./rejection-capture.js";
|
|
142
|
-
import { synthesizeError, formatClassifiedError, shouldRetryWithFreshAgent } from "./error-classifier.js";
|
|
143
|
+
import { synthesizeError, formatClassifiedError, shouldRetryWithFreshAgent, extractRunErrorSources } from "./error-classifier.js";
|
|
143
144
|
import type { ClassifiedError } from "./error-classifier.js";
|
|
144
145
|
import { createAgent, createCloudAgent } from "./session-lifecycle.js";
|
|
145
146
|
import { setMaxListeners } from "node:events";
|
|
@@ -644,9 +645,21 @@ async function executeCursorInner(
|
|
|
644
645
|
// the messaging reach; undefined lets a cloud sandbox runner's
|
|
645
646
|
// ambient session-scoped token or OSS's no-auth apply). The
|
|
646
647
|
// attachment header falls back to the ambient credential where no
|
|
647
|
-
// exchange happens.
|
|
648
|
-
|
|
649
|
-
|
|
648
|
+
// exchange happens. Unlike the env read (which hard-fails on a broken
|
|
649
|
+
// exchange — secrets are load-bearing there), this exchange is
|
|
650
|
+
// opportunistic: every consumer below degrades to an empty answer by
|
|
651
|
+
// contract, and the server refuses the ambient fallback safely, so a
|
|
652
|
+
// failed exchange must not kill the run.
|
|
653
|
+
let exchangedRunnerToken: string | undefined;
|
|
654
|
+
try {
|
|
655
|
+
exchangedRunnerToken =
|
|
656
|
+
await client.acquireScopedRunnerToken({ agentExecutionId: executionId });
|
|
657
|
+
} catch (err) {
|
|
658
|
+
console.warn(
|
|
659
|
+
"[execute-cursor] Scoped-token exchange failed for attachment/discovery " +
|
|
660
|
+
`reads; degrading to the ambient credential: ${err instanceof Error ? err.message : err}`,
|
|
661
|
+
);
|
|
662
|
+
}
|
|
650
663
|
const attachmentCredential = exchangedRunnerToken
|
|
651
664
|
?? config.stigmerTokenRef?.current
|
|
652
665
|
?? config.stigmerToken;
|
|
@@ -734,11 +747,11 @@ async function executeCursorInner(
|
|
|
734
747
|
// them as auto-approved); the global bypass empties the map entirely.
|
|
735
748
|
const leases = deriveActiveLeases(execution);
|
|
736
749
|
const globalBypass = leases.global;
|
|
737
|
-
|
|
738
|
-
|
|
750
|
+
// Layer-3 overrides ride each resolved server from its merged usage —
|
|
751
|
+
// see ResolvedMcpServer.toolApprovalOverrides (issue #349) — so there
|
|
752
|
+
// is no separate override input to pass here.
|
|
739
753
|
const mergedPolicies = mergeApprovalPolicies(
|
|
740
754
|
mcpResolution.resolvedServers,
|
|
741
|
-
agentOverrides,
|
|
742
755
|
leases,
|
|
743
756
|
);
|
|
744
757
|
heartbeat();
|
|
@@ -785,7 +798,10 @@ async function executeCursorInner(
|
|
|
785
798
|
storage: artifactStorage,
|
|
786
799
|
visionBudget,
|
|
787
800
|
});
|
|
788
|
-
const
|
|
801
|
+
const attachmentEntries = attachmentResults.map((a) => ({
|
|
802
|
+
path: a.relativePath,
|
|
803
|
+
...(a.renamedFrom !== undefined ? { renamedFrom: a.renamedFrom } : {}),
|
|
804
|
+
}));
|
|
789
805
|
// Vision facts, derived once from the single resolution result: the
|
|
790
806
|
// images the model will see inline (in attachment order) and the ones
|
|
791
807
|
// that degraded to path-only, disclosed in the prompt.
|
|
@@ -906,6 +922,11 @@ async function executeCursorInner(
|
|
|
906
922
|
captureIgnored,
|
|
907
923
|
gitWorkspace,
|
|
908
924
|
isUnattendedApprovalMode(execution),
|
|
925
|
+
// The enabled_tools capability manifest (issue #350): restricted
|
|
926
|
+
// servers' allow-lists, enforced by the hook's "disabled" arm ahead of
|
|
927
|
+
// every approval bypass. The Cursor SDK config cannot hide a server's
|
|
928
|
+
// tools, so this deny-at-call is the harness's enforcement.
|
|
929
|
+
enabledToolsBySlug(mcpResolution.resolvedServers),
|
|
909
930
|
);
|
|
910
931
|
const hitlGate = await installHitlGate({
|
|
911
932
|
workspaceRoot: primaryWorkspaceDir,
|
|
@@ -1130,7 +1151,7 @@ async function executeCursorInner(
|
|
|
1130
1151
|
subAgents: blueprint.subAgents,
|
|
1131
1152
|
workspaceDirs: blueprint.workspaceDirs,
|
|
1132
1153
|
workspaceFileRefs: spec.workspaceFileRefs ?? [],
|
|
1133
|
-
|
|
1154
|
+
attachments: attachmentEntries,
|
|
1134
1155
|
vision: visionPromptInfo,
|
|
1135
1156
|
pendingApprovals: adjudicatedApprovals,
|
|
1136
1157
|
appliedToolCallIds,
|
|
@@ -1721,12 +1742,10 @@ async function executeCursorInner(
|
|
|
1721
1742
|
status.phase = ExecutionPhase.EXECUTION_COMPLETED;
|
|
1722
1743
|
break;
|
|
1723
1744
|
case "error": {
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
?? resultAny.reason;
|
|
1729
|
-
const sdkErrorStr = sdkError ? String(sdkError) : undefined;
|
|
1745
|
+
// Shape-aware extraction, NOT String(): the result's error fields are
|
|
1746
|
+
// structured at runtime often enough that a bare coercion showed users
|
|
1747
|
+
// "[object Object]" and shadowed every fallback source below (oss#299).
|
|
1748
|
+
const runErrorSources = extractRunErrorSources(result);
|
|
1730
1749
|
|
|
1731
1750
|
// The SDK frequently resolves run.wait() to a bare { status: "error" }
|
|
1732
1751
|
// while the real reason (e.g. the original grpc-status 12 routing
|
|
@@ -1738,7 +1757,8 @@ async function executeCursorInner(
|
|
|
1738
1757
|
if (capturedRejection) clearCapturedRejection(executionId);
|
|
1739
1758
|
|
|
1740
1759
|
const classified = synthesizeError({
|
|
1741
|
-
|
|
1760
|
+
sdkError: runErrorSources.sdkError,
|
|
1761
|
+
sdkResultFields: runErrorSources.sdkResultFields,
|
|
1742
1762
|
streamErrorMessage: turnState.streamErrorMessage,
|
|
1743
1763
|
capturedRejection,
|
|
1744
1764
|
conversationErrorText,
|
|
@@ -1789,7 +1809,7 @@ async function executeCursorInner(
|
|
|
1789
1809
|
subAgents: blueprint.subAgents,
|
|
1790
1810
|
workspaceDirs: blueprint.workspaceDirs,
|
|
1791
1811
|
workspaceFileRefs: spec.workspaceFileRefs ?? [],
|
|
1792
|
-
|
|
1812
|
+
attachments: attachmentEntries,
|
|
1793
1813
|
vision: visionPromptInfo,
|
|
1794
1814
|
pendingApprovals: adjudicatedApprovals,
|
|
1795
1815
|
interactionMode,
|
|
@@ -1856,8 +1876,14 @@ async function executeCursorInner(
|
|
|
1856
1876
|
|
|
1857
1877
|
const retryConversationErrorText = await introspectConversation(retryRun, executionId);
|
|
1858
1878
|
|
|
1879
|
+
// Same shape-aware extraction as the primary error arm — the retry
|
|
1880
|
+
// previously String()-coerced result.result alone, so a structured
|
|
1881
|
+
// retry failure both read "[object Object]" and ignored the
|
|
1882
|
+
// error/message/reason fields the primary arm consults.
|
|
1883
|
+
const retryErrorSources = extractRunErrorSources(retryResult);
|
|
1859
1884
|
const retryClassified = synthesizeError({
|
|
1860
|
-
|
|
1885
|
+
sdkError: retryErrorSources.sdkError,
|
|
1886
|
+
sdkResultFields: retryErrorSources.sdkResultFields,
|
|
1861
1887
|
streamErrorMessage: turnState.streamErrorMessage,
|
|
1862
1888
|
capturedRejection: retryRejection,
|
|
1863
1889
|
conversationErrorText: retryConversationErrorText,
|
|
@@ -1974,6 +2000,7 @@ async function executeCursorInner(
|
|
|
1974
2000
|
`finalTextLength=${finalText.length}`,
|
|
1975
2001
|
);
|
|
1976
2002
|
try {
|
|
2003
|
+
const { extractStructuredOutput } = await import("./extract-structured-output.js");
|
|
1977
2004
|
structuredOutput = await extractStructuredOutput(
|
|
1978
2005
|
finalText, structuredOutputSchema, config, requestedModel,
|
|
1979
2006
|
);
|
|
@@ -2298,53 +2325,6 @@ function seedCursorTranscriptFromExecution(
|
|
|
2298
2325
|
return persisted.subAgentExecutions.map((sub) => clone(SubAgentExecutionSchema, sub));
|
|
2299
2326
|
}
|
|
2300
2327
|
|
|
2301
|
-
// ---------------------------------------------------------------------------
|
|
2302
|
-
// Structured Output Extraction (Cursor Harness Tier 2)
|
|
2303
|
-
// ---------------------------------------------------------------------------
|
|
2304
|
-
|
|
2305
|
-
/**
|
|
2306
|
-
* Extract structured data from an agent's free-text response using an
|
|
2307
|
-
* economy-tier LLM with withStructuredOutput (function-calling).
|
|
2308
|
-
* Guarantees schema-conformant JSON output via the API's tool-use mechanism.
|
|
2309
|
-
*
|
|
2310
|
-
* Construction (registry-id resolution, provider inference, proxy wiring) is
|
|
2311
|
-
* delegated to the shared buildChatModel so the economy model's registry id is
|
|
2312
|
-
* always resolved to a provider API id before the call.
|
|
2313
|
-
*/
|
|
2314
|
-
async function extractStructuredOutput(
|
|
2315
|
-
agentResponse: string,
|
|
2316
|
-
schema: Record<string, unknown>,
|
|
2317
|
-
config: Config,
|
|
2318
|
-
primaryModel: string,
|
|
2319
|
-
): Promise<unknown | null> {
|
|
2320
|
-
const { getEconomyModel } = await import("../../shared/model-registry.js");
|
|
2321
|
-
const { buildChatModel } = await import("../../shared/model-client.js");
|
|
2322
|
-
|
|
2323
|
-
const extractionModel = await getEconomyModel(primaryModel);
|
|
2324
|
-
const proxyEndpoint = config.proxyEndpoint ?? config.stigmerBackendEndpoint;
|
|
2325
|
-
|
|
2326
|
-
const { model: llm } = await buildChatModel({
|
|
2327
|
-
modelName: extractionModel,
|
|
2328
|
-
proxyEndpoint,
|
|
2329
|
-
stigmerToken: config.stigmerToken ?? undefined,
|
|
2330
|
-
maxTokens: 4096,
|
|
2331
|
-
});
|
|
2332
|
-
|
|
2333
|
-
const zodSchema = jsonSchemaToZod(schema);
|
|
2334
|
-
const structured = llm.withStructuredOutput(zodSchema);
|
|
2335
|
-
|
|
2336
|
-
const result = await structured.invoke([
|
|
2337
|
-
{ role: "system", content: "Extract the structured data from the agent's response. Return only the data that matches the schema." },
|
|
2338
|
-
{ role: "user", content: agentResponse },
|
|
2339
|
-
]);
|
|
2340
|
-
|
|
2341
|
-
return result ?? null;
|
|
2342
|
-
}
|
|
2343
|
-
|
|
2344
|
-
// Re-export for use within this module; shared implementation eliminates
|
|
2345
|
-
// the three duplicate converters that previously drifted independently.
|
|
2346
|
-
import { jsonSchemaToZod } from "../../shared/json-schema-to-zod.js";
|
|
2347
|
-
|
|
2348
2328
|
// ---------------------------------------------------------------------------
|
|
2349
2329
|
// Prompt selection
|
|
2350
2330
|
// ---------------------------------------------------------------------------
|
|
@@ -2365,7 +2345,11 @@ export interface BuildPromptInput {
|
|
|
2365
2345
|
subAgents: import("@stigmer/protos/ai/stigmer/agentic/agent/v1/spec_pb").SubAgent[];
|
|
2366
2346
|
workspaceDirs: string[];
|
|
2367
2347
|
workspaceFileRefs: string[];
|
|
2368
|
-
|
|
2348
|
+
/**
|
|
2349
|
+
* This turn's resolved attachments for the `<input_files>` section —
|
|
2350
|
+
* final paths plus duplicate-rename disclosure (attachment-resolver.ts).
|
|
2351
|
+
*/
|
|
2352
|
+
attachments: import("./prompt-builder.js").AttachmentPromptEntry[];
|
|
2369
2353
|
/**
|
|
2370
2354
|
* Vision facts for the input-files section (T04): which attachments the
|
|
2371
2355
|
* model sees inline and which degraded to path-only. PER-TURN like the
|
|
@@ -2458,7 +2442,7 @@ export function buildPrompt(input: BuildPromptInput): string {
|
|
|
2458
2442
|
subAgents,
|
|
2459
2443
|
workspaceDirs,
|
|
2460
2444
|
workspaceFileRefs,
|
|
2461
|
-
|
|
2445
|
+
attachments,
|
|
2462
2446
|
interactionMode,
|
|
2463
2447
|
buildFromPlan,
|
|
2464
2448
|
conversationCatchup,
|
|
@@ -2492,9 +2476,9 @@ export function buildPrompt(input: BuildPromptInput): string {
|
|
|
2492
2476
|
if (resolution.reason === "resumed_successfully") {
|
|
2493
2477
|
const prefixes = [
|
|
2494
2478
|
formatInteractionModePrefix(interactionMode),
|
|
2495
|
-
formatImplementPlanSection(buildFromPlan,
|
|
2496
|
-
|
|
2497
|
-
? formatInputFiles(
|
|
2479
|
+
formatImplementPlanSection(buildFromPlan, attachments),
|
|
2480
|
+
attachments.length > 0
|
|
2481
|
+
? formatInputFiles(attachments, input.vision)
|
|
2498
2482
|
: undefined,
|
|
2499
2483
|
conversationCatchup !== undefined
|
|
2500
2484
|
? formatConversationCatchupSection(conversationCatchup)
|
|
@@ -2517,7 +2501,7 @@ export function buildPrompt(input: BuildPromptInput): string {
|
|
|
2517
2501
|
subAgents,
|
|
2518
2502
|
workspaceDirs,
|
|
2519
2503
|
workspaceFileRefs,
|
|
2520
|
-
|
|
2504
|
+
attachments,
|
|
2521
2505
|
vision: input.vision,
|
|
2522
2506
|
interactionMode,
|
|
2523
2507
|
buildFromPlan,
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* same pipeline as the Python agent-runner's config_transformer.py.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import type { McpServerUsage } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/spec_pb";
|
|
17
|
+
import type { McpServerUsage, ToolApprovalOverride } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/spec_pb";
|
|
18
18
|
import type { McpServer } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/api_pb";
|
|
19
19
|
import type { ToolApprovalPolicy } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/spec_pb";
|
|
20
20
|
import type { StigmerClient } from "../../client/stigmer-client.js";
|
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
filterEnvToDeclaredKeys,
|
|
30
30
|
PlaceholderResolutionError,
|
|
31
31
|
} from "./placeholder-resolver.js";
|
|
32
|
+
import { effectiveEnabledTools } from "../../shared/mcp-enabled-tools.js";
|
|
32
33
|
|
|
33
34
|
/**
|
|
34
35
|
* Cursor SDK MCP server config shape (matches @cursor/sdk McpServerConfig).
|
|
@@ -70,6 +71,29 @@ export interface ResolvedMcpServer {
|
|
|
70
71
|
pinnedToolApprovals: ToolApprovalPolicy[];
|
|
71
72
|
/** True when the server has never been connected (no tool discovery yet). */
|
|
72
73
|
discoveredCapabilitiesEmpty: boolean;
|
|
74
|
+
/**
|
|
75
|
+
* The EFFECTIVE tool allow-list for this server (issue #350): the usage's
|
|
76
|
+
* enabled_tools, falling back to the server's default_enabled_tools when
|
|
77
|
+
* the usage's list is empty (see shared/mcp-enabled-tools.ts). Absent when
|
|
78
|
+
* unrestricted. The Cursor SDK config cannot hide tools, so this harness
|
|
79
|
+
* enforces it in the HITL hook: the restricted map is written into the
|
|
80
|
+
* approval state (mcpServerEnabledTools) and non-enabled calls are denied
|
|
81
|
+
* with the non-pausing "disabled" kind (see hook-script.ts).
|
|
82
|
+
*/
|
|
83
|
+
enabledTools?: string[];
|
|
84
|
+
/**
|
|
85
|
+
* Layer-3 per-agent approval overrides from the usage that resolved this
|
|
86
|
+
* server (issue #349): riding the resolved server is what SCOPES an
|
|
87
|
+
* override to its own server — a flat cross-server list is how an
|
|
88
|
+
* override once leaked onto (or silently un-gated) a same-named tool on
|
|
89
|
+
* another server. Deliberately REQUIRED, not optional: empty means "no
|
|
90
|
+
* overrides", and every construction site — including the shared
|
|
91
|
+
* resolver this one mirrors and the synthesized attachments, which have
|
|
92
|
+
* no usage and therefore no layer 3 — must say so explicitly, so a
|
|
93
|
+
* forgotten mirror cannot compile. Consumed by mergeApprovalPolicies
|
|
94
|
+
* (shared/approval-policy.ts).
|
|
95
|
+
*/
|
|
96
|
+
toolApprovalOverrides: ToolApprovalOverride[];
|
|
73
97
|
}
|
|
74
98
|
|
|
75
99
|
/**
|
|
@@ -115,7 +139,13 @@ export async function resolveMcpServers(
|
|
|
115
139
|
envVars,
|
|
116
140
|
ref.slug,
|
|
117
141
|
);
|
|
118
|
-
const server = mcpServerToResolved(
|
|
142
|
+
const server = mcpServerToResolved(
|
|
143
|
+
mcpServer,
|
|
144
|
+
ref.slug,
|
|
145
|
+
serverEnv,
|
|
146
|
+
usage.enabledTools,
|
|
147
|
+
usage.toolApprovalOverrides ?? [],
|
|
148
|
+
);
|
|
119
149
|
if (server) {
|
|
120
150
|
assertTransportAllowed(server.slug, server.connectionType, transportPosture);
|
|
121
151
|
resolved.push(server);
|
|
@@ -152,6 +182,8 @@ function mcpServerToResolved(
|
|
|
152
182
|
server: McpServer,
|
|
153
183
|
slug: string,
|
|
154
184
|
envVars: Record<string, string>,
|
|
185
|
+
usageEnabledTools?: readonly string[],
|
|
186
|
+
usageToolApprovalOverrides: ToolApprovalOverride[] = [],
|
|
155
187
|
): ResolvedMcpServer | null {
|
|
156
188
|
const spec = server.spec;
|
|
157
189
|
if (!spec) return null;
|
|
@@ -167,6 +199,8 @@ function mcpServerToResolved(
|
|
|
167
199
|
toolApprovals,
|
|
168
200
|
pinnedToolApprovals,
|
|
169
201
|
discoveredCapabilitiesEmpty,
|
|
202
|
+
enabledTools: effectiveEnabledTools(usageEnabledTools, spec.defaultEnabledTools),
|
|
203
|
+
toolApprovalOverrides: usageToolApprovalOverrides,
|
|
170
204
|
};
|
|
171
205
|
|
|
172
206
|
switch (spec.serverType.case) {
|
|
@@ -71,6 +71,18 @@ export interface VisionPromptInfo {
|
|
|
71
71
|
notViewable: NotViewableEntry[];
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
/**
|
|
75
|
+
* One resolved attachment as the `<input_files>` section renders it —
|
|
76
|
+
* the final workspace-relative path plus the duplicate-rename disclosure
|
|
77
|
+
* (attachment-resolver.ts / shared/attachment-naming.ts).
|
|
78
|
+
*/
|
|
79
|
+
export interface AttachmentPromptEntry {
|
|
80
|
+
/** Workspace-relative path the agent reads (`.stigmer/inputs/{filename}`). */
|
|
81
|
+
path: string;
|
|
82
|
+
/** Original filename, present only when a duplicate name was renamed. */
|
|
83
|
+
renamedFrom?: string;
|
|
84
|
+
}
|
|
85
|
+
|
|
74
86
|
export interface EnhancedPromptOptions {
|
|
75
87
|
instructions: string;
|
|
76
88
|
userMessage: string;
|
|
@@ -78,7 +90,10 @@ export interface EnhancedPromptOptions {
|
|
|
78
90
|
/**
|
|
79
91
|
* Datastores attached via `datastore_usages` — rendered as the
|
|
80
92
|
* `<available_datastores>` section (DD-005 SD-5, skills precedent)
|
|
81
|
-
* pointing the model at the synthesized record tools.
|
|
93
|
+
* pointing the model at the synthesized record tools. Always the healthy
|
|
94
|
+
* rendering here: the Cursor SDK connects MCP itself, so this harness can
|
|
95
|
+
* never observe the live roster — the section's standing
|
|
96
|
+
* failure-disclosure instruction is its whole coverage (issue #325).
|
|
82
97
|
*/
|
|
83
98
|
datastoreUsages?: DatastoreUsage[];
|
|
84
99
|
/**
|
|
@@ -90,7 +105,7 @@ export interface EnhancedPromptOptions {
|
|
|
90
105
|
subAgents: SubAgent[];
|
|
91
106
|
workspaceDirs: string[];
|
|
92
107
|
workspaceFileRefs: string[];
|
|
93
|
-
|
|
108
|
+
attachments: AttachmentPromptEntry[];
|
|
94
109
|
/** Inline/degraded image facts for the input-files section (T04 vision). */
|
|
95
110
|
vision?: VisionPromptInfo;
|
|
96
111
|
interactionMode?: InteractionMode;
|
|
@@ -153,7 +168,7 @@ export function buildEnhancedPrompt(options: EnhancedPromptOptions): string {
|
|
|
153
168
|
|
|
154
169
|
const implementPlan = formatImplementPlanSection(
|
|
155
170
|
options.buildFromPlan,
|
|
156
|
-
options.
|
|
171
|
+
options.attachments,
|
|
157
172
|
);
|
|
158
173
|
if (implementPlan) {
|
|
159
174
|
sections.push(implementPlan);
|
|
@@ -195,8 +210,8 @@ export function buildEnhancedPrompt(options: EnhancedPromptOptions): string {
|
|
|
195
210
|
sections.push(formatWorkspaceContext(safeDirs));
|
|
196
211
|
}
|
|
197
212
|
|
|
198
|
-
if (options.
|
|
199
|
-
sections.push(formatInputFiles(options.
|
|
213
|
+
if (options.attachments.length > 0) {
|
|
214
|
+
sections.push(formatInputFiles(options.attachments, options.vision));
|
|
200
215
|
}
|
|
201
216
|
|
|
202
217
|
if (options.workspaceFileRefs.length > 0) {
|
|
@@ -479,8 +494,18 @@ export function formatWorkspaceContext(dirs: string[]): string {
|
|
|
479
494
|
].join("\n");
|
|
480
495
|
}
|
|
481
496
|
|
|
482
|
-
export function formatInputFiles(
|
|
483
|
-
|
|
497
|
+
export function formatInputFiles(
|
|
498
|
+
attachments: readonly AttachmentPromptEntry[],
|
|
499
|
+
vision?: VisionPromptInfo,
|
|
500
|
+
): string {
|
|
501
|
+
// A duplicate-renamed file (attachment-naming.ts) discloses its original
|
|
502
|
+
// name so the agent can connect "the two report.pdfs" in the user's
|
|
503
|
+
// message to distinct files on disk.
|
|
504
|
+
const entries = attachments.map((a) =>
|
|
505
|
+
a.renamedFrom !== undefined
|
|
506
|
+
? `- \`${a.path}\` (renamed from duplicate '${a.renamedFrom}')`
|
|
507
|
+
: `- \`${a.path}\``,
|
|
508
|
+
);
|
|
484
509
|
// The vision lines (shared wording, attachment-vision.ts) tell the model
|
|
485
510
|
// which of these files it can already SEE inline versus which degraded to
|
|
486
511
|
// path-only — without them an agent silently ignores a photo the user
|
|
@@ -541,14 +566,14 @@ export function formatInteractionModePrefix(
|
|
|
541
566
|
*/
|
|
542
567
|
export function formatImplementPlanSection(
|
|
543
568
|
buildFromPlan: boolean | undefined,
|
|
544
|
-
|
|
569
|
+
attachments: readonly AttachmentPromptEntry[],
|
|
545
570
|
): string | undefined {
|
|
546
571
|
if (!buildFromPlan) {
|
|
547
572
|
return undefined;
|
|
548
573
|
}
|
|
549
574
|
|
|
550
575
|
const directive = buildImplementPlanDirective(
|
|
551
|
-
findApprovedPlanPath(
|
|
576
|
+
findApprovedPlanPath(attachments.map((a) => a.path)),
|
|
552
577
|
);
|
|
553
578
|
return ["<implement_plan>", directive, "</implement_plan>"].join("\n");
|
|
554
579
|
}
|
|
@@ -387,8 +387,11 @@ export async function consumeCursorTurnStream(
|
|
|
387
387
|
console.log(
|
|
388
388
|
`ExecuteCursor stream status: execution=${executionId}, status=${JSON.stringify(event)}`,
|
|
389
389
|
);
|
|
390
|
-
const statusEvent = event as { status?: string; message?:
|
|
391
|
-
|
|
390
|
+
const statusEvent = event as { status?: string; message?: unknown };
|
|
391
|
+
// The cast is a claim, not a guarantee: message is untyped at runtime
|
|
392
|
+
// (the oss#299 class of bug). A structured value here would crash
|
|
393
|
+
// classification downstream, so only capture actual text.
|
|
394
|
+
if (statusEvent.status === "ERROR" && typeof statusEvent.message === "string" && statusEvent.message.length > 0) {
|
|
392
395
|
state.streamErrorMessage = statusEvent.message;
|
|
393
396
|
}
|
|
394
397
|
}
|