zob-harness 0.9.1 → 0.9.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/.pi/extensions/zob-harness/src/domains/coms/coms-v2/registry.ts +24 -3
- package/.pi/extensions/zob-harness/src/domains/coms/coms-v2/types.ts +1 -0
- package/.pi/extensions/zob-harness/src/domains/coms/coms-v2/zpeer.ts +5 -3
- package/.pi/extensions/zob-harness/src/domains/delegation/child-runner.ts +28 -3
- package/.pi/extensions/zob-harness/src/runtime/events.ts +67 -33
- package/package.json +1 -1
- package/scripts/zpeer-local-e2e-smoke.mjs +18 -0
- package/scripts/zpeer-static-smoke.mjs +10 -3
|
@@ -97,6 +97,21 @@ function readLeasesFromDir(dir: string, nowMs: number, teamName?: string): ZobLi
|
|
|
97
97
|
.map((lease) => leaseToPeerCard(lease, nowMs));
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
function peerRegistryKey(peer: Pick<ZobLivePeerCard, "projectId" | "roleId" | "sessionHash">): string {
|
|
101
|
+
return `${peer.projectId}:${peer.roleId}:${peer.sessionHash}`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function mergeLeaseBackedAndAdhocPeers(leasePeers: ZobLivePeerCard[], agentPeers: ZobLivePeerCard[]): ZobLivePeerCard[] {
|
|
105
|
+
const byKey = new Map<string, ZobLivePeerCard>();
|
|
106
|
+
for (const peer of leasePeers) byKey.set(peerRegistryKey(peer), peer);
|
|
107
|
+
for (const peer of agentPeers) {
|
|
108
|
+
if (peer.zpeerAdhoc !== true) continue;
|
|
109
|
+
const key = peerRegistryKey(peer);
|
|
110
|
+
if (!byKey.has(key)) byKey.set(key, peer);
|
|
111
|
+
}
|
|
112
|
+
return [...byKey.values()];
|
|
113
|
+
}
|
|
114
|
+
|
|
100
115
|
function boundedOfflinePeerRetentionMs(value: number | undefined): number {
|
|
101
116
|
const env = Number.parseInt(process.env.ZOB_COMS_OFFLINE_PEER_RETENTION_MS ?? "", 10);
|
|
102
117
|
const raw = typeof value === "number" && Number.isFinite(value) ? value : Number.isFinite(env) ? env : DEFAULT_OFFLINE_PEER_RETENTION_MS;
|
|
@@ -514,8 +529,13 @@ export function unregisterCurrentZobLivePeer(repoRoot: string, teamName = "zob-c
|
|
|
514
529
|
export function readZobLiveRegistrySnapshot(repoRoot: string, teamName?: string): ZobLiveRegistrySnapshot {
|
|
515
530
|
const { dir, projectId, kind } = projectLeasesDir(repoRoot);
|
|
516
531
|
const nowMs = Date.now();
|
|
517
|
-
if (existsSync(dir)) return buildSnapshot(projectId, kind, readLeasesFromDir(dir, nowMs, teamName), teamName);
|
|
518
532
|
const agents = projectAgentsDir(repoRoot);
|
|
533
|
+
if (existsSync(dir)) {
|
|
534
|
+
return buildSnapshot(projectId, kind, mergeLeaseBackedAndAdhocPeers(
|
|
535
|
+
readLeasesFromDir(dir, nowMs, teamName),
|
|
536
|
+
readPeerCardsFromAgentsDir(agents.dir, nowMs, teamName),
|
|
537
|
+
), teamName);
|
|
538
|
+
}
|
|
519
539
|
return buildSnapshot(agents.projectId, agents.kind, readPeerCardsFromAgentsDir(agents.dir, nowMs, teamName), teamName);
|
|
520
540
|
}
|
|
521
541
|
|
|
@@ -523,9 +543,10 @@ export function readZobLiveRegistryAllProjectsSnapshot(repoRoot: string, teamNam
|
|
|
523
543
|
const { projectId, kind } = projectAgentsDir(repoRoot);
|
|
524
544
|
const nowMs = Date.now();
|
|
525
545
|
const leaseDirs = allProjectLeasesDirs();
|
|
546
|
+
const agentPeers = allProjectAgentsDirs().flatMap((dir) => readPeerCardsFromAgentsDir(dir, nowMs, teamName));
|
|
526
547
|
const hasLeaseDomain = leaseDirs.some((dir) => existsSync(dir));
|
|
527
548
|
const peers = hasLeaseDomain
|
|
528
|
-
? leaseDirs.flatMap((dir) => readLeasesFromDir(dir, nowMs, teamName))
|
|
529
|
-
:
|
|
549
|
+
? mergeLeaseBackedAndAdhocPeers(leaseDirs.flatMap((dir) => readLeasesFromDir(dir, nowMs, teamName)), agentPeers)
|
|
550
|
+
: agentPeers;
|
|
530
551
|
return buildSnapshot(projectId, kind, peers, teamName);
|
|
531
552
|
}
|
|
@@ -234,7 +234,7 @@ export function refreshZpeerSelf(repoRoot: string, peer: ZobLivePeerCard, roomId
|
|
|
234
234
|
const ensured = ensureZpeerFields(repoRoot, peer, roomId, alias, restoredMemberships);
|
|
235
235
|
if (!hasLocalSocketEndpointEvidence(ensured)) return ensured;
|
|
236
236
|
const refreshed = writeZobLivePeerCard(repoRoot, { ...ensured, heartbeatAt: new Date().toISOString(), status: "online" });
|
|
237
|
-
writeZobLiveTeamAgentLease(repoRoot, refreshed, { reason: "zpeer_refresh" });
|
|
237
|
+
if (refreshed.zpeerAdhoc !== true) writeZobLiveTeamAgentLease(repoRoot, refreshed, { reason: "zpeer_refresh" });
|
|
238
238
|
return refreshed;
|
|
239
239
|
}
|
|
240
240
|
|
|
@@ -479,8 +479,10 @@ export async function sendZpeerPrompt(repoRoot: string, self: ZobLivePeerCard, t
|
|
|
479
479
|
};
|
|
480
480
|
|
|
481
481
|
if (!selfMembership) return finish("attempt", { status: "blocked", reason: `current peer is not a member of room '${roomId}'`, targetAlias: targetAlias ?? undefined, taskHash, bodyStored: false });
|
|
482
|
-
|
|
483
|
-
|
|
482
|
+
if (self.zpeerAdhoc !== true) {
|
|
483
|
+
const leaseOwnership = ownsZobLiveTeamAgentLease(repoRoot, self);
|
|
484
|
+
if (!leaseOwnership.owned) return finish("attempt", { status: "blocked", reason: `current peer does not own stable team-agent lease (${leaseOwnership.reason})`, targetAlias: targetAlias ?? undefined, taskHash, bodyStored: false });
|
|
485
|
+
}
|
|
484
486
|
if (selfMembership.role === "observer") return finish("attempt", { status: "blocked", reason: `current peer is observer-only in room '${roomId}'`, targetAlias: targetAlias ?? undefined, taskHash, bodyStored: false });
|
|
485
487
|
if (!targetAlias) return finish("attempt", { status: "blocked", reason: "invalid target alias", bodyStored: false });
|
|
486
488
|
if (!transientPrompt.trim()) return finish("attempt", { status: "blocked", reason: "empty peer prompt", targetAlias, bodyStored: false });
|
|
@@ -3,7 +3,7 @@ import { existsSync, mkdirSync } from "node:fs";
|
|
|
3
3
|
import { mkdtemp, rm } from "node:fs/promises";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import { join } from "node:path";
|
|
6
|
-
import type
|
|
6
|
+
import { getAgentDir, type ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
7
7
|
|
|
8
8
|
import { discoverAgents } from "./agents.js";
|
|
9
9
|
import { SUPERVISED_READONLY_CHILD_TOOLS } from "../../core/constants.js";
|
|
@@ -24,6 +24,28 @@ function getPiInvocation(args: string[]): { command: string; args: string[] } {
|
|
|
24
24
|
return { command: "pi", args };
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
function childModelPattern(ctx: ExtensionContext, agent: HarnessAgent, modelOverride: string | undefined): string | undefined {
|
|
28
|
+
if (modelOverride?.trim()) return modelOverride.trim();
|
|
29
|
+
if (agent.model?.trim()) return agent.model.trim();
|
|
30
|
+
const model = ctx.model;
|
|
31
|
+
if (!model?.provider || !model.id) return undefined;
|
|
32
|
+
return `${model.provider}/${model.id}`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function providerFromModelPattern(model: string | undefined): string | undefined {
|
|
36
|
+
if (!model) return undefined;
|
|
37
|
+
const [provider] = model.split("/");
|
|
38
|
+
return provider && provider !== model ? provider : undefined;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function resolveCodexFastModeExtension(ctx: ExtensionContext, model: string | undefined): string | undefined {
|
|
42
|
+
const provider = ctx.model?.provider ?? providerFromModelPattern(model);
|
|
43
|
+
const usesCodexProvider = provider === "openai-codex" || provider === "codex-auto" || provider?.startsWith("codex-") === true;
|
|
44
|
+
if (!usesCodexProvider) return undefined;
|
|
45
|
+
const extensionPath = join(getAgentDir(), "extensions", "codex-fast-mode.ts");
|
|
46
|
+
return existsSync(extensionPath) ? extensionPath : undefined;
|
|
47
|
+
}
|
|
48
|
+
|
|
27
49
|
const CHILD_THINKING_LEVELS = new Set<string>(["low", "medium", "high", "xhigh"]);
|
|
28
50
|
|
|
29
51
|
function validateChildThinkingOverride(thinking: string | undefined, fieldName = "thinking"): string[] {
|
|
@@ -90,13 +112,14 @@ async function runChildAgent(
|
|
|
90
112
|
pathPolicy?: { allowedPaths?: string[]; forbiddenPaths?: string[]; sandboxRoot?: string },
|
|
91
113
|
thinkingOverride?: ChildThinkingLevel,
|
|
92
114
|
): Promise<ChildResult> {
|
|
115
|
+
const resolvedModel = childModelPattern(ctx, agent, modelOverride);
|
|
93
116
|
const result: ChildResult = {
|
|
94
117
|
agent: agent.name,
|
|
95
118
|
task,
|
|
96
119
|
exitCode: 0,
|
|
97
120
|
output: "",
|
|
98
121
|
stderr: "",
|
|
99
|
-
model:
|
|
122
|
+
model: resolvedModel,
|
|
100
123
|
usage: usageEmpty(),
|
|
101
124
|
};
|
|
102
125
|
|
|
@@ -123,10 +146,12 @@ async function runChildAgent(
|
|
|
123
146
|
|
|
124
147
|
try {
|
|
125
148
|
const childSafetyExtension = join(ctx.cwd, ".pi", "extensions", "zob-child-safety", "index.ts");
|
|
149
|
+
const childCodexFastModeExtension = resolveCodexFastModeExtension(ctx, resolvedModel);
|
|
126
150
|
const args = ["--mode", "json", "-p", "--no-extensions"];
|
|
151
|
+
if (childCodexFastModeExtension) args.push("-e", childCodexFastModeExtension);
|
|
127
152
|
if (existsSync(childSafetyExtension)) args.push("-e", childSafetyExtension);
|
|
128
153
|
args.push("--session", sessionPath);
|
|
129
|
-
const model =
|
|
154
|
+
const model = resolvedModel;
|
|
130
155
|
if (model) args.push("--model", model);
|
|
131
156
|
const thinking = resolveChildThinking(agent, thinkingOverride);
|
|
132
157
|
if (thinking) args.push("--thinking", thinking);
|
|
@@ -132,6 +132,19 @@ function activeZagentState(state: HarnessRuntimeState): ActiveZagentState | unde
|
|
|
132
132
|
return state.zagent.id ? state.zagent as ActiveZagentState : undefined;
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
+
function zpeerStableTeamAgentLeaseRequired(zagent: ActiveZagentState | undefined): boolean {
|
|
136
|
+
return Boolean(
|
|
137
|
+
zagent?.id
|
|
138
|
+
|| process.env.ZOB_ZAGENT_ID?.trim()
|
|
139
|
+
|| process.env.ZOB_ZTEAM_ID?.trim()
|
|
140
|
+
|| process.env.ZOB_COMS_ROLE_ID?.trim(),
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function withZpeerLeaseMode<T extends NonNullable<HarnessRuntimeState["zobLive"]["peerCard"]>>(peer: T, useStableTeamAgentLease: boolean): T {
|
|
145
|
+
return { ...peer, zpeerAdhoc: useStableTeamAgentLease ? undefined : true } as T;
|
|
146
|
+
}
|
|
147
|
+
|
|
135
148
|
function formatScopedZteamModeLabel(scopedMode: ActiveZagentState["scopedMode"]): string | undefined {
|
|
136
149
|
if (!scopedMode?.active || !scopedMode.teamId || !scopedMode.modeId || !scopedMode.baseMode) return undefined;
|
|
137
150
|
return `zob:${scopedMode.baseMode}@${scopedMode.teamId}/${scopedMode.modeId}`;
|
|
@@ -511,6 +524,7 @@ async function startOrRefreshZobLiveRuntime(pi: ExtensionAPI, state: HarnessRunt
|
|
|
511
524
|
if (carryoverProfile?.zagentId && state.zagent.id !== carryoverProfile.zagentId) loadActiveZagentById(state, repoRoot, carryoverProfile.zagentId);
|
|
512
525
|
const sharedZpeerProfile = zpeerProfile ? zpeerProfileIdIsSharedFallback(zpeerProfile.profileId) : false;
|
|
513
526
|
const zagent = activeZagentState(state);
|
|
527
|
+
const useStableTeamAgentLease = zpeerStableTeamAgentLeaseRequired(zagent);
|
|
514
528
|
const zagentMemberships = zagent ? zagentRoomMemberships(zagent) : undefined;
|
|
515
529
|
const zagentActiveRoomId = zagent?.activeRoom ?? zagentMemberships?.find((membership) => membership.roomId)?.roomId;
|
|
516
530
|
const zpeerProfileRoomId = zagentActiveRoomId ?? zpeerProfile?.activeRoomId ?? zpeerProfile?.roomId ?? carryoverProfile?.activeRoomId ?? carryoverProfile?.roomId;
|
|
@@ -579,7 +593,7 @@ async function startOrRefreshZobLiveRuntime(pi: ExtensionAPI, state: HarnessRunt
|
|
|
579
593
|
}, { triggerTurn: true, deliverAs: "followUp" });
|
|
580
594
|
return buildZobLiveAckEnvelope(envelope);
|
|
581
595
|
});
|
|
582
|
-
const peerCard = ensureZpeerFields(repoRoot, {
|
|
596
|
+
const peerCard = withZpeerLeaseMode(ensureZpeerFields(repoRoot, {
|
|
583
597
|
...basePeer,
|
|
584
598
|
team: zagent?.team ?? basePeer.team,
|
|
585
599
|
roleId: zagent?.id ?? basePeer.roleId,
|
|
@@ -588,58 +602,78 @@ async function startOrRefreshZobLiveRuntime(pi: ExtensionAPI, state: HarnessRunt
|
|
|
588
602
|
endpoint,
|
|
589
603
|
endpointHash: sha256(endpoint),
|
|
590
604
|
status: "online" as const,
|
|
591
|
-
}, zpeerProfileRoomId, zpeerProfileAlias, zpeerProfileMemberships);
|
|
605
|
+
}, zpeerProfileRoomId, zpeerProfileAlias, zpeerProfileMemberships), useStableTeamAgentLease);
|
|
592
606
|
state.zobLive.server = server;
|
|
593
|
-
|
|
594
|
-
|
|
607
|
+
if (!useStableTeamAgentLease) {
|
|
608
|
+
releaseZobLiveTeamAgentLease(repoRoot, peerCard, { reason: "runtime_adhoc" });
|
|
595
609
|
state.zobLive.peerCard = refreshZpeerSelf(repoRoot, peerCard);
|
|
596
|
-
state.zobLive.leaseOwned =
|
|
597
|
-
state.zobLive.leaseStatus = "
|
|
610
|
+
state.zobLive.leaseOwned = false;
|
|
611
|
+
state.zobLive.leaseStatus = "unavailable";
|
|
598
612
|
state.zobLive.leaseBlockReason = undefined;
|
|
599
613
|
state.zobLive.lastHeartbeatMs = Date.now();
|
|
600
614
|
scheduleZpeerHeartbeat(pi, state, ctx);
|
|
601
615
|
} else {
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
616
|
+
const leaseClaim = await claimZobLiveTeamAgentLease(repoRoot, peerCard, { reason: "runtime_start" });
|
|
617
|
+
if (leaseClaim.ok) {
|
|
618
|
+
state.zobLive.peerCard = refreshZpeerSelf(repoRoot, peerCard);
|
|
619
|
+
state.zobLive.leaseOwned = true;
|
|
620
|
+
state.zobLive.leaseStatus = "owned";
|
|
621
|
+
state.zobLive.leaseBlockReason = undefined;
|
|
622
|
+
state.zobLive.lastHeartbeatMs = Date.now();
|
|
623
|
+
scheduleZpeerHeartbeat(pi, state, ctx);
|
|
624
|
+
} else {
|
|
625
|
+
state.zobLive.peerCard = await stopLeaseBlockedLocalEndpoint(state, repoRoot, peerCard, server);
|
|
626
|
+
setZpeerLastEvent(state, {
|
|
627
|
+
kind: "blocked",
|
|
628
|
+
roomId: state.zobLive.peerCard.zpeerActiveRoomId ?? state.zobLive.peerCard.zpeerRoomId,
|
|
629
|
+
fromAlias: state.zobLive.peerCard.zpeerAlias,
|
|
630
|
+
status: "lease_blocked",
|
|
631
|
+
reason: "stable team-agent lease is held by a responsive live endpoint; duplicate local endpoint stopped and peer marked offline",
|
|
632
|
+
});
|
|
633
|
+
}
|
|
610
634
|
}
|
|
611
635
|
try { writeZpeerLocalProfileFromPeer(repoRoot, state.zobLive.peerCard, profileId); } catch { /* best-effort reload continuity; live runtime must remain available */ }
|
|
612
636
|
} else {
|
|
613
|
-
const peerCard = ensureZpeerFields(repoRoot, {
|
|
637
|
+
const peerCard = withZpeerLeaseMode(ensureZpeerFields(repoRoot, {
|
|
614
638
|
...state.zobLive.peerCard,
|
|
615
639
|
team: zagent?.team ?? state.zobLive.peerCard.team,
|
|
616
640
|
roleId: zagent?.id ?? state.zobLive.peerCard.roleId,
|
|
617
641
|
agent: zagent?.id ?? state.zobLive.peerCard.agent,
|
|
618
642
|
heartbeatAt: new Date().toISOString(),
|
|
619
643
|
status: "online",
|
|
620
|
-
}, zpeerProfileRoomId, zpeerProfileAlias, zpeerProfileMemberships);
|
|
621
|
-
|
|
622
|
-
|
|
644
|
+
}, zpeerProfileRoomId, zpeerProfileAlias, zpeerProfileMemberships), useStableTeamAgentLease);
|
|
645
|
+
if (!useStableTeamAgentLease) {
|
|
646
|
+
releaseZobLiveTeamAgentLease(repoRoot, peerCard, { reason: "runtime_adhoc" });
|
|
623
647
|
state.zobLive.peerCard = refreshZpeerSelf(repoRoot, peerCard);
|
|
624
|
-
state.zobLive.leaseOwned =
|
|
625
|
-
state.zobLive.leaseStatus = "
|
|
648
|
+
state.zobLive.leaseOwned = false;
|
|
649
|
+
state.zobLive.leaseStatus = "unavailable";
|
|
626
650
|
state.zobLive.leaseBlockReason = undefined;
|
|
627
651
|
state.zobLive.lastHeartbeatMs = Date.now();
|
|
628
652
|
scheduleZpeerHeartbeat(pi, state, ctx);
|
|
629
|
-
} else if (state.zobLive.server) {
|
|
630
|
-
state.zobLive.peerCard = await stopLeaseBlockedLocalEndpoint(state, repoRoot, peerCard, state.zobLive.server);
|
|
631
|
-
setZpeerLastEvent(state, {
|
|
632
|
-
kind: "blocked",
|
|
633
|
-
roomId: state.zobLive.peerCard.zpeerActiveRoomId ?? state.zobLive.peerCard.zpeerRoomId,
|
|
634
|
-
fromAlias: state.zobLive.peerCard.zpeerAlias,
|
|
635
|
-
status: "lease_blocked",
|
|
636
|
-
reason: "stable team-agent lease is held by a responsive live endpoint; duplicate local endpoint stopped and peer marked offline",
|
|
637
|
-
});
|
|
638
653
|
} else {
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
654
|
+
const leaseClaim = await claimZobLiveTeamAgentLease(repoRoot, peerCard, { reason: "runtime_refresh" });
|
|
655
|
+
if (leaseClaim.ok) {
|
|
656
|
+
state.zobLive.peerCard = refreshZpeerSelf(repoRoot, peerCard);
|
|
657
|
+
state.zobLive.leaseOwned = true;
|
|
658
|
+
state.zobLive.leaseStatus = "owned";
|
|
659
|
+
state.zobLive.leaseBlockReason = undefined;
|
|
660
|
+
state.zobLive.lastHeartbeatMs = Date.now();
|
|
661
|
+
scheduleZpeerHeartbeat(pi, state, ctx);
|
|
662
|
+
} else if (state.zobLive.server) {
|
|
663
|
+
state.zobLive.peerCard = await stopLeaseBlockedLocalEndpoint(state, repoRoot, peerCard, state.zobLive.server);
|
|
664
|
+
setZpeerLastEvent(state, {
|
|
665
|
+
kind: "blocked",
|
|
666
|
+
roomId: state.zobLive.peerCard.zpeerActiveRoomId ?? state.zobLive.peerCard.zpeerRoomId,
|
|
667
|
+
fromAlias: state.zobLive.peerCard.zpeerAlias,
|
|
668
|
+
status: "lease_blocked",
|
|
669
|
+
reason: "stable team-agent lease is held by a responsive live endpoint; duplicate local endpoint stopped and peer marked offline",
|
|
670
|
+
});
|
|
671
|
+
} else {
|
|
672
|
+
state.zobLive.peerCard = writeZobLivePeerCard(repoRoot, { ...peerCard, heartbeatAt: new Date().toISOString(), status: "offline" });
|
|
673
|
+
state.zobLive.leaseOwned = false;
|
|
674
|
+
state.zobLive.leaseStatus = "blocked";
|
|
675
|
+
state.zobLive.leaseBlockReason = "blocked_live_owner";
|
|
676
|
+
}
|
|
643
677
|
}
|
|
644
678
|
try { writeZpeerLocalProfileFromPeer(repoRoot, state.zobLive.peerCard, profileId); } catch { /* best-effort reload continuity; live runtime must remain available */ }
|
|
645
679
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zob-harness",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A governed Agent Factory for Pi: launch communicating agent teams, run tmux-backed factories, validate artifacts, and package repeatable workflows.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -200,6 +200,8 @@ async function main() {
|
|
|
200
200
|
const gammaEndpoint = join(root, 'gamma.sock');
|
|
201
201
|
const workerOneEndpoint = join(root, 'worker-one.sock');
|
|
202
202
|
const workerTwoEndpoint = join(root, 'worker-two.sock');
|
|
203
|
+
const adhocOneEndpoint = join(root, 'adhoc-one.sock');
|
|
204
|
+
const adhocTwoEndpoint = join(root, 'adhoc-two.sock');
|
|
203
205
|
const pendingReplies = new Map();
|
|
204
206
|
const receivedPrompts = [];
|
|
205
207
|
const receivedResponses = [];
|
|
@@ -244,6 +246,11 @@ async function main() {
|
|
|
244
246
|
receivedPrompts.push(incoming);
|
|
245
247
|
return envelope.buildZobLiveAckEnvelope(incoming);
|
|
246
248
|
}));
|
|
249
|
+
servers.push(await localTransport.bindZobLocalEndpoint(adhocOneEndpoint, async (incoming) => envelope.buildZobLiveAckEnvelope(incoming)));
|
|
250
|
+
servers.push(await localTransport.bindZobLocalEndpoint(adhocTwoEndpoint, async (incoming) => {
|
|
251
|
+
receivedPrompts.push(incoming);
|
|
252
|
+
return envelope.buildZobLiveAckEnvelope(incoming);
|
|
253
|
+
}));
|
|
247
254
|
|
|
248
255
|
const oldHeartbeatAt = new Date(Date.now() - 180_000).toISOString();
|
|
249
256
|
let alpha = zpeer.ensureZpeerFields(repoRoot, makePeer({ alias: 'alpha', roomId: 'room-one', endpoint: alphaEndpoint, endpointHash: hashing.sha256(alphaEndpoint), sha256: hashing.sha256, heartbeatAt: oldHeartbeatAt }), 'room-one', 'alpha');
|
|
@@ -279,6 +286,17 @@ async function main() {
|
|
|
279
286
|
});
|
|
280
287
|
});
|
|
281
288
|
|
|
289
|
+
const adhocOne = zpeer.refreshZpeerSelf(repoRoot, { ...zpeer.ensureZpeerFields(repoRoot, makePeer({ alias: 'adhocone', roomId: 'adhoc-room', endpoint: adhocOneEndpoint, endpointHash: hashing.sha256(adhocOneEndpoint), sha256: hashing.sha256 }), 'adhoc-room', 'adhocone'), zpeerAdhoc: true });
|
|
290
|
+
const adhocTwo = zpeer.refreshZpeerSelf(repoRoot, { ...zpeer.ensureZpeerFields(repoRoot, makePeer({ alias: 'adhoctwo', roomId: 'adhoc-room', endpoint: adhocTwoEndpoint, endpointHash: hashing.sha256(adhocTwoEndpoint), sha256: hashing.sha256 }), 'adhoc-room', 'adhoctwo'), zpeerAdhoc: true });
|
|
291
|
+
assert(adhocOne.zpeerAdhoc === true && adhocTwo.zpeerAdhoc === true, 'direct/ad-hoc zpeer peers must carry the ad-hoc marker');
|
|
292
|
+
const adhocSummary = zpeer.buildZpeerRoomSummary(repoRoot, adhocOne, 'adhoc-room');
|
|
293
|
+
assert(adhocSummary.peerCount === 2 && adhocSummary.online === 2, `ad-hoc room summary expected 2 online peers even with lease domain present, got ${adhocSummary.online}/${adhocSummary.peerCount}`);
|
|
294
|
+
assert(adhocSummary.aliases.includes('adhocone') && adhocSummary.aliases.includes('adhoctwo'), 'ad-hoc room summary must include both direct peers');
|
|
295
|
+
const adhocPromptCountBefore = receivedPrompts.length;
|
|
296
|
+
const adhocAsync = await zpeer.sendZpeerPrompt(repoRoot, adhocOne, 'adhoctwo', rawPrompt, waitForReply, { mode: 'async' });
|
|
297
|
+
assert(adhocAsync.status === 'waiting', `ad-hoc same-room send expected waiting after ACK, got ${adhocAsync.status}${adhocAsync.reason ? `: ${adhocAsync.reason}` : ''}`);
|
|
298
|
+
assert(receivedPrompts.length === adhocPromptCountBefore + 1 && receivedPrompts.at(-1).receiver === 'adhoctwo', 'ad-hoc same-room send must deliver to the target without a stable team-agent lease');
|
|
299
|
+
|
|
282
300
|
const joinedAlpha = await zpeer.joinZpeerRoom(repoRoot, alpha, 'shared-room', 'sharedalpha', 'bridge');
|
|
283
301
|
assert(joinedAlpha.ok === true, `alpha multi-room join expected ok, got ${joinedAlpha.reason ?? 'not ok'}`);
|
|
284
302
|
alpha = joinedAlpha.peer;
|
|
@@ -34,6 +34,7 @@ const files = [
|
|
|
34
34
|
'.pi/extensions/zob-harness/src/domains/coms/coms-v2/registry.ts',
|
|
35
35
|
'.pi/extensions/zob-harness/src/domains/coms/coms-v2/zpeer-profile.ts',
|
|
36
36
|
'.pi/extensions/zob-harness/src/domains/coms/coms-v2/transcript-capture.ts',
|
|
37
|
+
'.pi/extensions/zob-harness/src/domains/delegation/child-runner.ts',
|
|
37
38
|
'.pi/extensions/zob-harness/src/runtime/commands.ts',
|
|
38
39
|
'.pi/extensions/zob-harness/src/runtime/tools-coms.ts',
|
|
39
40
|
'.pi/extensions/zob-harness/src/runtime/events.ts',
|
|
@@ -82,7 +83,7 @@ for (const forbidden of ['transientPrompt:', 'transientResponse:', 'prompt:', 'o
|
|
|
82
83
|
const zpeer = contents['.pi/extensions/zob-harness/src/domains/coms/coms-v2/zpeer.ts'];
|
|
83
84
|
const liveRegistry = contents['.pi/extensions/zob-harness/src/domains/coms/coms-v2/registry.ts'];
|
|
84
85
|
const zpeerProfile = contents['.pi/extensions/zob-harness/src/domains/coms/coms-v2/zpeer-profile.ts'];
|
|
85
|
-
for (const needle of ['networkEnabled: false', 'localOnly: true', 'bodyStored: false', 'sendZobLocalEnvelope', 'taskHash', 'outputHash', 'ZpeerSendMode', 'status: "waiting"', 'status: "reply"', 'zpeerMembershipsForPeer', 'joinZpeerRoom', 'leaveZpeerRoom', 'useZpeerRoom', 'clearZpeerRoom', 'preservedSelf: true', 'roomId?: string', 'buildZpeerPeerRoomSummaries', 'active: membership.roomId === activeRoomId', 'peerRespondsToAliasPing', 'activeAliasCollision', 'zpeer-alias-ping']) {
|
|
86
|
+
for (const needle of ['networkEnabled: false', 'localOnly: true', 'bodyStored: false', 'sendZobLocalEnvelope', 'taskHash', 'outputHash', 'ZpeerSendMode', 'status: "waiting"', 'status: "reply"', 'zpeerMembershipsForPeer', 'joinZpeerRoom', 'leaveZpeerRoom', 'useZpeerRoom', 'clearZpeerRoom', 'preservedSelf: true', 'roomId?: string', 'buildZpeerPeerRoomSummaries', 'active: membership.roomId === activeRoomId', 'peerRespondsToAliasPing', 'activeAliasCollision', 'zpeer-alias-ping', 'zpeerAdhoc']) {
|
|
86
87
|
if (!zpeer.includes(needle)) failures.push(`zpeer missing ${needle}`);
|
|
87
88
|
}
|
|
88
89
|
for (const needle of ['peer-messages.jsonl', 'peer-status.jsonl', 'appendZpeerPeerRecords', 'reasonHash', 'bodyStored: false']) {
|
|
@@ -96,7 +97,8 @@ if (!zpeer.includes('const bothPeersAreWorkers = self.roleType === "worker" && t
|
|
|
96
97
|
if (hardTeamMismatchIndex !== -1 && (roomFirstTopologyIndex === -1 || hardTeamMismatchIndex < roomFirstTopologyIndex)) failures.push('zpeer topology must not hard-block cross-team peers before same-room allowance');
|
|
97
98
|
if (!zpeer.includes('const candidates = peersInRoom(repoRoot, roomId).filter') || !zpeer.includes('if (!selfMembership)') || !zpeer.includes('current peer is observer-only in room')) failures.push('zpeer same-room allowance must preserve room candidate, self membership, and observer guards');
|
|
98
99
|
if (!liveRegistry.includes('readZobLiveRegistryAllProjectsSnapshot') || !liveRegistry.includes('join(projectsDir, entry.name, "agents")')) failures.push('live registry must expose all-project agents room discovery helper');
|
|
99
|
-
|
|
100
|
+
if (!liveRegistry.includes('peer.zpeerAdhoc !== true') || !liveRegistry.includes('mergeLeaseBackedAndAdhocPeers(leaseDirs.flatMap')) failures.push('live registry must merge explicit ad-hoc room peer cards into lease-backed summaries');
|
|
101
|
+
for (const needle of ['ZobLiveTeamAgentLease', 'zob.live-team-agent-lease.v1', 'stableLease: true', 'exclusiveBy: "teamId+agentId"', 'claimZobLiveTeamAgentLease', 'leaseRespondsToPing', 'pingZobLocalEndpoint', 'releaseZobLiveTeamAgentLease', 'ownsZobLiveTeamAgentLease', 'sameLeaseOwner', 'retireInactiveZobLiveTeamAgentLeases', 'readLeasesFromDir', 'hasLeaseDomain', 'mergeLeaseBackedAndAdhocPeers']) {
|
|
100
102
|
if (!liveRegistry.includes(needle)) failures.push(`live registry stable lease support missing ${needle}`);
|
|
101
103
|
}
|
|
102
104
|
if (!zpeer.includes('readZobLiveRegistryAllProjectsSnapshot(repoRoot)')) failures.push('zpeer room discovery must use all-project registry snapshots');
|
|
@@ -172,7 +174,7 @@ for (const needle of ['readZpeerNewCarryoverProfile(repoRoot)', 'const carryover
|
|
|
172
174
|
}
|
|
173
175
|
if (!events.includes('event.source === "extension" && !event.text.trim()') || !events.includes('action: "handled" as const')) failures.push('runtime must handle empty extension follow-ups without continuing the agent');
|
|
174
176
|
if (!events.includes('ZPeer async reply received from @') || !events.includes('{ triggerTurn: true, deliverAs: "followUp" }')) failures.push('runtime must resume an idle agent with a follow-up when an async ZPeer reply arrives');
|
|
175
|
-
for (const needle of ['ZPEER AWARENESS (transient, rebuilt each turn)', 'buildZpeerPeerRoomSummaries(repoRoot, state.zobLive.peerCard)', '- rooms:', 'explicit roomId', 'zpeer_ask with mode=\\"async\\"', 'Passive wait rule', 'stop the turn and remain idle', 'avoid spam', 'Raw ZPeer bodies are transient', 'registerMessageRenderer("zob-zpeer-event"', 'scheduleZpeerHeartbeat', 'clearZpeerHeartbeatTimer', 'refreshZpeerSelf(repoRoot', 'kind: "response_sent"', 'kind: "inbound"']) {
|
|
177
|
+
for (const needle of ['ZPEER AWARENESS (transient, rebuilt each turn)', 'buildZpeerPeerRoomSummaries(repoRoot, state.zobLive.peerCard)', '- rooms:', 'explicit roomId', 'zpeer_ask with mode=\\"async\\"', 'Passive wait rule', 'stop the turn and remain idle', 'avoid spam', 'Raw ZPeer bodies are transient', 'registerMessageRenderer("zob-zpeer-event"', 'scheduleZpeerHeartbeat', 'clearZpeerHeartbeatTimer', 'refreshZpeerSelf(repoRoot', 'kind: "response_sent"', 'kind: "inbound"', 'zpeerStableTeamAgentLeaseRequired', 'withZpeerLeaseMode', 'leaseStatus = "unavailable"', 'runtime_adhoc']) {
|
|
176
178
|
if (!events.includes(needle)) failures.push(`runtime missing zpeer awareness/event support ${needle}`);
|
|
177
179
|
}
|
|
178
180
|
const responseSentBlock = events.match(/setZpeerLastEvent\(state, \{\s*kind: "response_sent"[\s\S]*?customType: "zob-zpeer-event"[\s\S]*?triggerTurn: false[\s\S]*?\}\);/)?.[0] ?? '';
|
|
@@ -212,6 +214,11 @@ const goalRuntime = contents['.pi/extensions/zob-harness/src/runtime/goal-runtim
|
|
|
212
214
|
for (const needle of ['state.zobLive.passivePeerWait?.suppressGoalContinuation === true', 'clearRuntimeGoalContinuationTimer(state)', 'return;']) {
|
|
213
215
|
if (!goalRuntime.includes(needle)) failures.push(`goal runtime missing passive peer continuation suppression ${needle}`);
|
|
214
216
|
}
|
|
217
|
+
|
|
218
|
+
const childRunner = contents['.pi/extensions/zob-harness/src/domains/delegation/child-runner.ts'];
|
|
219
|
+
for (const needle of ['childModelPattern', 'ctx.model', 'resolveCodexFastModeExtension', 'getAgentDir()', 'codex-fast-mode.ts', 'childCodexFastModeExtension', 'args.push("-e", childCodexFastModeExtension)', 'const model = resolvedModel']) {
|
|
220
|
+
if (!childRunner.includes(needle)) failures.push(`delegation child runner missing Codex auto/model inheritance support ${needle}`);
|
|
221
|
+
}
|
|
215
222
|
for (const needle of ['updatePassivePeerWaitState(state, result', 'result.status !== "waiting"', 'state.zobLive.passivePeerWait = undefined', 'schema: "zob.passive-peer-wait.v1"', 'source: "zpeer_ask"', 'suppressGoalContinuation: true', 'bodyStored: false', 'localOnly: true', 'networkEnabled: false']) {
|
|
216
223
|
if (!toolsComs.includes(needle)) failures.push(`zpeer_ask missing passive peer wait state handling ${needle}`);
|
|
217
224
|
}
|