@stigmer/runner 3.1.17 → 3.2.1

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 (37) hide show
  1. package/README.md +1 -1
  2. package/dist/.build-fingerprint +1 -1
  3. package/dist/activities/execute-cursor/cost-guard.d.ts +41 -0
  4. package/dist/activities/execute-cursor/cost-guard.js +50 -0
  5. package/dist/activities/execute-cursor/cost-guard.js.map +1 -0
  6. package/dist/activities/execute-cursor/index.js +30 -0
  7. package/dist/activities/execute-cursor/index.js.map +1 -1
  8. package/dist/activities/execute-cursor/turn-stream.d.ts +10 -1
  9. package/dist/activities/execute-cursor/turn-stream.js +42 -9
  10. package/dist/activities/execute-cursor/turn-stream.js.map +1 -1
  11. package/dist/activities/execute-deep-agent/setup.js +13 -3
  12. package/dist/activities/execute-deep-agent/setup.js.map +1 -1
  13. package/dist/activities/execute-deep-agent/streaming-terminal.d.ts +9 -0
  14. package/dist/activities/execute-deep-agent/streaming-terminal.js +10 -1
  15. package/dist/activities/execute-deep-agent/streaming-terminal.js.map +1 -1
  16. package/dist/main.js +17 -0
  17. package/dist/main.js.map +1 -1
  18. package/dist/preflight.d.ts +46 -0
  19. package/dist/preflight.js +55 -0
  20. package/dist/preflight.js.map +1 -0
  21. package/dist/shared/tool-rounds.d.ts +30 -0
  22. package/dist/shared/tool-rounds.js +49 -0
  23. package/dist/shared/tool-rounds.js.map +1 -0
  24. package/package.json +2 -2
  25. package/src/__tests__/preflight.test.ts +41 -0
  26. package/src/activities/execute-cursor/__tests__/cost-guard.test.ts +64 -0
  27. package/src/activities/execute-cursor/__tests__/turn-stream.test.ts +118 -0
  28. package/src/activities/execute-cursor/cost-guard.ts +56 -0
  29. package/src/activities/execute-cursor/index.ts +30 -0
  30. package/src/activities/execute-cursor/turn-stream.ts +61 -10
  31. package/src/activities/execute-deep-agent/__tests__/streaming-terminal.test.ts +40 -0
  32. package/src/activities/execute-deep-agent/setup.ts +16 -3
  33. package/src/activities/execute-deep-agent/streaming-terminal.ts +11 -1
  34. package/src/main.ts +17 -0
  35. package/src/preflight.ts +59 -0
  36. package/src/shared/__tests__/tool-rounds.test.ts +57 -0
  37. package/src/shared/tool-rounds.ts +56 -0
