pi-herdsman 0.22.0 → 0.22.2
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/README.md +3 -2
- package/SKILL.md +10 -6
- package/dist/agent-definitions/managed-lead.md +1 -1
- package/dist/agent-definitions/orchestrator-lead.md +1 -1
- package/dist/index.js +645 -260
- package/dist/index.js.map +3 -3
- package/docs/adr/0013-use-herdr-worktree-removal-for-project-retirement.md +1 -1
- package/docs/adr/0015-use-reserved-definition-for-managed-project-leads.md +5 -2
- package/docs/adr/0017-use-project-assignments-for-managed-lead-authority.md +2 -0
- package/docs/adr/0018-use-standalone-lead-execution-profiles.md +4 -2
- package/docs/adr/0019-retire-project-assignments-when-worktrees-are-absent.md +88 -0
- package/docs/concepts/coordination.md +12 -8
- package/docs/coordination-api.md +4 -3
- package/docs/development/product-philosophy.md +7 -0
- package/docs/development/smoke-testing.md +9 -8
- package/docs/guides/customizing-agents.md +13 -9
- package/docs/guides/project-orchestration.md +9 -8
- package/docs/reference/commands.md +3 -2
- package/docs/reference/staff.md +10 -11
- package/docs/reference/status-widget.md +13 -6
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -8,14 +8,14 @@ import {
|
|
|
8
8
|
SessionManager as SessionManager2
|
|
9
9
|
} from "@earendil-works/pi-coding-agent";
|
|
10
10
|
import { readFileSync as readFileSync9, statSync as statSync7, unlinkSync as unlinkSync9 } from "node:fs";
|
|
11
|
-
import { basename as basename6, dirname as dirname7, resolve as
|
|
11
|
+
import { basename as basename6, dirname as dirname7, resolve as resolve7 } from "node:path";
|
|
12
12
|
import { setTimeout as delay2 } from "node:timers/promises";
|
|
13
13
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
14
14
|
|
|
15
15
|
// package.json
|
|
16
16
|
var package_default = {
|
|
17
17
|
name: "pi-herdsman",
|
|
18
|
-
version: "0.22.
|
|
18
|
+
version: "0.22.2",
|
|
19
19
|
type: "module",
|
|
20
20
|
description: "Asynchronous Pi subagents and agent fleet orchestration for parallel coding agents with nested delegation, background work, and supervision in herdr.",
|
|
21
21
|
license: "Apache-2.0",
|
|
@@ -302,6 +302,7 @@ import {
|
|
|
302
302
|
rmdirSync as rmdirSync2,
|
|
303
303
|
statSync,
|
|
304
304
|
unlinkSync as unlinkSync2,
|
|
305
|
+
watch,
|
|
305
306
|
writeSync as writeSync2
|
|
306
307
|
} from "node:fs";
|
|
307
308
|
import { basename as basename2, dirname as dirname2, join as join3 } from "node:path";
|
|
@@ -568,6 +569,64 @@ var LIMITS = {
|
|
|
568
569
|
result: 4 * 1024 * 1024
|
|
569
570
|
};
|
|
570
571
|
var root = join3(herdsmanDataRoot(), "runtime", "mailboxes-v5");
|
|
572
|
+
function observeMailbox(directory, relevant, reconcile, onError) {
|
|
573
|
+
let watcher;
|
|
574
|
+
let scheduled;
|
|
575
|
+
let stopped = false;
|
|
576
|
+
let errorReported = false;
|
|
577
|
+
const schedule = () => {
|
|
578
|
+
if (stopped || scheduled) return;
|
|
579
|
+
scheduled = setImmediate(() => {
|
|
580
|
+
scheduled = void 0;
|
|
581
|
+
if (stopped) return;
|
|
582
|
+
try {
|
|
583
|
+
reconcile();
|
|
584
|
+
} catch (error2) {
|
|
585
|
+
onError(error2);
|
|
586
|
+
}
|
|
587
|
+
});
|
|
588
|
+
};
|
|
589
|
+
const attach = () => {
|
|
590
|
+
if (stopped || watcher) return;
|
|
591
|
+
try {
|
|
592
|
+
const next = watch(
|
|
593
|
+
directory,
|
|
594
|
+
{ persistent: false },
|
|
595
|
+
(_event, filename) => {
|
|
596
|
+
const name = filename?.toString();
|
|
597
|
+
if (!name || relevant(name)) schedule();
|
|
598
|
+
}
|
|
599
|
+
);
|
|
600
|
+
next.on("error", (error2) => {
|
|
601
|
+
next.close();
|
|
602
|
+
if (watcher === next) watcher = void 0;
|
|
603
|
+
if (!stopped && !errorReported) {
|
|
604
|
+
errorReported = true;
|
|
605
|
+
onError(error2);
|
|
606
|
+
}
|
|
607
|
+
});
|
|
608
|
+
watcher = next;
|
|
609
|
+
} catch (error2) {
|
|
610
|
+
if (!errorReported) {
|
|
611
|
+
errorReported = true;
|
|
612
|
+
onError(error2);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
};
|
|
616
|
+
attach();
|
|
617
|
+
const timer = setInterval(() => {
|
|
618
|
+
attach();
|
|
619
|
+
schedule();
|
|
620
|
+
}, 1e3);
|
|
621
|
+
timer.unref?.();
|
|
622
|
+
return () => {
|
|
623
|
+
stopped = true;
|
|
624
|
+
clearInterval(timer);
|
|
625
|
+
if (scheduled) clearImmediate(scheduled);
|
|
626
|
+
watcher?.close();
|
|
627
|
+
watcher = void 0;
|
|
628
|
+
};
|
|
629
|
+
}
|
|
571
630
|
function agentMailboxPath(workspaceId, agentLabel) {
|
|
572
631
|
return join3(
|
|
573
632
|
root,
|
|
@@ -1076,7 +1135,7 @@ function parseControlMarker(text) {
|
|
|
1076
1135
|
return UUID2.test(id) && id.length === 36 ? id : void 0;
|
|
1077
1136
|
}
|
|
1078
1137
|
function waitForState(path, predicate, options) {
|
|
1079
|
-
return new Promise((
|
|
1138
|
+
return new Promise((resolve8, reject) => {
|
|
1080
1139
|
let done = false;
|
|
1081
1140
|
let abortListener;
|
|
1082
1141
|
const finish = (error2, state) => {
|
|
@@ -1086,7 +1145,7 @@ function waitForState(path, predicate, options) {
|
|
|
1086
1145
|
clearInterval(interval);
|
|
1087
1146
|
if (abortListener && options.signal)
|
|
1088
1147
|
options.signal.removeEventListener("abort", abortListener);
|
|
1089
|
-
error2 ? reject(error2) :
|
|
1148
|
+
error2 ? reject(error2) : resolve8(state);
|
|
1090
1149
|
};
|
|
1091
1150
|
const check = () => {
|
|
1092
1151
|
try {
|
|
@@ -2144,16 +2203,51 @@ var MAX_PROCESS_CMDLINE_BYTES = 4 * 1024;
|
|
|
2144
2203
|
var POLL_INTERVAL = 75;
|
|
2145
2204
|
var INITIAL_RATIO = 0.65;
|
|
2146
2205
|
var AGENT_SPLIT_RATIO = 0.5;
|
|
2147
|
-
var
|
|
2206
|
+
var HERDR_EVENT_SUBSCRIPTIONS = [
|
|
2207
|
+
{ type: "workspace.created" },
|
|
2208
|
+
{ type: "workspace.updated" },
|
|
2209
|
+
{ type: "workspace.metadata_updated" },
|
|
2210
|
+
{ type: "workspace.renamed" },
|
|
2211
|
+
{ type: "pane.created" },
|
|
2148
2212
|
{ type: "pane.closed" },
|
|
2149
2213
|
{ type: "pane.exited" },
|
|
2150
2214
|
{ type: "pane.moved" },
|
|
2215
|
+
{ type: "pane.updated" },
|
|
2216
|
+
{ type: "pane.agent_detected" },
|
|
2217
|
+
{ type: "pane.agent_status_changed" },
|
|
2218
|
+
{ type: "tab.created" },
|
|
2151
2219
|
{ type: "tab.closed" },
|
|
2220
|
+
{ type: "tab.renamed" },
|
|
2152
2221
|
{ type: "workspace.closed" },
|
|
2222
|
+
{ type: "worktree.created" },
|
|
2223
|
+
{ type: "worktree.opened" },
|
|
2153
2224
|
{ type: "worktree.removed" }
|
|
2154
2225
|
];
|
|
2155
|
-
var
|
|
2156
|
-
var
|
|
2226
|
+
var HERDR_EVENT_SUBSCRIPTION_ID = "pi-herdsman:events";
|
|
2227
|
+
var HERDR_EVENT_RECONNECT_MS = 1e3;
|
|
2228
|
+
var PRESENTATION_MIN_INTERVAL_MS = 2e3;
|
|
2229
|
+
var LIFECYCLE_EVENTS = /* @__PURE__ */ new Set([
|
|
2230
|
+
"workspace_created",
|
|
2231
|
+
"workspace_closed",
|
|
2232
|
+
"pane_created",
|
|
2233
|
+
"pane_closed",
|
|
2234
|
+
"pane_exited",
|
|
2235
|
+
"pane_moved",
|
|
2236
|
+
"tab_created",
|
|
2237
|
+
"tab_closed",
|
|
2238
|
+
"worktree_created",
|
|
2239
|
+
"worktree_opened",
|
|
2240
|
+
"worktree_removed"
|
|
2241
|
+
]);
|
|
2242
|
+
var PRESENTATION_EVENTS = /* @__PURE__ */ new Set([
|
|
2243
|
+
"workspace_updated",
|
|
2244
|
+
"workspace_metadata_updated",
|
|
2245
|
+
"workspace_renamed",
|
|
2246
|
+
"pane_updated",
|
|
2247
|
+
"pane_agent_detected",
|
|
2248
|
+
"pane_agent_status_changed",
|
|
2249
|
+
"tab_renamed"
|
|
2250
|
+
]);
|
|
2157
2251
|
var MAX_EVENT_BUFFER_BYTES = 1024 * 1024;
|
|
2158
2252
|
var HERDR_AGENT_STATE_EXTENSION = join5(
|
|
2159
2253
|
getAgentDir3(),
|
|
@@ -2493,10 +2587,31 @@ async function herdrSessionSnapshot(pi, ctx, signal) {
|
|
|
2493
2587
|
function watchHerdrLifecycle(socketPath2, signal, onChange) {
|
|
2494
2588
|
let socket;
|
|
2495
2589
|
let retry;
|
|
2590
|
+
let presentationTimer;
|
|
2591
|
+
let nextPresentationAt = 0;
|
|
2592
|
+
const invalidatePresentation = () => {
|
|
2593
|
+
if (presentationTimer || signal.aborted) return;
|
|
2594
|
+
presentationTimer = setTimeout(
|
|
2595
|
+
() => {
|
|
2596
|
+
presentationTimer = void 0;
|
|
2597
|
+
if (signal.aborted) return;
|
|
2598
|
+
nextPresentationAt = Date.now() + PRESENTATION_MIN_INTERVAL_MS;
|
|
2599
|
+
onChange(void 0, "presentation");
|
|
2600
|
+
},
|
|
2601
|
+
Math.max(0, nextPresentationAt - Date.now())
|
|
2602
|
+
);
|
|
2603
|
+
presentationTimer.unref?.();
|
|
2604
|
+
};
|
|
2605
|
+
const flushPresentation = () => {
|
|
2606
|
+
if (presentationTimer) clearTimeout(presentationTimer);
|
|
2607
|
+
presentationTimer = void 0;
|
|
2608
|
+
nextPresentationAt = Date.now() + PRESENTATION_MIN_INTERVAL_MS;
|
|
2609
|
+
};
|
|
2496
2610
|
const connect = () => {
|
|
2497
2611
|
if (signal.aborted) return;
|
|
2498
2612
|
let buffer = "";
|
|
2499
2613
|
let subscribed = false;
|
|
2614
|
+
let disconnectReported = false;
|
|
2500
2615
|
const current = createConnection(socketPath2);
|
|
2501
2616
|
socket = current;
|
|
2502
2617
|
current.setEncoding("utf8");
|
|
@@ -2508,9 +2623,9 @@ function watchHerdrLifecycle(socketPath2, signal, onChange) {
|
|
|
2508
2623
|
current.once("connect", () => {
|
|
2509
2624
|
current.write(
|
|
2510
2625
|
`${JSON.stringify({
|
|
2511
|
-
id:
|
|
2626
|
+
id: HERDR_EVENT_SUBSCRIPTION_ID,
|
|
2512
2627
|
method: "events.subscribe",
|
|
2513
|
-
params: { subscriptions:
|
|
2628
|
+
params: { subscriptions: HERDR_EVENT_SUBSCRIPTIONS }
|
|
2514
2629
|
})}
|
|
2515
2630
|
`
|
|
2516
2631
|
);
|
|
@@ -2534,23 +2649,45 @@ function watchHerdrLifecycle(socketPath2, signal, onChange) {
|
|
|
2534
2649
|
drop();
|
|
2535
2650
|
return;
|
|
2536
2651
|
}
|
|
2652
|
+
if (message?.id === HERDR_EVENT_SUBSCRIPTION_ID && message?.error) {
|
|
2653
|
+
if (message.error.code === "events_lost" && !signal.aborted) {
|
|
2654
|
+
disconnectReported = true;
|
|
2655
|
+
flushPresentation();
|
|
2656
|
+
onChange(void 0, "reconcile");
|
|
2657
|
+
}
|
|
2658
|
+
drop();
|
|
2659
|
+
return;
|
|
2660
|
+
}
|
|
2537
2661
|
if (!subscribed) {
|
|
2538
|
-
if (message?.id !==
|
|
2662
|
+
if (signal.aborted || message?.id !== HERDR_EVENT_SUBSCRIPTION_ID || message?.result?.type !== "subscription_started") {
|
|
2539
2663
|
drop();
|
|
2540
2664
|
return;
|
|
2541
2665
|
}
|
|
2542
2666
|
subscribed = true;
|
|
2543
|
-
|
|
2667
|
+
flushPresentation();
|
|
2668
|
+
onChange(void 0, "reconcile");
|
|
2544
2669
|
continue;
|
|
2545
2670
|
}
|
|
2546
|
-
|
|
2671
|
+
if (signal.aborted) return;
|
|
2672
|
+
const event = message?.event;
|
|
2673
|
+
const category = LIFECYCLE_EVENTS.has(event) ? "lifecycle" : PRESENTATION_EVENTS.has(event) ? "presentation" : void 0;
|
|
2674
|
+
if (category === "presentation") invalidatePresentation();
|
|
2675
|
+
else if (category) {
|
|
2676
|
+
flushPresentation();
|
|
2677
|
+
onChange(removedHerdrWorktree(message), category);
|
|
2678
|
+
}
|
|
2547
2679
|
}
|
|
2548
2680
|
});
|
|
2549
2681
|
current.on("error", drop);
|
|
2550
2682
|
current.once("close", () => {
|
|
2551
2683
|
if (socket === current) socket = void 0;
|
|
2552
2684
|
if (signal.aborted) return;
|
|
2553
|
-
|
|
2685
|
+
if (subscribed && !disconnectReported) {
|
|
2686
|
+
disconnectReported = true;
|
|
2687
|
+
flushPresentation();
|
|
2688
|
+
onChange(void 0, "reconcile");
|
|
2689
|
+
}
|
|
2690
|
+
retry = setTimeout(connect, HERDR_EVENT_RECONNECT_MS);
|
|
2554
2691
|
retry.unref?.();
|
|
2555
2692
|
});
|
|
2556
2693
|
};
|
|
@@ -2558,6 +2695,7 @@ function watchHerdrLifecycle(socketPath2, signal, onChange) {
|
|
|
2558
2695
|
"abort",
|
|
2559
2696
|
() => {
|
|
2560
2697
|
if (retry) clearTimeout(retry);
|
|
2698
|
+
if (presentationTimer) clearTimeout(presentationTimer);
|
|
2561
2699
|
socket?.destroy();
|
|
2562
2700
|
},
|
|
2563
2701
|
{ once: true }
|
|
@@ -3895,7 +4033,7 @@ import {
|
|
|
3895
4033
|
} from "@earendil-works/pi-coding-agent";
|
|
3896
4034
|
import { contentText as contentText2 } from "@earendil-works/pi-ai";
|
|
3897
4035
|
import { realpathSync as realpathSync3, statSync as statSync5 } from "node:fs";
|
|
3898
|
-
import {
|
|
4036
|
+
import { unlinkSync as unlinkSync7 } from "node:fs";
|
|
3899
4037
|
import { createHash as createHash7, randomUUID as randomUUID8 } from "node:crypto";
|
|
3900
4038
|
import { homedir as homedir3 } from "node:os";
|
|
3901
4039
|
import { join as join9, resolve as resolve4 } from "node:path";
|
|
@@ -8090,7 +8228,7 @@ function projectSupervision(options) {
|
|
|
8090
8228
|
// extension/agent-controller.ts
|
|
8091
8229
|
function buildAgentStatusSnapshot(view, ctx, host) {
|
|
8092
8230
|
const ownerSessionId = ctx.sessionManager.getSessionId();
|
|
8093
|
-
const unresolvedMailboxState = host.scope.kind === "lead" &&
|
|
8231
|
+
const unresolvedMailboxState = host.scope.kind === "lead" && view.stateIssues.length > 0;
|
|
8094
8232
|
const listed = view.visible.map(
|
|
8095
8233
|
(snapshot) => host.listedAgentRecord(
|
|
8096
8234
|
view,
|
|
@@ -8262,6 +8400,7 @@ function createAgentStatusRuntime() {
|
|
|
8262
8400
|
if (ctx.mode !== "tui" || !ctx.hasUI) return;
|
|
8263
8401
|
const primed = statusPrimed && ctx === statusContext;
|
|
8264
8402
|
const generation = ctx === statusContext && requestActive ? statusGeneration : ++statusGeneration;
|
|
8403
|
+
clearTimer();
|
|
8265
8404
|
statusContext = ctx;
|
|
8266
8405
|
ctx.ui.setWidget("pi-herdsman", (tui, theme) => {
|
|
8267
8406
|
const widget = createStatusWidget(() => tui.requestRender(), theme);
|
|
@@ -8273,7 +8412,8 @@ function createAgentStatusRuntime() {
|
|
|
8273
8412
|
return widget;
|
|
8274
8413
|
});
|
|
8275
8414
|
requestActive = true;
|
|
8276
|
-
statusTimer = setInterval(() => void refresh(ctx, generation),
|
|
8415
|
+
statusTimer = setInterval(() => void refresh(ctx, generation), 1e4);
|
|
8416
|
+
statusTimer.unref?.();
|
|
8277
8417
|
statusPrimed = false;
|
|
8278
8418
|
if (!primed) void refresh(ctx, generation);
|
|
8279
8419
|
},
|
|
@@ -8506,11 +8646,6 @@ pending-result agent work still blocks escalation.`;
|
|
|
8506
8646
|
function durableIdentityKey(state) {
|
|
8507
8647
|
return `${state.workspaceId}\0${state.piSessionId}`;
|
|
8508
8648
|
}
|
|
8509
|
-
function durableParentCandidates(snapshot, child) {
|
|
8510
|
-
return snapshot.filter(
|
|
8511
|
-
({ state }) => state.workspaceId === child.workspaceId && state.piSessionId === child.ownerSessionId
|
|
8512
|
-
);
|
|
8513
|
-
}
|
|
8514
8649
|
function directChildStates(parent) {
|
|
8515
8650
|
return listAgentStates().filter(
|
|
8516
8651
|
({ state }) => state.workspaceId === parent.workspaceId && state.ownerSessionId === parent.piSessionId
|
|
@@ -8635,37 +8770,31 @@ function visibleAgentSnapshots(snapshot, scope, ownerSessionId) {
|
|
|
8635
8770
|
);
|
|
8636
8771
|
if (scope.kind === "managed-agent") return direct;
|
|
8637
8772
|
const visible = [...direct];
|
|
8638
|
-
const
|
|
8639
|
-
|
|
8640
|
-
)
|
|
8641
|
-
|
|
8642
|
-
|
|
8643
|
-
|
|
8644
|
-
|
|
8645
|
-
|
|
8646
|
-
|
|
8647
|
-
|
|
8648
|
-
|
|
8649
|
-
|
|
8650
|
-
|
|
8651
|
-
|
|
8652
|
-
|
|
8653
|
-
|
|
8654
|
-
|
|
8655
|
-
|
|
8656
|
-
|
|
8657
|
-
|
|
8773
|
+
const byIdentity = /* @__PURE__ */ new Map();
|
|
8774
|
+
const childrenByOwner = /* @__PURE__ */ new Map();
|
|
8775
|
+
for (const agent of agents) {
|
|
8776
|
+
const identity = durableIdentityKey(agent.state);
|
|
8777
|
+
const owner = `${agent.state.workspaceId}\0${agent.state.ownerSessionId}`;
|
|
8778
|
+
const identityCandidates = byIdentity.get(identity) ?? [];
|
|
8779
|
+
identityCandidates.push(agent);
|
|
8780
|
+
byIdentity.set(identity, identityCandidates);
|
|
8781
|
+
const children = childrenByOwner.get(owner) ?? [];
|
|
8782
|
+
children.push(agent);
|
|
8783
|
+
childrenByOwner.set(owner, children);
|
|
8784
|
+
}
|
|
8785
|
+
const seen = new Set(direct);
|
|
8786
|
+
for (let index = 0; index < visible.length; index++) {
|
|
8787
|
+
const parent = visible[index];
|
|
8788
|
+
const identity = durableIdentityKey(parent.state);
|
|
8789
|
+
if (byIdentity.get(identity)?.length !== 1) continue;
|
|
8790
|
+
for (const child of childrenByOwner.get(identity) ?? []) {
|
|
8791
|
+
if (seen.has(child)) continue;
|
|
8792
|
+
seen.add(child);
|
|
8793
|
+
visible.push({ ...child, parentLabel: parent.state.agentLabel });
|
|
8794
|
+
}
|
|
8658
8795
|
}
|
|
8659
8796
|
return visible;
|
|
8660
8797
|
}
|
|
8661
|
-
function unknownAgentRecords() {
|
|
8662
|
-
return listAgentStateIssues().map(({ diagnostic }) => ({
|
|
8663
|
-
state: "unknown",
|
|
8664
|
-
available_tools: [],
|
|
8665
|
-
managed: true,
|
|
8666
|
-
diagnostic
|
|
8667
|
-
}));
|
|
8668
|
-
}
|
|
8669
8798
|
function statusBreadcrumb(snapshot, candidate, identity, sameIdentity) {
|
|
8670
8799
|
if (!candidate) return ["?"];
|
|
8671
8800
|
const current = snapshot.agents.find(
|
|
@@ -9417,11 +9546,25 @@ function assertManagedAgentCascadeSafe(snapshots, sameIdentity, deliveredRootRes
|
|
|
9417
9546
|
async function managedAgentSnapshots(pi, ctx, dependencies, signal, proveLead = false, allWorkspaces = false, suppliedInventory, allowTranscriptDefinitionFallback = true) {
|
|
9418
9547
|
const inventory = suppliedInventory ?? await herdrSessionSnapshot(pi, ctx, signal);
|
|
9419
9548
|
const currentWorkspaceId = process.env.HERDR_WORKSPACE_ID ?? ctx.cwd;
|
|
9420
|
-
const allMailboxes =
|
|
9549
|
+
const { states: allMailboxes, issues: stateIssues } = scanAgentStates();
|
|
9421
9550
|
const mailboxes = allWorkspaces ? allMailboxes : allMailboxes.filter(
|
|
9422
9551
|
({ state }) => state.workspaceId === currentWorkspaceId
|
|
9423
9552
|
);
|
|
9424
|
-
const
|
|
9553
|
+
const pendingOwners = /* @__PURE__ */ new Set();
|
|
9554
|
+
const completedResultsPending = mailboxes.map(({ path, state }) => {
|
|
9555
|
+
let pending = false;
|
|
9556
|
+
if (state.completedRequestId) {
|
|
9557
|
+
try {
|
|
9558
|
+
pending = !!readResult(path, state.completedRequestId);
|
|
9559
|
+
} catch {
|
|
9560
|
+
pending = true;
|
|
9561
|
+
}
|
|
9562
|
+
}
|
|
9563
|
+
if (state.resultError || state.activeRequestId || pending)
|
|
9564
|
+
pendingOwners.add(`${state.workspaceId}\0${state.ownerSessionId}`);
|
|
9565
|
+
return pending;
|
|
9566
|
+
});
|
|
9567
|
+
const agents = mailboxes.map(({ path, state }, index) => {
|
|
9425
9568
|
const presence = managedAgentPresence(state, inventory);
|
|
9426
9569
|
const agent = presence.kind === "live" ? presence.agent : void 0;
|
|
9427
9570
|
let agentDefinition;
|
|
@@ -9440,14 +9583,7 @@ async function managedAgentSnapshots(pi, ctx, dependencies, signal, proveLead =
|
|
|
9440
9583
|
const piSessionId = session?.kind === "id" ? session.value : state.piSessionId;
|
|
9441
9584
|
const piSessionPath = session?.kind === "path" ? session.value : state.piSessionFile;
|
|
9442
9585
|
const lifecycleState = agent ? normalizeHerdrLifecycleState(agent) : "unknown";
|
|
9443
|
-
|
|
9444
|
-
if (state.completedRequestId) {
|
|
9445
|
-
try {
|
|
9446
|
-
completionPending = !!readResult(path, state.completedRequestId);
|
|
9447
|
-
} catch {
|
|
9448
|
-
completionPending = true;
|
|
9449
|
-
}
|
|
9450
|
-
}
|
|
9586
|
+
const completionPending = completedResultsPending[index];
|
|
9451
9587
|
const handoffPending = unacknowledgedRequestExists(path, state);
|
|
9452
9588
|
const liveState = agent ? agentControlState(
|
|
9453
9589
|
lifecycleState,
|
|
@@ -9457,19 +9593,7 @@ async function managedAgentSnapshots(pi, ctx, dependencies, signal, proveLead =
|
|
|
9457
9593
|
!!state.pendingAskId,
|
|
9458
9594
|
!!state.resultError
|
|
9459
9595
|
) : "unknown";
|
|
9460
|
-
const pendingDirectChildWork =
|
|
9461
|
-
({ path: childPath, state: child }) => {
|
|
9462
|
-
if (child.workspaceId !== state.workspaceId || child.ownerSessionId !== state.piSessionId)
|
|
9463
|
-
return false;
|
|
9464
|
-
if (child.resultError || child.activeRequestId) return true;
|
|
9465
|
-
if (!child.completedRequestId) return false;
|
|
9466
|
-
try {
|
|
9467
|
-
return !!readResult(childPath, child.completedRequestId);
|
|
9468
|
-
} catch {
|
|
9469
|
-
return true;
|
|
9470
|
-
}
|
|
9471
|
-
}
|
|
9472
|
-
);
|
|
9596
|
+
const pendingDirectChildWork = pendingOwners.has(durableIdentityKey(state));
|
|
9473
9597
|
const waitingForChildren = liveState === "settling" && !!state.activeRequestId && !completionPending && !handoffPending && !state.resultError && pendingDirectChildWork;
|
|
9474
9598
|
const existingLiveProjection = waitingForChildren ? "blocked" : liveState;
|
|
9475
9599
|
const projectedState = completionPending || state.resultError ? "settling" : presence.kind === "lost" ? "lost" : presence.kind === "unknown" ? "unknown" : existingLiveProjection;
|
|
@@ -9512,7 +9636,13 @@ async function managedAgentSnapshots(pi, ctx, dependencies, signal, proveLead =
|
|
|
9512
9636
|
} catch {
|
|
9513
9637
|
}
|
|
9514
9638
|
}
|
|
9515
|
-
return {
|
|
9639
|
+
return {
|
|
9640
|
+
agents,
|
|
9641
|
+
mailboxes,
|
|
9642
|
+
stateIssues,
|
|
9643
|
+
liveAgents: inventory.agents,
|
|
9644
|
+
leadSessionIds
|
|
9645
|
+
};
|
|
9516
9646
|
}
|
|
9517
9647
|
function listedAgentRecord(view, snapshot, ownerSessionId, scope, unresolvedMailboxState, dependencies) {
|
|
9518
9648
|
const { listed, state, presence, parentLabel } = snapshot;
|
|
@@ -9581,9 +9711,7 @@ function createAgentController(pi, options) {
|
|
|
9581
9711
|
const runtimes = /* @__PURE__ */ new Map();
|
|
9582
9712
|
const pendingStarts = /* @__PURE__ */ new Map();
|
|
9583
9713
|
const resultWatchers = /* @__PURE__ */ new Map();
|
|
9584
|
-
const resultWatchRetries = /* @__PURE__ */ new Map();
|
|
9585
9714
|
const askWatchers = /* @__PURE__ */ new Map();
|
|
9586
|
-
const askWatchRetries = /* @__PURE__ */ new Map();
|
|
9587
9715
|
const resultDeliveryInFlight = /* @__PURE__ */ new Set();
|
|
9588
9716
|
const askDeliveryInFlight = /* @__PURE__ */ new Set();
|
|
9589
9717
|
const resultDeliveryRetries = /* @__PURE__ */ new Map();
|
|
@@ -10279,7 +10407,8 @@ function createAgentController(pi, options) {
|
|
|
10279
10407
|
runtime.startedAt = Date.now();
|
|
10280
10408
|
runtime.contextPercent = void 0;
|
|
10281
10409
|
watchResult(runtime, context, signal);
|
|
10282
|
-
|
|
10410
|
+
if (runtime.activeRequestId === requestId)
|
|
10411
|
+
watchAsk(runtime, context, signal);
|
|
10283
10412
|
}
|
|
10284
10413
|
return requestId;
|
|
10285
10414
|
} finally {
|
|
@@ -10520,7 +10649,12 @@ function createAgentController(pi, options) {
|
|
|
10520
10649
|
scope,
|
|
10521
10650
|
context.sessionManager.getSessionId()
|
|
10522
10651
|
);
|
|
10523
|
-
const unknown = scope?.kind === "lead" ?
|
|
10652
|
+
const unknown = scope?.kind === "lead" ? snapshot2.stateIssues.map(({ diagnostic }) => ({
|
|
10653
|
+
state: "unknown",
|
|
10654
|
+
available_tools: [],
|
|
10655
|
+
managed: true,
|
|
10656
|
+
diagnostic
|
|
10657
|
+
})) : [];
|
|
10524
10658
|
const unresolved = unknown.length > 0;
|
|
10525
10659
|
const agents = visible.map(
|
|
10526
10660
|
(item) => listRecord(
|
|
@@ -10630,7 +10764,7 @@ function createAgentController(pi, options) {
|
|
|
10630
10764
|
"Agent belongs to another owner session",
|
|
10631
10765
|
"transcript"
|
|
10632
10766
|
);
|
|
10633
|
-
const unresolved = scope?.kind === "lead" &&
|
|
10767
|
+
const unresolved = scope?.kind === "lead" && view.stateIssues.length > 0;
|
|
10634
10768
|
const actions = listedAgentRecord(
|
|
10635
10769
|
view,
|
|
10636
10770
|
candidate,
|
|
@@ -11106,21 +11240,13 @@ function createAgentController(pi, options) {
|
|
|
11106
11240
|
if (!id) return;
|
|
11107
11241
|
cancelResultRetry(resultDeliveryRetries, runtime, id);
|
|
11108
11242
|
cancelResultRetry(resultCleanupRetries, runtime, id);
|
|
11109
|
-
const path =
|
|
11110
|
-
|
|
11111
|
-
if (retry) clearTimeout(retry);
|
|
11112
|
-
resultWatchRetries.delete(path);
|
|
11113
|
-
const watcher = resultWatchers.get(path);
|
|
11114
|
-
if (watcher) unwatchFile(path, watcher);
|
|
11243
|
+
const path = join9(runtime.mailboxPath, `result-${id}.json`);
|
|
11244
|
+
resultWatchers.get(path)?.();
|
|
11115
11245
|
resultWatchers.delete(path);
|
|
11116
11246
|
};
|
|
11117
11247
|
const stopAskWatcher = (runtime) => {
|
|
11118
11248
|
const path = agentStatePath(runtime.mailboxPath);
|
|
11119
|
-
|
|
11120
|
-
if (retry) clearTimeout(retry);
|
|
11121
|
-
askWatchRetries.delete(path);
|
|
11122
|
-
const watcher = askWatchers.get(path);
|
|
11123
|
-
if (watcher) unwatchFile(path, watcher);
|
|
11249
|
+
askWatchers.get(path)?.();
|
|
11124
11250
|
askWatchers.delete(path);
|
|
11125
11251
|
};
|
|
11126
11252
|
const invalidateRuntime = (label) => {
|
|
@@ -11141,29 +11267,10 @@ function createAgentController(pi, options) {
|
|
|
11141
11267
|
runtime.startedAt = void 0;
|
|
11142
11268
|
runtimes.delete(label);
|
|
11143
11269
|
};
|
|
11144
|
-
const watch = (path, watchers, retries, context, check, retry) => {
|
|
11145
|
-
watchers.set(path, check);
|
|
11146
|
-
try {
|
|
11147
|
-
check();
|
|
11148
|
-
if (watchers.get(path) !== check) return;
|
|
11149
|
-
watchFile(path, { interval: 250 }, check);
|
|
11150
|
-
} catch (error2) {
|
|
11151
|
-
watchers.delete(path);
|
|
11152
|
-
options.reportWatcherError(context, error2);
|
|
11153
|
-
if (!retries.has(path)) {
|
|
11154
|
-
retries.set(
|
|
11155
|
-
path,
|
|
11156
|
-
setTimeout(() => {
|
|
11157
|
-
retries.delete(path);
|
|
11158
|
-
retry();
|
|
11159
|
-
}, 250)
|
|
11160
|
-
);
|
|
11161
|
-
}
|
|
11162
|
-
}
|
|
11163
|
-
};
|
|
11164
11270
|
const watchResult = (runtime, context, signal, requestId = runtime.activeRequestId) => {
|
|
11165
11271
|
if (!requestId) return;
|
|
11166
|
-
const
|
|
11272
|
+
const filename = `result-${requestId}.json`;
|
|
11273
|
+
const path = join9(runtime.mailboxPath, filename);
|
|
11167
11274
|
stopResultWatcher(runtime, requestId);
|
|
11168
11275
|
const check = () => {
|
|
11169
11276
|
if (active && runtimes.get(runtime.label) === runtime)
|
|
@@ -11173,10 +11280,14 @@ function createAgentController(pi, options) {
|
|
|
11173
11280
|
} catch {
|
|
11174
11281
|
}
|
|
11175
11282
|
};
|
|
11176
|
-
|
|
11177
|
-
|
|
11178
|
-
|
|
11179
|
-
|
|
11283
|
+
const stop = observeMailbox(
|
|
11284
|
+
runtime.mailboxPath,
|
|
11285
|
+
(name) => name === filename,
|
|
11286
|
+
check,
|
|
11287
|
+
(error2) => options.reportWatcherError(context, error2)
|
|
11288
|
+
);
|
|
11289
|
+
resultWatchers.set(path, stop);
|
|
11290
|
+
check();
|
|
11180
11291
|
};
|
|
11181
11292
|
const watchAsk = (runtime, context, signal) => {
|
|
11182
11293
|
const path = agentStatePath(runtime.mailboxPath);
|
|
@@ -11189,21 +11300,16 @@ function createAgentController(pi, options) {
|
|
|
11189
11300
|
if (error2 instanceof OperationError && error2.detail.category === "incompatible_build")
|
|
11190
11301
|
return;
|
|
11191
11302
|
options.reportWatcherError(context, error2);
|
|
11192
|
-
if (!askWatchRetries.has(path))
|
|
11193
|
-
askWatchRetries.set(
|
|
11194
|
-
path,
|
|
11195
|
-
setTimeout(() => {
|
|
11196
|
-
askWatchRetries.delete(path);
|
|
11197
|
-
if (active && runtimes.get(runtime.label) === runtime)
|
|
11198
|
-
watchAsk(runtime, context, signal);
|
|
11199
|
-
}, 250)
|
|
11200
|
-
);
|
|
11201
11303
|
}
|
|
11202
11304
|
};
|
|
11203
|
-
|
|
11204
|
-
|
|
11205
|
-
|
|
11206
|
-
|
|
11305
|
+
const stop = observeMailbox(
|
|
11306
|
+
runtime.mailboxPath,
|
|
11307
|
+
(name) => name === "state.json" || name === "ask.json",
|
|
11308
|
+
check,
|
|
11309
|
+
(error2) => options.reportWatcherError(context, error2)
|
|
11310
|
+
);
|
|
11311
|
+
askWatchers.set(path, stop);
|
|
11312
|
+
check();
|
|
11207
11313
|
};
|
|
11208
11314
|
const resultIdentity = (runtime, requestId) => ({
|
|
11209
11315
|
runId: runtime.runId,
|
|
@@ -11717,6 +11823,8 @@ function createAgentController(pi, options) {
|
|
|
11717
11823
|
runtime.task = void 0;
|
|
11718
11824
|
runtime.startedAt = void 0;
|
|
11719
11825
|
runtime.contextPercent = void 0;
|
|
11826
|
+
if (pendingStarts.get(runtime.label)?.requestId === result.requestId)
|
|
11827
|
+
pendingStarts.delete(runtime.label);
|
|
11720
11828
|
if (!await finalizeDeliveredResult(runtime, result, context, signal))
|
|
11721
11829
|
scheduleResultCleanupRetry(runtime, result, context, signal);
|
|
11722
11830
|
clearCleanupError(runtime, "Result delivery failed; retrying:");
|
|
@@ -12025,6 +12133,7 @@ function createAgentController(pi, options) {
|
|
|
12025
12133
|
const pendingStart = {
|
|
12026
12134
|
label,
|
|
12027
12135
|
definition: agentDefinition,
|
|
12136
|
+
requestId: assignment.requestId,
|
|
12028
12137
|
...p.task !== void 0 ? { task: p.task } : {},
|
|
12029
12138
|
startedAt: Date.now(),
|
|
12030
12139
|
...scope.kind === "managed-agent" && process.env.PI_HERDSMAN_LABEL ? { parentLabel: process.env.PI_HERDSMAN_LABEL } : {}
|
|
@@ -12265,7 +12374,6 @@ ${result.stderr ?? ""}`.trim(),
|
|
|
12265
12374
|
p.action,
|
|
12266
12375
|
assignmentInput.resultBindings
|
|
12267
12376
|
);
|
|
12268
|
-
pendingStart.requestId = requestId;
|
|
12269
12377
|
accepted = true;
|
|
12270
12378
|
options.onChanged();
|
|
12271
12379
|
return {
|
|
@@ -12277,7 +12385,7 @@ ${result.stderr ?? ""}`.trim(),
|
|
|
12277
12385
|
pane_id: runtime.paneId,
|
|
12278
12386
|
session_id: runtime.piSessionId,
|
|
12279
12387
|
session_path: runtime.piSessionFile,
|
|
12280
|
-
request_id:
|
|
12388
|
+
request_id: requestId
|
|
12281
12389
|
};
|
|
12282
12390
|
} catch (caught) {
|
|
12283
12391
|
if (promptWriteFailed) throw caught;
|
|
@@ -12943,7 +13051,7 @@ ${result.stderr ?? ""}`.trim(),
|
|
|
12943
13051
|
...snapshot2,
|
|
12944
13052
|
visible: visibleAgentSnapshots(snapshot2, options.scope, ownerSessionId)
|
|
12945
13053
|
};
|
|
12946
|
-
const unresolvedMailboxState = options.scope?.kind === "lead" &&
|
|
13054
|
+
const unresolvedMailboxState = options.scope?.kind === "lead" && snapshot2.stateIssues.length > 0;
|
|
12947
13055
|
const now = Date.now();
|
|
12948
13056
|
const ownedRuns = new Set(
|
|
12949
13057
|
snapshot2.agents.filter(({ state }) => state.ownerSessionId === ownerSessionId).map(({ state }) => state.runId)
|
|
@@ -13418,11 +13526,15 @@ ${result.stderr ?? ""}`.trim(),
|
|
|
13418
13526
|
};
|
|
13419
13527
|
requestHealthScan();
|
|
13420
13528
|
if (process.env.HERDR_SOCKET_PATH) {
|
|
13421
|
-
watchHerdrLifecycle(
|
|
13422
|
-
|
|
13423
|
-
|
|
13424
|
-
|
|
13425
|
-
|
|
13529
|
+
watchHerdrLifecycle(
|
|
13530
|
+
process.env.HERDR_SOCKET_PATH,
|
|
13531
|
+
signal,
|
|
13532
|
+
(removed, category) => {
|
|
13533
|
+
options.onChanged();
|
|
13534
|
+
if (category !== "presentation") requestHealthScan();
|
|
13535
|
+
if (removed) options.onWorktreeRemoved?.(removed, ctx, signal);
|
|
13536
|
+
}
|
|
13537
|
+
);
|
|
13426
13538
|
}
|
|
13427
13539
|
schedule();
|
|
13428
13540
|
};
|
|
@@ -13524,14 +13636,10 @@ ${result.stderr ?? ""}`.trim(),
|
|
|
13524
13636
|
},
|
|
13525
13637
|
shutdown() {
|
|
13526
13638
|
active = false;
|
|
13527
|
-
for (const
|
|
13528
|
-
for (const
|
|
13639
|
+
for (const watcher of resultWatchers.values()) watcher();
|
|
13640
|
+
for (const watcher of askWatchers.values()) watcher();
|
|
13529
13641
|
resultWatchers.clear();
|
|
13530
13642
|
askWatchers.clear();
|
|
13531
|
-
for (const timer of resultWatchRetries.values()) clearTimeout(timer);
|
|
13532
|
-
for (const timer of askWatchRetries.values()) clearTimeout(timer);
|
|
13533
|
-
resultWatchRetries.clear();
|
|
13534
|
-
askWatchRetries.clear();
|
|
13535
13643
|
for (const timer of resultDeliveryRetries.values()) clearTimeout(timer);
|
|
13536
13644
|
for (const timer of resultCleanupRetries.values()) clearTimeout(timer);
|
|
13537
13645
|
for (const timer of askDeliveryRetries.values()) clearTimeout(timer);
|
|
@@ -13874,7 +13982,7 @@ function createManagedAgentExecutionState() {
|
|
|
13874
13982
|
resultWriteAttempts: 0,
|
|
13875
13983
|
retryTimer: void 0,
|
|
13876
13984
|
stateRetryTimer: void 0,
|
|
13877
|
-
|
|
13985
|
+
stopRequestObserver: void 0,
|
|
13878
13986
|
stateErrorReported: false,
|
|
13879
13987
|
resultErrorReported: false,
|
|
13880
13988
|
agentContext: void 0,
|
|
@@ -14202,7 +14310,7 @@ function createManagedAgentLeafStatus(options) {
|
|
|
14202
14310
|
else next.dispose();
|
|
14203
14311
|
return next;
|
|
14204
14312
|
});
|
|
14205
|
-
timer = setInterval(() => void refresh(ctx, activeGeneration),
|
|
14313
|
+
timer = setInterval(() => void refresh(ctx, activeGeneration), 1e4);
|
|
14206
14314
|
void refresh(ctx, activeGeneration);
|
|
14207
14315
|
}
|
|
14208
14316
|
};
|
|
@@ -15007,8 +15115,8 @@ This is the session-start definition snapshot. Use list_agents for live Agent st
|
|
|
15007
15115
|
}));
|
|
15008
15116
|
};
|
|
15009
15117
|
const resetRequestPump = () => {
|
|
15010
|
-
|
|
15011
|
-
execution.
|
|
15118
|
+
execution.stopRequestObserver?.();
|
|
15119
|
+
execution.stopRequestObserver = void 0;
|
|
15012
15120
|
requestState.reset();
|
|
15013
15121
|
};
|
|
15014
15122
|
const clearAgentRuntimes = () => controller?.clearRuntimes();
|
|
@@ -15573,12 +15681,13 @@ function registerManagedAgentSessionStartHandler(pi, execution, options) {
|
|
|
15573
15681
|
if (options.delegationEnabled)
|
|
15574
15682
|
options.startControllerHealthScanner(ctx, metadataSignal);
|
|
15575
15683
|
execution.initialized = true;
|
|
15576
|
-
|
|
15577
|
-
|
|
15684
|
+
execution.stopRequestObserver = observeMailbox(
|
|
15685
|
+
mailbox,
|
|
15686
|
+
(name) => /^request-.*\.json$/.test(name),
|
|
15578
15687
|
() => options.pumpRequest(ctx),
|
|
15579
|
-
|
|
15688
|
+
(error2) => options.appendError(ctx, error2)
|
|
15580
15689
|
);
|
|
15581
|
-
|
|
15690
|
+
options.pumpRequest(ctx);
|
|
15582
15691
|
if (!options.delegationEnabled && ctx.mode === "tui" && ctx.hasUI)
|
|
15583
15692
|
options.startLeafStatus(ctx);
|
|
15584
15693
|
const model = ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : void 0;
|
|
@@ -15596,6 +15705,7 @@ function registerManagedAgentSessionStartHandler(pi, execution, options) {
|
|
|
15596
15705
|
);
|
|
15597
15706
|
} catch (error2) {
|
|
15598
15707
|
execution.initialized = false;
|
|
15708
|
+
options.resetRequestPump();
|
|
15599
15709
|
options.statusRuntime.clear();
|
|
15600
15710
|
options.setControllerReady(false);
|
|
15601
15711
|
execution.assignment = void 0;
|
|
@@ -15612,6 +15722,7 @@ import { visibleWidth as visibleWidth2 } from "@earendil-works/pi-tui";
|
|
|
15612
15722
|
import { contentText as contentText4, getSupportedThinkingLevels } from "@earendil-works/pi-ai";
|
|
15613
15723
|
import { randomUUID as randomUUID10 } from "node:crypto";
|
|
15614
15724
|
import { realpathSync as realpathSync5, statSync as statSync6, unlinkSync as unlinkSync8 } from "node:fs";
|
|
15725
|
+
import { isAbsolute as isAbsolute5, resolve as resolve6 } from "node:path";
|
|
15615
15726
|
var LEAD_INSTANCE_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
15616
15727
|
function createLeadRuntimeState() {
|
|
15617
15728
|
return {
|
|
@@ -15866,6 +15977,13 @@ function createLeadSessionStartRuntime(host) {
|
|
|
15866
15977
|
...host.ownToolsSnapshot()
|
|
15867
15978
|
});
|
|
15868
15979
|
const activeManager = host.isLead() && host.roleTransitions.activeManager();
|
|
15980
|
+
if (activeManager && !sessionSignal.aborted) {
|
|
15981
|
+
try {
|
|
15982
|
+
await host.roleTransitions.reconcileCurrentManagerAssignments(ctx);
|
|
15983
|
+
} catch (error2) {
|
|
15984
|
+
host.appendRoleError(ctx, error2);
|
|
15985
|
+
}
|
|
15986
|
+
}
|
|
15869
15987
|
if (host.isLead() && (host.roleTransitions.chiefModeActive() || activeManager)) {
|
|
15870
15988
|
host.startSupervisionUI(ctx);
|
|
15871
15989
|
} else host.leadStatusRuntime.start(ctx);
|
|
@@ -16107,19 +16225,21 @@ function createLeadCommandRuntime(host) {
|
|
|
16107
16225
|
async openDefinitionsMenu(ctx) {
|
|
16108
16226
|
let selectedDefinition;
|
|
16109
16227
|
const loadDefinitions = async () => {
|
|
16110
|
-
const { definitions,
|
|
16228
|
+
const { definitions, leadDefinitions } = await host.contextAgentDefinitions(ctx);
|
|
16111
16229
|
return {
|
|
16112
16230
|
definitions,
|
|
16113
16231
|
leadDefinitions: [
|
|
16114
16232
|
FLEXIBLE_LEAD_DEFINITION_NAME,
|
|
16115
16233
|
ORCHESTRATOR_LEAD_DEFINITION_NAME,
|
|
16116
16234
|
MANAGED_LEAD_DEFINITION_NAME
|
|
16117
|
-
].map(
|
|
16118
|
-
|
|
16119
|
-
name
|
|
16120
|
-
|
|
16121
|
-
)
|
|
16122
|
-
|
|
16235
|
+
].map((name) => {
|
|
16236
|
+
const definition = leadDefinitions.find(
|
|
16237
|
+
(candidate) => candidate.name === name
|
|
16238
|
+
);
|
|
16239
|
+
if (!definition)
|
|
16240
|
+
throw new Error(`Lead definition ${name} not found`);
|
|
16241
|
+
return definition;
|
|
16242
|
+
})
|
|
16123
16243
|
};
|
|
16124
16244
|
};
|
|
16125
16245
|
const resolveDefinition = async (name) => {
|
|
@@ -16777,6 +16897,7 @@ function registerLeadRuntime(pi, options) {
|
|
|
16777
16897
|
pi,
|
|
16778
16898
|
isCurrentChief: (ctx) => options.leadRuntime.chiefMode === "active" && !!options.leadRuntime.chiefLease && options.leadRuntime.chiefLease.descriptor.piSessionId === ctx.sessionManager.getSessionId(),
|
|
16779
16899
|
currentManager: (...args) => roleTransitions.currentManager(...args),
|
|
16900
|
+
reconcileProjectAssignments: (...args) => roleTransitions.reconcileProjectAssignments(...args),
|
|
16780
16901
|
currentChief: (...args) => roleTransitions.currentChief(...args),
|
|
16781
16902
|
currentChiefAuthority: (...args) => roleTransitions.currentChiefAuthority(...args),
|
|
16782
16903
|
latestMessageText: (ctx) => coordinationRuntime.latestCustomMessageText(
|
|
@@ -16790,6 +16911,14 @@ function registerLeadRuntime(pi, options) {
|
|
|
16790
16911
|
return !!file2?.isFile() && file2.size > 0;
|
|
16791
16912
|
})
|
|
16792
16913
|
});
|
|
16914
|
+
const writeProjectAssignment2 = (...args) => {
|
|
16915
|
+
options.projectHost.writeProjectAssignment(...args);
|
|
16916
|
+
supervisionUiRuntime.requestRefresh();
|
|
16917
|
+
};
|
|
16918
|
+
const removeProjectAssignment2 = (...args) => {
|
|
16919
|
+
options.roleTransitionHost.removeProjectAssignment(...args);
|
|
16920
|
+
supervisionUiRuntime.requestRefresh();
|
|
16921
|
+
};
|
|
16793
16922
|
const clearSupervisionUI = (removeWidget = true) => {
|
|
16794
16923
|
supervisionUiRuntime.stopPeriodic();
|
|
16795
16924
|
if (removeWidget && leadRuntime.leadContext)
|
|
@@ -16939,6 +17068,7 @@ function registerLeadRuntime(pi, options) {
|
|
|
16939
17068
|
};
|
|
16940
17069
|
const roleTransitionServices = {
|
|
16941
17070
|
...roleHost,
|
|
17071
|
+
removeProjectAssignment: removeProjectAssignment2,
|
|
16942
17072
|
controllerScope: { kind: "lead" },
|
|
16943
17073
|
activationGuard,
|
|
16944
17074
|
advancePeerPresenceGeneration: () => coordinationRuntime.advancePresenceGeneration(),
|
|
@@ -17018,7 +17148,10 @@ function registerLeadRuntime(pi, options) {
|
|
|
17018
17148
|
...options.controllerServices,
|
|
17019
17149
|
scope: { kind: "lead" },
|
|
17020
17150
|
build: options.build,
|
|
17021
|
-
onChanged: () =>
|
|
17151
|
+
onChanged: () => {
|
|
17152
|
+
statusRuntime.requestRefresh();
|
|
17153
|
+
supervisionUiRuntime.requestRefresh();
|
|
17154
|
+
},
|
|
17022
17155
|
onWorkChanged: (ctx) => herdRunRuntime?.maybeFinish(ctx),
|
|
17023
17156
|
onWorktreeRemoved: (removed, ctx, signal) => {
|
|
17024
17157
|
void roleTransitions.retireRemovedProjectWork(removed, ctx, signal).then(async () => {
|
|
@@ -17111,9 +17244,11 @@ function registerLeadRuntime(pi, options) {
|
|
|
17111
17244
|
const projectHost = options.projectHost;
|
|
17112
17245
|
const projectServices = {
|
|
17113
17246
|
...projectHost,
|
|
17247
|
+
writeProjectAssignment: writeProjectAssignment2,
|
|
17114
17248
|
liveAgent,
|
|
17115
17249
|
liveLead,
|
|
17116
17250
|
currentManager: (...args) => roleTransitions.currentManager(...args),
|
|
17251
|
+
reconcileProjectAssignments: (...args) => roleTransitions.reconcileProjectAssignments(...args),
|
|
17117
17252
|
sameManagerDescriptor: projectHost.sameManagerDescriptor,
|
|
17118
17253
|
withProjectWorkLock: (...args) => roleTransitions.withProjectWorkLock(...args),
|
|
17119
17254
|
withProjectAssignmentLock: (...args) => roleTransitions.withProjectAssignmentLock(...args),
|
|
@@ -17588,6 +17723,8 @@ function createLeadSupervisionRuntime(host) {
|
|
|
17588
17723
|
manager.workspaceId,
|
|
17589
17724
|
ctx.signal
|
|
17590
17725
|
);
|
|
17726
|
+
if (group.repoKey !== manager.repoKey || group.primaryWorkspaceId !== manager.workspaceId)
|
|
17727
|
+
throw new Error("Manager workspace is not the assigned project");
|
|
17591
17728
|
const [leads, topology] = await Promise.all([
|
|
17592
17729
|
directReports(ctx, suppliedInventory, suppliedAgents),
|
|
17593
17730
|
host.runHerdr(
|
|
@@ -17597,8 +17734,15 @@ function createLeadSupervisionRuntime(host) {
|
|
|
17597
17734
|
{ signal: ctx.signal }
|
|
17598
17735
|
)
|
|
17599
17736
|
]);
|
|
17600
|
-
if (topology?.source?.repo_key !== group.repoKey || !Array.isArray(topology?.worktrees))
|
|
17737
|
+
if (topology?.source?.repo_key !== group.repoKey || topology?.source?.source_workspace_id !== group.primaryWorkspaceId || !Array.isArray(topology?.worktrees))
|
|
17601
17738
|
throw new Error("Herdr worktree topology is not authoritative");
|
|
17739
|
+
if (typeof topology.source.source_checkout_path === "string")
|
|
17740
|
+
await host.reconcileProjectAssignments?.(
|
|
17741
|
+
ctx,
|
|
17742
|
+
group.repoKey,
|
|
17743
|
+
group.primaryWorkspaceId,
|
|
17744
|
+
topology.source.source_checkout_path
|
|
17745
|
+
);
|
|
17602
17746
|
const assignments = host.listProjectAssignments(
|
|
17603
17747
|
host.supervisionRuntime(),
|
|
17604
17748
|
group.repoKey
|
|
@@ -17891,6 +18035,8 @@ function createLeadSupervisionRuntime(host) {
|
|
|
17891
18035
|
let generationId;
|
|
17892
18036
|
let stale = false;
|
|
17893
18037
|
let timer;
|
|
18038
|
+
let redrawTimer;
|
|
18039
|
+
let uiContext;
|
|
17894
18040
|
let inFlight;
|
|
17895
18041
|
let pending;
|
|
17896
18042
|
let overviewGeneration = 0;
|
|
@@ -18355,15 +18501,35 @@ function createLeadSupervisionRuntime(host) {
|
|
|
18355
18501
|
},
|
|
18356
18502
|
startPeriodic: (ctx) => {
|
|
18357
18503
|
if (timer) clearInterval(timer);
|
|
18358
|
-
|
|
18504
|
+
if (redrawTimer) clearInterval(redrawTimer);
|
|
18505
|
+
timer = void 0;
|
|
18506
|
+
redrawTimer = void 0;
|
|
18507
|
+
uiContext = void 0;
|
|
18508
|
+
if (ctx.mode !== "tui" || !ctx.hasUI) return;
|
|
18509
|
+
uiContext = ctx;
|
|
18510
|
+
timer = setInterval(() => {
|
|
18511
|
+
if (uiContext === ctx) void refresh(ctx);
|
|
18512
|
+
}, 1e4);
|
|
18359
18513
|
timer.unref?.();
|
|
18514
|
+
if (host.activeRole() === "manager") {
|
|
18515
|
+
redrawTimer = setInterval(() => {
|
|
18516
|
+
if (uiContext === ctx) renderWidget?.();
|
|
18517
|
+
}, 2e3);
|
|
18518
|
+
redrawTimer.unref?.();
|
|
18519
|
+
}
|
|
18520
|
+
},
|
|
18521
|
+
requestRefresh: () => {
|
|
18522
|
+
if (uiContext) void refresh(uiContext);
|
|
18360
18523
|
},
|
|
18361
18524
|
stopPeriodic: () => {
|
|
18362
18525
|
pending = void 0;
|
|
18526
|
+
uiContext = void 0;
|
|
18363
18527
|
renderOverview = void 0;
|
|
18364
18528
|
renderWidget = void 0;
|
|
18365
18529
|
if (timer) clearInterval(timer);
|
|
18366
18530
|
timer = void 0;
|
|
18531
|
+
if (redrawTimer) clearInterval(redrawTimer);
|
|
18532
|
+
redrawTimer = void 0;
|
|
18367
18533
|
}
|
|
18368
18534
|
};
|
|
18369
18535
|
}
|
|
@@ -18613,7 +18779,8 @@ function createLeadProjectRuntime(host) {
|
|
|
18613
18779
|
stopHerdrAgentPreservingPane: stopHerdrAgentPreservingPane2,
|
|
18614
18780
|
expectedSession: expectedSession3,
|
|
18615
18781
|
liveLead,
|
|
18616
|
-
withProjectWorkLock
|
|
18782
|
+
withProjectWorkLock,
|
|
18783
|
+
withProjectAssignmentLock
|
|
18617
18784
|
} = host;
|
|
18618
18785
|
const {
|
|
18619
18786
|
activeRole,
|
|
@@ -18683,35 +18850,44 @@ function createLeadProjectRuntime(host) {
|
|
|
18683
18850
|
);
|
|
18684
18851
|
const primaryWorkspaceId = group.primaryWorkspaceId;
|
|
18685
18852
|
const runtime = supervisionRuntime2();
|
|
18686
|
-
const
|
|
18687
|
-
|
|
18688
|
-
|
|
18689
|
-
|
|
18853
|
+
const branch = operation.branch;
|
|
18854
|
+
const topology = await runHerdr2(
|
|
18855
|
+
pi,
|
|
18856
|
+
ctx,
|
|
18857
|
+
["worktree", "list", "--workspace", primaryWorkspaceId],
|
|
18858
|
+
{ signal }
|
|
18690
18859
|
);
|
|
18860
|
+
if (topology?.source?.source_workspace_id !== primaryWorkspaceId || topology?.source?.repo_key !== manager.repoKey || !Array.isArray(topology?.worktrees))
|
|
18861
|
+
throw new Error("Herdr worktree topology is not authoritative");
|
|
18862
|
+
if (operation.action === "resume")
|
|
18863
|
+
await host.reconcileProjectAssignments(
|
|
18864
|
+
ctx,
|
|
18865
|
+
manager.repoKey,
|
|
18866
|
+
primaryWorkspaceId,
|
|
18867
|
+
typeof topology.source.source_checkout_path === "string" ? topology.source.source_checkout_path : "",
|
|
18868
|
+
true,
|
|
18869
|
+
branch
|
|
18870
|
+
);
|
|
18871
|
+
const existing = readProjectAssignment2(runtime, manager.repoKey, branch);
|
|
18691
18872
|
if (existing && existing.repoKey !== manager.repoKey)
|
|
18692
18873
|
throw new Error("Project assignment belongs to another repository");
|
|
18693
18874
|
if (operation.action === "delegate" && existing)
|
|
18694
18875
|
throw new Error(
|
|
18695
|
-
`Work already exists on ${
|
|
18876
|
+
`Work already exists on ${branch}; resume it with resume_project.`
|
|
18696
18877
|
);
|
|
18697
18878
|
if (operation.action === "resume" && !existing)
|
|
18698
18879
|
throw new Error(
|
|
18699
|
-
`No existing work was found on ${
|
|
18880
|
+
`No existing work was found on ${branch}; start it with delegate_project and a task.`
|
|
18700
18881
|
);
|
|
18701
18882
|
const presentationTask = operation.action === "resume" ? projectTaskSummary(existing.text) : void 0;
|
|
18702
18883
|
const id = operation.action === "resume" ? existing.id : operation.id;
|
|
18703
|
-
const branch = operation.branch;
|
|
18704
|
-
const topology = await runHerdr2(
|
|
18705
|
-
pi,
|
|
18706
|
-
ctx,
|
|
18707
|
-
["worktree", "list", "--workspace", primaryWorkspaceId],
|
|
18708
|
-
{ signal }
|
|
18709
|
-
);
|
|
18710
|
-
if (topology?.source?.source_workspace_id !== primaryWorkspaceId || topology?.source?.repo_key !== manager.repoKey || !Array.isArray(topology?.worktrees))
|
|
18711
|
-
throw new Error("Herdr worktree topology is not authoritative");
|
|
18712
18884
|
const branchWorktrees = topology.worktrees.filter(
|
|
18713
18885
|
(worktree) => worktree?.branch === branch
|
|
18714
18886
|
);
|
|
18887
|
+
if (operation.action === "resume" && branchWorktrees.length === 0)
|
|
18888
|
+
throw new Error(
|
|
18889
|
+
"Worktree availability could not be verified; the existing assignment was preserved. Retry when repository access is available."
|
|
18890
|
+
);
|
|
18715
18891
|
if (branchWorktrees.length > 1)
|
|
18716
18892
|
throw new Error(`Multiple Herdr worktrees match branch ${branch}`);
|
|
18717
18893
|
const live = operation.action === "resume" ? await liveAgent(ctx, existing.id) : [];
|
|
@@ -18830,7 +19006,6 @@ function createLeadProjectRuntime(host) {
|
|
|
18830
19006
|
text: prepared.text,
|
|
18831
19007
|
...prepared.resultBindings.length ? { resultBindings: prepared.resultBindings } : {}
|
|
18832
19008
|
};
|
|
18833
|
-
writeProjectAssignment2(runtime, assignment);
|
|
18834
19009
|
}
|
|
18835
19010
|
const promptPaths = [];
|
|
18836
19011
|
try {
|
|
@@ -18877,6 +19052,8 @@ function createLeadProjectRuntime(host) {
|
|
|
18877
19052
|
);
|
|
18878
19053
|
}
|
|
18879
19054
|
} else {
|
|
19055
|
+
if (operation.action === "resume")
|
|
19056
|
+
throw new Error("The existing project checkout is unavailable");
|
|
18880
19057
|
const args = [
|
|
18881
19058
|
"worktree",
|
|
18882
19059
|
"create",
|
|
@@ -18886,44 +19063,7 @@ function createLeadProjectRuntime(host) {
|
|
|
18886
19063
|
assignment.branch,
|
|
18887
19064
|
"--no-focus"
|
|
18888
19065
|
];
|
|
18889
|
-
|
|
18890
|
-
let savedCwd;
|
|
18891
|
-
if (assignment.piSessionFile) {
|
|
18892
|
-
const saved = SessionManager3.open(assignment.piSessionFile);
|
|
18893
|
-
if (saved.getSessionId() !== assignment.id)
|
|
18894
|
-
throw new Error(
|
|
18895
|
-
"Persisted project session identity does not match"
|
|
18896
|
-
);
|
|
18897
|
-
savedCwd = saved.getCwd();
|
|
18898
|
-
} else {
|
|
18899
|
-
const saved = (await SessionManager3.listAll()).filter(
|
|
18900
|
-
(session) => session.id === assignment.id
|
|
18901
|
-
);
|
|
18902
|
-
if (saved.length > 1)
|
|
18903
|
-
throw new Error(
|
|
18904
|
-
`Pi session ${assignment.id} is ambiguous; project work was preserved.`
|
|
18905
|
-
);
|
|
18906
|
-
if (saved.length === 1) {
|
|
18907
|
-
const sessionFile = saved[0].path;
|
|
18908
|
-
if (typeof sessionFile !== "string" || !sessionFile)
|
|
18909
|
-
throw new Error(
|
|
18910
|
-
"Legacy project session has no exact file path"
|
|
18911
|
-
);
|
|
18912
|
-
const session = SessionManager3.open(sessionFile);
|
|
18913
|
-
if (session.getSessionId() !== assignment.id)
|
|
18914
|
-
throw new Error(
|
|
18915
|
-
"Persisted project session identity does not match"
|
|
18916
|
-
);
|
|
18917
|
-
savedCwd = session.getCwd();
|
|
18918
|
-
assignment = await persistLeadSessionFile(
|
|
18919
|
-
assignment,
|
|
18920
|
-
realpathSync5(sessionFile)
|
|
18921
|
-
);
|
|
18922
|
-
}
|
|
18923
|
-
}
|
|
18924
|
-
args.push("--base", assignment.branch);
|
|
18925
|
-
if (savedCwd) args.push("--path", savedCwd);
|
|
18926
|
-
} else args.push("--base", operation.base ?? "HEAD");
|
|
19066
|
+
args.push("--base", operation.base ?? "HEAD");
|
|
18927
19067
|
const created = await runHerdr2(pi, ctx, args, { signal });
|
|
18928
19068
|
workspaceId = created?.workspace?.workspace_id;
|
|
18929
19069
|
paneId = created?.root_pane?.pane_id;
|
|
@@ -18938,6 +19078,40 @@ function createLeadProjectRuntime(host) {
|
|
|
18938
19078
|
(value2) => typeof value2 === "string" && value2
|
|
18939
19079
|
))
|
|
18940
19080
|
throw new Error("Herdr did not return exact worktree identities");
|
|
19081
|
+
if (operation.action === "delegate") {
|
|
19082
|
+
const [common, branchResult] = await Promise.all([
|
|
19083
|
+
pi.exec(
|
|
19084
|
+
"git",
|
|
19085
|
+
["rev-parse", "--path-format=absolute", "--git-common-dir"],
|
|
19086
|
+
{ cwd, signal, timeout: 1e4 }
|
|
19087
|
+
),
|
|
19088
|
+
pi.exec("git", ["symbolic-ref", "--quiet", "--short", "HEAD"], {
|
|
19089
|
+
cwd,
|
|
19090
|
+
signal,
|
|
19091
|
+
timeout: 1e4
|
|
19092
|
+
})
|
|
19093
|
+
]);
|
|
19094
|
+
if (common.code !== 0 || common.killed || common.stderr?.trim() || branchResult.code !== 0 || branchResult.killed || branchResult.stderr?.trim() || !isAbsolute5(String(common.stdout ?? "").trim()) || realpathSync5(String(common.stdout).trim()) !== realpathSync5(manager.repoKey) || String(branchResult.stdout ?? "").trim() !== assignment.branch)
|
|
19095
|
+
throw new Error(
|
|
19096
|
+
"Herdr checkout does not match the assigned repository and branch"
|
|
19097
|
+
);
|
|
19098
|
+
const fresh = await currentManager(ctx);
|
|
19099
|
+
if (!fresh || !sameManagerDescriptor2(fresh, manager))
|
|
19100
|
+
throw new Error(
|
|
19101
|
+
"Manager changed before project assignment was published"
|
|
19102
|
+
);
|
|
19103
|
+
await withProjectAssignmentLock(
|
|
19104
|
+
manager.repoKey,
|
|
19105
|
+
assignment.branch,
|
|
19106
|
+
() => {
|
|
19107
|
+
if (readProjectAssignment2(runtime, manager.repoKey, assignment.branch))
|
|
19108
|
+
throw new Error(
|
|
19109
|
+
`Work already exists on ${assignment.branch}; resume it with resume_project.`
|
|
19110
|
+
);
|
|
19111
|
+
writeProjectAssignment2(runtime, assignment);
|
|
19112
|
+
}
|
|
19113
|
+
);
|
|
19114
|
+
}
|
|
18941
19115
|
await assertUnoccupied(workspaceId);
|
|
18942
19116
|
let lead;
|
|
18943
19117
|
let startedLead = false;
|
|
@@ -19909,8 +20083,8 @@ function createLeadCoordinationRuntime(host) {
|
|
|
19909
20083
|
const enterCoordinationPublication = async () => {
|
|
19910
20084
|
const previous = coordinationPublication;
|
|
19911
20085
|
let release;
|
|
19912
|
-
coordinationPublication = new Promise((
|
|
19913
|
-
release =
|
|
20086
|
+
coordinationPublication = new Promise((resolve8) => {
|
|
20087
|
+
release = resolve8;
|
|
19914
20088
|
});
|
|
19915
20089
|
await previous;
|
|
19916
20090
|
return release;
|
|
@@ -20966,12 +21140,75 @@ async function resolveLeadControllerRole(state, ctx, requestedRole, deps, option
|
|
|
20966
21140
|
);
|
|
20967
21141
|
}
|
|
20968
21142
|
}
|
|
21143
|
+
function gitPathStatus(path) {
|
|
21144
|
+
try {
|
|
21145
|
+
return statSync6(path).isDirectory() ? "present" : "unknown";
|
|
21146
|
+
} catch (error2) {
|
|
21147
|
+
const code = error2.code;
|
|
21148
|
+
return code === "ENOENT" || code === "ENOTDIR" ? "absent" : "unknown";
|
|
21149
|
+
}
|
|
21150
|
+
}
|
|
21151
|
+
function parseGitWorktreeInventory(output) {
|
|
21152
|
+
if (!output.endsWith("\0\0")) return void 0;
|
|
21153
|
+
const fields = output.split("\0");
|
|
21154
|
+
if (fields.pop() !== "") return void 0;
|
|
21155
|
+
const records = [];
|
|
21156
|
+
let record = [];
|
|
21157
|
+
for (const field of fields) {
|
|
21158
|
+
if (field) record.push(field);
|
|
21159
|
+
else if (record.length) {
|
|
21160
|
+
records.push(record);
|
|
21161
|
+
record = [];
|
|
21162
|
+
}
|
|
21163
|
+
}
|
|
21164
|
+
if (record.length) records.push(record);
|
|
21165
|
+
if (!records.length) return void 0;
|
|
21166
|
+
const worktrees = [];
|
|
21167
|
+
for (const values of records) {
|
|
21168
|
+
if (!values[0]?.startsWith("worktree ")) return void 0;
|
|
21169
|
+
let path;
|
|
21170
|
+
let head;
|
|
21171
|
+
let branch;
|
|
21172
|
+
let bare = false;
|
|
21173
|
+
let detached = false;
|
|
21174
|
+
let locked = false;
|
|
21175
|
+
let prunable = false;
|
|
21176
|
+
const seen = /* @__PURE__ */ new Set();
|
|
21177
|
+
for (const value2 of values) {
|
|
21178
|
+
const key = value2.startsWith("worktree ") ? "worktree" : value2.startsWith("HEAD ") ? "HEAD" : value2.startsWith("branch refs/heads/") ? "branch" : value2 === "detached" || value2 === "bare" ? value2 : /^locked(?: |$)/.test(value2) ? "locked" : /^prunable(?: |$)/.test(value2) ? "prunable" : void 0;
|
|
21179
|
+
if (key && seen.has(key)) return void 0;
|
|
21180
|
+
if (key) seen.add(key);
|
|
21181
|
+
if (value2.startsWith("worktree ") && !path) path = value2.slice(9);
|
|
21182
|
+
else if (value2.startsWith("HEAD ") && !head) head = value2.slice(5);
|
|
21183
|
+
else if (value2.startsWith("branch refs/heads/") && !branch)
|
|
21184
|
+
branch = value2.slice("branch refs/heads/".length);
|
|
21185
|
+
else if (value2 === "detached" || value2 === "bare") {
|
|
21186
|
+
if (value2 === "bare") bare = true;
|
|
21187
|
+
else detached = true;
|
|
21188
|
+
} else if (/^locked(?: .*)?$/.test(value2)) locked = true;
|
|
21189
|
+
else if (/^prunable(?: .*)?$/.test(value2)) prunable = true;
|
|
21190
|
+
else if (!/^[^\s]+(?: .*)?$/.test(value2)) return void 0;
|
|
21191
|
+
}
|
|
21192
|
+
if (!path || !isAbsolute5(path) || (bare ? !!branch || detached : !head || !/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(head) || !branch && !detached) || branch !== void 0 && !branch)
|
|
21193
|
+
return void 0;
|
|
21194
|
+
worktrees.push({
|
|
21195
|
+
path,
|
|
21196
|
+
...branch ? { branch } : {},
|
|
21197
|
+
...detached ? { detached: true } : {},
|
|
21198
|
+
...locked ? { locked: true } : {},
|
|
21199
|
+
...prunable ? { prunable: true } : {}
|
|
21200
|
+
});
|
|
21201
|
+
}
|
|
21202
|
+
if (new Set(worktrees.map((worktree) => worktree.path)).size !== worktrees.length)
|
|
21203
|
+
return void 0;
|
|
21204
|
+
return worktrees;
|
|
21205
|
+
}
|
|
20969
21206
|
function createLeadRoleTransitions(state, host) {
|
|
20970
21207
|
const projectWorkLocks = /* @__PURE__ */ new Map();
|
|
20971
21208
|
const withProjectWorkLock = async (key, operation) => {
|
|
20972
21209
|
const previous = projectWorkLocks.get(key);
|
|
20973
21210
|
let release;
|
|
20974
|
-
const current = new Promise((
|
|
21211
|
+
const current = new Promise((resolve8) => release = resolve8);
|
|
20975
21212
|
projectWorkLocks.set(key, current);
|
|
20976
21213
|
await previous;
|
|
20977
21214
|
try {
|
|
@@ -21184,29 +21421,177 @@ function createLeadRoleTransitions(state, host) {
|
|
|
21184
21421
|
const assignment = scope ? projectAssignmentForScope(scope, ctx.sessionManager.getSessionId()) : void 0;
|
|
21185
21422
|
return assignment ? currentManager(ctx, scope) : host.currentChiefAuthority(ctx);
|
|
21186
21423
|
};
|
|
21187
|
-
const removeProjectAssignmentState = (assignment,
|
|
21424
|
+
const removeProjectAssignmentState = (assignment, _ctx) => {
|
|
21188
21425
|
const runtime = host.supervisionRuntime();
|
|
21189
21426
|
const current = host.readProjectAssignment(
|
|
21190
21427
|
runtime,
|
|
21191
21428
|
assignment.repoKey,
|
|
21192
21429
|
assignment.branch
|
|
21193
21430
|
);
|
|
21194
|
-
if (!current || current.id !== assignment.id)
|
|
21431
|
+
if (!current || current.repoKey !== assignment.repoKey || current.branch !== assignment.branch || current.id !== assignment.id)
|
|
21432
|
+
return false;
|
|
21433
|
+
host.removeProjectMessages(runtime, assignment.repoKey, assignment.branch);
|
|
21195
21434
|
host.removeProjectAssignment(
|
|
21196
21435
|
runtime,
|
|
21197
21436
|
assignment.repoKey,
|
|
21198
21437
|
assignment.branch
|
|
21199
21438
|
);
|
|
21439
|
+
return true;
|
|
21440
|
+
};
|
|
21441
|
+
const gitWorktreeInventory = async (ctx, repoKey, primaryWorkspaceId, sourceCheckoutPath) => {
|
|
21442
|
+
if (!isAbsolute5(sourceCheckoutPath)) return void 0;
|
|
21200
21443
|
try {
|
|
21201
|
-
host.
|
|
21202
|
-
|
|
21203
|
-
|
|
21204
|
-
|
|
21444
|
+
const scope = await host.worktreeGroupScope(
|
|
21445
|
+
ctx,
|
|
21446
|
+
primaryWorkspaceId,
|
|
21447
|
+
ctx.signal
|
|
21205
21448
|
);
|
|
21206
|
-
|
|
21207
|
-
|
|
21449
|
+
if (scope.repoKey !== repoKey || scope.primaryWorkspaceId !== primaryWorkspaceId)
|
|
21450
|
+
return void 0;
|
|
21451
|
+
const rootResult = await host.pi.exec(
|
|
21452
|
+
"git",
|
|
21453
|
+
["rev-parse", "--path-format=absolute", "--git-common-dir"],
|
|
21454
|
+
{ cwd: sourceCheckoutPath, signal: ctx.signal, timeout: 1e4 }
|
|
21455
|
+
);
|
|
21456
|
+
if (rootResult.code !== 0 || rootResult.killed || rootResult.stderr?.trim())
|
|
21457
|
+
return void 0;
|
|
21458
|
+
const commonDir = String(rootResult.stdout ?? "").trim();
|
|
21459
|
+
if (!commonDir || !isAbsolute5(commonDir)) return void 0;
|
|
21460
|
+
const result = await host.pi.exec(
|
|
21461
|
+
"git",
|
|
21462
|
+
["worktree", "list", "--porcelain", "-z"],
|
|
21463
|
+
{ cwd: sourceCheckoutPath, signal: ctx.signal, timeout: 1e4 }
|
|
21464
|
+
);
|
|
21465
|
+
if (result.code !== 0 || result.killed || result.stderr?.trim())
|
|
21466
|
+
return void 0;
|
|
21467
|
+
const inventory = parseGitWorktreeInventory(String(result.stdout ?? ""));
|
|
21468
|
+
if (!inventory || realpathSync5(commonDir) !== realpathSync5(repoKey) || gitPathStatus(sourceCheckoutPath) !== "present")
|
|
21469
|
+
return void 0;
|
|
21470
|
+
return inventory;
|
|
21471
|
+
} catch {
|
|
21472
|
+
return void 0;
|
|
21208
21473
|
}
|
|
21209
|
-
|
|
21474
|
+
};
|
|
21475
|
+
const reconcileProjectAssignments = async (ctx, repoKey, primaryWorkspaceId, sourceCheckoutPath, projectWorkLocked = false, onlyBranch, retryLockContention = false) => {
|
|
21476
|
+
const manager = await currentManager(ctx);
|
|
21477
|
+
if (!manager || manager.repoKey !== repoKey || manager.workspaceId !== primaryWorkspaceId)
|
|
21478
|
+
return;
|
|
21479
|
+
const assignments = listProjectAssignments(
|
|
21480
|
+
host.supervisionRuntime(),
|
|
21481
|
+
repoKey
|
|
21482
|
+
);
|
|
21483
|
+
const initialInventory = await gitWorktreeInventory(
|
|
21484
|
+
ctx,
|
|
21485
|
+
repoKey,
|
|
21486
|
+
primaryWorkspaceId,
|
|
21487
|
+
sourceCheckoutPath
|
|
21488
|
+
);
|
|
21489
|
+
if (!initialInventory) return;
|
|
21490
|
+
const knownCheckoutPath = (assignment) => {
|
|
21491
|
+
if (!assignment.piSessionFile) return void 0;
|
|
21492
|
+
try {
|
|
21493
|
+
const session = host.SessionManager.open(assignment.piSessionFile);
|
|
21494
|
+
if (session.getSessionId() !== assignment.id) return void 0;
|
|
21495
|
+
return resolve6(session.getCwd());
|
|
21496
|
+
} catch {
|
|
21497
|
+
return void 0;
|
|
21498
|
+
}
|
|
21499
|
+
};
|
|
21500
|
+
const checkoutMaySurvive = (assignment, inventory) => {
|
|
21501
|
+
const cwd = knownCheckoutPath(assignment);
|
|
21502
|
+
if (cwd)
|
|
21503
|
+
return inventory.some(
|
|
21504
|
+
(worktree) => resolve6(worktree.path) === cwd && gitPathStatus(worktree.path) !== "absent"
|
|
21505
|
+
);
|
|
21506
|
+
return inventory.some(
|
|
21507
|
+
(worktree) => resolve6(worktree.path) !== resolve6(sourceCheckoutPath) && gitPathStatus(worktree.path) !== "absent"
|
|
21508
|
+
);
|
|
21509
|
+
};
|
|
21510
|
+
for (const observed of assignments) {
|
|
21511
|
+
if (onlyBranch && observed.branch !== onlyBranch) continue;
|
|
21512
|
+
if (checkoutMaySurvive(observed, initialInventory)) continue;
|
|
21513
|
+
const initialMatches = initialInventory.filter(
|
|
21514
|
+
(worktree) => worktree.branch === observed.branch
|
|
21515
|
+
);
|
|
21516
|
+
if (initialMatches.length > 1 || initialMatches.some(
|
|
21517
|
+
(worktree) => worktree.locked || !worktree.prunable || gitPathStatus(worktree.path) !== "absent"
|
|
21518
|
+
))
|
|
21519
|
+
continue;
|
|
21520
|
+
const retireIfAbsent = async () => {
|
|
21521
|
+
const latestManager = await currentManager(ctx);
|
|
21522
|
+
if (!latestManager || !host.sameManagerDescriptor(latestManager, manager))
|
|
21523
|
+
return;
|
|
21524
|
+
const retireUnderAssignmentLock = async () => {
|
|
21525
|
+
const latestManager2 = await currentManager(ctx);
|
|
21526
|
+
if (!latestManager2 || !host.sameManagerDescriptor(latestManager2, manager))
|
|
21527
|
+
return;
|
|
21528
|
+
const current = host.readProjectAssignment(
|
|
21529
|
+
host.supervisionRuntime(),
|
|
21530
|
+
repoKey,
|
|
21531
|
+
observed.branch
|
|
21532
|
+
);
|
|
21533
|
+
if (!current || current.repoKey !== repoKey || current.branch !== observed.branch || current.id !== observed.id)
|
|
21534
|
+
return;
|
|
21535
|
+
const inventory = await gitWorktreeInventory(
|
|
21536
|
+
ctx,
|
|
21537
|
+
repoKey,
|
|
21538
|
+
primaryWorkspaceId,
|
|
21539
|
+
sourceCheckoutPath
|
|
21540
|
+
);
|
|
21541
|
+
if (!inventory) return;
|
|
21542
|
+
if (checkoutMaySurvive(current, inventory)) return;
|
|
21543
|
+
const matches = inventory.filter(
|
|
21544
|
+
(worktree) => worktree.branch === observed.branch
|
|
21545
|
+
);
|
|
21546
|
+
if (matches.length > 1 || matches.some(
|
|
21547
|
+
(worktree) => worktree.locked || !worktree.prunable || gitPathStatus(worktree.path) !== "absent"
|
|
21548
|
+
))
|
|
21549
|
+
return;
|
|
21550
|
+
removeProjectAssignmentState(current, ctx);
|
|
21551
|
+
};
|
|
21552
|
+
const deadline = retryLockContention ? Date.now() + 3e4 : 0;
|
|
21553
|
+
for (; ; ) {
|
|
21554
|
+
try {
|
|
21555
|
+
await withProjectAssignmentLock(
|
|
21556
|
+
repoKey,
|
|
21557
|
+
observed.branch,
|
|
21558
|
+
retireUnderAssignmentLock
|
|
21559
|
+
);
|
|
21560
|
+
return;
|
|
21561
|
+
} catch (error2) {
|
|
21562
|
+
if (!(error2 instanceof ProcessLockOccupiedError)) throw error2;
|
|
21563
|
+
if (!retryLockContention) return;
|
|
21564
|
+
if (ctx.signal.aborted) throw ctx.signal.reason;
|
|
21565
|
+
if (Date.now() >= deadline) throw error2;
|
|
21566
|
+
await host.delay(50, void 0, { signal: ctx.signal });
|
|
21567
|
+
}
|
|
21568
|
+
}
|
|
21569
|
+
};
|
|
21570
|
+
if (projectWorkLocked) await retireIfAbsent();
|
|
21571
|
+
else
|
|
21572
|
+
await withProjectWorkLock(
|
|
21573
|
+
`${repoKey}\0${observed.branch}`,
|
|
21574
|
+
retireIfAbsent
|
|
21575
|
+
);
|
|
21576
|
+
}
|
|
21577
|
+
};
|
|
21578
|
+
const reconcileCurrentManagerAssignments = async (ctx) => {
|
|
21579
|
+
const manager = await currentManager(ctx);
|
|
21580
|
+
if (!manager) return;
|
|
21581
|
+
const topology = await host.runHerdr(
|
|
21582
|
+
host.pi,
|
|
21583
|
+
ctx,
|
|
21584
|
+
["worktree", "list", "--workspace", manager.workspaceId],
|
|
21585
|
+
{ signal: ctx.signal }
|
|
21586
|
+
);
|
|
21587
|
+
if (topology?.source?.repo_key !== manager.repoKey || topology?.source?.source_workspace_id !== manager.workspaceId || typeof topology?.source?.source_checkout_path !== "string")
|
|
21588
|
+
throw new Error("Manager worktree topology is not authoritative");
|
|
21589
|
+
await reconcileProjectAssignments(
|
|
21590
|
+
ctx,
|
|
21591
|
+
manager.repoKey,
|
|
21592
|
+
manager.workspaceId,
|
|
21593
|
+
topology.source.source_checkout_path
|
|
21594
|
+
);
|
|
21210
21595
|
};
|
|
21211
21596
|
const takeover = async (ctx) => {
|
|
21212
21597
|
if (activeLeadRole(state) !== "lead" || state.chiefMode === "active" || state.roleSuspended)
|
|
@@ -21245,38 +21630,31 @@ function createLeadRoleTransitions(state, host) {
|
|
|
21245
21630
|
return true;
|
|
21246
21631
|
};
|
|
21247
21632
|
const retireRemovedProjectWork = async (removed, ctx, signal) => {
|
|
21248
|
-
|
|
21249
|
-
|
|
21250
|
-
|
|
21251
|
-
|
|
21252
|
-
|
|
21253
|
-
|
|
21254
|
-
|
|
21255
|
-
|
|
21633
|
+
let scope;
|
|
21634
|
+
try {
|
|
21635
|
+
scope = await currentWorktreeScope(ctx);
|
|
21636
|
+
} catch (error2) {
|
|
21637
|
+
if (error2 instanceof OperationError && error2.detail.details?.herdrCode === "workspace_not_found")
|
|
21638
|
+
return;
|
|
21639
|
+
throw error2;
|
|
21640
|
+
}
|
|
21256
21641
|
if (!scope || scope.repoKey !== removed.repoKey) return;
|
|
21257
|
-
await
|
|
21258
|
-
|
|
21259
|
-
|
|
21260
|
-
|
|
21261
|
-
|
|
21262
|
-
|
|
21263
|
-
|
|
21264
|
-
|
|
21265
|
-
|
|
21266
|
-
|
|
21267
|
-
|
|
21268
|
-
|
|
21269
|
-
|
|
21270
|
-
|
|
21271
|
-
|
|
21272
|
-
|
|
21273
|
-
} catch (error2) {
|
|
21274
|
-
if (!(error2 instanceof ProcessLockOccupiedError) || Date.now() >= deadline)
|
|
21275
|
-
throw error2;
|
|
21276
|
-
await host.delay(50, void 0, { signal });
|
|
21277
|
-
}
|
|
21278
|
-
}
|
|
21279
|
-
}
|
|
21642
|
+
const topology = await host.runHerdr(
|
|
21643
|
+
host.pi,
|
|
21644
|
+
ctx,
|
|
21645
|
+
["worktree", "list", "--workspace", scope.primaryWorkspaceId],
|
|
21646
|
+
{ signal }
|
|
21647
|
+
);
|
|
21648
|
+
if (topology?.source?.repo_key !== removed.repoKey || topology?.source?.source_workspace_id !== scope.primaryWorkspaceId || typeof topology?.source?.source_checkout_path !== "string")
|
|
21649
|
+
return;
|
|
21650
|
+
await reconcileProjectAssignments(
|
|
21651
|
+
ctx,
|
|
21652
|
+
removed.repoKey,
|
|
21653
|
+
scope.primaryWorkspaceId,
|
|
21654
|
+
topology.source.source_checkout_path,
|
|
21655
|
+
false,
|
|
21656
|
+
removed.branch,
|
|
21657
|
+
true
|
|
21280
21658
|
);
|
|
21281
21659
|
};
|
|
21282
21660
|
const beginShutdownRole = () => {
|
|
@@ -21545,6 +21923,11 @@ function createLeadRoleTransitions(state, host) {
|
|
|
21545
21923
|
host.advancePeerPresenceGeneration();
|
|
21546
21924
|
if (host.coordinationHealthy()) await host.schedulePeerPresence(ctx);
|
|
21547
21925
|
await host.publishLeadRole(ctx, "inactive", state.chiefModeGeneration);
|
|
21926
|
+
try {
|
|
21927
|
+
await reconcileCurrentManagerAssignments(ctx);
|
|
21928
|
+
} catch (error2) {
|
|
21929
|
+
host.appendDurableError(host.pi, ctx, "pi_herdsman_state_error", error2);
|
|
21930
|
+
}
|
|
21548
21931
|
return "Manager mode active.";
|
|
21549
21932
|
} catch (error2) {
|
|
21550
21933
|
state.controllerRole = "lead";
|
|
@@ -21886,11 +22269,13 @@ function createLeadRoleTransitions(state, host) {
|
|
|
21886
22269
|
withProjectAssignmentLock,
|
|
21887
22270
|
publishProjectMessage,
|
|
21888
22271
|
currentManager,
|
|
22272
|
+
reconcileCurrentManagerAssignments,
|
|
21889
22273
|
currentChief,
|
|
21890
22274
|
currentChiefAuthority,
|
|
21891
22275
|
currentSupervisor,
|
|
21892
22276
|
takeover,
|
|
21893
22277
|
retireRemovedProjectWork,
|
|
22278
|
+
reconcileProjectAssignments,
|
|
21894
22279
|
clearLeadContext: () => {
|
|
21895
22280
|
state.leadContext = void 0;
|
|
21896
22281
|
}
|
|
@@ -22560,9 +22945,9 @@ Use reply_agent with agent="${ask.agentLabel}" to answer this question.`,
|
|
|
22560
22945
|
const reconcileRoleTools = () => roleTransitions.reconcileRoleTools();
|
|
22561
22946
|
const isCurrentChief = (ctx) => leadRuntime.chiefMode === "active" && !!leadRuntime.chiefLease && leadRuntime.chiefLease.descriptor.piSessionId === ctx.sessionManager.getSessionId();
|
|
22562
22947
|
const chiefSystemPrompt = (options) => {
|
|
22563
|
-
const agentDir =
|
|
22948
|
+
const agentDir = resolve7(getAgentDir4());
|
|
22564
22949
|
const globalInstructions = (options?.contextFiles ?? []).filter(
|
|
22565
|
-
({ path, content }) => typeof path === "string" && typeof content === "string" &&
|
|
22950
|
+
({ path, content }) => typeof path === "string" && typeof content === "string" && resolve7(dirname7(path)) === agentDir && content.length > 0
|
|
22566
22951
|
).map(({ content }) => content).join("\n\n");
|
|
22567
22952
|
return [
|
|
22568
22953
|
CHIEF_ROLE_CHARTER,
|
|
@@ -22597,6 +22982,7 @@ ${globalInstructions}
|
|
|
22597
22982
|
paneId: () => process.env.HERDR_PANE_ID,
|
|
22598
22983
|
getSessionName: () => pi.getSessionName(),
|
|
22599
22984
|
runHerdr,
|
|
22985
|
+
SessionManager: SessionManager2,
|
|
22600
22986
|
reportLeadMetadata,
|
|
22601
22987
|
workspaceId: () => process.env.HERDR_WORKSPACE_ID,
|
|
22602
22988
|
identity: {
|
|
@@ -23310,7 +23696,6 @@ ${summary}`,
|
|
|
23310
23696
|
appendDurableError
|
|
23311
23697
|
},
|
|
23312
23698
|
statusSnapshotHost: {
|
|
23313
|
-
hasStateIssues: () => listAgentStateIssues().length > 0,
|
|
23314
23699
|
environmentIdentity: (ctx) => managedAgentEnvironmentIdentity(ctx, HERDSMAN_BUILD),
|
|
23315
23700
|
identityFromEnvironment: () => ({
|
|
23316
23701
|
definition: process.env.PI_HERDSMAN_AGENT_DEFINITION,
|