@stigmer/runner 3.10.0 → 3.11.1-dev.20260812192248
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 +14 -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 +39 -0
- package/dist/activities/discover-mcp-server.js +165 -28
- package/dist/activities/discover-mcp-server.js.map +1 -1
- package/dist/activities/emit-event.d.ts +14 -2
- package/dist/activities/emit-event.js +52 -17
- package/dist/activities/emit-event.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 +59 -3
- package/dist/activities/execute-cursor/error-classifier.js +115 -6
- 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 +51 -11
- package/dist/activities/execute-cursor/index.js +177 -93
- 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/model-pricing-data.d.ts +2 -0
- package/dist/activities/execute-cursor/model-pricing-data.js +13 -3
- package/dist/activities/execute-cursor/model-pricing-data.js.map +1 -1
- package/dist/activities/execute-cursor/prompt-builder.d.ts +51 -4
- package/dist/activities/execute-cursor/prompt-builder.js +49 -7
- package/dist/activities/execute-cursor/prompt-builder.js.map +1 -1
- package/dist/activities/execute-cursor/turn-recovery.d.ts +52 -0
- package/dist/activities/execute-cursor/turn-recovery.js +193 -0
- package/dist/activities/execute-cursor/turn-recovery.js.map +1 -0
- 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 +65 -24
- package/dist/activities/execute-deep-agent/setup.js.map +1 -1
- package/dist/activities/execute-deep-agent/shell-env.d.ts +5 -1
- package/dist/activities/execute-deep-agent/shell-env.js +7 -9
- package/dist/activities/execute-deep-agent/shell-env.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 +15 -2
- package/dist/activities/execute-deep-agent/subagent-transformer.js.map +1 -1
- package/dist/activities/execute-deep-agent/subagent-wiring.d.ts +27 -6
- package/dist/activities/execute-deep-agent/subagent-wiring.js +26 -5
- package/dist/activities/execute-deep-agent/subagent-wiring.js.map +1 -1
- package/dist/activities/hydrate-workflow-execution.js +8 -3
- package/dist/activities/hydrate-workflow-execution.js.map +1 -1
- package/dist/activities/run-command.d.ts +5 -2
- package/dist/activities/run-command.js +22 -10
- package/dist/activities/run-command.js.map +1 -1
- package/dist/activities/run-env.d.ts +40 -0
- package/dist/activities/run-env.js +66 -0
- package/dist/activities/run-env.js.map +1 -0
- 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/bootstrap.js +5 -0
- package/dist/bootstrap.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 +83 -21
- package/dist/client/stigmer-client.js +106 -29
- package/dist/client/stigmer-client.js.map +1 -1
- package/dist/encryption/config.d.ts +64 -0
- package/dist/encryption/config.js +108 -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/middleware/index.d.ts +3 -0
- package/dist/middleware/index.js +7 -0
- package/dist/middleware/index.js.map +1 -1
- package/dist/middleware/path-normalization.d.ts +57 -0
- package/dist/middleware/path-normalization.js +109 -0
- package/dist/middleware/path-normalization.js.map +1 -0
- package/dist/middleware/types.d.ts +8 -0
- package/dist/payload-codecs.d.ts +17 -0
- package/dist/payload-codecs.js +42 -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 +21 -17
- package/dist/runner-manager.js.map +1 -1
- package/dist/runner.js +11 -18
- 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/artifact-storage.d.ts +23 -1
- package/dist/shared/artifact-storage.js +51 -10
- package/dist/shared/artifact-storage.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 +26 -1
- package/dist/shared/checkpointer/http-saver.js +54 -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/conversation-catchup.d.ts +9 -2
- package/dist/shared/conversation-catchup.js +39 -6
- package/dist/shared/conversation-catchup.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/grpc-retry.d.ts +5 -1
- package/dist/shared/grpc-retry.js +5 -1
- package/dist/shared/grpc-retry.js.map +1 -1
- package/dist/shared/http-retry.d.ts +85 -0
- package/dist/shared/http-retry.js +101 -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 +11 -5
- package/dist/shared/mcp-manager.js +47 -8
- 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/mcp-schema-sanitizer.d.ts +70 -0
- package/dist/shared/mcp-schema-sanitizer.js +197 -0
- package/dist/shared/mcp-schema-sanitizer.js.map +1 -0
- package/dist/shared/model-client.d.ts +18 -5
- package/dist/shared/model-client.js +149 -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/model-pricing-data.d.ts +2 -0
- package/dist/shared/model-pricing-data.js +13 -3
- package/dist/shared/model-pricing-data.js.map +1 -1
- package/dist/shared/model-registry.js +3 -2
- package/dist/shared/model-registry.js.map +1 -1
- package/dist/shared/plan-mode-permissions.d.ts +33 -0
- package/dist/shared/plan-mode-permissions.js +35 -0
- package/dist/shared/plan-mode-permissions.js.map +1 -0
- package/dist/shared/registry-endpoint.d.ts +20 -0
- package/dist/shared/registry-endpoint.js +25 -0
- package/dist/shared/registry-endpoint.js.map +1 -1
- package/dist/shared/runner-credential-keys.d.ts +24 -0
- package/dist/shared/runner-credential-keys.js +47 -0
- package/dist/shared/runner-credential-keys.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/resolve.d.ts +19 -1
- package/dist/workflow-engine/resolve.js +37 -2
- package/dist/workflow-engine/resolve.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 +9 -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/dist/workflows/types.d.ts +8 -0
- package/package.json +9 -4
- package/src/__test-utils__/__tests__/vitest-global-setup.test.ts +37 -0
- package/src/__test-utils__/vitest-global-setup.ts +50 -0
- package/src/__tests__/bootstrap.test.ts +43 -0
- package/src/__tests__/claimcheck-codec.test.ts +36 -0
- package/src/__tests__/encryption-codec.test.ts +287 -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 +65 -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 +242 -1
- package/src/activities/__tests__/error-classifier.test.ts +94 -2
- package/src/activities/__tests__/run-command.test.ts +221 -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 +203 -31
- package/src/activities/emit-event.ts +65 -21
- 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 +151 -16
- 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__/model-pricing.test.ts +62 -1
- package/src/activities/execute-cursor/__tests__/prompt-builder-delegation.test.ts +1 -1
- package/src/activities/execute-cursor/__tests__/turn-recovery.test.ts +160 -0
- 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 +131 -7
- 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 +209 -109
- package/src/activities/execute-cursor/mcp-resolver.ts +36 -2
- package/src/activities/execute-cursor/model-pricing-data.ts +22 -3
- package/src/activities/execute-cursor/prompt-builder.ts +85 -9
- package/src/activities/execute-cursor/turn-recovery.ts +208 -0
- 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__/plan-mode-path-normalization.test.ts +174 -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__/shell-env.test.ts +24 -10
- package/src/activities/execute-deep-agent/__tests__/subagent-plan-mode-permissions.test.ts +178 -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 +85 -29
- package/src/activities/execute-deep-agent/shell-env.ts +8 -9
- package/src/activities/execute-deep-agent/subagent-transformer.ts +30 -2
- package/src/activities/execute-deep-agent/subagent-wiring.ts +39 -6
- package/src/activities/hydrate-workflow-execution.ts +8 -3
- package/src/activities/run-command.ts +25 -12
- package/src/activities/run-env.ts +79 -0
- package/src/activities/workflow-event-activities.ts +96 -69
- package/src/bootstrap.ts +5 -0
- package/src/claimcheck/payload-codec.ts +33 -1
- package/src/client/__tests__/stigmer-client.test.ts +110 -11
- package/src/client/stigmer-client.ts +150 -37
- package/src/encryption/config.ts +155 -0
- package/src/encryption/index.ts +3 -0
- package/src/encryption/payload-codec.ts +152 -0
- package/src/middleware/__tests__/path-normalization.test.ts +140 -0
- package/src/middleware/index.ts +8 -0
- package/src/middleware/path-normalization.ts +125 -0
- package/src/middleware/types.ts +11 -0
- package/src/payload-codecs.ts +62 -0
- package/src/preflight.ts +45 -0
- package/src/runner-manager.ts +26 -26
- package/src/runner.ts +12 -27
- package/src/shared/__tests__/approval-policy.test.ts +82 -39
- package/src/shared/__tests__/artifact-storage.test.ts +145 -2
- 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__/conversation-catchup.test.ts +24 -0
- 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 +68 -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 +137 -15
- package/src/shared/__tests__/mcp-resolver.test.ts +146 -3
- package/src/shared/__tests__/mcp-schema-sanitizer.test.ts +267 -0
- package/src/shared/__tests__/model-client.test.ts +200 -0
- package/src/shared/__tests__/model-error.test.ts +289 -1
- package/src/shared/__tests__/model-pricing.test.ts +62 -2
- package/src/shared/__tests__/model-registry.test.ts +22 -0
- 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/artifact-storage.ts +75 -10
- 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 +71 -8
- package/src/shared/conversation-attachment.ts +1 -0
- package/src/shared/conversation-catchup.ts +39 -6
- package/src/shared/datastore-attachment.ts +106 -11
- package/src/shared/grpc-retry.ts +5 -1
- package/src/shared/http-retry.ts +139 -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 +56 -8
- package/src/shared/mcp-resolver.ts +73 -2
- package/src/shared/mcp-schema-sanitizer.ts +224 -0
- package/src/shared/model-client.ts +179 -19
- package/src/shared/model-error.ts +222 -4
- package/src/shared/model-pricing-data.ts +22 -3
- package/src/shared/model-registry.ts +7 -2
- package/src/shared/plan-mode-permissions.ts +37 -0
- package/src/shared/registry-endpoint.ts +27 -0
- package/src/shared/runner-credential-keys.ts +46 -0
- package/src/worker.ts +4 -5
- package/src/workflow-engine/__tests__/tasks/emit-event.test.ts +127 -49
- package/src/workflow-engine/resolve.ts +48 -2
- package/src/workflow-engine/types.ts +18 -0
- package/src/workflows/__tests__/connect-mcp-server.test.ts +25 -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 +9 -0
- package/src/workflows/engine-core.ts +23 -2
- package/src/workflows/execute-from-execution.ts +12 -2
- package/src/workflows/types.ts +8 -0
- package/dist/activities/execute-cursor/mcp-config.d.ts +0 -30
- package/dist/activities/execute-cursor/mcp-config.js +0 -39
- package/dist/activities/execute-cursor/mcp-config.js.map +0 -1
- package/src/activities/execute-cursor/mcp-config.ts +0 -66
package/src/runner-manager.ts
CHANGED
|
@@ -22,7 +22,6 @@ import {
|
|
|
22
22
|
type InjectedSinks,
|
|
23
23
|
type WorkflowBundleOption,
|
|
24
24
|
} from "@temporalio/worker";
|
|
25
|
-
import type { PayloadCodec } from "@temporalio/common";
|
|
26
25
|
import type { Config } from "./config.js";
|
|
27
26
|
import { DEFAULT_CURSOR_AGENT_RESOLVE_TIMEOUT_MS, DEFAULT_CURSOR_STREAM_STALL_TIMEOUT_MS, DEFAULT_WORKSPACE_LOCK_TIMEOUT_MS } from "./config.js";
|
|
28
27
|
// Boot marks mirror the static runner's segment names where the work is the
|
|
@@ -32,6 +31,7 @@ import { markBoot } from "./shared/cold-start-timing.js";
|
|
|
32
31
|
import type { WorkerActivities } from "./worker.js";
|
|
33
32
|
import { resolveWorkflowSource, OTEL_WORKFLOW_INTERCEPTOR_MODULE } from "./workflow-source.js";
|
|
34
33
|
import { resolveRunnerBootstrap, refreshRunnerAccessToken } from "./bootstrap.js";
|
|
34
|
+
import { assertLlmBackendsPreflight } from "./preflight.js";
|
|
35
35
|
import { createRunnerTokenCoordinator } from "./runner-token-coordinator.js";
|
|
36
36
|
// Per-task-queue in-flight activity tracking lives in ./in-flight.ts so the
|
|
37
37
|
// activity interceptor (no manager-closure handle) and unit tests can reach it.
|
|
@@ -237,6 +237,8 @@ export async function createStigmerRunnerManager(
|
|
|
237
237
|
const runnerTokenRef = { current: options.stigmerToken ?? null };
|
|
238
238
|
const baseConfig = mapManagerOptionsToConfig(options, tokenRef, runnerTokenRef);
|
|
239
239
|
|
|
240
|
+
assertLlmBackendsPreflight(baseConfig.proxyEndpoint);
|
|
241
|
+
|
|
240
242
|
// Install the Cursor SDK interceptors BEFORE resolving Temporal coordinates.
|
|
241
243
|
// Coordinate discovery dials the control plane via StigmerClient, which loads
|
|
242
244
|
// @connectrpc/connect-node and snapshots the node:http2 ESM facade on first
|
|
@@ -291,8 +293,10 @@ export async function createStigmerRunnerManager(
|
|
|
291
293
|
// Resolve the runner bootstrap after the http2 patch is in place (discovery
|
|
292
294
|
// dials the control plane through connect-node). An explicit address wins;
|
|
293
295
|
// otherwise a token triggers control-plane discovery; otherwise localhost.
|
|
294
|
-
// Must happen before createAllActivities so
|
|
295
|
-
//
|
|
296
|
+
// Must happen before createAllActivities so the activities' Config carries
|
|
297
|
+
// the resolved coordinates. Only the worker connection dials Temporal — no
|
|
298
|
+
// activity does (emit-event, the last one, now routes signals through the
|
|
299
|
+
// server's SendSignal lane; see oss#517).
|
|
296
300
|
const bootstrap = await resolveRunnerBootstrap({
|
|
297
301
|
explicitAddress: options.temporalAddress,
|
|
298
302
|
explicitNamespace: options.temporalNamespace,
|
|
@@ -351,7 +355,24 @@ export async function createStigmerRunnerManager(
|
|
|
351
355
|
`[runner-manager] Artifact store: could not resolve config for boot log: ${err}`,
|
|
352
356
|
);
|
|
353
357
|
}
|
|
354
|
-
const
|
|
358
|
+
const { createPayloadCodecs } = await import("./payload-codecs.js");
|
|
359
|
+
// Server-managed payload-encryption keys (cloud desktop runners) ride the
|
|
360
|
+
// bootstrap response; an env-configured key still wins inside the loader.
|
|
361
|
+
// A cloud server that mints runner tokens but returns no key predates key
|
|
362
|
+
// management — payloads then rest plaintext in cloud Temporal history, which
|
|
363
|
+
// deserves a diagnosable warning rather than silence.
|
|
364
|
+
const payloadCodecs = await createPayloadCodecs(config, bootstrap.payloadEncryption);
|
|
365
|
+
if (
|
|
366
|
+
!bootstrap.payloadEncryption &&
|
|
367
|
+
bootstrap.runnerAccessToken &&
|
|
368
|
+
!process.env.STIGMER_PAYLOAD_ENCRYPTION_KEY
|
|
369
|
+
) {
|
|
370
|
+
console.warn(
|
|
371
|
+
"[runner-manager] Server minted a runner token but returned no payload " +
|
|
372
|
+
"encryption key; Temporal payloads will be plaintext (server predates " +
|
|
373
|
+
"runner key management?)",
|
|
374
|
+
);
|
|
375
|
+
}
|
|
355
376
|
|
|
356
377
|
const connection = await NativeConnection.connect({
|
|
357
378
|
address: config.temporalAddress,
|
|
@@ -396,9 +417,7 @@ export async function createStigmerRunnerManager(
|
|
|
396
417
|
activities,
|
|
397
418
|
workflowBundle,
|
|
398
419
|
maxConcurrentActivityTaskExecutions: config.maxConcurrentActivities,
|
|
399
|
-
dataConverter:
|
|
400
|
-
? { payloadCodecs: [payloadCodec] }
|
|
401
|
-
: undefined,
|
|
420
|
+
dataConverter: payloadCodecs?.length ? { payloadCodecs } : undefined,
|
|
402
421
|
sinks: interceptorConfig.sinks,
|
|
403
422
|
interceptors: interceptorConfig.interceptors,
|
|
404
423
|
});
|
|
@@ -739,25 +758,6 @@ async function createAllActivities(config: Config): Promise<WorkerActivities> {
|
|
|
739
758
|
};
|
|
740
759
|
}
|
|
741
760
|
|
|
742
|
-
async function createPayloadCodec(
|
|
743
|
-
config: Config,
|
|
744
|
-
): Promise<PayloadCodec | undefined> {
|
|
745
|
-
const { loadClaimcheckConfig, ClaimcheckPayloadCodec } = await import(
|
|
746
|
-
"./claimcheck/index.js"
|
|
747
|
-
);
|
|
748
|
-
const claimcheckConfig = loadClaimcheckConfig();
|
|
749
|
-
if (!claimcheckConfig.enabled) {
|
|
750
|
-
return undefined;
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
const { loadArtifactStorageConfig, createArtifactStorage } = await import(
|
|
754
|
-
"./shared/artifact-storage.js"
|
|
755
|
-
);
|
|
756
|
-
const storageConfig = loadArtifactStorageConfig(config);
|
|
757
|
-
const storage = createArtifactStorage(storageConfig);
|
|
758
|
-
return new ClaimcheckPayloadCodec(storage, claimcheckConfig);
|
|
759
|
-
}
|
|
760
|
-
|
|
761
761
|
interface InterceptorConfig {
|
|
762
762
|
sinks: InjectedSinks<any>;
|
|
763
763
|
interceptors: Record<string, any>;
|
package/src/runner.ts
CHANGED
|
@@ -15,11 +15,11 @@
|
|
|
15
15
|
import { mkdirSync } from "node:fs";
|
|
16
16
|
import { join } from "node:path";
|
|
17
17
|
import { homedir, tmpdir } from "node:os";
|
|
18
|
-
import type { PayloadCodec } from "@temporalio/common";
|
|
19
18
|
import type { Config } from "./config.js";
|
|
20
19
|
import { DEFAULT_CURSOR_AGENT_RESOLVE_TIMEOUT_MS, DEFAULT_CURSOR_STREAM_STALL_TIMEOUT_MS, DEFAULT_WORKSPACE_LOCK_TIMEOUT_MS } from "./config.js";
|
|
21
20
|
import type { WorkerActivities } from "./worker.js";
|
|
22
21
|
import { resolveRunnerBootstrap } from "./bootstrap.js";
|
|
22
|
+
import { assertLlmBackendsPreflight } from "./preflight.js";
|
|
23
23
|
import { markBoot, emitRunnerBootTiming } from "./shared/cold-start-timing.js";
|
|
24
24
|
|
|
25
25
|
/**
|
|
@@ -197,6 +197,8 @@ export async function createStigmerRunner(
|
|
|
197
197
|
|
|
198
198
|
const baseConfig = mapOptionsToConfig(options);
|
|
199
199
|
|
|
200
|
+
assertLlmBackendsPreflight(baseConfig.proxyEndpoint);
|
|
201
|
+
|
|
200
202
|
// Install the Cursor SDK interceptors BEFORE resolving Temporal coordinates.
|
|
201
203
|
// Coordinate discovery dials the control plane via StigmerClient, which loads
|
|
202
204
|
// @connectrpc/connect-node and snapshots the node:http2 ESM facade on first
|
|
@@ -235,8 +237,9 @@ export async function createStigmerRunner(
|
|
|
235
237
|
// Resolve Temporal coordinates after the http2 patch is in place (discovery
|
|
236
238
|
// dials the control plane through connect-node). Explicit address wins;
|
|
237
239
|
// otherwise a token triggers control-plane discovery; otherwise localhost.
|
|
238
|
-
//
|
|
239
|
-
//
|
|
240
|
+
// Only the worker connection consumes these coordinates — no activity dials
|
|
241
|
+
// Temporal directly (emit-event, the last one, now routes signals through
|
|
242
|
+
// the server's SendSignal lane; see oss#517).
|
|
240
243
|
//
|
|
241
244
|
// The static runner brings its own already-proxy-valid token (harness/CLI),
|
|
242
245
|
// so it does not consume the minted runner token from the bootstrap response;
|
|
@@ -302,10 +305,14 @@ export async function createStigmerRunner(
|
|
|
302
305
|
);
|
|
303
306
|
}
|
|
304
307
|
|
|
305
|
-
const
|
|
308
|
+
const { createPayloadCodecs } = await import("./payload-codecs.js");
|
|
309
|
+
// Server-managed keys from the bootstrap response apply here too (a CLI
|
|
310
|
+
// daemon with a cloud token is desktop-class); cloud sandboxes are
|
|
311
|
+
// unaffected — their env-injected platform key wins inside the loader.
|
|
312
|
+
const payloadCodecs = await createPayloadCodecs(config, coordinates.payloadEncryption);
|
|
306
313
|
|
|
307
314
|
const { startWorker } = await import("./worker.js");
|
|
308
|
-
const worker = await startWorker({ config, activities,
|
|
315
|
+
const worker = await startWorker({ config, activities, payloadCodecs });
|
|
309
316
|
markBoot("worker_created");
|
|
310
317
|
|
|
311
318
|
return {
|
|
@@ -455,28 +462,6 @@ async function createAllActivities(config: Config): Promise<WorkerActivities> {
|
|
|
455
462
|
};
|
|
456
463
|
}
|
|
457
464
|
|
|
458
|
-
async function createPayloadCodec(config: Config): Promise<PayloadCodec | undefined> {
|
|
459
|
-
const { loadClaimcheckConfig, ClaimcheckPayloadCodec } = await import("./claimcheck/index.js");
|
|
460
|
-
const claimcheckConfig = loadClaimcheckConfig();
|
|
461
|
-
if (!claimcheckConfig.enabled) {
|
|
462
|
-
return undefined;
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
const { loadArtifactStorageConfig, createArtifactStorage } = await import(
|
|
466
|
-
"./shared/artifact-storage.js"
|
|
467
|
-
);
|
|
468
|
-
const storageConfig = loadArtifactStorageConfig(config);
|
|
469
|
-
const storage = createArtifactStorage(storageConfig);
|
|
470
|
-
|
|
471
|
-
console.log(
|
|
472
|
-
`[runner] Claimcheck enabled (threshold=${claimcheckConfig.thresholdBytes}B, ` +
|
|
473
|
-
`compression=${claimcheckConfig.compressionEnabled}, ` +
|
|
474
|
-
`storage=${storageConfig.type})`,
|
|
475
|
-
);
|
|
476
|
-
|
|
477
|
-
return new ClaimcheckPayloadCodec(storage, claimcheckConfig);
|
|
478
|
-
}
|
|
479
|
-
|
|
480
465
|
function resolveDefaultWorkspaceDir(): string {
|
|
481
466
|
try {
|
|
482
467
|
const dir = join(homedir(), ".stigmer", "workspaces", "runner");
|
|
@@ -19,7 +19,6 @@ import {
|
|
|
19
19
|
} from "../approval-policy.js";
|
|
20
20
|
import type { ToolApprovalCategory } from "../tool-kind.js";
|
|
21
21
|
import type { ResolvedMcpServer } from "../mcp-resolver.js";
|
|
22
|
-
import type { ToolApprovalOverride } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/spec_pb";
|
|
23
22
|
import type { AgentExecution } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
|
|
24
23
|
import { ApprovalAction, ApprovalPolicySource } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
25
24
|
|
|
@@ -83,6 +82,9 @@ function makeServer(
|
|
|
83
82
|
slug: string,
|
|
84
83
|
toolApprovals: Array<{ toolName: string; message?: string }>,
|
|
85
84
|
pinnedToolApprovals: Array<{ toolName: string; message?: string }> = [],
|
|
85
|
+
// Layer-3 overrides ride the server they were declared on (issue #349) —
|
|
86
|
+
// there is no cross-server override input to the merge anymore.
|
|
87
|
+
toolApprovalOverrides: Array<{ toolName: string; requiresApproval: boolean; message?: string }> = [],
|
|
86
88
|
): ResolvedMcpServer {
|
|
87
89
|
return {
|
|
88
90
|
slug,
|
|
@@ -95,6 +97,11 @@ function makeServer(
|
|
|
95
97
|
toolName: a.toolName,
|
|
96
98
|
message: a.message ?? "",
|
|
97
99
|
})) as any[],
|
|
100
|
+
toolApprovalOverrides: toolApprovalOverrides.map(o => ({
|
|
101
|
+
toolName: o.toolName,
|
|
102
|
+
requiresApproval: o.requiresApproval,
|
|
103
|
+
message: o.message ?? "",
|
|
104
|
+
})) as any[],
|
|
98
105
|
discoveredCapabilitiesEmpty: false,
|
|
99
106
|
};
|
|
100
107
|
}
|
|
@@ -102,7 +109,7 @@ function makeServer(
|
|
|
102
109
|
describe("mergeApprovalPolicies", () => {
|
|
103
110
|
it("returns empty map under a global lease (spec.auto_approve_all)", () => {
|
|
104
111
|
const servers = [makeServer("github", [{ toolName: "push" }])];
|
|
105
|
-
const result = mergeApprovalPolicies(servers,
|
|
112
|
+
const result = mergeApprovalPolicies(servers, leases({ global: true }));
|
|
106
113
|
expect(result.size).toBe(0);
|
|
107
114
|
});
|
|
108
115
|
|
|
@@ -111,7 +118,7 @@ describe("mergeApprovalPolicies", () => {
|
|
|
111
118
|
makeServer("github", [{ toolName: "push" }]),
|
|
112
119
|
makeServer("database", [{ toolName: "drop_table" }]),
|
|
113
120
|
];
|
|
114
|
-
const result = mergeApprovalPolicies(servers,
|
|
121
|
+
const result = mergeApprovalPolicies(servers, leases({ servers: ["github"] }));
|
|
115
122
|
expect(result.has("github/push")).toBe(false);
|
|
116
123
|
expect(result.has("database/drop_table")).toBe(true);
|
|
117
124
|
});
|
|
@@ -122,7 +129,7 @@ describe("mergeApprovalPolicies", () => {
|
|
|
122
129
|
{ toolName: "create_issue", message: "Create issue?" },
|
|
123
130
|
]),
|
|
124
131
|
];
|
|
125
|
-
const result = mergeApprovalPolicies(servers,
|
|
132
|
+
const result = mergeApprovalPolicies(servers, NO_LEASES);
|
|
126
133
|
|
|
127
134
|
expect(result.size).toBe(1);
|
|
128
135
|
const policy = result.get("github/create_issue")!;
|
|
@@ -139,7 +146,7 @@ describe("mergeApprovalPolicies", () => {
|
|
|
139
146
|
[{ toolName: "push", message: "pinned message" }],
|
|
140
147
|
),
|
|
141
148
|
];
|
|
142
|
-
const result = mergeApprovalPolicies(servers,
|
|
149
|
+
const result = mergeApprovalPolicies(servers, NO_LEASES);
|
|
143
150
|
|
|
144
151
|
const policy = result.get("github/push")!;
|
|
145
152
|
expect(policy.approvalMessage).toBe("pinned message");
|
|
@@ -147,34 +154,74 @@ describe("mergeApprovalPolicies", () => {
|
|
|
147
154
|
|
|
148
155
|
it("agent overrides can disable approval", () => {
|
|
149
156
|
const servers = [
|
|
150
|
-
makeServer("github", [{ toolName: "push" }]
|
|
157
|
+
makeServer("github", [{ toolName: "push" }], [], [
|
|
158
|
+
{ toolName: "push", requiresApproval: false },
|
|
159
|
+
]),
|
|
151
160
|
];
|
|
152
|
-
const overrides: ToolApprovalOverride[] = [{
|
|
153
|
-
toolName: "push",
|
|
154
|
-
requiresApproval: false,
|
|
155
|
-
message: "",
|
|
156
|
-
}] as any[];
|
|
157
161
|
|
|
158
|
-
const result = mergeApprovalPolicies(servers,
|
|
162
|
+
const result = mergeApprovalPolicies(servers, NO_LEASES);
|
|
159
163
|
expect(result.size).toBe(0);
|
|
160
164
|
});
|
|
161
165
|
|
|
162
166
|
it("agent overrides can add new approval requirement", () => {
|
|
163
|
-
const servers = [
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
const result = mergeApprovalPolicies(servers, overrides, NO_LEASES);
|
|
167
|
+
const servers = [
|
|
168
|
+
makeServer("github", [], [], [
|
|
169
|
+
{ toolName: "delete_repo", requiresApproval: true, message: "Really delete?" },
|
|
170
|
+
]),
|
|
171
|
+
];
|
|
172
|
+
|
|
173
|
+
const result = mergeApprovalPolicies(servers, NO_LEASES);
|
|
171
174
|
expect(result.has("github/delete_repo")).toBe(true);
|
|
172
175
|
});
|
|
173
176
|
|
|
177
|
+
describe("override scoping (issue #349 — no cross-server leak)", () => {
|
|
178
|
+
it("a disable override on one server leaves a same-named classifier-gated tool on another server gated", () => {
|
|
179
|
+
// The dangerous direction: before #349 this override silently
|
|
180
|
+
// un-gated database/delete_item too (authorization widening).
|
|
181
|
+
const servers = [
|
|
182
|
+
makeServer("github", [{ toolName: "delete_item" }], [], [
|
|
183
|
+
{ toolName: "delete_item", requiresApproval: false },
|
|
184
|
+
]),
|
|
185
|
+
makeServer("database", [{ toolName: "delete_item" }]),
|
|
186
|
+
];
|
|
187
|
+
const result = mergeApprovalPolicies(servers, NO_LEASES);
|
|
188
|
+
expect(result.has("github/delete_item")).toBe(false);
|
|
189
|
+
const other = result.get("database/delete_item")!;
|
|
190
|
+
expect(other.requiresApproval).toBe(true);
|
|
191
|
+
expect(other.source).toBe("classifier_default");
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("an enable override on one server adds no gate for a same-named tool on another server", () => {
|
|
195
|
+
const servers = [
|
|
196
|
+
makeServer("github", [], [], [
|
|
197
|
+
{ toolName: "send_message", requiresApproval: true },
|
|
198
|
+
]),
|
|
199
|
+
makeServer("slack", []),
|
|
200
|
+
];
|
|
201
|
+
const result = mergeApprovalPolicies(servers, NO_LEASES);
|
|
202
|
+
expect(result.has("github/send_message")).toBe(true);
|
|
203
|
+
expect(result.has("slack/send_message")).toBe(false);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it("a message override on one server does not re-message a same-named gate on another server", () => {
|
|
207
|
+
const servers = [
|
|
208
|
+
makeServer("github", [{ toolName: "push", message: "github classifier" }], [], [
|
|
209
|
+
{ toolName: "push", requiresApproval: true, message: "github override" },
|
|
210
|
+
]),
|
|
211
|
+
makeServer("gitlab", [{ toolName: "push", message: "gitlab classifier" }]),
|
|
212
|
+
];
|
|
213
|
+
const result = mergeApprovalPolicies(servers, NO_LEASES);
|
|
214
|
+
expect(result.get("github/push")!.approvalMessage).toBe("github override");
|
|
215
|
+
const other = result.get("gitlab/push")!;
|
|
216
|
+
expect(other.approvalMessage).toBe("gitlab classifier");
|
|
217
|
+
expect(other.source).toBe("classifier_default");
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
|
|
174
221
|
describe("provenance (source stamping)", () => {
|
|
175
222
|
it("stamps classifier_default for a layer-1 tool approval", () => {
|
|
176
223
|
const servers = [makeServer("github", [{ toolName: "create_issue" }])];
|
|
177
|
-
const result = mergeApprovalPolicies(servers,
|
|
224
|
+
const result = mergeApprovalPolicies(servers, NO_LEASES);
|
|
178
225
|
expect(result.get("github/create_issue")!.source).toBe("classifier_default");
|
|
179
226
|
});
|
|
180
227
|
|
|
@@ -182,31 +229,27 @@ describe("mergeApprovalPolicies", () => {
|
|
|
182
229
|
const servers = [
|
|
183
230
|
makeServer("github", [{ toolName: "push" }], [{ toolName: "push" }]),
|
|
184
231
|
];
|
|
185
|
-
const result = mergeApprovalPolicies(servers,
|
|
232
|
+
const result = mergeApprovalPolicies(servers, NO_LEASES);
|
|
186
233
|
expect(result.get("github/push")!.source).toBe("pinned_override");
|
|
187
234
|
});
|
|
188
235
|
|
|
189
236
|
it("stamps agent_override when a per-agent override adds a gate", () => {
|
|
190
|
-
const servers = [
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
const result = mergeApprovalPolicies(servers, overrides, NO_LEASES);
|
|
237
|
+
const servers = [
|
|
238
|
+
makeServer("github", [], [], [
|
|
239
|
+
{ toolName: "delete_repo", requiresApproval: true, message: "Really delete?" },
|
|
240
|
+
]),
|
|
241
|
+
];
|
|
242
|
+
const result = mergeApprovalPolicies(servers, NO_LEASES);
|
|
197
243
|
expect(result.get("github/delete_repo")!.source).toBe("agent_override");
|
|
198
244
|
});
|
|
199
245
|
|
|
200
246
|
it("stamps agent_override when it overrides a classifier/pinned entry", () => {
|
|
201
247
|
const servers = [
|
|
202
|
-
makeServer("github", [{ toolName: "push", message: "classifier" }]
|
|
248
|
+
makeServer("github", [{ toolName: "push", message: "classifier" }], [], [
|
|
249
|
+
{ toolName: "push", requiresApproval: true, message: "agent says gate" },
|
|
250
|
+
]),
|
|
203
251
|
];
|
|
204
|
-
const
|
|
205
|
-
toolName: "push",
|
|
206
|
-
requiresApproval: true,
|
|
207
|
-
message: "agent says gate",
|
|
208
|
-
}] as any[];
|
|
209
|
-
const result = mergeApprovalPolicies(servers, overrides, NO_LEASES);
|
|
252
|
+
const result = mergeApprovalPolicies(servers, NO_LEASES);
|
|
210
253
|
const policy = result.get("github/push")!;
|
|
211
254
|
expect(policy.source).toBe("agent_override");
|
|
212
255
|
expect(policy.approvalMessage).toBe("agent says gate");
|
|
@@ -217,7 +260,7 @@ describe("mergeApprovalPolicies", () => {
|
|
|
217
260
|
const servers = [
|
|
218
261
|
makeServer("github", [{ toolName: "" }]),
|
|
219
262
|
];
|
|
220
|
-
const result = mergeApprovalPolicies(servers,
|
|
263
|
+
const result = mergeApprovalPolicies(servers, NO_LEASES);
|
|
221
264
|
expect(result.size).toBe(0);
|
|
222
265
|
});
|
|
223
266
|
|
|
@@ -225,7 +268,7 @@ describe("mergeApprovalPolicies", () => {
|
|
|
225
268
|
const servers = [
|
|
226
269
|
makeServer("github", [{ toolName: "push" }]),
|
|
227
270
|
];
|
|
228
|
-
const result = mergeApprovalPolicies(servers,
|
|
271
|
+
const result = mergeApprovalPolicies(servers, NO_LEASES);
|
|
229
272
|
expect(result.get("github/push")!.approvalMessage).toContain("Execute tool: push");
|
|
230
273
|
});
|
|
231
274
|
|
|
@@ -234,7 +277,7 @@ describe("mergeApprovalPolicies", () => {
|
|
|
234
277
|
makeServer("github", [{ toolName: "push" }]),
|
|
235
278
|
makeServer("database", [{ toolName: "drop_table" }]),
|
|
236
279
|
];
|
|
237
|
-
const result = mergeApprovalPolicies(servers,
|
|
280
|
+
const result = mergeApprovalPolicies(servers, NO_LEASES);
|
|
238
281
|
expect(result.size).toBe(2);
|
|
239
282
|
expect(result.has("github/push")).toBe(true);
|
|
240
283
|
expect(result.has("database/drop_table")).toBe(true);
|
|
@@ -294,7 +294,13 @@ describe("ProxyArtifactStorage", () => {
|
|
|
294
294
|
return new Response("server error", { status: 500 });
|
|
295
295
|
}) as typeof fetch;
|
|
296
296
|
|
|
297
|
-
|
|
297
|
+
// 500 is retryable, so this persistent failure exhausts the budget —
|
|
298
|
+
// delayFn injected to skip the real ~7.75 s of backoff (#468). The
|
|
299
|
+
// degrade contract under test (budget exhaustion still throws the
|
|
300
|
+
// caller-visible error) is unchanged.
|
|
301
|
+
const storage = new ProxyArtifactStorage("https://proxy.example.com", "tok", {
|
|
302
|
+
delayFn: async () => {},
|
|
303
|
+
});
|
|
298
304
|
await expect(
|
|
299
305
|
storage.upload("key", Buffer.from("x")),
|
|
300
306
|
).rejects.toThrow("Presigned upload failed (HTTP 500)");
|
|
@@ -411,11 +417,148 @@ describe("ProxyArtifactStorage", () => {
|
|
|
411
417
|
|
|
412
418
|
it("exists throws on an unexpected object status (a fault, not an existence answer)", async () => {
|
|
413
419
|
mockProxyFetch(() => new Response("boom", { status: 500 }));
|
|
414
|
-
|
|
420
|
+
// Persistent 500 exhausts the retry budget; delayFn skips the real
|
|
421
|
+
// backoff (#468). The fault-not-an-answer contract is unchanged.
|
|
422
|
+
const storage = new ProxyArtifactStorage("https://proxy.example.com", "tok", {
|
|
423
|
+
delayFn: async () => {},
|
|
424
|
+
});
|
|
415
425
|
await expect(storage.exists("key")).rejects.toThrow(
|
|
416
426
|
/Artifact existence check failed \(HTTP 500\) for key 'key'/,
|
|
417
427
|
);
|
|
418
428
|
});
|
|
429
|
+
|
|
430
|
+
// The bounded-backoff adoption (stigmer/stigmer#468). Classification policy
|
|
431
|
+
// is covered by shared/__tests__/http-retry.test.ts; these cases pin the
|
|
432
|
+
// loop's behavior at this client's call sites: transient failures recover
|
|
433
|
+
// (each of these fails on pre-#468 code, which threw on the first error),
|
|
434
|
+
// deterministic failures never retry, and a hung request is aborted at the
|
|
435
|
+
// per-request bound instead of stalling forever.
|
|
436
|
+
describe("retry behavior (#468)", () => {
|
|
437
|
+
let recordedDelays: number[];
|
|
438
|
+
|
|
439
|
+
beforeEach(() => {
|
|
440
|
+
recordedDelays = [];
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
function makeStorage() {
|
|
444
|
+
return new ProxyArtifactStorage("https://proxy.example.com", "tok", {
|
|
445
|
+
delayFn: async (ms) => {
|
|
446
|
+
recordedDelays.push(ms);
|
|
447
|
+
},
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
it("upload recovers from a transient presign failure", async () => {
|
|
452
|
+
let presignCalls = 0;
|
|
453
|
+
const fetchSpy = vi.fn(async (input: string | URL | Request) => {
|
|
454
|
+
const url = typeof input === "string" ? input : input.toString();
|
|
455
|
+
if (url.includes("presigned-upload-url")) {
|
|
456
|
+
presignCalls++;
|
|
457
|
+
if (presignCalls === 1) {
|
|
458
|
+
return new Response("bad gateway", { status: 502 });
|
|
459
|
+
}
|
|
460
|
+
return new Response(JSON.stringify({ url: "https://r2.example.com/put" }), { status: 200 });
|
|
461
|
+
}
|
|
462
|
+
return new Response("", { status: 200 });
|
|
463
|
+
});
|
|
464
|
+
globalThis.fetch = fetchSpy as unknown as typeof fetch;
|
|
465
|
+
|
|
466
|
+
const key = await makeStorage().upload("artifacts/e/f.txt", Buffer.from("x"));
|
|
467
|
+
|
|
468
|
+
expect(key).toBe("artifacts/e/f.txt");
|
|
469
|
+
expect(presignCalls).toBe(2);
|
|
470
|
+
expect(recordedDelays).toEqual([250]);
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
it("upload recovers from a transient R2 PUT failure, re-sending identical bytes", async () => {
|
|
474
|
+
const putBodies: string[] = [];
|
|
475
|
+
let putCalls = 0;
|
|
476
|
+
globalThis.fetch = vi.fn(async (input: string | URL | Request, init?: RequestInit) => {
|
|
477
|
+
const url = typeof input === "string" ? input : input.toString();
|
|
478
|
+
if (url.includes("presigned-upload-url")) {
|
|
479
|
+
return new Response(JSON.stringify({ url: "https://r2.example.com/put" }), { status: 200 });
|
|
480
|
+
}
|
|
481
|
+
putCalls++;
|
|
482
|
+
putBodies.push(String(init?.body));
|
|
483
|
+
if (putCalls === 1) {
|
|
484
|
+
return new Response("unavailable", { status: 503 });
|
|
485
|
+
}
|
|
486
|
+
return new Response("", { status: 200 });
|
|
487
|
+
}) as typeof fetch;
|
|
488
|
+
|
|
489
|
+
await makeStorage().upload("k", Buffer.from("same-bytes"));
|
|
490
|
+
|
|
491
|
+
expect(putCalls).toBe(2);
|
|
492
|
+
expect(putBodies[0]).toBe(putBodies[1]);
|
|
493
|
+
expect(recordedDelays).toEqual([250]);
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
it("download recovers from a transient network failure on the R2 GET", async () => {
|
|
497
|
+
let getCalls = 0;
|
|
498
|
+
globalThis.fetch = vi.fn(async (input: string | URL | Request) => {
|
|
499
|
+
const url = typeof input === "string" ? input : input.toString();
|
|
500
|
+
if (url.includes("presigned-download-url")) {
|
|
501
|
+
return new Response(JSON.stringify({ url: "https://r2.example.com/dl" }), { status: 200 });
|
|
502
|
+
}
|
|
503
|
+
getCalls++;
|
|
504
|
+
if (getCalls === 1) {
|
|
505
|
+
// undici's network-failure shape (retryable).
|
|
506
|
+
throw new TypeError("fetch failed");
|
|
507
|
+
}
|
|
508
|
+
return new Response(Buffer.from("payload"), { status: 200 });
|
|
509
|
+
}) as typeof fetch;
|
|
510
|
+
|
|
511
|
+
const got = await makeStorage().download("k");
|
|
512
|
+
|
|
513
|
+
expect(got.toString()).toBe("payload");
|
|
514
|
+
expect(getCalls).toBe(2);
|
|
515
|
+
expect(recordedDelays).toEqual([250]);
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
it("never retries a deterministic presign refusal (403)", async () => {
|
|
519
|
+
const fetchSpy = vi.fn(async () => new Response("forbidden", { status: 403 }));
|
|
520
|
+
globalThis.fetch = fetchSpy as unknown as typeof fetch;
|
|
521
|
+
|
|
522
|
+
await expect(makeStorage().upload("k", Buffer.from("x"))).rejects.toThrow(
|
|
523
|
+
"Failed to get presigned upload URL (HTTP 403)",
|
|
524
|
+
);
|
|
525
|
+
expect(fetchSpy).toHaveBeenCalledTimes(1);
|
|
526
|
+
expect(recordedDelays).toEqual([]);
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
it("aborts a hung request at the per-request bound and retries it", async () => {
|
|
530
|
+
let calls = 0;
|
|
531
|
+
globalThis.fetch = vi.fn((input: string | URL | Request, init?: RequestInit) => {
|
|
532
|
+
calls++;
|
|
533
|
+
if (calls === 1) {
|
|
534
|
+
// Hang until the AbortSignal.timeout fires, then reject the way
|
|
535
|
+
// undici does — proving the bound converts a stall into a
|
|
536
|
+
// retryable attempt instead of waiting forever.
|
|
537
|
+
return new Promise<Response>((_, reject) => {
|
|
538
|
+
init?.signal?.addEventListener("abort", () =>
|
|
539
|
+
reject(init.signal!.reason as Error),
|
|
540
|
+
);
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
return Promise.resolve(
|
|
544
|
+
new Response(JSON.stringify({ url: "https://r2.example.com/dl" }), { status: 200 }),
|
|
545
|
+
);
|
|
546
|
+
}) as typeof fetch;
|
|
547
|
+
|
|
548
|
+
const storage = new ProxyArtifactStorage("https://proxy.example.com", "tok", {
|
|
549
|
+
requestTimeoutMs: 20,
|
|
550
|
+
delayFn: async (ms) => {
|
|
551
|
+
recordedDelays.push(ms);
|
|
552
|
+
},
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
const url = await storage.getDownloadUrl("k");
|
|
556
|
+
|
|
557
|
+
expect(url).toBe("https://r2.example.com/dl");
|
|
558
|
+
expect(calls).toBe(2);
|
|
559
|
+
expect(recordedDelays).toEqual([250]);
|
|
560
|
+
});
|
|
561
|
+
});
|
|
419
562
|
});
|
|
420
563
|
|
|
421
564
|
// ── Factory ──────────────────────────────────────────────────────────
|