@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.
@@ -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
- let lastForwardedJobLogAt = Date.now();
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
- currentJobPhase = inferWorkerJobPhaseFromLogLine(cleaned) ?? currentJobPhase;
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;