@@ -0,0 +1,57 @@
1
+ import { describe, expect, it, vi, afterEach } from "vitest";
2
+ import {
3
+ MAX_TOOL_ROUNDS,
4
+ MIN_TOOL_ROUNDS,
5
+ SUPER_STEPS_PER_ROUND,
6
+ resolveRecursionLimit,
7
+ } from "../tool-rounds.js";
8
+
9
+ describe("resolveRecursionLimit", () => {
10
+ afterEach(() => {
11
+ vi.restoreAllMocks();
12
+ });
13
+
14
+ it("returns null for unset (proto contract: 0 = unlimited)", () => {
15
+ expect(resolveRecursionLimit(undefined)).toBeNull();
16
+ expect(resolveRecursionLimit(0)).toBeNull();
17
+ });
18
+
19
+ it("returns null for negative values (treated as unset, never a tiny limit)", () => {
20
+ expect(resolveRecursionLimit(-5)).toBeNull();
21
+ });
22
+
23
+ it("converts rounds to super-steps at the contract's x6 factor", () => {
24
+ expect(resolveRecursionLimit(10)).toBe(60);
25
+ expect(resolveRecursionLimit(100)).toBe(600);
26
+ expect(resolveRecursionLimit(MAX_TOOL_ROUNDS)).toBe(MAX_TOOL_ROUNDS * SUPER_STEPS_PER_ROUND);
27
+ });
28
+
29
+ it("clamps below-range values up to the minimum with a warning", () => {
30
+ const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
31
+
32
+ expect(resolveRecursionLimit(3)).toBe(MIN_TOOL_ROUNDS * SUPER_STEPS_PER_ROUND);
33
+
34
+ expect(warn).toHaveBeenCalledOnce();
35
+ expect(warn.mock.calls[0][0]).toContain("max_tool_rounds=3");
36
+ expect(warn.mock.calls[0][0]).toContain("clamping to 10");
37
+ });
38
+
39
+ it("clamps above-range values down to the maximum with a warning", () => {
40
+ const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
41
+
42
+ expect(resolveRecursionLimit(5000)).toBe(MAX_TOOL_ROUNDS * SUPER_STEPS_PER_ROUND);
43
+
44
+ expect(warn).toHaveBeenCalledOnce();
45
+ expect(warn.mock.calls[0][0]).toContain("max_tool_rounds=5000");
46
+ expect(warn.mock.calls[0][0]).toContain("clamping to 1000");
47
+ });
48
+
49
+ it("does not warn for in-range values", () => {
50
+ const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
51
+
52
+ resolveRecursionLimit(MIN_TOOL_ROUNDS);
53
+ resolveRecursionLimit(MAX_TOOL_ROUNDS);
54
+
55
+ expect(warn).not.toHaveBeenCalled();
56
+ });
57
+ });
@@ -0,0 +1,56 @@
1
+ /**
2
+ * ExecutionConfig.max_tool_rounds → LangGraph recursion_limit resolution.
3
+ *
4
+ * Implements the proto contract (agentexecution/v1/spec.proto):
5
+ * - 0 / unset = unlimited — no recursionLimit is placed on the invoke
6
+ * config, preserving the run-until-done + loop-detection posture.
7
+ * - When set, valid range is 10–1000 rounds; out-of-range values are
8
+ * clamped to the nearest bound with a warning log.
9
+ * - recursion_limit = rounds × 6: each model-tool round consumes multiple
10
+ * graph super-steps because every active middleware hook is its own
11
+ * node; ×6 is the contract's floor estimate.
12
+ *
13
+ * The SAME resolved limit must feed both LangGraph's hard stop (the invoke
14
+ * config) and the ExecutionBudgetMiddleware's wrap-up advisory, so the
15
+ * "~80% of budget" warning and the enforcement point can never disagree.
16
+ */
17
+
18
+ export const SUPER_STEPS_PER_ROUND = 6;
19
+ export const MIN_TOOL_ROUNDS = 10;
20
+ export const MAX_TOOL_ROUNDS = 1000;
21
+
22
+ /**
23
+ * The advisory-only budget used when max_tool_rounds is unset: high enough
24
+ * to never fire on a healthy run. Deliberately NOT enforced as a hard
25
+ * recursionLimit — unset means unlimited per the proto contract.
26
+ */
27
+ export const UNBOUNDED_ADVISORY_RECURSION_LIMIT = 6000;
28
+
29
+ /**
30
+ * The recursionLimit for the invoke config, or null when the execution is
31
+ * unbounded (max_tool_rounds 0/unset — the pre-existing default posture).
32
+ */
33
+ export function resolveRecursionLimit(maxToolRounds: number | undefined): number | null {
34
+ if (!maxToolRounds || maxToolRounds <= 0) {
35
+ return null;
36
+ }
37
+ return clampToolRounds(maxToolRounds) * SUPER_STEPS_PER_ROUND;
38
+ }
39
+
40
+ function clampToolRounds(requested: number): number {
41
+ if (requested < MIN_TOOL_ROUNDS) {
42
+ console.warn(
43
+ `[tool-rounds] max_tool_rounds=${requested} below the valid range ` +
44
+ `(${MIN_TOOL_ROUNDS}-${MAX_TOOL_ROUNDS}); clamping to ${MIN_TOOL_ROUNDS}`,
45
+ );
46
+ return MIN_TOOL_ROUNDS;
47
+ }
48
+ if (requested > MAX_TOOL_ROUNDS) {
49
+ console.warn(
50
+ `[tool-rounds] max_tool_rounds=${requested} above the valid range ` +
51
+ `(${MIN_TOOL_ROUNDS}-${MAX_TOOL_ROUNDS}); clamping to ${MAX_TOOL_ROUNDS}`,
52
+ );
53
+ return MAX_TOOL_ROUNDS;
54
+ }
55
+ return requested;
56
+ }