@pushpalsdev/cli 1.0.17 → 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 +542 -23
  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
@@ -25,6 +25,41 @@ import { relative, resolve as resolve2 } from "path";
25
25
  import { existsSync, readFileSync } from "fs";
26
26
  import { join, resolve, isAbsolute } from "path";
27
27
 
28
+ // ../shared/src/autonomy_policy.ts
29
+ var DRIVE_RE = /^[A-Za-z]:\//;
30
+ var SLASH_RE = /\/+/g;
31
+ function normalizeAutonomyComponentArea(value) {
32
+ const normalized = normalizeRepoRelativePath(value);
33
+ if (!normalized)
34
+ return null;
35
+ return normalized;
36
+ }
37
+ function normalizeRepoRelativePath(value) {
38
+ if (typeof value !== "string")
39
+ return null;
40
+ let path = value.trim();
41
+ if (!path)
42
+ return null;
43
+ path = path.normalize("NFC").replace(/\\/g, "/");
44
+ if (path.startsWith("/"))
45
+ return null;
46
+ if (DRIVE_RE.test(path))
47
+ return null;
48
+ path = path.replace(SLASH_RE, "/");
49
+ const out = [];
50
+ for (const rawSegment of path.split("/")) {
51
+ const segment = rawSegment.trim();
52
+ if (!segment || segment === ".")
53
+ continue;
54
+ if (segment === "..")
55
+ return null;
56
+ out.push(segment);
57
+ }
58
+ if (out.length === 0)
59
+ return null;
60
+ return out.join("/");
61
+ }
62
+
28
63
  // ../shared/src/local_network.ts
29
64
  var DEFAULT_LOCAL_LOOPBACK_HOST = "127.0.0.1";
