@linimin/pi-letscook 0.1.56 → 0.1.58

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/CHANGELOG.md CHANGED
@@ -2,9 +2,23 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.1.58
6
+
7
+ ### Changed
8
+
9
+ - tightened implementation-ready explicit `/cook` handoffs so fresh capsules must already carry a bounded first slice, repo-change-oriented acceptance, implementation surfaces, verification commands, and why-that-slice-first structure before workflow startup
10
+ - made fresh explicit but non-startable `/cook` handoffs fail closed with a dedicated operator message instead of falling back to broader recent discussion or silently drifting into planning
11
+ - expanded regressions and public parity so valid, vague, stale, done-workflow, and negative explicit-handoff cases stay truthful across runtime behavior, docs, and `npm run release-check`
12
+
13
+ ## 0.1.57
14
+
5
15
  ### Changed
6
16
 
7
- - made `/cook` derive a confirmable startup brief from recent discussion before any canonical workflow rewrite, then preserve the confirmed brief in canonical state as advisory intake for later re-grounding
17
+ - made explicit primary-agent `/cook` handoff the preferred startup-intake path by teaching ordinary-chat handoff turns to emit a structured `cook_handoff` capsule and letting `/cook` prefer that capsule over broad context re-inference when it is fresh, valid, and implementation-startable
18
+ - tightened implementation-ready explicit handoffs so the structured capsule must already carry a bounded `first_slice_goal`, repo-change-oriented acceptance, `implementation_surfaces`, `verification_commands`, and `why_this_slice_first` before `/cook` will start workflow from it
19
+ - kept the pre-`/cook` handoff capsule as advisory startup intake only, not canonical `.agent/**` workflow state, while still using context-derived startup as the fallback only when no fresh explicit handoff is blocking startup
20
+ - kept context-derived startup as a fallback only when there is no fresh explicit handoff blocking startup, so stale or invalidated capsules can still fall back to recent discussion while fresh non-startable handoffs fail closed instead of silently rewriting canonical state
21
+ - made finished-workflow suppression stay a safety layer instead of a replacement mission when a fresh explicit `/cook` handoff exists, and blocked negative rejection/suppression text from becoming a Startable startup mission
8
22
  - removed inline `/cook` arguments from the shipped entry path again so explicit bare `/cook` is the only public command, and fail closed when recent discussion is insufficient or unreliable
9
23
  - added a pre-`/cook` ordinary-chat handoff boundary so the primary agent is instructed to stop at `/cook` once a task has matured into completion-workflow scope instead of starting long-running implementation directly in ordinary chat
10
24
 
package/README.md CHANGED
@@ -49,12 +49,16 @@ Then run `/reload` in Pi.
49
49
 
50
50
  ## What `/cook` expects
51
51
 
52
- - recent main-chat discussion about concrete repo changes
52
+ - preferably a fresh explicit primary-agent `/cook` handoff capsule from the immediately preceding ordinary-chat turn
53
+ - for that handoff capsule to start workflow immediately, it must already be implementation-startable: a bounded `first_slice_goal`, repo-change-oriented acceptance, `implementation_surfaces`, `verification_commands`, and `why_this_slice_first`
54
+ - otherwise, when no fresh explicit handoff is blocking startup, recent main-chat discussion about concrete repo changes
53
55
  - enough detail to derive a startup brief with mission, scope, constraints or non-goals, acceptance, and notes or risks
54
56
  - README/CHANGELOG updates still count as concrete repo changes
55
- - assistant-produced summaries and plan/spec/design-doc/proposal-only artifacts do not
57
+ - assistant-produced summaries and plan/spec/design-doc/proposal-only artifacts still do not count unless they include the explicit structured `/cook` handoff capsule
56
58
 
57
- If recent discussion is missing, weak, ambiguous, assistant-produced, or only describes planning artifacts instead of concrete repo changes, `/cook` fails closed, leaves canonical `.agent/**` state unchanged, and tells you to clarify the mission in the main chat before rerunning `/cook`.
59
+ If no fresh valid handoff exists and recent discussion is missing, weak, ambiguous, assistant-produced, or only describes planning artifacts instead of concrete repo changes, `/cook` fails closed, leaves canonical `.agent/**` state unchanged, and tells you to clarify the mission in the main chat before rerunning `/cook`.
60
+
61
+ If a fresh explicit handoff exists but is still workflow-worthy rather than implementation-startable, `/cook` also fails closed instead of silently treating that capsule as planning support or canonical workflow state.
58
62
 
