@pushpalsdev/cli 1.0.40 → 1.0.42
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 +112 -35
- package/package.json +1 -1
- package/runtime/prompts/review_agent/merge_conflict_instruction.md +1 -0
- package/runtime/prompts/workerpals/openai_codex_task_execute_system_prompt.md +1 -0
- package/runtime/sandbox/apps/workerpals/src/execute_job.ts +379 -21
- package/runtime/sandbox/apps/workerpals/src/job_runner.ts +81 -54
- package/runtime/sandbox/apps/workerpals/src/merge_conflict_job.ts +359 -0
- package/runtime/sandbox/apps/workerpals/src/workerpals_main.ts +24 -0
- package/runtime/sandbox/prompts/workerpals/openai_codex_task_execute_system_prompt.md +1 -0
package/dist/pushpals-cli.js
CHANGED
|
@@ -189,7 +189,7 @@ class ServiceManager {
|
|
|
189
189
|
stderr: "ignore"
|
|
190
190
|
});
|
|
191
191
|
} else {
|
|
192
|
-
service.proc.kill();
|
|
192
|
+
service.proc.kill("SIGKILL");
|
|
193
193
|
}
|
|
194
194
|
} catch {}
|
|
195
195
|
}
|
|
@@ -1701,6 +1701,7 @@ function printUsage() {
|
|
|
1701
1701
|
console.log(" --no-auto-start Disable runtime auto-start when the server is down");
|
|
1702
1702
|
console.log(" --no-stream Disable live session event stream");
|
|
1703
1703
|
console.log(" --runtime-only Start the local runtime and wait for shutdown without opening the interactive chat");
|
|
1704
|
+
console.log(" --status-once Print active endpoints once and exit");
|
|
1704
1705
|
console.log(" --clear Remove repo-local PushPals state and exit");
|
|
1705
1706
|
console.log(" -h, --help Show this help");
|
|
1706
1707
|
console.log("");
|
|
@@ -1720,6 +1721,7 @@ function parseArgs(argv) {
|
|
|
1720
1721
|
noAutoStart: false,
|
|
1721
1722
|
noStream: false,
|
|
1722
1723
|
runtimeOnly: false,
|
|
1724
|
+
statusOnce: false,
|
|
1723
1725
|
clear: false
|
|
1724
1726
|
};
|
|
1725
1727
|
for (let i = 0;i < argv.length; i++) {
|
|
@@ -1740,6 +1742,10 @@ function parseArgs(argv) {
|
|
|
1740
1742
|
options.runtimeOnly = true;
|
|
1741
1743
|
continue;
|
|
1742
1744
|
}
|
|
1745
|
+
if (arg === "--status-once") {
|
|
1746
|
+
options.statusOnce = true;
|
|
1747
|
+
continue;
|
|
1748
|
+
}
|
|
1743
1749
|
if (arg === "--clear") {
|
|
1744
1750
|
options.clear = true;
|
|
1745
1751
|
continue;
|
|
@@ -1812,7 +1818,7 @@ function parsePositiveInt(value, fallback) {
|
|
|
1812
1818
|
function jsonHtmlBootstrap(value) {
|
|
1813
1819
|
return JSON.stringify(value).replace(/</g, "\\u003c");
|
|
1814
1820
|
}
|
|
1815
|
-
async function runCommandWithEnv(command, cwd, env) {
|
|
1821
|
+
async function runCommandWithEnv(command, cwd, env, timeoutMs) {
|
|
1816
1822
|
try {
|
|
1817
1823
|
const proc = Bun.spawn(command, {
|
|
1818
1824
|
cwd,
|
|
@@ -1820,12 +1826,48 @@ async function runCommandWithEnv(command, cwd, env) {
|
|
|
1820
1826
|
stdout: "pipe",
|
|
1821
1827
|
stderr: "pipe"
|
|
1822
1828
|
});
|
|
1829
|
+
let timedOut = false;
|
|
1830
|
+
let timer = null;
|
|
1831
|
+
if (typeof timeoutMs === "number" && Number.isFinite(timeoutMs) && timeoutMs > 0) {
|
|
1832
|
+
timer = setTimeout(() => {
|
|
1833
|
+
timedOut = true;
|
|
1834
|
+
try {
|
|
1835
|
+
const stopCommand = buildServiceStopCommand(proc.pid, process.platform);
|
|
1836
|
+
if (stopCommand) {
|
|
1837
|
+
Bun.spawnSync(stopCommand, {
|
|
1838
|
+
stdin: "ignore",
|
|
1839
|
+
stdout: "ignore",
|
|
1840
|
+
stderr: "ignore"
|
|
1841
|
+
});
|
|
1842
|
+
} else {
|
|
1843
|
+
proc.kill("SIGKILL");
|
|
1844
|
+
}
|
|
1845
|
+
} catch {}
|
|
1846
|
+
}, timeoutMs);
|
|
1847
|
+
}
|
|
1823
1848
|
const [stdout, stderr, exitCode] = await Promise.all([
|
|
1824
1849
|
new Response(proc.stdout).text(),
|
|
1825
1850
|
new Response(proc.stderr).text(),
|
|
1826
1851
|
proc.exited
|
|
1827
1852
|
]);
|
|
1828
|
-
|
|
1853
|
+
if (timer)
|
|
1854
|
+
clearTimeout(timer);
|
|
1855
|
+
const normalizedStdout = stdout.trim();
|
|
1856
|
+
const normalizedStderr = stderr.trim();
|
|
1857
|
+
if (timedOut) {
|
|
1858
|
+
return {
|
|
1859
|
+
ok: false,
|
|
1860
|
+
stdout: normalizedStdout,
|
|
1861
|
+
stderr: `timed out after ${timeoutMs}ms${normalizedStderr ? ` | ${normalizedStderr}` : ""}`,
|
|
1862
|
+
exitCode
|
|
1863
|
+
};
|
|
1864
|
+
}
|
|
1865
|
+
return {
|
|
1866
|
+
ok: exitCode === 0,
|
|
1867
|
+
stdout: normalizedStdout,
|
|
1868
|
+
stderr: normalizedStderr,
|
|
1869
|
+
exitCode
|
|
1870
|
+
};
|
|
1829
1871
|
} catch (err) {
|
|
1830
1872
|
return {
|
|
1831
1873
|
ok: false,
|
|
@@ -2610,7 +2652,7 @@ function stopRuntimeServices(services) {
|
|
|
2610
2652
|
stderr: "ignore"
|
|
2611
2653
|
});
|
|
2612
2654
|
} else {
|
|
2613
|
-
service.proc.kill();
|
|
2655
|
+
service.proc.kill("SIGKILL");
|
|
2614
2656
|
}
|
|
2615
2657
|
} catch {}
|
|
2616
2658
|
}
|
|
@@ -2661,6 +2703,34 @@ async function stopRuntimeServicesGracefully(services, timeoutMs = 1e4) {
|
|
|
2661
2703
|
stopRuntimeServices(remaining);
|
|
2662
2704
|
}
|
|
2663
2705
|
}
|
|
2706
|
+
async function shutdownEmbeddedServiceManagerGracefully(options) {
|
|
2707
|
+
const {
|
|
2708
|
+
serviceManager,
|
|
2709
|
+
serverUrl,
|
|
2710
|
+
repoRoot,
|
|
2711
|
+
reason,
|
|
2712
|
+
requestShutdown = requestLocalRuntimeShutdown,
|
|
2713
|
+
shutdownAcceptedDelayMs = 1500,
|
|
2714
|
+
onLog = (line) => console.log(line),
|
|
2715
|
+
onWarn = (line) => console.warn(line),
|
|
2716
|
+
cleanupTasks = []
|
|
2717
|
+
} = options;
|
|
2718
|
+
serviceManager.beginShutdown();
|
|
2719
|
+
const services = serviceManager.getServices();
|
|
2720
|
+
const shutdown = await requestShutdown(serverUrl, repoRoot, reason);
|
|
2721
|
+
if (shutdown.attempted && shutdown.accepted) {
|
|
2722
|
+
onLog("[pushpals] Local runtime shutdown accepted; waiting for services to exit...");
|
|
2723
|
+
await Bun.sleep(Math.max(0, shutdownAcceptedDelayMs));
|
|
2724
|
+
} else if (shutdown.attempted) {
|
|
2725
|
+
onWarn(`[pushpals] Local runtime shutdown request was not accepted${shutdown.detail ? `: ${shutdown.detail}` : "."}`);
|
|
2726
|
+
} else if (shutdown.detail) {
|
|
2727
|
+
onWarn(`[pushpals] ${shutdown.detail}`);
|
|
2728
|
+
}
|
|
2729
|
+
await stopRuntimeServicesGracefully(services);
|
|
2730
|
+
for (const task of cleanupTasks) {
|
|
2731
|
+
await task();
|
|
2732
|
+
}
|
|
2733
|
+
}
|
|
2664
2734
|
function prependExecutableDirToPath(env, executablePath, platform = process.platform) {
|
|
2665
2735
|
const resolvedPath = String(executablePath ?? "").trim();
|
|
2666
2736
|
if (!resolvedPath)
|
|
@@ -2930,6 +3000,7 @@ function resolveConfiguredDockerExecutable(env, platform = process.platform) {
|
|
|
2930
3000
|
}
|
|
2931
3001
|
async function cleanupLingeringWorkerpalWarmContainers(opts) {
|
|
2932
3002
|
const runCommandWithEnvFn = opts.runCommandWithEnvFn ?? runCommandWithEnv;
|
|
3003
|
+
const commandTimeoutMs = typeof opts.commandTimeoutMs === "number" && Number.isFinite(opts.commandTimeoutMs) ? Math.max(1, Math.floor(opts.commandTimeoutMs)) : 5000;
|
|
2933
3004
|
const dockerExecutable = resolveConfiguredDockerExecutable(opts.env, opts.platform ?? process.platform);
|
|
2934
3005
|
const list = await runCommandWithEnvFn([
|
|
2935
3006
|
dockerExecutable,
|
|
@@ -2939,7 +3010,7 @@ async function cleanupLingeringWorkerpalWarmContainers(opts) {
|
|
|
2939
3010
|
`label=${WORKERPAL_WARM_COMPONENT_LABEL}`,
|
|
2940
3011
|
"--filter",
|
|
2941
3012
|
`label=pushpals.repo=${opts.repoRoot}`
|
|
2942
|
-
], opts.repoRoot, opts.env);
|
|
3013
|
+
], opts.repoRoot, opts.env, commandTimeoutMs);
|
|
2943
3014
|
if (!list.ok) {
|
|
2944
3015
|
const detail = list.stderr || list.stdout || `exit ${list.exitCode}`;
|
|
2945
3016
|
return {
|
|
@@ -2956,7 +3027,7 @@ async function cleanupLingeringWorkerpalWarmContainers(opts) {
|
|
|
2956
3027
|
removed: 0
|
|
2957
3028
|
};
|
|
2958
3029
|
}
|
|
2959
|
-
const remove = await runCommandWithEnvFn([dockerExecutable, "rm", "-f", ...containerIds], opts.repoRoot, opts.env);
|
|
3030
|
+
const remove = await runCommandWithEnvFn([dockerExecutable, "rm", "-f", ...containerIds], opts.repoRoot, opts.env, commandTimeoutMs);
|
|
2960
3031
|
if (!remove.ok) {
|
|
2961
3032
|
const detail = remove.stderr || remove.stdout || `exit ${remove.exitCode}`;
|
|
2962
3033
|
return {
|
|
@@ -2974,7 +3045,8 @@ async function cleanupLingeringWorkerpalWarmContainers(opts) {
|
|
|
2974
3045
|
async function cleanupLingeringPushPalsGitWorktrees(opts) {
|
|
2975
3046
|
const runCommandWithEnvFn = opts.runCommandWithEnvFn ?? runCommandWithEnv;
|
|
2976
3047
|
const forceDeleteWorktreePathFn = opts.forceDeleteWorktreePathFn ?? forceDeleteWorktreePath;
|
|
2977
|
-
const
|
|
3048
|
+
const commandTimeoutMs = typeof opts.commandTimeoutMs === "number" && Number.isFinite(opts.commandTimeoutMs) ? Math.max(1, Math.floor(opts.commandTimeoutMs)) : 5000;
|
|
3049
|
+
const list = await runCommandWithEnvFn(["git", "worktree", "list", "--porcelain"], opts.repoRoot, opts.env, commandTimeoutMs);
|
|
2978
3050
|
if (!list.ok) {
|
|
2979
3051
|
const detail = list.stderr || list.stdout || `exit ${list.exitCode}`;
|
|
2980
3052
|
return {
|
|
@@ -2996,7 +3068,7 @@ async function cleanupLingeringPushPalsGitWorktrees(opts) {
|
|
|
2996
3068
|
let removed = 0;
|
|
2997
3069
|
const failures = [];
|
|
2998
3070
|
for (const entry of removable) {
|
|
2999
|
-
const remove = await runCommandWithEnvFn(["git", "worktree", "remove", "--force", "--force", entry.path], opts.repoRoot, opts.env);
|
|
3071
|
+
const remove = await runCommandWithEnvFn(["git", "worktree", "remove", "--force", "--force", entry.path], opts.repoRoot, opts.env, commandTimeoutMs);
|
|
3000
3072
|
if (remove.ok) {
|
|
3001
3073
|
removed += 1;
|
|
3002
3074
|
continue;
|
|
@@ -3009,7 +3081,7 @@ async function cleanupLingeringPushPalsGitWorktrees(opts) {
|
|
|
3009
3081
|
const removeDetail = remove.stderr || remove.stdout || `exit ${remove.exitCode}`;
|
|
3010
3082
|
failures.push(`${entry.path}: ${removeDetail}${forced.lastError ? ` | fallback: ${forced.lastError}` : ""}`);
|
|
3011
3083
|
}
|
|
3012
|
-
const prune = await runCommandWithEnvFn(["git", "worktree", "prune"], opts.repoRoot, opts.env);
|
|
3084
|
+
const prune = await runCommandWithEnvFn(["git", "worktree", "prune"], opts.repoRoot, opts.env, commandTimeoutMs);
|
|
3013
3085
|
if (!prune.ok) {
|
|
3014
3086
|
failures.push(`prune: ${prune.stderr || prune.stdout || `exit ${prune.exitCode}`}`);
|
|
3015
3087
|
}
|
|
@@ -3018,13 +3090,13 @@ async function cleanupLingeringPushPalsGitWorktrees(opts) {
|
|
|
3018
3090
|
"for-each-ref",
|
|
3019
3091
|
"--format=%(refname:short)",
|
|
3020
3092
|
`refs/heads/${SOURCE_CONTROL_MANAGER_TEMP_BRANCH_PREFIX}`
|
|
3021
|
-
], opts.repoRoot, opts.env);
|
|
3093
|
+
], opts.repoRoot, opts.env, commandTimeoutMs);
|
|
3022
3094
|
if (!deleteTempBranches.ok) {
|
|
3023
3095
|
failures.push(`list temp branches: ${deleteTempBranches.stderr || deleteTempBranches.stdout || `exit ${deleteTempBranches.exitCode}`}`);
|
|
3024
3096
|
} else {
|
|
3025
3097
|
const branches = deleteTempBranches.stdout.split(/\r?\n/g).map((value) => value.trim()).filter(Boolean);
|
|
3026
3098
|
for (const branch of branches) {
|
|
3027
|
-
const deleteResult = await runCommandWithEnvFn(["git", "branch", "-D", branch], opts.repoRoot, opts.env);
|
|
3099
|
+
const deleteResult = await runCommandWithEnvFn(["git", "branch", "-D", branch], opts.repoRoot, opts.env, commandTimeoutMs);
|
|
3028
3100
|
if (!deleteResult.ok) {
|
|
3029
3101
|
failures.push(`${branch}: ${deleteResult.stderr || deleteResult.stdout || `exit ${deleteResult.exitCode}`}`);
|
|
3030
3102
|
} else {
|
|
@@ -4799,21 +4871,16 @@ async function main() {
|
|
|
4799
4871
|
return;
|
|
4800
4872
|
const serviceManager = autoStartedServiceManager;
|
|
4801
4873
|
autoStartedServiceManager = null;
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
}
|
|
4812
|
-
serviceManager.stop();
|
|
4813
|
-
const services = serviceManager.getServices();
|
|
4814
|
-
await stopRuntimeServicesGracefully(services);
|
|
4815
|
-
await cleanupWorkerpalWarmContainersIfNeeded("cli shutdown");
|
|
4816
|
-
await cleanupPushPalsGitWorktreesIfNeeded("cli shutdown");
|
|
4874
|
+
await shutdownEmbeddedServiceManagerGracefully({
|
|
4875
|
+
serviceManager,
|
|
4876
|
+
serverUrl,
|
|
4877
|
+
repoRoot,
|
|
4878
|
+
reason,
|
|
4879
|
+
cleanupTasks: [
|
|
4880
|
+
() => cleanupWorkerpalWarmContainersIfNeeded("cli shutdown"),
|
|
4881
|
+
() => cleanupPushPalsGitWorktreesIfNeeded("cli shutdown")
|
|
4882
|
+
]
|
|
4883
|
+
});
|
|
4817
4884
|
};
|
|
4818
4885
|
if (!serverHealthy && workerpalDockerPrecheck.status === "failed") {
|
|
4819
4886
|
console.error(`[pushpals] Precheck failed: Docker-backed WorkerPal auto-spawn is required but Docker is unavailable (${workerpalDockerPrecheck.detail}).`);
|
|
@@ -5002,7 +5069,7 @@ ${line}
|
|
|
5002
5069
|
}
|
|
5003
5070
|
console.log(line);
|
|
5004
5071
|
};
|
|
5005
|
-
const streamTask = parsed.noStream ? Promise.resolve() : parsed.runtimeOnly ? Promise.resolve() : runSessionStream(serverUrl, activeSessionId, cliClient, printIncoming, streamAbort.signal);
|
|
5072
|
+
const streamTask = parsed.noStream ? Promise.resolve() : parsed.runtimeOnly || parsed.statusOnce ? Promise.resolve() : runSessionStream(serverUrl, activeSessionId, cliClient, printIncoming, streamAbort.signal);
|
|
5006
5073
|
let stopPromise = null;
|
|
5007
5074
|
const requestStop = () => {
|
|
5008
5075
|
if (stopPromise)
|
|
@@ -5066,10 +5133,25 @@ ${line}
|
|
|
5066
5133
|
await Promise.race([streamTask, Bun.sleep(2000)]);
|
|
5067
5134
|
return;
|
|
5068
5135
|
}
|
|
5136
|
+
const printStatusSnapshot = async () => {
|
|
5137
|
+
console.log(`[pushpals] serverUrl=${serverUrl}`);
|
|
5138
|
+
console.log(`[pushpals] sessionId=${activeSessionId}`);
|
|
5139
|
+
console.log(`[pushpals] repoRoot=${repoRoot}`);
|
|
5140
|
+
console.log(`[pushpals] pushpalsLog=${pushpalsLogPath ?? "unavailable"}`);
|
|
5141
|
+
console.log(monitoringHubUrl ? `[pushpals] monitoringHubUrl=${monitoringHubUrl}` : "[pushpals] monitoringHubUrl=unavailable");
|
|
5142
|
+
await reportWorkerExecutionReadiness();
|
|
5143
|
+
reportEmbeddedRuntimeHealth();
|
|
5144
|
+
};
|
|
5145
|
+
if (parsed.statusOnce) {
|
|
5146
|
+
await printStatusSnapshot();
|
|
5147
|
+
await requestStop();
|
|
5148
|
+
await Promise.race([streamTask, Bun.sleep(2000)]);
|
|
5149
|
+
return;
|
|
5150
|
+
}
|
|
5069
5151
|
rl = createInterface({
|
|
5070
5152
|
input: process.stdin,
|
|
5071
5153
|
output: process.stdout,
|
|
5072
|
-
terminal:
|
|
5154
|
+
terminal: Boolean(process.stdin.isTTY && process.stdout.isTTY)
|
|
5073
5155
|
});
|
|
5074
5156
|
rl.setPrompt("you> ");
|
|
5075
5157
|
rl.prompt();
|
|
@@ -5089,13 +5171,7 @@ ${line}
|
|
|
5089
5171
|
continue;
|
|
5090
5172
|
}
|
|
5091
5173
|
if (text === "/status") {
|
|
5092
|
-
|
|
5093
|
-
console.log(`[pushpals] sessionId=${activeSessionId}`);
|
|
5094
|
-
console.log(`[pushpals] repoRoot=${repoRoot}`);
|
|
5095
|
-
console.log(`[pushpals] pushpalsLog=${pushpalsLogPath ?? "unavailable"}`);
|
|
5096
|
-
console.log(monitoringHubUrl ? `[pushpals] monitoringHubUrl=${monitoringHubUrl}` : "[pushpals] monitoringHubUrl=unavailable");
|
|
5097
|
-
await reportWorkerExecutionReadiness();
|
|
5098
|
-
reportEmbeddedRuntimeHealth();
|
|
5174
|
+
await printStatusSnapshot();
|
|
5099
5175
|
rl.prompt();
|
|
5100
5176
|
continue;
|
|
5101
5177
|
}
|
|
@@ -5134,6 +5210,7 @@ if (import.meta.main) {
|
|
|
5134
5210
|
export {
|
|
5135
5211
|
waitForWorkerpalCapacity,
|
|
5136
5212
|
startEmbeddedMonitoringHub,
|
|
5213
|
+
shutdownEmbeddedServiceManagerGracefully,
|
|
5137
5214
|
shouldRunEmbeddedRuntimeStartupPrechecks,
|
|
5138
5215
|
shouldRestartEmbeddedService,
|
|
5139
5216
|
resolveWorkerExecutionReadiness,
|
package/package.json
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
Resolve merge conflicts for PR #{{pr_number}} ({{pr_url}}) on branch {{pr_head_ref}}.
|
|
2
2
|
This PR already passed ReviewAgent ({{review_score}}/10) but GitHub reports it is not mergeable due to conflicts.
|
|
3
3
|
Rebase {{pr_head_ref}} onto {{pr_base_ref}}, resolve all conflicts, keep intended behavior, run relevant tests, and push updates to the same branch.
|
|
4
|
+
If the worker sandbox already prepared an isolated branch or in-progress rebase state, use that current repo state as authoritative instead of re-deriving branch topology.
|
|
4
5
|
Do not create a new PR; update only the existing PR branch.
|
|
@@ -6,6 +6,7 @@ Non-negotiable runtime invariants:
|
|
|
6
6
|
- Do not modify tests or production code to bypass, stub, or remove Codex CLI usage due to assumed environment limitations.
|
|
7
7
|
- Do not "adapt around" missing Codex access by rewriting coverage or behavior expectations.
|
|
8
8
|
- If Codex CLI authentication/execution is unavailable, fail loudly with a clear error and stop.
|
|
9
|
+
- When worker guidance provides exact repo/branch/conflict state, treat that prepared sandbox state as authoritative and start from the current checkout instead of re-discovering topology.
|
|
9
10
|
|
|
10
11
|
Execution rules:
|
|
11
12
|
|