@pushpalsdev/cli 1.1.8 → 1.1.10
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 +68 -5
- package/monitor-ui/+not-found.html +1 -1
- package/monitor-ui/_expo/static/js/web/{entry-22a236a301d5ba71c53234f142ec71d4.js → entry-ff425ab85ad13c1920b8ee00abfae7dd.js} +1139 -1139
- package/monitor-ui/_expo/static/js/web/{index-968878738b5a9ca32445d688cec9db60.js → index-ec13ec62e2b37ed3c5f6d324ef6784e1.js} +6 -6
- package/monitor-ui/_sitemap.html +1 -1
- package/monitor-ui/index.html +1 -1
- package/monitor-ui/modal.html +1 -1
- package/package.json +1 -1
- package/runtime/sandbox/apps/workerpals/src/backends/openai_codex/test_openai_codex_runtime_config.py +48 -0
- package/runtime/sandbox/apps/workerpals/src/backends/shared/executor_base.py +107 -0
- package/runtime/sandbox/apps/workerpals/src/execute_job.ts +486 -6
- package/runtime/sandbox/apps/workerpals/src/workerpals_main.ts +95 -41
|
@@ -67,6 +67,7 @@ const DEFAULT_LLM_MODEL = "local-model";
|
|
|
67
67
|
const CODEX_UNAVAILABLE_WORKER_EXIT_CODE = 86;
|
|
68
68
|
const CODEX_UNAVAILABLE_DOCKER_SHUTDOWN_GRACE_MS = 5_000;
|
|
69
69
|
const CODEX_UNAVAILABLE_WORKER_FORCE_EXIT_MS = 4_000;
|
|
70
|
+
const DEFAULT_JOB_PROGRESS_LOG_EVERY_MS = 60_000;
|
|
70
71
|
const CONFIG = loadPushPalsConfig();
|
|
71
72
|
const LOG = new Logger("WorkerPals");
|
|
72
73
|
|
|
@@ -197,7 +198,12 @@ async function reportToolRunForUnsuccessfulJob(args: {
|
|
|
197
198
|
if (record.failureClass === "unknown" && record.tool === "shell") return;
|
|
198
199
|
|
|
199
200
|
try {
|
|
200
|
-
const response = await postJsonWithTimeout(
|
|
201
|
+
const response = await postJsonWithTimeout(
|
|
202
|
+
`${args.opts.server}/tool-runs`,
|
|
203
|
+
args.headers,
|
|
204
|
+
record,
|
|
205
|
+
5_000,
|
|
206
|
+
);
|
|
201
207
|
if (!response.ok) {
|
|
202
208
|
const detail = await response.text().catch(() => "");
|
|
203
209
|
console.warn(
|
|
@@ -315,6 +321,13 @@ function formatDurationMs(durationMs: number): string {
|
|
|
315
321
|
return `${minutes}m ${seconds}s`;
|
|
316
322
|
}
|
|
317
323
|
|
|
324
|
+
function resolveJobProgressLogEveryMs(): number {
|
|
325
|
+
const raw = Number.parseInt(process.env.PUSHPALS_WORKERPAL_PROGRESS_LOG_MS ?? "", 10);
|
|
326
|
+
if (Number.isFinite(raw) && raw === 0) return 0;
|
|
327
|
+
if (Number.isFinite(raw) && raw >= 10_000) return raw;
|
|
328
|
+
return DEFAULT_JOB_PROGRESS_LOG_EVERY_MS;
|
|
329
|
+
}
|
|
330
|
+
|
|
318
331
|
function sanitizeJobLogLine(line: string): string {
|
|
319
332
|
// Strip ANSI escape/control sequences and collapse whitespace.
|
|
320
333
|
const cleaned = line
|
|
@@ -985,8 +998,7 @@ function failNoChangeReviewFixJob(jobId: string, result: WorkerJobResult): Worke
|
|
|
985
998
|
return {
|
|
986
999
|
...result,
|
|
987
1000
|
ok: false,
|
|
988
|
-
summary:
|
|
989
|
-
`Rejected review-fix job ${jobId} produced no code changes; refusing unchanged branch re-review.`,
|
|
1001
|
+
summary: `Rejected review-fix job ${jobId} produced no code changes; refusing unchanged branch re-review.`,
|
|
990
1002
|
stderr: [
|
|
991
1003
|
result.stderr,
|
|
992
1004
|
"Review-fix jobs must make at least one concrete code/test/docs change before requesting another review.",
|
|
@@ -1002,9 +1014,7 @@ function taskExecuteOrigin(params: Record<string, unknown> | undefined): "user"
|
|
|
1002
1014
|
if (!params) return "user";
|
|
1003
1015
|
if (params.origin === "autonomy") return "autonomy";
|
|
1004
1016
|
const autonomy = params.autonomy;
|
|
1005
|
-
return autonomy && typeof autonomy === "object" && !Array.isArray(autonomy)
|
|
1006
|
-
? "autonomy"
|
|
1007
|
-
: "user";
|
|
1017
|
+
return autonomy && typeof autonomy === "object" && !Array.isArray(autonomy) ? "autonomy" : "user";
|
|
1008
1018
|
}
|
|
1009
1019
|
|
|
1010
1020
|
async function enqueueCompletion(
|
|
@@ -1109,15 +1119,19 @@ async function failActiveJobOnShutdown(
|
|
|
1109
1119
|
runtimeState.currentSessionId &&
|
|
1110
1120
|
shouldEmitDirectSessionJobEvent({ ok: false, statusPersistedToServer })
|
|
1111
1121
|
) {
|
|
1112
|
-
await transport.queueSessionCommand(
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1122
|
+
await transport.queueSessionCommand(
|
|
1123
|
+
runtimeState.currentSessionId,
|
|
1124
|
+
{
|
|
1125
|
+
type: "job_failed",
|
|
1126
|
+
payload: {
|
|
1127
|
+
jobId: activeJobId,
|
|
1128
|
+
message,
|
|
1129
|
+
detail,
|
|
1130
|
+
},
|
|
1131
|
+
from: `worker:${opts.workerId}`,
|
|
1118
1132
|
},
|
|
1119
|
-
|
|
1120
|
-
|
|
1133
|
+
{ priority: "high" },
|
|
1134
|
+
);
|
|
1121
1135
|
}
|
|
1122
1136
|
}
|
|
1123
1137
|
|
|
@@ -1224,10 +1238,7 @@ async function workerLoop(
|
|
|
1224
1238
|
const job = data.job;
|
|
1225
1239
|
|
|
1226
1240
|
if (job) {
|
|
1227
|
-
if (
|
|
1228
|
-
dockerExecutor &&
|
|
1229
|
-
dockerExecutor.shouldPrepareMergeConflictJobBeforeExecution(job)
|
|
1230
|
-
) {
|
|
1241
|
+
if (dockerExecutor && dockerExecutor.shouldPrepareMergeConflictJobBeforeExecution(job)) {
|
|
1231
1242
|
const deferMs = dockerExecutor.recommendedMergeConflictDeferMs();
|
|
1232
1243
|
const deferred = await deferClaimedJobForMaintenance(opts, headers, job.id, deferMs);
|
|
1233
1244
|
if (!deferred.ok) {
|
|
@@ -1325,50 +1336,86 @@ async function workerLoop(
|
|
|
1325
1336
|
}, heartbeatEveryMs);
|
|
1326
1337
|
|
|
1327
1338
|
if (job.sessionId) {
|
|
1328
|
-
await transport.queueSessionCommand(
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1339
|
+
await transport.queueSessionCommand(
|
|
1340
|
+
job.sessionId,
|
|
1341
|
+
{
|
|
1342
|
+
type: "job_claimed",
|
|
1343
|
+
payload: { jobId: job.id, workerId: opts.workerId },
|
|
1344
|
+
from: `worker:${opts.workerId}`,
|
|
1345
|
+
},
|
|
1346
|
+
{ priority: "high" },
|
|
1347
|
+
);
|
|
1333
1348
|
}
|
|
1334
1349
|
|
|
1335
1350
|
let stdoutSeq = 0;
|
|
1336
1351
|
let stderrSeq = 0;
|
|
1337
1352
|
let lastCleanLog = "";
|
|
1338
1353
|
let lastCleanLogAt = 0;
|
|
1354
|
+
let lastForwardedJobLogAt = Date.now();
|
|
1339
1355
|
|
|
1340
|
-
const
|
|
1341
|
-
? (stream: "stdout" | "stderr", line: string) => {
|
|
1356
|
+
const emitJobLog = job.sessionId
|
|
1357
|
+
? (stream: "stdout" | "stderr", line: string): boolean => {
|
|
1342
1358
|
const cleaned = sanitizeJobLogLine(line);
|
|
1343
|
-
if (!cleaned) return;
|
|
1344
|
-
// Print executor logs locally only in debug mode.
|
|
1345
|
-
if (LOG.isDebugEnabled()) LOG.debug(`[${stream}] ${cleaned}`);
|
|
1359
|
+
if (!cleaned) return false;
|
|
1346
1360
|
|
|
1347
1361
|
// Drop high-frequency terminal progress redraw spam; keep meaningful lines.
|
|
1348
|
-
if (isNoisyProgressLine(cleaned)) return;
|
|
1362
|
+
if (isNoisyProgressLine(cleaned)) return false;
|
|
1349
1363
|
|
|
1350
1364
|
// Collapse very noisy duplicate lines emitted in tight loops.
|
|
1351
1365
|
const now = Date.now();
|
|
1352
|
-
if (cleaned === lastCleanLog && now - lastCleanLogAt < 1_000) return;
|
|
1366
|
+
if (cleaned === lastCleanLog && now - lastCleanLogAt < 1_000) return false;
|
|
1353
1367
|
lastCleanLog = cleaned;
|
|
1354
1368
|
lastCleanLogAt = now;
|
|
1369
|
+
lastForwardedJobLogAt = now;
|
|
1355
1370
|
const logTs = new Date(now).toISOString();
|
|
1356
1371
|
|
|
1357
1372
|
const seq = stream === "stdout" ? ++stdoutSeq : ++stderrSeq;
|
|
1358
|
-
void transport.queueSessionCommand(
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1373
|
+
void transport.queueSessionCommand(
|
|
1374
|
+
job.sessionId,
|
|
1375
|
+
{
|
|
1376
|
+
type: "job_log",
|
|
1377
|
+
payload: { jobId: job.id, stream, seq, line: cleaned, ts: logTs },
|
|
1378
|
+
from: `worker:${opts.workerId}`,
|
|
1379
|
+
},
|
|
1380
|
+
{ droppable: true },
|
|
1381
|
+
);
|
|
1363
1382
|
void transport.queueJobLog(job.id, {
|
|
1364
1383
|
stream,
|
|
1365
1384
|
seq,
|
|
1366
1385
|
message: cleaned,
|
|
1367
1386
|
ts: logTs,
|
|
1368
1387
|
});
|
|
1388
|
+
return true;
|
|
1389
|
+
}
|
|
1390
|
+
: undefined;
|
|
1391
|
+
|
|
1392
|
+
const onLog = emitJobLog
|
|
1393
|
+
? (stream: "stdout" | "stderr", line: string) => {
|
|
1394
|
+
const cleaned = sanitizeJobLogLine(line);
|
|
1395
|
+
if (LOG.isDebugEnabled() && cleaned) LOG.debug(`[${stream}] ${cleaned}`);
|
|
1396
|
+
emitJobLog(stream, line);
|
|
1369
1397
|
}
|
|
1370
1398
|
: undefined;
|
|
1371
1399
|
|
|
1400
|
+
const jobClaimedAtMs = Date.now();
|
|
1401
|
+
const jobProgressLogEveryMs = resolveJobProgressLogEveryMs();
|
|
1402
|
+
const jobProgressTimer =
|
|
1403
|
+
emitJobLog && jobProgressLogEveryMs > 0
|
|
1404
|
+
? setInterval(() => {
|
|
1405
|
+
const now = Date.now();
|
|
1406
|
+
const quietForMs = Math.max(0, now - lastForwardedJobLogAt);
|
|
1407
|
+
if (quietForMs < jobProgressLogEveryMs) return;
|
|
1408
|
+
emitJobLog(
|
|
1409
|
+
"stdout",
|
|
1410
|
+
`[WorkerPals] Job ${job.id} still running after ${formatDurationMs(
|
|
1411
|
+
now - jobClaimedAtMs,
|
|
1412
|
+
)} (kind=${job.kind}, worker=${opts.workerId}, quiet_for=${formatDurationMs(
|
|
1413
|
+
quietForMs,
|
|
1414
|
+
)}).`,
|
|
1415
|
+
);
|
|
1416
|
+
}, jobProgressLogEveryMs)
|
|
1417
|
+
: null;
|
|
1418
|
+
|
|
1372
1419
|
let directWorktreePath: string | null = null;
|
|
1373
1420
|
let executionRepo = opts.repo;
|
|
1374
1421
|
let result: WorkerJobResult | null = null;
|
|
@@ -1611,11 +1658,15 @@ async function workerLoop(
|
|
|
1611
1658
|
durationMs: jobDurationMs,
|
|
1612
1659
|
phase: job.kind,
|
|
1613
1660
|
});
|
|
1614
|
-
const response = await postJsonWithTimeout(
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1661
|
+
const response = await postJsonWithTimeout(
|
|
1662
|
+
`${opts.server}/jobs/${job.id}/fail`,
|
|
1663
|
+
headers,
|
|
1664
|
+
{
|
|
1665
|
+
message: result.summary,
|
|
1666
|
+
detail: redactSensitiveText(result.stderr ?? ""),
|
|
1667
|
+
durationMs: jobDurationMs,
|
|
1668
|
+
},
|
|
1669
|
+
);
|
|
1619
1670
|
statusPersistedToServer = response.ok;
|
|
1620
1671
|
console.log(
|
|
1621
1672
|
`[WorkerPals] Job ${job.id} failed in ${formatDurationMs(jobDurationMs)}: ${result.summary}`,
|
|
@@ -1703,6 +1754,7 @@ async function workerLoop(
|
|
|
1703
1754
|
}
|
|
1704
1755
|
} finally {
|
|
1705
1756
|
clearInterval(busyHeartbeat);
|
|
1757
|
+
if (jobProgressTimer) clearInterval(jobProgressTimer);
|
|
1706
1758
|
if (recycleWorkerAfterJob) {
|
|
1707
1759
|
runtimeState.shutdownRequested = true;
|
|
1708
1760
|
const forceExitTimer = setTimeout(() => {
|
|
@@ -1895,7 +1947,9 @@ async function main(): Promise<void> {
|
|
|
1895
1947
|
},
|
|
1896
1948
|
}),
|
|
1897
1949
|
);
|
|
1898
|
-
await withTimeout(
|
|
1950
|
+
await withTimeout(
|
|
1951
|
+
failActiveJobOnShutdown(opts, headers, runtimeState, transport, signalName),
|
|
1952
|
+
);
|
|
1899
1953
|
await withTimeout(transport.flush());
|
|
1900
1954
|
if (dockerExecutor) {
|
|
1901
1955
|
await withTimeout(
|