59
63
  If you pass inline arguments to `/cook`, it also fails closed and tells you to move that intent into the main chat before rerunning bare `/cook`.
60
64
 
@@ -64,6 +68,10 @@ Only explicit `/cook` enters the workflow. Ordinary prompts stay in the main cha
64
68
 
65
69
  If a task has clearly matured into completion-workflow scope, the primary agent should hand you off to `/cook` instead of starting long-running implementation directly in ordinary chat.
66
70
 
71
+ That handoff should include an explicit structured `/cook` capsule in the assistant reply so `/cook` can confirm the already-formed mission instead of re-deriving it from broad ambient context.
72
+
73
+ The preferred capsule is still advisory startup intake, not canonical workflow state, and it only counts as implementation-ready when it already names the first bounded slice, repo-change-oriented acceptance, implementation surfaces, and verification commands.
74
+
67
75
  Important behavior:
68
76
  - `/cook` is the canonical workflow boundary and manual entry point
69
77
  - startup, refocus, and next-round routing stay confirm-first; nothing silently starts a workflow
@@ -81,13 +89,13 @@ I want to add login redirect handling and tests.
81
89
 
82
90
  ## What happens when you run `/cook`
83
91
 
84
- `/cook` first derives a startup brief from recent discussion, then shows the existing approval-only Start/Cancel gate.
92
+ `/cook` first looks for a fresh explicit primary-agent handoff capsule. If one is valid and implementation-startable, `/cook` builds the startup brief from that handoff and only uses recent discussion as validation or supplemental notes. `/cook` falls back to deriving a startup brief from recent discussion only when no fresh explicit handoff is blocking startup—for example, when there is no fresh capsule or only stale or invalidated capsules—before showing the existing approval-only Start/Cancel gate.
85
93
 
86
94
  | Repo state | What you'll see |
87
95
  |---|---|
88
- | No workflow yet | A startup brief built from recent main-chat discussion. You choose **Start** or **Cancel**. Weak, unreliable, or planning-only discussion fails closed. |
89
- | Active workflow exists | Usually a resume of the current workflow. If recent discussion clearly points to a different concrete repo change, `/cook` shows a chooser first and only rewrites canonical state after you confirm the new startup brief. Ambiguous discussion stays conservative. |
90
- | Previous workflow is `done` | A next-round startup brief from recent main-chat discussion, again behind **Start** or **Cancel**. Discussion that only restates already-finished work fails closed. |
96
+ | No workflow yet | If a fresh explicit handoff capsule exists and is implementation-startable, a startup brief built from that handoff. Otherwise, when no fresh explicit handoff is blocking startup, a startup brief built from recent main-chat discussion. You choose **Start** or **Cancel**. Weak, unreliable, stale, planning-only, or non-startable explicit-handoff intake fails closed. |
97
+ | Active workflow exists | Usually a resume of the current workflow. If a fresh explicit handoff capsule or recent discussion clearly points to a different concrete repo change, `/cook` shows a chooser first and only rewrites canonical state after you confirm the new startup brief. Ambiguous intake stays conservative. |
98
+ | Previous workflow is `done` | A fresh explicit handoff capsule can still start the next implementation round behind **Start** or **Cancel**. Without a fresh explicit handoff blocking startup, `/cook` can fall back to recent discussion. Discussion that only restates already-finished work still fails closed. |
91
99
 
92
100
  ## Confirmation and fail-closed behavior
93
101
 
@@ -96,13 +104,15 @@ I want to add login redirect handling and tests.
96
104
  - startup, next-round, and refocus proposals are approval-only
97
105
  - actions are **Start** and **Cancel**
98
106
  - **Cancel** is side-effect free: discuss changes in the main chat and rerun `/cook`
99
- - weak, ambiguous, assistant-produced, or planning-only discussion does not start a workflow
107
+ - weak, ambiguous, stale, invalid, assistant-produced, or planning-only intake does not start a workflow
100
108
  - when recent discussion suggests a different workflow, `/cook` shows a chooser before any canonical state rewrite
101
109
 
102
110
  When you accept startup or refocus, `/cook` persists the chosen workflow state in canonical `.agent/**` files before the re-ground round begins.
