@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,430 @@
1
+ /**
2
+ * Actions router — backs the direct, in-app agent action macros:
3
+ * POST /api/workflows/:id/deploy → deploy the linked agent (build + poll).
4
+ * POST /api/runs → start a prod execution → { executionId }.
5
+ *
6
+ * These are the "direct" replacements for the old CLI/agent-driven macros: the
7
+ * harness server calls the Sapiom backend itself (via {@link deploy} / {@link run}
8
+ * from @sapiom/agent-core), so an action never spawns a subprocess agent and never
9
+ * consumes the user's LLM credits. The Sapiom API key is held server-side and
10
+ * never forwarded to the browser — exactly like {@link createRunsRouter}: the
11
+ * SPA hits these local `/api/*` routes (no key in the request) and the router
12
+ * presents the key to the backend on its behalf.
13
+ *
14
+ * Deploy streams its build lifecycle as NDJSON (one JSON object per line, the
15
+ * same line-oriented convention the local-run stream uses) so the canvas can
16
+ * show "building…" the moment the build kicks off and a terminal line when it
17
+ * settles. Prod-run is a single request/response returning `{ executionId }`,
18
+ * which the existing live-canvas path then polls via the runs router.
19
+ *
20
+ * Run-local (`POST /api/runs/local`) is the offline sibling: it spawns the
21
+ * run-local bootstrap child, which runs the workflow in-process against stub
22
+ * capabilities and streams NDJSON back — one {@link LocalStepTrace} per line,
23
+ * then a terminal summary carrying `unusedStubs`/`stubWarnings`. It needs no
24
+ * API key and makes no network call, so it works signed-out and at zero cost.
25
+ */
26
+ import { spawn as spawnChildProcess } from "node:child_process";
27
+ import { createInterface } from "node:readline";
28
+ import { dirname, join } from "node:path";
29
+ import { fileURLToPath } from "node:url";
30
+ import { Router } from "express";
31
+ import { AgentOperationError, createClient, deploy as coreDeploy, run as coreRun, readConfig as coreReadConfig, } from "@sapiom/agent-core";
32
+ import { resolveCoreBaseUrl } from "../core/definition-slug-resolver.js";
33
+ import { staticApiKeyProvider, } from "../core/api-key-provider.js";
34
+ const DEFAULT_CORE_DEPS = {
35
+ createClient,
36
+ deploy: coreDeploy,
37
+ run: coreRun,
38
+ readConfig: coreReadConfig,
39
+ };
40
+ /**
41
+ * Resolve the compiled run-local bootstrap entry. This module lives at
42
+ * `dist/server/actions.js` (built) or `src/server/actions.ts` (tsx dev /
43
+ * vitest) — the bootstrap is its sibling one directory over in `core/`, with
44
+ * the same `.js`/`.ts` extension as this file. Reading the extension off
45
+ * `import.meta.url` (rather than hard-coding `.js`) keeps a real dev-server
46
+ * spawn resolvable too. Exported for unit coverage of the path math.
47
+ */
48
+ export function resolveRunLocalBootstrapPath(moduleUrl) {
49
+ const here = fileURLToPath(moduleUrl);
50
+ const ext = here.endsWith(".ts") ? ".ts" : ".js";
51
+ return join(dirname(here), "..", "core", `run-local-bootstrap${ext}`);
52
+ }
53
+ /**
54
+ * The default spawn: `node <bootstrap>` with `cwd` set to this package's root
55
+ * so the bootstrap's `import "@sapiom/agent-core"` resolves against the
56
+ * harness's real dependency (same technique as canvas-manifest-check). A `.ts`
57
+ * bootstrap (dev only) is loaded through the `tsx` register hook; the built
58
+ * `.js` runs on bare node. stdin is piped so the route can write the request.
59
+ */
60
+ function defaultRunLocalSpawn() {
61
+ const bootstrap = resolveRunLocalBootstrapPath(import.meta.url);
62
+ const nodeArgs = bootstrap.endsWith(".ts")
63
+ ? ["--import", "tsx", bootstrap]
64
+ : [bootstrap];
65
+ return spawnChildProcess(process.execPath, nodeArgs, {
66
+ cwd: join(dirname(fileURLToPath(import.meta.url)), "..", ".."),
67
+ stdio: ["pipe", "pipe", "pipe"],
68
+ });
69
+ }
70
+ /** Bounded stderr tail kept per run-local child for failure display. */
71
+ const RUN_LOCAL_STDERR_TAIL_CHARS = 2000;
72
+ /**
73
+ * The run-local request body the SPA POSTs. `sourceDir` is required; the rest
74
+ * mirror {@link RunLocalRequest} and are forwarded to the bootstrap as-is.
75
+ */
76
+ function parseRunLocalBody(body) {
77
+ if (typeof body !== "object" || body === null)
78
+ return null;
79
+ const b = body;
80
+ if (typeof b.sourceDir !== "string" || b.sourceDir.trim() === "")
81
+ return null;
82
+ return {
83
+ sourceDir: b.sourceDir,
84
+ input: b.input,
85
+ stubs: b.stubs,
86
+ maxAttemptsPerStep: typeof b.maxAttemptsPerStep === "number"
87
+ ? b.maxAttemptsPerStep
88
+ : undefined,
89
+ };
90
+ }
91
+ /**
92
+ * Extract the linked `definitionId` from a project's `sapiom.json`. The config
93
+ * file — not the registry — is the source of truth for a project's server-side
94
+ * definition id, so both deploy and (a future) resolve path read it here. Never
95
+ * throws: an unlinked/unreadable project returns null and the route maps that to
96
+ * a 409.
97
+ */
98
+ function readDefinitionId(readConfig, projectDir) {
99
+ let config;
100
+ try {
101
+ config = readConfig(projectDir);
102
+ }
103
+ catch {
104
+ // BAD_CONFIG (unparseable sapiom.json) — treat as "not linked" for the route.
105
+ return null;
106
+ }
107
+ return config?.definitionId ?? null;
108
+ }
109
+ /**
110
+ * Map an {@link AgentOperationError} (or any thrown value) to a terminal deploy
111
+ * stream event. The hint is forwarded — it is safe to do so because git errors
112
+ * are redacted at source (credentials stripped before they reach the hint field),
113
+ * so no key material can reach the browser via this path.
114
+ */
115
+ function toDeployErrorEvent(err) {
116
+ if (err instanceof AgentOperationError) {
117
+ return {
118
+ phase: "error",
119
+ code: err.code,
120
+ message: err.message,
121
+ ...(err.hint !== undefined ? { hint: err.hint } : {}),
122
+ };
123
+ }
124
+ return {
125
+ phase: "error",
126
+ code: "UNKNOWN",
127
+ message: err instanceof Error ? err.message : String(err),
128
+ };
129
+ }
130
+ /**
131
+ * Whether a thrown value is an upstream "the API key was rejected" — worth one
132
+ * refresh + retry before giving up. agent-core maps a 401/403 to an
133
+ * {@link AgentOperationError} with `code: "HTTP_401" | "HTTP_403"` (see
134
+ * client.ts), so we match on that code rather than an HTTP status here — the
135
+ * error-code analogue of run-state.ts's `isAuthRejection(status)`.
136
+ */
137
+ function isAuthRejectionError(err) {
138
+ return (err instanceof AgentOperationError &&
139
+ (err.code === "HTTP_401" || err.code === "HTTP_403"));
140
+ }
141
+ /**
142
+ * Run a core operation with the current API key and, when it fails with an auth
143
+ * rejection, refresh the shared credential store once and retry with the newer
144
+ * key — the deploy/prod-run analogue of run-state.ts's refresh-on-401 recovery.
145
+ *
146
+ * `invoke` is handed a freshly-minted client for the key in force (the boot key
147
+ * on the first attempt, the refreshed key on the retry) so a rotated/re-logged-in
148
+ * credential recovers in place instead of every action locking on the stale key.
149
+ * Retries only when refresh actually yields a *different, non-null* key —
150
+ * otherwise the original error is re-thrown unchanged (no wasted call, and the
151
+ * caller's error mapping sees the real auth failure).
152
+ *
153
+ * The caller has already checked `provider.getKey()` is non-null, so `apiKey!`
154
+ * here is safe; a concurrent sign-out would surface as a normal auth error.
155
+ */
156
+ async function withKeyRefreshRetry(provider, createClientFor, invoke) {
157
+ const apiKey = provider.getKey();
158
+ try {
159
+ return await invoke(createClientFor(apiKey));
160
+ }
161
+ catch (err) {
162
+ if (!isAuthRejectionError(err))
163
+ throw err;
164
+ const refreshed = await provider.refresh();
165
+ if (!refreshed || refreshed === apiKey)
166
+ throw err;
167
+ return invoke(createClientFor(refreshed));
168
+ }
169
+ }
170
+ /**
171
+ * Create the actions router. Mounts:
172
+ * - `POST /api/workflows/:id/deploy` — NDJSON build-status stream.
173
+ * - `POST /api/runs` — `{ executionId }` for a started prod execution.
174
+ * - `POST /api/runs/local` — NDJSON offline stub-run trace + summary.
175
+ *
176
+ * Deploy and prod-run run server-side with the held API key; run-local is fully
177
+ * offline and needs no key. None of them ever involve an AI coding agent.
178
+ */
179
+ export function createActionsRouter(opts) {
180
+ const router = Router();
181
+ const deps = { ...DEFAULT_CORE_DEPS, ...opts.coreDeps };
182
+ const baseUrl = opts.coreBaseUrl ?? resolveCoreBaseUrl();
183
+ const runLocalSpawn = opts.runLocalSpawn ?? defaultRunLocalSpawn;
184
+ // Normalize to a provider so deploy/prod-run always authenticate with the
185
+ // held API key and can refresh + retry when that key is rejected — a plain
186
+ // string|null becomes a no-op static provider (no refresh). Mirrors the runs
187
+ // router; keeps both action surfaces on the one credential contract.
188
+ const provider = opts.apiKey !== null && typeof opts.apiKey === "object"
189
+ ? opts.apiKey
190
+ : staticApiKeyProvider(opts.apiKey);
191
+ /** Mint a core client for a specific key against the resolved core host. */
192
+ const clientFor = (apiKey) => deps.createClient({ host: baseUrl, apiKey });
193
+ /**
194
+ * POST /api/workflows/:id/deploy
195
+ *
196
+ * Deploys the linked agent for the given workflow id: mints push credentials,
197
+ * pushes the synthesized tree, triggers a build, and polls to a terminal
198
+ * status — all inside @sapiom/agent-core's {@link deploy}. Streams NDJSON: a
199
+ * `building` line up front, then exactly one terminal `ready`/`error` line.
200
+ *
201
+ * 200 NDJSON stream (even a build failure is a 200 with a terminal `error`
202
+ * line — the request itself succeeded; the build outcome is in-band).
203
+ * 400 id missing/empty
204
+ * 404 workflow id not registered
205
+ * 409 workflow is not linked to a Sapiom agent (no definitionId)
206
+ * 503 harness is not signed in to Sapiom
207
+ */
208
+ router.post("/api/workflows/:id/deploy", async (req, res) => {
209
+ const id = req.params.id;
210
+ if (!id || typeof id !== "string" || id.trim() === "") {
211
+ res.status(400).json({ error: "workflow id is required" });
212
+ return;
213
+ }
214
+ if (!provider.getKey()) {
215
+ res.status(503).json({ error: "harness is not signed in to Sapiom" });
216
+ return;
217
+ }
218
+ const workflow = opts.resolveWorkflow(id);
219
+ if (!workflow) {
220
+ res.status(404).json({ error: "workflow not found" });
221
+ return;
222
+ }
223
+ const definitionId = readDefinitionId(deps.readConfig, workflow.path);
224
+ if (!definitionId) {
225
+ res
226
+ .status(409)
227
+ .json({ error: "workflow is not linked to a Sapiom agent" });
228
+ return;
229
+ }
230
+ // From here the outcome is streamed in-band as NDJSON — status is 200 and
231
+ // headers are committed before the (potentially long) build runs.
232
+ res.status(200);
233
+ res.setHeader("Content-Type", "application/x-ndjson; charset=utf-8");
234
+ res.setHeader("Cache-Control", "no-store");
235
+ const write = (event) => {
236
+ res.write(JSON.stringify(event) + "\n");
237
+ };
238
+ write({ phase: "building", definitionId });
239
+ try {
240
+ // Auth against the live key, refreshing + retrying once on a rejected key
241
+ // (same recovery the runs router gets). The building/terminal streaming
242
+ // shape is unchanged — the retry is transparent to the NDJSON stream.
243
+ const result = await withKeyRefreshRetry(provider, clientFor, (client) => deps.deploy({ projectDir: workflow.path, definitionId }, client));
244
+ write({
245
+ phase: "ready",
246
+ definitionId: result.definitionId,
247
+ buildRunId: result.buildRunId,
248
+ status: result.status,
249
+ });
250
+ }
251
+ catch (err) {
252
+ write(toDeployErrorEvent(err));
253
+ }
254
+ finally {
255
+ res.end();
256
+ }
257
+ });
258
+ /**
259
+ * POST /api/runs { definitionId, input }
260
+ *
261
+ * Starts a prod execution of the given definition and returns
262
+ * `{ executionId }` — the live-canvas path then polls the runs router for its
263
+ * per-step state. `input` is optional (defaults to an empty object for
264
+ * no-input agents). The key stays server-side.
265
+ *
266
+ * 200 { executionId } — execution created
267
+ * 400 definitionId missing/empty
268
+ * 502 gateway error (network or non-2xx from the backend)
269
+ * 503 harness is not signed in to Sapiom
270
+ */
271
+ router.post("/api/runs", async (req, res) => {
272
+ if (!provider.getKey()) {
273
+ res.status(503).json({ error: "harness is not signed in to Sapiom" });
274
+ return;
275
+ }
276
+ const body = (req.body ?? {});
277
+ const definitionId = body.definitionId;
278
+ if (typeof definitionId !== "string" || definitionId.trim() === "") {
279
+ res.status(400).json({ error: "definitionId is required" });
280
+ return;
281
+ }
282
+ try {
283
+ // Auth against the live key, refreshing + retrying once on a rejected key
284
+ // (same recovery the runs router gets), then return { executionId }.
285
+ const result = await withKeyRefreshRetry(provider, clientFor, (client) => deps.run({ definitionId, input: body.input }, client));
286
+ res.json({ executionId: result.executionId });
287
+ }
288
+ catch (err) {
289
+ if (err instanceof AgentOperationError) {
290
+ // The gateway/network failed — surface a 502 with the machine code, no
291
+ // credential hint (that hint names the login flow, not for the browser).
292
+ res.status(502).json({ error: err.message, code: err.code });
293
+ }
294
+ else {
295
+ res
296
+ .status(502)
297
+ .json({ error: err instanceof Error ? err.message : String(err) });
298
+ }
299
+ }
300
+ });
301
+ /**
302
+ * POST /api/runs/local { sourceDir, input?, stubs?, maxAttemptsPerStep? }
303
+ *
304
+ * Runs the workflow at `sourceDir` entirely offline against stub
305
+ * capabilities, in a child process (the run-local bootstrap), and streams the
306
+ * result back as NDJSON: one {@link LocalStepTrace} per line, then a terminal
307
+ * summary line `{ kind: "summary", outcome, output, error, unusedStubs,
308
+ * stubWarnings }`. A run that could not be invoked at all (bad project, bad
309
+ * stub file) yields a terminal `{ kind: "error", outcome: "failed", error }`
310
+ * line instead. Needs no API key and makes no network call — zero cost.
311
+ *
312
+ * The child owns the wire shapes; this handler validates the request, pipes
313
+ * it to the child's stdin, forwards each stdout line unchanged, and (only if
314
+ * the child dies without a terminal line) synthesizes one from its stderr so
315
+ * the stream always ends well-formed.
316
+ *
317
+ * 200 NDJSON stream (a failed *run* is still a 200 — the request succeeded;
318
+ * the outcome is in-band on the terminal line).
319
+ * 400 sourceDir missing/empty
320
+ */
321
+ router.post("/api/runs/local", (req, res) => {
322
+ const request = parseRunLocalBody(req.body);
323
+ if (!request) {
324
+ res.status(400).json({ error: "sourceDir is required" });
325
+ return;
326
+ }
327
+ let child;
328
+ try {
329
+ child = runLocalSpawn();
330
+ }
331
+ catch (err) {
332
+ // Never launched (e.g. the node binary or bootstrap is missing) — the
333
+ // request itself is fine, so answer in-band with a terminal error line.
334
+ res.status(200);
335
+ res.setHeader("Content-Type", "application/x-ndjson; charset=utf-8");
336
+ res.setHeader("Cache-Control", "no-store");
337
+ res.write(JSON.stringify({
338
+ kind: "error",
339
+ outcome: "failed",
340
+ error: err instanceof Error ? err.message : String(err),
341
+ }) + "\n");
342
+ res.end();
343
+ return;
344
+ }
345
+ // Headers are committed before the (potentially long) run streams.
346
+ res.status(200);
347
+ res.setHeader("Content-Type", "application/x-ndjson; charset=utf-8");
348
+ res.setHeader("Cache-Control", "no-store");
349
+ // Hand the request to the child, then close its stdin so the bootstrap's
350
+ // read-to-EOF completes.
351
+ child.stdin?.end(JSON.stringify(request));
352
+ // Forward each well-formed JSON line straight through — the bootstrap emits
353
+ // exactly the wire shapes, so no re-shaping happens here. A line that isn't
354
+ // JSON is stray stdout noise (an esbuild banner, a dependency's console
355
+ // write) and is dropped rather than corrupting the NDJSON stream — the same
356
+ // "degrade, never throw" stance as core/task-stream.ts.
357
+ let sawTerminalLine = false;
358
+ const onLine = (line) => {
359
+ const trimmed = line.trim();
360
+ if (trimmed === "")
361
+ return;
362
+ let parsed;
363
+ try {
364
+ parsed = JSON.parse(trimmed);
365
+ }
366
+ catch {
367
+ return; // non-JSON noise — not part of the contract.
368
+ }
369
+ // A `summary`/`error` line is terminal — track it so a crash after the
370
+ // summary isn't double-reported as a failure.
371
+ const kind = parsed.kind;
372
+ if (kind === "summary" || kind === "error")
373
+ sawTerminalLine = true;
374
+ res.write(trimmed + "\n");
375
+ };
376
+ // Keep a bounded stderr tail for the crash path (never forwarded inline —
377
+ // stderr is diagnostics, not part of the NDJSON contract).
378
+ let stderrTail = "";
379
+ child.stderr?.on("data", (chunk) => {
380
+ stderrTail = (stderrTail + String(chunk)).slice(-RUN_LOCAL_STDERR_TAIL_CHARS);
381
+ });
382
+ // End the response exactly once (mirrors task-manager's finish() guard).
383
+ // `crashReason` is captured from `exit`/`error` but the terminal decision is
384
+ // deferred to `settle()` so a still-buffered summary line is never clobbered.
385
+ let settled = false;
386
+ let crashReason = null;
387
+ const settle = () => {
388
+ if (settled)
389
+ return;
390
+ settled = true;
391
+ // Only synthesize a terminal line when the child produced none — otherwise
392
+ // the stream already ended well-formed with the bootstrap's own summary.
393
+ if (!sawTerminalLine) {
394
+ res.write(JSON.stringify({
395
+ kind: "error",
396
+ outcome: "failed",
397
+ error: stderrTail.trim() || crashReason || "run-local produced no output",
398
+ }) + "\n");
399
+ }
400
+ res.end();
401
+ };
402
+ // Drive the terminal decision off stdout's close (readline "close" fires
403
+ // only after every line has been emitted), so a summary line buffered when
404
+ // `exit` arrives is still forwarded first. If there's no stdout at all, fall
405
+ // back to `exit`/`error` directly.
406
+ if (child.stdout) {
407
+ const lines = createInterface({ input: child.stdout });
408
+ lines.on("line", onLine);
409
+ lines.on("close", settle);
410
+ child.on("error", (err) => {
411
+ crashReason = err.message;
412
+ });
413
+ child.on("exit", (code) => {
414
+ crashReason ?? (crashReason = `run-local process exited with code ${code ?? "null"}`);
415
+ });
416
+ }
417
+ else {
418
+ child.on("error", (err) => {
419
+ crashReason = err.message;
420
+ settle();
421
+ });
422
+ child.on("exit", (code) => {
423
+ crashReason ?? (crashReason = `run-local process exited with code ${code ?? "null"}`);
424
+ settle();
425
+ });
426
+ }
427
+ });
428
+ return router;
429
+ }
430
+ //# sourceMappingURL=actions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"actions.js","sourceRoot":"","sources":["../../src/server/actions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,92 @@
1
+ /**
2
+ * In-app auth routes — expose the CLI's browser OAuth flow over HTTP so the
3
+ * Studio web app can trigger sign-in/sign-out without restarting the server.
4
+ *
5
+ * Reuses `performBrowserAuth` from `@sapiom/mcp/auth` (the SAME function the
6
+ * CLI's `ensureAuthenticated` calls at boot) — no custom OAuth implementation,
7
+ * no changes to any Sapiom cloud endpoint. The flow:
8
+ *
9
+ * POST /api/auth/start
10
+ * ↳ performBrowserAuth(appURL, apiURL) ← existing CLI flow, unchanged
11
+ * opens browser → localhost callback → token exchange
12
+ * ↳ writeCredentials(...) ← writes ~/.sapiom/credentials.json
13
+ * ↳ apiKeyProvider.refresh() ← adopts the new key in-process
14
+ * ↳ bus.publish({ type: "auth.changed" }) ← notifies open WS connections
15
+ *
16
+ * POST /api/auth/disconnect
17
+ * ↳ clearCredentials(...)
18
+ * ↳ provider.clear()
19
+ * ↳ bus.publish({ type: "auth.changed", authenticated: false })
20
+ *
21
+ * GET /api/auth/status
22
+ * ↳ { authenticated, organizationName }
23
+ *
24
+ * All three routes sit behind the existing `X-Harness-Token` middleware applied
25
+ * at the /api level in server/index.ts — no extra auth layer needed here.
26
+ */
27
+ import { Router } from "express";
28
+ import { performBrowserAuth } from "@sapiom/mcp/auth";
29
+ import type { EventBus } from "../core/event-bus.js";
30
+ export interface AuthState {
31
+ authenticated: boolean;
32
+ organizationName: string | null;
33
+ }
34
+ /**
35
+ * A mutable holder for the harness's current auth identity — updated on
36
+ * sign-in/sign-out and queried by `GET /api/auth/status`. Shared between the
37
+ * auth router and any other consumer (e.g. `GET /api/state` in rest.ts) that
38
+ * wants live auth state rather than the boot-time snapshot.
39
+ */
40
+ export interface MutableAuthState {
41
+ get(): AuthState;
42
+ set(state: AuthState): void;
43
+ }
44
+ export declare function createMutableAuthState(initial: AuthState): MutableAuthState;
45
+ /**
46
+ * The minimal slice of {@link ApiKeyProvider} the auth router needs: a way
47
+ * to push a new key in after a successful browser auth (or clear it on
48
+ * disconnect), without importing the full provider shape.
49
+ */
50
+ export interface AuthKeyTarget {
51
+ /**
52
+ * Re-reads `~/.sapiom/credentials.json` and adopts the key found there.
53
+ * Called after `performBrowserAuth` + `writeCredentials` complete so the
54
+ * provider's in-memory key matches the freshly written credential.
55
+ */
56
+ refresh(): Promise<string | null>;
57
+ /**
58
+ * Unconditionally zero the in-memory key (sets it to null). Called on
59
+ * disconnect so that the provider returns null immediately — distinct from
60
+ * {@link refresh}, whose guard deliberately keeps the cached key when the
61
+ * store has nothing to offer.
62
+ */
63
+ clear(): void;
64
+ }
65
+ export interface AuthRoutesOptions {
66
+ /** Mutable auth state shared with `GET /api/state` and `GET /api/auth/status`. */
67
+ authState: MutableAuthState;
68
+ /**
69
+ * The live API key provider — `refresh()` is called after sign-in writes
70
+ * `~/.sapiom/credentials.json` so the in-process key adopts the new value.
71
+ */
72
+ apiKeyProvider: AuthKeyTarget;
73
+ /** The server event bus — broadcasts `auth.changed` after every state transition. */
74
+ bus: EventBus;
75
+ /**
76
+ * Overrides SAPIOM_ENVIRONMENT for the OAuth flow. Defaults to
77
+ * `process.env.SAPIOM_ENVIRONMENT` (the same override the CLI uses).
78
+ */
79
+ environment?: string;
80
+ /**
81
+ * Injectable seam for `performBrowserAuth` — lets tests mock the full OAuth
82
+ * round-trip without opening a real browser or binding a local port.
83
+ */
84
+ performBrowserAuthImpl?: typeof performBrowserAuth;
85
+ }
86
+ /**
87
+ * Mount the in-app auth routes. The caller is responsible for gating these
88
+ * behind the boot-token middleware (applied at the /api level in
89
+ * server/index.ts before any router) — these routes do NOT add a second layer.
90
+ */
91
+ export declare function createAuthRouter(opts: AuthRoutesOptions): Router;
92
+ //# sourceMappingURL=auth-routes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-routes.d.ts","sourceRoot":"","sources":["../../src/server/auth-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAEL,kBAAkB,EAGnB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAMrD,MAAM,WAAW,SAAS;IACxB,aAAa,EAAE,OAAO,CAAC;IACvB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AAED;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,GAAG,IAAI,SAAS,CAAC;IACjB,GAAG,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI,CAAC;CAC7B;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,SAAS,GAAG,gBAAgB,CAQ3E;AAMD;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B;;;;OAIG;IACH,OAAO,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAClC;;;;;OAKG;IACH,KAAK,IAAI,IAAI,CAAC;CACf;AAMD,MAAM,WAAW,iBAAiB;IAChC,kFAAkF;IAClF,SAAS,EAAE,gBAAgB,CAAC;IAC5B;;;OAGG;IACH,cAAc,EAAE,aAAa,CAAC;IAC9B,qFAAqF;IACrF,GAAG,EAAE,QAAQ,CAAC;IACd;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,sBAAsB,CAAC,EAAE,OAAO,kBAAkB,CAAC;CACpD;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,iBAAiB,GAAG,MAAM,CAuIhE"}