@stigmer/runner 3.9.0 → 3.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -1
- package/dist/.build-fingerprint +1 -1
- package/dist/activities/call-llm.js +9 -10
- package/dist/activities/call-llm.js.map +1 -1
- package/dist/activities/classify-tool-approvals.d.ts +2 -1
- package/dist/activities/classify-tool-approvals.js +28 -2
- package/dist/activities/classify-tool-approvals.js.map +1 -1
- package/dist/activities/discover-mcp-server.d.ts +32 -0
- package/dist/activities/discover-mcp-server.js +162 -27
- package/dist/activities/discover-mcp-server.js.map +1 -1
- package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.d.ts +8 -0
- package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.js +1 -1
- package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.js.map +1 -1
- package/dist/activities/execute-cursor/approval-state.d.ts +28 -2
- package/dist/activities/execute-cursor/approval-state.js +7 -1
- package/dist/activities/execute-cursor/approval-state.js.map +1 -1
- package/dist/activities/execute-cursor/attachment-resolver.d.ts +14 -0
- package/dist/activities/execute-cursor/attachment-resolver.js +25 -4
- package/dist/activities/execute-cursor/attachment-resolver.js.map +1 -1
- package/dist/activities/execute-cursor/blueprint-resolver.d.ts +1 -9
- package/dist/activities/execute-cursor/blueprint-resolver.js +6 -22
- package/dist/activities/execute-cursor/blueprint-resolver.js.map +1 -1
- package/dist/activities/execute-cursor/env-resolver.js +3 -1
- package/dist/activities/execute-cursor/env-resolver.js.map +1 -1
- package/dist/activities/execute-cursor/error-classifier.d.ts +40 -3
- package/dist/activities/execute-cursor/error-classifier.js +81 -3
- package/dist/activities/execute-cursor/error-classifier.js.map +1 -1
- package/dist/activities/execute-cursor/extract-structured-output.d.ts +29 -0
- package/dist/activities/execute-cursor/extract-structured-output.js +58 -0
- package/dist/activities/execute-cursor/extract-structured-output.js.map +1 -0
- package/dist/activities/execute-cursor/hook-script.d.ts +14 -3
- package/dist/activities/execute-cursor/hook-script.js +72 -10
- package/dist/activities/execute-cursor/hook-script.js.map +1 -1
- package/dist/activities/execute-cursor/index.d.ts +5 -1
- package/dist/activities/execute-cursor/index.js +59 -58
- package/dist/activities/execute-cursor/index.js.map +1 -1
- package/dist/activities/execute-cursor/mcp-resolver.d.ts +24 -1
- package/dist/activities/execute-cursor/mcp-resolver.js +5 -2
- package/dist/activities/execute-cursor/mcp-resolver.js.map +1 -1
- package/dist/activities/execute-cursor/prompt-builder.d.ts +18 -4
- package/dist/activities/execute-cursor/prompt-builder.js +12 -7
- package/dist/activities/execute-cursor/prompt-builder.js.map +1 -1
- package/dist/activities/execute-cursor/turn-stream.js +4 -1
- package/dist/activities/execute-cursor/turn-stream.js.map +1 -1
- package/dist/activities/execute-deep-agent/attachment-injector.d.ts +18 -1
- package/dist/activities/execute-deep-agent/attachment-injector.js +68 -23
- package/dist/activities/execute-deep-agent/attachment-injector.js.map +1 -1
- package/dist/activities/execute-deep-agent/environment.js +3 -1
- package/dist/activities/execute-deep-agent/environment.js.map +1 -1
- package/dist/activities/execute-deep-agent/index.js +15 -0
- package/dist/activities/execute-deep-agent/index.js.map +1 -1
- package/dist/activities/execute-deep-agent/prompt-builder.d.ts +7 -7
- package/dist/activities/execute-deep-agent/prompt-builder.js +8 -2
- package/dist/activities/execute-deep-agent/prompt-builder.js.map +1 -1
- package/dist/activities/execute-deep-agent/setup.d.ts +10 -0
- package/dist/activities/execute-deep-agent/setup.js +65 -26
- package/dist/activities/execute-deep-agent/setup.js.map +1 -1
- package/dist/activities/execute-deep-agent/subagent-transformer.d.ts +18 -1
- package/dist/activities/execute-deep-agent/subagent-transformer.js +8 -1
- package/dist/activities/execute-deep-agent/subagent-transformer.js.map +1 -1
- package/dist/activities/execute-deep-agent/subagent-wiring.d.ts +11 -4
- package/dist/activities/execute-deep-agent/subagent-wiring.js +13 -4
- package/dist/activities/execute-deep-agent/subagent-wiring.js.map +1 -1
- package/dist/activities/hydrate-workflow-execution.js +3 -1
- package/dist/activities/hydrate-workflow-execution.js.map +1 -1
- package/dist/activities/workflow-event-activities.d.ts +28 -10
- package/dist/activities/workflow-event-activities.js +87 -58
- package/dist/activities/workflow-event-activities.js.map +1 -1
- package/dist/claimcheck/payload-codec.js +21 -1
- package/dist/claimcheck/payload-codec.js.map +1 -1
- package/dist/client/stigmer-client.d.ts +9 -4
- package/dist/client/stigmer-client.js +28 -15
- package/dist/client/stigmer-client.js.map +1 -1
- package/dist/encryption/config.d.ts +32 -0
- package/dist/encryption/config.js +68 -0
- package/dist/encryption/config.js.map +1 -0
- package/dist/encryption/index.d.ts +3 -0
- package/dist/encryption/index.js +3 -0
- package/dist/encryption/index.js.map +1 -0
- package/dist/encryption/payload-codec.d.ts +41 -0
- package/dist/encryption/payload-codec.js +130 -0
- package/dist/encryption/payload-codec.js.map +1 -0
- package/dist/payload-codecs.d.ts +16 -0
- package/dist/payload-codecs.js +38 -0
- package/dist/payload-codecs.js.map +1 -0
- package/dist/preflight.d.ts +31 -0
- package/dist/preflight.js +43 -0
- package/dist/preflight.js.map +1 -1
- package/dist/runner-manager.js +5 -15
- package/dist/runner-manager.js.map +1 -1
- package/dist/runner.js +5 -16
- package/dist/runner.js.map +1 -1
- package/dist/shared/approval-policy.d.ts +9 -3
- package/dist/shared/approval-policy.js +15 -6
- package/dist/shared/approval-policy.js.map +1 -1
- package/dist/shared/attachment-naming.d.ts +53 -0
- package/dist/shared/attachment-naming.js +59 -0
- package/dist/shared/attachment-naming.js.map +1 -0
- package/dist/shared/attachment-vision.d.ts +43 -2
- package/dist/shared/attachment-vision.js +72 -6
- package/dist/shared/attachment-vision.js.map +1 -1
- package/dist/shared/caller-identity.d.ts +23 -2
- package/dist/shared/caller-identity.js +36 -5
- package/dist/shared/caller-identity.js.map +1 -1
- package/dist/shared/channel-attachment.js +1 -0
- package/dist/shared/channel-attachment.js.map +1 -1
- package/dist/shared/checkpointer/http-saver.d.ts +42 -1
- package/dist/shared/checkpointer/http-saver.js +96 -8
- package/dist/shared/checkpointer/http-saver.js.map +1 -1
- package/dist/shared/conversation-attachment.js +1 -0
- package/dist/shared/conversation-attachment.js.map +1 -1
- package/dist/shared/datastore-attachment.d.ts +50 -7
- package/dist/shared/datastore-attachment.js +93 -11
- package/dist/shared/datastore-attachment.js.map +1 -1
- package/dist/shared/http-retry.d.ts +43 -0
- package/dist/shared/http-retry.js +50 -0
- package/dist/shared/http-retry.js.map +1 -0
- package/dist/shared/llm-backend.d.ts +275 -0
- package/dist/shared/llm-backend.js +425 -0
- package/dist/shared/llm-backend.js.map +1 -0
- package/dist/shared/llm-proxy.d.ts +8 -0
- package/dist/shared/llm-proxy.js +15 -0
- package/dist/shared/llm-proxy.js.map +1 -1
- package/dist/shared/mcp-enabled-tools.d.ts +57 -0
- package/dist/shared/mcp-enabled-tools.js +86 -0
- package/dist/shared/mcp-enabled-tools.js.map +1 -0
- package/dist/shared/mcp-manager.d.ts +17 -2
- package/dist/shared/mcp-manager.js +37 -25
- package/dist/shared/mcp-manager.js.map +1 -1
- package/dist/shared/mcp-resolver.d.ts +39 -2
- package/dist/shared/mcp-resolver.js +38 -2
- package/dist/shared/mcp-resolver.js.map +1 -1
- package/dist/shared/model-client.d.ts +12 -5
- package/dist/shared/model-client.js +138 -18
- package/dist/shared/model-client.js.map +1 -1
- package/dist/shared/model-error.js +198 -5
- package/dist/shared/model-error.js.map +1 -1
- package/dist/shared/model-registry.d.ts +20 -2
- package/dist/shared/model-registry.js +37 -2
- package/dist/shared/model-registry.js.map +1 -1
- package/dist/shared/plan-mode-permissions.d.ts +26 -0
- package/dist/shared/plan-mode-permissions.js +28 -0
- package/dist/shared/plan-mode-permissions.js.map +1 -0
- package/dist/worker.d.ts +2 -1
- package/dist/worker.js +2 -4
- package/dist/worker.js.map +1 -1
- package/dist/workflow-engine/types.d.ts +18 -0
- package/dist/workflow-engine/types.js.map +1 -1
- package/dist/workflows/call-agent-orchestrator.d.ts +9 -0
- package/dist/workflows/call-agent-orchestrator.js +1 -0
- package/dist/workflows/call-agent-orchestrator.js.map +1 -1
- package/dist/workflows/connect-mcp-server.js +7 -0
- package/dist/workflows/connect-mcp-server.js.map +1 -1
- package/dist/workflows/engine-core.js +23 -2
- package/dist/workflows/engine-core.js.map +1 -1
- package/dist/workflows/execute-from-execution.d.ts +1 -1
- package/dist/workflows/execute-from-execution.js +11 -1
- package/dist/workflows/execute-from-execution.js.map +1 -1
- package/package.json +8 -2
- package/src/__tests__/claimcheck-codec.test.ts +36 -0
- package/src/__tests__/encryption-codec.test.ts +234 -0
- package/src/__tests__/fixtures/encrypted-payload-fixture.json +15 -0
- package/src/__tests__/history-encryption-e2e.test.ts +243 -0
- package/src/__tests__/preflight.test.ts +50 -2
- package/src/activities/__tests__/call-llm.test.ts +75 -0
- package/src/activities/__tests__/classify-tool-approvals.test.ts +117 -1
- package/src/activities/__tests__/discover-mcp-server.hang.test.ts +103 -0
- package/src/activities/__tests__/discover-mcp-server.test.ts +203 -0
- package/src/activities/__tests__/workflow-event-activities.test.ts +107 -8
- package/src/activities/call-llm.ts +9 -16
- package/src/activities/classify-tool-approvals.ts +34 -4
- package/src/activities/discover-mcp-server.ts +190 -32
- package/src/activities/execute-cursor/__test-utils__/cursor-hook-harness.ts +9 -0
- package/src/activities/execute-cursor/__tests__/approval-gate.test.ts +14 -0
- package/src/activities/execute-cursor/__tests__/attachment-resolver.test.ts +92 -0
- package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +40 -14
- package/src/activities/execute-cursor/__tests__/error-classifier-extraction.test.ts +208 -0
- package/src/activities/execute-cursor/__tests__/extract-structured-output.test.ts +120 -0
- package/src/activities/execute-cursor/__tests__/hook-script.test.ts +93 -0
- package/src/activities/execute-cursor/__tests__/mcp-resolver.test.ts +125 -0
- package/src/activities/execute-cursor/__tests__/prompt-builder-delegation.test.ts +1 -1
- package/src/activities/execute-cursor/__tests__/turn-stream.test.ts +13 -0
- package/src/activities/execute-cursor/approval-state.ts +30 -1
- package/src/activities/execute-cursor/attachment-resolver.ts +38 -3
- package/src/activities/execute-cursor/blueprint-resolver.ts +7 -27
- package/src/activities/execute-cursor/env-resolver.ts +3 -1
- package/src/activities/execute-cursor/error-classifier.ts +91 -4
- package/src/activities/execute-cursor/extract-structured-output.ts +72 -0
- package/src/activities/execute-cursor/hook-script.ts +74 -10
- package/src/activities/execute-cursor/index.ts +63 -72
- package/src/activities/execute-cursor/mcp-resolver.ts +36 -2
- package/src/activities/execute-cursor/prompt-builder.ts +34 -9
- package/src/activities/execute-cursor/turn-stream.ts +5 -2
- package/src/activities/execute-deep-agent/__tests__/attachment-injector.test.ts +132 -8
- package/src/activities/execute-deep-agent/__tests__/datastore-degradation.test.ts +104 -0
- package/src/activities/execute-deep-agent/__tests__/hitl-reject.test.ts +2 -0
- package/src/activities/execute-deep-agent/__tests__/hitl-resume-approve-all.test.ts +1 -0
- package/src/activities/execute-deep-agent/__tests__/hitl-resume-history.test.ts +1 -0
- package/src/activities/execute-deep-agent/__tests__/prompt-builder.test.ts +34 -5
- package/src/activities/execute-deep-agent/__tests__/sequential-gate-resume.test.ts +1 -0
- package/src/activities/execute-deep-agent/__tests__/subagent-plan-mode-permissions.test.ts +173 -0
- package/src/activities/execute-deep-agent/__tests__/subagent-wiring.test.ts +12 -7
- package/src/activities/execute-deep-agent/attachment-injector.ts +94 -30
- package/src/activities/execute-deep-agent/environment.ts +3 -1
- package/src/activities/execute-deep-agent/index.ts +20 -0
- package/src/activities/execute-deep-agent/prompt-builder.ts +20 -10
- package/src/activities/execute-deep-agent/setup.ts +86 -31
- package/src/activities/execute-deep-agent/subagent-transformer.ts +23 -1
- package/src/activities/execute-deep-agent/subagent-wiring.ts +14 -4
- package/src/activities/hydrate-workflow-execution.ts +3 -1
- package/src/activities/workflow-event-activities.ts +96 -69
- package/src/claimcheck/payload-codec.ts +33 -1
- package/src/client/__tests__/stigmer-client.test.ts +8 -8
- package/src/client/stigmer-client.ts +32 -18
- package/src/encryption/config.ts +91 -0
- package/src/encryption/index.ts +3 -0
- package/src/encryption/payload-codec.ts +152 -0
- package/src/payload-codecs.ts +56 -0
- package/src/preflight.ts +45 -0
- package/src/runner-manager.ts +6 -24
- package/src/runner.ts +6 -25
- package/src/shared/__tests__/approval-policy.test.ts +82 -39
- package/src/shared/__tests__/attachment-naming.test.ts +159 -0
- package/src/shared/__tests__/attachment-vision.test.ts +97 -0
- package/src/shared/__tests__/bedrock-adapter.test.ts +213 -0
- package/src/shared/__tests__/bedrock-seam.test.ts +390 -0
- package/src/shared/__tests__/caller-identity.test.ts +25 -0
- package/src/shared/__tests__/channel-attachment.test.ts +1 -1
- package/src/shared/__tests__/connect-backfill.test.ts +1 -0
- package/src/shared/__tests__/conversation-attachment.test.ts +1 -1
- package/src/shared/__tests__/datastore-attachment.test.ts +129 -1
- package/src/shared/__tests__/foundry-adapter.test.ts +276 -0
- package/src/shared/__tests__/foundry-seam.test.ts +482 -0
- package/src/shared/__tests__/http-retry.test.ts +67 -0
- package/src/shared/__tests__/llm-backend.test.ts +616 -0
- package/src/shared/__tests__/mcp-enabled-tools.test.ts +86 -0
- package/src/shared/__tests__/mcp-manager.test.ts +170 -2
- package/src/shared/__tests__/mcp-resolver.test.ts +146 -3
- package/src/shared/__tests__/model-client.test.ts +154 -0
- package/src/shared/__tests__/model-error.test.ts +289 -1
- package/src/shared/__tests__/model-registry.test.ts +71 -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/attachment-naming.ts +78 -0
- package/src/shared/attachment-vision.ts +92 -9
- package/src/shared/caller-identity.ts +40 -5
- package/src/shared/channel-attachment.ts +1 -0
- package/src/shared/checkpointer/__tests__/http-saver.test.ts +196 -1
- package/src/shared/checkpointer/http-saver.ts +117 -9
- package/src/shared/conversation-attachment.ts +1 -0
- package/src/shared/datastore-attachment.ts +106 -11
- package/src/shared/http-retry.ts +50 -0
- package/src/shared/llm-backend.ts +544 -0
- package/src/shared/llm-proxy.ts +15 -0
- package/src/shared/mcp-enabled-tools.ts +105 -0
- package/src/shared/mcp-manager.ts +43 -26
- package/src/shared/mcp-resolver.ts +73 -2
- package/src/shared/model-client.ts +161 -19
- package/src/shared/model-error.ts +222 -4
- package/src/shared/model-registry.ts +50 -2
- package/src/shared/plan-mode-permissions.ts +30 -0
- package/src/worker.ts +4 -5
- package/src/workflow-engine/types.ts +18 -0
- package/src/workflows/__tests__/execute-serverless-workflow.test.ts +68 -2
- package/src/workflows/call-agent-orchestrator.ts +10 -0
- package/src/workflows/connect-mcp-server.ts +7 -0
- package/src/workflows/engine-core.ts +23 -2
- package/src/workflows/execute-from-execution.ts +12 -2
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single assembly point for the worker's payload codec chain, shared by
|
|
3
|
+
* the standalone runner (runner.ts) and the runner manager
|
|
4
|
+
* (runner-manager.ts) so the two entrypoints cannot drift.
|
|
5
|
+
*
|
|
6
|
+
* Codec order is a correctness property, not a style choice. Temporal
|
|
7
|
+
* applies codecs in array order on encode and in reverse on decode, so
|
|
8
|
+
* with [encryption, claimcheck]:
|
|
9
|
+
*
|
|
10
|
+
* encode: encrypt → claim-check (payloads the claim-check codec
|
|
11
|
+
* relocates to object storage are already ciphertext)
|
|
12
|
+
* decode: claim-check → decrypt (restore the blob, then decrypt)
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { PayloadCodec } from "@temporalio/common";
|
|
16
|
+
import type { Config } from "./config.js";
|
|
17
|
+
|
|
18
|
+
export async function createPayloadCodecs(
|
|
19
|
+
config: Config,
|
|
20
|
+
): Promise<PayloadCodec[] | undefined> {
|
|
21
|
+
const codecs: PayloadCodec[] = [];
|
|
22
|
+
|
|
23
|
+
const { loadPayloadEncryptionConfig, EncryptionPayloadCodec } = await import(
|
|
24
|
+
"./encryption/index.js"
|
|
25
|
+
);
|
|
26
|
+
const encryptionConfig = loadPayloadEncryptionConfig();
|
|
27
|
+
if (encryptionConfig) {
|
|
28
|
+
codecs.push(new EncryptionPayloadCodec(encryptionConfig));
|
|
29
|
+
console.log(
|
|
30
|
+
`[runner] Payload encryption enabled (key_id=${encryptionConfig.primary.keyId}` +
|
|
31
|
+
(encryptionConfig.secondary
|
|
32
|
+
? `, secondary_key_id=${encryptionConfig.secondary.keyId})`
|
|
33
|
+
: ")"),
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const { loadClaimcheckConfig, ClaimcheckPayloadCodec } = await import(
|
|
38
|
+
"./claimcheck/index.js"
|
|
39
|
+
);
|
|
40
|
+
const claimcheckConfig = loadClaimcheckConfig();
|
|
41
|
+
if (claimcheckConfig.enabled) {
|
|
42
|
+
const { loadArtifactStorageConfig, createArtifactStorage } = await import(
|
|
43
|
+
"./shared/artifact-storage.js"
|
|
44
|
+
);
|
|
45
|
+
const storageConfig = loadArtifactStorageConfig(config);
|
|
46
|
+
const storage = createArtifactStorage(storageConfig);
|
|
47
|
+
codecs.push(new ClaimcheckPayloadCodec(storage, claimcheckConfig));
|
|
48
|
+
console.log(
|
|
49
|
+
`[runner] Claimcheck enabled (threshold=${claimcheckConfig.thresholdBytes}B, ` +
|
|
50
|
+
`compression=${claimcheckConfig.compressionEnabled}, ` +
|
|
51
|
+
`storage=${storageConfig.type})`,
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return codecs.length > 0 ? codecs : undefined;
|
|
56
|
+
}
|
package/src/preflight.ts
CHANGED
|
@@ -21,8 +21,15 @@
|
|
|
21
21
|
* The gate is unconditional (not checkpointer-type-dependent): the eager
|
|
22
22
|
* import chain needs `node:sqlite` to LOAD regardless of configuration, so
|
|
23
23
|
* the gate mirrors the actual load-time requirement.
|
|
24
|
+
*
|
|
25
|
+
* This module also hosts {@link assertLlmBackendsPreflight}, the
|
|
26
|
+
* deployment-config gate the runner factories run at construction. The
|
|
27
|
+
* llm-backend import below is a pure, dependency-free module, so it does
|
|
28
|
+
* not compromise the load-order guarantees above.
|
|
24
29
|
*/
|
|
25
30
|
|
|
31
|
+
import { preflightLlmBackends } from "./shared/llm-backend.js";
|
|
32
|
+
|
|
26
33
|
/**
|
|
27
34
|
* True when this Node binary provides the built-in `node:sqlite`.
|
|
28
35
|
*
|
|
@@ -57,3 +64,41 @@ export function preflightNodeRuntime(
|
|
|
57
64
|
`Use Node >= 22.13 (22.x line) or >= 23.4 (23.x and later).`
|
|
58
65
|
);
|
|
59
66
|
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Validate LLM backend configuration (STIGMER_ANTHROPIC_BACKEND /
|
|
70
|
+
* STIGMER_OPENAI_BACKEND) at runner construction, throwing an actionable
|
|
71
|
+
* operator message on fatal misconfiguration and logging precedence
|
|
72
|
+
* warnings.
|
|
73
|
+
*
|
|
74
|
+
* Called from the runner factories rather than main.ts because
|
|
75
|
+
* @stigmer/runner is a public library: in-process embedders never execute
|
|
76
|
+
* main.ts, while all of main.ts's process modes construct through the
|
|
77
|
+
* factories. A library must never process.exit, so this throws — manager
|
|
78
|
+
* mode's factory catch converts it to a fatal IPC error for the desktop
|
|
79
|
+
* host, static/pool modes exit through main()'s fatal handler, and
|
|
80
|
+
* embedders get a rejected promise.
|
|
81
|
+
*
|
|
82
|
+
* Backend misconfiguration is deployment-static; failing here means a
|
|
83
|
+
* broken deployment refuses to accept work instead of failing every
|
|
84
|
+
* execution mid-flight (the createClient factories construct lazily on
|
|
85
|
+
* first request, far too late for a good operator experience).
|
|
86
|
+
*
|
|
87
|
+
* @param proxyEndpoint The effective proxy endpoint. Factories receive it
|
|
88
|
+
* via options (env-derived in main.ts flows, caller-supplied for
|
|
89
|
+
* embedders), so it is threaded explicitly instead of re-read from env —
|
|
90
|
+
* an options-supplied proxy must downgrade backend vars to warnings the
|
|
91
|
+
* same way STIGMER_PROXY_ENDPOINT does.
|
|
92
|
+
*/
|
|
93
|
+
export function assertLlmBackendsPreflight(proxyEndpoint?: string | null): void {
|
|
94
|
+
const env = proxyEndpoint
|
|
95
|
+
? { ...process.env, STIGMER_PROXY_ENDPOINT: proxyEndpoint }
|
|
96
|
+
: process.env;
|
|
97
|
+
const { error, warnings } = preflightLlmBackends(env);
|
|
98
|
+
for (const warning of warnings) {
|
|
99
|
+
console.warn(`[runner] ${warning}`);
|
|
100
|
+
}
|
|
101
|
+
if (error !== null) {
|
|
102
|
+
throw new Error(error);
|
|
103
|
+
}
|
|
104
|
+
}
|
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
|
|
@@ -351,7 +353,8 @@ export async function createStigmerRunnerManager(
|
|
|
351
353
|
`[runner-manager] Artifact store: could not resolve config for boot log: ${err}`,
|
|
352
354
|
);
|
|
353
355
|
}
|
|
354
|
-
const
|
|
356
|
+
const { createPayloadCodecs } = await import("./payload-codecs.js");
|
|
357
|
+
const payloadCodecs = await createPayloadCodecs(config);
|
|
355
358
|
|
|
356
359
|
const connection = await NativeConnection.connect({
|
|
357
360
|
address: config.temporalAddress,
|
|
@@ -396,9 +399,7 @@ export async function createStigmerRunnerManager(
|
|
|
396
399
|
activities,
|
|
397
400
|
workflowBundle,
|
|
398
401
|
maxConcurrentActivityTaskExecutions: config.maxConcurrentActivities,
|
|
399
|
-
dataConverter:
|
|
400
|
-
? { payloadCodecs: [payloadCodec] }
|
|
401
|
-
: undefined,
|
|
402
|
+
dataConverter: payloadCodecs?.length ? { payloadCodecs } : undefined,
|
|
402
403
|
sinks: interceptorConfig.sinks,
|
|
403
404
|
interceptors: interceptorConfig.interceptors,
|
|
404
405
|
});
|
|
@@ -739,25 +740,6 @@ async function createAllActivities(config: Config): Promise<WorkerActivities> {
|
|
|
739
740
|
};
|
|
740
741
|
}
|
|
741
742
|
|
|
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
743
|
interface InterceptorConfig {
|
|
762
744
|
sinks: InjectedSinks<any>;
|
|
763
745
|
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
|
|
@@ -302,10 +304,11 @@ export async function createStigmerRunner(
|
|
|
302
304
|
);
|
|
303
305
|
}
|
|
304
306
|
|
|
305
|
-
const
|
|
307
|
+
const { createPayloadCodecs } = await import("./payload-codecs.js");
|
|
308
|
+
const payloadCodecs = await createPayloadCodecs(config);
|
|
306
309
|
|
|
307
310
|
const { startWorker } = await import("./worker.js");
|
|
308
|
-
const worker = await startWorker({ config, activities,
|
|
311
|
+
const worker = await startWorker({ config, activities, payloadCodecs });
|
|
309
312
|
markBoot("worker_created");
|
|
310
313
|
|
|
311
314
|
return {
|
|
@@ -455,28 +458,6 @@ async function createAllActivities(config: Config): Promise<WorkerActivities> {
|
|
|
455
458
|
};
|
|
456
459
|
}
|
|
457
460
|
|
|
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
461
|
function resolveDefaultWorkspaceDir(): string {
|
|
481
462
|
try {
|
|
482
463
|
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);
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { uniquifyFilename, allocateUniqueName } from "../attachment-naming.js";
|
|
6
|
+
import { injectAttachments } from "../../activities/execute-deep-agent/attachment-injector.js";
|
|
7
|
+
import { resolveAttachments } from "../../activities/execute-cursor/attachment-resolver.js";
|
|
8
|
+
import { getPlatformDir } from "../workspace/platform-dir.js";
|
|
9
|
+
import { makeInMemoryArtifactStorage } from "../../__test-utils__/fake-artifact-storage.js";
|
|
10
|
+
import type { WorkspaceBackend } from "../workspace/types.js";
|
|
11
|
+
|
|
12
|
+
describe("uniquifyFilename", () => {
|
|
13
|
+
// This table is the byte-for-byte twin of the React SDK suite
|
|
14
|
+
// (sdk/react/src/attachment/__tests__/attachment-utils.test.ts) — the two
|
|
15
|
+
// implementations are kept in sync by hand, so their pins must agree.
|
|
16
|
+
const cases: Array<{
|
|
17
|
+
scenario: string;
|
|
18
|
+
name: string;
|
|
19
|
+
taken: string[];
|
|
20
|
+
expected: string;
|
|
21
|
+
}> = [
|
|
22
|
+
{
|
|
23
|
+
scenario: "returns the name unchanged when nothing collides",
|
|
24
|
+
name: "report.pdf",
|
|
25
|
+
taken: [],
|
|
26
|
+
expected: "report.pdf",
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
scenario: "suffixes -2 before the extension on first collision",
|
|
30
|
+
name: "report.pdf",
|
|
31
|
+
taken: ["report.pdf"],
|
|
32
|
+
expected: "report-2.pdf",
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
scenario: "walks past already-taken suffixes",
|
|
36
|
+
name: "report.pdf",
|
|
37
|
+
taken: ["report.pdf", "report-2.pdf", "report-3.pdf"],
|
|
38
|
+
expected: "report-4.pdf",
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
scenario: "handles names without an extension",
|
|
42
|
+
name: "Makefile",
|
|
43
|
+
taken: ["Makefile"],
|
|
44
|
+
expected: "Makefile-2",
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
scenario: "treats a leading dot as a hidden-file prefix, not an extension",
|
|
48
|
+
name: ".env",
|
|
49
|
+
taken: [".env"],
|
|
50
|
+
expected: ".env-2",
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
scenario: "only splits on the last dot of a multi-dot name",
|
|
54
|
+
name: "backup.tar.gz",
|
|
55
|
+
taken: ["backup.tar.gz"],
|
|
56
|
+
expected: "backup.tar-2.gz",
|
|
57
|
+
},
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
for (const { scenario, name, taken, expected } of cases) {
|
|
61
|
+
it(scenario, () => {
|
|
62
|
+
expect(uniquifyFilename(name, new Set(taken))).toBe(expected);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe("allocateUniqueName", () => {
|
|
68
|
+
it("claims the allocated name so sequential allocations see each other", () => {
|
|
69
|
+
const taken = new Set<string>();
|
|
70
|
+
|
|
71
|
+
expect(allocateUniqueName("data.csv", taken)).toEqual({ name: "data.csv" });
|
|
72
|
+
expect(allocateUniqueName("data.csv", taken)).toEqual({
|
|
73
|
+
name: "data-2.csv",
|
|
74
|
+
renamedFrom: "data.csv",
|
|
75
|
+
});
|
|
76
|
+
expect(allocateUniqueName("data.csv", taken)).toEqual({
|
|
77
|
+
name: "data-3.csv",
|
|
78
|
+
renamedFrom: "data.csv",
|
|
79
|
+
});
|
|
80
|
+
expect(taken).toEqual(new Set(["data.csv", "data-2.csv", "data-3.csv"]));
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("carries no renamedFrom when the name was free (nothing to disclose)", () => {
|
|
84
|
+
const allocated = allocateUniqueName("report.pdf", new Set());
|
|
85
|
+
expect(allocated.name).toBe("report.pdf");
|
|
86
|
+
expect("renamedFrom" in allocated && allocated.renamedFrom !== undefined).toBe(false);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe("cross-harness naming parity", () => {
|
|
91
|
+
// Both harnesses must resolve the same attachment list to the same final
|
|
92
|
+
// basenames — one platform answer, not two accidental ones (issue #364).
|
|
93
|
+
let workspaceDir: string;
|
|
94
|
+
let sessionId: string;
|
|
95
|
+
let platformDir: string;
|
|
96
|
+
|
|
97
|
+
beforeEach(() => {
|
|
98
|
+
workspaceDir = mkdtempSync(join(tmpdir(), "naming-parity-ws-"));
|
|
99
|
+
sessionId = `parity-session-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
100
|
+
platformDir = getPlatformDir(sessionId);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
afterEach(() => {
|
|
104
|
+
rmSync(platformDir, { recursive: true, force: true });
|
|
105
|
+
rmSync(workspaceDir, { recursive: true, force: true });
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
function makeAttachment(filename: string, storageKey: string) {
|
|
109
|
+
return {
|
|
110
|
+
filename,
|
|
111
|
+
storageKey,
|
|
112
|
+
mountPath: "",
|
|
113
|
+
contentType: "text/plain",
|
|
114
|
+
extract: false,
|
|
115
|
+
localPath: "",
|
|
116
|
+
$typeName: "ai.stigmer.agentic.agentexecution.v1.Attachment" as const,
|
|
117
|
+
$unknown: undefined,
|
|
118
|
+
} as any;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
it("both harnesses produce identical final basenames for the same duplicate-heavy list", async () => {
|
|
122
|
+
const { storage } = makeInMemoryArtifactStorage();
|
|
123
|
+
await storage.upload("attachments/01A/data.csv", Buffer.from("a"), "text/plain");
|
|
124
|
+
await storage.upload("attachments/01B/data.csv", Buffer.from("b"), "text/plain");
|
|
125
|
+
await storage.upload("attachments/01C/Makefile", Buffer.from("c"), "text/plain");
|
|
126
|
+
await storage.upload("attachments/01D/Makefile", Buffer.from("d"), "text/plain");
|
|
127
|
+
const attachments = [
|
|
128
|
+
makeAttachment("data.csv", "attachments/01A/data.csv"),
|
|
129
|
+
makeAttachment("data.csv", "attachments/01B/data.csv"),
|
|
130
|
+
makeAttachment("Makefile", "attachments/01C/Makefile"),
|
|
131
|
+
makeAttachment("Makefile", "attachments/01D/Makefile"),
|
|
132
|
+
];
|
|
133
|
+
|
|
134
|
+
const backend = { writeFileBuffer: vi.fn() } as unknown as WorkspaceBackend;
|
|
135
|
+
const injected = await injectAttachments({
|
|
136
|
+
backend,
|
|
137
|
+
attachments,
|
|
138
|
+
storage,
|
|
139
|
+
isLocalMode: false,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
const resolved = await resolveAttachments(attachments, {
|
|
143
|
+
sessionId,
|
|
144
|
+
primaryWorkspaceDir: workspaceDir,
|
|
145
|
+
mode: "cloud",
|
|
146
|
+
storage,
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
const injectedNames = injected.map((f) => f.filename);
|
|
150
|
+
const resolvedNames = resolved.map((r) => r.filename);
|
|
151
|
+
expect(injectedNames).toEqual(["data.csv", "data-2.csv", "Makefile", "Makefile-2"]);
|
|
152
|
+
expect(resolvedNames).toEqual(injectedNames);
|
|
153
|
+
expect(injected.map((f) => f.renamedFrom)).toEqual(
|
|
154
|
+
resolved.map((r) => r.renamedFrom),
|
|
155
|
+
);
|
|
156
|
+
// The resolver's files really land under the renamed paths.
|
|
157
|
+
expect(readFileSync(join(platformDir, "inputs", "data-2.csv"), "utf-8")).toBe("b");
|
|
158
|
+
});
|
|
159
|
+
});
|