import { END } from "@uncaged/nerve-core"; import type { Moderator, ThreadContext } from "@uncaged/nerve-core"; import type { AnswererMeta } from "./roles/answerer.js"; import type { ExplorerMeta } from "./roles/explorer.js"; import type { QuestionerMeta } from "./roles/questioner.js"; export type WorkflowMeta = { questioner: QuestionerMeta; answerer: AnswererMeta; explorer: ExplorerMeta; }; type Steps = ThreadContext["steps"]; function lastQuestionerRemaining(steps: Steps): QuestionerMeta | undefined { for (let i = steps.length - 1; i >= 0; i--) { const s = steps[i]; if (s.role === "questioner") return s.meta; } return undefined; } /** End when the last two explorer invocations both added no new cards (issue #266 stagnation rule). */ function lastTwoExplorerRunsBothEmpty(steps: Steps): boolean { const explorerSteps = steps.filter((s) => s.role === "explorer"); if (explorerSteps.length < 2) return false; const e1 = explorerSteps[explorerSteps.length - 1].meta as ExplorerMeta; const e2 = explorerSteps[explorerSteps.length - 2].meta as ExplorerMeta; return e1.new_cards.length === 0 && e2.new_cards.length === 0; } function queueAfterSkippedExplorer(steps: Steps): string[] { const q = lastQuestionerRemaining(steps); return q?.remaining_queue ?? []; } function queueAfterExplorerStep(steps: Steps): string[] { const last = steps[steps.length - 1]; if (!last || last.role !== "explorer") return []; const q = lastQuestionerRemaining(steps); if (!q) return []; const e = last.meta as ExplorerMeta; return [...q.remaining_queue, ...e.new_cards]; } export const moderator: Moderator = (context) => { const { steps } = context; if (steps.length === 0) { return "questioner"; } const last = steps[steps.length - 1]; if (last.role === "questioner") { return "answerer"; } if (last.role === "answerer") { const am = last.meta as AnswererMeta; if (am.has_unanswered) { return "explorer"; } const q = queueAfterSkippedExplorer(steps); if (q.length === 0) { return END; } return "questioner"; } if (last.role === "explorer") { if (lastTwoExplorerRunsBothEmpty(steps)) { return END; } const q = queueAfterExplorerStep(steps); if (q.length === 0) { return END; } return "questioner"; } return END; };