@sapiom/harness 0.1.4 → 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 (98) hide show
  1. package/CHANGELOG.md +74 -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 +99 -0
  11. package/dist/core/canvas-run-state.d.ts.map +1 -0
  12. package/dist/core/canvas-run-state.js +191 -0
  13. package/dist/core/canvas-run-state.js.map +1 -0
  14. package/dist/core/canvas-svg.d.ts.map +1 -1
  15. package/dist/core/canvas-svg.js +28 -8
  16. package/dist/core/canvas-svg.js.map +1 -1
  17. package/dist/core/canvas-template.d.ts +4 -3
  18. package/dist/core/canvas-template.d.ts.map +1 -1
  19. package/dist/core/canvas-template.js +75 -3
  20. package/dist/core/canvas-template.js.map +1 -1
  21. package/dist/core/definition-slug-resolver.d.ts +46 -0
  22. package/dist/core/definition-slug-resolver.d.ts.map +1 -0
  23. package/dist/core/definition-slug-resolver.js +110 -0
  24. package/dist/core/definition-slug-resolver.js.map +1 -0
  25. package/dist/core/execution-detector.d.ts +59 -0
  26. package/dist/core/execution-detector.d.ts.map +1 -0
  27. package/dist/core/execution-detector.js +110 -0
  28. package/dist/core/execution-detector.js.map +1 -0
  29. package/dist/core/inject/mcp-config.d.ts.map +1 -1
  30. package/dist/core/inject/mcp-config.js +9 -1
  31. package/dist/core/inject/mcp-config.js.map +1 -1
  32. package/dist/core/render-local-run.d.ts +82 -0
  33. package/dist/core/render-local-run.d.ts.map +1 -0
  34. package/dist/core/render-local-run.js +105 -0
  35. package/dist/core/render-local-run.js.map +1 -0
  36. package/dist/core/render-log-slice.d.ts +30 -0
  37. package/dist/core/render-log-slice.d.ts.map +1 -0
  38. package/dist/core/render-log-slice.js +49 -0
  39. package/dist/core/render-log-slice.js.map +1 -0
  40. package/dist/core/render-run-state.d.ts +9 -0
  41. package/dist/core/render-run-state.d.ts.map +1 -0
  42. package/dist/core/render-run-state.js +120 -0
  43. package/dist/core/render-run-state.js.map +1 -0
  44. package/dist/core/run-local-bootstrap.d.ts +93 -0
  45. package/dist/core/run-local-bootstrap.d.ts.map +1 -0
  46. package/dist/core/run-local-bootstrap.js +155 -0
  47. package/dist/core/run-local-bootstrap.js.map +1 -0
  48. package/dist/core/run-state.d.ts +60 -0
  49. package/dist/core/run-state.d.ts.map +1 -0
  50. package/dist/core/run-state.js +122 -0
  51. package/dist/core/run-state.js.map +1 -0
  52. package/dist/core/stub-feedback.d.ts +52 -0
  53. package/dist/core/stub-feedback.d.ts.map +1 -0
  54. package/dist/core/stub-feedback.js +33 -0
  55. package/dist/core/stub-feedback.js.map +1 -0
  56. package/dist/core/workflow-registry.d.ts +12 -1
  57. package/dist/core/workflow-registry.d.ts.map +1 -1
  58. package/dist/core/workflow-registry.js +2 -0
  59. package/dist/core/workflow-registry.js.map +1 -1
  60. package/dist/server/actions.d.ts +139 -0
  61. package/dist/server/actions.d.ts.map +1 -0
  62. package/dist/server/actions.js +430 -0
  63. package/dist/server/actions.js.map +1 -0
  64. package/dist/server/auth-routes.d.ts +92 -0
  65. package/dist/server/auth-routes.d.ts.map +1 -0
  66. package/dist/server/auth-routes.js +151 -0
  67. package/dist/server/auth-routes.js.map +1 -0
  68. package/dist/server/index.d.ts.map +1 -1
  69. package/dist/server/index.js +0 -0
  70. package/dist/server/index.js.map +1 -1
  71. package/dist/server/rest.d.ts +5 -0
  72. package/dist/server/rest.d.ts.map +1 -1
  73. package/dist/server/rest.js +36 -12
  74. package/dist/server/rest.js.map +1 -1
  75. package/dist/server/runs.d.ts +34 -0
  76. package/dist/server/runs.d.ts.map +1 -0
  77. package/dist/server/runs.js +59 -0
  78. package/dist/server/runs.js.map +1 -0
  79. package/dist/server/static.d.ts +15 -1
  80. package/dist/server/static.d.ts.map +1 -1
  81. package/dist/server/static.js +54 -4
  82. package/dist/server/static.js.map +1 -1
  83. package/dist/shared/types.d.ts +136 -2
  84. package/dist/shared/types.d.ts.map +1 -1
  85. package/dist/shared/types.js.map +1 -1
  86. package/dist/web/assets/index-DCtyI4VW.css +32 -0
  87. package/dist/web/assets/index-WSw5RvYR.js +445 -0
  88. package/dist/web/canvas/sess-boot/index.html +305 -0
  89. package/dist/web/index.html +4 -9
  90. package/dist/web/welcome-hero-dark.png +0 -0
  91. package/dist/web/welcome-hero-light.png +0 -0
  92. package/package.json +6 -3
  93. package/dist/server/skills.d.ts +0 -51
  94. package/dist/server/skills.d.ts.map +0 -1
  95. package/dist/server/skills.js +0 -312
  96. package/dist/server/skills.js.map +0 -1
  97. package/dist/web/assets/index-BhKGSTTT.js +0 -214
  98. package/dist/web/assets/index-DRTrwTku.css +0 -32
