@stigmer/runner 3.40.0 → 3.41.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 +3 -1
- package/dist/.build-fingerprint +1 -1
- package/dist/__test-utils__/execution-record-fixture.d.ts +2 -1
- package/dist/__test-utils__/execution-record-fixture.js.map +1 -1
- package/dist/activities/call-agent.js +1 -1
- package/dist/activities/discover-mcp-server.d.ts +7 -1
- package/dist/activities/discover-mcp-server.js +13 -29
- package/dist/activities/discover-mcp-server.js.map +1 -1
- package/dist/activities/execute-cursor/__test-utils__/fold.d.ts +4 -2
- package/dist/activities/execute-cursor/__test-utils__/fold.js +3 -4
- package/dist/activities/execute-cursor/__test-utils__/fold.js.map +1 -1
- package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.js +1 -0
- package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.js.map +1 -1
- package/dist/activities/execute-cursor/approval-policy.d.ts +1 -29
- package/dist/activities/execute-cursor/approval-policy.js +11 -35
- package/dist/activities/execute-cursor/approval-policy.js.map +1 -1
- package/dist/activities/execute-cursor/boundary-rows.js +4 -4
- package/dist/activities/execute-cursor/boundary-rows.js.map +1 -1
- package/dist/activities/execute-cursor/translator.d.ts +12 -17
- package/dist/activities/execute-cursor/translator.js +13 -30
- package/dist/activities/execute-cursor/translator.js.map +1 -1
- package/dist/activities/execute-cursor/turn-stream.js +4 -2
- package/dist/activities/execute-cursor/turn-stream.js.map +1 -1
- package/dist/activities/execute-deep-agent/__test-utils__/hermetic-deep-agent.d.ts +11 -1
- package/dist/activities/execute-deep-agent/__test-utils__/hermetic-deep-agent.js +22 -3
- package/dist/activities/execute-deep-agent/__test-utils__/hermetic-deep-agent.js.map +1 -1
- package/dist/activities/execute-deep-agent/inline-publisher.d.ts +33 -2
- package/dist/activities/execute-deep-agent/inline-publisher.js +33 -7
- package/dist/activities/execute-deep-agent/inline-publisher.js.map +1 -1
- package/dist/activities/execute-deep-agent/platform-route.d.ts +48 -13
- package/dist/activities/execute-deep-agent/platform-route.js +87 -19
- package/dist/activities/execute-deep-agent/platform-route.js.map +1 -1
- package/dist/activities/execute-deep-agent/prompt-builder.d.ts +70 -35
- package/dist/activities/execute-deep-agent/prompt-builder.js +70 -55
- package/dist/activities/execute-deep-agent/prompt-builder.js.map +1 -1
- package/dist/activities/execute-deep-agent/streaming-side-effects.d.ts +20 -3
- package/dist/activities/execute-deep-agent/streaming-side-effects.js +26 -4
- package/dist/activities/execute-deep-agent/streaming-side-effects.js.map +1 -1
- package/dist/activities/execute-deep-agent/translator.d.ts +5 -4
- package/dist/activities/execute-deep-agent/translator.js +5 -4
- package/dist/activities/execute-deep-agent/translator.js.map +1 -1
- package/dist/activities/execute-deep-agent/turn-settle.js +6 -6
- package/dist/activities/execute-deep-agent/turn-settle.js.map +1 -1
- package/dist/activities/execute-deep-agent/turn-setup.d.ts +18 -13
- package/dist/activities/execute-deep-agent/turn-setup.js +42 -36
- package/dist/activities/execute-deep-agent/turn-setup.js.map +1 -1
- package/dist/activities/execute-deep-agent/turn-stream.d.ts +10 -6
- package/dist/activities/execute-deep-agent/turn-stream.js +6 -16
- package/dist/activities/execute-deep-agent/turn-stream.js.map +1 -1
- package/dist/activities/execute-deep-agent/turn.d.ts +4 -1
- package/dist/activities/execute-deep-agent/turn.js +15 -6
- package/dist/activities/execute-deep-agent/turn.js.map +1 -1
- package/dist/activities/workflow-event-activities.js +2 -0
- package/dist/activities/workflow-event-activities.js.map +1 -1
- package/dist/config.d.ts +9 -0
- package/dist/config.js +2 -0
- package/dist/config.js.map +1 -1
- package/dist/exit-backstop.d.ts +64 -0
- package/dist/exit-backstop.js +79 -0
- package/dist/exit-backstop.js.map +1 -0
- package/dist/harness/terminal-table.d.ts +12 -5
- package/dist/harness/terminal-table.js +13 -13
- package/dist/harness/terminal-table.js.map +1 -1
- package/dist/harness/transcript/builder.d.ts +21 -0
- package/dist/harness/transcript/builder.js +53 -31
- package/dist/harness/transcript/builder.js.map +1 -1
- package/dist/harness/transcript/events.d.ts +22 -26
- package/dist/harness/transcript/events.js +6 -5
- package/dist/harness/transcript/events.js.map +1 -1
- package/dist/harness/turn-context.d.ts +3 -1
- package/dist/harness/turn-context.js +5 -3
- package/dist/harness/turn-context.js.map +1 -1
- package/dist/main.js +10 -4
- package/dist/main.js.map +1 -1
- package/dist/middleware/cost-advisory.d.ts +6 -3
- package/dist/middleware/cost-advisory.js +6 -3
- package/dist/middleware/cost-advisory.js.map +1 -1
- package/dist/middleware/execution-budget.d.ts +22 -7
- package/dist/middleware/execution-budget.js +57 -35
- package/dist/middleware/execution-budget.js.map +1 -1
- package/dist/middleware/index.d.ts +10 -7
- package/dist/middleware/index.js +10 -7
- package/dist/middleware/index.js.map +1 -1
- package/dist/middleware/types.d.ts +6 -1
- package/dist/runner-manager.js +3 -2
- package/dist/runner-manager.js.map +1 -1
- package/dist/runner.d.ts +25 -0
- package/dist/runner.js +41 -14
- package/dist/runner.js.map +1 -1
- package/dist/shared/approval-policy.d.ts +23 -13
- package/dist/shared/approval-policy.js +47 -14
- package/dist/shared/approval-policy.js.map +1 -1
- package/dist/shared/caller-identity.d.ts +4 -5
- package/dist/shared/caller-identity.js +4 -5
- package/dist/shared/caller-identity.js.map +1 -1
- package/dist/shared/channel-attachment.js +3 -2
- package/dist/shared/channel-attachment.js.map +1 -1
- package/dist/shared/connect-backfill.d.ts +2 -1
- package/dist/shared/connect-backfill.js +2 -2
- package/dist/shared/connect-backfill.js.map +1 -1
- package/dist/shared/mcp-resolver.d.ts +10 -1
- package/dist/shared/mcp-resolver.js +11 -2
- package/dist/shared/mcp-resolver.js.map +1 -1
- package/dist/shared/memory-attachment.js +3 -2
- package/dist/shared/memory-attachment.js.map +1 -1
- package/dist/shared/model-error.d.ts +25 -3
- package/dist/shared/model-error.js +78 -10
- package/dist/shared/model-error.js.map +1 -1
- package/dist/shared/platform-server-address.d.ts +71 -0
- package/dist/shared/platform-server-address.js +103 -0
- package/dist/shared/platform-server-address.js.map +1 -0
- package/dist/shared/prompt-sections.d.ts +6 -28
- package/dist/shared/prompt-sections.js +6 -35
- package/dist/shared/prompt-sections.js.map +1 -1
- package/dist/shared/skill-resolver.d.ts +2 -3
- package/dist/shared/skill-resolver.js +2 -3
- package/dist/shared/skill-resolver.js.map +1 -1
- package/dist/shared/synthesized-attachment.d.ts +0 -5
- package/dist/shared/synthesized-attachment.js +0 -12
- package/dist/shared/synthesized-attachment.js.map +1 -1
- package/dist/shared/tool-rounds.d.ts +51 -23
- package/dist/shared/tool-rounds.js +67 -24
- package/dist/shared/tool-rounds.js.map +1 -1
- package/dist/shared/workspace/platform-dir.d.ts +15 -0
- package/dist/shared/workspace/platform-dir.js +18 -1
- package/dist/shared/workspace/platform-dir.js.map +1 -1
- package/dist/shared/workspace/sources/git.js +13 -6
- package/dist/shared/workspace/sources/git.js.map +1 -1
- package/dist/shared/workspace/stigmer-link.d.ts +21 -14
- package/dist/shared/workspace/stigmer-link.js +91 -29
- package/dist/shared/workspace/stigmer-link.js.map +1 -1
- package/dist/tools/url-guard.d.ts +5 -1
- package/dist/tools/url-guard.js +9 -2
- package/dist/tools/url-guard.js.map +1 -1
- package/dist/worker.d.ts +11 -2
- package/dist/worker.js +1 -1
- package/dist/worker.js.map +1 -1
- package/dist/workflow-engine/tasks/human-input.d.ts +16 -0
- package/dist/workflow-engine/tasks/human-input.js +79 -37
- package/dist/workflow-engine/tasks/human-input.js.map +1 -1
- package/dist/workflow-engine/types.d.ts +14 -4
- package/dist/workflow-engine/types.js.map +1 -1
- package/dist/workflows/call-agent-orchestrator.d.ts +13 -0
- package/dist/workflows/call-agent-orchestrator.js.map +1 -1
- package/dist/workflows/engine-core.d.ts +25 -0
- package/dist/workflows/engine-core.js.map +1 -1
- package/dist/workflows/execute-from-execution.d.ts +3 -0
- package/dist/workflows/execute-from-execution.js.map +1 -1
- package/dist/workflows/human-input-orchestrator.d.ts +9 -12
- package/dist/workflows/human-input-orchestrator.js +12 -44
- package/dist/workflows/human-input-orchestrator.js.map +1 -1
- package/package.json +5 -5
- package/src/__test-utils__/config-fixture.ts +1 -0
- package/src/__test-utils__/execution-record-fixture.ts +2 -1
- package/src/__test-utils__/harness-contract/runtime-contract.ts +5 -3
- package/src/__test-utils__/test-gate.ts +8 -0
- package/src/__tests__/exit-backstop.test.ts +91 -0
- package/src/__tests__/golden-e2e.test.ts +80 -62
- package/src/__tests__/history-encryption-e2e.test.ts +51 -27
- package/src/__tests__/runner.test.ts +60 -2
- package/src/activities/__tests__/classify-tool-approvals.test.ts +1 -0
- package/src/activities/__tests__/discover-mcp-server.hang.test.ts +5 -1
- package/src/activities/__tests__/discover-mcp-server.test.ts +90 -49
- package/src/activities/__tests__/workflow-event-activities.test.ts +28 -7
- package/src/activities/call-agent.ts +1 -1
- package/src/activities/discover-mcp-server.ts +27 -37
- package/src/activities/execute-cursor/__test-utils__/fold.ts +6 -4
- package/src/activities/execute-cursor/__test-utils__/hermetic-cursor.ts +1 -0
- package/src/activities/execute-cursor/__tests__/approval-gate.test.ts +4 -29
- package/src/activities/execute-cursor/__tests__/hermetic/bypassed-tool-call.test.ts +120 -0
- package/src/activities/execute-cursor/__tests__/hermetic/file-review-capture.test.ts +5 -5
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/file-review-capture.status.json +0 -2
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/resolution-error.status.json +1 -1
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/shell-output-delta.error-delta.status.json +0 -3
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/shell-output-delta.output-first.status.json +0 -2
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/shell-output-delta.status.json +0 -2
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/stall.status.json +0 -2
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/unattributed-hook-block.status.json +0 -3
- package/src/activities/execute-cursor/__tests__/hermetic/shell-output-delta.test.ts +9 -7
- package/src/activities/execute-cursor/__tests__/hermetic/thrown-error-arms.test.ts +6 -1
- package/src/activities/execute-cursor/__tests__/hitl-resume-history.test.ts +8 -11
- package/src/activities/execute-cursor/__tests__/sequential-gate-resume.test.ts +1 -3
- package/src/activities/execute-cursor/__tests__/translator.test.ts +68 -15
- package/src/activities/execute-cursor/approval-policy.ts +11 -36
- package/src/activities/execute-cursor/boundary-rows.ts +3 -3
- package/src/activities/execute-cursor/translator.ts +15 -32
- package/src/activities/execute-cursor/turn-stream.ts +4 -1
- package/src/activities/execute-deep-agent/__test-utils__/hermetic-deep-agent.ts +33 -2
- package/src/activities/execute-deep-agent/__tests__/adapter.test.ts +1 -0
- package/src/activities/execute-deep-agent/__tests__/goldens/system-prompt.build-from-plan.prompt.md +1 -27
- package/src/activities/execute-deep-agent/__tests__/goldens/system-prompt.everything.prompt.md +26 -30
- package/src/activities/execute-deep-agent/__tests__/goldens/system-prompt.plan-mode.prompt.md +1 -26
- package/src/activities/execute-deep-agent/__tests__/goldens/user-message.everything.md +36 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/file-review-capture.test.ts +4 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/file-review-capture.status.json +1 -1
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/inline-artifact.turn1.status.json +1 -1
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/inline-artifact.turn2.status.json +1 -1
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/recursion-limit.persisted.status.json +8 -73
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/resolution-error.status.json +1 -1
- package/src/activities/execute-deep-agent/__tests__/hermetic/inline-artifact-across-gate.test.ts +17 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/recursion-limit.test.ts +86 -26
- package/src/activities/execute-deep-agent/__tests__/hermetic/resolution-error.test.ts +9 -5
- package/src/activities/execute-deep-agent/__tests__/hermetic/thrown-turn-drains-publishes.test.ts +159 -0
- package/src/activities/execute-deep-agent/__tests__/inline-publisher.test.ts +84 -0
- package/src/activities/execute-deep-agent/__tests__/platform-route.test.ts +47 -10
- package/src/activities/execute-deep-agent/__tests__/prompt-builder.test.ts +127 -199
- package/src/activities/execute-deep-agent/__tests__/prompt-goldens.test.ts +31 -26
- package/src/activities/execute-deep-agent/__tests__/streaming-side-effects.test.ts +34 -0
- package/src/activities/execute-deep-agent/__tests__/turn-stream.test.ts +13 -5
- package/src/activities/execute-deep-agent/inline-publisher.ts +47 -7
- package/src/activities/execute-deep-agent/platform-route.ts +109 -20
- package/src/activities/execute-deep-agent/prompt-builder.ts +104 -92
- package/src/activities/execute-deep-agent/streaming-side-effects.ts +27 -4
- package/src/activities/execute-deep-agent/translator.ts +5 -4
- package/src/activities/execute-deep-agent/turn-settle.ts +6 -6
- package/src/activities/execute-deep-agent/turn-setup.ts +43 -38
- package/src/activities/execute-deep-agent/turn-stream.ts +15 -22
- package/src/activities/execute-deep-agent/turn.ts +16 -7
- package/src/activities/workflow-event-activities.ts +2 -0
- package/src/config.ts +11 -0
- package/src/exit-backstop.ts +97 -0
- package/src/harness/README.md +5 -5
- package/src/harness/terminal-table.ts +13 -13
- package/src/harness/transcript/__tests__/builder.test.ts +93 -21
- package/src/harness/transcript/builder.ts +53 -30
- package/src/harness/transcript/events.ts +22 -27
- package/src/harness/turn-context.ts +5 -3
- package/src/main.ts +9 -5
- package/src/middleware/__tests__/execution-budget.test.ts +181 -103
- package/src/middleware/cost-advisory.ts +6 -3
- package/src/middleware/execution-budget.ts +60 -34
- package/src/middleware/index.ts +10 -7
- package/src/middleware/types.ts +6 -1
- package/src/runner-manager.ts +3 -2
- package/src/runner.ts +57 -15
- package/src/shared/__tests__/approval-policy.test.ts +41 -0
- package/src/shared/__tests__/artifact-storage.test.ts +1 -0
- package/src/shared/__tests__/connect-backfill.test.ts +15 -11
- package/src/shared/__tests__/mcp-resolver.test.ts +72 -10
- package/src/shared/__tests__/model-error.test.ts +71 -3
- package/src/shared/__tests__/platform-server-address.test.ts +122 -0
- package/src/shared/__tests__/prompt-sections.test.ts +2 -54
- package/src/shared/__tests__/tool-rounds.test.ts +64 -14
- package/src/shared/approval-policy.ts +57 -14
- package/src/shared/caller-identity.ts +4 -5
- package/src/shared/channel-attachment.ts +2 -2
- package/src/shared/connect-backfill.ts +5 -1
- package/src/shared/mcp-resolver.ts +11 -1
- package/src/shared/memory-attachment.ts +2 -2
- package/src/shared/model-error.ts +87 -9
- package/src/shared/platform-server-address.ts +123 -0
- package/src/shared/prompt-sections.ts +6 -55
- package/src/shared/skill-resolver.ts +2 -3
- package/src/shared/synthesized-attachment.ts +0 -12
- package/src/shared/tool-rounds.ts +70 -24
- package/src/shared/workspace/__tests__/git-source.test.ts +11 -5
- package/src/shared/workspace/__tests__/stigmer-link.test.ts +107 -10
- package/src/shared/workspace/platform-dir.ts +19 -1
- package/src/shared/workspace/sources/git.ts +14 -8
- package/src/shared/workspace/stigmer-link.ts +99 -29
- package/src/tools/__tests__/url-guard.test.ts +19 -1
- package/src/tools/url-guard.ts +9 -1
- package/src/worker.ts +12 -2
- package/src/workflow-engine/__tests__/golden-execution.test.ts +2 -1
- package/src/workflow-engine/__tests__/loader.test.ts +1 -1
- package/src/workflow-engine/__tests__/tasks/human-input.test.ts +133 -57
- package/src/workflow-engine/tasks/human-input.ts +92 -42
- package/src/workflow-engine/types.ts +16 -4
- package/src/workflows/call-agent-orchestrator.ts +7 -0
- package/src/workflows/engine-core.ts +18 -0
- package/src/workflows/execute-from-execution.ts +2 -1
- package/src/workflows/human-input-orchestrator.ts +15 -56
- package/dist/shared/skill-relevance.d.ts +0 -65
- package/dist/shared/skill-relevance.js +0 -175
- package/dist/shared/skill-relevance.js.map +0 -1
- package/src/activities/execute-deep-agent/__tests__/goldens/system-prompt.skills-below-threshold.prompt.md +0 -146
- package/src/shared/__tests__/skill-relevance.test.ts +0 -292
- package/src/shared/skill-relevance.ts +0 -222
|
@@ -31,6 +31,12 @@ import {
|
|
|
31
31
|
} from "../shared/mcp-transport-guard.js";
|
|
32
32
|
import { detectOAuthChallenge } from "../shared/mcp-oauth-detect.js";
|
|
33
33
|
import { injectAnonymousCallerIdentityForDiscovery } from "../shared/caller-identity.js";
|
|
34
|
+
import {
|
|
35
|
+
fillPlatformServerAddress,
|
|
36
|
+
platformServerAddress,
|
|
37
|
+
SERVER_ADDRESS_ENV_KEY,
|
|
38
|
+
type PlatformEndpoints,
|
|
39
|
+
} from "../shared/platform-server-address.js";
|
|
34
40
|
import { startHeartbeat } from "../shared/heartbeat.js";
|
|
35
41
|
import { withTimeout } from "../shared/with-timeout.js";
|
|
36
42
|
import type { McpServer } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/api_pb";
|
|
@@ -70,10 +76,6 @@ const STDIO_INIT_TIMEOUT_MS = 270_000;
|
|
|
70
76
|
*/
|
|
71
77
|
const REDACTED_MARKER = "***REDACTED***";
|
|
72
78
|
|
|
73
|
-
const PLATFORM_INJECTABLE_MAP: Record<string, string> = {
|
|
74
|
-
STIGMER_SERVER_ADDRESS: "STIGMER_MCP_PUBLIC_ENDPOINT",
|
|
75
|
-
};
|
|
76
|
-
|
|
77
79
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
78
80
|
// Types
|
|
79
81
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -237,36 +239,6 @@ function structToPlainObject(struct: unknown): Record<string, unknown> | null {
|
|
|
237
239
|
}
|
|
238
240
|
}
|
|
239
241
|
|
|
240
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
241
|
-
// Platform Env Injection
|
|
242
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
243
|
-
|
|
244
|
-
export function injectPlatformEnv(
|
|
245
|
-
declaredEnvKeys: Set<string>,
|
|
246
|
-
envVars: Record<string, string>,
|
|
247
|
-
): Record<string, string> {
|
|
248
|
-
if (declaredEnvKeys.size === 0) return envVars;
|
|
249
|
-
|
|
250
|
-
let result: Record<string, string> | undefined;
|
|
251
|
-
|
|
252
|
-
for (const [targetKey, sourceKey] of Object.entries(PLATFORM_INJECTABLE_MAP)) {
|
|
253
|
-
if (!declaredEnvKeys.has(targetKey)) continue;
|
|
254
|
-
const value = process.env[sourceKey];
|
|
255
|
-
if (!value) continue;
|
|
256
|
-
|
|
257
|
-
if (!result) result = { ...envVars };
|
|
258
|
-
if (targetKey in result && result[targetKey] !== value) {
|
|
259
|
-
console.info(
|
|
260
|
-
`Platform env var '${targetKey}' overrides value from ExecutionContext ` +
|
|
261
|
-
`(platform infra vars are authoritative)`,
|
|
262
|
-
);
|
|
263
|
-
}
|
|
264
|
-
result[targetKey] = value;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
return result ?? envVars;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
242
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
271
243
|
// Core Discovery Logic (no Temporal coupling)
|
|
272
244
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -281,6 +253,12 @@ export interface DiscoverDeps {
|
|
|
281
253
|
* tool enumeration.
|
|
282
254
|
*/
|
|
283
255
|
transportPosture: McpTransportPosture;
|
|
256
|
+
/**
|
|
257
|
+
* The runner's endpoints a missing STIGMER_SERVER_ADDRESS is filled from
|
|
258
|
+
* (the runner Config satisfies it) — the same fill execution applies, so
|
|
259
|
+
* discovery dials what the run will dial.
|
|
260
|
+
*/
|
|
261
|
+
platformEndpoints: PlatformEndpoints;
|
|
284
262
|
}
|
|
285
263
|
|
|
286
264
|
export async function discoverMcpServer(
|
|
@@ -305,16 +283,25 @@ export async function discoverMcpServer(
|
|
|
305
283
|
const previousState = extractPreviousState(mcpServer);
|
|
306
284
|
|
|
307
285
|
const declaredEnv = mcpServer.spec.env ?? {};
|
|
286
|
+
// A key the platform fills for this transport is not a credential the
|
|
287
|
+
// connect flow must deliver, so it never makes an empty EC a failure.
|
|
288
|
+
const platformFillsAddress =
|
|
289
|
+
platformServerAddress(mcpServer.spec.serverType.case, deps.platformEndpoints) !== null;
|
|
290
|
+
const credentialDeclarations = platformFillsAddress
|
|
291
|
+
? Object.fromEntries(
|
|
292
|
+
Object.entries(declaredEnv).filter(([key]) => key !== SERVER_ADDRESS_ENV_KEY),
|
|
293
|
+
)
|
|
294
|
+
: declaredEnv;
|
|
308
295
|
const envVars = await resolveEnvVarsForDiscovery(
|
|
309
296
|
stigmerClient,
|
|
310
297
|
executionContextId ?? null,
|
|
311
298
|
executionContextToken ?? null,
|
|
312
299
|
slug,
|
|
313
|
-
|
|
300
|
+
credentialDeclarations,
|
|
314
301
|
);
|
|
315
302
|
|
|
316
303
|
const declaredEnvKeys = new Set(Object.keys(declaredEnv));
|
|
317
|
-
const platformEnv =
|
|
304
|
+
const platformEnv = fillPlatformServerAddress(mcpServer, envVars, deps.platformEndpoints);
|
|
318
305
|
|
|
319
306
|
// Discovery runs with no session, so every declared caller-identity key
|
|
320
307
|
// resolves to the anonymous sentinel — without it, a server templating
|
|
@@ -385,7 +372,9 @@ export async function discoverMcpServer(
|
|
|
385
372
|
* sentinel can only produce a misleading 401.
|
|
386
373
|
* - No non-optional declarations → the old lenient path (warn and continue):
|
|
387
374
|
* servers declaring nothing (or only optional/injected keys like the
|
|
388
|
-
* caller-identity family) legitimately discover without an EC.
|
|
375
|
+
* caller-identity family) legitimately discover without an EC. A
|
|
376
|
+
* STIGMER_SERVER_ADDRESS the platform fills for the server's transport is
|
|
377
|
+
* left out of the count by the caller (platform-server-address.ts).
|
|
389
378
|
*/
|
|
390
379
|
async function resolveEnvVarsForDiscovery(
|
|
391
380
|
client: StigmerClient,
|
|
@@ -635,6 +624,7 @@ export function createDiscoverMcpServerActivities(config: Config) {
|
|
|
635
624
|
return await discoverMcpServer(input, {
|
|
636
625
|
stigmerClient,
|
|
637
626
|
transportPosture: resolveMcpTransportPosture(config.mode),
|
|
627
|
+
platformEndpoints: config,
|
|
638
628
|
});
|
|
639
629
|
} finally {
|
|
640
630
|
hb.stop();
|
|
@@ -21,7 +21,7 @@ import { create } from "@bufbuild/protobuf";
|
|
|
21
21
|
import type { InteractionUpdate, SDKMessage } from "@cursor/sdk";
|
|
22
22
|
import { AgentExecutionStatusSchema, type AgentExecutionStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
|
|
23
23
|
import type { AgentMessage, ToolCall } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
|
|
24
|
-
import { TranscriptBuilder } from "../../../harness/transcript/builder.js";
|
|
24
|
+
import { TranscriptBuilder, type TranscriptObserver } from "../../../harness/transcript/builder.js";
|
|
25
25
|
import { CursorTranslator, type CursorTranslatorOptions } from "../translator.js";
|
|
26
26
|
|
|
27
27
|
export interface CursorFoldOptions {
|
|
@@ -35,6 +35,8 @@ export interface CursorFoldOptions {
|
|
|
35
35
|
readonly messages?: AgentMessage[];
|
|
36
36
|
readonly policies?: CursorTranslatorOptions["policies"];
|
|
37
37
|
readonly leases?: CursorTranslatorOptions["leases"];
|
|
38
|
+
/** The builder's observer, as the runtime passes its turn timeline; a test that moves a fake clock between folds passes one. */
|
|
39
|
+
readonly observer?: TranscriptObserver;
|
|
38
40
|
}
|
|
39
41
|
|
|
40
42
|
export class CursorFold {
|
|
@@ -50,12 +52,12 @@ export class CursorFold {
|
|
|
50
52
|
leases: options.leases ?? { global: false, categories: new Set() },
|
|
51
53
|
seeded: this.status.messages,
|
|
52
54
|
});
|
|
53
|
-
this.builder = new TranscriptBuilder("exec-fold", this.status);
|
|
55
|
+
this.builder = new TranscriptBuilder("exec-fold", this.status, options.observer);
|
|
54
56
|
}
|
|
55
57
|
|
|
56
|
-
/** One stream event, translated and folded, then the deltas queued so far — the loop's order. */
|
|
58
|
+
/** One stream event, translated and folded as one observation, then the deltas queued so far — the loop's order. */
|
|
57
59
|
event(event: SDKMessage): this {
|
|
58
|
-
|
|
60
|
+
this.builder.applyObservation(this.translator.translate(event));
|
|
59
61
|
this.drain();
|
|
60
62
|
return this;
|
|
61
63
|
}
|
|
@@ -148,6 +148,7 @@ export function hermeticCursorConfig(env: HermeticEnvironment): Config {
|
|
|
148
148
|
temporalConnection: {},
|
|
149
149
|
stigmerBackendEndpoint: "http://localhost:7234",
|
|
150
150
|
mcpBridgeEndpoint: null,
|
|
151
|
+
mcpPublicEndpoint: null,
|
|
151
152
|
stigmerTokenRef: { current: null },
|
|
152
153
|
cursorApiKey: FIXTURE.cursorApiKey,
|
|
153
154
|
workspaceRootDir: env.workspaceRootDir,
|
|
@@ -23,8 +23,6 @@ import { ApprovalAction } from "@stigmer/protos/ai/stigmer/agentic/agentexecutio
|
|
|
23
23
|
|
|
24
24
|
import {
|
|
25
25
|
approvalCategory,
|
|
26
|
-
builtInRequiresApproval,
|
|
27
|
-
getBuiltInApprovalMessage,
|
|
28
26
|
getBuiltInGatedList,
|
|
29
27
|
getBuiltInGatedCategories,
|
|
30
28
|
extractArgKey,
|
|
@@ -81,22 +79,10 @@ describe("approvalCategory (cross-taxonomy drift-guard)", () => {
|
|
|
81
79
|
});
|
|
82
80
|
});
|
|
83
81
|
|
|
84
|
-
describe("
|
|
85
|
-
it("
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
it("allows read-only built-in tools", () => {
|
|
92
|
-
for (const t of ["Read", "read", "Grep", "grep", "Glob", "glob", "ls", "think", "task"]) {
|
|
93
|
-
expect(builtInRequiresApproval(t)).toBe(false);
|
|
94
|
-
}
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
it("fails open for unknown tools (parity with native, avoids denying auto-approved MCP)", () => {
|
|
98
|
-
expect(builtInRequiresApproval("SomeFutureTool")).toBe(false);
|
|
99
|
-
expect(builtInRequiresApproval("search_services")).toBe(false);
|
|
82
|
+
describe("the hook's gated set", () => {
|
|
83
|
+
it("fails open for unknown tools: no category, so the hook allows them (parity with native, avoids denying auto-approved MCP)", () => {
|
|
84
|
+
expect(approvalCategory("SomeFutureTool")).toBeUndefined();
|
|
85
|
+
expect(approvalCategory("search_services")).toBeUndefined();
|
|
100
86
|
});
|
|
101
87
|
|
|
102
88
|
it("exposes the gated set in the HOOK taxonomy (what the hook matches)", () => {
|
|
@@ -114,17 +100,6 @@ describe("builtInRequiresApproval", () => {
|
|
|
114
100
|
});
|
|
115
101
|
});
|
|
116
102
|
|
|
117
|
-
describe("getBuiltInApprovalMessage", () => {
|
|
118
|
-
it("returns a category template for gated tools in EITHER taxonomy", () => {
|
|
119
|
-
expect(getBuiltInApprovalMessage("Write")).toContain("{{args.path}}");
|
|
120
|
-
expect(getBuiltInApprovalMessage("edit")).toContain("{{args.path}}");
|
|
121
|
-
expect(getBuiltInApprovalMessage("Shell")).toContain("{{args.command}}");
|
|
122
|
-
expect(getBuiltInApprovalMessage("shell")).toContain("{{args.command}}");
|
|
123
|
-
expect(getBuiltInApprovalMessage("Read")).toBeUndefined();
|
|
124
|
-
expect(getBuiltInApprovalMessage("read")).toBeUndefined();
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
|
|
128
103
|
describe("extractArgKey (spans both taxonomies' field names)", () => {
|
|
129
104
|
it("extracts the salient value regardless of field name (file_path or path)", () => {
|
|
130
105
|
expect(extractArgKey({ file_path: "a.txt" })).toBe("a.txt"); // hook shape
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hermetic: a GATED built-in that runs under the execution's global bypass
|
|
3
|
+
* (`spec.auto_approve_all`), through the whole `ExecuteCursor` activity.
|
|
4
|
+
*
|
|
5
|
+
* Invariant pinned (#1117): `requiresApproval` says whether THIS call was held
|
|
6
|
+
* for a person's decision, on every harness. A shell the bypass lets run is
|
|
7
|
+
* never held, so its row carries no approval fields — the same answer the
|
|
8
|
+
* native harness gives, where a call that starts was authorized by
|
|
9
|
+
* construction. The row still carries the provenance that explains why it
|
|
10
|
+
* ran (`AUTO_APPROVE_ALL`), which is the field that answers "which policy
|
|
11
|
+
* layer governed this call". Until #1117 the Cursor translator stamped the
|
|
12
|
+
* policy's category verdict on the row at tool start, lease- and bypass-blind,
|
|
13
|
+
* so this row read `requiresApproval: true` with an approval message nobody
|
|
14
|
+
* was ever shown.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
|
|
18
|
+
import { ApprovalPolicySource, ToolCallStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
19
|
+
|
|
20
|
+
vi.mock("@cursor/sdk", async () =>
|
|
21
|
+
(await import("../../__test-utils__/scripted-sdk.js")).scriptedCursorSdkModule(),
|
|
22
|
+
);
|
|
23
|
+
vi.mock("@cursor/sdk/sqlite", async () =>
|
|
24
|
+
(await import("../../__test-utils__/scripted-sdk.js")).scriptedCursorSqliteModule(),
|
|
25
|
+
);
|
|
26
|
+
vi.mock("../../../../client/stigmer-client.js", async () =>
|
|
27
|
+
(await import("../../../../__test-utils__/hermetic-activity.js")).hermeticStigmerClientModule(),
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
import {
|
|
31
|
+
ScriptedClock,
|
|
32
|
+
createHermeticEnvironment,
|
|
33
|
+
type HermeticEnvironment,
|
|
34
|
+
} from "../../../../__test-utils__/hermetic-activity.js";
|
|
35
|
+
import { ScriptedCursorAgent, sdkEvents, step } from "../../__test-utils__/scripted-agent.js";
|
|
36
|
+
import {
|
|
37
|
+
FIXTURE,
|
|
38
|
+
SDK_CATALOG,
|
|
39
|
+
beginCursorScenario,
|
|
40
|
+
cursorExecutionRecord,
|
|
41
|
+
runCursorTurn,
|
|
42
|
+
} from "../../__test-utils__/hermetic-cursor.js";
|
|
43
|
+
import { stubRegistryFetch } from "../../../../__test-utils__/model-registry-fixture.js";
|
|
44
|
+
|
|
45
|
+
const AGENT_ID = "agent-hermetic-bypass-0001";
|
|
46
|
+
const RUN_ID = "run-hermetic-bypass-0001";
|
|
47
|
+
const CALL_ID = "call-hermetic-shell-0001";
|
|
48
|
+
const USER_MESSAGE = "Run the tests.";
|
|
49
|
+
const SHELL_ARGS = { command: "npm test" };
|
|
50
|
+
const SHELL_RESULT = "ok 12 tests\n";
|
|
51
|
+
const ASSISTANT_TEXT = "All twelve tests pass.";
|
|
52
|
+
|
|
53
|
+
describe("ExecuteCursor hermetic — a gated built-in under the global bypass", () => {
|
|
54
|
+
let env: HermeticEnvironment;
|
|
55
|
+
let registry: ReturnType<typeof stubRegistryFetch>;
|
|
56
|
+
const clock = new ScriptedClock();
|
|
57
|
+
|
|
58
|
+
beforeAll(() => {
|
|
59
|
+
env = createHermeticEnvironment();
|
|
60
|
+
registry = stubRegistryFetch();
|
|
61
|
+
clock.install();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
afterAll(() => {
|
|
65
|
+
clock.uninstall();
|
|
66
|
+
registry.restore();
|
|
67
|
+
env.dispose();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("persists the shell it ran as not held: no approval fields, AUTO_APPROVE_ALL provenance", async () => {
|
|
71
|
+
// ── Arrange ──────────────────────────────────────────────────────────────
|
|
72
|
+
const ev = sdkEvents(AGENT_ID, RUN_ID);
|
|
73
|
+
const agent = new ScriptedCursorAgent({
|
|
74
|
+
agentId: AGENT_ID,
|
|
75
|
+
runIds: [RUN_ID],
|
|
76
|
+
observeStep: () => clock.tick(),
|
|
77
|
+
turns: [
|
|
78
|
+
[
|
|
79
|
+
step.event(ev.init()),
|
|
80
|
+
step.event(ev.assistant("Running them now.")),
|
|
81
|
+
step.event(ev.toolCall(CALL_ID, "shell", "running", SHELL_ARGS)),
|
|
82
|
+
step.event(ev.toolCall(CALL_ID, "shell", "completed", SHELL_ARGS, SHELL_RESULT)),
|
|
83
|
+
step.event(ev.assistant(ASSISTANT_TEXT)),
|
|
84
|
+
step.turnEnded({ inputTokens: 1_500, outputTokens: 40, cacheReadTokens: 0, cacheWriteTokens: 0 }),
|
|
85
|
+
step.finished({ result: ASSISTANT_TEXT, model: { id: FIXTURE.model, params: [] } }),
|
|
86
|
+
],
|
|
87
|
+
],
|
|
88
|
+
});
|
|
89
|
+
const record = cursorExecutionRecord({ message: USER_MESSAGE, autoApproveAll: true });
|
|
90
|
+
const scenario = beginCursorScenario({
|
|
91
|
+
env,
|
|
92
|
+
clock,
|
|
93
|
+
record,
|
|
94
|
+
sdk: { agents: [agent], catalog: SDK_CATALOG },
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// ── Act ──────────────────────────────────────────────────────────────────
|
|
98
|
+
const invocation = await runCursorTurn(scenario);
|
|
99
|
+
|
|
100
|
+
// ── Assert: the turn ran to completion, nothing parked ───────────────────
|
|
101
|
+
expect(invocation.outcome.kind).toBe("returned");
|
|
102
|
+
expect((invocation.outcome as { value: Record<string, unknown> }).value.phase).toBe("EXECUTION_COMPLETED");
|
|
103
|
+
expect(record.waitingToolCalls(), "a bypassed call is never parked").toHaveLength(0);
|
|
104
|
+
expect(agent.runs[0].cancelCalls, "nothing was denied, so nothing was cancelled").toHaveLength(0);
|
|
105
|
+
|
|
106
|
+
// ── Assert: the row says it ran, and why, and that nobody was asked ───────
|
|
107
|
+
const rows = record.toolCalls();
|
|
108
|
+
expect(rows).toHaveLength(1);
|
|
109
|
+
const row = rows[0];
|
|
110
|
+
expect(row.name).toBe("shell");
|
|
111
|
+
expect(row.status).toBe(ToolCallStatus.TOOL_CALL_COMPLETED);
|
|
112
|
+
expect(row.approvalPolicySource, "the layer that let it run").toBe(ApprovalPolicySource.AUTO_APPROVE_ALL);
|
|
113
|
+
expect(row.requiresApproval, "never held for a decision").toBe(false);
|
|
114
|
+
expect(row.approvalMessage, "no card was ever shown").toBe("");
|
|
115
|
+
expect(row.approvalRequestedAt, "no approval was ever requested").toBe("");
|
|
116
|
+
|
|
117
|
+
// ── Assert: hermeticity ──────────────────────────────────────────────────
|
|
118
|
+
expect(registry.urls.every((u) => u.includes("/model-registry"))).toBe(true);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
@@ -160,15 +160,15 @@ describe.skipIf(!hasBash)("ExecuteCursor hermetic — file-review capture on a g
|
|
|
160
160
|
expect(candidate.value.changeSetId).toBe(CHANGE_SET_ID);
|
|
161
161
|
expect(candidate.value.changes.map((c) => c.pathAfter)).toEqual([FILE]);
|
|
162
162
|
|
|
163
|
-
// The flowed edit row:
|
|
164
|
-
//
|
|
165
|
-
//
|
|
166
|
-
//
|
|
163
|
+
// The flowed edit row: `edit` is a gated category, but capture mode let it
|
|
164
|
+
// FLOW, so nobody was asked — the row is COMPLETED, never WAITING_APPROVAL,
|
|
165
|
+
// carries no approval fields (#1117: `requiresApproval` means the call was
|
|
166
|
+
// held), and the review happens on the file card instead.
|
|
167
167
|
const rows = record.toolCalls();
|
|
168
168
|
expect(rows).toHaveLength(1);
|
|
169
169
|
expect(rows[0].name).toBe("edit");
|
|
170
170
|
expect(rows[0].status).toBe(ToolCallStatus.TOOL_CALL_COMPLETED);
|
|
171
|
-
expect(rows[0].requiresApproval, "
|
|
171
|
+
expect(rows[0].requiresApproval, "held for a decision? no: capture mode let it flow").toBe(false);
|
|
172
172
|
expect(record.waitingToolCalls(), "no gate row — the review is on the file card").toHaveLength(0);
|
|
173
173
|
|
|
174
174
|
// ── Assert: hermeticity ──────────────────────────────────────────────────
|
package/src/activities/execute-cursor/__tests__/hermetic/goldens/file-review-capture.status.json
CHANGED
|
@@ -16,8 +16,6 @@
|
|
|
16
16
|
"status": "TOOL_CALL_COMPLETED",
|
|
17
17
|
"startedAt": "2026-01-01T00:00:05.000Z",
|
|
18
18
|
"completedAt": "2026-01-01T00:00:06.000Z",
|
|
19
|
-
"requiresApproval": true,
|
|
20
|
-
"approvalMessage": "Write file: notes.md",
|
|
21
19
|
"argsPreview": "{\"path\":\"notes.md\",\"content\":\"# Notes\\n\\nA fixture file.\\n\\nReviewed by the hermetic run.\\n\"}",
|
|
22
20
|
"toolKind": "TOOL_KIND_FILE_EDIT",
|
|
23
21
|
"approvalPolicySource": "APPROVAL_POLICY_SOURCE_BUILTIN_CATEGORY",
|
package/src/activities/execute-cursor/__tests__/hermetic/goldens/resolution-error.status.json
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
}
|
|
13
13
|
],
|
|
14
14
|
"phase": "EXECUTION_FAILED",
|
|
15
|
-
"error": "
|
|
15
|
+
"error": "[Error] hermetic fault injected at getAgent",
|
|
16
16
|
"startedAt": "2026-01-01T00:00:00.000Z",
|
|
17
17
|
"completedAt": "2026-01-01T00:00:00.000Z"
|
|
18
18
|
}
|
|
@@ -15,9 +15,6 @@
|
|
|
15
15
|
"startedAt": "2026-01-01T00:00:25.000Z",
|
|
16
16
|
"completedAt": "2026-01-01T00:00:27.000Z",
|
|
17
17
|
"error": "sh: npm: command not found",
|
|
18
|
-
"requiresApproval": true,
|
|
19
|
-
"approvalMessage": "Run command: npm test",
|
|
20
|
-
"approvalRequestedAt": "2026-01-01T00:00:27.000Z",
|
|
21
18
|
"argsPreview": "{\"command\":\"npm test\"}",
|
|
22
19
|
"toolKind": "TOOL_KIND_SHELL",
|
|
23
20
|
"approvalPolicySource": "APPROVAL_POLICY_SOURCE_BUILTIN_CATEGORY",
|
|
@@ -15,8 +15,6 @@
|
|
|
15
15
|
"status": "TOOL_CALL_COMPLETED",
|
|
16
16
|
"startedAt": "2026-01-01T00:00:16.000Z",
|
|
17
17
|
"completedAt": "2026-01-01T00:00:19.000Z",
|
|
18
|
-
"requiresApproval": true,
|
|
19
|
-
"approvalMessage": "Run command: npm test",
|
|
20
18
|
"argsPreview": "{\"command\":\"npm test\"}",
|
|
21
19
|
"toolKind": "TOOL_KIND_SHELL",
|
|
22
20
|
"approvalPolicySource": "APPROVAL_POLICY_SOURCE_BUILTIN_CATEGORY",
|
package/src/activities/execute-cursor/__tests__/hermetic/goldens/shell-output-delta.status.json
CHANGED
|
@@ -15,8 +15,6 @@
|
|
|
15
15
|
"status": "TOOL_CALL_COMPLETED",
|
|
16
16
|
"startedAt": "2026-01-01T00:00:03.000Z",
|
|
17
17
|
"completedAt": "2026-01-01T00:00:08.000Z",
|
|
18
|
-
"requiresApproval": true,
|
|
19
|
-
"approvalMessage": "Run command: npm test",
|
|
20
18
|
"argsPreview": "{\"command\":\"npm test\"}",
|
|
21
19
|
"toolKind": "TOOL_KIND_SHELL",
|
|
22
20
|
"approvalPolicySource": "APPROVAL_POLICY_SOURCE_BUILTIN_CATEGORY",
|
|
@@ -13,8 +13,6 @@
|
|
|
13
13
|
},
|
|
14
14
|
"status": "TOOL_CALL_RUNNING",
|
|
15
15
|
"startedAt": "2026-01-01T00:00:03.000Z",
|
|
16
|
-
"requiresApproval": true,
|
|
17
|
-
"approvalMessage": "Run command: npm test",
|
|
18
16
|
"argsPreview": "{\"command\":\"npm test\"}",
|
|
19
17
|
"toolKind": "TOOL_KIND_SHELL",
|
|
20
18
|
"approvalPolicySource": "APPROVAL_POLICY_SOURCE_BUILTIN_CATEGORY",
|
package/src/activities/execute-cursor/__tests__/hermetic/goldens/unattributed-hook-block.status.json
CHANGED
|
@@ -15,9 +15,6 @@
|
|
|
15
15
|
"startedAt": "2026-01-01T00:00:03.000Z",
|
|
16
16
|
"completedAt": "2026-01-01T00:00:03.000Z",
|
|
17
17
|
"error": "blocked by a hook",
|
|
18
|
-
"requiresApproval": true,
|
|
19
|
-
"approvalMessage": "Run command: rm -rf build",
|
|
20
|
-
"approvalRequestedAt": "2026-01-01T00:00:03.000Z",
|
|
21
18
|
"argsPreview": "{\"command\":\"rm -rf build\"}",
|
|
22
19
|
"toolKind": "TOOL_KIND_SHELL",
|
|
23
20
|
"approvalPolicySource": "APPROVAL_POLICY_SOURCE_BUILTIN_CATEGORY",
|
|
@@ -17,8 +17,9 @@
|
|
|
17
17
|
* the stream's `running` instant; the live output reaches `result`
|
|
18
18
|
* (`streamingSource: OUTPUT` while it streams, cleared at finalize); the
|
|
19
19
|
* stream's `completed` stamps `completedAt` and its result wins; the shell
|
|
20
|
-
* row is a gated built-in
|
|
21
|
-
*
|
|
20
|
+
* row is a gated built-in the hook never saw, so it was never held and
|
|
21
|
+
* carries no approval fields (#1117 moved `requiresApproval` and "Run
|
|
22
|
+
* command: …" off rows that ran). Predicted: NO move (the
|
|
22
23
|
* translator drains the queued deltas AFTER the stream event of the
|
|
23
24
|
* same window, as the enricher applied them, so the stream's instant wins
|
|
24
25
|
* exactly as before).
|
|
@@ -33,9 +34,10 @@
|
|
|
33
34
|
* Arm 3 — the completion delta reports an ERROR, and a model event sits
|
|
34
35
|
* between it and the stream's own `error`. Pinned: the translator honours the
|
|
35
36
|
* delta's `result.status: "error"` — the row is FAILED from the
|
|
36
|
-
* instant the failure was observed (`completedAt`
|
|
37
|
-
*
|
|
38
|
-
*
|
|
37
|
+
* instant the failure was observed (`completedAt` at the delta's own tick),
|
|
38
|
+
* and the stream's `error` event that follows supplies the text. Until #1117
|
|
39
|
+
* a gated shell's ordinary failure also stamped `approvalRequestedAt`, though
|
|
40
|
+
* nobody was ever asked; a failure is not a request. Until #1097 (as this net first
|
|
39
41
|
* pinned it) the enricher promoted every completion delta to
|
|
40
42
|
* COMPLETED, the monotonic merge then refused the stream's FAILED, and the
|
|
41
43
|
* row ended COMPLETED carrying an `error` — a row that lied. Moved in #1097
|
|
@@ -178,7 +180,7 @@ describe("ExecuteCursor hermetic — the delta channel on a shell row", () => {
|
|
|
178
180
|
expect(row.result, "the live output reached the row").toBe(FULL_OUTPUT);
|
|
179
181
|
expect(row.isStreaming, "finalize closed the OUTPUT stream").toBe(false);
|
|
180
182
|
expect(row.startedAt < row.completedAt).toBe(true);
|
|
181
|
-
expect(row.requiresApproval, "a shell
|
|
183
|
+
expect(row.requiresApproval, "a shell the hook never saw was never held").toBe(false);
|
|
182
184
|
expect(row.approvalRequestedAt).toBe("");
|
|
183
185
|
expect(registry.urls.every((u) => u.includes("/model-registry"))).toBe(true);
|
|
184
186
|
|
|
@@ -268,7 +270,7 @@ describe("ExecuteCursor hermetic — the delta channel on a shell row", () => {
|
|
|
268
270
|
expect(row.status).toBe(ToolCallStatus.TOOL_CALL_FAILED);
|
|
269
271
|
expect(row.error, "the stream's error event supplied the text").toBe(SHELL_ERROR);
|
|
270
272
|
expect(row.result).toBe("");
|
|
271
|
-
expect(row.approvalRequestedAt, "a
|
|
273
|
+
expect(row.approvalRequestedAt, "a failure is not an approval request").toBe("");
|
|
272
274
|
|
|
273
275
|
const json = JSON.stringify(toJson(AgentExecutionStatusSchema, record.lastFullStatus!), null, 2) + "\n";
|
|
274
276
|
await expect(json).toMatchFileSnapshot("./goldens/shell-output-delta.error-delta.status.json");
|
|
@@ -37,6 +37,11 @@
|
|
|
37
37
|
* golden is unchanged: its turn fails before the workspace is provisioned,
|
|
38
38
|
* so no capture is pinned.
|
|
39
39
|
*
|
|
40
|
+
* Golden hunk since stigmer#1122: `resolution-error`'s `status.error` is
|
|
41
|
+
* `[Error] <message>`, the details row's text, where it was
|
|
42
|
+
* `Execution failed: [Error] <message>`; one grammar for a FAILED error,
|
|
43
|
+
* whichever side caught the fault.
|
|
44
|
+
*
|
|
40
45
|
* Regenerate ONLY after a deliberate behavior change:
|
|
41
46
|
* npx vitest run src/activities/execute-cursor/__tests__/hermetic -u
|
|
42
47
|
*/
|
|
@@ -180,7 +185,7 @@ describe("ExecuteCursor hermetic — thrown-error arms of the outer catch", () =
|
|
|
180
185
|
expect(slim.phase).toBe("EXECUTION_FAILED");
|
|
181
186
|
expect(record.persistedPhases).toEqual([ExecutionPhase.EXECUTION_FAILED]);
|
|
182
187
|
const final = record.lastFullStatus!;
|
|
183
|
-
expect(final.error).toBe(`
|
|
188
|
+
expect(final.error).toBe(`[Error] ${CONTROL_PLANE_FAULT}`);
|
|
184
189
|
expect(final.completedAt).not.toBe("");
|
|
185
190
|
expect(systemMessages(final)).toEqual([
|
|
186
191
|
GENERIC_SYSTEM_MESSAGE,
|
|
@@ -296,14 +296,13 @@ describe("Cursor HITL resume — append-only transcript", () => {
|
|
|
296
296
|
expect(status.subAgentExecutions.some((s) => s.id === "sub_1")).toBe(true);
|
|
297
297
|
});
|
|
298
298
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
299
|
+
// The transcript-superset fix keeps the run off the watchdog, but the OTHER
|
|
300
|
+
// half of "no loop" is that the approved tools' grants actually let the hook
|
|
301
|
+
// ALLOW the re-issued calls. Drive the real bash hook with the grants the
|
|
302
|
+
// resume mints from the persisted run-1 decisions and prove the two approved
|
|
303
|
+
// MCP tools pass while the genuinely-new `click` is still gated. The hook
|
|
304
|
+
// needs bash, mirroring the d() guard.
|
|
305
|
+
it.skipIf(!hasBash)("FIX: the approved tools are ALLOWED by the hook on resume — not re-denied (the loop's source)", () => {
|
|
307
306
|
const { pendingApprovals, decisions } = reconstructAdjudicatedApprovals(persistedRunOneMessages());
|
|
308
307
|
const grants = buildApprovalGrants(pendingApprovals, decisions);
|
|
309
308
|
const state = buildApprovalState(new Map(), false, new Set(), grants);
|
|
@@ -426,9 +425,7 @@ describe("Cursor HITL resume — two approvals then clean completion (no loop)",
|
|
|
426
425
|
expect(guardRejectionReason(committed, seeded)).toBeUndefined();
|
|
427
426
|
});
|
|
428
427
|
|
|
429
|
-
it("the built-in grants actually let the hook allow both re-issues (full closure)", () => {
|
|
430
|
-
if (!hasBash) return;
|
|
431
|
-
|
|
428
|
+
it.skipIf(!hasBash)("the built-in grants actually let the hook allow both re-issues (full closure)", () => {
|
|
432
429
|
const { pendingApprovals, decisions } = reconstructAdjudicatedApprovals(committedBuiltInApprovals());
|
|
433
430
|
const grants = buildApprovalGrants(pendingApprovals, decisions);
|
|
434
431
|
const state = buildApprovalState(new Map(), false, new Set(), grants);
|
|
@@ -170,9 +170,7 @@ describe("Cursor sequential gates A->B across resume", () => {
|
|
|
170
170
|
expect(guardRejectionReason(committed, seeded)).toBeUndefined();
|
|
171
171
|
});
|
|
172
172
|
|
|
173
|
-
it("the hook ALLOWS gate A's re-issue and DENIES gate B (out-of-process closure)", () => {
|
|
174
|
-
if (!hasBash) return;
|
|
175
|
-
|
|
173
|
+
it.skipIf(!hasBash)("the hook ALLOWS gate A's re-issue and DENIES gate B (out-of-process closure)", () => {
|
|
176
174
|
// Only gate A is approved going into the resume; its grant is what the hook
|
|
177
175
|
// reads. Gate B has no grant yet (it gates for the first time this turn).
|
|
178
176
|
const { pendingApprovals, decisions } = reconstructAdjudicatedApprovals(approvedGateA());
|