@openclaw/lobster 2026.9.5 → 2026.9.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 (100) hide show
  1. package/node_modules/@clawdbot/lobster/README.md +20 -133
  2. package/node_modules/@clawdbot/lobster/VISION.md +15 -238
  3. package/node_modules/@clawdbot/lobster/bin/clawd.invoke.js +2 -21
  4. package/node_modules/@clawdbot/lobster/bin/invoke.js +19 -0
  5. package/node_modules/@clawdbot/lobster/bin/lobster.js +2 -21
  6. package/node_modules/@clawdbot/lobster/bin/openclaw.invoke.js +2 -24
  7. package/node_modules/@clawdbot/lobster/dist/src/cli.js +32 -48
  8. package/node_modules/@clawdbot/lobster/dist/src/cli.js.map +1 -1
  9. package/node_modules/@clawdbot/lobster/dist/src/command_tokens.js +81 -0
  10. package/node_modules/@clawdbot/lobster/dist/src/command_tokens.js.map +1 -0
  11. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/dedupe.js +1 -12
  12. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/dedupe.js.map +1 -1
  13. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/group_by.js +2 -14
  14. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/group_by.js.map +1 -1
  15. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/llm_invoke.js +1 -337
  16. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/llm_invoke.js.map +1 -1
  17. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/map.js +2 -13
  18. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/map.js.map +1 -1
  19. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/sort.js +4 -21
  20. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/sort.js.map +1 -1
  21. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/state.js +1 -1
  22. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/state.js.map +1 -1
  23. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/template.js +2 -13
  24. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/template.js.map +1 -1
  25. package/node_modules/@clawdbot/lobster/dist/src/commands/workflows/workflows_run.js +0 -18
  26. package/node_modules/@clawdbot/lobster/dist/src/commands/workflows/workflows_run.js.map +1 -1
  27. package/node_modules/@clawdbot/lobster/dist/src/core/llm_accounting.js +304 -0
  28. package/node_modules/@clawdbot/lobster/dist/src/core/llm_accounting.js.map +1 -0
  29. package/node_modules/@clawdbot/lobster/dist/src/core/value_path.js +11 -0
  30. package/node_modules/@clawdbot/lobster/dist/src/core/value_path.js.map +1 -0
  31. package/node_modules/@clawdbot/lobster/dist/src/parser.js +2 -67
  32. package/node_modules/@clawdbot/lobster/dist/src/parser.js.map +1 -1
  33. package/node_modules/@clawdbot/lobster/dist/src/recipes/github/index.js +1 -1
  34. package/node_modules/@clawdbot/lobster/dist/src/recipes/github/pr-monitor.js +5 -64
  35. package/node_modules/@clawdbot/lobster/dist/src/recipes/github/pr-monitor.js.map +1 -1
  36. package/node_modules/@clawdbot/lobster/dist/src/recipes/github/read_pr.js +45 -0
  37. package/node_modules/@clawdbot/lobster/dist/src/recipes/github/read_pr.js.map +1 -0
  38. package/node_modules/@clawdbot/lobster/dist/src/recipes/github/snapshot.js +45 -0
  39. package/node_modules/@clawdbot/lobster/dist/src/recipes/github/snapshot.js.map +1 -0
  40. package/node_modules/@clawdbot/lobster/dist/src/recipes/github/stages/pr-view.js +9 -47
  41. package/node_modules/@clawdbot/lobster/dist/src/recipes/github/stages/pr-view.js.map +1 -1
  42. package/node_modules/@clawdbot/lobster/dist/src/sdk/Lobster.js +18 -135
  43. package/node_modules/@clawdbot/lobster/dist/src/sdk/Lobster.js.map +1 -1
  44. package/node_modules/@clawdbot/lobster/dist/src/sdk/index.js +1 -1
  45. package/node_modules/@clawdbot/lobster/dist/src/sdk/primitives/approve.js +1 -2
  46. package/node_modules/@clawdbot/lobster/dist/src/sdk/primitives/approve.js.map +1 -1
  47. package/node_modules/@clawdbot/lobster/dist/src/sdk/primitives/diff.js +2 -8
  48. package/node_modules/@clawdbot/lobster/dist/src/sdk/primitives/diff.js.map +1 -1
  49. package/node_modules/@clawdbot/lobster/dist/src/sdk/primitives/exec.js +15 -81
  50. package/node_modules/@clawdbot/lobster/dist/src/sdk/primitives/exec.js.map +1 -1
  51. package/node_modules/@clawdbot/lobster/dist/src/sdk/primitives/state.js +3 -9
  52. package/node_modules/@clawdbot/lobster/dist/src/sdk/primitives/state.js.map +1 -1
  53. package/node_modules/@clawdbot/lobster/dist/src/sdk/runtime.js +0 -12
  54. package/node_modules/@clawdbot/lobster/dist/src/sdk/runtime.js.map +1 -1
  55. package/node_modules/@clawdbot/lobster/dist/src/sdk/state_env.js +6 -0
  56. package/node_modules/@clawdbot/lobster/dist/src/sdk/state_env.js.map +1 -0
  57. package/node_modules/@clawdbot/lobster/dist/src/state/atomic_file.js +192 -0
  58. package/node_modules/@clawdbot/lobster/dist/src/state/atomic_file.js.map +1 -0
  59. package/node_modules/@clawdbot/lobster/dist/src/state/store.js +2 -188
  60. package/node_modules/@clawdbot/lobster/dist/src/state/store.js.map +1 -1
  61. package/node_modules/@clawdbot/lobster/dist/src/workflows/dry_run.js +262 -0
  62. package/node_modules/@clawdbot/lobster/dist/src/workflows/dry_run.js.map +1 -0
  63. package/node_modules/@clawdbot/lobster/dist/src/workflows/expressions.js +345 -0
  64. package/node_modules/@clawdbot/lobster/dist/src/workflows/expressions.js.map +1 -0
  65. package/node_modules/@clawdbot/lobster/dist/src/workflows/file.js +21 -1036
  66. package/node_modules/@clawdbot/lobster/dist/src/workflows/file.js.map +1 -1
  67. package/node_modules/@clawdbot/lobster/dist/src/workflows/github_pr_monitor.js +8 -77
  68. package/node_modules/@clawdbot/lobster/dist/src/workflows/github_pr_monitor.js.map +1 -1
  69. package/node_modules/@clawdbot/lobster/dist/src/workflows/graph.js +1 -7
  70. package/node_modules/@clawdbot/lobster/dist/src/workflows/graph.js.map +1 -1
  71. package/node_modules/@clawdbot/lobster/dist/src/workflows/load.js +347 -0
  72. package/node_modules/@clawdbot/lobster/dist/src/workflows/load.js.map +1 -0
  73. package/node_modules/@clawdbot/lobster/dist/src/workflows/types.js +2 -0
  74. package/node_modules/@clawdbot/lobster/dist/src/workflows/types.js.map +1 -0
  75. package/node_modules/@clawdbot/lobster/dist/src/workflows/values.js +93 -0
  76. package/node_modules/@clawdbot/lobster/dist/src/workflows/values.js.map +1 -0
  77. package/node_modules/@clawdbot/lobster/dist/test/cancellation.test.js +1 -0
  78. package/node_modules/@clawdbot/lobster/dist/test/cancellation.test.js.map +1 -1
  79. package/node_modules/@clawdbot/lobster/dist/test/cli_run_errors.test.js +53 -0
  80. package/node_modules/@clawdbot/lobster/dist/test/cli_run_errors.test.js.map +1 -0
  81. package/node_modules/@clawdbot/lobster/dist/test/command_arguments.test.js +82 -0
  82. package/node_modules/@clawdbot/lobster/dist/test/command_arguments.test.js.map +1 -0
  83. package/node_modules/@clawdbot/lobster/dist/test/cost_tracker.test.js +49 -19
  84. package/node_modules/@clawdbot/lobster/dist/test/cost_tracker.test.js.map +1 -1
  85. package/node_modules/@clawdbot/lobster/dist/test/helpers/node_shell_fixture.js +17 -0
  86. package/node_modules/@clawdbot/lobster/dist/test/helpers/node_shell_fixture.js.map +1 -0
  87. package/node_modules/@clawdbot/lobster/dist/test/llm_invoke.test.js +2 -0
  88. package/node_modules/@clawdbot/lobster/dist/test/llm_invoke.test.js.map +1 -1
  89. package/node_modules/@clawdbot/lobster/dist/test/pipeline_resume_state.test.js +65 -0
  90. package/node_modules/@clawdbot/lobster/dist/test/pipeline_resume_state.test.js.map +1 -0
  91. package/node_modules/@clawdbot/lobster/dist/test/process_output_limit.test.js +5 -3
  92. package/node_modules/@clawdbot/lobster/dist/test/process_output_limit.test.js.map +1 -1
  93. package/node_modules/@clawdbot/lobster/dist/test/sdk_exec.test.js +72 -25
  94. package/node_modules/@clawdbot/lobster/dist/test/sdk_exec.test.js.map +1 -1
  95. package/node_modules/@clawdbot/lobster/dist/test/workflow_graph.test.js +1 -1
  96. package/node_modules/@clawdbot/lobster/dist/test/workflow_graph.test.js.map +1 -1
  97. package/node_modules/@clawdbot/lobster/package.json +6 -8
  98. package/package.json +5 -5
  99. package/node_modules/@clawdbot/lobster/dist/src/sdk/token.js +0 -9
  100. package/node_modules/@clawdbot/lobster/dist/src/sdk/token.js.map +0 -1
