@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
|
@@ -26,6 +26,14 @@
|
|
|
26
26
|
*/
|
|
27
27
|
|
|
28
28
|
import type { LlmProvider } from "./llm-proxy.js";
|
|
29
|
+
import {
|
|
30
|
+
parseAnthropicBackend,
|
|
31
|
+
BACKEND_DOC_URL,
|
|
32
|
+
BEDROCK_INFERENCE_PREFIX_ENV,
|
|
33
|
+
FOUNDRY_DEPLOYMENT_MAP_ENV,
|
|
34
|
+
FOUNDRY_RESOURCE_ENV,
|
|
35
|
+
type AnthropicBackend,
|
|
36
|
+
} from "./llm-backend.js";
|
|
29
37
|
|
|
30
38
|
/**
|
|
31
39
|
* Machine-readable code the cloud proxy embeds in rewritten platform-fault
|
|
@@ -96,6 +104,7 @@ export function classifyModelCallError(
|
|
|
96
104
|
): ClassifiedModelError | undefined {
|
|
97
105
|
const root = unwrapModelError(err);
|
|
98
106
|
const message = root instanceof Error ? root.message : String(root);
|
|
107
|
+
const backend = resolveDirectBackend(ctx);
|
|
99
108
|
|
|
100
109
|
// 1. Platform sentinel — before status mapping (see module doc).
|
|
101
110
|
if (message.includes(PLATFORM_CAPACITY_SENTINEL)) {
|
|
@@ -106,6 +115,69 @@ export function classifyModelCallError(
|
|
|
106
115
|
};
|
|
107
116
|
}
|
|
108
117
|
|
|
118
|
+
// 1b. Backend credential acquisition — also before status mapping: these
|
|
119
|
+
// failures come from the auth library (thrown while adapting the
|
|
120
|
+
// request, usually with no HTTP status) and won't self-heal on retry.
|
|
121
|
+
// Raw, they read like library internals ("Could not load the default
|
|
122
|
+
// credentials"); the operator needs to hear "fix your cloud
|
|
123
|
+
// credentials".
|
|
124
|
+
if (backend === "vertex" && isGoogleCredentialMessage(message)) {
|
|
125
|
+
return {
|
|
126
|
+
code: "LLM_BACKEND_CREDENTIALS",
|
|
127
|
+
retryable: false,
|
|
128
|
+
message:
|
|
129
|
+
`The vertex backend could not acquire Google credentials for ${modelLabel(ctx)}. ` +
|
|
130
|
+
`Set GOOGLE_APPLICATION_CREDENTIALS to a service-account key, or run on a GCP ` +
|
|
131
|
+
`identity (workload identity / metadata server). ANTHROPIC_VERTEX_PROJECT_ID is ` +
|
|
132
|
+
`only needed when the credentials don't carry a project. See ${BACKEND_DOC_URL}. ` +
|
|
133
|
+
`Underlying error: ${message}`,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
if (backend === "bedrock" && isAwsCredentialMessage(message)) {
|
|
137
|
+
return {
|
|
138
|
+
code: "LLM_BACKEND_CREDENTIALS",
|
|
139
|
+
retryable: false,
|
|
140
|
+
message:
|
|
141
|
+
`The bedrock backend could not acquire AWS credentials for ${modelLabel(ctx)}. ` +
|
|
142
|
+
`Provide credentials through the standard AWS chain (environment keys, an IAM ` +
|
|
143
|
+
`role / IRSA, config files) or set AWS_BEARER_TOKEN_BEDROCK. See ${BACKEND_DOC_URL}. ` +
|
|
144
|
+
`Underlying error: ${message}`,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
if (backend === "foundry" && isFoundryCredentialMessage(message)) {
|
|
148
|
+
// Only the keyless Entra path can land here: API-key failures arrive
|
|
149
|
+
// as HTTP 401s (status arm below), while a failing token provider
|
|
150
|
+
// throws statusless from inside the Foundry SDK's authHeaders.
|
|
151
|
+
return {
|
|
152
|
+
code: "LLM_BACKEND_CREDENTIALS",
|
|
153
|
+
retryable: false,
|
|
154
|
+
message:
|
|
155
|
+
`The foundry backend could not acquire a Microsoft Entra ID token for ${modelLabel(ctx)}. ` +
|
|
156
|
+
`Give the runner an Azure identity the credential chain can resolve (workload ` +
|
|
157
|
+
`identity / managed identity, service-principal env vars, or az login), or set ` +
|
|
158
|
+
`ANTHROPIC_FOUNDRY_API_KEY to use API-key auth instead. See ${BACKEND_DOC_URL}. ` +
|
|
159
|
+
`Underlying error: ${message}`,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// 1c. Bedrock's inference-profile rejection — a config condition, not a
|
|
164
|
+
// bad request: newer Claude models cannot be invoked by bare model id
|
|
165
|
+
// (AWS lists their in-region endpoint as N/A). The operator remedy is
|
|
166
|
+
// one env var, so say exactly that instead of relaying AWS prose that
|
|
167
|
+
// talks about ARNs and provisioned throughput.
|
|
168
|
+
if (backend === "bedrock" && isBedrockInferenceProfileMessage(message)) {
|
|
169
|
+
return {
|
|
170
|
+
code: "LLM_BACKEND_MODEL_ROUTING",
|
|
171
|
+
retryable: false,
|
|
172
|
+
message:
|
|
173
|
+
`Bedrock requires an inference profile for ${modelLabel(ctx)} — the bare model ` +
|
|
174
|
+
`id cannot be invoked on-demand. Set ${BEDROCK_INFERENCE_PREFIX_ENV} to your ` +
|
|
175
|
+
`deployment's geography (e.g. "us", "eu", or "global"), or map this model ` +
|
|
176
|
+
`explicitly in STIGMER_BEDROCK_MODEL_MAP. See ${BACKEND_DOC_URL}. ` +
|
|
177
|
+
`Underlying error: ${message}`,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
109
181
|
// 2. Provider billing prose. In direct mode this is the user's own
|
|
110
182
|
// account and the fix is theirs. In proxy mode these patterns should
|
|
111
183
|
// never appear (the proxy rewrites them), but a version-skewed proxy
|
|
@@ -135,7 +207,7 @@ export function classifyModelCallError(
|
|
|
135
207
|
? (root as { status: number }).status
|
|
136
208
|
: undefined;
|
|
137
209
|
if (status !== undefined) {
|
|
138
|
-
return classifyByStatus(status, message, ctx);
|
|
210
|
+
return classifyByStatus(status, message, ctx, backend);
|
|
139
211
|
}
|
|
140
212
|
|
|
141
213
|
// 4. Connection/timeout heuristics on the root error's class name. Strict
|
|
@@ -175,6 +247,7 @@ function classifyByStatus(
|
|
|
175
247
|
status: number,
|
|
176
248
|
rawMessage: string,
|
|
177
249
|
ctx: ModelErrorContext,
|
|
250
|
+
backend: AnthropicBackend,
|
|
178
251
|
): ClassifiedModelError {
|
|
179
252
|
const context = modelLabel(ctx);
|
|
180
253
|
|
|
@@ -186,7 +259,23 @@ function classifyByStatus(
|
|
|
186
259
|
message: ctx.proxyMode
|
|
187
260
|
? `The Stigmer platform rejected this model call (authentication, HTTP 401) for ${context}. ` +
|
|
188
261
|
`Your session token may have expired — retry the execution, and contact support if it persists.`
|
|
189
|
-
|
|
262
|
+
// On a cloud backend the credential is a cloud identity — "check
|
|
263
|
+
// your API key" would send the operator hunting for a key that
|
|
264
|
+
// isn't in play.
|
|
265
|
+
: backend === "vertex"
|
|
266
|
+
? `Google rejected this Vertex AI call (authentication, HTTP 401) for ${context}. ` +
|
|
267
|
+
`The credentials are expired or not valid for this project — check ` +
|
|
268
|
+
`GOOGLE_APPLICATION_CREDENTIALS or the runner's GCP identity. See ${BACKEND_DOC_URL}.`
|
|
269
|
+
: backend === "bedrock"
|
|
270
|
+
? `AWS rejected this Bedrock call (authentication, HTTP 401) for ${context}. ` +
|
|
271
|
+
`The credentials are expired or invalid — check the runner's AWS identity ` +
|
|
272
|
+
`(environment keys, IAM role / IRSA) or AWS_BEARER_TOKEN_BEDROCK. See ${BACKEND_DOC_URL}.`
|
|
273
|
+
: backend === "foundry"
|
|
274
|
+
? `Azure rejected this Microsoft Foundry call (authentication, HTTP 401) for ${context}. ` +
|
|
275
|
+
`The credential is expired or not valid for this Foundry resource — check ` +
|
|
276
|
+
`ANTHROPIC_FOUNDRY_API_KEY (find it on the deployment's Details tab) or the ` +
|
|
277
|
+
`runner's Azure identity. See ${BACKEND_DOC_URL}.`
|
|
278
|
+
: `Authentication failed for ${context}. Check that your API key is valid and not expired.`,
|
|
190
279
|
};
|
|
191
280
|
case 403:
|
|
192
281
|
return {
|
|
@@ -195,13 +284,50 @@ function classifyByStatus(
|
|
|
195
284
|
message: ctx.proxyMode
|
|
196
285
|
? `The Stigmer platform denied this model call (authorization, HTTP 403) for ${context}. ` +
|
|
197
286
|
`Verify this execution is permitted to use the model, and contact support if it persists.`
|
|
198
|
-
:
|
|
287
|
+
: backend === "vertex"
|
|
288
|
+
? `Vertex AI denied this call (HTTP 403) for ${context}. Grant the runner's ` +
|
|
289
|
+
`service account the "Vertex AI User" role (aiplatform.endpoints.predict) ` +
|
|
290
|
+
`in the target project. See ${BACKEND_DOC_URL}.`
|
|
291
|
+
: backend === "bedrock"
|
|
292
|
+
// The most common Bedrock setup mistake: Anthropic models must
|
|
293
|
+
// be enabled per account ("Model access" in the Bedrock console,
|
|
294
|
+
// including the use-case submission), on top of IAM.
|
|
295
|
+
? `Bedrock denied this call (HTTP 403) for ${context}. Enable this Claude model ` +
|
|
296
|
+
`under "Model access" in the Bedrock console (Anthropic models require a ` +
|
|
297
|
+
`use-case submission), and grant the runner's identity bedrock:InvokeModel ` +
|
|
298
|
+
`for the model and its inference profile. See ${BACKEND_DOC_URL}.`
|
|
299
|
+
: backend === "foundry"
|
|
300
|
+
? `Microsoft Foundry denied this call (HTTP 403) for ${context}. Grant the ` +
|
|
301
|
+
`runner's Azure identity the "Foundry User" (or "Cognitive Services User") ` +
|
|
302
|
+
`RBAC role on the Foundry resource. See ${BACKEND_DOC_URL}.`
|
|
303
|
+
: `Access denied for ${context}. Verify that your API key has permission to use this model.`,
|
|
199
304
|
};
|
|
200
305
|
case 404:
|
|
201
306
|
return {
|
|
202
307
|
code: "LLM_MODEL_NOT_FOUND",
|
|
203
308
|
retryable: false,
|
|
204
|
-
|
|
309
|
+
// The most common Vertex setup mistake: Claude models must be enabled
|
|
310
|
+
// per project in Model Garden, and availability varies by region.
|
|
311
|
+
message: backend === "vertex"
|
|
312
|
+
? `Model not found on Vertex AI: ${context}. Enable this Claude model for your ` +
|
|
313
|
+
`project in the Vertex AI Model Garden, and confirm it is available in ` +
|
|
314
|
+
`${describeVertexRegion()} — availability varies by region. See ${BACKEND_DOC_URL}.`
|
|
315
|
+
: backend === "bedrock"
|
|
316
|
+
? `Model not found on Bedrock: ${context}. Confirm the model is available in ` +
|
|
317
|
+
`${describeBedrockRegion()} — availability varies by region — and that the ` +
|
|
318
|
+
`resolved Bedrock id is right for your deployment (STIGMER_BEDROCK_MODEL_MAP ` +
|
|
319
|
+
`overrides, ${BEDROCK_INFERENCE_PREFIX_ENV} for inference profiles). See ${BACKEND_DOC_URL}.`
|
|
320
|
+
: backend === "foundry"
|
|
321
|
+
// The most common Foundry setup mistake: Foundry routes by
|
|
322
|
+
// DEPLOYMENT NAME, and deployments are created one by one in
|
|
323
|
+
// the portal — a model with no deployment (or a custom name)
|
|
324
|
+
// 404s even though the model itself exists on Foundry.
|
|
325
|
+
? `Model deployment not found on Microsoft Foundry: ${context}. Foundry routes ` +
|
|
326
|
+
`by deployment name — confirm a deployment for this model exists in ` +
|
|
327
|
+
`${describeFoundryResource()} (default deployment names are the dateless ` +
|
|
328
|
+
`model ids), or map it to your custom deployment name in ` +
|
|
329
|
+
`${FOUNDRY_DEPLOYMENT_MAP_ENV}. See ${BACKEND_DOC_URL}.`
|
|
330
|
+
: `Model not found: ${context}. Verify the model name is correct and available in your account.`,
|
|
205
331
|
};
|
|
206
332
|
case 400:
|
|
207
333
|
return {
|
|
@@ -260,6 +386,98 @@ export function describeExecutionError(
|
|
|
260
386
|
};
|
|
261
387
|
}
|
|
262
388
|
|
|
389
|
+
/**
|
|
390
|
+
* The backend serving this direct-mode Anthropic call — the only condition
|
|
391
|
+
* under which a backend's specific arms may speak. Proxied calls and other
|
|
392
|
+
* providers read as "public" (no backend wording applies). The backend is
|
|
393
|
+
* resolved from env here (deployment-static, like the API keys
|
|
394
|
+
* model-client reads) rather than threaded through every activity's
|
|
395
|
+
* ModelErrorContext; an invalid var value also reads as public, since
|
|
396
|
+
* classification must never throw and invalid values are already fatal at
|
|
397
|
+
* the factories' preflight and at model construction.
|
|
398
|
+
*/
|
|
399
|
+
function resolveDirectBackend(ctx: ModelErrorContext): AnthropicBackend {
|
|
400
|
+
if (ctx.proxyMode || ctx.provider !== "anthropic") return "public";
|
|
401
|
+
const parsed = parseAnthropicBackend();
|
|
402
|
+
return parsed.ok ? parsed.backend : "public";
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Google credential-acquisition prose, matched against the raw message.
|
|
407
|
+
* Narrow by design (mirrors isProviderBillingMessage): pinned to
|
|
408
|
+
* google-auth-library's ADC failure, its project-detection failure, the
|
|
409
|
+
* Vertex SDK's own projectId error, and OAuth's invalid_grant (expired or
|
|
410
|
+
* revoked service-account key). A miss falls through to status
|
|
411
|
+
* classification — never worse than the raw error.
|
|
412
|
+
*/
|
|
413
|
+
function isGoogleCredentialMessage(message: string): boolean {
|
|
414
|
+
const lower = message.toLowerCase();
|
|
415
|
+
return (
|
|
416
|
+
lower.includes("could not load the default credentials")
|
|
417
|
+
|| lower.includes("unable to detect a project id")
|
|
418
|
+
|| lower.includes("no projectid was given")
|
|
419
|
+
|| lower.includes("invalid_grant")
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* AWS credential-acquisition prose, matched against the raw message.
|
|
425
|
+
* Narrow by design (mirrors isGoogleCredentialMessage): pinned to the AWS
|
|
426
|
+
* credential provider chain's terminal failure
|
|
427
|
+
* (@aws-sdk/credential-providers' CredentialsProviderError wordings) and
|
|
428
|
+
* the SigV4 signer's invalid-shape error. A miss falls through to status
|
|
429
|
+
* classification — never worse than the raw error.
|
|
430
|
+
*/
|
|
431
|
+
function isAwsCredentialMessage(message: string): boolean {
|
|
432
|
+
const lower = message.toLowerCase();
|
|
433
|
+
return (
|
|
434
|
+
lower.includes("could not load credentials from any providers")
|
|
435
|
+
|| lower.includes("credential is missing")
|
|
436
|
+
|| lower.includes("resolved credential object is not valid")
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Bedrock's bare-model-id rejection prose (HTTP 400 ValidationException):
|
|
442
|
+
* "Invocation of model ID … with on-demand throughput isn't supported.
|
|
443
|
+
* Retry your request with the ID or ARN of an inference profile …".
|
|
444
|
+
* Matched narrowly on the phrase that only this condition carries.
|
|
445
|
+
*/
|
|
446
|
+
function isBedrockInferenceProfileMessage(message: string): boolean {
|
|
447
|
+
return message.toLowerCase().includes("on-demand throughput isn't supported");
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Entra ID token-acquisition prose, matched against the raw message.
|
|
452
|
+
* Narrow by design (mirrors the Google/AWS matchers), and narrower than it
|
|
453
|
+
* looks: the Foundry SDK wraps EVERY token-provider failure — whatever
|
|
454
|
+
* @azure/identity's credential chain threw — in this one prefix before
|
|
455
|
+
* rethrowing (pinned by foundry-seam.test.ts), so a single phrase covers
|
|
456
|
+
* the whole family. A miss falls through to status classification — never
|
|
457
|
+
* worse than the raw error.
|
|
458
|
+
*/
|
|
459
|
+
function isFoundryCredentialMessage(message: string): boolean {
|
|
460
|
+
return message.toLowerCase().includes("failed to get token from azureadtokenprovider");
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/** "region {value}" when CLOUD_ML_REGION is set, else a pointer to the var. */
|
|
464
|
+
function describeVertexRegion(): string {
|
|
465
|
+
const region = process.env.CLOUD_ML_REGION?.trim();
|
|
466
|
+
return region ? `region "${region}"` : "your CLOUD_ML_REGION";
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/** "region {value}" when AWS_REGION is set, else a pointer to the var. */
|
|
470
|
+
function describeBedrockRegion(): string {
|
|
471
|
+
const region = process.env.AWS_REGION?.trim();
|
|
472
|
+
return region ? `region "${region}"` : "your AWS_REGION";
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/** `resource "{value}"` when the resource var is set, else a generic label. */
|
|
476
|
+
function describeFoundryResource(): string {
|
|
477
|
+
const resource = process.env[FOUNDRY_RESOURCE_ENV]?.trim();
|
|
478
|
+
return resource ? `resource "${resource}"` : "your Foundry resource";
|
|
479
|
+
}
|
|
480
|
+
|
|
263
481
|
/**
|
|
264
482
|
* Provider billing-exhaustion prose, matched against the raw message. Narrow
|
|
265
483
|
* by design: these phrases are pinned to real provider wordings (Anthropic's
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Model registry — provider lookup
|
|
2
|
+
* Model registry — provider lookup, economy-tier model derivation, and
|
|
3
|
+
* model capability resolution.
|
|
3
4
|
*
|
|
4
5
|
* Fetches the model registry from the runner's control plane (see
|
|
5
6
|
* registry-endpoint.ts for endpoint resolution — same endpoint as
|
|
6
7
|
* model-pricing-data.ts) and uses `costTier` + `harness` fields to
|
|
7
|
-
* dynamically resolve economy-tier models for extraction/summarization
|
|
8
|
+
* dynamically resolve economy-tier models for extraction/summarization,
|
|
9
|
+
* plus `capabilities` catalog metadata for per-model capability lookups
|
|
10
|
+
* (getModelVisionCapability).
|
|
8
11
|
*/
|
|
9
12
|
|
|
10
13
|
import { resolveModelRegistryUrl, buildRegistryHeaders } from "./registry-endpoint.js";
|
|
@@ -22,6 +25,14 @@ interface RegistryModel {
|
|
|
22
25
|
costTier: string;
|
|
23
26
|
harness: string;
|
|
24
27
|
featured: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Tri-state vision capability from the registry's `capabilities` block.
|
|
30
|
+
* The registry serializes `capabilities` only for models whose capabilities
|
|
31
|
+
* have actually been assessed, so `undefined` means "never assessed" —
|
|
32
|
+
* deliberately distinct from an explicit `false` ("assessed as blind").
|
|
33
|
+
* Consumers gate only on the explicit `false` (see attachment-vision.ts).
|
|
34
|
+
*/
|
|
35
|
+
visionCapability?: boolean;
|
|
25
36
|
}
|
|
26
37
|
|
|
27
38
|
let cache: { models: readonly RegistryModel[]; expiresAt: number } | null = null;
|
|
@@ -41,9 +52,20 @@ function parseRegistry(json: unknown): RegistryModel[] {
|
|
|
41
52
|
costTier: (m.costTier as string) ?? "standard",
|
|
42
53
|
harness: (m.harness as string) ?? "native",
|
|
43
54
|
featured: !!m.featured,
|
|
55
|
+
visionCapability: parseVisionCapability(m.capabilities),
|
|
44
56
|
}));
|
|
45
57
|
}
|
|
46
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Extract `capabilities.vision` preserving the tri-state: a missing or
|
|
61
|
+
* malformed `capabilities` block stays `undefined` (never coerced to false).
|
|
62
|
+
*/
|
|
63
|
+
function parseVisionCapability(capabilities: unknown): boolean | undefined {
|
|
64
|
+
if (!capabilities || typeof capabilities !== "object") return undefined;
|
|
65
|
+
const vision = (capabilities as Record<string, unknown>).vision;
|
|
66
|
+
return typeof vision === "boolean" ? vision : undefined;
|
|
67
|
+
}
|
|
68
|
+
|
|
47
69
|
async function fetchRegistry(): Promise<readonly RegistryModel[]> {
|
|
48
70
|
const url = resolveModelRegistryUrl();
|
|
49
71
|
const res = await fetch(url, { headers: buildRegistryHeaders() });
|
|
@@ -211,6 +233,32 @@ export async function resolveToApiModelId(registryId: string): Promise<string> {
|
|
|
211
233
|
return entry.apiModelId ?? registryId;
|
|
212
234
|
}
|
|
213
235
|
|
|
236
|
+
/**
|
|
237
|
+
* Look up a model's vision capability from the registry's `capabilities`
|
|
238
|
+
* catalog metadata. Returns the tri-state the vision policy expects
|
|
239
|
+
* (attachment-vision.ts): `false` only when the registry explicitly says the
|
|
240
|
+
* model cannot see images; `undefined` whenever the answer is unknown —
|
|
241
|
+
* capability never assessed, model not in the registry, registry
|
|
242
|
+
* unreachable, or no concrete model name (the Cursor harness's ""/"default"
|
|
243
|
+
* Auto pool). Callers gate on the explicit `false` only, so every unknown
|
|
244
|
+
* degrades to today's behavior instead of blocking images.
|
|
245
|
+
*
|
|
246
|
+
* Matches by registry `id` OR `apiModelId`: getDefaultModel() hands the
|
|
247
|
+
* deep-agent harness the provider API id, while executionConfig.modelName
|
|
248
|
+
* carries the registry id, so both forms arrive here.
|
|
249
|
+
*/
|
|
250
|
+
export async function getModelVisionCapability(
|
|
251
|
+
modelName: string,
|
|
252
|
+
): Promise<boolean | undefined> {
|
|
253
|
+
if (!modelName || modelName === "default") return undefined;
|
|
254
|
+
|
|
255
|
+
const registry = await getRegistry();
|
|
256
|
+
const entry = registry.find(
|
|
257
|
+
(m) => m.id === modelName || m.apiModelId === modelName,
|
|
258
|
+
);
|
|
259
|
+
return entry?.visionCapability;
|
|
260
|
+
}
|
|
261
|
+
|
|
214
262
|
/** Exposed for testing — resets the in-memory cache. */
|
|
215
263
|
export function _resetRegistryCache(): void {
|
|
216
264
|
cache = null;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Plan-mode filesystem permission rules — the enforcement twin of
|
|
3
|
+
* `plan-mode-prompt.ts` (which carries the instruction half of the contract).
|
|
4
|
+
*
|
|
5
|
+
* Plan mode is read-only BY CONSTRUCTION on the native harness: these rules
|
|
6
|
+
* deny every filesystem write operation at the tool level so
|
|
7
|
+
* write_file/edit_file cannot mutate the workspace regardless of what the
|
|
8
|
+
* model was told. Rules are first-match-wins with a permissive default, so a
|
|
9
|
+
* single deny-all-writes rule is sufficient. (The Cursor harness has no
|
|
10
|
+
* tool-level lever and enforces plan mode via its prompt prefix instead.)
|
|
11
|
+
*
|
|
12
|
+
* Applied in execute-deep-agent/setup.ts to the parent graph AND threaded
|
|
13
|
+
* into every compiled sub-agent graph: deepagents' parent-permission
|
|
14
|
+
* inheritance covers only spec-style sub-agents, and ours are pre-built
|
|
15
|
+
* CompiledSubAgents, so each sub-agent graph must carry the rules itself
|
|
16
|
+
* (issue #255). Kept as its own side-effect-free module so tests can pin the
|
|
17
|
+
* production rules without dragging in setup.ts's import graph.
|
|
18
|
+
*
|
|
19
|
+
* Invariant: never combine these rules with a shell-capable (sandbox)
|
|
20
|
+
* backend — deepagents rejects that pairing at graph construction (see the
|
|
21
|
+
* cas-capture-backend.ts header). Plan mode guarantees it by construction:
|
|
22
|
+
* it is the mode that clears `shellEnv`, and `shellEnv` is the single switch
|
|
23
|
+
* for shell capability on both the parent and sub-agent backends.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import type { FilesystemPermission } from "deepagents";
|
|
27
|
+
|
|
28
|
+
export const PLAN_MODE_PERMISSIONS: FilesystemPermission[] = [
|
|
29
|
+
{ operations: ["write"], paths: ["/**"], mode: "deny" },
|
|
30
|
+
];
|
package/src/worker.ts
CHANGED
|
@@ -27,11 +27,12 @@ export interface WorkerActivities {
|
|
|
27
27
|
export interface StartWorkerOptions {
|
|
28
28
|
config: Config;
|
|
29
29
|
activities: WorkerActivities;
|
|
30
|
-
|
|
30
|
+
/** Ordered codec chain from createPayloadCodecs (order is load-bearing). */
|
|
31
|
+
payloadCodecs?: PayloadCodec[];
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
export async function startWorker(opts: StartWorkerOptions): Promise<Worker> {
|
|
34
|
-
const { config, activities,
|
|
35
|
+
const { config, activities, payloadCodecs } = opts;
|
|
35
36
|
|
|
36
37
|
const connection = await NativeConnection.connect({
|
|
37
38
|
address: config.temporalAddress,
|
|
@@ -89,9 +90,7 @@ export async function startWorker(opts: StartWorkerOptions): Promise<Worker> {
|
|
|
89
90
|
? { workflowBundle: { codePath: workflowSource.codePath } }
|
|
90
91
|
: { workflowsPath: workflowSource.workflowsPath }),
|
|
91
92
|
maxConcurrentActivityTaskExecutions: config.maxConcurrentActivities,
|
|
92
|
-
dataConverter:
|
|
93
|
-
? { payloadCodecs: [payloadCodec] }
|
|
94
|
-
: undefined,
|
|
93
|
+
dataConverter: payloadCodecs?.length ? { payloadCodecs } : undefined,
|
|
95
94
|
sinks,
|
|
96
95
|
interceptors: {
|
|
97
96
|
...(activityInterceptors.length > 0 ? { activity: activityInterceptors } : {}),
|
|
@@ -800,6 +800,24 @@ export type WorkflowEventDescriptor =
|
|
|
800
800
|
interface EventBase {
|
|
801
801
|
readonly taskName?: string;
|
|
802
802
|
readonly occurredAt: string;
|
|
803
|
+
/**
|
|
804
|
+
* Workflow-assigned event sequence number, stamped at the emit funnels
|
|
805
|
+
* (engine-core's emitEvents and the agent-call orchestrator's
|
|
806
|
+
* emitProgress) from the workflow-owned monotonic counter. Assigning
|
|
807
|
+
* inside the deterministic sandbox makes the number stable across
|
|
808
|
+
* activity retries and worker restarts, so persistence is idempotent
|
|
809
|
+
* (the store skips already-persisted sequences).
|
|
810
|
+
*
|
|
811
|
+
* A plain `number`, not `bigint`: descriptors cross the
|
|
812
|
+
* workflow→activity boundary through Temporal's JSON payload converter,
|
|
813
|
+
* which cannot serialize BigInt. The emit activity converts to the
|
|
814
|
+
* proto's uint64.
|
|
815
|
+
*
|
|
816
|
+
* Absent only when replaying histories recorded before the
|
|
817
|
+
* "workflow-assigned-event-sequences" patch — the emit activity then
|
|
818
|
+
* falls back to its legacy process-global counter.
|
|
819
|
+
*/
|
|
820
|
+
readonly sequenceNumber?: number;
|
|
803
821
|
}
|
|
804
822
|
|
|
805
823
|
export interface ExecutionStartedEvent extends EventBase {
|
|
@@ -4,6 +4,8 @@ import type { WorkflowModel } from "../../workflow-engine/types.js";
|
|
|
4
4
|
import type { ExecuteServerlessWorkflowInput } from "../execute-serverless-workflow.js";
|
|
5
5
|
|
|
6
6
|
const mockEvaluateExpressions = vi.fn();
|
|
7
|
+
const mockResetEventSequence = vi.fn();
|
|
8
|
+
const mockEmitWorkflowEvents = vi.fn();
|
|
7
9
|
|
|
8
10
|
vi.mock("@temporalio/workflow", () => ({
|
|
9
11
|
proxyLocalActivities: vi.fn(() => ({
|
|
@@ -12,8 +14,9 @@ vi.mock("@temporalio/workflow", () => ({
|
|
|
12
14
|
input: unknown,
|
|
13
15
|
stateVars: Record<string, unknown>,
|
|
14
16
|
) => mockEvaluateExpressions(exprs, input, stateVars),
|
|
15
|
-
ResetEventSequence:
|
|
16
|
-
EmitWorkflowEvents:
|
|
17
|
+
ResetEventSequence: (executionId: string) => mockResetEventSequence(executionId),
|
|
18
|
+
EmitWorkflowEvents: (executionId: string, events: unknown[], taskStatuses: unknown[]) =>
|
|
19
|
+
mockEmitWorkflowEvents(executionId, events, taskStatuses),
|
|
17
20
|
})),
|
|
18
21
|
proxyActivities: vi.fn(() => ({
|
|
19
22
|
CallHttp: vi.fn(),
|
|
@@ -246,6 +249,69 @@ describe("executeServerlessWorkflow", () => {
|
|
|
246
249
|
});
|
|
247
250
|
});
|
|
248
251
|
|
|
252
|
+
describe("workflow-assigned event sequences", () => {
|
|
253
|
+
interface StampedEvent {
|
|
254
|
+
type: string;
|
|
255
|
+
sequenceNumber?: number;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function emittedEvents(): StampedEvent[] {
|
|
259
|
+
return mockEmitWorkflowEvents.mock.calls.flatMap(call => call[1] as StampedEvent[]);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const model: WorkflowModel = {
|
|
263
|
+
document: { dsl: "1.0.0", name: "test-sequences" },
|
|
264
|
+
do: [
|
|
265
|
+
{ key: "step1", task: { kind: "set", set: { a: 1 } } },
|
|
266
|
+
{ key: "step2", task: { kind: "set", set: { b: 2 } } },
|
|
267
|
+
],
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
it("stamps a strictly monotonic sequence continuing from the high-water mark", async () => {
|
|
271
|
+
mockResetEventSequence.mockResolvedValue(10);
|
|
272
|
+
|
|
273
|
+
await runWorkflow({
|
|
274
|
+
model,
|
|
275
|
+
workflow_input: null,
|
|
276
|
+
env: {},
|
|
277
|
+
metadata: { execution_id: "wfx-seq-1" },
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
const events = emittedEvents();
|
|
281
|
+
expect(events.length).toBeGreaterThan(0);
|
|
282
|
+
// execution_started, task events, execution_completed — all from one
|
|
283
|
+
// counter, gapless, continuing after the persisted high-water mark.
|
|
284
|
+
const sequences = events.map(e => e.sequenceNumber);
|
|
285
|
+
const expected = Array.from({ length: events.length }, (_, i) => 11 + i);
|
|
286
|
+
expect(sequences).toEqual(expected);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it("starts at 1 for a fresh execution (high-water mark 0)", async () => {
|
|
290
|
+
mockResetEventSequence.mockResolvedValue(0);
|
|
291
|
+
|
|
292
|
+
await runWorkflow({
|
|
293
|
+
model,
|
|
294
|
+
workflow_input: null,
|
|
295
|
+
env: {},
|
|
296
|
+
metadata: { execution_id: "wfx-seq-2" },
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
expect(emittedEvents()[0]?.sequenceNumber).toBe(1);
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it("emits no events without an execution_id (direct invocation, child workflows)", async () => {
|
|
303
|
+
mockResetEventSequence.mockResolvedValue(0);
|
|
304
|
+
|
|
305
|
+
await runWorkflow({
|
|
306
|
+
model,
|
|
307
|
+
workflow_input: null,
|
|
308
|
+
env: {},
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
expect(mockEmitWorkflowEvents).not.toHaveBeenCalled();
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
|
|
249
315
|
describe("switch with flow directives", () => {
|
|
250
316
|
it("handles switch-driven branching through the workflow", async () => {
|
|
251
317
|
const model: WorkflowModel = {
|
|
@@ -94,6 +94,15 @@ export interface AgentCallOrchestrationInput {
|
|
|
94
94
|
parentWorkflowId: string;
|
|
95
95
|
taskName: string;
|
|
96
96
|
workflowExecutionId: string;
|
|
97
|
+
/**
|
|
98
|
+
* Allocator for the workflow-owned event sequence counter, shared with
|
|
99
|
+
* engine-core's emit funnel so progress events and task events draw
|
|
100
|
+
* from one monotonic series. Safe to pass as a closure — the
|
|
101
|
+
* orchestrator is a plain function call inside the same workflow run,
|
|
102
|
+
* not a child workflow. Undefined when replaying pre-patch histories
|
|
103
|
+
* (the emit activity then assigns from its legacy counter).
|
|
104
|
+
*/
|
|
105
|
+
nextEventSequence?: () => number;
|
|
97
106
|
}
|
|
98
107
|
|
|
99
108
|
/**
|
|
@@ -318,6 +327,7 @@ async function emitProgress(
|
|
|
318
327
|
type: "agent_call_progress",
|
|
319
328
|
taskName: input.taskName,
|
|
320
329
|
occurredAt: new Date().toISOString(),
|
|
330
|
+
sequenceNumber: input.nextEventSequence?.(),
|
|
321
331
|
childExecutionId: childExecId,
|
|
322
332
|
agentSlug: input.config.agent ?? "",
|
|
323
333
|
agentPhase: progress?.agentPhase ?? 0,
|
|
@@ -41,6 +41,13 @@ import type {
|
|
|
41
41
|
type DiscoverActivities = ReturnType<typeof createDiscoverMcpServerActivities>;
|
|
42
42
|
type ClassifyActivities = ReturnType<typeof createClassifyToolApprovalsActivities>;
|
|
43
43
|
|
|
44
|
+
// Discovery's bounds ladder (issue #239): the activity heartbeats every 15s,
|
|
45
|
+
// so heartbeatTimeout is pure LIVENESS (dead worker/pod detection) — it no
|
|
46
|
+
// longer kills slow-but-alive discoveries. The activity bounds its own WORK
|
|
47
|
+
// with a transport-aware init timeout (30s HTTP / 270s stdio) that fails with
|
|
48
|
+
// an actionable, endpoint-naming error; startToCloseTimeout is the hard cap
|
|
49
|
+
// above both. Keep the ordering: work bound < hard cap, heartbeat interval
|
|
50
|
+
// (15s) < heartbeatTimeout.
|
|
44
51
|
const discover = proxyActivities<DiscoverActivities>({
|
|
45
52
|
startToCloseTimeout: "600s",
|
|
46
53
|
heartbeatTimeout: "60s",
|
|
@@ -145,7 +145,21 @@ export async function runWorkflowEngine(
|
|
|
145
145
|
|
|
146
146
|
recordExecutionStartMetric(model.document.name);
|
|
147
147
|
|
|
148
|
-
|
|
148
|
+
// Event sequence numbers are workflow state: assigned here in the
|
|
149
|
+
// deterministic sandbox (seeded from the persisted high-water mark) so
|
|
150
|
+
// they are stable across activity retries, worker restarts, and
|
|
151
|
+
// concurrent executions — the store can then treat re-sent sequences as
|
|
152
|
+
// idempotent duplicates. Pre-patch histories recorded the activity
|
|
153
|
+
// result as void and assigned sequences inside the emit activity from a
|
|
154
|
+
// process-global counter; they must keep doing so on replay, hence the
|
|
155
|
+
// gate. Remove the gate (and the activity's legacy counter) once
|
|
156
|
+
// pre-patch executions have drained.
|
|
157
|
+
const workflowAssignedSequences = patched("workflow-assigned-event-sequences");
|
|
158
|
+
const eventLogHighWaterMark = await eventProxy.ResetEventSequence(executionId);
|
|
159
|
+
let eventSequence = workflowAssignedSequences ? Number(eventLogHighWaterMark ?? 0) : 0;
|
|
160
|
+
const nextEventSequence = workflowAssignedSequences
|
|
161
|
+
? () => ++eventSequence
|
|
162
|
+
: undefined;
|
|
149
163
|
|
|
150
164
|
let recoveryContext: RecoveryContext | undefined;
|
|
151
165
|
if (options?.recoveryMode && executionId) {
|
|
@@ -170,9 +184,15 @@ export async function runWorkflowEngine(
|
|
|
170
184
|
|
|
171
185
|
const emitEvents = async (events: WorkflowEventDescriptor[]): Promise<void> => {
|
|
172
186
|
if (!executionId || events.length === 0) return;
|
|
187
|
+
const stamped = nextEventSequence
|
|
188
|
+
? events.map(e => ({ ...e, sequenceNumber: nextEventSequence() }))
|
|
189
|
+
: events;
|
|
173
190
|
try {
|
|
174
|
-
await eventProxy.EmitWorkflowEvents(executionId,
|
|
191
|
+
await eventProxy.EmitWorkflowEvents(executionId, stamped, taskStatusAccumulator.toArray());
|
|
175
192
|
} catch (err) {
|
|
193
|
+
// Final guard after the activity's retries are exhausted: a run must
|
|
194
|
+
// not die because its timeline write failed. With workflow-assigned
|
|
195
|
+
// sequences the result is a gap in the log, never a poisoned log.
|
|
176
196
|
log.warn("Failed to emit workflow events (non-fatal)", {
|
|
177
197
|
executionId,
|
|
178
198
|
eventCount: events.length,
|
|
@@ -246,6 +266,7 @@ export async function runWorkflowEngine(
|
|
|
246
266
|
parentWorkflowId: agentMeta.parentWorkflowId || workflowInfo().workflowId,
|
|
247
267
|
taskName: agentMeta.taskName,
|
|
248
268
|
workflowExecutionId: agentMeta.workflowExecutionId || executionId,
|
|
269
|
+
nextEventSequence,
|
|
249
270
|
}),
|
|
250
271
|
promoteTaskOutput: (taskOutput: unknown, wexId: string, taskName: string, displayName?: string) =>
|
|
251
272
|
promoteProxy.PromoteTaskOutput(taskOutput, wexId || executionId, taskName, displayName),
|
|
@@ -63,7 +63,7 @@ export interface ExecuteFromExecutionInput {
|
|
|
63
63
|
|
|
64
64
|
export async function executeFromExecution(
|
|
65
65
|
input: ExecuteFromExecutionInput,
|
|
66
|
-
): Promise<
|
|
66
|
+
): Promise<void> {
|
|
67
67
|
const { checkPause } = setupPauseResumeHandlers();
|
|
68
68
|
|
|
69
69
|
log.info("Hydrating workflow execution from slim IDs", {
|
|
@@ -86,7 +86,17 @@ export async function executeFromExecution(
|
|
|
86
86
|
});
|
|
87
87
|
|
|
88
88
|
try {
|
|
89
|
-
|
|
89
|
+
// The engine's output is deliberately NOT returned. Both orchestrator
|
|
90
|
+
// parents discard this workflow's result (Java awaits it as Void, Go
|
|
91
|
+
// passes nil to Get), and the output already reaches users through
|
|
92
|
+
// PromoteTaskOutput and the event log. Returning it would (a) persist
|
|
93
|
+
// the full workflow output — which may embed secrets — as a plaintext
|
|
94
|
+
// payload in the cross-language parent's history, and (b) hand the Java
|
|
95
|
+
// parent a payload it must decode: once the payload-encryption codec is
|
|
96
|
+
// active, an encrypted result would fail Java's converter lookup
|
|
97
|
+
// (fromPayloads has no Void special-case). A void return produces a
|
|
98
|
+
// data-less binary/null payload that every SDK handles natively.
|
|
99
|
+
await runWorkflowEngine(materialized, {
|
|
90
100
|
checkPause,
|
|
91
101
|
recoveryMode: input.recovery_mode ?? false,
|
|
92
102
|
});
|