ultimate-pi 0.24.0 → 0.25.0

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.
Files changed (48) hide show
  1. package/.pi/extensions/agt-prompt-guard.ts +20 -6
  2. package/.pi/extensions/harness-auto-compact.ts +94 -0
  3. package/.pi/extensions/harness-debate-tools.ts +26 -2
  4. package/.pi/extensions/harness-live-widget.ts +19 -2
  5. package/.pi/extensions/harness-plan-approval.ts +62 -19
  6. package/.pi/extensions/harness-plan-orchestration.ts +140 -0
  7. package/.pi/extensions/harness-run-context.ts +457 -48
  8. package/.pi/extensions/harness-web-tools.ts +1 -0
  9. package/.pi/extensions/policy-gate.ts +9 -0
  10. package/.pi/harness/agents.manifest.json +1 -1
  11. package/.pi/harness/docs/adrs/0056-agent-native-speed-wiring.md +26 -0
  12. package/.pi/harness/env.harness.template +7 -1
  13. package/.pi/lib/harness-auto-approve.ts +140 -0
  14. package/.pi/lib/harness-auto-compact-policy.ts +85 -0
  15. package/.pi/lib/harness-phase-telemetry.ts +7 -0
  16. package/.pi/lib/harness-phase-worker.ts +23 -0
  17. package/.pi/lib/harness-plan-fsm.ts +162 -0
  18. package/.pi/lib/harness-plan-route.ts +134 -0
  19. package/.pi/lib/harness-posthog.ts +4 -1
  20. package/.pi/lib/harness-remediation.ts +79 -0
  21. package/.pi/lib/harness-repair-brief.ts +2 -2
  22. package/.pi/lib/harness-review-parallel.ts +18 -0
  23. package/.pi/lib/harness-run-context.ts +119 -72
  24. package/.pi/lib/harness-spawn-budget.ts +32 -4
  25. package/.pi/lib/harness-spawn-topology.ts +36 -1
  26. package/.pi/lib/harness-subagent-precheck.ts +3 -2
  27. package/.pi/lib/harness-subagent-progress.ts +8 -5
  28. package/.pi/lib/harness-subagents-bridge.ts +14 -12
  29. package/.pi/lib/harness-vcc-settings.ts +36 -0
  30. package/.pi/lib/plan-approval-readiness.ts +9 -5
  31. package/.pi/lib/plan-debate-eligibility-snapshot.ts +90 -0
  32. package/.pi/lib/plan-debate-eligibility.ts +12 -7
  33. package/.pi/lib/plan-debate-focus.ts +23 -11
  34. package/.pi/lib/plan-debate-gate.ts +71 -29
  35. package/.pi/lib/plan-debate-round-status.ts +23 -8
  36. package/.pi/lib/plan-headless-ux.ts +598 -0
  37. package/.pi/lib/plan-human-gates.ts +24 -85
  38. package/.pi/lib/plan-messenger.ts +3 -3
  39. package/.pi/lib/plan-review-gate.ts +56 -0
  40. package/.pi/prompts/harness-abort.md +1 -0
  41. package/.pi/prompts/harness-auto.md +1 -1
  42. package/.pi/prompts/harness-clear.md +6 -6
  43. package/.pi/prompts/harness-plan.md +15 -2
  44. package/.pi/prompts/harness-review.md +2 -2
  45. package/.pi/scripts/harness-project-toggle.mjs +1 -1
  46. package/CHANGELOG.md +10 -0
  47. package/README.md +2 -2
  48. package/package.json +1 -1
@@ -23,6 +23,7 @@ import {
23
23
  resetHarnessPolicyDenyCount,
24
24
  } from "../lib/agt/kill-switch-state.js";
25
25
  import { runAskUser } from "../lib/ask-user/index.js";
26
+ import { isHarnessNonInteractive } from "../lib/ask-user/policy.js";
26
27
  import { claimHarnessGovernanceLoad } from "../lib/extension-load-guard.js";
27
28
  import { getHarnessPackageRoot } from "../lib/harness-paths.js";
