@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.
@@ -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
- | "matching_generated_startup_plan"
138
- | "no_generated_startup_plan"
139
- | "generated_replacement_startup_plan"
140
- | "generated_startup_plan_not_startable";
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 startup-plan step could not prepare a concrete workflow startup plan from the current task context. Clarify the mission, scope, acceptance, or verification intent in the main chat, then rerun /cook.";
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(root: string | undefined): void {
221
- if (!root) return;
222
- activatedCompletionRoutingRoots.add(path.resolve(root));
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 activatedCompletionRoutingRoots.has(path.resolve(snapshot.files.root));
228
+ return false;
229
229
  }
230
230
 
231
231
  function latestUserOrCustomTurnText(ctx: { sessionManager?: any }): string | undefined {
232
- return collectRecentDiscussionEntries(ctx as { sessionManager: any }, { isRecord, asString, isStaleContextError }, 1)[0]?.text;
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
- if (!/^\/skill:completion-protocol\b/.test(latest)) return false;
239
- return /(?:Start or continue the completion workflow for this repo\.|Resume the completion workflow from canonical state\.)/.test(latest);
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 hasCompletionRoutingActivation(snapshot) && isCompletionDriverPromptTurn(ctx);
266
+ return isCompletionWorkflowSessionTurn(snapshot, ctx);
244
267
  }
245
268
 
246
- function shouldInjectCookHandoffBoundary(event: { prompt?: string }, ctx: { sessionManager?: any }): boolean {
269
+ function shouldInjectCookHandoffBoundary(
270
+ event: { prompt?: string },
271
+ ctx: { sessionManager?: any },
272
+ snapshot?: CompletionStateSnapshot,
273
+ ): boolean {
247
274
  if (roleFromEnv()) return false;
248
- if (isCompletionDriverPromptTurn(ctx)) return false;
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 = assessCookHandoffText(raw, projectName, {
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/startup-plan.json, .agent/plan.json, .agent/active-slice.json, .agent/slice-history.jsonl, .agent/stop-check-history.jsonl, and .agent/verification-evidence.json from disk.",
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
- return `/skill:completion-protocol Start 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\nUser goal:\n${goal}\n\n${intentBlock}Driver instructions:\n- Canonical truth is in .agent/**. Re-read .agent/state.json, .agent/startup-plan.json, .agent/plan.json, .agent/active-slice.json, and .agent/verification-evidence.json before acting when they exist.\n- Treat .agent/startup-plan.json as the approved startup plan captured at /cook. completion-regrounder should use it as planning input, then derive canonical slices from current repo truth.\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- 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.`;
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
- return `/skill:completion-protocol Resume 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\nResume instructions:\n- Re-read .agent/state.json, .agent/startup-plan.json, .agent/plan.json, .agent/active-slice.json, and .agent/verification-evidence.json before acting.\n- Treat .agent/startup-plan.json as the approved workflow plan captured at /cook, then let completion-regrounder reconcile or rebuild canonical slices from repo truth when needed.\n- If canonical state is missing, invalid, contradictory, stale, or ambiguous, route to completion-regrounder first.\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.`;
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: capture the approved startup plan into .agent, let completion-regrounder split canonical slices, or resume the current workflow from canonical state",
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 (hasCompletionRoutingActivation(snapshot) && isCompletionDriverPromptTurn(ctx)) {
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 (hasCompletionRoutingActivation(snapshot) && isCompletionDriverPromptTurn(ctx)) {
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) || (hasCompletionRoutingActivation(snapshot) && isCompletionDriverPromptTurn(ctx));
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 explicit /cook workflow driver turns.",
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 explicit /cook workflow driver turn.";
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") {