@stigmer/runner 3.12.2 → 3.12.4
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/dist/.build-fingerprint +1 -1
- package/dist/activities/call-http.js +12 -0
- package/dist/activities/call-http.js.map +1 -1
- package/dist/activities/call-llm.d.ts +18 -0
- package/dist/activities/call-llm.js +56 -2
- package/dist/activities/call-llm.js.map +1 -1
- package/dist/activities/execute-cursor/agent-session-cache.d.ts +72 -0
- package/dist/activities/execute-cursor/agent-session-cache.js +186 -0
- package/dist/activities/execute-cursor/agent-session-cache.js.map +1 -0
- package/dist/activities/execute-cursor/index.js +61 -28
- package/dist/activities/execute-cursor/index.js.map +1 -1
- package/dist/activities/execute-cursor/service-tier.d.ts +5 -15
- package/dist/activities/execute-cursor/service-tier.js +5 -21
- package/dist/activities/execute-cursor/service-tier.js.map +1 -1
- package/dist/activities/execute-cursor/skill-resolver.d.ts +15 -0
- package/dist/activities/execute-cursor/skill-resolver.js +45 -7
- package/dist/activities/execute-cursor/skill-resolver.js.map +1 -1
- package/dist/activities/execute-deep-agent/__test-utils__/scripted-model.d.ts +9 -1
- package/dist/activities/execute-deep-agent/__test-utils__/scripted-model.js +13 -2
- package/dist/activities/execute-deep-agent/__test-utils__/scripted-model.js.map +1 -1
- package/dist/activities/execute-deep-agent/setup.js +14 -0
- package/dist/activities/execute-deep-agent/setup.js.map +1 -1
- package/dist/activities/execute-deep-agent/shell-env.d.ts +8 -5
- package/dist/activities/execute-deep-agent/shell-env.js +10 -7
- package/dist/activities/execute-deep-agent/shell-env.js.map +1 -1
- package/dist/activities/execute-deep-agent/subagent-wiring.d.ts +5 -1
- package/dist/activities/execute-deep-agent/subagent-wiring.js +9 -1
- package/dist/activities/execute-deep-agent/subagent-wiring.js.map +1 -1
- package/dist/client/stigmer-client.d.ts +9 -1
- package/dist/client/stigmer-client.js +10 -0
- package/dist/client/stigmer-client.js.map +1 -1
- package/dist/config.js +10 -5
- package/dist/config.js.map +1 -1
- package/dist/encryption/config.js +7 -2
- package/dist/encryption/config.js.map +1 -1
- package/dist/main.js +12 -6
- package/dist/main.js.map +1 -1
- package/dist/middleware/index.d.ts +6 -5
- package/dist/middleware/index.js +8 -5
- package/dist/middleware/index.js.map +1 -1
- package/dist/middleware/tool-intent.d.ts +57 -0
- package/dist/middleware/tool-intent.js +152 -0
- package/dist/middleware/tool-intent.js.map +1 -0
- package/dist/payload-codecs.js +2 -1
- package/dist/payload-codecs.js.map +1 -1
- package/dist/runner-manager.js +20 -7
- package/dist/runner-manager.js.map +1 -1
- package/dist/runner.js +19 -6
- package/dist/runner.js.map +1 -1
- package/dist/shared/fingerprint-secret.d.ts +3 -2
- package/dist/shared/fingerprint-secret.js +5 -3
- package/dist/shared/fingerprint-secret.js.map +1 -1
- package/dist/shared/llm-backend.js +8 -1
- package/dist/shared/llm-backend.js.map +1 -1
- package/dist/shared/model-client.d.ts +15 -0
- package/dist/shared/model-client.js +57 -13
- package/dist/shared/model-client.js.map +1 -1
- package/dist/shared/registry-endpoint.d.ts +5 -0
- package/dist/shared/registry-endpoint.js +7 -1
- package/dist/shared/registry-endpoint.js.map +1 -1
- package/dist/shared/runner-credential-keys.d.ts +26 -1
- package/dist/shared/runner-credential-keys.js +34 -1
- package/dist/shared/runner-credential-keys.js.map +1 -1
- package/dist/shared/runner-credential-store.d.ts +77 -0
- package/dist/shared/runner-credential-store.js +111 -0
- package/dist/shared/runner-credential-store.js.map +1 -0
- package/dist/shared/service-tier.d.ts +55 -0
- package/dist/shared/service-tier.js +67 -0
- package/dist/shared/service-tier.js.map +1 -0
- package/dist/shared/skill-writer.js +2 -2
- package/dist/shared/skill-writer.js.map +1 -1
- package/dist/shared/zip-extract.d.ts +10 -3
- package/dist/shared/zip-extract.js +10 -3
- package/dist/shared/zip-extract.js.map +1 -1
- package/dist/workflow-engine/tasks/call-function.d.ts +14 -0
- package/dist/workflow-engine/tasks/call-function.js +49 -5
- package/dist/workflow-engine/tasks/call-function.js.map +1 -1
- package/dist/workflow-engine/types.d.ts +6 -0
- package/dist/workflow-engine/types.js.map +1 -1
- package/dist/workflows/engine-core.js +36 -8
- package/dist/workflows/engine-core.js.map +1 -1
- package/package.json +2 -2
- package/src/activities/__tests__/call-http.test.ts +36 -0
- package/src/activities/__tests__/call-llm.test.ts +77 -0
- package/src/activities/call-http.ts +17 -0
- package/src/activities/call-llm.ts +78 -2
- package/src/activities/execute-cursor/__tests__/agent-session-cache.test.ts +220 -0
- package/src/activities/execute-cursor/__tests__/service-tier.test.ts +1 -1
- package/src/activities/execute-cursor/__tests__/skill-resolver.test.ts +104 -1
- package/src/activities/execute-cursor/agent-session-cache.ts +229 -0
- package/src/activities/execute-cursor/index.ts +66 -20
- package/src/activities/execute-cursor/service-tier.ts +5 -29
- package/src/activities/execute-cursor/skill-resolver.ts +52 -8
- package/src/activities/execute-deep-agent/__test-utils__/scripted-model.ts +13 -2
- package/src/activities/execute-deep-agent/__tests__/subagent-wiring.test.ts +21 -20
- package/src/activities/execute-deep-agent/setup.ts +15 -0
- package/src/activities/execute-deep-agent/shell-env.ts +10 -7
- package/src/activities/execute-deep-agent/subagent-wiring.ts +10 -1
- package/src/client/stigmer-client.ts +12 -1
- package/src/config.ts +10 -5
- package/src/encryption/config.ts +8 -2
- package/src/main.ts +16 -6
- package/src/middleware/__tests__/tool-intent.test.ts +266 -0
- package/src/middleware/index.ts +9 -5
- package/src/middleware/tool-intent.ts +174 -0
- package/src/payload-codecs.ts +2 -1
- package/src/runner-manager.ts +29 -6
- package/src/runner.ts +25 -6
- package/src/shared/__tests__/model-client.test.ts +99 -0
- package/src/shared/__tests__/runner-credential-store.test.ts +155 -0
- package/src/shared/__tests__/zip-extract.test.ts +46 -11
- package/src/shared/fingerprint-secret.ts +5 -3
- package/src/shared/llm-backend.ts +7 -1
- package/src/shared/model-client.ts +76 -13
- package/src/shared/registry-endpoint.ts +9 -1
- package/src/shared/runner-credential-keys.ts +36 -1
- package/src/shared/runner-credential-store.ts +115 -0
- package/src/shared/service-tier.ts +78 -0
- package/src/shared/skill-writer.ts +2 -2
- package/src/shared/zip-extract.ts +14 -7
- package/src/workflow-engine/__tests__/golden-execution.test.ts +20 -1
- package/src/workflow-engine/__tests__/tasks/call-function.test.ts +105 -0
- package/src/workflow-engine/tasks/call-function.ts +74 -13
- package/src/workflow-engine/types.ts +6 -0
- package/src/workflows/engine-core.ts +39 -8
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Skill-artifact ZIP extraction:
|
|
2
|
+
* Skill-artifact ZIP extraction: byte-preserving entries with non-fatal
|
|
3
3
|
* structural failure.
|
|
4
4
|
*
|
|
5
|
+
* Entries are returned as raw bytes, never decoded: skill artifacts carry
|
|
6
|
+
* binary assets (images, fonts, archives) alongside text, and a UTF-8
|
|
7
|
+
* decode/encode round-trip silently corrupts anything that isn't valid
|
|
8
|
+
* UTF-8 (issue #683). No consumer parses entry content as text — SKILL.md
|
|
9
|
+
* is excluded from extraction on every path and written from the Skill
|
|
10
|
+
* spec instead — so there is deliberately no text accessor to misuse.
|
|
11
|
+
*
|
|
5
12
|
* Structural parsing lives in zip-structure.ts (central-directory-based —
|
|
6
13
|
* see that module's doc for why local-header walks are never acceptable,
|
|
7
14
|
* issue #450). This module owns the skill-artifact *policy* on top of it:
|
|
@@ -33,8 +40,8 @@ import { EOCD_MIN_SIZE, parseZipStructure, type ZipStructuralEntry } from "./zip
|
|
|
33
40
|
export interface ZipFileEntry {
|
|
34
41
|
/** Relative path within the archive (forward-slash separated). */
|
|
35
42
|
readonly path: string;
|
|
36
|
-
/**
|
|
37
|
-
readonly content:
|
|
43
|
+
/** Raw file bytes, exactly as stored in the archive. */
|
|
44
|
+
readonly content: Uint8Array;
|
|
38
45
|
}
|
|
39
46
|
|
|
40
47
|
/**
|
|
@@ -91,17 +98,17 @@ function isExcluded(name: string, excludeSet: ReadonlySet<string>): boolean {
|
|
|
91
98
|
|
|
92
99
|
// ─── Decompression ───────────────────────────────────────────────────────
|
|
93
100
|
|
|
94
|
-
async function decompressEntry(entry: ZipStructuralEntry): Promise<
|
|
101
|
+
async function decompressEntry(entry: ZipStructuralEntry): Promise<Uint8Array> {
|
|
95
102
|
if (entry.compressionMethod === 0) {
|
|
96
|
-
return
|
|
103
|
+
return entry.compressedData;
|
|
97
104
|
}
|
|
98
105
|
|
|
99
106
|
if (entry.compressionMethod === 8) {
|
|
100
|
-
return new Promise<
|
|
107
|
+
return new Promise<Uint8Array>((resolve, reject) => {
|
|
101
108
|
const inflate = createInflateRaw();
|
|
102
109
|
const chunks: Buffer[] = [];
|
|
103
110
|
inflate.on("data", (chunk: Buffer) => chunks.push(chunk));
|
|
104
|
-
inflate.on("end", () => resolve(Buffer.concat(chunks)
|
|
111
|
+
inflate.on("end", () => resolve(Buffer.concat(chunks)));
|
|
105
112
|
inflate.on("error", reject);
|
|
106
113
|
inflate.end(Buffer.from(entry.compressedData));
|
|
107
114
|
});
|
|
@@ -523,8 +523,27 @@ describe("Golden Execution — Tier 1d: Advanced Tasks", () => {
|
|
|
523
523
|
|
|
524
524
|
await executeDoTasks(model.do, null, state, model, evaluateExpressionBatch, ctx);
|
|
525
525
|
|
|
526
|
-
expect(mockCallFunction).toHaveBeenCalledTimes(
|
|
526
|
+
expect(mockCallFunction).toHaveBeenCalledTimes(3);
|
|
527
527
|
expect(state.data.events_emitted).toBe(true);
|
|
528
|
+
|
|
529
|
+
// Delivery targets authored in the DSL (oss#530) must reach the
|
|
530
|
+
// function-call boundary verbatim: the runner's emit activity
|
|
531
|
+
// discriminates each entry structurally on its webhook/signal key.
|
|
532
|
+
const deliveryConfig = mockCallFunction.mock.calls[2][1] as Record<string, unknown>;
|
|
533
|
+
expect(deliveryConfig.delivery).toEqual([
|
|
534
|
+
{
|
|
535
|
+
webhook: {
|
|
536
|
+
url: "https://hooks.acme.com/orders",
|
|
537
|
+
headers: { "X-Source": "stigmer" },
|
|
538
|
+
},
|
|
539
|
+
},
|
|
540
|
+
{
|
|
541
|
+
signal: {
|
|
542
|
+
execution_id: "wfx_shipping_001",
|
|
543
|
+
signal_name: "order-fulfilled",
|
|
544
|
+
},
|
|
545
|
+
},
|
|
546
|
+
]);
|
|
528
547
|
});
|
|
529
548
|
|
|
530
549
|
it("#20 human-input — HITL approval gates with timeout policies", async () => {
|
|
@@ -208,6 +208,111 @@ describe("CallFunctionTaskBuilder", () => {
|
|
|
208
208
|
// The config resolver must never pre-evaluate them — the pre-fix
|
|
209
209
|
// behavior substituted the evaluated boolean back into the config and
|
|
210
210
|
// crashed the validate activity with `expr.includes is not a function`.
|
|
211
|
+
describe("llm on_invalid policy orchestration (#686)", () => {
|
|
212
|
+
const schemaTaskDef = (extra: Record<string, unknown>): CallFunctionTaskDef => ({
|
|
213
|
+
kind: "call:function",
|
|
214
|
+
call: "llm",
|
|
215
|
+
with: {
|
|
216
|
+
model: "gpt-4o-mini",
|
|
217
|
+
prompt: "Classify",
|
|
218
|
+
response_schema: { type: "object", properties: { answer: { type: "number" } } },
|
|
219
|
+
...extra,
|
|
220
|
+
},
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
const invalidResult = {
|
|
224
|
+
result: undefined, model: "gpt-4o-mini", provider: "openai",
|
|
225
|
+
input_tokens: 0, output_tokens: 0, parse_error: "answer: Required",
|
|
226
|
+
};
|
|
227
|
+
const validResult = {
|
|
228
|
+
result: { answer: 42 }, model: "gpt-4o-mini", provider: "openai",
|
|
229
|
+
input_tokens: 10, output_tokens: 5,
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
it("ON_INVALID_RETRY re-prompts with the validation errors and succeeds", async () => {
|
|
233
|
+
mockCallFunction
|
|
234
|
+
.mockResolvedValueOnce(invalidResult)
|
|
235
|
+
.mockResolvedValueOnce(validResult);
|
|
236
|
+
|
|
237
|
+
const builder = new CallFunctionTaskBuilder(
|
|
238
|
+
"classify", schemaTaskDef({ on_invalid: "ON_INVALID_RETRY", max_retries: 2 }),
|
|
239
|
+
);
|
|
240
|
+
const result = await builder.build()(null, createState(), makeCtx());
|
|
241
|
+
|
|
242
|
+
expect(result).toMatchObject({ structured: { answer: 42 } });
|
|
243
|
+
expect(mockCallFunction).toHaveBeenCalledTimes(2);
|
|
244
|
+
const retryConfig = mockCallFunction.mock.calls[1][1] as Record<string, unknown>;
|
|
245
|
+
expect(retryConfig.prompt).toContain("Classify");
|
|
246
|
+
expect(retryConfig.prompt).toContain("answer: Required");
|
|
247
|
+
expect(retryConfig.prompt).toContain("RETRY");
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it("ON_INVALID_RETRY exhausts max_retries then fails without a fallback_task", async () => {
|
|
251
|
+
mockCallFunction.mockResolvedValue(invalidResult);
|
|
252
|
+
|
|
253
|
+
const builder = new CallFunctionTaskBuilder(
|
|
254
|
+
"classify", schemaTaskDef({ on_invalid: "ON_INVALID_RETRY", max_retries: 2 }),
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
await expect(builder.build()(null, createState(), makeCtx()))
|
|
258
|
+
.rejects.toThrow(/validation failed after 3 attempt\(s\).*answer: Required/);
|
|
259
|
+
// first attempt + max_retries retries
|
|
260
|
+
expect(mockCallFunction).toHaveBeenCalledTimes(3);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it("ON_INVALID_RETRY defaults max_retries to 1 (proto contract)", async () => {
|
|
264
|
+
mockCallFunction.mockResolvedValue(invalidResult);
|
|
265
|
+
|
|
266
|
+
const builder = new CallFunctionTaskBuilder(
|
|
267
|
+
"classify", schemaTaskDef({ on_invalid: "ON_INVALID_RETRY" }),
|
|
268
|
+
);
|
|
269
|
+
|
|
270
|
+
await expect(builder.build()(null, createState(), makeCtx())).rejects.toThrow();
|
|
271
|
+
expect(mockCallFunction).toHaveBeenCalledTimes(2);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it("exhausted retries branch to fallback_task when set", async () => {
|
|
275
|
+
mockCallFunction.mockResolvedValue(invalidResult);
|
|
276
|
+
|
|
277
|
+
const builder = new CallFunctionTaskBuilder(
|
|
278
|
+
"classify",
|
|
279
|
+
schemaTaskDef({ on_invalid: "ON_INVALID_RETRY", max_retries: 1, fallback_task: "human_review" }),
|
|
280
|
+
);
|
|
281
|
+
const result = await builder.build()(null, createState(), makeCtx());
|
|
282
|
+
|
|
283
|
+
expect(result).toEqual({
|
|
284
|
+
__flow_directive__: "human_review",
|
|
285
|
+
validation_errors: ["answer: Required"],
|
|
286
|
+
});
|
|
287
|
+
expect(mockCallFunction).toHaveBeenCalledTimes(2);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it("ON_INVALID_FALLBACK branches immediately without retrying", async () => {
|
|
291
|
+
mockCallFunction.mockResolvedValue(invalidResult);
|
|
292
|
+
|
|
293
|
+
const builder = new CallFunctionTaskBuilder(
|
|
294
|
+
"classify",
|
|
295
|
+
schemaTaskDef({ on_invalid: "ON_INVALID_FALLBACK", fallback_task: "human_review" }),
|
|
296
|
+
);
|
|
297
|
+
const result = await builder.build()(null, createState(), makeCtx());
|
|
298
|
+
|
|
299
|
+
expect(result).toMatchObject({ __flow_directive__: "human_review" });
|
|
300
|
+
expect(mockCallFunction).toHaveBeenCalledTimes(1);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
it("a parse_error without a soft policy passes through as normal output (activity owns the failure)", async () => {
|
|
304
|
+
// Default policy: the activity throws LLM_SCHEMA_VALIDATION itself and
|
|
305
|
+
// never returns parse_error — the engine must not add a second layer.
|
|
306
|
+
mockCallFunction.mockResolvedValue(validResult);
|
|
307
|
+
|
|
308
|
+
const builder = new CallFunctionTaskBuilder("classify", schemaTaskDef({}));
|
|
309
|
+
const result = await builder.build()(null, createState(), makeCtx());
|
|
310
|
+
|
|
311
|
+
expect(result).toMatchObject({ structured: { answer: 42 } });
|
|
312
|
+
expect(mockCallFunction).toHaveBeenCalledTimes(1);
|
|
313
|
+
});
|
|
314
|
+
});
|
|
315
|
+
|
|
211
316
|
describe("deferred expression fields", () => {
|
|
212
317
|
it("passes validate rules[].expression through unresolved while input and message interpolate", async () => {
|
|
213
318
|
mockCallFunction.mockResolvedValue({ valid: true, errors: [] });
|
|
@@ -162,26 +162,87 @@ export class CallFunctionTaskBuilder implements TaskBuilder {
|
|
|
162
162
|
const executionId =
|
|
163
163
|
(state.env.__stigmer_execution_id as string | undefined) || undefined;
|
|
164
164
|
|
|
165
|
-
const
|
|
166
|
-
callType,
|
|
167
|
-
resolved,
|
|
168
|
-
state.env,
|
|
169
|
-
{
|
|
170
|
-
workflowExecutionId: executionId,
|
|
171
|
-
},
|
|
172
|
-
);
|
|
165
|
+
const fnMeta = { workflowExecutionId: executionId };
|
|
173
166
|
|
|
174
167
|
if (callType === "llm") {
|
|
175
|
-
return
|
|
176
|
-
result as LlmActivityResult,
|
|
177
|
-
!!resolved.response_schema,
|
|
178
|
-
);
|
|
168
|
+
return this.executeLlmCall(resolved, state.env, fnMeta, ctx);
|
|
179
169
|
}
|
|
180
170
|
|
|
181
|
-
return
|
|
171
|
+
return ctx.callFunction(callType, resolved, state.env, fnMeta);
|
|
182
172
|
};
|
|
183
173
|
}
|
|
184
174
|
|
|
175
|
+
/**
|
|
176
|
+
* Executes a `call: llm` with schema-validation policy orchestration —
|
|
177
|
+
* the llm twin of call-agent's output-contract loop (#686).
|
|
178
|
+
*
|
|
179
|
+
* With ON_INVALID_RETRY / ON_INVALID_FALLBACK the activity reports a
|
|
180
|
+
* validation miss as a `parse_error` result instead of throwing; each
|
|
181
|
+
* retry is its own activity invocation (visible in Temporal history)
|
|
182
|
+
* re-prompting with the validation errors so the model can self-correct.
|
|
183
|
+
* Exhausted retries (or immediate FALLBACK) branch to `fallback_task`
|
|
184
|
+
* via the engine's flow directive; without one, the task fails — the
|
|
185
|
+
* proto contract. ON_INVALID_FAIL (default) keeps the activity's
|
|
186
|
+
* throwing path: one attempt, LLM_SCHEMA_VALIDATION on miss.
|
|
187
|
+
*/
|
|
188
|
+
private async executeLlmCall(
|
|
189
|
+
resolved: Record<string, unknown>,
|
|
190
|
+
env: Record<string, unknown>,
|
|
191
|
+
fnMeta: { workflowExecutionId?: string },
|
|
192
|
+
ctx: Parameters<TaskExecutorFn>[2],
|
|
193
|
+
): Promise<unknown> {
|
|
194
|
+
const hasSchema = !!resolved.response_schema;
|
|
195
|
+
const onInvalid = (resolved.on_invalid as string | undefined) ?? "ON_INVALID_FAIL";
|
|
196
|
+
const softHandling =
|
|
197
|
+
hasSchema && (onInvalid === "ON_INVALID_RETRY" || onInvalid === "ON_INVALID_FALLBACK");
|
|
198
|
+
|
|
199
|
+
// Proto: max_retries "Default: 1", meaningful only for ON_INVALID_RETRY.
|
|
200
|
+
const maxRetries =
|
|
201
|
+
onInvalid === "ON_INVALID_RETRY" ? (resolved.max_retries as number | undefined) ?? 1 : 0;
|
|
202
|
+
|
|
203
|
+
let attempts = 0;
|
|
204
|
+
let lastParseError = "";
|
|
205
|
+
|
|
206
|
+
do {
|
|
207
|
+
const attemptConfig =
|
|
208
|
+
attempts === 0
|
|
209
|
+
? resolved
|
|
210
|
+
: {
|
|
211
|
+
...resolved,
|
|
212
|
+
prompt:
|
|
213
|
+
`${resolved.prompt}\n\n[RETRY — Your previous response did not match ` +
|
|
214
|
+
`the required output schema. Validation errors: ${lastParseError}. ` +
|
|
215
|
+
`Please ensure your response strictly matches the JSON schema provided.]`,
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
const result = (await ctx.callFunction(
|
|
219
|
+
"llm",
|
|
220
|
+
attemptConfig,
|
|
221
|
+
env,
|
|
222
|
+
fnMeta,
|
|
223
|
+
)) as LlmActivityResult;
|
|
224
|
+
|
|
225
|
+
if (!softHandling || !result.parse_error) {
|
|
226
|
+
return normalizeLlmOutput(result, hasSchema);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
lastParseError = result.parse_error;
|
|
230
|
+
attempts++;
|
|
231
|
+
} while (onInvalid === "ON_INVALID_RETRY" && attempts <= maxRetries);
|
|
232
|
+
|
|
233
|
+
if (typeof resolved.fallback_task === "string" && resolved.fallback_task !== "") {
|
|
234
|
+
return {
|
|
235
|
+
__flow_directive__: resolved.fallback_task,
|
|
236
|
+
validation_errors: [lastParseError],
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
throw new Error(
|
|
241
|
+
`LLM output validation failed after ${attempts} attempt(s) ` +
|
|
242
|
+
`for task '${this.taskName}': ${lastParseError}`,
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
185
246
|
async shouldRun(): Promise<boolean> {
|
|
186
247
|
return true;
|
|
187
248
|
}
|
|
@@ -336,6 +336,12 @@ export interface HttpCallConfig {
|
|
|
336
336
|
readonly query?: Record<string, string>;
|
|
337
337
|
readonly output?: string;
|
|
338
338
|
readonly redirect?: string;
|
|
339
|
+
/**
|
|
340
|
+
* Request timeout in seconds (proto: HttpCallTaskConfig.timeout_seconds,
|
|
341
|
+
* 1-300). Aborts the request and fails the task with HTTP_CALL_TIMEOUT;
|
|
342
|
+
* the engine also widens the activity's startToClose to fit it (#686).
|
|
343
|
+
*/
|
|
344
|
+
readonly timeout_seconds?: number;
|
|
339
345
|
}
|
|
340
346
|
|
|
341
347
|
export type EndpointDef = string | { readonly uri: string; readonly authentication?: Record<string, unknown> };
|
|
@@ -67,17 +67,44 @@ const evalProxy = proxyLocalActivities<EvalActivities>({
|
|
|
67
67
|
startToCloseTimeout: "10s",
|
|
68
68
|
});
|
|
69
69
|
|
|
70
|
+
const CALL_PROXY_RETRY = {
|
|
71
|
+
maximumAttempts: 5,
|
|
72
|
+
initialInterval: "1s",
|
|
73
|
+
backoffCoefficient: 2,
|
|
74
|
+
maximumInterval: "1m",
|
|
75
|
+
} as const;
|
|
76
|
+
|
|
70
77
|
const callProxy = proxyActivities<HttpActivities & GrpcActivities & FunctionActivities>({
|
|
71
78
|
startToCloseTimeout: "5m",
|
|
72
79
|
heartbeatTimeout: "30s",
|
|
73
|
-
retry:
|
|
74
|
-
maximumAttempts: 5,
|
|
75
|
-
initialInterval: "1s",
|
|
76
|
-
backoffCoefficient: 2,
|
|
77
|
-
maximumInterval: "1m",
|
|
78
|
-
},
|
|
80
|
+
retry: CALL_PROXY_RETRY,
|
|
79
81
|
});
|
|
80
82
|
|
|
83
|
+
/**
|
|
84
|
+
* Task-config timeouts (llm_call.timeout ≤600s, http_call.timeout_seconds
|
|
85
|
+
* ≤300s) can exceed or crowd the default 5m startToClose, which would kill
|
|
86
|
+
* the activity before its own well-typed LLM_TIMEOUT / HTTP_CALL_TIMEOUT
|
|
87
|
+
* failure fires. When a task declares a budget, proxy its call with
|
|
88
|
+
* startToClose = budget + 30s so the in-activity bound always wins (#686).
|
|
89
|
+
*
|
|
90
|
+
* proxyActivities in workflow code is a deterministic proxy construction
|
|
91
|
+
* (no Temporal commands); the memo just avoids rebuilding per call.
|
|
92
|
+
*/
|
|
93
|
+
const timeoutAwareProxies = new Map<number, typeof callProxy>();
|
|
94
|
+
function callProxyFor(timeoutSeconds: number | undefined): typeof callProxy {
|
|
95
|
+
if (!timeoutSeconds || !Number.isFinite(timeoutSeconds)) return callProxy;
|
|
96
|
+
let proxy = timeoutAwareProxies.get(timeoutSeconds);
|
|
97
|
+
if (!proxy) {
|
|
98
|
+
proxy = proxyActivities<HttpActivities & GrpcActivities & FunctionActivities>({
|
|
99
|
+
startToCloseTimeout: `${timeoutSeconds + 30}s`,
|
|
100
|
+
heartbeatTimeout: "30s",
|
|
101
|
+
retry: CALL_PROXY_RETRY,
|
|
102
|
+
});
|
|
103
|
+
timeoutAwareProxies.set(timeoutSeconds, proxy);
|
|
104
|
+
}
|
|
105
|
+
return proxy;
|
|
106
|
+
}
|
|
107
|
+
|
|
81
108
|
const runProxy = proxyActivities<RunActivities>({
|
|
82
109
|
startToCloseTimeout: "5m",
|
|
83
110
|
heartbeatTimeout: "30s",
|
|
@@ -240,7 +267,7 @@ export async function runWorkflowEngine(
|
|
|
240
267
|
runWorkflow: (config: RunWorkflowExecutionConfig) => orchestrateRunWorkflow(config),
|
|
241
268
|
awaitHumanInput: (config: HumanInputExecutionConfig) => orchestrateHumanInput(config),
|
|
242
269
|
callHttp: (config: HttpCallConfig, runtimeEnv: Record<string, unknown>) =>
|
|
243
|
-
|
|
270
|
+
callProxyFor(config.timeout_seconds).CallHttp(config, runtimeEnv),
|
|
244
271
|
callGrpc: (config: GrpcCallConfig, runtimeEnv: Record<string, unknown>) =>
|
|
245
272
|
callProxy.CallGrpc(config, runtimeEnv),
|
|
246
273
|
callFunction: (
|
|
@@ -249,7 +276,11 @@ export async function runWorkflowEngine(
|
|
|
249
276
|
runtimeEnv: Record<string, unknown>,
|
|
250
277
|
fnMeta: CallFunctionMetadata,
|
|
251
278
|
) =>
|
|
252
|
-
|
|
279
|
+
callProxyFor(
|
|
280
|
+
call === "llm" && typeof config.timeout === "number"
|
|
281
|
+
? config.timeout
|
|
282
|
+
: undefined,
|
|
283
|
+
).CallFunction(
|
|
253
284
|
call,
|
|
254
285
|
config,
|
|
255
286
|
runtimeEnv,
|