28
29
  import {
@@ -40,10 +41,12 @@ import {
40
41
  claimRunOwnership,
41
42
  createFreshRunContext,
42
43
  criticalPathWorkItemIdsFromPlanPacket,
44
+ deleteProjectActiveRun,
43
45
  driftGateActive,
44
46
  ensureReviewOutcomeFromEval,
45
47
  evaluateCrossSessionResume,
46
48
  extractWritePathFromToolInput,
49
+ findActiveRunOwnershipConflict,
47
50
  formatActivePlanBlock,
48
51
  formatCrossSessionResumeMessage,
49
52
  formatPlanContextBlock,
@@ -56,6 +59,7 @@ import {
56
59
  harnessAutoTasksDiffer,
57
60
  hasHarnessAbortSignal,
58
61
  hasPlanUserApproval,
62
+ indexOfLastPlanCommand,
59
63
  inferHarnessPhase,
60
64
  isAmendPlanAllowed,
61
65
  isHarnessBootstrapPrompt,
@@ -80,6 +84,7 @@ import {
80
84
  reconcileReviewRouting,
81
85
  reconcileStaleExecuteCompletion,
82
86
  refreshRunContextProgress,
87
+ releaseForeignQaRunOwnership,
83
88
  relPathUnderActiveRun,
84
89
  resetRunContextForHarnessAuto,
85
90
  resolveArgsForCommand,
@@ -109,6 +114,14 @@ import {
109
114
  } from "../lib/harness-yaml.js";
110
115
  import { isReviewRoundArtifactPath } from "../lib/plan-debate-gate.js";
111
116
  import { isReviewRoundYamlWriteAllowed } from "../lib/plan-debate-write-guard.js";
117
+ import {
118
+ endHeadlessHarnessPrintSession,
119
+ maybeForceHeadlessPlanProgress,
120
+ maybeHeadlessQaAutoExecuteSmoke,
121
+ seedHeadlessTaskClarificationIfNeeded,
122
+ shouldEndHeadlessHarnessPrintSession,
123
+ tryHeadlessAutoPlanFinalize,
124
+ } from "../lib/plan-headless-ux.js";
112
125
  import {
113
126
  formatPlanHumanGateBlock,
114
127
  resolvePlanHumanGateStatus,
@@ -154,6 +167,15 @@ function persistContext(pi: ExtensionAPI, ctx: HarnessRunContext): void {
154
167
  pi.events.emit("harness-run-context:updated", { run_id: ctx.run_id });
155
168
  }
156
169
 
170
+ function notifyHarnessHandoff(
171
+ ctx: { hasUI: boolean; ui: { notify(message: string, type?: string): void } },
172
+ message: string,
173
+ level: "info" | "warning" = "info",
174
+ ): void {
175
+ if (ctx.hasUI) ctx.ui.notify(message, level);
176
+ // Headless (-p/json): appendEntry records handoff; never inject user-visible messages.
177
+ }
178
+
157
179
  const PLAN_REVISION_ARTIFACT_FILES = new Set([
158
180
  "planning-context.yaml",
159
181
  "decomposition.yaml",
@@ -480,7 +502,7 @@ async function applyAbortSignal(input: {
480
502
  entries: unknown[];
481
503
  userPrompt: string;
482
504
  }): Promise<HarnessRunContext | null> {
483
- if (!input.userPrompt.toLowerCase().includes("harness-abort")) {
505
+ if (!hasHarnessAbortSignal(input.userPrompt)) {
484
506
  return input.activeCtx;
485
507
  }
486
508
  const nextCtx =
@@ -498,6 +520,43 @@ async function applyAbortSignal(input: {
498
520
  return nextCtx;
499
521
  }
500
522
 
523
+ function appendAbortPolicyState(
524
+ pi: ExtensionAPI,
525
+ reason: string,
526
+ abortedAt: string,
527
+ ): void {
528
+ pi.appendEntry("harness-policy-state", {
529
+ phase: "plan",
530
+ approvedPlan: false,
531
+ planId: null,
532
+ budgetBypass: false,
533
+ aborted: true,
534
+ abortReason: reason,
535
+ abortedAt,
536
+ updatedAt: abortedAt,
537
+ });
538
+ }
539
+
540
+ function abortActiveRunContext(input: {
541
+ pi: ExtensionAPI;
542
+ activeCtx: HarnessRunContext;
543
+ reason: string;
544
+ }): HarnessRunContext {
545
+ const abortedAt = nowIso();
546
+ input.activeCtx.phase = "plan";
547
+ input.activeCtx.status = "aborted";
548
+ input.activeCtx.plan_ready = false;
549
+ input.activeCtx.last_outcome = "aborted";
550
+ input.activeCtx.last_completed_step = "abort";
551
+ input.activeCtx.next_recommended_command = input.activeCtx.task_summary
552
+ ? `/harness-plan "${input.activeCtx.task_summary}"`
553
+ : '/harness-plan "<task>"';
554
+ input.activeCtx.updated_at = abortedAt;
555
+ appendAbortPolicyState(input.pi, input.reason, abortedAt);
556
+ persistContext(input.pi, input.activeCtx);
557
+ return input.activeCtx;
558
+ }
559
+
501
560
  async function maybeHandleClarificationFollowUp(input: {
502
561
  pi: ExtensionAPI;
503
562
  activeCtx: HarnessRunContext;
@@ -558,7 +617,7 @@ function contextPrompt(systemPrompt: string, activeCtx: HarnessRunContext) {
558
617
  };
559
618
  }
560
619
 
561
- function createNewRunContextForCommand(input: {
620
+ async function createNewRunContextForCommand(input: {
562
621
  pi: ExtensionAPI;
563
622
  activeCtx: HarnessRunContext | null;
564
623
  sessionId: string;
@@ -567,6 +626,18 @@ function createNewRunContextForCommand(input: {
567
626
  userPrompt: string;
568
627
  systemPrompt: string;
569
628
  }) {
629
+ const ownershipConflict = await findActiveRunOwnershipConflict(
630
+ input.projectRoot,
631
+ input.sessionId,
632
+ );
633
+ if (ownershipConflict) {
634
+ return {
635
+ activeCtx: input.activeCtx,
636
+ response: blockRunContextMessage(
637
+ `Another Pi session (${ownershipConflict.ownerPiSessionId}) owns active run ${ownershipConflict.runId}. Finish or abort that run before /harness-new-run.`,
638
+ ),
639
+ };
640
+ }
570
641
  if (input.activeCtx?.status === "active") {
571
642
  input.activeCtx.status = "aborted";
572
643
  input.activeCtx.plan_ready = false;
@@ -649,7 +720,7 @@ type ActiveContextAccess = {
649
720
  set(ctx: HarnessRunContext | null): void;
650
721
  };
651
722
 
652
- const HARNESS_CLEAR_CONFIRM_OPTION = "Delete historical runs";
723
+ const HARNESS_CLEAR_CONFIRM_OPTION = "Delete all harness runs";
653
724
 
654
725
  function isHarnessClearConfirmed(response: unknown): boolean {
655
726
  if (!response || typeof response !== "object") return false;
@@ -672,23 +743,23 @@ function registerHarnessClearCommand(
672
743
  ): void {
673
744
  pi.registerCommand("harness-clear", {
674
745
  description:
675
- "Delete historical harness runs under .pi/harness/runs while preserving the active run",
746
+ "Delete all harness runs under .pi/harness/runs, including the active run",
676
747
  handler: async (_args, ctx) => {
677
748
  const entries = getEntries(ctx);
678
749
  const projectRoot = process.cwd();
679
750
  const latest = active.get() ?? getLatestRunContext(entries);
680
751
  const pointer = await loadProjectActiveRun(projectRoot);
681
- const protectedRunIds = new Set<string>();
682
- if (latest?.run_id) protectedRunIds.add(latest.run_id);
683
- if (pointer?.run_id) protectedRunIds.add(pointer.run_id);
684
- const manifest = await buildHarnessClearManifest(
685
- projectRoot,
686
- protectedRunIds,
687
- );
688
- if (manifest.candidates.length === 0) {
752
+ const activeRunIds = [
753
+ ...new Set(
754
+ [latest?.run_id, pointer?.run_id].filter(Boolean) as string[],
755
+ ),
756
+ ].sort();
757
+ const manifest = await buildHarnessClearManifest(projectRoot);
758
+ const hasTargets =
759
+ manifest.candidates.length > 0 || activeRunIds.length > 0;
760
+ if (!hasTargets) {
689
761
  const message = [
690
- "/harness-clear: no historical run directories eligible for deletion.",
691
- ` protected: ${manifest.protected_run_ids.join(", ") || "(none)"}`,
762
+ "/harness-clear: no harness runs found.",
692
763
  ` skipped: ${manifest.skipped.length}`,
693
764
  ].join("\n");
694
765
  if (ctx.hasUI) ctx.ui.notify(message, "info");
@@ -700,8 +771,10 @@ function registerHarnessClearCommand(
700
771
  });
701
772
  pi.appendEntry("harness-clear-result", {
702
773
  approved: false,
774
+ cleared_all: false,
703
775
  deleted: 0,
704
- protected: manifest.protected_run_ids,
776
+ active_cleared: false,
777
+ active_run_ids: activeRunIds,
705
778
  skipped: manifest.skipped,
706
779
  recorded_at: nowIso(),
707
780
  });
@@ -709,11 +782,12 @@ function registerHarnessClearCommand(
709
782
  }
710
783
  const ask = await runAskUser(
711
784
  {
712
- question: `Delete ${manifest.candidates.length} historical harness run directories?`,
785
+ question: `Delete all ${manifest.candidates.length} harness run directories, including the current run?`,
713
786
  context: [
714
- "Scope: .pi/harness/runs/<run_id> only (historical runs).",
715
- `Preserved active run ids: ${manifest.protected_run_ids.join(", ") || "(none)"}`,
716
- `Candidates: ${manifest.candidates.map((item) => item.run_id).join(", ")}`,
787
+ "Scope: .pi/harness/runs/<run_id> directories plus .pi/harness/active-run.json.",
788
+ "The in-session active run context will also be cleared.",
789
+ `Active run ids: ${activeRunIds.join(", ") || "(none)"}`,
790
+ `Candidates: ${manifest.candidates.map((item) => item.run_id).join(", ") || "(none)"}`,
717
791
  ].join("\n"),
718
792
  options: [HARNESS_CLEAR_CONFIRM_OPTION, "Cancel"],
719
793
  allowSkip: true,
@@ -734,8 +808,10 @@ function registerHarnessClearCommand(
734
808
  });
735
809
  pi.appendEntry("harness-clear-result", {
736
810
  approved: false,
811
+ cleared_all: false,
737
812
  deleted: 0,
738
- protected: manifest.protected_run_ids,
813
+ active_cleared: false,
814
+ active_run_ids: activeRunIds,
739
815
  skipped: manifest.skipped,
740
816
  ask_error: ask.error,
741
817
  recorded_at: nowIso(),
@@ -758,8 +834,10 @@ function registerHarnessClearCommand(
758
834
  });
759
835
  pi.appendEntry("harness-clear-result", {
760
836
  approved: false,
837
+ cleared_all: false,
761
838
  deleted: 0,
762
- protected: manifest.protected_run_ids,
839
+ active_cleared: false,
840
+ active_run_ids: activeRunIds,
763
841
  skipped: manifest.skipped,
764
842
  recorded_at: nowIso(),
765
843
  });
@@ -778,10 +856,13 @@ function registerHarnessClearCommand(
778
856
  });
779
857
  }
780
858
  }
859
+ const activePointerDeleted = await deleteProjectActiveRun(projectRoot);
860
+ active.set(null);
781
861
  const message = [
782
862
  "/harness-clear complete.",
783
863
  ` deleted: ${deleted}`,
784
- ` protected: ${manifest.protected_run_ids.length}`,
864
+ ` active_cleared: true`,
865
+ ` active_pointer_deleted: ${activePointerDeleted}`,
785
866
  ` skipped: ${manifest.skipped.length + failed.length}`,
786
867
  ].join("\n");
787
868
  if (ctx.hasUI) ctx.ui.notify(message, "info");
@@ -793,11 +874,18 @@ function registerHarnessClearCommand(
793
874
  });
794
875
  pi.appendEntry("harness-clear-result", {
795
876
  approved: true,
877
+ cleared_all: failed.length === 0,
796
878
  deleted,
797
- protected: manifest.protected_run_ids,
879
+ active_cleared: true,
880
+ active_pointer_deleted: activePointerDeleted,
881
+ active_run_ids: activeRunIds,
798
882
  skipped: [...manifest.skipped, ...failed],
799
883
  recorded_at: nowIso(),
800
884
  });
885
+ pi.events.emit("harness-runs-cleared", {
886
+ deleted,
887
+ projectRoot,
888
+ });
801
889
  },
802
890
  });
803
891
  }
@@ -1199,13 +1287,7 @@ function handleAgentEndAbort(input: {
1199
1287
  : '/harness-plan "<task>"';
1200
1288
  persistContext(input.pi, input.activeCtx);
1201
1289
  const msg = `Harness aborted. Next: ${input.activeCtx.next_recommended_command}`;
1202
- if (input.ctx.hasUI) input.ctx.ui.notify(msg, "warning");
1203
- else
1204
- input.pi.sendMessage({
1205
- customType: "harness-step-handoff",
1206
- content: msg,
1207
- display: true,
1208
- });
1290
+ notifyHarnessHandoff(input.ctx, msg, "warning");
1209
1291
  }
1210
1292
 
1211
1293
  async function updatePlanReadinessAfterAgent(input: {
@@ -1242,13 +1324,7 @@ async function updatePlanReadinessAfterAgent(input: {
1242
1324
  ) {
1243
1325
  const msg =
1244
1326
  "A draft plan-packet.yaml is on disk, but user approval was not recorded. Complete Review Gate (debate rounds + harness_debate_consensus), then call approve_plan; use create_plan only after Approve.";
1245
- if (input.ctx.hasUI) input.ctx.ui.notify(msg, "warning");
1246
- else
1247
- input.pi.sendMessage({
1248
- customType: "harness-plan-packet",
1249
- content: msg,
1250
- display: true,
1251
- });
1327
+ notifyHarnessHandoff(input.ctx, msg, "warning");
1252
1328
  }
1253
1329
  persistContext(input.pi, input.activeCtx);
1254
1330
  }
@@ -1326,6 +1402,16 @@ function registerPlanApprovalCapture(
1326
1402
  });
1327
1403
  }
1328
1404
 
1405
+ function registerHeadlessPlanProgressWatcher(
1406
+ pi: ExtensionAPI,
1407
+ active: ActiveContextAccess,
1408
+ ): void {
1409
+ pi.on("tool_result", async (event, ctx) => {
1410
+ if (event.isError) return;
1411
+ await handlePlanToolResultForHeadlessProgress({ pi, ctx, active });
1412
+ });
1413
+ }
1414
+
1329
1415
  function registerExecutorHandoffReconcile(
1330
1416
  pi: ExtensionAPI,
1331
1417
  active: ActiveContextAccess,
@@ -1489,10 +1575,46 @@ async function resolveCommandRunContext(input: {
1489
1575
  persistContext(input.pi, activeCtx);
1490
1576
  activeCtx = null;
1491
1577
  }
1578
+ if (
1579
+ activeCtx &&
1580
+ (input.command === "harness-plan" || input.command === "harness-auto") &&
1581
+ activeCtx.owner_pi_session_id !== input.sessionId
1582
+ ) {
1583
+ const foreignRunConflict = await findActiveRunOwnershipConflict(
1584
+ input.projectRoot,
1585
+ input.sessionId,
1586
+ );
1587
+ if (foreignRunConflict) {
1588
+ return {
1589
+ activeCtx,
1590
+ resolved,
1591
+ response: blockRunContextMessage(
1592
+ `Another Pi session (${foreignRunConflict.ownerPiSessionId}) owns active run ${foreignRunConflict.runId}. Finish or abort that run before starting a new plan.`,
1593
+ ),
1594
+ };
1595
+ }
1596
+ activeCtx = null;
1597
+ }
1492
1598
  const reuseRun =
1493
1599
  activeCtx &&
1494
1600
  shouldReuseHarnessRunId(input.userPrompt, activeCtx, input.command);
1495
1601
  if (!activeCtx || !reuseRun) {
1602
+ if (process.env.HARNESS_QA_SMOKE === "1") {
1603
+ await releaseForeignQaRunOwnership(input.projectRoot, input.sessionId);
1604
+ }
1605
+ const ownershipConflict = await findActiveRunOwnershipConflict(
1606
+ input.projectRoot,
1607
+ input.sessionId,
1608
+ );
1609
+ if (ownershipConflict) {
1610
+ return {
1611
+ activeCtx,
1612
+ resolved,
1613
+ response: blockRunContextMessage(
1614
+ `Another Pi session (${ownershipConflict.ownerPiSessionId}) owns active run ${ownershipConflict.runId}. Finish or abort that run before starting a new plan.`,
1615
+ ),
1616
+ };
1617
+ }
1496
1618
  if (activeCtx?.status === "active") {
1497
1619
  activeCtx.status = "aborted";
1498
1620
  activeCtx.plan_ready = false;
@@ -1508,6 +1630,11 @@ async function resolveCommandRunContext(input: {
1508
1630
  } else if (input.command === "harness-auto") {
1509
1631
  activeCtx = resetRunContextForHarnessAuto(activeCtx);
1510
1632
  if (task) activeCtx.task_summary = task;
1633
+ } else if (
1634
+ input.command === "harness-plan" &&
1635
+ activeCtx.status === "aborted"
1636
+ ) {
1637
+ activeCtx = resetRunContextForHarnessAuto(activeCtx);
1511
1638
  }
1512
1639
  if (input.command === "harness-plan") {
1513
1640
  if (task) activeCtx.task_summary = task;
@@ -1600,8 +1727,44 @@ async function handlePreResolvedHarnessCommand(args: {
1600
1727
  };
1601
1728
  }
1602
1729
  }
1730
+ if (command === "harness-abort") {
1731
+ if (!activeCtx) {
1732
+ if (process.env.HARNESS_QA_SMOKE === "1") {
1733
+ const released = await releaseForeignQaRunOwnership(
1734
+ projectRoot,
1735
+ sessionId,
1736
+ );
1737
+ if (released) {
1738
+ return {
1739
+ activeCtx: null,
1740
+ response: blockRunContextMessage(
1741
+ 'Stale QA harness run released from disk. Next: /harness-plan "<task>"',
1742
+ ),
1743
+ handled: true,
1744
+ };
1745
+ }
1746
+ }
1747
+ return {
1748
+ activeCtx,
1749
+ response: blockRunContextMessage(
1750
+ 'No active harness run to abort. Next: /harness-plan "<task>"',
1751
+ ),
1752
+ handled: true,
1753
+ };
1754
+ }
1755
+ const reason = parsedArgs.trim() || "manual abort";
1756
+ const aborted = abortActiveRunContext({ pi, activeCtx, reason });
1757
+ return {
1758
+ activeCtx: aborted,
1759
+ response: blockRunContextMessage(
1760
+ `Harness aborted. Mutating tools are blocked until a new approved plan is attached. Next: ${aborted.next_recommended_command}`,
1761
+ ),
1762
+ handled: true,
1763
+ };
1764
+ }
1765
+
1603
1766
  if (command === "harness-new-run") {
1604
- const next = createNewRunContextForCommand({
1767
+ const next = await createNewRunContextForCommand({
1605
1768
  pi,
1606
1769
  activeCtx,
1607
1770
  sessionId,
@@ -1708,6 +1871,8 @@ async function handleBeforeAgentStart(input: {
1708
1871
  }
1709
1872
  if (!parsed) return undefined;
1710
1873
  const { command, args } = parsed;
1874
+ const planQuick = parseArgFlag(args, "--quick") != null;
1875
+ const planRisk = parseArgFlag(args, "--risk") ?? "med";
1711
1876
  const preResolved = await handlePreResolvedHarnessCommand({
1712
1877
  pi: input.pi,
1713
1878
  activeCtx,
@@ -1742,6 +1907,19 @@ async function handleBeforeAgentStart(input: {
1742
1907
  return blockRunContextMessage(
1743
1908
  'No active harness run. Run /harness-plan "<task>" first, or /harness-use-run <run-id> for recovery.',
1744
1909
  );
1910
+ if (
1911
+ isHarnessNonInteractive() &&
1912
+ (await shouldEndHeadlessHarnessPrintSession({
1913
+ command,
1914
+ runCtx: activeCtx,
1915
+ projectRoot,
1916
+ }))
1917
+ ) {
1918
+ endHeadlessHarnessPrintSession(input.ctx);
1919
+ return {
1920
+ systemPrompt: `${input.event.systemPrompt}\n\n[Harness] Headless session complete; ending.`,
1921
+ };
1922
+ }
1745
1923
  activeCtx.phase = policyPhase;
1746
1924
  activeCtx.updated_at = new Date().toISOString();
1747
1925
  activeCtx.pi_session_id = sessionId;
@@ -1817,6 +1995,26 @@ async function handleBeforeAgentStart(input: {
1817
1995
  activeCtx,
1818
1996
  );
1819
1997
  Object.assign(activeCtx, syncedCtx);
1998
+ if (command === "harness-plan" || command === "harness-auto") {
1999
+ const runDir = join(
2000
+ projectRoot,
2001
+ ".pi",
2002
+ "harness",
2003
+ "runs",
2004
+ activeCtx.run_id,
2005
+ );
2006
+ await seedHeadlessTaskClarificationIfNeeded({
2007
+ runDir,
2008
+ taskSummary: activeCtx.task_summary ?? "",
2009
+ riskLevel: planRisk,
2010
+ quick: planQuick,
2011
+ });
2012
+ const resynced = await syncPlanLastOutcomeFromTaskClarification(
2013
+ projectRoot,
2014
+ activeCtx,
2015
+ );
2016
+ Object.assign(activeCtx, resynced);
2017
+ }
1820
2018
  input.active.set(activeCtx);
1821
2019
  persistContext(input.pi, activeCtx);
1822
2020
  if (command === "harness-plan" || command === "harness-auto") {
@@ -1824,13 +2022,12 @@ async function handleBeforeAgentStart(input: {
1824
2022
  }
1825
2023
  let gateBlock = "";
1826
2024
  if (command === "harness-plan" || command === "harness-auto") {
1827
- const quick = parseArgFlag(args, "--quick") != null;
1828
2025
  const gateStatus = await resolvePlanHumanGateStatus(
1829
2026
  projectRoot,
1830
2027
  activeCtx.run_id,
1831
2028
  entries,
1832
2029
  {
1833
- quick,
2030
+ quick: planQuick,
1834
2031
  taskSummary: activeCtx.task_summary ?? undefined,
1835
2032
  lastOutcome: activeCtx.last_outcome ?? undefined,
1836
2033
  },
@@ -1843,6 +2040,138 @@ async function handleBeforeAgentStart(input: {
1843
2040
  };
1844
2041
  }
1845
2042
 
2043
+ async function applyHeadlessPlanFinalizeAndQaSmoke(input: {
2044
+ pi: ExtensionAPI;
2045
+ ctx: any;
2046
+ active: ActiveContextAccess;
2047
+ command: string;
2048
+ args: string;
2049
+ activeCtx: HarnessRunContext;
2050
+ entries: unknown[];
2051
+ }): Promise<void> {
2052
+ const projectRoot = process.cwd();
2053
+ const planQuick = parseArgFlag(input.args, "--quick") != null;
2054
+ const planRisk = parseArgFlag(input.args, "--risk") ?? "med";
2055
+ const outcome = await tryHeadlessAutoPlanFinalize({
2056
+ projectRoot,
2057
+ runCtx: input.activeCtx,
2058
+ taskSummary: input.activeCtx.task_summary ?? "",
2059
+ entries: input.entries,
2060
+ riskLevel: planRisk,
2061
+ quick: planQuick,
2062
+ deps: {
2063
+ appendEntry: (type, data) => input.pi.appendEntry(type, data),
2064
+ getEntries: () => getEntries(input.ctx),
2065
+ getSubagentEntries: () => getEntries(input.ctx),
2066
+ onPlanCommitted: (updated, packet, planPath) => {
2067
+ input.pi.appendEntry("harness-run-context", updated);
2068
+ input.pi.appendEntry(
2069
+ "harness-plan-packet",
2070
+ planPacketSummary(packet, planPath, "ready"),
2071
+ );
2072
+ },
2073
+ },
2074
+ });
2075
+ if (
2076
+ outcome.progress.seeded_clarification ||
2077
+ outcome.progress.seeded_planning_context ||
2078
+ outcome.progress.patched_review_gate ||
2079
+ outcome.progress.wrote_consensus_bypass
2080
+ ) {
2081
+ input.pi.appendEntry("harness-headless-plan-progress", {
2082
+ run_id: input.activeCtx.run_id,
2083
+ ...outcome.progress,
2084
+ recorded_at: nowIso(),
2085
+ });
2086
+ }
2087
+ if (outcome.finalized) {
2088
+ const synced = await syncPlanReadyFromDisk(
2089
+ projectRoot,
2090
+ input.activeCtx,
2091
+ input.entries,
2092
+ );
2093
+ Object.assign(input.activeCtx, synced);
2094
+ persistContext(input.pi, input.activeCtx);
2095
+ input.active.set(input.activeCtx);
2096
+ input.pi.appendEntry("harness-headless-plan-finalized", {
2097
+ run_id: input.activeCtx.run_id,
2098
+ source: "headless_auto",
2099
+ recorded_at: nowIso(),
2100
+ });
2101
+ input.activeCtx.next_recommended_command = "/harness-run";
2102
+ persistContext(input.pi, input.activeCtx);
2103
+ if (input.command === "harness-auto") {
2104
+ await maybeHeadlessQaAutoExecuteSmoke({
2105
+ projectRoot,
2106
+ runCtx: input.activeCtx,
2107
+ command: input.command,
2108
+ });
2109
+ persistContext(input.pi, input.activeCtx);
2110
+ }
2111
+ if (
2112
+ await shouldEndHeadlessHarnessPrintSession({
2113
+ command: input.command,
2114
+ runCtx: input.activeCtx,
2115
+ projectRoot,
2116
+ })
2117
+ ) {
2118
+ endHeadlessHarnessPrintSession(input.ctx);
2119
+ }
2120
+ } else if (outcome.reason && outcome.progress.force_reason) {
2121
+ input.pi.appendEntry("harness-headless-plan-progress", {
2122
+ run_id: input.activeCtx.run_id,
2123
+ finalize_blocked: outcome.reason,
2124
+ recorded_at: nowIso(),
2125
+ });
2126
+ }
2127
+ }
2128
+
2129
+ async function handleHeadlessPlanProgressCheck(input: {
2130
+ pi: ExtensionAPI;
2131
+ ctx: any;
2132
+ active: ActiveContextAccess;
2133
+ }): Promise<void> {
2134
+ const entries = getEntries(input.ctx);
2135
+ const turn = getLatestHarnessTurn(entries);
2136
+ if (
2137
+ !turn ||
2138
+ (turn.command !== "harness-plan" && turn.command !== "harness-auto")
2139
+ ) {
2140
+ return;
2141
+ }
2142
+ const activeCtx = input.active.get() ?? getLatestRunContext(entries);
2143
+ if (!activeCtx?.run_id || activeCtx.plan_ready) return;
2144
+ await applyHeadlessPlanFinalizeAndQaSmoke({
2145
+ pi: input.pi,
2146
+ ctx: input.ctx,
2147
+ active: input.active,
2148
+ command: turn.command,
2149
+ args: turn.args,
2150
+ activeCtx,
2151
+ entries,
2152
+ });
2153
+ }
2154
+
2155
+ async function handleTurnStart(input: {
2156
+ pi: ExtensionAPI;
2157
+ ctx: any;
2158
+ active: ActiveContextAccess;
2159
+ }): Promise<void> {
2160
+ await handleHeadlessPlanProgressCheck(input);
2161
+ }
2162
+
2163
+ async function handlePlanToolResultForHeadlessProgress(input: {
2164
+ pi: ExtensionAPI;
2165
+ ctx: any;
2166
+ active: ActiveContextAccess;
2167
+ }): Promise<void> {
2168
+ const entries = getEntries(input.ctx);
2169
+ const since = Math.max(0, indexOfLastPlanCommand(entries));
2170
+ const sinceEntries = entries.length - since;
2171
+ if (sinceEntries > 0 && sinceEntries % 12 !== 0) return;
2172
+ await handleHeadlessPlanProgressCheck(input);
2173
+ }
2174
+
1846
2175
  async function handleAgentEnd(input: {
1847
2176
  pi: ExtensionAPI;
1848
2177
  ctx: any;
@@ -1877,6 +2206,29 @@ async function handleAgentEnd(input: {
1877
2206
  parsed?.command === "harness-plan" ||
1878
2207
  parsed?.command === "harness-auto"
1879
2208
  ) {
2209
+ const planArgs = parsed.args ?? "";
2210
+ const quick = parseArgFlag(planArgs, "--quick") != null;
2211
+ const risk = parseArgFlag(planArgs, "--risk") ?? "med";
2212
+ const forced = await maybeForceHeadlessPlanProgress({
2213
+ projectRoot,
2214
+ runId: activeCtx.run_id,
2215
+ taskSummary: activeCtx.task_summary ?? "",
2216
+ entries,
2217
+ riskLevel: risk,
2218
+ quick,
2219
+ });
2220
+ if (
2221
+ forced.seeded_clarification ||
2222
+ forced.seeded_planning_context ||
2223
+ forced.patched_review_gate ||
2224
+ forced.wrote_consensus_bypass
2225
+ ) {
2226
+ input.pi.appendEntry("harness-headless-plan-progress", {
2227
+ run_id: activeCtx.run_id,
2228
+ ...forced,
2229
+ recorded_at: nowIso(),
2230
+ });
2231
+ }
1880
2232
  const synced = await syncPlanLastOutcomeFromTaskClarification(
1881
2233
  projectRoot,
1882
2234
  activeCtx,
@@ -1884,6 +2236,41 @@ async function handleAgentEnd(input: {
1884
2236
  Object.assign(activeCtx, synced);
1885
2237
  persistContext(input.pi, activeCtx);
1886
2238
  }
2239
+ if (
2240
+ parsed?.command === "harness-plan" ||
2241
+ parsed?.command === "harness-auto"
2242
+ ) {
2243
+ if (!activeCtx.plan_ready) {
2244
+ await applyHeadlessPlanFinalizeAndQaSmoke({
2245
+ pi: input.pi,
2246
+ ctx: input.ctx,
2247
+ active: input.active,
2248
+ command: parsed.command,
2249
+ args: parsed.args ?? "",
2250
+ activeCtx,
2251
+ entries,
2252
+ });
2253
+ } else if (
2254
+ parsed.command === "harness-auto" &&
2255
+ process.env.HARNESS_QA_SMOKE === "1"
2256
+ ) {
2257
+ await maybeHeadlessQaAutoExecuteSmoke({
2258
+ projectRoot,
2259
+ runCtx: activeCtx,
2260
+ command: parsed.command,
2261
+ });
2262
+ persistContext(input.pi, activeCtx);
2263
+ if (
2264
+ await shouldEndHeadlessHarnessPrintSession({
2265
+ command: parsed.command,
2266
+ runCtx: activeCtx,
2267
+ projectRoot,
2268
+ })
2269
+ ) {
2270
+ endHeadlessHarnessPrintSession(input.ctx);
2271
+ }
2272
+ }
2273
+ }
1887
2274
  const statuses = await resolveCompletionStatuses(
1888
2275
  entries,
1889
2276
  activeCtx.run_id,
@@ -1970,14 +2357,17 @@ async function handleAgentEnd(input: {
1970
2357
  phase: activeCtx.phase,
1971
2358
  });
1972
2359
  if (next && parsed) {
1973
- const notify = `Next: ${next}`;
1974
- if (input.ctx.hasUI) input.ctx.ui.notify(notify, "info");
1975
- else
1976
- input.pi.sendMessage({
1977
- customType: "harness-step-handoff",
1978
- content: notify,
1979
- display: true,
1980
- });
2360
+ notifyHarnessHandoff(input.ctx, `Next: ${next}`);
2361
+ }
2362
+ if (
2363
+ parsed &&
2364
+ (await shouldEndHeadlessHarnessPrintSession({
2365
+ command: parsed.command,
2366
+ runCtx: activeCtx,
2367
+ projectRoot,
2368
+ }))
2369
+ ) {
2370
+ endHeadlessHarnessPrintSession(input.ctx);
1981
2371
  }
1982
2372
  }
1983
2373
 
@@ -2643,6 +3033,20 @@ export default function harnessRunContext(pi: ExtensionAPI) {
2643
3033
  },
2644
3034
  };
2645
3035
 
3036
+ pi.events.on("harness-run-aborted", (payload: unknown) => {
3037
+ const reason =
3038
+ typeof (payload as { reason?: unknown })?.reason === "string"
3039
+ ? (payload as { reason: string }).reason || "manual abort"
3040
+ : "manual abort";
3041
+ if (activeCtx) {
3042
+ abortActiveRunContext({ pi, activeCtx, reason });
3043
+ }
3044
+ });
3045
+
3046
+ pi.events.on("harness-runs-cleared", () => {
3047
+ activeCtx = null;
3048
+ });
3049
+
2646
3050
  pi.on("session_start", async (_event, ctx) => {
2647
3051
  const entries = getEntries(ctx);
2648
3052
  activeCtx = hydrateFromSession(entries);
@@ -2673,11 +3077,16 @@ export default function harnessRunContext(pi: ExtensionAPI) {
2673
3077
  handleBeforeAgentStart({ pi, event, ctx, active: activeAccess }),
2674
3078
  );
2675
3079
 
3080
+ pi.on("turn_start", async (_event, ctx) => {
3081
+ await handleTurnStart({ pi, ctx, active: activeAccess });
3082
+ });
3083
+
2676
3084
  pi.on("agent_end", async (_event, ctx) => {
2677
3085
  await handleAgentEnd({ pi, ctx, active: activeAccess });
2678
3086
  });
2679
3087
 
2680
3088
  registerPlanApprovalCapture(pi, activeAccess);
3089
+ registerHeadlessPlanProgressWatcher(pi, activeAccess);
2681
3090
  registerExecutorHandoffReconcile(pi, activeAccess);
2682
3091
  registerHarnessToolCallGuards(pi, activeAccess);
2683
3092
  registerHarnessRunStatusCommand(pi, activeAccess);