@stigmer/runner 3.12.4 → 3.12.6

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 (180) hide show
  1. package/dist/.build-fingerprint +1 -1
  2. package/dist/activities/call-agent.js +19 -6
  3. package/dist/activities/call-agent.js.map +1 -1
  4. package/dist/activities/execute-cursor/cursor-mcp-config.d.ts +45 -0
  5. package/dist/activities/execute-cursor/cursor-mcp-config.js +76 -0
  6. package/dist/activities/execute-cursor/cursor-mcp-config.js.map +1 -0
  7. package/dist/activities/execute-cursor/error-classifier.d.ts +9 -0
  8. package/dist/activities/execute-cursor/error-classifier.js +30 -1
  9. package/dist/activities/execute-cursor/error-classifier.js.map +1 -1
  10. package/dist/activities/execute-cursor/index.d.ts +10 -1
  11. package/dist/activities/execute-cursor/index.js +62 -36
  12. package/dist/activities/execute-cursor/index.js.map +1 -1
  13. package/dist/activities/execute-cursor/prompt-builder.d.ts +13 -2
  14. package/dist/activities/execute-cursor/prompt-builder.js +11 -0
  15. package/dist/activities/execute-cursor/prompt-builder.js.map +1 -1
  16. package/dist/activities/execute-cursor/service-tier.d.ts +38 -29
  17. package/dist/activities/execute-cursor/service-tier.js +92 -63
  18. package/dist/activities/execute-cursor/service-tier.js.map +1 -1
  19. package/dist/activities/execute-cursor/session-lifecycle.d.ts +1 -1
  20. package/dist/activities/execute-cursor/skill-resolver.d.ts +2 -21
  21. package/dist/activities/execute-cursor/skill-resolver.js +5 -108
  22. package/dist/activities/execute-cursor/skill-resolver.js.map +1 -1
  23. package/dist/activities/execute-cursor/usage-accumulator.d.ts +16 -2
  24. package/dist/activities/execute-cursor/usage-accumulator.js +12 -2
  25. package/dist/activities/execute-cursor/usage-accumulator.js.map +1 -1
  26. package/dist/activities/execute-deep-agent/approval-file-change.d.ts +9 -0
  27. package/dist/activities/execute-deep-agent/approval-file-change.js +2 -1
  28. package/dist/activities/execute-deep-agent/approval-file-change.js.map +1 -1
  29. package/dist/activities/execute-deep-agent/cas-capture-backend.d.ts +18 -1
  30. package/dist/activities/execute-deep-agent/cas-capture-backend.js +20 -3
  31. package/dist/activities/execute-deep-agent/cas-capture-backend.js.map +1 -1
  32. package/dist/activities/execute-deep-agent/index.js +57 -5
  33. package/dist/activities/execute-deep-agent/index.js.map +1 -1
  34. package/dist/activities/execute-deep-agent/prompt-builder.d.ts +9 -0
  35. package/dist/activities/execute-deep-agent/prompt-builder.js +10 -0
  36. package/dist/activities/execute-deep-agent/prompt-builder.js.map +1 -1
  37. package/dist/activities/execute-deep-agent/setup.js +17 -16
  38. package/dist/activities/execute-deep-agent/setup.js.map +1 -1
  39. package/dist/activities/execute-deep-agent/subagent-transformer.js +17 -11
  40. package/dist/activities/execute-deep-agent/subagent-transformer.js.map +1 -1
  41. package/dist/activities/execute-deep-agent/subagent-wiring.d.ts +3 -3
  42. package/dist/index.d.ts +1 -1
  43. package/dist/index.js +1 -1
  44. package/dist/middleware/path-normalization.d.ts +49 -49
  45. package/dist/middleware/path-normalization.js +73 -84
  46. package/dist/middleware/path-normalization.js.map +1 -1
  47. package/dist/middleware/types.d.ts +3 -3
  48. package/dist/runner-manager.d.ts +1 -1
  49. package/dist/runner-manager.js +22 -15
  50. package/dist/runner-manager.js.map +1 -1
  51. package/dist/runner.js +11 -0
  52. package/dist/runner.js.map +1 -1
  53. package/dist/shared/args-preview.d.ts +8 -0
  54. package/dist/shared/args-preview.js +14 -3
  55. package/dist/shared/args-preview.js.map +1 -1
  56. package/dist/shared/artifact-storage.d.ts +11 -1
  57. package/dist/shared/artifact-storage.js +10 -1
  58. package/dist/shared/artifact-storage.js.map +1 -1
  59. package/dist/shared/caller-identity.d.ts +10 -7
  60. package/dist/shared/caller-identity.js +10 -7
  61. package/dist/shared/caller-identity.js.map +1 -1
  62. package/dist/shared/declared-preferences.d.ts +47 -0
  63. package/dist/shared/declared-preferences.js +64 -0
  64. package/dist/shared/declared-preferences.js.map +1 -0
  65. package/dist/shared/mcp-enabled-tools.d.ts +2 -3
  66. package/dist/shared/mcp-enabled-tools.js +2 -3
  67. package/dist/shared/mcp-enabled-tools.js.map +1 -1
  68. package/dist/shared/mcp-resolver.d.ts +20 -15
  69. package/dist/shared/mcp-resolver.js +11 -12
  70. package/dist/shared/mcp-resolver.js.map +1 -1
  71. package/dist/shared/placeholder-resolver.d.ts +9 -2
  72. package/dist/shared/placeholder-resolver.js +9 -2
  73. package/dist/shared/placeholder-resolver.js.map +1 -1
  74. package/dist/shared/plan-mode-permissions.d.ts +26 -46
  75. package/dist/shared/plan-mode-permissions.js +27 -55
  76. package/dist/shared/plan-mode-permissions.js.map +1 -1
  77. package/dist/shared/skill-mount.d.ts +89 -0
  78. package/dist/shared/skill-mount.js +142 -0
  79. package/dist/shared/skill-mount.js.map +1 -0
  80. package/dist/shared/skill-writer.d.ts +28 -26
  81. package/dist/shared/skill-writer.js +79 -102
  82. package/dist/shared/skill-writer.js.map +1 -1
  83. package/dist/shared/thinking-mode.d.ts +35 -0
  84. package/dist/shared/thinking-mode.js +43 -0
  85. package/dist/shared/thinking-mode.js.map +1 -0
  86. package/dist/shared/worker-shutdown.d.ts +67 -0
  87. package/dist/shared/worker-shutdown.js +79 -0
  88. package/dist/shared/worker-shutdown.js.map +1 -0
  89. package/dist/shared/workspace/types.d.ts +3 -2
  90. package/dist/workflow-engine/loader.js +82 -6
  91. package/dist/workflow-engine/loader.js.map +1 -1
  92. package/dist/workflow-engine/tasks/human-input.d.ts +2 -1
  93. package/dist/workflow-engine/tasks/human-input.js +31 -3
  94. package/dist/workflow-engine/tasks/human-input.js.map +1 -1
  95. package/dist/workflow-engine/types.d.ts +17 -3
  96. package/dist/workflow-engine/types.js.map +1 -1
  97. package/dist/workflows/human-input-orchestrator.d.ts +4 -0
  98. package/dist/workflows/human-input-orchestrator.js +13 -0
  99. package/dist/workflows/human-input-orchestrator.js.map +1 -1
  100. package/package.json +15 -11
  101. package/src/activities/call-agent.ts +20 -6
  102. package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +50 -0
  103. package/src/activities/execute-cursor/__tests__/cursor-mcp-config.test.ts +111 -0
  104. package/src/activities/execute-cursor/__tests__/error-classifier-billing.test.ts +67 -0
  105. package/src/activities/execute-cursor/__tests__/service-tier.test.ts +67 -7
  106. package/src/activities/execute-cursor/__tests__/skill-resolver.test.ts +3 -94
  107. package/src/activities/execute-cursor/__tests__/usage-accumulator.test.ts +33 -1
  108. package/src/activities/execute-cursor/cursor-mcp-config.ts +107 -0
  109. package/src/activities/execute-cursor/error-classifier.ts +42 -1
  110. package/src/activities/execute-cursor/index.ts +78 -41
  111. package/src/activities/execute-cursor/prompt-builder.ts +29 -2
  112. package/src/activities/execute-cursor/service-tier.ts +94 -63
  113. package/src/activities/execute-cursor/session-lifecycle.ts +1 -1
  114. package/src/activities/execute-cursor/skill-resolver.ts +10 -137
  115. package/src/activities/execute-cursor/usage-accumulator.ts +11 -1
  116. package/src/activities/execute-deep-agent/__tests__/approval-file-change.test.ts +15 -0
  117. package/src/activities/execute-deep-agent/__tests__/cas-capture-backend.test.ts +75 -0
  118. package/src/activities/execute-deep-agent/__tests__/hitl-reject.test.ts +1 -0
  119. package/src/activities/execute-deep-agent/__tests__/hitl-resume-approve-all.test.ts +1 -0
  120. package/src/activities/execute-deep-agent/__tests__/hitl-resume-history.test.ts +1 -0
  121. package/src/activities/execute-deep-agent/__tests__/index.test.ts +1 -0
  122. package/src/activities/execute-deep-agent/__tests__/plan-mode-path-normalization.test.ts +42 -29
  123. package/src/activities/execute-deep-agent/__tests__/prompt-builder.test.ts +52 -0
  124. package/src/activities/execute-deep-agent/__tests__/sequential-gate-resume.test.ts +1 -0
  125. package/src/activities/execute-deep-agent/__tests__/subagent-plan-mode-permissions.test.ts +11 -8
  126. package/src/activities/execute-deep-agent/approval-file-change.ts +11 -1
  127. package/src/activities/execute-deep-agent/cas-capture-backend.ts +20 -3
  128. package/src/activities/execute-deep-agent/index.ts +57 -5
  129. package/src/activities/execute-deep-agent/prompt-builder.ts +22 -0
  130. package/src/activities/execute-deep-agent/setup.ts +21 -19
  131. package/src/activities/execute-deep-agent/subagent-transformer.ts +19 -12
  132. package/src/activities/execute-deep-agent/subagent-wiring.ts +3 -3
  133. package/src/index.ts +1 -1
  134. package/src/middleware/__tests__/path-normalization.test.ts +32 -32
  135. package/src/middleware/path-normalization.ts +78 -90
  136. package/src/middleware/types.ts +3 -3
  137. package/src/runner-manager.ts +26 -16
  138. package/src/runner.ts +14 -0
  139. package/src/shared/__tests__/artifact-storage.test.ts +28 -0
  140. package/src/shared/__tests__/bedrock-seam.test.ts +5 -5
  141. package/src/shared/__tests__/declared-preferences.test.ts +109 -0
  142. package/src/shared/__tests__/foundry-seam.test.ts +5 -5
  143. package/src/shared/__tests__/plan-mode-permissions.test.ts +15 -44
  144. package/src/shared/__tests__/skill-mount.test.ts +238 -0
  145. package/src/shared/__tests__/skill-writer.test.ts +151 -128
  146. package/src/shared/__tests__/vertex-adapter.test.ts +30 -3
  147. package/src/shared/__tests__/vertex-seam.test.ts +5 -5
  148. package/src/shared/__tests__/worker-shutdown.test.ts +101 -0
  149. package/src/shared/args-preview.ts +17 -3
  150. package/src/shared/artifact-storage.ts +20 -1
  151. package/src/shared/caller-identity.ts +10 -7
  152. package/src/shared/declared-preferences.ts +84 -0
  153. package/src/shared/mcp-enabled-tools.ts +2 -3
  154. package/src/shared/mcp-resolver.ts +20 -20
  155. package/src/shared/placeholder-resolver.ts +9 -2
  156. package/src/shared/plan-mode-permissions.ts +27 -58
  157. package/src/shared/skill-mount.ts +179 -0
  158. package/src/shared/skill-writer.ts +96 -130
  159. package/src/shared/thinking-mode.ts +52 -0
  160. package/src/shared/worker-shutdown.ts +99 -0
  161. package/src/shared/workspace/types.ts +3 -2
  162. package/src/workflow-engine/__tests__/loader.test.ts +95 -0
  163. package/src/workflow-engine/__tests__/tasks/human-input.test.ts +156 -0
  164. package/src/workflow-engine/loader.ts +97 -6
  165. package/src/workflow-engine/tasks/human-input.ts +41 -6
  166. package/src/workflow-engine/types.ts +18 -3
  167. package/src/workflows/human-input-orchestrator.ts +20 -2
  168. package/dist/activities/execute-cursor/connect-backfill.d.ts +0 -19
  169. package/dist/activities/execute-cursor/connect-backfill.js +0 -27
  170. package/dist/activities/execute-cursor/connect-backfill.js.map +0 -1
  171. package/dist/activities/execute-cursor/mcp-resolver.d.ts +0 -120
  172. package/dist/activities/execute-cursor/mcp-resolver.js +0 -194
  173. package/dist/activities/execute-cursor/mcp-resolver.js.map +0 -1
  174. package/dist/activities/execute-cursor/placeholder-resolver.d.ts +0 -34
  175. package/dist/activities/execute-cursor/placeholder-resolver.js +0 -82
  176. package/dist/activities/execute-cursor/placeholder-resolver.js.map +0 -1
  177. package/src/activities/execute-cursor/__tests__/mcp-resolver.test.ts +0 -125
  178. package/src/activities/execute-cursor/connect-backfill.ts +0 -52
  179. package/src/activities/execute-cursor/mcp-resolver.ts +0 -324
  180. package/src/activities/execute-cursor/placeholder-resolver.ts +0 -109
