pi-herdsman 0.17.0 → 0.17.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/dist/index.js +126 -78
- package/dist/index.js.map +2 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -31,7 +31,7 @@ import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
|
31
31
|
// package.json
|
|
32
32
|
var package_default = {
|
|
33
33
|
name: "pi-herdsman",
|
|
34
|
-
version: "0.17.
|
|
34
|
+
version: "0.17.1",
|
|
35
35
|
type: "module",
|
|
36
36
|
description: "Asynchronous Pi subagents and agent fleet orchestration for parallel coding agents with nested delegation, background work, and supervision in herdr.",
|
|
37
37
|
license: "Apache-2.0",
|
|
@@ -1805,6 +1805,7 @@ var HerdrStartFailure = class extends Error {
|
|
|
1805
1805
|
};
|
|
1806
1806
|
var SETTLE_TIMEOUT = 2e3;
|
|
1807
1807
|
var START_DIAGNOSTIC_TIMEOUT = 2e3;
|
|
1808
|
+
var FRESH_PANE_BUSY_RETRY_TIMEOUT = 2e3;
|
|
1808
1809
|
var HERDR_START_TIMEOUT_MIN = 3001;
|
|
1809
1810
|
var HERDR_START_TIMEOUT_MAX = 3e5;
|
|
1810
1811
|
var STARTUP_TIMEOUT_MIN = HERDR_START_TIMEOUT_MIN + START_DIAGNOSTIC_TIMEOUT;
|
|
@@ -2044,6 +2045,12 @@ function sameCwd(observed, expected) {
|
|
|
2044
2045
|
if (typeof observed !== "string") return false;
|
|
2045
2046
|
return canonicalCwd(observed) === canonicalCwd(expected);
|
|
2046
2047
|
}
|
|
2048
|
+
function matchesAttemptTerminal(pane, attempt) {
|
|
2049
|
+
return pane?.pane_id === attempt.paneId && pane.workspace_id === attempt.workspaceId && pane.tab_id === attempt.tabId && pane.terminal_id === attempt.terminalId;
|
|
2050
|
+
}
|
|
2051
|
+
function matchesAttemptPane(pane, attempt) {
|
|
2052
|
+
return matchesAttemptTerminal(pane, attempt) && sameCwd(pane.cwd, attempt.cwd);
|
|
2053
|
+
}
|
|
2047
2054
|
function capturedShellProcess(process2) {
|
|
2048
2055
|
return process2.foreground_processes?.find(
|
|
2049
2056
|
(candidate) => candidate.pid === process2.shell_pid
|
|
@@ -2383,6 +2390,7 @@ async function startHerdrAgent(pi, ctx, options) {
|
|
|
2383
2390
|
const env = validateEnvironment(options.env ?? []);
|
|
2384
2391
|
const release = await lockLifecycle(ctx, options.signal);
|
|
2385
2392
|
let attempt;
|
|
2393
|
+
let retryAttempted = false;
|
|
2386
2394
|
let stage = "topology";
|
|
2387
2395
|
try {
|
|
2388
2396
|
const cwd = canonicalCwd(options.cwd);
|
|
@@ -2397,7 +2405,7 @@ async function startHerdrAgent(pi, ctx, options) {
|
|
|
2397
2405
|
error("start", "caller pane is required for split placement");
|
|
2398
2406
|
let tab;
|
|
2399
2407
|
let panes;
|
|
2400
|
-
let
|
|
2408
|
+
let createdPane;
|
|
2401
2409
|
let createdTab = false;
|
|
2402
2410
|
const tabs = placement.kind === "tab" && !placement.tabId ? void 0 : (await runHerdr(
|
|
2403
2411
|
pi,
|
|
@@ -2461,7 +2469,7 @@ async function startHerdrAgent(pi, ctx, options) {
|
|
|
2461
2469
|
}
|
|
2462
2470
|
);
|
|
2463
2471
|
tab = made.tab;
|
|
2464
|
-
|
|
2472
|
+
createdPane = made.root_pane;
|
|
2465
2473
|
createdTab = true;
|
|
2466
2474
|
} else {
|
|
2467
2475
|
const existingPanes = panes ?? (await runHerdr(
|
|
@@ -2511,31 +2519,44 @@ async function startHerdrAgent(pi, ctx, options) {
|
|
|
2511
2519
|
timeout: startupCallTimeout(startupDeadline)
|
|
2512
2520
|
}
|
|
2513
2521
|
);
|
|
2514
|
-
|
|
2522
|
+
createdPane = split.pane;
|
|
2515
2523
|
}
|
|
2516
2524
|
}
|
|
2517
|
-
|
|
2525
|
+
const paneId = createdPane?.pane_id;
|
|
2526
|
+
const terminalId = createdPane?.terminal_id;
|
|
2527
|
+
if (typeof paneId !== "string" || !paneId)
|
|
2528
|
+
error("start", "Herdr did not return a pane");
|
|
2529
|
+
if (typeof terminalId !== "string" || !terminalId)
|
|
2530
|
+
error("start", `pane ${paneId} did not include terminal identity`);
|
|
2518
2531
|
attempt = {
|
|
2519
2532
|
herdrAgent: alias(workspaceId, options.label, options.runId),
|
|
2520
2533
|
workspaceId,
|
|
2521
2534
|
tabId: tab.tab_id,
|
|
2522
2535
|
paneId,
|
|
2536
|
+
terminalId,
|
|
2523
2537
|
cwd,
|
|
2524
|
-
createdTab
|
|
2538
|
+
createdTab,
|
|
2539
|
+
launchMayHaveStarted: false
|
|
2525
2540
|
};
|
|
2526
2541
|
let started;
|
|
2527
2542
|
stage = "pane_readiness";
|
|
2528
|
-
let shell;
|
|
2529
2543
|
try {
|
|
2530
|
-
|
|
2544
|
+
await waitForShellMarker(
|
|
2531
2545
|
pi,
|
|
2532
2546
|
ctx,
|
|
2533
2547
|
paneId,
|
|
2534
|
-
void 0,
|
|
2535
2548
|
startupDeadline,
|
|
2536
2549
|
"start",
|
|
2537
2550
|
options.signal
|
|
2538
2551
|
);
|
|
2552
|
+
attempt.shellProcess = await paneProcess(
|
|
2553
|
+
pi,
|
|
2554
|
+
ctx,
|
|
2555
|
+
paneId,
|
|
2556
|
+
options.signal,
|
|
2557
|
+
startupDeadline,
|
|
2558
|
+
true
|
|
2559
|
+
);
|
|
2539
2560
|
} catch (failure) {
|
|
2540
2561
|
if (failure instanceof OperationError)
|
|
2541
2562
|
throw withStartupDiagnostic(
|
|
@@ -2551,7 +2572,6 @@ async function startHerdrAgent(pi, ctx, options) {
|
|
|
2551
2572
|
throw failure;
|
|
2552
2573
|
}
|
|
2553
2574
|
stage = "ownership_capture";
|
|
2554
|
-
attempt.shellProcess = shell;
|
|
2555
2575
|
const currentPanes = (await runHerdr(pi, ctx, ["pane", "list", "--workspace", workspaceId], {
|
|
2556
2576
|
signal: options.signal,
|
|
2557
2577
|
timeout: startupCallTimeout(startupDeadline)
|
|
@@ -2559,8 +2579,8 @@ async function startHerdrAgent(pi, ctx, options) {
|
|
|
2559
2579
|
const currentPane = currentPanes.find(
|
|
2560
2580
|
(item) => item.pane_id === paneId
|
|
2561
2581
|
);
|
|
2562
|
-
if (!currentPane
|
|
2563
|
-
error("start", `pane ${paneId}
|
|
2582
|
+
if (!matchesAttemptPane(currentPane, attempt))
|
|
2583
|
+
error("start", `pane ${paneId} identity changed before launch`);
|
|
2564
2584
|
if (createdTab) {
|
|
2565
2585
|
const tabPanes = currentPanes.filter(
|
|
2566
2586
|
(item) => item.tab_id === tab.tab_id
|
|
@@ -2568,67 +2588,81 @@ async function startHerdrAgent(pi, ctx, options) {
|
|
|
2568
2588
|
if (tabPanes.length !== 1 || tabPanes[0]?.pane_id !== paneId)
|
|
2569
2589
|
error("start", `tab ${tab.tab_id} topology changed before launch`);
|
|
2570
2590
|
}
|
|
2571
|
-
if (!sameCwd(currentPane.cwd, cwd))
|
|
2572
|
-
error("start", `pane ${paneId} cwd does not match requested cwd`, {
|
|
2573
|
-
expected_cwd: cwd,
|
|
2574
|
-
observed_cwd: typeof currentPane.cwd === "string" ? currentPane.cwd : null
|
|
2575
|
-
});
|
|
2576
2591
|
stage = "agent_start";
|
|
2577
|
-
const
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
paneId,
|
|
2581
|
-
options.signal,
|
|
2582
|
-
startupDeadline,
|
|
2583
|
-
true
|
|
2592
|
+
const retryDeadline = Math.min(
|
|
2593
|
+
Date.now() + FRESH_PANE_BUSY_RETRY_TIMEOUT,
|
|
2594
|
+
startupDeadline - START_DIAGNOSTIC_TIMEOUT
|
|
2584
2595
|
);
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
"--pane",
|
|
2601
|
-
paneId,
|
|
2602
|
-
"--timeout",
|
|
2603
|
-
String(Math.min(remaining, childTimeout)),
|
|
2604
|
-
"--",
|
|
2605
|
-
...options.extensionPath ? ["--extension", options.extensionPath] : [],
|
|
2606
|
-
"--extension",
|
|
2607
|
-
HERDR_AGENT_STATE_EXTENSION,
|
|
2608
|
-
...options.agentArgs ?? []
|
|
2609
|
-
],
|
|
2610
|
-
{
|
|
2611
|
-
signal: options.signal,
|
|
2612
|
-
// Outlive Herdr's own readiness deadline by the diagnostic window so
|
|
2613
|
-
// its structured failure is the one reported. With the identical
|
|
2614
|
-
// value this command's kill always won the race, and "Herdr command
|
|
2615
|
-
// was killed" replaced Herdr's reason.
|
|
2616
|
-
timeout: Math.min(remaining, childTimeout) + START_DIAGNOSTIC_TIMEOUT
|
|
2617
|
-
}
|
|
2618
|
-
);
|
|
2619
|
-
} catch (failure) {
|
|
2620
|
-
if (isUnstructuredResultFailure(failure))
|
|
2621
|
-
throw withStartupDiagnostic(
|
|
2622
|
-
failure,
|
|
2623
|
-
await captureStartupDiagnostic(
|
|
2624
|
-
pi,
|
|
2625
|
-
ctx,
|
|
2596
|
+
for (; ; ) {
|
|
2597
|
+
const remaining = startupCallTimeout(startupDeadline, childTimeout);
|
|
2598
|
+
if (remaining < HERDR_START_TIMEOUT_MIN)
|
|
2599
|
+
error("start", "startup deadline exhausted before agent start");
|
|
2600
|
+
try {
|
|
2601
|
+
started = await runHerdr(
|
|
2602
|
+
pi,
|
|
2603
|
+
ctx,
|
|
2604
|
+
[
|
|
2605
|
+
"agent",
|
|
2606
|
+
"start",
|
|
2607
|
+
attempt.herdrAgent,
|
|
2608
|
+
"--kind",
|
|
2609
|
+
"pi",
|
|
2610
|
+
"--pane",
|
|
2626
2611
|
paneId,
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2612
|
+
"--timeout",
|
|
2613
|
+
String(Math.min(remaining, childTimeout)),
|
|
2614
|
+
"--",
|
|
2615
|
+
...options.extensionPath ? ["--extension", options.extensionPath] : [],
|
|
2616
|
+
"--extension",
|
|
2617
|
+
HERDR_AGENT_STATE_EXTENSION,
|
|
2618
|
+
...options.agentArgs ?? []
|
|
2619
|
+
],
|
|
2620
|
+
{
|
|
2621
|
+
signal: options.signal,
|
|
2622
|
+
// Outlive Herdr's own readiness deadline by the diagnostic window so
|
|
2623
|
+
// its structured failure is the one reported. With the identical
|
|
2624
|
+
// value this command's kill always won the race, and "Herdr command
|
|
2625
|
+
// was killed" replaced Herdr's reason.
|
|
2626
|
+
timeout: Math.min(remaining, childTimeout) + START_DIAGNOSTIC_TIMEOUT
|
|
2627
|
+
}
|
|
2630
2628
|
);
|
|
2631
|
-
|
|
2629
|
+
attempt.launchMayHaveStarted = true;
|
|
2630
|
+
break;
|
|
2631
|
+
} catch (failure) {
|
|
2632
|
+
const busy = failure instanceof OperationError && failure.detail.details?.herdrCode === "agent_pane_busy";
|
|
2633
|
+
if (busy && Date.now() < retryDeadline) {
|
|
2634
|
+
retryAttempted = true;
|
|
2635
|
+
const pane = (await runHerdr(pi, ctx, ["pane", "get", paneId], {
|
|
2636
|
+
signal: options.signal,
|
|
2637
|
+
timeout: startupCallTimeout(startupDeadline)
|
|
2638
|
+
})).pane;
|
|
2639
|
+
if (!matchesAttemptPane(pane, attempt))
|
|
2640
|
+
error(
|
|
2641
|
+
"start",
|
|
2642
|
+
`pane ${paneId} identity changed during startup retry`
|
|
2643
|
+
);
|
|
2644
|
+
const remainingRetry = retryDeadline - Date.now();
|
|
2645
|
+
if (remainingRetry > 0)
|
|
2646
|
+
await sleep(Math.min(POLL_INTERVAL, remainingRetry), void 0, {
|
|
2647
|
+
signal: options.signal
|
|
2648
|
+
});
|
|
2649
|
+
if (Date.now() >= retryDeadline) throw failure;
|
|
2650
|
+
continue;
|
|
2651
|
+
}
|
|
2652
|
+
if (!busy) attempt.launchMayHaveStarted = true;
|
|
2653
|
+
if (isUnstructuredResultFailure(failure))
|
|
2654
|
+
throw withStartupDiagnostic(
|
|
2655
|
+
failure,
|
|
2656
|
+
await captureStartupDiagnostic(
|
|
2657
|
+
pi,
|
|
2658
|
+
ctx,
|
|
2659
|
+
paneId,
|
|
2660
|
+
startupDeadline,
|
|
2661
|
+
options.signal
|
|
2662
|
+
)
|
|
2663
|
+
);
|
|
2664
|
+
throw failure;
|
|
2665
|
+
}
|
|
2632
2666
|
}
|
|
2633
2667
|
stage = "agent_result";
|
|
2634
2668
|
const agent = started.agent;
|
|
@@ -2640,7 +2674,8 @@ async function startHerdrAgent(pi, ctx, options) {
|
|
|
2640
2674
|
attempt.agent = agent;
|
|
2641
2675
|
return attempt;
|
|
2642
2676
|
} catch (cause) {
|
|
2643
|
-
if (attempt)
|
|
2677
|
+
if (attempt)
|
|
2678
|
+
throw new HerdrStartFailure(cause, stage, attempt, retryAttempted);
|
|
2644
2679
|
throw cause;
|
|
2645
2680
|
} finally {
|
|
2646
2681
|
release();
|
|
@@ -2718,7 +2753,7 @@ function normalizePaneProcess(value2, paneId, required) {
|
|
|
2718
2753
|
...foreground !== void 0 ? { foreground_processes: Object.freeze(foreground) } : {}
|
|
2719
2754
|
});
|
|
2720
2755
|
}
|
|
2721
|
-
async function
|
|
2756
|
+
async function waitForShellMarker(pi, ctx, paneId, deadline, operation, signal) {
|
|
2722
2757
|
const timeout = () => {
|
|
2723
2758
|
if (operation === "start") return startupCallTimeout(deadline);
|
|
2724
2759
|
const remaining = deadline - Date.now();
|
|
@@ -2751,11 +2786,17 @@ async function proveShellReady(pi, ctx, paneId, expected, deadline, operation, s
|
|
|
2751
2786
|
noResult: true
|
|
2752
2787
|
}
|
|
2753
2788
|
);
|
|
2789
|
+
}
|
|
2790
|
+
async function proveShellReady(pi, ctx, paneId, expected, deadline, operation, signal) {
|
|
2791
|
+
await waitForShellMarker(pi, ctx, paneId, deadline, operation, signal);
|
|
2792
|
+
const timeout = operation === "start" ? startupCallTimeout(deadline) : deadline - Date.now();
|
|
2793
|
+
if (timeout <= 0)
|
|
2794
|
+
error(operation, `pane ${paneId} shell readiness deadline exhausted`);
|
|
2754
2795
|
const result = await runHerdr(
|
|
2755
2796
|
pi,
|
|
2756
2797
|
ctx,
|
|
2757
2798
|
["pane", "process-info", "--pane", paneId],
|
|
2758
|
-
{ signal, timeout
|
|
2799
|
+
{ signal, timeout }
|
|
2759
2800
|
);
|
|
2760
2801
|
const value2 = result?.process_info;
|
|
2761
2802
|
const shell = normalizePaneProcess(value2, paneId, true);
|
|
@@ -3026,7 +3067,7 @@ async function rollbackHerdrStart(pi, ctx, started, signal) {
|
|
|
3026
3067
|
const release = await lockLifecycle(ctx, signal);
|
|
3027
3068
|
try {
|
|
3028
3069
|
const expectedShell = started.shellProcess;
|
|
3029
|
-
if (!expectedShell)
|
|
3070
|
+
if (started.launchMayHaveStarted && !expectedShell)
|
|
3030
3071
|
error("rollback", `pane ${started.paneId} process ownership is unproven`);
|
|
3031
3072
|
const listedAgents2 = await runHerdr(pi, ctx, ["agent", "list"], {
|
|
3032
3073
|
signal
|
|
@@ -3042,7 +3083,9 @@ async function rollbackHerdrStart(pi, ctx, started, signal) {
|
|
|
3042
3083
|
);
|
|
3043
3084
|
if (replacement)
|
|
3044
3085
|
error("rollback", `pane ${started.paneId} has a replacement agent`);
|
|
3045
|
-
if (managed)
|
|
3086
|
+
if (!started.launchMayHaveStarted && managed)
|
|
3087
|
+
error("rollback", `pane ${started.paneId} agent ownership changed`);
|
|
3088
|
+
if (started.launchMayHaveStarted && managed) {
|
|
3046
3089
|
const observedSession = sessionIdentity(managed.agent_session);
|
|
3047
3090
|
const sessionReference = started.sessionReference ?? (observedSession?.kind === "id" ? { id: observedSession.value } : observedSession?.kind === "path" ? { path: observedSession.value } : void 0);
|
|
3048
3091
|
const initial = await proveExactRunningAgent(
|
|
@@ -3128,11 +3171,16 @@ async function rollbackHerdrStart(pi, ctx, started, signal) {
|
|
|
3128
3171
|
error("rollback", `tab ${started.tabId} pane ownership is unproven`);
|
|
3129
3172
|
}
|
|
3130
3173
|
const pane = (await runHerdr(pi, ctx, ["pane", "get", started.paneId], { signal })).pane;
|
|
3131
|
-
if (!pane
|
|
3174
|
+
if (!matchesAttemptTerminal(pane, started))
|
|
3132
3175
|
error("rollback", `pane ${started.paneId} ownership is unproven`);
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3176
|
+
if (started.launchMayHaveStarted || !sameCwd(pane.cwd, started.cwd)) {
|
|
3177
|
+
const observed = await paneProcess(pi, ctx, started.paneId, signal);
|
|
3178
|
+
if (!expectedShell || !observed || !sameShellProcessOwner(expectedShell, observed))
|
|
3179
|
+
error(
|
|
3180
|
+
"rollback",
|
|
3181
|
+
`pane ${started.paneId} process ownership is unproven`
|
|
3182
|
+
);
|
|
3183
|
+
}
|
|
3136
3184
|
if (!managed) {
|
|
3137
3185
|
const listedAgents3 = await runHerdr(pi, ctx, ["agent", "list"], {
|
|
3138
3186
|
signal
|