@pushpalsdev/cli 1.1.21 → 1.1.23
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 +25 -1
- package/package.json +1 -1
- package/runtime/sandbox/apps/workerpals/src/backends/openai_codex/openai_codex_executor.py +288 -31
- package/runtime/sandbox/apps/workerpals/src/backends/openai_codex/test_openai_codex_runtime_config.py +505 -0
- package/runtime/sandbox/apps/workerpals/src/common/types.ts +69 -0
- package/runtime/sandbox/apps/workerpals/src/docker_executor.ts +75 -16
- package/runtime/sandbox/apps/workerpals/src/execute_job.ts +334 -19
- package/runtime/sandbox/apps/workerpals/src/job_runner.ts +3 -0
- package/runtime/sandbox/apps/workerpals/src/workerpals_main.ts +131 -3
|
@@ -48,6 +48,7 @@ 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
50
|
import { resolveFreshWorktreeBaseRef } from "./worktree_base_ref.js";
|
|
51
|
+
import type { JobDiagnostics, JobPhaseSpanDiagnostics } from "./common/types.js";
|
|
51
52
|
|
|
52
53
|
type CommitRef = {
|
|
53
54
|
branch: string;
|
|
@@ -417,6 +418,67 @@ function inferWorkerJobPhaseFromLogLine(line: string): WorkerJobPhase | null {
|
|
|
417
418
|
return null;
|
|
418
419
|
}
|
|
419
420
|
|
|
421
|
+
function mergeWorkerDiagnostics(
|
|
422
|
+
base: JobDiagnostics | undefined,
|
|
423
|
+
extra: JobDiagnostics,
|
|
424
|
+
): JobDiagnostics {
|
|
425
|
+
return {
|
|
426
|
+
...(base ?? {}),
|
|
427
|
+
...extra,
|
|
428
|
+
attempts: [...(base?.attempts ?? []), ...(extra.attempts ?? [])],
|
|
429
|
+
phaseSpans: [...(base?.phaseSpans ?? []), ...(extra.phaseSpans ?? [])],
|
|
430
|
+
validationRuns: [...(base?.validationRuns ?? []), ...(extra.validationRuns ?? [])],
|
|
431
|
+
patchSnapshots: [...(base?.patchSnapshots ?? []), ...(extra.patchSnapshots ?? [])],
|
|
432
|
+
terminal:
|
|
433
|
+
base?.terminal || extra.terminal
|
|
434
|
+
? {
|
|
435
|
+
...(base?.terminal ?? {}),
|
|
436
|
+
...(extra.terminal ?? {}),
|
|
437
|
+
metadata: {
|
|
438
|
+
...(base?.terminal?.metadata ?? {}),
|
|
439
|
+
...(extra.terminal?.metadata ?? {}),
|
|
440
|
+
},
|
|
441
|
+
}
|
|
442
|
+
: undefined,
|
|
443
|
+
metadata: {
|
|
444
|
+
...(extra.metadata ?? {}),
|
|
445
|
+
...(base?.metadata ?? {}),
|
|
446
|
+
},
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function inferWorkerTerminalFailureClass(result: JobResult): string {
|
|
451
|
+
if (result.ok) return "success";
|
|
452
|
+
const text = `${result.summary ?? ""}\n${result.stderr ?? ""}\n${result.stdout ?? ""}`.toLowerCase();
|
|
453
|
+
if (/timed out|timeout|signal 15|terminated|exit 143|exit 137/.test(text)) return "timeout";
|
|
454
|
+
if (/no publishable|non-publishable|node_modules/.test(text)) return "artifact_only_no_publishable_patch";
|
|
455
|
+
if (/validationgate|validation/.test(text)) return "validation";
|
|
456
|
+
if (/scopegate|scope/.test(text)) return "scope";
|
|
457
|
+
if (/criticgate|critic/.test(text)) return "critic";
|
|
458
|
+
if (/publish/.test(text)) return "publish";
|
|
459
|
+
return "worker_failure";
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function buildPhaseSpanDiagnostics(
|
|
463
|
+
spans: Array<{ phase: WorkerJobPhase; startedAtMs: number; finishedAtMs?: number }>,
|
|
464
|
+
attempt: number,
|
|
465
|
+
fallbackFinishedAtMs: number,
|
|
466
|
+
outcome: string,
|
|
467
|
+
): JobPhaseSpanDiagnostics[] {
|
|
468
|
+
return spans.slice(0, 32).map((span) => {
|
|
469
|
+
const startedAtMs = Math.max(0, span.startedAtMs);
|
|
470
|
+
const finishedAtMs = Math.max(startedAtMs, span.finishedAtMs ?? fallbackFinishedAtMs);
|
|
471
|
+
return {
|
|
472
|
+
attempt,
|
|
473
|
+
phase: span.phase,
|
|
474
|
+
startedAt: new Date(startedAtMs).toISOString(),
|
|
475
|
+
finishedAt: new Date(finishedAtMs).toISOString(),
|
|
476
|
+
durationMs: finishedAtMs - startedAtMs,
|
|
477
|
+
outcome,
|
|
478
|
+
};
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
|
|
420
482
|
export function shouldEmitDirectSessionJobEvent(options: {
|
|
421
483
|
ok: boolean;
|
|
422
484
|
statusPersistedToServer: boolean;
|
|
@@ -1417,8 +1479,21 @@ async function workerLoop(
|
|
|
1417
1479
|
let stderrSeq = 0;
|
|
1418
1480
|
let lastCleanLog = "";
|
|
1419
1481
|
let lastCleanLogAt = 0;
|
|
1420
|
-
|
|
1482
|
+
const jobClaimedAtMs = Date.now();
|
|
1483
|
+
let lastForwardedJobLogAt = jobClaimedAtMs;
|
|
1421
1484
|
let currentJobPhase: WorkerJobPhase | null = null;
|
|
1485
|
+
const phaseSpans: Array<{
|
|
1486
|
+
phase: WorkerJobPhase;
|
|
1487
|
+
startedAtMs: number;
|
|
1488
|
+
finishedAtMs?: number;
|
|
1489
|
+
}> = [];
|
|
1490
|
+
const noteJobPhase = (phase: WorkerJobPhase | null, atMs = Date.now()): void => {
|
|
1491
|
+
if (!phase || phase === currentJobPhase) return;
|
|
1492
|
+
const previous = phaseSpans[phaseSpans.length - 1];
|
|
1493
|
+
if (previous && previous.finishedAtMs == null) previous.finishedAtMs = atMs;
|
|
1494
|
+
currentJobPhase = phase;
|
|
1495
|
+
phaseSpans.push({ phase, startedAtMs: atMs });
|
|
1496
|
+
};
|
|
1422
1497
|
|
|
1423
1498
|
const emitJobLog = job.sessionId
|
|
1424
1499
|
? (stream: "stdout" | "stderr", line: string): boolean => {
|
|
@@ -1434,7 +1509,7 @@ async function workerLoop(
|
|
|
1434
1509
|
lastCleanLog = cleaned;
|
|
1435
1510
|
lastCleanLogAt = now;
|
|
1436
1511
|
lastForwardedJobLogAt = now;
|
|
1437
|
-
|
|
1512
|
+
noteJobPhase(inferWorkerJobPhaseFromLogLine(cleaned), now);
|
|
1438
1513
|
const logTs = new Date(now).toISOString();
|
|
1439
1514
|
|
|
1440
1515
|
const seq = stream === "stdout" ? ++stdoutSeq : ++stderrSeq;
|
|
@@ -1472,7 +1547,6 @@ async function workerLoop(
|
|
|
1472
1547
|
}
|
|
1473
1548
|
: undefined;
|
|
1474
1549
|
|
|
1475
|
-
const jobClaimedAtMs = Date.now();
|
|
1476
1550
|
const jobProgressLogEveryMs = resolveJobProgressLogEveryMs();
|
|
1477
1551
|
const jobProgressTimer =
|
|
1478
1552
|
emitJobLog && jobProgressLogEveryMs > 0
|
|
@@ -1673,6 +1747,57 @@ async function workerLoop(
|
|
|
1673
1747
|
}
|
|
1674
1748
|
}
|
|
1675
1749
|
|
|
1750
|
+
const finalizedAtMs = Date.now();
|
|
1751
|
+
const jobAttemptRaw = Number((job as { attempt?: unknown }).attempt ?? 1);
|
|
1752
|
+
const jobAttempt =
|
|
1753
|
+
Number.isFinite(jobAttemptRaw) && jobAttemptRaw > 0 ? Math.floor(jobAttemptRaw) : 1;
|
|
1754
|
+
const llm = workerLlmConfig(CONFIG);
|
|
1755
|
+
result = {
|
|
1756
|
+
...result,
|
|
1757
|
+
diagnostics: mergeWorkerDiagnostics(result.diagnostics, {
|
|
1758
|
+
attempts: [
|
|
1759
|
+
{
|
|
1760
|
+
attempt: jobAttempt,
|
|
1761
|
+
workerId: opts.workerId,
|
|
1762
|
+
backend: resolveExecutor(CONFIG),
|
|
1763
|
+
model: llm.model,
|
|
1764
|
+
startedAt: new Date(jobStartedAtMs).toISOString(),
|
|
1765
|
+
finishedAt: new Date(finalizedAtMs).toISOString(),
|
|
1766
|
+
durationMs: Math.max(0, finalizedAtMs - jobStartedAtMs),
|
|
1767
|
+
terminalReason: result.summary,
|
|
1768
|
+
exitCode: result.exitCode ?? (result.ok ? 0 : 1),
|
|
1769
|
+
metadata: {
|
|
1770
|
+
docker: Boolean(dockerExecutor),
|
|
1771
|
+
jobKind: job.kind,
|
|
1772
|
+
provider: llm.provider,
|
|
1773
|
+
cooldownMs: result.cooldownMs ?? 0,
|
|
1774
|
+
},
|
|
1775
|
+
},
|
|
1776
|
+
],
|
|
1777
|
+
phaseSpans: buildPhaseSpanDiagnostics(
|
|
1778
|
+
phaseSpans,
|
|
1779
|
+
jobAttempt,
|
|
1780
|
+
finalizedAtMs,
|
|
1781
|
+
result.ok ? "completed" : result.publishBlocked ? "publish_blocked" : "failed",
|
|
1782
|
+
),
|
|
1783
|
+
terminal: {
|
|
1784
|
+
failureClass: inferWorkerTerminalFailureClass(result),
|
|
1785
|
+
terminalStage: currentJobPhase ?? (result.ok ? "completed" : "worker"),
|
|
1786
|
+
executorBackend: resolveExecutor(CONFIG),
|
|
1787
|
+
summary: result.summary,
|
|
1788
|
+
watchdogFired: /timed out|timeout|signal 15|terminated|exit 143|exit 137/i.test(
|
|
1789
|
+
`${result.summary}\n${result.stderr ?? ""}`,
|
|
1790
|
+
),
|
|
1791
|
+
metadata: {
|
|
1792
|
+
workerId: opts.workerId,
|
|
1793
|
+
docker: Boolean(dockerExecutor),
|
|
1794
|
+
jobKind: job.kind,
|
|
1795
|
+
phase: currentJobPhase,
|
|
1796
|
+
},
|
|
1797
|
+
},
|
|
1798
|
+
}),
|
|
1799
|
+
};
|
|
1800
|
+
|
|
1676
1801
|
let statusPersistedToServer = false;
|
|
1677
1802
|
if (result.publishBlocked) {
|
|
1678
1803
|
await reportToolRunForUnsuccessfulJob({
|
|
@@ -1691,6 +1816,7 @@ async function workerLoop(
|
|
|
1691
1816
|
detail: redactSensitiveText(result.stderr ?? ""),
|
|
1692
1817
|
publishBlocked: result.publishBlocked,
|
|
1693
1818
|
durationMs: jobDurationMs,
|
|
1819
|
+
diagnostics: result.diagnostics,
|
|
1694
1820
|
},
|
|
1695
1821
|
);
|
|
1696
1822
|
statusPersistedToServer = response.ok;
|
|
@@ -1715,6 +1841,7 @@ async function workerLoop(
|
|
|
1715
1841
|
summary: result.summary,
|
|
1716
1842
|
durationMs: jobDurationMs,
|
|
1717
1843
|
prUrl: jobPrUrl,
|
|
1844
|
+
diagnostics: result.diagnostics,
|
|
1718
1845
|
artifacts: [
|
|
1719
1846
|
...(result.stdout ? [{ kind: "stdout", text: result.stdout }] : []),
|
|
1720
1847
|
...(result.stderr ? [{ kind: "stderr", text: result.stderr }] : []),
|
|
@@ -1741,6 +1868,7 @@ async function workerLoop(
|
|
|
1741
1868
|
message: result.summary,
|
|
1742
1869
|
detail: redactSensitiveText(result.stderr ?? ""),
|
|
1743
1870
|
durationMs: jobDurationMs,
|
|
1871
|
+
diagnostics: result.diagnostics,
|
|
1744
1872
|
},
|
|
1745
1873
|
);
|
|
1746
1874
|
statusPersistedToServer = response.ok;
|