@sapiom/orchestration-core 0.1.1 → 0.3.0
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 +42 -0
- package/dist/cjs/index.d.ts +29 -29
- package/dist/cjs/index.js +3 -1
- package/dist/cjs/inspect.d.ts +18 -1
- package/dist/cjs/inspect.js +39 -1
- package/dist/cjs/local/dispatcher.d.ts +7 -5
- package/dist/cjs/local/dispatcher.js +49 -15
- package/dist/cjs/local/run-local.d.ts +10 -4
- package/dist/cjs/local/run-local.js +31 -9
- package/dist/cjs/scaffold.d.ts +1 -0
- package/dist/cjs/scaffold.js +52 -22
- package/dist/esm/index.d.ts +29 -29
- package/dist/esm/index.js +15 -15
- package/dist/esm/inspect.d.ts +18 -1
- package/dist/esm/inspect.js +37 -1
- package/dist/esm/local/dispatcher.d.ts +7 -5
- package/dist/esm/local/dispatcher.js +52 -18
- package/dist/esm/local/run-local.d.ts +10 -4
- package/dist/esm/local/run-local.js +38 -16
- package/dist/esm/scaffold.d.ts +1 -0
- package/dist/esm/scaffold.js +56 -26
- package/dist/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/tsconfig.esm.tsbuildinfo +1 -1
- package/package.json +4 -4
- package/templates/coding-pause/AGENTS.md +48 -0
- package/templates/coding-pause/CLAUDE.md +7 -0
- package/templates/coding-pause/README.md +53 -0
- package/templates/coding-pause/_gitignore +3 -0
- package/templates/coding-pause/index.ts +109 -0
- package/templates/coding-pause/package.json +21 -0
- package/templates/coding-pause/tsconfig.json +12 -0
- package/templates/default/AGENTS.md +21 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,47 @@
|
|
|
1
1
|
# @sapiom/orchestration-core
|
|
2
2
|
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- e17b2d1: `scaffold` now initializes a git repository with an initial commit, so a freshly scaffolded project is immediately deployable (deploy requires a repo with at least one commit). Best-effort: if `git` is unavailable the project is left uninitialized. `ScaffoldResult` gains `gitInitialized`.
|
|
8
|
+
|
|
9
|
+
Adds `waitForExecution` (and `isExecutionTerminal`) to poll an execution until it reaches a terminal state, settles on a pause that needs a signal, or a wait budget elapses — so callers don't hand-roll a poll loop. The `sapiom_dev_orchestrations_inspect` MCP tool gains opt-in `wait` / `maxWaitSeconds` options that use it and return `done` / `waiting` / `hint`; a non-blocking inspect of a still-running execution now also hints to use `wait`.
|
|
10
|
+
|
|
11
|
+
Fixes `sapiom_dev_orchestrations_link`: omitting `name` no longer sends an empty name (which the gateway rejected). It now defaults to the orchestration's name read from `index.ts` (matching `defineOrchestration({ name })`), and returns a clear error if no name is available.
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- e17b2d1: **BREAKING (`@sapiom/tools`):** align the coding-run resume payload with the shape a resumed step actually receives. `CodingResultPayload` now carries `executionEnvironment: { type, id } | null` instead of `sandbox: { name, workspaceRoot }`. Re-attach a resumed run's sandbox with `ctx.sapiom.sandboxes.attach(result.executionEnvironment.id)` (for a `blaxel_sandbox`).
|
|
16
|
+
|
|
17
|
+
Adds `codingResultSchema` (runtime validation of the resume payload), `toResumePayload`, `ExecutionEnvironmentRef`, and `EXECUTION_ENVIRONMENT_BLAXEL_SANDBOX`. The stub client now emits the same payload shape a resumed step receives, so a step written against the local loop runs identically once deployed.
|
|
18
|
+
|
|
19
|
+
The `coding-pause` template and its guidance are updated to the new shape.
|
|
20
|
+
|
|
21
|
+
- Updated dependencies [e17b2d1]
|
|
22
|
+
- @sapiom/tools@0.4.0
|
|
23
|
+
- @sapiom/orchestration@0.1.8
|
|
24
|
+
|
|
25
|
+
## 0.2.0
|
|
26
|
+
|
|
27
|
+
### Minor Changes
|
|
28
|
+
|
|
29
|
+
- 704c9ac: Make the local development loop (`run_local`) production-faithful and trustworthy for the dispatch/pause pattern (`agent.coding.launch` + `pauseUntilSignal`).
|
|
30
|
+
|
|
31
|
+
- Stub capability handles now survive JSON serialization, so a paused/resumed coding workflow runs end-to-end locally instead of failing with an opaque `'sandbox.toJSON' is not a method or field` error.
|
|
32
|
+
- The payload a paused step resumes with is delivered as plain JSON — the same shape production sends over the wire — so authors re-attach handles by name (`sandboxes.attach(...)`) locally exactly as they would in prod.
|
|
33
|
+
- `@sapiom/tools` exports `CodingResultPayload`: the shape a step resumed from `pauseUntilSignal(codingHandle, …)` receives, so resumed steps can be annotated instead of hand-rolling the type.
|
|
34
|
+
- Stubbing a handle-returning capability with plain JSON no longer strips the handle's instance methods (e.g. `repo.pushFromSandbox`), and `repositories.list` stubs are coerced and shape-checked.
|
|
35
|
+
- A dispatched `launch()` accepts the `agent.coding.launch` stub key as well as the shared `agent.coding.run` (ordered candidate resolution), so the stub key matching the call the author wrote takes effect.
|
|
36
|
+
- `run_local` now reports `unusedStubs` (a supplied key that matched no call) and `stubWarnings` (a key that matched but carried the wrong shape), surfacing stubs that silently didn't take effect; the MCP `run_local` also serializes its result defensively.
|
|
37
|
+
- New `coding-pause` scaffold template for the launch + pause + resume pattern, and AGENTS docs documenting the resume-input contract, list stub item shape, failure-branch stubbing, and step determinism under replay.
|
|
38
|
+
|
|
39
|
+
### Patch Changes
|
|
40
|
+
|
|
41
|
+
- Updated dependencies [704c9ac]
|
|
42
|
+
- @sapiom/tools@0.3.0
|
|
43
|
+
- @sapiom/orchestration@0.1.7
|
|
44
|
+
|
|
3
45
|
## 0.1.1
|
|
4
46
|
|
|
5
47
|
### Patch Changes
|
package/dist/cjs/index.d.ts
CHANGED
|
@@ -1,29 +1,29 @@
|
|
|
1
|
-
export { OrchestrationError } from
|
|
2
|
-
export type { StructuredError } from
|
|
3
|
-
export { GatewayClient, createClient, DEFAULT_WORKFLOWS_HOST } from
|
|
4
|
-
export type { ClientOptions, GatewayErrorBody } from
|
|
5
|
-
export { readConfig, requireConfig, writeConfig, CONFIG_FILE } from
|
|
6
|
-
export type { SapiomConfig } from
|
|
7
|
-
export { scaffold, resolveVersions, resolveTemplate, listTemplates, DEFAULT_TEMPLATE } from
|
|
8
|
-
export type { ScaffoldOptions, ScaffoldResult, ResolvedVersions } from
|
|
9
|
-
export { check } from
|
|
10
|
-
export type { CheckOptions, CheckResult } from
|
|
11
|
-
export { link } from
|
|
12
|
-
export type { LinkOptions, LinkResult, DefinitionSummary } from
|
|
13
|
-
export { deploy } from
|
|
14
|
-
export type { DeployOptions, DeployResult } from
|
|
15
|
-
export { run, parseJsonInput } from
|
|
16
|
-
export type { RunOptions, RunResult } from
|
|
17
|
-
export { inspect, listExecutions, inspectBuild } from
|
|
18
|
-
export type { InspectOptions, InspectResult, ListExecutionsResult, InspectBuildOptions, InspectBuildResult, ExecutionDetail, StepRecord, BuildDetail, } from
|
|
19
|
-
export { signal, parseSignalPayload } from
|
|
20
|
-
export type { SignalOptions, SignalResult } from
|
|
21
|
-
export { assertDeployable, pushHead } from
|
|
22
|
-
export { parseStubFile, STUB_FILE_VERSION } from
|
|
23
|
-
export type { StubFile, StepStubs, StubResponse } from
|
|
24
|
-
export { runLocal, runLocalFromDir, STUBS_FILE } from
|
|
25
|
-
export type { RunLocalOptions, LocalRunResult, LocalRunOutcome } from
|
|
26
|
-
export { loadDefinition } from
|
|
27
|
-
export type { LoadedDefinition } from
|
|
28
|
-
export { LocalStubDispatcher } from
|
|
29
|
-
export type { LocalStepTrace, LogEntry } from
|
|
1
|
+
export { OrchestrationError } from "./errors.js";
|
|
2
|
+
export type { StructuredError } from "./errors.js";
|
|
3
|
+
export { GatewayClient, createClient, DEFAULT_WORKFLOWS_HOST, } from "./client.js";
|
|
4
|
+
export type { ClientOptions, GatewayErrorBody } from "./client.js";
|
|
5
|
+
export { readConfig, requireConfig, writeConfig, CONFIG_FILE, } from "./config.js";
|
|
6
|
+
export type { SapiomConfig } from "./config.js";
|
|
7
|
+
export { scaffold, resolveVersions, resolveTemplate, listTemplates, DEFAULT_TEMPLATE, } from "./scaffold.js";
|
|
8
|
+
export type { ScaffoldOptions, ScaffoldResult, ResolvedVersions, } from "./scaffold.js";
|
|
9
|
+
export { check } from "./check.js";
|
|
10
|
+
export type { CheckOptions, CheckResult } from "./check.js";
|
|
11
|
+
export { link } from "./link.js";
|
|
12
|
+
export type { LinkOptions, LinkResult, DefinitionSummary } from "./link.js";
|
|
13
|
+
export { deploy } from "./deploy.js";
|
|
14
|
+
export type { DeployOptions, DeployResult } from "./deploy.js";
|
|
15
|
+
export { run, parseJsonInput } from "./run.js";
|
|
16
|
+
export type { RunOptions, RunResult } from "./run.js";
|
|
17
|
+
export { inspect, listExecutions, inspectBuild, waitForExecution, isExecutionTerminal, } from "./inspect.js";
|
|
18
|
+
export type { InspectOptions, InspectResult, ListExecutionsResult, InspectBuildOptions, InspectBuildResult, ExecutionDetail, StepRecord, BuildDetail, WaitForExecutionOptions, WaitForExecutionResult, WaitStopReason, } from "./inspect.js";
|
|
19
|
+
export { signal, parseSignalPayload } from "./signal.js";
|
|
20
|
+
export type { SignalOptions, SignalResult } from "./signal.js";
|
|
21
|
+
export { assertDeployable, pushHead } from "./git.js";
|
|
22
|
+
export { parseStubFile, STUB_FILE_VERSION } from "./local/stubs.js";
|
|
23
|
+
export type { StubFile, StepStubs, StubResponse } from "./local/stubs.js";
|
|
24
|
+
export { runLocal, runLocalFromDir, STUBS_FILE } from "./local/run-local.js";
|
|
25
|
+
export type { RunLocalOptions, LocalRunResult, LocalRunOutcome, } from "./local/run-local.js";
|
|
26
|
+
export { loadDefinition } from "./local/load.js";
|
|
27
|
+
export type { LoadedDefinition } from "./local/load.js";
|
|
28
|
+
export { LocalStubDispatcher } from "./local/dispatcher.js";
|
|
29
|
+
export type { LocalStepTrace, LogEntry } from "./local/dispatcher.js";
|
package/dist/cjs/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.LocalStubDispatcher = exports.loadDefinition = exports.STUBS_FILE = exports.runLocalFromDir = exports.runLocal = exports.STUB_FILE_VERSION = exports.parseStubFile = exports.pushHead = exports.assertDeployable = exports.parseSignalPayload = exports.signal = exports.inspectBuild = exports.listExecutions = exports.inspect = exports.parseJsonInput = exports.run = exports.deploy = exports.link = exports.check = exports.DEFAULT_TEMPLATE = exports.listTemplates = exports.resolveTemplate = exports.resolveVersions = exports.scaffold = exports.CONFIG_FILE = exports.writeConfig = exports.requireConfig = exports.readConfig = exports.DEFAULT_WORKFLOWS_HOST = exports.createClient = exports.GatewayClient = exports.OrchestrationError = void 0;
|
|
3
|
+
exports.LocalStubDispatcher = exports.loadDefinition = exports.STUBS_FILE = exports.runLocalFromDir = exports.runLocal = exports.STUB_FILE_VERSION = exports.parseStubFile = exports.pushHead = exports.assertDeployable = exports.parseSignalPayload = exports.signal = exports.isExecutionTerminal = exports.waitForExecution = exports.inspectBuild = exports.listExecutions = exports.inspect = exports.parseJsonInput = exports.run = exports.deploy = exports.link = exports.check = exports.DEFAULT_TEMPLATE = exports.listTemplates = exports.resolveTemplate = exports.resolveVersions = exports.scaffold = exports.CONFIG_FILE = exports.writeConfig = exports.requireConfig = exports.readConfig = exports.DEFAULT_WORKFLOWS_HOST = exports.createClient = exports.GatewayClient = exports.OrchestrationError = void 0;
|
|
4
4
|
var errors_js_1 = require("./errors.js");
|
|
5
5
|
Object.defineProperty(exports, "OrchestrationError", { enumerable: true, get: function () { return errors_js_1.OrchestrationError; } });
|
|
6
6
|
var client_js_1 = require("./client.js");
|
|
@@ -31,6 +31,8 @@ var inspect_js_1 = require("./inspect.js");
|
|
|
31
31
|
Object.defineProperty(exports, "inspect", { enumerable: true, get: function () { return inspect_js_1.inspect; } });
|
|
32
32
|
Object.defineProperty(exports, "listExecutions", { enumerable: true, get: function () { return inspect_js_1.listExecutions; } });
|
|
33
33
|
Object.defineProperty(exports, "inspectBuild", { enumerable: true, get: function () { return inspect_js_1.inspectBuild; } });
|
|
34
|
+
Object.defineProperty(exports, "waitForExecution", { enumerable: true, get: function () { return inspect_js_1.waitForExecution; } });
|
|
35
|
+
Object.defineProperty(exports, "isExecutionTerminal", { enumerable: true, get: function () { return inspect_js_1.isExecutionTerminal; } });
|
|
34
36
|
var signal_js_1 = require("./signal.js");
|
|
35
37
|
Object.defineProperty(exports, "signal", { enumerable: true, get: function () { return signal_js_1.signal; } });
|
|
36
38
|
Object.defineProperty(exports, "parseSignalPayload", { enumerable: true, get: function () { return signal_js_1.parseSignalPayload; } });
|
package/dist/cjs/inspect.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { GatewayClient } from
|
|
1
|
+
import { GatewayClient } from "./client.js";
|
|
2
2
|
export interface StepRecord {
|
|
3
3
|
stepName: string;
|
|
4
4
|
attempt: number;
|
|
@@ -12,6 +12,7 @@ export interface ExecutionDetail {
|
|
|
12
12
|
id: string;
|
|
13
13
|
status: string;
|
|
14
14
|
currentStep?: string | null;
|
|
15
|
+
pausedSignalName?: string | null;
|
|
15
16
|
error?: unknown;
|
|
16
17
|
steps?: StepRecord[];
|
|
17
18
|
}
|
|
@@ -27,6 +28,22 @@ export interface InspectResult {
|
|
|
27
28
|
execution: ExecutionDetail;
|
|
28
29
|
}
|
|
29
30
|
export declare function inspect(opts: InspectOptions, client: GatewayClient): Promise<InspectResult>;
|
|
31
|
+
export declare function isExecutionTerminal(status: string): boolean;
|
|
32
|
+
export type WaitStopReason = "terminal" | "needs-signal" | "timeout";
|
|
33
|
+
export interface WaitForExecutionOptions {
|
|
34
|
+
executionId: string;
|
|
35
|
+
maxWaitMs?: number;
|
|
36
|
+
pollMs?: number;
|
|
37
|
+
autoResumeSignals?: string[];
|
|
38
|
+
sleep?: (ms: number) => Promise<void>;
|
|
39
|
+
now?: () => number;
|
|
40
|
+
}
|
|
41
|
+
export interface WaitForExecutionResult {
|
|
42
|
+
execution: ExecutionDetail;
|
|
43
|
+
reason: WaitStopReason;
|
|
44
|
+
done: boolean;
|
|
45
|
+
}
|
|
46
|
+
export declare function waitForExecution(opts: WaitForExecutionOptions, client: GatewayClient): Promise<WaitForExecutionResult>;
|
|
30
47
|
export interface ListExecutionsResult {
|
|
31
48
|
executions: ExecutionDetail[];
|
|
32
49
|
}
|
package/dist/cjs/inspect.js
CHANGED
|
@@ -1,14 +1,52 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.inspect = inspect;
|
|
4
|
+
exports.isExecutionTerminal = isExecutionTerminal;
|
|
5
|
+
exports.waitForExecution = waitForExecution;
|
|
4
6
|
exports.listExecutions = listExecutions;
|
|
5
7
|
exports.inspectBuild = inspectBuild;
|
|
6
8
|
async function inspect(opts, client) {
|
|
7
9
|
const execution = await client.get(`/executions/${opts.executionId}`);
|
|
8
10
|
return { execution };
|
|
9
11
|
}
|
|
12
|
+
const TERMINAL_STATUSES = new Set([
|
|
13
|
+
"completed",
|
|
14
|
+
"failed",
|
|
15
|
+
"cancelled",
|
|
16
|
+
"canceled",
|
|
17
|
+
]);
|
|
18
|
+
function isExecutionTerminal(status) {
|
|
19
|
+
return TERMINAL_STATUSES.has(status);
|
|
20
|
+
}
|
|
21
|
+
const AUTO_RESUME_PAUSE_SIGNALS = ["agent.coding.result"];
|
|
22
|
+
const defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
23
|
+
async function waitForExecution(opts, client) {
|
|
24
|
+
const maxWaitMs = opts.maxWaitMs ?? 45000;
|
|
25
|
+
const autoResume = opts.autoResumeSignals ?? AUTO_RESUME_PAUSE_SIGNALS;
|
|
26
|
+
const sleep = opts.sleep ?? defaultSleep;
|
|
27
|
+
const now = opts.now ?? Date.now;
|
|
28
|
+
const deadline = now() + maxWaitMs;
|
|
29
|
+
let interval = opts.pollMs ?? 1000;
|
|
30
|
+
for (;;) {
|
|
31
|
+
const { execution } = await inspect({ executionId: opts.executionId }, client);
|
|
32
|
+
if (isExecutionTerminal(execution.status)) {
|
|
33
|
+
return { execution, reason: "terminal", done: true };
|
|
34
|
+
}
|
|
35
|
+
if (execution.status === "paused") {
|
|
36
|
+
const signal = execution.pausedSignalName ?? null;
|
|
37
|
+
if (!signal || !autoResume.includes(signal)) {
|
|
38
|
+
return { execution, reason: "needs-signal", done: false };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const remaining = deadline - now();
|
|
42
|
+
if (remaining <= 0)
|
|
43
|
+
return { execution, reason: "timeout", done: false };
|
|
44
|
+
await sleep(Math.min(interval, remaining));
|
|
45
|
+
interval = Math.min(interval * 1.5, 5000);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
10
48
|
async function listExecutions(client) {
|
|
11
|
-
const executions = await client.get(
|
|
49
|
+
const executions = await client.get("/executions");
|
|
12
50
|
return { executions };
|
|
13
51
|
}
|
|
14
52
|
async function inspectBuild(opts, client) {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { type NextStepDirective, type OrchestrationDefinition } from
|
|
2
|
-
import { type StepDispatcher, type StepDispatchRequest, type WorkflowRunnerCore } from
|
|
3
|
-
import type { StubFile } from
|
|
1
|
+
import { type NextStepDirective, type OrchestrationDefinition } from "@sapiom/orchestration";
|
|
2
|
+
import { type StepDispatcher, type StepDispatchRequest, type WorkflowRunnerCore } from "@sapiom/orchestration-runtime";
|
|
3
|
+
import type { StubFile } from "./stubs.js";
|
|
4
4
|
export interface LogEntry {
|
|
5
|
-
level:
|
|
5
|
+
level: "info" | "warn" | "error" | "debug";
|
|
6
6
|
msg: string;
|
|
7
7
|
meta?: Record<string, unknown>;
|
|
8
8
|
}
|
|
@@ -10,7 +10,7 @@ export interface LocalStepTrace {
|
|
|
10
10
|
step: string;
|
|
11
11
|
attempt: number;
|
|
12
12
|
input: unknown;
|
|
13
|
-
status:
|
|
13
|
+
status: "succeeded" | "threw";
|
|
14
14
|
output?: unknown;
|
|
15
15
|
directive?: NextStepDirective;
|
|
16
16
|
error?: {
|
|
@@ -27,6 +27,8 @@ export declare class LocalStubDispatcher implements StepDispatcher {
|
|
|
27
27
|
private maxAttemptsPerStep;
|
|
28
28
|
private signals;
|
|
29
29
|
readonly trace: LocalStepTrace[];
|
|
30
|
+
readonly usedKeysByStep: Map<string, Set<string>>;
|
|
31
|
+
readonly stubWarnings: Set<string>;
|
|
30
32
|
constructor(definition: OrchestrationDefinition, stubs: StubFile);
|
|
31
33
|
setCore(core: WorkflowRunnerCore): void;
|
|
32
34
|
setMaxAttempts(max: number): void;
|
|
@@ -11,6 +11,8 @@ class LocalStubDispatcher {
|
|
|
11
11
|
this.core = null;
|
|
12
12
|
this.signals = new Map();
|
|
13
13
|
this.trace = [];
|
|
14
|
+
this.usedKeysByStep = new Map();
|
|
15
|
+
this.stubWarnings = new Set();
|
|
14
16
|
}
|
|
15
17
|
setCore(core) {
|
|
16
18
|
this.core = core;
|
|
@@ -23,7 +25,7 @@ class LocalStubDispatcher {
|
|
|
23
25
|
}
|
|
24
26
|
async dispatch(request) {
|
|
25
27
|
if (!this.core)
|
|
26
|
-
throw new Error(
|
|
28
|
+
throw new Error("LocalStubDispatcher: setCore() was not called");
|
|
27
29
|
const step = this.definition.steps[request.stepName];
|
|
28
30
|
if (!step)
|
|
29
31
|
throw new Error(`LocalStubDispatcher: no step '${request.stepName}' in the definition`);
|
|
@@ -33,7 +35,17 @@ class LocalStubDispatcher {
|
|
|
33
35
|
const logs = [];
|
|
34
36
|
const sharedStore = new orchestration_1.InMemoryContextStore(request.shared);
|
|
35
37
|
const overrides = (this.stubs.steps[request.stepName] ?? {});
|
|
36
|
-
|
|
38
|
+
let usedKeys = this.usedKeysByStep.get(request.stepName);
|
|
39
|
+
if (!usedKeys) {
|
|
40
|
+
usedKeys = new Set();
|
|
41
|
+
this.usedKeysByStep.set(request.stepName, usedKeys);
|
|
42
|
+
}
|
|
43
|
+
const sapiom = (0, stub_1.createStubClient)({
|
|
44
|
+
overrides,
|
|
45
|
+
signals: this.signals,
|
|
46
|
+
usedKeys,
|
|
47
|
+
warnings: this.stubWarnings,
|
|
48
|
+
});
|
|
37
49
|
const ctx = {
|
|
38
50
|
executionId: request.executionId,
|
|
39
51
|
workflowName: request.workflowName,
|
|
@@ -56,7 +68,7 @@ class LocalStubDispatcher {
|
|
|
56
68
|
step: request.stepName,
|
|
57
69
|
attempt: request.attempt,
|
|
58
70
|
input: request.input,
|
|
59
|
-
status:
|
|
71
|
+
status: "threw",
|
|
60
72
|
error: { name: e.name, message: e.message, stack: e.stack },
|
|
61
73
|
logs,
|
|
62
74
|
});
|
|
@@ -74,7 +86,7 @@ class LocalStubDispatcher {
|
|
|
74
86
|
step: request.stepName,
|
|
75
87
|
attempt: request.attempt,
|
|
76
88
|
input: request.input,
|
|
77
|
-
status:
|
|
89
|
+
status: "succeeded",
|
|
78
90
|
output,
|
|
79
91
|
directive,
|
|
80
92
|
logs,
|
|
@@ -94,22 +106,44 @@ function makeLogger(sink) {
|
|
|
94
106
|
const at = (level) => (msg, meta) => {
|
|
95
107
|
sink.push({ level, msg, ...(meta ? { meta } : {}) });
|
|
96
108
|
};
|
|
97
|
-
return {
|
|
109
|
+
return {
|
|
110
|
+
info: at("info"),
|
|
111
|
+
warn: at("warn"),
|
|
112
|
+
error: at("error"),
|
|
113
|
+
debug: at("debug"),
|
|
114
|
+
};
|
|
98
115
|
}
|
|
99
116
|
function splitDirective(d) {
|
|
100
117
|
switch (d.kind) {
|
|
101
|
-
case
|
|
102
|
-
return {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
case 'pause_until_signal':
|
|
118
|
+
case "continue":
|
|
119
|
+
return {
|
|
120
|
+
output: d.input,
|
|
121
|
+
wire: { kind: "continue", stepName: d.stepName, input: d.input },
|
|
122
|
+
};
|
|
123
|
+
case "terminate":
|
|
108
124
|
return {
|
|
109
125
|
output: d.output,
|
|
110
|
-
wire: { kind:
|
|
126
|
+
wire: { kind: "terminate", reason: d.reason },
|
|
127
|
+
};
|
|
128
|
+
case "fail":
|
|
129
|
+
return {
|
|
130
|
+
output: d.output,
|
|
131
|
+
wire: { kind: "fail", reason: d.reason },
|
|
132
|
+
};
|
|
133
|
+
case "pause_until_signal":
|
|
134
|
+
return {
|
|
135
|
+
output: d.output,
|
|
136
|
+
wire: {
|
|
137
|
+
kind: "pause_until_signal",
|
|
138
|
+
signal: d.signal,
|
|
139
|
+
timeoutMs: d.timeoutMs,
|
|
140
|
+
resumeStep: d.resumeStep,
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
case "retry":
|
|
144
|
+
return {
|
|
145
|
+
output: undefined,
|
|
146
|
+
wire: { kind: "retry", delayMs: d.delayMs, reason: d.reason },
|
|
111
147
|
};
|
|
112
|
-
case 'retry':
|
|
113
|
-
return { output: undefined, wire: { kind: 'retry', delayMs: d.delayMs, reason: d.reason } };
|
|
114
148
|
}
|
|
115
149
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { OrchestrationDefinition, WorkflowManifest } from
|
|
2
|
-
import { type LocalStepTrace } from
|
|
3
|
-
import { type StubFile } from
|
|
1
|
+
import type { OrchestrationDefinition, WorkflowManifest } from "@sapiom/orchestration";
|
|
2
|
+
import { type LocalStepTrace } from "./dispatcher.js";
|
|
3
|
+
import { type StubFile } from "./stubs.js";
|
|
4
4
|
export declare const STUBS_FILE: string;
|
|
5
5
|
export interface RunLocalOptions {
|
|
6
6
|
definition: OrchestrationDefinition;
|
|
@@ -9,13 +9,19 @@ export interface RunLocalOptions {
|
|
|
9
9
|
stubs?: StubFile;
|
|
10
10
|
maxAttemptsPerStep?: number;
|
|
11
11
|
}
|
|
12
|
-
export type LocalRunOutcome =
|
|
12
|
+
export type LocalRunOutcome = "completed" | "failed" | "paused" | "running";
|
|
13
|
+
export interface UnusedStub {
|
|
14
|
+
step: string;
|
|
15
|
+
key: string;
|
|
16
|
+
}
|
|
13
17
|
export interface LocalRunResult {
|
|
14
18
|
outcome: LocalRunOutcome;
|
|
15
19
|
executionId: string;
|
|
16
20
|
output?: unknown;
|
|
17
21
|
error?: unknown;
|
|
18
22
|
steps: LocalStepTrace[];
|
|
23
|
+
unusedStubs: UnusedStub[];
|
|
24
|
+
stubWarnings: string[];
|
|
19
25
|
}
|
|
20
26
|
export declare function runLocal(opts: RunLocalOptions): Promise<LocalRunResult>;
|
|
21
27
|
export declare function runLocalFromDir(opts: {
|
|
@@ -13,18 +13,18 @@ const errors_js_1 = require("../errors.js");
|
|
|
13
13
|
const dispatcher_js_1 = require("./dispatcher.js");
|
|
14
14
|
const load_js_1 = require("./load.js");
|
|
15
15
|
const stubs_js_1 = require("./stubs.js");
|
|
16
|
-
exports.STUBS_FILE = node_path_1.default.join(
|
|
16
|
+
exports.STUBS_FILE = node_path_1.default.join(".sapiom-dev", "stubs.json");
|
|
17
17
|
function loadStubsFile(sourceDir) {
|
|
18
18
|
const file = node_path_1.default.join(sourceDir, exports.STUBS_FILE);
|
|
19
19
|
if (!(0, node_fs_1.existsSync)(file))
|
|
20
20
|
return undefined;
|
|
21
21
|
let raw;
|
|
22
22
|
try {
|
|
23
|
-
raw = JSON.parse((0, node_fs_1.readFileSync)(file,
|
|
23
|
+
raw = JSON.parse((0, node_fs_1.readFileSync)(file, "utf8"));
|
|
24
24
|
}
|
|
25
25
|
catch (err) {
|
|
26
26
|
throw new errors_js_1.OrchestrationError({
|
|
27
|
-
code:
|
|
27
|
+
code: "STUBS_INVALID",
|
|
28
28
|
message: `${exports.STUBS_FILE} is not valid JSON.`,
|
|
29
29
|
hint: err instanceof Error ? err.message : String(err),
|
|
30
30
|
});
|
|
@@ -39,7 +39,11 @@ async function runLocal(opts) {
|
|
|
39
39
|
const dispatcher = new dispatcher_js_1.LocalStubDispatcher(opts.definition, stubs);
|
|
40
40
|
const signals = new Map();
|
|
41
41
|
dispatcher.setSignals(signals);
|
|
42
|
-
const core = new orchestration_runtime_1.WorkflowRunnerCore({
|
|
42
|
+
const core = new orchestration_runtime_1.WorkflowRunnerCore({
|
|
43
|
+
store,
|
|
44
|
+
dispatcher,
|
|
45
|
+
observer: orchestration_runtime_1.NOOP_OBSERVER,
|
|
46
|
+
});
|
|
43
47
|
dispatcher.setCore(core);
|
|
44
48
|
dispatcher.setMaxAttempts(max);
|
|
45
49
|
const executionId = await core.createExecution(opts.definition.name, opts.definition.entry, opts.input, {
|
|
@@ -49,25 +53,43 @@ async function runLocal(opts) {
|
|
|
49
53
|
while (guard++ < MAX_ADVANCES) {
|
|
50
54
|
await core.advance(executionId, max);
|
|
51
55
|
const row = await store.loadExecution(executionId);
|
|
52
|
-
if (!row ||
|
|
56
|
+
if (!row ||
|
|
57
|
+
row.status === "completed" ||
|
|
58
|
+
row.status === "failed" ||
|
|
59
|
+
row.status === "cancelled")
|
|
53
60
|
break;
|
|
54
|
-
if (row.status ===
|
|
55
|
-
const payload = signals.get(row.pausedSignalCorrelationId ??
|
|
61
|
+
if (row.status === "paused") {
|
|
62
|
+
const payload = signals.get(row.pausedSignalCorrelationId ?? "") ?? {};
|
|
56
63
|
await core.resetForResume(executionId, { fromStepInput: payload });
|
|
57
64
|
}
|
|
58
65
|
}
|
|
59
66
|
const final = await store.loadExecution(executionId);
|
|
60
|
-
const outcome = final?.status ===
|
|
67
|
+
const outcome = final?.status === "cancelled" ? "failed" : (final?.status ?? "running");
|
|
68
|
+
const unusedStubs = [];
|
|
69
|
+
for (const [step, used] of dispatcher.usedKeysByStep) {
|
|
70
|
+
for (const key of Object.keys(stubs.steps[step] ?? {})) {
|
|
71
|
+
if (!used.has(key))
|
|
72
|
+
unusedStubs.push({ step, key });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
61
75
|
return {
|
|
62
76
|
outcome,
|
|
63
77
|
executionId,
|
|
64
78
|
output: final?.output,
|
|
65
79
|
error: final?.error,
|
|
66
80
|
steps: dispatcher.trace,
|
|
81
|
+
unusedStubs,
|
|
82
|
+
stubWarnings: [...dispatcher.stubWarnings],
|
|
67
83
|
};
|
|
68
84
|
}
|
|
69
85
|
async function runLocalFromDir(opts) {
|
|
70
86
|
const { definition, manifest } = await (0, load_js_1.loadDefinition)(opts.sourceDir);
|
|
71
87
|
const stubs = opts.stubs ?? loadStubsFile(opts.sourceDir);
|
|
72
|
-
return runLocal({
|
|
88
|
+
return runLocal({
|
|
89
|
+
definition,
|
|
90
|
+
manifest,
|
|
91
|
+
input: opts.input,
|
|
92
|
+
stubs,
|
|
93
|
+
maxAttemptsPerStep: opts.maxAttemptsPerStep,
|
|
94
|
+
});
|
|
73
95
|
}
|
package/dist/cjs/scaffold.d.ts
CHANGED
package/dist/cjs/scaffold.js
CHANGED
|
@@ -8,34 +8,37 @@ exports.resolveVersions = resolveVersions;
|
|
|
8
8
|
exports.listTemplates = listTemplates;
|
|
9
9
|
exports.resolveTemplate = resolveTemplate;
|
|
10
10
|
exports.scaffold = scaffold;
|
|
11
|
+
const node_child_process_1 = require("node:child_process");
|
|
11
12
|
const node_fs_1 = require("node:fs");
|
|
12
13
|
const node_path_1 = __importDefault(require("node:path"));
|
|
13
14
|
const node_url_1 = require("node:url");
|
|
14
15
|
const errors_js_1 = require("./errors.js");
|
|
15
16
|
function resolveModuleDir() {
|
|
16
|
-
if (typeof __dirname !==
|
|
17
|
+
if (typeof __dirname !== "undefined")
|
|
17
18
|
return __dirname;
|
|
18
19
|
try {
|
|
19
|
-
const metaUrl = eval(
|
|
20
|
-
if (typeof metaUrl ===
|
|
20
|
+
const metaUrl = eval("import.meta.url");
|
|
21
|
+
if (typeof metaUrl === "string")
|
|
21
22
|
return node_path_1.default.dirname((0, node_url_1.fileURLToPath)(metaUrl));
|
|
22
23
|
}
|
|
23
24
|
catch {
|
|
24
25
|
}
|
|
25
|
-
return
|
|
26
|
+
return "";
|
|
26
27
|
}
|
|
27
28
|
const moduleDir = resolveModuleDir();
|
|
28
29
|
function getTemplatesDir(override) {
|
|
29
|
-
return override ??
|
|
30
|
+
return (override ??
|
|
31
|
+
process.env.SAPIOM_TEMPLATES_DIR ??
|
|
32
|
+
node_path_1.default.resolve(moduleDir, "..", "..", "templates"));
|
|
30
33
|
}
|
|
31
|
-
exports.DEFAULT_TEMPLATE =
|
|
32
|
-
const DOTFILE_NAMES = new Set([
|
|
33
|
-
const REGISTRY =
|
|
34
|
+
exports.DEFAULT_TEMPLATE = "default";
|
|
35
|
+
const DOTFILE_NAMES = new Set(["_gitignore", "_npmrc"]);
|
|
36
|
+
const REGISTRY = "https://registry.npmjs.org";
|
|
34
37
|
const VERSION_FALLBACK = {
|
|
35
|
-
orchestration:
|
|
36
|
-
tools:
|
|
38
|
+
orchestration: "0.1.1",
|
|
39
|
+
tools: "0.1.1",
|
|
37
40
|
};
|
|
38
|
-
const ZOD_VERSION =
|
|
41
|
+
const ZOD_VERSION = "3.25.76";
|
|
39
42
|
async function latestNpmVersion(pkg) {
|
|
40
43
|
try {
|
|
41
44
|
const res = await fetch(`${REGISTRY}/${encodeURIComponent(pkg)}/latest`, {
|
|
@@ -44,7 +47,7 @@ async function latestNpmVersion(pkg) {
|
|
|
44
47
|
if (!res.ok)
|
|
45
48
|
return null;
|
|
46
49
|
const json = (await res.json());
|
|
47
|
-
return typeof json.version ===
|
|
50
|
+
return typeof json.version === "string" ? json.version : null;
|
|
48
51
|
}
|
|
49
52
|
catch {
|
|
50
53
|
return null;
|
|
@@ -52,8 +55,8 @@ async function latestNpmVersion(pkg) {
|
|
|
52
55
|
}
|
|
53
56
|
async function resolveVersions() {
|
|
54
57
|
const [orchestration, tools] = await Promise.all([
|
|
55
|
-
latestNpmVersion(
|
|
56
|
-
latestNpmVersion(
|
|
58
|
+
latestNpmVersion("@sapiom/orchestration"),
|
|
59
|
+
latestNpmVersion("@sapiom/tools"),
|
|
57
60
|
]);
|
|
58
61
|
return {
|
|
59
62
|
orchestration: orchestration ?? VERSION_FALLBACK.orchestration,
|
|
@@ -72,9 +75,11 @@ function resolveTemplate(name, templatesDir) {
|
|
|
72
75
|
if (!(0, node_fs_1.existsSync)(dir) || !(0, node_fs_1.statSync)(dir).isDirectory()) {
|
|
73
76
|
const available = listTemplates(templatesDir);
|
|
74
77
|
throw new errors_js_1.OrchestrationError({
|
|
75
|
-
code:
|
|
78
|
+
code: "UNKNOWN_TEMPLATE",
|
|
76
79
|
message: `Unknown template '${name}'.` +
|
|
77
|
-
(available.length
|
|
80
|
+
(available.length
|
|
81
|
+
? ` Available: ${available.join(", ")}.`
|
|
82
|
+
: " No templates are bundled."),
|
|
78
83
|
});
|
|
79
84
|
}
|
|
80
85
|
return dir;
|
|
@@ -82,7 +87,7 @@ function resolveTemplate(name, templatesDir) {
|
|
|
82
87
|
function applyReplacements(file, replacements) {
|
|
83
88
|
let content;
|
|
84
89
|
try {
|
|
85
|
-
content = (0, node_fs_1.readFileSync)(file,
|
|
90
|
+
content = (0, node_fs_1.readFileSync)(file, "utf8");
|
|
86
91
|
}
|
|
87
92
|
catch {
|
|
88
93
|
return;
|
|
@@ -111,7 +116,7 @@ function copyTemplate(templateDir, targetDir, replacements) {
|
|
|
111
116
|
walk(targetDir, (file) => {
|
|
112
117
|
const base = node_path_1.default.basename(file);
|
|
113
118
|
if (DOTFILE_NAMES.has(base)) {
|
|
114
|
-
const dotted = node_path_1.default.join(node_path_1.default.dirname(file),
|
|
119
|
+
const dotted = node_path_1.default.join(node_path_1.default.dirname(file), "." + base.slice(1));
|
|
115
120
|
(0, node_fs_1.renameSync)(file, dotted);
|
|
116
121
|
applyReplacements(dotted, replacements);
|
|
117
122
|
return;
|
|
@@ -119,13 +124,37 @@ function copyTemplate(templateDir, targetDir, replacements) {
|
|
|
119
124
|
applyReplacements(file, replacements);
|
|
120
125
|
});
|
|
121
126
|
}
|
|
127
|
+
function initGitRepo(dir) {
|
|
128
|
+
const tryGit = (args) => {
|
|
129
|
+
try {
|
|
130
|
+
(0, node_child_process_1.execFileSync)("git", args, { cwd: dir, stdio: "ignore" });
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
if (!tryGit(["init"]))
|
|
138
|
+
return false;
|
|
139
|
+
tryGit(["add", "-A"]);
|
|
140
|
+
return (tryGit(["commit", "-m", "Initial commit"]) ||
|
|
141
|
+
tryGit([
|
|
142
|
+
"-c",
|
|
143
|
+
"user.name=Sapiom",
|
|
144
|
+
"-c",
|
|
145
|
+
"user.email=noreply@sapiom.ai",
|
|
146
|
+
"commit",
|
|
147
|
+
"-m",
|
|
148
|
+
"Initial commit",
|
|
149
|
+
]));
|
|
150
|
+
}
|
|
122
151
|
async function scaffold(opts) {
|
|
123
152
|
const { targetDir } = opts;
|
|
124
153
|
const template = opts.template ?? exports.DEFAULT_TEMPLATE;
|
|
125
154
|
const projectName = opts.projectName ?? node_path_1.default.basename(targetDir);
|
|
126
155
|
if ((0, node_fs_1.existsSync)(targetDir) && (0, node_fs_1.readdirSync)(targetDir).length > 0) {
|
|
127
156
|
throw new errors_js_1.OrchestrationError({
|
|
128
|
-
code:
|
|
157
|
+
code: "DIR_NOT_EMPTY",
|
|
129
158
|
message: `Target directory '${targetDir}' already exists and is not empty.`,
|
|
130
159
|
});
|
|
131
160
|
}
|
|
@@ -138,8 +167,9 @@ async function scaffold(opts) {
|
|
|
138
167
|
__TOOLS_VERSION__: versions.tools,
|
|
139
168
|
__ZOD_VERSION__: versions.zod,
|
|
140
169
|
});
|
|
141
|
-
const devDir = node_path_1.default.join(targetDir,
|
|
170
|
+
const devDir = node_path_1.default.join(targetDir, ".sapiom-dev");
|
|
142
171
|
(0, node_fs_1.mkdirSync)(devDir, { recursive: true });
|
|
143
|
-
(0, node_fs_1.writeFileSync)(node_path_1.default.join(devDir,
|
|
144
|
-
|
|
172
|
+
(0, node_fs_1.writeFileSync)(node_path_1.default.join(devDir, "stubs.json"), JSON.stringify({ version: 1, steps: {} }, null, 2) + "\n");
|
|
173
|
+
const gitInitialized = initGitRepo(targetDir);
|
|
174
|
+
return { targetDir, template, projectName, gitInitialized };
|
|
145
175
|
}
|