@stigmer/runner 3.11.0 → 3.12.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 +2 -0
- package/dist/.build-fingerprint +1 -1
- package/dist/activities/discover-mcp-server.d.ts +7 -0
- package/dist/activities/discover-mcp-server.js +6 -4
- 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/attachment-resolver.d.ts +11 -0
- package/dist/activities/execute-cursor/attachment-resolver.js +8 -0
- 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 +0 -1
- package/dist/activities/execute-cursor/blueprint-resolver.js.map +1 -1
- package/dist/activities/execute-cursor/error-classifier.d.ts +19 -0
- package/dist/activities/execute-cursor/error-classifier.js +34 -3
- package/dist/activities/execute-cursor/error-classifier.js.map +1 -1
- package/dist/activities/execute-cursor/index.d.ts +52 -12
- package/dist/activities/execute-cursor/index.js +141 -67
- package/dist/activities/execute-cursor/index.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 +48 -11
- package/dist/activities/execute-cursor/prompt-builder.js +57 -10
- 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-deep-agent/attachment-injector.d.ts +11 -0
- package/dist/activities/execute-deep-agent/attachment-injector.js +7 -1
- package/dist/activities/execute-deep-agent/attachment-injector.js.map +1 -1
- package/dist/activities/execute-deep-agent/index.js +0 -15
- package/dist/activities/execute-deep-agent/index.js.map +1 -1
- package/dist/activities/execute-deep-agent/mcp-gate.d.ts +0 -2
- package/dist/activities/execute-deep-agent/mcp-gate.js +0 -1
- package/dist/activities/execute-deep-agent/mcp-gate.js.map +1 -1
- package/dist/activities/execute-deep-agent/prompt-builder.d.ts +7 -8
- package/dist/activities/execute-deep-agent/prompt-builder.js +14 -7
- package/dist/activities/execute-deep-agent/prompt-builder.js.map +1 -1
- package/dist/activities/execute-deep-agent/setup.d.ts +0 -10
- package/dist/activities/execute-deep-agent/setup.js +22 -49
- 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.js +7 -1
- package/dist/activities/execute-deep-agent/subagent-transformer.js.map +1 -1
- package/dist/activities/execute-deep-agent/subagent-wiring.d.ts +17 -3
- package/dist/activities/execute-deep-agent/subagent-wiring.js +14 -2
- package/dist/activities/execute-deep-agent/subagent-wiring.js.map +1 -1
- package/dist/activities/hydrate-workflow-execution.js +8 -5
- 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/bootstrap.js +5 -0
- package/dist/bootstrap.js.map +1 -1
- package/dist/client/stigmer-client.d.ts +83 -26
- package/dist/client/stigmer-client.js +88 -24
- package/dist/client/stigmer-client.js.map +1 -1
- package/dist/config.d.ts +6 -5
- package/dist/config.js.map +1 -1
- package/dist/encryption/config.d.ts +41 -9
- package/dist/encryption/config.js +63 -23
- package/dist/encryption/config.js.map +1 -1
- 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 +2 -1
- package/dist/payload-codecs.js +7 -3
- package/dist/payload-codecs.js.map +1 -1
- package/dist/runner-manager.js +17 -3
- package/dist/runner-manager.js.map +1 -1
- package/dist/runner.js +7 -3
- package/dist/runner.js.map +1 -1
- package/dist/shared/artifact-storage.d.ts +34 -1
- package/dist/shared/artifact-storage.js +53 -10
- package/dist/shared/artifact-storage.js.map +1 -1
- package/dist/shared/attachment-download-urls.d.ts +62 -0
- package/dist/shared/attachment-download-urls.js +79 -0
- package/dist/shared/attachment-download-urls.js.map +1 -0
- package/dist/shared/channel-attachment.d.ts +1 -1
- package/dist/shared/channel-attachment.js +1 -1
- package/dist/shared/checkpointer/http-saver.d.ts +4 -20
- package/dist/shared/checkpointer/http-saver.js +15 -57
- package/dist/shared/checkpointer/http-saver.js.map +1 -1
- package/dist/shared/conversation-attachment.d.ts +2 -3
- package/dist/shared/conversation-attachment.js +2 -3
- 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/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 +51 -9
- package/dist/shared/http-retry.js +60 -9
- package/dist/shared/http-retry.js.map +1 -1
- package/dist/shared/mcp-manager.d.ts +8 -4
- package/dist/shared/mcp-manager.js +30 -4
- package/dist/shared/mcp-manager.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/mcp-transport-guard.d.ts +2 -2
- package/dist/shared/mcp-transport-guard.js +2 -2
- package/dist/shared/model-client.d.ts +6 -0
- package/dist/shared/model-client.js +14 -3
- package/dist/shared/model-client.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 +7 -0
- package/dist/shared/plan-mode-permissions.js +7 -0
- package/dist/shared/plan-mode-permissions.js.map +1 -1
- 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/shared/synthesized-attachment.d.ts +4 -4
- package/dist/shared/synthesized-attachment.js +4 -4
- 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/workflows/connect-mcp-server.js +2 -0
- package/dist/workflows/connect-mcp-server.js.map +1 -1
- package/dist/workflows/types.d.ts +8 -0
- package/package.json +3 -4
- package/src/__test-utils__/__tests__/vitest-global-setup.test.ts +37 -0
- package/src/__test-utils__/fake-artifact-storage.ts +10 -1
- package/src/__test-utils__/vitest-global-setup.ts +50 -0
- package/src/__tests__/bootstrap.test.ts +43 -0
- package/src/__tests__/encryption-codec.test.ts +53 -0
- package/src/__tests__/preflight.test.ts +15 -0
- package/src/activities/__tests__/discover-mcp-server.test.ts +39 -1
- package/src/activities/__tests__/error-classifier.test.ts +94 -2
- package/src/activities/__tests__/run-command.test.ts +221 -0
- package/src/activities/discover-mcp-server.ts +16 -2
- package/src/activities/emit-event.ts +65 -21
- package/src/activities/execute-cursor/__tests__/attachment-resolver.test.ts +63 -3
- package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +173 -31
- package/src/activities/execute-cursor/__tests__/model-pricing.test.ts +62 -1
- package/src/activities/execute-cursor/__tests__/turn-recovery.test.ts +160 -0
- package/src/activities/execute-cursor/attachment-resolver.ts +24 -0
- package/src/activities/execute-cursor/blueprint-resolver.ts +1 -10
- package/src/activities/execute-cursor/error-classifier.ts +40 -3
- package/src/activities/execute-cursor/index.ts +175 -74
- package/src/activities/execute-cursor/model-pricing-data.ts +22 -3
- package/src/activities/execute-cursor/prompt-builder.ts +89 -22
- package/src/activities/execute-cursor/turn-recovery.ts +208 -0
- package/src/activities/execute-deep-agent/__tests__/attachment-injector.test.ts +105 -0
- package/src/activities/execute-deep-agent/__tests__/hitl-reject.test.ts +0 -2
- package/src/activities/execute-deep-agent/__tests__/hitl-resume-approve-all.test.ts +0 -1
- package/src/activities/execute-deep-agent/__tests__/hitl-resume-history.test.ts +0 -1
- package/src/activities/execute-deep-agent/__tests__/mcp-gate.test.ts +2 -7
- 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 +71 -0
- package/src/activities/execute-deep-agent/__tests__/sequential-gate-resume.test.ts +0 -1
- 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 +22 -17
- package/src/activities/execute-deep-agent/attachment-injector.ts +20 -1
- package/src/activities/execute-deep-agent/index.ts +0 -19
- package/src/activities/execute-deep-agent/mcp-gate.ts +0 -3
- package/src/activities/execute-deep-agent/prompt-builder.ts +27 -15
- package/src/activities/execute-deep-agent/setup.ts +22 -73
- package/src/activities/execute-deep-agent/shell-env.ts +8 -9
- package/src/activities/execute-deep-agent/subagent-transformer.ts +7 -1
- package/src/activities/execute-deep-agent/subagent-wiring.ts +26 -3
- package/src/activities/hydrate-workflow-execution.ts +8 -5
- package/src/activities/run-command.ts +25 -12
- package/src/activities/run-env.ts +79 -0
- package/src/bootstrap.ts +5 -0
- package/src/client/__tests__/stigmer-client.test.ts +104 -5
- package/src/client/stigmer-client.ts +128 -29
- package/src/config.ts +6 -5
- package/src/encryption/config.ts +87 -23
- 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 +8 -2
- package/src/runner-manager.ts +21 -3
- package/src/runner.ts +7 -3
- package/src/shared/__tests__/artifact-storage.test.ts +154 -2
- package/src/shared/__tests__/attachment-download-urls.test.ts +70 -0
- package/src/shared/__tests__/channel-attachment.test.ts +2 -2
- package/src/shared/__tests__/conversation-catchup.test.ts +24 -0
- package/src/shared/__tests__/http-retry.test.ts +5 -4
- package/src/shared/__tests__/mcp-manager.test.ts +53 -13
- package/src/shared/__tests__/mcp-schema-sanitizer.test.ts +267 -0
- package/src/shared/__tests__/model-client.test.ts +46 -0
- 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 +19 -41
- package/src/shared/artifact-storage.ts +86 -10
- package/src/shared/attachment-download-urls.ts +98 -0
- package/src/shared/channel-attachment.ts +1 -1
- package/src/shared/checkpointer/http-saver.ts +15 -60
- package/src/shared/conversation-attachment.ts +2 -3
- package/src/shared/conversation-catchup.ts +39 -6
- package/src/shared/grpc-retry.ts +5 -1
- package/src/shared/http-retry.ts +98 -9
- package/src/shared/mcp-manager.ts +35 -4
- package/src/shared/mcp-schema-sanitizer.ts +224 -0
- package/src/shared/mcp-transport-guard.ts +2 -2
- package/src/shared/model-client.ts +21 -3
- package/src/shared/model-pricing-data.ts +22 -3
- package/src/shared/model-registry.ts +7 -2
- package/src/shared/plan-mode-permissions.ts +7 -0
- package/src/shared/registry-endpoint.ts +27 -0
- package/src/shared/runner-credential-keys.ts +46 -0
- package/src/shared/synthesized-attachment.ts +4 -4
- package/src/workflow-engine/__tests__/tasks/emit-event.test.ts +127 -49
- package/src/workflow-engine/resolve.ts +48 -2
- package/src/workflows/__tests__/connect-mcp-server.test.ts +25 -0
- package/src/workflows/connect-mcp-server.ts +2 -0
- 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/dist/shared/datastore-attachment.d.ts +0 -99
- package/dist/shared/datastore-attachment.js +0 -184
- package/dist/shared/datastore-attachment.js.map +0 -1
- package/src/activities/execute-cursor/mcp-config.ts +0 -66
- package/src/activities/execute-deep-agent/__tests__/datastore-degradation.test.ts +0 -104
- package/src/shared/__tests__/datastore-attachment.test.ts +0 -293
- package/src/shared/datastore-attachment.ts +0 -209
package/src/encryption/config.ts
CHANGED
|
@@ -1,15 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Payload-encryption configuration (stigmer-cloud#227).
|
|
2
|
+
* Payload-encryption configuration (stigmer-cloud#227, stigmer#398).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Two key sources, strict precedence:
|
|
5
|
+
*
|
|
6
|
+
* 1. Environment (STIGMER_PAYLOAD_ENCRYPTION_KEY(_ID)) — the operator's
|
|
7
|
+
* explicit choice: self-hosted deployments sharing one key with their
|
|
8
|
+
* server, and cloud sandboxes injected with the platform key. When the
|
|
9
|
+
* env key is set, bootstrap-delivered material is ignored entirely.
|
|
10
|
+
* 2. Bootstrap-delivered — server-managed per-identity keys handed to
|
|
11
|
+
* desktop-class runners by getRunnerBootstrapConfig. Held in memory
|
|
12
|
+
* only; persistence lives server-side, which is what makes Temporal
|
|
13
|
+
* replay work across runner restarts (every boot re-fetches the SAME
|
|
14
|
+
* key).
|
|
15
|
+
*
|
|
16
|
+
* Encryption is enabled iff a key is present from either source — the same
|
|
17
|
+
* enabled-iff-configured pattern as the claim-check codec. A malformed key
|
|
18
|
+
* fails the boot rather than silently running plaintext: an operator who set
|
|
19
|
+
* the key (or a server that minted one) intended history to be encrypted.
|
|
8
20
|
*
|
|
9
21
|
* Key rotation: payloads carry the id of the key that encrypted them.
|
|
10
|
-
* During a rotation window the previous key stays readable via
|
|
11
|
-
*
|
|
12
|
-
*
|
|
22
|
+
* During a rotation window the previous key stays readable via the
|
|
23
|
+
* secondary pair while new payloads are written under the primary key.
|
|
24
|
+
* Workers capture keys at construction, so a rotated bootstrap key lands
|
|
25
|
+
* on the next runner boot — there is no live re-key.
|
|
13
26
|
*/
|
|
14
27
|
|
|
15
28
|
export interface EncryptionKey {
|
|
@@ -25,6 +38,19 @@ export interface PayloadEncryptionConfig {
|
|
|
25
38
|
readonly secondary?: EncryptionKey;
|
|
26
39
|
}
|
|
27
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Server-managed key material delivered by getRunnerBootstrapConfig.
|
|
43
|
+
* Structurally mirrors {@link BootstrapPayloadEncryptionKeys} in
|
|
44
|
+
* stigmer-client.ts — declared here so this leaf module stays free of
|
|
45
|
+
* client imports.
|
|
46
|
+
*/
|
|
47
|
+
export interface BootstrapKeyMaterial {
|
|
48
|
+
readonly key: string;
|
|
49
|
+
readonly keyId?: string;
|
|
50
|
+
readonly secondaryKey?: string;
|
|
51
|
+
readonly secondaryKeyId?: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
28
54
|
const KEY_ENV = "STIGMER_PAYLOAD_ENCRYPTION_KEY";
|
|
29
55
|
const KEY_ID_ENV = "STIGMER_PAYLOAD_ENCRYPTION_KEY_ID";
|
|
30
56
|
const SECONDARY_KEY_ENV = "STIGMER_PAYLOAD_ENCRYPTION_SECONDARY_KEY";
|
|
@@ -36,29 +62,57 @@ const AES_256_KEY_BYTES = 32;
|
|
|
36
62
|
* Returns the encryption config, or undefined when encryption is not
|
|
37
63
|
* configured (the codec is then simply not installed).
|
|
38
64
|
*
|
|
65
|
+
* Source precedence: the env key wins outright; bootstrap-delivered
|
|
66
|
+
* material applies only when no env key is set (see the module doc).
|
|
67
|
+
*
|
|
39
68
|
* @throws when a key is present but malformed, or a key id is missing —
|
|
40
69
|
* key misconfiguration must stop the boot, not degrade to plaintext.
|
|
70
|
+
* This applies equally to bootstrap material: a server that hands out
|
|
71
|
+
* a bad key or omits its id has broken the protocol contract, and
|
|
72
|
+
* running plaintext against a server that manages keys would silently
|
|
73
|
+
* defeat the feature.
|
|
41
74
|
*/
|
|
42
|
-
export function loadPayloadEncryptionConfig(
|
|
75
|
+
export function loadPayloadEncryptionConfig(
|
|
76
|
+
bootstrap?: BootstrapKeyMaterial,
|
|
77
|
+
): PayloadEncryptionConfig | undefined {
|
|
43
78
|
const rawKey = process.env[KEY_ENV];
|
|
44
|
-
if (
|
|
45
|
-
|
|
79
|
+
if (rawKey) {
|
|
80
|
+
const primary: EncryptionKey = {
|
|
81
|
+
keyId: requireKeyId(KEY_ID_ENV),
|
|
82
|
+
key: parseKey(rawKey, KEY_ENV),
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const rawSecondary = process.env[SECONDARY_KEY_ENV];
|
|
86
|
+
const secondary: EncryptionKey | undefined = rawSecondary
|
|
87
|
+
? {
|
|
88
|
+
keyId: requireKeyId(SECONDARY_KEY_ID_ENV),
|
|
89
|
+
key: parseKey(rawSecondary, SECONDARY_KEY_ENV),
|
|
90
|
+
}
|
|
91
|
+
: undefined;
|
|
92
|
+
|
|
93
|
+
return { primary, secondary };
|
|
46
94
|
}
|
|
47
95
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
96
|
+
if (bootstrap?.key) {
|
|
97
|
+
const primary: EncryptionKey = {
|
|
98
|
+
keyId: requireBootstrapKeyId(bootstrap.keyId, "payload_encryption_key_id"),
|
|
99
|
+
key: parseKey(bootstrap.key, "bootstrap payload_encryption_key"),
|
|
100
|
+
};
|
|
52
101
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
102
|
+
const secondary: EncryptionKey | undefined = bootstrap.secondaryKey
|
|
103
|
+
? {
|
|
104
|
+
keyId: requireBootstrapKeyId(
|
|
105
|
+
bootstrap.secondaryKeyId,
|
|
106
|
+
"payload_encryption_secondary_key_id",
|
|
107
|
+
),
|
|
108
|
+
key: parseKey(bootstrap.secondaryKey, "bootstrap payload_encryption_secondary_key"),
|
|
109
|
+
}
|
|
110
|
+
: undefined;
|
|
60
111
|
|
|
61
|
-
|
|
112
|
+
return { primary, secondary };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return undefined;
|
|
62
116
|
}
|
|
63
117
|
|
|
64
118
|
function requireKeyId(envName: string): string {
|
|
@@ -74,6 +128,16 @@ function requireKeyId(envName: string): string {
|
|
|
74
128
|
return keyId;
|
|
75
129
|
}
|
|
76
130
|
|
|
131
|
+
function requireBootstrapKeyId(keyId: string | undefined, fieldName: string): string {
|
|
132
|
+
if (!keyId) {
|
|
133
|
+
throw new Error(
|
|
134
|
+
`Runner bootstrap returned a payload encryption key without its ${fieldName} — ` +
|
|
135
|
+
`refusing to encrypt under an unidentified key (server contract violation)`,
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
return keyId;
|
|
139
|
+
}
|
|
140
|
+
|
|
77
141
|
function parseKey(rawBase64: string, envName: string): Buffer {
|
|
78
142
|
let key: Buffer;
|
|
79
143
|
try {
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
import { ToolMessage } from "@langchain/core/messages";
|
|
3
|
+
import {
|
|
4
|
+
createPathNormalizationMiddleware,
|
|
5
|
+
normalizeWorkspacePathArg,
|
|
6
|
+
} from "../path-normalization.js";
|
|
7
|
+
import type { ToolCallRequest } from "../types.js";
|
|
8
|
+
|
|
9
|
+
const ROOT = "/workspace/session-1/repo";
|
|
10
|
+
|
|
11
|
+
function makeRequest(
|
|
12
|
+
name: string,
|
|
13
|
+
args: Record<string, unknown>,
|
|
14
|
+
): ToolCallRequest {
|
|
15
|
+
return {
|
|
16
|
+
toolCall: { id: "tc_1", name, args },
|
|
17
|
+
tool: undefined,
|
|
18
|
+
state: { messages: [] },
|
|
19
|
+
runtime: {},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Run the middleware and return the args the inner handler observed. */
|
|
24
|
+
async function argsSeenByHandler(
|
|
25
|
+
name: string,
|
|
26
|
+
args: Record<string, unknown>,
|
|
27
|
+
): Promise<Record<string, unknown>> {
|
|
28
|
+
const mw = createPathNormalizationMiddleware({ rootDir: ROOT });
|
|
29
|
+
const handler = vi.fn(async (req: ToolCallRequest) => {
|
|
30
|
+
return new ToolMessage({ content: "ok", tool_call_id: req.toolCall.id, name });
|
|
31
|
+
});
|
|
32
|
+
await mw.wrapToolCall!(makeRequest(name, args), handler);
|
|
33
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
34
|
+
return handler.mock.calls[0][0].toolCall.args;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
describe("normalizeWorkspacePathArg", () => {
|
|
38
|
+
it("maps a workspace-relative path to its rootDir-joined absolute form", () => {
|
|
39
|
+
expect(normalizeWorkspacePathArg("src/main.py", ROOT)).toBe(`${ROOT}/src/main.py`);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("strips a leading ./ before joining", () => {
|
|
43
|
+
expect(normalizeWorkspacePathArg("./notes.md", ROOT)).toBe(`${ROOT}/notes.md`);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("handles multi-workspace entry-relative paths", () => {
|
|
47
|
+
expect(normalizeWorkspacePathArg("entry-1/src/main.py", ROOT)).toBe(
|
|
48
|
+
`${ROOT}/entry-1/src/main.py`,
|
|
49
|
+
);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("resolves interior .. segments that stay inside the root", () => {
|
|
53
|
+
expect(normalizeWorkspacePathArg("src/../notes.md", ROOT)).toBe(`${ROOT}/notes.md`);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("leaves absolute paths untouched", () => {
|
|
57
|
+
expect(normalizeWorkspacePathArg("/etc/hosts", ROOT)).toBeUndefined();
|
|
58
|
+
expect(normalizeWorkspacePathArg(`${ROOT}/src/main.py`, ROOT)).toBeUndefined();
|
|
59
|
+
expect(normalizeWorkspacePathArg("/", ROOT)).toBeUndefined();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("refuses to rewrite relatives that escape the workspace root", () => {
|
|
63
|
+
// A naive join would resolve `..` away and hand upstream validation a
|
|
64
|
+
// clean out-of-root absolute path — an out-of-root read that today's
|
|
65
|
+
// validation refuses. The guard keeps the refusal.
|
|
66
|
+
expect(normalizeWorkspacePathArg("../sibling/secret.txt", ROOT)).toBeUndefined();
|
|
67
|
+
expect(normalizeWorkspacePathArg("src/../../escape.txt", ROOT)).toBeUndefined();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("leaves ~-carrying paths untouched (upstream refuses them either way)", () => {
|
|
71
|
+
expect(normalizeWorkspacePathArg("~/notes.md", ROOT)).toBeUndefined();
|
|
72
|
+
expect(normalizeWorkspacePathArg("docs/~/x.md", ROOT)).toBeUndefined();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("leaves the empty string untouched", () => {
|
|
76
|
+
expect(normalizeWorkspacePathArg("", ROOT)).toBeUndefined();
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe("createPathNormalizationMiddleware", () => {
|
|
81
|
+
it("rewrites file_path on the file tools", async () => {
|
|
82
|
+
for (const tool of ["read_file", "write_file", "edit_file"]) {
|
|
83
|
+
const args = await argsSeenByHandler(tool, {
|
|
84
|
+
file_path: "src/main.py",
|
|
85
|
+
content: "x",
|
|
86
|
+
});
|
|
87
|
+
expect(args.file_path).toBe(`${ROOT}/src/main.py`);
|
|
88
|
+
// Sibling args ride along untouched.
|
|
89
|
+
expect(args.content).toBe("x");
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("rewrites the base path on ls/glob/grep", async () => {
|
|
94
|
+
for (const tool of ["ls", "glob", "grep"]) {
|
|
95
|
+
const args = await argsSeenByHandler(tool, { path: "src", pattern: "**/*.py" });
|
|
96
|
+
expect(args.path).toBe(`${ROOT}/src`);
|
|
97
|
+
// The pattern is never a path — byte-untouched.
|
|
98
|
+
expect(args.pattern).toBe("**/*.py");
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("passes absolute paths through byte-untouched", async () => {
|
|
103
|
+
const args = await argsSeenByHandler("read_file", { file_path: "/etc/hosts" });
|
|
104
|
+
expect(args.file_path).toBe("/etc/hosts");
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("leaves an omitted base path (backend default) alone", async () => {
|
|
108
|
+
const args = await argsSeenByHandler("ls", {});
|
|
109
|
+
expect(args.path).toBeUndefined();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("never touches tools outside the built-in filesystem set", async () => {
|
|
113
|
+
// An MCP tool could plausibly carry a same-named arg with different
|
|
114
|
+
// semantics; only the six built-in names are rewritten.
|
|
115
|
+
const args = await argsSeenByHandler("vendor_upload", { file_path: "src/main.py" });
|
|
116
|
+
expect(args.file_path).toBe("src/main.py");
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("leaves non-string path args for the tool's own input validation", async () => {
|
|
120
|
+
const args = await argsSeenByHandler("read_file", { file_path: 42 });
|
|
121
|
+
expect(args.file_path).toBe(42);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("preserves the original request identity fields on rewrite", async () => {
|
|
125
|
+
const mw = createPathNormalizationMiddleware({ rootDir: ROOT });
|
|
126
|
+
const handler = vi.fn(async (req: ToolCallRequest) => {
|
|
127
|
+
return new ToolMessage({ content: "ok", tool_call_id: req.toolCall.id, name: req.toolCall.name });
|
|
128
|
+
});
|
|
129
|
+
const original = makeRequest("read_file", { file_path: "notes.md" });
|
|
130
|
+
await mw.wrapToolCall!(original, handler);
|
|
131
|
+
|
|
132
|
+
const seen = handler.mock.calls[0][0];
|
|
133
|
+
expect(seen.toolCall.id).toBe("tc_1");
|
|
134
|
+
expect(seen.toolCall.name).toBe("read_file");
|
|
135
|
+
expect(seen.state).toBe(original.state);
|
|
136
|
+
expect(seen.runtime).toBe(original.runtime);
|
|
137
|
+
// The original request is never mutated in place.
|
|
138
|
+
expect(original.toolCall.args.file_path).toBe("notes.md");
|
|
139
|
+
});
|
|
140
|
+
});
|
package/src/middleware/index.ts
CHANGED
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
* Assembles the ordered middleware array from a MiddlewareStackConfig.
|
|
5
5
|
* The composition order matches the Python create_deep_agent injection:
|
|
6
6
|
*
|
|
7
|
+
* 0. Path normalization (conditional: only on permission-rule-bearing
|
|
8
|
+
* graphs — FIRST, so every downstream middleware observes canonical
|
|
9
|
+
* workspace-absolute paths)
|
|
7
10
|
* 1. Loop detection (always)
|
|
8
11
|
* 2. Execution budget (always)
|
|
9
12
|
* 3. Tool truncation (always)
|
|
@@ -15,6 +18,7 @@
|
|
|
15
18
|
|
|
16
19
|
import type { StigmerMiddleware, MiddlewareStackConfig } from "./types.js";
|
|
17
20
|
import type { GracefulStopMiddleware } from "./graceful-stop.js";
|
|
21
|
+
import { createPathNormalizationMiddleware } from "./path-normalization.js";
|
|
18
22
|
import { createLoopDetectionMiddleware } from "./loop-detection.js";
|
|
19
23
|
import { createExecutionBudgetMiddleware } from "./execution-budget.js";
|
|
20
24
|
import { createToolTruncationMiddleware } from "./tool-truncation.js";
|
|
@@ -39,6 +43,10 @@ export function buildMiddlewareStack(
|
|
|
39
43
|
): MiddlewareStackResult {
|
|
40
44
|
const stack: StigmerMiddleware[] = [];
|
|
41
45
|
|
|
46
|
+
if (config.pathNormalization) {
|
|
47
|
+
stack.push(createPathNormalizationMiddleware(config.pathNormalization));
|
|
48
|
+
}
|
|
49
|
+
|
|
42
50
|
stack.push(createLoopDetectionMiddleware(config.loopDetection));
|
|
43
51
|
|
|
44
52
|
stack.push(createExecutionBudgetMiddleware(config.executionBudget));
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace-relative path normalization for permission-rule-bearing graphs
|
|
3
|
+
* (issue #429).
|
|
4
|
+
*
|
|
5
|
+
* deepagents' permission enforcement canonicalizes tool-call paths BEFORE any
|
|
6
|
+
* rule or backend runs, and its validation refuses non-absolute paths — on
|
|
7
|
+
* EVERY filesystem tool call once a graph carries any permission rules, reads
|
|
8
|
+
* included. Plan mode is the only rule-bearing production configuration
|
|
9
|
+
* (shared/plan-mode-permissions.ts), so there a workspace-relative path —
|
|
10
|
+
* the shape the multi-workspace prompt explicitly mandates, and one models
|
|
11
|
+
* routinely choose in single-workspace sessions — fails with
|
|
12
|
+
* `path must be absolute` instead of just working, burning tool rounds until
|
|
13
|
+
* the model adapts by switching to absolute paths.
|
|
14
|
+
*
|
|
15
|
+
* This middleware repairs the contract at our own seam: it rewrites
|
|
16
|
+
* workspace-relative paths on the deepagents built-in filesystem tools to
|
|
17
|
+
* workspace-absolute before the tool (and therefore enforcement) runs. The
|
|
18
|
+
* write-deny rule then fires as designed and reads succeed on the first try.
|
|
19
|
+
* Rule-less (act-mode) graphs never install it — the legacy backend already
|
|
20
|
+
* resolves relative paths under the workspace root, so act mode keeps a
|
|
21
|
+
* byte-zero delta.
|
|
22
|
+
*
|
|
23
|
+
* Invariant — nothing becomes newly reachable: only paths whose resolution
|
|
24
|
+
* stays INSIDE the workspace root are rewritten. Escaping relatives (`../x`)
|
|
25
|
+
* and `~`-carrying paths are left raw so today's validation refusal keeps
|
|
26
|
+
* speaking (a naive join would resolve `..` away and smuggle an out-of-root
|
|
27
|
+
* read past validation), and absolute paths pass through byte-untouched.
|
|
28
|
+
* The middleware converts false errors into correct behavior — never a
|
|
29
|
+
* refusal into an allowance the rules didn't decide.
|
|
30
|
+
*
|
|
31
|
+
* Tool matching is by bare built-in name, the house doctrine (an MCP server
|
|
32
|
+
* is not expected to shadow a built-in name — see shared/tool-kind.ts); the
|
|
33
|
+
* rewrite touches only the tool's path-bearing argument, never glob/grep
|
|
34
|
+
* patterns. Install FIRST in the stack so every downstream middleware
|
|
35
|
+
* (approval gate, error hints, otel spans) observes canonical paths.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
import { isAbsolute, relative } from "node:path";
|
|
39
|
+
import { resolveWorkspacePath } from "../shared/file-change.js";
|
|
40
|
+
import type { StigmerMiddleware } from "./types.js";
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The path-bearing argument of each deepagents built-in filesystem tool.
|
|
44
|
+
* `ls`/`glob`/`grep` take a base directory `path` (defaulting to "/" when
|
|
45
|
+
* omitted — absolute, so untouched here); the file tools take `file_path`.
|
|
46
|
+
* Confirmed against the installed deepagents' tool definitions.
|
|
47
|
+
*/
|
|
48
|
+
const PATH_ARG_BY_TOOL: ReadonlyMap<string, string> = new Map([
|
|
49
|
+
["ls", "path"],
|
|
50
|
+
["glob", "path"],
|
|
51
|
+
["grep", "path"],
|
|
52
|
+
["read_file", "file_path"],
|
|
53
|
+
["write_file", "file_path"],
|
|
54
|
+
["edit_file", "file_path"],
|
|
55
|
+
]);
|
|
56
|
+
|
|
57
|
+
export interface PathNormalizationConfig {
|
|
58
|
+
/** The workspace root the graph's filesystem backend resolves against. */
|
|
59
|
+
readonly rootDir: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Rewrite `raw` to its workspace-absolute form, or return undefined when the
|
|
64
|
+
* value must be left untouched (absolute already, `~`-carrying, or escaping
|
|
65
|
+
* the workspace root). Exported for direct unit testing of the mapping table.
|
|
66
|
+
*/
|
|
67
|
+
export function normalizeWorkspacePathArg(
|
|
68
|
+
raw: string,
|
|
69
|
+
rootDir: string,
|
|
70
|
+
): string | undefined {
|
|
71
|
+
if (raw.length === 0 || isAbsolute(raw)) return undefined;
|
|
72
|
+
// Upstream validation refuses `~` segments even in absolute paths, so a
|
|
73
|
+
// rewrite could not make such a call succeed — leave the raw shape (and
|
|
74
|
+
// therefore the honest refusal) intact.
|
|
75
|
+
if (raw.split("/").includes("~")) return undefined;
|
|
76
|
+
|
|
77
|
+
const { absolutePath } = resolveWorkspacePath(raw, rootDir, false);
|
|
78
|
+
|
|
79
|
+
// No-new-reachability guard: `join` inside the resolver normalizes `..`
|
|
80
|
+
// segments away, so an escaping relative would otherwise pass upstream
|
|
81
|
+
// validation as a clean out-of-root absolute path. Today that shape is
|
|
82
|
+
// refused; keep it that way.
|
|
83
|
+
const rel = relative(rootDir, absolutePath);
|
|
84
|
+
if (rel.startsWith("..") || isAbsolute(rel)) return undefined;
|
|
85
|
+
|
|
86
|
+
return absolutePath;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Create middleware that normalizes workspace-relative paths on the built-in
|
|
91
|
+
* filesystem tools before permission enforcement sees them.
|
|
92
|
+
*
|
|
93
|
+
* Installed only on graphs that carry filesystem permission rules — derive
|
|
94
|
+
* the install condition from the same expression that supplies the rules
|
|
95
|
+
* (setup.ts / compileSubagents) so the rules and their normalization shim
|
|
96
|
+
* cannot drift apart.
|
|
97
|
+
*/
|
|
98
|
+
export function createPathNormalizationMiddleware(
|
|
99
|
+
config: PathNormalizationConfig,
|
|
100
|
+
): StigmerMiddleware {
|
|
101
|
+
const { rootDir } = config;
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
name: "PathNormalizationMiddleware",
|
|
105
|
+
|
|
106
|
+
wrapToolCall(request, handler) {
|
|
107
|
+
const argKey = PATH_ARG_BY_TOOL.get(request.toolCall.name);
|
|
108
|
+
if (!argKey) return handler(request);
|
|
109
|
+
|
|
110
|
+
const raw = request.toolCall.args[argKey];
|
|
111
|
+
if (typeof raw !== "string") return handler(request);
|
|
112
|
+
|
|
113
|
+
const normalized = normalizeWorkspacePathArg(raw, rootDir);
|
|
114
|
+
if (normalized === undefined) return handler(request);
|
|
115
|
+
|
|
116
|
+
return handler({
|
|
117
|
+
...request,
|
|
118
|
+
toolCall: {
|
|
119
|
+
...request.toolCall,
|
|
120
|
+
args: { ...request.toolCall.args, [argKey]: normalized },
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
}
|
package/src/middleware/types.ts
CHANGED
|
@@ -100,6 +100,11 @@ export interface OtelSpansConfig {
|
|
|
100
100
|
import type { ApprovalGateConfig } from "./approval-gate.js";
|
|
101
101
|
export type { ApprovalGateConfig };
|
|
102
102
|
|
|
103
|
+
// Same idiom as ApprovalGateConfig above: the normalization middleware
|
|
104
|
+
// consumes it, this module re-exports it for stack-config assembly.
|
|
105
|
+
import type { PathNormalizationConfig } from "./path-normalization.js";
|
|
106
|
+
export type { PathNormalizationConfig };
|
|
107
|
+
|
|
103
108
|
/**
|
|
104
109
|
* Top-level configuration for buildMiddlewareStack().
|
|
105
110
|
* All sections are optional — the factory applies sensible defaults.
|
|
@@ -111,4 +116,10 @@ export interface MiddlewareStackConfig {
|
|
|
111
116
|
readonly costCap?: CostCapConfig | null;
|
|
112
117
|
readonly otelSpans?: Partial<OtelSpansConfig>;
|
|
113
118
|
readonly approvalGate?: ApprovalGateConfig | null;
|
|
119
|
+
/**
|
|
120
|
+
* Workspace-relative path normalization (issue #429). Set iff the graph
|
|
121
|
+
* carries filesystem permission rules — derive from the same expression
|
|
122
|
+
* that supplies the rules so the two cannot drift apart.
|
|
123
|
+
*/
|
|
124
|
+
readonly pathNormalization?: PathNormalizationConfig | null;
|
|
114
125
|
}
|
package/src/payload-codecs.ts
CHANGED
|
@@ -14,20 +14,26 @@
|
|
|
14
14
|
|
|
15
15
|
import type { PayloadCodec } from "@temporalio/common";
|
|
16
16
|
import type { Config } from "./config.js";
|
|
17
|
+
import type { BootstrapKeyMaterial } from "./encryption/config.js";
|
|
17
18
|
|
|
18
19
|
export async function createPayloadCodecs(
|
|
19
20
|
config: Config,
|
|
21
|
+
bootstrapKeys?: BootstrapKeyMaterial,
|
|
20
22
|
): Promise<PayloadCodec[] | undefined> {
|
|
21
23
|
const codecs: PayloadCodec[] = [];
|
|
22
24
|
|
|
23
25
|
const { loadPayloadEncryptionConfig, EncryptionPayloadCodec } = await import(
|
|
24
26
|
"./encryption/index.js"
|
|
25
27
|
);
|
|
26
|
-
|
|
28
|
+
// Env-configured keys win outright; server-managed (bootstrap) keys apply
|
|
29
|
+
// only when the env is silent — see encryption/config.ts for the rationale.
|
|
30
|
+
const encryptionConfig = loadPayloadEncryptionConfig(bootstrapKeys);
|
|
27
31
|
if (encryptionConfig) {
|
|
28
32
|
codecs.push(new EncryptionPayloadCodec(encryptionConfig));
|
|
33
|
+
const source = process.env.STIGMER_PAYLOAD_ENCRYPTION_KEY ? "env" : "bootstrap";
|
|
29
34
|
console.log(
|
|
30
|
-
`[runner] Payload encryption enabled (
|
|
35
|
+
`[runner] Payload encryption enabled (source=${source}, ` +
|
|
36
|
+
`key_id=${encryptionConfig.primary.keyId}` +
|
|
31
37
|
(encryptionConfig.secondary
|
|
32
38
|
? `, secondary_key_id=${encryptionConfig.secondary.keyId})`
|
|
33
39
|
: ")"),
|
package/src/runner-manager.ts
CHANGED
|
@@ -293,8 +293,10 @@ export async function createStigmerRunnerManager(
|
|
|
293
293
|
// Resolve the runner bootstrap after the http2 patch is in place (discovery
|
|
294
294
|
// dials the control plane through connect-node). An explicit address wins;
|
|
295
295
|
// otherwise a token triggers control-plane discovery; otherwise localhost.
|
|
296
|
-
// Must happen before createAllActivities so
|
|
297
|
-
//
|
|
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).
|
|
298
300
|
const bootstrap = await resolveRunnerBootstrap({
|
|
299
301
|
explicitAddress: options.temporalAddress,
|
|
300
302
|
explicitNamespace: options.temporalNamespace,
|
|
@@ -354,7 +356,23 @@ export async function createStigmerRunnerManager(
|
|
|
354
356
|
);
|
|
355
357
|
}
|
|
356
358
|
const { createPayloadCodecs } = await import("./payload-codecs.js");
|
|
357
|
-
|
|
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
|
+
}
|
|
358
376
|
|
|
359
377
|
const connection = await NativeConnection.connect({
|
|
360
378
|
address: config.temporalAddress,
|
package/src/runner.ts
CHANGED
|
@@ -237,8 +237,9 @@ export async function createStigmerRunner(
|
|
|
237
237
|
// Resolve Temporal coordinates after the http2 patch is in place (discovery
|
|
238
238
|
// dials the control plane through connect-node). Explicit address wins;
|
|
239
239
|
// otherwise a token triggers control-plane discovery; otherwise localhost.
|
|
240
|
-
//
|
|
241
|
-
//
|
|
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).
|
|
242
243
|
//
|
|
243
244
|
// The static runner brings its own already-proxy-valid token (harness/CLI),
|
|
244
245
|
// so it does not consume the minted runner token from the bootstrap response;
|
|
@@ -305,7 +306,10 @@ export async function createStigmerRunner(
|
|
|
305
306
|
}
|
|
306
307
|
|
|
307
308
|
const { createPayloadCodecs } = await import("./payload-codecs.js");
|
|
308
|
-
|
|
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);
|
|
309
313
|
|
|
310
314
|
const { startWorker } = await import("./worker.js");
|
|
311
315
|
const worker = await startWorker({ config, activities, payloadCodecs });
|