@stigmer/runner-slim 3.12.3 → 3.12.5

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.
Files changed (3) hide show
  1. package/main.js +590 -701
  2. package/package.json +6 -6
  3. package/workflow-bundle.js +108 -15
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stigmer/runner-slim",
3
- "version": "3.12.3",
3
+ "version": "3.12.5",
4
4
  "description": "Self-contained Stigmer runner build for embedding in desktop apps — the bundle-friendly @stigmer/runner",
5
5
  "license": "Apache-2.0",
6
6
  "engines": {
@@ -16,11 +16,11 @@
16
16
  "jq-wasm": "^1.1.0-jq-1.8.1"
17
17
  },
18
18
  "optionalDependencies": {
19
- "@stigmer/runner-slim-darwin-arm64": "3.12.3",
20
- "@stigmer/runner-slim-darwin-x64": "3.12.3",
21
- "@stigmer/runner-slim-linux-x64": "3.12.3",
22
- "@stigmer/runner-slim-linux-arm64": "3.12.3",
23
- "@stigmer/runner-slim-win32-x64": "3.12.3"
19
+ "@stigmer/runner-slim-darwin-arm64": "3.12.5",
20
+ "@stigmer/runner-slim-darwin-x64": "3.12.5",
21
+ "@stigmer/runner-slim-linux-x64": "3.12.5",
22
+ "@stigmer/runner-slim-linux-arm64": "3.12.5",
23
+ "@stigmer/runner-slim-win32-x64": "3.12.5"
24
24
  },
25
25
  "keywords": [
26
26
  "stigmer",
@@ -26416,15 +26416,59 @@ class CallFunctionTaskBuilder {
26416
26416
  resolved.input = input;
26417
26417
  }
26418
26418
  const executionId = state.env.__stigmer_execution_id || undefined;
26419
- const result = await ctx.callFunction(callType, resolved, state.env, {
26420
- workflowExecutionId: executionId,
26421
- });
26419
+ const fnMeta = { workflowExecutionId: executionId };
26422
26420
  if (callType === "llm") {
26423
- return normalizeLlmOutput(result, !!resolved.response_schema);
26421
+ return this.executeLlmCall(resolved, state.env, fnMeta, ctx);
26424
26422
  }
26425
- return result;
26423
+ return ctx.callFunction(callType, resolved, state.env, fnMeta);
26426
26424
  };
26427
26425
  }
26426
+ /**
26427
+ * Executes a `call: llm` with schema-validation policy orchestration —
26428
+ * the llm twin of call-agent's output-contract loop (#686).
26429
+ *
26430
+ * With ON_INVALID_RETRY / ON_INVALID_FALLBACK the activity reports a
26431
+ * validation miss as a `parse_error` result instead of throwing; each
26432
+ * retry is its own activity invocation (visible in Temporal history)
26433
+ * re-prompting with the validation errors so the model can self-correct.
26434
+ * Exhausted retries (or immediate FALLBACK) branch to `fallback_task`
26435
+ * via the engine's flow directive; without one, the task fails — the
26436
+ * proto contract. ON_INVALID_FAIL (default) keeps the activity's
26437
+ * throwing path: one attempt, LLM_SCHEMA_VALIDATION on miss.
26438
+ */
26439
+ async executeLlmCall(resolved, env, fnMeta, ctx) {
26440
+ const hasSchema = !!resolved.response_schema;
26441
+ const onInvalid = resolved.on_invalid ?? "ON_INVALID_FAIL";
26442
+ const softHandling = hasSchema && (onInvalid === "ON_INVALID_RETRY" || onInvalid === "ON_INVALID_FALLBACK");
26443
+ // Proto: max_retries "Default: 1", meaningful only for ON_INVALID_RETRY.
26444
+ const maxRetries = onInvalid === "ON_INVALID_RETRY" ? resolved.max_retries ?? 1 : 0;
26445
+ let attempts = 0;
26446
+ let lastParseError = "";
26447
+ do {
26448
+ const attemptConfig = attempts === 0
26449
+ ? resolved
26450
+ : {
26451
+ ...resolved,
26452
+ prompt: `${resolved.prompt}\n\n[RETRY — Your previous response did not match ` +
26453
+ `the required output schema. Validation errors: ${lastParseError}. ` +
26454
+ `Please ensure your response strictly matches the JSON schema provided.]`,
26455
+ };
26456
+ const result = (await ctx.callFunction("llm", attemptConfig, env, fnMeta));
26457
+ if (!softHandling || !result.parse_error) {
26458
+ return normalizeLlmOutput(result, hasSchema);
26459
+ }
26460
+ lastParseError = result.parse_error;
26461
+ attempts++;
26462
+ } while (onInvalid === "ON_INVALID_RETRY" && attempts <= maxRetries);
26463
+ if (typeof resolved.fallback_task === "string" && resolved.fallback_task !== "") {
26464
+ return {
26465
+ __flow_directive__: resolved.fallback_task,
26466
+ validation_errors: [lastParseError],
26467
+ };
26468
+ }
26469
+ throw new Error(`LLM output validation failed after ${attempts} attempt(s) ` +
26470
+ `for task '${this.taskName}': ${lastParseError}`);
26471
+ }
26428
26472
  async shouldRun() {
26429
26473
  return true;
26430
26474
  }
