stageflow 0.19.0 → 0.23.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/README.md +2 -1
- package/dist/agent/activity.d.ts +5 -0
- package/dist/agent/activityObserver.d.ts +3 -0
- package/dist/agent/activityObserver.js +6 -1
- package/dist/agent/piAdapter.js +7 -3
- package/dist/agent/port.d.ts +2 -0
- package/dist/agent/providerInspect.d.ts +19 -0
- package/dist/agent/providerInspect.js +35 -0
- package/dist/agent/streamLogRedact.d.ts +2 -0
- package/dist/agent/streamLogRedact.js +13 -0
- package/dist/cli/artifactCommand.js +2 -2
- package/dist/cli/envelopeCommand.js +2 -2
- package/dist/cli/exportRunCommand.js +2 -2
- package/dist/cli/hostClient.d.ts +43 -0
- package/dist/cli/hostClient.js +182 -0
- package/dist/cli/providersCommand.js +5 -3
- package/dist/cli/runCommand.d.ts +6 -5
- package/dist/cli/runCommand.js +23 -31
- package/dist/cli/runOutput.d.ts +1 -1
- package/dist/cli/runsCommand.d.ts +3 -7
- package/dist/cli/runsCommand.js +33 -94
- package/dist/cli.js +1 -5
- package/dist/config/describePipeline.d.ts +26 -0
- package/dist/config/describePipeline.js +37 -0
- package/dist/config/loadPipeline.d.ts +13 -1
- package/dist/config/loadPipeline.js +34 -4
- package/dist/config/mergePipelineIncludes.d.ts +11 -0
- package/dist/config/mergePipelineIncludes.js +75 -0
- package/dist/config/validateCatalog.d.ts +2 -2
- package/dist/config/validateCatalog.js +24 -17
- package/dist/mcp/catalogTools.js +146 -52
- package/dist/mcp/controlTools.js +77 -19
- package/dist/mcp/deps.d.ts +3 -0
- package/dist/mcp/projectMcpTools.d.ts +3 -0
- package/dist/mcp/projectMcpTools.js +25 -0
- package/dist/mcp/providerTools.d.ts +3 -0
- package/dist/mcp/providerTools.js +17 -0
- package/dist/mcp/readArtifact.d.ts +9 -0
- package/dist/mcp/readArtifact.js +11 -0
- package/dist/mcp/server.js +9 -1
- package/dist/mcp/tailStreamLog.d.ts +8 -0
- package/dist/mcp/tailStreamLog.js +28 -0
- package/dist/mcp/toolResults.d.ts +17 -0
- package/dist/mcp/toolResults.js +15 -0
- package/dist/mcp/tools.js +4 -0
- package/dist/mcp/waitRun.js +4 -4
- package/dist/mcp/waitingGates.d.ts +1 -0
- package/dist/mcp/waitingGates.js +3 -2
- package/dist/package-meta.d.ts +1 -1
- package/dist/package-meta.js +1 -1
- package/dist/projection/projectRun.d.ts +3 -0
- package/dist/projection/projectRun.js +7 -0
- package/dist/runstore/port.d.ts +3 -1
- package/dist/runstore/sqlite/SqliteRunStore.d.ts +2 -1
- package/dist/runstore/sqlite/SqliteRunStore.js +15 -1
- package/dist/runstore/workspaceLayout.d.ts +1 -0
- package/dist/runstore/workspaceLayout.js +3 -0
- package/dist/runtime/feedbackLoopCoordinator.d.ts +9 -1
- package/dist/runtime/feedbackLoopCoordinator.js +50 -20
- package/dist/runtime/feedbackLoopDecision.js +33 -1
- package/dist/runtime/pipelineRunner.d.ts +3 -3
- package/dist/runtime/pipelineRunner.js +3 -1
- package/dist/runtime/resumeReconstruct.js +2 -2
- package/dist/runtime/runManager.d.ts +2 -1
- package/dist/runtime/runManager.js +36 -16
- package/dist/runtime/settingsFile.d.ts +2 -0
- package/dist/runtime/settingsFile.js +19 -0
- package/dist/runtime/stageAttemptBootstrap.d.ts +3 -0
- package/dist/runtime/stageAttemptBootstrap.js +8 -2
- package/dist/runtime/stageStreamLog.d.ts +17 -0
- package/dist/runtime/stageStreamLog.js +89 -0
- package/dist/runtime/stageWorker.js +2 -1
- package/dist/server/bootstrap.js +9 -2
- package/dist/server/ensureGlobalService.d.ts +35 -0
- package/dist/server/ensureGlobalService.js +138 -0
- package/dist/server/http.d.ts +20 -1
- package/dist/server/http.js +450 -402
- package/dist/server/mcpHost.js +7 -8
- package/dist/server/operatorResults.d.ts +10 -0
- package/dist/server/operatorResults.js +24 -0
- package/dist/server/providerRoutes.d.ts +0 -6
- package/dist/server/providerRoutes.js +10 -18
- package/dist/types/pipeline.d.ts +8 -0
- package/dist/ui/assets/{index-DMVdl1qB.js → index-Da5EHU6M.js} +5 -5
- package/dist/ui/index.html +1 -1
- package/package.json +1 -1
- package/skills/stageflow/references/control-surface.md +2 -2
- package/skills/stageflow-run/SKILL.md +27 -9
- package/skills/stageflow-run/references/mcp-call.md +1 -1
- package/skills/stageflow-run/scripts/mcp-call.mjs +9 -0
package/README.md
CHANGED
|
@@ -190,7 +190,7 @@ Runtime state lives in **`<git-root>/.stageflow/`** when inside a git repository
|
|
|
190
190
|
|
|
191
191
|
Host MCP via `sf ui` or `sf mcp` at `http://127.0.0.1:3847/mcp` (URL printed on boot). Sessions are the default. Point a Cursor (or other) MCP client at that URL. Do not run both hosts against the same store.
|
|
192
192
|
|
|
193
|
-
HITL-aware tools include `wait_run`, `answer_gate`, and `
|
|
193
|
+
HITL-aware tools include `wait_run`, `answer_gate`, `list_waiting`, and `decide_feedback_loop`. Inspect tools (`list_providers`, `list_models`, `describe_pipeline`, …) are read-only. Full tool list: [docs/mcp.md](docs/mcp.md).
|
|
194
194
|
|
|
195
195
|
Stages use project `.mcp.json` plus stage `mcp` names. See [YAML catalog — Stage MCP](docs/yaml-catalog.md#stage-mcp).
|
|
196
196
|
|
|
@@ -222,6 +222,7 @@ If you want deterministic YAML routing across many agents, look at [Conductor](h
|
|
|
222
222
|
| [playwright-mcp](examples/playwright-mcp/) | One stage: open a page and save a PNG screenshot (Playwright MCP) |
|
|
223
223
|
| [context7-mcp](examples/context7-mcp/) | Three stages: resolve a library, fetch docs, write a brief (Context7 MCP) |
|
|
224
224
|
| [github-release](examples/github-release/) | Dogfood: draft + publish GitHub Release |
|
|
225
|
+
| [mcp-hitl-tour](examples/mcp-hitl-tour/) | MCP-first HITL: gates, fan-out/join, `wait_for_human` decide |
|
|
225
226
|
| [ci-validate](examples/ci-validate/) | Strict validate in CI |
|
|
226
227
|
|
|
227
228
|
Index: [examples/README.md](examples/README.md)
|
package/dist/agent/activity.d.ts
CHANGED
|
@@ -86,6 +86,11 @@ export type StageLifecycleEvent = {
|
|
|
86
86
|
/** Operator chose to leave the verified failure terminal. */
|
|
87
87
|
| {
|
|
88
88
|
event: "manual_recovery_stopped";
|
|
89
|
+
} | {
|
|
90
|
+
event: "feedback_loop_decided";
|
|
91
|
+
decision: "extend" | "continue" | "abandon";
|
|
92
|
+
loopId: string;
|
|
93
|
+
reason?: string;
|
|
89
94
|
};
|
|
90
95
|
export type StageLogLine = StageActivityEvent | StageLifecycleEvent;
|
|
91
96
|
export declare function truncateActivityText(value: unknown, limit?: number): string | undefined;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type StageActivityEvent } from "./activity.js";
|
|
2
2
|
export declare const TOOL_PROGRESS_THROTTLE_MS = 300;
|
|
3
|
+
export declare const STREAM_FLUSH_THROTTLE_MS = 300;
|
|
3
4
|
export type StageActivityObserver = {
|
|
4
5
|
onAssistantTextDelta(delta: string): void;
|
|
5
6
|
onThinkingDelta(delta: string): void;
|
|
@@ -10,6 +11,8 @@ export type StageActivityObserver = {
|
|
|
10
11
|
};
|
|
11
12
|
export type CreateStageActivityObserverOptions = {
|
|
12
13
|
onActivity?: (event: StageActivityEvent) => void;
|
|
14
|
+
/** Fired for every non-empty assistant text delta, regardless of writeStderr. */
|
|
15
|
+
onAssistantTextDelta?: (delta: string) => void;
|
|
13
16
|
writeStderr?: boolean;
|
|
14
17
|
toolProgressThrottleMs?: number;
|
|
15
18
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { truncateActivityText, } from "./activity.js";
|
|
2
2
|
export const TOOL_PROGRESS_THROTTLE_MS = 300;
|
|
3
|
+
export const STREAM_FLUSH_THROTTLE_MS = 300;
|
|
3
4
|
function writeIndentedPreview(preview) {
|
|
4
5
|
for (const line of preview.split("\n")) {
|
|
5
6
|
process.stderr.write(` ${line}\n`);
|
|
@@ -127,7 +128,11 @@ export function createStageActivityObserver(options = {}) {
|
|
|
127
128
|
};
|
|
128
129
|
return {
|
|
129
130
|
onAssistantTextDelta(delta) {
|
|
130
|
-
if (!
|
|
131
|
+
if (!delta) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
options.onAssistantTextDelta?.(delta);
|
|
135
|
+
if (!writeStderr) {
|
|
131
136
|
return;
|
|
132
137
|
}
|
|
133
138
|
assistantStreamedForMessage = true;
|
package/dist/agent/piAdapter.js
CHANGED
|
@@ -371,8 +371,12 @@ export function routeSessionEventToProgress(event, options) {
|
|
|
371
371
|
* Map Pi session events to StageActivityEvent at the adapter edge, then
|
|
372
372
|
* forward to the activity observer (stderr + onActivity).
|
|
373
373
|
*/
|
|
374
|
-
function attachStageProgress(session, onActivity, usage) {
|
|
375
|
-
const observer = createStageActivityObserver({
|
|
374
|
+
function attachStageProgress(session, onActivity, usage, onAssistantTextDelta) {
|
|
375
|
+
const observer = createStageActivityObserver({
|
|
376
|
+
onActivity,
|
|
377
|
+
onAssistantTextDelta,
|
|
378
|
+
writeStderr: true,
|
|
379
|
+
});
|
|
376
380
|
const verbose = readActivityVerbose();
|
|
377
381
|
const unsubscribe = session.subscribe((event) => {
|
|
378
382
|
routeSessionEventToProgress(event, {
|
|
@@ -1119,7 +1123,7 @@ export class PiAgentAdapter {
|
|
|
1119
1123
|
return;
|
|
1120
1124
|
}
|
|
1121
1125
|
session = bound;
|
|
1122
|
-
unsubscribeProgress = attachStageProgress(session, input.onActivity, wiring.usage);
|
|
1126
|
+
unsubscribeProgress = attachStageProgress(session, input.onActivity, wiring.usage, input.onAssistantTextDelta);
|
|
1123
1127
|
})();
|
|
1124
1128
|
const runWithTimeout = async (work) => {
|
|
1125
1129
|
await preparePromise;
|
package/dist/agent/port.d.ts
CHANGED
|
@@ -60,6 +60,8 @@ export type StageRunInput = {
|
|
|
60
60
|
sessionMode?: StageSessionMode;
|
|
61
61
|
/** Optional observe hook; HITL wait/answer uses openStage beside this. */
|
|
62
62
|
onActivity?: (event: StageActivityEvent) => void;
|
|
63
|
+
/** Optional raw assistant-text-delta sink, fired alongside onActivity, not instead of it. */
|
|
64
|
+
onAssistantTextDelta?: (delta: string) => void;
|
|
63
65
|
skillFilePath?: string;
|
|
64
66
|
forkEmitContext?: ForkEmitContext;
|
|
65
67
|
cloneEmitContext?: CloneEmitContext;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type PiHomeDetectResult, type ProviderAuthContext, type ProviderAuthStatus, type ProviderSummary, type ProvidersListResult } from "./providerAuth.js";
|
|
2
|
+
export type ProviderReadinessRow = ProviderSummary & {
|
|
3
|
+
configured: boolean;
|
|
4
|
+
authKind?: ProviderAuthStatus["authKind"];
|
|
5
|
+
source?: string;
|
|
6
|
+
};
|
|
7
|
+
export type ProviderReadinessInspect = {
|
|
8
|
+
authShell: ProvidersListResult["authShell"];
|
|
9
|
+
via: ProvidersListResult["via"];
|
|
10
|
+
detect: PiHomeDetectResult;
|
|
11
|
+
providers: ProviderReadinessRow[];
|
|
12
|
+
};
|
|
13
|
+
export declare function inspectProviderReadiness(cwd: string, ctx?: ProviderAuthContext): Promise<ProviderReadinessInspect>;
|
|
14
|
+
export declare function mapProviderAuthError(err: unknown): {
|
|
15
|
+
status: number;
|
|
16
|
+
body: {
|
|
17
|
+
error: string;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { defaultContext, detectPiHome, getAuthStatus, listProviders, ProviderAuthError, } from "./providerAuth.js";
|
|
2
|
+
function statusList(statuses) {
|
|
3
|
+
return Array.isArray(statuses) ? statuses : [statuses];
|
|
4
|
+
}
|
|
5
|
+
export async function inspectProviderReadiness(cwd, ctx = defaultContext) {
|
|
6
|
+
const listed = await listProviders(cwd, ctx);
|
|
7
|
+
const statuses = statusList(await getAuthStatus(cwd, undefined, ctx));
|
|
8
|
+
const statusById = new Map(statuses.map((status) => [status.providerId, status]));
|
|
9
|
+
const detect = detectPiHome(cwd);
|
|
10
|
+
return {
|
|
11
|
+
authShell: listed.authShell,
|
|
12
|
+
via: listed.via,
|
|
13
|
+
detect,
|
|
14
|
+
providers: listed.providers.map((provider) => {
|
|
15
|
+
const status = statusById.get(provider.id);
|
|
16
|
+
return {
|
|
17
|
+
...provider,
|
|
18
|
+
configured: status?.configured ?? false,
|
|
19
|
+
...(status?.authKind !== undefined
|
|
20
|
+
? { authKind: status.authKind }
|
|
21
|
+
: {}),
|
|
22
|
+
...(status?.source !== undefined ? { source: status.source } : {}),
|
|
23
|
+
};
|
|
24
|
+
}),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export function mapProviderAuthError(err) {
|
|
28
|
+
if (err instanceof ProviderAuthError) {
|
|
29
|
+
return { status: err.status, body: { error: err.message } };
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
status: 500,
|
|
33
|
+
body: { error: "Provider auth operation failed" },
|
|
34
|
+
};
|
|
35
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const SECRET_PATTERNS = [
|
|
2
|
+
/\bsk-[A-Za-z0-9_-]{16,}\b/g,
|
|
3
|
+
/\bghp_[A-Za-z0-9]{20,}\b/g,
|
|
4
|
+
/\b[A-Za-z_]*KEY[A-Za-z_]*\s*[:=]\s*['"]?[A-Za-z0-9+/_-]{16,}['"]?/gi,
|
|
5
|
+
];
|
|
6
|
+
/** Scrub common secret-shaped substrings before text is persisted or returned. */
|
|
7
|
+
export function redactSecrets(text) {
|
|
8
|
+
let out = text;
|
|
9
|
+
for (const pattern of SECRET_PATTERNS) {
|
|
10
|
+
out = out.replace(pattern, "[redacted]");
|
|
11
|
+
}
|
|
12
|
+
return out;
|
|
13
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { writeFileSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { readRunArtifact } from "../mcp/readArtifact.js";
|
|
4
|
+
import { globalStageflowHome } from "../project/globalHome.js";
|
|
4
5
|
import { createRunStore } from "../runstore/createStore.js";
|
|
5
6
|
import { isInsideDir } from "../runstore/workspaceLayout.js";
|
|
6
7
|
export const ARTIFACT_USAGE = `Usage:
|
|
@@ -73,7 +74,6 @@ function resolveSafeOutPath(outPath, cwd) {
|
|
|
73
74
|
}
|
|
74
75
|
export async function runArtifactCommand(args, options = {}) {
|
|
75
76
|
const cwd = options.cwd ?? process.cwd();
|
|
76
|
-
const projectRoot = options.projectRoot ?? cwd;
|
|
77
77
|
const out = { ...defaultIo, ...options.io };
|
|
78
78
|
let parsed;
|
|
79
79
|
try {
|
|
@@ -99,7 +99,7 @@ export async function runArtifactCommand(args, options = {}) {
|
|
|
99
99
|
out.error(ARTIFACT_USAGE);
|
|
100
100
|
return 1;
|
|
101
101
|
}
|
|
102
|
-
const store = createRunStore({ rootDir:
|
|
102
|
+
const store = createRunStore({ rootDir: globalStageflowHome() });
|
|
103
103
|
try {
|
|
104
104
|
const contents = await readRunArtifact(store, parsed.runId, parsed.artifactPath);
|
|
105
105
|
if (parsed.outPath !== undefined) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { buildHandoffDeliverable, isHandoffSkipped, } from "./handoffFormat.js";
|
|
4
|
+
import { globalStageflowHome } from "../project/globalHome.js";
|
|
4
5
|
import { createRunStore } from "../runstore/createStore.js";
|
|
5
6
|
export const ENVELOPE_USAGE = `Usage:
|
|
6
7
|
sf envelope get --run <runId> --stage <stageId> [--json] [--from <sf-run.json>] [--detect-stage <id>] [--format envelope|handoff]`;
|
|
@@ -198,7 +199,6 @@ function formatHandoffHuman(result) {
|
|
|
198
199
|
}
|
|
199
200
|
export async function runEnvelopeCommand(args, options = {}) {
|
|
200
201
|
const cwd = options.cwd ?? process.cwd();
|
|
201
|
-
const projectRoot = options.projectRoot ?? cwd;
|
|
202
202
|
const out = { ...defaultIo, ...options.io };
|
|
203
203
|
let parsed;
|
|
204
204
|
try {
|
|
@@ -224,7 +224,7 @@ export async function runEnvelopeCommand(args, options = {}) {
|
|
|
224
224
|
out.error(ENVELOPE_USAGE);
|
|
225
225
|
return 1;
|
|
226
226
|
}
|
|
227
|
-
const store = createRunStore({ rootDir:
|
|
227
|
+
const store = createRunStore({ rootDir: globalStageflowHome() });
|
|
228
228
|
let runContext;
|
|
229
229
|
try {
|
|
230
230
|
runContext = resolveRunContext(parsed, cwd, store);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { projectRun } from "../projection/projectRun.js";
|
|
4
|
+
import { globalStageflowHome } from "../project/globalHome.js";
|
|
4
5
|
import { createRunStore } from "../runstore/createStore.js";
|
|
5
6
|
import { isInsideDir } from "../runstore/workspaceLayout.js";
|
|
6
7
|
export const EXPORT_RUN_USAGE = `Usage:
|
|
@@ -94,7 +95,6 @@ function assertRunComplete(status, runId) {
|
|
|
94
95
|
}
|
|
95
96
|
export async function runExportRunCommand(args, options = {}) {
|
|
96
97
|
const cwd = options.cwd ?? process.cwd();
|
|
97
|
-
const projectRoot = options.projectRoot ?? cwd;
|
|
98
98
|
const out = { ...defaultIo, ...options.io };
|
|
99
99
|
let parsed;
|
|
100
100
|
try {
|
|
@@ -126,7 +126,7 @@ export async function runExportRunCommand(args, options = {}) {
|
|
|
126
126
|
out.error(EXPORT_RUN_USAGE);
|
|
127
127
|
return 1;
|
|
128
128
|
}
|
|
129
|
-
const store = createRunStore({ rootDir:
|
|
129
|
+
const store = createRunStore({ rootDir: globalStageflowHome() });
|
|
130
130
|
try {
|
|
131
131
|
const detail = await store.readRun(runId);
|
|
132
132
|
assertRunComplete(detail.status, runId);
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { PipelineRunResult } from "../runtime/pipelineRunner.js";
|
|
2
|
+
import type { AbandonStageResult, DecideFeedbackLoopResult, StartRunResult, StopManualRecoveryResult } from "../runtime/runManager.js";
|
|
3
|
+
import type { DeliverAnswerResult } from "../runtime/stageHitl.js";
|
|
4
|
+
import type { RetryStageResult } from "../runtime/runRetryCoordinator.js";
|
|
5
|
+
import type { RunDetail, RunStore } from "../runstore/port.js";
|
|
6
|
+
export declare function computeRunDir(runId: string): string;
|
|
7
|
+
export declare function httpReadRun(base: string, runId: string): Promise<RunDetail>;
|
|
8
|
+
/** Thin RunStore-shaped reader backed by the global service, for the CLI output helpers that only need `readRun`. */
|
|
9
|
+
export declare function httpStoreReader(base: string): Pick<RunStore, "readRun">;
|
|
10
|
+
export declare function pollRunUntilTerminal(base: string, runId: string, options?: {
|
|
11
|
+
pollIntervalMs?: number;
|
|
12
|
+
}): Promise<RunDetail>;
|
|
13
|
+
export declare function runDetailToPipelineRunResult(detail: RunDetail): PipelineRunResult;
|
|
14
|
+
export type HttpStartRunInput = {
|
|
15
|
+
pipeline: string;
|
|
16
|
+
task: string;
|
|
17
|
+
checkoutOverride?: string;
|
|
18
|
+
skipGates?: boolean;
|
|
19
|
+
gitSha?: string;
|
|
20
|
+
ciPrUrl?: string;
|
|
21
|
+
ciJobUrl?: string;
|
|
22
|
+
};
|
|
23
|
+
export declare function httpStartRun(base: string, input: HttpStartRunInput): Promise<StartRunResult>;
|
|
24
|
+
export declare function httpRerun(base: string, runId: string): Promise<StartRunResult>;
|
|
25
|
+
export declare function httpDeliverAnswer(base: string, runId: string, stageId: string, answer: unknown): Promise<DeliverAnswerResult>;
|
|
26
|
+
export declare function httpDecideFeedbackLoop(base: string, runId: string, stageId: string, input: {
|
|
27
|
+
decision: "extend" | "continue" | "abandon";
|
|
28
|
+
loopId?: string;
|
|
29
|
+
reason?: string;
|
|
30
|
+
}): Promise<DecideFeedbackLoopResult>;
|
|
31
|
+
type RetryOrchestrationResult = {
|
|
32
|
+
ok: true;
|
|
33
|
+
pipeline: PipelineRunResult;
|
|
34
|
+
} | Extract<RetryStageResult, {
|
|
35
|
+
ok: false;
|
|
36
|
+
}>;
|
|
37
|
+
export declare function httpRetryStageUntilStop(base: string, runId: string, stageId: string): Promise<RetryOrchestrationResult>;
|
|
38
|
+
export declare function httpResumeTimedOutStage(base: string, runId: string, stageId: string): Promise<RetryStageResult>;
|
|
39
|
+
export declare function httpRecoverManualStageUntilStop(base: string, runId: string, stageId: string, guidance?: string): Promise<RetryOrchestrationResult>;
|
|
40
|
+
export declare function httpStopManualRecovery(base: string, runId: string, stageId: string): Promise<StopManualRecoveryResult>;
|
|
41
|
+
export declare function httpAbandonStage(base: string, runId: string, stageId: string): Promise<AbandonStageResult>;
|
|
42
|
+
export declare function resolveAbsolute(cwd: string, maybeRelative: string): string;
|
|
43
|
+
export {};
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { globalStageflowHome } from "../project/globalHome.js";
|
|
3
|
+
import { PipelineValidationError } from "../runtime/pipelineValidationError.js";
|
|
4
|
+
import { runWorkspaceDir, storeRootFor } from "../runstore/paths.js";
|
|
5
|
+
async function postJson(base, urlPath, body) {
|
|
6
|
+
const res = await fetch(`${base}${urlPath}`, {
|
|
7
|
+
method: "POST",
|
|
8
|
+
headers: { "Content-Type": "application/json" },
|
|
9
|
+
body: JSON.stringify(body ?? {}),
|
|
10
|
+
});
|
|
11
|
+
return { status: res.status, body: await parseJsonBody(res) };
|
|
12
|
+
}
|
|
13
|
+
async function getJson(base, urlPath) {
|
|
14
|
+
const res = await fetch(`${base}${urlPath}`);
|
|
15
|
+
return { status: res.status, body: await parseJsonBody(res) };
|
|
16
|
+
}
|
|
17
|
+
async function parseJsonBody(res) {
|
|
18
|
+
const text = await res.text();
|
|
19
|
+
if (text.length === 0)
|
|
20
|
+
return undefined;
|
|
21
|
+
try {
|
|
22
|
+
return JSON.parse(text);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function extractError(body, status) {
|
|
29
|
+
if (body && typeof body === "object" && typeof body.error === "string") {
|
|
30
|
+
return body.error;
|
|
31
|
+
}
|
|
32
|
+
return `request to global Stageflow service failed (status ${status})`;
|
|
33
|
+
}
|
|
34
|
+
function enc(value) {
|
|
35
|
+
return encodeURIComponent(value);
|
|
36
|
+
}
|
|
37
|
+
export function computeRunDir(runId) {
|
|
38
|
+
return runWorkspaceDir(storeRootFor(globalStageflowHome()), runId);
|
|
39
|
+
}
|
|
40
|
+
export async function httpReadRun(base, runId) {
|
|
41
|
+
const { status, body } = await getJson(base, `/api/runs/${enc(runId)}`);
|
|
42
|
+
if (status !== 200) {
|
|
43
|
+
throw new Error(extractError(body, status));
|
|
44
|
+
}
|
|
45
|
+
return body;
|
|
46
|
+
}
|
|
47
|
+
/** Thin RunStore-shaped reader backed by the global service, for the CLI output helpers that only need `readRun`. */
|
|
48
|
+
export function httpStoreReader(base) {
|
|
49
|
+
return { readRun: (runId) => httpReadRun(base, runId) };
|
|
50
|
+
}
|
|
51
|
+
function isRunTerminal(detail) {
|
|
52
|
+
return (detail.status === "succeeded" ||
|
|
53
|
+
detail.status === "failed" ||
|
|
54
|
+
(detail.status === "running" && detail.waiting_stage_id !== undefined));
|
|
55
|
+
}
|
|
56
|
+
function sleep(ms) {
|
|
57
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
58
|
+
}
|
|
59
|
+
export async function pollRunUntilTerminal(base, runId, options) {
|
|
60
|
+
const pollIntervalMs = options?.pollIntervalMs ?? 500;
|
|
61
|
+
for (;;) {
|
|
62
|
+
const detail = await httpReadRun(base, runId);
|
|
63
|
+
if (isRunTerminal(detail))
|
|
64
|
+
return detail;
|
|
65
|
+
await sleep(pollIntervalMs);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
export function runDetailToPipelineRunResult(detail) {
|
|
69
|
+
const outcome = detail.status === "succeeded"
|
|
70
|
+
? "succeeded"
|
|
71
|
+
: detail.status === "failed"
|
|
72
|
+
? "failed"
|
|
73
|
+
: "waiting";
|
|
74
|
+
return {
|
|
75
|
+
ok: outcome === "succeeded",
|
|
76
|
+
outcome,
|
|
77
|
+
runId: detail.run_id,
|
|
78
|
+
runDir: computeRunDir(detail.run_id),
|
|
79
|
+
...(outcome === "failed" && detail.failed_reason !== undefined
|
|
80
|
+
? { reason: detail.failed_reason }
|
|
81
|
+
: {}),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function toStartFailure(status, body) {
|
|
85
|
+
const rest = body && typeof body === "object" ? { ...body } : {};
|
|
86
|
+
const error = typeof rest.error === "string" ? rest.error : undefined;
|
|
87
|
+
delete rest.error;
|
|
88
|
+
return {
|
|
89
|
+
ok: false,
|
|
90
|
+
reason: error ?? extractError(body, status),
|
|
91
|
+
status,
|
|
92
|
+
...rest,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function validationResultFrom(body) {
|
|
96
|
+
if (body && typeof body === "object" && "validation" in body) {
|
|
97
|
+
return body.validation;
|
|
98
|
+
}
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
export async function httpStartRun(base, input) {
|
|
102
|
+
const { status, body } = await postJson(base, "/api/runs", input);
|
|
103
|
+
if (status === 400) {
|
|
104
|
+
const validation = validationResultFrom(body);
|
|
105
|
+
if (validation !== undefined)
|
|
106
|
+
throw new PipelineValidationError(validation);
|
|
107
|
+
}
|
|
108
|
+
if (status !== 202)
|
|
109
|
+
return toStartFailure(status, body);
|
|
110
|
+
const runId = body.runId;
|
|
111
|
+
return {
|
|
112
|
+
ok: true,
|
|
113
|
+
runId,
|
|
114
|
+
done: pollRunUntilTerminal(base, runId).then(runDetailToPipelineRunResult),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
export async function httpRerun(base, runId) {
|
|
118
|
+
const { status, body } = await postJson(base, `/api/runs/${enc(runId)}/rerun`, {});
|
|
119
|
+
if (status !== 202)
|
|
120
|
+
return toStartFailure(status, body);
|
|
121
|
+
const newRunId = body.runId;
|
|
122
|
+
return {
|
|
123
|
+
ok: true,
|
|
124
|
+
runId: newRunId,
|
|
125
|
+
done: pollRunUntilTerminal(base, newRunId).then(runDetailToPipelineRunResult),
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
export async function httpDeliverAnswer(base, runId, stageId, answer) {
|
|
129
|
+
const { status, body } = await postJson(base, `/api/runs/${enc(runId)}/stages/${enc(stageId)}/answer`, answer);
|
|
130
|
+
if (status === 202)
|
|
131
|
+
return { ok: true };
|
|
132
|
+
return { ok: false, reason: extractError(body, status), status: status };
|
|
133
|
+
}
|
|
134
|
+
export async function httpDecideFeedbackLoop(base, runId, stageId, input) {
|
|
135
|
+
const { status, body } = await postJson(base, `/api/runs/${enc(runId)}/stages/${enc(stageId)}/feedback-decision`, input);
|
|
136
|
+
if (status === 202) {
|
|
137
|
+
const parsed = body;
|
|
138
|
+
return { ok: true, effect: parsed.effect, loopId: parsed.loopId };
|
|
139
|
+
}
|
|
140
|
+
return { ok: false, reason: extractError(body, status), status };
|
|
141
|
+
}
|
|
142
|
+
export async function httpRetryStageUntilStop(base, runId, stageId) {
|
|
143
|
+
const { status, body } = await postJson(base, `/api/runs/${enc(runId)}/stages/${enc(stageId)}/retry`, {});
|
|
144
|
+
if (status !== 202) {
|
|
145
|
+
return { ok: false, reason: extractError(body, status), status };
|
|
146
|
+
}
|
|
147
|
+
const detail = await pollRunUntilTerminal(base, runId);
|
|
148
|
+
return { ok: true, pipeline: runDetailToPipelineRunResult(detail) };
|
|
149
|
+
}
|
|
150
|
+
export async function httpResumeTimedOutStage(base, runId, stageId) {
|
|
151
|
+
const { status, body } = await postJson(base, `/api/runs/${enc(runId)}/stages/${enc(stageId)}/resume`, {});
|
|
152
|
+
if (status === 202) {
|
|
153
|
+
const parsed = body;
|
|
154
|
+
return { ok: true, runId: parsed.runId, stageId: parsed.stageId, attemptIndex: parsed.attemptIndex };
|
|
155
|
+
}
|
|
156
|
+
return { ok: false, reason: extractError(body, status), status };
|
|
157
|
+
}
|
|
158
|
+
export async function httpRecoverManualStageUntilStop(base, runId, stageId, guidance) {
|
|
159
|
+
const { status, body } = await postJson(base, `/api/runs/${enc(runId)}/stages/${enc(stageId)}/recovery`, guidance !== undefined ? { guidance } : {});
|
|
160
|
+
if (status !== 202) {
|
|
161
|
+
return { ok: false, reason: extractError(body, status), status };
|
|
162
|
+
}
|
|
163
|
+
const detail = await pollRunUntilTerminal(base, runId);
|
|
164
|
+
return { ok: true, pipeline: runDetailToPipelineRunResult(detail) };
|
|
165
|
+
}
|
|
166
|
+
export async function httpStopManualRecovery(base, runId, stageId) {
|
|
167
|
+
const { status, body } = await postJson(base, `/api/runs/${enc(runId)}/stages/${enc(stageId)}/recovery/stop`, {});
|
|
168
|
+
if (status === 202)
|
|
169
|
+
return body;
|
|
170
|
+
return { ok: false, reason: extractError(body, status), status };
|
|
171
|
+
}
|
|
172
|
+
export async function httpAbandonStage(base, runId, stageId) {
|
|
173
|
+
const { status, body } = await postJson(base, `/api/runs/${enc(runId)}/stages/${enc(stageId)}/abandon`, {});
|
|
174
|
+
if (status === 202) {
|
|
175
|
+
const parsed = body;
|
|
176
|
+
return { ok: true, runId: parsed.runId, stageId: parsed.stageId };
|
|
177
|
+
}
|
|
178
|
+
return { ok: false, reason: extractError(body, status), status };
|
|
179
|
+
}
|
|
180
|
+
export function resolveAbsolute(cwd, maybeRelative) {
|
|
181
|
+
return path.resolve(cwd, maybeRelative);
|
|
182
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { defaultContext, detectPiHome, getAuthStatus, getCredentialSourceSettings, listProviders, loginWithApiKey, loginWithOauth, logoutProvider,
|
|
1
|
+
import { defaultContext, detectPiHome, getAuthStatus, getCredentialSourceSettings, listProviders, loginWithApiKey, loginWithOauth, logoutProvider, setCredentialSource, } from "../agent/providerAuth.js";
|
|
2
|
+
import { mapProviderAuthError } from "../agent/providerInspect.js";
|
|
2
3
|
import { createTerminalAuthInteraction } from "./terminalAuthInteraction.js";
|
|
3
4
|
import { promptSecret, readApiKeyFromEnv } from "./terminalSecret.js";
|
|
4
5
|
export const PROVIDERS_USAGE = `Usage:
|
|
@@ -152,8 +153,9 @@ export async function runProvidersCommand(args, cwd, io = {}, ctx = defaultConte
|
|
|
152
153
|
return 0;
|
|
153
154
|
}
|
|
154
155
|
catch (err) {
|
|
155
|
-
|
|
156
|
-
|
|
156
|
+
const mapped = mapProviderAuthError(err);
|
|
157
|
+
if (mapped.status === 404) {
|
|
158
|
+
out.error(mapped.body.error);
|
|
157
159
|
return 1;
|
|
158
160
|
}
|
|
159
161
|
throw err;
|
package/dist/cli/runCommand.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { RunStore } from "../runstore/port.js";
|
|
2
|
-
import {
|
|
2
|
+
import type { StartRunResult } from "../runtime/runManager.js";
|
|
3
|
+
import { type EnsureGlobalServiceResult } from "../server/ensureGlobalService.js";
|
|
3
4
|
import { type CliRunReportIo } from "./runOutput.js";
|
|
4
5
|
export declare const RUN_USAGE = "Usage:\n sf run --task <path> --pipeline <path> [--checkout <path>] [--json] [--include stages] [--skip-gates] [--git-sha <sha>] [--ci-pr-url <url>] [--ci-job-url <url>] [--operator-cwd <path>] [--operator-agent-dir <path>]";
|
|
5
6
|
export type RunCommandIo = CliRunReportIo;
|
|
@@ -16,15 +17,15 @@ export declare function completeCliRun(started: Extract<StartRunResult, {
|
|
|
16
17
|
ok: true;
|
|
17
18
|
}>, io?: RunCommandIo, options?: {
|
|
18
19
|
json?: boolean;
|
|
19
|
-
store?: RunStore
|
|
20
|
+
store?: Pick<RunStore, "readRun">;
|
|
20
21
|
includeStages?: boolean;
|
|
21
22
|
}): Promise<number>;
|
|
22
23
|
export declare function runRunCommand(args: string[], options?: {
|
|
23
24
|
cwd?: string;
|
|
24
|
-
projectRoot?: string;
|
|
25
|
-
isGitProject?: boolean;
|
|
26
25
|
io?: Partial<RunCommandIo>;
|
|
27
26
|
startRun?: StartRunFn;
|
|
28
|
-
store?: RunStore
|
|
27
|
+
store?: Pick<RunStore, "readRun">;
|
|
29
28
|
env?: Record<string, string | undefined>;
|
|
29
|
+
hostBaseUrl?: string;
|
|
30
|
+
ensureService?: () => Promise<EnsureGlobalServiceResult>;
|
|
30
31
|
}): Promise<number>;
|
package/dist/cli/runCommand.js
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
import { resolveGlobalOnlyAgentPort } from "../agent/resolveAgentPort.js";
|
|
2
|
-
import { createRunStore } from "../runstore/createStore.js";
|
|
3
1
|
import { PipelineValidationError } from "../runtime/pipelineRunner.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
2
|
+
import { ensureGlobalService, hostBaseUrl, } from "../server/ensureGlobalService.js";
|
|
3
|
+
import { httpStartRun, httpStoreReader, resolveAbsolute } from "./hostClient.js";
|
|
6
4
|
import { reportCliRun, } from "./runOutput.js";
|
|
7
5
|
import { resolveCiIdentity } from "./ciIdentity.js";
|
|
8
|
-
import { resolveOperatorCatalog } from "./operatorCatalog.js";
|
|
9
6
|
import { exitCodeForValidation, formatValidationHuman, formatValidationJson, } from "./validateOutput.js";
|
|
10
7
|
export const RUN_USAGE = `Usage:
|
|
11
8
|
sf run --task <path> --pipeline <path> [--checkout <path>] [--json] [--include stages] [--skip-gates] [--git-sha <sha>] [--ci-pr-url <url>] [--ci-job-url <url>] [--operator-cwd <path>] [--operator-agent-dir <path>]`;
|
|
@@ -133,19 +130,23 @@ function parseRunArgs(args) {
|
|
|
133
130
|
operatorAgentDir,
|
|
134
131
|
};
|
|
135
132
|
}
|
|
136
|
-
function defaultStartRun(cwd,
|
|
133
|
+
function defaultStartRun(cwd, base, ensureService) {
|
|
137
134
|
return async (input) => {
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
135
|
+
const ensured = await ensureService();
|
|
136
|
+
if (!ensured.ok) {
|
|
137
|
+
return { ok: false, reason: ensured.message, status: 503 };
|
|
138
|
+
}
|
|
139
|
+
return httpStartRun(base, {
|
|
140
|
+
pipeline: resolveAbsolute(cwd, input.pipeline),
|
|
141
|
+
task: resolveAbsolute(cwd, input.task),
|
|
142
|
+
...(input.checkoutOverride !== undefined
|
|
143
|
+
? { checkoutOverride: resolveAbsolute(cwd, input.checkoutOverride) }
|
|
144
|
+
: {}),
|
|
145
|
+
...(input.skipGates !== undefined ? { skipGates: input.skipGates } : {}),
|
|
146
|
+
...(input.gitSha !== undefined ? { gitSha: input.gitSha } : {}),
|
|
147
|
+
...(input.ciPrUrl !== undefined ? { ciPrUrl: input.ciPrUrl } : {}),
|
|
148
|
+
...(input.ciJobUrl !== undefined ? { ciJobUrl: input.ciJobUrl } : {}),
|
|
147
149
|
});
|
|
148
|
-
return manager.startRun(input);
|
|
149
150
|
};
|
|
150
151
|
}
|
|
151
152
|
export async function completeCliRun(started, io = defaultIo, options = {}) {
|
|
@@ -159,9 +160,8 @@ export async function completeCliRun(started, io = defaultIo, options = {}) {
|
|
|
159
160
|
}
|
|
160
161
|
export async function runRunCommand(args, options = {}) {
|
|
161
162
|
const cwd = options.cwd ?? process.cwd();
|
|
162
|
-
const projectRoot = options.projectRoot ?? cwd;
|
|
163
|
-
const isGitProject = options.isGitProject ?? false;
|
|
164
163
|
const out = { ...defaultIo, ...options.io };
|
|
164
|
+
const base = options.hostBaseUrl ?? hostBaseUrl();
|
|
165
165
|
let parsed;
|
|
166
166
|
try {
|
|
167
167
|
parsed = parseRunArgs(args);
|
|
@@ -185,16 +185,11 @@ export async function runRunCommand(args, options = {}) {
|
|
|
185
185
|
out.error("error: --include stages requires --json");
|
|
186
186
|
return 1;
|
|
187
187
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
operatorAgentDir: parsed.operatorAgentDir,
|
|
192
|
-
},
|
|
193
|
-
env: options.env ?? process.env,
|
|
194
|
-
defaultCwd: cwd,
|
|
195
|
-
});
|
|
188
|
+
if (parsed.operatorCwd !== undefined || parsed.operatorAgentDir !== undefined) {
|
|
189
|
+
out.error("warning: --operator-cwd/--operator-agent-dir have no effect on `sf run` now that pipelines execute in the shared global Stageflow service; set STAGEFLOW_OPERATOR_CWD/STAGEFLOW_OPERATOR_AGENT_DIR before that service first starts instead.");
|
|
190
|
+
}
|
|
196
191
|
const startRun = options.startRun ??
|
|
197
|
-
defaultStartRun(cwd,
|
|
192
|
+
defaultStartRun(cwd, base, options.ensureService ?? (() => ensureGlobalService()));
|
|
198
193
|
const identity = resolveCiIdentity({
|
|
199
194
|
flags: {
|
|
200
195
|
gitSha: parsed.gitSha,
|
|
@@ -203,10 +198,7 @@ export async function runRunCommand(args, options = {}) {
|
|
|
203
198
|
},
|
|
204
199
|
env: options.env ?? process.env,
|
|
205
200
|
});
|
|
206
|
-
const store = options.store ??
|
|
207
|
-
(parsed.includeStages
|
|
208
|
-
? createRunStore({ rootDir: projectRoot })
|
|
209
|
-
: undefined);
|
|
201
|
+
const store = options.store ?? (parsed.includeStages ? httpStoreReader(base) : undefined);
|
|
210
202
|
try {
|
|
211
203
|
const started = await startRun({
|
|
212
204
|
task: parsed.task,
|
package/dist/cli/runOutput.d.ts
CHANGED
|
@@ -17,6 +17,6 @@ export type CliRunReportEvent = {
|
|
|
17
17
|
export declare function reportCliRun(event: CliRunReportEvent, options: {
|
|
18
18
|
json?: boolean;
|
|
19
19
|
io: CliRunReportIo;
|
|
20
|
-
store?: RunStore
|
|
20
|
+
store?: Pick<RunStore, "readRun">;
|
|
21
21
|
includeStages?: boolean;
|
|
22
22
|
}): Promise<number>;
|
|
@@ -1,22 +1,18 @@
|
|
|
1
1
|
import type { RunStore } from "../runstore/port.js";
|
|
2
|
-
import {
|
|
2
|
+
import { type EnsureGlobalServiceResult } from "../server/ensureGlobalService.js";
|
|
3
3
|
export declare const RUNS_USAGE = "Usage:\n sf runs list [--status created|running|succeeded|failed] [--since <iso>] [--pipeline <id-or-path>] [--json]\n sf runs show --run <runId> [--from <sf-run.json>] [--json]\n sf runs verify --run <runId> --stage <stageId> [--json]\n sf runs recover --run <runId> --stage <stageId> [--guidance <text>] [--stop] [--json]\n sf runs waiting [--run <runId>] [--json]\n sf runs wait --run <runId> [--from <sf-run.json>] [--until any|waiting|terminal] [--timeout-ms <n>] [--json]\n sf runs answer --run <runId> --stage <stageId> [--answer '<json>'] [--json]\n sf runs feedback-decide --run <runId> --stage <sourceStageId> [--loop <loopId>] --decision extend|continue|abandon [--reason <text>] [--json]\n sf runs retry --run <runId> --stage <stageId> [--json]\n sf runs resume --run <runId> --stage <stageId> [--json]\n sf runs abandon --run <runId> --stage <stageId> [--json]\n sf runs rerun --run <runId> [--json]";
|
|
4
4
|
export type RunsCommandIo = {
|
|
5
5
|
log: (line: string) => void;
|
|
6
6
|
error: (line: string) => void;
|
|
7
7
|
};
|
|
8
|
-
export type HostProbeResult = "up" | "down";
|
|
9
|
-
export declare function defaultProbeHost(): Promise<HostProbeResult>;
|
|
10
8
|
export declare function runRunsCommand(args: string[], options?: {
|
|
11
9
|
cwd?: string;
|
|
12
|
-
projectRoot?: string;
|
|
13
|
-
isGitProject?: boolean;
|
|
14
10
|
io?: Partial<RunsCommandIo>;
|
|
15
11
|
store?: RunStore;
|
|
16
|
-
probeHost?: () => Promise<HostProbeResult>;
|
|
17
12
|
stdinIsTTY?: boolean;
|
|
18
13
|
readStdin?: () => string;
|
|
19
14
|
waitSignal?: AbortSignal;
|
|
20
15
|
env?: NodeJS.ProcessEnv;
|
|
21
|
-
|
|
16
|
+
hostBaseUrl?: string;
|
|
17
|
+
ensureService?: () => Promise<EnsureGlobalServiceResult>;
|
|
22
18
|
}): Promise<number>;
|