103
111
 
104
112
  The confirmed startup brief is also preserved there as advisory intake for later re-grounding. It does not replace `.agent/plan.json` or `.agent/active-slice.json`, which remain under regrounder authority.
105
113
 
114
+ The pre-`/cook` handoff capsule itself is not canonical workflow state. It is only startup intake for `/cook`.
115
+
106
116
  ## Observability
107
117
 
108
118
  When canonical `.agent/**` state exists and no role is actively running, the extension shows a completion widget sourced from that state. The widget summarizes:
@@ -38,7 +38,7 @@ type ContextProposalAlternate = {
38
38
  analysis: ContextProposalAnalysis;
39
39
  goalText: string;
40
40
  basisPreview: string;
41
- source: "session" | "analyst";
41
+ source: "session" | "analyst" | "handoff_capsule";
42
42
  };
43
43
 
44
44
  type ContextProposal = ContextProposalAlternate & {
@@ -55,11 +55,22 @@ type ExistingWorkflowDecision =
55
55
  | { action: "continue"; currentMissionAnchor: string }
56
56
  | { action: "refocus"; currentMissionAnchor: string; missionAnchor: string; proposal: ContextProposal };
57
57
 
58
+ type CookContextProposalResult = {
59
+ proposal?: ContextProposal;
60
+ blockedFailureMessage?: string;
61
+ };
62
+
58
63
  type ActiveWorkflowProposalAssessment = {
59
- action: "continue" | "refocus" | "unclear";
64
+ action: "continue" | "refocus" | "unclear" | "blocked";
60
65
  currentMissionAnchor: string;
61
66
  proposal?: ContextProposal;
62
- reason: "matching_mission" | "clear_refocus" | "missing_proposal" | "ambiguous_discussion";
67
+ blockedFailureMessage?: string;
68
+ reason:
69
+ | "matching_mission"
70
+ | "clear_refocus"
71
+ | "missing_proposal"
72
+ | "ambiguous_discussion"
73
+ | "fresh_explicit_handoff_not_startable";
63
74
  };
64
75
 
65
76
  type ExistingWorkflowChooserOptions = {
@@ -110,7 +121,7 @@ export type CompletionDriverDeps = {
110
121
  missionAnchor?: string,
111
122
  ) => string;
112
123
  completionResumePrompt: (taskType: string, evaluationProfile: string) => string;
113
- deriveCookContextProposal: (ctx: DriverContext, projectName: string) => Promise<ContextProposal | undefined>;
124
+ deriveCookContextProposal: (ctx: DriverContext, projectName: string) => Promise<CookContextProposalResult>;
114
125
  confirmContextProposal: (
115
126
  ctx: { hasUI: boolean; ui: any },
116
127
  proposal: ContextProposal,
@@ -305,7 +316,18 @@ async function assessActiveWorkflowProposalRouting(
305
316
  ): Promise<ActiveWorkflowProposalAssessment> {
306
317
  const currentMission = currentMissionAnchor(snapshot);
307
318
  const projectName = path.basename(snapshot.files.root);
308
- const proposal = await deps.deriveCookContextProposal(ctx, projectName);
319
+ const derived = await deps.deriveCookContextProposal(ctx, projectName);
320
+ if (derived.blockedFailureMessage) {
321
+ const assessment: ActiveWorkflowProposalAssessment = {
322
+ action: "blocked",
323
+ currentMissionAnchor: currentMission,
324
+ blockedFailureMessage: derived.blockedFailureMessage,
325
+ reason: "fresh_explicit_handoff_not_startable",
326
+ };
327
+ deps.maybeWriteActiveWorkflowRoutingSnapshot(assessment);
328
+ return assessment;
329
+ }
330
+ const proposal = derived.proposal;
309
331
  if (!proposal) {
310
332
  const assessment: ActiveWorkflowProposalAssessment = {
311
333
  action: "unclear",
@@ -519,7 +541,12 @@ export async function runCookEntry(
519
541
  if (!snapshot) {
520
542
  const root = findRepoRoot(cwd) ?? cwd;
521
543
  const projectName = path.basename(root);
522
- const proposal = await deps.deriveCookContextProposal(ctx, projectName);
544
+ const derived = await deps.deriveCookContextProposal(ctx, projectName);
545
+ if (derived.blockedFailureMessage) {
546
+ deps.emitCommandText(ctx, derived.blockedFailureMessage, "info");
547
+ return;
548
+ }
549
+ const proposal = derived.proposal;
523
550
  if (!proposal) {
524
551
  deps.emitCommandText(ctx, buildCookStructuredDiscussionFailureMessage(deps), "info");
525
552
  return;
@@ -559,7 +586,12 @@ export async function runCookEntry(
559
586
  if (!goal) {
560
587
  if (workflowDone) {
561
588
  const projectName = path.basename(snapshot.files.root);
562
- const proposal = await deps.deriveCookContextProposal(ctx, projectName);
589
+ const derived = await deps.deriveCookContextProposal(ctx, projectName);
590
+ if (derived.blockedFailureMessage) {
591
+ deps.emitCommandText(ctx, derived.blockedFailureMessage, "info");
592
+ return;
593
+ }
594
+ const proposal = derived.proposal;
563
595
  if (!proposal) {
564
596
  deps.emitCommandText(ctx, buildCookStructuredDiscussionFailureMessage(deps, "The previous completion workflow is already done."), "info");
565
597
  return;
@@ -586,6 +618,10 @@ export async function runCookEntry(
586
618
  deps.emitCommandText(ctx, `Started a new completion workflow round from recent discussion: ${decision.missionAnchor}`, "info");
587
619
  } else {
588
620
  const assessment = await assessActiveWorkflowProposalRouting(ctx, snapshot, deps);
621
+ if (assessment.action === "blocked") {
622
+ deps.emitCommandText(ctx, assessment.blockedFailureMessage ?? buildCookStructuredDiscussionFailureMessage(deps), "info");
623
+ return;
624
+ }
589
625
  if (!assessment.proposal || assessment.action === "continue") {
590
626
  await resumeActiveWorkflowFromCanonicalState(pi, ctx, snapshot, deps);
591
627
  return;
@@ -16,7 +16,9 @@ import {
16
16
  import {
17
17
  assessMissionAnchor,
18
18
  collectRecentDiscussionEntries,
19
+ collectRecentSessionMessages,
19
20
  deriveCookContextProposalFromRecentDiscussion,
21
+ assessLatestCookHandoffProposal,
20
22
  finalizeContextProposalAnalysis,
21
23
  isWeakMissionAnchor,
22
24
  missionAnchorsLikelyEquivalent,
@@ -120,11 +122,22 @@ function candidateSlices(plan: JsonRecord | undefined): JsonRecord[] {
120
122
  return Array.isArray(slices) ? slices.filter(isRecord) : [];
121
123
  }
122
124
 
125
+ type CookContextProposalResult = {
126
+ proposal?: ContextProposal;
127
+ blockedFailureMessage?: string;
128
+ };
129
+
123
130
  type ActiveWorkflowProposalAssessment = {
124
- action: "continue" | "refocus" | "unclear";
131
+ action: "continue" | "refocus" | "unclear" | "blocked";
125
132
  currentMissionAnchor: string;
126
133
  proposal?: ContextProposal;
127
- reason: "matching_mission" | "clear_refocus" | "missing_proposal" | "ambiguous_discussion";
134
+ blockedFailureMessage?: string;
135
+ reason:
136
+ | "matching_mission"
137
+ | "clear_refocus"
138
+ | "missing_proposal"
139
+ | "ambiguous_discussion"
140
+ | "fresh_explicit_handoff_not_startable";
128
141
  };
129
142
 
130
143
  function completionTestWorkflowActionOverride(): "continue" | "refocus" | "cancel" | undefined {
@@ -269,6 +282,7 @@ function maybeWriteActiveWorkflowRoutingSnapshot(assessment: ActiveWorkflowPropo
269
282
  action: assessment.action,
270
283
  reason: assessment.reason,
271
284
  currentMissionAnchor: assessment.currentMissionAnchor,
285
+ blockedFailureMessage: assessment.blockedFailureMessage ?? null,
272
286
  proposedMissionAnchor: assessment.proposal?.mission ?? null,
273
287
  proposalSource: assessment.proposal?.source ?? null,
274
288
  possibleNoise: assessment.proposal?.analysis.possibleNoise ?? [],
@@ -363,8 +377,12 @@ async function promptContextProposalConfirmationAction(
363
377
  async function deriveCookContextProposal(
364
378
  ctx: { cwd: string; hasUI: boolean; ui: any; sessionManager: any; model?: any; modelRegistry?: any },
365
379
  projectName: string,
366
- ): Promise<ContextProposal | undefined> {
367
- const recentEntries = collectRecentDiscussionEntries(ctx, { isRecord, asString, isStaleContextError });
380
+ ): Promise<CookContextProposalResult> {
381
+ const recentMessages = collectRecentSessionMessages(ctx, { isRecord, asString, asNumber, isStaleContextError });
382
+ const recentEntries = recentMessages
383
+ .filter((entry) => (entry.role === "user" || entry.role === "custom") && !entry.isCommand)
384
+ .slice(0, 8)
385
+ .map((entry) => ({ role: entry.role, text: entry.text }));
368
386
  const snapshot = await loadCompletionSnapshot(getCtxCwd(ctx));
369
387
  const workflowContextLines = snapshot
370
388
  ? [
@@ -378,41 +396,56 @@ async function deriveCookContextProposal(
378
396
  `verification summary: ${asString(snapshot.verificationEvidence?.summary) ?? "(none)"}`,
379
397
  ]
380
398
  : [];
381
- return await deriveCookContextProposalFromRecentDiscussion(projectName, recentEntries, {
399
+ const explicitHandoff = assessLatestCookHandoffProposal(recentMessages, projectName, {
382
400
  asString,
383
401
  asStringArray,
384
- workflowContext: snapshot
385
- ? {
386
- currentMissionAnchor:
387
- asString(snapshot.state?.mission_anchor) ?? asString(snapshot.plan?.mission_anchor) ?? asString(snapshot.active?.mission_anchor),
388
- latestCompletedSlice: asString(snapshot.state?.latest_completed_slice),
389
- latestVerifiedSlice: asString(snapshot.state?.latest_verified_slice),
390
- activeSliceGoal: asString(snapshot.active?.goal),
391
- activeSliceWhyNow: asString(snapshot.active?.why_now),
392
- verificationGoal: asString(snapshot.verificationEvidence?.goal),
393
- verificationSummary: asString(snapshot.verificationEvidence?.summary),
394
- continuationPolicy: asString(snapshot.state?.continuation_policy),
395
- }
396
- : undefined,
397
- analyzeContextProposal: async (entries) =>
398
- await analyzeContextProposalWithAgent({
399
- ctx,
400
- projectName,
401
- recentEntries: entries,
402
- workflowContextLines,
403
- liveRoleActivityByRoot,
404
- completionStatusKey: COMPLETION_STATUS_KEY,
405
- safeUiCall,
406
- getCtxCwd,
407
- getCtxHasUI,
408
- getCtxUi,
409
- }),
410
402
  assessMissionAnchor,
403
+ normalizeMissionAnchorText,
411
404
  isWeakMissionAnchor,
412
405
  missionAnchorsStrictlyEquivalent,
413
- normalizeMissionAnchorText,
414
406
  stripCodeBlocks,
415
407
  });
408
+ if (explicitHandoff.status === "startable") return { proposal: explicitHandoff.proposal };
409
+ if (explicitHandoff.status === "fresh_but_not_startable") {
410
+ return { blockedFailureMessage: explicitHandoff.message };
411
+ }
412
+ return {
413
+ proposal: await deriveCookContextProposalFromRecentDiscussion(projectName, recentEntries, {
414
+ asString,
415
+ asStringArray,
416
+ workflowContext: snapshot
417
+ ? {
418
+ currentMissionAnchor:
419
+ asString(snapshot.state?.mission_anchor) ?? asString(snapshot.plan?.mission_anchor) ?? asString(snapshot.active?.mission_anchor),
420
+ latestCompletedSlice: asString(snapshot.state?.latest_completed_slice),
421
+ latestVerifiedSlice: asString(snapshot.state?.latest_verified_slice),
422
+ activeSliceGoal: asString(snapshot.active?.goal),
423
+ activeSliceWhyNow: asString(snapshot.active?.why_now),
424
+ verificationGoal: asString(snapshot.verificationEvidence?.goal),
425
+ verificationSummary: asString(snapshot.verificationEvidence?.summary),
426
+ continuationPolicy: asString(snapshot.state?.continuation_policy),
427
+ }
428
+ : undefined,
429
+ analyzeContextProposal: async (entries) =>
430
+ await analyzeContextProposalWithAgent({
431
+ ctx,
432
+ projectName,
433
+ recentEntries: entries,
434
+ workflowContextLines,
435
+ liveRoleActivityByRoot,
436
+ completionStatusKey: COMPLETION_STATUS_KEY,
437
+ safeUiCall,
438
+ getCtxCwd,
439
+ getCtxHasUI,
440
+ getCtxUi,
441
+ }),
442
+ assessMissionAnchor,
443
+ isWeakMissionAnchor,
444
+ missionAnchorsStrictlyEquivalent,
445
+ normalizeMissionAnchorText,
446
+ stripCodeBlocks,
447
+ }),
448
+ };
416
449
  }
417
450
 
418
451
  async function confirmContextProposal(
@@ -10,7 +10,7 @@ import type {
10
10
 
11
11
  export type AdvisoryStartupBrief = {
12
12
  kind: "startup_brief";
13
- source: "recent_discussion";
13
+ source: "recent_discussion" | "primary_agent_handoff";
14
14
  confirmed: true;
15
15
  captured_at: string;
16
16
  goal_text: string;
@@ -31,7 +31,12 @@ export function buildCookHandoffBoundaryReminder(): string {
31
31
  "/cook is the only explicit entrypoint into long-running completion workflow.",
32
32
  "When you judge that the task has matured into completion-workflow scope — for example the user has clearly shifted from exploration into implementation intent, you have just produced a concrete plan or proposal whose next step would naturally be implementation, or the task spans multiple files, steps, or verification surfaces — stop short of long-running implementation and tell the user to run /cook.",
33
33
  "At that handoff point, do not begin long-running product implementation in ordinary chat, do not edit tracked product files for that workflow-level task, and do not act as though /cook had already been invoked.",
34
- "When handing off, explain that /cook will derive a startup brief from recent discussion and ask for confirmation before workflow start.",
34
+ "Distinguish a workflow-worthy handoff from an implementation-ready handoff: only emit the implementation-ready capsule when the first bounded implementation slice is concrete enough to start immediately.",
35
+ "When handing off, explain that /cook will first look for a fresh explicit primary-agent handoff capsule and otherwise fall back to recent discussion.",
36
+ "If the task is workflow-worthy but that first slice is still vague, tell the user to run /cook without emitting an implementation-ready capsule yet.",
37
+ "Otherwise append one exact fenced block in the same assistant reply using ```cook_handoff ... ``` JSON with kind/source/handoff_kind plus mission, scope, constraints or non_goals, acceptance, risks, notes, captured_at, source_turn_id, first_slice_goal, first_slice_non_goals, implementation_surfaces, verification_commands, why_this_slice_first, and optional task_type/evaluation_profile/why_cook_now.",
38
+ "Use handoff_kind implementation_workflow_handoff for that implementation-ready capsule.",
39
+ "The capsule is startup intake for /cook only: do not present it as canonical .agent state, an active slice, or a persistent repo contract.",
35
40
  "If the task is still ordinary Q&A, lightweight brainstorming, or a tiny one-off fix, continue normally without forcing /cook.",
36
41
  ].join(" ");
37
42
  }
@@ -84,13 +89,13 @@ function buildAdvisoryStartupBriefNotes(analysis: ContextProposalAnalysis): stri
84
89
  }
85
90
 
86
91
  export function buildAdvisoryStartupBrief(args: {
87
- proposal: Pick<ContextProposal, "goalText" | "mission" | "scope" | "constraints" | "acceptance">;
92
+ proposal: Pick<ContextProposal, "goalText" | "mission" | "scope" | "constraints" | "acceptance" | "source">;
88
93
  analysis: ContextProposalAnalysis;
89
94
  capturedAt?: string;
90
95
  }): AdvisoryStartupBrief {
91
96
  return {
92
97
  kind: "startup_brief",
93
- source: "recent_discussion",
98
+ source: args.proposal.source === "handoff_capsule" ? "primary_agent_handoff" : "recent_discussion",
94
99
  confirmed: true,
95
100
  captured_at: args.capturedAt ?? new Date().toISOString(),
96
101
  goal_text: args.proposal.goalText,