stageflow 0.27.0 → 0.30.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 +4 -0
- package/builtin-skills/browser/SKILL.md +42 -0
- package/dist/agent/claudeAdapter.js +2 -2
- package/dist/agent/cursorProvider.d.ts +2 -0
- package/dist/agent/cursorProvider.js +30 -5
- package/dist/agent/fakeAgent.d.ts +1 -0
- package/dist/agent/fakeAgent.js +13 -0
- package/dist/agent/piAdapter.d.ts +3 -24
- package/dist/agent/piAdapter.js +49 -47
- package/dist/agent/piSessionFactory.d.ts +38 -0
- package/dist/agent/piSessionFactory.js +60 -0
- package/dist/agent/port.d.ts +2 -0
- package/dist/agent/streamLogRedact.d.ts +6 -0
- package/dist/agent/streamLogRedact.js +23 -1
- package/dist/browser/anchor.d.ts +27 -0
- package/dist/browser/anchor.js +127 -0
- package/dist/browser/auditSink.d.ts +38 -0
- package/dist/browser/auditSink.js +34 -0
- package/dist/browser/browserHost.d.ts +86 -0
- package/dist/browser/browserHost.js +7 -0
- package/dist/browser/browserSweep.d.ts +14 -0
- package/dist/browser/browserSweep.js +54 -0
- package/dist/browser/browserTeardown.d.ts +56 -0
- package/dist/browser/browserTeardown.js +235 -0
- package/dist/browser/fakeRemoteBrowserHost.d.ts +2 -0
- package/dist/browser/fakeRemoteBrowserHost.js +34 -0
- package/dist/browser/gateHandoff.d.ts +25 -0
- package/dist/browser/gateHandoff.js +83 -0
- package/dist/browser/humanLogin.d.ts +18 -0
- package/dist/browser/humanLogin.js +34 -0
- package/dist/browser/keyProvider.d.ts +10 -0
- package/dist/browser/keyProvider.js +39 -0
- package/dist/browser/localBrowserHost.d.ts +10 -0
- package/dist/browser/localBrowserHost.js +152 -0
- package/dist/browser/localProfileLock.d.ts +2 -0
- package/dist/browser/localProfileLock.js +218 -0
- package/dist/browser/localProfileStore.d.ts +5 -0
- package/dist/browser/localProfileStore.js +91 -0
- package/dist/browser/loginCheck.d.ts +53 -0
- package/dist/browser/loginCheck.js +147 -0
- package/dist/browser/memoryProfileLock.d.ts +2 -0
- package/dist/browser/memoryProfileLock.js +53 -0
- package/dist/browser/memoryProfileStore.d.ts +2 -0
- package/dist/browser/memoryProfileStore.js +31 -0
- package/dist/browser/navigationAudit.d.ts +16 -0
- package/dist/browser/navigationAudit.js +53 -0
- package/dist/browser/persistedEnv.d.ts +3 -0
- package/dist/browser/persistedEnv.js +15 -0
- package/dist/browser/profileLock.d.ts +40 -0
- package/dist/browser/profileLock.js +10 -0
- package/dist/browser/profileStore.d.ts +23 -0
- package/dist/browser/profileStore.js +39 -0
- package/dist/browser/runLiveness.d.ts +13 -0
- package/dist/browser/runLiveness.js +39 -0
- package/dist/browser/sitePolicy.d.ts +10 -0
- package/dist/browser/sitePolicy.js +61 -0
- package/dist/browser/stageBrowserEnv.d.ts +22 -0
- package/dist/browser/stageBrowserEnv.js +147 -0
- package/dist/browser/stageProfileLock.d.ts +17 -0
- package/dist/browser/stageProfileLock.js +39 -0
- package/dist/cli/browserCommand.d.ts +44 -0
- package/dist/cli/browserCommand.js +472 -0
- package/dist/cli/hostClient.d.ts +20 -0
- package/dist/cli/hostClient.js +36 -2
- package/dist/cli/initCommand.d.ts +1 -0
- package/dist/cli/initCommand.js +23 -7
- package/dist/cli/initTemplates.d.ts +5 -2
- package/dist/cli/initTemplates.js +25 -2
- package/dist/cli/runsCommand.js +6 -2
- package/dist/cli/triggerCommand.d.ts +12 -0
- package/dist/cli/triggerCommand.js +183 -0
- package/dist/cli/validateCommand.d.ts +1 -1
- package/dist/cli/validateCommand.js +23 -6
- package/dist/cli.js +31 -2
- package/dist/config/browseCatalog.d.ts +1 -0
- package/dist/config/browseCatalog.js +5 -4
- package/dist/config/builtinSkills.d.ts +4 -0
- package/dist/config/builtinSkills.js +23 -0
- package/dist/config/catalogRelativePath.d.ts +1 -0
- package/dist/config/catalogRelativePath.js +10 -2
- package/dist/config/createTrigger.d.ts +27 -0
- package/dist/config/createTrigger.js +232 -0
- package/dist/config/draftPackage.d.ts +117 -0
- package/dist/config/draftPackage.js +550 -0
- package/dist/config/ensureCatalogScanRoot.d.ts +16 -0
- package/dist/config/ensureCatalogScanRoot.js +73 -0
- package/dist/config/hostConfig.d.ts +2 -0
- package/dist/config/hostConfig.js +27 -0
- package/dist/config/listSkills.d.ts +2 -2
- package/dist/config/listSkills.js +17 -3
- package/dist/config/loadOutcome.d.ts +6 -0
- package/dist/config/loadPipeline.js +16 -0
- package/dist/config/loadStage.js +7 -0
- package/dist/config/loadStageflowManifest.js +25 -0
- package/dist/config/loadTrigger.d.ts +9 -0
- package/dist/config/loadTrigger.js +120 -0
- package/dist/config/multiProjectCatalog.d.ts +2 -0
- package/dist/config/multiProjectCatalog.js +26 -18
- package/dist/config/normalizePipelineStageEntry.js +8 -1
- package/dist/config/parseCompletionContract.js +6 -0
- package/dist/config/pipelineStageKeys.js +1 -0
- package/dist/config/publishDraftPackage.d.ts +7 -0
- package/dist/config/publishDraftPackage.js +25 -0
- package/dist/config/resolveCatalogContext.js +1 -1
- package/dist/config/resolvePipelineDag.js +1 -1
- package/dist/config/scanCatalogPaths.d.ts +1 -1
- package/dist/config/scanCatalogPaths.js +6 -2
- package/dist/config/stageBrowser.d.ts +12 -0
- package/dist/config/stageBrowser.js +118 -0
- package/dist/config/triggerCatalog.d.ts +10 -0
- package/dist/config/triggerCatalog.js +37 -0
- package/dist/config/validateCatalog.d.ts +13 -3
- package/dist/config/validateCatalog.js +83 -2
- package/dist/config/validateCompletionContract.js +8 -0
- package/dist/envelope/preEmitChecks.d.ts +1 -1
- package/dist/envelope/preEmitChecks.js +8 -0
- package/dist/logging/redact.d.ts +4 -0
- package/dist/logging/redact.js +17 -0
- package/dist/mcp/catalogTools.js +43 -8
- package/dist/mcp/controlTools.js +3 -1
- package/dist/mcp/deps.d.ts +2 -0
- package/dist/mcp/server.js +1 -0
- package/dist/mcp/tools.js +2 -0
- package/dist/mcp/triggerTools.d.ts +3 -0
- package/dist/mcp/triggerTools.js +176 -0
- package/dist/operatorAgent/docsRetrieval.d.ts +40 -0
- package/dist/operatorAgent/docsRetrieval.js +269 -0
- package/dist/operatorAgent/draftContext.d.ts +16 -0
- package/dist/operatorAgent/draftContext.js +68 -0
- package/dist/operatorAgent/fakeHost.d.ts +39 -0
- package/dist/operatorAgent/fakeHost.js +92 -0
- package/dist/operatorAgent/host.d.ts +40 -0
- package/dist/operatorAgent/host.js +179 -0
- package/dist/operatorAgent/index.d.ts +9 -0
- package/dist/operatorAgent/index.js +8 -0
- package/dist/operatorAgent/piModel.d.ts +80 -0
- package/dist/operatorAgent/piModel.js +528 -0
- package/dist/operatorAgent/profiles/workshopAuthor.d.ts +23 -0
- package/dist/operatorAgent/profiles/workshopAuthor.js +338 -0
- package/dist/operatorAgent/profiles/workshopAuthorPlaybook.d.ts +6 -0
- package/dist/operatorAgent/profiles/workshopAuthorPlaybook.js +103 -0
- package/dist/operatorAgent/profiles/workshopAuthorTools.d.ts +57 -0
- package/dist/operatorAgent/profiles/workshopAuthorTools.js +623 -0
- package/dist/operatorAgent/proposals.d.ts +32 -0
- package/dist/operatorAgent/proposals.js +144 -0
- package/dist/operatorAgent/types.d.ts +128 -0
- package/dist/operatorAgent/types.js +1 -0
- package/dist/package-meta.d.ts +1 -1
- package/dist/package-meta.js +1 -1
- package/dist/project/findManifestRoot.d.ts +3 -0
- package/dist/project/findManifestRoot.js +35 -0
- package/dist/project/resolveStageflowContext.d.ts +7 -0
- package/dist/project/resolveStageflowContext.js +44 -28
- package/dist/runstore/assertTmpdir.js +6 -2
- package/dist/runstore/port.d.ts +28 -0
- package/dist/runstore/sqlite/SqliteRunStore.d.ts +8 -1
- package/dist/runstore/sqlite/SqliteRunStore.js +90 -0
- package/dist/runstore/sqlite/migrations/008-triggers-table.d.ts +7 -0
- package/dist/runstore/sqlite/migrations/008-triggers-table.js +19 -0
- package/dist/runstore/sqlite/migrations/009-trigger-next-run.d.ts +7 -0
- package/dist/runstore/sqlite/migrations/009-trigger-next-run.js +19 -0
- package/dist/runstore/sqlite/migrations/010-trigger-adapter-state.d.ts +7 -0
- package/dist/runstore/sqlite/migrations/010-trigger-adapter-state.js +17 -0
- package/dist/runstore/sqlite/migrations/index.js +6 -0
- package/dist/runtime/answerResume.d.ts +1 -0
- package/dist/runtime/answerResume.js +3 -1
- package/dist/runtime/completionCheckRunner.d.ts +12 -1
- package/dist/runtime/completionCheckRunner.js +22 -0
- package/dist/runtime/credentialBinding.d.ts +2 -0
- package/dist/runtime/credentialBinding.js +3 -0
- package/dist/runtime/emailSource.d.ts +72 -0
- package/dist/runtime/emailSource.js +304 -0
- package/dist/runtime/githubPollSource.d.ts +86 -0
- package/dist/runtime/githubPollSource.js +284 -0
- package/dist/runtime/pipelineRunner.d.ts +4 -0
- package/dist/runtime/pipelineRunner.js +3 -0
- package/dist/runtime/pipelineScheduler.d.ts +2 -0
- package/dist/runtime/pipelineScheduler.js +85 -1
- package/dist/runtime/repositoryMaterialize.d.ts +1 -1
- package/dist/runtime/runManager.d.ts +11 -0
- package/dist/runtime/runManager.js +149 -1
- package/dist/runtime/runRetryCoordinator.d.ts +2 -0
- package/dist/runtime/runRetryCoordinator.js +1 -0
- package/dist/runtime/scheduleSource.d.ts +38 -0
- package/dist/runtime/scheduleSource.js +119 -0
- package/dist/runtime/settingsFile.d.ts +5 -0
- package/dist/runtime/settingsFile.js +61 -54
- package/dist/runtime/stageAttemptBootstrap.js +55 -3
- package/dist/runtime/stageEnvironment.js +1 -0
- package/dist/runtime/stageHitl.d.ts +1 -0
- package/dist/runtime/stageHitl.js +5 -1
- package/dist/runtime/stageProcessLauncher.d.ts +2 -0
- package/dist/runtime/stageProcessLauncher.js +44 -5
- package/dist/runtime/stageRunner.d.ts +5 -0
- package/dist/runtime/stageRunner.js +30 -6
- package/dist/runtime/stageStreamLog.d.ts +1 -0
- package/dist/runtime/stageStreamLog.js +2 -1
- package/dist/runtime/triggerCatalog.d.ts +6 -0
- package/dist/runtime/triggerCatalog.js +24 -0
- package/dist/runtime/triggerEventMatch.d.ts +2 -0
- package/dist/runtime/triggerEventMatch.js +6 -0
- package/dist/runtime/triggerPort.d.ts +14 -0
- package/dist/runtime/triggerPort.js +1 -0
- package/dist/runtime/triggerRunner.d.ts +17 -0
- package/dist/runtime/triggerRunner.js +89 -0
- package/dist/runtime/verifiedStageExecution.d.ts +7 -0
- package/dist/runtime/verifiedStageExecution.js +14 -0
- package/dist/runtime/webhookSignature.d.ts +1 -0
- package/dist/runtime/webhookSignature.js +12 -0
- package/dist/server/bootstrap.d.ts +6 -0
- package/dist/server/bootstrap.js +64 -0
- package/dist/server/controlToken.js +13 -0
- package/dist/server/createHttpHost.js +3 -0
- package/dist/server/healthSurfaces.js +31 -6
- package/dist/server/http.d.ts +15 -0
- package/dist/server/http.js +1251 -74
- package/dist/server/requestAuthContext.d.ts +1 -1
- package/dist/server/triggerRoutes.d.ts +15 -0
- package/dist/server/triggerRoutes.js +268 -0
- package/dist/tools/askOperator.d.ts +3 -2
- package/dist/types/completion.d.ts +3 -0
- package/dist/types/pipeline.d.ts +3 -1
- package/dist/types/preEmitCheck.d.ts +7 -0
- package/dist/types/stage.d.ts +14 -0
- package/dist/types/stageflowManifest.d.ts +3 -0
- package/dist/types/trigger.d.ts +18 -0
- package/dist/types/trigger.js +1 -0
- package/dist/ui/assets/{index-CSsDued1.css → index-Bv4p1aPK.css} +1 -1
- package/dist/ui/assets/index-CntB4bZR.js +133 -0
- package/dist/ui/index.html +2 -2
- package/dist/workshop/autosave.d.ts +58 -0
- package/dist/workshop/autosave.js +222 -0
- package/dist/workshop/buildStore.d.ts +79 -0
- package/dist/workshop/buildStore.js +326 -0
- package/dist/workshop/chatTurn.d.ts +232 -0
- package/dist/workshop/chatTurn.js +475 -0
- package/dist/workshop/modelSettings.d.ts +18 -0
- package/dist/workshop/modelSettings.js +44 -0
- package/dist/workshop/sessionStore.d.ts +61 -0
- package/dist/workshop/sessionStore.js +254 -0
- package/dist/workshop/workshopPin.d.ts +72 -0
- package/dist/workshop/workshopPin.js +193 -0
- package/examples/README.md +2 -1
- package/examples/browser-session/README.md +77 -0
- package/examples/browser-session/browser-session.pipeline.yaml +50 -0
- package/examples/browser-session/browser-session.task.yaml +6 -0
- package/examples/browser-session/check-login.yaml +22 -0
- package/examples/browser-session/fixture-server.mjs +64 -0
- package/examples/browser-session/human-login.yaml +31 -0
- package/examples/browser-session/merge.yaml +36 -0
- package/examples/browser-session/work-a.yaml +41 -0
- package/examples/browser-session/work-b.yaml +41 -0
- package/examples/stage-mcp/README.md +3 -2
- package/examples/stageflow.yaml +51 -0
- package/package.json +5 -1
- package/skills/stageflow/references/control-surface.md +3 -1
- package/skills/stageflow-author/references/stage-prompt-template.md +4 -4
- package/skills/stageflow-run/SKILL.md +2 -0
- package/skills/stageflow-run/references/task-and-pipeline-selection.md +3 -1
- package/skills/stageflow-setup/SKILL.md +11 -0
- package/dist/ui/assets/index-B6hlSXXn.js +0 -120
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { bindingKindForOrigin, catalogOrSeededOrigin, decideWorkspaceConfigTrust, } from "../config/configOrigin.js";
|
|
2
|
+
import { loginCheckPromptBlock, readStageLoginCheck, } from "../browser/loginCheck.js";
|
|
3
|
+
import { humanLoginPromptBlock, isHumanLoginStage } from "../browser/humanLogin.js";
|
|
4
|
+
import { BROWSER_SKILL_NAME, resolveBuiltinSkillFile, } from "../config/builtinSkills.js";
|
|
2
5
|
import { resolveSkillByName, } from "../config/listSkills.js";
|
|
3
6
|
import { loadHostConfig } from "../config/hostConfig.js";
|
|
4
7
|
import { defaultSeededRoots } from "../config/seededCatalog.js";
|
|
@@ -7,6 +10,7 @@ import { mkdir } from "node:fs/promises";
|
|
|
7
10
|
import { runSkillsDir } from "./runSkills.js";
|
|
8
11
|
import { MCP_CATALOG_FILENAME, STAGEFLOW_STAGE_ARTIFACTS_DIR_ENV, StageMcpError, mcpCatalogExists, mcpCatalogPath, resolveStageMcpServers, stampStagePromptArtifactsDir, } from "../config/resolveStageMcpServers.js";
|
|
9
12
|
import { attemptArtifactsDir, attemptStreamLogPath } from "../runstore/workspaceLayout.js";
|
|
13
|
+
import { redactBrowserActivityEvent, redactBrowserSecrets, } from "../agent/streamLogRedact.js";
|
|
10
14
|
import { createStageStreamLogWriter, } from "./stageStreamLog.js";
|
|
11
15
|
import { resolveCloneEmitContext, resolveForkEmitContext } from "../config/resolveForkEmitContext.js";
|
|
12
16
|
import { createAttemptQaTrailReader } from "../hitl/qaTrail.js";
|
|
@@ -68,6 +72,17 @@ async function resolveStageSkillForRun(stage, catalog, options) {
|
|
|
68
72
|
skillOrigin: resolved.origin,
|
|
69
73
|
};
|
|
70
74
|
}
|
|
75
|
+
/** Browser stages get the `browser` skill (run > checkout > host > built-in). A missing skill is not an error. */
|
|
76
|
+
async function resolveBrowserSkillFile(stage, catalog, options) {
|
|
77
|
+
if (stage.browser === undefined || stage.skill === BROWSER_SKILL_NAME) {
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
const resolved = await resolveStageSkillForRun({ skill: BROWSER_SKILL_NAME }, catalog, options);
|
|
81
|
+
if (resolved.ok && resolved.skillFilePath !== undefined) {
|
|
82
|
+
return resolved.skillFilePath;
|
|
83
|
+
}
|
|
84
|
+
return resolveBuiltinSkillFile(BROWSER_SKILL_NAME);
|
|
85
|
+
}
|
|
71
86
|
async function resolveAttemptMcpServers(allowlist, factoryCwd, artifactsDir, options) {
|
|
72
87
|
const names = allowlist ?? [];
|
|
73
88
|
if (names.length === 0)
|
|
@@ -151,6 +166,13 @@ async function openStageWithOperatorCatalog(agent, input, catalog, factoryCwd, a
|
|
|
151
166
|
});
|
|
152
167
|
if (!skill.ok)
|
|
153
168
|
return skill;
|
|
169
|
+
const browserSkillFilePath = await resolveBrowserSkillFile(input.stage, catalog, {
|
|
170
|
+
bindingKind: resolveOptions.bindingKind,
|
|
171
|
+
checkoutRoot: resolveOptions.checkoutRoot,
|
|
172
|
+
factoryCwd,
|
|
173
|
+
trustWorkspaceConfig: resolveOptions.trustWorkspaceConfig,
|
|
174
|
+
workspaceDir: resolveOptions.workspaceDir,
|
|
175
|
+
});
|
|
154
176
|
let resolvedMcpServers;
|
|
155
177
|
const origins = [];
|
|
156
178
|
try {
|
|
@@ -207,16 +229,41 @@ async function openStageWithOperatorCatalog(agent, input, catalog, factoryCwd, a
|
|
|
207
229
|
})).catch(() => null);
|
|
208
230
|
}
|
|
209
231
|
}
|
|
232
|
+
let loginCheck;
|
|
233
|
+
const humanLogin = isHumanLoginStage(resolveOptions.dag, input.stage.id, input.stage.browser);
|
|
234
|
+
if (input.stage.browser?.check !== undefined && !humanLogin) {
|
|
235
|
+
loginCheck = await readStageLoginCheck(resolveOptions.workspaceDir, stageIdForManifest);
|
|
236
|
+
if (loginCheck === undefined) {
|
|
237
|
+
return { ok: false, reason: "browser login check result is missing" };
|
|
238
|
+
}
|
|
239
|
+
}
|
|
210
240
|
try {
|
|
211
241
|
const handle = agent.openStage({
|
|
212
242
|
...input,
|
|
213
243
|
stage: {
|
|
214
244
|
...input.stage,
|
|
215
|
-
system_prompt: stampStagePromptArtifactsDir(input.stage.system_prompt, artifactsDir)
|
|
245
|
+
system_prompt: `${stampStagePromptArtifactsDir(input.stage.system_prompt, artifactsDir)}${loginCheck !== undefined ? `\n\n${loginCheckPromptBlock(loginCheck)}` : ""}${humanLogin ? `\n\n${humanLoginPromptBlock(input.stage.browser?.login_url ?? input.stage.browser?.check?.url)}` : ""}`,
|
|
246
|
+
...(loginCheck !== undefined
|
|
247
|
+
? {
|
|
248
|
+
pre_emit_checks: [
|
|
249
|
+
...(input.stage.pre_emit_checks ?? []),
|
|
250
|
+
{
|
|
251
|
+
id: "browser_login_check",
|
|
252
|
+
type: "browser_login_check",
|
|
253
|
+
state: loginCheck.state,
|
|
254
|
+
logged_in: loginCheck.logged_in,
|
|
255
|
+
url: loginCheck.url,
|
|
256
|
+
},
|
|
257
|
+
],
|
|
258
|
+
}
|
|
259
|
+
: {}),
|
|
216
260
|
},
|
|
217
261
|
...(skill.skillFilePath !== undefined
|
|
218
262
|
? { skillFilePath: skill.skillFilePath }
|
|
219
263
|
: {}),
|
|
264
|
+
...(browserSkillFilePath !== undefined
|
|
265
|
+
? { browserSkillFilePath }
|
|
266
|
+
: {}),
|
|
220
267
|
...(resolvedMcpServers !== undefined ? { resolvedMcpServers } : {}),
|
|
221
268
|
onResolvedModel: async (info) => {
|
|
222
269
|
await patchRunManifest(resolveOptions.store, resolveOptions.runId, (m) => withStageResolvedModel(m, info.stageId, {
|
|
@@ -357,7 +404,10 @@ export async function openStageAttempt(input) {
|
|
|
357
404
|
return priorInput;
|
|
358
405
|
const roots = resolveAttemptRoots(input, stageId);
|
|
359
406
|
const attempt = input.attemptCtx?.attempt ?? 1;
|
|
360
|
-
const
|
|
407
|
+
const streamLogPath = attemptStreamLogPath(input.workspaceDir, stageId, attempt);
|
|
408
|
+
const streamWriter = input.streamLogWriterFactory
|
|
409
|
+
? input.streamLogWriterFactory(streamLogPath)
|
|
410
|
+
: createStageStreamLogWriter(streamLogPath, input.stage.browser !== undefined ? { redact: redactBrowserSecrets } : {});
|
|
361
411
|
const resumeToken = input.resumeToken ??
|
|
362
412
|
resumeSessionFilePath(input.workspaceDir, stageId, attempt);
|
|
363
413
|
const forkEmitContext = resolveForkEmitContext(input.dag, definitionId);
|
|
@@ -394,7 +444,9 @@ export async function openStageAttempt(input) {
|
|
|
394
444
|
resumeToken,
|
|
395
445
|
...(input.sessionMode !== undefined ? { sessionMode: input.sessionMode } : {}),
|
|
396
446
|
onActivity: (event) => {
|
|
397
|
-
input.onActivity?.(
|
|
447
|
+
input.onActivity?.(input.stage.browser !== undefined
|
|
448
|
+
? redactBrowserActivityEvent(event)
|
|
449
|
+
: event);
|
|
398
450
|
void streamWriter.flush();
|
|
399
451
|
},
|
|
400
452
|
onAssistantTextDelta: streamWriter.onDelta,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { parseHostGateContext } from "../browser/gateHandoff.js";
|
|
1
2
|
import { appendOperatorAnswer, appendOperatorPrompt, derivePendingPrompt, } from "../hitl/qaTrail.js";
|
|
2
3
|
import { deriveExecutionPatchFromEvent } from "../runstore/stageExecution.js";
|
|
3
4
|
import { attemptContext } from "./stageAttemptContext.js";
|
|
@@ -7,7 +8,10 @@ export function tryParsePendingPrompt(pendingRequest) {
|
|
|
7
8
|
return undefined;
|
|
8
9
|
}
|
|
9
10
|
try {
|
|
10
|
-
return
|
|
11
|
+
return {
|
|
12
|
+
...normalizePromptIds(parseAskOperatorParams(pendingRequest)),
|
|
13
|
+
...parseHostGateContext(pendingRequest),
|
|
14
|
+
};
|
|
11
15
|
}
|
|
12
16
|
catch {
|
|
13
17
|
return undefined;
|
|
@@ -17,6 +17,7 @@ export type StageLaunchInput = {
|
|
|
17
17
|
bindingKind?: DerivedBindingKind;
|
|
18
18
|
grants?: ResolvedStageGrants;
|
|
19
19
|
attemptHome?: string;
|
|
20
|
+
browserEnv?: Record<string, string>;
|
|
20
21
|
};
|
|
21
22
|
export type StageLaunchResult = {
|
|
22
23
|
type: "succeeded";
|
|
@@ -45,6 +46,7 @@ export declare class StageProcessLauncher {
|
|
|
45
46
|
private readonly env;
|
|
46
47
|
private readonly explicitChildExtras;
|
|
47
48
|
private readonly cliEntry;
|
|
49
|
+
private readonly workerExecArgv;
|
|
48
50
|
private readonly logger;
|
|
49
51
|
private readonly forkFn;
|
|
50
52
|
private readonly active;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import * as childProcess from "node:child_process";
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
3
|
import { logger as rootLogger, resolveLogMaxLineBytes, } from "../logging/logger.js";
|
|
4
|
+
import { BROWSER_ENV_PREFIX } from "../browser/browserHost.js";
|
|
4
5
|
import { PACKAGE_VERSION } from "../package-meta.js";
|
|
6
|
+
import { PI_HOME_AUTH_PATH_ENV, piHomeAuthPath } from "./credentialBinding.js";
|
|
5
7
|
import { redactString } from "../logging/redact.js";
|
|
6
8
|
import { getNamedSecrets } from "../logging/namedSecrets.js";
|
|
7
9
|
import { readMaxActiveStageProcesses, } from "./stageConcurrency.js";
|
|
@@ -10,6 +12,28 @@ import { buildStageEnvironment, isAmbientBlockedEnv, isForeverDeniedSecret, } fr
|
|
|
10
12
|
import { ensureStageCacheDirs } from "./stageCacheEnv.js";
|
|
11
13
|
import { SF_STAGE_WORKER, STAGE_WORKER_EXIT, } from "./stageWorkerProtocol.js";
|
|
12
14
|
import { overlayStageBindingEnv, } from "./stageRoots.js";
|
|
15
|
+
function defaultStageCliEntry() {
|
|
16
|
+
const source = import.meta.url.split("?")[0]?.endsWith(".ts") === true;
|
|
17
|
+
return fileURLToPath(new URL(source ? "../cli.ts" : "../cli.js", import.meta.url));
|
|
18
|
+
}
|
|
19
|
+
function isTypeScriptEntry(entry) {
|
|
20
|
+
return /\.(?:m|c)?ts$/.test(entry);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* A compiled `dist/cli.js` child is plain node. `npm run dev` is TypeScript,
|
|
24
|
+
* and this launcher sets execArgv explicitly so the parent's tsx loader is
|
|
25
|
+
* not inherited. Load that entry with the same tsx hooks the dev CLI uses.
|
|
26
|
+
*/
|
|
27
|
+
function tsxLoaderExecArgv() {
|
|
28
|
+
try {
|
|
29
|
+
const preflight = fileURLToPath(import.meta.resolve("tsx/preflight"));
|
|
30
|
+
const loader = import.meta.resolve("tsx");
|
|
31
|
+
return ["--require", preflight, "--import", loader];
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
}
|
|
13
37
|
function flushCappedPartial(log, event, text, maxLineBytes) {
|
|
14
38
|
let remaining = redactString(text, { namedSecrets: getNamedSecrets() });
|
|
15
39
|
while (Buffer.byteLength(remaining, "utf8") > maxLineBytes) {
|
|
@@ -109,6 +133,7 @@ export class StageProcessLauncher {
|
|
|
109
133
|
env;
|
|
110
134
|
explicitChildExtras;
|
|
111
135
|
cliEntry;
|
|
136
|
+
workerExecArgv;
|
|
112
137
|
logger;
|
|
113
138
|
forkFn;
|
|
114
139
|
active = new Map();
|
|
@@ -126,9 +151,11 @@ export class StageProcessLauncher {
|
|
|
126
151
|
: undefined;
|
|
127
152
|
this.maxActive = readMaxActiveStageProcesses(this.env, options.maxActiveStageProcesses);
|
|
128
153
|
this.heapMb = getContainerLimits().maxOldSpaceSizeMb;
|
|
129
|
-
this.cliEntry =
|
|
130
|
-
|
|
131
|
-
|
|
154
|
+
this.cliEntry = options.cliEntry ?? defaultStageCliEntry();
|
|
155
|
+
this.workerExecArgv = [
|
|
156
|
+
`--max-old-space-size=${this.heapMb}`,
|
|
157
|
+
...(isTypeScriptEntry(this.cliEntry) ? tsxLoaderExecArgv() : []),
|
|
158
|
+
];
|
|
132
159
|
this.logger =
|
|
133
160
|
options.logger ?? rootLogger.child({ component: "runtime" });
|
|
134
161
|
this.forkFn = options.forkFn ?? childProcess.fork;
|
|
@@ -290,6 +317,7 @@ export class StageProcessLauncher {
|
|
|
290
317
|
cacheVars: ensureStageCacheDirs(),
|
|
291
318
|
grants: input.grants,
|
|
292
319
|
attemptHome: input.attemptHome,
|
|
320
|
+
runVars: input.browserEnv,
|
|
293
321
|
packageVersion: PACKAGE_VERSION,
|
|
294
322
|
});
|
|
295
323
|
for (const warning of built.warnings) {
|
|
@@ -301,14 +329,25 @@ export class StageProcessLauncher {
|
|
|
301
329
|
const withExtras = this.explicitChildExtras !== undefined
|
|
302
330
|
? { ...built.env, ...this.explicitChildExtras }
|
|
303
331
|
: built.env;
|
|
304
|
-
const
|
|
332
|
+
const overlaid = overlayStageBindingEnv(withExtras, input.env ?? {}, input.bindingKind ?? "unbound");
|
|
333
|
+
const withBrowser = input.browserEnv !== undefined
|
|
334
|
+
? {
|
|
335
|
+
...Object.fromEntries(Object.entries(overlaid).filter(([name]) => !name.startsWith(BROWSER_ENV_PREFIX))),
|
|
336
|
+
...input.browserEnv,
|
|
337
|
+
}
|
|
338
|
+
: overlaid;
|
|
339
|
+
const childEnv = {
|
|
340
|
+
...withBrowser,
|
|
341
|
+
[PI_HOME_AUTH_PATH_ENV]: piHomeAuthPath(),
|
|
342
|
+
};
|
|
305
343
|
const child = this.forkFn(this.cliEntry, args, {
|
|
306
344
|
cwd: input.rootDir,
|
|
307
345
|
env: { ...childEnv, [SF_STAGE_WORKER]: "1" },
|
|
308
346
|
stdio: ["pipe", "pipe", "pipe", "ipc"],
|
|
309
347
|
detached: true,
|
|
310
348
|
// Explicit: do not inherit Host execArgv; set heap from cgroup budget.
|
|
311
|
-
|
|
349
|
+
// TypeScript entries also get the tsx loader, since that inheritance is off.
|
|
350
|
+
execArgv: this.workerExecArgv,
|
|
312
351
|
});
|
|
313
352
|
const key = activeKey(input.runId, input.stageId);
|
|
314
353
|
const tracked = {
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { AgentPort, FeedbackLoopContext, StageHandle, StageRunResult, StageSessionMode } from "../agent/port.js";
|
|
2
|
+
import { type HostGateContext } from "../browser/gateHandoff.js";
|
|
3
|
+
import type { StageBrowserSupport } from "../browser/browserHost.js";
|
|
2
4
|
import type { StageEnvelope } from "../types/envelope.js";
|
|
3
5
|
import type { LoadedStageConfig } from "../types/stage.js";
|
|
4
6
|
import type { TaskFile } from "../types/task.js";
|
|
@@ -49,6 +51,8 @@ export type RunStageOptions = {
|
|
|
49
51
|
feedbackLoopContext?: FeedbackLoopContext;
|
|
50
52
|
resumeToken?: string;
|
|
51
53
|
stageEnv?: Record<string, string>;
|
|
54
|
+
/** Test seam for the Host login check run by a `browser_login` verify item. */
|
|
55
|
+
browser?: Pick<StageBrowserSupport, "runner" | "loginCheck">;
|
|
52
56
|
};
|
|
53
57
|
/**
|
|
54
58
|
* Yield loop: next() until completed; on wait, coordinator enterWait (KTD6).
|
|
@@ -62,5 +66,6 @@ export declare function runStageYieldLoop(options: {
|
|
|
62
66
|
store?: RunStore;
|
|
63
67
|
attemptCtx?: StageAttemptContext;
|
|
64
68
|
skipGates?: boolean;
|
|
69
|
+
gateContext?: HostGateContext;
|
|
65
70
|
}): Promise<RunStageYieldLoopResult>;
|
|
66
71
|
export declare function runStage(options: RunStageOptions): Promise<RunStageOutcome>;
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import { hostGateContextFor, stampGateRequest, } from "../browser/gateHandoff.js";
|
|
1
2
|
import { deriveExecutionPatchFromEvent } from "../runstore/stageExecution.js";
|
|
2
3
|
import { buildStageRoots, withResolvedAuthPath, } from "./stageRoots.js";
|
|
3
4
|
import { createDefaultHitlQaHooks, recordWaitEnter, } from "./stageHitl.js";
|
|
4
5
|
import { openStageAttempt, } from "./stageAttemptBootstrap.js";
|
|
5
6
|
import { createVerifiedStageExecution } from "./verifiedStageExecution.js";
|
|
7
|
+
import { BROWSER_STAGE_PATTERNS, redact } from "../logging/redact.js";
|
|
8
|
+
import { getNamedSecrets } from "../logging/namedSecrets.js";
|
|
6
9
|
import { isStageTimeoutReason } from "../agent/stageTimeout.js";
|
|
7
10
|
export function isRunStageWaiting(outcome) {
|
|
8
11
|
return "waiting" in outcome && outcome.waiting === true;
|
|
@@ -44,11 +47,16 @@ async function appendLifecycleEventAndPatchExecution(options) {
|
|
|
44
47
|
}
|
|
45
48
|
}
|
|
46
49
|
async function finalizeStageResult(options) {
|
|
47
|
-
const { store, runId, stageId, result, activityChain, attemptCtx } = options;
|
|
50
|
+
const { store, runId, stageId, result, activityChain, attemptCtx, browser } = options;
|
|
48
51
|
const attemptOpt = attemptCtx?.eventOptions();
|
|
49
52
|
await activityChain;
|
|
50
53
|
if (result.envelope) {
|
|
51
|
-
await store.writeEnvelope(runId, stageId,
|
|
54
|
+
await store.writeEnvelope(runId, stageId, browser
|
|
55
|
+
? redact(result.envelope, {
|
|
56
|
+
patterns: BROWSER_STAGE_PATTERNS,
|
|
57
|
+
namedSecrets: getNamedSecrets(),
|
|
58
|
+
})
|
|
59
|
+
: result.envelope, attemptOpt);
|
|
52
60
|
}
|
|
53
61
|
if (!result.ok) {
|
|
54
62
|
await appendLifecycleEventAndPatchExecution({
|
|
@@ -75,10 +83,11 @@ async function finalizeStageResult(options) {
|
|
|
75
83
|
* Yield loop: next() until completed; on wait, coordinator enterWait (KTD6).
|
|
76
84
|
*/
|
|
77
85
|
export async function runStageYieldLoop(options) {
|
|
78
|
-
const { handle, runId, stageId, hitl, workerMode, store, attemptCtx, skipGates } = options;
|
|
86
|
+
const { handle, runId, stageId, hitl, workerMode, store, attemptCtx, skipGates, gateContext } = options;
|
|
79
87
|
while (true) {
|
|
80
88
|
const event = await handle.next();
|
|
81
89
|
if (event.status === "waiting_for_input") {
|
|
90
|
+
const request = stampGateRequest(event.request, gateContext);
|
|
82
91
|
if (skipGates) {
|
|
83
92
|
return {
|
|
84
93
|
ok: false,
|
|
@@ -96,7 +105,7 @@ export async function runStageYieldLoop(options) {
|
|
|
96
105
|
store,
|
|
97
106
|
runId,
|
|
98
107
|
stageId,
|
|
99
|
-
request
|
|
108
|
+
request,
|
|
100
109
|
attemptCtx,
|
|
101
110
|
qaHooks: createDefaultHitlQaHooks(store),
|
|
102
111
|
});
|
|
@@ -108,14 +117,14 @@ export async function runStageYieldLoop(options) {
|
|
|
108
117
|
reason: "stage requested wait but no HITL controller is configured",
|
|
109
118
|
};
|
|
110
119
|
}
|
|
111
|
-
await hitl.enterWait(runId, stageId, handle,
|
|
120
|
+
await hitl.enterWait(runId, stageId, handle, request, attemptCtx);
|
|
112
121
|
continue;
|
|
113
122
|
}
|
|
114
123
|
return event.result;
|
|
115
124
|
}
|
|
116
125
|
}
|
|
117
126
|
export async function runStage(options) {
|
|
118
|
-
const { agent, store, runId, stage, task, dag, checkoutRoot, workspaceDir, hitl, skipStarted, existingHandle, workerMode, roots: rootsOverride, attemptCtx, factoryCwd, operatorCatalog, completedEnvelopes, skipGates, sessionMode, feedbackLoopContext, resumeToken, stageEnv, } = options;
|
|
127
|
+
const { agent, store, runId, stage, task, dag, checkoutRoot, workspaceDir, hitl, skipStarted, existingHandle, workerMode, roots: rootsOverride, attemptCtx, factoryCwd, operatorCatalog, completedEnvelopes, skipGates, sessionMode, feedbackLoopContext, resumeToken, stageEnv, browser: browserSeams, } = options;
|
|
119
128
|
const stageId = options.stageId ?? stage.id;
|
|
120
129
|
const attemptOpt = attemptCtx?.eventOptions();
|
|
121
130
|
const baseRoots = rootsOverride ??
|
|
@@ -144,6 +153,19 @@ export async function runStage(options) {
|
|
|
144
153
|
dag,
|
|
145
154
|
roots,
|
|
146
155
|
commandEnv: stageEnv ?? process.env,
|
|
156
|
+
...(stage.browser?.check !== undefined
|
|
157
|
+
? {
|
|
158
|
+
browserLogin: {
|
|
159
|
+
check: stage.browser.check,
|
|
160
|
+
...(browserSeams?.runner !== undefined
|
|
161
|
+
? { runner: browserSeams.runner }
|
|
162
|
+
: {}),
|
|
163
|
+
...(browserSeams?.loginCheck !== undefined
|
|
164
|
+
? { options: browserSeams.loginCheck }
|
|
165
|
+
: {}),
|
|
166
|
+
},
|
|
167
|
+
}
|
|
168
|
+
: {}),
|
|
147
169
|
});
|
|
148
170
|
await verifiedExecution.prepare();
|
|
149
171
|
console.error(`Running stage ${stageId} (${stage.model})...`);
|
|
@@ -209,6 +231,7 @@ export async function runStage(options) {
|
|
|
209
231
|
store: workerMode ? store : undefined,
|
|
210
232
|
attemptCtx,
|
|
211
233
|
skipGates,
|
|
234
|
+
gateContext: hostGateContextFor(stage.browser),
|
|
212
235
|
});
|
|
213
236
|
}
|
|
214
237
|
catch (err) {
|
|
@@ -253,6 +276,7 @@ export async function runStage(options) {
|
|
|
253
276
|
result,
|
|
254
277
|
activityChain,
|
|
255
278
|
attemptCtx,
|
|
279
|
+
browser: stage.browser !== undefined,
|
|
256
280
|
});
|
|
257
281
|
}
|
|
258
282
|
finally {
|
|
@@ -54,6 +54,7 @@ async function appendChunk(streamLogPath, chunk, capBytes) {
|
|
|
54
54
|
export function createStageStreamLogWriter(streamLogPath, options = {}) {
|
|
55
55
|
const flushThrottleMs = options.flushThrottleMs ?? STREAM_FLUSH_THROTTLE_MS;
|
|
56
56
|
const capBytes = options.capBytes ?? DEFAULT_CAP_BYTES;
|
|
57
|
+
const scrub = options.redact ?? redactSecrets;
|
|
57
58
|
let buffer = "";
|
|
58
59
|
let timer = null;
|
|
59
60
|
// Serializes disk writes so the throttle timer and an eager flush() never race.
|
|
@@ -65,7 +66,7 @@ export function createStageStreamLogWriter(streamLogPath, options = {}) {
|
|
|
65
66
|
}
|
|
66
67
|
if (!buffer)
|
|
67
68
|
return writeChain;
|
|
68
|
-
const chunk =
|
|
69
|
+
const chunk = scrub(buffer);
|
|
69
70
|
buffer = "";
|
|
70
71
|
writeChain = writeChain.then(() => appendChunk(streamLogPath, chunk, capBytes));
|
|
71
72
|
return writeChain;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { TriggerFile } from "../types/trigger.js";
|
|
2
|
+
export declare function findCatalogPathById(paths: string[], id: string): Promise<string | undefined>;
|
|
3
|
+
export declare function findTriggerDefinition(triggerPaths: string[], triggerId: string): Promise<{
|
|
4
|
+
path: string;
|
|
5
|
+
definition: TriggerFile;
|
|
6
|
+
} | undefined>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { readYamlObject } from "../config/readYamlObject.js";
|
|
2
|
+
import { loadTriggerOutcome } from "../config/loadTrigger.js";
|
|
3
|
+
export async function findCatalogPathById(paths, id) {
|
|
4
|
+
for (const filePath of paths) {
|
|
5
|
+
try {
|
|
6
|
+
const raw = await readYamlObject(filePath);
|
|
7
|
+
if (raw?.id === id)
|
|
8
|
+
return filePath;
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
// Unreadable/invalid catalog files are surfaced by `sf validate`; skip here.
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
export async function findTriggerDefinition(triggerPaths, triggerId) {
|
|
17
|
+
for (const filePath of triggerPaths) {
|
|
18
|
+
const outcome = await loadTriggerOutcome(filePath);
|
|
19
|
+
if (outcome.ok && outcome.value.id === triggerId) {
|
|
20
|
+
return { path: filePath, definition: outcome.value };
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Subset-match: every key/value in `match` must equal-match `payload`. No `match` matches everything. */
|
|
2
|
+
export function matchesEventFilter(match, payload) {
|
|
3
|
+
if (!match)
|
|
4
|
+
return true;
|
|
5
|
+
return Object.entries(match).every(([key, value]) => payload[key] === value);
|
|
6
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { TaskFile } from "../types/task.js";
|
|
2
|
+
export type TriggerFireEvent = {
|
|
3
|
+
triggerId: string;
|
|
4
|
+
task?: TaskFile;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Abstract seam for anything that decides "fire trigger X now" — a croner-backed
|
|
8
|
+
* schedule loop, a GitHub/email poller, a webhook receiver. No adapter implements
|
|
9
|
+
* this yet; it exists so those later adapters build against a locked contract.
|
|
10
|
+
*/
|
|
11
|
+
export interface TriggerSourcePort {
|
|
12
|
+
start(onFire: (event: TriggerFireEvent) => Promise<void>): Promise<void>;
|
|
13
|
+
stop(): Promise<void>;
|
|
14
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { RunStore } from "../runstore/port.js";
|
|
2
|
+
import type { RunManager, StartRunResult } from "./runManager.js";
|
|
3
|
+
import type { TaskFile } from "../types/task.js";
|
|
4
|
+
/**
|
|
5
|
+
* Resolves a trigger id to its `*.trigger.yaml` catalog definition, resolves its
|
|
6
|
+
* `pipeline`/`task` refs to catalog files, and starts a real run through
|
|
7
|
+
* `RunManager.startRun` — the same internal entry point the CLI/MCP/console use.
|
|
8
|
+
* On a successful start, records the fire on the trigger's store row.
|
|
9
|
+
*
|
|
10
|
+
* `task` presence on the trigger definition is the either/or mode switch: a
|
|
11
|
+
* catalog-mode trigger (`task` set) rejects a supplied `options.task`; a
|
|
12
|
+
* dynamic-mode trigger (`task` absent) requires one.
|
|
13
|
+
*/
|
|
14
|
+
export declare function fireTrigger(triggerId: string, store: RunStore, runManager: RunManager, options?: {
|
|
15
|
+
cwd?: string;
|
|
16
|
+
task?: TaskFile;
|
|
17
|
+
}): Promise<StartRunResult>;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { catalogContextFromStageflow } from "../config/resolveCatalogContext.js";
|
|
3
|
+
import { getCatalogScanPaths } from "../config/browseCatalog.js";
|
|
4
|
+
import { resolveStageflowContext } from "../project/resolveStageflowContext.js";
|
|
5
|
+
import { findCatalogPathById, findTriggerDefinition } from "./triggerCatalog.js";
|
|
6
|
+
/**
|
|
7
|
+
* Resolves a trigger id to its `*.trigger.yaml` catalog definition, resolves its
|
|
8
|
+
* `pipeline`/`task` refs to catalog files, and starts a real run through
|
|
9
|
+
* `RunManager.startRun` — the same internal entry point the CLI/MCP/console use.
|
|
10
|
+
* On a successful start, records the fire on the trigger's store row.
|
|
11
|
+
*
|
|
12
|
+
* `task` presence on the trigger definition is the either/or mode switch: a
|
|
13
|
+
* catalog-mode trigger (`task` set) rejects a supplied `options.task`; a
|
|
14
|
+
* dynamic-mode trigger (`task` absent) requires one.
|
|
15
|
+
*/
|
|
16
|
+
export async function fireTrigger(triggerId, store, runManager, options = {}) {
|
|
17
|
+
const cwd = options.cwd ?? process.cwd();
|
|
18
|
+
const ctx = catalogContextFromStageflow(await resolveStageflowContext(cwd));
|
|
19
|
+
const scanPaths = await getCatalogScanPaths(ctx);
|
|
20
|
+
if (!scanPaths) {
|
|
21
|
+
return { ok: false, reason: "No Stageflow catalog found", status: 404 };
|
|
22
|
+
}
|
|
23
|
+
const found = await findTriggerDefinition(scanPaths.triggerPaths, triggerId);
|
|
24
|
+
if (!found) {
|
|
25
|
+
return { ok: false, reason: `Trigger not found: ${triggerId}`, status: 404 };
|
|
26
|
+
}
|
|
27
|
+
const { path: triggerPath, definition: trigger } = found;
|
|
28
|
+
if (!trigger.enabled) {
|
|
29
|
+
return { ok: false, reason: `Trigger is disabled: ${triggerId}`, status: 409 };
|
|
30
|
+
}
|
|
31
|
+
const suppliedTask = options.task;
|
|
32
|
+
if (trigger.task !== undefined && suppliedTask !== undefined) {
|
|
33
|
+
return {
|
|
34
|
+
ok: false,
|
|
35
|
+
reason: `Trigger "${triggerId}" already has a catalog task "${trigger.task}"; task override is not allowed`,
|
|
36
|
+
status: 422,
|
|
37
|
+
code: "trigger.task_override_not_allowed",
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
if (trigger.task === undefined && suppliedTask === undefined) {
|
|
41
|
+
return {
|
|
42
|
+
ok: false,
|
|
43
|
+
reason: `Trigger "${triggerId}" has no catalog task; a task must be supplied when firing`,
|
|
44
|
+
status: 422,
|
|
45
|
+
code: "trigger.task_required",
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
const pipelinePath = await findCatalogPathById(scanPaths.pipelinePaths, trigger.pipeline);
|
|
49
|
+
if (!pipelinePath) {
|
|
50
|
+
return {
|
|
51
|
+
ok: false,
|
|
52
|
+
reason: `Trigger "${triggerId}" references unknown pipeline "${trigger.pipeline}"`,
|
|
53
|
+
status: 400,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
let taskInput;
|
|
57
|
+
if (trigger.task !== undefined) {
|
|
58
|
+
const taskPath = await findCatalogPathById(scanPaths.taskPaths, trigger.task);
|
|
59
|
+
if (!taskPath) {
|
|
60
|
+
return {
|
|
61
|
+
ok: false,
|
|
62
|
+
reason: `Trigger "${triggerId}" references unknown task "${trigger.task}"`,
|
|
63
|
+
status: 400,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
taskInput = taskPath;
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
taskInput = suppliedTask;
|
|
70
|
+
}
|
|
71
|
+
const projectRoot = ctx.projectRoot ?? undefined;
|
|
72
|
+
const definitionRef = projectRoot !== undefined
|
|
73
|
+
? path.relative(projectRoot, triggerPath).replace(/\\/g, "/")
|
|
74
|
+
: triggerPath;
|
|
75
|
+
await store.upsertTrigger({
|
|
76
|
+
id: triggerId,
|
|
77
|
+
definitionRef,
|
|
78
|
+
enabled: trigger.enabled,
|
|
79
|
+
});
|
|
80
|
+
const started = await runManager.startRun({
|
|
81
|
+
pipeline: pipelinePath,
|
|
82
|
+
task: taskInput,
|
|
83
|
+
...(projectRoot !== undefined ? { projectRoot } : {}),
|
|
84
|
+
});
|
|
85
|
+
if (started.ok) {
|
|
86
|
+
await store.recordTriggerFired(triggerId, started.runId);
|
|
87
|
+
}
|
|
88
|
+
return started;
|
|
89
|
+
}
|
|
@@ -2,6 +2,8 @@ import type { StageRunResult } from "../agent/port.js";
|
|
|
2
2
|
import type { RunStore } from "../runstore/port.js";
|
|
3
3
|
import type { ResolvedPipelineDag } from "../types/pipeline.js";
|
|
4
4
|
import type { StageConfig } from "../types/stage.js";
|
|
5
|
+
import type { BrowserRunner } from "../browser/browserHost.js";
|
|
6
|
+
import { type LoginCheckConfig, type LoginCheckOptions } from "../browser/loginCheck.js";
|
|
5
7
|
import type { StageRoots } from "./stageRoots.js";
|
|
6
8
|
/** Sidecar written before completion command checks so they can inspect fork_choice. */
|
|
7
9
|
export declare const COMPLETION_CANDIDATE_ENVELOPE_FILENAME = "completion-candidate-envelope.json";
|
|
@@ -29,4 +31,9 @@ export declare function createVerifiedStageExecution(options: {
|
|
|
29
31
|
dag?: ResolvedPipelineDag;
|
|
30
32
|
roots: StageRoots;
|
|
31
33
|
commandEnv?: NodeJS.ProcessEnv;
|
|
34
|
+
browserLogin?: {
|
|
35
|
+
check: LoginCheckConfig;
|
|
36
|
+
runner?: BrowserRunner;
|
|
37
|
+
options?: LoginCheckOptions;
|
|
38
|
+
};
|
|
32
39
|
}): VerifiedStageExecution;
|
|
@@ -9,6 +9,9 @@ import { COMPLETION_VERIFICATION_FAILURE_PREFIX, } from "../types/completion.js"
|
|
|
9
9
|
import { runCompletionContract, } from "./completionCheckRunner.js";
|
|
10
10
|
import { readCompletionCheckoutBaseline, writeCompletionCheckoutBaseline, } from "./completionCheckoutBaseline.js";
|
|
11
11
|
import { createGitCheckoutCapability } from "./gitCheckoutCapability.js";
|
|
12
|
+
import { readStagePersistedBrowserEnv } from "../browser/stageBrowserEnv.js";
|
|
13
|
+
import { defaultBrowserRunner } from "../browser/browserTeardown.js";
|
|
14
|
+
import { runLoginCheck, } from "../browser/loginCheck.js";
|
|
12
15
|
/** Sidecar written before completion command checks so they can inspect fork_choice. */
|
|
13
16
|
export const COMPLETION_CANDIDATE_ENVELOPE_FILENAME = "completion-candidate-envelope.json";
|
|
14
17
|
export function completionVerificationFailureReason(failedCheckIds) {
|
|
@@ -88,6 +91,17 @@ export function createVerifiedStageExecution(options) {
|
|
|
88
91
|
checkoutBefore,
|
|
89
92
|
checkoutError,
|
|
90
93
|
checklistAttestations: candidate.envelope.checklist_attestations,
|
|
94
|
+
...(options.browserLogin !== undefined
|
|
95
|
+
? {
|
|
96
|
+
browserLogin: async () => {
|
|
97
|
+
const env = await readStagePersistedBrowserEnv(roots.runWorkspaceDir, stageId);
|
|
98
|
+
if (env === undefined) {
|
|
99
|
+
throw new Error("browser session env is missing for this stage");
|
|
100
|
+
}
|
|
101
|
+
return runLoginCheck(env, options.browserLogin.check, options.browserLogin.runner ?? defaultBrowserRunner, options.browserLogin.options);
|
|
102
|
+
},
|
|
103
|
+
}
|
|
104
|
+
: {}),
|
|
91
105
|
onCheckStart: async (check) => {
|
|
92
106
|
await store.upsertVerificationCheckResult(runId, stageId, {
|
|
93
107
|
check_id: check.id,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function verifyHmacSignature(rawBody: Buffer, headerValue: string, secret: string, scheme: "hex" | "base64"): boolean;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
export function verifyHmacSignature(rawBody, headerValue, secret, scheme) {
|
|
3
|
+
const eqIndex = headerValue.indexOf("=");
|
|
4
|
+
const hasPrefix = eqIndex !== -1 && eqIndex < headerValue.length - 1;
|
|
5
|
+
const provided = hasPrefix ? headerValue.slice(eqIndex + 1) : headerValue;
|
|
6
|
+
const computed = crypto.createHmac("sha256", secret).update(rawBody).digest(scheme);
|
|
7
|
+
const providedBuffer = Buffer.from(provided, scheme);
|
|
8
|
+
const computedBuffer = Buffer.from(computed, scheme);
|
|
9
|
+
if (providedBuffer.length !== computedBuffer.length)
|
|
10
|
+
return false;
|
|
11
|
+
return crypto.timingSafeEqual(providedBuffer, computedBuffer);
|
|
12
|
+
}
|