@pushpalsdev/cli 1.1.18 → 1.1.20
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 +16 -2
- package/package.json +1 -1
- package/runtime/configs/default.toml +1 -0
- package/runtime/sandbox/.pushpals-remotebuddy-fallback.js +199 -13
- package/runtime/sandbox/apps/workerpals/src/backends/openai_codex/openai_codex_executor.py +250 -6
- package/runtime/sandbox/apps/workerpals/src/backends/openai_codex/test_openai_codex_runtime_config.py +223 -0
- package/runtime/sandbox/apps/workerpals/src/backends/shared/executor_base.py +9 -0
- package/runtime/sandbox/apps/workerpals/src/docker_executor.ts +42 -1
- package/runtime/sandbox/apps/workerpals/src/execute_job.ts +450 -5
- package/runtime/sandbox/apps/workerpals/src/workerpals_main.ts +13 -26
- package/runtime/sandbox/apps/workerpals/src/worktree_base_ref.ts +141 -0
- package/runtime/sandbox/configs/default.toml +1 -0
- package/runtime/sandbox/packages/shared/src/config.ts +9 -0
|
@@ -47,6 +47,7 @@ import { DockerExecutionExhaustedError, DockerExecutor } from "./docker_executor
|
|
|
47
47
|
import { forceDeleteWorktreePath } from "./common/worktree_cleanup.js";
|
|
48
48
|
import { WorkerServerTransport, type WorkerHeartbeatPayload } from "./common/server_transport.js";
|
|
49
49
|
import { DEFAULT_DOCKER_TIMEOUT_MS, parseDockerTimeoutMs } from "./timeout_policy.js";
|
|
50
|
+
import { resolveFreshWorktreeBaseRef } from "./worktree_base_ref.js";
|
|
50
51
|
|
|
51
52
|
type CommitRef = {
|
|
52
53
|
branch: string;
|
|
@@ -314,6 +315,8 @@ async function reportWorkerLlmUsage(
|
|
|
314
315
|
}
|
|
315
316
|
|
|
316
317
|
function integrationBranchName(): string {
|
|
318
|
+
const configuredIntegrationBranch = CONFIG.sourceControlManager.mainBranch.trim();
|
|
319
|
+
if (configuredIntegrationBranch) return configuredIntegrationBranch;
|
|
317
320
|
const configuredBaseRef = CONFIG.workerpals.baseRef.trim();
|
|
318
321
|
if (!configuredBaseRef) return "main_agents";
|
|
319
322
|
return configuredBaseRef.replace(/^origin\//, "").trim() || "main_agents";
|
|
@@ -665,33 +668,17 @@ async function runJob(
|
|
|
665
668
|
}
|
|
666
669
|
|
|
667
670
|
async function resolveWorktreeBaseRef(repo: string, requestedRef: string): Promise<string> {
|
|
668
|
-
|
|
669
|
-
const integrationRemoteRef = `origin/${integrationBranch}`;
|
|
670
|
-
const candidates = new Set<string>([
|
|
671
|
+
return resolveFreshWorktreeBaseRef({
|
|
671
672
|
requestedRef,
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
`[WorkerPals] Could not refresh ${requestedRef}; continuing with local refs (${fetchResult.stderr || fetchResult.stdout})`,
|
|
682
|
-
);
|
|
683
|
-
}
|
|
684
|
-
candidates.add(branch);
|
|
685
|
-
} else if (requestedRef !== "HEAD") {
|
|
686
|
-
candidates.add(`origin/${requestedRef}`);
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
for (const ref of candidates) {
|
|
690
|
-
const parsed = await git(repo, ["rev-parse", "--verify", "--quiet", ref]);
|
|
691
|
-
if (parsed.ok) return ref;
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
return "HEAD";
|
|
673
|
+
integrationBranch: integrationBranchName(),
|
|
674
|
+
sourceBaseBranch: CONFIG.sourceControlManager.baseBranch,
|
|
675
|
+
git: (args) => git(repo, args),
|
|
676
|
+
log: (level, message) => {
|
|
677
|
+
const line = `[WorkerPals] ${message}`;
|
|
678
|
+
if (level === "warn") console.warn(line);
|
|
679
|
+
else console.log(line);
|
|
680
|
+
},
|
|
681
|
+
});
|
|
695
682
|
}
|
|
696
683
|
|
|
697
684
|
async function createIsolatedWorktree(
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
export type GitBaseRefCommandResult = {
|
|
2
|
+
ok: boolean;
|
|
3
|
+
stdout?: string;
|
|
4
|
+
stderr?: string;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export type GitBaseRefCommand = (args: string[]) => Promise<GitBaseRefCommandResult>;
|
|
8
|
+
|
|
9
|
+
export type WorktreeBaseRefLogLevel = "info" | "warn";
|
|
10
|
+
|
|
11
|
+
export type ResolveFreshWorktreeBaseRefOptions = {
|
|
12
|
+
requestedRef: string;
|
|
13
|
+
integrationBranch: string;
|
|
14
|
+
sourceBaseBranch: string;
|
|
15
|
+
remote?: string;
|
|
16
|
+
git: GitBaseRefCommand;
|
|
17
|
+
log?: (level: WorktreeBaseRefLogLevel, message: string) => void;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
function normalizeBranchName(value: string): string {
|
|
21
|
+
return value
|
|
22
|
+
.trim()
|
|
23
|
+
.replace(/^refs\/heads\//, "")
|
|
24
|
+
.replace(/^origin\//, "")
|
|
25
|
+
.replace(/^\/+|\/+$/g, "");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function normalizeRequestedRef(value: string): string {
|
|
29
|
+
return value.trim() || "HEAD";
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function remoteRef(remote: string, branch: string): string {
|
|
33
|
+
return `${remote}/${branch}`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function isIntegrationBaseRequest(ref: string, integrationBranch: string, remote: string): boolean {
|
|
37
|
+
const normalized = normalizeRequestedRef(ref);
|
|
38
|
+
const branch = normalizeBranchName(normalized);
|
|
39
|
+
return (
|
|
40
|
+
branch === integrationBranch ||
|
|
41
|
+
normalized === remoteRef(remote, integrationBranch) ||
|
|
42
|
+
normalized === `refs/remotes/${remote}/${integrationBranch}`
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function fetchRemoteBranch(
|
|
47
|
+
git: GitBaseRefCommand,
|
|
48
|
+
remote: string,
|
|
49
|
+
branch: string,
|
|
50
|
+
): Promise<GitBaseRefCommandResult> {
|
|
51
|
+
if (!remote || !branch || branch === "HEAD") return { ok: true };
|
|
52
|
+
return git(["fetch", remote, branch, "--quiet"]);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function refExists(git: GitBaseRefCommand, ref: string): Promise<boolean> {
|
|
56
|
+
const result = await git(["rev-parse", "--verify", "--quiet", ref]);
|
|
57
|
+
return result.ok;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function isAncestor(
|
|
61
|
+
git: GitBaseRefCommand,
|
|
62
|
+
ancestorRef: string,
|
|
63
|
+
descendantRef: string,
|
|
64
|
+
): Promise<boolean> {
|
|
65
|
+
const result = await git(["merge-base", "--is-ancestor", ancestorRef, descendantRef]);
|
|
66
|
+
return result.ok;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export async function resolveExistingWorktreeBaseRef(
|
|
70
|
+
options: Omit<ResolveFreshWorktreeBaseRefOptions, "sourceBaseBranch" | "log">,
|
|
71
|
+
): Promise<string> {
|
|
72
|
+
const remote = (options.remote ?? "origin").trim() || "origin";
|
|
73
|
+
const requestedRef = normalizeRequestedRef(options.requestedRef);
|
|
74
|
+
const integrationBranch = normalizeBranchName(options.integrationBranch) || "main_agents";
|
|
75
|
+
const integrationRemoteRef = remoteRef(remote, integrationBranch);
|
|
76
|
+
const candidates = new Set<string>([
|
|
77
|
+
requestedRef,
|
|
78
|
+
integrationRemoteRef,
|
|
79
|
+
integrationBranch,
|
|
80
|
+
"HEAD",
|
|
81
|
+
]);
|
|
82
|
+
|
|
83
|
+
if (requestedRef.startsWith(`${remote}/`)) {
|
|
84
|
+
const branch = requestedRef.slice(`${remote}/`.length);
|
|
85
|
+
await fetchRemoteBranch(options.git, remote, branch);
|
|
86
|
+
candidates.add(branch);
|
|
87
|
+
} else if (requestedRef !== "HEAD") {
|
|
88
|
+
candidates.add(remoteRef(remote, requestedRef));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
for (const ref of candidates) {
|
|
92
|
+
if (await refExists(options.git, ref)) return ref;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return "HEAD";
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export async function resolveFreshWorktreeBaseRef(
|
|
99
|
+
options: ResolveFreshWorktreeBaseRefOptions,
|
|
100
|
+
): Promise<string> {
|
|
101
|
+
const remote = (options.remote ?? "origin").trim() || "origin";
|
|
102
|
+
const requestedRef = normalizeRequestedRef(options.requestedRef);
|
|
103
|
+
const integrationBranch = normalizeBranchName(options.integrationBranch) || "main_agents";
|
|
104
|
+
const sourceBaseBranch = normalizeBranchName(options.sourceBaseBranch) || "main";
|
|
105
|
+
const resolvedRef = await resolveExistingWorktreeBaseRef({
|
|
106
|
+
requestedRef,
|
|
107
|
+
integrationBranch,
|
|
108
|
+
remote,
|
|
109
|
+
git: options.git,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
if (
|
|
113
|
+
!sourceBaseBranch ||
|
|
114
|
+
sourceBaseBranch === integrationBranch ||
|
|
115
|
+
!isIntegrationBaseRequest(requestedRef, integrationBranch, remote)
|
|
116
|
+
) {
|
|
117
|
+
return resolvedRef;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const sourceBaseRef = remoteRef(remote, sourceBaseBranch);
|
|
121
|
+
const fetchSource = await fetchRemoteBranch(options.git, remote, sourceBaseBranch);
|
|
122
|
+
if (!fetchSource.ok) {
|
|
123
|
+
options.log?.(
|
|
124
|
+
"warn",
|
|
125
|
+
`Could not refresh ${sourceBaseRef}; checking local ref before keeping ${resolvedRef} (${fetchSource.stderr || fetchSource.stdout || "fetch failed"}).`,
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (!(await refExists(options.git, sourceBaseRef))) return resolvedRef;
|
|
130
|
+
|
|
131
|
+
if (resolvedRef !== "HEAD" && (await refExists(options.git, resolvedRef))) {
|
|
132
|
+
const sourceAlreadyIncluded = await isAncestor(options.git, sourceBaseRef, resolvedRef);
|
|
133
|
+
if (sourceAlreadyIncluded) return resolvedRef;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
options.log?.(
|
|
137
|
+
"warn",
|
|
138
|
+
`Worktree base ${resolvedRef} does not contain ${sourceBaseRef}; using ${sourceBaseRef} for new WorkerPal jobs to avoid stale integration-branch checkouts.`,
|
|
139
|
+
);
|
|
140
|
+
return sourceBaseRef;
|
|
141
|
+
}
|
|
@@ -128,6 +128,7 @@ export interface PushPalsConfig {
|
|
|
128
128
|
enabled: boolean;
|
|
129
129
|
killSwitchEnabled: boolean;
|
|
130
130
|
tickIntervalMs: number;
|
|
131
|
+
startupGraceMs: number;
|
|
131
132
|
heartbeatLogMs: number;
|
|
132
133
|
visionContextMaxChars: number;
|
|
133
134
|
ideationBudgetMs: number;
|
|
@@ -1671,6 +1672,14 @@ export function loadPushPalsConfig(options: LoadOptions = {}): PushPalsConfig {
|
|
|
1671
1672
|
120_000,
|
|
1672
1673
|
),
|
|
1673
1674
|
),
|
|
1675
|
+
startupGraceMs: Math.max(
|
|
1676
|
+
0,
|
|
1677
|
+
asInt(
|
|
1678
|
+
parseIntEnv("REMOTEBUDDY_AUTONOMY_STARTUP_GRACE_MS") ??
|
|
1679
|
+
remoteAutonomyNode.startup_grace_ms,
|
|
1680
|
+
120_000,
|
|
1681
|
+
),
|
|
1682
|
+
),
|
|
1674
1683
|
heartbeatLogMs: Math.max(
|
|
1675
1684
|
1_000,
|
|
1676
1685
|
asInt(
|