import { activeShellTransitionIds } from "./refinement.ts"; import { snapshotHash } from "./snapshot.ts"; import type { Arc, AttentionFuture, AttentionView, ExecutionPosition, PlanCommitment, Transition, WorkflowState, } from "./types.ts"; function activeLeaf(state: WorkflowState): Transition | undefined { return state.reservation ? state.transitions[state.reservation.transitionId] : undefined; } function outgoing(state: WorkflowState, fromId: string, kind?: Arc["kind"]): Arc[] { return Object.values(state.arcs) .filter((arc) => arc.fromId === fromId && (kind === undefined || arc.kind === kind)) .sort((a, b) => a.id.localeCompare(b.id)); } function readConditionsSatisfied(state: WorkflowState, transition: Transition): boolean { const reads = Object.values(state.arcs).filter((arc) => arc.toId === transition.id && arc.kind === "read"); return reads.every((arc) => { const place = state.places[arc.fromId]; if (!place) return false; return place.type === "Gate" ? place.gateState === "open" : place.state === "satisfied"; }); } function transitionsAfterPlace(state: WorkflowState, placeId: string, enabledOnly = true): Transition[] { return outgoing(state, placeId) .filter((arc) => arc.kind === "flow") .map((arc) => state.transitions[arc.toId]) .filter( (transition): transition is Transition => transition !== undefined && transition.status === "planned" && (!enabledOnly || readConditionsSatisfied(state, transition)), ); } const COMMITMENT_RANK: Record = { committed: 0, provisional: 1, directional: 2, }; function commitmentLabel(transition: Transition): string { switch (transition.planning.commitment) { case "committed": return `◆ ${transition.intent}`; case "provisional": return `⧖ ${transition.intent} [暂定|随当前结果调整]`; case "directional": return `◇ ${transition.intent} [方向|尚未展开]`; } } function unfoldedOutcomePlace( state: WorkflowState, placeId: string, outcome: "success" | "failure", ): string { let current = placeId; const seen = new Set(); while (true) { const refinement = Object.values(state.refinements).find( (candidate) => candidate.status === "active" && (outcome === "success" ? candidate.successExitPlaceId === current : candidate.failureExitPlaceId === current), ); if (!refinement || seen.has(refinement.id)) return current; seen.add(refinement.id); const parentOutput = outgoing(state, refinement.parentTransitionId, outcome)[0]; if (!parentOutput) return current; current = parentOutput.toId; } } function evidenceRelated(current: Transition | undefined, candidate: Transition): boolean { if (!current) return false; const selectors = new Set( [...current.contract.evidenceSelectors, ...current.contract.expectedEvidence].map((value) => value.trim().toLowerCase()), ); return ( candidate.planning.dependsOn.some( (dependency) => (dependency.kind === "evidence_selector" && selectors.has(dependency.selector.trim().toLowerCase())) || (dependency.kind === "transition_outcome" && dependency.transitionId === current.id), ) || candidate.contract.evidenceSelectors.some((selector) => selectors.has(selector.trim().toLowerCase())) ); } function futurePriority( current: Transition | undefined, candidate: { transition: Transition; distance: number; origin: "success" | "failure" | "current" }, ): number { const planning = candidate.transition.planning; if (planning.commitment === "committed" && candidate.distance === 0) return 0; if (planning.commitment === "provisional" && evidenceRelated(current, candidate.transition)) return 1; if (candidate.origin === "failure" || candidate.transition.type === "Recover") return 2; if (planning.commitment === "directional" && planning.source === "human") return 3; return 4 + COMMITMENT_RANK[planning.commitment]; } function futureTransitions(state: WorkflowState, transition: Transition | undefined): AttentionFuture[] { const queue: Array<{ placeId: string; distance: number; origin: "success" | "failure" | "current" }> = transition ? outgoing(state, transition.id) .filter((arc) => arc.kind === "success" || arc.kind === "failure") .map((arc) => ({ placeId: unfoldedOutcomePlace(state, arc.toId, arc.kind as "success" | "failure"), distance: 0, origin: arc.kind as "success" | "failure", })) : [{ placeId: state.token.placeId, distance: 0, origin: "current" }]; const seenPlaces = new Set(); const seenTransitions = new Set(); const candidates: Array<{ transition: Transition; distance: number; origin: "success" | "failure" | "current"; }> = []; while (queue.length > 0 && candidates.length < 16) { const current = queue.shift()!; const placeKey = `${current.origin}:${current.placeId}`; if (seenPlaces.has(placeKey) || current.distance > 6) continue; seenPlaces.add(placeKey); for (const candidate of transitionsAfterPlace(state, current.placeId, false)) { if (seenTransitions.has(candidate.id)) continue; seenTransitions.add(candidate.id); candidates.push({ transition: candidate, distance: current.distance, origin: current.origin }); for (const arc of outgoing(state, candidate.id).filter( (item) => item.kind === "success" || item.kind === "failure", )) { queue.push({ placeId: arc.toId, distance: current.distance + 1, origin: current.origin }); } } } return candidates .sort( (a, b) => futurePriority(transition, a) - futurePriority(transition, b) || a.distance - b.distance || COMMITMENT_RANK[a.transition.planning.commitment] - COMMITMENT_RANK[b.transition.planning.commitment] || a.transition.id.localeCompare(b.transition.id), ) .slice(0, 8) .map(({ transition: candidate }) => ({ transitionId: candidate.id, intent: candidate.intent, commitment: candidate.planning.commitment, label: commitmentLabel(candidate), })); } function watchItems(state: WorkflowState, transition: Transition | undefined, future: AttentionFuture[]): string[] { const items: string[] = []; for (const place of Object.values(state.places)) { if (place.type === "Gate" && place.gateState !== "open") { items.push(`Gate ${place.label}: ${place.gateState ?? "closed"}`); } } if (transition) { if (!transition.contract.whyNow) items.push("Active action is missing why-now context"); if (transition.contract.expectedEvidence.length === 0) items.push("Active action has no expected evidence"); if (!transition.contract.exitCondition) items.push("Active action has no exit condition"); if (!transition.contract.failureCondition) items.push("Active action has no failure behavior"); const failures = outgoing(state, transition.id, "failure"); if (failures.length > 0) { const place = state.places[failures[0]!.toId]; if (place) items.push(`Failure path: ${place.label}`); } } for (const candidate of future.filter((item) => item.commitment === "provisional").slice(0, 2)) { const planning = state.transitions[candidate.transitionId]?.planning; if (planning?.reconsiderWhen) items.push(`暂定调整条件: ${planning.reconsiderWhen}`); } if (!state.rootIntent) items.push("North Star is undeclared"); return items.slice(0, 6); } function executionPosition(state: WorkflowState, alignment: AttentionView["alignment"]): ExecutionPosition { const shellIds = activeShellTransitionIds(state); const leafId = state.reservation?.transitionId; let mode: ExecutionPosition["mode"]; if (state.token.status === "complete") mode = "complete"; else if (state.token.status === "blocked" || state.places[state.token.placeId]?.gateState === "blocked") mode = "blocked"; else if (leafId) mode = "executing"; else mode = "at_place"; return { mode, ...(leafId ? { activeLeafTransitionId: leafId } : {}), activeShellTransitionIds: shellIds, tokenPlaceId: state.token.placeId, alignment, }; } export function deriveAttentionView(state: WorkflowState): AttentionView { const leaf = activeLeaf(state); const shellIds = activeShellTransitionIds(state); const alignment = leaf ? "pending" : "unknown"; const future = futureTransitions(state, leaf); const stack = [ ...(state.rootIntent ? [state.rootIntent] : []), ...shellIds.map((id) => state.transitions[id]?.intent ?? id), ...(leaf ? [leaf.intent] : []), ]; const position = executionPosition(state, alignment); const tokenPlace = state.places[state.token.placeId]; const next = future.length > 0 ? future.slice(0, 6).map((item) => item.label) : ["未来尚未声明"]; const now = leaf?.intent ?? (position.mode === "blocked" ? `Blocked at ${tokenPlace?.label ?? state.token.placeId}` : position.mode === "complete" ? "Execution complete" : `At ${tokenPlace?.label ?? state.token.placeId}; no active leaf`); return { revision: state.revision, rootIntent: state.rootIntent ?? "Undeclared North Star", stack, now, why: leaf?.contract.whyNow ?? (leaf ? "Why-now is unknown" : "Awaiting the next committed leaf"), next, future, watch: watchItems(state, leaf, future), alignment, position, ...(leaf ? { activeLeafTransitionId: leaf.id } : {}), activeShellTransitionIds: shellIds, snapshotHash: snapshotHash(state), }; } export function formatWidgetLines(view: AttentionView): string[] { const stack = view.stack.length > 0 ? view.stack.join(" › ") : "No declared stack"; const next = view.next.length > 0 ? view.next.join(" → ") : "No declared successor"; const watch = view.watch.length > 0 ? view.watch.join(" · ") : `Alignment ${view.alignment}`; return [ `STACK ${stack}`, `NOW ${view.now}`, `WHY ${view.why}`, `NEXT ${next}`, `WATCH ${watch}`, ]; }