zob-harness 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.pi/capabilities/zob-public-runtime-capabilities.json +35 -1
- package/.pi/extensions/zob-harness/src/coms-v2/registry.ts +61 -27
- package/.pi/extensions/zob-harness/src/coms-v2/zpeer-profile.ts +139 -16
- package/.pi/extensions/zob-harness/src/coms-v2/zpeer.ts +100 -25
- package/.pi/extensions/zob-harness/src/constants.ts +8 -0
- package/.pi/extensions/zob-harness/src/goal-runtime.ts +4 -0
- package/.pi/extensions/zob-harness/src/model-routing.ts +2 -2
- package/.pi/extensions/zob-harness/src/rules.ts +1 -1
- package/.pi/extensions/zob-harness/src/runtime/commands.ts +360 -14
- package/.pi/extensions/zob-harness/src/runtime/delegation-overlay.ts +22 -4
- package/.pi/extensions/zob-harness/src/runtime/events.ts +297 -27
- package/.pi/extensions/zob-harness/src/runtime/mode-intent.ts +5 -1
- package/.pi/extensions/zob-harness/src/runtime/state.ts +37 -0
- package/.pi/extensions/zob-harness/src/runtime/tools-coms.ts +30 -3
- package/.pi/extensions/zob-harness/src/runtime/widget.ts +119 -33
- package/.pi/extensions/zob-harness/src/types/core.ts +1 -1
- package/.pi/extensions/zob-harness/src/zagents.ts +534 -0
- package/.pi/model-routing.json +2 -1
- package/.pi/prompts/vanilla.md +14 -0
- package/.pi/skills/zob-zagent-creator/SKILL.md +85 -0
- package/package.json +3 -1
- package/scripts/zagent-static-smoke.mjs +155 -0
- package/scripts/zpeer-local-e2e-smoke.mjs +88 -20
- package/scripts/zpeer-static-smoke.mjs +78 -9
|
@@ -64,7 +64,7 @@ function chooseModelClass(input: ModelRoutingDryRunInput): { modelClass: ModelCl
|
|
|
64
64
|
const risk = normalizeRisk(input.risk);
|
|
65
65
|
const contextTokens = numberOrZero(input.contextTokens);
|
|
66
66
|
const isOracleLike = includesAny(input.taskType, ["oracle", "review", "validate"]) || includesAny(input.outputContract, ["oracle"]);
|
|
67
|
-
const isImplementationLike = includesAny(input.taskType, ["implement", "factory", "synthesis", "worker"]) || input.mode === "implement" || input.mode === "factory";
|
|
67
|
+
const isImplementationLike = includesAny(input.taskType, ["implement", "factory", "synthesis", "worker"]) || input.mode === "implement" || input.mode === "factory" || input.mode === "vanilla";
|
|
68
68
|
|
|
69
69
|
if (contextTokens >= 120_000) {
|
|
70
70
|
reasonCodes.push("context_tokens_high");
|
|
@@ -239,7 +239,7 @@ export function validateModelRoutingConfig(repoRoot: string): Record<string, unk
|
|
|
239
239
|
}
|
|
240
240
|
|
|
241
241
|
const expectedClasses: ModelClass[] = ["cheap_scout", "balanced_worker", "strong_reasoning", "strong_oracle", "high_context"];
|
|
242
|
-
const modes: ModeName[] = ["explore", "plan", "implement", "oracle", "factory", "orchestrator"];
|
|
242
|
+
const modes: ModeName[] = ["explore", "plan", "implement", "oracle", "factory", "orchestrator", "vanilla"];
|
|
243
243
|
const modelClasses = isRecord(parsed?.modelClasses) ? parsed.modelClasses : undefined;
|
|
244
244
|
const defaults = isRecord(parsed?.defaults) ? parsed.defaults : undefined;
|
|
245
245
|
const byMode = isRecord(defaults?.byMode) ? defaults.byMode : undefined;
|
|
@@ -8,7 +8,7 @@ import { isRecord } from "./utils/records.js";
|
|
|
8
8
|
const RULE_PACK_SCHEMA = "zob.rule-pack.v1";
|
|
9
9
|
const RULE_RESOLUTION_SCHEMA = "zob.rule-resolution.v1";
|
|
10
10
|
const RULE_PACK_ORDER = ["always", "project", "runtime", "factory", "orchestration", "prompts", "docs", "sandbox", "oracle"];
|
|
11
|
-
const MODE_NAMES = new Set<ModeName>(["explore", "plan", "implement", "oracle", "factory", "orchestrator"]);
|
|
11
|
+
const MODE_NAMES = new Set<ModeName>(["explore", "plan", "implement", "oracle", "factory", "orchestrator", "vanilla"]);
|
|
12
12
|
const ENFORCEMENT_LEVELS = new Set<RuleEnforcementLevel>(["advisory", "warn", "preflight_fail", "block", "no_ship", "human_approval"]);
|
|
13
13
|
|
|
14
14
|
const PROFILE_PATHS: Array<{ profile: string; patterns: string[] }> = [
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
1
|
+
import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import type { AutocompleteItem } from "@earendil-works/pi-tui";
|
|
3
3
|
|
|
4
4
|
import { MODE_PROMPTS } from "../constants.js";
|
|
@@ -9,12 +9,14 @@ import { buildComputeWorkflowShape } from "../compute-workflow-shape.js";
|
|
|
9
9
|
import { buildDaemonRuntimeState, buildDaemonTickPlan, type DaemonRuntimeState, type DaemonTickPlan } from "../daemon-runtime.js";
|
|
10
10
|
import { runQueueDaemonTick } from "../queue.js";
|
|
11
11
|
import { buildProjectDnaAgenticPlan, buildProjectDnaQueryResult, buildProjectDnaReadinessAudit } from "../project-dna.js";
|
|
12
|
+
import { formatZagentList, formatZteamList, listZagentManifests, listZteamManifests, loadZagentManifest, loadZteamManifest, normalizeZagentRoomBindings, readZagentPrompt, type ZAgentManifest, type ZAgentRoomBinding, type ZTeamAgentManifest, type ZTeamManifest, type ZTeamMemberManifest } from "../zagents.js";
|
|
12
13
|
import { resolveAdaptiveZmodeEntrypoint, renderAdaptiveZmodeTemplate } from "./adaptive-zmode.js";
|
|
13
14
|
import { handleZcompactCommand } from "./auto-compaction.js";
|
|
14
15
|
import { sha256 } from "../utils/hashing.js";
|
|
15
16
|
import { buildZcommitPlan, formatZcommitPlan, formatZcommitStatus, readZcommitPolicy, runGovernedZcommitAdopt, runGovernedZcommitCommit, runGovernedZcommitPush, type ZcommitAdoptResult, type ZcommitCommandResult, type ZcommitOwnedPathRef, type ZcommitToggleState } from "../git-ops.js";
|
|
16
|
-
import { writeZpeerLocalProfileFromPeer } from "../coms-v2/zpeer-profile.js";
|
|
17
|
-
import { buildZpeerRoomSummary, changeZpeerAlias, changeZpeerRoom, joinZpeerRoom, leaveZpeerRoom, peerAliasInRoom, refreshZpeerSelf, sendZpeerPrompt, useZpeerRoom, zpeerMembershipsForPeer, type ZpeerSendMode } from "../coms-v2/zpeer.js";
|
|
17
|
+
import { clearZpeerNewCarryoverProfile, writeZpeerLocalProfileFromPeer } from "../coms-v2/zpeer-profile.js";
|
|
18
|
+
import { buildZpeerRoomSummary, changeZpeerAlias, changeZpeerRoom, clearZpeerRoom, joinZpeerRoom, leaveZpeerRoom, peerAliasInRoom, refreshZpeerSelf, sendZpeerPrompt, useZpeerRoom, zpeerMembershipsForPeer, type ZpeerSendMode } from "../coms-v2/zpeer.js";
|
|
19
|
+
import { markZpeerNewHardResetPending } from "./events.js";
|
|
18
20
|
import { parseBillableJobIntake, validateBillableJobIntake } from "../goal.js";
|
|
19
21
|
import { handleGoalCommand, handleGoalGateCommand, pauseRuntimeGoalForStop } from "../goal-runtime.js";
|
|
20
22
|
import { formatRuleResolution, resolveRuleProfile } from "../rules.js";
|
|
@@ -37,6 +39,11 @@ import { applyMode, renderHarnessWidget } from "./widget.js";
|
|
|
37
39
|
const COMPUTE_PROFILES = ["auto", "low", "medium", "high", "xhigh", "max"] as const;
|
|
38
40
|
const COMPUTE_DOMAINS = ["generic", "project-dna", "factory", "orchestration"] as const;
|
|
39
41
|
|
|
42
|
+
function zpeerCommandProfileId(ctx: ExtensionCommandContext): string {
|
|
43
|
+
const sessionIdentity = ctx.sessionManager.getSessionFile() ?? ctx.sessionManager.getSessionId();
|
|
44
|
+
return `session-${sha256(sessionIdentity).slice(0, 24)}`;
|
|
45
|
+
}
|
|
46
|
+
|
|
40
47
|
function zcommitArgumentCompletions(prefix: string): AutocompleteItem[] | null {
|
|
41
48
|
const query = prefix.trim().toLowerCase();
|
|
42
49
|
const items: AutocompleteItem[] = [
|
|
@@ -527,6 +534,156 @@ function stopCommandLedgerEntry(input: {
|
|
|
527
534
|
};
|
|
528
535
|
}
|
|
529
536
|
|
|
537
|
+
function zagentArgumentCompletions(prefix: string): AutocompleteItem[] | null {
|
|
538
|
+
const query = prefix.trim().toLowerCase();
|
|
539
|
+
const ids = listZagentManifests(process.cwd()).map((agent) => agent.manifest.id).filter(Boolean);
|
|
540
|
+
const items: AutocompleteItem[] = [
|
|
541
|
+
{ value: "list", label: "list", description: "list project-local ZAgents" },
|
|
542
|
+
...ids.flatMap((id) => [
|
|
543
|
+
{ value: `show ${id}`, label: `show ${id}`, description: "show manifest metadata" },
|
|
544
|
+
{ value: `use ${id}`, label: `use ${id}`, description: "load ZAgent and apply ZPeer profile" },
|
|
545
|
+
]),
|
|
546
|
+
];
|
|
547
|
+
const filtered = query
|
|
548
|
+
? items.filter((item) => item.value.toLowerCase().startsWith(query) || item.label.toLowerCase().includes(query) || item.description?.toLowerCase().includes(query))
|
|
549
|
+
: items;
|
|
550
|
+
return filtered.length > 0 ? filtered.slice(0, 20) : null;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
function zteamArgumentCompletions(prefix: string): AutocompleteItem[] | null {
|
|
554
|
+
const query = prefix.trim().toLowerCase();
|
|
555
|
+
const ids = listZteamManifests(process.cwd()).map((team) => team.manifest.id).filter(Boolean);
|
|
556
|
+
const items: AutocompleteItem[] = [
|
|
557
|
+
{ value: "list", label: "list", description: "list project-local ZTeams" },
|
|
558
|
+
...ids.flatMap((id) => [
|
|
559
|
+
{ value: `show ${id}`, label: `show ${id}`, description: "show team manifest metadata" },
|
|
560
|
+
{ value: `launch-plan ${id}`, label: `launch-plan ${id}`, description: "print full-session launch commands" },
|
|
561
|
+
]),
|
|
562
|
+
];
|
|
563
|
+
const filtered = query
|
|
564
|
+
? items.filter((item) => item.value.toLowerCase().startsWith(query) || item.label.toLowerCase().includes(query) || item.description?.toLowerCase().includes(query))
|
|
565
|
+
: items;
|
|
566
|
+
return filtered.length > 0 ? filtered.slice(0, 20) : null;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
function zagentLedgerEntry(action: string, input: { id?: string; teamId?: string; status: "ok" | "blocked"; roomIds?: string[]; alias?: string; path?: string; promptRef?: string; promptBody?: string; errors?: string[] }): Record<string, unknown> {
|
|
570
|
+
return {
|
|
571
|
+
schema: "zob.zagent-command.v1",
|
|
572
|
+
action,
|
|
573
|
+
status: input.status,
|
|
574
|
+
idHash: input.id ? sha256(input.id) : undefined,
|
|
575
|
+
teamIdHash: input.teamId ? sha256(input.teamId) : undefined,
|
|
576
|
+
aliasHash: input.alias ? sha256(input.alias) : undefined,
|
|
577
|
+
roomIdHashes: (input.roomIds ?? []).map((roomId) => sha256(roomId)),
|
|
578
|
+
pathHash: input.path ? sha256(input.path) : undefined,
|
|
579
|
+
promptRefHash: input.promptRef ? sha256(input.promptRef) : undefined,
|
|
580
|
+
promptHash: input.promptBody ? sha256(input.promptBody) : undefined,
|
|
581
|
+
errorHashes: (input.errors ?? []).map((error) => sha256(error)),
|
|
582
|
+
localOnly: true,
|
|
583
|
+
networkEnabled: false,
|
|
584
|
+
bodyStored: false,
|
|
585
|
+
promptBodiesStored: false,
|
|
586
|
+
outputBodiesStored: false,
|
|
587
|
+
generatedAt: new Date().toISOString(),
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
function formatZagentShow(loaded: ReturnType<typeof loadZagentManifest>): string {
|
|
592
|
+
const rooms = normalizeZagentRoomBindings(loaded.manifest.rooms, loaded.manifest.defaultRoom, loaded.manifest.activeRoom);
|
|
593
|
+
return [
|
|
594
|
+
`ZAgent ${loaded.manifest.id}`,
|
|
595
|
+
`path: ${loaded.path}`,
|
|
596
|
+
`status: ${loaded.errors.length === 0 ? "ok" : `blocked (${loaded.errors.length} error${loaded.errors.length === 1 ? "" : "s"})`}`,
|
|
597
|
+
loaded.manifest.description ? `description: ${loaded.manifest.description}` : undefined,
|
|
598
|
+
loaded.manifest.team ? `team: ${loaded.manifest.team}` : undefined,
|
|
599
|
+
loaded.manifest.role ? `role: ${loaded.manifest.role}` : undefined,
|
|
600
|
+
loaded.manifest.alias ? `alias: @${loaded.manifest.alias}` : undefined,
|
|
601
|
+
rooms.length ? `rooms: ${rooms.map((room) => `${room.id}${room.alias ? `@${room.alias}` : ""}${room.active ? "*" : ""}`).join(", ")}` : "rooms: none",
|
|
602
|
+
loaded.manifest.promptRef ? `promptRef: ${loaded.manifest.promptRef}` : "promptRef: none",
|
|
603
|
+
loaded.promptPath ? `promptPath: ${loaded.promptPath}` : undefined,
|
|
604
|
+
loaded.errors.length ? `errors:\n- ${loaded.errors.join("\n- ")}` : undefined,
|
|
605
|
+
"safety: project-local, localOnly=true, networkEnabled=false, bodyStored=false",
|
|
606
|
+
].filter((line): line is string => Boolean(line)).join("\n");
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
function formatZteamShow(loaded: ReturnType<typeof loadZteamManifest>): string {
|
|
610
|
+
const rooms = normalizeZagentRoomBindings(loaded.manifest.rooms, loaded.manifest.defaultRoom, loaded.manifest.activeRoom);
|
|
611
|
+
const members = zteamMembers(loaded.manifest);
|
|
612
|
+
return [
|
|
613
|
+
`ZTeam ${loaded.manifest.id}`,
|
|
614
|
+
`path: ${loaded.path}`,
|
|
615
|
+
`status: ${loaded.errors.length === 0 ? "ok" : `blocked (${loaded.errors.length} error${loaded.errors.length === 1 ? "" : "s"})`}`,
|
|
616
|
+
loaded.manifest.description ? `description: ${loaded.manifest.description}` : undefined,
|
|
617
|
+
rooms.length ? `rooms: ${rooms.map((room) => `${room.id}${room.active ? "*" : ""}`).join(", ")}` : "rooms: none",
|
|
618
|
+
`agents: ${members.map((member) => member.id).join(", ") || "none"}`,
|
|
619
|
+
loaded.errors.length ? `errors:\n- ${loaded.errors.join("\n- ")}` : undefined,
|
|
620
|
+
"safety: launch-plan only; commands are printed, not spawned",
|
|
621
|
+
].filter((line): line is string => Boolean(line)).join("\n");
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
function normalizeZpeerRole(role: string | undefined): "member" | "bridge" | "observer" {
|
|
625
|
+
return role === "bridge" || role === "observer" ? role : "member";
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
async function applyZagentToZpeer(repoRoot: string, peer: NonNullable<HarnessRuntimeState["zobLive"]["peerCard"]>, manifest: ZAgentManifest): Promise<{ ok: true; peer: NonNullable<HarnessRuntimeState["zobLive"]["peerCard"]> } | { ok: false; reason: string; peer: NonNullable<HarnessRuntimeState["zobLive"]["peerCard"]> }> {
|
|
629
|
+
let current = refreshZpeerSelf(repoRoot, peer);
|
|
630
|
+
const rooms = normalizeZagentRoomBindings(manifest.rooms, manifest.defaultRoom, manifest.activeRoom);
|
|
631
|
+
if (rooms.length === 0 && manifest.alias) {
|
|
632
|
+
const changed = await changeZpeerAlias(repoRoot, current, manifest.alias);
|
|
633
|
+
if (!changed.ok) return { ok: false, reason: changed.reason, peer: current };
|
|
634
|
+
current = changed.peer;
|
|
635
|
+
}
|
|
636
|
+
for (const room of rooms) {
|
|
637
|
+
const joined = await joinZpeerRoom(repoRoot, current, room.id, room.alias ?? manifest.alias, normalizeZpeerRole(room.role));
|
|
638
|
+
if (!joined.ok) return { ok: false, reason: joined.reason, peer: current };
|
|
639
|
+
current = joined.peer;
|
|
640
|
+
}
|
|
641
|
+
const activeRoom = rooms.find((room) => room.active)?.id ?? manifest.activeRoom ?? manifest.defaultRoom;
|
|
642
|
+
if (activeRoom) {
|
|
643
|
+
const used = useZpeerRoom(repoRoot, current, activeRoom);
|
|
644
|
+
if (!used.ok) return { ok: false, reason: used.reason, peer: current };
|
|
645
|
+
current = used.peer;
|
|
646
|
+
}
|
|
647
|
+
return { ok: true, peer: current };
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
function zteamMemberId(member: ZTeamMemberManifest | ZTeamAgentManifest): string {
|
|
651
|
+
return "zagentId" in member ? member.zagentId : member.id;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
function zteamMembers(team: ZTeamManifest): Array<{ id: string; alias?: string; room?: string; rooms?: ZAgentRoomBinding[]; role?: string; active?: boolean }> {
|
|
655
|
+
const rawMembers = [...(team.members ?? []), ...(team.agents ?? [])];
|
|
656
|
+
return rawMembers.map((member) => ({
|
|
657
|
+
id: zteamMemberId(member),
|
|
658
|
+
alias: member.alias,
|
|
659
|
+
room: member.room,
|
|
660
|
+
rooms: normalizeZagentRoomBindings(member.rooms ?? (member.room ? [member.room] : undefined), team.defaultRoom, member.active ? (member.room ?? team.activeRoom) : undefined),
|
|
661
|
+
role: member.role,
|
|
662
|
+
active: member.active,
|
|
663
|
+
}));
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
function zteamLaunchPlanText(team: ZTeamManifest): { text: string; roomIds: string[]; agentIds: string[] } {
|
|
667
|
+
const teamRooms = normalizeZagentRoomBindings(team.rooms, team.defaultRoom, team.activeRoom).map((room) => room.id);
|
|
668
|
+
const members = zteamMembers(team);
|
|
669
|
+
const roomIds = [...new Set([...teamRooms, ...members.flatMap((member) => (member.rooms ?? []).map((room) => room.id))])];
|
|
670
|
+
const agentIds = members.map((member) => member.id);
|
|
671
|
+
const lines = [
|
|
672
|
+
`# ZTeam launch-plan: ${team.id}`,
|
|
673
|
+
"No processes spawned. Copy/paste each command in a separate terminal when approved.",
|
|
674
|
+
"",
|
|
675
|
+
...members.map((member) => {
|
|
676
|
+
const rooms = (member.rooms ?? []).map((room) => `${room.id}${room.active ? "*" : ""}`).join(", ") || teamRooms.join(", ") || "default";
|
|
677
|
+
const alias = member.alias ? ` alias=@${member.alias}` : "";
|
|
678
|
+
return `ZOB_ZAGENT_ID=${member.id} pi # expected_rooms=${rooms}${alias}`;
|
|
679
|
+
}),
|
|
680
|
+
"",
|
|
681
|
+
`Expected rooms: ${roomIds.join(", ") || "default"}`,
|
|
682
|
+
"After each session starts, run /zagent use <id> to bind its ZPeer alias/rooms.",
|
|
683
|
+
];
|
|
684
|
+
return { text: lines.join("\n"), roomIds, agentIds };
|
|
685
|
+
}
|
|
686
|
+
|
|
530
687
|
function delegationArgumentCompletions(state: HarnessRuntimeState, prefix: string): AutocompleteItem[] | null {
|
|
531
688
|
const query = prefix.trim().toLowerCase();
|
|
532
689
|
const items: AutocompleteItem[] = [];
|
|
@@ -546,8 +703,43 @@ function delegationArgumentCompletions(state: HarnessRuntimeState, prefix: strin
|
|
|
546
703
|
}
|
|
547
704
|
|
|
548
705
|
export function registerHarnessCommands(pi: ExtensionAPI, state: HarnessRuntimeState): void {
|
|
706
|
+
// Exact `/new` is handled by Pi before extension input/command hooks. Soft carryover
|
|
707
|
+
// is therefore written from the `session_shutdown` reason="new" hook in events.ts.
|
|
708
|
+
// Keep this registration only for `/new hard`, where users need an explicit clean reset.
|
|
709
|
+
pi.registerCommand("new", {
|
|
710
|
+
description: "Hard reset helper for ZPeer/ZAgent continuity. Exact /new soft carryover is handled on session_shutdown reason=new.",
|
|
711
|
+
getArgumentCompletions: (prefix) => {
|
|
712
|
+
const query = prefix.trim().toLowerCase();
|
|
713
|
+
const items: AutocompleteItem[] = [
|
|
714
|
+
{ value: "hard", label: "hard", description: "clear ZPeer/ZAgent carryover before starting a clean new session" },
|
|
715
|
+
];
|
|
716
|
+
const filtered = query ? items.filter((item) => item.value.startsWith(query) || item.label.includes(query)) : items;
|
|
717
|
+
return filtered.length > 0 ? filtered : null;
|
|
718
|
+
},
|
|
719
|
+
handler: async (args, ctx) => {
|
|
720
|
+
const hard = args.trim().split(/\s+/).filter(Boolean)[0]?.toLowerCase() === "hard";
|
|
721
|
+
if (hard) {
|
|
722
|
+
markZpeerNewHardResetPending(ctx.cwd);
|
|
723
|
+
clearZpeerNewCarryoverProfile(ctx.cwd);
|
|
724
|
+
}
|
|
725
|
+
pi.appendEntry("zob-znew", {
|
|
726
|
+
schema: "zob.znew-command.v1",
|
|
727
|
+
source: "registered_command",
|
|
728
|
+
action: hard ? "new_hard" : "new_soft_deferred_to_session_shutdown",
|
|
729
|
+
carryoverWritten: false,
|
|
730
|
+
carryoverCleared: hard,
|
|
731
|
+
carryoverDeferredToShutdown: !hard,
|
|
732
|
+
localOnly: true,
|
|
733
|
+
networkEnabled: false,
|
|
734
|
+
bodyStored: false,
|
|
735
|
+
generatedAt: new Date().toISOString(),
|
|
736
|
+
});
|
|
737
|
+
await ctx.newSession();
|
|
738
|
+
},
|
|
739
|
+
});
|
|
740
|
+
|
|
549
741
|
pi.registerCommand("zmode", {
|
|
550
|
-
description: "Switch ZOB harness mode: explore | plan | implement | oracle | factory | orchestrator. Orchestrator routes to adaptive-chief-vision plan_only defaults.",
|
|
742
|
+
description: "Switch ZOB harness mode: explore | plan | implement | oracle | factory | orchestrator | vanilla. Orchestrator routes to adaptive-chief-vision plan_only defaults; vanilla restores Pi base-style unrestricted tool access outside ZOB governance.",
|
|
551
743
|
handler: async (args, ctx) => {
|
|
552
744
|
const requestedText = args.trim();
|
|
553
745
|
const adaptiveEntrypoint = resolveAdaptiveZmodeEntrypoint(requestedText);
|
|
@@ -645,6 +837,147 @@ export function registerHarnessCommands(pi: ExtensionAPI, state: HarnessRuntimeS
|
|
|
645
837
|
}, { triggerTurn: false });
|
|
646
838
|
};
|
|
647
839
|
|
|
840
|
+
pi.registerCommand("zagent", {
|
|
841
|
+
description: "Project-local full-session ZAgents: /zagent list | show <id> | use <id>",
|
|
842
|
+
getArgumentCompletions: zagentArgumentCompletions,
|
|
843
|
+
handler: async (args, ctx) => {
|
|
844
|
+
const parts = args.trim().split(/\s+/).filter(Boolean);
|
|
845
|
+
const action = (parts[0] ?? "list").toLowerCase();
|
|
846
|
+
if (action === "list") {
|
|
847
|
+
const agents = listZagentManifests(ctx.cwd);
|
|
848
|
+
const roomIds = agents.flatMap((agent) => normalizeZagentRoomBindings(agent.manifest.rooms, agent.manifest.defaultRoom, agent.manifest.activeRoom).map((room) => room.id));
|
|
849
|
+
pi.appendEntry("zob-zagent", zagentLedgerEntry("list", { status: "ok", roomIds }));
|
|
850
|
+
renderHarnessWidget(pi, state, ctx);
|
|
851
|
+
void pi.sendMessage({ customType: "zob-zagent-list", content: formatZagentList(agents), display: true, details: { bodyStored: false } }, { triggerTurn: false });
|
|
852
|
+
ctx.ui.notify(`zagent list: ${agents.length} project-local manifest${agents.length === 1 ? "" : "s"}`, "info");
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
855
|
+
if (action === "show") {
|
|
856
|
+
const id = parts[1];
|
|
857
|
+
if (!id) {
|
|
858
|
+
ctx.ui.notify("Usage: /zagent show <id>", "warning");
|
|
859
|
+
return;
|
|
860
|
+
}
|
|
861
|
+
const loaded = loadZagentManifest(ctx.cwd, id);
|
|
862
|
+
const rooms = normalizeZagentRoomBindings(loaded.manifest.rooms, loaded.manifest.defaultRoom, loaded.manifest.activeRoom);
|
|
863
|
+
pi.appendEntry("zob-zagent", zagentLedgerEntry("show", { id: loaded.manifest.id, status: loaded.errors.length === 0 ? "ok" : "blocked", roomIds: rooms.map((room) => room.id), alias: loaded.manifest.alias, path: loaded.path, promptRef: loaded.manifest.promptRef, errors: loaded.errors }));
|
|
864
|
+
renderHarnessWidget(pi, state, ctx);
|
|
865
|
+
void pi.sendMessage({ customType: "zob-zagent-show", content: formatZagentShow(loaded), display: true, details: { id: loaded.manifest.id, bodyStored: false } }, { triggerTurn: false });
|
|
866
|
+
ctx.ui.notify(`zagent ${loaded.manifest.id}: ${loaded.errors.length === 0 ? "ok" : `blocked (${loaded.errors.length})`}`, loaded.errors.length === 0 ? "info" : "warning");
|
|
867
|
+
return;
|
|
868
|
+
}
|
|
869
|
+
if (action === "use") {
|
|
870
|
+
const id = parts[1];
|
|
871
|
+
if (!id) {
|
|
872
|
+
ctx.ui.notify("Usage: /zagent use <id>", "warning");
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
const loaded = loadZagentManifest(ctx.cwd, id);
|
|
876
|
+
const prompt = readZagentPrompt(ctx.cwd, loaded.manifest.promptRef);
|
|
877
|
+
const rooms = normalizeZagentRoomBindings(loaded.manifest.rooms, loaded.manifest.defaultRoom, loaded.manifest.activeRoom);
|
|
878
|
+
const errors = [...loaded.errors, ...prompt.errors];
|
|
879
|
+
state.zagent = {
|
|
880
|
+
id: loaded.manifest.id,
|
|
881
|
+
team: loaded.manifest.team,
|
|
882
|
+
role: loaded.manifest.role,
|
|
883
|
+
alias: loaded.manifest.alias,
|
|
884
|
+
description: loaded.manifest.description,
|
|
885
|
+
rooms,
|
|
886
|
+
activeRoom: rooms.find((room) => room.active)?.id ?? loaded.manifest.activeRoom ?? loaded.manifest.defaultRoom,
|
|
887
|
+
prompt: prompt.body,
|
|
888
|
+
promptRef: loaded.manifest.promptRef,
|
|
889
|
+
path: loaded.path,
|
|
890
|
+
errors,
|
|
891
|
+
loadedAt: new Date().toISOString(),
|
|
892
|
+
};
|
|
893
|
+
if (errors.length > 0) {
|
|
894
|
+
pi.appendEntry("zob-zagent", zagentLedgerEntry("use_blocked", { id: loaded.manifest.id, teamId: loaded.manifest.team, status: "blocked", roomIds: rooms.map((room) => room.id), alias: loaded.manifest.alias, path: loaded.path, promptRef: loaded.manifest.promptRef, promptBody: prompt.body, errors }));
|
|
895
|
+
renderHarnessWidget(pi, state, ctx);
|
|
896
|
+
ctx.ui.notify(`/zagent use ${id} blocked: ${errors[0]}`, "warning");
|
|
897
|
+
return;
|
|
898
|
+
}
|
|
899
|
+
if (!state.zobLive.peerCard) {
|
|
900
|
+
const peerErrors = ["current session has not registered a local ZPeer endpoint yet"];
|
|
901
|
+
state.zagent.errors = peerErrors;
|
|
902
|
+
pi.appendEntry("zob-zagent", zagentLedgerEntry("use_blocked", { id: loaded.manifest.id, teamId: loaded.manifest.team, status: "blocked", roomIds: rooms.map((room) => room.id), alias: loaded.manifest.alias, path: loaded.path, promptRef: loaded.manifest.promptRef, promptBody: prompt.body, errors: peerErrors }));
|
|
903
|
+
renderHarnessWidget(pi, state, ctx);
|
|
904
|
+
ctx.ui.notify(`/zagent use ${id} loaded manifest/prompt but ZPeer is unavailable: ${peerErrors[0]}`, "warning");
|
|
905
|
+
return;
|
|
906
|
+
}
|
|
907
|
+
const applied = await applyZagentToZpeer(ctx.cwd, state.zobLive.peerCard, loaded.manifest);
|
|
908
|
+
state.zobLive.peerCard = applied.peer;
|
|
909
|
+
if (!applied.ok) {
|
|
910
|
+
state.zagent.errors = [applied.reason];
|
|
911
|
+
pi.appendEntry("zob-zagent", zagentLedgerEntry("use_blocked", { id: loaded.manifest.id, teamId: loaded.manifest.team, status: "blocked", roomIds: rooms.map((room) => room.id), alias: loaded.manifest.alias, path: loaded.path, promptRef: loaded.manifest.promptRef, promptBody: prompt.body, errors: [applied.reason] }));
|
|
912
|
+
renderHarnessWidget(pi, state, ctx);
|
|
913
|
+
ctx.ui.notify(`/zagent use ${id} ZPeer apply blocked: ${applied.reason}`, "warning");
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
916
|
+
state.zobLive.peerCard = refreshZpeerSelf(ctx.cwd, applied.peer);
|
|
917
|
+
state.zobLive.peerCard = {
|
|
918
|
+
...state.zobLive.peerCard,
|
|
919
|
+
team: loaded.manifest.team ?? state.zobLive.peerCard.team,
|
|
920
|
+
roleId: loaded.manifest.id,
|
|
921
|
+
agent: loaded.manifest.id,
|
|
922
|
+
};
|
|
923
|
+
writeZpeerLocalProfileFromPeer(ctx.cwd, state.zobLive.peerCard, zpeerCommandProfileId(ctx));
|
|
924
|
+
pi.appendEntry("zob-zagent", zagentLedgerEntry("use", { id: loaded.manifest.id, teamId: loaded.manifest.team, status: "ok", roomIds: zpeerMembershipsForPeer(state.zobLive.peerCard).map((membership) => membership.roomId), alias: state.zobLive.peerCard.zpeerAlias, path: loaded.path, promptRef: loaded.manifest.promptRef, promptBody: prompt.body }));
|
|
925
|
+
renderHarnessWidget(pi, state, ctx);
|
|
926
|
+
const active = state.zobLive.peerCard.zpeerRoomId ?? state.zobLive.peerCard.zpeerActiveRoomId ?? "default";
|
|
927
|
+
ctx.ui.notify(`zagent ${loaded.manifest.id} loaded; ZPeer @${state.zobLive.peerCard.zpeerAlias ?? "?"} active=${active} rooms=${zpeerMembershipsForPeer(state.zobLive.peerCard).length}; promptHash=${prompt.body ? sha256(prompt.body).slice(0, 12) : "none"}`, "info");
|
|
928
|
+
return;
|
|
929
|
+
}
|
|
930
|
+
ctx.ui.notify("Usage: /zagent list | /zagent show <id> | /zagent use <id>", "warning");
|
|
931
|
+
},
|
|
932
|
+
});
|
|
933
|
+
|
|
934
|
+
pi.registerCommand("zteam", {
|
|
935
|
+
description: "Project-local ZTeams: /zteam list | show <id> | launch-plan <id>",
|
|
936
|
+
getArgumentCompletions: zteamArgumentCompletions,
|
|
937
|
+
handler: async (args, ctx) => {
|
|
938
|
+
const parts = args.trim().split(/\s+/).filter(Boolean);
|
|
939
|
+
const action = (parts[0] ?? "list").toLowerCase();
|
|
940
|
+
if (action === "list") {
|
|
941
|
+
const teams = listZteamManifests(ctx.cwd);
|
|
942
|
+
const roomIds = teams.flatMap((team) => normalizeZagentRoomBindings(team.manifest.rooms, team.manifest.defaultRoom, team.manifest.activeRoom).map((room) => room.id));
|
|
943
|
+
pi.appendEntry("zob-zagent", zagentLedgerEntry("zteam_list", { status: "ok", roomIds }));
|
|
944
|
+
renderHarnessWidget(pi, state, ctx);
|
|
945
|
+
void pi.sendMessage({ customType: "zob-zteam-list", content: formatZteamList(teams), display: true, details: { bodyStored: false } }, { triggerTurn: false });
|
|
946
|
+
ctx.ui.notify(`zteam list: ${teams.length} project-local manifest${teams.length === 1 ? "" : "s"}`, "info");
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
if (action === "show") {
|
|
950
|
+
const id = parts[1];
|
|
951
|
+
if (!id) {
|
|
952
|
+
ctx.ui.notify("Usage: /zteam show <id>", "warning");
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
955
|
+
const loaded = loadZteamManifest(ctx.cwd, id);
|
|
956
|
+
const rooms = normalizeZagentRoomBindings(loaded.manifest.rooms, loaded.manifest.defaultRoom, loaded.manifest.activeRoom);
|
|
957
|
+
pi.appendEntry("zob-zagent", zagentLedgerEntry("zteam_show", { teamId: loaded.manifest.id, status: loaded.errors.length === 0 ? "ok" : "blocked", roomIds: rooms.map((room) => room.id), path: loaded.path, errors: loaded.errors }));
|
|
958
|
+
renderHarnessWidget(pi, state, ctx);
|
|
959
|
+
void pi.sendMessage({ customType: "zob-zteam-show", content: formatZteamShow(loaded), display: true, details: { id: loaded.manifest.id, bodyStored: false } }, { triggerTurn: false });
|
|
960
|
+
ctx.ui.notify(`zteam ${loaded.manifest.id}: ${loaded.errors.length === 0 ? "ok" : `blocked (${loaded.errors.length})`}`, loaded.errors.length === 0 ? "info" : "warning");
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
963
|
+
if (action === "launch-plan") {
|
|
964
|
+
const id = parts[1];
|
|
965
|
+
if (!id) {
|
|
966
|
+
ctx.ui.notify("Usage: /zteam launch-plan <id>", "warning");
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
969
|
+
const loaded = loadZteamManifest(ctx.cwd, id);
|
|
970
|
+
const plan = zteamLaunchPlanText(loaded.manifest);
|
|
971
|
+
pi.appendEntry("zob-zagent", zagentLedgerEntry("zteam_launch_plan", { teamId: loaded.manifest.id, status: loaded.errors.length === 0 ? "ok" : "blocked", roomIds: plan.roomIds, path: loaded.path, errors: loaded.errors }));
|
|
972
|
+
renderHarnessWidget(pi, state, ctx);
|
|
973
|
+
void pi.sendMessage({ customType: "zob-zteam-launch-plan", content: loaded.errors.length ? `${plan.text}\n\nBlocked manifest errors:\n- ${loaded.errors.join("\n- ")}` : plan.text, display: true, details: { id: loaded.manifest.id, agentIdHashes: plan.agentIds.map((agentId) => sha256(agentId)), roomIdHashes: plan.roomIds.map((roomId) => sha256(roomId)), bodyStored: false } }, { triggerTurn: false });
|
|
974
|
+
ctx.ui.notify(`zteam ${loaded.manifest.id} launch-plan printed; spawn count=0; expectedRooms=${plan.roomIds.join(",") || "default"}`, loaded.errors.length === 0 ? "info" : "warning");
|
|
975
|
+
return;
|
|
976
|
+
}
|
|
977
|
+
ctx.ui.notify("Usage: /zteam list | /zteam show <id> | /zteam launch-plan <id>", "warning");
|
|
978
|
+
},
|
|
979
|
+
});
|
|
980
|
+
|
|
648
981
|
pi.registerCommand("zpeer", {
|
|
649
982
|
description: "Room-scoped local peer sessions: /zpeer, /zpeer name <alias>, /zpeer room <roomId>, /zpeer @alias <prompt>",
|
|
650
983
|
handler: async (args, ctx) => {
|
|
@@ -684,27 +1017,28 @@ export function registerHarnessCommands(pi: ExtensionAPI, state: HarnessRuntimeS
|
|
|
684
1017
|
}
|
|
685
1018
|
const parts = trimmed.split(/\s+/);
|
|
686
1019
|
const verb = parts[0]?.toLowerCase();
|
|
1020
|
+
const zpeerProfileId = zpeerCommandProfileId(ctx);
|
|
687
1021
|
if (verb === "name") {
|
|
688
|
-
const result = changeZpeerAlias(ctx.cwd, self, parts[1] ?? "");
|
|
1022
|
+
const result = await changeZpeerAlias(ctx.cwd, self, parts[1] ?? "");
|
|
689
1023
|
if (!result.ok) {
|
|
690
1024
|
ctx.ui.notify(`/zpeer name blocked: ${result.reason}`, "warning");
|
|
691
1025
|
return;
|
|
692
1026
|
}
|
|
693
1027
|
state.zobLive.peerCard = result.peer;
|
|
694
|
-
writeZpeerLocalProfileFromPeer(ctx.cwd, result.peer);
|
|
1028
|
+
writeZpeerLocalProfileFromPeer(ctx.cwd, result.peer, zpeerProfileId);
|
|
695
1029
|
pi.appendEntry("zob-zpeer", { schema: "zob.zpeer-command.v1", action: "name", aliasHash: sha256(result.peer.zpeerAlias ?? ""), roomIdHash: sha256(result.peer.zpeerRoomId ?? "default"), localOnly: true, networkEnabled: false, bodyStored: false, promptBodiesStored: false, outputBodiesStored: false, generatedAt: new Date().toISOString() });
|
|
696
1030
|
renderHarnessWidget(pi, state, ctx);
|
|
697
1031
|
ctx.ui.notify(`zpeer alias set to @${result.peer.zpeerAlias}`, "info");
|
|
698
1032
|
return;
|
|
699
1033
|
}
|
|
700
1034
|
if (verb === "room") {
|
|
701
|
-
const result = changeZpeerRoom(ctx.cwd, self, parts[1] ?? "");
|
|
1035
|
+
const result = await changeZpeerRoom(ctx.cwd, self, parts[1] ?? "");
|
|
702
1036
|
if (!result.ok) {
|
|
703
1037
|
ctx.ui.notify(`/zpeer room blocked: ${result.reason}`, "warning");
|
|
704
1038
|
return;
|
|
705
1039
|
}
|
|
706
1040
|
state.zobLive.peerCard = result.peer;
|
|
707
|
-
writeZpeerLocalProfileFromPeer(ctx.cwd, result.peer);
|
|
1041
|
+
writeZpeerLocalProfileFromPeer(ctx.cwd, result.peer, zpeerProfileId);
|
|
708
1042
|
pi.appendEntry("zob-zpeer", { schema: "zob.zpeer-command.v1", action: "room", aliasHash: sha256(result.peer.zpeerAlias ?? ""), roomIdHash: sha256(result.peer.zpeerRoomId ?? "default"), membershipCount: zpeerMembershipsForPeer(result.peer).length, localOnly: true, networkEnabled: false, bodyStored: false, promptBodiesStored: false, outputBodiesStored: false, generatedAt: new Date().toISOString() });
|
|
709
1043
|
renderHarnessWidget(pi, state, ctx);
|
|
710
1044
|
ctx.ui.notify(`zpeer room set to ${result.peer.zpeerRoomId} as @${result.peer.zpeerAlias}`, "info");
|
|
@@ -718,17 +1052,28 @@ export function registerHarnessCommands(pi: ExtensionAPI, state: HarnessRuntimeS
|
|
|
718
1052
|
ctx.ui.notify(`zpeer active=${self.zpeerRoomId ?? "default"} rooms=${summaries.map((summary) => `${summary.roomId}(${summary.online}/${summary.peerCount})`).join(", ") || "none"}`, "info");
|
|
719
1053
|
return;
|
|
720
1054
|
}
|
|
1055
|
+
if (verb === "clear") {
|
|
1056
|
+
const result = clearZpeerRoom(ctx.cwd, self, parts[1] ?? self.zpeerRoomId ?? "default");
|
|
1057
|
+
if (!result.ok) {
|
|
1058
|
+
ctx.ui.notify(`/zpeer clear blocked: ${result.reason}`, "warning");
|
|
1059
|
+
return;
|
|
1060
|
+
}
|
|
1061
|
+
pi.appendEntry("zob-zpeer", { schema: "zob.zpeer-command.v1", action: "clear", roomIdHash: sha256(result.roomId), clearedCount: result.cleared, preservedSelf: result.preservedSelf, localOnly: true, networkEnabled: false, bodyStored: false, promptBodiesStored: false, outputBodiesStored: false, generatedAt: new Date().toISOString() });
|
|
1062
|
+
renderHarnessWidget(pi, state, ctx);
|
|
1063
|
+
ctx.ui.notify(`zpeer room ${result.roomId} cleared: ${result.cleared} other peer${result.cleared === 1 ? "" : "s"} marked offline/removed; current session preserved`, "info");
|
|
1064
|
+
return;
|
|
1065
|
+
}
|
|
721
1066
|
if (verb === "join") {
|
|
722
1067
|
const asIndex = parts.indexOf("as");
|
|
723
1068
|
const alias = asIndex >= 0 ? parts[asIndex + 1] : undefined;
|
|
724
1069
|
const role = parts.includes("--bridge") ? "bridge" : parts.includes("--observer") ? "observer" : "member";
|
|
725
|
-
const result = joinZpeerRoom(ctx.cwd, self, parts[1] ?? "", alias, role);
|
|
1070
|
+
const result = await joinZpeerRoom(ctx.cwd, self, parts[1] ?? "", alias, role);
|
|
726
1071
|
if (!result.ok) {
|
|
727
1072
|
ctx.ui.notify(`/zpeer join blocked: ${result.reason}`, "warning");
|
|
728
1073
|
return;
|
|
729
1074
|
}
|
|
730
1075
|
state.zobLive.peerCard = result.peer;
|
|
731
|
-
writeZpeerLocalProfileFromPeer(ctx.cwd, result.peer);
|
|
1076
|
+
writeZpeerLocalProfileFromPeer(ctx.cwd, result.peer, zpeerProfileId);
|
|
732
1077
|
pi.appendEntry("zob-zpeer", { schema: "zob.zpeer-command.v1", action: "join", aliasHash: sha256(alias ?? result.peer.zpeerAlias ?? ""), roomIdHash: sha256(parts[1] ?? "default"), membershipCount: zpeerMembershipsForPeer(result.peer).length, localOnly: true, networkEnabled: false, bodyStored: false, promptBodiesStored: false, outputBodiesStored: false, generatedAt: new Date().toISOString() });
|
|
733
1078
|
renderHarnessWidget(pi, state, ctx);
|
|
734
1079
|
ctx.ui.notify(`zpeer joined ${parts[1]} (${role}); active=${result.peer.zpeerRoomId}`, "info");
|
|
@@ -741,7 +1086,7 @@ export function registerHarnessCommands(pi: ExtensionAPI, state: HarnessRuntimeS
|
|
|
741
1086
|
return;
|
|
742
1087
|
}
|
|
743
1088
|
state.zobLive.peerCard = result.peer;
|
|
744
|
-
writeZpeerLocalProfileFromPeer(ctx.cwd, result.peer);
|
|
1089
|
+
writeZpeerLocalProfileFromPeer(ctx.cwd, result.peer, zpeerProfileId);
|
|
745
1090
|
pi.appendEntry("zob-zpeer", { schema: "zob.zpeer-command.v1", action: "use", aliasHash: sha256(result.peer.zpeerAlias ?? ""), roomIdHash: sha256(result.peer.zpeerRoomId ?? "default"), membershipCount: zpeerMembershipsForPeer(result.peer).length, localOnly: true, networkEnabled: false, bodyStored: false, promptBodiesStored: false, outputBodiesStored: false, generatedAt: new Date().toISOString() });
|
|
746
1091
|
renderHarnessWidget(pi, state, ctx);
|
|
747
1092
|
ctx.ui.notify(`zpeer active room set to ${result.peer.zpeerRoomId} as @${result.peer.zpeerAlias}`, "info");
|
|
@@ -754,7 +1099,7 @@ export function registerHarnessCommands(pi: ExtensionAPI, state: HarnessRuntimeS
|
|
|
754
1099
|
return;
|
|
755
1100
|
}
|
|
756
1101
|
state.zobLive.peerCard = result.peer;
|
|
757
|
-
writeZpeerLocalProfileFromPeer(ctx.cwd, result.peer);
|
|
1102
|
+
writeZpeerLocalProfileFromPeer(ctx.cwd, result.peer, zpeerProfileId);
|
|
758
1103
|
pi.appendEntry("zob-zpeer", { schema: "zob.zpeer-command.v1", action: "leave", roomIdHash: sha256(parts[1] ?? "default"), membershipCount: zpeerMembershipsForPeer(result.peer).length, localOnly: true, networkEnabled: false, bodyStored: false, promptBodiesStored: false, outputBodiesStored: false, generatedAt: new Date().toISOString() });
|
|
759
1104
|
renderHarnessWidget(pi, state, ctx);
|
|
760
1105
|
ctx.ui.notify(`zpeer left ${parts[1]}; active=${result.peer.zpeerRoomId}`, "info");
|
|
@@ -818,11 +1163,12 @@ export function registerHarnessCommands(pi: ExtensionAPI, state: HarnessRuntimeS
|
|
|
818
1163
|
ctx.ui.notify(`zpeer ${result.roomId ?? eventRoomId} @${targetAlias} reply · response displayed transiently · outputHash=${result.outputHash ?? "present"}`, "info");
|
|
819
1164
|
} else {
|
|
820
1165
|
const ok = result.status === "reply" || result.status === "completed" || result.status === "waiting" || result.status === "delivered";
|
|
821
|
-
|
|
1166
|
+
const passiveWaitSuffix = result.status === "waiting" ? " · idle/passive wait; no follow-up turn queued" : "";
|
|
1167
|
+
ctx.ui.notify(ok ? `zpeer ${result.roomId ?? eventRoomId} @${targetAlias} ${result.status}${result.outputHash ? ` outputHash=${result.outputHash}` : ""}${passiveWaitSuffix}` : `zpeer ${result.roomId ?? eventRoomId} @${targetAlias} ${result.status}: ${result.reason ?? "see metadata"}`, ok ? "info" : "warning");
|
|
822
1168
|
}
|
|
823
1169
|
return;
|
|
824
1170
|
}
|
|
825
|
-
ctx.ui.notify("Usage: /zpeer | /zpeer rooms | /zpeer join <roomId> [as <alias>] | /zpeer use <roomId> | /zpeer leave <roomId> | /zpeer @alias <prompt> | /zpeer in <roomId> @alias <prompt>", "warning");
|
|
1171
|
+
ctx.ui.notify("Usage: /zpeer | /zpeer rooms | /zpeer clear <roomId> | /zpeer join <roomId> [as <alias>] | /zpeer use <roomId> | /zpeer leave <roomId> | /zpeer @alias <prompt> | /zpeer in <roomId> @alias <prompt>", "warning");
|
|
826
1172
|
},
|
|
827
1173
|
});
|
|
828
1174
|
|
|
@@ -62,6 +62,7 @@ export class DelegationOverlayComponent implements Component {
|
|
|
62
62
|
private listScroll = 0;
|
|
63
63
|
private logScroll = 0;
|
|
64
64
|
private sortIndex = 0;
|
|
65
|
+
private activePane: "list" | "feed" = "list";
|
|
65
66
|
private cachedTranscriptKey?: string;
|
|
66
67
|
private cachedTranscriptLines: string[] = [];
|
|
67
68
|
private followTail = true;
|
|
@@ -151,12 +152,24 @@ export class DelegationOverlayComponent implements Component {
|
|
|
151
152
|
this.helpVisible = !this.helpVisible;
|
|
152
153
|
return;
|
|
153
154
|
}
|
|
155
|
+
if (matchesKey(data, "left")) {
|
|
156
|
+
this.activePane = "list";
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
if (matchesKey(data, "right")) {
|
|
160
|
+
this.activePane = "feed";
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
154
163
|
|
|
155
164
|
const rows = this.rows();
|
|
156
165
|
const selectable = rows.filter((row) => row.kind === "run" && row.run);
|
|
157
166
|
const currentIndex = Math.max(0, selectable.findIndex((row) => row.id === this.selectedRunId));
|
|
158
167
|
|
|
159
168
|
if (matchesKey(data, "up")) {
|
|
169
|
+
if (this.activePane === "feed") {
|
|
170
|
+
this.scrollTranscript("up");
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
160
173
|
if (selectable.length === 0) return;
|
|
161
174
|
const next = selectable[Math.max(0, currentIndex - 1)];
|
|
162
175
|
this.selectedRunId = next?.id;
|
|
@@ -165,6 +178,10 @@ export class DelegationOverlayComponent implements Component {
|
|
|
165
178
|
this.ensureSelectionOnNextRender = true;
|
|
166
179
|
this.ensureSelectedVisible(rows);
|
|
167
180
|
} else if (matchesKey(data, "down")) {
|
|
181
|
+
if (this.activePane === "feed") {
|
|
182
|
+
this.scrollTranscript("down");
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
168
185
|
if (selectable.length === 0) return;
|
|
169
186
|
const next = selectable[Math.min(selectable.length - 1, currentIndex + 1)];
|
|
170
187
|
this.selectedRunId = next?.id;
|
|
@@ -227,12 +244,13 @@ export class DelegationOverlayComponent implements Component {
|
|
|
227
244
|
const rightRule = "─".repeat(Math.max(0, inner - titleWidth - leftRule.length));
|
|
228
245
|
const lines: string[] = [th.fg("border", `╭${leftRule}`) + th.fg("accent", title) + th.fg("border", `${rightRule}╮`)];
|
|
229
246
|
|
|
230
|
-
const headerLeft = `${th.fg("accent", "Agents")} ${th.fg("muted", delegateCloseButton())}`;
|
|
247
|
+
const headerLeft = `${th.fg(this.activePane === "list" ? "accent" : "muted", this.activePane === "list" ? "▶ Agents" : " Agents")} ${th.fg("muted", delegateCloseButton())}`;
|
|
231
248
|
const selectedBadge = delegationSignalBadge(selected);
|
|
232
249
|
const selectedBadgeText = formatDelegationSignalBadge(selectedBadge);
|
|
233
|
-
const
|
|
250
|
+
const selectedTitle = selected
|
|
234
251
|
? `${th.fg(statusColor(selected.status), `${statusIcon(selected.status)} ${selected.agent}`)}${selectedBadgeText ? ` ${th.fg(delegationSignalColor(selectedBadge), selectedBadgeText)}` : ""}${formatDelegationModelLabel(selected) ? ` ${th.fg("muted", `(${formatDelegationModelLabel(selected)})`)}` : ""} ${th.fg("dim", formatDuration(delegationDurationMs(selected)))} ${th.fg("accent", formatDelegationCostLabel(selected))} ${th.fg("muted", formatDelegationContextLabel(selected))}`
|
|
235
252
|
: th.fg("warning", "No delegation selected");
|
|
253
|
+
const headerRight = `${th.fg(this.activePane === "feed" ? "accent" : "muted", this.activePane === "feed" ? "▶ Feed" : " Feed")} ${selectedTitle}`;
|
|
236
254
|
lines.push(this.row(padToWidth(headerLeft, listWidth) + th.fg("dim", "│") + padToWidth(headerRight, logWidth), inner));
|
|
237
255
|
lines.push(th.fg("border", `├${"─".repeat(listWidth)}┼${"─".repeat(logWidth)}┤`));
|
|
238
256
|
|
|
@@ -249,10 +267,10 @@ export class DelegationOverlayComponent implements Component {
|
|
|
249
267
|
|
|
250
268
|
const filterInfo = this.filter || this.filterEditing ? ` · filter=${this.filterEditing ? ">" : ""}${this.filter || "<type>"}` : "";
|
|
251
269
|
const helpInfo = this.helpVisible
|
|
252
|
-
? " · help: / filter · ? hide help · Esc clears filter/closes · ↑↓ select · [] list · PgUp/PgDn feed · s sort · r refresh"
|
|
270
|
+
? " · help: / filter · ? hide help · Esc clears filter/closes · ←→ focus list/feed · ↑↓ select/scroll · [] list · PgUp/PgDn feed · s sort · r refresh"
|
|
253
271
|
: " · / filter · ? help";
|
|
254
272
|
const noMatchInfo = noMatch ? ` · ${noMatch}` : "";
|
|
255
|
-
const scrollInfo = `${rows.length} rows${filterInfo}${noMatchInfo} · list ${Math.min(this.listScroll + 1, rows.length || 1)}/${Math.max(1, rows.length)} · feed ${Math.min(this.logScroll + 1, transcript.length || 1)}/${Math.max(1, transcript.length)} · wheel over list/feed · click agent · [close]/Esc · ↑↓ select · [] list · PgUp/PgDn feed · End live-tail · s sort · r refresh${helpInfo}`;
|
|
273
|
+
const scrollInfo = `${rows.length} rows${filterInfo}${noMatchInfo} · focus ${this.activePane} · list ${Math.min(this.listScroll + 1, rows.length || 1)}/${Math.max(1, rows.length)} · feed ${Math.min(this.logScroll + 1, transcript.length || 1)}/${Math.max(1, transcript.length)} · wheel over list/feed · click agent · [close]/Esc · ←→ focus · ↑↓ select/scroll · [] list · PgUp/PgDn feed · End live-tail · s sort · r refresh${helpInfo}`;
|
|
256
274
|
lines.push(th.fg("border", `├${"─".repeat(inner)}┤`));
|
|
257
275
|
lines.push(this.row(th.fg("dim", truncateToWidth(scrollInfo, inner, "…")), inner));
|
|
258
276
|
lines.push(th.fg("border", `╰${"─".repeat(inner)}╯`));
|