@sapiom/harness 0.1.4 → 0.1.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 (62) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/core/canvas-run-state.d.ts +95 -0
  3. package/dist/core/canvas-run-state.d.ts.map +1 -0
  4. package/dist/core/canvas-run-state.js +162 -0
  5. package/dist/core/canvas-run-state.js.map +1 -0
  6. package/dist/core/canvas-svg.d.ts.map +1 -1
  7. package/dist/core/canvas-svg.js +28 -8
  8. package/dist/core/canvas-svg.js.map +1 -1
  9. package/dist/core/canvas-template.d.ts +4 -3
  10. package/dist/core/canvas-template.d.ts.map +1 -1
  11. package/dist/core/canvas-template.js +75 -3
  12. package/dist/core/canvas-template.js.map +1 -1
  13. package/dist/core/definition-slug-resolver.d.ts +33 -0
  14. package/dist/core/definition-slug-resolver.d.ts.map +1 -0
  15. package/dist/core/definition-slug-resolver.js +81 -0
  16. package/dist/core/definition-slug-resolver.js.map +1 -0
  17. package/dist/core/execution-detector.d.ts +59 -0
  18. package/dist/core/execution-detector.d.ts.map +1 -0
  19. package/dist/core/execution-detector.js +110 -0
  20. package/dist/core/execution-detector.js.map +1 -0
  21. package/dist/core/inject/mcp-config.d.ts.map +1 -1
  22. package/dist/core/inject/mcp-config.js +9 -1
  23. package/dist/core/inject/mcp-config.js.map +1 -1
  24. package/dist/core/render-run-state.d.ts +9 -0
  25. package/dist/core/render-run-state.d.ts.map +1 -0
  26. package/dist/core/render-run-state.js +139 -0
  27. package/dist/core/render-run-state.js.map +1 -0
  28. package/dist/core/run-spend.d.ts +55 -0
  29. package/dist/core/run-spend.d.ts.map +1 -0
  30. package/dist/core/run-spend.js +109 -0
  31. package/dist/core/run-spend.js.map +1 -0
  32. package/dist/core/run-state.d.ts +45 -0
  33. package/dist/core/run-state.d.ts.map +1 -0
  34. package/dist/core/run-state.js +72 -0
  35. package/dist/core/run-state.js.map +1 -0
  36. package/dist/core/run-transactions.d.ts +60 -0
  37. package/dist/core/run-transactions.d.ts.map +1 -0
  38. package/dist/core/run-transactions.js +129 -0
  39. package/dist/core/run-transactions.js.map +1 -0
  40. package/dist/core/workflow-registry.d.ts +12 -1
  41. package/dist/core/workflow-registry.d.ts.map +1 -1
  42. package/dist/core/workflow-registry.js +2 -0
  43. package/dist/core/workflow-registry.js.map +1 -1
  44. package/dist/server/index.d.ts.map +1 -1
  45. package/dist/server/index.js +0 -0
  46. package/dist/server/index.js.map +1 -1
  47. package/dist/server/rest.d.ts +5 -0
  48. package/dist/server/rest.d.ts.map +1 -1
  49. package/dist/server/rest.js +36 -10
  50. package/dist/server/rest.js.map +1 -1
  51. package/dist/server/runs.d.ts +33 -0
  52. package/dist/server/runs.d.ts.map +1 -0
  53. package/dist/server/runs.js +124 -0
  54. package/dist/server/runs.js.map +1 -0
  55. package/dist/shared/types.d.ts +90 -0
  56. package/dist/shared/types.d.ts.map +1 -1
  57. package/dist/shared/types.js.map +1 -1
  58. package/dist/web/assets/index-DM84J83Y.js +234 -0
  59. package/dist/web/assets/{index-DRTrwTku.css → index-DemtWW4L.css} +1 -1
  60. package/dist/web/index.html +2 -2
  61. package/package.json +1 -1
  62. package/dist/web/assets/index-BhKGSTTT.js +0 -214
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Serve-time enrichment: resolves a deployed agent's `definitionSlug` from the
3
+ * Sapiom Agents API by its `definitionId`. The registry only knows the id (from
4
+ * `sapiom.json`'s `{ "definitionId": "188" }`) — the slug lives server-side,
5
+ * keyed by id.
6
+ *
7
+ * Resolution is cached in-memory (id→slug is stable once deployed; a slug
8
+ * never changes for a given id) so repeat calls across requests are free.
9
+ * Null resolutions are NOT cached: a transient network failure should be
10
+ * retryable without a server restart.
11
+ *
12
+ * Mirrors `@sapiom/tools`'s DEFAULT_BASE_URL for the base URL resolution.
13
+ */
14
+ /** Returns the agents API base URL, honouring the same env-var precedence as
15
+ * @sapiom/tools's DEFAULT_BASE_URL. */
16
+ export function resolveAgentsBaseUrl() {
17
+ return (process.env.SAPIOM_AGENTS_URL ??
18
+ process.env.SAPIOM_TOOLS_BASE ??
19
+ "https://tools.sapiom.ai");
20
+ }
21
+ /**
22
+ * Creates a resolver that fetches `GET /agents/v1/definitions/<id>` with the
23
+ * caller's API key and returns the `slug` field from the response.
24
+ *
25
+ * Safe to call from any request handler: never throws, returns null on any
26
+ * failure (network, 4xx, missing field, unparseable body).
27
+ */
28
+ export function createDefinitionSlugResolver(opts) {
29
+ const { apiKey, baseUrl = resolveAgentsBaseUrl(), fetchImpl = fetch } = opts;
30
+ const cache = new Map();
31
+ // Each id's resolution failure is logged at most once. resolve() runs on
32
+ // every /api/state and /api/workflows poll and null results are deliberately
33
+ // not cached (so a transient failure stays retryable), so without this a
34
+ // persistent failure — e.g. the harness signed into an account that can't
35
+ // see this agent — would reprint on every poll. One line per id is enough to
36
+ // diagnose why the snippet panel fell back to the project name.
37
+ const loggedFailures = new Set();
38
+ const warnOnce = (definitionId, reason) => {
39
+ if (loggedFailures.has(definitionId))
40
+ return;
41
+ loggedFailures.add(definitionId);
42
+ console.error(`[harness] could not resolve deployed-agent slug for definitionId=${definitionId} ` +
43
+ `at ${baseUrl} (${reason}); the snippet panel will fall back to the project name — ` +
44
+ `check the harness is signed into the account that owns this agent`);
45
+ };
46
+ return {
47
+ async resolve(definitionId) {
48
+ // Not signed in: expected (a harness launched without auth), not a
49
+ // failure worth logging — the panel's project-name fallback covers it.
50
+ if (!apiKey)
51
+ return null;
52
+ const cached = cache.get(definitionId);
53
+ if (cached !== undefined)
54
+ return cached;
55
+ try {
56
+ const url = `${baseUrl}/agents/v1/definitions/${encodeURIComponent(definitionId)}`;
57
+ const response = await fetchImpl(url, {
58
+ headers: { "x-sapiom-api-key": apiKey },
59
+ });
60
+ if (!response.ok) {
61
+ warnOnce(definitionId, `HTTP ${response.status}`);
62
+ return null;
63
+ }
64
+ const body = (await response.json());
65
+ const slug = typeof body.slug === "string" ? body.slug : null;
66
+ if (slug !== null) {
67
+ cache.set(definitionId, slug);
68
+ }
69
+ else {
70
+ warnOnce(definitionId, "response had no string `slug` field");
71
+ }
72
+ return slug;
73
+ }
74
+ catch (err) {
75
+ warnOnce(definitionId, err instanceof Error ? err.message : String(err));
76
+ return null;
77
+ }
78
+ },
79
+ };
80
+ }
81
+ //# sourceMappingURL=definition-slug-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"definition-slug-resolver.js","sourceRoot":"","sources":["../../src/core/definition-slug-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH;wCACwC;AACxC,MAAM,UAAU,oBAAoB;IAClC,OAAO,CACL,OAAO,CAAC,GAAG,CAAC,iBAAiB;QAC7B,OAAO,CAAC,GAAG,CAAC,iBAAiB;QAC7B,yBAAyB,CAC1B,CAAC;AACJ,CAAC;AAMD;;;;;;GAMG;AACH,MAAM,UAAU,4BAA4B,CAAC,IAK5C;IACC,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,oBAAoB,EAAE,EAAE,SAAS,GAAG,KAAK,EAAE,GAAG,IAAI,CAAC;IAC7E,MAAM,KAAK,GAAG,IAAI,GAAG,EAAkB,CAAC;IACxC,yEAAyE;IACzE,6EAA6E;IAC7E,yEAAyE;IACzE,0EAA0E;IAC1E,6EAA6E;IAC7E,gEAAgE;IAChE,MAAM,cAAc,GAAG,IAAI,GAAG,EAAU,CAAC;IACzC,MAAM,QAAQ,GAAG,CAAC,YAAoB,EAAE,MAAc,EAAQ,EAAE;QAC9D,IAAI,cAAc,CAAC,GAAG,CAAC,YAAY,CAAC;YAAE,OAAO;QAC7C,cAAc,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;QACjC,OAAO,CAAC,KAAK,CACX,oEAAoE,YAAY,GAAG;YACjF,MAAM,OAAO,KAAK,MAAM,4DAA4D;YACpF,mEAAmE,CACtE,CAAC;IACJ,CAAC,CAAC;IAEF,OAAO;QACL,KAAK,CAAC,OAAO,CAAC,YAAoB;YAChC,mEAAmE;YACnE,uEAAuE;YACvE,IAAI,CAAC,MAAM;gBAAE,OAAO,IAAI,CAAC;YAEzB,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;YACvC,IAAI,MAAM,KAAK,SAAS;gBAAE,OAAO,MAAM,CAAC;YAExC,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,GAAG,OAAO,0BAA0B,kBAAkB,CAAC,YAAY,CAAC,EAAE,CAAC;gBACnF,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,GAAG,EAAE;oBACpC,OAAO,EAAE,EAAE,kBAAkB,EAAE,MAAM,EAAE;iBACxC,CAAC,CAAC;gBAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;oBACjB,QAAQ,CAAC,YAAY,EAAE,QAAQ,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;oBAClD,OAAO,IAAI,CAAC;gBACd,CAAC;gBAED,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAA4B,CAAC;gBAChE,MAAM,IAAI,GAAG,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;gBAC9D,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;oBAClB,KAAK,CAAC,GAAG,CAAC,YAAY,EAAE,IAAI,CAAC,CAAC;gBAChC,CAAC;qBAAM,CAAC;oBACN,QAAQ,CAAC,YAAY,EAAE,qCAAqC,CAAC,CAAC;gBAChE,CAAC;gBACD,OAAO,IAAI,CAAC;YACd,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,QAAQ,CACN,YAAY,EACZ,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CACjD,CAAC;gBACF,OAAO,IAAI,CAAC;YACd,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Execution detection — mirrors {@link PortDetector} (core/port-detector.ts).
3
+ *
4
+ * A run can start two ways in the harness, and this catches both:
5
+ * 1. The CLI prints `✓ Started execution <executionId>` (`sapiom agents run`).
6
+ * 2. The coding agent starts the run via the MCP run tool — the common path —
7
+ * whose result carries the id as an `executionId` field
8
+ * (`"executionId":"<id>"` / `executionId: <id>`).
9
+ * The detector is fed the SAME tool.call output PortDetector is (server/index.ts
10
+ * `onNormalizedEvent`, including the tool RESPONSE summary), so an MCP-triggered
11
+ * run is caught without changing how runs are triggered. It fires `onExecution`
12
+ * once per distinct (session, executionId); the integrator wires that to
13
+ * broadcast `{ type: "execution.started", … }` on /ws/events.
14
+ *
15
+ * Same streaming-safe design as PortDetector: a match touching the very end of
16
+ * the buffered text is held back (the id may still be growing in the next
17
+ * chunk) and finalized by `flush()`, which the integrator calls right after
18
+ * feeding a discrete, already-complete string (one tool.call field).
19
+ */
20
+ /**
21
+ * Pure parser: every execution id announced in a chunk of CLI/tool output, in
22
+ * order (empty when the chunk announces none). This is the mutation-tested
23
+ * core — the {@link ExecutionDetector} class only adds streaming buffering and
24
+ * per-session dedupe on top of it.
25
+ */
26
+ export declare function parseExecutionIds(text: string): string[];
27
+ /** Where a detected run executes. Only `"prod"` is emitted today: the CLI's
28
+ * `Started execution` line is the prod-run announcement; local runs are
29
+ * rendered from their final result, not polled (see the design). */
30
+ export type ExecutionTarget = "prod" | "local";
31
+ export interface ExecutionDetectorDeps {
32
+ onExecution(harnessSessionId: string, executionId: string, target: ExecutionTarget): void;
33
+ }
34
+ /**
35
+ * Stateful, streaming-safe, and dedupes per (session, executionId) so a run id
36
+ * that reappears in output only ever fires `onExecution` once for the session's
37
+ * lifetime. `reset()` clears a session's state on exit so a reused session id
38
+ * starts clean.
39
+ */
40
+ export declare class ExecutionDetector {
41
+ private readonly deps;
42
+ private readonly buffers;
43
+ private readonly seen;
44
+ constructor(deps: ExecutionDetectorDeps);
45
+ feed(chunk: string, harnessSessionId: string): void;
46
+ /**
47
+ * Finalizes whatever's held back for a session, immediately. Call this right
48
+ * after `feed()` whenever the fed text is a discrete, complete string (one
49
+ * tool.call field): without it, an id landing at the very end — the common
50
+ * shape "✓ Started execution exec_123" — would be held pending forever, since
51
+ * no "next chunk" is coming.
52
+ */
53
+ flush(harnessSessionId: string): void;
54
+ /** Drops buffered/dedupe state for a session — call on session exit so a
55
+ * reused session id starts clean. */
56
+ reset(harnessSessionId: string): void;
57
+ private tryEmit;
58
+ }
59
+ //# sourceMappingURL=execution-detector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"execution-detector.d.ts","sourceRoot":"","sources":["../../src/core/execution-detector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAgBH;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAMxD;AAED;;qEAEqE;AACrE,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,OAAO,CAAC;AAE/C,MAAM,WAAW,qBAAqB;IACpC,WAAW,CACT,gBAAgB,EAAE,MAAM,EACxB,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,eAAe,GACtB,IAAI,CAAC;CACT;AAED;;;;;GAKG;AACH,qBAAa,iBAAiB;IAIhB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAHjC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA6B;IACrD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAkC;gBAE1B,IAAI,EAAE,qBAAqB;IAExD,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,GAAG,IAAI;IAsBnD;;;;;;OAMG;IACH,KAAK,CAAC,gBAAgB,EAAE,MAAM,GAAG,IAAI;IAUrC;0CACsC;IACtC,KAAK,CAAC,gBAAgB,EAAE,MAAM,GAAG,IAAI;IAKrC,OAAO,CAAC,OAAO;CAUhB"}
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Execution detection — mirrors {@link PortDetector} (core/port-detector.ts).
3
+ *
4
+ * A run can start two ways in the harness, and this catches both:
5
+ * 1. The CLI prints `✓ Started execution <executionId>` (`sapiom agents run`).
6
+ * 2. The coding agent starts the run via the MCP run tool — the common path —
7
+ * whose result carries the id as an `executionId` field
8
+ * (`"executionId":"<id>"` / `executionId: <id>`).
9
+ * The detector is fed the SAME tool.call output PortDetector is (server/index.ts
10
+ * `onNormalizedEvent`, including the tool RESPONSE summary), so an MCP-triggered
11
+ * run is caught without changing how runs are triggered. It fires `onExecution`
12
+ * once per distinct (session, executionId); the integrator wires that to
13
+ * broadcast `{ type: "execution.started", … }` on /ws/events.
14
+ *
15
+ * Same streaming-safe design as PortDetector: a match touching the very end of
16
+ * the buffered text is held back (the id may still be growing in the next
17
+ * chunk) and finalized by `flush()`, which the integrator calls right after
18
+ * feeding a discrete, already-complete string (one tool.call field).
19
+ */
20
+ /** Execution ids are the stable handles the executions API mints (e.g.
21
+ * `exec_ab12…`). Id-safe characters only, so a trailing space, newline, or
22
+ * ANSI reset ends the match rather than being captured into the id. */
23
+ const EXECUTION_ID = String.raw `[A-Za-z0-9_-]+`;
24
+ /** Two anchors, one capture group: the CLI's `Started execution <id>` line, or
25
+ * the `executionId` field from the run tool's structured result (quote/colon/
26
+ * space/equals between the key and the id). Both are start-time signals tied to
27
+ * a real run, so a match always refers to a run the user is actually driving. */
28
+ const EXECUTION_PATTERN_SOURCE = String.raw `(?:Started execution |executionId["'\s:=]+)(${EXECUTION_ID})`;
29
+ /** Long enough to hold either anchor ("Started execution " / `executionId":"`)
30
+ * split across a chunk boundary, plus a few id characters either side. */
31
+ const TAIL_SAFETY = 48;
32
+ /**
33
+ * Pure parser: every execution id announced in a chunk of CLI/tool output, in
34
+ * order (empty when the chunk announces none). This is the mutation-tested
35
+ * core — the {@link ExecutionDetector} class only adds streaming buffering and
36
+ * per-session dedupe on top of it.
37
+ */
38
+ export function parseExecutionIds(text) {
39
+ const regex = new RegExp(EXECUTION_PATTERN_SOURCE, "g");
40
+ const ids = [];
41
+ let match;
42
+ while ((match = regex.exec(text)))
43
+ ids.push(match[1]);
44
+ return ids;
45
+ }
46
+ /**
47
+ * Stateful, streaming-safe, and dedupes per (session, executionId) so a run id
48
+ * that reappears in output only ever fires `onExecution` once for the session's
49
+ * lifetime. `reset()` clears a session's state on exit so a reused session id
50
+ * starts clean.
51
+ */
52
+ export class ExecutionDetector {
53
+ constructor(deps) {
54
+ this.deps = deps;
55
+ this.buffers = new Map();
56
+ this.seen = new Map();
57
+ }
58
+ feed(chunk, harnessSessionId) {
59
+ const combined = (this.buffers.get(harnessSessionId) ?? "") + chunk;
60
+ const regex = new RegExp(EXECUTION_PATTERN_SOURCE, "g");
61
+ let match;
62
+ let pendingFrom = null;
63
+ while ((match = regex.exec(combined))) {
64
+ const end = match.index + match[0].length;
65
+ if (end === combined.length) {
66
+ // The id could still be growing (more chars due next chunk) — hold back.
67
+ pendingFrom = match.index;
68
+ break;
69
+ }
70
+ this.tryEmit(harnessSessionId, match[1]);
71
+ }
72
+ const retainFrom = pendingFrom ?? Math.max(0, combined.length - TAIL_SAFETY);
73
+ this.buffers.set(harnessSessionId, combined.slice(retainFrom));
74
+ }
75
+ /**
76
+ * Finalizes whatever's held back for a session, immediately. Call this right
77
+ * after `feed()` whenever the fed text is a discrete, complete string (one
78
+ * tool.call field): without it, an id landing at the very end — the common
79
+ * shape "✓ Started execution exec_123" — would be held pending forever, since
80
+ * no "next chunk" is coming.
81
+ */
82
+ flush(harnessSessionId) {
83
+ const pending = this.buffers.get(harnessSessionId);
84
+ if (!pending)
85
+ return;
86
+ // Re-parse the whole pending buffer (vs an inline loop) — same result, and
87
+ // tryEmit dedupes so a match already emitted by feed() is a harmless no-op.
88
+ for (const id of parseExecutionIds(pending))
89
+ this.tryEmit(harnessSessionId, id);
90
+ this.buffers.delete(harnessSessionId);
91
+ }
92
+ /** Drops buffered/dedupe state for a session — call on session exit so a
93
+ * reused session id starts clean. */
94
+ reset(harnessSessionId) {
95
+ this.buffers.delete(harnessSessionId);
96
+ this.seen.delete(harnessSessionId);
97
+ }
98
+ tryEmit(harnessSessionId, executionId) {
99
+ let ids = this.seen.get(harnessSessionId);
100
+ if (!ids) {
101
+ ids = new Set();
102
+ this.seen.set(harnessSessionId, ids);
103
+ }
104
+ if (ids.has(executionId))
105
+ return;
106
+ ids.add(executionId);
107
+ this.deps.onExecution(harnessSessionId, executionId, "prod");
108
+ }
109
+ }
110
+ //# sourceMappingURL=execution-detector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"execution-detector.js","sourceRoot":"","sources":["../../src/core/execution-detector.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-config.d.ts","sourceRoot":"","sources":["../../../src/core/inject/mcp-config.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,gBAAgB;IAC/B,0EAA0E;IAC1E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;;OAQG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB;iFAC6E;IAC7E,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CACrC,gBAAgB,EAAE,MAAM,EACxB,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,MAAM,CAAC,CA6BjB"}
1
+ {"version":3,"file":"mcp-config.d.ts","sourceRoot":"","sources":["../../../src/core/inject/mcp-config.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,gBAAgB;IAC/B,0EAA0E;IAC1E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;;OAQG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB;iFAC6E;IAC7E,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CACrC,gBAAgB,EAAE,MAAM,EACxB,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,MAAM,CAAC,CAqCjB"}
@@ -25,7 +25,15 @@ export async function generateMcpConfig(harnessSessionId, options = {}) {
25
25
  },
26
26
  "sapiom-dev": {
27
27
  command: "npx",
28
- args: ["-y", "@sapiom/mcp"],
28
+ // Pin the dist-tag (`@latest`) rather than the bare name so npx always
29
+ // resolves the PUBLISHED package from the registry. A bare
30
+ // `@sapiom/mcp` resolves a LOCAL workspace copy whenever the harness
31
+ // runs from inside the sapiom-js monorepo (dogfooding/dev) — whose bin
32
+ // isn't linked, so the server fails to launch ("sapiom-mcp: command
33
+ // not found" → JSON-RPC -32000). A dist-tag spec forces registry
34
+ // resolution and is behaviourally identical to what a real user
35
+ // (outside the monorepo) already gets, so it's a pure robustness fix.
36
+ args: ["-y", "@sapiom/mcp@latest"],
29
37
  ...(devEnv ? { env: devEnv } : {}),
30
38
  },
31
39
  },
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-config.js","sourceRoot":"","sources":["../../../src/core/inject/mcp-config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACvC,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAoBhD;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,gBAAwB,EACxB,UAA4B,EAAE;IAE9B,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,aAAa,IAAI,aAAa,CAAC,SAAS,CAAC,EAAE,gBAAgB,CAAC,CAAC;IACtG,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAEzC,MAAM,iBAAiB,GAAG,OAAO,CAAC,WAAW,IAAI,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC;IAChF,MAAM,MAAM,GAAuC,iBAAiB;QAClE,CAAC,CAAC,EAAE,kBAAkB,EAAE,iBAAiB,EAAE;QAC3C,CAAC,CAAC,SAAS,CAAC;IAEd,MAAM,MAAM,GAAG;QACb,UAAU,EAAE;YACV,MAAM,EAAE;gBACN,IAAI,EAAE,MAAM;gBACZ,GAAG,EAAE,8BAA8B;gBACnC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,WAAW,EAAE,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACxE;YACD,YAAY,EAAE;gBACZ,OAAO,EAAE,KAAK;gBACd,IAAI,EAAE,CAAC,IAAI,EAAE,aAAa,CAAC;gBAC3B,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACnC;SACF;KACF,CAAC;IAEF,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,iBAAiB,CAAC,CAAC;IACnD,yEAAyE;IACzE,oEAAoE;IACpE,MAAM,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACtF,OAAO,QAAQ,CAAC;AAClB,CAAC"}
1
+ {"version":3,"file":"mcp-config.js","sourceRoot":"","sources":["../../../src/core/inject/mcp-config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACvC,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAoBhD;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,gBAAwB,EACxB,UAA4B,EAAE;IAE9B,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,aAAa,IAAI,aAAa,CAAC,SAAS,CAAC,EAAE,gBAAgB,CAAC,CAAC;IACtG,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAEzC,MAAM,iBAAiB,GAAG,OAAO,CAAC,WAAW,IAAI,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC;IAChF,MAAM,MAAM,GAAuC,iBAAiB;QAClE,CAAC,CAAC,EAAE,kBAAkB,EAAE,iBAAiB,EAAE;QAC3C,CAAC,CAAC,SAAS,CAAC;IAEd,MAAM,MAAM,GAAG;QACb,UAAU,EAAE;YACV,MAAM,EAAE;gBACN,IAAI,EAAE,MAAM;gBACZ,GAAG,EAAE,8BAA8B;gBACnC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,WAAW,EAAE,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACxE;YACD,YAAY,EAAE;gBACZ,OAAO,EAAE,KAAK;gBACd,uEAAuE;gBACvE,2DAA2D;gBAC3D,qEAAqE;gBACrE,uEAAuE;gBACvE,oEAAoE;gBACpE,iEAAiE;gBACjE,gEAAgE;gBAChE,sEAAsE;gBACtE,IAAI,EAAE,CAAC,IAAI,EAAE,oBAAoB,CAAC;gBAClC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACnC;SACF;KACF,CAAC;IAEF,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,iBAAiB,CAAC,CAAC;IACnD,yEAAyE;IACzE,oEAAoE;IACpE,MAAM,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACtF,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -0,0 +1,9 @@
1
+ import type { ExecutionProjection } from "@sapiom/agent-core";
2
+ import type { RunView } from "../shared/types.js";
3
+ /**
4
+ * Map a decoded {@link ExecutionProjection} to the {@link RunView} the canvas
5
+ * renders. Order-preserving over `steps`; total (never throws) because the
6
+ * projection is already the decoded, degradation-tolerant shape.
7
+ */
8
+ export declare function renderRunState(decoded: ExecutionProjection): RunView;
9
+ //# sourceMappingURL=render-run-state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render-run-state.d.ts","sourceRoot":"","sources":["../../src/core/render-run-state.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAEV,mBAAmB,EAEpB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,KAAK,EAAE,OAAO,EAAwB,MAAM,oBAAoB,CAAC;AAoHxE;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAMpE"}
@@ -0,0 +1,139 @@
1
+ /**
2
+ * renderRunState — the ONE pure mapper from a decoded {@link ExecutionProjection}
3
+ * to the canvas's {@link RunView} render state.
4
+ *
5
+ * Pure and deterministic: no LLM, no I/O, no clock. Every field is derived from
6
+ * the decoded projection, so the same function drives the polling path today and
7
+ * a future WebSocket push (only the data source swaps, never this mapping). Cost
8
+ * and latency are read straight from the projection and rendered statically — the
9
+ * LLM is only ever invoked later by an explicit debug-macro press, never to
10
+ * compute what's shown here.
11
+ *
12
+ * Honest absence is preserved end to end: a step with no cost on this read gets
13
+ * no `costUsd` (not `0`), a still-running step gets no `latencyMs`, and a step
14
+ * with no logs gets no `logSlice` — matching the SDK's "null cost is honest
15
+ * absence, never a fabricated $0" contract.
16
+ */
17
+ import { isExecutionTerminal } from "@sapiom/agent-core";
18
+ /**
19
+ * Cap on the characters of a step's executor log buffer surfaced in `logSlice`.
20
+ * The TAIL is kept (most recent lines) because failures surface at the end of a
21
+ * log. This is a payload guard on the poll response; the debug-macro context
22
+ * extractor does the final, smaller trim for prompt injection.
23
+ */
24
+ const LOG_SLICE_MAX = 4000;
25
+ /**
26
+ * Fold the run's lifecycle status into the four states the UI distinguishes.
27
+ * Reuses `isExecutionTerminal` (agent-core's single source of truth for "won't
28
+ * advance on its own") so a newly-added terminal status can never be misdrawn as
29
+ * still-running: anything non-terminal (running, paused, queued, unknown) is
30
+ * `running`; the terminal set normalizes `canceled` → `cancelled`.
31
+ */
32
+ function toRunStatus(raw) {
33
+ if (!isExecutionTerminal(raw))
34
+ return "running";
35
+ if (raw === "completed")
36
+ return "completed";
37
+ if (raw === "failed")
38
+ return "failed";
39
+ return "cancelled"; // the only remaining terminal statuses: "cancelled" / "canceled"
40
+ }
41
+ /**
42
+ * Fold a step's raw status into a {@link StepStatus}. Handles the real engine
43
+ * vocabulary (`succeeded`/`threw` from prod agents surface) plus the earlier
44
+ * values (`completed`/`failed`/`cancelled`/`canceled`). A cancelled or threw
45
+ * step folds to `failed` (it did not pass — mirrors run-local.ts); anything
46
+ * not yet running/passed/failed is `pending` (unstarted, queued, or unknown).
47
+ */
48
+ function toStepStatus(raw) {
49
+ if (raw === "completed" || raw === "succeeded")
50
+ return "passed";
51
+ if (raw === "failed" ||
52
+ raw === "threw" ||
53
+ raw === "cancelled" ||
54
+ raw === "canceled")
55
+ return "failed";
56
+ if (raw === "running")
57
+ return "running";
58
+ return "pending";
59
+ }
60
+ /** Captured USD as a number; `undefined` on honest absence (null cost) or an
61
+ * unparseable amount — never a fabricated `0`. */
62
+ function toCostUsd(cost) {
63
+ if (!cost)
64
+ return undefined;
65
+ const n = Number(cost.capturedUsd);
66
+ return Number.isFinite(n) ? n : undefined;
67
+ }
68
+ /** `finishedAt − startedAt` in ms; `undefined` while still running (no finish)
69
+ * or when either timestamp is unparseable. A negative delta (clock skew) is
70
+ * dropped rather than shown as a misleading negative latency. */
71
+ function toLatencyMs(startedAt, finishedAt) {
72
+ if (!startedAt || !finishedAt)
73
+ return undefined;
74
+ const start = Date.parse(startedAt);
75
+ const end = Date.parse(finishedAt);
76
+ if (!Number.isFinite(start) || !Number.isFinite(end))
77
+ return undefined;
78
+ const delta = end - start;
79
+ return delta >= 0 ? delta : undefined;
80
+ }
81
+ /** One executor log entry → a compact line. Accepts the `{ ts, level, msg }`
82
+ * wire shape (or `message`), a bare string, or anything else (stringified). */
83
+ function formatLogEntry(entry) {
84
+ if (typeof entry === "string")
85
+ return entry;
86
+ if (entry !== null && typeof entry === "object") {
87
+ const e = entry;
88
+ const parts = [e.ts, e.level, e.msg ?? e.message].filter((p) => typeof p === "string" || typeof p === "number");
89
+ if (parts.length > 0)
90
+ return parts.map(String).join(" ");
91
+ }
92
+ return String(entry);
93
+ }
94
+ /** Format the executor log buffer into a trimmed, tail-preserving slice, or
95
+ * `undefined` when there are no usable logs. */
96
+ function toLogSlice(logs) {
97
+ if (!Array.isArray(logs) || logs.length === 0)
98
+ return undefined;
99
+ const text = logs.map(formatLogEntry).join("\n").trim();
100
+ if (text === "")
101
+ return undefined;
102
+ return text.length > LOG_SLICE_MAX
103
+ ? text.slice(text.length - LOG_SLICE_MAX)
104
+ : text;
105
+ }
106
+ /** Map one projection step to its render view. Optional fields are assigned only
107
+ * when present so absence stays absent (not `undefined`) in the JSON payload. */
108
+ function toStepView(step) {
109
+ const view = {
110
+ id: step.spanId ?? `step-${step.stepOrder}-${step.attempt}`,
111
+ name: step.stepName,
112
+ status: toStepStatus(step.status),
113
+ };
114
+ const costUsd = toCostUsd(step.cost);
115
+ if (costUsd !== undefined)
116
+ view.costUsd = costUsd;
117
+ const latencyMs = toLatencyMs(step.startedAt, step.finishedAt);
118
+ if (latencyMs !== undefined)
119
+ view.latencyMs = latencyMs;
120
+ if (step.error?.message)
121
+ view.error = step.error.message;
122
+ const logSlice = toLogSlice(step.logs);
123
+ if (logSlice !== undefined)
124
+ view.logSlice = logSlice;
125
+ return view;
126
+ }
127
+ /**
128
+ * Map a decoded {@link ExecutionProjection} to the {@link RunView} the canvas
129
+ * renders. Order-preserving over `steps`; total (never throws) because the
130
+ * projection is already the decoded, degradation-tolerant shape.
131
+ */
132
+ export function renderRunState(decoded) {
133
+ return {
134
+ executionId: decoded.id,
135
+ status: toRunStatus(decoded.status),
136
+ steps: decoded.steps.map(toStepView),
137
+ };
138
+ }
139
+ //# sourceMappingURL=render-run-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render-run-state.js","sourceRoot":"","sources":["../../src/core/render-run-state.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * run-spend — fetches the cost/spend summary for a prod execution from the
3
+ * core surface and maps it to a {@link RunSpend} the canvas can poll.
4
+ *
5
+ * WHY the core surface: the spend endpoint lives at
6
+ * `GET /v1/workflows/executions/:id/spend` on api.sapiom.ai (the CORE
7
+ * capability surface), which is distinct from the agents surface
8
+ * (tools.sapiom.ai) that run-state.ts uses. The same caveat applies:
9
+ * env-consistency with the run's environment matters — both surfaces must be
10
+ * overridden together when pointing at non-prod.
11
+ *
12
+ * WHY the key stays server-side: same rationale as run-state.ts — the Sapiom
13
+ * API key is a harness credential that must never reach the browser. The
14
+ * harness server fetches on behalf of the canvas, so the SPA polls a local
15
+ * `/api/runs/:id/spend` endpoint (no key in the request) rather than calling
16
+ * the upstream surface directly.
17
+ */
18
+ import type { RunSpend } from "../shared/types.js";
19
+ /**
20
+ * Resolve the CORE surface base URL for the spend endpoint.
21
+ *
22
+ * The spend endpoint lives on the CORE host (`api.<env>`), distinct from the
23
+ * agents host run-state.ts uses (`tools.<env>`). Crucially, a run lives in the
24
+ * agents env, and its spend must be read from the MATCHING core env — otherwise
25
+ * a prod run's spend queried against dev 401s. So we DERIVE the core host from
26
+ * the agents host (`tools.<env>` → `api.<env>`) rather than reading
27
+ * `SAPIOM_API_URL`, which in some setups points at a different env than the
28
+ * agents surface. An explicit `SAPIOM_CORE_URL` still wins for full control.
29
+ */
30
+ export declare function resolveCoreBaseUrl(): string;
31
+ export type RunSpendResult = {
32
+ ok: true;
33
+ spend: RunSpend;
34
+ } | {
35
+ ok: false;
36
+ status: number;
37
+ error: string;
38
+ };
39
+ export interface RunSpendFetcherOpts {
40
+ apiKey: string | null;
41
+ baseUrl?: string;
42
+ /** Injectable fetch implementation — defaults to global fetch. Test seam. */
43
+ fetchImpl?: typeof fetch;
44
+ }
45
+ export interface RunSpendFetcher {
46
+ fetch(executionId: string): Promise<RunSpendResult>;
47
+ }
48
+ /**
49
+ * Create a fetcher that resolves an execution's spend/cost from the core
50
+ * surface. The fetcher is intentionally non-throwing: all error paths return
51
+ * a typed {@link RunSpendResult} with `ok: false` so the router can forward
52
+ * the appropriate HTTP status without a try/catch at the call site.
53
+ */
54
+ export declare function createRunSpendFetcher(opts: RunSpendFetcherOpts): RunSpendFetcher;
55
+ //# sourceMappingURL=run-spend.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-spend.d.ts","sourceRoot":"","sources":["../../src/core/run-spend.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAGnD;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAc3C;AAED,MAAM,MAAM,cAAc,GACtB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,QAAQ,CAAA;CAAE,GAC7B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEjD,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,6EAA6E;IAC7E,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;CACrD;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,mBAAmB,GACxB,eAAe,CAsEjB"}
@@ -0,0 +1,109 @@
1
+ /**
2
+ * run-spend — fetches the cost/spend summary for a prod execution from the
3
+ * core surface and maps it to a {@link RunSpend} the canvas can poll.
4
+ *
5
+ * WHY the core surface: the spend endpoint lives at
6
+ * `GET /v1/workflows/executions/:id/spend` on api.sapiom.ai (the CORE
7
+ * capability surface), which is distinct from the agents surface
8
+ * (tools.sapiom.ai) that run-state.ts uses. The same caveat applies:
9
+ * env-consistency with the run's environment matters — both surfaces must be
10
+ * overridden together when pointing at non-prod.
11
+ *
12
+ * WHY the key stays server-side: same rationale as run-state.ts — the Sapiom
13
+ * API key is a harness credential that must never reach the browser. The
14
+ * harness server fetches on behalf of the canvas, so the SPA polls a local
15
+ * `/api/runs/:id/spend` endpoint (no key in the request) rather than calling
16
+ * the upstream surface directly.
17
+ */
18
+ import { resolveAgentsBaseUrl } from "./run-state.js";
19
+ /**
20
+ * Resolve the CORE surface base URL for the spend endpoint.
21
+ *
22
+ * The spend endpoint lives on the CORE host (`api.<env>`), distinct from the
23
+ * agents host run-state.ts uses (`tools.<env>`). Crucially, a run lives in the
24
+ * agents env, and its spend must be read from the MATCHING core env — otherwise
25
+ * a prod run's spend queried against dev 401s. So we DERIVE the core host from
26
+ * the agents host (`tools.<env>` → `api.<env>`) rather than reading
27
+ * `SAPIOM_API_URL`, which in some setups points at a different env than the
28
+ * agents surface. An explicit `SAPIOM_CORE_URL` still wins for full control.
29
+ */
30
+ export function resolveCoreBaseUrl() {
31
+ const override = process.env.SAPIOM_CORE_URL;
32
+ if (override)
33
+ return override;
34
+ const agents = resolveAgentsBaseUrl();
35
+ try {
36
+ const url = new URL(agents);
37
+ if (url.hostname.startsWith("tools.")) {
38
+ url.hostname = `api.${url.hostname.slice("tools.".length)}`;
39
+ return url.origin;
40
+ }
41
+ }
42
+ catch {
43
+ // Unparseable agents URL — fall through to the prod default.
44
+ }
45
+ return "https://api.sapiom.ai";
46
+ }
47
+ /**
48
+ * Create a fetcher that resolves an execution's spend/cost from the core
49
+ * surface. The fetcher is intentionally non-throwing: all error paths return
50
+ * a typed {@link RunSpendResult} with `ok: false` so the router can forward
51
+ * the appropriate HTTP status without a try/catch at the call site.
52
+ */
53
+ export function createRunSpendFetcher(opts) {
54
+ const { apiKey, baseUrl = resolveCoreBaseUrl(), fetchImpl = fetch } = opts;
55
+ return {
56
+ async fetch(executionId) {
57
+ // No API key — do not touch the network; the harness is not signed in.
58
+ if (!apiKey) {
59
+ return {
60
+ ok: false,
61
+ status: 503,
62
+ error: "harness is not signed in to Sapiom",
63
+ };
64
+ }
65
+ let res;
66
+ try {
67
+ res = await fetchImpl(`${baseUrl}/v1/workflows/executions/${encodeURIComponent(executionId)}/spend`, { headers: { "x-api-key": apiKey } });
68
+ }
69
+ catch {
70
+ return { ok: false, status: 502, error: "gateway unreachable" };
71
+ }
72
+ if (res.status === 404) {
73
+ return { ok: false, status: 404, error: "spend not found" };
74
+ }
75
+ if (!res.ok) {
76
+ return {
77
+ ok: false,
78
+ status: 502,
79
+ error: `gateway responded ${res.status}`,
80
+ };
81
+ }
82
+ try {
83
+ const raw = (await res.json());
84
+ // The response is ExecutionSpendDto — we map the capability sub-object
85
+ // fields to RunSpend, falling back to top-level totalUsd when needed.
86
+ const capability = raw.capability;
87
+ const totalUsd = capability?.totalUsd ??
88
+ raw.totalUsd ??
89
+ "0";
90
+ const settleState = capability?.settleState ?? "pending";
91
+ const rawByStep = capability?.byStep ?? [];
92
+ const byStep = rawByStep.map((s) => {
93
+ const step = s;
94
+ return {
95
+ name: step.stepName,
96
+ totalUsd: step.totalUsd,
97
+ entryCount: step.entryCount,
98
+ };
99
+ });
100
+ const spend = { executionId, totalUsd, settleState, byStep };
101
+ return { ok: true, spend };
102
+ }
103
+ catch {
104
+ return { ok: false, status: 502, error: "could not decode spend" };
105
+ }
106
+ },
107
+ };
108
+ }
109
+ //# sourceMappingURL=run-spend.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-spend.js","sourceRoot":"","sources":["../../src/core/run-spend.ts"],"names":[],"mappings":"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"}