@pushpalsdev/cli 1.0.85 → 1.0.93

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 (25) hide show
  1. package/dist/pushpals-cli.js +1 -1
  2. package/package.json +2 -2
  3. package/runtime/prompts/remotebuddy/autonomy_ideation_system_prompt.md +4 -1
  4. package/runtime/prompts/remotebuddy/autonomy_planning_system_prompt.md +1 -1
  5. package/runtime/prompts/remotebuddy/remotebuddy_system_prompt.md +2 -2
  6. package/runtime/prompts/workerpals/miniswe_completion_requirement.md +1 -1
  7. package/runtime/prompts/workerpals/miniswe_explicit_targets_block.md +1 -1
  8. package/runtime/prompts/workerpals/openai_codex_task_execute_system_prompt.md +4 -1
  9. package/runtime/prompts/workerpals/openhands_minimal_system_prompt.j2 +3 -1
  10. package/runtime/prompts/workerpals/openhands_task_execute_system_prompt.md +2 -1
  11. package/runtime/prompts/workerpals/workerpals_system_prompt.md +2 -2
  12. package/runtime/sandbox/.pushpals-remotebuddy-fallback.js +41 -45
  13. package/runtime/sandbox/apps/workerpals/src/backends/miniswe/miniswe_executor.py +5 -34
  14. package/runtime/sandbox/apps/workerpals/src/backends/openhands/openhands_executor.py +3 -2
  15. package/runtime/sandbox/apps/workerpals/src/execute_job.ts +328 -71
  16. package/runtime/sandbox/apps/workerpals/src/workerpals_main.ts +70 -25
  17. package/runtime/sandbox/packages/shared/src/autonomy_policy.ts +14 -8
  18. package/runtime/sandbox/packages/shared/src/communication.ts +4 -1
  19. package/runtime/sandbox/packages/shared/src/config.ts +1 -1
  20. package/runtime/sandbox/prompts/workerpals/miniswe_completion_requirement.md +1 -1
  21. package/runtime/sandbox/prompts/workerpals/miniswe_explicit_targets_block.md +1 -1
  22. package/runtime/sandbox/prompts/workerpals/openai_codex_task_execute_system_prompt.md +4 -1
  23. package/runtime/sandbox/prompts/workerpals/openhands_minimal_system_prompt.j2 +3 -1
  24. package/runtime/sandbox/prompts/workerpals/openhands_task_execute_system_prompt.md +2 -1
  25. package/runtime/sandbox/prompts/workerpals/workerpals_system_prompt.md +2 -2
@@ -65,6 +65,8 @@ type WorkerJobResult = JobResult & {
65
65
 
66
66
  const DEFAULT_LLM_MODEL = "local-model";
67
67
  const CODEX_UNAVAILABLE_WORKER_EXIT_CODE = 86;
68
+ const CODEX_UNAVAILABLE_DOCKER_SHUTDOWN_GRACE_MS = 5_000;
69
+ const CODEX_UNAVAILABLE_WORKER_FORCE_EXIT_MS = 4_000;
68
70
  const CONFIG = loadPushPalsConfig();
69
71
  const LOG = new Logger("WorkerPals");
70
72
 
@@ -360,6 +362,36 @@ function shouldRecycleWorkerForCodexUnavailableFailure(
360
362
  ].some((needle) => text.includes(needle));
361
363
  }
362
364
 