@@ -26935,11 +26979,11 @@ async function executeHumanInputTask(taskDef, taskName, state, ctx) {
26935
26979
  ...review.artifactEvents,
26936
26980
  ]);
26937
26981
  }
26938
- const result = await ctx.awaitHumanInput({
26982
+ const result = applyTimeoutOutcomeContract(await ctx.awaitHumanInput({
26939
26983
  signalName,
26940
26984
  timeoutSeconds,
26941
26985
  onTimeout,
26942
- });
26986
+ }), config.outcomes);
26943
26987
  if (ctx.emitEvents) {
26944
26988
  await ctx.emitEvents([{
26945
26989
  type: "approval_resolved",
@@ -26965,6 +27009,27 @@ function validateConfig(config, taskName) {
26965
27009
  throw new Error(`human_input task '${taskName}': 'prompt' is required`);
26966
27010
  }
26967
27011
  }
27012
+ /**
27013
+ * Applies the proto contract for timeout auto-resolution with custom
27014
+ * outcomes (HumanInputTaskConfig.outcomes doc): auto-approve resolves to
27015
+ * the FIRST declared outcome and auto-deny to the LAST, so `then` routing
27016
+ * and downstream outcome switches see declared outcome names — never the
27017
+ * orchestrator's internal approve/deny words, which a reviewer of a
27018
+ * custom-outcome gate was never offered. Binary gates (no custom outcomes)
27019
+ * keep the plain approve/deny result.
27020
+ */
27021
+ function applyTimeoutOutcomeContract(result, outcomes) {
27022
+ if (!result.auto_resolved || result.reason !== "timeout" || !outcomes?.length) {
27023
+ return result;
27024
+ }
27025
+ if (result.outcome === "approve") {
27026
+ return { ...result, outcome: outcomes[0].name };
27027
+ }
27028
+ if (result.outcome === "deny") {
27029
+ return { ...result, outcome: outcomes[outcomes.length - 1].name };
27030
+ }
27031
+ return result;
27032
+ }
26968
27033
  const NO_REVIEW_PAYLOAD = { artifactEvents: [] };
26969
27034
  /**
26970
27035
  * Resolves the review payload's `${ ... }` expressions and decides how it
@@ -28440,16 +28505,42 @@ __webpack_require__.r(__webpack_exports__);
28440
28505
  const evalProxy = (0,_temporalio_workflow__WEBPACK_IMPORTED_MODULE_0__.proxyLocalActivities)({
28441
28506
  startToCloseTimeout: "10s",
28442
28507
  });
28508
+ const CALL_PROXY_RETRY = {
28509
+ maximumAttempts: 5,
28510
+ initialInterval: "1s",
28511
+ backoffCoefficient: 2,
28512
+ maximumInterval: "1m",
28513
+ };
28443
28514
  const callProxy = (0,_temporalio_workflow__WEBPACK_IMPORTED_MODULE_0__.proxyActivities)({
28444
28515
  startToCloseTimeout: "5m",
28445
28516
  heartbeatTimeout: "30s",
28446
- retry: {
28447
- maximumAttempts: 5,
28448
- initialInterval: "1s",
28449
- backoffCoefficient: 2,
28450
- maximumInterval: "1m",
28451
- },
28517
+ retry: CALL_PROXY_RETRY,
28452
28518
  });
28519
+ /**
28520
+ * Task-config timeouts (llm_call.timeout ≤600s, http_call.timeout_seconds
28521
+ * ≤300s) can exceed or crowd the default 5m startToClose, which would kill
28522
+ * the activity before its own well-typed LLM_TIMEOUT / HTTP_CALL_TIMEOUT
28523
+ * failure fires. When a task declares a budget, proxy its call with
28524
+ * startToClose = budget + 30s so the in-activity bound always wins (#686).
28525
+ *
28526
+ * proxyActivities in workflow code is a deterministic proxy construction
28527
+ * (no Temporal commands); the memo just avoids rebuilding per call.
28528
+ */
28529
+ const timeoutAwareProxies = new Map();
28530
+ function callProxyFor(timeoutSeconds) {
28531
+ if (!timeoutSeconds || !Number.isFinite(timeoutSeconds))
28532
+ return callProxy;
28533
+ let proxy = timeoutAwareProxies.get(timeoutSeconds);
28534
+ if (!proxy) {
28535
+ proxy = (0,_temporalio_workflow__WEBPACK_IMPORTED_MODULE_0__.proxyActivities)({
28536
+ startToCloseTimeout: `${timeoutSeconds + 30}s`,
28537
+ heartbeatTimeout: "30s",
28538
+ retry: CALL_PROXY_RETRY,
28539
+ });
28540
+ timeoutAwareProxies.set(timeoutSeconds, proxy);
28541
+ }
28542
+ return proxy;
28543
+ }
28453
28544
  const runProxy = (0,_temporalio_workflow__WEBPACK_IMPORTED_MODULE_0__.proxyActivities)({
28454
28545
  startToCloseTimeout: "5m",
28455
28546
  heartbeatTimeout: "30s",
@@ -28589,9 +28680,11 @@ async function runWorkflowEngine(input, options) {
28589
28680
  : runProxy.RunShell(config),
28590
28681
  runWorkflow: (config) => (0,_run_orchestrator_js__WEBPACK_IMPORTED_MODULE_4__.orchestrateRunWorkflow)(config),
28591
28682
  awaitHumanInput: (config) => (0,_human_input_orchestrator_js__WEBPACK_IMPORTED_MODULE_5__.orchestrateHumanInput)(config),
28592
- callHttp: (config, runtimeEnv) => callProxy.CallHttp(config, runtimeEnv),
28683
+ callHttp: (config, runtimeEnv) => callProxyFor(config.timeout_seconds).CallHttp(config, runtimeEnv),
28593
28684
  callGrpc: (config, runtimeEnv) => callProxy.CallGrpc(config, runtimeEnv),
28594
- callFunction: (call, config, runtimeEnv, fnMeta) => callProxy.CallFunction(call, config, runtimeEnv, fnMeta.workflowExecutionId ?? executionId),
28685
+ callFunction: (call, config, runtimeEnv, fnMeta) => callProxyFor(call === "llm" && typeof config.timeout === "number"
28686
+ ? config.timeout
28687
+ : undefined).CallFunction(call, config, runtimeEnv, fnMeta.workflowExecutionId ?? executionId),
28595
28688
  callAgent: (config, runtimeEnv, agentMeta) => (0,_call_agent_orchestrator_js__WEBPACK_IMPORTED_MODULE_2__.orchestrateAgentCall)({
28596
28689
  config,
28597
28690
  runtimeEnv,