@@ -0,0 +1,53 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { spawnSync } from "node:child_process";
4
+ import { mkdtemp, rm, writeFile } from "node:fs/promises";
5
+ import { join } from "node:path";
6
+ import { tmpdir } from "node:os";
7
+ for (const kind of ["missing", "directory", "extension"]) {
8
+ test(`tool run reports ${kind} workflow target errors as JSON`, async () => {
9
+ const dir = await mkdtemp(join(tmpdir(), "lobster-target-error-"));
10
+ try {
11
+ const target = kind === "directory"
12
+ ? dir
13
+ : join(dir, kind === "extension" ? "workflow.txt" : "missing.lobster");
14
+ if (kind === "extension")
15
+ await writeFile(target, JSON.stringify({ steps: [{ id: "ok", run: "echo ok" }] }));
16
+ const result = spawnSync(process.execPath, ["bin/lobster.js", "run", "--mode", "tool", "--file", target], { encoding: "utf8" });
17
+ assert.equal(result.status, 2);
18
+ const envelope = JSON.parse(result.stdout);
19
+ assert.equal(envelope.protocolVersion, 1);
20
+ assert.equal(envelope.ok, false);
21
+ assert.equal(envelope.error.type, "parse_error");
22
+ assert.match(envelope.error.message, kind === "missing" ? /ENOENT/ : kind === "directory" ? /not a file/ : /must end in/);
23
+ assert.equal(result.stderr, "");
24
+ }
25
+ finally {
26
+ await rm(dir, { recursive: true, force: true });
27
+ }
28
+ });
29
+ }
30
+ test("human run reports missing workflow without an uncaught stack", async () => {
31
+ const dir = await mkdtemp(join(tmpdir(), "lobster-human-error-"));
32
+ try {
33
+ const result = spawnSync(process.execPath, ["bin/lobster.js", "run", "--file", join(dir, "missing.json")], { encoding: "utf8" });
34
+ assert.equal(result.status, 2);
35
+ assert.equal(result.stdout, "");
36
+ assert.match(result.stderr, /^Parse error: .*ENOENT/);
37
+ assert.doesNotMatch(result.stderr, /at async|node:internal/);
38
+ }
39
+ finally {
40
+ await rm(dir, { recursive: true, force: true });
41
+ }
42
+ });
43
+ for (const flag of ["--help", "-h"]) {
44
+ test(`graph ${flag} shows graph-specific options`, () => {
45
+ const result = spawnSync(process.execPath, ["bin/lobster.js", "graph", flag], {
46
+ encoding: "utf8",
47
+ });
48
+ assert.equal(result.status, 0);
49
+ assert.match(result.stdout, /--format\s+Output format: mermaid \(default\), dot, ascii/);
50
+ assert.equal(result.stderr, "");
51
+ });
52
+ }
53
+ //# sourceMappingURL=cli_run_errors.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli_run_errors.test.js","sourceRoot":"","sources":["../../test/cli_run_errors.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,82 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { execFile } from "node:child_process";
4
+ import { createServer } from "node:http";
5
+ import { promisify } from "node:util";
6
+ import { parsePipeline } from "../src/parser.js";
7
+ import { Lobster, exec } from "../src/sdk/index.js";
8
+ const runFile = promisify(execFile);
9
+ const argvScript = "console.log(JSON.stringify(process.argv.slice(1)))";
10
+ const argvCommand = `${JSON.stringify(process.execPath)} -e '${argvScript}' "" tail`;
11
+ test("pipeline parsing preserves empty positional arguments", () => {
12
+ assert.deepEqual(parsePipeline(`exec node "" ' ' '' tail`)[0].args._, [
13
+ "node",
14
+ "",
15
+ " ",
16
+ "",
17
+ "tail",
18
+ ]);
19
+ });
20
+ test("pipeline parsing preserves an empty quoted option value", () => {
21
+ const args = parsePipeline(`custom --value "" tail`)[0].args;
22
+ assert.equal(args["value"], "");
23
+ assert.deepEqual(args._, ["tail"]);
24
+ });
25
+ test("built CLI passes explicit empty arguments to a child process", async () => {
26
+ const { stdout } = await runFile(process.execPath, [
27
+ "bin/lobster.js",
28
+ "run",
29
+ "--mode",
30
+ "tool",
31
+ `exec --json ${argvCommand}`,
32
+ ]);
33
+ assert.deepEqual(JSON.parse(stdout).output, ["", "tail"]);
34
+ });
35
+ test("SDK exec passes explicit empty arguments to a child process", async () => {
36
+ const result = await new Lobster().pipe(exec(argvCommand)).run();
37
+ assert.equal(result.ok, true, JSON.stringify(result));
38
+ assert.deepEqual(result.output, ["", "tail"]);
39
+ });
40
+ for (const alias of ["openclaw.invoke", "clawd.invoke"]) {
41
+ test(`${alias} shim preserves punctuation and literal pipeline characters`, async () => {
42
+ const expected = {
43
+ text: 'O\'Malley says "hello"\nnext line',
44
+ path: "C:\\folder\\",
45
+ literal: "$HOME `id` | head",
46
+ empty: "",
47
+ };
48
+ const requests = [];
49
+ const server = createServer(async (req, res) => {
50
+ let body = "";
51
+ for await (const chunk of req)
52
+ body += chunk;
53
+ const payload = JSON.parse(body);
54
+ requests.push(payload.args);
55
+ res.setHeader("content-type", "application/json");
56
+ res.end(JSON.stringify({ ok: true, result: [payload.args] }));
57
+ });
58
+ await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
59
+ try {
60
+ const address = server.address();
61
+ assert.ok(address && typeof address === "object");
62
+ const { stdout } = await runFile(process.execPath, [
63
+ `bin/${alias}.js`,
64
+ "--url",
65
+ `http://127.0.0.1:${address.port}`,
66
+ "--tool",
67
+ "proof",
68
+ "--action",
69
+ "read",
70
+ "--args-json",
71
+ JSON.stringify(expected),
72
+ ], { env: { ...process.env, OPENCLAW_TOKEN: "", CLAWD_TOKEN: "" } });
73
+ assert.deepEqual(JSON.parse(stdout), [expected]);
74
+ assert.deepEqual(requests, [expected]);
75
+ }
76
+ finally {
77
+ server.closeAllConnections();
78
+ await new Promise((resolve) => server.close(() => resolve()));
79
+ }
80
+ });
81
+ }
82
+ //# sourceMappingURL=command_arguments.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command_arguments.test.js","sourceRoot":"","sources":["../../test/command_arguments.test.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAC;AAEpD,MAAM,OAAO,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AACpC,MAAM,UAAU,GAAG,oDAAoD,CAAC;AACxE,MAAM,WAAW,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,UAAU,WAAW,CAAC;AAErF,IAAI,CAAC,uDAAuD,EAAE,GAAG,EAAE;IAClE,MAAM,CAAC,SAAS,CAAC,aAAa,CAAC,0BAA0B,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE;QACrE,MAAM;QACN,EAAE;QACF,GAAG;QACH,EAAE;QACF,MAAM;KACN,CAAC,CAAC;AACJ,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,yDAAyD,EAAE,GAAG,EAAE;IACpE,MAAM,IAAI,GAAG,aAAa,CAAC,wBAAwB,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC7D,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC;IAChC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC;AACpC,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,8DAA8D,EAAE,KAAK,IAAI,EAAE;IAC/E,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,QAAQ,EAAE;QAClD,gBAAgB;QAChB,KAAK;QACL,QAAQ;QACR,MAAM;QACN,eAAe,WAAW,EAAE;KAC5B,CAAC,CAAC;IACH,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC;AAC3D,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,6DAA6D,EAAE,KAAK,IAAI,EAAE;IAC9E,MAAM,MAAM,GAAG,MAAM,IAAI,OAAO,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;IACjE,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;IACtD,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC;AAC/C,CAAC,CAAC,CAAC;AAEH,KAAK,MAAM,KAAK,IAAI,CAAC,iBAAiB,EAAE,cAAc,CAAC,EAAE,CAAC;IACzD,IAAI,CAAC,GAAG,KAAK,6DAA6D,EAAE,KAAK,IAAI,EAAE;QACtF,MAAM,QAAQ,GAAG;YAChB,IAAI,EAAE,mCAAmC;YACzC,IAAI,EAAE,cAAc;YACpB,OAAO,EAAE,mBAAmB;YAC5B,KAAK,EAAE,EAAE;SACT,CAAC;QACF,MAAM,QAAQ,GAAc,EAAE,CAAC;QAC/B,MAAM,MAAM,GAAG,YAAY,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;YAC9C,IAAI,IAAI,GAAG,EAAE,CAAC;YACd,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG;gBAAE,IAAI,IAAI,KAAK,CAAC;YAC7C,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YACjC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YAC5B,GAAG,CAAC,SAAS,CAAC,cAAc,EAAE,kBAAkB,CAAC,CAAC;YAClD,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;QAC/D,CAAC,CAAC,CAAC;QACH,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC,CAAC;QAC7E,IAAI,CAAC;YACJ,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC;YACjC,MAAM,CAAC,EAAE,CAAC,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC;YAClD,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,OAAO,CAC/B,OAAO,CAAC,QAAQ,EAChB;gBACC,OAAO,KAAK,KAAK;gBACjB,OAAO;gBACP,oBAAoB,OAAO,CAAC,IAAI,EAAE;gBAClC,QAAQ;gBACR,OAAO;gBACP,UAAU;gBACV,MAAM;gBACN,aAAa;gBACb,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC;aACxB,EACD,EAAE,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,cAAc,EAAE,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,EAAE,CAChE,CAAC;YACF,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;YACjD,MAAM,CAAC,SAAS,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;QACxC,CAAC;gBAAS,CAAC;YACV,MAAM,CAAC,mBAAmB,EAAE,CAAC;YAC7B,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QACrE,CAAC;IACF,CAAC,CAAC,CAAC;AACJ,CAAC"}
@@ -7,7 +7,7 @@ import path from "node:path";
7
7
  import { PassThrough } from "node:stream";