365
+ async function shutdownDockerExecutorBeforeCodexRecycle(
366
+ dockerExecutor: DockerExecutor | null,
367
+ ): Promise<void> {
368
+ if (!dockerExecutor) return;
369
+
370
+ let timeout: ReturnType<typeof setTimeout> | null = null;
371
+ let timedOut = false;
372
+ try {
373
+ await Promise.race([
374
+ dockerExecutor.shutdown(),
375
+ new Promise<void>((resolvePromise) => {
376
+ timeout = setTimeout(() => {
377
+ timedOut = true;
378
+ resolvePromise();
379
+ }, CODEX_UNAVAILABLE_DOCKER_SHUTDOWN_GRACE_MS);
380
+ }),
381
+ ]);
382
+ } catch (err) {
383
+ console.error(`[WorkerPals] Docker shutdown cleanup failed: ${String(err)}`);
384
+ } finally {
385
+ if (timeout) clearTimeout(timeout);
386
+ }
387
+
388
+ if (timedOut) {
389
+ console.warn(
390
+ `[WorkerPals] Docker shutdown cleanup exceeded ${CODEX_UNAVAILABLE_DOCKER_SHUTDOWN_GRACE_MS}ms; exiting worker for Codex recycle anyway.`,
391
+ );
392
+ }
393
+ }
394
+
363
395
  function parseArgs(): {
364
396
  server: string;
365
397
  pollMs: number;
@@ -1667,21 +1699,46 @@ async function workerLoop(
1667
1699
  }
1668
1700
  } finally {
1669
1701
  clearInterval(busyHeartbeat);
1670
- if (
1671
- !recycleWorkerAfterJob &&
1672
- job.sessionId &&
1673
- result?.cooldownMs &&
1674
- result.cooldownMs > 0
1675
- ) {
1676
- await transport.queueSessionCommand(job.sessionId, {
1677
- type: "assistant_message",
1678
- payload: {
1679
- text: `WorkerPal is cooling down for ${formatDurationMs(result.cooldownMs)} after transient infrastructure failures.`,
1702
+ if (recycleWorkerAfterJob) {
1703
+ runtimeState.shutdownRequested = true;
1704
+ const forceExitTimer = setTimeout(() => {
1705
+ console.warn(
1706
+ `[WorkerPals] Forcing worker recycle ${CODEX_UNAVAILABLE_WORKER_FORCE_EXIT_MS}ms after Codex backend failure.`,
1707
+ );
1708
+ process.exit(CODEX_UNAVAILABLE_WORKER_EXIT_CODE);
1709
+ }, CODEX_UNAVAILABLE_WORKER_FORCE_EXIT_MS);
1710
+ try {
1711
+ await maybeHeartbeat("offline", null, true);
1712
+ if (directWorktreePath) {
1713
+ await removeIsolatedWorktree(opts.repo, directWorktreePath).catch((err) => {
1714
+ console.error(
1715
+ `[WorkerPals] Failed to remove isolated worktree before Codex recycle: ${String(
1716
+ err,
1717
+ )}`,
1718
+ );
1719
+ });
1720
+ directWorktreePath = null;
1721
+ }
1722
+ await shutdownDockerExecutorBeforeCodexRecycle(dockerExecutor);
1723
+ } finally {
1724
+ clearTimeout(forceExitTimer);
1725
+ process.exit(CODEX_UNAVAILABLE_WORKER_EXIT_CODE);
1726
+ }
1727
+ }
1728
+ if (job.sessionId && result?.cooldownMs && result.cooldownMs > 0) {
1729
+ await transport.queueSessionCommand(
1730
+ job.sessionId,
1731
+ {
1732
+ type: "assistant_message",
1733
+ payload: {
1734
+ text: `WorkerPal is cooling down for ${formatDurationMs(result.cooldownMs)} after transient infrastructure failures.`,
1735
+ },
1736
+ from: `worker:${opts.workerId}`,
1680
1737
  },
1681
- from: `worker:${opts.workerId}`,
1682
- }, { priority: "high" });
1738
+ { priority: "high" },
1739
+ );
1683
1740
  }
1684
- if (!recycleWorkerAfterJob && result?.cooldownMs && result.cooldownMs > 0) {
1741
+ if (result?.cooldownMs && result.cooldownMs > 0) {
1685
1742
  const cooldownMs = Math.max(0, Math.floor(result.cooldownMs));
1686
1743
  console.warn(
1687
1744
  `[WorkerPals] Entering cooldown for ${formatDurationMs(cooldownMs)} after retry exhaustion.`,
@@ -1697,18 +1754,6 @@ async function workerLoop(
1697
1754
  console.error(`[WorkerPals] Failed to remove isolated worktree: ${String(err)}`);
1698
1755
  });
1699
1756
  }
1700
- if (recycleWorkerAfterJob) {
1701
- runtimeState.shutdownRequested = true;
1702
- await maybeHeartbeat("offline", null, true);
1703
- if (dockerExecutor) {
1704
- try {
1705
- await dockerExecutor.shutdown();
1706
- } catch (err) {
1707
- console.error(`[WorkerPals] Docker shutdown cleanup failed: ${String(err)}`);
1708
- }
1709
- }
1710
- process.exit(CODEX_UNAVAILABLE_WORKER_EXIT_CODE);
1711
- }
1712
1757
  }
1713
1758
  }
1714
1759
  }
@@ -319,7 +319,11 @@ export function validateScopeInvariants(
319
319
  componentArea: AutonomyComponentArea | null | undefined,
320
320
  targetPathsInput: unknown[],
321
321
  writeGlobsInput: unknown[],
322
- options?: { requireWriteGlobs?: boolean; allowMultipleComponentRoots?: boolean },
322
+ options?: {
323
+ requireWriteGlobs?: boolean;
324
+ allowMultipleComponentRoots?: boolean;
325
+ hintsOnly?: boolean;
326
+ },
323
327
  ): ScopeValidationResult {
324
328
  const errors: string[] = [];
325
329
  const scopeSeeds = collectScopeSeedPaths(targetPathsInput, writeGlobsInput);
@@ -327,7 +331,8 @@ export function validateScopeInvariants(
327
331
  normalizeAutonomyComponentArea(componentArea) ??
328
332
  deriveAutonomyComponentArea(targetPathsInput, writeGlobsInput);
329
333
  const allowMultipleComponentRoots = options?.allowMultipleComponentRoots === true;
330
- if (!normalizedComponentArea && scopeSeeds.length > 1 && !allowMultipleComponentRoots) {
334
+ const hintsOnly = options?.hintsOnly === true;
335
+ if (!hintsOnly && !normalizedComponentArea && scopeSeeds.length > 1 && !allowMultipleComponentRoots) {
331
336
  errors.push(
332
337
  `scope spans multiple component roots: ${scopeSeeds.slice(0, 6).join(", ")}`,
333
338
  );
@@ -341,7 +346,7 @@ export function validateScopeInvariants(
341
346
  errors.push(`invalid target_path: ${String(raw ?? "")}`);
342
347
  continue;
343
348
  }
344
- if (rootPrefix && !underRoot(normalized, rootPrefix)) {
349
+ if (!hintsOnly && rootPrefix && !underRoot(normalized, rootPrefix)) {
345
350
  errors.push(`target_path outside component root: ${normalized}`);
346
351
  continue;
347
352
  }
@@ -362,20 +367,21 @@ export function validateScopeInvariants(
362
367
  errors.push(`invalid write_glob: ${String(raw ?? "")}`);
363
368
  continue;
364
369
  }
365
- if (hasForbiddenBroadGlob(normalized)) {
370
+ if (!hintsOnly && hasForbiddenBroadGlob(normalized)) {
366
371
  errors.push(`forbidden broad write_glob: ${normalized}`);
367
372
  continue;
368
373
  }
369
374
  const prefix = literalPrefix(normalized);
370
- if (!prefix) {
375
+ if (!hintsOnly && !prefix) {
371
376
  errors.push(`write_glob literal prefix cannot be empty: ${normalized}`);
372
377
  continue;
373
378
  }
374
- if (rootPrefix && !underRoot(prefix, rootPrefix)) {
379
+ if (!hintsOnly && rootPrefix && !underRoot(prefix, rootPrefix)) {
375
380
  errors.push(`write_glob outside component root: ${normalized}`);
376
381
  continue;
377
382
  }
378
383
  if (
384
+ !hintsOnly &&
379
385
  !normalizedTargetPaths.some(
380
386
  (targetPath) => targetPath === prefix || targetPath.startsWith(`${prefix}/`),
381
387
  )
@@ -393,13 +399,13 @@ export function validateScopeInvariants(
393
399
  errors.push("write_globs must be provided and non-empty");
394
400
  }
395
401
 
396
- if (normalizedTargetPaths.length > 0 && normalizedWriteGlobs.length > 0) {
402
+ if (!hintsOnly && normalizedTargetPaths.length > 0 && normalizedWriteGlobs.length > 0) {
397
403
  for (const targetPath of normalizedTargetPaths) {
398
404
  const covered = normalizedWriteGlobs.some((glob) => matchesGlob(targetPath, glob));
399
405
  if (!covered) errors.push(`target_path not covered by write_globs: ${targetPath}`);
400
406
  }
401
407
  }
402
- if (!normalizedComponentArea && !allowMultipleComponentRoots) {
408
+ if (!hintsOnly && !normalizedComponentArea && !allowMultipleComponentRoots) {
403
409
  errors.push("component_area could not be derived from scope");
404
410
  }
405
411
 
@@ -53,6 +53,7 @@ export interface CommunicationManagerOptions {
53
53
  sessionId: string;
54
54
  from: string;
55
55
  authToken?: string | null;
56
+ fetchImpl?: typeof fetch;
56
57
  }
57
58
 
58
59
  export class CommunicationManager {
@@ -60,12 +61,14 @@ export class CommunicationManager {
60
61
  private readonly sessionId: string;
61
62
  private readonly from: string;
62
63
  private readonly authToken: string | null;
64
+ private readonly fetchImpl: typeof fetch;
63
65
 
64
66
  constructor(opts: CommunicationManagerOptions) {
65
67
  this.serverUrl = opts.serverUrl;
66
68
  this.sessionId = opts.sessionId;
67
69
  this.from = opts.from;
68
70
  this.authToken = opts.authToken ?? null;
71
+ this.fetchImpl = opts.fetchImpl ?? fetch;
69
72
  }
70
73
 
71
74
  private headers(): Record<string, string> {
@@ -121,7 +124,7 @@ export class CommunicationManager {
121
124
  if (meta.turnId) body.turnId = meta.turnId;
122
125
  if (meta.parentId) body.parentId = meta.parentId;
123
126
 
124
- const response = await fetch(this.commandUrl(sessionId), {
127
+ const response = await this.fetchImpl(this.commandUrl(sessionId), {
125
128
  method: "POST",
126
129
  headers: this.headers(),
127
130
  body: JSON.stringify(body),
@@ -1929,7 +1929,7 @@ export function loadPushPalsConfig(options: LoadOptions = {}): PushPalsConfig {
1929
1929
  ),
1930
1930
  allowReadAnywhere:
1931
1931
  parseBoolEnv("REMOTEBUDDY_AUTONOMY_ALLOW_READ_ANYWHERE") ??
1932
- asBoolean(remoteAutonomyNode.allow_read_anywhere, false),
1932
+ asBoolean(remoteAutonomyNode.allow_read_anywhere, true),
1933
1933
  prFeedbackCommentRows: Math.max(
1934
1934
  1,
1935
1935
  Math.min(
@@ -1 +1 @@
1
- Completion requirement: handle all requested edits across all explicit target paths before setting done=true.
1
+ Completion requirement: solve the requested task before setting done=true. Target paths are relevance hints; edit other behavior-owning files when needed and explain why.
@@ -1,2 +1,2 @@
1
- Explicit target paths:
1
+ Target path hints:
2
2
  {{targets_block}}
@@ -11,7 +11,10 @@ Non-negotiable runtime invariants:
11
11
 
12
12
  Execution rules:
13
13
 
14
- - Keep edits minimal, correct, and scoped to the requested task.
14
+ - Keep edits minimal, correct, and relevant to the requested task.
15
+ - You have repo-wide read/write access inside an isolated WorkerPal sandbox. Target paths and write globs are starting-point/relevance hints, not hard write boundaries.
16
+ - If the hinted file is a thin wrapper or the behavior lives elsewhere, edit the behavior-owning file(s) needed to solve the task and explain the scope expansion in your final response.
17
+ - Avoid irrelevant sprawl; the review agent will judge whether changed files are necessary for the requested outcome.
15
18
  - Read relevant files before editing, then run focused validation.
16
19
  - Use direct commands without shell wrappers. Prefer plain commands like `git diff -- path`, `git add <path>`, `git status --porcelain`, and `pwd`.
17
20
  - Do not wrap commands in `/bin/bash -lc`, `sh -lc`, `cmd /c`, or `powershell -Command`, and avoid pipelines, `awk`, heredocs, or multi-command shell snippets unless they are truly unavoidable.
@@ -1,10 +1,12 @@
1
1
  You are PushPals WorkerPal running inside OpenHands.
2
2
 
3
- Operate with strict scope:
3
+ Operate with strict relevance:
4
4
  - Focus only on the current user task.
5
5
  - If the task is a question, answer directly and do not edit files.
6
6
  - If code/file changes are requested, implement them end-to-end.
7
7
  - Reuse existing repository conventions and avoid unrelated refactors/docs.
8
+ - Treat target paths and write globs as starting-point/relevance hints, not hard write boundaries.
9
+ - If the behavior lives outside the hinted files, edit the owning files and explain the scope expansion.
8
10
 
9
11
  Execution loop:
10
12
  - Prefer concrete execution over planning chatter.
@@ -3,7 +3,8 @@ You are PushPals WorkerPal running inside OpenHands.
3
3
  Execution rules:
4
4
 
5
5
  - Focus only on the task below.
6
- - Keep changes minimal, correct, and scoped to the request.
6
+ - Keep changes minimal, correct, and relevant to the request.
7
+ - Target paths and write globs are starting-point/relevance hints, not hard write boundaries; if the behavior lives elsewhere, edit the owning files and explain why.
7
8
  - Read relevant files before editing.
8
9
  - Reuse existing project conventions and tooling.
9
10
  - If the task is a question/explanation, answer directly and do not edit files.
@@ -6,7 +6,7 @@ Your mission:
6
6
 
7
7
  - Take the user (or RemoteBuddy) request and fully execute it end-to-end.
8
8
  - You are responsible for breaking the work down into concrete subtasks, completing them, validating, reviewing your own changes, and preparing a high-quality commit message when the work is ready.
9
- - You have full read/write access within the assigned repository only; do not access files outside that repository.
9
+ - You have full read/write access within the assigned repository sandbox only; do not access files outside that repository. Target paths and write globs are starting-point/relevance hints, not hard write boundaries.
10
10
 
11
11
  Mindset:
12
12
 
@@ -64,7 +64,7 @@ Execution workflow (you MUST follow this):
64
64
  - Performance: no unnecessary work on UI thread, no extra network calls, no large bundles
65
65
  - Cross-platform: iOS/Android/Web differences guarded appropriately
66
66
  - Security: no secret leakage, safe networking defaults, no unsafe shell usage
67
- - Make any final polish edits that improve clarity without changing scope.
67
+ - Make any final polish edits that improve clarity without drifting from the task.
68
68
 
69
69
  7. Prepare to commit (when appropriate)
70
70
  - When the work is ready, produce a detailed commit message (do NOT actually commit unless your system explicitly allows it).