UNPKG

paqad-ai

Version:

Spec-driven development framework — AI agents that think before they type

373 lines (368 loc) • 12.6 kB
import { createRequire as __paqadCreateRequire } from 'module'; const require = __paqadCreateRequire(import.meta.url); import { currentStageOfAgent, isChecksRed, judgeStageReport, resolveStageReport } from "./chunk-WSTTWUET.js"; import { estimateTokens } from "./chunk-F657QMM6.js"; import { readTranscriptUsage } from "./chunk-YZTSRIE3.js"; import { judgeMustNot, transcriptToolUses } from "./chunk-GCMANDHS.js"; import { extractAssistantText, extractMarkers } from "./chunk-OAEANL22.js"; import { appendFeatureStageRow, currentFeature, readFeatureStageUnit } from "./chunk-5M55PDWL.js"; import { STAGE_AGENT_KIND } from "./chunk-7TBVBCDH.js"; import { sha256Hex } from "./chunk-RJMJYPHL.js"; import { MANDATORY_STAGE_AGENTS, STAGE_AGENT_STAGES, stageForAgentName, stripPluginAgentNamespace } from "./chunk-NCYDV2N5.js"; import { STAGE_EFFORT_LEVELS, STAGE_MODELS, resolveStageAgentSettings } from "./chunk-ACJQW7SB.js"; import { resolveHookSession } from "./chunk-SG6LXLDH.js"; import { PATHS } from "./chunk-IHMBVS6H.js"; import { init_esm_shims } from "./chunk-IDFRWBMN.js"; // src/stage-isolation/subagent-completion.ts init_esm_shims(); import { readFileSync as readFileSync2 } from "fs"; // src/stage-isolation/stage-settings.ts init_esm_shims(); import { mkdirSync, readFileSync, writeFileSync } from "fs"; import { dirname, join } from "path"; var SAFE_SESSION_ID = /^[A-Za-z0-9._-]{1,128}$/; function isSafeSessionId(sessionId) { return SAFE_SESSION_ID.test(sessionId); } var STAGE_SETTINGS_DIR = "stage-settings.d"; function stageSettingsPath(projectRoot, sessionId) { return join(projectRoot, PATHS.AGENCY_SESSION_DIR, STAGE_SETTINGS_DIR, `${sessionId}.json`); } function writeStageSettings(projectRoot, sessionId, env = process.env) { if (!isSafeSessionId(sessionId)) { throw new Error("unsafe session id"); } const settings = resolveStageAgentSettings(projectRoot, env); const path = stageSettingsPath(projectRoot, sessionId); mkdirSync(dirname(path), { recursive: true }); writeFileSync(path, `${JSON.stringify(settings, null, 2)} `, "utf8"); return settings; } function effortToPass(configured, fileDefault) { if (configured !== "inherit") return `effort ${configured}`; if (fileDefault !== "inherit") { return `your own current effort level as effort, because its agent file defaults to ${fileDefault} effort`; } return null; } function modelToPass(configured, fileDefault) { if (configured !== "inherit") return `model ${configured}`; if (fileDefault !== "inherit") { return `your own current model as model, because its agent file defaults to ${fileDefault}`; } return null; } function stagePassAdvice(setting, fileDefault) { const parts = [ effortToPass(setting.effort, fileDefault.effort), modelToPass(setting.model, fileDefault.model) ].filter((part) => part !== null); if (parts.length === 0) { return "pass nothing on its Agent call, so it runs at the session's own effort and model"; } return `on its Agent call, pass ${parts.join(" and ")}`; } function stageSettingsReport(settings) { const fileDefaults = resolveStageAgentSettings(); return Object.fromEntries( Object.entries(settings).map(([stage, setting]) => [ stage, { ...setting, pass: stagePassAdvice(setting, fileDefaults[stage]) } ]) ); } function isStageAgentSetting(value) { if (!value || typeof value !== "object") return false; const { effort, model } = value; return STAGE_EFFORT_LEVELS.includes(effort) && STAGE_MODELS.includes(model); } function readStageSetting(projectRoot, sessionId, stage) { if (!isSafeSessionId(sessionId)) return null; try { const parsed = JSON.parse( readFileSync(stageSettingsPath(projectRoot, sessionId), "utf8") ); const setting = parsed?.[stage]; return isStageAgentSetting(setting) ? { effort: setting.effort, model: setting.model } : null; } catch { return null; } } // src/stage-isolation/subagent-completion.ts function stageFromAgentType(agentType) { if (typeof agentType !== "string") { return null; } return stageForAgentName(stripPluginAgentNamespace(agentType)); } function nonNegativeInt(value) { return typeof value === "number" && Number.isFinite(value) && value >= 0 ? Math.floor(value) : null; } function resolveTokenCounts(payload, transcriptText) { const usage = payload.usage ?? void 0; const input = usage ? nonNegativeInt(usage.input_tokens) : null; const output = usage ? nonNegativeInt(usage.output_tokens) : null; if (input !== null && output !== null) { const cached = nonNegativeInt(usage?.cache_read_input_tokens) ?? nonNegativeInt(usage?.cached_input_tokens) ?? 0; return { tokens_input: input, tokens_cached: cached, tokens_output: output, exact: true }; } const estimate = estimateTokens(transcriptText); return { tokens_input: estimate, tokens_cached: 0, tokens_output: 0, exact: false }; } function readAgentTranscriptText(path) { try { return readFileSync2(path, "utf8"); } catch { return null; } } function resolveStageAgentTokens(payload, transcriptText) { const path = payload.agent_transcript_path; if (typeof path === "string" && path.trim() !== "") { const agentText = readAgentTranscriptText(path); const usage = readTranscriptUsage(path); if (agentText !== null && usage.messages.length > 0) { return { tokens_used: usage.totals.total, tokens_not_recarried: estimateTokens(agentText), exact: true }; } } const counts = resolveTokenCounts(payload, transcriptText); return { tokens_used: counts.tokens_input + counts.tokens_output, tokens_not_recarried: estimateTokens(transcriptText), exact: false }; } function requestedSetting(projectRoot, sessionId, stage) { const setting = readStageSetting(projectRoot, sessionId, stage); return setting ? { requested_effort: setting.effort, requested_model: setting.model } : {}; } function agentIdField(payload) { return typeof payload.agent_id === "string" && payload.agent_id.trim() !== "" ? { agent_id: payload.agent_id.trim() } : {}; } function transcriptStartedStages(payload) { const path = payload.agent_transcript_path; if (typeof path !== "string" || path.trim() === "") return []; const text = readAgentTranscriptText(path); if (text === null) return []; return extractMarkers(extractAssistantText(text)).filter((marker) => marker.phase === "start").map((marker) => marker.stage); } function ledgerStartedStages(projectRoot, dirName, agentId) { return readFeatureStageUnit(projectRoot, dirName).filter((row) => row.kind === "stage_start" && row.agent_id === agentId).map((row) => String(row.stage)); } function stagesRunByAgent(projectRoot, dirName, stage, payload) { const agentId = agentIdField(payload).agent_id; const ran = /* @__PURE__ */ new Set([ stage, ...transcriptStartedStages(payload), ...agentId ? ledgerStartedStages(projectRoot, dirName, agentId) : [] ]); return STAGE_AGENT_STAGES.filter((candidate) => ran.has(candidate)); } function agentTurns(payload) { const path = payload.agent_transcript_path; if (typeof path !== "string" || path.trim() === "") return null; const usage = readTranscriptUsage(path); return usage.messages.length > 0 ? usage.messages.length : null; } function turnLimitReason(stage, turns) { const limit = MANDATORY_STAGE_AGENTS.find((def) => def.ledgerStage === stage)?.maxTurns; return limit !== void 0 && turns !== null && turns >= limit ? `${stage} reached its turn limit of ${limit} and returned no report` : null; } function judgeStageAgentReturn(stage, payload, context) { const rows = readFeatureStageUnit(context.projectRoot, context.dirName); const agentId = agentIdField(payload).agent_id ?? null; const report = resolveStageReport({ rows, agentId, payload, adapter: context.adapter, fallbackTranscript: context.fallbackTranscript }); const path = payload.agent_transcript_path; const text = typeof path === "string" && path.trim() !== "" ? readAgentTranscriptText(path) : null; const violations = text === null ? [] : judgeMustNot(stage, transcriptToolUses(text)); const current = currentStageOfAgent( rows, agentId, typeof payload.agent_type === "string" ? payload.agent_type : null ) ?? stage; const turns = agentTurns(payload); const row = report.handbackRow; const shared = { report_source: report.source, gate_checked: report.gateChecked, ...turns === null ? {} : { api_turns: turns }, ...violations.length > 0 ? { must_not: violations } : {} }; if (row) { return { ...shared, report_status: String(row.report_status), report_stated: row.report_stated === true, advance: row.advance === true && violations.length === 0, decision: typeof row.decision === "string" ? row.decision : null, report_hash: String(row.report_hash), ...typeof row.stated_status === "string" ? { stated_status: row.stated_status } : {}, ...typeof row.reason === "string" ? { reason: row.reason } : {} }; } const judged = judgeStageReport({ stage: current, agentId, reading: report.reading, rows, violations, checksRed: isChecksRed(context.projectRoot, context.dirName, current) }); const turnLimit = report.source === "none" ? turnLimitReason(current, turns) : null; const reason = judged.reason ?? turnLimit; return { ...shared, report_status: judged.report_status, report_stated: judged.report_stated, advance: judged.advance, decision: judged.decision, ...report.message === null ? {} : { report_hash: sha256Hex(report.message) }, ...judged.stated_status ? { stated_status: judged.stated_status } : {}, ...reason ? { reason } : {} }; } function recordStageAgentStart(input) { try { const agentType = typeof input.payload.agent_type === "string" ? input.payload.agent_type : null; const stage = stageFromAgentType(agentType); if (!stage || agentType === null) { return false; } const sessionId = resolveHookSession(input.projectRoot, input.payload); const dirName = currentFeature(input.projectRoot, sessionId); if (!dirName) { return false; } appendFeatureStageRow( input.projectRoot, sessionId, dirName, { kind: STAGE_AGENT_KIND, stage, agent: agentType, ...agentIdField(input.payload), event_status: "started", host_pid: process.ppid, tokens_used: 0, tokens_not_recarried: 0, estimate: true }, input.now ); return true; } catch { return false; } } function recordStageAgentCompletion(input) { try { const stage = stageFromAgentType( typeof input.payload.agent_type === "string" ? input.payload.agent_type : null ); if (!stage) { return false; } const sessionId = resolveHookSession(input.projectRoot, input.payload); const dirName = currentFeature(input.projectRoot, sessionId); if (!dirName) { return false; } const tokens = resolveStageAgentTokens(input.payload, input.transcriptText); const stages = stagesRunByAgent(input.projectRoot, dirName, stage, input.payload); const judgement = judgeStageAgentReturn(stage, input.payload, { projectRoot: input.projectRoot, dirName, adapter: input.adapter, fallbackTranscript: input.transcriptText }); if (judgement.report_source === "none") { input.log?.( `stage-agent-completion: no report found agent=${agentIdField(input.payload).agent_id ?? "none"} stage=${stage}` ); } appendFeatureStageRow( input.projectRoot, sessionId, dirName, { kind: STAGE_AGENT_KIND, stage, ...stages.length > 1 ? { stages } : {}, agent: input.payload.agent_type, ...agentIdField(input.payload), event_status: "completed", tokens_used: tokens.tokens_used, tokens_not_recarried: tokens.tokens_not_recarried, estimate: !tokens.exact, ...requestedSetting(input.projectRoot, sessionId, stage), ...judgement }, input.now ); return true; } catch { return false; } } export { SAFE_SESSION_ID, isSafeSessionId, writeStageSettings, stageSettingsReport, stageFromAgentType, resolveTokenCounts, resolveStageAgentTokens, stagesRunByAgent, judgeStageAgentReturn, recordStageAgentStart, recordStageAgentCompletion };