@sapiom/harness 0.1.5 → 0.1.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 (86) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/dist/cli/bin.js +24 -7
  3. package/dist/cli/bin.js.map +1 -1
  4. package/dist/cli/consent.js +1 -1
  5. package/dist/core/adapters/adapter.d.ts +2 -2
  6. package/dist/core/api-key-provider.d.ts +79 -0
  7. package/dist/core/api-key-provider.d.ts.map +1 -0
  8. package/dist/core/api-key-provider.js +91 -0
  9. package/dist/core/api-key-provider.js.map +1 -0
  10. package/dist/core/canvas-run-state.d.ts +9 -5
  11. package/dist/core/canvas-run-state.d.ts.map +1 -1
  12. package/dist/core/canvas-run-state.js +42 -13
  13. package/dist/core/canvas-run-state.js.map +1 -1
  14. package/dist/core/definition-slug-resolver.d.ts +13 -0
  15. package/dist/core/definition-slug-resolver.d.ts.map +1 -1
  16. package/dist/core/definition-slug-resolver.js +29 -0
  17. package/dist/core/definition-slug-resolver.js.map +1 -1
  18. package/dist/core/render-local-run.d.ts +82 -0
  19. package/dist/core/render-local-run.d.ts.map +1 -0
  20. package/dist/core/render-local-run.js +105 -0
  21. package/dist/core/render-local-run.js.map +1 -0
  22. package/dist/core/render-log-slice.d.ts +30 -0
  23. package/dist/core/render-log-slice.d.ts.map +1 -0
  24. package/dist/core/render-log-slice.js +49 -0
  25. package/dist/core/render-log-slice.js.map +1 -0
  26. package/dist/core/render-run-state.d.ts.map +1 -1
  27. package/dist/core/render-run-state.js +30 -49
  28. package/dist/core/render-run-state.js.map +1 -1
  29. package/dist/core/run-local-bootstrap.d.ts +93 -0
  30. package/dist/core/run-local-bootstrap.d.ts.map +1 -0
  31. package/dist/core/run-local-bootstrap.js +155 -0
  32. package/dist/core/run-local-bootstrap.js.map +1 -0
  33. package/dist/core/run-state.d.ts +16 -1
  34. package/dist/core/run-state.d.ts.map +1 -1
  35. package/dist/core/run-state.js +75 -25
  36. package/dist/core/run-state.js.map +1 -1
  37. package/dist/core/stub-feedback.d.ts +52 -0
  38. package/dist/core/stub-feedback.d.ts.map +1 -0
  39. package/dist/core/stub-feedback.js +33 -0
  40. package/dist/core/stub-feedback.js.map +1 -0
  41. package/dist/server/actions.d.ts +139 -0
  42. package/dist/server/actions.d.ts.map +1 -0
  43. package/dist/server/actions.js +430 -0
  44. package/dist/server/actions.js.map +1 -0
  45. package/dist/server/auth-routes.d.ts +92 -0
  46. package/dist/server/auth-routes.d.ts.map +1 -0
  47. package/dist/server/auth-routes.js +151 -0
  48. package/dist/server/auth-routes.js.map +1 -0
  49. package/dist/server/index.d.ts.map +1 -1
  50. package/dist/server/index.js +0 -0
  51. package/dist/server/index.js.map +1 -1
  52. package/dist/server/rest.d.ts.map +1 -1
  53. package/dist/server/rest.js +0 -2
  54. package/dist/server/rest.js.map +1 -1
  55. package/dist/server/runs.d.ts +16 -15
  56. package/dist/server/runs.d.ts.map +1 -1
  57. package/dist/server/runs.js +15 -80
  58. package/dist/server/runs.js.map +1 -1
  59. package/dist/server/static.d.ts +15 -1
  60. package/dist/server/static.d.ts.map +1 -1
  61. package/dist/server/static.js +54 -4
  62. package/dist/server/static.js.map +1 -1
  63. package/dist/shared/types.d.ts +79 -35
  64. package/dist/shared/types.d.ts.map +1 -1
  65. package/dist/shared/types.js.map +1 -1
  66. package/dist/web/assets/index-DCtyI4VW.css +32 -0
  67. package/dist/web/assets/index-WSw5RvYR.js +445 -0
  68. package/dist/web/canvas/sess-boot/index.html +305 -0
  69. package/dist/web/index.html +4 -9
  70. package/dist/web/welcome-hero-dark.png +0 -0
  71. package/dist/web/welcome-hero-light.png +0 -0
  72. package/package.json +6 -3
  73. package/dist/core/run-spend.d.ts +0 -55
  74. package/dist/core/run-spend.d.ts.map +0 -1
  75. package/dist/core/run-spend.js +0 -109
  76. package/dist/core/run-spend.js.map +0 -1
  77. package/dist/core/run-transactions.d.ts +0 -60
  78. package/dist/core/run-transactions.d.ts.map +0 -1
  79. package/dist/core/run-transactions.js +0 -129
  80. package/dist/core/run-transactions.js.map +0 -1
  81. package/dist/server/skills.d.ts +0 -51
  82. package/dist/server/skills.d.ts.map +0 -1
  83. package/dist/server/skills.js +0 -312
  84. package/dist/server/skills.js.map +0 -1
  85. package/dist/web/assets/index-DM84J83Y.js +0 -234
  86. package/dist/web/assets/index-DemtWW4L.css +0 -32
