zob-harness 0.9.0 → 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/capabilities/zob-public-runtime-capabilities.json +16 -17
- package/.pi/extensions/zob-harness/index.ts +1 -1
- package/.pi/extensions/zob-harness/src/domains/autonomy/autonomous-runtime/dry-run.ts +1107 -0
- package/.pi/extensions/zob-harness/src/domains/autonomy/autonomous-runtime/report-writers.ts +325 -0
- package/.pi/extensions/zob-harness/src/domains/autonomy/autonomous-runtime/smoke-run.ts +1286 -0
- package/.pi/extensions/zob-harness/src/domains/autonomy/autonomous-runtime/types.ts +30 -0
- package/.pi/extensions/zob-harness/src/domains/autonomy/autonomous-runtime/validation.ts +184 -0
- package/.pi/extensions/zob-harness/src/domains/autonomy/autonomous-runtime.ts +4 -2912
- package/.pi/extensions/zob-harness/src/domains/compute/compute-profile.ts +2 -1
- 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/domains/goal/goal-todos/constants.ts +19 -0
- package/.pi/extensions/zob-harness/src/domains/goal/goal-todos/formatting.ts +148 -0
- package/.pi/extensions/zob-harness/src/domains/goal/goal-todos/normalize.ts +476 -0
- package/.pi/extensions/zob-harness/src/domains/goal/goal-todos/operations.ts +393 -0
- package/.pi/extensions/zob-harness/src/domains/goal/goal-todos/parsing.ts +277 -0
- package/.pi/extensions/zob-harness/src/domains/goal/goal-todos/reducer.ts +110 -0
- package/.pi/extensions/zob-harness/src/domains/goal/goal-todos.ts +6 -1429
- package/.pi/extensions/zob-harness/src/domains/governance/governed-requests.ts +3 -1
- package/.pi/extensions/zob-harness/src/domains/governance/merge-queue.ts +3 -1
- package/.pi/extensions/zob-harness/src/domains/governance/sandbox/helpers.ts +124 -0
- package/.pi/extensions/zob-harness/src/domains/governance/sandbox/runners.ts +444 -0
- package/.pi/extensions/zob-harness/src/domains/governance/sandbox/simulation.ts +569 -0
- package/.pi/extensions/zob-harness/src/domains/governance/sandbox/types.ts +127 -0
- package/.pi/extensions/zob-harness/src/domains/governance/sandbox/validation.ts +273 -0
- package/.pi/extensions/zob-harness/src/domains/governance/sandbox.ts +4 -1508
- package/.pi/extensions/zob-harness/src/domains/governance/worker-pool.ts +3 -1
- package/.pi/extensions/zob-harness/src/domains/governance/workspace-claims.ts +3 -1
- package/.pi/extensions/zob-harness/src/domains/orchestration/room.ts +8 -2
- package/.pi/extensions/zob-harness/src/domains/promotion/coms.ts +8 -1
- package/.pi/extensions/zob-harness/src/runtime/commands/autonomy.ts +188 -0
- package/.pi/extensions/zob-harness/src/runtime/commands/compute.ts +165 -0
- package/.pi/extensions/zob-harness/src/runtime/commands/daemon.ts +191 -0
- package/.pi/extensions/zob-harness/src/runtime/commands/delegates.ts +47 -0
- package/.pi/extensions/zob-harness/src/runtime/commands/goal.ts +70 -0
- package/.pi/extensions/zob-harness/src/runtime/commands/intent.ts +383 -0
- package/.pi/extensions/zob-harness/src/runtime/commands/misc.ts +229 -0
- package/.pi/extensions/zob-harness/src/runtime/commands/project-dna.ts +130 -0
- package/.pi/extensions/zob-harness/src/runtime/commands/types.ts +3 -0
- package/.pi/extensions/zob-harness/src/runtime/commands/zcommit.ts +145 -0
- package/.pi/extensions/zob-harness/src/runtime/commands/zlive.ts +1606 -0
- package/.pi/extensions/zob-harness/src/runtime/commands/zmode.ts +42 -0
- package/.pi/extensions/zob-harness/src/runtime/commands.ts +26 -3109
- package/.pi/extensions/zob-harness/src/runtime/events.ts +67 -33
- package/.pi/extensions/zob-harness/src/runtime/goal-runtime/commands.ts +194 -0
- package/.pi/extensions/zob-harness/src/runtime/goal-runtime/events.ts +81 -0
- package/.pi/extensions/zob-harness/src/runtime/goal-runtime/state.ts +662 -0
- package/.pi/extensions/zob-harness/src/runtime/goal-runtime/tools.ts +1005 -0
- package/.pi/extensions/zob-harness/src/runtime/goal-runtime.ts +5 -1949
- package/.pi/extensions/zob-harness/src/runtime/tools-delegation/helpers.ts +786 -0
- package/.pi/extensions/zob-harness/src/runtime/tools-delegation/register.ts +1120 -0
- package/.pi/extensions/zob-harness/src/runtime/tools-delegation/types.ts +77 -0
- package/.pi/extensions/zob-harness/src/runtime/tools-delegation.ts +1 -1904
- package/.pi/extensions/zob-harness/src/runtime/zob-intro.ts +46 -15
- package/.pi/factories/project-dna/batch-manifest.json +1 -1
- package/.pi/factories/project-dna/pilot-manifest.json +1 -1
- package/.pi/factories/project-dna/smoke-manifest.json +1 -1
- package/README.md +29 -8
- package/package.json +14 -5
- package/scripts/git-ops/commit-policy-smoke.mjs +33 -6
- package/scripts/goal-todo/child-goal-ref-smoke.mjs +30 -3
- package/scripts/goal-todo/handoff-static-smoke.mjs +31 -3
- package/scripts/harness-intake/lib/cli-io.mjs +89 -0
- package/scripts/harness-intake/lib/constants.mjs +59 -0
- package/scripts/harness-intake/lib/infer-spec.mjs +127 -0
- package/scripts/harness-intake/lib/profiles.mjs +458 -0
- package/scripts/harness-intake/lib/run-init.mjs +307 -0
- package/scripts/harness-intake/lib/scan.mjs +266 -0
- package/scripts/harness-intake/lib/tmux.mjs +92 -0
- package/scripts/harness-intake/lib/validate.mjs +152 -0
- package/scripts/harness-intake/lib.mjs +8 -1521
- package/scripts/harness-switch/static-smoke.mjs +1 -1
- package/scripts/model-catalog/validate-economy.mjs +3 -1
- package/scripts/model-catalog/validate.mjs +3 -1
- package/scripts/project-dna/scan/scan.mjs +5 -2
- package/scripts/project-dna/scan/validate-scan-artifacts.mjs +3 -1
- package/scripts/project-dna/validation/validate-ontology.mjs +3 -1
- package/scripts/project-dna/validation/validate-scaffold.mjs +2 -2
- package/scripts/zagent-static-smoke.mjs +30 -2
- package/scripts/zpeer-local-e2e-smoke.mjs +18 -0
- package/scripts/zpeer-static-smoke.mjs +40 -5
- package/scripts/zteam-hot-add/smoke.mjs +30 -2
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Dirent } from "node:fs";
|
|
1
2
|
import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
2
3
|
import { basename, extname, join, relative, resolve } from "node:path";
|
|
3
4
|
|
|
@@ -261,7 +262,7 @@ function collectTargetStats(repoRoot: string, targetPath: string | undefined): T
|
|
|
261
262
|
stats.maxFilesReached = true;
|
|
262
263
|
return;
|
|
263
264
|
}
|
|
264
|
-
let entries;
|
|
265
|
+
let entries: Dirent[];
|
|
265
266
|
try {
|
|
266
267
|
entries = readdirSync(absolutePath, { withFileTypes: true });
|
|
267
268
|
} catch (error) {
|
|
@@ -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);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { GoalTodoChildGoalStatus, GoalTodoClaimTargetReadiness, GoalTodoClaimValidationConfidence, GoalTodoClaimValidationRecommendedAction, GoalTodoClaimValidationStatus, GoalTodoClaimValidationVerdict, GoalTodoDelegationStatus, GoalTodoOwner, GoalTodoPriority, GoalTodoStatus, GoalTodoStatusClaim } from "../goal-todo-types.js";
|
|
2
|
+
|
|
3
|
+
export const ZOB_GOAL_TODO_ENTRY_TYPE = "zob-goal-todo";
|
|
4
|
+
|
|
5
|
+
export const OPEN_REQUIRED_STATUSES = new Set<GoalTodoStatus>(["planned", "ready", "in_progress", "delegated", "claim_returned", "needs_review", "needs_oracle", "needs_user", "blocked"]);
|
|
6
|
+
export const ACTIVE_STATUSES = new Set<GoalTodoStatus>(["ready", "in_progress", "delegated", "claim_returned", "needs_review"]);
|
|
7
|
+
export const ACTIONABLE_STATUSES = new Set<GoalTodoStatus>(["planned", "ready", "in_progress", "needs_review", "needs_user", "needs_oracle", "blocked"]);
|
|
8
|
+
export const VALID_STATUS: readonly GoalTodoStatus[] = ["planned", "ready", "in_progress", "delegated", "claim_returned", "needs_review", "needs_oracle", "needs_user", "blocked", "done", "skipped"];
|
|
9
|
+
export const VALID_OWNER: readonly GoalTodoOwner[] = ["agent", "user", "oracle", "subagent", "factory", "orchestration"];
|
|
10
|
+
export const VALID_PRIORITY: readonly GoalTodoPriority[] = ["low", "normal", "high", "critical"];
|
|
11
|
+
export const VALID_DELEGATION_STATUS: readonly GoalTodoDelegationStatus[] = ["queued", "running", "claim_returned", "accepted", "rejected", "failed"];
|
|
12
|
+
export const VALID_CHILD_GOAL_STATUS: readonly GoalTodoChildGoalStatus[] = ["ready_for_oracle", "incomplete", "blocked"];
|
|
13
|
+
export const VALID_STATUS_CLAIM: readonly GoalTodoStatusClaim[] = ["done", "incomplete", "blocked"];
|
|
14
|
+
export const VALID_TARGET_READINESS: readonly GoalTodoClaimTargetReadiness[] = ["ready_for_parent_acceptance", "needs_parent_review", "blocked"];
|
|
15
|
+
export const VALID_VALIDATION_STATUS: readonly GoalTodoClaimValidationStatus[] = ["queued", "running", "passed", "warn", "failed", "blocked"];
|
|
16
|
+
export const VALID_VALIDATION_VERDICT: readonly GoalTodoClaimValidationVerdict[] = ["PASS", "WARN", "FAIL"];
|
|
17
|
+
export const VALID_VALIDATION_ACTION: readonly GoalTodoClaimValidationRecommendedAction[] = ["accept_claim", "needs_review", "reject_claim", "block"];
|
|
18
|
+
export const VALID_VALIDATION_CONFIDENCE: readonly GoalTodoClaimValidationConfidence[] = ["LOW", "MEDIUM", "HIGH"];
|
|
19
|
+
export const SHA256_HEX = /^[a-f0-9]{64}$/i;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import type { GoalTodoCompletionDiagnostics, GoalTodoNode, GoalTodoState, GoalTodoSummary } from "../goal-todo-types.js";
|
|
2
|
+
import { ACTIONABLE_STATUSES, ACTIVE_STATUSES, OPEN_REQUIRED_STATUSES } from "./constants.js";
|
|
3
|
+
import { cloneNode } from "./normalize.js";
|
|
4
|
+
import { childrenOf, nextValidGoalTodoActions, validateGoalTodoGraph } from "./operations.js";
|
|
5
|
+
import { goalNodes } from "./reducer.js";
|
|
6
|
+
|
|
7
|
+
export function summarizeGoalTodos(todoState: GoalTodoState, goalId?: string): GoalTodoSummary {
|
|
8
|
+
const nodes = goalId ? goalNodes(todoState, goalId) : todoState.nodes.map(cloneNode);
|
|
9
|
+
const done = nodes.filter((node) => node.status === "done").length;
|
|
10
|
+
const skipped = nodes.filter((node) => node.status === "skipped").length;
|
|
11
|
+
const open = nodes.filter((node) => OPEN_REQUIRED_STATUSES.has(node.status)).length;
|
|
12
|
+
const active = nodes.filter((node) => ACTIVE_STATUSES.has(node.status)).length;
|
|
13
|
+
const blocked = nodes.filter((node) => node.status === "blocked").length;
|
|
14
|
+
const delegated = nodes.filter((node) => node.status === "delegated" || node.delegation?.status === "running" || node.delegation?.status === "queued").length;
|
|
15
|
+
const claimReturned = nodes.filter((node) => node.status === "claim_returned").length;
|
|
16
|
+
const validationQueued = nodes.filter((node) => node.validation?.status === "queued").length;
|
|
17
|
+
const validationRunning = nodes.filter((node) => node.validation?.status === "running").length;
|
|
18
|
+
const validationPassed = nodes.filter((node) => node.validation?.status === "passed").length;
|
|
19
|
+
const validationFailed = nodes.filter((node) => node.validation?.status === "failed" || node.validation?.status === "blocked" || node.validation?.status === "warn").length;
|
|
20
|
+
const needsUser = nodes.filter((node) => node.status === "needs_user").length;
|
|
21
|
+
const needsOracle = nodes.filter((node) => node.status === "needs_oracle").length;
|
|
22
|
+
const nextAgent = nodes.find((node) => node.owner === "agent" && (node.status === "ready" || node.status === "planned" || node.status === "in_progress"));
|
|
23
|
+
const nextUser = nodes.find((node) => ACTIONABLE_STATUSES.has(node.status) && (node.owner === "user" || node.status === "needs_user"));
|
|
24
|
+
return {
|
|
25
|
+
goalId,
|
|
26
|
+
total: nodes.length,
|
|
27
|
+
required: nodes.filter((node) => node.required).length,
|
|
28
|
+
done,
|
|
29
|
+
skipped,
|
|
30
|
+
open,
|
|
31
|
+
active,
|
|
32
|
+
blocked,
|
|
33
|
+
delegated,
|
|
34
|
+
claimReturned,
|
|
35
|
+
validationQueued,
|
|
36
|
+
validationRunning,
|
|
37
|
+
validationPassed,
|
|
38
|
+
validationFailed,
|
|
39
|
+
needsUser,
|
|
40
|
+
needsOracle,
|
|
41
|
+
nextAgent,
|
|
42
|
+
nextUser,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function formatGoalTodoSummary(summary: GoalTodoSummary): string {
|
|
47
|
+
if (summary.total === 0) return "todos unset";
|
|
48
|
+
const closed = summary.done + summary.skipped;
|
|
49
|
+
const validation = summary.validationQueued + summary.validationRunning > 0 ? ` · validation ${summary.validationQueued + summary.validationRunning}` : summary.validationFailed > 0 ? ` · validation_alerts ${summary.validationFailed}` : "";
|
|
50
|
+
return `todos ${closed}/${summary.total} · open ${summary.open} · active ${summary.active} · blocked ${summary.blocked} · delegated ${summary.delegated} · claims ${summary.claimReturned}${validation}`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function formatGoalTodoHudLine(todoState: GoalTodoState, goalId: string | undefined): string {
|
|
54
|
+
if (!goalId) return "todos unset";
|
|
55
|
+
const summary = summarizeGoalTodos(todoState, goalId);
|
|
56
|
+
const next = summary.nextAgent ? ` · next agent ${summary.nextAgent.path} ${summary.nextAgent.title}` : summary.nextUser ? ` · next user ${summary.nextUser.path} ${summary.nextUser.title}` : "";
|
|
57
|
+
return `${formatGoalTodoSummary(summary)}${next}`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function formatGoalTodoPromptHint(todoState: GoalTodoState, goalId: string | undefined): string {
|
|
61
|
+
if (!goalId) return "- goal_todos: no active runtime goal";
|
|
62
|
+
const summary = summarizeGoalTodos(todoState, goalId);
|
|
63
|
+
if (summary.total === 0) return "- goal_todos: none set; create TODOs for long, multi-step goal work when appropriate";
|
|
64
|
+
return [
|
|
65
|
+
`- goal_todos: ${formatGoalTodoSummary(summary)}`,
|
|
66
|
+
summary.nextAgent ? `- next_agent_todo: ${summary.nextAgent.path} ${summary.nextAgent.title}` : undefined,
|
|
67
|
+
summary.nextUser ? `- next_user_todo: ${summary.nextUser.path} ${summary.nextUser.title}` : undefined,
|
|
68
|
+
summary.claimReturned > 0 ? "- delegated_claims: claim_returned TODOs require parent evidence checks or agentic validation; use resolve_goal_todo(action=auto|complete|accept_claim), complete_goal_todo, /goal todo done, or accept_goal_todo_claim to accept returned claims" : undefined,
|
|
69
|
+
summary.validationQueued + summary.validationRunning > 0 ? `- claim_validation: ${summary.validationQueued + summary.validationRunning} oracle validation(s) queued/running; auto-accept only after PASS/no_ship=false` : undefined,
|
|
70
|
+
"- completion rule: use resolve_goal_todo for done/skip/claim/block/reopen transitions; required TODOs must be done/skipped with evidence before propose_goal_completion",
|
|
71
|
+
].filter((line): line is string => typeof line === "string").join("\n");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function formatGoalTodoTree(todoState: GoalTodoState, goalId: string | undefined): string {
|
|
75
|
+
if (!goalId) return "No active runtime goal; goal TODOs require a goalId.";
|
|
76
|
+
const nodes = goalNodes(todoState, goalId);
|
|
77
|
+
if (nodes.length === 0) return "No goal TODOs set. Use /goal todo add <title>.";
|
|
78
|
+
const icon = (node: GoalTodoNode): string => {
|
|
79
|
+
if (node.status === "done") return "✓";
|
|
80
|
+
if (node.status === "skipped") return "↷";
|
|
81
|
+
if (node.status === "blocked") return "▲";
|
|
82
|
+
if (node.status === "delegated") return "⇄";
|
|
83
|
+
if (node.status === "claim_returned") return "◇";
|
|
84
|
+
if (node.status === "needs_user") return "?";
|
|
85
|
+
if (node.status === "needs_oracle") return "◆";
|
|
86
|
+
if (node.status === "in_progress") return "●";
|
|
87
|
+
return "○";
|
|
88
|
+
};
|
|
89
|
+
const diagnostics = goalTodoCompletionDiagnostics(todoState, goalId);
|
|
90
|
+
const lines = [formatGoalTodoSummary(summarizeGoalTodos(todoState, goalId)), formatGoalTodoDiagnostics(diagnostics)];
|
|
91
|
+
const walk = (parentId: string | undefined, indent = ""): void => {
|
|
92
|
+
const children = childrenOf(todoState, goalId, parentId);
|
|
93
|
+
children.forEach((node, index) => {
|
|
94
|
+
const last = index === children.length - 1;
|
|
95
|
+
const branch = indent ? (last ? "└─" : "├─") : "";
|
|
96
|
+
const required = node.required ? "req" : "opt";
|
|
97
|
+
const delegation = node.delegation?.runId ? ` · run ${node.delegation.runId}` : "";
|
|
98
|
+
const claim = node.claim?.claimHash ? ` · claim ${node.claim.claimHash.slice(0, 12)}` : "";
|
|
99
|
+
const validation = node.validation ? ` · validation ${node.validation.status}${node.validation.verdict ? `/${node.validation.verdict}` : ""}${node.validation.runId ? ` ${node.validation.runId}` : ""}` : "";
|
|
100
|
+
const blocker = node.blocker ? ` · blocker ${node.blocker}` : "";
|
|
101
|
+
lines.push(`${indent}${branch}${icon(node)} ${node.path} ${node.title} [${node.status}/${node.owner}/${required}/${node.priority}] · id ${node.id}${delegation}${claim}${validation}${blocker}`);
|
|
102
|
+
walk(node.id, `${indent}${last ? " " : "│ "}`);
|
|
103
|
+
});
|
|
104
|
+
};
|
|
105
|
+
walk(undefined);
|
|
106
|
+
return lines.join("\n");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function goalTodoCompletionBlockers(todoState: GoalTodoState, goalId: string | undefined): string[] {
|
|
110
|
+
if (!goalId) return [];
|
|
111
|
+
const nodes = goalNodes(todoState, goalId);
|
|
112
|
+
if (nodes.length === 0) return [];
|
|
113
|
+
const blockers: string[] = [];
|
|
114
|
+
for (const node of nodes) {
|
|
115
|
+
if (node.required && OPEN_REQUIRED_STATUSES.has(node.status)) blockers.push(`todo ${node.path} '${node.title}' is required and ${node.status}`);
|
|
116
|
+
if (node.required && node.status === "skipped" && !node.skipReason?.trim()) blockers.push(`todo ${node.path} '${node.title}' skipped without explicit reason`);
|
|
117
|
+
const evidenceRequired = todoState.policy.requireEvidenceForCritical && (node.priority === "critical" || Boolean(node.delegation) || node.owner === "factory" || node.owner === "orchestration");
|
|
118
|
+
if (node.status === "done" && evidenceRequired && node.evidenceRefs.length === 0 && node.validationCommands.length === 0 && !node.artifacts?.outputHash) blockers.push(`todo ${node.path} '${node.title}' done without evidence`);
|
|
119
|
+
if (node.status === "skipped" && evidenceRequired && node.evidenceRefs.length === 0 && node.validationCommands.length === 0 && !node.artifacts?.outputHash) blockers.push(`todo ${node.path} '${node.title}' skipped without evidence`);
|
|
120
|
+
if (node.status === "done" && node.delegation && node.delegation.status !== "accepted") blockers.push(`todo ${node.path} '${node.title}' delegated claim is not parent-accepted`);
|
|
121
|
+
if (node.status === "done") {
|
|
122
|
+
const openChildren = nodes.filter((candidate) => candidate.parentId === node.id && candidate.required && OPEN_REQUIRED_STATUSES.has(candidate.status));
|
|
123
|
+
if (openChildren.length > 0) blockers.push(`todo ${node.path} '${node.title}' is done but has open required child TODOs`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
blockers.push(...validateGoalTodoGraph(todoState, goalId));
|
|
127
|
+
return blockers;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function goalTodoCompletionDiagnostics(todoState: GoalTodoState, goalId: string | undefined): GoalTodoCompletionDiagnostics {
|
|
131
|
+
const nodes = goalId ? goalNodes(todoState, goalId) : [];
|
|
132
|
+
const completionBlockers = goalTodoCompletionBlockers(todoState, goalId);
|
|
133
|
+
const reviewNoShip = nodes.some((node) => node.reviewNoShip === true);
|
|
134
|
+
const hardNoShip = completionBlockers.length > 0;
|
|
135
|
+
return {
|
|
136
|
+
completionReady: completionBlockers.length === 0 && !reviewNoShip,
|
|
137
|
+
hardNoShip,
|
|
138
|
+
reviewNoShip,
|
|
139
|
+
effectiveNoShip: hardNoShip || reviewNoShip,
|
|
140
|
+
completionBlockers,
|
|
141
|
+
nextValidActions: Object.fromEntries(nodes.map((node) => [node.id, nextValidGoalTodoActions(node)])),
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function formatGoalTodoDiagnostics(diagnostics: GoalTodoCompletionDiagnostics): string {
|
|
146
|
+
const blockers = diagnostics.completionBlockers.slice(0, 3).join(" | ") || "none";
|
|
147
|
+
return `completion_ready=${diagnostics.completionReady} · hard_no_ship=${diagnostics.hardNoShip} · review_no_ship=${diagnostics.reviewNoShip} · effective_no_ship=${diagnostics.effectiveNoShip} · completion_blockers=${blockers}`;
|
|
148
|
+
}
|