@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.
Files changed (106) hide show
  1. package/dist/pushpals-cli.js +277 -12
  2. package/package.json +1 -1
  3. package/runtime/sandbox/apps/workerpals/.python-version +1 -0
  4. package/runtime/sandbox/apps/workerpals/Dockerfile.sandbox +71 -0
  5. package/runtime/sandbox/apps/workerpals/package.json +25 -0
  6. package/runtime/sandbox/apps/workerpals/pyproject.toml +8 -0
  7. package/runtime/sandbox/apps/workerpals/src/backends/backend_config.ts +111 -0
  8. package/runtime/sandbox/apps/workerpals/src/backends/miniswe/miniswe_executor.py +2029 -0
  9. package/runtime/sandbox/apps/workerpals/src/backends/miniswe_backend.ts +48 -0
  10. package/runtime/sandbox/apps/workerpals/src/backends/openai_codex/openai_codex_executor.py +1259 -0
  11. package/runtime/sandbox/apps/workerpals/src/backends/openai_codex/test_openai_codex_runtime_config.py +110 -0
  12. package/runtime/sandbox/apps/workerpals/src/backends/openai_codex_backend.ts +67 -0
  13. package/runtime/sandbox/apps/workerpals/src/backends/openhands/openhands_executor.py +563 -0
  14. package/runtime/sandbox/apps/workerpals/src/backends/openhands_backend.ts +161 -0
  15. package/runtime/sandbox/apps/workerpals/src/backends/openhands_task_execute.ts +536 -0
  16. package/runtime/sandbox/apps/workerpals/src/backends/shared/executor_base.py +746 -0
  17. package/runtime/sandbox/apps/workerpals/src/backends/shared/test_settings_resolver.py +60 -0
  18. package/runtime/sandbox/apps/workerpals/src/backends/task_execute_registry.ts +21 -0
  19. package/runtime/sandbox/apps/workerpals/src/backends/types.ts +52 -0
  20. package/runtime/sandbox/apps/workerpals/src/common/execution_utils.ts +149 -0
  21. package/runtime/sandbox/apps/workerpals/src/common/executor_backend.ts +15 -0
  22. package/runtime/sandbox/apps/workerpals/src/common/generic_python_executor.ts +210 -0
  23. package/runtime/sandbox/apps/workerpals/src/common/logger.ts +65 -0
  24. package/runtime/sandbox/apps/workerpals/src/common/types.ts +9 -0
  25. package/runtime/sandbox/apps/workerpals/src/common/worktree_cleanup.ts +66 -0
  26. package/runtime/sandbox/apps/workerpals/src/context_manager.ts +45 -0
  27. package/runtime/sandbox/apps/workerpals/src/docker_executor.ts +1842 -0
  28. package/runtime/sandbox/apps/workerpals/src/execute_job.ts +3063 -0
  29. package/runtime/sandbox/apps/workerpals/src/job_runner.ts +194 -0
  30. package/runtime/sandbox/apps/workerpals/src/shell_manager.ts +210 -0
  31. package/runtime/sandbox/apps/workerpals/src/timeout_policy.ts +24 -0
  32. package/runtime/sandbox/apps/workerpals/src/workerpals_main.ts +1436 -0
  33. package/runtime/sandbox/apps/workerpals/tsconfig.json +15 -0
  34. package/runtime/sandbox/apps/workerpals/uv.lock +2014 -0
  35. package/runtime/sandbox/bun.lock +2591 -0
  36. package/runtime/sandbox/configs/backend.toml +79 -0
  37. package/runtime/sandbox/configs/default.toml +260 -0
  38. package/runtime/sandbox/configs/dev.toml +2 -0
  39. package/runtime/sandbox/configs/local.example.toml +129 -0
  40. package/runtime/sandbox/package.json +65 -0
  41. package/runtime/sandbox/packages/protocol/README.md +168 -0
  42. package/runtime/sandbox/packages/protocol/package.json +37 -0
  43. package/runtime/sandbox/packages/protocol/scripts/copy-schemas.js +17 -0
  44. package/runtime/sandbox/packages/protocol/src/a2a/README.md +52 -0
  45. package/runtime/sandbox/packages/protocol/src/a2a/mapping.ts +55 -0
  46. package/runtime/sandbox/packages/protocol/src/index.browser.ts +25 -0
  47. package/runtime/sandbox/packages/protocol/src/index.ts +25 -0
  48. package/runtime/sandbox/packages/protocol/src/schemas/approvals.schema.json +6 -0
  49. package/runtime/sandbox/packages/protocol/src/schemas/envelope.schema.json +96 -0
  50. package/runtime/sandbox/packages/protocol/src/schemas/events.schema.json +679 -0
  51. package/runtime/sandbox/packages/protocol/src/schemas/http.schema.json +50 -0
  52. package/runtime/sandbox/packages/protocol/src/types.ts +267 -0
  53. package/runtime/sandbox/packages/protocol/src/validate.browser.ts +154 -0
  54. package/runtime/sandbox/packages/protocol/src/validate.ts +233 -0
  55. package/runtime/sandbox/packages/protocol/src/version.ts +1 -0
  56. package/runtime/sandbox/packages/protocol/tsconfig.json +20 -0
  57. package/runtime/sandbox/packages/shared/package.json +19 -0
  58. package/runtime/sandbox/packages/shared/src/autonomy_policy.ts +400 -0
  59. package/runtime/sandbox/packages/shared/src/client_preflight.ts +297 -0
  60. package/runtime/sandbox/packages/shared/src/communication.ts +313 -0
  61. package/runtime/sandbox/packages/shared/src/config.ts +2201 -0
  62. package/runtime/sandbox/packages/shared/src/config_template_parity.ts +70 -0
  63. package/runtime/sandbox/packages/shared/src/git_backend.ts +205 -0
  64. package/runtime/sandbox/packages/shared/src/index.ts +100 -0
  65. package/runtime/sandbox/packages/shared/src/local_network.ts +101 -0
  66. package/runtime/sandbox/packages/shared/src/localbuddy_runtime.ts +329 -0
  67. package/runtime/sandbox/packages/shared/src/prompts.ts +64 -0
  68. package/runtime/sandbox/packages/shared/src/repo.ts +134 -0
  69. package/runtime/sandbox/packages/shared/src/session_event_visibility.ts +25 -0
  70. package/runtime/sandbox/packages/shared/src/vision.ts +247 -0
  71. package/runtime/sandbox/packages/shared/tsconfig.json +16 -0
  72. package/runtime/sandbox/prompts/workerpals/codex_quality_critic_instruction_prompt.md +14 -0
  73. package/runtime/sandbox/prompts/workerpals/commit_message_prompt.md +36 -0
  74. package/runtime/sandbox/prompts/workerpals/commit_message_user_prompt.md +7 -0
  75. package/runtime/sandbox/prompts/workerpals/miniswe_broker_system_prompt.md +33 -0
  76. package/runtime/sandbox/prompts/workerpals/miniswe_broker_task_prompt.md +5 -0
  77. package/runtime/sandbox/prompts/workerpals/miniswe_completion_requirement.md +1 -0
  78. package/runtime/sandbox/prompts/workerpals/miniswe_context_compaction_retry_prompt.md +1 -0
  79. package/runtime/sandbox/prompts/workerpals/miniswe_explicit_targets_block.md +2 -0
  80. package/runtime/sandbox/prompts/workerpals/miniswe_recovery_guidance_base.md +4 -0
  81. package/runtime/sandbox/prompts/workerpals/miniswe_recovery_guidance_blocker_line.md +1 -0
  82. package/runtime/sandbox/prompts/workerpals/miniswe_strict_tool_use_guidance.md +6 -0
  83. package/runtime/sandbox/prompts/workerpals/miniswe_supplemental_guidance_section.md +2 -0
  84. package/runtime/sandbox/prompts/workerpals/miniswe_timeout_note.md +1 -0
  85. package/runtime/sandbox/prompts/workerpals/miniswe_toolcall_retry_guidance.md +1 -0
  86. package/runtime/sandbox/prompts/workerpals/openai_codex_default_system_prompt.md +4 -0
  87. package/runtime/sandbox/prompts/workerpals/openai_codex_instruction_wrapper.md +5 -0
  88. package/runtime/sandbox/prompts/workerpals/openai_codex_runtime_policy_appendix.md +5 -0
  89. package/runtime/sandbox/prompts/workerpals/openai_codex_supplemental_guidance_section.md +2 -0
  90. package/runtime/sandbox/prompts/workerpals/openai_codex_task_execute_system_prompt.md +12 -0
  91. package/runtime/sandbox/prompts/workerpals/openhands_minimal_security_policy.j2 +8 -0
  92. package/runtime/sandbox/prompts/workerpals/openhands_minimal_system_prompt.j2 +20 -0
  93. package/runtime/sandbox/prompts/workerpals/openhands_strict_tool_use_message.md +1 -0
  94. package/runtime/sandbox/prompts/workerpals/openhands_supplemental_guidance_message.md +2 -0
  95. package/runtime/sandbox/prompts/workerpals/openhands_task_execute_fallback_system_prompt.md +1 -0
  96. package/runtime/sandbox/prompts/workerpals/openhands_task_execute_system_prompt.md +21 -0
  97. package/runtime/sandbox/prompts/workerpals/openhands_task_user_prompt.md +6 -0
  98. package/runtime/sandbox/prompts/workerpals/openhands_timeout_note.md +1 -0
  99. package/runtime/sandbox/prompts/workerpals/pr_description.md +42 -0
  100. package/runtime/sandbox/prompts/workerpals/task_quality_critic_system_prompt.md +9 -0
  101. package/runtime/sandbox/prompts/workerpals/task_quality_critic_user_prompt.md +17 -0
  102. package/runtime/sandbox/prompts/workerpals/workerpals_system_prompt.md +115 -0
  103. package/runtime/sandbox/protocol/schemas/approvals.schema.json +6 -0
  104. package/runtime/sandbox/protocol/schemas/envelope.schema.json +96 -0
  105. package/runtime/sandbox/protocol/schemas/events.schema.json +679 -0
  106. package/runtime/sandbox/protocol/schemas/http.schema.json +50 -0
@@ -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.startsWith("packages/protocol/src/schemas/") ? join2(runtimeRoot, "protocol", "schemas", pathValue.slice("packages/protocol/src/schemas/".length)) : join2(runtimeRoot, 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pushpalsdev/cli",
3
- "version": "1.0.18",
3
+ "version": "1.0.19",
4
4
  "description": "PushPals terminal CLI for LocalBuddy -> RemoteBuddy orchestration",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -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,8 @@
1
+ [project]
2
+ name = "workerpals"
3
+ version = "0.1.0"
4
+ description = "Add your description here"
5
+ requires-python = ">=3.12"
6
+ dependencies = [
7
+ "mini-swe-agent>=2.1.0",
8
+ ]
@@ -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
+ }