@@ -0,0 +1,305 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>leasing — demo canvas</title>
7
+ <!--
8
+ Static demo canvas for the GitHub Pages build of Sapiom Harness.
9
+ In the real product this document is generated by the harness server's
10
+ deterministic canvas renderer into .sapiom/canvas/renders/<slug>.html and
11
+ served at /canvas/<sessionId>/. This copy reproduces that renderer's
12
+ CASCADING layout (vertical flow, group bands, labeled branch fork) for the
13
+ demo workspace only — it is not agent output.
14
+
15
+ The document is VISUAL ONLY: workflow name/description/steps belong to the
16
+ app chrome around the iframe. Connector system: 1px hairline verticals
17
+ with one small arrowhead; branch labels are mono chips on the fork.
18
+ -->
19
+ <style>
20
+ :root {
21
+ --bg: #fafafa; --panel: #ffffff; --ink: #141417; --ink-muted: #54545e;
22
+ --ink-faint: #6a6a74; --line: rgba(17,17,20,.08); --line-2: rgba(17,17,20,.15);
23
+ --brand: #167e3a; --brand-wash: rgba(22,126,58,.12); --node: #f2f2f3;
24
+ --dot: rgba(17,17,20,.12);
25
+ }
26
+ [data-theme="dark"] {
27
+ --bg: #0a0a0c; --panel: #101013; --ink: #f4f4f5; --ink-muted: #a7a7b0;
28
+ --ink-faint: #85858f; --line: rgba(255,255,255,.07); --line-2: rgba(255,255,255,.13);
29
+ --brand: #6be195; --brand-wash: rgba(107,225,149,.14); --node: #17171b;
30
+ --dot: rgba(255,255,255,.10);
31
+ }
32
+ * { box-sizing: border-box; }
33
+ html, body { height: 100%; }
34
+ body {
35
+ margin: 0; padding: 0; background: var(--bg); color: var(--ink);
36
+ font: 13px/1.5 "Geist", system-ui, -apple-system, "Segoe UI", sans-serif;
37
+ }
38
+ /* Full-bleed card: the surrounding pane provides the card chrome, so the
39
+ dotted grid runs edge to edge with no inner frame of its own. The
40
+ bottom padding reserves the strip where the app docks its zoom
41
+ controls, so the graph never renders underneath them; the same inset
42
+ is posted to the app as the fit-bounds exclusion (see the size post). */
43
+ .canvas-panel {
44
+ position: relative; min-height: 100%;
45
+ padding: 40px 20px 72px;
46
+ background-image: radial-gradient(var(--dot) 1px, transparent 1px);
47
+ background-size: 16px 16px;
48
+ }
49
+ /* Kept for the markup contract; header text belongs to the app chrome
50
+ around this iframe, so it renders visually hidden here. */
51
+ .canvas-title, .canvas-subtitle {
52
+ position: absolute; width: 1px; height: 1px; overflow: hidden;
53
+ clip: rect(0 0 0 0); margin: 0;
54
+ }
55
+ .canvas-badge {
56
+ /* Top-left: the app's view controls own the frame's top-right corner. */
57
+ position: absolute; top: 10px; left: 10px;
58
+ font: 500 9px/1 "Geist Mono", ui-monospace, monospace;
59
+ padding: 4px 8px; border-radius: 8px;
60
+ background: var(--brand-wash); color: var(--brand); border: 1px solid var(--line);
61
+ }
62
+ /* The graph is the pan/zoom subject: the app posts view messages and this
63
+ wrapper transforms, while the dotted board behind it stays put filling
64
+ the pane (see the view-contract script at the end of body). */
65
+ .cascade { display: flex; flex-direction: column; align-items: center; transform-origin: 50% 0; }
66
+ .group {
67
+ display: flex; flex-direction: column; align-items: center;
68
+ border: 1px dashed var(--line-2); border-radius: 10px;
69
+ padding: 20px 24px 16px; position: relative; min-width: 200px;
70
+ }
71
+ .group + .group { margin-top: 0; }
72
+ .group-label {
73
+ position: absolute; top: -7px; left: 12px;
74
+ font: 500 9px/1 "Geist Mono", ui-monospace, monospace; letter-spacing: .04em;
75
+ color: var(--ink-faint); background: var(--panel); padding: 2px 6px;
76
+ }
77
+ .node {
78
+ background: var(--node); border: 1px solid var(--line); border-radius: 8px;
79
+ padding: 10px 18px; min-width: 128px; text-align: center;
80
+ }
81
+ .node .kind {
82
+ display: block; color: var(--ink-faint);
83
+ font: 500 9px/1.6 "Geist Mono", ui-monospace, monospace; letter-spacing: .02em;
84
+ }
85
+ .node .label { font-weight: 590; font-size: 12px; }
86
+ .node .meta {
87
+ display: block; color: var(--ink-muted);
88
+ font: 500 9px/1.7 "Geist Mono", ui-monospace, monospace;
89
+ }
90
+ .node--entry { border-color: var(--brand); }
91
+ /* Interaction states, driven by app messages (the app's gesture layer sits
92
+ over this iframe, so native :hover can never fire here): hover lifts and
93
+ tints, press settles, select rings while the app's drill-down is open. */
94
+ .node {
95
+ cursor: pointer;
96
+ transition: border-color 130ms ease-out, background 130ms ease-out,
97
+ transform 130ms ease-out, box-shadow 130ms ease-out;
98
+ }
99
+ .node.is-hover, .node:hover {
100
+ border-color: var(--brand);
101
+ background: color-mix(in srgb, var(--brand) 6%, var(--node));
102
+ transform: translateY(-1px);
103
+ }
104
+ .node.is-pressed {
105
+ transform: translateY(0);
106
+ background: color-mix(in srgb, var(--brand) 12%, var(--node));
107
+ }
108
+ /* Selected: keep the ring, and add a soft brand-tinted fill so the picked
109
+ node reads at a glance (between hover 6% and press 12%). Uses the theme's
110
+ own node surface, so contrast holds in light and dark. */
111
+ .node.is-selected {
112
+ border-color: var(--brand);
113
+ box-shadow: 0 0 0 1px var(--brand);
114
+ background: color-mix(in srgb, var(--brand) 10%, var(--node));
115
+ }
116
+ @media (prefers-reduced-motion: reduce) {
117
+ .node { transition: none; }
118
+ .node.is-hover, .node:hover { transform: none; }
119
+ }
120
+ /* Vertical connector: 1px hairline, one small arrowhead. */
121
+ .edge {
122
+ width: 1px; height: 28px; background: var(--line-2);
123
+ position: relative; flex-shrink: 0;
124
+ }
125
+ .edge::after {
126
+ content: ""; position: absolute; bottom: 0; left: -2.5px;
127
+ border: 3px solid transparent; border-top: 4px solid var(--ink-faint);
128
+ }
129
+ /* Branch fork: two curved hairline paths with mono label chips. */
130
+ .fork { position: relative; width: 300px; height: 56px; flex-shrink: 0; }
131
+ .fork svg { position: absolute; inset: 0; width: 100%; height: 100%; }
132
+ .fork path { fill: none; stroke: var(--line-2); stroke-width: 1; }
133
+ .fork-label {
134
+ position: absolute; top: 12px; transform: translateX(-50%);
135
+ font: 500 9px/1 "Geist Mono", ui-monospace, monospace;
136
+ color: var(--ink-faint); background: var(--panel);
137
+ border: 1px solid var(--line); border-radius: 6px; padding: 3px 6px;
138
+ white-space: nowrap;
139
+ }
140
+ .outcomes { display: flex; gap: 24px; }
141
+ </style>
142
+ </head>
143
+ <body>
144
+ <script>
145
+ var theme = new URLSearchParams(location.search).get("theme");
146
+ if (theme === "dark" || theme === "light") document.documentElement.dataset.theme = theme;
147
+ </script>
148
+ <div class="canvas-panel">
149
+ <h1 class="canvas-title">leasing</h1>
150
+ <p class="canvas-subtitle">Deterministic workflow diagram — generated from the project's sapiom.json.</p>
151
+ <span class="canvas-badge">demo render</span>
152
+ <div class="cascade">
153
+ <div class="group">
154
+ <span class="group-label">intake &amp; screening</span>
155
+ <div class="node node--entry" data-node-id="intake"><span class="kind">entry</span><span class="label">intake</span></div>
156
+ <div class="edge"></div>
157
+ <div class="node" data-node-id="screen"><span class="kind">step</span><span class="label">screen</span><span class="meta">1 input</span></div>
158
+ <div class="edge"></div>
159
+ <div class="node" data-node-id="credit-check"><span class="kind">step</span><span class="label">credit-check</span><span class="meta">2 inputs · 30s limit</span></div>
160
+ </div>
161
+ <div class="edge"></div>
162
+ <div class="node" data-node-id="approve"><span class="kind">gate</span><span class="label">approve?</span><span class="meta">1 input · 2 branches</span></div>
163
+ <div class="fork" aria-hidden="true">
164
+ <svg viewBox="0 0 300 56" preserveAspectRatio="none">
165
+ <path d="M150 0 C 150 28, 75 28, 75 56" />
166
+ <path d="M150 0 C 150 28, 225 28, 225 56" />
167
+ </svg>
168
+ <span class="fork-label" style="left: 25%">score ≥ 620</span>
169
+ <span class="fork-label" style="left: 75%">declined</span>
170
+ </div>
171
+ <div class="outcomes">
172
+ <div class="node" data-node-id="draft-lease"><span class="kind">exit</span><span class="label">draft-lease</span><span class="meta">2 inputs</span></div>
173
+ <div class="node" data-node-id="manual-review"><span class="kind">exit</span><span class="label">manual-review</span><span class="meta">2 inputs</span></div>
174
+ </div>
175
+ </div>
176
+ </div>
177
+
178
+ <script type="application/json" id="sapiom-graph">
179
+ {
180
+ "name": "leasing",
181
+ "entry": "intake",
182
+ "nodes": [
183
+ { "id": "intake", "kind": "entry", "label": "intake", "role": "entry", "description": "Logs the incoming order and stamps the time it arrived.", "timeoutMs": null, "inputSchema": null, "capabilities": ["records.read"] },
184
+ { "id": "screen", "kind": "step", "label": "screen", "role": "step", "description": "Runs the applicant against the screening rules.", "timeoutMs": null, "inputSchema": { "type": "object", "properties": { "application": { "type": "object" } }, "required": ["application"] }, "capabilities": ["records.read"] },
185
+ { "id": "credit-check", "kind": "step", "label": "credit-check", "role": "step", "description": "Pulls the credit score used by the approval gate.", "timeoutMs": 30000, "inputSchema": { "type": "object", "properties": { "application": { "type": "object" }, "bureau": { "type": "string" } }, "required": ["application"] }, "capabilities": ["credit.check"] },
186
+ { "id": "approve", "kind": "step", "label": "approve?", "role": "step · branches", "description": "Branches on the score: 620 and above auto-drafts a lease; everything else escalates.", "timeoutMs": null, "inputSchema": { "type": "object", "properties": { "score": { "type": "number" } }, "required": ["score"] }, "capabilities": ["rules.evaluate"] },
187
+ { "id": "draft-lease", "kind": "terminal-success", "label": "draft-lease", "role": "terminal · success", "description": "Auto-drafts the lease and completes.", "timeoutMs": null, "inputSchema": { "type": "object", "properties": { "application": { "type": "object" }, "score": { "type": "number" } }, "required": ["application", "score"] }, "capabilities": ["records.write"] },
188
+ { "id": "manual-review", "kind": "terminal-warn", "label": "manual-review", "role": "terminal · needs attention", "description": "Escalates to a human for manual review.", "timeoutMs": null, "inputSchema": { "type": "object", "properties": { "application": { "type": "object" }, "score": { "type": "number" } }, "required": ["application", "score"] }, "capabilities": ["records.write"] }
189
+ ],
190
+ "edges": [
191
+ { "from": "intake", "to": "screen", "kind": "sequential", "label": "" },
192
+ { "from": "screen", "to": "credit-check", "kind": "sequential", "label": "" },
193
+ { "from": "credit-check", "to": "approve", "kind": "sequential", "label": "" },
194
+ { "from": "approve", "to": "draft-lease", "kind": "branching", "label": "score ≥ 620" },
195
+ { "from": "approve", "to": "manual-review", "kind": "branching", "label": "declined" }
196
+ ],
197
+ "groups": [{ "label": "intake & screening", "nodeIds": ["intake", "screen", "credit-check"] }],
198
+ "warnings": []
199
+ }
200
+ </script>
201
+ <script>
202
+ // View contract: the harness posts {type:"sapiom-canvas:view", zoom, x, y}
203
+ // and the GRAPH transforms in place — the dotted board never moves, so
204
+ // panning reads as moving the diagram across an anchored surface. The
205
+ // graph payload above is posted once so the app's step drill-down works.
206
+ (function () {
207
+ var graphEl = document.getElementById("sapiom-graph");
208
+ if (graphEl) {
209
+ try {
210
+ parent.postMessage({ type: "sapiom-canvas:graph", graph: JSON.parse(graphEl.textContent || "{}") }, "*");
211
+ } catch (err) { /* skip drill-down */ }
212
+ }
213
+
214
+ // Fit contract: post the graph's natural size plus the chrome insets
215
+ // the app must keep clear (the panel's own padding — the bottom strip
216
+ // is where the app docks its zoom controls). The app computes a
217
+ // fit-to-view from these bounds on first render and on pane resizes.
218
+ function postSize() {
219
+ var cascade = document.querySelector(".cascade");
220
+ if (!cascade) return;
221
+ try {
222
+ parent.postMessage({
223
+ type: "sapiom-canvas:size",
224
+ width: cascade.offsetWidth,
225
+ height: cascade.offsetHeight,
226
+ insetTop: 40,
227
+ insetBottom: 72,
228
+ insetX: 20
229
+ }, "*");
230
+ } catch (err) { /* no parent */ }
231
+ }
232
+ // After layout: rAF covers first paint; load covers font swaps that can
233
+ // still nudge the cascade's measured height.
234
+ requestAnimationFrame(postSize);
235
+ window.addEventListener("load", postSize);
236
+
237
+ // Pointer contract: the app's gesture layer owns raw pointer events, so
238
+ // clicks, hovers, and presses all arrive as messages with iframe-relative
239
+ // coordinates. This document hit-tests its own nodes, applies the state
240
+ // classes, and answers picks with the node id (drill-down) and hovers
241
+ // with {type:"sapiom-canvas:hit"} (the app's cursor affordance).
242
+ function nodeAt(x, y) {
243
+ if (typeof x !== "number" || typeof y !== "number" || x < 0 || y < 0) return null;
244
+ var el = document.elementFromPoint(x, y);
245
+ return el && el.closest ? el.closest("[data-node-id]") : null;
246
+ }
247
+ var hoverEl = null;
248
+ var pressEl = null;
249
+ var selectedEl = null;
250
+ window.addEventListener("message", function (event) {
251
+ var data = event.data;
252
+ if (!data) return;
253
+ if (data.type === "sapiom-canvas:pick") {
254
+ var node = nodeAt(data.x, data.y);
255
+ if (node) {
256
+ try {
257
+ parent.postMessage({ type: "sapiom-canvas:node", id: node.getAttribute("data-node-id") }, "*");
258
+ } catch (err) { /* no parent */ }
259
+ }
260
+ } else if (data.type === "sapiom-canvas:hover") {
261
+ var over = nodeAt(data.x, data.y);
262
+ if (over !== hoverEl) {
263
+ if (hoverEl) hoverEl.classList.remove("is-hover");
264
+ if (over) over.classList.add("is-hover");
265
+ hoverEl = over;
266
+ try {
267
+ parent.postMessage({ type: "sapiom-canvas:hit", id: over ? over.getAttribute("data-node-id") : null }, "*");
268
+ } catch (err) { /* no parent */ }
269
+ }
270
+ } else if (data.type === "sapiom-canvas:press") {
271
+ if (pressEl) { pressEl.classList.remove("is-pressed"); pressEl = null; }
272
+ if (data.down) {
273
+ var pressed = nodeAt(data.x, data.y);
274
+ if (pressed) { pressed.classList.add("is-pressed"); pressEl = pressed; }
275
+ }
276
+ } else if (data.type === "sapiom-canvas:select") {
277
+ if (selectedEl) { selectedEl.classList.remove("is-selected"); selectedEl = null; }
278
+ if (typeof data.id === "string") {
279
+ var nodes = document.querySelectorAll("[data-node-id]");
280
+ for (var i = 0; i < nodes.length; i++) {
281
+ if (nodes[i].getAttribute("data-node-id") === data.id) {
282
+ nodes[i].classList.add("is-selected");
283
+ selectedEl = nodes[i];
284
+ break;
285
+ }
286
+ }
287
+ }
288
+ }
289
+ });
290
+
291
+ var graph = document.querySelector(".cascade");
292
+ if (!graph) return;
293
+ window.addEventListener("message", function (event) {
294
+ var data = event.data;
295
+ if (!data || data.type !== "sapiom-canvas:view") return;
296
+ var zoom = typeof data.zoom === "number" ? data.zoom : 1;
297
+ var x = typeof data.x === "number" ? data.x : 0;
298
+ var y = typeof data.y === "number" ? data.y : 0;
299
+ graph.style.transform =
300
+ zoom === 1 && x === 0 && y === 0 ? "" : "translate(" + x + "px, " + y + "px) scale(" + zoom + ")";
301
+ });
302
+ })();
303
+ </script>
304
+ </body>
305
+ </html>
@@ -3,23 +3,18 @@
3
3
  <head>
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <title>Sapiom Harness</title>
6
+ <title>Sapiom Studio</title>
7
7
  <script>
