@linimin/pi-letscook 0.1.68 → 0.1.70
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/.agent/README.md +2 -3
- package/.agent/verify_completion_control_plane.sh +21 -34
- package/CHANGELOG.md +16 -0
- package/README.md +23 -26
- package/agents/completion-bootstrapper.md +1 -2
- package/agents/completion-regrounder.md +10 -16
- package/extensions/completion/driver.ts +69 -136
- package/extensions/completion/index.ts +94 -81
- package/extensions/completion/policy-guards.ts +1 -1
- package/extensions/completion/prompt-surfaces.ts +63 -161
- package/extensions/completion/proposal.ts +26 -61
- package/extensions/completion/role-runner.ts +161 -57
- package/extensions/completion/state-store.ts +43 -85
- package/extensions/completion/types.ts +2 -3
- package/package.json +1 -1
- package/scripts/active-slice-contract-test.sh +21 -0
- package/scripts/canonical-evidence-artifact-test.sh +57 -65
- package/scripts/context-proposal-test.sh +1430 -310
- package/scripts/legacy-cleanup-test.sh +1 -1
- package/scripts/refocus-test.sh +459 -185
- package/scripts/release-check.sh +14 -15
- package/scripts/role-runner-contract-test.sh +4 -2
- package/scripts/smoke-test.sh +36 -78
- package/skills/completion-protocol/SKILL.md +8 -9
- package/skills/completion-protocol/references/completion.md +2 -37
- package/skills/cook-handoff-boundary/SKILL.md +19 -18
|
@@ -9,22 +9,19 @@ import { Container, matchesKey, SelectList, Text } from "@mariozechner/pi-tui";
|
|
|
9
9
|
import { Type } from "typebox";
|
|
10
10
|
import {
|
|
11
11
|
autoContinueWorkflowIfNeeded,
|
|
12
|
-
completionContinuationFingerprint,
|
|
13
|
-
markQueuedDriverPromptInFlight,
|
|
14
12
|
registerCookCommand,
|
|
15
13
|
} from "./driver";
|
|
16
14
|
import {
|
|
17
|
-
assessCookHandoffText,
|
|
18
15
|
assessMissionAnchor,
|
|
19
16
|
collectRecentDiscussionEntries,
|
|
20
17
|
collectRecentSessionMessages,
|
|
18
|
+
assessLatestCookHandoffProposal,
|
|
21
19
|
finalizeContextProposalAnalysis,
|
|
22
20
|
isWeakMissionAnchor,
|
|
23
21
|
missionAnchorsLikelyEquivalent,
|
|
24
22
|
missionAnchorsStrictlyEquivalent,
|
|
25
23
|
normalizeMissionAnchorText,
|
|
26
24
|
resolveContextProposalConfirmationAction,
|
|
27
|
-
retagContextProposalSource,
|
|
28
25
|
stripCodeBlocks,
|
|
29
26
|
} from "./proposal";
|
|
30
27
|
import type {
|
|
@@ -48,8 +45,7 @@ import {
|
|
|
48
45
|
maybeWriteContextProposalSnapshot,
|
|
49
46
|
} from "./prompt-surfaces";
|
|
50
47
|
import { toolCallBlockReason } from "./policy-guards";
|
|
51
|
-
import { runCompletionRole } from "./role-runner";
|
|
52
|
-
import { generateCookHandoffWithAgent } from "./role-runner";
|
|
48
|
+
import { analyzeContextProposalWithAgent, generateCookHandoffWithAgent, runCompletionRole } from "./role-runner";
|
|
53
49
|
import {
|
|
54
50
|
applyLiveRoleEvent,
|
|
55
51
|
buildInlineRunningLines,
|
|
@@ -106,7 +102,6 @@ const RUBRIC_EVALUATION_ROLES = ["completion-reviewer", "completion-auditor", "c
|
|
|
106
102
|
type RubricEvaluationRole = (typeof RUBRIC_EVALUATION_ROLES)[number];
|
|
107
103
|
|
|
108
104
|
const liveRoleActivityByRoot = new Map<string, LiveRoleActivity>();
|
|
109
|
-
const activatedCompletionRoutingRoots = new Set<string>();
|
|
110
105
|
const LIVE_ROLE_HEARTBEAT_MS = 5_000;
|
|
111
106
|
const COOK_HANDOFF_BLOCK_REGEX = /```cook_handoff\s*[\s\S]*?```/giu;
|
|
112
107
|
|
|
@@ -134,10 +129,10 @@ type ActiveWorkflowProposalAssessment = {
|
|
|
134
129
|
proposal?: ContextProposal;
|
|
135
130
|
blockedFailureMessage?: string;
|
|
136
131
|
reason:
|
|
137
|
-
| "
|
|
138
|
-
| "
|
|
139
|
-
| "
|
|
140
|
-
| "
|
|
132
|
+
| "matching_mission"
|
|
133
|
+
| "missing_explicit_handoff"
|
|
134
|
+
| "fresh_explicit_handoff"
|
|
135
|
+
| "fresh_explicit_handoff_not_startable";
|
|
141
136
|
};
|
|
142
137
|
|
|
143
138
|
function completionTestWorkflowActionOverride(): "continue" | "refocus" | "cancel" | undefined {
|
|
@@ -211,44 +206,76 @@ function maybeWriteTestSnapshot(targetPath: string | undefined, content: string)
|
|
|
211
206
|
|
|
212
207
|
const COOK_MAIN_CHAT_RERUN_GUIDANCE = "Discuss changes in the main chat and rerun /cook.";
|
|
213
208
|
const COOK_STRUCTURED_DISCUSSION_FAILURE_DETAIL =
|
|
214
|
-
"/cook failed closed because the
|
|
209
|
+
"/cook failed closed because the primary-agent handoff step could not prepare a concrete startup handoff from the current task context. Clarify the mission, first slice, or verification intent in the main chat, then rerun /cook.";
|
|
215
210
|
|
|
216
211
|
function isWorkflowDone(snapshot: CompletionStateSnapshot | undefined): boolean {
|
|
217
212
|
return asString(snapshot?.state?.continuation_policy) === "done";
|
|
218
213
|
}
|
|
219
214
|
|
|
220
|
-
function activateCompletionRoutingForRoot(
|
|
221
|
-
|
|
222
|
-
|
|
215
|
+
function activateCompletionRoutingForRoot(_root: string | undefined): void {
|
|
216
|
+
// Workflow-entry legitimacy is derived from canonical .agent state rather than in-memory routing activation.
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function hasActiveWorkflowEntry(snapshot: CompletionStateSnapshot | undefined): boolean {
|
|
220
|
+
if (!snapshot) return false;
|
|
221
|
+
if (isWorkflowDone(snapshot)) return false;
|
|
222
|
+
return asString(snapshot.state?.workflow_entry_status) === "active" || isRecord(snapshot.startupBrief) || isRecord(snapshot.state?.advisory_startup_brief);
|
|
223
223
|
}
|
|
224
224
|
|
|
225
225
|
function hasCompletionRoutingActivation(snapshot: CompletionStateSnapshot | undefined): boolean {
|
|
226
226
|
if (!snapshot) return false;
|
|
227
227
|
if (roleFromEnv()) return true;
|
|
228
|
-
return
|
|
228
|
+
return false;
|
|
229
229
|
}
|
|
230
230
|
|
|
231
231
|
function latestUserOrCustomTurnText(ctx: { sessionManager?: any }): string | undefined {
|
|
232
|
-
|
|
232
|
+
const messages = collectRecentSessionMessages(ctx as { sessionManager: any }, { isRecord, asString, asNumber, isStaleContextError }, 4);
|
|
233
|
+
return messages.find((entry) => entry.role === "user" || entry.role === "custom")?.text;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function isCookCommandTurn(ctx: { sessionManager?: any }): boolean {
|
|
237
|
+
const latest = latestUserOrCustomTurnText(ctx);
|
|
238
|
+
if (!latest) return false;
|
|
239
|
+
return /^\/cook\b/.test(latest.trim());
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function extractWorkflowSessionIdFromPrompt(text: string): string | undefined {
|
|
243
|
+
const match = text.match(/^- workflow_session_id:\s*(.+)$/m);
|
|
244
|
+
return match?.[1]?.trim() || undefined;
|
|
233
245
|
}
|
|
234
246
|
|
|
235
|
-
function isCompletionDriverPromptTurn(ctx: { sessionManager?: any }): boolean {
|
|
247
|
+
function isCompletionDriverPromptTurn(snapshot: CompletionStateSnapshot | undefined, ctx: { sessionManager?: any }): boolean {
|
|
236
248
|
const latest = latestUserOrCustomTurnText(ctx);
|
|
237
249
|
if (!latest) return false;
|
|
238
|
-
|
|
239
|
-
|
|
250
|
+
const isLegacySkillPrompt = /^\/skill:completion-protocol\b/.test(latest);
|
|
251
|
+
const isWorkflowDriverPrompt = /^COMPLETION WORKFLOW DRIVER\b/m.test(latest);
|
|
252
|
+
if (!isLegacySkillPrompt && !isWorkflowDriverPrompt) return false;
|
|
253
|
+
if (!/(?:Start or continue the completion workflow for this repo\.|Resume the completion workflow from canonical state\.)/.test(latest)) return false;
|
|
254
|
+
const canonicalSessionId = asString(snapshot?.state?.workflow_session_id);
|
|
255
|
+
const promptSessionId = extractWorkflowSessionIdFromPrompt(latest);
|
|
256
|
+
if (canonicalSessionId && promptSessionId && canonicalSessionId !== promptSessionId) return false;
|
|
257
|
+
return true;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function isCompletionWorkflowSessionTurn(snapshot: CompletionStateSnapshot | undefined, ctx: { sessionManager?: any }): boolean {
|
|
261
|
+
if (!(hasCompletionRoutingActivation(snapshot) || hasActiveWorkflowEntry(snapshot))) return false;
|
|
262
|
+
return isCompletionDriverPromptTurn(snapshot, ctx) || isCookCommandTurn(ctx);
|
|
240
263
|
}
|
|
241
264
|
|
|
242
265
|
function shouldInjectCompletionWorkflowContext(snapshot: CompletionStateSnapshot | undefined, ctx: { sessionManager?: any }): boolean {
|
|
243
|
-
return
|
|
266
|
+
return isCompletionWorkflowSessionTurn(snapshot, ctx);
|
|
244
267
|
}
|
|
245
268
|
|
|
246
|
-
function shouldInjectCookHandoffBoundary(
|
|
269
|
+
function shouldInjectCookHandoffBoundary(
|
|
270
|
+
event: { prompt?: string },
|
|
271
|
+
ctx: { sessionManager?: any },
|
|
272
|
+
snapshot?: CompletionStateSnapshot,
|
|
273
|
+
): boolean {
|
|
247
274
|
if (roleFromEnv()) return false;
|
|
248
|
-
if (
|
|
275
|
+
if (isCompletionWorkflowSessionTurn(snapshot, ctx)) return false;
|
|
249
276
|
const prompt = typeof event.prompt === "string" ? event.prompt.trim() : "";
|
|
250
277
|
if (!prompt) return false;
|
|
251
|
-
if (prompt.startsWith("/")) return false;
|
|
278
|
+
if (prompt.startsWith("/") || /^COMPLETION WORKFLOW DRIVER\b/m.test(prompt)) return false;
|
|
252
279
|
return true;
|
|
253
280
|
}
|
|
254
281
|
|
|
@@ -378,13 +405,36 @@ function stripCookHandoffBlocks(text: string): string {
|
|
|
378
405
|
return text.replace(COOK_HANDOFF_BLOCK_REGEX, " ").replace(/\s+/g, " ").trim();
|
|
379
406
|
}
|
|
380
407
|
|
|
408
|
+
async function deriveCookStartupProposal(
|
|
409
|
+
ctx: { cwd: string; hasUI: boolean; ui: any; sessionManager: any; model?: any; modelRegistry?: any },
|
|
410
|
+
projectName: string,
|
|
411
|
+
): Promise<CookContextProposalResult> {
|
|
412
|
+
const recentMessages = collectRecentSessionMessages(ctx, { isRecord, asString, asNumber, isStaleContextError });
|
|
413
|
+
const explicitHandoff = assessLatestCookHandoffProposal(recentMessages, projectName, {
|
|
414
|
+
asString,
|
|
415
|
+
asStringArray,
|
|
416
|
+
assessMissionAnchor,
|
|
417
|
+
normalizeMissionAnchorText,
|
|
418
|
+
isWeakMissionAnchor,
|
|
419
|
+
missionAnchorsStrictlyEquivalent,
|
|
420
|
+
stripCodeBlocks,
|
|
421
|
+
});
|
|
422
|
+
if (explicitHandoff.status === "startable") return { proposal: explicitHandoff.proposal };
|
|
423
|
+
if (explicitHandoff.status === "fresh_but_not_startable") {
|
|
424
|
+
return { blockedFailureMessage: explicitHandoff.message };
|
|
425
|
+
}
|
|
426
|
+
return {};
|
|
427
|
+
}
|
|
428
|
+
|
|
381
429
|
async function deriveCookContextProposal(
|
|
382
430
|
ctx: { cwd: string; hasUI: boolean; ui: any; sessionManager: any; model?: any; modelRegistry?: any },
|
|
383
431
|
projectName: string,
|
|
384
432
|
): Promise<CookContextProposalResult> {
|
|
433
|
+
const explicit = await deriveCookStartupProposal(ctx, projectName);
|
|
434
|
+
if (explicit.proposal || explicit.blockedFailureMessage) return explicit;
|
|
385
435
|
const recentMessages = collectRecentSessionMessages(ctx, { isRecord, asString, asNumber, isStaleContextError });
|
|
386
436
|
const recentEntries = recentMessages
|
|
387
|
-
.filter((entry) => !entry.isCommand && (entry.role === "user" || entry.role === "custom" || entry.role === "summary"))
|
|
437
|
+
.filter((entry) => !entry.isCommand && (entry.role === "user" || entry.role === "assistant" || entry.role === "custom" || entry.role === "summary"))
|
|
388
438
|
.slice(0, 12)
|
|
389
439
|
.map((entry) => ({ role: entry.role, text: stripCookHandoffBlocks(entry.text) }))
|
|
390
440
|
.filter((entry) => entry.text.length > 0);
|
|
@@ -397,7 +447,6 @@ async function deriveCookContextProposal(
|
|
|
397
447
|
`latest verified slice: ${asString(snapshot.state?.latest_verified_slice) ?? "(none)"}`,
|
|
398
448
|
`active slice goal: ${asString(snapshot.active?.goal) ?? "(none)"}`,
|
|
399
449
|
`active slice why_now: ${asString(snapshot.active?.why_now) ?? "(none)"}`,
|
|
400
|
-
`approved startup plan summary: ${asString(snapshot.startupPlan?.goal_text) ?? "(none)"}`,
|
|
401
450
|
`verification goal: ${asString(snapshot.verificationEvidence?.goal) ?? "(none)"}`,
|
|
402
451
|
`verification summary: ${asString(snapshot.verificationEvidence?.summary) ?? "(none)"}`,
|
|
403
452
|
]
|
|
@@ -415,7 +464,9 @@ async function deriveCookContextProposal(
|
|
|
415
464
|
getCtxUi,
|
|
416
465
|
});
|
|
417
466
|
if (!raw) return {};
|
|
418
|
-
const generated =
|
|
467
|
+
const generated = assessLatestCookHandoffProposal([
|
|
468
|
+
{ role: "assistant", text: raw, messageId: "generated-primary-agent-handoff", timestampMs: Date.now(), isCommand: false },
|
|
469
|
+
], projectName, {
|
|
419
470
|
asString,
|
|
420
471
|
asStringArray,
|
|
421
472
|
assessMissionAnchor,
|
|
@@ -423,14 +474,8 @@ async function deriveCookContextProposal(
|
|
|
423
474
|
isWeakMissionAnchor,
|
|
424
475
|
missionAnchorsStrictlyEquivalent,
|
|
425
476
|
stripCodeBlocks,
|
|
426
|
-
}, {
|
|
427
|
-
messageId: "generated-primary-agent-handoff",
|
|
428
|
-
timestampMs: Date.now(),
|
|
429
|
-
context: "same_entry_synthesis",
|
|
430
477
|
});
|
|
431
|
-
if (generated.status === "startable") {
|
|
432
|
-
return { proposal: retagContextProposalSource(generated.proposal, "deferred_primary_agent_handoff") };
|
|
433
|
-
}
|
|
478
|
+
if (generated.status === "startable") return { proposal: generated.proposal };
|
|
434
479
|
if (generated.status === "fresh_but_not_startable") return { blockedFailureMessage: generated.message };
|
|
435
480
|
return {};
|
|
436
481
|
}
|
|
@@ -469,19 +514,13 @@ async function confirmContextProposal(
|
|
|
469
514
|
async function scaffoldCompletionFiles(
|
|
470
515
|
root: string,
|
|
471
516
|
missionAnchor: string,
|
|
472
|
-
options?: {
|
|
473
|
-
analysis?: ContextProposalAnalysis;
|
|
474
|
-
continuationReason?: string;
|
|
475
|
-
advisoryStartupBrief?: JsonRecord;
|
|
476
|
-
approvedStartupPlan?: JsonRecord;
|
|
477
|
-
},
|
|
517
|
+
options?: { analysis?: ContextProposalAnalysis; continuationReason?: string; advisoryStartupBrief?: JsonRecord },
|
|
478
518
|
) {
|
|
479
519
|
const routing = finalizeContextProposalAnalysis(options?.analysis, [missionAnchor]);
|
|
480
520
|
return await scaffoldCompletionFilesOnDisk(root, missionAnchor, {
|
|
481
521
|
analysis: { taskType: routing.taskType, evaluationProfile: routing.evaluationProfile },
|
|
482
522
|
continuationReason: options?.continuationReason,
|
|
483
523
|
advisoryStartupBrief: options?.advisoryStartupBrief,
|
|
484
|
-
approvedStartupPlan: options?.approvedStartupPlan,
|
|
485
524
|
});
|
|
486
525
|
}
|
|
487
526
|
|
|
@@ -644,20 +683,6 @@ function verificationEvidenceContext(snapshot: CompletionStateSnapshot) {
|
|
|
644
683
|
};
|
|
645
684
|
}
|
|
646
685
|
|
|
647
|
-
function startupPlanContext(snapshot: CompletionStateSnapshot) {
|
|
648
|
-
const startupPlan = snapshot.startupPlan;
|
|
649
|
-
return {
|
|
650
|
-
path: path.relative(snapshot.files.root, snapshot.files.startupPlanPath) || ".agent/startup-plan.json",
|
|
651
|
-
status: startupPlan ? "present" : "missing",
|
|
652
|
-
source: asString(startupPlan?.source),
|
|
653
|
-
plannedSurfaces: asStringArray(startupPlan?.planned_surfaces),
|
|
654
|
-
verificationIntent: asStringArray(startupPlan?.verification_intent),
|
|
655
|
-
summary:
|
|
656
|
-
asString(startupPlan?.goal_text) ??
|
|
657
|
-
(startupPlan ? "Approved startup plan is present but its goal_text is missing." : "Approved startup plan is missing."),
|
|
658
|
-
};
|
|
659
|
-
}
|
|
660
|
-
|
|
661
686
|
function buildEvaluationRoleContextLines(snapshot: CompletionStateSnapshot, role: RubricEvaluationRole): string[] {
|
|
662
687
|
return buildExtractedEvaluationRoleContextLines(snapshot, role, {
|
|
663
688
|
asString,
|
|
@@ -688,7 +713,6 @@ function composeSystemReminder(snapshot: CompletionStateSnapshot, sliceHistory:
|
|
|
688
713
|
const exactActiveContract = activeCarriesExactHandoff(snapshot.active);
|
|
689
714
|
const activeContractDrift = activeSliceContractDriftSummary(snapshot);
|
|
690
715
|
const evidence = verificationEvidenceContext(snapshot);
|
|
691
|
-
const startupPlan = startupPlanContext(snapshot);
|
|
692
716
|
const activePriorityLine = activePriority !== undefined ? `Active slice priority: ${activePriority}` : undefined;
|
|
693
717
|
const activeWhyNowLine = activeWhyNow ? `Active slice why_now: ${activeWhyNow}` : undefined;
|
|
694
718
|
const implementationSurfacesLine =
|
|
@@ -718,7 +742,6 @@ function composeSystemReminder(snapshot: CompletionStateSnapshot, sliceHistory:
|
|
|
718
742
|
implementationSurfacesLine,
|
|
719
743
|
verificationCommandsLine,
|
|
720
744
|
evidence,
|
|
721
|
-
startupPlan,
|
|
722
745
|
evaluationRoleReminderText: isRubricEvaluationRole(nextRole) ? buildEvaluationRoleReminderText(snapshot, nextRole) : undefined,
|
|
723
746
|
});
|
|
724
747
|
}
|
|
@@ -738,19 +761,17 @@ function buildPostCompactionDriverInstructions(snapshot: CompletionStateSnapshot
|
|
|
738
761
|
const exactActiveContract = activeCarriesExactHandoff(snapshot.active);
|
|
739
762
|
const activeContractDrift = activeSliceContractDriftSummary(snapshot);
|
|
740
763
|
const evidence = verificationEvidenceContext(snapshot);
|
|
741
|
-
const startupPlan = startupPlanContext(snapshot);
|
|
742
764
|
const lines = [
|
|
743
765
|
"POST-COMPACTION RECOVERY MODE is active.",
|
|
744
766
|
`Compaction marker time: ${markerAt}`,
|
|
745
767
|
"Treat the previous conversation as lossy continuity support only.",
|
|
746
|
-
"Before taking any substantive action, re-read .agent/state.json, .agent/
|
|
768
|
+
"Before taking any substantive action, re-read .agent/state.json, .agent/plan.json, .agent/active-slice.json, .agent/slice-history.jsonl, .agent/stop-check-history.jsonl, and .agent/verification-evidence.json from disk.",
|
|
747
769
|
`Canonical task_type is currently: ${taskType}`,
|
|
748
770
|
`Canonical evaluation_profile is currently: ${evaluationProfile}`,
|
|
749
771
|
`Canonical next mandatory role is currently: ${nextRole}`,
|
|
750
772
|
`Canonical next mandatory action is currently: ${nextAction}`,
|
|
751
773
|
`Canonical continuation policy is currently: ${continuation}`,
|
|
752
774
|
`Canonical active slice is currently: ${activeSliceId}`,
|
|
753
|
-
`Canonical approved startup plan is currently: ${startupPlan.path} (${startupPlan.status})`,
|
|
754
775
|
`Canonical verification evidence artifact is currently: ${evidence.path} (${evidence.status})`,
|
|
755
776
|
"Do not trust pre-compaction memory over canonical files.",
|
|
756
777
|
"If the canonical state is ambiguous, inconsistent, missing, or stale after re-reading it, your first mandatory action is to dispatch completion-regrounder rather than guessing.",
|
|
@@ -765,10 +786,6 @@ function buildPostCompactionDriverInstructions(snapshot: CompletionStateSnapshot
|
|
|
765
786
|
if (activeWhyNow) lines.push(`Canonical active-slice why_now is currently: ${activeWhyNow}`);
|
|
766
787
|
if (implementationSurfaces.length > 0) lines.push(`Canonical implementation surfaces are currently: ${implementationSurfaces.join(", ")}`);
|
|
767
788
|
if (verificationCommands.length > 0) lines.push(`Canonical verification commands are currently: ${verificationCommands.join(" | ")}`);
|
|
768
|
-
if (startupPlan.source) lines.push(`Canonical approved startup plan source is currently: ${startupPlan.source}`);
|
|
769
|
-
if (startupPlan.plannedSurfaces.length > 0) lines.push(`Canonical approved startup plan surfaces are currently: ${startupPlan.plannedSurfaces.join(" | ")}`);
|
|
770
|
-
if (startupPlan.verificationIntent.length > 0) lines.push(`Canonical approved startup plan verification intent is currently: ${startupPlan.verificationIntent.join(" | ")}`);
|
|
771
|
-
lines.push(`Canonical approved startup plan summary is currently: ${startupPlan.summary}`);
|
|
772
789
|
if (evidence.subjectType) lines.push(`Canonical verification evidence subject is currently: ${evidence.subjectType}`);
|
|
773
790
|
if (evidence.outcome) lines.push(`Canonical verification evidence outcome is currently: ${evidence.outcome}`);
|
|
774
791
|
if (evidence.recordedAt) lines.push(`Canonical verification evidence recorded_at is currently: ${evidence.recordedAt}`);
|
|
@@ -859,7 +876,6 @@ function composeResumeCapsule(snapshot: CompletionStateSnapshot, sliceHistory: J
|
|
|
859
876
|
const verificationCommands = asStringArray(snapshot.active?.verification_commands);
|
|
860
877
|
const remainingBefore = asStringArray(snapshot.active?.remaining_contract_ids_before);
|
|
861
878
|
const evidence = verificationEvidenceContext(snapshot);
|
|
862
|
-
const startupPlan = startupPlanContext(snapshot);
|
|
863
879
|
const implementationSurfacesLine =
|
|
864
880
|
implementationSurfaces.length > 0 ? `- implementation_surfaces: ${implementationSurfaces.join(" | ")}` : undefined;
|
|
865
881
|
const verificationCommandsLine =
|
|
@@ -880,7 +896,6 @@ function composeResumeCapsule(snapshot: CompletionStateSnapshot, sliceHistory: J
|
|
|
880
896
|
activeSliceMatchesPlan: activeSliceMatchesPlan(snapshot),
|
|
881
897
|
activeSliceContractDrift: activeSliceContractDriftSummary(snapshot),
|
|
882
898
|
implementerHandoffSnapshot: handoffSnapshotState(snapshot.active),
|
|
883
|
-
startupPlan,
|
|
884
899
|
evidence,
|
|
885
900
|
activeSlice: {
|
|
886
901
|
sliceId: asString(snapshot.active?.slice_id) ?? asString(snapshot.activeSlice?.slice_id),
|
|
@@ -911,6 +926,7 @@ function completionKickoff(
|
|
|
911
926
|
evaluationProfile: string,
|
|
912
927
|
intent: "auto" | "continue" | "refocus" = "auto",
|
|
913
928
|
missionAnchor?: string,
|
|
929
|
+
workflowSessionId?: string,
|
|
914
930
|
): string {
|
|
915
931
|
const intentBlock =
|
|
916
932
|
intent === "continue" && missionAnchor
|
|
@@ -918,11 +934,13 @@ function completionKickoff(
|
|
|
918
934
|
: intent === "refocus" && missionAnchor
|
|
919
935
|
? `Updated canonical mission anchor:\n${missionAnchor}\n\nWorkflow intent:\n- The user explicitly refocused the workflow before this kickoff.\n- Re-read canonical .agent/** state and continue from the refocused mission.\n\n`
|
|
920
936
|
: "";
|
|
921
|
-
|
|
937
|
+
const sessionBlock = workflowSessionId ? `Workflow session:\n- workflow_session_id: ${workflowSessionId}\n\n` : "";
|
|
938
|
+
return `COMPLETION WORKFLOW DRIVER\nStart or continue the completion workflow for this repo.\n\nBefore acting, read:\n- ${SKILL_PATH}\n- ${REFERENCE_PATH}\n\nCanonical routing profile:\n- task_type: ${taskType}\n- evaluation_profile: ${evaluationProfile}\n\n${sessionBlock}User goal:\n${goal}\n\n${intentBlock}Driver instructions:\n- Canonical truth is in .agent/**. Re-read .agent/state.json, .agent/startup-brief.json, .agent/plan.json, .agent/active-slice.json, and .agent/verification-evidence.json before acting when they exist.\n- If tracked completion contract files are missing or onboarding is required, invoke completion_role with role completion-bootstrapper.\n- Otherwise follow the mandatory dispatch rules from completion-protocol.\n- Treat .agent/startup-brief.json as canonical intake, not as the canonical slice plan.\n- For selected, in-progress, committed, or done slices, treat .agent/active-slice.json as the canonical implementation contract and route to completion-regrounder if it drifts from the selected plan slice or the exact handoff is unclear.\n- Consume .agent/verification-evidence.json instead of temp-only verification summaries when it is populated.\n- Use completion_role for all completion-* role work. Do not directly implement tracked product changes yourself.\n- Continue dispatching mandatory roles while continuation_policy == continue.\n- Only stop for the user when continuation_policy is await_user_input, blocked, paused, or done.`;
|
|
922
939
|
}
|
|
923
940
|
|
|
924
|
-
function completionResumePrompt(taskType: string, evaluationProfile: string): string {
|
|
925
|
-
|
|
941
|
+
function completionResumePrompt(taskType: string, evaluationProfile: string, workflowSessionId?: string): string {
|
|
942
|
+
const sessionBlock = workflowSessionId ? `Workflow session:\n- workflow_session_id: ${workflowSessionId}\n\n` : "";
|
|
943
|
+
return `COMPLETION WORKFLOW DRIVER\nResume the completion workflow from canonical state.\n\nBefore acting, read:\n- ${SKILL_PATH}\n- ${REFERENCE_PATH}\n\nCanonical routing profile:\n- task_type: ${taskType}\n- evaluation_profile: ${evaluationProfile}\n\n${sessionBlock}Resume instructions:\n- Re-read .agent/state.json, .agent/startup-brief.json, .agent/plan.json, .agent/active-slice.json, and .agent/verification-evidence.json before acting.\n- If canonical state is missing, invalid, contradictory, stale, or ambiguous, route to completion-regrounder first.\n- Treat .agent/startup-brief.json as canonical intake, not as the canonical slice plan.\n- For selected, in-progress, committed, or done slices, treat .agent/active-slice.json as the canonical implementation contract and route to completion-regrounder if it drifts from the selected plan slice or the exact handoff is unclear.\n- Consume .agent/verification-evidence.json instead of temp-only verification summaries when it is populated.\n- Continue from next_mandatory_role and next_mandatory_action.\n- Use completion_role for all completion-* role work.\n- Continue dispatching mandatory roles while continuation_policy == continue.\n- Only stop for the user when continuation_policy is await_user_input, blocked, paused, or done.`;
|
|
926
944
|
}
|
|
927
945
|
|
|
928
946
|
export default function completionExtension(pi: ExtensionAPI) {
|
|
@@ -938,7 +956,7 @@ export default function completionExtension(pi: ExtensionAPI) {
|
|
|
938
956
|
structuredDiscussionFailureDetail: COOK_STRUCTURED_DISCUSSION_FAILURE_DETAIL,
|
|
939
957
|
mainChatRerunGuidance: COOK_MAIN_CHAT_RERUN_GUIDANCE,
|
|
940
958
|
cookCommandSpec: {
|
|
941
|
-
description: "/cook workflow:
|
|
959
|
+
description: "/cook workflow: start or replace workflow only from an explicit primary-agent handoff, or resume the current workflow from canonical state",
|
|
942
960
|
},
|
|
943
961
|
buildContextProposalContinuationReason,
|
|
944
962
|
completionKickoff,
|
|
@@ -951,6 +969,7 @@ export default function completionExtension(pi: ExtensionAPI) {
|
|
|
951
969
|
completionTestWorkflowMissionOverride,
|
|
952
970
|
confirmContextProposal,
|
|
953
971
|
deriveCookContextProposal,
|
|
972
|
+
deriveCookStartupProposal,
|
|
954
973
|
emitCommandText,
|
|
955
974
|
finalizeContextProposalAnalysis,
|
|
956
975
|
getCtxCwd,
|
|
@@ -972,7 +991,7 @@ export default function completionExtension(pi: ExtensionAPI) {
|
|
|
972
991
|
await refreshCompletionStatus({ ctx, ...statusSurfaceArgs });
|
|
973
992
|
if (shouldTestAutoContinueOnSessionStart()) {
|
|
974
993
|
const snapshot = await loadCompletionSnapshot(getCtxCwd(ctx));
|
|
975
|
-
if (
|
|
994
|
+
if (isCompletionWorkflowSessionTurn(snapshot, ctx)) {
|
|
976
995
|
await autoContinueWorkflowIfNeeded(pi, ctx, driverDeps);
|
|
977
996
|
}
|
|
978
997
|
}
|
|
@@ -988,19 +1007,13 @@ export default function completionExtension(pi: ExtensionAPI) {
|
|
|
988
1007
|
await fsp.rm(snapshot.files.compactionMarkerPath, { force: true });
|
|
989
1008
|
}
|
|
990
1009
|
await refreshCompletionStatus({ ctx, ...statusSurfaceArgs });
|
|
991
|
-
if (
|
|
1010
|
+
if (isCompletionWorkflowSessionTurn(snapshot, ctx)) {
|
|
992
1011
|
await autoContinueWorkflowIfNeeded(pi, ctx, driverDeps);
|
|
993
1012
|
}
|
|
994
1013
|
});
|
|
995
1014
|
|
|
996
1015
|
pi.on("before_agent_start", async (event, ctx) => {
|
|
997
1016
|
const loaded = await loadCompletionDataForReminder(getCtxCwd(ctx));
|
|
998
|
-
const driverPromptTurn = isCompletionDriverPromptTurn(ctx);
|
|
999
|
-
if (loaded && driverPromptTurn) {
|
|
1000
|
-
const rootKey = completionRootKey(loaded.snapshot, getCtxCwd(ctx));
|
|
1001
|
-
const fingerprint = completionContinuationFingerprint(loaded.snapshot);
|
|
1002
|
-
if (fingerprint) markQueuedDriverPromptInFlight(rootKey, fingerprint);
|
|
1003
|
-
}
|
|
1004
1017
|
const systemPrompt = getSystemPromptSafe(ctx);
|
|
1005
1018
|
if (!systemPrompt) return;
|
|
1006
1019
|
if (loaded && shouldInjectCompletionWorkflowContext(loaded.snapshot, ctx)) {
|
|
@@ -1025,7 +1038,7 @@ export default function completionExtension(pi: ExtensionAPI) {
|
|
|
1025
1038
|
systemPrompt: `${systemPrompt}\n\n${additions.join("\n\n")}`,
|
|
1026
1039
|
};
|
|
1027
1040
|
}
|
|
1028
|
-
if (!shouldInjectCookHandoffBoundary(event, ctx)) return;
|
|
1041
|
+
if (!shouldInjectCookHandoffBoundary(event, ctx, loaded?.snapshot)) return;
|
|
1029
1042
|
const handoffReminder = buildCookHandoffBoundaryReminder();
|
|
1030
1043
|
maybeWriteTestSnapshot(completionTestCookHandoffReminderPath(), handoffReminder);
|
|
1031
1044
|
return {
|
|
@@ -1068,7 +1081,7 @@ export default function completionExtension(pi: ExtensionAPI) {
|
|
|
1068
1081
|
const snapshot = await loadCompletionSnapshot(cwd);
|
|
1069
1082
|
const completionActive = Boolean(snapshot) && asString(snapshot?.state?.continuation_policy) !== "done";
|
|
1070
1083
|
const root = snapshot?.files.root ?? findRepoRoot(cwd) ?? cwd;
|
|
1071
|
-
const completionRoleDispatchAllowed = Boolean(role) || (
|
|
1084
|
+
const completionRoleDispatchAllowed = Boolean(role) || isCompletionWorkflowSessionTurn(snapshot, ctx);
|
|
1072
1085
|
const reason = toolCallBlockReason({
|
|
1073
1086
|
toolName: event.toolName,
|
|
1074
1087
|
input: isRecord(event.input) ? event.input : undefined,
|
|
@@ -1089,7 +1102,7 @@ export default function completionExtension(pi: ExtensionAPI) {
|
|
|
1089
1102
|
"Use completion_role when driving the completion workflow and a mandatory completion role must act next.",
|
|
1090
1103
|
"Use completion_role only for completion-bootstrapper, completion-regrounder, completion-implementer, completion-reviewer, completion-auditor, or completion-stop-judge.",
|
|
1091
1104
|
"Do not use completion_role from inside a completion role; only the workflow driver may dispatch roles.",
|
|
1092
|
-
"Do not call completion_role from ordinary chat; it is reserved for
|
|
1105
|
+
"Do not call completion_role from ordinary chat; it is reserved for active /cook workflow sessions.",
|
|
1093
1106
|
],
|
|
1094
1107
|
parameters: Type.Object({
|
|
1095
1108
|
role: StringEnum(ROLE_NAMES, { description: "The completion role to invoke." }),
|
|
@@ -72,7 +72,7 @@ export function toolCallBlockReason(args: {
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
if (toolName === "completion_role" && !completionRoleDispatchAllowed) {
|
|
75
|
-
return "completion_role may only be used from an
|
|
75
|
+
return "completion_role may only be used from an active /cook workflow session.";
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
if (toolName === "edit" || toolName === "write") {
|