@@ -1528,6 +1528,101 @@ do:
1528
1528
  }
1529
1529
  });
1530
1530
 
1531
+ // The server converter persists on_timeout as the proto enum NAME
1532
+ // (HumanInputTimeoutPolicy.String()), so every applied workflow carries
1533
+ // that form in its validated YAML. stigmer/stigmer#779: the loader used
1534
+ // to cast it unvalidated, and the orchestrator's switch silently treated
1535
+ // the unrecognized string as fail.
1536
+ const humanInputYamlWithOnTimeout = (onTimeout: string) => `
1537
+ document:
1538
+ dsl: '1.0.0'
1539
+ name: test
1540
+ do:
1541
+ - timedApproval:
1542
+ call: human_input
1543
+ with:
1544
+ prompt: "Approve within time limit"
1545
+ timeout: 3600
1546
+ on_timeout: ${onTimeout}
1547
+ `;
1548
+
1549
+ it("call: human_input normalizes proto enum-name on_timeout values to the internal policy words", () => {
1550
+ const cases = [
1551
+ ["HUMAN_INPUT_TIMEOUT_FAIL", "fail"],
1552
+ ["HUMAN_INPUT_TIMEOUT_APPROVE", "approve"],
1553
+ ["HUMAN_INPUT_TIMEOUT_DENY", "deny"],
1554
+ ] as const;
1555
+ for (const [wireForm, internalForm] of cases) {
1556
+ const model = loadWorkflowFromYaml(humanInputYamlWithOnTimeout(wireForm));
1557
+ const task = model.do[0].task;
1558
+ expect(task.kind).toBe("human_input");
1559
+ if (task.kind === "human_input") {
1560
+ expect(task.humanInput.onTimeout).toBe(internalForm);
1561
+ }
1562
+ }
1563
+ });
1564
+
1565
+ // The escalate outcome-by-name contract (stigmer/stigmer#781): the policy
1566
+ // loads only when the gate declares an outcome named "escalate" with
1567
+ // `then` set — the timeout resolves to that outcome and follows its branch.
1568
+ const escalateYamlWithOutcomes = (onTimeout: string, outcomesYaml: string) => `
1569
+ document:
1570
+ dsl: '1.0.0'
1571
+ name: test
1572
+ do:
1573
+ - timedApproval:
1574
+ call: human_input
1575
+ with:
1576
+ prompt: "Approve within time limit"
1577
+ timeout: 3600
1578
+ on_timeout: ${onTimeout}
1579
+ outcomes:
1580
+ ${outcomesYaml}
1581
+ `;
1582
+
1583
+ it("call: human_input accepts the escalate policy when an escalate outcome with then exists", () => {
1584
+ for (const form of ["HUMAN_INPUT_TIMEOUT_ESCALATE", "escalate"]) {
1585
+ const model = loadWorkflowFromYaml(escalateYamlWithOutcomes(form, `
1586
+ - name: proceed
1587
+ - name: escalate
1588
+ then: escalationPath
1589
+ `));
1590
+ const task = model.do[0].task;
1591
+ expect(task.kind).toBe("human_input");
1592
+ if (task.kind === "human_input") {
1593
+ expect(task.humanInput.onTimeout).toBe("escalate");
1594
+ }
1595
+ }
1596
+ });
1597
+
1598
+ it("call: human_input rejects the escalate policy without an escalate outcome", () => {
1599
+ expect(() => loadWorkflowFromYaml(humanInputYamlWithOnTimeout("escalate")))
1600
+ .toThrow(/timedApproval.*escalate.*requires an outcome named 'escalate'/);
1601
+ expect(() => loadWorkflowFromYaml(escalateYamlWithOutcomes("HUMAN_INPUT_TIMEOUT_ESCALATE", `
1602
+ - name: proceed
1603
+ - name: reject
1604
+ `)))
1605
+ .toThrow(/timedApproval.*requires an outcome named 'escalate'/);
1606
+ });
1607
+
1608
+ it("call: human_input rejects the escalate policy when the escalate outcome has no then", () => {
1609
+ expect(() => loadWorkflowFromYaml(escalateYamlWithOutcomes("escalate", `
1610
+ - name: proceed
1611
+ - name: escalate
1612
+ `)))
1613
+ .toThrow(/timedApproval.*requires an outcome named 'escalate' with 'then' set/);
1614
+ });
1615
+
1616
+ it("call: human_input rejects unknown on_timeout values instead of silently failing at timeout", () => {
1617
+ expect(() => loadWorkflowFromYaml(humanInputYamlWithOnTimeout("sometimes")))
1618
+ .toThrow(/timedApproval.*unknown on_timeout value 'sometimes'.*fail, approve, deny/);
1619
+ });
1620
+
1621
+ it("call: human_input rejects non-string on_timeout values", () => {
1622
+ expect(() => loadWorkflowFromYaml(humanInputYamlWithOnTimeout("42")))
1623
+ .toThrow(/timedApproval.*on_timeout/);
1624
+ });
1625
+
1531
1626
  it("call: human_input with payload and ui_hint", () => {
1532
1627
  const yaml = `
1533
1628
  document:
@@ -246,6 +246,162 @@ describe("executeHumanInputTask", () => {
246
246
  });
247
247
  });
248
248
 
249
+ // HumanInputTaskConfig.outcomes contract (human_input.proto): with custom
250
+ // outcomes, timeout auto-approve resolves to the FIRST outcome and timeout
251
+ // auto-deny to the LAST — downstream `then` routing and outcome switches
252
+ // must see declared outcome names, never the internal approve/deny words a
253
+ // reviewer was never offered. stigmer/stigmer#779 made this path reachable
254
+ // for the first time.
255
+ describe("timeout outcome mapping with custom outcomes", () => {
256
+ const outcomes = [
257
+ { name: "proceed", label: "Proceed", then: "deployStep" },
258
+ { name: "needs_revision", label: "Needs revision", then: "gatherMore" },
259
+ { name: "reject", label: "Reject" },
260
+ ];
261
+
262
+ it("maps timeout auto-approve to the FIRST outcome and routes its then", async () => {
263
+ const awaitFn: AwaitHumanInputFn = async () => ({
264
+ outcome: "approve",
265
+ auto_resolved: true,
266
+ reason: "timeout",
267
+ });
268
+
269
+ const taskDef: HumanInputTaskDef = {
270
+ kind: "human_input",
271
+ humanInput: { prompt: "Review", timeout: 5, onTimeout: "approve", outcomes },
272
+ };
273
+
274
+ const state = createState();
275
+ const result = await executeHumanInputTask(taskDef, "gate", state, makeCtx(awaitFn));
276
+
277
+ expect(result).toEqual({
278
+ outcome: "proceed",
279
+ auto_resolved: true,
280
+ reason: "timeout",
281
+ __flow_directive__: "deployStep",
282
+ });
283
+ expect(state.data.gate).toEqual({
284
+ outcome: "proceed",
285
+ auto_resolved: true,
286
+ reason: "timeout",
287
+ });
288
+ });
289
+
290
+ it("maps timeout auto-deny to the LAST outcome", async () => {
291
+ const awaitFn: AwaitHumanInputFn = async () => ({
292
+ outcome: "deny",
293
+ auto_resolved: true,
294
+ reason: "timeout",
295
+ });
296
+
297
+ const taskDef: HumanInputTaskDef = {
298
+ kind: "human_input",
299
+ humanInput: { prompt: "Review", timeout: 5, onTimeout: "deny", outcomes },
300
+ };
301
+
302
+ const result = await executeHumanInputTask(taskDef, "gate", createState(), makeCtx(awaitFn));
303
+
304
+ // "reject" is the last outcome and declares no `then` — no directive.
305
+ expect(result).toEqual({ outcome: "reject", auto_resolved: true, reason: "timeout" });
306
+ });
307
+
308
+ // The escalate outcome-by-name contract (stigmer/stigmer#781): the policy
309
+ // word IS the declared outcome's name, so no positional remapping happens
310
+ // — the ordinary name lookup finds the "escalate" outcome and routes its
311
+ // `then`, regardless of the outcome's position in the list.
312
+ it("resolves timeout escalate to the escalate-named outcome and routes its then", async () => {
313
+ const awaitFn: AwaitHumanInputFn = async () => ({
314
+ outcome: "escalate",
315
+ auto_resolved: true,
316
+ reason: "timeout",
317
+ });
318
+
319
+ const escalateOutcomes = [
320
+ { name: "proceed", label: "Proceed", then: "deployStep" },
321
+ { name: "escalate", label: "Escalate", then: "escalationPath" },
322
+ { name: "reject", label: "Reject" },
323
+ ];
324
+ const taskDef: HumanInputTaskDef = {
325
+ kind: "human_input",
326
+ humanInput: {
327
+ prompt: "Review", timeout: 5, onTimeout: "escalate", outcomes: escalateOutcomes,
328
+ },
329
+ };
330
+
331
+ const state = createState();
332
+ const result = await executeHumanInputTask(taskDef, "gate", state, makeCtx(awaitFn));
333
+
334
+ expect(result).toEqual({
335
+ outcome: "escalate",
336
+ auto_resolved: true,
337
+ reason: "timeout",
338
+ __flow_directive__: "escalationPath",
339
+ });
340
+ expect(state.data.gate).toEqual({
341
+ outcome: "escalate",
342
+ auto_resolved: true,
343
+ reason: "timeout",
344
+ });
345
+ });
346
+
347
+ it("reports the mapped outcome on the approval_resolved event", async () => {
348
+ const emitted: WorkflowEventDescriptor[][] = [];
349
+ const emitFn: EmitEventsFn = async (events) => { emitted.push(events); };
350
+ const awaitFn: AwaitHumanInputFn = async () => ({
351
+ outcome: "approve",
352
+ auto_resolved: true,
353
+ reason: "timeout",
354
+ });
355
+
356
+ const taskDef: HumanInputTaskDef = {
357
+ kind: "human_input",
358
+ humanInput: { prompt: "Review", timeout: 5, onTimeout: "approve", outcomes },
359
+ };
360
+
361
+ await executeHumanInputTask(taskDef, "gate", createState(), makeCtx(awaitFn, emitFn));
362
+
363
+ const resolved = emitted.flat().find((e) => e.type === "approval_resolved");
364
+ expect(resolved).toMatchObject({ outcome: "proceed", autoResolved: true });
365
+ });
366
+
367
+ it("leaves reviewer-selected outcomes untouched", async () => {
368
+ const awaitFn: AwaitHumanInputFn = async () => ({
369
+ outcome: "needs_revision",
370
+ reviewer: "alice",
371
+ });
372
+
373
+ const taskDef: HumanInputTaskDef = {
374
+ kind: "human_input",
375
+ humanInput: { prompt: "Review", timeout: 5, onTimeout: "approve", outcomes },
376
+ };
377
+
378
+ const result = await executeHumanInputTask(taskDef, "gate", createState(), makeCtx(awaitFn));
379
+
380
+ expect(result).toEqual({
381
+ outcome: "needs_revision",
382
+ reviewer: "alice",
383
+ __flow_directive__: "gatherMore",
384
+ });
385
+ });
386
+
387
+ it("keeps plain approve/deny for binary gates without custom outcomes", async () => {
388
+ const awaitFn: AwaitHumanInputFn = async () => ({
389
+ outcome: "approve",
390
+ auto_resolved: true,
391
+ reason: "timeout",
392
+ });
393
+
394
+ const taskDef: HumanInputTaskDef = {
395
+ kind: "human_input",
396
+ humanInput: { prompt: "Review", timeout: 5, onTimeout: "approve" },
397
+ };
398
+
399
+ const result = await executeHumanInputTask(taskDef, "gate", createState(), makeCtx(awaitFn));
400
+
401
+ expect(result).toEqual({ outcome: "approve", auto_resolved: true, reason: "timeout" });
402
+ });
403
+ });
404
+
249
405
  describe("event emission", () => {
250
406
  it("emits approval_requested before blocking with correct fields", async () => {
251
407
  const emitted: WorkflowEventDescriptor[][] = [];
@@ -34,6 +34,7 @@ import type {
34
34
  BackoffConfig,
35
35
  JitterConfig,
36
36
  DurationDef,
37
+ HumanInputTimeoutPolicy,
37
38
  } from "./types.js";
38
39
 
39
40
  const SUPPORTED_DSL_RANGE = ">=1.0.0 <2.0.0";
@@ -548,12 +549,14 @@ function parseAgentCallRunConfig(raw: unknown): AgentCallConfig["run_config"] {
548
549
  }
549
550
  const obj = raw as Record<string, unknown>;
550
551
 
551
- const known = new Set(["model_name", "max_cost_usd", "max_tool_rounds", "service_tier"]);
552
+ const known = new Set([
553
+ "model_name", "max_cost_usd", "max_tool_rounds", "service_tier", "thinking_mode",
554
+ ]);
552
555
  for (const key of Object.keys(obj)) {
553
556
  if (!known.has(key)) {
554
557
  throw new Error(
555
558
  `call:agent 'run_config' has unknown field '${key}' ` +
556
- `(expected: model_name, max_cost_usd, max_tool_rounds, service_tier)`,
559
+ `(expected: model_name, max_cost_usd, max_tool_rounds, service_tier, thinking_mode)`,
557
560
  );
558
561
  }
559
562
  }
@@ -571,12 +574,14 @@ function parseAgentCallRunConfig(raw: unknown): AgentCallConfig["run_config"] {
571
574
  throw new Error("call:agent 'run_config.max_tool_rounds' must be a number >= 0");
572
575
  }
573
576
  const serviceTier = parseServiceTier(obj.service_tier);
577
+ const thinkingMode = parseThinkingMode(obj.thinking_mode);
574
578
 
575
579
  return {
576
580
  model_name: modelName,
577
581
  max_cost_usd: maxCostUsd,
578
582
  max_tool_rounds: maxToolRounds,
579
583
  service_tier: serviceTier,
584
+ thinking_mode: thinkingMode,
580
585
  };
581
586
  }
582
587
 
@@ -608,6 +613,73 @@ function parseServiceTier(raw: unknown): string | undefined {
608
613
  return canonical;
609
614
  }
610
615
 
616
+ /**
617
+ * Maps YAML thinking-mode shorthands to canonical enum names, mirroring
618
+ * SERVICE_TIER_SHORTHANDS (stigmer/stigmer#772). Unknown values are
619
+ * authoring errors: the mode exists to make the served variant
620
+ * deterministic, so a typo must never silently fall back to the default.
621
+ */
622
+ const THINKING_MODE_SHORTHANDS: Record<string, string> = {
623
+ disabled: "THINKING_MODE_DISABLED",
624
+ enabled: "THINKING_MODE_ENABLED",
625
+ };
626
+
627
+ function parseThinkingMode(raw: unknown): string | undefined {
628
+ if (raw === undefined || raw === null) return undefined;
629
+ if (typeof raw !== "string") {
630
+ throw new Error("call:agent 'run_config.thinking_mode' must be a string");
631
+ }
632
+ const canonical =
633
+ THINKING_MODE_SHORTHANDS[raw.toLowerCase()] ??
634
+ (Object.values(THINKING_MODE_SHORTHANDS).includes(raw) ? raw : undefined);
635
+ if (!canonical) {
636
+ throw new Error(
637
+ `call:agent 'run_config.thinking_mode' has unknown value '${raw}' ` +
638
+ `(expected: disabled, enabled)`,
639
+ );
640
+ }
641
+ return canonical;
642
+ }
643
+
644
+ /**
645
+ * Maps on_timeout values to the runner's internal policy words, mirroring
646
+ * SERVICE_TIER_SHORTHANDS. The persisted CNCF YAML carries the proto enum
647
+ * NAMES (the server converter emits `HumanInputTimeoutPolicy.String()`),
648
+ * while hand-written fixtures use the lowercase words — both are accepted.
649
+ * Unknown values are authoring errors: a timeout policy must never silently
650
+ * fall back to fail (stigmer/stigmer#779 — the unvalidated cast let every
651
+ * enum-name policy reach the orchestrator unrecognized, so gates configured
652
+ * to auto-approve/deny failed at their first real timeout instead).
653
+ */
654
+ const ON_TIMEOUT_VOCABULARY: Record<string, HumanInputTimeoutPolicy> = {
655
+ fail: "fail",
656
+ approve: "approve",
657
+ deny: "deny",
658
+ escalate: "escalate",
659
+ HUMAN_INPUT_TIMEOUT_FAIL: "fail",
660
+ HUMAN_INPUT_TIMEOUT_APPROVE: "approve",
661
+ HUMAN_INPUT_TIMEOUT_DENY: "deny",
662
+ HUMAN_INPUT_TIMEOUT_ESCALATE: "escalate",
663
+ };
664
+
665
+ function parseOnTimeout(
666
+ taskName: string,
667
+ raw: unknown,
668
+ ): HumanInputTimeoutPolicy | undefined {
669
+ if (raw === undefined || raw === null) return undefined;
670
+ if (typeof raw !== "string") {
671
+ throw new Error(`human_input task '${taskName}': 'on_timeout' must be a string`);
672
+ }
673
+ const policy = ON_TIMEOUT_VOCABULARY[raw];
674
+ if (!policy) {
675
+ throw new Error(
676
+ `human_input task '${taskName}': unknown on_timeout value '${raw}' ` +
677
+ `(expected: fail, approve, deny, escalate, or a HUMAN_INPUT_TIMEOUT_* enum name)`,
678
+ );
679
+ }
680
+ return policy;
681
+ }
682
+
611
683
  function parseHumanInputConfig(taskName: string, raw: unknown): import("./types.js").HumanInputConfig {
612
684
  if (!raw || typeof raw !== "object") {
613
685
  throw new Error(`human_input task '${taskName}' requires a 'with' configuration block`);
@@ -618,11 +690,30 @@ function parseHumanInputConfig(taskName: string, raw: unknown): import("./types.
618
690
  throw new Error(`human_input task '${taskName}' requires 'prompt' in 'with'`);
619
691
  }
620
692
 
693
+ const outcomes = Array.isArray(obj.outcomes) && obj.outcomes.length > 0
694
+ ? obj.outcomes.map((o: any) => ({ name: o.name, label: o.label, then: o.then }))
695
+ : undefined;
696
+ const onTimeout = parseOnTimeout(taskName, obj.on_timeout);
697
+
698
+ // The escalate policy's outcome-by-name contract (stigmer/stigmer#781):
699
+ // a timeout resolves to the outcome NAMED "escalate", so that outcome must
700
+ // exist and declare where the escalation branch goes. Checked at load so a
701
+ // misconfigured gate fails before it ever waits on a reviewer — the server
702
+ // validator enforces the same shape at apply; this covers hand-written YAML.
703
+ if (onTimeout === "escalate") {
704
+ const escalation = outcomes?.find((o) => o.name === "escalate");
705
+ if (!escalation || !escalation.then) {
706
+ throw new Error(
707
+ `human_input task '${taskName}': on_timeout policy 'escalate' requires ` +
708
+ `an outcome named 'escalate' with 'then' set (the timeout resolves to ` +
709
+ `that outcome and follows its 'then' branch)`,
710
+ );
711
+ }
712
+ }
713
+
621
714
  return {
622
715
  prompt: obj.prompt,
623
- outcomes: Array.isArray(obj.outcomes) && obj.outcomes.length > 0
624
- ? obj.outcomes.map((o: any) => ({ name: o.name, label: o.label, then: o.then }))
625
- : undefined,
716
+ outcomes,
626
717
  formSchema: obj.form_schema && typeof obj.form_schema === "object"
627
718
  ? obj.form_schema as Record<string, unknown>
628
719
  : undefined,
@@ -630,7 +721,7 @@ function parseHumanInputConfig(taskName: string, raw: unknown): import("./types.
630
721
  ? obj.approvers.filter((a: unknown) => typeof a === "string") as string[]
631
722
  : undefined,
632
723
  timeout: typeof obj.timeout === "number" ? obj.timeout : undefined,
633
- onTimeout: (obj.on_timeout as "fail" | "approve" | "deny") ?? undefined,
724
+ onTimeout,
634
725
  // Any JSON shape is a valid payload (expression string, object, array),
635
726
  // so only null/undefined mean "no payload" here.
636
727
  payload: obj.payload ?? undefined,
@@ -2,7 +2,8 @@
2
2
  * Human input task executor — HITL approval gate.
3
3
  *
4
4
  * Pauses workflow execution until a human reviewer responds via signal.
5
- * Supports configurable timeout with policies (fail, auto-approve, auto-deny).
5
+ * Supports configurable timeout with policies (fail, auto-approve,
6
+ * auto-deny, escalate).
6
7
  *
7
8
  * The kernel validates the config and delegates to `ctx.awaitHumanInput()`
8
9
  * which is wired to the Temporal workflow layer's signal/timer selector.
@@ -96,11 +97,14 @@ export async function executeHumanInputTask(
96
97
  ]);
97
98
  }
98
99
 
99
- const result: HumanInputResult = await ctx.awaitHumanInput({
100
- signalName,
101
- timeoutSeconds,
102
- onTimeout,
103
- });
100
+ const result: HumanInputResult = applyTimeoutOutcomeContract(
101
+ await ctx.awaitHumanInput({
102
+ signalName,
103
+ timeoutSeconds,
104
+ onTimeout,
105
+ }),
106
+ config.outcomes,
107
+ );
104
108
 
105
109
  if (ctx.emitEvents) {
106
110
  await ctx.emitEvents([{
@@ -132,6 +136,37 @@ function validateConfig(config: HumanInputConfig, taskName: string): void {
132
136
  }
133
137
  }
134
138
 
139
+ /**
140
+ * Applies the proto contract for timeout auto-resolution with custom
141
+ * outcomes (HumanInputTaskConfig.outcomes doc): auto-approve resolves to
142
+ * the FIRST declared outcome and auto-deny to the LAST, so `then` routing
143
+ * and downstream outcome switches see declared outcome names — never the
144
+ * orchestrator's internal approve/deny words, which a reviewer of a
145
+ * custom-outcome gate was never offered. Binary gates (no custom outcomes)
146
+ * keep the plain approve/deny result.
147
+ *
148
+ * Escalate needs no remapping by construction (stigmer/stigmer#781): its
149
+ * policy word IS the declared outcome's name — the loader (and the server
150
+ * validator) only accept the escalate policy when an outcome named
151
+ * "escalate" with `then` exists, so the caller's ordinary name lookup
152
+ * routes the escalation branch.
153
+ */
154
+ function applyTimeoutOutcomeContract(
155
+ result: HumanInputResult,
156
+ outcomes: HumanInputConfig["outcomes"],
157
+ ): HumanInputResult {
158
+ if (!result.auto_resolved || result.reason !== "timeout" || !outcomes?.length) {
159
+ return result;
160
+ }
161
+ if (result.outcome === "approve") {
162
+ return { ...result, outcome: outcomes[0].name };
163
+ }
164
+ if (result.outcome === "deny") {
165
+ return { ...result, outcome: outcomes[outcomes.length - 1].name };
166
+ }
167
+ return result;
168
+ }
169
+
135
170
  interface ResolvedReviewPayload {
136
171
  /** Resolved payload for inline delivery on the approval_requested event. */
137
172
  readonly payload?: unknown;
@@ -557,14 +557,23 @@ export interface RunWorkflowExecutionConfig {
557
557
 
558
558
  /**
559
559
  * Blocks workflow execution until a human provides input via signal.
560
- * Supports configurable timeout with policies (fail, approve, deny).
560
+ * Supports configurable timeout with policies (fail, approve, deny, escalate).
561
561
  */
562
562
  export type AwaitHumanInputFn = (config: HumanInputExecutionConfig) => Promise<HumanInputResult>;
563
563
 
564
+ /**
565
+ * Internal timeout-policy vocabulary — the loader-normalized form of the
566
+ * proto's HumanInputTimeoutPolicy enum. "escalate" carries the outcome-by-name
567
+ * contract (stigmer/stigmer#781): it is only loadable when the gate declares
568
+ * an outcome named "escalate" with `then` set, and a timeout resolves to that
569
+ * outcome so the existing `then`/flow-directive routing takes the branch.
570
+ */
571
+ export type HumanInputTimeoutPolicy = "fail" | "approve" | "deny" | "escalate";
572
+
564
573
  export interface HumanInputExecutionConfig {
565
574
  readonly signalName: string;
566
575
  readonly timeoutSeconds: number;
567
- readonly onTimeout: "fail" | "approve" | "deny";
576
+ readonly onTimeout: HumanInputTimeoutPolicy;
568
577
  }
569
578
 
570
579
  export interface HumanInputResult {
@@ -602,7 +611,7 @@ export interface HumanInputConfig {
602
611
  readonly formSchema?: Record<string, unknown>;
603
612
  readonly approvers?: readonly string[];
604
613
  readonly timeout?: number;
605
- readonly onTimeout?: "fail" | "approve" | "deny";
614
+ readonly onTimeout?: HumanInputTimeoutPolicy;
606
615
  /**
607
616
  * Material for the reviewer to examine — a whole-value expression
608
617
  * string, or an object/array with embedded expressions. Resolved at
@@ -749,6 +758,12 @@ export interface AgentCallRunConfig {
749
758
  * ("standard"/"fast") to these, mirroring harness.
750
759
  */
751
760
  readonly service_tier?: string;
761
+ /**
762
+ * Canonical ThinkingMode enum name ("THINKING_MODE_DISABLED" /
763
+ * "THINKING_MODE_ENABLED"); the loader maps the YAML shorthands
764
+ * ("disabled"/"enabled") to these, mirroring service_tier (#772).
765
+ */
766
+ readonly thinking_mode?: string;
752
767
  }
753
768
 
754
769
  export interface AgentCallOutputContract {
@@ -7,6 +7,10 @@
7
7
  * - "fail": throws an error
8
8
  * - "approve": returns auto-approved output
9
9
  * - "deny": returns auto-denied output
10
+ * - "escalate": returns the "escalate" outcome — by the outcome-by-name
11
+ * contract (stigmer/stigmer#781) the loader guarantees the gate declares
12
+ * an outcome with that exact name and a `then` branch, so the executor's
13
+ * ordinary outcome routing takes the escalation path.
10
14
  *
11
15
  * Signal payload shape:
12
16
  * { outcome, form_data?, reviewer, reviewer_actor?, responded_at }
@@ -16,7 +20,11 @@
16
20
 
17
21
  import { defineSignal, setHandler, condition } from "@temporalio/workflow";
18
22
 
19
- import type { HumanInputExecutionConfig, HumanInputResult } from "../workflow-engine/types.js";
23
+ import type {
24
+ HumanInputExecutionConfig,
25
+ HumanInputResult,
26
+ HumanInputTimeoutPolicy,
27
+ } from "../workflow-engine/types.js";
20
28
 
21
29
  /**
22
30
  * Orchestrates a human_input task — blocks until signal or timeout.
@@ -51,7 +59,7 @@ export async function orchestrateHumanInput(
51
59
 
52
60
  function handleTimeout(
53
61
  signalName: string,
54
- policy: "fail" | "approve" | "deny",
62
+ policy: HumanInputTimeoutPolicy,
55
63
  ): HumanInputResult {
56
64
  switch (policy) {
57
65
  case "approve":
@@ -68,6 +76,16 @@ function handleTimeout(
68
76
  reason: "timeout",
69
77
  };
70
78
 
79
+ case "escalate":
80
+ // The outcome word IS the declared outcome's name (loader-enforced),
81
+ // so no positional remapping happens downstream — the executor's name
82
+ // lookup routes the escalation branch directly.
83
+ return {
84
+ outcome: "escalate",
85
+ auto_resolved: true,
86
+ reason: "timeout",
87
+ };
88
+
71
89
  case "fail":
72
90
  default:
73
91
  throw new Error(
@@ -1,19 +0,0 @@
1
- /**
2
- * Connect backfill for MCP servers without discovered capabilities.
3
- *
4
- * Thin wrapper around the shared connect-backfill module that preserves
5
- * the cursor-specific McpResolutionResult interface (which includes
6
- * cursorConfig). The core backfill logic lives in shared/connect-backfill.ts.
7
- *
8
- * Non-fatal: if connect fails for any server, the original servers are
9
- * kept and execution continues with empty policies (fail-closed).
10
- */
11
- import type { McpResolutionResult } from "./mcp-resolver.js";
12
- import type { StigmerClient } from "../../client/stigmer-client.js";
13
- import type { McpTransportPosture } from "../../shared/mcp-transport-guard.js";
14
- import type { McpServerUsage } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/spec_pb";
15
- /**
16
- * Run connect backfill and return an updated cursor-specific
17
- * McpResolutionResult with rebuilt cursorConfig.
18
- */
19
- export declare function backfillMcpServersIfNeeded(client: StigmerClient, currentResult: McpResolutionResult, usages: McpServerUsage[], envVars: Record<string, string>, org: string, transportPosture: McpTransportPosture, onHeartbeat?: () => void, secretKeys?: ReadonlySet<string>): Promise<McpResolutionResult>;
@@ -1,27 +0,0 @@
1
- /**
2
- * Connect backfill for MCP servers without discovered capabilities.
3
- *
4
- * Thin wrapper around the shared connect-backfill module that preserves
5
- * the cursor-specific McpResolutionResult interface (which includes
6
- * cursorConfig). The core backfill logic lives in shared/connect-backfill.ts.
7
- *
8
- * Non-fatal: if connect fails for any server, the original servers are
9
- * kept and execution continues with empty policies (fail-closed).
10
- */
11
- import { toCursorMcpConfig } from "./mcp-resolver.js";
12
- import { backfillMcpServersIfNeeded as sharedBackfill } from "../../shared/connect-backfill.js";
13
- /**
14
- * Run connect backfill and return an updated cursor-specific
15
- * McpResolutionResult with rebuilt cursorConfig.
16
- */
17
- export async function backfillMcpServersIfNeeded(client, currentResult, usages, envVars, org, transportPosture, onHeartbeat, secretKeys) {
18
- const updatedServers = await sharedBackfill(client, currentResult.resolvedServers, usages, envVars, org, transportPosture, onHeartbeat, secretKeys);
19
- if (updatedServers === currentResult.resolvedServers) {
20
- return currentResult;
21
- }
22
- return {
23
- cursorConfig: toCursorMcpConfig(updatedServers),
24
- resolvedServers: updatedServers,
25
- };
26
- }
27
- //# sourceMappingURL=connect-backfill.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"connect-backfill.js","sourceRoot":"","sources":["../../../src/activities/execute-cursor/connect-backfill.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAItD,OAAO,EAAE,0BAA0B,IAAI,cAAc,EAAE,MAAM,kCAAkC,CAAC;AAEhG;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,0BAA0B,CAC9C,MAAqB,EACrB,aAAkC,EAClC,MAAwB,EACxB,OAA+B,EAC/B,GAAW,EACX,gBAAqC,EACrC,WAAwB,EACxB,UAAgC;IAEhC,MAAM,cAAc,GAAG,MAAM,cAAc,CACzC,MAAM,EACN,aAAa,CAAC,eAAe,EAC7B,MAAM,EACN,OAAO,EACP,GAAG,EACH,gBAAgB,EAChB,WAAW,EACX,UAAU,CACX,CAAC;IAEF,IAAI,cAAc,KAAK,aAAa,CAAC,eAAe,EAAE,CAAC;QACrD,OAAO,aAAa,CAAC;IACvB,CAAC;IAED,OAAO;QACL,YAAY,EAAE,iBAAiB,CAAC,cAAc,CAAC;QAC/C,eAAe,EAAE,cAAc;KAChC,CAAC;AACJ,CAAC"}