@pushpalsdev/cli 1.0.18 → 1.0.19
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/dist/pushpals-cli.js +277 -12
- package/package.json +1 -1
- package/runtime/sandbox/apps/workerpals/.python-version +1 -0
- package/runtime/sandbox/apps/workerpals/Dockerfile.sandbox +71 -0
- package/runtime/sandbox/apps/workerpals/package.json +25 -0
- package/runtime/sandbox/apps/workerpals/pyproject.toml +8 -0
- package/runtime/sandbox/apps/workerpals/src/backends/backend_config.ts +111 -0
- package/runtime/sandbox/apps/workerpals/src/backends/miniswe/miniswe_executor.py +2029 -0
- package/runtime/sandbox/apps/workerpals/src/backends/miniswe_backend.ts +48 -0
- package/runtime/sandbox/apps/workerpals/src/backends/openai_codex/openai_codex_executor.py +1259 -0
- package/runtime/sandbox/apps/workerpals/src/backends/openai_codex/test_openai_codex_runtime_config.py +110 -0
- package/runtime/sandbox/apps/workerpals/src/backends/openai_codex_backend.ts +67 -0
- package/runtime/sandbox/apps/workerpals/src/backends/openhands/openhands_executor.py +563 -0
- package/runtime/sandbox/apps/workerpals/src/backends/openhands_backend.ts +161 -0
- package/runtime/sandbox/apps/workerpals/src/backends/openhands_task_execute.ts +536 -0
- package/runtime/sandbox/apps/workerpals/src/backends/shared/executor_base.py +746 -0
- package/runtime/sandbox/apps/workerpals/src/backends/shared/test_settings_resolver.py +60 -0
- package/runtime/sandbox/apps/workerpals/src/backends/task_execute_registry.ts +21 -0
- package/runtime/sandbox/apps/workerpals/src/backends/types.ts +52 -0
- package/runtime/sandbox/apps/workerpals/src/common/execution_utils.ts +149 -0
- package/runtime/sandbox/apps/workerpals/src/common/executor_backend.ts +15 -0
- package/runtime/sandbox/apps/workerpals/src/common/generic_python_executor.ts +210 -0
- package/runtime/sandbox/apps/workerpals/src/common/logger.ts +65 -0
- package/runtime/sandbox/apps/workerpals/src/common/types.ts +9 -0
- package/runtime/sandbox/apps/workerpals/src/common/worktree_cleanup.ts +66 -0
- package/runtime/sandbox/apps/workerpals/src/context_manager.ts +45 -0
- package/runtime/sandbox/apps/workerpals/src/docker_executor.ts +1842 -0
- package/runtime/sandbox/apps/workerpals/src/execute_job.ts +3063 -0
- package/runtime/sandbox/apps/workerpals/src/job_runner.ts +194 -0
- package/runtime/sandbox/apps/workerpals/src/shell_manager.ts +210 -0
- package/runtime/sandbox/apps/workerpals/src/timeout_policy.ts +24 -0
- package/runtime/sandbox/apps/workerpals/src/workerpals_main.ts +1436 -0
- package/runtime/sandbox/apps/workerpals/tsconfig.json +15 -0
- package/runtime/sandbox/apps/workerpals/uv.lock +2014 -0
- package/runtime/sandbox/bun.lock +2591 -0
- package/runtime/sandbox/configs/backend.toml +79 -0
- package/runtime/sandbox/configs/default.toml +260 -0
- package/runtime/sandbox/configs/dev.toml +2 -0
- package/runtime/sandbox/configs/local.example.toml +129 -0
- package/runtime/sandbox/package.json +65 -0
- package/runtime/sandbox/packages/protocol/README.md +168 -0
- package/runtime/sandbox/packages/protocol/package.json +37 -0
- package/runtime/sandbox/packages/protocol/scripts/copy-schemas.js +17 -0
- package/runtime/sandbox/packages/protocol/src/a2a/README.md +52 -0
- package/runtime/sandbox/packages/protocol/src/a2a/mapping.ts +55 -0
- package/runtime/sandbox/packages/protocol/src/index.browser.ts +25 -0
- package/runtime/sandbox/packages/protocol/src/index.ts +25 -0
- package/runtime/sandbox/packages/protocol/src/schemas/approvals.schema.json +6 -0
- package/runtime/sandbox/packages/protocol/src/schemas/envelope.schema.json +96 -0
- package/runtime/sandbox/packages/protocol/src/schemas/events.schema.json +679 -0
- package/runtime/sandbox/packages/protocol/src/schemas/http.schema.json +50 -0
- package/runtime/sandbox/packages/protocol/src/types.ts +267 -0
- package/runtime/sandbox/packages/protocol/src/validate.browser.ts +154 -0
- package/runtime/sandbox/packages/protocol/src/validate.ts +233 -0
- package/runtime/sandbox/packages/protocol/src/version.ts +1 -0
- package/runtime/sandbox/packages/protocol/tsconfig.json +20 -0
- package/runtime/sandbox/packages/shared/package.json +19 -0
- package/runtime/sandbox/packages/shared/src/autonomy_policy.ts +400 -0
- package/runtime/sandbox/packages/shared/src/client_preflight.ts +297 -0
- package/runtime/sandbox/packages/shared/src/communication.ts +313 -0
- package/runtime/sandbox/packages/shared/src/config.ts +2201 -0
- package/runtime/sandbox/packages/shared/src/config_template_parity.ts +70 -0
- package/runtime/sandbox/packages/shared/src/git_backend.ts +205 -0
- package/runtime/sandbox/packages/shared/src/index.ts +100 -0
- package/runtime/sandbox/packages/shared/src/local_network.ts +101 -0
- package/runtime/sandbox/packages/shared/src/localbuddy_runtime.ts +329 -0
- package/runtime/sandbox/packages/shared/src/prompts.ts +64 -0
- package/runtime/sandbox/packages/shared/src/repo.ts +134 -0
- package/runtime/sandbox/packages/shared/src/session_event_visibility.ts +25 -0
- package/runtime/sandbox/packages/shared/src/vision.ts +247 -0
- package/runtime/sandbox/packages/shared/tsconfig.json +16 -0
- package/runtime/sandbox/prompts/workerpals/codex_quality_critic_instruction_prompt.md +14 -0
- package/runtime/sandbox/prompts/workerpals/commit_message_prompt.md +36 -0
- package/runtime/sandbox/prompts/workerpals/commit_message_user_prompt.md +7 -0
- package/runtime/sandbox/prompts/workerpals/miniswe_broker_system_prompt.md +33 -0
- package/runtime/sandbox/prompts/workerpals/miniswe_broker_task_prompt.md +5 -0
- package/runtime/sandbox/prompts/workerpals/miniswe_completion_requirement.md +1 -0
- package/runtime/sandbox/prompts/workerpals/miniswe_context_compaction_retry_prompt.md +1 -0
- package/runtime/sandbox/prompts/workerpals/miniswe_explicit_targets_block.md +2 -0
- package/runtime/sandbox/prompts/workerpals/miniswe_recovery_guidance_base.md +4 -0
- package/runtime/sandbox/prompts/workerpals/miniswe_recovery_guidance_blocker_line.md +1 -0
- package/runtime/sandbox/prompts/workerpals/miniswe_strict_tool_use_guidance.md +6 -0
- package/runtime/sandbox/prompts/workerpals/miniswe_supplemental_guidance_section.md +2 -0
- package/runtime/sandbox/prompts/workerpals/miniswe_timeout_note.md +1 -0
- package/runtime/sandbox/prompts/workerpals/miniswe_toolcall_retry_guidance.md +1 -0
- package/runtime/sandbox/prompts/workerpals/openai_codex_default_system_prompt.md +4 -0
- package/runtime/sandbox/prompts/workerpals/openai_codex_instruction_wrapper.md +5 -0
- package/runtime/sandbox/prompts/workerpals/openai_codex_runtime_policy_appendix.md +5 -0
- package/runtime/sandbox/prompts/workerpals/openai_codex_supplemental_guidance_section.md +2 -0
- package/runtime/sandbox/prompts/workerpals/openai_codex_task_execute_system_prompt.md +12 -0
- package/runtime/sandbox/prompts/workerpals/openhands_minimal_security_policy.j2 +8 -0
- package/runtime/sandbox/prompts/workerpals/openhands_minimal_system_prompt.j2 +20 -0
- package/runtime/sandbox/prompts/workerpals/openhands_strict_tool_use_message.md +1 -0
- package/runtime/sandbox/prompts/workerpals/openhands_supplemental_guidance_message.md +2 -0
- package/runtime/sandbox/prompts/workerpals/openhands_task_execute_fallback_system_prompt.md +1 -0
- package/runtime/sandbox/prompts/workerpals/openhands_task_execute_system_prompt.md +21 -0
- package/runtime/sandbox/prompts/workerpals/openhands_task_user_prompt.md +6 -0
- package/runtime/sandbox/prompts/workerpals/openhands_timeout_note.md +1 -0
- package/runtime/sandbox/prompts/workerpals/pr_description.md +42 -0
- package/runtime/sandbox/prompts/workerpals/task_quality_critic_system_prompt.md +9 -0
- package/runtime/sandbox/prompts/workerpals/task_quality_critic_user_prompt.md +17 -0
- package/runtime/sandbox/prompts/workerpals/workerpals_system_prompt.md +115 -0
- package/runtime/sandbox/protocol/schemas/approvals.schema.json +6 -0
- package/runtime/sandbox/protocol/schemas/envelope.schema.json +96 -0
- package/runtime/sandbox/protocol/schemas/events.schema.json +679 -0
- package/runtime/sandbox/protocol/schemas/http.schema.json +50 -0
package/dist/pushpals-cli.js
CHANGED
|
@@ -1472,6 +1472,130 @@ function buildRuntimeAssetSource(root, protocolSchemasDir) {
|
|
|
1472
1472
|
protocolSchemasDir
|
|
1473
1473
|
};
|
|
1474
1474
|
}
|
|
1475
|
+
function buildWorkerpalSandboxPaths(runtimeRoot) {
|
|
1476
|
+
const root = join2(runtimeRoot, "sandbox");
|
|
1477
|
+
return {
|
|
1478
|
+
root,
|
|
1479
|
+
dockerfilePath: join2(root, "apps", "workerpals", "Dockerfile.sandbox"),
|
|
1480
|
+
packageJsonPath: join2(root, "package.json"),
|
|
1481
|
+
workerpalsDir: join2(root, "apps", "workerpals"),
|
|
1482
|
+
sharedDir: join2(root, "packages", "shared"),
|
|
1483
|
+
protocolDir: join2(root, "packages", "protocol"),
|
|
1484
|
+
configsDir: join2(root, "configs"),
|
|
1485
|
+
workerpalsPromptsDir: join2(root, "prompts", "workerpals"),
|
|
1486
|
+
protocolSchemasDir: join2(root, "protocol", "schemas")
|
|
1487
|
+
};
|
|
1488
|
+
}
|
|
1489
|
+
function normalizeGitTrackedPath(pathValue) {
|
|
1490
|
+
return String(pathValue ?? "").trim().replace(/\\/g, "/").replace(/^\.\/+/, "").replace(/\/+$/, "");
|
|
1491
|
+
}
|
|
1492
|
+
function listTrackedRepoFilesForPath(repoRoot, sourcePath) {
|
|
1493
|
+
const normalizedSource = normalizeGitTrackedPath(sourcePath);
|
|
1494
|
+
if (!normalizedSource)
|
|
1495
|
+
return [];
|
|
1496
|
+
const proc = Bun.spawnSync(["git", "ls-files", "-z", "--", normalizedSource], {
|
|
1497
|
+
cwd: repoRoot,
|
|
1498
|
+
stdout: "pipe",
|
|
1499
|
+
stderr: "pipe",
|
|
1500
|
+
env: {
|
|
1501
|
+
...process.env,
|
|
1502
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
1503
|
+
GCM_INTERACTIVE: "Never"
|
|
1504
|
+
}
|
|
1505
|
+
});
|
|
1506
|
+
if (proc.exitCode !== 0) {
|
|
1507
|
+
const stderr = Buffer.from(proc.stderr ?? []).toString("utf8").trim();
|
|
1508
|
+
throw new Error(`git ls-files failed for ${normalizedSource}${stderr ? `: ${stderr}` : ""}`);
|
|
1509
|
+
}
|
|
1510
|
+
return Buffer.from(proc.stdout ?? []).toString("utf8").split("\x00").map(normalizeGitTrackedPath).filter(Boolean);
|
|
1511
|
+
}
|
|
1512
|
+
function copyTrackedRepoPath(repoRoot, sourcePath, destinationPath, force = true) {
|
|
1513
|
+
const normalizedSource = normalizeGitTrackedPath(sourcePath);
|
|
1514
|
+
if (!normalizedSource) {
|
|
1515
|
+
throw new Error("sourcePath is required");
|
|
1516
|
+
}
|
|
1517
|
+
const absoluteSource = resolve4(repoRoot, normalizedSource);
|
|
1518
|
+
if (!existsSync4(absoluteSource)) {
|
|
1519
|
+
throw new Error(`tracked repo source is missing: ${absoluteSource}`);
|
|
1520
|
+
}
|
|
1521
|
+
const trackedFiles = listTrackedRepoFilesForPath(repoRoot, normalizedSource);
|
|
1522
|
+
const sourceStat = lstatSync(absoluteSource);
|
|
1523
|
+
if (!sourceStat.isDirectory()) {
|
|
1524
|
+
if (!trackedFiles.includes(normalizedSource)) {
|
|
1525
|
+
throw new Error(`tracked repo file is not tracked by git: ${normalizedSource}`);
|
|
1526
|
+
}
|
|
1527
|
+
mkdirSync(dirname(destinationPath), { recursive: true });
|
|
1528
|
+
cpSync(absoluteSource, destinationPath, {
|
|
1529
|
+
recursive: false,
|
|
1530
|
+
force,
|
|
1531
|
+
errorOnExist: false
|
|
1532
|
+
});
|
|
1533
|
+
return;
|
|
1534
|
+
}
|
|
1535
|
+
if (trackedFiles.length === 0) {
|
|
1536
|
+
throw new Error(`tracked repo directory has no tracked files: ${normalizedSource}`);
|
|
1537
|
+
}
|
|
1538
|
+
for (const trackedFile of trackedFiles) {
|
|
1539
|
+
const relativePath = trackedFile === normalizedSource ? basename(trackedFile) : trackedFile.slice(normalizedSource.length + 1);
|
|
1540
|
+
const sourceFile = resolve4(repoRoot, trackedFile);
|
|
1541
|
+
const targetFile = join2(destinationPath, relativePath);
|
|
1542
|
+
mkdirSync(dirname(targetFile), { recursive: true });
|
|
1543
|
+
cpSync(sourceFile, targetFile, {
|
|
1544
|
+
recursive: false,
|
|
1545
|
+
force,
|
|
1546
|
+
errorOnExist: false
|
|
1547
|
+
});
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
function isCompleteWorkerpalSandboxRoot(root) {
|
|
1551
|
+
return existsSync4(join2(root, "package.json")) && existsSync4(join2(root, "apps", "workerpals", "Dockerfile.sandbox")) && existsSync4(join2(root, "packages", "shared", "package.json")) && existsSync4(join2(root, "packages", "protocol", "package.json")) && existsSync4(join2(root, "configs", "default.toml")) && existsSync4(join2(root, "prompts", "workerpals")) && existsSync4(join2(root, "protocol", "schemas", "envelope.schema.json")) && existsSync4(join2(root, "protocol", "schemas", "events.schema.json"));
|
|
1552
|
+
}
|
|
1553
|
+
function populateWorkerpalSandboxRuntimeAssets(runtimeRoot, force) {
|
|
1554
|
+
const sandbox = buildWorkerpalSandboxPaths(runtimeRoot);
|
|
1555
|
+
cpSync(join2(runtimeRoot, "configs"), sandbox.configsDir, {
|
|
1556
|
+
recursive: true,
|
|
1557
|
+
force,
|
|
1558
|
+
errorOnExist: false
|
|
1559
|
+
});
|
|
1560
|
+
cpSync(join2(runtimeRoot, "prompts", "workerpals"), sandbox.workerpalsPromptsDir, {
|
|
1561
|
+
recursive: true,
|
|
1562
|
+
force,
|
|
1563
|
+
errorOnExist: false
|
|
1564
|
+
});
|
|
1565
|
+
cpSync(join2(runtimeRoot, "protocol", "schemas"), sandbox.protocolSchemasDir, {
|
|
1566
|
+
recursive: true,
|
|
1567
|
+
force,
|
|
1568
|
+
errorOnExist: false
|
|
1569
|
+
});
|
|
1570
|
+
}
|
|
1571
|
+
function copySourceCheckoutWorkerpalSandboxBuildContext(sourceRoot, runtimeRoot, force) {
|
|
1572
|
+
const sandbox = buildWorkerpalSandboxPaths(runtimeRoot);
|
|
1573
|
+
const copyPairs = [
|
|
1574
|
+
["package.json", sandbox.packageJsonPath],
|
|
1575
|
+
["apps/workerpals", sandbox.workerpalsDir],
|
|
1576
|
+
["packages/shared", sandbox.sharedDir],
|
|
1577
|
+
["packages/protocol", sandbox.protocolDir]
|
|
1578
|
+
];
|
|
1579
|
+
for (const [fromPath, toPath] of copyPairs) {
|
|
1580
|
+
copyTrackedRepoPath(sourceRoot, fromPath, toPath, force);
|
|
1581
|
+
}
|
|
1582
|
+
if (existsSync4(join2(sourceRoot, "bun.lock"))) {
|
|
1583
|
+
copyTrackedRepoPath(sourceRoot, "bun.lock", join2(sandbox.root, "bun.lock"), force);
|
|
1584
|
+
}
|
|
1585
|
+
populateWorkerpalSandboxRuntimeAssets(runtimeRoot, force);
|
|
1586
|
+
}
|
|
1587
|
+
function copyWorkerpalSandboxBuildContext(source, runtimeRoot, force) {
|
|
1588
|
+
const packagedSandboxRoot = join2(source.root, "sandbox");
|
|
1589
|
+
if (isCompleteWorkerpalSandboxRoot(packagedSandboxRoot)) {
|
|
1590
|
+
cpSync(packagedSandboxRoot, join2(runtimeRoot, "sandbox"), {
|
|
1591
|
+
recursive: true,
|
|
1592
|
+
force,
|
|
1593
|
+
errorOnExist: false
|
|
1594
|
+
});
|
|
1595
|
+
return;
|
|
1596
|
+
}
|
|
1597
|
+
copySourceCheckoutWorkerpalSandboxBuildContext(source.root, runtimeRoot, force);
|
|
1598
|
+
}
|
|
1475
1599
|
function isCompleteRuntimeAssetSource(source) {
|
|
1476
1600
|
return existsSync4(source.envExamplePath) && existsSync4(source.visionExamplePath) && existsSync4(join2(source.configsDir, "default.toml")) && existsSync4(source.promptsDir) && existsSync4(join2(source.protocolSchemasDir, "envelope.schema.json")) && existsSync4(join2(source.protocolSchemasDir, "events.schema.json"));
|
|
1477
1601
|
}
|
|
@@ -1658,6 +1782,7 @@ function copyRuntimeAssetBundle(source, runtimeRoot, force) {
|
|
|
1658
1782
|
force,
|
|
1659
1783
|
errorOnExist: false
|
|
1660
1784
|
});
|
|
1785
|
+
copyWorkerpalSandboxBuildContext(source, runtimeRoot, force);
|
|
1661
1786
|
}
|
|
1662
1787
|
function copyBundledRuntimeAssets(runtimeRoot, force = true) {
|
|
1663
1788
|
const bundledSource = resolveBundledRuntimeAssetSource();
|
|
@@ -1693,7 +1818,7 @@ async function downloadRuntimeAssetsFromSourceTag(runtimeRoot, tag) {
|
|
|
1693
1818
|
throw new Error(`Failed to fetch runtime source tree for ${tag} (HTTP ${treeResponse.status})`);
|
|
1694
1819
|
}
|
|
1695
1820
|
const treePayload = await treeResponse.json();
|
|
1696
|
-
const paths = (treePayload.tree ?? []).filter((entry) => entry.type === "blob" && typeof entry.path === "string").map((entry) => String(entry.path)).filter((pathValue) => pathValue === ".env.example" || pathValue === "vision.example.md" || pathValue.startsWith("configs/") || pathValue.startsWith("prompts/") || pathValue.startsWith("packages/protocol/src/schemas/"));
|
|
1821
|
+
const paths = (treePayload.tree ?? []).filter((entry) => entry.type === "blob" && typeof entry.path === "string").map((entry) => String(entry.path)).filter((pathValue) => pathValue === ".env.example" || pathValue === "vision.example.md" || pathValue === "package.json" || pathValue === "bun.lock" || pathValue.startsWith("configs/") || pathValue.startsWith("prompts/workerpals/") || pathValue.startsWith("prompts/") || pathValue.startsWith("apps/workerpals/") || pathValue.startsWith("packages/shared/") || pathValue.startsWith("packages/protocol/") || pathValue.startsWith("packages/protocol/src/schemas/"));
|
|
1697
1822
|
if (paths.length === 0) {
|
|
1698
1823
|
throw new Error(`Runtime source tree for ${tag} did not include prompts/config assets`);
|
|
1699
1824
|
}
|
|
@@ -1701,10 +1826,16 @@ async function downloadRuntimeAssetsFromSourceTag(runtimeRoot, tag) {
|
|
|
1701
1826
|
for (const pathValue of sorted) {
|
|
1702
1827
|
const rawUrl = `https://raw.githubusercontent.com/${GITHUB_OWNER}/${GITHUB_REPO}/${encodeURIComponent(tag)}/${pathValue}`;
|
|
1703
1828
|
const body = await fetchTextFromUrl(rawUrl, 20000);
|
|
1704
|
-
const outPath = pathValue.
|
|
1829
|
+
const outPath = pathValue === "package.json" || pathValue === "bun.lock" ? join2(runtimeRoot, "sandbox", pathValue) : pathValue.startsWith("apps/workerpals/") || pathValue.startsWith("packages/shared/") || pathValue.startsWith("packages/protocol/") ? join2(runtimeRoot, "sandbox", pathValue) : join2(runtimeRoot, pathValue);
|
|
1705
1830
|
mkdirSync(dirname(outPath), { recursive: true });
|
|
1706
1831
|
writeFileSync(outPath, body, "utf8");
|
|
1832
|
+
if (pathValue.startsWith("packages/protocol/src/schemas/")) {
|
|
1833
|
+
const runtimeSchemaPath = join2(runtimeRoot, "protocol", "schemas", pathValue.slice("packages/protocol/src/schemas/".length));
|
|
1834
|
+
mkdirSync(dirname(runtimeSchemaPath), { recursive: true });
|
|
1835
|
+
writeFileSync(runtimeSchemaPath, body, "utf8");
|
|
1836
|
+
}
|
|
1707
1837
|
}
|
|
1838
|
+
populateWorkerpalSandboxRuntimeAssets(runtimeRoot, true);
|
|
1708
1839
|
}
|
|
1709
1840
|
async function ensureRuntimeAssets(runtimeRoot, runtimeTag) {
|
|
1710
1841
|
console.log(`[pushpals] Preparing embedded runtime assets for ${runtimeTag}...`);
|
|
@@ -1712,12 +1843,12 @@ async function ensureRuntimeAssets(runtimeRoot, runtimeTag) {
|
|
|
1712
1843
|
const currentTag = existsSync4(markerPath) ? readFileSync4(markerPath, "utf8").trim() : "";
|
|
1713
1844
|
const protocolSchemasDir = join2(runtimeRoot, "protocol", "schemas");
|
|
1714
1845
|
const hasProtocolSchemas = existsSync4(join2(protocolSchemasDir, "envelope.schema.json")) && existsSync4(join2(protocolSchemasDir, "events.schema.json"));
|
|
1715
|
-
const hasAssets = existsSync4(join2(runtimeRoot, ".env.example")) && existsSync4(join2(runtimeRoot, "vision.example.md")) && existsSync4(join2(runtimeRoot, "configs", "default.toml")) && existsSync4(join2(runtimeRoot, "prompts")) && hasProtocolSchemas;
|
|
1846
|
+
const hasAssets = existsSync4(join2(runtimeRoot, ".env.example")) && existsSync4(join2(runtimeRoot, "vision.example.md")) && existsSync4(join2(runtimeRoot, "configs", "default.toml")) && existsSync4(join2(runtimeRoot, "prompts")) && hasProtocolSchemas && isCompleteWorkerpalSandboxRoot(join2(runtimeRoot, "sandbox"));
|
|
1716
1847
|
if (!hasAssets || currentTag !== runtimeTag) {
|
|
1717
1848
|
console.log(`[pushpals] Embedded runtime assets ${hasAssets ? "are stale" : "are missing"}; refreshing bundle...`);
|
|
1718
1849
|
copyBundledRuntimeAssets(runtimeRoot);
|
|
1719
1850
|
const hasProtocolSchemasAfterCopy = existsSync4(join2(protocolSchemasDir, "envelope.schema.json")) && existsSync4(join2(protocolSchemasDir, "events.schema.json"));
|
|
1720
|
-
const hasAssetsAfterCopy = existsSync4(join2(runtimeRoot, ".env.example")) && existsSync4(join2(runtimeRoot, "vision.example.md")) && existsSync4(join2(runtimeRoot, "configs", "default.toml")) && existsSync4(join2(runtimeRoot, "prompts")) && hasProtocolSchemasAfterCopy;
|
|
1851
|
+
const hasAssetsAfterCopy = existsSync4(join2(runtimeRoot, ".env.example")) && existsSync4(join2(runtimeRoot, "vision.example.md")) && existsSync4(join2(runtimeRoot, "configs", "default.toml")) && existsSync4(join2(runtimeRoot, "prompts")) && hasProtocolSchemasAfterCopy && isCompleteWorkerpalSandboxRoot(join2(runtimeRoot, "sandbox"));
|
|
1721
1852
|
if (!hasAssetsAfterCopy) {
|
|
1722
1853
|
console.log("[pushpals] Bundled runtime assets are incomplete; falling back to release source downloads...");
|
|
1723
1854
|
await downloadRuntimeAssetsFromSourceTag(runtimeRoot, runtimeTag);
|
|
@@ -1787,7 +1918,9 @@ function buildEmbeddedRuntimeEnv(baseEnv, opts) {
|
|
|
1787
1918
|
PUSHPALS_PROJECT_ROOT_OVERRIDE: opts.repoRoot,
|
|
1788
1919
|
...useRuntimeConfig ? {
|
|
1789
1920
|
PUSHPALS_CONFIG_DIR_OVERRIDE: join2(opts.runtimeRoot, "configs"),
|
|
1790
|
-
PUSHPALS_PROMPTS_ROOT_OVERRIDE: opts.runtimeRoot
|
|
1921
|
+
PUSHPALS_PROMPTS_ROOT_OVERRIDE: opts.runtimeRoot,
|
|
1922
|
+
PUSHPALS_WORKERPALS_SANDBOX_ROOT: join2(opts.runtimeRoot, "sandbox"),
|
|
1923
|
+
...typeof opts.runtimeTag === "string" && opts.runtimeTag.trim() ? { PUSHPALS_RUNTIME_TAG: opts.runtimeTag.trim() } : {}
|
|
1791
1924
|
} : {
|
|
1792
1925
|
PUSHPALS_PROMPTS_ROOT_OVERRIDE: opts.repoRoot
|
|
1793
1926
|
},
|
|
@@ -2263,6 +2396,116 @@ async function resolveWorkerpalDockerProbe(cwd, env, platform = process.platform
|
|
|
2263
2396
|
detail: failures.join(" | ") || "docker"
|
|
2264
2397
|
};
|
|
2265
2398
|
}
|
|
2399
|
+
var WORKERPAL_SANDBOX_RUNTIME_TAG_LABEL = "pushpals.runtime_tag";
|
|
2400
|
+
var WORKERPAL_SANDBOX_COMPONENT_LABEL = "pushpals.component=workerpals-sandbox";
|
|
2401
|
+
function resolveConfiguredDockerExecutable(env, platform = process.platform) {
|
|
2402
|
+
const configured = String(env.PUSHPALS_DOCKER_BIN_ABSOLUTE ?? env.PUSHPALS_DOCKER_BIN ?? (platform === "win32" ? "docker.exe" : "docker")).trim();
|
|
2403
|
+
return configured || (platform === "win32" ? "docker.exe" : "docker");
|
|
2404
|
+
}
|
|
2405
|
+
async function inspectDockerImageRuntimeTag(dockerExecutable, imageName, cwd, env) {
|
|
2406
|
+
const inspect = await runCommandWithEnv([
|
|
2407
|
+
dockerExecutable,
|
|
2408
|
+
"image",
|
|
2409
|
+
"inspect",
|
|
2410
|
+
"--format",
|
|
2411
|
+
`{{ index .Config.Labels "${WORKERPAL_SANDBOX_RUNTIME_TAG_LABEL}" }}`,
|
|
2412
|
+
imageName
|
|
2413
|
+
], cwd, env);
|
|
2414
|
+
if (!inspect.ok)
|
|
2415
|
+
return "";
|
|
2416
|
+
const value = inspect.stdout.trim();
|
|
2417
|
+
return value === "<no value>" ? "" : value;
|
|
2418
|
+
}
|
|
2419
|
+
async function ensureWorkerpalDockerImageReady(opts) {
|
|
2420
|
+
const runtimeTag = String(opts.runtimeTag ?? "").trim();
|
|
2421
|
+
if (!runtimeTag) {
|
|
2422
|
+
return {
|
|
2423
|
+
ok: false,
|
|
2424
|
+
detail: "embedded runtime tag is required to prepare the WorkerPal sandbox image"
|
|
2425
|
+
};
|
|
2426
|
+
}
|
|
2427
|
+
await (opts.ensureRuntimeAssetsFn ?? ensureRuntimeAssets)(opts.runtimeRoot, runtimeTag);
|
|
2428
|
+
const sandbox = buildWorkerpalSandboxPaths(opts.runtimeRoot);
|
|
2429
|
+
if (!isCompleteWorkerpalSandboxRoot(sandbox.root)) {
|
|
2430
|
+
return {
|
|
2431
|
+
ok: false,
|
|
2432
|
+
detail: `embedded WorkerPal sandbox assets are incomplete at ${sandbox.root}`
|
|
2433
|
+
};
|
|
2434
|
+
}
|
|
2435
|
+
const dockerExecutable = resolveConfiguredDockerExecutable(opts.env, opts.platform ?? process.platform);
|
|
2436
|
+
const inspectImageRuntimeTagFn = opts.inspectImageRuntimeTagFn ?? inspectDockerImageRuntimeTag;
|
|
2437
|
+
const runCommandWithEnvFn = opts.runCommandWithEnvFn ?? runCommandWithEnv;
|
|
2438
|
+
const existingRuntimeTag = await inspectImageRuntimeTagFn(dockerExecutable, opts.dockerImage, sandbox.root, opts.env);
|
|
2439
|
+
if (existingRuntimeTag === runtimeTag) {
|
|
2440
|
+
return {
|
|
2441
|
+
ok: true,
|
|
2442
|
+
detail: `WorkerPal sandbox image is ready locally (${opts.dockerImage}, runtimeTag=${runtimeTag})`
|
|
2443
|
+
};
|
|
2444
|
+
}
|
|
2445
|
+
console.log(existingRuntimeTag ? `[pushpals] WorkerPal sandbox image ${opts.dockerImage} is stale (runtimeTag=${existingRuntimeTag}); rebuilding locally...` : `[pushpals] WorkerPal sandbox image ${opts.dockerImage} is missing; building locally...`);
|
|
2446
|
+
const build = await runCommandWithEnvFn([
|
|
2447
|
+
dockerExecutable,
|
|
2448
|
+
"build",
|
|
2449
|
+
"-f",
|
|
2450
|
+
"apps/workerpals/Dockerfile.sandbox",
|
|
2451
|
+
"--label",
|
|
2452
|
+
`${WORKERPAL_SANDBOX_RUNTIME_TAG_LABEL}=${runtimeTag}`,
|
|
2453
|
+
"--label",
|
|
2454
|
+
WORKERPAL_SANDBOX_COMPONENT_LABEL,
|
|
2455
|
+
"-t",
|
|
2456
|
+
opts.dockerImage,
|
|
2457
|
+
"."
|
|
2458
|
+
], sandbox.root, opts.env);
|
|
2459
|
+
if (!build.ok) {
|
|
2460
|
+
const detail = build.stderr || build.stdout || `docker build exited ${build.exitCode}`;
|
|
2461
|
+
return {
|
|
2462
|
+
ok: false,
|
|
2463
|
+
detail: `failed to build local WorkerPal sandbox image ${opts.dockerImage}: ${detail}`
|
|
2464
|
+
};
|
|
2465
|
+
}
|
|
2466
|
+
return {
|
|
2467
|
+
ok: true,
|
|
2468
|
+
detail: `built local WorkerPal sandbox image ${opts.dockerImage} for runtimeTag=${runtimeTag}`
|
|
2469
|
+
};
|
|
2470
|
+
}
|
|
2471
|
+
async function prepareEmbeddedWorkerpalDockerImageIfNeeded(opts) {
|
|
2472
|
+
if (!opts.preparedRuntime.preflightUsesEmbeddedRuntime) {
|
|
2473
|
+
return {
|
|
2474
|
+
status: "skipped",
|
|
2475
|
+
detail: "repo is using source-checkout runtime assets",
|
|
2476
|
+
runtimeTag: ""
|
|
2477
|
+
};
|
|
2478
|
+
}
|
|
2479
|
+
if (!opts.config.remotebuddy.autoSpawnWorkerpals || !opts.config.remotebuddy.workerpalDocker || !opts.config.remotebuddy.workerpalRequireDocker) {
|
|
2480
|
+
return {
|
|
2481
|
+
status: "skipped",
|
|
2482
|
+
detail: "embedded docker-backed WorkerPal auto-spawn is not required",
|
|
2483
|
+
runtimeTag: ""
|
|
2484
|
+
};
|
|
2485
|
+
}
|
|
2486
|
+
if (opts.dockerPrecheck.status === "failed") {
|
|
2487
|
+
return {
|
|
2488
|
+
status: "failed",
|
|
2489
|
+
detail: opts.dockerPrecheck.detail,
|
|
2490
|
+
runtimeTag: ""
|
|
2491
|
+
};
|
|
2492
|
+
}
|
|
2493
|
+
const runtimeTag = opts.preparedRuntime.runtimeTag || String(opts.runtimeTagHint ?? "").trim() || await (opts.resolveRuntimeReleaseTagFn ?? resolveRuntimeReleaseTag)(opts.runtimeTagHint);
|
|
2494
|
+
if (!runtimeTag) {
|
|
2495
|
+
return {
|
|
2496
|
+
status: "failed",
|
|
2497
|
+
detail: "embedded runtime tag is required to prepare the WorkerPal sandbox image",
|
|
2498
|
+
runtimeTag: ""
|
|
2499
|
+
};
|
|
2500
|
+
}
|
|
2501
|
+
const ensureResult = await (opts.ensureWorkerpalDockerImageReadyFn ?? ensureWorkerpalDockerImageReady)({
|
|
2502
|
+
runtimeRoot: opts.preparedRuntime.runtimeRoot,
|
|
2503
|
+
runtimeTag,
|
|
2504
|
+
dockerImage: opts.config.remotebuddy.workerpalImage ?? opts.config.workerpals.dockerImage,
|
|
2505
|
+
env: opts.dockerPrecheck.env
|
|
2506
|
+
});
|
|
2507
|
+
return ensureResult.ok ? { status: "ok", detail: ensureResult.detail, runtimeTag } : { status: "failed", detail: ensureResult.detail, runtimeTag };
|
|
2508
|
+
}
|
|
2266
2509
|
async function precheckSourceControlManagerGitAvailability(opts) {
|
|
2267
2510
|
const platform = opts.platform ?? process.platform;
|
|
2268
2511
|
const env = buildEmbeddedRuntimeEnv(opts.baseEnv ?? process.env, {
|
|
@@ -2824,7 +3067,8 @@ async function autoStartRuntimeServices(opts) {
|
|
|
2824
3067
|
repoRoot: opts.repoRoot,
|
|
2825
3068
|
runtimeRoot,
|
|
2826
3069
|
useRuntimeConfig: opts.preparedRuntime.preflightUsesEmbeddedRuntime,
|
|
2827
|
-
sessionId: opts.sessionId
|
|
3070
|
+
sessionId: opts.sessionId,
|
|
3071
|
+
runtimeTag
|
|
2828
3072
|
});
|
|
2829
3073
|
runtimeEnv.PUSHPALS_WORKERPALS_BIN = runtimeBinaries.workerpals;
|
|
2830
3074
|
const preconfiguredRuntimeGitBinary = runtimeEnv.PUSHPALS_GIT_BIN_ABSOLUTE ?? runtimeEnv.PUSHPALS_GIT_BIN;
|
|
@@ -3642,6 +3886,7 @@ async function main() {
|
|
|
3642
3886
|
};
|
|
3643
3887
|
let autoStartedServices = [];
|
|
3644
3888
|
let pushpalsLogPath;
|
|
3889
|
+
let resolvedRuntimeTagForAutoStart = preparedRuntime.runtimeTag || parsed.runtimeTag || "";
|
|
3645
3890
|
const stopAutoStartedServices = () => {
|
|
3646
3891
|
if (autoStartedServices.length === 0)
|
|
3647
3892
|
return;
|
|
@@ -3650,17 +3895,32 @@ async function main() {
|
|
|
3650
3895
|
};
|
|
3651
3896
|
let serverHealthy = await probeServer(serverUrl);
|
|
3652
3897
|
const serverWasAlreadyHealthy = serverHealthy;
|
|
3898
|
+
if (!serverHealthy && workerpalDockerPrecheck.status === "failed") {
|
|
3899
|
+
console.error(`[pushpals] Precheck failed: Docker-backed WorkerPal auto-spawn is required but Docker is unavailable (${workerpalDockerPrecheck.detail}).`);
|
|
3900
|
+
console.error("[pushpals] Precheck failed: start Docker Desktop or the Docker daemon, then retry pushpals.");
|
|
3901
|
+
process.exit(1);
|
|
3902
|
+
}
|
|
3903
|
+
if (workerpalDockerPrecheck.status !== "failed") {
|
|
3904
|
+
const workerpalImagePrecheck = await prepareEmbeddedWorkerpalDockerImageIfNeeded({
|
|
3905
|
+
preparedRuntime,
|
|
3906
|
+
config,
|
|
3907
|
+
dockerPrecheck: workerpalDockerPrecheck,
|
|
3908
|
+
runtimeTagHint: resolvedRuntimeTagForAutoStart || parsed.runtimeTag
|
|
3909
|
+
});
|
|
3910
|
+
if (workerpalImagePrecheck.status === "failed") {
|
|
3911
|
+
console.error(`[pushpals] Precheck failed: ${workerpalImagePrecheck.detail}.`);
|
|
3912
|
+
process.exit(1);
|
|
3913
|
+
}
|
|
3914
|
+
if (workerpalImagePrecheck.runtimeTag) {
|
|
3915
|
+
resolvedRuntimeTagForAutoStart = workerpalImagePrecheck.runtimeTag;
|
|
3916
|
+
}
|
|
3917
|
+
}
|
|
3653
3918
|
let remoteBuddyConsumerHealth = {
|
|
3654
3919
|
ok: false,
|
|
3655
3920
|
detail: `No connected RemoteBuddy session consumer found for session ${sessionId}`
|
|
3656
3921
|
};
|
|
3657
3922
|
if (!serverHealthy) {
|
|
3658
3923
|
if (!parsed.noAutoStart) {
|
|
3659
|
-
if (workerpalDockerPrecheck.status === "failed") {
|
|
3660
|
-
console.error(`[pushpals] Precheck failed: Docker-backed WorkerPal auto-spawn is required but Docker is unavailable (${workerpalDockerPrecheck.detail}).`);
|
|
3661
|
-
console.error("[pushpals] Precheck failed: start Docker Desktop or the Docker daemon, then retry pushpals.");
|
|
3662
|
-
process.exit(1);
|
|
3663
|
-
}
|
|
3664
3924
|
try {
|
|
3665
3925
|
const startedRuntime = await autoStartRuntimeServices({
|
|
3666
3926
|
repoRoot,
|
|
@@ -3670,7 +3930,7 @@ async function main() {
|
|
|
3670
3930
|
sourceControlManagerPort: config.sourceControlManager.port,
|
|
3671
3931
|
sourceControlManagerRemote: config.sourceControlManager.remote,
|
|
3672
3932
|
preparedRuntime,
|
|
3673
|
-
requestedRuntimeTag: parsed.runtimeTag,
|
|
3933
|
+
requestedRuntimeTag: resolvedRuntimeTagForAutoStart || parsed.runtimeTag,
|
|
3674
3934
|
startLocalBuddy: resolveCliLocalBuddyAutostart(parsed.runtimeOnly, Boolean(config.localbuddy.enabled)),
|
|
3675
3935
|
baseEnv: workerpalDockerPrecheck.env
|
|
3676
3936
|
});
|
|
@@ -3929,6 +4189,7 @@ export {
|
|
|
3929
4189
|
resolveCliLocalBuddyAutostart,
|
|
3930
4190
|
resolveBundledRuntimeAssetSource,
|
|
3931
4191
|
resolveBundledMonitoringHubRoot,
|
|
4192
|
+
prepareEmbeddedWorkerpalDockerImageIfNeeded,
|
|
3932
4193
|
prepareCliRuntime,
|
|
3933
4194
|
precheckWorkerpalDockerAvailability,
|
|
3934
4195
|
precheckSourceControlManagerGitAvailability,
|
|
@@ -3941,7 +4202,11 @@ export {
|
|
|
3941
4202
|
formatSessionEventLine,
|
|
3942
4203
|
extractRemoteBuddySessionConsumerHealth,
|
|
3943
4204
|
extractRemoteBuddyAutonomousEngineState,
|
|
4205
|
+
ensureWorkerpalDockerImageReady,
|
|
4206
|
+
downloadRuntimeAssetsFromSourceTag,
|
|
4207
|
+
copyTrackedRepoPath,
|
|
3944
4208
|
bundledMonitoringHubNeedsRefresh,
|
|
4209
|
+
buildWorkerpalSandboxPaths,
|
|
3945
4210
|
buildServiceStopCommand,
|
|
3946
4211
|
buildRuntimeServiceLogPaths,
|
|
3947
4212
|
buildOpenMonitoringHubCommand,
|
package/package.json
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.12
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# PushPals Worker Sandbox Dockerfile
|
|
2
|
+
# This image provides an isolated environment for job execution
|
|
3
|
+
# The entire monorepo is available in the container for job execution
|
|
4
|
+
|
|
5
|
+
FROM oven/bun:1-debian AS base
|
|
6
|
+
|
|
7
|
+
# Install git and other essential tools
|
|
8
|
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
9
|
+
git \
|
|
10
|
+
curl \
|
|
11
|
+
ca-certificates \
|
|
12
|
+
openssh-client \
|
|
13
|
+
grep \
|
|
14
|
+
jq \
|
|
15
|
+
ripgrep \
|
|
16
|
+
fd-find \
|
|
17
|
+
less \
|
|
18
|
+
unzip \
|
|
19
|
+
zip \
|
|
20
|
+
procps \
|
|
21
|
+
dnsutils \
|
|
22
|
+
iputils-ping \
|
|
23
|
+
python3 \
|
|
24
|
+
python3-venv \
|
|
25
|
+
python3-pip \
|
|
26
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
27
|
+
|
|
28
|
+
# Worker runtime in one shared venv (uv default layout).
|
|
29
|
+
# Keep the venv at /workspace/.venv so all backends use the same Python runtime.
|
|
30
|
+
RUN mkdir -p /workspace \
|
|
31
|
+
&& python3 -m venv /workspace/.venv \
|
|
32
|
+
&& /workspace/.venv/bin/pip install --no-cache-dir --upgrade pip \
|
|
33
|
+
&& /workspace/.venv/bin/pip install --no-cache-dir \
|
|
34
|
+
openhands-sdk \
|
|
35
|
+
openhands-agent-server \
|
|
36
|
+
openhands-workspace \
|
|
37
|
+
openhands-tools \
|
|
38
|
+
mini-swe-agent \
|
|
39
|
+
playwright \
|
|
40
|
+
&& bun add -g @openai/codex \
|
|
41
|
+
&& /workspace/.venv/bin/playwright install --with-deps chromium
|
|
42
|
+
|
|
43
|
+
ENV PATH="/workspace/.venv/bin:/root/.bun/bin:${PATH}"
|
|
44
|
+
ENV WORKERPALS_OPENHANDS_PYTHON="/workspace/.venv/bin/python"
|
|
45
|
+
ENV WORKERPALS_OPENHANDS_WORKSPACE_PYTHON="/workspace/.venv/bin/python"
|
|
46
|
+
ENV WORKERPALS_MINISWE_PYTHON="/workspace/.venv/bin/python"
|
|
47
|
+
ENV PUSHPALS_OPENAI_CODEX_PYTHON="/workspace/.venv/bin/python"
|
|
48
|
+
|
|
49
|
+
# Configure git for the worker
|
|
50
|
+
RUN git config --global user.name "PushPals Worker" \
|
|
51
|
+
&& git config --global user.email "worker@pushpals.local" \
|
|
52
|
+
&& git config --global safe.directory '*' \
|
|
53
|
+
&& git config --global core.autocrlf input
|
|
54
|
+
|
|
55
|
+
# Set up the full monorepo structure
|
|
56
|
+
WORKDIR /workspace
|
|
57
|
+
|
|
58
|
+
# Copy entire repository (build context should be repo root)
|
|
59
|
+
COPY . .
|
|
60
|
+
|
|
61
|
+
# Install workspace dependencies (including devDeps needed for protocol build)
|
|
62
|
+
RUN bun install
|
|
63
|
+
|
|
64
|
+
# Build protocol package (needed by worker)
|
|
65
|
+
RUN cd packages/protocol && bun run build
|
|
66
|
+
|
|
67
|
+
# Set working directory for job execution
|
|
68
|
+
WORKDIR /workspace
|
|
69
|
+
|
|
70
|
+
# Default entrypoint runs the job runner with base64-encoded job spec
|
|
71
|
+
ENTRYPOINT ["bun", "run", "/workspace/apps/workerpals/src/job_runner.ts"]
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "workerpals",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "bun --watch --no-clear-screen src/workerpals_main.ts",
|
|
7
|
+
"start": "bun run src/workerpals_main.ts",
|
|
8
|
+
"build:sandbox": "docker build -f Dockerfile.sandbox -t pushpals-worker-sandbox:latest ../..",
|
|
9
|
+
"docker:build": "docker build -f Dockerfile.sandbox -t pushpals-worker-sandbox:latest ../..",
|
|
10
|
+
"docker:build:local": "docker build -f Dockerfile.sandbox -t pushpals-worker-sandbox:local ../..",
|
|
11
|
+
"docker:run": "docker run --rm -it pushpals-worker-sandbox:latest",
|
|
12
|
+
"dev:docker": "bun run src/workerpals_main.ts --docker",
|
|
13
|
+
"workerpals:docker": "bun run src/workerpals_main.ts --docker",
|
|
14
|
+
"workerpals:docker:local": "bun run src/workerpals_main.ts --docker --docker-image pushpals-worker-sandbox:local"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"protocol": "workspace:*",
|
|
18
|
+
"shared": "workspace:*"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"typescript": "~5.9.2",
|
|
22
|
+
"@types/bun": "latest"
|
|
23
|
+
},
|
|
24
|
+
"private": true
|
|
25
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { readFileSync } from "fs";
|
|
2
|
+
import { resolve } from "path";
|
|
3
|
+
import { loadPushPalsConfig } from "shared";
|
|
4
|
+
import type { ExecutorBackend } from "../common/types.js";
|
|
5
|
+
import { MINISWE_BACKEND } from "./miniswe_backend.js";
|
|
6
|
+
import { OPENAI_CODEX_BACKEND } from "./openai_codex_backend.js";
|
|
7
|
+
import { OPENHANDS_BACKEND } from "./openhands_backend.js";
|
|
8
|
+
import type { BackendTaskExecutor, DockerBackendSpec } from "./types.js";
|
|
9
|
+
import { registerBackendTaskExecutor } from "./task_execute_registry.js";
|
|
10
|
+
|
|
11
|
+
const FALLBACK_DEFAULT_EXECUTOR: ExecutorBackend = "miniswe";
|
|
12
|
+
|
|
13
|
+
interface BackendTomlShape {
|
|
14
|
+
default_backend?: string;
|
|
15
|
+
env?: { shared_passthrough?: unknown };
|
|
16
|
+
backends?: Record<
|
|
17
|
+
string,
|
|
18
|
+
{
|
|
19
|
+
script_segments?: unknown;
|
|
20
|
+
passthrough_env?: unknown;
|
|
21
|
+
python_config_key?: string;
|
|
22
|
+
timeout_config_key?: string;
|
|
23
|
+
}
|
|
24
|
+
>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function toStrings(value: unknown): string[] {
|
|
28
|
+
if (!Array.isArray(value)) return [];
|
|
29
|
+
return value
|
|
30
|
+
.filter((item): item is string => typeof item === "string")
|
|
31
|
+
.map((item) => item.trim())
|
|
32
|
+
.filter(Boolean);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function loadBackendToml(): BackendTomlShape {
|
|
36
|
+
const projectRoot = loadPushPalsConfig().projectRoot;
|
|
37
|
+
const path = resolve(projectRoot, "configs", "backend.toml");
|
|
38
|
+
try {
|
|
39
|
+
const parsed = Bun.TOML.parse(readFileSync(path, "utf-8")) as unknown;
|
|
40
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
41
|
+
return parsed as BackendTomlShape;
|
|
42
|
+
} catch {
|
|
43
|
+
return {};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const config = loadBackendToml();
|
|
48
|
+
const backendEntries = Object.entries(config.backends ?? {});
|
|
49
|
+
|
|
50
|
+
export const BACKEND_EXECUTOR_SCRIPT_SEGMENTS: Record<string, readonly string[]> =
|
|
51
|
+
Object.fromEntries(
|
|
52
|
+
backendEntries.map(([name, spec]) => [name, toStrings(spec?.script_segments)]),
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
export const EXECUTOR_BACKENDS = Object.keys(BACKEND_EXECUTOR_SCRIPT_SEGMENTS) as ExecutorBackend[];
|
|
56
|
+
|
|
57
|
+
export const DEFAULT_EXECUTOR = (
|
|
58
|
+
typeof config.default_backend === "string" &&
|
|
59
|
+
EXECUTOR_BACKENDS.includes(config.default_backend as ExecutorBackend)
|
|
60
|
+
? config.default_backend
|
|
61
|
+
: (EXECUTOR_BACKENDS[0] ?? FALLBACK_DEFAULT_EXECUTOR)
|
|
62
|
+
) as ExecutorBackend;
|
|
63
|
+
|
|
64
|
+
export const SHARED_DOCKER_PASSTHROUGH_ENV = toStrings(config.env?.shared_passthrough);
|
|
65
|
+
|
|
66
|
+
export const BACKEND_DOCKER_PASSTHROUGH_ENV: Record<string, readonly string[]> = Object.fromEntries(
|
|
67
|
+
backendEntries.map(([name, spec]) => [name, toStrings(spec?.passthrough_env)]),
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
export const BACKEND_RUNTIME_CONFIG_KEYS: Record<
|
|
71
|
+
string,
|
|
72
|
+
{ pythonKey: string; timeoutKey: string }
|
|
73
|
+
> = Object.fromEntries(
|
|
74
|
+
backendEntries.map(([name, spec]) => [
|
|
75
|
+
name,
|
|
76
|
+
{
|
|
77
|
+
pythonKey: spec?.python_config_key?.trim() || `${name}Python`,
|
|
78
|
+
timeoutKey: spec?.timeout_config_key?.trim() || `${name}TimeoutMs`,
|
|
79
|
+
},
|
|
80
|
+
]),
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
export const DOCKER_BACKENDS: readonly DockerBackendSpec[] = [
|
|
84
|
+
OPENHANDS_BACKEND,
|
|
85
|
+
MINISWE_BACKEND,
|
|
86
|
+
OPENAI_CODEX_BACKEND,
|
|
87
|
+
];
|
|
88
|
+
|
|
89
|
+
export function getDockerBackendSpec(name: ExecutorBackend): DockerBackendSpec {
|
|
90
|
+
const spec = DOCKER_BACKENDS.find((entry) => entry.name === name);
|
|
91
|
+
if (!spec) {
|
|
92
|
+
throw new Error(`Unknown docker backend: ${name}`);
|
|
93
|
+
}
|
|
94
|
+
return spec;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ---- Auto-register task executors from backend modules ----------------------
|
|
98
|
+
// Each DockerBackendSpec provides a taskExecute hook. Register them so that
|
|
99
|
+
// execute_job.ts can discover executors via the registry without hardcoding
|
|
100
|
+
// backend names.
|
|
101
|
+
|
|
102
|
+
for (const backend of DOCKER_BACKENDS) {
|
|
103
|
+
registerBackendTaskExecutor(backend.name, backend.taskExecute);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function getBackendTaskExecutorFromSpec(
|
|
107
|
+
name: ExecutorBackend,
|
|
108
|
+
): BackendTaskExecutor | undefined {
|
|
109
|
+
const spec = DOCKER_BACKENDS.find((entry) => entry.name === name);
|
|
110
|
+
return spec?.taskExecute ?? undefined;
|
|
111
|
+
}
|