@stigmer/runner-slim 3.2.0 → 3.2.2

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 +33 -17
  2. package/package.json +6 -6
  3. package/workflow-bundle.js +133 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stigmer/runner-slim",
3
- "version": "3.2.0",
3
+ "version": "3.2.2",
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.2.0",
20
- "@stigmer/runner-slim-darwin-x64": "3.2.0",
21
- "@stigmer/runner-slim-linux-x64": "3.2.0",
22
- "@stigmer/runner-slim-linux-arm64": "3.2.0",
23
- "@stigmer/runner-slim-win32-x64": "3.2.0"
19
+ "@stigmer/runner-slim-darwin-arm64": "3.2.2",
20
+ "@stigmer/runner-slim-darwin-x64": "3.2.2",
21
+ "@stigmer/runner-slim-linux-x64": "3.2.2",
22
+ "@stigmer/runner-slim-linux-arm64": "3.2.2",
23
+ "@stigmer/runner-slim-win32-x64": "3.2.2"
24
24
  },
25
25
  "keywords": [
26
26
  "stigmer",
@@ -24214,6 +24214,7 @@ function deepClone(value) {
24214
24214
  "use strict";
24215
24215
  __webpack_require__.r(__webpack_exports__);
24216
24216
  /* harmony export */ __webpack_require__.d(__webpack_exports__, {
24217
+ /* harmony export */ TASK_STARTED_INPUT_SUMMARY_PATCH: () => (/* binding */ TASK_STARTED_INPUT_SUMMARY_PATCH),
24217
24218
  /* harmony export */ executeDoTasks: () => (/* binding */ executeDoTasks)
24218
24219
  /* harmony export */ });
24219
24220
  /* harmony import */ var _types_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./types.js */ "./dist/workflow-engine/types.js");
@@ -24253,6 +24254,37 @@ __webpack_require__.r(__webpack_exports__);
24253
24254
 
24254
24255
 
24255
24256
 
24257
+ /**
24258
+ * Deterministic-version gate id for the task_started emission reorder
24259
+ * (input resolution moved ahead of the event so it can carry
24260
+ * `input_summary`). Pre-change histories replay the old order — emit
24261
+ * first, resolve second — via `TaskExecutionContext.isPatched`.
24262
+ */
24263
+ const TASK_STARTED_INPUT_SUMMARY_PATCH = "task-started-input-summary";
24264
+ /**
24265
+ * Truncation budget for event I/O summaries. Deliberately far below the
24266
+ * 64KB snapshot cap: events are the live-stream preview source and the
24267
+ * proto documents them as "truncated to prevent event bloat" — the full
24268
+ * I/O lives on `status.tasks[]`.
24269
+ */
24270
+ const EVENT_SUMMARY_MAX_BYTES = 8_192;
24271
+ /**
24272
+ * Projects a task I/O value into the shape an event summary Struct can
24273
+ * carry: truncated to the event budget, objects only (mirroring the
24274
+ * snapshot path's `toJsonObject` semantics — proto Struct fields cannot
24275
+ * carry scalars or arrays, and `status.tasks[].input/output` share the
24276
+ * same limitation). Oversize values come back as `truncatePayload`'s
24277
+ * `{_truncated, _preview}` marker object, which IS carried.
24278
+ */
24279
+ function toEventSummary(value) {
24280
+ if (value === null || value === undefined)
24281
+ return undefined;
24282
+ const truncated = (0,_task_status_accumulator_js__WEBPACK_IMPORTED_MODULE_9__.truncatePayload)(value, EVENT_SUMMARY_MAX_BYTES);
24283
+ if (truncated === null || typeof truncated !== "object" || Array.isArray(truncated)) {
24284
+ return undefined;
24285
+ }
24286
+ return truncated;
24287
+ }
24256
24288
  /**
24257
24289
  * Returns the task kind string used for event emission. For call:function
24258
24290
  * tasks, appends the specific function name (e.g., "call:function:llm")
@@ -24349,20 +24381,37 @@ async function executeDoTasks(tasks, input, state, doc, evaluateExpressions, ctx
24349
24381
  const kind = eventTaskKind(entry);
24350
24382
  effectiveCtx.taskStatusAccumulator?.taskStarted(entry.key, kind);
24351
24383
  const attemptNumber = effectiveCtx.taskStatusAccumulator?.getAttempt(entry.key) ?? 1;
24352
- if (effectiveCtx.emitEvents) {
24384
+ const emitTaskStarted = async (inputSummary) => {
24385
+ if (!effectiveCtx.emitEvents)
24386
+ return;
24353
24387
  await effectiveCtx.emitEvents([{
24354
24388
  type: "task_started",
24355
24389
  taskName: entry.key,
24356
24390
  occurredAt: new Date().toISOString(),
24357
24391
  taskKind: kind,
24358
24392
  attemptNumber,
24393
+ inputSummary,
24359
24394
  }]);
24360
- }
24395
+ };
24396
+ // Emission-order gate: carrying `input_summary` on task_started
24397
+ // requires resolving the input BEFORE emitting, which reorders two
24398
+ // local-activity commands — a non-deterministic change for in-flight
24399
+ // histories (a gated or waiting task can be parked across a deploy).
24400
+ // Pre-change histories replay the old emit-then-resolve order.
24401
+ const emitStartedAfterResolve = effectiveCtx.isPatched?.(TASK_STARTED_INPUT_SUMMARY_PATCH) ?? true;
24402
+ if (!emitStartedAfterResolve) {
24403
+ await emitTaskStarted();
24404
+ }
24405
+ let startedEmitted = !emitStartedAfterResolve;
24361
24406
  const taskStartMs = Date.now();
24362
24407
  let taskOutput;
24363
24408
  try {
24364
24409
  const effectiveInput = await resolveTaskInput(entry, input, state, effectiveCtx);
24365
24410
  effectiveCtx.taskStatusAccumulator?.taskStartedWithInput(entry.key, kind, (0,_task_status_accumulator_js__WEBPACK_IMPORTED_MODULE_9__.truncatePayload)(effectiveInput));
24411
+ if (emitStartedAfterResolve) {
24412
+ await emitTaskStarted(toEventSummary(effectiveInput));
24413
+ startedEmitted = true;
24414
+ }
24366
24415
  taskOutput = await runSingleTask(entry, effectiveInput, state, doc, effectiveCtx);
24367
24416
  }
24368
24417
  catch (taskErr) {
@@ -24378,6 +24427,12 @@ async function executeDoTasks(tasks, input, state, doc, evaluateExpressions, ctx
24378
24427
  });
24379
24428
  }
24380
24429
  if (effectiveCtx.emitEvents) {
24430
+ // Input resolution failed before the reordered task_started could
24431
+ // fire — emit it now so consumers always see the started→failed pair.
24432
+ if (!startedEmitted) {
24433
+ await emitTaskStarted();
24434
+ startedEmitted = true;
24435
+ }
24381
24436
  await effectiveCtx.emitEvents([{
24382
24437
  type: "task_failed",
24383
24438
  taskName: entry.key,
@@ -24419,6 +24474,10 @@ async function executeDoTasks(tasks, input, state, doc, evaluateExpressions, ctx
24419
24474
  durationMs: taskDurationMs,
24420
24475
  costMicros: costInfo.costMicros,
24421
24476
  tokensUsed: costInfo.inputTokens + costInfo.outputTokens,
24477
+ // The live-stream preview source for task cards: post-promotion
24478
+ // output (the artifact-promoted form is what downstream sees),
24479
+ // truncated to the event budget. Full output rides the snapshot.
24480
+ outputSummary: toEventSummary(promotedOutput),
24422
24481
  }];
24423
24482
  for (const evt of promotionEvents) {
24424
24483
  events.push(evt);
@@ -26217,6 +26276,69 @@ __webpack_require__.r(__webpack_exports__);
26217
26276
  * Mirrors Go's custom call function dispatch in `task_builder.go`.
26218
26277
  */
26219
26278
 
26279
+ /**
26280
+ * Detaches deferred-code fields from a call-function config before
26281
+ * expression resolution and returns a restorer that puts the originals
26282
+ * back afterwards.
26283
+ *
26284
+ * Some config fields carry jq CODE that the activity evaluates later
26285
+ * against its own input — not data to interpolate at config time:
26286
+ *
26287
+ * - `transform.expression` — evaluated by `transformAction` against the
26288
+ * transform's `input`.
26289
+ * - `validate.rules[].expression` — evaluated by `validateAction` per
26290
+ * rule against the validated data.
26291
+ *
26292
+ * Without the detach, a `${ }`-wrapped predicate is pre-evaluated here
26293
+ * against the WRONG input and its non-string RESULT is substituted back
26294
+ * into the config; the activity then tries to evaluate that result as jq
26295
+ * (the `expr.includes is not a function` crash class). All other fields
26296
+ * — including `rules[].message` and the llm/eval prompt fields — are
26297
+ * data and continue to interpolate normally. The original config object
26298
+ * is never mutated (shared across retries).
26299
+ */
26300
+ function detachDeferredCode(callType, config) {
26301
+ if (callType === "transform" && "expression" in config) {
26302
+ const { expression, ...rest } = config;
26303
+ return {
26304
+ config: rest,
26305
+ restore: (resolved) => {
26306
+ resolved.expression = expression;
26307
+ },
26308
+ };
26309
+ }
26310
+ if (callType === "validate" && Array.isArray(config.rules)) {
26311
+ const originalRules = config.rules;
26312
+ const strippedRules = originalRules.map((rule) => {
26313
+ if (isPlainObject(rule) && "expression" in rule) {
26314
+ const { expression: _deferred, ...rest } = rule;
26315
+ return rest;
26316
+ }
26317
+ return rule;
26318
+ });
26319
+ return {
26320
+ config: { ...config, rules: strippedRules },
26321
+ restore: (resolved) => {
26322
+ const resolvedRules = resolved.rules;
26323
+ if (!Array.isArray(resolvedRules))
26324
+ return;
26325
+ for (let i = 0; i < resolvedRules.length; i++) {
26326
+ const original = originalRules[i];
26327
+ const target = resolvedRules[i];
26328
+ if (isPlainObject(original) &&
26329
+ isPlainObject(target) &&
26330
+ "expression" in original) {
26331
+ target.expression = original.expression;
26332
+ }
26333
+ }
26334
+ },
26335
+ };
26336
+ }
26337
+ return { config, restore: () => { } };
26338
+ }
26339
+ function isPlainObject(value) {
26340
+ return value !== null && typeof value === "object" && !Array.isArray(value);
26341
+ }
26220
26342
  /**
26221
26343
  * Normalizes the raw LlmCallResult from the activity into the documented
26222
26344
  * task output contract: { text, structured, model, usage }.
@@ -26248,8 +26370,12 @@ class CallFunctionTaskBuilder {
26248
26370
  build() {
26249
26371
  return async (input, state, ctx) => {
26250
26372
  const withConfig = this.taskDef.with ?? {};
26251
- const resolved = await (0,_resolve_js__WEBPACK_IMPORTED_MODULE_0__.resolveConfigExpressions)(withConfig, input, state, ctx.evaluateExpressions);
26252
26373
  const callType = this.taskDef.call;
26374
+ // Deferred-code fields (transform/validate expressions) must reach
26375
+ // the activity as unresolved jq strings — see detachDeferredCode.
26376
+ const deferred = detachDeferredCode(callType, withConfig);
26377
+ const resolved = await (0,_resolve_js__WEBPACK_IMPORTED_MODULE_0__.resolveConfigExpressions)(deferred.config, input, state, ctx.evaluateExpressions);
26378
+ deferred.restore(resolved);
26253
26379
  if ((callType === "transform" || callType === "validate") &&
26254
26380
  !("input" in resolved) || resolved.input === undefined) {
26255
26381
  resolved.input = input;
@@ -28371,6 +28497,10 @@ async function runWorkflowEngine(input, options) {
28371
28497
  checkPause: options?.checkPause,
28372
28498
  emitEvents,
28373
28499
  taskStatusAccumulator,
28500
+ // Deterministic version gate for engine command-order changes (the
28501
+ // kernel never imports Temporal APIs — it receives patched() as an
28502
+ // opaque callback, like every other capability on this context).
28503
+ isPatched: (changeId) => (0,_temporalio_workflow__WEBPACK_IMPORTED_MODULE_0__.patched)(changeId),
28374
28504
  sleep: async (durationMs) => {
28375
28505
  try {
28376
28506
  await (0,_temporalio_workflow__WEBPACK_IMPORTED_MODULE_0__.sleep)(durationMs);