fix: emit real checkpoint artifact for wrapper integration
This commit is contained in:
@@ -46,6 +46,23 @@ async function safeReadText(filePath: string): Promise<string | null> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getReadableCheckpointArtifact(workspaceDir: string, wrapperResult: any): Promise<{ relativePath: string; absolutePath: string; content: string; } | null> {
|
||||||
|
const relativePath = typeof wrapperResult?.externalizedCheckpointPath === "string"
|
||||||
|
? wrapperResult.externalizedCheckpointPath.trim()
|
||||||
|
: "";
|
||||||
|
if (!relativePath) return null;
|
||||||
|
|
||||||
|
const absolutePath = path.resolve(workspaceDir, relativePath);
|
||||||
|
try {
|
||||||
|
const raw = await fs.readFile(absolutePath, "utf-8");
|
||||||
|
const content = raw.trim();
|
||||||
|
if (!content) return null;
|
||||||
|
return { relativePath, absolutePath, content };
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function runJsonScript(scriptPath: string, workspaceDir: string, input: Record<string, unknown>, timeout: number): Promise<any | null> {
|
async function runJsonScript(scriptPath: string, workspaceDir: string, input: Record<string, unknown>, timeout: number): Promise<any | null> {
|
||||||
let tempInputPath: string | null = null;
|
let tempInputPath: string | null = null;
|
||||||
|
|
||||||
@@ -95,7 +112,7 @@ async function runLongTaskWrapper(workspaceDir: string, ctx: any): Promise<any |
|
|||||||
return runJsonScript(wrapperPath, workspaceDir, input, LONG_TASK_WRAPPER_TIMEOUT_MS);
|
return runJsonScript(wrapperPath, workspaceDir, input, LONG_TASK_WRAPPER_TIMEOUT_MS);
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildProgressEvidence(wrapperResult: any): Record<string, unknown> | null {
|
function buildProgressEvidence(wrapperResult: any, readableCheckpointArtifact: { relativePath: string; absolutePath: string; content: string; } | null): Record<string, unknown> | null {
|
||||||
const candidate = wrapperResult?.progressEvidence;
|
const candidate = wrapperResult?.progressEvidence;
|
||||||
if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) {
|
if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) {
|
||||||
return null;
|
return null;
|
||||||
@@ -128,6 +145,13 @@ function buildProgressEvidence(wrapperResult: any): Record<string, unknown> | nu
|
|||||||
progressEvidence.verificationResult = verificationResult;
|
progressEvidence.verificationResult = verificationResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (readableCheckpointArtifact) {
|
||||||
|
progressEvidence.checkpointPath = readableCheckpointArtifact.relativePath;
|
||||||
|
if (!progressEvidence.verificationResult) {
|
||||||
|
progressEvidence.verificationResult = `checkpoint artifact readable at ${readableCheckpointArtifact.relativePath}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return Object.keys(progressEvidence).length > 0 ? progressEvidence : null;
|
return Object.keys(progressEvidence).length > 0 ? progressEvidence : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,14 +182,14 @@ function shouldClaimProgression(wrapperResult: any, progressEvidence: Record<str
|
|||||||
return wrapperResult.silentLaunchOk === true;
|
return wrapperResult.silentLaunchOk === true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildGateLockInput(wrapperResult: any): Record<string, unknown> {
|
function buildGateLockInput(wrapperResult: any, readableCheckpointArtifact: { relativePath: string; absolutePath: string; content: string; } | null): Record<string, unknown> {
|
||||||
if (!wrapperResult || wrapperResult.classification !== "long_task") {
|
if (!wrapperResult || wrapperResult.classification !== "long_task") {
|
||||||
return { classification: wrapperResult?.classification ?? "general_chat" };
|
return { classification: wrapperResult?.classification ?? "general_chat" };
|
||||||
}
|
}
|
||||||
|
|
||||||
const needsOwnerDecision = wrapperResult.needsOwnerDecision === true;
|
const needsOwnerDecision = wrapperResult.needsOwnerDecision === true;
|
||||||
const silentCandidate = wrapperResult.silentCandidate === true;
|
const silentCandidate = wrapperResult.silentCandidate === true;
|
||||||
const progressEvidence = buildProgressEvidence(wrapperResult);
|
const progressEvidence = buildProgressEvidence(wrapperResult, readableCheckpointArtifact);
|
||||||
const requiredNextAction = typeof wrapperResult.requiredNextAction === "string"
|
const requiredNextAction = typeof wrapperResult.requiredNextAction === "string"
|
||||||
? wrapperResult.requiredNextAction.trim()
|
? wrapperResult.requiredNextAction.trim()
|
||||||
: "";
|
: "";
|
||||||
@@ -197,8 +221,7 @@ function buildGateLockInput(wrapperResult: any): Record<string, unknown> {
|
|||||||
const progressEvidenceReason = claimedProgression && !progressEvidence
|
const progressEvidenceReason = claimedProgression && !progressEvidence
|
||||||
? "progression claim requires concrete evidence such as sessionKey, runId, modified_files, or verification result"
|
? "progression claim requires concrete evidence such as sessionKey, runId, modified_files, or verification result"
|
||||||
: "";
|
: "";
|
||||||
const hasExternalizedCheckpointEvidence = typeof wrapperResult.externalizedCheckpointPath === "string"
|
const hasExternalizedCheckpointEvidence = Boolean(readableCheckpointArtifact);
|
||||||
&& wrapperResult.externalizedCheckpointPath.trim().length > 0;
|
|
||||||
const hasButtonPathClosureEvidence = needsOwnerDecision && wrapperResult.silentLaunchOk === true;
|
const hasButtonPathClosureEvidence = needsOwnerDecision && wrapperResult.silentLaunchOk === true;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -228,8 +251,8 @@ function buildGateLockInput(wrapperResult: any): Record<string, unknown> {
|
|||||||
dispatchEvidence: "",
|
dispatchEvidence: "",
|
||||||
fileChangeEvidence: "",
|
fileChangeEvidence: "",
|
||||||
verificationEvidence: "",
|
verificationEvidence: "",
|
||||||
checkpointArtifactEvidence: hasExternalizedCheckpointEvidence ? wrapperResult.externalizedCheckpointPath.trim() : "",
|
checkpointArtifactEvidence: hasExternalizedCheckpointEvidence ? readableCheckpointArtifact.relativePath : "",
|
||||||
externalizedCheckpointPath: hasExternalizedCheckpointEvidence ? wrapperResult.externalizedCheckpointPath.trim() : "",
|
externalizedCheckpointPath: hasExternalizedCheckpointEvidence ? readableCheckpointArtifact.relativePath : "",
|
||||||
externalizedTrigger: hasExternalizedCheckpointEvidence ? "hook-preflight-checkpoint" : "",
|
externalizedTrigger: hasExternalizedCheckpointEvidence ? "hook-preflight-checkpoint" : "",
|
||||||
handoffMode: hasButtonPathClosureEvidence ? (wrapperResult.handoff?.mode ?? "button_path") : "direct_reply",
|
handoffMode: hasButtonPathClosureEvidence ? (wrapperResult.handoff?.mode ?? "button_path") : "direct_reply",
|
||||||
replyClosureMode: hasButtonPathClosureEvidence ? (wrapperResult.handoff?.mode ?? "button_path") : "direct_reply",
|
replyClosureMode: hasButtonPathClosureEvidence ? (wrapperResult.handoff?.mode ?? "button_path") : "direct_reply",
|
||||||
@@ -238,7 +261,8 @@ function buildGateLockInput(wrapperResult: any): Record<string, unknown> {
|
|||||||
|
|
||||||
async function runLongTaskGateLock(workspaceDir: string, wrapperResult: any): Promise<GateLockResult | null> {
|
async function runLongTaskGateLock(workspaceDir: string, wrapperResult: any): Promise<GateLockResult | null> {
|
||||||
const gateLockPath = path.join(workspaceDir, "scripts", "long_task_gate_lock.mjs");
|
const gateLockPath = path.join(workspaceDir, "scripts", "long_task_gate_lock.mjs");
|
||||||
const input = buildGateLockInput(wrapperResult);
|
const readableCheckpointArtifact = await getReadableCheckpointArtifact(workspaceDir, wrapperResult);
|
||||||
|
const input = buildGateLockInput(wrapperResult, readableCheckpointArtifact);
|
||||||
return runJsonScript(gateLockPath, workspaceDir, input, LONG_TASK_GATE_LOCK_TIMEOUT_MS);
|
return runJsonScript(gateLockPath, workspaceDir, input, LONG_TASK_GATE_LOCK_TIMEOUT_MS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
function fail(code, message) {
|
function fail(code, message) {
|
||||||
process.stderr.write(`${code}: ${message}\n`);
|
process.stderr.write(`${code}: ${message}\n`);
|
||||||
@@ -124,6 +125,35 @@ function toSlug(value) {
|
|||||||
.slice(0, 48);
|
.slice(0, 48);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ensureCheckpointArtifact(externalizedCheckpointPath, input, classificationResult) {
|
||||||
|
if (classificationResult.classification !== 'long_task') return null;
|
||||||
|
if (!classificationResult.silentCandidate) return null;
|
||||||
|
if (!externalizedCheckpointPath) return null;
|
||||||
|
|
||||||
|
const artifactPath = path.resolve(process.cwd(), externalizedCheckpointPath);
|
||||||
|
const artifact = {
|
||||||
|
kind: 'long_task_checkpoint',
|
||||||
|
triggerKind: input.triggerKind || 'artifact',
|
||||||
|
checkpointTrigger: input.checkpointTrigger || '',
|
||||||
|
currentStep: input.currentStep || '',
|
||||||
|
nextStep: input.nextStep || '',
|
||||||
|
waitingOn: input.waitingOn || '',
|
||||||
|
blocker: input.blocker || '',
|
||||||
|
};
|
||||||
|
|
||||||
|
fs.mkdirSync(path.dirname(artifactPath), { recursive: true });
|
||||||
|
fs.writeFileSync(artifactPath, JSON.stringify(artifact, null, 2) + '\n', 'utf8');
|
||||||
|
|
||||||
|
const stats = fs.statSync(artifactPath);
|
||||||
|
const readable = fs.readFileSync(artifactPath, 'utf8');
|
||||||
|
|
||||||
|
return {
|
||||||
|
absolutePath: artifactPath,
|
||||||
|
bytes: stats.size,
|
||||||
|
readable: readable.trim().length > 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function buildExternalizedCheckpointPath(input, classificationResult) {
|
function buildExternalizedCheckpointPath(input, classificationResult) {
|
||||||
if (classificationResult.classification !== 'long_task') return '';
|
if (classificationResult.classification !== 'long_task') return '';
|
||||||
if (!classificationResult.silentCandidate) return '';
|
if (!classificationResult.silentCandidate) return '';
|
||||||
@@ -138,14 +168,16 @@ function buildExternalizedCheckpointPath(input, classificationResult) {
|
|||||||
return `checkpoints/${stableSeed}.json`;
|
return `checkpoints/${stableSeed}.json`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildProgressEvidence(input, classificationResult, externalizedCheckpointPath) {
|
function buildProgressEvidence(input, classificationResult, externalizedCheckpointPath, checkpointArtifact) {
|
||||||
if (classificationResult.classification !== 'long_task') return null;
|
if (classificationResult.classification !== 'long_task') return null;
|
||||||
if (!classificationResult.silentCandidate) return null;
|
if (!classificationResult.silentCandidate) return null;
|
||||||
if (!externalizedCheckpointPath) return null;
|
if (!externalizedCheckpointPath) return null;
|
||||||
|
if (!checkpointArtifact || checkpointArtifact.readable !== true) return null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
sessionKey: toSlug([input.currentStep, input.waitingOn, input.nextStep].filter(Boolean).join('-')) || 'long-task-session',
|
sessionKey: toSlug([input.currentStep, input.waitingOn, input.nextStep].filter(Boolean).join('-')) || 'long-task-session',
|
||||||
checkpointPath: externalizedCheckpointPath,
|
checkpointPath: externalizedCheckpointPath,
|
||||||
|
verificationResult: `checkpoint artifact readable at ${externalizedCheckpointPath}`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,7 +229,8 @@ function main() {
|
|||||||
const classificationResult = classify(input);
|
const classificationResult = classify(input);
|
||||||
const taskRecord = bootstrapTaskState(input, classificationResult);
|
const taskRecord = bootstrapTaskState(input, classificationResult);
|
||||||
const externalizedCheckpointPath = buildExternalizedCheckpointPath(input, classificationResult);
|
const externalizedCheckpointPath = buildExternalizedCheckpointPath(input, classificationResult);
|
||||||
const progressEvidence = buildProgressEvidence(input, classificationResult, externalizedCheckpointPath);
|
const checkpointArtifact = ensureCheckpointArtifact(externalizedCheckpointPath, input, classificationResult);
|
||||||
|
const progressEvidence = buildProgressEvidence(input, classificationResult, externalizedCheckpointPath, checkpointArtifact);
|
||||||
const silentLaunch = validateSilentLaunch(input, classificationResult);
|
const silentLaunch = validateSilentLaunch(input, classificationResult);
|
||||||
const handoff = planHandoff(classificationResult);
|
const handoff = planHandoff(classificationResult);
|
||||||
|
|
||||||
@@ -210,6 +243,7 @@ function main() {
|
|||||||
taskRecord,
|
taskRecord,
|
||||||
progressEvidence,
|
progressEvidence,
|
||||||
externalizedCheckpointPath,
|
externalizedCheckpointPath,
|
||||||
|
checkpointArtifact,
|
||||||
silentLaunchOk: silentLaunch.ok,
|
silentLaunchOk: silentLaunch.ok,
|
||||||
silentLaunchReason: silentLaunch.reason,
|
silentLaunchReason: silentLaunch.reason,
|
||||||
recommendedFallback: silentLaunch.recommendedFallback,
|
recommendedFallback: silentLaunch.recommendedFallback,
|
||||||
|
|||||||
Reference in New Issue
Block a user