taskplane 0.29.2 → 0.30.1
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/bin/gitignore-patterns.mjs +11 -8
- package/bin/rpc-wrapper.mjs +410 -357
- package/bin/taskplane.mjs +533 -250
- package/dashboard/public/app.js +124 -15
- package/dashboard/public/style.css +83 -2
- package/extensions/reviewer-extension.ts +17 -11
- package/extensions/taskplane/abort.ts +50 -18
- package/extensions/taskplane/agent-bridge-extension.ts +232 -105
- package/extensions/taskplane/agent-host.ts +224 -97
- package/extensions/taskplane/cleanup.ts +71 -42
- package/extensions/taskplane/config-loader.ts +142 -58
- package/extensions/taskplane/config-schema.ts +6 -13
- package/extensions/taskplane/config.ts +10 -2
- package/extensions/taskplane/diagnostic-reports.ts +59 -47
- package/extensions/taskplane/diagnostics.ts +13 -13
- package/extensions/taskplane/discovery.ts +78 -63
- package/extensions/taskplane/engine-worker.ts +53 -46
- package/extensions/taskplane/engine.ts +1760 -602
- package/extensions/taskplane/execution.ts +469 -207
- package/extensions/taskplane/extension.ts +1073 -598
- package/extensions/taskplane/formatting.ts +136 -124
- package/extensions/taskplane/git.ts +0 -2
- package/extensions/taskplane/lane-runner.ts +652 -319
- package/extensions/taskplane/mailbox.ts +57 -49
- package/extensions/taskplane/merge.ts +662 -383
- package/extensions/taskplane/messages.ts +109 -51
- package/extensions/taskplane/migrations.ts +1 -1
- package/extensions/taskplane/path-resolver.ts +8 -9
- package/extensions/taskplane/persistence.ts +425 -262
- package/extensions/taskplane/process-registry.ts +36 -7
- package/extensions/taskplane/quality-gate.ts +107 -55
- package/extensions/taskplane/resume.ts +832 -280
- package/extensions/taskplane/sessions.ts +1 -1
- package/extensions/taskplane/settings-tui.ts +505 -164
- package/extensions/taskplane/sidecar-telemetry.ts +25 -10
- package/extensions/taskplane/supervisor.ts +477 -270
- package/extensions/taskplane/task-executor-core.ts +178 -53
- package/extensions/taskplane/types.ts +209 -108
- package/extensions/taskplane/verification.ts +27 -22
- package/extensions/taskplane/waves.ts +59 -43
- package/extensions/taskplane/workspace.ts +14 -12
- package/extensions/taskplane/worktree.ts +218 -196
- package/package.json +14 -2
|
@@ -28,12 +28,37 @@
|
|
|
28
28
|
|
|
29
29
|
import { join, dirname } from "path";
|
|
30
30
|
import { fileURLToPath } from "url";
|
|
31
|
-
import {
|
|
32
|
-
|
|
31
|
+
import {
|
|
32
|
+
existsSync,
|
|
33
|
+
readFileSync,
|
|
34
|
+
readdirSync,
|
|
35
|
+
writeFileSync,
|
|
36
|
+
unlinkSync,
|
|
37
|
+
mkdirSync,
|
|
38
|
+
renameSync,
|
|
39
|
+
statSync,
|
|
40
|
+
openSync,
|
|
41
|
+
readSync,
|
|
42
|
+
closeSync,
|
|
43
|
+
appendFileSync,
|
|
44
|
+
} from "fs";
|
|
45
|
+
import {
|
|
46
|
+
stat as fsStat,
|
|
47
|
+
open as fsOpen,
|
|
48
|
+
readFile as fsReadFile,
|
|
49
|
+
writeFile as fsWriteFile,
|
|
50
|
+
rename as fsRename,
|
|
51
|
+
} from "fs/promises";
|
|
33
52
|
import { execFileSync } from "child_process";
|
|
34
53
|
import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
35
54
|
import type { Model, Api } from "@mariozechner/pi-ai";
|
|
36
|
-
import type {
|
|
55
|
+
import type {
|
|
56
|
+
OrchBatchRuntimeState,
|
|
57
|
+
OrchestratorConfig,
|
|
58
|
+
PersistedBatchState,
|
|
59
|
+
EngineEvent,
|
|
60
|
+
EngineEventType,
|
|
61
|
+
} from "./types.ts";
|
|
37
62
|
import type { Tier0Event, Tier0EventType } from "./persistence.ts";
|
|
38
63
|
|
|
39
64
|
// ── Recovery Action Classification (TP-041 Step 4) ───────────────────
|
|
@@ -99,7 +124,9 @@ export function requiresConfirmation(
|
|
|
99
124
|
*
|
|
100
125
|
* @since TP-041
|
|
101
126
|
*/
|
|
102
|
-
export const ACTION_CLASSIFICATION_EXAMPLES: Readonly<
|
|
127
|
+
export const ACTION_CLASSIFICATION_EXAMPLES: Readonly<
|
|
128
|
+
Record<RecoveryActionClassification, readonly string[]>
|
|
129
|
+
> = {
|
|
103
130
|
diagnostic: [
|
|
104
131
|
"Reading batch-state.json, STATUS.md, events.jsonl, merge results",
|
|
105
132
|
"Running git status, git log, git diff",
|
|
@@ -126,7 +153,6 @@ export const ACTION_CLASSIFICATION_EXAMPLES: Readonly<Record<RecoveryActionClass
|
|
|
126
153
|
],
|
|
127
154
|
};
|
|
128
155
|
|
|
129
|
-
|
|
130
156
|
// ── Audit Trail (TP-041 Step 4) ──────────────────────────────────────
|
|
131
157
|
|
|
132
158
|
/**
|
|
@@ -295,7 +321,6 @@ export function readAuditTrail(
|
|
|
295
321
|
}
|
|
296
322
|
}
|
|
297
323
|
|
|
298
|
-
|
|
299
324
|
// ── Branch Protection Detection (TP-043) ─────────────────────────────
|
|
300
325
|
|
|
301
326
|
/**
|
|
@@ -325,30 +350,35 @@ export type BranchProtectionStatus = "protected" | "unprotected" | "unknown";
|
|
|
325
350
|
*
|
|
326
351
|
* @since TP-043
|
|
327
352
|
*/
|
|
328
|
-
export function detectBranchProtection(
|
|
329
|
-
branch: string,
|
|
330
|
-
cwd: string,
|
|
331
|
-
): BranchProtectionStatus {
|
|
353
|
+
export function detectBranchProtection(branch: string, cwd: string): BranchProtectionStatus {
|
|
332
354
|
try {
|
|
333
355
|
// Get owner/repo from gh (handles SSH, HTTPS, and gh-specific remotes)
|
|
334
|
-
const repoInfo = execFileSync(
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
356
|
+
const repoInfo = execFileSync(
|
|
357
|
+
"gh",
|
|
358
|
+
["repo", "view", "--json", "owner,name", "--jq", '.owner.login + "/" + .name'],
|
|
359
|
+
{
|
|
360
|
+
encoding: "utf-8",
|
|
361
|
+
timeout: 15_000,
|
|
362
|
+
cwd,
|
|
363
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
364
|
+
},
|
|
365
|
+
).trim();
|
|
340
366
|
|
|
341
367
|
if (!repoInfo || !repoInfo.includes("/")) {
|
|
342
368
|
return "unknown";
|
|
343
369
|
}
|
|
344
370
|
|
|
345
371
|
// Check branch protection via GitHub API
|
|
346
|
-
const result = execFileSync(
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
372
|
+
const result = execFileSync(
|
|
373
|
+
"gh",
|
|
374
|
+
["api", `repos/${repoInfo}/branches/${branch}/protection`, "--silent"],
|
|
375
|
+
{
|
|
376
|
+
encoding: "utf-8",
|
|
377
|
+
timeout: 15_000,
|
|
378
|
+
cwd,
|
|
379
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
380
|
+
},
|
|
381
|
+
);
|
|
352
382
|
|
|
353
383
|
// If we get here (no error), the API returned 200 → branch is protected
|
|
354
384
|
return "protected";
|
|
@@ -366,7 +396,6 @@ export function detectBranchProtection(
|
|
|
366
396
|
}
|
|
367
397
|
}
|
|
368
398
|
|
|
369
|
-
|
|
370
399
|
// ── Supervisor-Managed Integration Flow (TP-043) ─────────────────────
|
|
371
400
|
|
|
372
401
|
/**
|
|
@@ -468,8 +497,8 @@ export function buildIntegrationPlan(
|
|
|
468
497
|
// - Override: use as-is (test injection path)
|
|
469
498
|
// - Remotes exist: detect via gh API
|
|
470
499
|
// - No remotes: treat as unprotected (can't create PRs anyway)
|
|
471
|
-
const protection =
|
|
472
|
-
?? (remotes ? detectBranchProtection(baseBranch, cwd) : "unprotected");
|
|
500
|
+
const protection =
|
|
501
|
+
protectionOverride ?? (remotes ? detectBranchProtection(baseBranch, cwd) : "unprotected");
|
|
473
502
|
|
|
474
503
|
// Step 3: Always try FF first, then merge, then PR (TP-149).
|
|
475
504
|
// Protected branches may still allow FF/merge via API tokens.
|
|
@@ -544,7 +573,9 @@ export function formatIntegrationPlan(plan: IntegrationPlan): string {
|
|
|
544
573
|
lines.push(``);
|
|
545
574
|
lines.push(`- **Mode:** ${modeLabels[plan.mode] || plan.mode}`);
|
|
546
575
|
lines.push(`- **From:** \`${plan.orchBranch}\` → \`${plan.baseBranch}\``);
|
|
547
|
-
lines.push(
|
|
576
|
+
lines.push(
|
|
577
|
+
`- **Tasks:** ${plan.succeededTasks} succeeded${plan.failedTasks > 0 ? `, ${plan.failedTasks} failed` : ""}`,
|
|
578
|
+
);
|
|
548
579
|
lines.push(`- **Rationale:** ${plan.rationale}`);
|
|
549
580
|
|
|
550
581
|
if (plan.branchProtection === "protected") {
|
|
@@ -571,7 +602,8 @@ export function formatIntegrationOutcome(
|
|
|
571
602
|
detail: string,
|
|
572
603
|
): string {
|
|
573
604
|
if (success) {
|
|
574
|
-
const modeLabel =
|
|
605
|
+
const modeLabel =
|
|
606
|
+
plan.mode === "ff" ? "Fast-forwarded" : plan.mode === "merge" ? "Merged" : "Created PR for";
|
|
575
607
|
return `✅ **Integration complete!** ${modeLabel} \`${plan.orchBranch}\` → \`${plan.baseBranch}\`.\n${detail}`;
|
|
576
608
|
}
|
|
577
609
|
return `❌ **Integration failed** (\`${plan.orchBranch}\` → \`${plan.baseBranch}\`).\n${detail}`;
|
|
@@ -587,8 +619,20 @@ export function formatIntegrationOutcome(
|
|
|
587
619
|
*/
|
|
588
620
|
export type IntegrationExecutor = (
|
|
589
621
|
mode: "ff" | "merge" | "pr",
|
|
590
|
-
context: {
|
|
591
|
-
|
|
622
|
+
context: {
|
|
623
|
+
orchBranch: string;
|
|
624
|
+
baseBranch: string;
|
|
625
|
+
batchId: string;
|
|
626
|
+
currentBranch: string;
|
|
627
|
+
notices: string[];
|
|
628
|
+
},
|
|
629
|
+
) => {
|
|
630
|
+
success: boolean;
|
|
631
|
+
integratedLocally: boolean;
|
|
632
|
+
commitCount: string;
|
|
633
|
+
message: string;
|
|
634
|
+
error?: string;
|
|
635
|
+
};
|
|
592
636
|
|
|
593
637
|
/**
|
|
594
638
|
* Dependencies for programmatic CI polling and PR merge (R002-2).
|
|
@@ -631,11 +675,15 @@ export async function pollPrCiStatus(
|
|
|
631
675
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
632
676
|
// Wait before polling (except first attempt — check immediately)
|
|
633
677
|
if (attempt > 1) {
|
|
634
|
-
await new Promise(resolve => setTimeout(resolve, delayMs));
|
|
678
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
635
679
|
}
|
|
636
680
|
|
|
637
681
|
const result = deps.runCommand("gh", [
|
|
638
|
-
"pr",
|
|
682
|
+
"pr",
|
|
683
|
+
"checks",
|
|
684
|
+
orchBranch,
|
|
685
|
+
"--json",
|
|
686
|
+
"name,state,conclusion",
|
|
639
687
|
]);
|
|
640
688
|
|
|
641
689
|
if (!result.ok) {
|
|
@@ -661,16 +709,18 @@ export async function pollPrCiStatus(
|
|
|
661
709
|
}
|
|
662
710
|
|
|
663
711
|
// Check if all checks are complete
|
|
664
|
-
const allComplete = checks.every(c =>
|
|
665
|
-
c.state === "COMPLETED" || c.state === "completed",
|
|
666
|
-
);
|
|
712
|
+
const allComplete = checks.every((c) => c.state === "COMPLETED" || c.state === "completed");
|
|
667
713
|
if (!allComplete) continue; // Some still pending — keep polling
|
|
668
714
|
|
|
669
715
|
// All complete — check conclusions
|
|
670
|
-
const allPassing = checks.every(
|
|
671
|
-
c
|
|
672
|
-
|
|
673
|
-
|
|
716
|
+
const allPassing = checks.every(
|
|
717
|
+
(c) =>
|
|
718
|
+
c.conclusion === "SUCCESS" ||
|
|
719
|
+
c.conclusion === "success" ||
|
|
720
|
+
c.conclusion === "NEUTRAL" ||
|
|
721
|
+
c.conclusion === "neutral" ||
|
|
722
|
+
c.conclusion === "SKIPPED" ||
|
|
723
|
+
c.conclusion === "skipped",
|
|
674
724
|
);
|
|
675
725
|
|
|
676
726
|
if (allPassing) {
|
|
@@ -678,16 +728,23 @@ export async function pollPrCiStatus(
|
|
|
678
728
|
}
|
|
679
729
|
|
|
680
730
|
// Some checks failed
|
|
681
|
-
const failed = checks.filter(
|
|
682
|
-
c
|
|
683
|
-
|
|
684
|
-
|
|
731
|
+
const failed = checks.filter(
|
|
732
|
+
(c) =>
|
|
733
|
+
c.conclusion !== "SUCCESS" &&
|
|
734
|
+
c.conclusion !== "success" &&
|
|
735
|
+
c.conclusion !== "NEUTRAL" &&
|
|
736
|
+
c.conclusion !== "neutral" &&
|
|
737
|
+
c.conclusion !== "SKIPPED" &&
|
|
738
|
+
c.conclusion !== "skipped",
|
|
685
739
|
);
|
|
686
|
-
const failedNames = failed.map(c => `${c.name}: ${c.conclusion}`).join(", ");
|
|
740
|
+
const failedNames = failed.map((c) => `${c.name}: ${c.conclusion}`).join(", ");
|
|
687
741
|
return { status: "fail", detail: `CI check(s) failed: ${failedNames}` };
|
|
688
742
|
}
|
|
689
743
|
|
|
690
|
-
return {
|
|
744
|
+
return {
|
|
745
|
+
status: "timeout",
|
|
746
|
+
detail: `CI checks did not complete within ${maxAttempts} polling attempts.`,
|
|
747
|
+
};
|
|
691
748
|
}
|
|
692
749
|
|
|
693
750
|
/**
|
|
@@ -706,13 +763,14 @@ export async function pollPrCiStatus(
|
|
|
706
763
|
*
|
|
707
764
|
* @since TP-043
|
|
708
765
|
*/
|
|
709
|
-
export function mergePr(
|
|
710
|
-
orchBranch: string,
|
|
711
|
-
deps: CiDeps,
|
|
712
|
-
): { success: boolean; detail: string } {
|
|
766
|
+
export function mergePr(orchBranch: string, deps: CiDeps): { success: boolean; detail: string } {
|
|
713
767
|
// Try regular merge first (preserves per-commit history)
|
|
714
768
|
const mergeResult = deps.runCommand("gh", [
|
|
715
|
-
"pr",
|
|
769
|
+
"pr",
|
|
770
|
+
"merge",
|
|
771
|
+
orchBranch,
|
|
772
|
+
"--merge",
|
|
773
|
+
"--delete-branch",
|
|
716
774
|
]);
|
|
717
775
|
if (mergeResult.ok) {
|
|
718
776
|
return { success: true, detail: "PR merged and remote branch deleted." };
|
|
@@ -720,7 +778,11 @@ export function mergePr(
|
|
|
720
778
|
|
|
721
779
|
// Regular merge not allowed — try squash as fallback
|
|
722
780
|
const squashResult = deps.runCommand("gh", [
|
|
723
|
-
"pr",
|
|
781
|
+
"pr",
|
|
782
|
+
"merge",
|
|
783
|
+
orchBranch,
|
|
784
|
+
"--squash",
|
|
785
|
+
"--delete-branch",
|
|
724
786
|
]);
|
|
725
787
|
if (squashResult.ok) {
|
|
726
788
|
return { success: true, detail: "PR merged (squash) and remote branch deleted." };
|
|
@@ -744,9 +806,17 @@ export interface SummaryDeps {
|
|
|
744
806
|
/** Operator identifier for file naming */
|
|
745
807
|
opId: string;
|
|
746
808
|
/** Batch diagnostics (taskExits, batchCost) — null if unavailable */
|
|
747
|
-
diagnostics: {
|
|
809
|
+
diagnostics: {
|
|
810
|
+
taskExits: Record<string, { classification: string; cost: number; durationSec: number }>;
|
|
811
|
+
batchCost: number;
|
|
812
|
+
} | null;
|
|
748
813
|
/** Merge results for cost breakdown */
|
|
749
|
-
mergeResults: Array<{
|
|
814
|
+
mergeResults: Array<{
|
|
815
|
+
waveIndex: number;
|
|
816
|
+
status: string;
|
|
817
|
+
failedLane: number | null;
|
|
818
|
+
failureReason: string | null;
|
|
819
|
+
}>;
|
|
750
820
|
}
|
|
751
821
|
|
|
752
822
|
/**
|
|
@@ -787,12 +857,14 @@ async function handlePrLifecycle(
|
|
|
787
857
|
pi.sendMessage(
|
|
788
858
|
{
|
|
789
859
|
customType: "supervisor-integration-result",
|
|
790
|
-
content: [
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
860
|
+
content: [
|
|
861
|
+
{
|
|
862
|
+
type: "text",
|
|
863
|
+
text:
|
|
864
|
+
`✅ **Integration complete!** PR merged into \`${plan.baseBranch}\`.\n` +
|
|
865
|
+
`${ciResult.detail}\n${mergeOutcome.detail}`,
|
|
866
|
+
},
|
|
867
|
+
],
|
|
796
868
|
display: "Integration complete — PR merged",
|
|
797
869
|
},
|
|
798
870
|
{ triggerTurn: false },
|
|
@@ -801,12 +873,14 @@ async function handlePrLifecycle(
|
|
|
801
873
|
pi.sendMessage(
|
|
802
874
|
{
|
|
803
875
|
customType: "supervisor-integration-result",
|
|
804
|
-
content: [
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
876
|
+
content: [
|
|
877
|
+
{
|
|
878
|
+
type: "text",
|
|
879
|
+
text:
|
|
880
|
+
`⚠️ **CI passed but merge failed.** ${mergeOutcome.detail}\n` +
|
|
881
|
+
`The PR is still open — merge manually on GitHub.`,
|
|
882
|
+
},
|
|
883
|
+
],
|
|
810
884
|
display: "CI passed but PR merge failed",
|
|
811
885
|
},
|
|
812
886
|
{ triggerTurn: false },
|
|
@@ -816,12 +890,14 @@ async function handlePrLifecycle(
|
|
|
816
890
|
pi.sendMessage(
|
|
817
891
|
{
|
|
818
892
|
customType: "supervisor-integration-result",
|
|
819
|
-
content: [
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
893
|
+
content: [
|
|
894
|
+
{
|
|
895
|
+
type: "text",
|
|
896
|
+
text:
|
|
897
|
+
`❌ **CI checks failed.** ${ciResult.detail}\n` +
|
|
898
|
+
`The PR is still open. Fix the issues and merge manually, or close and retry.`,
|
|
899
|
+
},
|
|
900
|
+
],
|
|
825
901
|
display: "CI checks failed — manual intervention needed",
|
|
826
902
|
},
|
|
827
903
|
{ triggerTurn: false },
|
|
@@ -831,12 +907,14 @@ async function handlePrLifecycle(
|
|
|
831
907
|
pi.sendMessage(
|
|
832
908
|
{
|
|
833
909
|
customType: "supervisor-integration-result",
|
|
834
|
-
content: [
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
910
|
+
content: [
|
|
911
|
+
{
|
|
912
|
+
type: "text",
|
|
913
|
+
text:
|
|
914
|
+
`⏰ **CI check timeout.** ${ciResult.detail}\n` +
|
|
915
|
+
`The PR is still open. Check CI status manually and merge when ready.`,
|
|
916
|
+
},
|
|
917
|
+
],
|
|
840
918
|
display: "CI check timeout — check manually",
|
|
841
919
|
},
|
|
842
920
|
{ triggerTurn: false },
|
|
@@ -845,7 +923,14 @@ async function handlePrLifecycle(
|
|
|
845
923
|
|
|
846
924
|
// TP-043: Generate batch summary before deactivation
|
|
847
925
|
if (batchState && summaryDeps && state.stateRoot) {
|
|
848
|
-
presentBatchSummary(
|
|
926
|
+
presentBatchSummary(
|
|
927
|
+
pi,
|
|
928
|
+
batchState,
|
|
929
|
+
state.stateRoot,
|
|
930
|
+
summaryDeps.opId,
|
|
931
|
+
summaryDeps.diagnostics,
|
|
932
|
+
summaryDeps.mergeResults,
|
|
933
|
+
);
|
|
849
934
|
}
|
|
850
935
|
|
|
851
936
|
// Always deactivate after PR lifecycle completes (R002 issue #3)
|
|
@@ -897,7 +982,14 @@ export function triggerSupervisorIntegration(
|
|
|
897
982
|
// TP-043: Helper to generate summary before deactivation
|
|
898
983
|
const summarizeAndDeactivate = () => {
|
|
899
984
|
if (summaryDeps && state.stateRoot) {
|
|
900
|
-
presentBatchSummary(
|
|
985
|
+
presentBatchSummary(
|
|
986
|
+
pi,
|
|
987
|
+
batchState,
|
|
988
|
+
state.stateRoot,
|
|
989
|
+
summaryDeps.opId,
|
|
990
|
+
summaryDeps.diagnostics,
|
|
991
|
+
summaryDeps.mergeResults,
|
|
992
|
+
);
|
|
901
993
|
}
|
|
902
994
|
deactivateSupervisor(pi, state);
|
|
903
995
|
};
|
|
@@ -910,10 +1002,12 @@ export function triggerSupervisorIntegration(
|
|
|
910
1002
|
pi.sendMessage(
|
|
911
1003
|
{
|
|
912
1004
|
customType: "supervisor-integration",
|
|
913
|
-
content: [
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
1005
|
+
content: [
|
|
1006
|
+
{
|
|
1007
|
+
type: "text",
|
|
1008
|
+
text: `📋 **Batch complete.** No integration needed (no orch branch or no succeeded tasks). Supervisor deactivating.`,
|
|
1009
|
+
},
|
|
1010
|
+
],
|
|
917
1011
|
display: "No integration needed — supervisor deactivating",
|
|
918
1012
|
},
|
|
919
1013
|
{ triggerTurn: false },
|
|
@@ -932,23 +1026,26 @@ export function triggerSupervisorIntegration(
|
|
|
932
1026
|
pi.sendMessage(
|
|
933
1027
|
{
|
|
934
1028
|
customType: "supervisor-integration",
|
|
935
|
-
content: [
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
1029
|
+
content: [
|
|
1030
|
+
{
|
|
1031
|
+
type: "text",
|
|
1032
|
+
text:
|
|
1033
|
+
`🏁 **Batch complete!** Ready to integrate.\n\n` +
|
|
1034
|
+
planText +
|
|
1035
|
+
`\n\n` +
|
|
1036
|
+
`**Action required:** Ask the operator for confirmation.\n\n` +
|
|
1037
|
+
`Say something like: "The batch completed successfully. I'd like to integrate ` +
|
|
1038
|
+
`the changes from \`${plan.orchBranch}\` into \`${plan.baseBranch}\` using ` +
|
|
1039
|
+
`${plan.mode === "ff" ? "fast-forward" : plan.mode === "merge" ? "a merge commit" : "a pull request"}. ` +
|
|
1040
|
+
`${plan.rationale} Shall I proceed?"\n\n` +
|
|
1041
|
+
`If the operator confirms, run: \`/orch-integrate${modeFlag}\`\n` +
|
|
1042
|
+
`If the operator declines, acknowledge and deactivate.\n` +
|
|
1043
|
+
`If the operator wants a different mode, adjust the flag:\n` +
|
|
1044
|
+
` - Fast-forward: \`/orch-integrate\`\n` +
|
|
1045
|
+
` - Merge commit: \`/orch-integrate --merge\`\n` +
|
|
1046
|
+
` - Pull request: \`/orch-integrate --pr\``,
|
|
1047
|
+
},
|
|
1048
|
+
],
|
|
952
1049
|
display: "Integration plan ready — awaiting operator confirmation",
|
|
953
1050
|
},
|
|
954
1051
|
{ triggerTurn: true },
|
|
@@ -972,13 +1069,16 @@ export function triggerSupervisorIntegration(
|
|
|
972
1069
|
pi.sendMessage(
|
|
973
1070
|
{
|
|
974
1071
|
customType: "supervisor-integration",
|
|
975
|
-
content: [
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
1072
|
+
content: [
|
|
1073
|
+
{
|
|
1074
|
+
type: "text",
|
|
1075
|
+
text:
|
|
1076
|
+
`🏁 **Batch complete!** Integration executor unavailable.\n\n` +
|
|
1077
|
+
planText +
|
|
1078
|
+
`\n\n` +
|
|
1079
|
+
`Run \`/orch-integrate${modeFlag}\` to integrate manually.`,
|
|
1080
|
+
},
|
|
1081
|
+
],
|
|
982
1082
|
display: "Auto-integration fallback — run /orch-integrate",
|
|
983
1083
|
},
|
|
984
1084
|
{ triggerTurn: false },
|
|
@@ -1017,10 +1117,12 @@ export function triggerSupervisorIntegration(
|
|
|
1017
1117
|
pi.sendMessage(
|
|
1018
1118
|
{
|
|
1019
1119
|
customType: "supervisor-integration-progress",
|
|
1020
|
-
content: [
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1120
|
+
content: [
|
|
1121
|
+
{
|
|
1122
|
+
type: "text",
|
|
1123
|
+
text: `${outcomeText}\n\n⏳ Waiting for CI checks to complete...`,
|
|
1124
|
+
},
|
|
1125
|
+
],
|
|
1024
1126
|
display: "PR created — polling CI status",
|
|
1025
1127
|
},
|
|
1026
1128
|
{ triggerTurn: false },
|
|
@@ -1034,10 +1136,12 @@ export function triggerSupervisorIntegration(
|
|
|
1034
1136
|
pi.sendMessage(
|
|
1035
1137
|
{
|
|
1036
1138
|
customType: "supervisor-integration-result",
|
|
1037
|
-
content: [
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1139
|
+
content: [
|
|
1140
|
+
{
|
|
1141
|
+
type: "text",
|
|
1142
|
+
text: `❌ **CI monitoring crashed:** ${msg}\nThe PR is still open — check status and merge manually.`,
|
|
1143
|
+
},
|
|
1144
|
+
],
|
|
1041
1145
|
display: "CI monitoring crashed",
|
|
1042
1146
|
},
|
|
1043
1147
|
{ triggerTurn: false },
|
|
@@ -1049,10 +1153,12 @@ export function triggerSupervisorIntegration(
|
|
|
1049
1153
|
pi.sendMessage(
|
|
1050
1154
|
{
|
|
1051
1155
|
customType: "supervisor-integration-result",
|
|
1052
|
-
content: [
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1156
|
+
content: [
|
|
1157
|
+
{
|
|
1158
|
+
type: "text",
|
|
1159
|
+
text: `PR created. CI polling unavailable — check status and merge manually on GitHub.`,
|
|
1160
|
+
},
|
|
1161
|
+
],
|
|
1056
1162
|
display: "PR created — merge manually",
|
|
1057
1163
|
},
|
|
1058
1164
|
{ triggerTurn: false },
|
|
@@ -1066,10 +1172,12 @@ export function triggerSupervisorIntegration(
|
|
|
1066
1172
|
pi.sendMessage(
|
|
1067
1173
|
{
|
|
1068
1174
|
customType: "supervisor-integration-result",
|
|
1069
|
-
content: [
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1175
|
+
content: [
|
|
1176
|
+
{
|
|
1177
|
+
type: "text",
|
|
1178
|
+
text: outcomeText,
|
|
1179
|
+
},
|
|
1180
|
+
],
|
|
1073
1181
|
display: `Integration complete (${plan.mode})`,
|
|
1074
1182
|
},
|
|
1075
1183
|
{ triggerTurn: false },
|
|
@@ -1083,12 +1191,13 @@ export function triggerSupervisorIntegration(
|
|
|
1083
1191
|
pi.sendMessage(
|
|
1084
1192
|
{
|
|
1085
1193
|
customType: "supervisor-integration-result",
|
|
1086
|
-
content: [
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1194
|
+
content: [
|
|
1195
|
+
{
|
|
1196
|
+
type: "text",
|
|
1197
|
+
text:
|
|
1198
|
+
outcomeText + `\n\n` + `Run \`/orch-integrate\` manually to retry with a different mode.`,
|
|
1199
|
+
},
|
|
1200
|
+
],
|
|
1092
1201
|
display: "Integration failed — run /orch-integrate manually",
|
|
1093
1202
|
},
|
|
1094
1203
|
{ triggerTurn: false },
|
|
@@ -1097,7 +1206,6 @@ export function triggerSupervisorIntegration(
|
|
|
1097
1206
|
}
|
|
1098
1207
|
}
|
|
1099
1208
|
|
|
1100
|
-
|
|
1101
1209
|
// ── Batch Summary Generation (TP-043 Step 2) ────────────────────────
|
|
1102
1210
|
|
|
1103
1211
|
/**
|
|
@@ -1236,10 +1344,7 @@ const TIER0_SUMMARY_TYPES = new Set([
|
|
|
1236
1344
|
*
|
|
1237
1345
|
* @since TP-043
|
|
1238
1346
|
*/
|
|
1239
|
-
export function readTier0EventsForBatch(
|
|
1240
|
-
stateRoot: string,
|
|
1241
|
-
batchId: string,
|
|
1242
|
-
): Tier0EventSummary[] {
|
|
1347
|
+
export function readTier0EventsForBatch(stateRoot: string, batchId: string): Tier0EventSummary[] {
|
|
1243
1348
|
const eventsPath = join(stateRoot, ".pi", "supervisor", "events.jsonl");
|
|
1244
1349
|
if (!existsSync(eventsPath)) return [];
|
|
1245
1350
|
|
|
@@ -1324,24 +1429,36 @@ function computeV2BatchCost(stateRoot: string, batchId: string): number {
|
|
|
1324
1429
|
try {
|
|
1325
1430
|
const lanesDir = join(stateRoot, ".pi", "runtime", batchId, "lanes");
|
|
1326
1431
|
if (!existsSync(lanesDir)) return 0;
|
|
1327
|
-
const files = readdirSync(lanesDir).filter(f => f.startsWith("lane-") && f.endsWith(".json"));
|
|
1432
|
+
const files = readdirSync(lanesDir).filter((f) => f.startsWith("lane-") && f.endsWith(".json"));
|
|
1328
1433
|
let total = 0;
|
|
1329
1434
|
for (const f of files) {
|
|
1330
1435
|
try {
|
|
1331
1436
|
const snap = JSON.parse(readFileSync(join(lanesDir, f), "utf-8"));
|
|
1332
1437
|
total += snap.worker?.costUsd || 0;
|
|
1333
1438
|
total += snap.reviewer?.costUsd || 0;
|
|
1334
|
-
} catch {
|
|
1439
|
+
} catch {
|
|
1440
|
+
/* skip */
|
|
1441
|
+
}
|
|
1335
1442
|
}
|
|
1336
1443
|
return total;
|
|
1337
|
-
} catch {
|
|
1444
|
+
} catch {
|
|
1445
|
+
return 0;
|
|
1446
|
+
}
|
|
1338
1447
|
}
|
|
1339
1448
|
|
|
1340
1449
|
export function collectBatchSummaryData(
|
|
1341
1450
|
batchState: OrchBatchRuntimeState,
|
|
1342
1451
|
stateRoot: string,
|
|
1343
|
-
diagnostics?: {
|
|
1344
|
-
|
|
1452
|
+
diagnostics?: {
|
|
1453
|
+
taskExits: Record<string, { classification: string; cost: number; durationSec: number }>;
|
|
1454
|
+
batchCost: number;
|
|
1455
|
+
} | null,
|
|
1456
|
+
mergeResults?: Array<{
|
|
1457
|
+
waveIndex: number;
|
|
1458
|
+
status: string;
|
|
1459
|
+
failedLane: number | null;
|
|
1460
|
+
failureReason: string | null;
|
|
1461
|
+
}>,
|
|
1345
1462
|
): BatchSummaryData {
|
|
1346
1463
|
// Read audit trail for incidents
|
|
1347
1464
|
const auditEntries = readAuditTrail(stateRoot, { batchId: batchState.batchId });
|
|
@@ -1350,7 +1467,7 @@ export function collectBatchSummaryData(
|
|
|
1350
1467
|
const tier0Events = readTier0EventsForBatch(stateRoot, batchState.batchId);
|
|
1351
1468
|
|
|
1352
1469
|
// Extract wave results (may not exist if batch failed during planning)
|
|
1353
|
-
const waveResults = (batchState.waveResults || []).map(wr => ({
|
|
1470
|
+
const waveResults = (batchState.waveResults || []).map((wr) => ({
|
|
1354
1471
|
waveIndex: wr.waveIndex,
|
|
1355
1472
|
startedAt: wr.startedAt,
|
|
1356
1473
|
endedAt: wr.endedAt,
|
|
@@ -1370,8 +1487,11 @@ export function collectBatchSummaryData(
|
|
|
1370
1487
|
byTaskId.set(segment.taskId, existing);
|
|
1371
1488
|
}
|
|
1372
1489
|
|
|
1373
|
-
const multiSegmentTasks: NonNullable<BatchSummaryData["segmentOutcomes"]>["multiSegmentTasks"] =
|
|
1374
|
-
|
|
1490
|
+
const multiSegmentTasks: NonNullable<BatchSummaryData["segmentOutcomes"]>["multiSegmentTasks"] =
|
|
1491
|
+
[];
|
|
1492
|
+
for (const [taskId, taskSegments] of [...byTaskId.entries()].sort((a, b) =>
|
|
1493
|
+
a[0].localeCompare(b[0]),
|
|
1494
|
+
)) {
|
|
1375
1495
|
if (taskSegments.length <= 1) continue;
|
|
1376
1496
|
const succeeded = taskSegments.filter((segment) => segment.status === "succeeded").length;
|
|
1377
1497
|
const failed = taskSegments.filter((segment) => segment.status === "failed").length;
|
|
@@ -1415,9 +1535,10 @@ export function collectBatchSummaryData(
|
|
|
1415
1535
|
failedTasks: batchState.failedTasks,
|
|
1416
1536
|
skippedTasks: batchState.skippedTasks,
|
|
1417
1537
|
blockedTasks: batchState.blockedTasks,
|
|
1418
|
-
batchCost:
|
|
1419
|
-
|
|
1420
|
-
|
|
1538
|
+
batchCost:
|
|
1539
|
+
(diagnostics?.batchCost ?? 0) > 0
|
|
1540
|
+
? diagnostics!.batchCost
|
|
1541
|
+
: computeV2BatchCost(stateRoot, batchState.batchId),
|
|
1421
1542
|
wavePlan: [], // Not directly available on runtime state — use waveResults
|
|
1422
1543
|
waveResults,
|
|
1423
1544
|
taskExits: diagnostics?.taskExits ?? {},
|
|
@@ -1453,9 +1574,8 @@ export function formatBatchSummary(data: BatchSummaryData): string {
|
|
|
1453
1574
|
lines.push("");
|
|
1454
1575
|
|
|
1455
1576
|
// Duration
|
|
1456
|
-
const duration =
|
|
1457
|
-
? formatDurationMs(data.endedAt - data.startedAt)
|
|
1458
|
-
: "In progress";
|
|
1577
|
+
const duration =
|
|
1578
|
+
data.endedAt && data.startedAt ? formatDurationMs(data.endedAt - data.startedAt) : "In progress";
|
|
1459
1579
|
lines.push(`**Duration:** ${duration}`);
|
|
1460
1580
|
|
|
1461
1581
|
// Cost
|
|
@@ -1484,11 +1604,12 @@ export function formatBatchSummary(data: BatchSummaryData): string {
|
|
|
1484
1604
|
} else {
|
|
1485
1605
|
for (const wave of data.waveResults) {
|
|
1486
1606
|
const waveNum = wave.waveIndex + 1;
|
|
1487
|
-
const taskCount =
|
|
1607
|
+
const taskCount =
|
|
1608
|
+
wave.succeededTaskIds.length + wave.failedTaskIds.length + wave.skippedTaskIds.length;
|
|
1488
1609
|
const waveDuration = formatDurationMs(wave.endedAt - wave.startedAt);
|
|
1489
1610
|
|
|
1490
1611
|
// Check for merge result for this wave
|
|
1491
|
-
const mergeResult = data.mergeResults.find(mr => mr.waveIndex === wave.waveIndex);
|
|
1612
|
+
const mergeResult = data.mergeResults.find((mr) => mr.waveIndex === wave.waveIndex);
|
|
1492
1613
|
let mergeInfo = "";
|
|
1493
1614
|
if (mergeResult) {
|
|
1494
1615
|
if (mergeResult.status === "succeeded") {
|
|
@@ -1500,11 +1621,16 @@ export function formatBatchSummary(data: BatchSummaryData): string {
|
|
|
1500
1621
|
}
|
|
1501
1622
|
}
|
|
1502
1623
|
|
|
1503
|
-
const statusIcon =
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1624
|
+
const statusIcon =
|
|
1625
|
+
wave.overallStatus === "succeeded"
|
|
1626
|
+
? "✅"
|
|
1627
|
+
: wave.overallStatus === "failed"
|
|
1628
|
+
? "❌"
|
|
1629
|
+
: wave.overallStatus === "partial"
|
|
1630
|
+
? "⚠️"
|
|
1631
|
+
: wave.overallStatus === "aborted"
|
|
1632
|
+
? "🛑"
|
|
1633
|
+
: "❓";
|
|
1508
1634
|
|
|
1509
1635
|
lines.push(`- Wave ${waveNum} (${taskCount} tasks): ${waveDuration} ${statusIcon}${mergeInfo}`);
|
|
1510
1636
|
|
|
@@ -1522,7 +1648,9 @@ export function formatBatchSummary(data: BatchSummaryData): string {
|
|
|
1522
1648
|
if (!data.segmentOutcomes) {
|
|
1523
1649
|
lines.push("Segment data not available.");
|
|
1524
1650
|
} else if (data.segmentOutcomes.multiSegmentTasks.length === 0) {
|
|
1525
|
-
lines.push(
|
|
1651
|
+
lines.push(
|
|
1652
|
+
`No multi-segment task outcomes recorded (${data.segmentOutcomes.totalSegments} segment record(s) total).`,
|
|
1653
|
+
);
|
|
1526
1654
|
} else {
|
|
1527
1655
|
const statusParts = [
|
|
1528
1656
|
`${data.segmentOutcomes.succeeded} succeeded`,
|
|
@@ -1541,7 +1669,9 @@ export function formatBatchSummary(data: BatchSummaryData): string {
|
|
|
1541
1669
|
if (task.pending > 0) taskParts.push(`${task.pending} pending`);
|
|
1542
1670
|
if (task.skipped > 0) taskParts.push(`${task.skipped} skipped`);
|
|
1543
1671
|
if (task.stalled > 0) taskParts.push(`${task.stalled} stalled`);
|
|
1544
|
-
lines.push(
|
|
1672
|
+
lines.push(
|
|
1673
|
+
` - ${task.taskId}: ${task.terminalSegments}/${task.totalSegments} terminal (${taskParts.join(", ")})`,
|
|
1674
|
+
);
|
|
1545
1675
|
}
|
|
1546
1676
|
}
|
|
1547
1677
|
lines.push("");
|
|
@@ -1552,7 +1682,7 @@ export function formatBatchSummary(data: BatchSummaryData): string {
|
|
|
1552
1682
|
|
|
1553
1683
|
// Extract incidents from audit trail: non-diagnostic actions
|
|
1554
1684
|
const incidents = data.auditEntries.filter(
|
|
1555
|
-
e => e.classification !== "diagnostic" && e.result !== "pending",
|
|
1685
|
+
(e) => e.classification !== "diagnostic" && e.result !== "pending",
|
|
1556
1686
|
);
|
|
1557
1687
|
|
|
1558
1688
|
const hasTier0Events = data.tier0Events.length > 0;
|
|
@@ -1576,16 +1706,16 @@ export function formatBatchSummary(data: BatchSummaryData): string {
|
|
|
1576
1706
|
}
|
|
1577
1707
|
|
|
1578
1708
|
for (const [pattern, events] of byPattern) {
|
|
1579
|
-
const attempts = events.filter(e => e.type === "tier0_recovery_attempt").length;
|
|
1580
|
-
const successes = events.filter(e => e.type === "tier0_recovery_success").length;
|
|
1581
|
-
const exhausted = events.filter(e => e.type === "tier0_recovery_exhausted").length;
|
|
1582
|
-
const escalations = events.filter(e => e.type === "tier0_escalation").length;
|
|
1709
|
+
const attempts = events.filter((e) => e.type === "tier0_recovery_attempt").length;
|
|
1710
|
+
const successes = events.filter((e) => e.type === "tier0_recovery_success").length;
|
|
1711
|
+
const exhausted = events.filter((e) => e.type === "tier0_recovery_exhausted").length;
|
|
1712
|
+
const escalations = events.filter((e) => e.type === "tier0_escalation").length;
|
|
1583
1713
|
|
|
1584
|
-
const statusIcon = exhausted > 0 || escalations > 0 ? "❌"
|
|
1585
|
-
: successes > 0 ? "✅"
|
|
1586
|
-
: "⏳";
|
|
1714
|
+
const statusIcon = exhausted > 0 || escalations > 0 ? "❌" : successes > 0 ? "✅" : "⏳";
|
|
1587
1715
|
|
|
1588
|
-
lines.push(
|
|
1716
|
+
lines.push(
|
|
1717
|
+
`- **${pattern}** ${statusIcon} — ${attempts} attempt(s), ${successes} success(es), ${exhausted} exhausted`,
|
|
1718
|
+
);
|
|
1589
1719
|
|
|
1590
1720
|
// Show affected tasks
|
|
1591
1721
|
const taskIds = new Set<string>();
|
|
@@ -1600,21 +1730,21 @@ export function formatBatchSummary(data: BatchSummaryData): string {
|
|
|
1600
1730
|
}
|
|
1601
1731
|
|
|
1602
1732
|
// Show escalation details
|
|
1603
|
-
for (const evt of events.filter(e => e.type === "tier0_escalation")) {
|
|
1733
|
+
for (const evt of events.filter((e) => e.type === "tier0_escalation")) {
|
|
1604
1734
|
if (evt.suggestion) {
|
|
1605
1735
|
lines.push(` - Escalation: ${evt.suggestion}`);
|
|
1606
1736
|
}
|
|
1607
1737
|
}
|
|
1608
1738
|
|
|
1609
1739
|
// Show resolution details
|
|
1610
|
-
for (const evt of events.filter(e => e.type === "tier0_recovery_success")) {
|
|
1740
|
+
for (const evt of events.filter((e) => e.type === "tier0_recovery_success")) {
|
|
1611
1741
|
if (evt.resolution) {
|
|
1612
1742
|
lines.push(` - Resolution: ${evt.resolution}`);
|
|
1613
1743
|
}
|
|
1614
1744
|
}
|
|
1615
1745
|
|
|
1616
1746
|
// Show error details for exhausted
|
|
1617
|
-
for (const evt of events.filter(e => e.type === "tier0_recovery_exhausted")) {
|
|
1747
|
+
for (const evt of events.filter((e) => e.type === "tier0_recovery_exhausted")) {
|
|
1618
1748
|
if (evt.error) {
|
|
1619
1749
|
lines.push(` - Error: ${evt.error}`);
|
|
1620
1750
|
}
|
|
@@ -1633,10 +1763,14 @@ export function formatBatchSummary(data: BatchSummaryData): string {
|
|
|
1633
1763
|
let incidentNum = 0;
|
|
1634
1764
|
for (const entry of incidents) {
|
|
1635
1765
|
incidentNum++;
|
|
1636
|
-
const resultIcon =
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1766
|
+
const resultIcon =
|
|
1767
|
+
entry.result === "success"
|
|
1768
|
+
? "✅"
|
|
1769
|
+
: entry.result === "failure"
|
|
1770
|
+
? "❌"
|
|
1771
|
+
: entry.result === "skipped"
|
|
1772
|
+
? "⏭️"
|
|
1773
|
+
: "❓";
|
|
1640
1774
|
lines.push(`${incidentNum}. **${entry.action}** (${entry.classification}) ${resultIcon}`);
|
|
1641
1775
|
lines.push(` ${entry.context}`);
|
|
1642
1776
|
if (entry.detail && entry.detail !== entry.context) {
|
|
@@ -1666,16 +1800,22 @@ export function formatBatchSummary(data: BatchSummaryData): string {
|
|
|
1666
1800
|
const recommendations: string[] = [];
|
|
1667
1801
|
|
|
1668
1802
|
// Timeout recommendations: look for merge failures in audit trail
|
|
1669
|
-
const mergeFailures = data.mergeResults.filter(mr => mr.status === "failed");
|
|
1803
|
+
const mergeFailures = data.mergeResults.filter((mr) => mr.status === "failed");
|
|
1670
1804
|
if (mergeFailures.length > 0) {
|
|
1671
|
-
recommendations.push(
|
|
1805
|
+
recommendations.push(
|
|
1806
|
+
"- Consider increasing `merge.timeoutMinutes` — merge failures were detected during this batch.",
|
|
1807
|
+
);
|
|
1672
1808
|
}
|
|
1673
1809
|
|
|
1674
1810
|
// Failure rate recommendations
|
|
1675
1811
|
if (data.totalTasks > 0 && data.failedTasks > 0) {
|
|
1676
1812
|
const failureRate = data.failedTasks / data.totalTasks;
|
|
1677
1813
|
if (failureRate > 0.3) {
|
|
1678
|
-
recommendations.push(
|
|
1814
|
+
recommendations.push(
|
|
1815
|
+
"- High failure rate (" +
|
|
1816
|
+
Math.round(failureRate * 100) +
|
|
1817
|
+
"%) — consider reducing task scope or adding more context to PROMPT.md files.",
|
|
1818
|
+
);
|
|
1679
1819
|
}
|
|
1680
1820
|
}
|
|
1681
1821
|
|
|
@@ -1683,18 +1823,28 @@ export function formatBatchSummary(data: BatchSummaryData): string {
|
|
|
1683
1823
|
const longTasks = Object.entries(data.taskExits).filter(([, exit]) => exit.durationSec > 3600);
|
|
1684
1824
|
if (longTasks.length > 0) {
|
|
1685
1825
|
const names = longTasks.map(([id]) => id).join(", ");
|
|
1686
|
-
recommendations.push(
|
|
1826
|
+
recommendations.push(
|
|
1827
|
+
`- Long-running tasks detected (${names}): ${longTasks.length} task(s) exceeded 1 hour — consider splitting into smaller tasks.`,
|
|
1828
|
+
);
|
|
1687
1829
|
}
|
|
1688
1830
|
|
|
1689
1831
|
// Recovery recommendations — check both audit trail and Tier 0 events
|
|
1690
|
-
const recoveryExhaustedAudit = data.auditEntries.filter(
|
|
1691
|
-
|
|
1692
|
-
|
|
1832
|
+
const recoveryExhaustedAudit = data.auditEntries.filter(
|
|
1833
|
+
(e) =>
|
|
1834
|
+
e.action === "tier0_recovery_exhausted" ||
|
|
1835
|
+
(e.classification === "tier0_known" && e.result === "failure"),
|
|
1836
|
+
);
|
|
1837
|
+
const recoveryExhaustedTier0 = data.tier0Events.filter(
|
|
1838
|
+
(e) => e.type === "tier0_recovery_exhausted",
|
|
1839
|
+
);
|
|
1840
|
+
const escalationsTier0 = data.tier0Events.filter((e) => e.type === "tier0_escalation");
|
|
1693
1841
|
if (recoveryExhaustedAudit.length > 0 || recoveryExhaustedTier0.length > 0) {
|
|
1694
|
-
recommendations.push(
|
|
1842
|
+
recommendations.push(
|
|
1843
|
+
"- Recovery budget was exhausted for some issues — review recurring failures and consider addressing root causes.",
|
|
1844
|
+
);
|
|
1695
1845
|
}
|
|
1696
1846
|
if (escalationsTier0.length > 0) {
|
|
1697
|
-
const uniqueSuggestions = [...new Set(escalationsTier0.map(e => e.suggestion).filter(Boolean))];
|
|
1847
|
+
const uniqueSuggestions = [...new Set(escalationsTier0.map((e) => e.suggestion).filter(Boolean))];
|
|
1698
1848
|
if (uniqueSuggestions.length > 0) {
|
|
1699
1849
|
for (const suggestion of uniqueSuggestions) {
|
|
1700
1850
|
recommendations.push(`- Tier 0 escalation: ${suggestion}`);
|
|
@@ -1704,7 +1854,9 @@ export function formatBatchSummary(data: BatchSummaryData): string {
|
|
|
1704
1854
|
|
|
1705
1855
|
// Blocked tasks recommendations
|
|
1706
1856
|
if (data.blockedTasks > 0) {
|
|
1707
|
-
recommendations.push(
|
|
1857
|
+
recommendations.push(
|
|
1858
|
+
`- ${data.blockedTasks} task(s) were blocked due to upstream failures — fix failed tasks and re-run with \`/orch-resume\`.`,
|
|
1859
|
+
);
|
|
1708
1860
|
}
|
|
1709
1861
|
|
|
1710
1862
|
if (recommendations.length === 0) {
|
|
@@ -1744,10 +1896,14 @@ export function formatBatchSummary(data: BatchSummaryData): string {
|
|
|
1744
1896
|
|
|
1745
1897
|
totalCost += waveCost;
|
|
1746
1898
|
const waveDurationStr = formatDurationMs(waveDurationSec * 1000);
|
|
1747
|
-
lines.push(
|
|
1899
|
+
lines.push(
|
|
1900
|
+
`| ${waveNum} | ${allTaskIds.length} | $${waveCost.toFixed(2)} | ${waveDurationStr} |`,
|
|
1901
|
+
);
|
|
1748
1902
|
}
|
|
1749
1903
|
|
|
1750
|
-
lines.push(
|
|
1904
|
+
lines.push(
|
|
1905
|
+
`| **Total** | **${data.totalTasks}** | **$${totalCost.toFixed(2)}** | **${duration}** |`,
|
|
1906
|
+
);
|
|
1751
1907
|
}
|
|
1752
1908
|
lines.push("");
|
|
1753
1909
|
|
|
@@ -1780,8 +1936,16 @@ export function generateBatchSummary(
|
|
|
1780
1936
|
batchState: OrchBatchRuntimeState,
|
|
1781
1937
|
stateRoot: string,
|
|
1782
1938
|
opId: string,
|
|
1783
|
-
diagnostics?: {
|
|
1784
|
-
|
|
1939
|
+
diagnostics?: {
|
|
1940
|
+
taskExits: Record<string, { classification: string; cost: number; durationSec: number }>;
|
|
1941
|
+
batchCost: number;
|
|
1942
|
+
} | null,
|
|
1943
|
+
mergeResults?: Array<{
|
|
1944
|
+
waveIndex: number;
|
|
1945
|
+
status: string;
|
|
1946
|
+
failedLane: number | null;
|
|
1947
|
+
failureReason: string | null;
|
|
1948
|
+
}>,
|
|
1785
1949
|
): string {
|
|
1786
1950
|
const data = collectBatchSummaryData(batchState, stateRoot, diagnostics, mergeResults);
|
|
1787
1951
|
const markdown = formatBatchSummary(data);
|
|
@@ -1822,19 +1986,29 @@ export function presentBatchSummary(
|
|
|
1822
1986
|
batchState: OrchBatchRuntimeState,
|
|
1823
1987
|
stateRoot: string,
|
|
1824
1988
|
opId: string,
|
|
1825
|
-
diagnostics?: {
|
|
1826
|
-
|
|
1989
|
+
diagnostics?: {
|
|
1990
|
+
taskExits: Record<string, { classification: string; cost: number; durationSec: number }>;
|
|
1991
|
+
batchCost: number;
|
|
1992
|
+
} | null,
|
|
1993
|
+
mergeResults?: Array<{
|
|
1994
|
+
waveIndex: number;
|
|
1995
|
+
status: string;
|
|
1996
|
+
failedLane: number | null;
|
|
1997
|
+
failureReason: string | null;
|
|
1998
|
+
}>,
|
|
1827
1999
|
): void {
|
|
1828
2000
|
const summary = generateBatchSummary(batchState, stateRoot, opId, diagnostics, mergeResults);
|
|
1829
2001
|
|
|
1830
2002
|
// Build a concise conversation message (full details in the file)
|
|
1831
|
-
const duration =
|
|
1832
|
-
|
|
1833
|
-
|
|
2003
|
+
const duration =
|
|
2004
|
+
batchState.endedAt && batchState.startedAt
|
|
2005
|
+
? formatDurationMs(batchState.endedAt - batchState.startedAt)
|
|
2006
|
+
: "in progress";
|
|
1834
2007
|
// TP-115: Use V2 lane snapshot cost when diagnostics.batchCost is zero
|
|
1835
|
-
const rawCost =
|
|
1836
|
-
|
|
1837
|
-
|
|
2008
|
+
const rawCost =
|
|
2009
|
+
(diagnostics?.batchCost ?? 0) > 0
|
|
2010
|
+
? diagnostics!.batchCost
|
|
2011
|
+
: computeV2BatchCost(stateRoot, batchState.batchId);
|
|
1838
2012
|
const cost = rawCost > 0 ? `$${rawCost.toFixed(2)}` : "not tracked";
|
|
1839
2013
|
const filename = `${opId}-${batchState.batchId}-summary.md`;
|
|
1840
2014
|
|
|
@@ -1856,7 +2030,6 @@ export function presentBatchSummary(
|
|
|
1856
2030
|
);
|
|
1857
2031
|
}
|
|
1858
2032
|
|
|
1859
|
-
|
|
1860
2033
|
// ── Supervisor Config Types ──────────────────────────────────────────
|
|
1861
2034
|
|
|
1862
2035
|
/**
|
|
@@ -1906,7 +2079,6 @@ function resolvePrimerPath(): string {
|
|
|
1906
2079
|
}
|
|
1907
2080
|
}
|
|
1908
2081
|
|
|
1909
|
-
|
|
1910
2082
|
// ── Template Loading (TP-058) ────────────────────────────────────────
|
|
1911
2083
|
|
|
1912
2084
|
/**
|
|
@@ -1937,7 +2109,9 @@ function resolveBaseTemplatePath(name: string): string {
|
|
|
1937
2109
|
*
|
|
1938
2110
|
* @since TP-058
|
|
1939
2111
|
*/
|
|
1940
|
-
function parseSupervisorTemplate(
|
|
2112
|
+
function parseSupervisorTemplate(
|
|
2113
|
+
filePath: string,
|
|
2114
|
+
): { fm: Record<string, string>; body: string } | null {
|
|
1941
2115
|
if (!existsSync(filePath)) return null;
|
|
1942
2116
|
const raw = readFileSync(filePath, "utf-8").replace(/\r\n/g, "\n");
|
|
1943
2117
|
const match = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
@@ -1947,7 +2121,8 @@ function parseSupervisorTemplate(filePath: string): { fm: Record<string, string>
|
|
|
1947
2121
|
const idx = line.indexOf(":");
|
|
1948
2122
|
if (idx > 0) {
|
|
1949
2123
|
const key = line.slice(0, idx).trim();
|
|
1950
|
-
if (!key.startsWith("#")) {
|
|
2124
|
+
if (!key.startsWith("#")) {
|
|
2125
|
+
// Skip commented-out frontmatter
|
|
1951
2126
|
fm[key] = line.slice(idx + 1).trim();
|
|
1952
2127
|
}
|
|
1953
2128
|
}
|
|
@@ -1970,7 +2145,11 @@ function parseSupervisorTemplate(filePath: string): { fm: Record<string, string>
|
|
|
1970
2145
|
*
|
|
1971
2146
|
* @since TP-058
|
|
1972
2147
|
*/
|
|
1973
|
-
export function loadSupervisorTemplate(
|
|
2148
|
+
export function loadSupervisorTemplate(
|
|
2149
|
+
name: string,
|
|
2150
|
+
stateRoot: string,
|
|
2151
|
+
localName?: string,
|
|
2152
|
+
): string | null {
|
|
1974
2153
|
const basePath = resolveBaseTemplatePath(name);
|
|
1975
2154
|
const baseDef = parseSupervisorTemplate(basePath);
|
|
1976
2155
|
|
|
@@ -2013,7 +2192,6 @@ function replaceTemplateVars(template: string, vars: Record<string, string>): st
|
|
|
2013
2192
|
});
|
|
2014
2193
|
}
|
|
2015
2194
|
|
|
2016
|
-
|
|
2017
2195
|
/**
|
|
2018
2196
|
* Build the guardrails section dynamically based on integration mode (TP-043).
|
|
2019
2197
|
* Extracted as a helper so both the template path and inline fallback can reuse it.
|
|
@@ -2021,9 +2199,10 @@ function replaceTemplateVars(template: string, vars: Record<string, string>): st
|
|
|
2021
2199
|
*/
|
|
2022
2200
|
function buildGuardrailsSection(integrationMode: string): string {
|
|
2023
2201
|
if (integrationMode === "supervised" || integrationMode === "auto") {
|
|
2024
|
-
const modeNote =
|
|
2025
|
-
|
|
2026
|
-
|
|
2202
|
+
const modeNote =
|
|
2203
|
+
integrationMode === "supervised"
|
|
2204
|
+
? `**Supervised mode:** Before executing integration, describe your plan and ask the operator for confirmation.`
|
|
2205
|
+
: `**Auto mode:** Execute integration directly. Report the outcome to the operator. Pause only on errors or conflicts.`;
|
|
2027
2206
|
return `## What You Must NEVER Do
|
|
2028
2207
|
|
|
2029
2208
|
1. Never delete \`.pi/batch-state.json\` without operator approval
|
|
@@ -2105,9 +2284,10 @@ export function buildSupervisorSystemPrompt(
|
|
|
2105
2284
|
const autonomyLabel = supervisorConfig.autonomy;
|
|
2106
2285
|
|
|
2107
2286
|
// Build wave plan summary
|
|
2108
|
-
const waveSummary =
|
|
2109
|
-
|
|
2110
|
-
|
|
2287
|
+
const waveSummary =
|
|
2288
|
+
batchState.totalWaves > 0
|
|
2289
|
+
? `${batchState.currentWaveIndex + 1}/${batchState.totalWaves} waves`
|
|
2290
|
+
: "planning";
|
|
2111
2291
|
|
|
2112
2292
|
const actionsPath = auditTrailPath(stateRoot);
|
|
2113
2293
|
const integrationMode = config.orchestrator.integration;
|
|
@@ -2311,7 +2491,6 @@ Now that you've activated:
|
|
|
2311
2491
|
return prompt;
|
|
2312
2492
|
}
|
|
2313
2493
|
|
|
2314
|
-
|
|
2315
2494
|
// ── Routing System Prompt (TP-042) ───────────────────────────────────
|
|
2316
2495
|
|
|
2317
2496
|
/**
|
|
@@ -2615,7 +2794,6 @@ outcomes, and can handle failures.
|
|
|
2615
2794
|
return prompt;
|
|
2616
2795
|
}
|
|
2617
2796
|
|
|
2618
|
-
|
|
2619
2797
|
// ── Activation ───────────────────────────────────────────────────────
|
|
2620
2798
|
|
|
2621
2799
|
/**
|
|
@@ -2868,7 +3046,11 @@ export async function activateSupervisor(
|
|
|
2868
3046
|
// Idempotent — safe even if called from takeover paths that may have
|
|
2869
3047
|
// started a tailer previously (stopEventTailer is called in deactivate).
|
|
2870
3048
|
startEventTailer(pi, state.eventTailer, state, (key, text) => {
|
|
2871
|
-
try {
|
|
3049
|
+
try {
|
|
3050
|
+
ctx.ui.setStatus(key, text);
|
|
3051
|
+
} catch {
|
|
3052
|
+
/* non-fatal */
|
|
3053
|
+
}
|
|
2872
3054
|
});
|
|
2873
3055
|
|
|
2874
3056
|
// Send activation message to trigger the supervisor's first turn.
|
|
@@ -2941,7 +3123,14 @@ export async function deactivateSupervisor(
|
|
|
2941
3123
|
// confirmation), present it now — before we clear state refs.
|
|
2942
3124
|
if (state.pendingSummaryDeps && state.batchStateRef && state.stateRoot) {
|
|
2943
3125
|
const deps = state.pendingSummaryDeps;
|
|
2944
|
-
presentBatchSummary(
|
|
3126
|
+
presentBatchSummary(
|
|
3127
|
+
pi,
|
|
3128
|
+
state.batchStateRef,
|
|
3129
|
+
state.stateRoot,
|
|
3130
|
+
deps.opId,
|
|
3131
|
+
deps.diagnostics,
|
|
3132
|
+
deps.mergeResults,
|
|
3133
|
+
);
|
|
2945
3134
|
state.pendingSummaryDeps = null;
|
|
2946
3135
|
}
|
|
2947
3136
|
|
|
@@ -3012,7 +3201,14 @@ export async function transitionToRoutingMode(
|
|
|
3012
3201
|
// Present deferred batch summary if any
|
|
3013
3202
|
if (state.pendingSummaryDeps && state.batchStateRef && state.stateRoot) {
|
|
3014
3203
|
const deps = state.pendingSummaryDeps;
|
|
3015
|
-
presentBatchSummary(
|
|
3204
|
+
presentBatchSummary(
|
|
3205
|
+
pi,
|
|
3206
|
+
state.batchStateRef,
|
|
3207
|
+
state.stateRoot,
|
|
3208
|
+
deps.opId,
|
|
3209
|
+
deps.diagnostics,
|
|
3210
|
+
deps.mergeResults,
|
|
3211
|
+
);
|
|
3016
3212
|
state.pendingSummaryDeps = null;
|
|
3017
3213
|
}
|
|
3018
3214
|
|
|
@@ -3028,14 +3224,16 @@ export async function transitionToRoutingMode(
|
|
|
3028
3224
|
pi.sendMessage(
|
|
3029
3225
|
{
|
|
3030
3226
|
customType: "supervisor-routing-transition",
|
|
3031
|
-
content: [
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3227
|
+
content: [
|
|
3228
|
+
{
|
|
3229
|
+
type: "text",
|
|
3230
|
+
text:
|
|
3231
|
+
`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n` +
|
|
3232
|
+
`🔀 **Ready for your input.**\n\n` +
|
|
3233
|
+
routingContext.contextMessage +
|
|
3234
|
+
`\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`,
|
|
3235
|
+
},
|
|
3236
|
+
],
|
|
3039
3237
|
display: `Supervisor — ${routingContext.routingState}`,
|
|
3040
3238
|
},
|
|
3041
3239
|
{ triggerTurn: true },
|
|
@@ -3058,10 +3256,7 @@ export async function transitionToRoutingMode(
|
|
|
3058
3256
|
*
|
|
3059
3257
|
* @since TP-041
|
|
3060
3258
|
*/
|
|
3061
|
-
export function registerSupervisorPromptHook(
|
|
3062
|
-
pi: ExtensionAPI,
|
|
3063
|
-
state: SupervisorState,
|
|
3064
|
-
): void {
|
|
3259
|
+
export function registerSupervisorPromptHook(pi: ExtensionAPI, state: SupervisorState): void {
|
|
3065
3260
|
pi.on("before_agent_start", (_event) => {
|
|
3066
3261
|
if (!state.active) {
|
|
3067
3262
|
return undefined; // No-op: don't modify system prompt
|
|
@@ -3072,10 +3267,7 @@ export function registerSupervisorPromptHook(
|
|
|
3072
3267
|
// batch planning, etc.), not batch monitoring. Use the routing prompt
|
|
3073
3268
|
// which includes script guidance from the primer.
|
|
3074
3269
|
if (state.routingContext) {
|
|
3075
|
-
const systemPrompt = buildRoutingSystemPrompt(
|
|
3076
|
-
state.routingContext,
|
|
3077
|
-
state.stateRoot,
|
|
3078
|
-
);
|
|
3270
|
+
const systemPrompt = buildRoutingSystemPrompt(state.routingContext, state.stateRoot);
|
|
3079
3271
|
return { systemPrompt };
|
|
3080
3272
|
}
|
|
3081
3273
|
|
|
@@ -3127,7 +3319,6 @@ export function resolveSupervisorConfig(
|
|
|
3127
3319
|
};
|
|
3128
3320
|
}
|
|
3129
3321
|
|
|
3130
|
-
|
|
3131
3322
|
// ── Lockfile Types + Helpers (TP-041 Step 2) ─────────────────────────
|
|
3132
3323
|
|
|
3133
3324
|
/** Heartbeat interval in milliseconds (30 seconds). */
|
|
@@ -3279,7 +3470,10 @@ export async function readLockfileAsync(stateRoot: string): Promise<SupervisorLo
|
|
|
3279
3470
|
*
|
|
3280
3471
|
* @since TP-070
|
|
3281
3472
|
*/
|
|
3282
|
-
export async function writeLockfileAsync(
|
|
3473
|
+
export async function writeLockfileAsync(
|
|
3474
|
+
stateRoot: string,
|
|
3475
|
+
lock: SupervisorLockfile,
|
|
3476
|
+
): Promise<void> {
|
|
3283
3477
|
const dir = join(stateRoot, ".pi", "supervisor");
|
|
3284
3478
|
if (!existsSync(dir)) {
|
|
3285
3479
|
mkdirSync(dir, { recursive: true });
|
|
@@ -3357,9 +3551,7 @@ export function isLockStale(lock: SupervisorLockfile): boolean {
|
|
|
3357
3551
|
* If batch-state.json has one of these phases, there's no active batch
|
|
3358
3552
|
* and no lockfile arbitration is needed.
|
|
3359
3553
|
*/
|
|
3360
|
-
const TERMINAL_PHASES = new Set<string>([
|
|
3361
|
-
"idle", "completed", "failed", "stopped",
|
|
3362
|
-
]);
|
|
3554
|
+
const TERMINAL_PHASES = new Set<string>(["idle", "completed", "failed", "stopped"]);
|
|
3363
3555
|
|
|
3364
3556
|
/**
|
|
3365
3557
|
* Check whether a batch phase is terminal (no active batch).
|
|
@@ -3443,16 +3635,15 @@ export function checkSupervisorLockOnStartup(
|
|
|
3443
3635
|
*
|
|
3444
3636
|
* @since TP-041
|
|
3445
3637
|
*/
|
|
3446
|
-
export function buildTakeoverSummary(
|
|
3447
|
-
stateRoot: string,
|
|
3448
|
-
batchState: PersistedBatchState,
|
|
3449
|
-
): string {
|
|
3638
|
+
export function buildTakeoverSummary(stateRoot: string, batchState: PersistedBatchState): string {
|
|
3450
3639
|
const lines: string[] = [];
|
|
3451
3640
|
|
|
3452
3641
|
lines.push(`📋 **Taking over batch ${batchState.batchId}**`);
|
|
3453
3642
|
lines.push("");
|
|
3454
3643
|
lines.push(`**Phase:** ${batchState.phase}`);
|
|
3455
|
-
lines.push(
|
|
3644
|
+
lines.push(
|
|
3645
|
+
`**Wave:** ${batchState.currentWaveIndex + 1}/${batchState.wavePlan?.length ?? batchState.totalWaves ?? "?"}`,
|
|
3646
|
+
);
|
|
3456
3647
|
lines.push(`**Base branch:** ${batchState.baseBranch}`);
|
|
3457
3648
|
|
|
3458
3649
|
// Task summary from persisted state
|
|
@@ -3461,7 +3652,9 @@ export function buildTakeoverSummary(
|
|
|
3461
3652
|
const failed = tasks.filter((t) => t.status === "failed").length;
|
|
3462
3653
|
const running = tasks.filter((t) => t.status === "running").length;
|
|
3463
3654
|
const pending = tasks.filter((t) => t.status === "pending").length;
|
|
3464
|
-
lines.push(
|
|
3655
|
+
lines.push(
|
|
3656
|
+
`**Tasks:** ${succeeded} succeeded, ${failed} failed, ${running} running, ${pending} pending`,
|
|
3657
|
+
);
|
|
3465
3658
|
|
|
3466
3659
|
// Recent actions from audit trail (using readAuditTrail helper)
|
|
3467
3660
|
const recentActions = readAuditTrail(stateRoot, { limit: 5 });
|
|
@@ -3543,10 +3736,12 @@ export function startHeartbeat(
|
|
|
3543
3736
|
pi.sendMessage(
|
|
3544
3737
|
{
|
|
3545
3738
|
customType: "supervisor-yield",
|
|
3546
|
-
content: [
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3739
|
+
content: [
|
|
3740
|
+
{
|
|
3741
|
+
type: "text",
|
|
3742
|
+
text: "⚡ Another session has taken over supervisor duties. Yielding.",
|
|
3743
|
+
},
|
|
3744
|
+
],
|
|
3550
3745
|
display: "Supervisor yielded to another session",
|
|
3551
3746
|
},
|
|
3552
3747
|
{ triggerTurn: false },
|
|
@@ -3583,7 +3778,6 @@ export function startHeartbeat(
|
|
|
3583
3778
|
return timer;
|
|
3584
3779
|
}
|
|
3585
3780
|
|
|
3586
|
-
|
|
3587
3781
|
// ── Engine Event Consumption + Notifications (TP-041 Step 3) ─────────
|
|
3588
3782
|
|
|
3589
3783
|
/**
|
|
@@ -3825,7 +4019,11 @@ export function readNewBytes(eventsPath: string, byteOffset: number): [string, n
|
|
|
3825
4019
|
return ["", byteOffset];
|
|
3826
4020
|
} finally {
|
|
3827
4021
|
if (fd !== null) {
|
|
3828
|
-
try {
|
|
4022
|
+
try {
|
|
4023
|
+
closeSync(fd);
|
|
4024
|
+
} catch {
|
|
4025
|
+
/* best-effort */
|
|
4026
|
+
}
|
|
3829
4027
|
}
|
|
3830
4028
|
}
|
|
3831
4029
|
|
|
@@ -3843,7 +4041,10 @@ export function readNewBytes(eventsPath: string, byteOffset: number): [string, n
|
|
|
3843
4041
|
*
|
|
3844
4042
|
* @since TP-070
|
|
3845
4043
|
*/
|
|
3846
|
-
export async function readNewBytesAsync(
|
|
4044
|
+
export async function readNewBytesAsync(
|
|
4045
|
+
eventsPath: string,
|
|
4046
|
+
byteOffset: number,
|
|
4047
|
+
): Promise<[string, number]> {
|
|
3847
4048
|
try {
|
|
3848
4049
|
const stats = await fsStat(eventsPath);
|
|
3849
4050
|
const fileSize = stats.size;
|
|
@@ -3879,10 +4080,7 @@ export async function readNewBytesAsync(eventsPath: string, byteOffset: number):
|
|
|
3879
4080
|
*
|
|
3880
4081
|
* @since TP-041
|
|
3881
4082
|
*/
|
|
3882
|
-
export function parseJsonlLines(
|
|
3883
|
-
data: string,
|
|
3884
|
-
partialLine: string,
|
|
3885
|
-
): [ParsedEvent[], string] {
|
|
4083
|
+
export function parseJsonlLines(data: string, partialLine: string): [ParsedEvent[], string] {
|
|
3886
4084
|
const combined = partialLine + data;
|
|
3887
4085
|
const lines = combined.split("\n");
|
|
3888
4086
|
|
|
@@ -3939,9 +4137,7 @@ export function formatEventNotification(
|
|
|
3939
4137
|
return `🔀 Wave ${waveNum} merge starting...`;
|
|
3940
4138
|
}
|
|
3941
4139
|
case "merge_success": {
|
|
3942
|
-
const waveProg = event.totalWaves
|
|
3943
|
-
? ` (${waveNum}/${event.totalWaves})`
|
|
3944
|
-
: "";
|
|
4140
|
+
const waveProg = event.totalWaves ? ` (${waveNum}/${event.totalWaves})` : "";
|
|
3945
4141
|
const testInfo = event.testCount ? ` Tests pass (${event.testCount}).` : " Tests pass.";
|
|
3946
4142
|
return `✅ **Wave ${waveNum} merged successfully**${waveProg}.${testInfo}`;
|
|
3947
4143
|
}
|
|
@@ -3951,8 +4147,10 @@ export function formatEventNotification(
|
|
|
3951
4147
|
if (autonomy === "autonomous") {
|
|
3952
4148
|
return `⚠️ Wave ${waveNum} merge failed${laneInfo}: ${reason}. Attempting recovery...`;
|
|
3953
4149
|
}
|
|
3954
|
-
return
|
|
3955
|
-
|
|
4150
|
+
return (
|
|
4151
|
+
`⚠️ **Wave ${waveNum} merge failed**${laneInfo}: ${reason}.\n` +
|
|
4152
|
+
` Recovery may be needed. Check the merge logs for details.`
|
|
4153
|
+
);
|
|
3956
4154
|
}
|
|
3957
4155
|
case "merge_health_warning": {
|
|
3958
4156
|
const lane = event.laneNumber !== undefined ? event.laneNumber : "?";
|
|
@@ -3971,20 +4169,23 @@ export function formatEventNotification(
|
|
|
3971
4169
|
case "batch_complete": {
|
|
3972
4170
|
const parts: string[] = [];
|
|
3973
4171
|
if (event.succeededTasks !== undefined) parts.push(`${event.succeededTasks} succeeded`);
|
|
3974
|
-
if (event.failedTasks !== undefined && event.failedTasks > 0)
|
|
3975
|
-
|
|
3976
|
-
if (event.
|
|
4172
|
+
if (event.failedTasks !== undefined && event.failedTasks > 0)
|
|
4173
|
+
parts.push(`${event.failedTasks} failed`);
|
|
4174
|
+
if (event.skippedTasks !== undefined && event.skippedTasks > 0)
|
|
4175
|
+
parts.push(`${event.skippedTasks} skipped`);
|
|
4176
|
+
if (event.blockedTasks !== undefined && event.blockedTasks > 0)
|
|
4177
|
+
parts.push(`${event.blockedTasks} blocked`);
|
|
3977
4178
|
const summary = parts.length > 0 ? parts.join(", ") : "all tasks processed";
|
|
3978
|
-
const duration = event.batchDurationMs
|
|
3979
|
-
? ` in ${formatDuration(event.batchDurationMs)}`
|
|
3980
|
-
: "";
|
|
4179
|
+
const duration = event.batchDurationMs ? ` in ${formatDuration(event.batchDurationMs)}` : "";
|
|
3981
4180
|
return `🏁 **Batch complete!** ${summary}${duration}.`;
|
|
3982
4181
|
}
|
|
3983
4182
|
case "batch_paused": {
|
|
3984
4183
|
const reason = event.reason || "unknown reason";
|
|
3985
4184
|
if (autonomy === "interactive") {
|
|
3986
|
-
return
|
|
3987
|
-
|
|
4185
|
+
return (
|
|
4186
|
+
`⏸️ **Batch paused:** ${reason}\n` +
|
|
4187
|
+
` What would you like to do? Options: fix the issue, skip the task, or abort.`
|
|
4188
|
+
);
|
|
3988
4189
|
}
|
|
3989
4190
|
return `⏸️ **Batch paused:** ${reason}`;
|
|
3990
4191
|
}
|
|
@@ -3995,12 +4196,16 @@ export function formatEventNotification(
|
|
|
3995
4196
|
return `⚡ **Tier 0 escalation** (${pattern}): Investigating automatically. ${suggestion}`;
|
|
3996
4197
|
}
|
|
3997
4198
|
if (autonomy === "interactive") {
|
|
3998
|
-
return
|
|
3999
|
-
|
|
4199
|
+
return (
|
|
4200
|
+
`❌ **Tier 0 escalation** (${pattern}): ${suggestion}\n` +
|
|
4201
|
+
` Need your input on how to proceed.`
|
|
4202
|
+
);
|
|
4000
4203
|
}
|
|
4001
4204
|
// supervised
|
|
4002
|
-
return
|
|
4003
|
-
|
|
4205
|
+
return (
|
|
4206
|
+
`⚡ **Tier 0 escalation** (${pattern}): ${suggestion}\n` +
|
|
4207
|
+
` Diagnosing — will ask if novel recovery is needed.`
|
|
4208
|
+
);
|
|
4004
4209
|
}
|
|
4005
4210
|
default:
|
|
4006
4211
|
return `📌 Event: ${event.type} (wave ${waveNum})`;
|
|
@@ -4039,9 +4244,7 @@ export function formatTaskDigest(
|
|
|
4039
4244
|
}
|
|
4040
4245
|
|
|
4041
4246
|
if (buf.recoveryAttempts > 0 && autonomy !== "autonomous") {
|
|
4042
|
-
const successRate = buf.recoverySuccesses > 0
|
|
4043
|
-
? ` (${buf.recoverySuccesses} succeeded)`
|
|
4044
|
-
: "";
|
|
4247
|
+
const successRate = buf.recoverySuccesses > 0 ? ` (${buf.recoverySuccesses} succeeded)` : "";
|
|
4045
4248
|
parts.push(`🔄 ${buf.recoveryAttempts} recovery attempt(s)${successRate}`);
|
|
4046
4249
|
}
|
|
4047
4250
|
|
|
@@ -4305,7 +4508,11 @@ export function startEventTailer(
|
|
|
4305
4508
|
if (tailer.pollTimer && typeof tailer.pollTimer === "object" && "unref" in tailer.pollTimer) {
|
|
4306
4509
|
tailer.pollTimer.unref();
|
|
4307
4510
|
}
|
|
4308
|
-
if (
|
|
4511
|
+
if (
|
|
4512
|
+
tailer.digestTimer &&
|
|
4513
|
+
typeof tailer.digestTimer === "object" &&
|
|
4514
|
+
"unref" in tailer.digestTimer
|
|
4515
|
+
) {
|
|
4309
4516
|
tailer.digestTimer.unref();
|
|
4310
4517
|
}
|
|
4311
4518
|
}
|