@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.
@@ -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
- const integrationBranch = integrationBranchName();
669
- const integrationRemoteRef = `origin/${integrationBranch}`;
670
- const candidates = new Set<string>([
671
+ return resolveFreshWorktreeBaseRef({
671
672
  requestedRef,
672
- integrationRemoteRef,
673
- integrationBranch,
674
- "HEAD",
675
- ]);
676
- if (requestedRef.startsWith("origin/")) {
677
- const branch = requestedRef.slice("origin/".length);
678
- const fetchResult = await git(repo, ["fetch", "origin", branch, "--quiet"]);
679
- if (!fetchResult.ok) {
680
- console.warn(
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
+ }
@@ -75,6 +75,7 @@ session_id = "remotebuddy-dev"
75
75
  enabled = true
76
76
  kill_switch_enabled = false
77
77
  tick_interval_ms = 300000
78
+ startup_grace_ms = 120000
78
79
  heartbeat_log_ms = 30000
79
80
  vision_context_max_chars = 65536
80
81
  ideation_budget_ms = 20000
@@ -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(