@pushpalsdev/cli 1.0.28 → 1.0.30
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
CHANGED
|
@@ -1946,8 +1946,14 @@ function cleanupLegacyRuntimeBinaryLayouts(runtimeRoot, platformKey, activeBinDi
|
|
|
1946
1946
|
function buildEmbeddedRuntimeEnv(baseEnv, opts) {
|
|
1947
1947
|
const env = normalizeChildProcessEnv(baseEnv);
|
|
1948
1948
|
const useRuntimeConfig = opts.useRuntimeConfig !== false;
|
|
1949
|
+
const inherited = { ...env };
|
|
1950
|
+
if (!useRuntimeConfig) {
|
|
1951
|
+
delete inherited.PUSHPALS_CONFIG_DIR_OVERRIDE;
|
|
1952
|
+
delete inherited.PUSHPALS_WORKERPALS_SANDBOX_ROOT;
|
|
1953
|
+
delete inherited.PUSHPALS_RUNTIME_TAG;
|
|
1954
|
+
}
|
|
1949
1955
|
return {
|
|
1950
|
-
...
|
|
1956
|
+
...inherited,
|
|
1951
1957
|
PUSHPALS_REPO_ROOT_OVERRIDE: opts.repoRoot,
|
|
1952
1958
|
PUSHPALS_PROJECT_ROOT_OVERRIDE: opts.repoRoot,
|
|
1953
1959
|
...useRuntimeConfig ? {
|
|
@@ -2484,6 +2490,36 @@ async function resolveWorkerpalDockerProbe(cwd, env, platform = process.platform
|
|
|
2484
2490
|
var WORKERPAL_SANDBOX_RUNTIME_TAG_LABEL = "pushpals.runtime_tag";
|
|
2485
2491
|
var WORKERPAL_SANDBOX_COMPONENT_LABEL = "pushpals.component=workerpals-sandbox";
|
|
2486
2492
|
var WORKERPAL_WARM_COMPONENT_LABEL = "pushpals.component=workerpals-warm";
|
|
2493
|
+
var SOURCE_CONTROL_MANAGER_TEMP_BRANCH_PREFIX = "_source_control_manager/";
|
|
2494
|
+
function normalizeFsPathForComparison(value) {
|
|
2495
|
+
const resolved = resolve4(String(value ?? "").trim()).replace(/\\/g, "/").replace(/\/+$/, "");
|
|
2496
|
+
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
2497
|
+
}
|
|
2498
|
+
function parseGitWorktreeListPorcelain(stdout) {
|
|
2499
|
+
const entries = [];
|
|
2500
|
+
const blocks = String(stdout ?? "").split(/\r?\n\r?\n/g).map((block) => block.trim()).filter(Boolean);
|
|
2501
|
+
for (const block of blocks) {
|
|
2502
|
+
const lines = block.split(/\r?\n/g);
|
|
2503
|
+
const pathLine = lines.find((line) => line.startsWith("worktree "));
|
|
2504
|
+
if (!pathLine)
|
|
2505
|
+
continue;
|
|
2506
|
+
const branchLine = lines.find((line) => line.startsWith("branch "));
|
|
2507
|
+
entries.push({
|
|
2508
|
+
path: pathLine.slice("worktree ".length).trim(),
|
|
2509
|
+
branch: branchLine ? branchLine.slice("branch ".length).trim() : null,
|
|
2510
|
+
detached: lines.includes("detached")
|
|
2511
|
+
});
|
|
2512
|
+
}
|
|
2513
|
+
return entries;
|
|
2514
|
+
}
|
|
2515
|
+
function isWorkerpalEphemeralWorktreePath(repoRoot, worktreePath) {
|
|
2516
|
+
const expectedPrefix = `${normalizeFsPathForComparison(join2(repoRoot, ".worktrees"))}/`;
|
|
2517
|
+
const normalizedPath = normalizeFsPathForComparison(worktreePath);
|
|
2518
|
+
if (!normalizedPath.startsWith(expectedPrefix))
|
|
2519
|
+
return false;
|
|
2520
|
+
const leaf = basename(normalizedPath);
|
|
2521
|
+
return /^(job|selfcheck)-.*-workerpal-[a-z0-9._-]+/i.test(leaf);
|
|
2522
|
+
}
|
|
2487
2523
|
function resolveConfiguredDockerExecutable(env, platform = process.platform) {
|
|
2488
2524
|
const configured = String(env.PUSHPALS_DOCKER_BIN_ABSOLUTE ?? env.PUSHPALS_DOCKER_BIN ?? (platform === "win32" ? "docker.exe" : "docker")).trim();
|
|
2489
2525
|
return configured || (platform === "win32" ? "docker.exe" : "docker");
|
|
@@ -2531,6 +2567,80 @@ async function cleanupLingeringWorkerpalWarmContainers(opts) {
|
|
|
2531
2567
|
removed: containerIds.length
|
|
2532
2568
|
};
|
|
2533
2569
|
}
|
|
2570
|
+
async function cleanupLingeringPushPalsGitWorktrees(opts) {
|
|
2571
|
+
const runCommandWithEnvFn = opts.runCommandWithEnvFn ?? runCommandWithEnv;
|
|
2572
|
+
const list = await runCommandWithEnvFn(["git", "worktree", "list", "--porcelain"], opts.repoRoot, opts.env);
|
|
2573
|
+
if (!list.ok) {
|
|
2574
|
+
const detail = list.stderr || list.stdout || `exit ${list.exitCode}`;
|
|
2575
|
+
return {
|
|
2576
|
+
ok: false,
|
|
2577
|
+
detail: `failed to inspect lingering PushPals git artifacts: ${detail}`,
|
|
2578
|
+
removed: 0
|
|
2579
|
+
};
|
|
2580
|
+
}
|
|
2581
|
+
const currentRepoPath = normalizeFsPathForComparison(opts.repoRoot);
|
|
2582
|
+
const removable = parseGitWorktreeListPorcelain(list.stdout).filter((entry) => {
|
|
2583
|
+
const normalizedPath = normalizeFsPathForComparison(entry.path);
|
|
2584
|
+
if (normalizedPath === currentRepoPath)
|
|
2585
|
+
return false;
|
|
2586
|
+
if (entry.branch?.startsWith(`refs/heads/${SOURCE_CONTROL_MANAGER_TEMP_BRANCH_PREFIX}`)) {
|
|
2587
|
+
return true;
|
|
2588
|
+
}
|
|
2589
|
+
return isWorkerpalEphemeralWorktreePath(opts.repoRoot, entry.path);
|
|
2590
|
+
});
|
|
2591
|
+
let removed = 0;
|
|
2592
|
+
const failures = [];
|
|
2593
|
+
for (const entry of removable) {
|
|
2594
|
+
const remove = await runCommandWithEnvFn(["git", "worktree", "remove", "--force", "--force", entry.path], opts.repoRoot, opts.env);
|
|
2595
|
+
if (remove.ok) {
|
|
2596
|
+
removed += 1;
|
|
2597
|
+
continue;
|
|
2598
|
+
}
|
|
2599
|
+
failures.push(`${entry.path}: ${remove.stderr || remove.stdout || `exit ${remove.exitCode}`}`);
|
|
2600
|
+
}
|
|
2601
|
+
const prune = await runCommandWithEnvFn(["git", "worktree", "prune"], opts.repoRoot, opts.env);
|
|
2602
|
+
if (!prune.ok) {
|
|
2603
|
+
failures.push(`prune: ${prune.stderr || prune.stdout || `exit ${prune.exitCode}`}`);
|
|
2604
|
+
}
|
|
2605
|
+
const deleteTempBranches = await runCommandWithEnvFn([
|
|
2606
|
+
"git",
|
|
2607
|
+
"for-each-ref",
|
|
2608
|
+
"--format=%(refname:short)",
|
|
2609
|
+
`refs/heads/${SOURCE_CONTROL_MANAGER_TEMP_BRANCH_PREFIX}`
|
|
2610
|
+
], opts.repoRoot, opts.env);
|
|
2611
|
+
if (!deleteTempBranches.ok) {
|
|
2612
|
+
failures.push(`list temp branches: ${deleteTempBranches.stderr || deleteTempBranches.stdout || `exit ${deleteTempBranches.exitCode}`}`);
|
|
2613
|
+
} else {
|
|
2614
|
+
const branches = deleteTempBranches.stdout.split(/\r?\n/g).map((value) => value.trim()).filter(Boolean);
|
|
2615
|
+
for (const branch of branches) {
|
|
2616
|
+
const deleteResult = await runCommandWithEnvFn(["git", "branch", "-D", branch], opts.repoRoot, opts.env);
|
|
2617
|
+
if (!deleteResult.ok) {
|
|
2618
|
+
failures.push(`${branch}: ${deleteResult.stderr || deleteResult.stdout || `exit ${deleteResult.exitCode}`}`);
|
|
2619
|
+
} else {
|
|
2620
|
+
removed += 1;
|
|
2621
|
+
}
|
|
2622
|
+
}
|
|
2623
|
+
}
|
|
2624
|
+
if (removed === 0 && failures.length === 0) {
|
|
2625
|
+
return {
|
|
2626
|
+
ok: true,
|
|
2627
|
+
detail: "no lingering PushPals git artifacts found",
|
|
2628
|
+
removed: 0
|
|
2629
|
+
};
|
|
2630
|
+
}
|
|
2631
|
+
if (failures.length > 0) {
|
|
2632
|
+
return {
|
|
2633
|
+
ok: false,
|
|
2634
|
+
detail: `removed ${removed} lingering PushPals git artifact(s), but cleanup was incomplete: ${failures.join(" | ")}`,
|
|
2635
|
+
removed
|
|
2636
|
+
};
|
|
2637
|
+
}
|
|
2638
|
+
return {
|
|
2639
|
+
ok: true,
|
|
2640
|
+
detail: `removed ${removed} lingering PushPals git artifact(s)`,
|
|
2641
|
+
removed
|
|
2642
|
+
};
|
|
2643
|
+
}
|
|
2534
2644
|
async function inspectDockerImageRuntimeTag(dockerExecutable, imageName, cwd, env) {
|
|
2535
2645
|
const inspect = await runCommandWithEnv([
|
|
2536
2646
|
dockerExecutable,
|
|
@@ -3843,8 +3953,45 @@ function formatSessionEventLine(event) {
|
|
|
3843
3953
|
}
|
|
3844
3954
|
return null;
|
|
3845
3955
|
}
|
|
3956
|
+
function buildSessionEventReplayFingerprint(event) {
|
|
3957
|
+
const type = String(event.type ?? "").trim().toLowerCase();
|
|
3958
|
+
if (type !== "status")
|
|
3959
|
+
return null;
|
|
3960
|
+
const payload = event.payload ?? {};
|
|
3961
|
+
const source = String(event.from ?? payload.agentId ?? "status").trim().toLowerCase();
|
|
3962
|
+
const state = String(payload.state ?? "").trim().toLowerCase();
|
|
3963
|
+
const detail = String(payload.detail ?? "").trim().toLowerCase();
|
|
3964
|
+
const message = String(payload.message ?? "").trim().toLowerCase();
|
|
3965
|
+
return {
|
|
3966
|
+
source,
|
|
3967
|
+
fingerprint: `${type}:${source}:${state}:${detail}:${message}`
|
|
3968
|
+
};
|
|
3969
|
+
}
|
|
3970
|
+
function createSessionEventReplayFilter() {
|
|
3971
|
+
const seenEventIds = new Set;
|
|
3972
|
+
const lastStatusFingerprintBySource = new Map;
|
|
3973
|
+
return {
|
|
3974
|
+
shouldRender(event) {
|
|
3975
|
+
const eventId = String(event.id ?? "").trim();
|
|
3976
|
+
if (eventId) {
|
|
3977
|
+
if (seenEventIds.has(eventId))
|
|
3978
|
+
return false;
|
|
3979
|
+
seenEventIds.add(eventId);
|
|
3980
|
+
}
|
|
3981
|
+
const replayStatus = buildSessionEventReplayFingerprint(event);
|
|
3982
|
+
if (!replayStatus)
|
|
3983
|
+
return true;
|
|
3984
|
+
const previous = lastStatusFingerprintBySource.get(replayStatus.source);
|
|
3985
|
+
if (previous === replayStatus.fingerprint)
|
|
3986
|
+
return false;
|
|
3987
|
+
lastStatusFingerprintBySource.set(replayStatus.source, replayStatus.fingerprint);
|
|
3988
|
+
return true;
|
|
3989
|
+
}
|
|
3990
|
+
};
|
|
3991
|
+
}
|
|
3846
3992
|
async function runSessionStream(serverUrl, sessionId, client, print, signal) {
|
|
3847
3993
|
let cursor = 0;
|
|
3994
|
+
const replayFilter = createSessionEventReplayFilter();
|
|
3848
3995
|
while (!signal.aborted) {
|
|
3849
3996
|
try {
|
|
3850
3997
|
const response = await fetchWithTimeout(`${serverUrl}/sessions/${encodeURIComponent(sessionId)}/events${buildClientTransportQuery(cursor, client)}`, {}, 15000);
|
|
@@ -3894,6 +4041,8 @@ async function runSessionStream(serverUrl, sessionId, client, print, signal) {
|
|
|
3894
4041
|
cursor = Math.max(cursor, blockCursor, serverCursor);
|
|
3895
4042
|
if (!parsed.envelope)
|
|
3896
4043
|
continue;
|
|
4044
|
+
if (!replayFilter.shouldRender(parsed.envelope))
|
|
4045
|
+
continue;
|
|
3897
4046
|
const line = formatSessionEventLine(parsed.envelope);
|
|
3898
4047
|
if (line)
|
|
3899
4048
|
print(line);
|
|
@@ -4034,6 +4183,19 @@ async function main() {
|
|
|
4034
4183
|
console.log(`[pushpals] ${cleanup.detail} (${phase}).`);
|
|
4035
4184
|
}
|
|
4036
4185
|
};
|
|
4186
|
+
const cleanupPushPalsGitWorktreesIfNeeded = async (phase) => {
|
|
4187
|
+
const cleanup = await cleanupLingeringPushPalsGitWorktrees({
|
|
4188
|
+
repoRoot,
|
|
4189
|
+
env: workerpalDockerPrecheck.env
|
|
4190
|
+
});
|
|
4191
|
+
if (!cleanup.ok) {
|
|
4192
|
+
console.warn(`[pushpals] PushPals worktree cleanup warning (${phase}): ${cleanup.detail}`);
|
|
4193
|
+
return;
|
|
4194
|
+
}
|
|
4195
|
+
if (cleanup.removed > 0) {
|
|
4196
|
+
console.log(`[pushpals] ${cleanup.detail} (${phase}).`);
|
|
4197
|
+
}
|
|
4198
|
+
};
|
|
4037
4199
|
const stopAutoStartedServices = () => {
|
|
4038
4200
|
if (autoStartedServices.length === 0)
|
|
4039
4201
|
return;
|
|
@@ -4056,6 +4218,7 @@ async function main() {
|
|
|
4056
4218
|
}
|
|
4057
4219
|
await stopRuntimeServicesGracefully(services);
|
|
4058
4220
|
await cleanupWorkerpalWarmContainersIfNeeded("cli shutdown");
|
|
4221
|
+
await cleanupPushPalsGitWorktreesIfNeeded("cli shutdown");
|
|
4059
4222
|
};
|
|
4060
4223
|
let serverHealthy = await probeServer(serverUrl);
|
|
4061
4224
|
const serverWasAlreadyHealthy = serverHealthy;
|
|
@@ -4085,6 +4248,7 @@ async function main() {
|
|
|
4085
4248
|
};
|
|
4086
4249
|
if (!serverHealthy) {
|
|
4087
4250
|
await cleanupWorkerpalWarmContainersIfNeeded("startup preflight");
|
|
4251
|
+
await cleanupPushPalsGitWorktreesIfNeeded("startup preflight");
|
|
4088
4252
|
if (!parsed.noAutoStart) {
|
|
4089
4253
|
try {
|
|
4090
4254
|
const startedRuntime = await autoStartRuntimeServices({
|
|
@@ -4381,8 +4545,10 @@ export {
|
|
|
4381
4545
|
ensureWorkerpalDockerImageReady,
|
|
4382
4546
|
ensureRuntimeBinaries,
|
|
4383
4547
|
downloadRuntimeAssetsFromSourceTag,
|
|
4548
|
+
createSessionEventReplayFilter,
|
|
4384
4549
|
copyTrackedRepoPath,
|
|
4385
4550
|
cleanupLingeringWorkerpalWarmContainers,
|
|
4551
|
+
cleanupLingeringPushPalsGitWorktrees,
|
|
4386
4552
|
bundledMonitoringHubNeedsRefresh,
|
|
4387
4553
|
buildWorkerpalSandboxPaths,
|
|
4388
4554
|
buildServiceStopCommand,
|
package/package.json
CHANGED
|
@@ -130,14 +130,51 @@ export function redactSensitiveText(value: string): string {
|
|
|
130
130
|
}
|
|
131
131
|
|
|
132
132
|
export function buildCriticRevisionIssues(
|
|
133
|
-
critic:
|
|
133
|
+
critic:
|
|
134
|
+
| {
|
|
135
|
+
score: number;
|
|
136
|
+
findings?: string[];
|
|
137
|
+
mustFix?: string[];
|
|
138
|
+
revisionGuidance?: string;
|
|
139
|
+
}
|
|
140
|
+
| null
|
|
141
|
+
| undefined,
|
|
134
142
|
qualityCriticMinScore: number,
|
|
135
143
|
): string[] {
|
|
136
144
|
if (!critic) return [];
|
|
137
145
|
if (critic.score >= qualityCriticMinScore) return [];
|
|
138
|
-
|
|
146
|
+
const issues = [
|
|
139
147
|
`Critic score ${critic.score.toFixed(1)} is below required threshold ${qualityCriticMinScore}.`,
|
|
140
148
|
];
|
|
149
|
+
const mustFix = Array.isArray(critic.mustFix) ? critic.mustFix : [];
|
|
150
|
+
const findings = Array.isArray(critic.findings) ? critic.findings : [];
|
|
151
|
+
const revisionGuidance = String(critic.revisionGuidance ?? "").trim();
|
|
152
|
+
const actionableItems = (mustFix.length > 0 ? mustFix : findings)
|
|
153
|
+
.map((entry) => toSingleLine(entry, 180))
|
|
154
|
+
.filter(Boolean)
|
|
155
|
+
.slice(0, 3);
|
|
156
|
+
for (const item of actionableItems) {
|
|
157
|
+
issues.push(mustFix.length > 0 ? `Critic must-fix: ${item}` : `Critic finding: ${item}`);
|
|
158
|
+
}
|
|
159
|
+
if (revisionGuidance) {
|
|
160
|
+
issues.push(`Critic revision guidance: ${toSingleLine(revisionGuidance, 220)}`);
|
|
161
|
+
}
|
|
162
|
+
return issues;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function buildQualityGateRevisionIssues(
|
|
166
|
+
qualityIssues: string[],
|
|
167
|
+
critic: CriticReview | null,
|
|
168
|
+
qualityCriticMinScore: number,
|
|
169
|
+
): string[] {
|
|
170
|
+
const normalizedQualityIssues = qualityIssues
|
|
171
|
+
.map((entry) => String(entry ?? "").trim())
|
|
172
|
+
.filter(Boolean);
|
|
173
|
+
if (!critic || critic.score >= qualityCriticMinScore) {
|
|
174
|
+
return [...normalizedQualityIssues];
|
|
175
|
+
}
|
|
176
|
+
const merged = [...normalizedQualityIssues, ...buildCriticRevisionIssues(critic, qualityCriticMinScore)];
|
|
177
|
+
return [...new Set(merged)];
|
|
141
178
|
}
|
|
142
179
|
|
|
143
180
|
export function resolveReviewFixCompletionBranch(
|
|
@@ -3006,9 +3043,10 @@ export async function executeJob(
|
|
|
3006
3043
|
: executor === "openai_codex"
|
|
3007
3044
|
? await runCodexCriticReview(repo, attemptParams, quality, runtimeConfig, onLog)
|
|
3008
3045
|
: await runTaskCriticReview(repo, attemptParams, quality, runtimeConfig, onLog);
|
|
3046
|
+
const deterministicRequiresRevision = !quality.ok;
|
|
3009
3047
|
const criticRequiresRevision = Boolean(critic && critic.score < qualityCriticMinScore);
|
|
3010
3048
|
|
|
3011
|
-
if (!criticRequiresRevision) {
|
|
3049
|
+
if (!deterministicRequiresRevision && !criticRequiresRevision) {
|
|
3012
3050
|
if (critic) {
|
|
3013
3051
|
onLog?.(
|
|
3014
3052
|
"stdout",
|
|
@@ -3018,10 +3056,11 @@ export async function executeJob(
|
|
|
3018
3056
|
return result;
|
|
3019
3057
|
}
|
|
3020
3058
|
|
|
3021
|
-
const issues
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3059
|
+
const issues = buildQualityGateRevisionIssues(
|
|
3060
|
+
quality.issues,
|
|
3061
|
+
critic,
|
|
3062
|
+
qualityCriticMinScore,
|
|
3063
|
+
);
|
|
3025
3064
|
const issueSummary = issues.map((entry) => toSingleLine(entry, 180)).join(" | ");
|
|
3026
3065
|
if (revisionAttempt >= qualityMaxAutoRevisions) {
|
|
3027
3066
|
if (qualitySoftPassOnExhausted) {
|