30
65
  function isLoopbackHost(hostname) {
@@ -398,14 +433,26 @@ function loadPushPalsConfig(options = {}) {
398
433
  "tests/unit": 2
399
434
  };
400
435
  const remoteAutonomyDispatchByComponentRaw = asStringNumberRecord(remoteAutonomyNode.max_dispatch_per_hour_by_component);
401
- const remoteAutonomyDispatchByComponent = {
402
- ...remoteAutonomyDispatchByComponentCfg
436
+ const legacyAutonomyComponentAliasMap = new Map(Object.keys(remoteAutonomyDispatchByComponentCfg).flatMap((key) => {
437
+ const direct = normalizeAutonomyComponentArea(key);
438
+ const legacyUnderscore = normalizeAutonomyComponentArea(key.replace(/\//g, "_"));
439
+ const legacyHyphen = normalizeAutonomyComponentArea(key.replace(/\//g, "-"));
440
+ return [direct, legacyUnderscore, legacyHyphen].filter((value) => Boolean(value)).map((value) => [value, key]);
441
+ }));
442
+ const coerceAutonomyComponentConfigKey = (value) => {
443
+ const direct = normalizeAutonomyComponentArea(value);
444
+ const legacyAliasCandidate = normalizeAutonomyComponentArea(value.trim().toLowerCase().replace(/\\/g, "/").replace(/_+/g, "/").replace(/-+/g, "/").replace(/\/+/g, "/"));
445
+ if (legacyAliasCandidate && legacyAutonomyComponentAliasMap.has(legacyAliasCandidate)) {
446
+ return legacyAutonomyComponentAliasMap.get(legacyAliasCandidate) ?? legacyAliasCandidate;
447
+ }
448
+ return direct;
403
449
  };
404
- const normalizeAutonomyComponentKey = (value) => value.trim().toLowerCase().replace(/\\/g, "/").replace(/_+/g, "/").replace(/-+/g, "/").replace(/\/+/g, "/");
405
- const canonicalComponentByNormalized = new Map(Object.keys(remoteAutonomyDispatchByComponentCfg).map((key) => [normalizeAutonomyComponentKey(key), key]));
450
+ const remoteAutonomyDispatchByComponent = Object.fromEntries(Object.entries(remoteAutonomyDispatchByComponentCfg).map(([key, value]) => [
451
+ coerceAutonomyComponentConfigKey(key) ?? key,
452
+ value
453
+ ]));
406
454
  for (const [rawKey, rawValue] of Object.entries(remoteAutonomyDispatchByComponentRaw)) {
407
- const normalized = normalizeAutonomyComponentKey(rawKey);
408
- const canonical = canonicalComponentByNormalized.get(normalized);
455
+ const canonical = coerceAutonomyComponentConfigKey(rawKey);
409
456
  if (!canonical)
410
457
  continue;
411
458
  const parsed = typeof rawValue === "number" ? rawValue : typeof rawValue === "string" ? Number.parseInt(rawValue.trim(), 10) : Number.NaN;
@@ -1369,9 +1416,9 @@ function parsePositiveInt(value, fallback) {
1369
1416
  function jsonHtmlBootstrap(value) {
1370
1417
  return JSON.stringify(value).replace(/</g, "\\u003c");
1371
1418
  }
1372
- async function runGitWithEnv(args, cwd, env) {
1419
+ async function runCommandWithEnv(command, cwd, env) {
1373
1420
  try {
1374
- const proc = Bun.spawn(["git", ...args], {
1421
+ const proc = Bun.spawn(command, {
1375
1422
  cwd,
1376
1423
  env,
1377
1424
  stdout: "pipe",
@@ -1392,6 +1439,9 @@ async function runGitWithEnv(args, cwd, env) {
1392
1439
  };
1393
1440
  }
1394
1441
  }
1442
+ async function runGitWithEnv(args, cwd, env) {
1443
+ return await runCommandWithEnv(["git", ...args], cwd, env);
1444
+ }
1395
1445
  async function runGit(args, cwd) {
1396
1446
  return await runGitWithEnv(args, cwd, {
1397
1447
  ...process.env,
@@ -1422,6 +1472,130 @@ function buildRuntimeAssetSource(root, protocolSchemasDir) {
1422
1472
  protocolSchemasDir
1423
1473
  };
1424
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
+ }
1425
1599
  function isCompleteRuntimeAssetSource(source) {
1426
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"));
1427
1601
  }
@@ -1608,6 +1782,7 @@ function copyRuntimeAssetBundle(source, runtimeRoot, force) {
1608
1782
  force,
1609
1783
  errorOnExist: false
1610
1784
  });
1785
+ copyWorkerpalSandboxBuildContext(source, runtimeRoot, force);
1611
1786
  }
1612
1787
  function copyBundledRuntimeAssets(runtimeRoot, force = true) {
1613
1788
  const bundledSource = resolveBundledRuntimeAssetSource();
@@ -1643,7 +1818,7 @@ async function downloadRuntimeAssetsFromSourceTag(runtimeRoot, tag) {
1643
1818
  throw new Error(`Failed to fetch runtime source tree for ${tag} (HTTP ${treeResponse.status})`);
1644
1819
  }
1645
1820
  const treePayload = await treeResponse.json();
1646
- 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/"));
1647
1822
  if (paths.length === 0) {
1648
1823
  throw new Error(`Runtime source tree for ${tag} did not include prompts/config assets`);
1649
1824
  }
@@ -1651,10 +1826,16 @@ async function downloadRuntimeAssetsFromSourceTag(runtimeRoot, tag) {
1651
1826
  for (const pathValue of sorted) {
1652
1827
  const rawUrl = `https://raw.githubusercontent.com/${GITHUB_OWNER}/${GITHUB_REPO}/${encodeURIComponent(tag)}/${pathValue}`;
1653
1828
  const body = await fetchTextFromUrl(rawUrl, 20000);
1654
- 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);
1655
1830
  mkdirSync(dirname(outPath), { recursive: true });
1656
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
+ }
1657
1837
  }
1838
+ populateWorkerpalSandboxRuntimeAssets(runtimeRoot, true);
1658
1839
  }
1659
1840
  async function ensureRuntimeAssets(runtimeRoot, runtimeTag) {
1660
1841
  console.log(`[pushpals] Preparing embedded runtime assets for ${runtimeTag}...`);
@@ -1662,12 +1843,12 @@ async function ensureRuntimeAssets(runtimeRoot, runtimeTag) {
1662
1843
  const currentTag = existsSync4(markerPath) ? readFileSync4(markerPath, "utf8").trim() : "";
1663
1844
  const protocolSchemasDir = join2(runtimeRoot, "protocol", "schemas");
1664
1845
  const hasProtocolSchemas = existsSync4(join2(protocolSchemasDir, "envelope.schema.json")) && existsSync4(join2(protocolSchemasDir, "events.schema.json"));
1665
- 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"));
1666
1847
  if (!hasAssets || currentTag !== runtimeTag) {
1667
1848
  console.log(`[pushpals] Embedded runtime assets ${hasAssets ? "are stale" : "are missing"}; refreshing bundle...`);
1668
1849
  copyBundledRuntimeAssets(runtimeRoot);
1669
1850
  const hasProtocolSchemasAfterCopy = existsSync4(join2(protocolSchemasDir, "envelope.schema.json")) && existsSync4(join2(protocolSchemasDir, "events.schema.json"));
1670
- 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"));
1671
1852
  if (!hasAssetsAfterCopy) {
1672
1853
  console.log("[pushpals] Bundled runtime assets are incomplete; falling back to release source downloads...");
1673
1854
  await downloadRuntimeAssetsFromSourceTag(runtimeRoot, runtimeTag);
@@ -1737,14 +1918,18 @@ function buildEmbeddedRuntimeEnv(baseEnv, opts) {
1737
1918
  PUSHPALS_PROJECT_ROOT_OVERRIDE: opts.repoRoot,
1738
1919
  ...useRuntimeConfig ? {
1739
1920
  PUSHPALS_CONFIG_DIR_OVERRIDE: join2(opts.runtimeRoot, "configs"),
1740
- 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() } : {}
1741
1924
  } : {
1742
1925
  PUSHPALS_PROMPTS_ROOT_OVERRIDE: opts.repoRoot
1743
1926
  },
1744
1927
  PUSHPALS_PROTOCOL_SCHEMAS_DIR: join2(opts.runtimeRoot, "protocol", "schemas"),
1745
1928
  ...typeof opts.sessionId === "string" && opts.sessionId.trim() ? { PUSHPALS_SESSION_ID: opts.sessionId.trim() } : {},
1746
1929
  ...typeof env.PUSHPALS_GIT_BIN === "string" && env.PUSHPALS_GIT_BIN.trim() ? { PUSHPALS_GIT_BIN: env.PUSHPALS_GIT_BIN.trim() } : {},
1747
- ...typeof env.PUSHPALS_GIT_BIN_ABSOLUTE === "string" && env.PUSHPALS_GIT_BIN_ABSOLUTE.trim() ? { PUSHPALS_GIT_BIN_ABSOLUTE: env.PUSHPALS_GIT_BIN_ABSOLUTE.trim() } : {}
1930
+ ...typeof env.PUSHPALS_GIT_BIN_ABSOLUTE === "string" && env.PUSHPALS_GIT_BIN_ABSOLUTE.trim() ? { PUSHPALS_GIT_BIN_ABSOLUTE: env.PUSHPALS_GIT_BIN_ABSOLUTE.trim() } : {},
1931
+ ...typeof env.PUSHPALS_DOCKER_BIN === "string" && env.PUSHPALS_DOCKER_BIN.trim() ? { PUSHPALS_DOCKER_BIN: env.PUSHPALS_DOCKER_BIN.trim() } : {},
1932
+ ...typeof env.PUSHPALS_DOCKER_BIN_ABSOLUTE === "string" && env.PUSHPALS_DOCKER_BIN_ABSOLUTE.trim() ? { PUSHPALS_DOCKER_BIN_ABSOLUTE: env.PUSHPALS_DOCKER_BIN_ABSOLUTE.trim() } : {}
1748
1933
  };
1749
1934
  }
1750
1935
  function normalizeChildProcessEnv(baseEnv, platform = process.platform) {
@@ -2020,6 +2205,19 @@ function applyResolvedGitBinaryToRuntimeEnv(env, resolvedGitBinary, platform = p
2020
2205
  }
2021
2206
  return env;
2022
2207
  }
2208
+ function applyResolvedDockerBinaryToRuntimeEnv(env, resolvedDockerBinary, platform = process.platform) {
2209
+ const resolvedPath = String(resolvedDockerBinary ?? "").trim();
2210
+ if (!resolvedPath)
2211
+ return env;
2212
+ prependExecutableDirToPath(env, resolvedPath, platform);
2213
+ env.PUSHPALS_DOCKER_BIN = basename(resolvedPath);
2214
+ if (resolvedPath.includes("/") || resolvedPath.includes("\\")) {
2215
+ env.PUSHPALS_DOCKER_BIN_ABSOLUTE = resolvedPath;
2216
+ } else {
2217
+ delete env.PUSHPALS_DOCKER_BIN_ABSOLUTE;
2218
+ }
2219
+ return env;
2220
+ }
2023
2221
  function resolveRuntimeGitExecutableCandidates(env, platform = process.platform) {
2024
2222
  const candidates = [];
2025
2223
  const seen = new Set;
@@ -2039,6 +2237,25 @@ function resolveRuntimeGitExecutableCandidates(env, platform = process.platform)
2039
2237
  pushCandidate("git");
2040
2238
  return candidates;
2041
2239
  }
2240
+ function resolveRuntimeDockerExecutableCandidates(env, platform = process.platform) {
2241
+ const candidates = [];
2242
+ const seen = new Set;
2243
+ const pushCandidate = (value) => {
2244
+ const trimmed = String(value ?? "").trim();
2245
+ if (!trimmed)
2246
+ return;
2247
+ const key = platform === "win32" ? trimmed.toLowerCase() : trimmed;
2248
+ if (seen.has(key))
2249
+ return;
2250
+ seen.add(key);
2251
+ candidates.push(trimmed);
2252
+ };
2253
+ pushCandidate(env.PUSHPALS_DOCKER_BIN ?? "");
2254
+ pushCandidate(env.PUSHPALS_DOCKER_BIN_ABSOLUTE ?? "");
2255
+ pushCandidate(platform === "win32" ? "docker.exe" : "docker");
2256
+ pushCandidate("docker");
2257
+ return candidates;
2258
+ }
2042
2259
  function resolveWindowsShellExecutableCandidatesForEnv(env, platform = process.platform) {
2043
2260
  if (platform !== "win32")
2044
2261
  return [];
@@ -2153,6 +2370,142 @@ async function resolveSourceControlManagerGitProbe(cwd, env, platform = process.
2153
2370
  detail: candidates.join(", ") || "git"
2154
2371
  };
2155
2372
  }
2373
+ async function resolveWorkerpalDockerProbe(cwd, env, platform = process.platform) {
2374
+ const resolvedDockerBinary = await resolveCommandPath(platform === "win32" ? "docker.exe" : "docker", cwd, env);
2375
+ if (resolvedDockerBinary) {
2376
+ prependExecutableDirToPath(env, resolvedDockerBinary, platform);
2377
+ env.PUSHPALS_DOCKER_BIN = basename(resolvedDockerBinary);
2378
+ env.PUSHPALS_DOCKER_BIN_ABSOLUTE = resolvedDockerBinary;
2379
+ }
2380
+ const candidates = resolveRuntimeDockerExecutableCandidates(env, platform);
2381
+ const failures = [];
2382
+ for (const candidate of candidates) {
2383
+ const result = await runCommandWithEnv([candidate, "version", "--format", "{{.Server.Version}}"], cwd, env);
2384
+ if (result.ok) {
2385
+ const version = result.stdout.trim();
2386
+ return {
2387
+ ok: true,
2388
+ detail: version ? `${candidate} (${version})` : candidate
2389
+ };
2390
+ }
2391
+ const detail = result.stderr || result.stdout || `exit ${result.exitCode}`;
2392
+ failures.push(`${candidate}: ${detail}`);
2393
+ }
2394
+ return {
2395
+ ok: false,
2396
+ detail: failures.join(" | ") || "docker"
2397
+ };
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
+ }
2156
2509
  async function precheckSourceControlManagerGitAvailability(opts) {
2157
2510
  const platform = opts.platform ?? process.platform;
2158
2511
  const env = buildEmbeddedRuntimeEnv(opts.baseEnv ?? process.env, {
@@ -2161,8 +2514,9 @@ async function precheckSourceControlManagerGitAvailability(opts) {
2161
2514
  useRuntimeConfig: opts.preflightUsesEmbeddedRuntime,
2162
2515
  sessionId: opts.sessionId
2163
2516
  });
2164
- if (env.PUSHPALS_GIT_BIN) {
2165
- applyResolvedGitBinaryToRuntimeEnv(env, env.PUSHPALS_GIT_BIN, platform);
2517
+ const preconfiguredGitBinary = env.PUSHPALS_GIT_BIN_ABSOLUTE ?? env.PUSHPALS_GIT_BIN;
2518
+ if (preconfiguredGitBinary) {
2519
+ applyResolvedGitBinaryToRuntimeEnv(env, preconfiguredGitBinary, platform);
2166
2520
  }
2167
2521
  const remoteStatus = opts.gitRemoteCheckFn ? await opts.gitRemoteCheckFn(opts.repoRoot, opts.remote, env) : opts.repoHasRemoteFn ? await opts.repoHasRemoteFn(opts.repoRoot, opts.remote) ? { status: "ok", remote: opts.remote } : { status: "missing_remote", remote: opts.remote } : await checkGitRemoteConfigured(opts.repoRoot, opts.remote, env);
2168
2522
  if (remoteStatus.status === "missing_remote") {
@@ -2198,6 +2552,55 @@ async function precheckSourceControlManagerGitAvailability(opts) {
2198
2552
  env
2199
2553
  };
2200
2554
  }
2555
+ async function precheckWorkerpalDockerAvailability(opts) {
2556
+ const env = buildEmbeddedRuntimeEnv(opts.baseEnv ?? process.env, {
2557
+ repoRoot: opts.repoRoot,
2558
+ runtimeRoot: opts.runtimeRoot,
2559
+ useRuntimeConfig: opts.preflightUsesEmbeddedRuntime,
2560
+ sessionId: opts.sessionId
2561
+ });
2562
+ const preconfiguredDockerBinary = env.PUSHPALS_DOCKER_BIN_ABSOLUTE ?? env.PUSHPALS_DOCKER_BIN;
2563
+ if (preconfiguredDockerBinary) {
2564
+ applyResolvedDockerBinaryToRuntimeEnv(env, preconfiguredDockerBinary, opts.platform ?? process.platform);
2565
+ }
2566
+ if (!opts.autoSpawnWorkerpals) {
2567
+ return {
2568
+ status: "skipped",
2569
+ detail: "WorkerPal auto-spawn is disabled",
2570
+ env
2571
+ };
2572
+ }
2573
+ if (!opts.dockerEnabled) {
2574
+ return {
2575
+ status: "skipped",
2576
+ detail: "WorkerPal docker mode is disabled",
2577
+ env
2578
+ };
2579
+ }
2580
+ if (!opts.requireDocker) {
2581
+ return {
2582
+ status: "skipped",
2583
+ detail: "WorkerPal docker mode is optional",
2584
+ env
2585
+ };
2586
+ }
2587
+ const dockerProbe = await (opts.dockerProbeFn ?? resolveWorkerpalDockerProbe)(opts.repoRoot, env, opts.platform ?? process.platform);
2588
+ if (!dockerProbe.ok) {
2589
+ return {
2590
+ status: "failed",
2591
+ detail: dockerProbe.detail,
2592
+ env
2593
+ };
2594
+ }
2595
+ return {
2596
+ status: "ok",
2597
+ detail: dockerProbe.detail,
2598
+ env
2599
+ };
2600
+ }
2601
+ function resolveWorkerpalCapacityTimeoutMs(config) {
2602
+ return Math.max(config.remotebuddy.waitForWorkerpalMs, config.remotebuddy.workerpalStartupTimeoutMs, config.remotebuddy.workerpalDocker ? config.workerpals.dockerAgentStartupTimeoutMs + 15000 : 0, 1e4);
2603
+ }
2201
2604
  async function checkGitRemoteConfigured(repoRoot, remote, env) {
2202
2605
  const normalizedRemote = String(remote ?? "").trim();
2203
2606
  if (!normalizedRemote) {
@@ -2539,6 +2942,42 @@ async function probeSourceControlManager(port) {
2539
2942
  return false;
2540
2943
  }
2541
2944
  }
2945
+ async function fetchWorkerStatusRows(serverUrl, ttlMs) {
2946
+ const payload = await fetchJsonWithTimeout(`${serverUrl}/workers?ttlMs=${Math.max(1000, Math.floor(ttlMs))}`, {}, 1e4);
2947
+ if (!payload?.ok || !Array.isArray(payload.workers)) {
2948
+ return [];
2949
+ }
2950
+ return payload.workers;
2951
+ }
2952
+ async function waitForWorkerpalCapacity(opts) {
2953
+ const deadline = Date.now() + Math.max(1000, opts.timeoutMs);
2954
+ let lastObservedOnline = 0;
2955
+ while (Date.now() < deadline) {
2956
+ const workers = await (opts.fetchWorkersFn ?? fetchWorkerStatusRows)(opts.serverUrl, opts.ttlMs);
2957
+ const onlineWorkers = workers.filter((worker) => Boolean(worker?.isOnline) && String(worker?.status ?? "").trim().toLowerCase() !== "offline");
2958
+ const idleWorkers = onlineWorkers.filter((worker) => Number(worker?.activeJobCount ?? 0) <= 0);
2959
+ if (onlineWorkers.length > 0) {
2960
+ lastObservedOnline = Math.max(lastObservedOnline, onlineWorkers.length);
2961
+ }
2962
+ if (idleWorkers.length > 0) {
2963
+ return {
2964
+ ok: true,
2965
+ detail: `${idleWorkers.length} idle / ${onlineWorkers.length} online`
2966
+ };
2967
+ }
2968
+ await (opts.sleepFn ?? Bun.sleep)(DEFAULT_RUNTIME_BOOT_POLL_MS);
2969
+ }
2970
+ if (lastObservedOnline > 0) {
2971
+ return {
2972
+ ok: false,
2973
+ detail: `${lastObservedOnline} online WorkerPal(s) reported but none became idle within ${Math.max(1000, opts.timeoutMs)}ms`
2974
+ };
2975
+ }
2976
+ return {
2977
+ ok: false,
2978
+ detail: `no online WorkerPal reported within ${Math.max(1000, opts.timeoutMs)}ms`
2979
+ };
2980
+ }
2542
2981
  async function fetchWithTimeout(url, init = {}, timeoutMs = HTTP_TIMEOUT_MS) {
2543
2982
  const controller = new AbortController;
2544
2983
  const timer = setTimeout(() => controller.abort(), timeoutMs);
@@ -2624,15 +3063,21 @@ async function autoStartRuntimeServices(opts) {
2624
3063
  }
2625
3064
  await ensureRuntimeAssets(runtimeRoot, runtimeTag);
2626
3065
  const runtimeBinaries = await ensureRuntimeBinaries(runtimeRoot, runtimeTag);
2627
- const runtimeEnv = buildEmbeddedRuntimeEnv(process.env, {
3066
+ const runtimeEnv = buildEmbeddedRuntimeEnv(opts.baseEnv ?? process.env, {
2628
3067
  repoRoot: opts.repoRoot,
2629
3068
  runtimeRoot,
2630
3069
  useRuntimeConfig: opts.preparedRuntime.preflightUsesEmbeddedRuntime,
2631
- sessionId: opts.sessionId
3070
+ sessionId: opts.sessionId,
3071
+ runtimeTag
2632
3072
  });
2633
3073
  runtimeEnv.PUSHPALS_WORKERPALS_BIN = runtimeBinaries.workerpals;
2634
- if (runtimeEnv.PUSHPALS_GIT_BIN) {
2635
- applyResolvedGitBinaryToRuntimeEnv(runtimeEnv, runtimeEnv.PUSHPALS_GIT_BIN);
3074
+ const preconfiguredRuntimeGitBinary = runtimeEnv.PUSHPALS_GIT_BIN_ABSOLUTE ?? runtimeEnv.PUSHPALS_GIT_BIN;
3075
+ if (preconfiguredRuntimeGitBinary) {
3076
+ applyResolvedGitBinaryToRuntimeEnv(runtimeEnv, preconfiguredRuntimeGitBinary);
3077
+ }
3078
+ const preconfiguredRuntimeDockerBinary = runtimeEnv.PUSHPALS_DOCKER_BIN_ABSOLUTE ?? runtimeEnv.PUSHPALS_DOCKER_BIN;
3079
+ if (preconfiguredRuntimeDockerBinary) {
3080
+ applyResolvedDockerBinaryToRuntimeEnv(runtimeEnv, preconfiguredRuntimeDockerBinary);
2636
3081
  }
2637
3082
  const gitLookupCommand = typeof runtimeEnv.PUSHPALS_GIT_BIN === "string" && runtimeEnv.PUSHPALS_GIT_BIN.trim() ? runtimeEnv.PUSHPALS_GIT_BIN.trim() : "git";
2638
3083
  const resolvedGitBinary = await resolveCommandPath(gitLookupCommand, opts.repoRoot, runtimeEnv);
@@ -2720,6 +3165,24 @@ ${tail}` : ""}`);
2720
3165
  appendRuntimeServicesLogLine(runtimeServicesLogPath, "[pushpals] embedded remotebuddy autonomous engine is disabled (remotebuddy.autonomy.enabled=false).");
2721
3166
  };
2722
3167
  reportRemoteBuddyAutonomousEngineState();
3168
+ if (runtimePreflight.config.remotebuddy.autoSpawnWorkerpals) {
3169
+ const workerpalReadyTimeoutMs = resolveWorkerpalCapacityTimeoutMs(runtimePreflight.config);
3170
+ const workerpalCapacity = await waitForWorkerpalCapacity({
3171
+ serverUrl: opts.serverUrl,
3172
+ timeoutMs: workerpalReadyTimeoutMs,
3173
+ ttlMs: runtimePreflight.config.remotebuddy.workerpalOnlineTtlMs
3174
+ });
3175
+ if (!workerpalCapacity.ok) {
3176
+ const tail = readLogTail(remotebuddyService.logPath);
3177
+ appendRuntimeServicesLogLine(runtimeServicesLogPath, `[pushpals] embedded workerpal capacity did not become available within ${workerpalReadyTimeoutMs}ms.`);
3178
+ stopRuntimeServices(services);
3179
+ throw new Error(`Embedded WorkerPal capacity did not become available within ${workerpalReadyTimeoutMs}ms (${workerpalCapacity.detail}). ` + `See ${remotebuddyService.logPath}${tail ? `
3180
+ --- remotebuddy log tail ---
3181
+ ${tail}` : ""}`);
3182
+ }
3183
+ console.log(`[pushpals] Embedded WorkerPal capacity is ready (${workerpalCapacity.detail}).`);
3184
+ appendRuntimeServicesLogLine(runtimeServicesLogPath, `[pushpals] embedded workerpal capacity ready (${workerpalCapacity.detail}).`);
3185
+ }
2723
3186
  const scmHealthy = await probeSourceControlManager(opts.sourceControlManagerPort);
2724
3187
  const scmGitProbe = await resolveSourceControlManagerGitProbe(opts.repoRoot, runtimeEnv, process.platform);
2725
3188
  const scmRemoteStatus = await checkGitRemoteConfigured(opts.repoRoot, opts.sourceControlManagerRemote, runtimeEnv);
@@ -3396,6 +3859,15 @@ async function main() {
3396
3859
  console.error(`[pushpals] Precheck failed: embedded SourceControlManager git command is unavailable (${scmGitPrecheck.detail}).`);
3397
3860
  process.exit(1);
3398
3861
  }
3862
+ const workerpalDockerPrecheck = await precheckWorkerpalDockerAvailability({
3863
+ repoRoot,
3864
+ runtimeRoot: preparedRuntime.runtimeRoot,
3865
+ preflightUsesEmbeddedRuntime: preparedRuntime.preflightUsesEmbeddedRuntime,
3866
+ autoSpawnWorkerpals: Boolean(config.remotebuddy.autoSpawnWorkerpals),
3867
+ dockerEnabled: Boolean(config.remotebuddy.workerpalDocker),
3868
+ requireDocker: Boolean(config.remotebuddy.workerpalRequireDocker),
3869
+ baseEnv: scmGitPrecheck.env
3870
+ });
3399
3871
  const precheckPassed = await enforcePushpalsRemoteBranchPrecheck(repoRoot, config.sourceControlManager.remote, config.sourceControlManager.mainBranch);
3400
3872
  if (!precheckPassed) {
3401
3873
  process.exit(1);
@@ -3414,6 +3886,7 @@ async function main() {
3414
3886
  };
3415
3887
  let autoStartedServices = [];
3416
3888
  let pushpalsLogPath;
3889
+ let resolvedRuntimeTagForAutoStart = preparedRuntime.runtimeTag || parsed.runtimeTag || "";
3417
3890
  const stopAutoStartedServices = () => {
3418
3891
  if (autoStartedServices.length === 0)
3419
3892
  return;
@@ -3422,6 +3895,26 @@ async function main() {
3422
3895
  };
3423
3896
  let serverHealthy = await probeServer(serverUrl);
3424
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
+ }
3425
3918
  let remoteBuddyConsumerHealth = {
3426
3919
  ok: false,
3427
3920
  detail: `No connected RemoteBuddy session consumer found for session ${sessionId}`
@@ -3437,8 +3930,9 @@ async function main() {
3437
3930
  sourceControlManagerPort: config.sourceControlManager.port,
3438
3931
  sourceControlManagerRemote: config.sourceControlManager.remote,
3439
3932
  preparedRuntime,
3440
- requestedRuntimeTag: parsed.runtimeTag,
3441
- startLocalBuddy: resolveCliLocalBuddyAutostart(parsed.runtimeOnly, Boolean(config.localbuddy.enabled))
3933
+ requestedRuntimeTag: resolvedRuntimeTagForAutoStart || parsed.runtimeTag,
3934
+ startLocalBuddy: resolveCliLocalBuddyAutostart(parsed.runtimeOnly, Boolean(config.localbuddy.enabled)),
3935
+ baseEnv: workerpalDockerPrecheck.env
3442
3936
  });
3443
3937
  autoStartedServices = startedRuntime.services;
3444
3938
  pushpalsLogPath = startedRuntime.pushpalsLogPath;
@@ -3493,6 +3987,22 @@ async function main() {
3493
3987
  }
3494
3988
  process.exit(1);
3495
3989
  }
3990
+ const workerpalCapacity = await waitForWorkerpalCapacity({
3991
+ serverUrl,
3992
+ timeoutMs: resolveWorkerpalCapacityTimeoutMs(config),
3993
+ ttlMs: config.remotebuddy.workerpalOnlineTtlMs
3994
+ });
3995
+ if (!workerpalCapacity.ok) {
3996
+ stopAutoStartedServices();
3997
+ console.error(`[pushpals] WorkerPal capacity is not ready for repo ${repoRoot}: ${workerpalCapacity.detail}.`);
3998
+ if (workerpalDockerPrecheck.status === "failed") {
3999
+ console.error(`[pushpals] Docker precheck detail: ${workerpalDockerPrecheck.detail}`);
4000
+ } else if (serverWasAlreadyHealthy) {
4001
+ console.error("[pushpals] A PushPals runtime is already serving this repo, but it does not currently have an idle WorkerPal available.");
4002
+ console.error("[pushpals] Wait for a worker to become idle or restart the runtime after fixing WorkerPal startup.");
4003
+ }
4004
+ process.exit(1);
4005
+ }
3496
4006
  const saved = statePath ? readCliState(statePath) : {};
3497
4007
  pushpalsLogPath = pushpalsLogPath || (typeof saved.pushpalsLogPath === "string" ? saved.pushpalsLogPath : undefined);
3498
4008
  const preferredHubUrl = normalizeUrl(parsed.monitoringHubUrl ?? process.env.PUSHPALS_MONITOR_URL ?? saved.monitoringHubUrl ?? "");
@@ -3667,17 +4177,21 @@ if (import.meta.main) {
3667
4177
  });
3668
4178
  }
3669
4179
  export {
4180
+ waitForWorkerpalCapacity,
3670
4181
  startEmbeddedMonitoringHub,
3671
4182
  resolveWindowsWhereExecutableCandidatesForEnv,
3672
4183
  resolveWindowsShellExecutableCandidatesForEnv,
3673
4184
  resolveRuntimeGitExecutableCandidates,
4185
+ resolveRuntimeDockerExecutableCandidates,
3674
4186
  resolvePreferredRuntimeReleaseTag,
3675
4187
  resolveCommandPath,
3676
4188
  resolveCliStatePath,
3677
4189
  resolveCliLocalBuddyAutostart,
3678
4190
  resolveBundledRuntimeAssetSource,
3679
4191
  resolveBundledMonitoringHubRoot,
4192
+ prepareEmbeddedWorkerpalDockerImageIfNeeded,
3680
4193
  prepareCliRuntime,
4194
+ precheckWorkerpalDockerAvailability,
3681
4195
  precheckSourceControlManagerGitAvailability,
3682
4196
  normalizeRepoPathForComparison,
3683
4197
  normalizeCliInteractiveMessage,
@@ -3688,12 +4202,17 @@ export {
3688
4202
  formatSessionEventLine,
3689
4203
  extractRemoteBuddySessionConsumerHealth,
3690
4204
  extractRemoteBuddyAutonomousEngineState,
4205
+ ensureWorkerpalDockerImageReady,
4206
+ downloadRuntimeAssetsFromSourceTag,
4207
+ copyTrackedRepoPath,
3691
4208
  bundledMonitoringHubNeedsRefresh,
4209
+ buildWorkerpalSandboxPaths,
3692
4210
  buildServiceStopCommand,
3693
4211
  buildRuntimeServiceLogPaths,
3694
4212
  buildOpenMonitoringHubCommand,
3695
4213
  buildEmbeddedRuntimeEnv,
3696
4214
  buildEmbeddedMonitoringHubHtml,
3697
4215
  buildCliClearTargets,
3698
- applyResolvedGitBinaryToRuntimeEnv
4216
+ applyResolvedGitBinaryToRuntimeEnv,
4217
+ applyResolvedDockerBinaryToRuntimeEnv
3699
4218
  };