@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.
- package/dist/pushpals-cli.js +1 -1
- package/package.json +2 -2
- package/runtime/prompts/remotebuddy/autonomy_ideation_system_prompt.md +4 -1
- package/runtime/prompts/remotebuddy/autonomy_planning_system_prompt.md +1 -1
- package/runtime/prompts/remotebuddy/remotebuddy_system_prompt.md +2 -2
- package/runtime/prompts/workerpals/miniswe_completion_requirement.md +1 -1
- package/runtime/prompts/workerpals/miniswe_explicit_targets_block.md +1 -1
- package/runtime/prompts/workerpals/openai_codex_task_execute_system_prompt.md +4 -1
- package/runtime/prompts/workerpals/openhands_minimal_system_prompt.j2 +3 -1
- package/runtime/prompts/workerpals/openhands_task_execute_system_prompt.md +2 -1
- package/runtime/prompts/workerpals/workerpals_system_prompt.md +2 -2
- package/runtime/sandbox/.pushpals-remotebuddy-fallback.js +41 -45
- package/runtime/sandbox/apps/workerpals/src/backends/miniswe/miniswe_executor.py +5 -34
- package/runtime/sandbox/apps/workerpals/src/backends/openhands/openhands_executor.py +3 -2
- package/runtime/sandbox/apps/workerpals/src/execute_job.ts +328 -71
- package/runtime/sandbox/apps/workerpals/src/workerpals_main.ts +70 -25
- package/runtime/sandbox/packages/shared/src/autonomy_policy.ts +14 -8
- package/runtime/sandbox/packages/shared/src/communication.ts +4 -1
- package/runtime/sandbox/packages/shared/src/config.ts +1 -1
- package/runtime/sandbox/prompts/workerpals/miniswe_completion_requirement.md +1 -1
- package/runtime/sandbox/prompts/workerpals/miniswe_explicit_targets_block.md +1 -1
- package/runtime/sandbox/prompts/workerpals/openai_codex_task_execute_system_prompt.md +4 -1
- package/runtime/sandbox/prompts/workerpals/openhands_minimal_system_prompt.j2 +3 -1
- package/runtime/sandbox/prompts/workerpals/openhands_task_execute_system_prompt.md +2 -1
- 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
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
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
|
-
|
|
1682
|
-
|
|
1738
|
+
{ priority: "high" },
|
|
1739
|
+
);
|
|
1683
1740
|
}
|
|
1684
|
-
if (
|
|
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?: {
|
|
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
|
-
|
|
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
|
|
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,
|
|
1932
|
+
asBoolean(remoteAutonomyNode.allow_read_anywhere, true),
|
|
1933
1933
|
prFeedbackCommentRows: Math.max(
|
|
1934
1934
|
1,
|
|
1935
1935
|
Math.min(
|
|
@@ -1 +1 @@
|
|
|
1
|
-
Completion requirement:
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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).
|