8
8
  // Set the theme before first paint to avoid a flash of the wrong one.
9
9
  // Storage key must match web/src/lib/theme.ts (this can't import it).
10
10
  (function () {
11
11
  var stored = localStorage.getItem("sapiom-harness-theme");
12
- var theme =
13
- stored === "light" || stored === "dark"
14
- ? stored
15
- : window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches
16
- ? "dark"
17
- : "light";
12
+ var theme = stored === "light" || stored === "dark" ? stored : "dark";
18
13
  document.documentElement.dataset.theme = theme;
19
14
  })();
20
15
  </script>
21
- <script type="module" crossorigin src="/assets/index-DM84J83Y.js"></script>
22
- <link rel="stylesheet" crossorigin href="/assets/index-DemtWW4L.css">
16
+ <script type="module" crossorigin src="/assets/index-WSw5RvYR.js"></script>
17
+ <link rel="stylesheet" crossorigin href="/assets/index-DCtyI4VW.css">
23
18
  </head>
24
19
  <body>
25
20
  <div id="root"></div>
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sapiom/harness",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Sapiom Harness — a CLI-launched local web app that runs your coding agent (Claude Code, Codex) in a Sapiom-configured environment: MCP pre-wired, workflows tracked, one-click deploy/run, and a live canvas for previews.",
5
5
  "keywords": [
6
6
  "sapiom",
@@ -47,13 +47,15 @@
47
47
  "open": "^10.1.0",
48
48
  "ws": "^8.18.0",
49
49
  "zod": "^3.25.0",
50
- "@sapiom/agent": "^0.6.4",
51
- "@sapiom/agent-core": "^0.9.5",
50
+ "@sapiom/agent": "^0.6.7",
51
+ "@sapiom/agent-core": "^0.9.9",
52
52
  "@sapiom/analytics-core": "^0.2.1",
53
53
  "@sapiom/mcp": "^0.11.3"
54
54
  },