@@ -0,0 +1,120 @@
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).
8
+ * Latency is 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 still-running step gets no
13
+ * `latencyMs`, and a step with no logs gets no `logSlice`. The inspector
14
+ * surfaces logs, latency, and pass/fail only — no cost.
15
+ */
16
+ import { isExecutionTerminal } from "@sapiom/agent-core";
17
+ // The log-buffer formatter lives in its own dependency-free module so the
18
+ // local-run mapper (imported by the browser SPA) can share the SAME formatter
19
+ // WITHOUT dragging this file's `@sapiom/agent-core` runtime import — and its
20
+ // `node:fs` reach — into the web bundle.
21
+ import { toLogSlice } from "./render-log-slice.js";
22
+ /**
23
+ * Fold the run's lifecycle status into the four states the UI distinguishes.
24
+ * Reuses `isExecutionTerminal` (agent-core's single source of truth for "won't
25
+ * advance on its own") so a newly-added terminal status can never be misdrawn as
26
+ * still-running: anything non-terminal (running, paused, queued, unknown) is
27
+ * `running`; the terminal set normalizes `canceled` → `cancelled`.
28
+ */
29
+ function toRunStatus(raw) {
30
+ if (!isExecutionTerminal(raw))
31
+ return "running";
32
+ if (raw === "completed")
33
+ return "completed";
34
+ if (raw === "failed")
35
+ return "failed";
36
+ return "cancelled"; // the only remaining terminal statuses: "cancelled" / "canceled"
37
+ }
38
+ /**
39
+ * Fold a step's raw status into a {@link StepStatus}. Handles the real engine
40
+ * vocabulary (`succeeded`/`threw` from prod agents surface) plus the earlier
41
+ * values (`completed`/`failed`/`cancelled`/`canceled`). A cancelled or threw
42
+ * step folds to `failed` (it did not pass — mirrors run-local.ts); anything
43
+ * not yet running/passed/failed is `pending` (unstarted, queued, or unknown).
44
+ */
45
+ function toStepStatus(raw) {
46
+ if (raw === "completed" || raw === "succeeded")
47
+ return "passed";
48
+ if (raw === "failed" ||
49
+ raw === "threw" ||
50
+ raw === "cancelled" ||
51
+ raw === "canceled")
52
+ return "failed";
53
+ if (raw === "running")
54
+ return "running";
55
+ return "pending";
56
+ }
57
+ /** `finishedAt − startedAt` in ms; `undefined` while still running (no finish)
58
+ * or when either timestamp is unparseable. A negative delta (clock skew) is
59
+ * dropped rather than shown as a misleading negative latency. */
60
+ function toLatencyMs(startedAt, finishedAt) {
61
+ if (!startedAt || !finishedAt)
62
+ return undefined;
63
+ const start = Date.parse(startedAt);
64
+ const end = Date.parse(finishedAt);
65
+ if (!Number.isFinite(start) || !Number.isFinite(end))
66
+ return undefined;
67
+ const delta = end - start;
68
+ return delta >= 0 ? delta : undefined;
69
+ }
70
+ /** Map one projection step to its render view. Optional fields are assigned only
71
+ * when present so absence stays absent (not `undefined`) in the JSON payload. */
72
+ function toStepView(step) {
73
+ const view = {
74
+ id: step.spanId ?? `step-${step.stepOrder}-${step.attempt}`,
75
+ name: step.stepName,
76
+ status: toStepStatus(step.status),
77
+ };
78
+ const latencyMs = toLatencyMs(step.startedAt, step.finishedAt);
79
+ if (latencyMs !== undefined)
80
+ view.latencyMs = latencyMs;
81
+ if (step.error?.message)
82
+ view.error = step.error.message;
83
+ const logSlice = toLogSlice(step.logs);
84
+ if (logSlice !== undefined)
85
+ view.logSlice = logSlice;
86
+ // Real per-step IO for the inspector's "Last run" block. The decoder
87
+ // collapses an absent input/output to `null` (its absence sentinel), so a
88
+ // `null` here means "the read carried nothing" — surfaced as ABSENT, never a
89
+ // fabricated payload. Any non-null value (including `0`, `false`, `""`) is a
90
+ // real payload and passes through. Capability, not model: these are the
91
+ // step's own values, with no provider/model surfaced.
92
+ if (step.input !== null)
93
+ view.input = step.input;
94
+ if (step.output !== null)
95
+ view.output = step.output;
96
+ // `step.events` are capability execution events forwarded by dispatched
97
+ // capabilities (e.g. tool_use / thinking / result events from a coding run).
98
+ // They do NOT represent dotted workflow capability calls (search.webSearch,
99
+ // memory.append, etc.) with args/results in the StepCall format, so we
100
+ // cannot map them to StepView.calls without fabricating structure that isn't
101
+ // there. `calls` is left absent for prod steps. The inspector's
102
+ // input/output/logs already carry the step-level evidence for prod runs;
103
+ // per-call detail is a fast-follow that requires a server-side addition to
104
+ // the projection shape (emitting dotted-capability call records alongside
105
+ // their args/results at the step level).
106
+ return view;
107
+ }
108
+ /**
109
+ * Map a decoded {@link ExecutionProjection} to the {@link RunView} the canvas
110
+ * renders. Order-preserving over `steps`; total (never throws) because the
111
+ * projection is already the decoded, degradation-tolerant shape.
112
+ */
113
+ export function renderRunState(decoded) {
114
+ return {
115
+ executionId: decoded.id,
116
+ status: toRunStatus(decoded.status),
117
+ steps: decoded.steps.map(toStepView),
118
+ };
119
+ }
120
+ //# 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,93 @@
1
+ /**
2
+ * run-local bootstrap — the child-process entrypoint behind
3
+ * `POST /api/runs/local`.
4
+ *
5
+ * It runs an agent entirely in-process against stub capabilities
6
+ * (`runLocalFromDir` from @sapiom/agent-core) and writes the result as an
7
+ * NDJSON stream to stdout: one line per {@link LocalStepTrace}, then a single
8
+ * terminal summary line `{ outcome, output, error, unusedStubs, stubWarnings }`.
9
+ * Fully offline and zero-cost — run-local resolves every `ctx.sapiom.*` call
10
+ * from stubs and never touches the network.
11
+ *
12
+ * Why a separate child process (not an in-process import):
13
+ * 1. `runLocalFromDir` esbuild-bundles and dynamically `import()`s a workflow
14
+ * project the harness doesn't control, so a stray top-level side effect,
15
+ * infinite loop, or crash in someone else's step body is bounded to this
16
+ * one child instead of taking the long-lived harness server down.
17
+ * 2. It sidesteps a Vite/Vitest limitation: the dynamic `import(\`file://…\`)`
18
+ * inside the loader gets intercepted by the SSR dynamic-import-vars
19
+ * transform when pulled into a Vitest module graph, and mishandles the
20
+ * tmpdir `file://` URL on darwin. A plain child `node` process never goes
21
+ * through that transform. (Mirrors the reasoning in canvas-manifest-check.)
22
+ *
23
+ * Contract with the route ({@link createActionsRouter}):
24
+ * - Request arrives as one JSON object on **stdin**: `{ sourceDir, input?,
25
+ * stubs?, maxAttemptsPerStep? }`.
26
+ * - Output is line-oriented JSON on **stdout** — the route forwards each line
27
+ * through unchanged, so the shapes here ARE the wire shapes the SPA parses.
28
+ * - Diagnostics go to **stderr**; the route keeps a bounded tail for failures.
29
+ * - Exit 0 once the terminal line is written (even for a failed run — a failed
30
+ * *run* is a successful *invocation*); exit 1 only when no run happened
31
+ * (bad request, load error) after writing a terminal `error` line.
32
+ */
33
+ import { type LocalRunOutcome, type StubFile } from "@sapiom/agent-core";
34
+ /** The request shape the route writes to this child's stdin. */
35
+ export interface RunLocalRequest {
36
+ /** Absolute path to the agent project directory (contains `index.ts`). */
37
+ sourceDir: string;
38
+ /** The workflow's entry-step input (optional; agent-core defaults it). */
39
+ input?: unknown;
40
+ /** Explicit stub overrides. When omitted the project's committed
41
+ * `.sapiom-dev/stubs.json` is used (agent-core's own precedence). */
42
+ stubs?: StubFile;
43
+ /** Per-step attempt cap (optional; agent-core defaults it). */
44
+ maxAttemptsPerStep?: number;
45
+ }
46
+ /**
47
+ * The terminal NDJSON line, written after every per-step trace line. Carries
48
+ * the run-level outcome and the two stub-hygiene signals the inspector surfaces
49
+ * (WB15-2): `unusedStubs` (supplied keys that matched no capability call) and
50
+ * `stubWarnings` (keys that matched but carried the wrong shape).
51
+ *
52
+ * `kind: "summary"` discriminates it from a step-trace line so a consumer never
53
+ * has to guess which line is terminal.
54
+ */
55
+ export interface RunLocalSummaryLine {
56
+ kind: "summary";
57
+ outcome: LocalRunOutcome;
58
+ output?: unknown;
59
+ error?: unknown;
60
+ unusedStubs: Array<{
61
+ step: string;
62
+ key: string;
63
+ }>;
64
+ stubWarnings: string[];
65
+ }
66
+ /**
67
+ * The terminal line written when the run could not be *invoked* at all — a
68
+ * malformed request, an unreadable stub file, a project that fails to load.
69
+ * `outcome` is always `"failed"`; the child then exits non-zero. Distinct from
70
+ * a `summary` line, which represents a run that actually executed (even if that
71
+ * run's own outcome was `failed`).
72
+ */
73
+ export interface RunLocalErrorLine {
74
+ kind: "error";
75
+ outcome: "failed";
76
+ error: string;
77
+ }
78
+ /**
79
+ * Parse the stdin payload into a {@link RunLocalRequest}. Throws a plain Error
80
+ * with a caller-safe message (no key material, no provider names) on anything
81
+ * that isn't a JSON object carrying a non-empty string `sourceDir`.
82
+ */
83
+ export declare function parseRunLocalRequest(raw: string): RunLocalRequest;
84
+ /**
85
+ * Run the request and emit its NDJSON stream to `out`. Returns the process exit
86
+ * code: 0 when a run executed (any outcome), 1 when the run could not be
87
+ * invoked (a terminal `error` line is emitted first). Never throws — every
88
+ * failure becomes an in-band terminal line, because a half-written stream with
89
+ * a thrown stack on stderr is far harder for the route to reason about than a
90
+ * clean terminal line plus an exit code.
91
+ */
92
+ export declare function runBootstrap(request: RunLocalRequest, out: NodeJS.WritableStream): Promise<number>;
93
+ //# sourceMappingURL=run-local-bootstrap.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-local-bootstrap.d.ts","sourceRoot":"","sources":["../../src/core/run-local-bootstrap.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,OAAO,EAGL,KAAK,eAAe,EACpB,KAAK,QAAQ,EACd,MAAM,oBAAoB,CAAC;AAE5B,gEAAgE;AAChE,MAAM,WAAW,eAAe;IAC9B,0EAA0E;IAC1E,SAAS,EAAE,MAAM,CAAC;IAClB,0EAA0E;IAC1E,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB;0EACsE;IACtE,KAAK,CAAC,EAAE,QAAQ,CAAC;IACjB,+DAA+D;IAC/D,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,SAAS,CAAC;IAChB,OAAO,EAAE,eAAe,CAAC;IACzB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,WAAW,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAClD,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,QAAQ,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;CACf;AAWD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,CAwBjE;AAYD;;;;;;;GAOG;AACH,wBAAsB,YAAY,CAChC,OAAO,EAAE,eAAe,EACxB,GAAG,EAAE,MAAM,CAAC,cAAc,GACzB,OAAO,CAAC,MAAM,CAAC,CAkCjB"}
@@ -0,0 +1,155 @@
1
+ /**
2
+ * run-local bootstrap — the child-process entrypoint behind
3
+ * `POST /api/runs/local`.
4
+ *
5
+ * It runs an agent entirely in-process against stub capabilities
6
+ * (`runLocalFromDir` from @sapiom/agent-core) and writes the result as an
7
+ * NDJSON stream to stdout: one line per {@link LocalStepTrace}, then a single
8
+ * terminal summary line `{ outcome, output, error, unusedStubs, stubWarnings }`.
9
+ * Fully offline and zero-cost — run-local resolves every `ctx.sapiom.*` call
10
+ * from stubs and never touches the network.
11
+ *
12
+ * Why a separate child process (not an in-process import):
13
+ * 1. `runLocalFromDir` esbuild-bundles and dynamically `import()`s a workflow
14
+ * project the harness doesn't control, so a stray top-level side effect,
15
+ * infinite loop, or crash in someone else's step body is bounded to this
16
+ * one child instead of taking the long-lived harness server down.
17
+ * 2. It sidesteps a Vite/Vitest limitation: the dynamic `import(\`file://…\`)`
18
+ * inside the loader gets intercepted by the SSR dynamic-import-vars
19
+ * transform when pulled into a Vitest module graph, and mishandles the
20
+ * tmpdir `file://` URL on darwin. A plain child `node` process never goes
21
+ * through that transform. (Mirrors the reasoning in canvas-manifest-check.)
22
+ *
23
+ * Contract with the route ({@link createActionsRouter}):
24
+ * - Request arrives as one JSON object on **stdin**: `{ sourceDir, input?,
25
+ * stubs?, maxAttemptsPerStep? }`.
26
+ * - Output is line-oriented JSON on **stdout** — the route forwards each line
27
+ * through unchanged, so the shapes here ARE the wire shapes the SPA parses.
28
+ * - Diagnostics go to **stderr**; the route keeps a bounded tail for failures.
29
+ * - Exit 0 once the terminal line is written (even for a failed run — a failed
30
+ * *run* is a successful *invocation*); exit 1 only when no run happened
31
+ * (bad request, load error) after writing a terminal `error` line.
32
+ */
33
+ import { runLocalFromDir, } from "@sapiom/agent-core";
34
+ /** Read the entire request payload from a readable stream as one UTF-8 string. */
35
+ async function readAll(stream) {
36
+ const chunks = [];
37
+ for await (const chunk of stream) {
38
+ chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
39
+ }
40
+ return Buffer.concat(chunks).toString("utf8");
41
+ }
42
+ /**
43
+ * Parse the stdin payload into a {@link RunLocalRequest}. Throws a plain Error
44
+ * with a caller-safe message (no key material, no provider names) on anything
45
+ * that isn't a JSON object carrying a non-empty string `sourceDir`.
46
+ */
47
+ export function parseRunLocalRequest(raw) {
48
+ let parsed;
49
+ try {
50
+ parsed = JSON.parse(raw);
51
+ }
52
+ catch {
53
+ throw new Error("run-local request is not valid JSON");
54
+ }
55
+ if (typeof parsed !== "object" || parsed === null) {
56
+ throw new Error("run-local request must be a JSON object");
57
+ }
58
+ const body = parsed;
59
+ const sourceDir = body.sourceDir;
60
+ if (typeof sourceDir !== "string" || sourceDir.trim() === "") {
61
+ throw new Error("run-local request requires a non-empty sourceDir");
62
+ }
63
+ return {
64
+ sourceDir,
65
+ input: body.input,
66
+ stubs: body.stubs,
67
+ maxAttemptsPerStep: typeof body.maxAttemptsPerStep === "number"
68
+ ? body.maxAttemptsPerStep
69
+ : undefined,
70
+ };
71
+ }
72
+ /** Serialize one step trace as a single NDJSON line (newline-terminated). */
73
+ function traceLine(step) {
74
+ return JSON.stringify(step) + "\n";
75
+ }
76
+ /** Serialize the terminal summary as a single NDJSON line. */
77
+ function summaryLine(line) {
78
+ return JSON.stringify(line) + "\n";
79
+ }
80
+ /**
81
+ * Run the request and emit its NDJSON stream to `out`. Returns the process exit
82
+ * code: 0 when a run executed (any outcome), 1 when the run could not be
83
+ * invoked (a terminal `error` line is emitted first). Never throws — every
84
+ * failure becomes an in-band terminal line, because a half-written stream with
85
+ * a thrown stack on stderr is far harder for the route to reason about than a
86
+ * clean terminal line plus an exit code.
87
+ */
88
+ export async function runBootstrap(request, out) {
89
+ try {
90
+ const result = await runLocalFromDir({
91
+ sourceDir: request.sourceDir,
92
+ input: request.input,
93
+ stubs: request.stubs,
94
+ maxAttemptsPerStep: request.maxAttemptsPerStep,
95
+ });
96
+ // One line per step-attempt, in execution order — the consumer parses them
97
+ // incrementally rather than buffering the whole trace as a single blob.
98
+ for (const step of result.steps) {
99
+ out.write(traceLine(step));
100
+ }
101
+ const summary = {
102
+ kind: "summary",
103
+ outcome: result.outcome,
104
+ output: result.output,
105
+ error: result.error,
106
+ unusedStubs: result.unusedStubs,
107
+ stubWarnings: result.stubWarnings,
108
+ };
109
+ out.write(summaryLine(summary));
110
+ return 0;
111
+ }
112
+ catch (err) {
113
+ const errorLine = {
114
+ kind: "error",
115
+ outcome: "failed",
116
+ error: err instanceof Error ? err.message : String(err),
117
+ };
118
+ out.write(JSON.stringify(errorLine) + "\n");
119
+ return 1;
120
+ }
121
+ }
122
+ /**
123
+ * Entrypoint: read the request from stdin, run it, exit with the run's code.
124
+ * A stdin/read failure (or a request that can't be parsed) also degrades to a
125
+ * terminal `error` line + exit 1 — the route always sees a well-formed final
126
+ * line regardless of how the child failed.
127
+ */
128
+ async function main() {
129
+ let request;
130
+ try {
131
+ request = parseRunLocalRequest(await readAll(process.stdin));
132
+ }
133
+ catch (err) {
134
+ const errorLine = {
135
+ kind: "error",
136
+ outcome: "failed",
137
+ error: err instanceof Error ? err.message : String(err),
138
+ };
139
+ process.stdout.write(JSON.stringify(errorLine) + "\n");
140
+ process.exitCode = 1;
141
+ return;
142
+ }
143
+ process.exitCode = await runBootstrap(request, process.stdout);
144
+ }
145
+ // Only self-invoke when run as the child entrypoint, never when imported by a
146
+ // unit test (which drives runBootstrap / parseRunLocalRequest directly). The
147
+ // built entry is dist/core/run-local-bootstrap.js; import.meta.url ends with
148
+ // this module's own file, and process.argv[1] is the script node was told to
149
+ // run — they share a basename only for the real child launch.
150
+ const invokedAsScript = typeof process.argv[1] === "string" &&
151
+ import.meta.url === new URL(`file://${process.argv[1]}`).href;
152
+ if (invokedAsScript) {
153
+ void main();
154
+ }
155
+ //# sourceMappingURL=run-local-bootstrap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-local-bootstrap.js","sourceRoot":"","sources":["../../src/core/run-local-bootstrap.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * run-state — fetches a prod execution projection from the agents surface and
3
+ * renders it to a {@link RunView} the canvas can poll.
4
+ *
5
+ * WHY the agents surface: deployed agent runs are owned by the agents runtime
6
+ * (`GET /agents/v1/executions/:id`), not the core-capabilities surface. The
7
+ * response is the full ExecutionProjection JSON (same shape agent-core's
8
+ * decodeExecutionProjection consumes).
9
+ *
10
+ * WHY the key stays server-side: the Sapiom API key is a harness credential that
11
+ * must never reach the browser. The harness server fetches on behalf of the
12
+ * canvas, so the SPA polls a local `/api/runs/:id/state` endpoint (no key in the
13
+ * request) rather than calling the upstream surface directly.
14
+ *
15
+ * NOTE: this env-precedence helper for the agents base URL is duplicated
16
+ * elsewhere in the harness; consolidate into one shared helper when convenient.
17
+ */
18
+ import type { RunView } from "../shared/types.js";
19
+ import { type ApiKeyProvider } from "./api-key-provider.js";
20
+ /** Resolve the agents surface base URL from the environment. */
21
+ export declare function resolveAgentsBaseUrl(): string;
22
+ export type RunStateResult = {
23
+ ok: true;
24
+ runView: RunView;
25
+ } | {
26
+ ok: false;
27
+ status: number;
28
+ error: string;
29
+ };
30
+ export interface RunStateFetcherOpts {
31
+ /**
32
+ * Sapiom API key. Accepts either a plain `string | null` (the boot-time key)
33
+ * or an {@link ApiKeyProvider} — pass a provider to get the refresh-on-401
34
+ * recovery path. A bare string never refreshes.
35
+ */
36
+ apiKey: string | null | ApiKeyProvider;
37
+ baseUrl?: string;
38
+ /** Injectable fetch implementation — defaults to global fetch. Test seam. */
39
+ fetchImpl?: typeof fetch;
40
+ }
41
+ export interface RunStateFetcher {
42
+ fetch(executionId: string): Promise<RunStateResult>;
43
+ }
44
+ /** Upstream statuses that mean "the API key was rejected" — worth one refresh
45
+ * + retry before we give up. 401 is the common case; 403 covers a key that
46
+ * authenticated but lost authorization (e.g. rotated to a new org). */
47
+ export declare function isAuthRejection(status: number): boolean;
48
+ /**
49
+ * Create a fetcher that resolves an execution's current render state from the
50
+ * agents surface. The fetcher is intentionally non-throwing: all error paths
51
+ * return a typed {@link RunStateResult} with `ok: false` so the router can
52
+ * forward the appropriate HTTP status without a try/catch at the call site.
53
+ *
54
+ * When constructed with an {@link ApiKeyProvider}, a 401/403 from the upstream
55
+ * call triggers exactly one credential refresh + retry: if the shared store has
56
+ * a newer key (e.g. the user re-logged-in), the retry uses it and the Studio
57
+ * recovers in place instead of surfacing a dead-end auth error.
58
+ */
59
+ export declare function createRunStateFetcher(opts: RunStateFetcherOpts): RunStateFetcher;
60
+ //# sourceMappingURL=run-state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-state.d.ts","sourceRoot":"","sources":["../../src/core/run-state.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAElD,OAAO,EACL,KAAK,cAAc,EAEpB,MAAM,uBAAuB,CAAC;AAgB/B,gEAAgE;AAChE,wBAAgB,oBAAoB,IAAI,MAAM,CAM7C;AAED,MAAM,MAAM,cAAc,GACtB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,GAC9B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEjD,MAAM,WAAW,mBAAmB;IAClC;;;;OAIG;IACH,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,cAAc,CAAC;IACvC,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;;wEAEwE;AACxE,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAEvD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,mBAAmB,GACxB,eAAe,CA4EjB"}
@@ -0,0 +1,122 @@
1
+ /**
2
+ * run-state — fetches a prod execution projection from the agents surface and
3
+ * renders it to a {@link RunView} the canvas can poll.
4
+ *
5
+ * WHY the agents surface: deployed agent runs are owned by the agents runtime
6
+ * (`GET /agents/v1/executions/:id`), not the core-capabilities surface. The
7
+ * response is the full ExecutionProjection JSON (same shape agent-core's
8
+ * decodeExecutionProjection consumes).
9
+ *
10
+ * WHY the key stays server-side: the Sapiom API key is a harness credential that
11
+ * must never reach the browser. The harness server fetches on behalf of the
12
+ * canvas, so the SPA polls a local `/api/runs/:id/state` endpoint (no key in the
13
+ * request) rather than calling the upstream surface directly.
14
+ *
15
+ * NOTE: this env-precedence helper for the agents base URL is duplicated
16
+ * elsewhere in the harness; consolidate into one shared helper when convenient.
17
+ */
18
+ import { decodeExecutionProjection } from "@sapiom/agent-core";
19
+ import { renderRunState } from "./render-run-state.js";
20
+ import { staticApiKeyProvider, } from "./api-key-provider.js";
21
+ /**
22
+ * Normalize the polymorphic `apiKey` option to an {@link ApiKeyProvider}. A
23
+ * plain string (or null) becomes a no-refresh provider so existing static-key
24
+ * callers keep working unchanged; a provider is used as-is.
25
+ */
26
+ function toApiKeyProvider(apiKey) {
27
+ if (apiKey === null || typeof apiKey === "string") {
28
+ return staticApiKeyProvider(apiKey);
29
+ }
30
+ return apiKey;
31
+ }
32
+ /** Resolve the agents surface base URL from the environment. */
33
+ export function resolveAgentsBaseUrl() {
34
+ return (process.env.SAPIOM_AGENTS_URL ??
35
+ process.env.SAPIOM_TOOLS_BASE ??
36
+ "https://tools.sapiom.ai");
37
+ }
38
+ /** Upstream statuses that mean "the API key was rejected" — worth one refresh
39
+ * + retry before we give up. 401 is the common case; 403 covers a key that
40
+ * authenticated but lost authorization (e.g. rotated to a new org). */
41
+ export function isAuthRejection(status) {
42
+ return status === 401 || status === 403;
43
+ }
44
+ /**
45
+ * Create a fetcher that resolves an execution's current render state from the
46
+ * agents surface. The fetcher is intentionally non-throwing: all error paths
47
+ * return a typed {@link RunStateResult} with `ok: false` so the router can
48
+ * forward the appropriate HTTP status without a try/catch at the call site.
49
+ *
50
+ * When constructed with an {@link ApiKeyProvider}, a 401/403 from the upstream
51
+ * call triggers exactly one credential refresh + retry: if the shared store has
52
+ * a newer key (e.g. the user re-logged-in), the retry uses it and the Studio
53
+ * recovers in place instead of surfacing a dead-end auth error.
54
+ */
55
+ export function createRunStateFetcher(opts) {
56
+ const { baseUrl = resolveAgentsBaseUrl(), fetchImpl = fetch } = opts;
57
+ const provider = toApiKeyProvider(opts.apiKey);
58
+ const requestOnce = async (executionId, apiKey) => {
59
+ try {
60
+ const res = await fetchImpl(`${baseUrl}/agents/v1/executions/${encodeURIComponent(executionId)}`, { headers: { "x-sapiom-api-key": apiKey } });
61
+ return { kind: "ok", res };
62
+ }
63
+ catch {
64
+ return {
65
+ kind: "err",
66
+ result: { ok: false, status: 502, error: "gateway unreachable" },
67
+ };
68
+ }
69
+ };
70
+ const decode = async (res) => {
71
+ if (res.status === 404) {
72
+ return { ok: false, status: 404, error: "execution not found" };
73
+ }
74
+ if (!res.ok) {
75
+ return {
76
+ ok: false,
77
+ status: 502,
78
+ error: `gateway responded ${res.status}`,
79
+ };
80
+ }
81
+ try {
82
+ const raw = (await res.json());
83
+ const runView = renderRunState(decodeExecutionProjection(raw));
84
+ return { ok: true, runView };
85
+ }
86
+ catch {
87
+ return { ok: false, status: 502, error: "could not decode execution" };
88
+ }
89
+ };
90
+ return {
91
+ async fetch(executionId) {
92
+ // No API key — do not touch the network; the harness is not signed in.
93
+ let apiKey = provider.getKey();
94
+ if (!apiKey) {
95
+ return {
96
+ ok: false,
97
+ status: 503,
98
+ error: "harness is not signed in to Sapiom",
99
+ };
100
+ }
101
+ const first = await requestOnce(executionId, apiKey);
102
+ if (first.kind === "err")
103
+ return first.result;
104
+ // Refresh-on-401: re-read the shared credential store once and retry with
105
+ // the newer key when the rejection was an auth failure and refresh
106
+ // actually produced a different key. Any other status (or an unchanged
107
+ // key) falls through to normal decoding/error mapping — no wasted retry.
108
+ if (isAuthRejection(first.res.status)) {
109
+ const refreshed = await provider.refresh();
110
+ if (refreshed && refreshed !== apiKey) {
111
+ apiKey = refreshed;
112
+ const second = await requestOnce(executionId, apiKey);
113
+ if (second.kind === "err")
114
+ return second.result;
115
+ return decode(second.res);
116
+ }
117
+ }
118
+ return decode(first.res);
119
+ },
120
+ };
121
+ }
122
+ //# sourceMappingURL=run-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-state.js","sourceRoot":"","sources":["../../src/core/run-state.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * stub-feedback — the pure, read-only derivations behind the run-inspector's
3
+ * stub affordances (WB15-2). Given a {@link RunView} (which already carries the
4
+ * run-level stub facts set by {@link renderLocalRun}), decide:
5
+ * - whether a given step should show the "stubbed" chip, and
6
+ * - what the read-only stub-hygiene notice should say (or nothing).
7
+ *
8
+ * Pure and deterministic: no LLM, no I/O, no clock — every result is a function
9
+ * of the RunView alone, so both are Stryker targets with mutation-first tests.
10
+ *
11
+ * Read-only by design: this surfaces what a stub run DID (served a call, or a
12
+ * supplied stub matched nothing / had the wrong shape). Editing stubs is a
13
+ * deferred fast-follow and lives nowhere here.
14
+ *
15
+ * Honesty is the whole point: the chip appears only for a step that actually ran
16
+ * in a stub-served run (never for a prod run, never for a step the run never
17
+ * reached), and the notice is `null` unless there is a real problem to report —
18
+ * so a clean run shows no chrome at all.
19
+ */
20
+ import type { RunView } from "../shared/types.js";
21
+ /**
22
+ * Should the "stubbed" chip render for the step named `stepName`?
23
+ *
24
+ * True only when BOTH hold:
25
+ * 1. the run was stub-served (`run.stubbed` — set by renderLocalRun for an
26
+ * offline local run; never by renderRunState for a prod run), and
27
+ * 2. that step actually ran this run (it appears in `run.steps`).
28
+ *
29
+ * A local run resolves every `ctx.sapiom.*` call from a stub, so a step that ran
30
+ * was stub-served — that is the honest granularity (agent-core records no
31
+ * per-CALL stub attribution). A step the run never reached, or any step of a
32
+ * real run, gets no chip. A null run (nothing observed yet) is never stubbed.
33
+ */
34
+ export declare function stepIsStubbed(run: RunView | null, stepName: string): boolean;
35
+ /**
36
+ * The read-only stub-hygiene notice for a run, or `null` when there is nothing
37
+ * honest to show. Present only the signals that carry real content:
38
+ * - `unusedStubs`: supplied stub keys that matched no capability call (a no-op
39
+ * mock — almost always a typo or the wrong path form), and
40
+ * - `stubWarnings`: stub values that matched a key but had the wrong shape.
41
+ *
42
+ * Returns `null` unless at least one non-empty signal exists, so the caller can
43
+ * render nothing for a clean run (honesty — no empty "0 issues" panel). The
44
+ * returned arrays are always non-empty when present (empty ones are dropped),
45
+ * so the component never has to re-check length.
46
+ */
47
+ export interface StubNotice {
48
+ unusedStubs?: NonNullable<RunView["unusedStubs"]>;
49
+ stubWarnings?: NonNullable<RunView["stubWarnings"]>;
50
+ }
51
+ export declare function stubNotice(run: RunView | null): StubNotice | null;
52
+ //# sourceMappingURL=stub-feedback.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stub-feedback.d.ts","sourceRoot":"","sources":["../../src/core/stub-feedback.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAElD;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAG5E;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,UAAU;IACzB,WAAW,CAAC,EAAE,WAAW,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC;IAClD,YAAY,CAAC,EAAE,WAAW,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC;CACrD;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,GAAG,UAAU,GAAG,IAAI,CAYjE"}