stageflow 0.39.0 → 0.42.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 +46 -19
- package/dist/agent/claudeAdapter.js +15 -1
- package/dist/agent/claudeTools.d.ts +13 -0
- package/dist/agent/claudeTools.js +98 -0
- package/dist/agent/piAdapter.js +38 -16
- package/dist/agent/sandboxPiTools.d.ts +99 -0
- package/dist/agent/sandboxPiTools.js +109 -0
- package/dist/agents/authorChat.d.ts +4 -0
- package/dist/agents/authorChat.js +113 -12
- package/dist/agents/authorSessionStore.d.ts +35 -0
- package/dist/agents/authorSessionStore.js +152 -0
- package/dist/agents/chatTurn.d.ts +2 -0
- package/dist/agents/chatTurn.js +17 -1
- package/dist/agents/errors.d.ts +1 -1
- package/dist/agents/errors.js +1 -0
- package/dist/agents/evalGate.d.ts +61 -0
- package/dist/agents/evalGate.js +103 -0
- package/dist/agents/evalSession.d.ts +26 -0
- package/dist/agents/evalSession.js +29 -0
- package/dist/agents/hostContext.d.ts +3 -0
- package/dist/agents/hostContext.js +4 -1
- package/dist/agents/runtime/piRuntime.d.ts +1 -1
- package/dist/agents/runtime/piRuntime.js +4 -4
- package/dist/agents/runtime/types.d.ts +3 -1
- package/dist/agents/service.d.ts +27 -2
- package/dist/agents/service.js +88 -13
- package/dist/agents/sessions.d.ts +8 -0
- package/dist/agents/sessions.js +50 -10
- package/dist/agents/store.d.ts +14 -3
- package/dist/agents/store.js +45 -7
- package/dist/agents/toolRisk.d.ts +21 -0
- package/dist/agents/toolRisk.js +50 -0
- package/dist/cli/doctorCommand.js +26 -0
- package/dist/cli/evalCommand.d.ts +20 -0
- package/dist/cli/evalCommand.js +720 -0
- package/dist/cli/exportAllCommand.js +1 -1
- package/dist/cli/hostClient.d.ts +60 -0
- package/dist/cli/hostClient.js +71 -0
- package/dist/cli/validateOutput.d.ts +2 -2
- package/dist/cli/validateOutput.js +2 -2
- package/dist/cli.js +10 -0
- package/dist/config/atomicWrite.d.ts +15 -0
- package/dist/config/atomicWrite.js +43 -0
- package/dist/config/browseCatalog.d.ts +2 -0
- package/dist/config/browseCatalog.js +4 -2
- package/dist/config/csv.d.ts +9 -0
- package/dist/config/csv.js +81 -0
- package/dist/config/datasetAgentCheck.d.ts +18 -0
- package/dist/config/datasetAgentCheck.js +109 -0
- package/dist/config/datasetCandidates.d.ts +22 -0
- package/dist/config/datasetCandidates.js +86 -0
- package/dist/config/datasetFromSessions.d.ts +34 -0
- package/dist/config/datasetFromSessions.js +105 -0
- package/dist/config/datasetHash.d.ts +1 -0
- package/dist/config/datasetHash.js +4 -0
- package/dist/config/datasetImport.d.ts +21 -0
- package/dist/config/datasetImport.js +120 -0
- package/dist/config/datasetStageCheck.d.ts +9 -0
- package/dist/config/datasetStageCheck.js +17 -0
- package/dist/config/datasetTypes.d.ts +27 -0
- package/dist/config/datasetTypes.js +1 -0
- package/dist/config/ensureCatalogScanRoot.d.ts +2 -1
- package/dist/config/ensureCatalogScanRoot.js +2 -6
- package/dist/config/evalCatalog.d.ts +22 -0
- package/dist/config/evalCatalog.js +67 -0
- package/dist/config/evalWriteRoots.d.ts +17 -0
- package/dist/config/evalWriteRoots.js +109 -0
- package/dist/config/hostConfig.js +3 -0
- package/dist/config/installEvalSample.d.ts +34 -0
- package/dist/config/installEvalSample.js +182 -0
- package/dist/config/loadDataset.d.ts +16 -0
- package/dist/config/loadDataset.js +318 -0
- package/dist/config/loadEval.d.ts +32 -0
- package/dist/config/loadEval.js +1085 -0
- package/dist/config/loadOutcome.d.ts +5 -0
- package/dist/config/loadStageflowManifest.js +64 -0
- package/dist/config/loadTask.js +34 -1
- package/dist/config/resolveAgentTarget.d.ts +32 -0
- package/dist/config/resolveAgentTarget.js +118 -0
- package/dist/config/resolveEvalTarget.d.ts +21 -0
- package/dist/config/resolveEvalTarget.js +89 -0
- package/dist/config/scanCatalogPaths.d.ts +1 -1
- package/dist/config/scanCatalogPaths.js +17 -5
- package/dist/config/validateCatalog.d.ts +1 -1
- package/dist/config/validateCatalog.js +9 -1
- package/dist/config/validateEval.d.ts +36 -0
- package/dist/config/validateEval.js +405 -0
- package/dist/config/writeDataset.d.ts +54 -0
- package/dist/config/writeDataset.js +186 -0
- package/dist/config/writeEval.d.ts +25 -0
- package/dist/config/writeEval.js +398 -0
- package/dist/diagnostics/checks.js +1 -1
- package/dist/evals/agentDiff.d.ts +13 -0
- package/dist/evals/agentDiff.js +11 -0
- package/dist/evals/canonical.d.ts +3 -0
- package/dist/evals/canonical.js +24 -0
- package/dist/evals/caseTask.d.ts +3 -0
- package/dist/evals/caseTask.js +9 -0
- package/dist/evals/compare.d.ts +94 -0
- package/dist/evals/compare.js +201 -0
- package/dist/evals/constants.d.ts +60 -0
- package/dist/evals/constants.js +95 -0
- package/dist/evals/engine/FakeEngine.d.ts +33 -0
- package/dist/evals/engine/FakeEngine.js +176 -0
- package/dist/evals/engine/index.d.ts +9 -0
- package/dist/evals/engine/index.js +19 -0
- package/dist/evals/engine/promptfoo/PromptfooEngine.d.ts +52 -0
- package/dist/evals/engine/promptfoo/PromptfooEngine.js +311 -0
- package/dist/evals/engine/promptfoo/errors.d.ts +5 -0
- package/dist/evals/engine/promptfoo/errors.js +8 -0
- package/dist/evals/engine/promptfoo/generate.d.ts +30 -0
- package/dist/evals/engine/promptfoo/generate.js +342 -0
- package/dist/evals/engine/promptfoo/guard.d.ts +10 -0
- package/dist/evals/engine/promptfoo/guard.js +116 -0
- package/dist/evals/engine/promptfoo/install.d.ts +17 -0
- package/dist/evals/engine/promptfoo/install.js +73 -0
- package/dist/evals/engine/promptfoo/judgeProviders.d.ts +2 -0
- package/dist/evals/engine/promptfoo/judgeProviders.js +9 -0
- package/dist/evals/engine/promptfoo/judgeServices.d.ts +6 -0
- package/dist/evals/engine/promptfoo/judgeServices.js +41 -0
- package/dist/evals/engine/promptfoo/locate.d.ts +21 -0
- package/dist/evals/engine/promptfoo/locate.js +68 -0
- package/dist/evals/engine/promptfoo/nodeVersion.d.ts +7 -0
- package/dist/evals/engine/promptfoo/nodeVersion.js +14 -0
- package/dist/evals/engine/promptfoo/normalize.d.ts +66 -0
- package/dist/evals/engine/promptfoo/normalize.js +261 -0
- package/dist/evals/engine/promptfoo/pinned.d.ts +5 -0
- package/dist/evals/engine/promptfoo/pinned.js +5 -0
- package/dist/evals/engine/promptfoo/rubricPrompt.d.ts +14 -0
- package/dist/evals/engine/promptfoo/rubricPrompt.js +103 -0
- package/dist/evals/engine/promptfoo/runTokens.d.ts +18 -0
- package/dist/evals/engine/promptfoo/runTokens.js +31 -0
- package/dist/evals/engine/promptfoo/status.d.ts +13 -0
- package/dist/evals/engine/promptfoo/status.js +16 -0
- package/dist/evals/estimate.d.ts +56 -0
- package/dist/evals/estimate.js +66 -0
- package/dist/evals/graders/codeGrader.d.ts +6 -0
- package/dist/evals/graders/codeGrader.js +85 -0
- package/dist/evals/graders/conversationCheckGrader.d.ts +6 -0
- package/dist/evals/graders/conversationCheckGrader.js +62 -0
- package/dist/evals/graders/fieldGrader.d.ts +11 -0
- package/dist/evals/graders/fieldGrader.js +117 -0
- package/dist/evals/graders/goal.d.ts +3 -0
- package/dist/evals/graders/goal.js +14 -0
- package/dist/evals/graders/index.d.ts +18 -0
- package/dist/evals/graders/index.js +34 -0
- package/dist/evals/graders/notApplicable.d.ts +5 -0
- package/dist/evals/graders/notApplicable.js +9 -0
- package/dist/evals/graders/path.d.ts +26 -0
- package/dist/evals/graders/path.js +75 -0
- package/dist/evals/graders/referenceGrader.d.ts +5 -0
- package/dist/evals/graders/referenceGrader.js +59 -0
- package/dist/evals/graders/schemaGrader.d.ts +5 -0
- package/dist/evals/graders/schemaGrader.js +29 -0
- package/dist/evals/graders/subject.d.ts +6 -0
- package/dist/evals/graders/subject.js +33 -0
- package/dist/evals/graders/toolCallsGrader.d.ts +6 -0
- package/dist/evals/graders/toolCallsGrader.js +100 -0
- package/dist/evals/graders/transcript.d.ts +12 -0
- package/dist/evals/graders/transcript.js +57 -0
- package/dist/evals/graders/types.d.ts +20 -0
- package/dist/evals/graders/types.js +1 -0
- package/dist/evals/judgeProviders.d.ts +16 -0
- package/dist/evals/judgeProviders.js +27 -0
- package/dist/evals/lineDiff.d.ts +5 -0
- package/dist/evals/lineDiff.js +34 -0
- package/dist/evals/listStatus.d.ts +31 -0
- package/dist/evals/listStatus.js +48 -0
- package/dist/evals/manager.d.ts +174 -0
- package/dist/evals/manager.js +787 -0
- package/dist/evals/plan.d.ts +58 -0
- package/dist/evals/plan.js +602 -0
- package/dist/evals/providerKeys.d.ts +1 -0
- package/dist/evals/providerKeys.js +21 -0
- package/dist/evals/read/agentTargets.d.ts +37 -0
- package/dist/evals/read/agentTargets.js +101 -0
- package/dist/evals/read/api.d.ts +196 -0
- package/dist/evals/read/api.js +358 -0
- package/dist/evals/read/compare.d.ts +4 -0
- package/dist/evals/read/compare.js +39 -0
- package/dist/evals/read/datasets.d.ts +105 -0
- package/dist/evals/read/datasets.js +318 -0
- package/dist/evals/read/list.d.ts +66 -0
- package/dist/evals/read/list.js +173 -0
- package/dist/evals/read/results.d.ts +46 -0
- package/dist/evals/read/results.js +107 -0
- package/dist/evals/read/runs.d.ts +149 -0
- package/dist/evals/read/runs.js +289 -0
- package/dist/evals/read/suggestions.d.ts +43 -0
- package/dist/evals/read/suggestions.js +73 -0
- package/dist/evals/runner/FakeAgentBackend.d.ts +40 -0
- package/dist/evals/runner/FakeAgentBackend.js +82 -0
- package/dist/evals/runner/agentCaseRunner.d.ts +24 -0
- package/dist/evals/runner/agentCaseRunner.js +388 -0
- package/dist/evals/runner/caseRunner.d.ts +7 -0
- package/dist/evals/runner/caseRunner.js +17 -0
- package/dist/evals/runner/conversationSubject.d.ts +18 -0
- package/dist/evals/runner/conversationSubject.js +123 -0
- package/dist/evals/runner/simulatorModel.d.ts +51 -0
- package/dist/evals/runner/simulatorModel.js +58 -0
- package/dist/evals/runner/simulatorPrompt.d.ts +29 -0
- package/dist/evals/runner/simulatorPrompt.js +59 -0
- package/dist/evals/runner/stageCaseRunner.d.ts +30 -0
- package/dist/evals/runner/stageCaseRunner.js +279 -0
- package/dist/evals/runner/userDriver.d.ts +71 -0
- package/dist/evals/runner/userDriver.js +104 -0
- package/dist/evals/scoring.d.ts +34 -0
- package/dist/evals/scoring.js +67 -0
- package/dist/evals/snapshot.d.ts +183 -0
- package/dist/evals/snapshot.js +65 -0
- package/dist/evals/stageDiff.d.ts +8 -0
- package/dist/evals/stageDiff.js +48 -0
- package/dist/evals/stats.d.ts +95 -0
- package/dist/evals/stats.js +220 -0
- package/dist/evals/statsAdapter.d.ts +4 -0
- package/dist/evals/statsAdapter.js +30 -0
- package/dist/evals/statsTypes.d.ts +51 -0
- package/dist/evals/statsTypes.js +18 -0
- package/dist/evals/storeError.d.ts +5 -0
- package/dist/evals/storeError.js +8 -0
- package/dist/evals/storeTypes.d.ts +207 -0
- package/dist/evals/storeTypes.js +1 -0
- package/dist/evals/tTable.d.ts +3 -0
- package/dist/evals/tTable.js +15 -0
- package/dist/evals/triage/constants.d.ts +26 -0
- package/dist/evals/triage/constants.js +33 -0
- package/dist/evals/triage/credentials.d.ts +13 -0
- package/dist/evals/triage/credentials.js +20 -0
- package/dist/evals/triage/jevClassifier.d.ts +14 -0
- package/dist/evals/triage/jevClassifier.js +124 -0
- package/dist/evals/triage/judgeClassifier.d.ts +10 -0
- package/dist/evals/triage/judgeClassifier.js +76 -0
- package/dist/evals/triage/prompts.d.ts +16 -0
- package/dist/evals/triage/prompts.js +64 -0
- package/dist/evals/triage/service.d.ts +79 -0
- package/dist/evals/triage/service.js +217 -0
- package/dist/evals/triage/stateBuilder.d.ts +19 -0
- package/dist/evals/triage/stateBuilder.js +125 -0
- package/dist/evals/triage/types.d.ts +60 -0
- package/dist/evals/triage/types.js +8 -0
- package/dist/evals/types.d.ts +347 -0
- package/dist/evals/types.js +1 -0
- package/dist/mcp/catalogTools.js +11 -1
- package/dist/mcp/deps.d.ts +7 -0
- package/dist/mcp/evalTools.d.ts +3 -0
- package/dist/mcp/evalTools.js +280 -0
- package/dist/mcp/server.js +1 -0
- package/dist/mcp/tools.js +2 -0
- package/dist/models/response.d.ts +7 -2
- package/dist/models/response.js +6 -1
- package/dist/operatorAgent/agentAuthorContext.d.ts +1 -1
- package/dist/operatorAgent/agentAuthorContext.js +1 -1
- package/dist/operatorAgent/piModel.js +17 -4
- package/dist/operatorAgent/profiles/agentAuthor.d.ts +3 -1
- package/dist/operatorAgent/profiles/agentAuthor.js +145 -20
- package/dist/operatorAgent/profiles/agentAuthorPlaybook.d.ts +1 -1
- package/dist/operatorAgent/profiles/agentAuthorPlaybook.js +48 -15
- package/dist/operatorAgent/profiles/authorReadable.d.ts +2 -0
- package/dist/operatorAgent/profiles/authorReadable.js +26 -0
- package/dist/package-meta.d.ts +1 -1
- package/dist/package-meta.js +1 -1
- package/dist/runstore/port.d.ts +43 -0
- package/dist/runstore/sqlite/SqliteRunStore.d.ts +28 -0
- package/dist/runstore/sqlite/SqliteRunStore.js +99 -5
- package/dist/runstore/sqlite/evalStore.d.ts +20 -0
- package/dist/runstore/sqlite/evalStore.js +541 -0
- package/dist/runstore/sqlite/migrations/015-evals.d.ts +12 -0
- package/dist/runstore/sqlite/migrations/015-evals.js +157 -0
- package/dist/runstore/sqlite/migrations/016-eval-subject-ref.d.ts +12 -0
- package/dist/runstore/sqlite/migrations/016-eval-subject-ref.js +20 -0
- package/dist/runstore/sqlite/migrations/017-eval-result-suggestions.d.ts +11 -0
- package/dist/runstore/sqlite/migrations/017-eval-result-suggestions.js +36 -0
- package/dist/runstore/sqlite/migrations/index.js +6 -0
- package/dist/runstore/sqlite/suggestionStore.d.ts +14 -0
- package/dist/runstore/sqlite/suggestionStore.js +132 -0
- package/dist/runtime/gitCheckoutCapability.d.ts +1 -0
- package/dist/runtime/gitCheckoutCapability.js +1 -1
- package/dist/runtime/gitWorktreeCheckout.d.ts +22 -0
- package/dist/runtime/gitWorktreeCheckout.js +120 -0
- package/dist/runtime/pipelineRunner.d.ts +2 -1
- package/dist/runtime/pipelineRunner.js +2 -0
- package/dist/runtime/runManager.d.ts +2 -1
- package/dist/runtime/runManager.js +8 -6
- package/dist/runtime/runRetentionSweep.js +2 -2
- package/dist/runtime/sandboxContainer.d.ts +62 -0
- package/dist/runtime/sandboxContainer.js +151 -0
- package/dist/runtime/sandboxDesk.d.ts +28 -0
- package/dist/runtime/sandboxDesk.js +242 -0
- package/dist/runtime/sandboxFiles.d.ts +20 -0
- package/dist/runtime/sandboxFiles.js +150 -0
- package/dist/runtime/stageEnvironment.js +2 -0
- package/dist/runtime/stageRoots.d.ts +9 -0
- package/dist/runtime/stageRoots.js +14 -0
- package/dist/runtime/stageRunner.d.ts +5 -0
- package/dist/runtime/stageRunner.js +38 -4
- package/dist/runtime/stageSandbox.d.ts +16 -0
- package/dist/runtime/stageSandbox.js +54 -0
- package/dist/runtime/stageWorker.js +138 -105
- package/dist/runtime/workspaceBinding.d.ts +2 -1
- package/dist/runtime/workspaceBinding.js +20 -4
- package/dist/server/agentAuthorRoutes.js +50 -1
- package/dist/server/bootstrap.d.ts +8 -0
- package/dist/server/bootstrap.js +56 -0
- package/dist/server/controlToken.js +3 -0
- package/dist/server/evalInternalRoutes.d.ts +14 -0
- package/dist/server/evalInternalRoutes.js +87 -0
- package/dist/server/evalRouteContext.d.ts +46 -0
- package/dist/server/evalRouteContext.js +43 -0
- package/dist/server/evalRoutes.d.ts +5 -0
- package/dist/server/evalRoutes.js +1427 -0
- package/dist/server/evalTriageRoutes.d.ts +2 -0
- package/dist/server/evalTriageRoutes.js +95 -0
- package/dist/server/http.d.ts +5 -0
- package/dist/server/http.js +32 -2
- package/dist/server/mcpHost.js +3 -0
- package/dist/toolkit/meta.d.ts +1 -1
- package/dist/toolkit/meta.js +15 -0
- package/dist/toolkit/profiles.js +1 -1
- package/dist/tools/readEmail.d.ts +3 -3
- package/dist/types/stageflowManifest.d.ts +6 -0
- package/dist/types/task.d.ts +11 -1
- package/dist/ui/assets/AssistantRuntimeProvider-qIX1xNoH.js +9 -0
- package/dist/ui/assets/{MarkdownText-D7tYaIFO.js → MarkdownText-BTrOqu5J.js} +1 -1
- package/dist/ui/assets/chat-Df_wCpak.js +5 -0
- package/dist/ui/assets/chat-P7KQznHm.css +2 -0
- package/dist/ui/assets/{main-BFo79krC.css → main-CHlmTNJe.css} +1 -1
- package/dist/ui/assets/main-CvVfxmMd.js +271 -0
- package/dist/ui/chat.html +3 -3
- package/dist/ui/index.html +3 -3
- package/examples/README.md +2 -0
- package/examples/document-policy-review/README.md +35 -0
- package/examples/document-policy-review/agents/compliance.agent.yaml +42 -0
- package/examples/document-policy-review/documents/acme-vendor-sow.md +15 -0
- package/examples/document-policy-review/documents/support-runbook.md +13 -0
- package/examples/document-policy-review/policies/acceptable-use.md +13 -0
- package/examples/document-policy-review/policies/data-handling.md +17 -0
- package/examples/document-policy-review/policies/vendor-security.md +17 -0
- package/examples/evals/README.md +123 -0
- package/examples/evals/critique-sample.dataset.jsonl +8 -0
- package/examples/evals/critique-sample.eval.yaml +34 -0
- package/examples/evals/critique-sample.task.yaml +4 -0
- package/examples/evals/eval-sample.pipeline.yaml +35 -0
- package/examples/evals/support-sample.agent.yaml +20 -0
- package/examples/evals/support-sample.dataset.jsonl +6 -0
- package/examples/evals/support-sample.eval.yaml +38 -0
- package/examples/memory/README.md +2 -0
- package/examples/memory/probe-bash.task.yaml +3 -0
- package/examples/memory/sandbox-memory-bash.pipeline.yaml +28 -0
- package/examples/ship-feature/README.md +15 -0
- package/examples/stageflow.yaml +6 -0
- package/package.json +1 -1
- package/skills/stageflow/SKILL.md +5 -2
- package/skills/stageflow/references/control-surface.md +4 -2
- package/skills/stageflow/references/evals.md +68 -0
- package/skills/stageflow-author/SKILL.md +4 -0
- package/skills/stageflow-run/SKILL.md +4 -0
- package/dist/ui/assets/AssistantRuntimeProvider-CsiPFhCT.js +0 -9
- package/dist/ui/assets/chat-BbLukq9W.js +0 -5
- package/dist/ui/assets/chat-zIZU_bP1.css +0 -2
- package/dist/ui/assets/main-CijYMeM0.js +0 -259
|
@@ -530,7 +530,7 @@ export class RunManager {
|
|
|
530
530
|
*/
|
|
531
531
|
async attachWaitingStages() {
|
|
532
532
|
const found = [];
|
|
533
|
-
const runs = await this.options.store.listRuns();
|
|
533
|
+
const runs = await this.options.store.listRuns({ origin: "any" });
|
|
534
534
|
for (const summary of runs) {
|
|
535
535
|
if (summary.status !== "running")
|
|
536
536
|
continue;
|
|
@@ -627,7 +627,7 @@ export class RunManager {
|
|
|
627
627
|
}
|
|
628
628
|
async reconcileOrphanedStages() {
|
|
629
629
|
const reconciled = [];
|
|
630
|
-
const runs = await this.options.store.listRuns();
|
|
630
|
+
const runs = await this.options.store.listRuns({ origin: "any" });
|
|
631
631
|
for (const summary of runs) {
|
|
632
632
|
let meta;
|
|
633
633
|
try {
|
|
@@ -806,7 +806,7 @@ export class RunManager {
|
|
|
806
806
|
}
|
|
807
807
|
async resumeStalledSchedules() {
|
|
808
808
|
const resumed = [];
|
|
809
|
-
const runs = await this.options.store.listRuns();
|
|
809
|
+
const runs = await this.options.store.listRuns({ origin: "any" });
|
|
810
810
|
for (const summary of runs) {
|
|
811
811
|
if (summary.status !== "running")
|
|
812
812
|
continue;
|
|
@@ -828,7 +828,7 @@ export class RunManager {
|
|
|
828
828
|
}
|
|
829
829
|
/** Rebuild in-memory admission queue from persisted `queued` rows (R27). */
|
|
830
830
|
async reenqueuePersistedQueuedRuns() {
|
|
831
|
-
const queued = await this.options.store.listRuns({ status: "queued" });
|
|
831
|
+
const queued = await this.options.store.listRuns({ status: "queued", origin: "any" });
|
|
832
832
|
const ordered = [...queued].sort((a, b) => a.created_at.localeCompare(b.created_at));
|
|
833
833
|
this.admissionQueue.clear();
|
|
834
834
|
for (const row of ordered) {
|
|
@@ -1273,7 +1273,7 @@ export class RunManager {
|
|
|
1273
1273
|
gitSha: input.gitSha,
|
|
1274
1274
|
ciPrUrl: input.ciPrUrl,
|
|
1275
1275
|
ciJobUrl: input.ciJobUrl,
|
|
1276
|
-
}, derivedProjectRoot, resolved.kind === "path" ? resolved.taskPath : undefined, submission, undefined, input.callerId, input.skills, input.dispatchKey);
|
|
1276
|
+
}, derivedProjectRoot, resolved.kind === "path" ? resolved.taskPath : undefined, submission, undefined, input.callerId, input.skills, input.dispatchKey, input.origin);
|
|
1277
1277
|
}
|
|
1278
1278
|
async rerun(runId, options) {
|
|
1279
1279
|
if (!this.acceptingWork) {
|
|
@@ -2219,7 +2219,7 @@ export class RunManager {
|
|
|
2219
2219
|
this.attachedWaiting.delete(`${runId}\0${stageId}`);
|
|
2220
2220
|
}
|
|
2221
2221
|
}
|
|
2222
|
-
async reserveAndStartPipeline(taskYaml, pipeline, taskLabel, cwd, checkoutOverride, skipGates, ciIdentity, projectRoot, taskPath, submission, pinned, callerId, skills, dispatchKey) {
|
|
2222
|
+
async reserveAndStartPipeline(taskYaml, pipeline, taskLabel, cwd, checkoutOverride, skipGates, ciIdentity, projectRoot, taskPath, submission, pinned, callerId, skills, dispatchKey, origin) {
|
|
2223
2223
|
const persistence = pipelinePersistenceForStart(pipeline);
|
|
2224
2224
|
if (!persistence.ok) {
|
|
2225
2225
|
return {
|
|
@@ -2419,6 +2419,7 @@ export class RunManager {
|
|
|
2419
2419
|
...(callerId !== undefined && callerId !== null
|
|
2420
2420
|
? { callerId }
|
|
2421
2421
|
: {}),
|
|
2422
|
+
...(origin !== undefined ? { origin } : {}),
|
|
2422
2423
|
runManifest,
|
|
2423
2424
|
skipGates,
|
|
2424
2425
|
});
|
|
@@ -2506,6 +2507,7 @@ export class RunManager {
|
|
|
2506
2507
|
skipGates,
|
|
2507
2508
|
schedulingHalt,
|
|
2508
2509
|
callerId,
|
|
2510
|
+
...(origin !== undefined ? { origin } : {}),
|
|
2509
2511
|
...(hasSkills ? { skills: validatedSkills } : {}),
|
|
2510
2512
|
});
|
|
2511
2513
|
if (started.created === false)
|
|
@@ -252,7 +252,7 @@ export async function runRetentionSweep(store, a2aStore, options = {}) {
|
|
|
252
252
|
const windows = options.windows ?? retentionWindowsFromEnv(env);
|
|
253
253
|
const artifactMaxBytes = options.artifactMaxBytes ?? slimArtifactMaxBytesFromEnv(env);
|
|
254
254
|
const bareCacheTtlMs = options.bareCacheTtlMs ?? bareCacheTtlFromEnv(env);
|
|
255
|
-
const runs = await store.listRuns();
|
|
255
|
+
const runs = await store.listRuns({ origin: "any" });
|
|
256
256
|
const slimmed = [];
|
|
257
257
|
const purged = [];
|
|
258
258
|
const metas = new Map();
|
|
@@ -305,7 +305,7 @@ export async function runRetentionSweep(store, a2aStore, options = {}) {
|
|
|
305
305
|
}
|
|
306
306
|
}
|
|
307
307
|
// Re-list for bare-cache decisions after PURGE removed rows when execute.
|
|
308
|
-
const runsForBare = execute ? await store.listRuns() : runs;
|
|
308
|
+
const runsForBare = execute ? await store.listRuns({ origin: "any" }) : runs;
|
|
309
309
|
const bareCachesEvicted = await evictBareCaches({
|
|
310
310
|
runs: runsForBare,
|
|
311
311
|
now,
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export declare const SANDBOX_CONTAINER_IMAGE_ENV = "STAGEFLOW_STAGE_CONTAINER_IMAGE";
|
|
2
|
+
export declare const SANDBOX_CONTAINER_DOCKER_BIN_ENV = "STAGEFLOW_STAGE_CONTAINER_DOCKER_BIN";
|
|
3
|
+
/** Docker container names allow only `[a-zA-Z0-9_.-]`. */
|
|
4
|
+
export declare function sanitizeContainerNameSegment(value: string): string;
|
|
5
|
+
export declare function buildContainerName(input: {
|
|
6
|
+
runId: string;
|
|
7
|
+
stageId: string;
|
|
8
|
+
attempt?: number;
|
|
9
|
+
}, suffix: string): string;
|
|
10
|
+
export type SandboxContainerOptions = {
|
|
11
|
+
image: string;
|
|
12
|
+
dockerBin: string;
|
|
13
|
+
};
|
|
14
|
+
export declare function resolveSandboxContainerOptions(env: Record<string, string | undefined>, override?: {
|
|
15
|
+
image: string;
|
|
16
|
+
dockerBin?: string;
|
|
17
|
+
}): SandboxContainerOptions | undefined;
|
|
18
|
+
/** Fixed path inside the sandbox. The daemon owns the volume; we never pass it a path from this process. */
|
|
19
|
+
export declare const SANDBOX_MOUNT_PATH = "/workspace";
|
|
20
|
+
export declare function sandboxVolumeName(containerName: string): string;
|
|
21
|
+
export declare function buildStartContainerArgs(params: {
|
|
22
|
+
image: string;
|
|
23
|
+
containerName: string;
|
|
24
|
+
}): string[];
|
|
25
|
+
export declare function startSandboxContainer(params: {
|
|
26
|
+
image: string;
|
|
27
|
+
runId: string;
|
|
28
|
+
stageId: string;
|
|
29
|
+
attempt?: number;
|
|
30
|
+
dockerBin?: string;
|
|
31
|
+
}): Promise<{
|
|
32
|
+
containerName: string;
|
|
33
|
+
}>;
|
|
34
|
+
export declare function stopSandboxContainer(params: {
|
|
35
|
+
containerName: string;
|
|
36
|
+
dockerBin?: string;
|
|
37
|
+
}): Promise<void>;
|
|
38
|
+
export type SandboxExecResult = {
|
|
39
|
+
stdout: string;
|
|
40
|
+
stderr: string;
|
|
41
|
+
exitCode: number;
|
|
42
|
+
};
|
|
43
|
+
export declare function execInSandboxContainer(params: {
|
|
44
|
+
containerName: string;
|
|
45
|
+
command: string;
|
|
46
|
+
dockerBin?: string;
|
|
47
|
+
timeoutMs?: number;
|
|
48
|
+
}): Promise<SandboxExecResult>;
|
|
49
|
+
export declare function copyToSandboxContainer(params: {
|
|
50
|
+
containerName: string;
|
|
51
|
+
hostPath: string;
|
|
52
|
+
containerPath: string;
|
|
53
|
+
dockerBin?: string;
|
|
54
|
+
contents?: boolean;
|
|
55
|
+
}): Promise<void>;
|
|
56
|
+
export declare function copyFromSandboxContainer(params: {
|
|
57
|
+
containerName: string;
|
|
58
|
+
containerPath: string;
|
|
59
|
+
hostPath: string;
|
|
60
|
+
dockerBin?: string;
|
|
61
|
+
contents?: boolean;
|
|
62
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { promisify } from "node:util";
|
|
4
|
+
export const SANDBOX_CONTAINER_IMAGE_ENV = "STAGEFLOW_STAGE_CONTAINER_IMAGE";
|
|
5
|
+
export const SANDBOX_CONTAINER_DOCKER_BIN_ENV = "STAGEFLOW_STAGE_CONTAINER_DOCKER_BIN";
|
|
6
|
+
const execFileAsync = promisify(execFile);
|
|
7
|
+
/** Docker container names allow only `[a-zA-Z0-9_.-]`. */
|
|
8
|
+
export function sanitizeContainerNameSegment(value) {
|
|
9
|
+
return value.replace(/[^a-zA-Z0-9_.-]/g, "-");
|
|
10
|
+
}
|
|
11
|
+
export function buildContainerName(input, suffix) {
|
|
12
|
+
const runId = sanitizeContainerNameSegment(input.runId);
|
|
13
|
+
const stageId = sanitizeContainerNameSegment(input.stageId);
|
|
14
|
+
const attempt = input.attempt ?? 1;
|
|
15
|
+
return `stageflow-${runId}-${stageId}-${attempt}-${suffix}`;
|
|
16
|
+
}
|
|
17
|
+
export function resolveSandboxContainerOptions(env, override) {
|
|
18
|
+
if (override) {
|
|
19
|
+
if (!override.image || override.image.trim() === "") {
|
|
20
|
+
throw new Error("sandboxContainer: image must be a non-empty string");
|
|
21
|
+
}
|
|
22
|
+
return { image: override.image, dockerBin: override.dockerBin || "docker" };
|
|
23
|
+
}
|
|
24
|
+
const image = env[SANDBOX_CONTAINER_IMAGE_ENV];
|
|
25
|
+
if (!image || image.trim() === "")
|
|
26
|
+
return undefined;
|
|
27
|
+
return {
|
|
28
|
+
image,
|
|
29
|
+
dockerBin: env[SANDBOX_CONTAINER_DOCKER_BIN_ENV] || "docker",
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/** Fixed path inside the sandbox. The daemon owns the volume; we never pass it a path from this process. */
|
|
33
|
+
export const SANDBOX_MOUNT_PATH = "/workspace";
|
|
34
|
+
export function sandboxVolumeName(containerName) {
|
|
35
|
+
const safe = containerName.replace(/[^A-Za-z0-9_.-]/g, "_");
|
|
36
|
+
return `sf-bash-${safe}`;
|
|
37
|
+
}
|
|
38
|
+
export function buildStartContainerArgs(params) {
|
|
39
|
+
const { image, containerName } = params;
|
|
40
|
+
return [
|
|
41
|
+
"run",
|
|
42
|
+
"-d",
|
|
43
|
+
"--name",
|
|
44
|
+
containerName,
|
|
45
|
+
"-v",
|
|
46
|
+
`${sandboxVolumeName(containerName)}:${SANDBOX_MOUNT_PATH}`,
|
|
47
|
+
"-w",
|
|
48
|
+
SANDBOX_MOUNT_PATH,
|
|
49
|
+
image,
|
|
50
|
+
"sleep",
|
|
51
|
+
"infinity",
|
|
52
|
+
];
|
|
53
|
+
}
|
|
54
|
+
function errorMessage(err) {
|
|
55
|
+
if (err && typeof err === "object" && "stderr" in err) {
|
|
56
|
+
const stderr = err.stderr;
|
|
57
|
+
if (typeof stderr === "string" && stderr.trim().length > 0)
|
|
58
|
+
return stderr;
|
|
59
|
+
}
|
|
60
|
+
return err instanceof Error ? err.message : String(err);
|
|
61
|
+
}
|
|
62
|
+
export async function startSandboxContainer(params) {
|
|
63
|
+
const containerName = buildContainerName({ runId: params.runId, stageId: params.stageId, attempt: params.attempt }, randomUUID().slice(0, 8));
|
|
64
|
+
const args = buildStartContainerArgs({
|
|
65
|
+
image: params.image,
|
|
66
|
+
containerName,
|
|
67
|
+
});
|
|
68
|
+
try {
|
|
69
|
+
await execFileAsync(params.dockerBin ?? "docker", args);
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
throw new Error(`failed to start sandbox container: ${errorMessage(err)}`);
|
|
73
|
+
}
|
|
74
|
+
return { containerName };
|
|
75
|
+
}
|
|
76
|
+
const NO_SUCH_CONTAINER_PATTERN = /no such container/i;
|
|
77
|
+
const NO_SUCH_VOLUME_PATTERN = /no such volume/i;
|
|
78
|
+
export async function stopSandboxContainer(params) {
|
|
79
|
+
try {
|
|
80
|
+
await execFileAsync(params.dockerBin ?? "docker", [
|
|
81
|
+
"rm",
|
|
82
|
+
"-f",
|
|
83
|
+
params.containerName,
|
|
84
|
+
]);
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
const stderr = stderrOf(err);
|
|
88
|
+
if (!NO_SUCH_CONTAINER_PATTERN.test(stderr)) {
|
|
89
|
+
throw new Error(`failed to stop sandbox container: ${errorMessage(err)}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
93
|
+
await execFileAsync(params.dockerBin ?? "docker", [
|
|
94
|
+
"volume",
|
|
95
|
+
"rm",
|
|
96
|
+
sandboxVolumeName(params.containerName),
|
|
97
|
+
]);
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
const stderr = stderrOf(err);
|
|
101
|
+
if (NO_SUCH_VOLUME_PATTERN.test(stderr))
|
|
102
|
+
return;
|
|
103
|
+
throw new Error(`failed to remove sandbox volume: ${errorMessage(err)}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function stderrOf(err) {
|
|
107
|
+
if (err && typeof err === "object" && "stderr" in err) {
|
|
108
|
+
return String(err.stderr ?? "");
|
|
109
|
+
}
|
|
110
|
+
return "";
|
|
111
|
+
}
|
|
112
|
+
export async function execInSandboxContainer(params) {
|
|
113
|
+
const args = ["exec", "-w", SANDBOX_MOUNT_PATH, params.containerName, "bash", "-c", params.command];
|
|
114
|
+
try {
|
|
115
|
+
const { stdout, stderr } = await execFileAsync(params.dockerBin ?? "docker", args, {
|
|
116
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
117
|
+
...(params.timeoutMs !== undefined ? { timeout: params.timeoutMs } : {}),
|
|
118
|
+
});
|
|
119
|
+
return { stdout, stderr, exitCode: 0 };
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
const e = err;
|
|
123
|
+
// `code` is a number only for a completed exec that exited non-zero.
|
|
124
|
+
// Anything else (ENOENT for a missing docker binary, the container
|
|
125
|
+
// having been removed mid-exec, etc.) is an infra failure, not a
|
|
126
|
+
// command result — rethrow so callers don't mistake it for "the
|
|
127
|
+
// command ran and exited 1".
|
|
128
|
+
if (typeof e.code !== "number") {
|
|
129
|
+
throw new Error(`failed to exec in sandbox container: ${errorMessage(err)}`);
|
|
130
|
+
}
|
|
131
|
+
return { stdout: e.stdout ?? "", stderr: e.stderr ?? "", exitCode: e.code };
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
export async function copyToSandboxContainer(params) {
|
|
135
|
+
const src = params.contents ? `${params.hostPath}/.` : params.hostPath;
|
|
136
|
+
await dockerCp(params.dockerBin, src, `${params.containerName}:${params.containerPath}`, "into");
|
|
137
|
+
}
|
|
138
|
+
export async function copyFromSandboxContainer(params) {
|
|
139
|
+
const src = params.contents
|
|
140
|
+
? `${params.containerName}:${params.containerPath}/.`
|
|
141
|
+
: `${params.containerName}:${params.containerPath}`;
|
|
142
|
+
await dockerCp(params.dockerBin, src, params.hostPath, "from");
|
|
143
|
+
}
|
|
144
|
+
async function dockerCp(dockerBin, src, dest, direction) {
|
|
145
|
+
try {
|
|
146
|
+
await execFileAsync(dockerBin ?? "docker", ["cp", src, dest]);
|
|
147
|
+
}
|
|
148
|
+
catch (err) {
|
|
149
|
+
throw new Error(`failed to copy ${direction} sandbox: ${errorMessage(err)}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export type SandboxDeskRole = "artifacts" | "envelope" | "checkout" | "grant" | "work";
|
|
2
|
+
export type SandboxDeskBinding = {
|
|
3
|
+
role: SandboxDeskRole;
|
|
4
|
+
hostPath: string;
|
|
5
|
+
containerPath: string;
|
|
6
|
+
shape: "directory" | "file" | "mirror";
|
|
7
|
+
copyIn: boolean;
|
|
8
|
+
copyOut: boolean;
|
|
9
|
+
optional: boolean;
|
|
10
|
+
};
|
|
11
|
+
export type SandboxDesk = {
|
|
12
|
+
bindings: readonly SandboxDeskBinding[];
|
|
13
|
+
};
|
|
14
|
+
export declare function buildSandboxDesk(input: {
|
|
15
|
+
attemptDir: string;
|
|
16
|
+
checkoutRoot?: string;
|
|
17
|
+
grantedPaths?: readonly string[];
|
|
18
|
+
}): SandboxDesk;
|
|
19
|
+
export declare function populateSandboxDesk(params: {
|
|
20
|
+
containerName: string;
|
|
21
|
+
dockerBin?: string;
|
|
22
|
+
desk: SandboxDesk;
|
|
23
|
+
}): Promise<void>;
|
|
24
|
+
export declare function syncSandboxDesk(params: {
|
|
25
|
+
containerName: string;
|
|
26
|
+
dockerBin?: string;
|
|
27
|
+
desk: SandboxDesk;
|
|
28
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { access, mkdir, rename, rm, stat } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { copyFromSandboxContainer, copyToSandboxContainer, execInSandboxContainer, SANDBOX_MOUNT_PATH, } from "./sandboxContainer.js";
|
|
5
|
+
import { shellQuote } from "./sandboxFiles.js";
|
|
6
|
+
export function buildSandboxDesk(input) {
|
|
7
|
+
const attemptDir = input.attemptDir;
|
|
8
|
+
const bindings = [
|
|
9
|
+
{
|
|
10
|
+
role: "artifacts",
|
|
11
|
+
hostPath: path.join(attemptDir, "artifacts"),
|
|
12
|
+
containerPath: `${SANDBOX_MOUNT_PATH}/artifacts`,
|
|
13
|
+
shape: "directory",
|
|
14
|
+
copyIn: true,
|
|
15
|
+
copyOut: false,
|
|
16
|
+
optional: true,
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
role: "envelope",
|
|
20
|
+
hostPath: path.join(attemptDir, "envelope.json"),
|
|
21
|
+
containerPath: `${SANDBOX_MOUNT_PATH}/envelope.json`,
|
|
22
|
+
shape: "file",
|
|
23
|
+
copyIn: true,
|
|
24
|
+
copyOut: false,
|
|
25
|
+
optional: true,
|
|
26
|
+
},
|
|
27
|
+
];
|
|
28
|
+
const checkout = input.checkoutRoot?.trim();
|
|
29
|
+
if (checkout) {
|
|
30
|
+
bindings.push({
|
|
31
|
+
role: "checkout",
|
|
32
|
+
hostPath: checkout,
|
|
33
|
+
containerPath: `${SANDBOX_MOUNT_PATH}/checkout`,
|
|
34
|
+
shape: "directory",
|
|
35
|
+
copyIn: true,
|
|
36
|
+
copyOut: true,
|
|
37
|
+
optional: false,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
const grants = input.grantedPaths ?? [];
|
|
41
|
+
for (let index = 0; index < grants.length; index += 1) {
|
|
42
|
+
const hostPath = grants[index];
|
|
43
|
+
bindings.push({
|
|
44
|
+
role: "grant",
|
|
45
|
+
hostPath,
|
|
46
|
+
containerPath: `${SANDBOX_MOUNT_PATH}/grants/${grantSegment(index, hostPath)}`,
|
|
47
|
+
shape: "mirror",
|
|
48
|
+
copyIn: true,
|
|
49
|
+
copyOut: true,
|
|
50
|
+
optional: false,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
bindings.push({
|
|
54
|
+
role: "work",
|
|
55
|
+
hostPath: path.join(attemptDir, "work"),
|
|
56
|
+
containerPath: `${SANDBOX_MOUNT_PATH}/work`,
|
|
57
|
+
shape: "directory",
|
|
58
|
+
copyIn: false,
|
|
59
|
+
copyOut: true,
|
|
60
|
+
optional: true,
|
|
61
|
+
});
|
|
62
|
+
return { bindings };
|
|
63
|
+
}
|
|
64
|
+
function grantSegment(index, hostPath) {
|
|
65
|
+
const base = path.basename(hostPath).replace(/[^A-Za-z0-9_.-]/g, "_");
|
|
66
|
+
const safe = base.length > 0 && base !== "." && base !== ".." ? base : "path";
|
|
67
|
+
return `${index}-${safe}`;
|
|
68
|
+
}
|
|
69
|
+
async function pathExists(target) {
|
|
70
|
+
try {
|
|
71
|
+
await access(target);
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
export async function populateSandboxDesk(params) {
|
|
79
|
+
const dirs = new Set([
|
|
80
|
+
`${SANDBOX_MOUNT_PATH}/artifacts`,
|
|
81
|
+
`${SANDBOX_MOUNT_PATH}/work`,
|
|
82
|
+
`${SANDBOX_MOUNT_PATH}/grants`,
|
|
83
|
+
]);
|
|
84
|
+
for (const binding of params.desk.bindings) {
|
|
85
|
+
if (binding.role === "checkout" || binding.shape === "mirror") {
|
|
86
|
+
dirs.add(binding.containerPath);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const made = await execInSandboxContainer({
|
|
90
|
+
containerName: params.containerName,
|
|
91
|
+
dockerBin: params.dockerBin,
|
|
92
|
+
command: `mkdir -p -- ${[...dirs].map(shellQuote).join(" ")}`,
|
|
93
|
+
});
|
|
94
|
+
if (made.exitCode !== 0) {
|
|
95
|
+
throw new Error(`failed to prepare sandbox desk: ${made.stderr.trim() || made.stdout.trim() || `exit ${made.exitCode}`}`);
|
|
96
|
+
}
|
|
97
|
+
for (const binding of params.desk.bindings) {
|
|
98
|
+
if (!binding.copyIn)
|
|
99
|
+
continue;
|
|
100
|
+
if (!(await pathExists(binding.hostPath))) {
|
|
101
|
+
if (binding.optional)
|
|
102
|
+
continue;
|
|
103
|
+
throw new Error(`sandbox desk source missing: ${binding.hostPath}`);
|
|
104
|
+
}
|
|
105
|
+
await copyBindingIn(params, binding);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
export async function syncSandboxDesk(params) {
|
|
109
|
+
for (const binding of params.desk.bindings) {
|
|
110
|
+
if (!binding.copyOut)
|
|
111
|
+
continue;
|
|
112
|
+
await copyBindingOut(params, binding);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
async function copyBindingIn(params, binding) {
|
|
116
|
+
if (binding.shape === "file") {
|
|
117
|
+
await copyToSandboxContainer({
|
|
118
|
+
containerName: params.containerName,
|
|
119
|
+
dockerBin: params.dockerBin,
|
|
120
|
+
hostPath: binding.hostPath,
|
|
121
|
+
containerPath: binding.containerPath,
|
|
122
|
+
});
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (binding.shape === "directory") {
|
|
126
|
+
await copyToSandboxContainer({
|
|
127
|
+
containerName: params.containerName,
|
|
128
|
+
dockerBin: params.dockerBin,
|
|
129
|
+
hostPath: binding.hostPath,
|
|
130
|
+
containerPath: binding.containerPath,
|
|
131
|
+
contents: true,
|
|
132
|
+
});
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
const info = await stat(binding.hostPath);
|
|
136
|
+
if (info.isDirectory()) {
|
|
137
|
+
await copyToSandboxContainer({
|
|
138
|
+
containerName: params.containerName,
|
|
139
|
+
dockerBin: params.dockerBin,
|
|
140
|
+
hostPath: binding.hostPath,
|
|
141
|
+
containerPath: binding.containerPath,
|
|
142
|
+
contents: true,
|
|
143
|
+
});
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
await copyToSandboxContainer({
|
|
147
|
+
containerName: params.containerName,
|
|
148
|
+
dockerBin: params.dockerBin,
|
|
149
|
+
hostPath: binding.hostPath,
|
|
150
|
+
containerPath: path.posix.join(binding.containerPath, path.basename(binding.hostPath)),
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
async function copyBindingOut(params, binding) {
|
|
154
|
+
if (binding.role === "checkout" || binding.role === "work") {
|
|
155
|
+
await mirrorDirectoryFromContainer(params, binding.containerPath, binding.hostPath);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
if (binding.shape === "directory") {
|
|
159
|
+
await mkdir(binding.hostPath, { recursive: true });
|
|
160
|
+
await copyFromSandboxContainer({
|
|
161
|
+
containerName: params.containerName,
|
|
162
|
+
dockerBin: params.dockerBin,
|
|
163
|
+
containerPath: binding.containerPath,
|
|
164
|
+
hostPath: binding.hostPath,
|
|
165
|
+
contents: true,
|
|
166
|
+
});
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
const info = await stat(binding.hostPath);
|
|
170
|
+
if (info.isDirectory()) {
|
|
171
|
+
await mkdir(binding.hostPath, { recursive: true });
|
|
172
|
+
await copyFromSandboxContainer({
|
|
173
|
+
containerName: params.containerName,
|
|
174
|
+
dockerBin: params.dockerBin,
|
|
175
|
+
containerPath: binding.containerPath,
|
|
176
|
+
hostPath: binding.hostPath,
|
|
177
|
+
contents: true,
|
|
178
|
+
});
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
await mkdir(path.dirname(binding.hostPath), { recursive: true });
|
|
182
|
+
await copyFromSandboxContainer({
|
|
183
|
+
containerName: params.containerName,
|
|
184
|
+
dockerBin: params.dockerBin,
|
|
185
|
+
containerPath: path.posix.join(binding.containerPath, path.basename(binding.hostPath)),
|
|
186
|
+
hostPath: binding.hostPath,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
async function mirrorDirectoryFromContainer(params, containerPath, hostPath) {
|
|
190
|
+
let probe;
|
|
191
|
+
try {
|
|
192
|
+
probe = await execInSandboxContainer({
|
|
193
|
+
containerName: params.containerName,
|
|
194
|
+
dockerBin: params.dockerBin,
|
|
195
|
+
command: `test -d ${shellQuote(containerPath)}`,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
catch (err) {
|
|
199
|
+
throw new Error(`sandbox desk directory unavailable, not replacing ${hostPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
200
|
+
}
|
|
201
|
+
if (probe.exitCode !== 0) {
|
|
202
|
+
throw new Error(`sandbox desk directory missing, not replacing ${hostPath}`);
|
|
203
|
+
}
|
|
204
|
+
await mkdir(path.dirname(hostPath), { recursive: true });
|
|
205
|
+
const staging = path.join(path.dirname(hostPath), `.${path.basename(hostPath)}.sf-sync-${randomBytes(4).toString("hex")}`);
|
|
206
|
+
await mkdir(staging);
|
|
207
|
+
try {
|
|
208
|
+
await copyFromSandboxContainer({
|
|
209
|
+
containerName: params.containerName,
|
|
210
|
+
dockerBin: params.dockerBin,
|
|
211
|
+
containerPath,
|
|
212
|
+
hostPath: staging,
|
|
213
|
+
contents: true,
|
|
214
|
+
});
|
|
215
|
+
await replaceDirectory(hostPath, staging);
|
|
216
|
+
}
|
|
217
|
+
catch (err) {
|
|
218
|
+
await rm(staging, { recursive: true, force: true });
|
|
219
|
+
throw err;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
async function replaceDirectory(hostPath, staging) {
|
|
223
|
+
const backup = path.join(path.dirname(hostPath), `.${path.basename(hostPath)}.sf-sync-bak-${randomBytes(4).toString("hex")}`);
|
|
224
|
+
let hostMoved = false;
|
|
225
|
+
try {
|
|
226
|
+
if (await pathExists(hostPath)) {
|
|
227
|
+
await rename(hostPath, backup);
|
|
228
|
+
hostMoved = true;
|
|
229
|
+
}
|
|
230
|
+
await rename(staging, hostPath);
|
|
231
|
+
hostMoved = false;
|
|
232
|
+
}
|
|
233
|
+
catch (err) {
|
|
234
|
+
if (hostMoved && !(await pathExists(hostPath)) && (await pathExists(backup))) {
|
|
235
|
+
await rename(backup, hostPath);
|
|
236
|
+
}
|
|
237
|
+
throw err;
|
|
238
|
+
}
|
|
239
|
+
if (await pathExists(backup)) {
|
|
240
|
+
await rm(backup, { recursive: true, force: true });
|
|
241
|
+
}
|
|
242
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type SandboxExecResult } from "./sandboxContainer.js";
|
|
2
|
+
export declare class SandboxPathError extends Error {
|
|
3
|
+
constructor(message: string);
|
|
4
|
+
}
|
|
5
|
+
export declare function shellQuote(value: string): string;
|
|
6
|
+
/** Resolve a tool path onto the sandbox mount. Host absolutes and `..` escapes are rejected. */
|
|
7
|
+
export declare function confineSandboxPath(inputPath: string): string;
|
|
8
|
+
export type SandboxFileTarget = {
|
|
9
|
+
containerName: string;
|
|
10
|
+
dockerBin?: string;
|
|
11
|
+
};
|
|
12
|
+
export declare function readSandboxFile(target: SandboxFileTarget, inputPath: string): Promise<string>;
|
|
13
|
+
export declare function writeSandboxFile(target: SandboxFileTarget, inputPath: string, content: string): Promise<void>;
|
|
14
|
+
export declare function editSandboxFile(target: SandboxFileTarget, inputPath: string, edits: readonly {
|
|
15
|
+
oldText: string;
|
|
16
|
+
newText: string;
|
|
17
|
+
}[]): Promise<void>;
|
|
18
|
+
export declare function sliceSandboxLines(text: string, offset?: number, limit?: number): string;
|
|
19
|
+
export declare function formatSandboxBashResult(result: SandboxExecResult): string;
|
|
20
|
+
export declare function runSandboxBash(target: SandboxFileTarget, command: string, timeoutMs?: number): Promise<string>;
|