8
8
  import { createDefaultRegistry } from "../src/commands/registry.js";
9
9
  import { CostTracker } from "../src/core/cost_tracker.js";
10
- import { createLlmSpendLedger, llmProvenanceOf, restoreLlmProvenance, } from "../src/commands/stdlib/llm_invoke.js";
10
+ import { createLlmSpendLedger, llmProvenanceOf, restoreLlmProvenance, } from "../src/core/llm_accounting.js";
11
11
  import { runWorkflowFile } from "../src/workflows/file.js";
12
12
  import { decodeToken } from "../src/token.js";
13
13
  test("CostTracker records usage and computes totals", () => {
@@ -121,19 +121,25 @@ async function runWorkflow(workflow, envOverride, llmAdapters) {
121
121
  stderr.on("data", (d) => {
122
122
  stderrOutput += String(d);
123
123
  });
124
- const result = await runWorkflowFile({
125
- filePath,
126
- ctx: {
127
- stdin: process.stdin,
128
- stdout: process.stdout,
129
- stderr,
130
- env: { ...process.env, LOBSTER_STATE_DIR: stateDir, ...envOverride },
131
- mode: "tool",
132
- registry: createDefaultRegistry(),
133
- llmAdapters,
134
- },
135
- });
136
- return { result, stderrOutput };
124
+ try {
125
+ const result = await runWorkflowFile({
126
+ filePath,
127
+ ctx: {
128
+ stdin: process.stdin,
129
+ stdout: process.stdout,
130
+ stderr,
131
+ env: { ...process.env, LOBSTER_STATE_DIR: stateDir, ...envOverride },
132
+ mode: "tool",
133
+ registry: createDefaultRegistry(),
134
+ llmAdapters,
135
+ },
136
+ });
137
+ return { result, stderrOutput };
138
+ }
139
+ finally {
140
+ stderr.destroy();
141
+ await fsp.rm(tmpDir, { recursive: true, force: true });
142
+ }
137
143
  }
138
144
  test("workflow result includes _meta.cost when usage is present", async () => {
139
145
  const { result } = await runWorkflow({
@@ -920,11 +926,6 @@ test("workflow cost tracking bills both live calls a retried step's replays stan
920
926
  await fsp.rm(cacheDir, { recursive: true, force: true });
921
927
  }
922
928
  });
923
- // A gate pauses a run rather than resetting what it spent. The resume is a different run — a
924
- // different process, even — so the call made before the gate is only in the paused run's
925
- // record; if a later step repeats the prompt, it replays and correctly bills nothing. Without
926
- // carrying that record forward, the provider call before the gate would appear in no total at
927
- // all, and a `cost_limit` could be walked past one gate at a time.
928
929
  // A gate pauses a run, it does not reset what the run has spent. The resume is a separate run
929
930
  // with its own accounting, so a call made before the gate lives only in the paused run's
930
931
  // record: if a later step repeats the prompt it replays, correctly bills nothing, and the
@@ -1960,4 +1961,33 @@ for (const loop of [false, true]) {
1960
1961
  assert.equal(calls, 1, "a downstream failure must not hide paid usage from the retry budget");
1961
1962
  });
1962
1963
  }
1964
+ for (const rendered of [false, true]) {
1965
+ for (const passthrough of ["head --n 1", "pick usage"]) {
1966
+ test(`uninterrupted workflow does not rebill ${rendered ? "rendered" : "live"} output through ${passthrough}`, async () => {
1967
+ const provider = await startFakeProvider();
1968
+ const cacheDir = await fsp.mkdtemp(path.join(os.tmpdir(), "lobster-cost-step-reference-"));
1969
+ try {
1970
+ const { result } = await runWorkflow({
1971
+ cost_limit: { max_usd: 0.01, action: "stop" },
1972
+ steps: [
1973
+ {
1974
+ id: "live",
1975
+ pipeline: `llm.invoke --model gpt-4o --prompt Summarize${rendered ? " | json" : ""}`,
1976
+ },
1977
+ { id: "copy", pipeline: passthrough, stdin: "$live.json" },
1978
+ { id: "again", pipeline: "head --n 1", stdin: "$copy.json" },
1979
+ ],
1980
+ }, { OPENCLAW_URL: provider.url, LOBSTER_CACHE_DIR: cacheDir });
1981
+ assert.equal(provider.requests(), 1);
1982
+ assert.equal(result._meta?.cost?.totalInputTokens, 1000);
1983
+ assert.equal(result._meta?.cost?.totalOutputTokens, 500);
1984
+ assert.deepEqual(result._meta?.cost?.byStep.map((entry) => entry.stepId), ["live"]);
1985
+ }
1986
+ finally {
1987
+ await provider.close();
1988
+ await fsp.rm(cacheDir, { recursive: true, force: true });
1989
+ }
1990
+ });
1991
+ }
1992
+ }
1963
1993
  //# sourceMappingURL=cost_tracker.test.js.map