55
55
  "devDependencies": {
56
56
  "@playwright/test": "^1.61.0",
57
+ "@stryker-mutator/core": "^9.6.1",
58
+ "@stryker-mutator/vitest-runner": "^9.6.1",
57
59
  "@types/express": "^4.17.21",
58
60
  "@types/node": "^20.11.30",
59
61
  "@types/react": "^19.0.0",
@@ -87,6 +89,7 @@
87
89
  "start": "node dist/cli/bin.js",
88
90
  "test": "vitest run",
89
91
  "test:watch": "vitest",
92
+ "test:mutation": "stryker run",
90
93
  "test:ui": "playwright test --config web/e2e/playwright.config.ts",
91
94
  "test:canvas": "playwright test --config e2e/playwright.config.ts",
92
95
  "typecheck": "tsc --noEmit && tsc --noEmit -p web/tsconfig.json",
@@ -1,55 +0,0 @@
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
@@ -1 +0,0 @@
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"}
@@ -1,109 +0,0 @@
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
@@ -1 +0,0 @@
1
- {"version":3,"file":"run-spend.js","sourceRoot":"","sources":["../../src/core/run-spend.ts"],"names":[],"mappings":"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"}
@@ -1,60 +0,0 @@
1
- /**
2
- * run-transactions — fetches the per-call cost drill-down for an execution
3
- * from the core surface and maps it to a {@link RunCall}[] the canvas shows
4
- * when a user clicks a step and asks "why is this costly".
5
- *
6
- * WHY this exists alongside run-spend: `/spend` gives per-STEP aggregates
7
- * (totalUsd + entryCount). This endpoint itemises the individual billable
8
- * capability calls that make up that total (e.g. "renderPdfs $7.95 = 1 sandbox
9
- * call"), which is what makes the debug macros actionable.
10
- *
11
- * WHY provider-agnostic: the raw upstream service name (the provider) is mapped
12
- * to a generic capability label here, server-side, so the browser never
13
- * receives — and this open-source file never hard-codes — a provider or model
14
- * name. See the capability-label heuristic below (keyed on generic op/resource
15
- * tokens, never a brand).
16
- *
17
- * WHY the key stays server-side: identical rationale to run-state / run-spend —
18
- * the Sapiom API key is a harness credential that must never reach the browser.
19
- */
20
- import type { RunCall } from "../shared/types.js";
21
- export type RunCallsResult = {
22
- ok: true;
23
- calls: RunCall[];
24
- } | {
25
- ok: false;
26
- status: number;
27
- error: string;
28
- };
29
- export interface RunTransactionsFetcherOpts {
30
- apiKey: string | null;
31
- /** Override the core base URL (resolved from env by default). Test seam. */
32
- baseUrl?: string;
33
- /** Injectable fetch implementation — defaults to global fetch. Test seam. */
34
- fetchImpl?: typeof fetch;
35
- }
36
- export interface RunTransactionsFetcher {
37
- fetch(executionId: string): Promise<RunCallsResult>;
38
- }
39
- /**
40
- * Map a raw upstream operation/resource to a provider-agnostic capability
41
- * label. Keyed ONLY on generic tokens (message / sandbox / search) and the
42
- * operation verb — deliberately never on a provider/brand name, so this file
43
- * stays clean of internal service names and the label is stable if the
44
- * underlying provider is swapped. Unknown shapes fall back to the operation
45
- * verb, which is itself generic ("create", "execute", …).
46
- */
47
- export declare function capabilityLabel(op: string, resource: string): string;
48
- /**
49
- * Create a fetcher that resolves an execution's per-call cost breakdown from
50
- * the core transactions endpoint. Like the spend fetcher, it is intentionally
51
- * non-throwing: every error path returns a typed {@link RunCallsResult} with
52
- * `ok: false` so the router forwards the status without a try/catch.
53
- *
54
- * Only BILLABLE calls are returned (active captured USD > 0) — the free
55
- * `workflows` orchestration rows are dropped as noise. Each call is attributed
56
- * to `metadata.workflowStepName ?? actionName` so the canvas can match it to a
57
- * step node.
58
- */
59
- export declare function createRunTransactionsFetcher(opts: RunTransactionsFetcherOpts): RunTransactionsFetcher;
60
- //# sourceMappingURL=run-transactions.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"run-transactions.d.ts","sourceRoot":"","sources":["../../src/core/run-transactions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAGlD,MAAM,MAAM,cAAc,GACtB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,OAAO,EAAE,CAAA;CAAE,GAC9B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEjD,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,4EAA4E;IAC5E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,6EAA6E;IAC7E,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B;AAED,MAAM,WAAW,sBAAsB;IACrC,KAAK,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;CACrD;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAQpE;AAeD;;;;;;;;;;GAUG;AACH,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,0BAA0B,GAC/B,sBAAsB,CAyExB"}