@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.
- package/CHANGELOG.md +61 -0
- package/dist/cli/bin.js +24 -7
- package/dist/cli/bin.js.map +1 -1
- package/dist/cli/consent.js +1 -1
- package/dist/core/adapters/adapter.d.ts +2 -2
- package/dist/core/api-key-provider.d.ts +79 -0
- package/dist/core/api-key-provider.d.ts.map +1 -0
- package/dist/core/api-key-provider.js +91 -0
- package/dist/core/api-key-provider.js.map +1 -0
- package/dist/core/canvas-run-state.d.ts +9 -5
- package/dist/core/canvas-run-state.d.ts.map +1 -1
- package/dist/core/canvas-run-state.js +42 -13
- package/dist/core/canvas-run-state.js.map +1 -1
- package/dist/core/definition-slug-resolver.d.ts +13 -0
- package/dist/core/definition-slug-resolver.d.ts.map +1 -1
- package/dist/core/definition-slug-resolver.js +29 -0
- package/dist/core/definition-slug-resolver.js.map +1 -1
- package/dist/core/render-local-run.d.ts +82 -0
- package/dist/core/render-local-run.d.ts.map +1 -0
- package/dist/core/render-local-run.js +105 -0
- package/dist/core/render-local-run.js.map +1 -0
- package/dist/core/render-log-slice.d.ts +30 -0
- package/dist/core/render-log-slice.d.ts.map +1 -0
- package/dist/core/render-log-slice.js +49 -0
- package/dist/core/render-log-slice.js.map +1 -0
- package/dist/core/render-run-state.d.ts.map +1 -1
- package/dist/core/render-run-state.js +30 -49
- package/dist/core/render-run-state.js.map +1 -1
- package/dist/core/run-local-bootstrap.d.ts +93 -0
- package/dist/core/run-local-bootstrap.d.ts.map +1 -0
- package/dist/core/run-local-bootstrap.js +155 -0
- package/dist/core/run-local-bootstrap.js.map +1 -0
- package/dist/core/run-state.d.ts +16 -1
- package/dist/core/run-state.d.ts.map +1 -1
- package/dist/core/run-state.js +75 -25
- package/dist/core/run-state.js.map +1 -1
- package/dist/core/stub-feedback.d.ts +52 -0
- package/dist/core/stub-feedback.d.ts.map +1 -0
- package/dist/core/stub-feedback.js +33 -0
- package/dist/core/stub-feedback.js.map +1 -0
- package/dist/server/actions.d.ts +139 -0
- package/dist/server/actions.d.ts.map +1 -0
- package/dist/server/actions.js +430 -0
- package/dist/server/actions.js.map +1 -0
- package/dist/server/auth-routes.d.ts +92 -0
- package/dist/server/auth-routes.d.ts.map +1 -0
- package/dist/server/auth-routes.js +151 -0
- package/dist/server/auth-routes.js.map +1 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +0 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/rest.d.ts.map +1 -1
- package/dist/server/rest.js +0 -2
- package/dist/server/rest.js.map +1 -1
- package/dist/server/runs.d.ts +16 -15
- package/dist/server/runs.d.ts.map +1 -1
- package/dist/server/runs.js +15 -80
- package/dist/server/runs.js.map +1 -1
- package/dist/server/static.d.ts +15 -1
- package/dist/server/static.d.ts.map +1 -1
- package/dist/server/static.js +54 -4
- package/dist/server/static.js.map +1 -1
- package/dist/shared/types.d.ts +79 -35
- package/dist/shared/types.d.ts.map +1 -1
- package/dist/shared/types.js.map +1 -1
- package/dist/web/assets/index-DCtyI4VW.css +32 -0
- package/dist/web/assets/index-WSw5RvYR.js +445 -0
- package/dist/web/canvas/sess-boot/index.html +305 -0
- package/dist/web/index.html +4 -9
- package/dist/web/welcome-hero-dark.png +0 -0
- package/dist/web/welcome-hero-light.png +0 -0
- package/package.json +6 -3
- package/dist/core/run-spend.d.ts +0 -55
- package/dist/core/run-spend.d.ts.map +0 -1
- package/dist/core/run-spend.js +0 -109
- package/dist/core/run-spend.js.map +0 -1
- package/dist/core/run-transactions.d.ts +0 -60
- package/dist/core/run-transactions.d.ts.map +0 -1
- package/dist/core/run-transactions.js +0 -129
- package/dist/core/run-transactions.js.map +0 -1
- package/dist/server/skills.d.ts +0 -51
- package/dist/server/skills.d.ts.map +0 -1
- package/dist/server/skills.js +0 -312
- package/dist/server/skills.js.map +0 -1
- package/dist/web/assets/index-DM84J83Y.js +0 -234
- package/dist/web/assets/index-DemtWW4L.css +0 -32
|
@@ -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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"}
|
package/dist/core/run-state.d.ts
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
* elsewhere in the harness; consolidate into one shared helper when convenient.
|
|
17
17
|
*/
|
|
18
18
|
import type { RunView } from "../shared/types.js";
|
|
19
|
+
import { type ApiKeyProvider } from "./api-key-provider.js";
|
|
19
20
|
/** Resolve the agents surface base URL from the environment. */
|
|
20
21
|
export declare function resolveAgentsBaseUrl(): string;
|
|
21
22
|
export type RunStateResult = {
|
|
@@ -27,7 +28,12 @@ export type RunStateResult = {
|
|
|
27
28
|
error: string;
|
|
28
29
|
};
|
|
29
30
|
export interface RunStateFetcherOpts {
|
|
30
|
-
|
|
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;
|
|
31
37
|
baseUrl?: string;
|
|
32
38
|
/** Injectable fetch implementation — defaults to global fetch. Test seam. */
|
|
33
39
|
fetchImpl?: typeof fetch;
|
|
@@ -35,11 +41,20 @@ export interface RunStateFetcherOpts {
|
|
|
35
41
|
export interface RunStateFetcher {
|
|
36
42
|
fetch(executionId: string): Promise<RunStateResult>;
|
|
37
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;
|
|
38
48
|
/**
|
|
39
49
|
* Create a fetcher that resolves an execution's current render state from the
|
|
40
50
|
* agents surface. The fetcher is intentionally non-throwing: all error paths
|
|
41
51
|
* return a typed {@link RunStateResult} with `ok: false` so the router can
|
|
42
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.
|
|
43
58
|
*/
|
|
44
59
|
export declare function createRunStateFetcher(opts: RunStateFetcherOpts): RunStateFetcher;
|
|
45
60
|
//# sourceMappingURL=run-state.d.ts.map
|
|
@@ -1 +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;
|
|
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"}
|
package/dist/core/run-state.js
CHANGED
|
@@ -17,23 +17,80 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import { decodeExecutionProjection } from "@sapiom/agent-core";
|
|
19
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
|
+
}
|
|
20
32
|
/** Resolve the agents surface base URL from the environment. */
|
|
21
33
|
export function resolveAgentsBaseUrl() {
|
|
22
34
|
return (process.env.SAPIOM_AGENTS_URL ??
|
|
23
35
|
process.env.SAPIOM_TOOLS_BASE ??
|
|
24
36
|
"https://tools.sapiom.ai");
|
|
25
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
|
+
}
|
|
26
44
|
/**
|
|
27
45
|
* Create a fetcher that resolves an execution's current render state from the
|
|
28
46
|
* agents surface. The fetcher is intentionally non-throwing: all error paths
|
|
29
47
|
* return a typed {@link RunStateResult} with `ok: false` so the router can
|
|
30
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.
|
|
31
54
|
*/
|
|
32
55
|
export function createRunStateFetcher(opts) {
|
|
33
|
-
const {
|
|
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
|
+
};
|
|
34
90
|
return {
|
|
35
91
|
async fetch(executionId) {
|
|
36
92
|
// No API key — do not touch the network; the harness is not signed in.
|
|
93
|
+
let apiKey = provider.getKey();
|
|
37
94
|
if (!apiKey) {
|
|
38
95
|
return {
|
|
39
96
|
ok: false,
|
|
@@ -41,31 +98,24 @@ export function createRunStateFetcher(opts) {
|
|
|
41
98
|
error: "harness is not signed in to Sapiom",
|
|
42
99
|
};
|
|
43
100
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if (res.status
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
try {
|
|
62
|
-
const raw = (await res.json());
|
|
63
|
-
const runView = renderRunState(decodeExecutionProjection(raw));
|
|
64
|
-
return { ok: true, runView };
|
|
65
|
-
}
|
|
66
|
-
catch {
|
|
67
|
-
return { ok: false, status: 502, error: "could not decode execution" };
|
|
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
|
+
}
|
|
68
117
|
}
|
|
118
|
+
return decode(first.res);
|
|
69
119
|
},
|
|
70
120
|
};
|
|
71
121
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run-state.js","sourceRoot":"","sources":["../../src/core/run-state.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AAG/D,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"run-state.js","sourceRoot":"","sources":["../../src/core/run-state.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AAG/D,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAEL,oBAAoB,GACrB,MAAM,uBAAuB,CAAC;AAE/B;;;;GAIG;AACH,SAAS,gBAAgB,CACvB,MAAsC;IAEtC,IAAI,MAAM,KAAK,IAAI,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QAClD,OAAO,oBAAoB,CAAC,MAAM,CAAC,CAAC;IACtC,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,gEAAgE;AAChE,MAAM,UAAU,oBAAoB;IAClC,OAAO,CACL,OAAO,CAAC,GAAG,CAAC,iBAAiB;QAC7B,OAAO,CAAC,GAAG,CAAC,iBAAiB;QAC7B,yBAAyB,CAC1B,CAAC;AACJ,CAAC;AAsBD;;wEAEwE;AACxE,MAAM,UAAU,eAAe,CAAC,MAAc;IAC5C,OAAO,MAAM,KAAK,GAAG,IAAI,MAAM,KAAK,GAAG,CAAC;AAC1C,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,qBAAqB,CACnC,IAAyB;IAEzB,MAAM,EAAE,OAAO,GAAG,oBAAoB,EAAE,EAAE,SAAS,GAAG,KAAK,EAAE,GAAG,IAAI,CAAC;IACrE,MAAM,QAAQ,GAAG,gBAAgB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAE/C,MAAM,WAAW,GAAG,KAAK,EACvB,WAAmB,EACnB,MAAc,EAGd,EAAE;QACF,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,SAAS,CACzB,GAAG,OAAO,yBAAyB,kBAAkB,CAAC,WAAW,CAAC,EAAE,EACpE,EAAE,OAAO,EAAE,EAAE,kBAAkB,EAAE,MAAM,EAAE,EAAE,CAC5C,CAAC;YACF,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC;QAC7B,CAAC;QAAC,MAAM,CAAC;YACP,OAAO;gBACL,IAAI,EAAE,KAAK;gBACX,MAAM,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,qBAAqB,EAAE;aACjE,CAAC;QACJ,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,MAAM,GAAG,KAAK,EAAE,GAAa,EAA2B,EAAE;QAC9D,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YACvB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,qBAAqB,EAAE,CAAC;QAClE,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,MAAM,EAAE,GAAG;gBACX,KAAK,EAAE,qBAAqB,GAAG,CAAC,MAAM,EAAE;aACzC,CAAC;QACJ,CAAC;QACD,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAA4B,CAAC;YAC1D,MAAM,OAAO,GAAG,cAAc,CAAC,yBAAyB,CAAC,GAAG,CAAC,CAAC,CAAC;YAC/D,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;QAC/B,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,4BAA4B,EAAE,CAAC;QACzE,CAAC;IACH,CAAC,CAAC;IAEF,OAAO;QACL,KAAK,CAAC,KAAK,CAAC,WAAmB;YAC7B,uEAAuE;YACvE,IAAI,MAAM,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC;YAC/B,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,OAAO;oBACL,EAAE,EAAE,KAAK;oBACT,MAAM,EAAE,GAAG;oBACX,KAAK,EAAE,oCAAoC;iBAC5C,CAAC;YACJ,CAAC;YAED,MAAM,KAAK,GAAG,MAAM,WAAW,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;YACrD,IAAI,KAAK,CAAC,IAAI,KAAK,KAAK;gBAAE,OAAO,KAAK,CAAC,MAAM,CAAC;YAE9C,0EAA0E;YAC1E,mEAAmE;YACnE,uEAAuE;YACvE,yEAAyE;YACzE,IAAI,eAAe,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;gBACtC,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC,OAAO,EAAE,CAAC;gBAC3C,IAAI,SAAS,IAAI,SAAS,KAAK,MAAM,EAAE,CAAC;oBACtC,MAAM,GAAG,SAAS,CAAC;oBACnB,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;oBACtD,IAAI,MAAM,CAAC,IAAI,KAAK,KAAK;wBAAE,OAAO,MAAM,CAAC,MAAM,CAAC;oBAChD,OAAO,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;gBAC5B,CAAC;YACH,CAAC;YAED,OAAO,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC3B,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Should the "stubbed" chip render for the step named `stepName`?
|
|
3
|
+
*
|
|
4
|
+
* True only when BOTH hold:
|
|
5
|
+
* 1. the run was stub-served (`run.stubbed` — set by renderLocalRun for an
|
|
6
|
+
* offline local run; never by renderRunState for a prod run), and
|
|
7
|
+
* 2. that step actually ran this run (it appears in `run.steps`).
|
|
8
|
+
*
|
|
9
|
+
* A local run resolves every `ctx.sapiom.*` call from a stub, so a step that ran
|
|
10
|
+
* was stub-served — that is the honest granularity (agent-core records no
|
|
11
|
+
* per-CALL stub attribution). A step the run never reached, or any step of a
|
|
12
|
+
* real run, gets no chip. A null run (nothing observed yet) is never stubbed.
|
|
13
|
+
*/
|
|
14
|
+
export function stepIsStubbed(run, stepName) {
|
|
15
|
+
if (!run || run.stubbed !== true)
|
|
16
|
+
return false;
|
|
17
|
+
return run.steps.some((s) => s.name === stepName);
|
|
18
|
+
}
|
|
19
|
+
export function stubNotice(run) {
|
|
20
|
+
if (!run)
|
|
21
|
+
return null;
|
|
22
|
+
const notice = {};
|
|
23
|
+
if (run.unusedStubs && run.unusedStubs.length > 0) {
|
|
24
|
+
notice.unusedStubs = run.unusedStubs;
|
|
25
|
+
}
|
|
26
|
+
if (run.stubWarnings && run.stubWarnings.length > 0) {
|
|
27
|
+
notice.stubWarnings = run.stubWarnings;
|
|
28
|
+
}
|
|
29
|
+
// Empty object == no real signal; report honest absence as null so the
|
|
30
|
+
// inspector renders no notice block at all.
|
|
31
|
+
return notice.unusedStubs || notice.stubWarnings ? notice : null;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=stub-feedback.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stub-feedback.js","sourceRoot":"","sources":["../../src/core/stub-feedback.ts"],"names":[],"mappings":"AAqBA;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,aAAa,CAAC,GAAmB,EAAE,QAAgB;IACjE,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,OAAO,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAC/C,OAAO,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC;AACpD,CAAC;AAmBD,MAAM,UAAU,UAAU,CAAC,GAAmB;IAC5C,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IACtB,MAAM,MAAM,GAAe,EAAE,CAAC;IAC9B,IAAI,GAAG,CAAC,WAAW,IAAI,GAAG,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAClD,MAAM,CAAC,WAAW,GAAG,GAAG,CAAC,WAAW,CAAC;IACvC,CAAC;IACD,IAAI,GAAG,CAAC,YAAY,IAAI,GAAG,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACpD,MAAM,CAAC,YAAY,GAAG,GAAG,CAAC,YAAY,CAAC;IACzC,CAAC;IACD,uEAAuE;IACvE,4CAA4C;IAC5C,OAAO,MAAM,CAAC,WAAW,IAAI,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;AACnE,CAAC"}
|
|
@@ -0,0 +1,139 @@
|
|
|
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 { Router } from "express";
|
|
27
|
+
import { createClient, deploy as coreDeploy, run as coreRun, readConfig as coreReadConfig } from "@sapiom/agent-core";
|
|
28
|
+
import { type ApiKeyProvider } from "../core/api-key-provider.js";
|
|
29
|
+
/**
|
|
30
|
+
* A registered workflow the actions router can act on — the subset of
|
|
31
|
+
* {@link WorkflowInfo} deploy needs. Resolved by the injected
|
|
32
|
+
* {@link ActionsRouterOpts.resolveWorkflow} so the router stays decoupled from
|
|
33
|
+
* the registry (mirrors the rest router's `findWorkflow` seam).
|
|
34
|
+
*/
|
|
35
|
+
export interface ActionWorkflow {
|
|
36
|
+
/** Absolute path to the agent project directory (deploy's `projectDir`). */
|
|
37
|
+
path: string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* One line of the deploy NDJSON stream. `building` is emitted once the build is
|
|
41
|
+
* triggered; exactly one terminal line (`ready` | `error`) closes the stream.
|
|
42
|
+
* `capability`-agnostic and credential-free by construction.
|
|
43
|
+
*/
|
|
44
|
+
export type DeployStreamEvent = {
|
|
45
|
+
phase: "building";
|
|
46
|
+
definitionId: string;
|
|
47
|
+
} | {
|
|
48
|
+
phase: "ready";
|
|
49
|
+
definitionId: string;
|
|
50
|
+
buildRunId: string;
|
|
51
|
+
status: string;
|
|
52
|
+
} | {
|
|
53
|
+
phase: "error";
|
|
54
|
+
code: string;
|
|
55
|
+
message: string;
|
|
56
|
+
hint?: string;
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Injectable core operations. Real implementations are the @sapiom/agent-core
|
|
60
|
+
* exports; tests substitute fakes so no route ever touches git or the network.
|
|
61
|
+
* Undocumented for prod — a test seam only, mirroring `fetchImpl` in runs.ts.
|
|
62
|
+
*/
|
|
63
|
+
export interface ActionsCoreDeps {
|
|
64
|
+
createClient: typeof createClient;
|
|
65
|
+
deploy: typeof coreDeploy;
|
|
66
|
+
run: typeof coreRun;
|
|
67
|
+
readConfig: typeof coreReadConfig;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The slice of node's ChildProcess the run-local route uses — injectable so
|
|
71
|
+
* tests drive a fake child (a scripted stdout stream) without spawning a real
|
|
72
|
+
* `node` process. Mirrors {@link TaskProcess} in task-manager.ts.
|
|
73
|
+
*/
|
|
74
|
+
export interface RunLocalChildProcess {
|
|
75
|
+
/** Where the request JSON is written; closed immediately after. */
|
|
76
|
+
stdin: NodeJS.WritableStream | null;
|
|
77
|
+
/** Line-oriented NDJSON the route forwards to the HTTP response. */
|
|
78
|
+
stdout: NodeJS.ReadableStream | null;
|
|
79
|
+
/** Diagnostics; a bounded tail is kept for failure reporting. */
|
|
80
|
+
stderr: NodeJS.ReadableStream | null;
|
|
81
|
+
on(event: "exit", listener: (code: number | null) => void): unknown;
|
|
82
|
+
on(event: "error", listener: (err: Error) => void): unknown;
|
|
83
|
+
}
|
|
84
|
+
/** Spawn the run-local bootstrap child. Test seam — defaults to `node`ing the
|
|
85
|
+
* compiled bootstrap. */
|
|
86
|
+
export type RunLocalSpawnFn = () => RunLocalChildProcess;
|
|
87
|
+
/**
|
|
88
|
+
* Resolve the compiled run-local bootstrap entry. This module lives at
|
|
89
|
+
* `dist/server/actions.js` (built) or `src/server/actions.ts` (tsx dev /
|
|
90
|
+
* vitest) — the bootstrap is its sibling one directory over in `core/`, with
|
|
91
|
+
* the same `.js`/`.ts` extension as this file. Reading the extension off
|
|
92
|
+
* `import.meta.url` (rather than hard-coding `.js`) keeps a real dev-server
|
|
93
|
+
* spawn resolvable too. Exported for unit coverage of the path math.
|
|
94
|
+
*/
|
|
95
|
+
export declare function resolveRunLocalBootstrapPath(moduleUrl: string): string;
|
|
96
|
+
export interface ActionsRouterOpts {
|
|
97
|
+
/**
|
|
98
|
+
* Sapiom credential the deploy/prod-run actions authenticate with. Accepts
|
|
99
|
+
* either a plain `string | null` (the boot-time key) or an
|
|
100
|
+
* {@link ApiKeyProvider}; pass a provider — exactly like
|
|
101
|
+
* {@link createRunsRouter} — so a rejected key can refresh + retry and so each
|
|
102
|
+
* request reads the current key rather than a boot-time snapshot. This is the
|
|
103
|
+
* API key (`sk_…`), NOT the local boot token. `null` (or a provider whose
|
|
104
|
+
* `getKey()` is null) means the harness is not signed in.
|
|
105
|
+
*/
|
|
106
|
+
apiKey: string | null | ApiKeyProvider;
|
|
107
|
+
/**
|
|
108
|
+
* Backend host for @sapiom/agent-core's GatewayClient (the CORE surface —
|
|
109
|
+
* `/v1/workflows` is appended by the client). Resolved from env by default.
|
|
110
|
+
* Test seam.
|
|
111
|
+
*/
|
|
112
|
+
coreBaseUrl?: string;
|
|
113
|
+
/**
|
|
114
|
+
* Resolve a workflow `:id` (as it appears in the route path) to the registered
|
|
115
|
+
* workflow, or null when unknown. The caller supplies this from the live
|
|
116
|
+
* registry — the router does not read the registry directly.
|
|
117
|
+
*/
|
|
118
|
+
resolveWorkflow: (id: string) => ActionWorkflow | null;
|
|
119
|
+
/** Injectable core operations. Test seam; defaults to the real exports. */
|
|
120
|
+
coreDeps?: Partial<ActionsCoreDeps>;
|
|
121
|
+
/**
|
|
122
|
+
* Spawn the run-local bootstrap child. Undocumented for prod — a test seam
|
|
123
|
+
* only (defaults to `node`ing the compiled bootstrap), so a test can stream a
|
|
124
|
+
* scripted trace without spawning a real process, mirroring `fetchImpl` in
|
|
125
|
+
* runs.ts and the `spawnProcess` seam in task-manager.ts.
|
|
126
|
+
*/
|
|
127
|
+
runLocalSpawn?: RunLocalSpawnFn;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Create the actions router. Mounts:
|
|
131
|
+
* - `POST /api/workflows/:id/deploy` — NDJSON build-status stream.
|
|
132
|
+
* - `POST /api/runs` — `{ executionId }` for a started prod execution.
|
|
133
|
+
* - `POST /api/runs/local` — NDJSON offline stub-run trace + summary.
|
|
134
|
+
*
|
|
135
|
+
* Deploy and prod-run run server-side with the held API key; run-local is fully
|
|
136
|
+
* offline and needs no key. None of them ever involve an AI coding agent.
|
|
137
|
+
*/
|
|
138
|
+
export declare function createActionsRouter(opts: ActionsRouterOpts): Router;
|
|
139
|
+
//# sourceMappingURL=actions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../../src/server/actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAOH,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAEL,YAAY,EACZ,MAAM,IAAI,UAAU,EACpB,GAAG,IAAI,OAAO,EACd,UAAU,IAAI,cAAc,EAI7B,MAAM,oBAAoB,CAAC;AAI5B,OAAO,EACL,KAAK,cAAc,EAEpB,MAAM,6BAA6B,CAAC;AAErC;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,4EAA4E;IAC5E,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;GAIG;AACH,MAAM,MAAM,iBAAiB,GACzB;IAAE,KAAK,EAAE,UAAU,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,GAC3C;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC5E;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAErE;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,YAAY,EAAE,OAAO,YAAY,CAAC;IAClC,MAAM,EAAE,OAAO,UAAU,CAAC;IAC1B,GAAG,EAAE,OAAO,OAAO,CAAC;IACpB,UAAU,EAAE,OAAO,cAAc,CAAC;CACnC;AASD;;;;GAIG;AACH,MAAM,WAAW,oBAAoB;IACnC,mEAAmE;IACnE,KAAK,EAAE,MAAM,CAAC,cAAc,GAAG,IAAI,CAAC;IACpC,oEAAoE;IACpE,MAAM,EAAE,MAAM,CAAC,cAAc,GAAG,IAAI,CAAC;IACrC,iEAAiE;IACjE,MAAM,EAAE,MAAM,CAAC,cAAc,GAAG,IAAI,CAAC;IACrC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,GAAG,OAAO,CAAC;IACpE,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,GAAG,OAAO,CAAC;CAC7D;AAED;0BAC0B;AAC1B,MAAM,MAAM,eAAe,GAAG,MAAM,oBAAoB,CAAC;AAEzD;;;;;;;GAOG;AACH,wBAAgB,4BAA4B,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAItE;AA0CD,MAAM,WAAW,iBAAiB;IAChC;;;;;;;;OAQG;IACH,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,cAAc,CAAC;IACvC;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,eAAe,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,cAAc,GAAG,IAAI,CAAC;IACvD,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC;IACpC;;;;;OAKG;IACH,aAAa,CAAC,EAAE,eAAe,CAAC;CACjC;AA4FD;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,iBAAiB,GAAG,MAAM,CAuRnE"}
|