stageflow 0.19.0 → 0.25.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 +16 -3
- package/dist/a2a/configDiscovery.d.ts +9 -0
- package/dist/a2a/configDiscovery.js +18 -0
- package/dist/a2a/contracts.d.ts +93 -0
- package/dist/a2a/contracts.js +163 -0
- package/dist/a2a/limits.d.ts +14 -0
- package/dist/a2a/limits.js +29 -0
- package/dist/a2a/registry.d.ts +44 -0
- package/dist/a2a/registry.js +159 -0
- package/dist/a2a/server.d.ts +29 -0
- package/dist/a2a/server.js +341 -0
- package/dist/a2a/service.d.ts +97 -0
- package/dist/a2a/service.js +496 -0
- package/dist/a2a/store.d.ts +106 -0
- package/dist/a2a/store.js +245 -0
- 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/a2aCommand.d.ts +8 -0
- package/dist/cli/a2aCommand.js +131 -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/runStageCommand.d.ts +33 -0
- package/dist/cli/runStageCommand.js +407 -0
- package/dist/cli/runsCommand.d.ts +3 -7
- package/dist/cli/runsCommand.js +33 -94
- package/dist/cli.js +20 -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 +285 -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/createStore.d.ts +10 -0
- package/dist/runstore/createStore.js +13 -4
- package/dist/runstore/port.d.ts +17 -2
- package/dist/runstore/sqlite/SqliteRunStore.d.ts +11 -1
- package/dist/runstore/sqlite/SqliteRunStore.js +81 -26
- package/dist/runstore/sqlite/schema.d.ts +1 -1
- package/dist/runstore/sqlite/schema.js +6 -0
- package/dist/runstore/submission.d.ts +11 -0
- package/dist/runstore/submission.js +7 -0
- 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 +5 -3
- package/dist/runtime/pipelineRunner.js +6 -1
- package/dist/runtime/pipelineScheduler.js +32 -15
- package/dist/runtime/reloadRunCatalog.js +7 -1
- package/dist/runtime/resolveEnvelopeRef.d.ts +30 -0
- package/dist/runtime/resolveEnvelopeRef.js +79 -0
- package/dist/runtime/resumeReconstruct.js +2 -2
- package/dist/runtime/runManager.d.ts +24 -2
- package/dist/runtime/runManager.js +83 -19
- 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.d.ts +2 -0
- package/dist/server/bootstrap.js +22 -4
- package/dist/server/createHttpHost.js +6 -0
- 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 +2 -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
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { mkdir, unlink, writeFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import Database from "better-sqlite3";
|
|
5
|
+
import { storeRootFor } from "../runstore/paths.js";
|
|
6
|
+
import { MESSAGE_TOMBSTONE_RETENTION_MS, TERMINAL_RETENTION_MS } from "./limits.js";
|
|
7
|
+
const SCHEMA_SQL = `
|
|
8
|
+
CREATE TABLE IF NOT EXISTS a2a_contexts (
|
|
9
|
+
context_id TEXT PRIMARY KEY,
|
|
10
|
+
caller_id TEXT NOT NULL,
|
|
11
|
+
created_at TEXT NOT NULL
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
CREATE TABLE IF NOT EXISTS a2a_tasks (
|
|
15
|
+
task_id TEXT PRIMARY KEY,
|
|
16
|
+
context_id TEXT NOT NULL,
|
|
17
|
+
caller_id TEXT NOT NULL,
|
|
18
|
+
publication_id TEXT NOT NULL,
|
|
19
|
+
publication_revision TEXT NOT NULL,
|
|
20
|
+
submission_key TEXT NOT NULL UNIQUE,
|
|
21
|
+
run_id TEXT,
|
|
22
|
+
state TEXT NOT NULL,
|
|
23
|
+
result_json TEXT,
|
|
24
|
+
kind TEXT NOT NULL DEFAULT 'invoke',
|
|
25
|
+
result_stage_id TEXT,
|
|
26
|
+
created_at TEXT NOT NULL,
|
|
27
|
+
updated_at TEXT NOT NULL
|
|
28
|
+
);
|
|
29
|
+
CREATE INDEX IF NOT EXISTS a2a_tasks_context ON a2a_tasks(context_id);
|
|
30
|
+
CREATE INDEX IF NOT EXISTS a2a_tasks_caller ON a2a_tasks(caller_id);
|
|
31
|
+
|
|
32
|
+
CREATE TABLE IF NOT EXISTS a2a_messages (
|
|
33
|
+
caller_id TEXT NOT NULL,
|
|
34
|
+
message_id TEXT NOT NULL,
|
|
35
|
+
task_id TEXT NOT NULL,
|
|
36
|
+
operation TEXT NOT NULL,
|
|
37
|
+
request_hash TEXT NOT NULL,
|
|
38
|
+
outcome_json TEXT,
|
|
39
|
+
created_at TEXT NOT NULL,
|
|
40
|
+
PRIMARY KEY (caller_id, message_id)
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
CREATE TABLE IF NOT EXISTS a2a_artifacts (
|
|
44
|
+
artifact_id TEXT PRIMARY KEY,
|
|
45
|
+
task_id TEXT NOT NULL,
|
|
46
|
+
name TEXT NOT NULL,
|
|
47
|
+
media_type TEXT,
|
|
48
|
+
size INTEGER NOT NULL,
|
|
49
|
+
content_path TEXT NOT NULL,
|
|
50
|
+
hash TEXT NOT NULL,
|
|
51
|
+
created_at TEXT NOT NULL
|
|
52
|
+
);
|
|
53
|
+
CREATE INDEX IF NOT EXISTS a2a_artifacts_task ON a2a_artifacts(task_id);
|
|
54
|
+
`;
|
|
55
|
+
function nowIso() {
|
|
56
|
+
return new Date().toISOString();
|
|
57
|
+
}
|
|
58
|
+
export class A2aStore {
|
|
59
|
+
db;
|
|
60
|
+
artifactsRoot;
|
|
61
|
+
ownsConnection;
|
|
62
|
+
/**
|
|
63
|
+
* `connection`, when passed, is the SqliteRunStore's own connection to the same `state.db` file
|
|
64
|
+
* (see `createRunStoreWithConnection`) so the two stores share one transaction domain. Falls
|
|
65
|
+
* back to opening its own connection when no shared one is available (e.g. a caller-supplied
|
|
66
|
+
* non-SQLite `RunStore` in bootstrap).
|
|
67
|
+
*/
|
|
68
|
+
constructor(rootDir, connection) {
|
|
69
|
+
const storeRoot = storeRootFor(rootDir);
|
|
70
|
+
if (connection) {
|
|
71
|
+
this.db = connection;
|
|
72
|
+
this.ownsConnection = false;
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
this.db = new Database(path.join(storeRoot, "state.db"));
|
|
76
|
+
this.db.pragma("journal_mode = WAL");
|
|
77
|
+
this.db.pragma("busy_timeout = 5000");
|
|
78
|
+
this.ownsConnection = true;
|
|
79
|
+
}
|
|
80
|
+
this.db.exec(SCHEMA_SQL);
|
|
81
|
+
this.migrateAddStandaloneColumns();
|
|
82
|
+
this.artifactsRoot = path.join(storeRoot, "a2a-artifacts");
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Additive migration for a `state.db` created before the run_stage A2A
|
|
86
|
+
* operation existed: `CREATE TABLE IF NOT EXISTS` above never alters an
|
|
87
|
+
* already-existing a2a_tasks table, so a pre-existing one is missing these
|
|
88
|
+
* two columns. Same PRAGMA table_info-guarded ADD COLUMN pattern as
|
|
89
|
+
* src/runstore/sqlite/SqliteRunStore.ts.
|
|
90
|
+
*/
|
|
91
|
+
migrateAddStandaloneColumns() {
|
|
92
|
+
const cols = this.db.prepare(`PRAGMA table_info(a2a_tasks)`).all();
|
|
93
|
+
if (!cols.some((c) => c.name === "kind")) {
|
|
94
|
+
this.db.exec(`ALTER TABLE a2a_tasks ADD COLUMN kind TEXT NOT NULL DEFAULT 'invoke'`);
|
|
95
|
+
}
|
|
96
|
+
if (!cols.some((c) => c.name === "result_stage_id")) {
|
|
97
|
+
this.db.exec(`ALTER TABLE a2a_tasks ADD COLUMN result_stage_id TEXT`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
close() {
|
|
101
|
+
if (this.ownsConnection)
|
|
102
|
+
this.db.close();
|
|
103
|
+
}
|
|
104
|
+
ensureContext(callerId, contextId) {
|
|
105
|
+
if (contextId) {
|
|
106
|
+
const row = this.db
|
|
107
|
+
.prepare("SELECT caller_id FROM a2a_contexts WHERE context_id = ?")
|
|
108
|
+
.get(contextId);
|
|
109
|
+
if (row && row.caller_id !== callerId) {
|
|
110
|
+
throw new Error("Context belongs to a different caller");
|
|
111
|
+
}
|
|
112
|
+
if (row)
|
|
113
|
+
return contextId;
|
|
114
|
+
this.db
|
|
115
|
+
.prepare("INSERT INTO a2a_contexts (context_id, caller_id, created_at) VALUES (?, ?, ?)")
|
|
116
|
+
.run(contextId, callerId, nowIso());
|
|
117
|
+
return contextId;
|
|
118
|
+
}
|
|
119
|
+
const generated = randomUUID();
|
|
120
|
+
this.db
|
|
121
|
+
.prepare("INSERT INTO a2a_contexts (context_id, caller_id, created_at) VALUES (?, ?, ?)")
|
|
122
|
+
.run(generated, callerId, nowIso());
|
|
123
|
+
return generated;
|
|
124
|
+
}
|
|
125
|
+
getMessage(callerId, messageId) {
|
|
126
|
+
const row = this.db
|
|
127
|
+
.prepare("SELECT task_id AS taskId, request_hash AS requestHash, outcome_json AS outcomeJson FROM a2a_messages WHERE caller_id = ? AND message_id = ?")
|
|
128
|
+
.get(callerId, messageId);
|
|
129
|
+
if (!row)
|
|
130
|
+
return undefined;
|
|
131
|
+
return { taskId: row.taskId, requestHash: row.requestHash, outcome: row.outcomeJson ? JSON.parse(row.outcomeJson) : undefined };
|
|
132
|
+
}
|
|
133
|
+
recordMessage(params) {
|
|
134
|
+
this.db
|
|
135
|
+
.prepare("INSERT INTO a2a_messages (caller_id, message_id, task_id, operation, request_hash, outcome_json, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)")
|
|
136
|
+
.run(params.callerId, params.messageId, params.taskId, params.operation, params.requestHash, JSON.stringify(params.outcome), nowIso());
|
|
137
|
+
}
|
|
138
|
+
getTaskBySubmissionKey(key) {
|
|
139
|
+
return this.db.prepare("SELECT * FROM a2a_tasks WHERE submission_key = ?").get(key);
|
|
140
|
+
}
|
|
141
|
+
createTask(params) {
|
|
142
|
+
const at = nowIso();
|
|
143
|
+
this.db
|
|
144
|
+
.prepare(`INSERT INTO a2a_tasks
|
|
145
|
+
(task_id, context_id, caller_id, publication_id, publication_revision, submission_key, run_id, state, result_json, kind, result_stage_id, created_at, updated_at)
|
|
146
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, 'submitted', NULL, ?, ?, ?, ?)`)
|
|
147
|
+
.run(params.taskId, params.contextId, params.callerId, params.publicationId, params.publicationRevision, params.submissionKey, params.runId, params.kind ?? "invoke", params.resultStageId ?? null, at, at);
|
|
148
|
+
return this.getTask(params.taskId);
|
|
149
|
+
}
|
|
150
|
+
getTask(taskId) {
|
|
151
|
+
return this.db.prepare("SELECT * FROM a2a_tasks WHERE task_id = ?").get(taskId);
|
|
152
|
+
}
|
|
153
|
+
listTasksForCaller(callerId, contextId) {
|
|
154
|
+
if (contextId) {
|
|
155
|
+
return this.db
|
|
156
|
+
.prepare("SELECT * FROM a2a_tasks WHERE caller_id = ? AND context_id = ? ORDER BY created_at ASC")
|
|
157
|
+
.all(callerId, contextId);
|
|
158
|
+
}
|
|
159
|
+
return this.db
|
|
160
|
+
.prepare("SELECT * FROM a2a_tasks WHERE caller_id = ? ORDER BY created_at ASC")
|
|
161
|
+
.all(callerId);
|
|
162
|
+
}
|
|
163
|
+
getArtifact(taskId, artifactId) {
|
|
164
|
+
return this.db
|
|
165
|
+
.prepare("SELECT * FROM a2a_artifacts WHERE task_id = ? AND artifact_id = ?")
|
|
166
|
+
.get(taskId, artifactId);
|
|
167
|
+
}
|
|
168
|
+
listArtifacts(taskId) {
|
|
169
|
+
return this.db.prepare("SELECT * FROM a2a_artifacts WHERE task_id = ?").all(taskId);
|
|
170
|
+
}
|
|
171
|
+
/** Freezes a successful outcome exactly once; a second call for an already-terminal task is a no-op. */
|
|
172
|
+
async freezeCompleted(taskId, result) {
|
|
173
|
+
return this.freeze(taskId, "completed", result);
|
|
174
|
+
}
|
|
175
|
+
/** Freezes a failure exactly once; a second call for an already-terminal task is a no-op. */
|
|
176
|
+
async freezeFailed(taskId) {
|
|
177
|
+
return this.freeze(taskId, "failed", undefined);
|
|
178
|
+
}
|
|
179
|
+
async freeze(taskId, state, result) {
|
|
180
|
+
const current = this.getTask(taskId);
|
|
181
|
+
if (!current || current.state === "completed" || current.state === "failed")
|
|
182
|
+
return;
|
|
183
|
+
const artifactRows = [];
|
|
184
|
+
if (result) {
|
|
185
|
+
for (const artifact of result.artifacts) {
|
|
186
|
+
const artifactId = randomUUID();
|
|
187
|
+
const hash = createHash("sha256").update(artifact.bytes).digest("hex");
|
|
188
|
+
const dir = path.join(this.artifactsRoot, taskId);
|
|
189
|
+
await mkdir(dir, { recursive: true });
|
|
190
|
+
const contentPath = path.join(dir, artifactId);
|
|
191
|
+
await writeFile(contentPath, artifact.bytes);
|
|
192
|
+
artifactRows.push({
|
|
193
|
+
artifact_id: artifactId,
|
|
194
|
+
task_id: taskId,
|
|
195
|
+
name: artifact.name,
|
|
196
|
+
media_type: artifact.mediaType ?? null,
|
|
197
|
+
size: artifact.bytes.length,
|
|
198
|
+
content_path: contentPath,
|
|
199
|
+
hash,
|
|
200
|
+
created_at: nowIso(),
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
const resultJson = result ? JSON.stringify({ summary: result.summary, payload: result.payload }) : null;
|
|
205
|
+
const transact = this.db.transaction(() => {
|
|
206
|
+
const outcome = this.db
|
|
207
|
+
.prepare("UPDATE a2a_tasks SET state = ?, result_json = ?, updated_at = ? WHERE task_id = ? AND state NOT IN ('completed', 'failed')")
|
|
208
|
+
.run(state, resultJson, nowIso(), taskId);
|
|
209
|
+
if (outcome.changes === 0)
|
|
210
|
+
return;
|
|
211
|
+
for (const row of artifactRows) {
|
|
212
|
+
this.db
|
|
213
|
+
.prepare("INSERT INTO a2a_artifacts (artifact_id, task_id, name, media_type, size, content_path, hash, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)")
|
|
214
|
+
.run(row.artifact_id, row.task_id, row.name, row.media_type, row.size, row.content_path, row.hash, row.created_at);
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
transact();
|
|
218
|
+
}
|
|
219
|
+
countNonterminal(callerId) {
|
|
220
|
+
const row = this.db
|
|
221
|
+
.prepare("SELECT COUNT(*) AS n FROM a2a_tasks WHERE caller_id = ? AND state NOT IN ('completed', 'failed')")
|
|
222
|
+
.get(callerId);
|
|
223
|
+
return row.n;
|
|
224
|
+
}
|
|
225
|
+
/** Deletes terminal tasks (and their frozen artifact files) and message tombstones older than the retention window. */
|
|
226
|
+
async pruneExpired(now, terminalRetentionMs = TERMINAL_RETENTION_MS, messageRetentionMs = MESSAGE_TOMBSTONE_RETENTION_MS) {
|
|
227
|
+
const terminalCutoff = new Date(now.getTime() - terminalRetentionMs).toISOString();
|
|
228
|
+
const messageCutoff = new Date(now.getTime() - messageRetentionMs).toISOString();
|
|
229
|
+
const expiredTasks = this.db
|
|
230
|
+
.prepare("SELECT task_id FROM a2a_tasks WHERE state IN ('completed', 'failed') AND updated_at < ?")
|
|
231
|
+
.all(terminalCutoff);
|
|
232
|
+
for (const { task_id: taskId } of expiredTasks) {
|
|
233
|
+
for (const artifact of this.listArtifacts(taskId)) {
|
|
234
|
+
await unlink(artifact.content_path).catch(() => undefined);
|
|
235
|
+
}
|
|
236
|
+
this.db.transaction(() => {
|
|
237
|
+
this.db.prepare("DELETE FROM a2a_artifacts WHERE task_id = ?").run(taskId);
|
|
238
|
+
this.db.prepare("DELETE FROM a2a_messages WHERE task_id = ?").run(taskId);
|
|
239
|
+
this.db.prepare("DELETE FROM a2a_tasks WHERE task_id = ?").run(taskId);
|
|
240
|
+
})();
|
|
241
|
+
}
|
|
242
|
+
const removedMessages = this.db.prepare("DELETE FROM a2a_messages WHERE created_at < ?").run(messageCutoff).changes;
|
|
243
|
+
return { removedTasks: expiredTasks.length, removedMessages };
|
|
244
|
+
}
|
|
245
|
+
}
|
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
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const A2A_USAGE = "sf a2a validate [--config <path>]\nsf a2a list [--config <path>]\nsf a2a add-caller <id> [--config <path>] [--token-env <NAME>]\n\nValidate or inspect an A2A publication configuration, or register a new caller.\n--config defaults to <project-root>/a2a.yaml when omitted -- the same file\nSTAGEFLOW_A2A_CONFIG would point at if set. Caller credentials are read from\nthe environment variables named in the configuration; add-caller only writes\nthe caller's id and its token_env name to the file, never a secret value.";
|
|
2
|
+
export declare function runA2aCommand(args: string[], options: {
|
|
3
|
+
cwd: string;
|
|
4
|
+
projectRoot?: string;
|
|
5
|
+
env?: NodeJS.ProcessEnv;
|
|
6
|
+
log?: (line: string) => void;
|
|
7
|
+
error?: (line: string) => void;
|
|
8
|
+
}): Promise<number>;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { randomBytes } from "node:crypto";
|
|
3
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
4
|
+
import { parse, stringify } from "yaml";
|
|
5
|
+
import { loadPublicationRegistry } from "../a2a/registry.js";
|
|
6
|
+
import { a2aConfigPathFor, resolveA2aConfigPath } from "../a2a/configDiscovery.js";
|
|
7
|
+
export const A2A_USAGE = `sf a2a validate [--config <path>]
|
|
8
|
+
sf a2a list [--config <path>]
|
|
9
|
+
sf a2a add-caller <id> [--config <path>] [--token-env <NAME>]
|
|
10
|
+
|
|
11
|
+
Validate or inspect an A2A publication configuration, or register a new caller.
|
|
12
|
+
--config defaults to <project-root>/a2a.yaml when omitted -- the same file
|
|
13
|
+
STAGEFLOW_A2A_CONFIG would point at if set. Caller credentials are read from
|
|
14
|
+
the environment variables named in the configuration; add-caller only writes
|
|
15
|
+
the caller's id and its token_env name to the file, never a secret value.`;
|
|
16
|
+
const SCAFFOLD = { version: 1, callers: [], publications: [] };
|
|
17
|
+
function deriveTokenEnvName(callerId) {
|
|
18
|
+
const normalized = callerId
|
|
19
|
+
.trim()
|
|
20
|
+
.toUpperCase()
|
|
21
|
+
.replace(/[^A-Z0-9]+/g, "_")
|
|
22
|
+
.replace(/^_+|_+$/g, "");
|
|
23
|
+
return (normalized || "CALLER") + "_TOKEN";
|
|
24
|
+
}
|
|
25
|
+
function parseFlags(args) {
|
|
26
|
+
let config;
|
|
27
|
+
let tokenEnv;
|
|
28
|
+
const positionals = [];
|
|
29
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
30
|
+
if (args[i] === "--config") {
|
|
31
|
+
config = args[(i += 1)];
|
|
32
|
+
}
|
|
33
|
+
else if (args[i] === "--token-env") {
|
|
34
|
+
tokenEnv = args[(i += 1)];
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
positionals.push(args[i]);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return { config, tokenEnv, positionals };
|
|
41
|
+
}
|
|
42
|
+
async function addCaller(configPath, callerId, tokenEnvOverride) {
|
|
43
|
+
let raw;
|
|
44
|
+
try {
|
|
45
|
+
raw = await readFile(configPath, "utf8");
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
raw = undefined;
|
|
49
|
+
}
|
|
50
|
+
const config = raw ? (parse(raw) ?? {}) : { ...SCAFFOLD };
|
|
51
|
+
if (typeof config !== "object" || Array.isArray(config)) {
|
|
52
|
+
throw new Error(configPath + " does not contain a YAML mapping");
|
|
53
|
+
}
|
|
54
|
+
if (config.version === undefined)
|
|
55
|
+
config.version = 1;
|
|
56
|
+
if (config.callers === undefined)
|
|
57
|
+
config.callers = [];
|
|
58
|
+
if (config.publications === undefined)
|
|
59
|
+
config.publications = [];
|
|
60
|
+
const callers = config.callers;
|
|
61
|
+
if (!Array.isArray(callers)) {
|
|
62
|
+
throw new Error('"callers" in ' + configPath + " is not a list");
|
|
63
|
+
}
|
|
64
|
+
if (callers.some((entry) => entry && typeof entry === "object" && entry.id === callerId)) {
|
|
65
|
+
throw new Error('Caller "' + callerId + '" already exists in ' + configPath);
|
|
66
|
+
}
|
|
67
|
+
const tokenEnv = tokenEnvOverride ?? deriveTokenEnvName(callerId);
|
|
68
|
+
callers.push({ id: callerId, token_env: tokenEnv });
|
|
69
|
+
await mkdir(path.dirname(configPath), { recursive: true });
|
|
70
|
+
await writeFile(configPath, stringify(config));
|
|
71
|
+
const token = randomBytes(24).toString("hex");
|
|
72
|
+
return { tokenEnv, token, created: raw === undefined };
|
|
73
|
+
}
|
|
74
|
+
export async function runA2aCommand(args, options) {
|
|
75
|
+
const log = options.log ?? console.log;
|
|
76
|
+
const error = options.error ?? console.error;
|
|
77
|
+
const env = options.env ?? process.env;
|
|
78
|
+
const projectRoot = options.projectRoot ?? options.cwd;
|
|
79
|
+
if (args[0] === "--help" || args[0] === "-h") {
|
|
80
|
+
log(A2A_USAGE);
|
|
81
|
+
return 0;
|
|
82
|
+
}
|
|
83
|
+
const sub = args[0];
|
|
84
|
+
if (sub !== "validate" && sub !== "list" && sub !== "add-caller") {
|
|
85
|
+
error(A2A_USAGE);
|
|
86
|
+
return 1;
|
|
87
|
+
}
|
|
88
|
+
const { config, tokenEnv, positionals } = parseFlags(args.slice(1));
|
|
89
|
+
if (sub === "add-caller") {
|
|
90
|
+
if (positionals.length !== 1 || !positionals[0]) {
|
|
91
|
+
error(A2A_USAGE);
|
|
92
|
+
return 1;
|
|
93
|
+
}
|
|
94
|
+
const configPath = config ? path.resolve(options.cwd, config) : a2aConfigPathFor(projectRoot);
|
|
95
|
+
try {
|
|
96
|
+
const result = await addCaller(configPath, positionals[0], tokenEnv);
|
|
97
|
+
log(JSON.stringify({ ok: true, configPath, callerId: positionals[0], tokenEnv: result.tokenEnv, created: result.created }));
|
|
98
|
+
log('Generated a token for caller "' + positionals[0] + '". Export it before starting the host (or store it in your secrets manager):');
|
|
99
|
+
log(" export " + result.tokenEnv + "=" + result.token);
|
|
100
|
+
if (result.created) {
|
|
101
|
+
log("Created " + configPath + " with no publications yet -- add at least one under `publications:` before this config can be loaded.");
|
|
102
|
+
}
|
|
103
|
+
return 0;
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
106
|
+
error(err instanceof Error ? err.message : String(err));
|
|
107
|
+
return 1;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (positionals.length !== 0) {
|
|
111
|
+
error(A2A_USAGE);
|
|
112
|
+
return 1;
|
|
113
|
+
}
|
|
114
|
+
const configPath = config ? path.resolve(options.cwd, config) : resolveA2aConfigPath(projectRoot, env);
|
|
115
|
+
if (!configPath) {
|
|
116
|
+
error("No A2A configuration found. Pass --config <path>, or create " + a2aConfigPathFor(projectRoot) + ".");
|
|
117
|
+
return 1;
|
|
118
|
+
}
|
|
119
|
+
try {
|
|
120
|
+
const registry = await loadPublicationRegistry(configPath, env);
|
|
121
|
+
log(JSON.stringify({ ok: true, configPath: registry.configPath, publicUrl: registry.publicUrl, mode: "discovery", publications: registry.summaries() }));
|
|
122
|
+
if (sub === "list") {
|
|
123
|
+
log("Configuration validated locally. An already-running host must be restarted explicitly to pick up changes.");
|
|
124
|
+
}
|
|
125
|
+
return 0;
|
|
126
|
+
}
|
|
127
|
+
catch (err) {
|
|
128
|
+
error(err instanceof Error ? err.message : String(err));
|
|
129
|
+
return 1;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -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 {};
|