@mgiles/perk 3.1.0 → 3.3.0

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.
Files changed (211) hide show
  1. package/README.md +5 -0
  2. package/extension/authoring/gist/draft.ts +198 -0
  3. package/extension/authoring/gist/prose.ts +46 -0
  4. package/extension/authoring/gist/review.ts +133 -0
  5. package/extension/authoring/gist/save.ts +118 -0
  6. package/extension/authoring/objective/draft.ts +345 -0
  7. package/extension/{factories/objectiveDreamReport.ts → authoring/objective/dreamReportGate.ts} +74 -131
  8. package/extension/authoring/objective/planning.ts +124 -0
  9. package/extension/authoring/objective/prose.ts +103 -0
  10. package/extension/authoring/objective/review.ts +128 -0
  11. package/extension/authoring/objective/save.ts +224 -0
  12. package/extension/authoring/plan/draft.ts +84 -0
  13. package/extension/authoring/plan/prose.ts +41 -0
  14. package/extension/authoring/plan/review.ts +269 -0
  15. package/extension/authoring/plan/save.ts +256 -0
  16. package/extension/authoring/plan/source.ts +82 -0
  17. package/extension/authoring/refinement/context.ts +468 -0
  18. package/extension/authoring/refinement/draft.ts +261 -0
  19. package/extension/authoring/refinement/prose.ts +79 -0
  20. package/extension/authoring/refinement/review.ts +111 -0
  21. package/extension/authoring/refinement/save.ts +119 -0
  22. package/extension/authoring/review/approvalGate.ts +34 -0
  23. package/extension/authoring/review/draftContext.ts +68 -0
  24. package/extension/codeReview/automated.ts +352 -0
  25. package/extension/codeReview/submission.ts +229 -0
  26. package/extension/delivery/address.ts +295 -0
  27. package/extension/delivery/ci.ts +355 -0
  28. package/extension/delivery/commitCompact.ts +93 -0
  29. package/extension/delivery/conflictResolution.ts +247 -0
  30. package/extension/delivery/ready.ts +193 -0
  31. package/extension/delivery/stackConflict.ts +361 -0
  32. package/extension/delivery/stackObjective.ts +16 -0
  33. package/extension/delivery/stackReconcile.ts +165 -0
  34. package/extension/delivery/submit.ts +171 -0
  35. package/extension/index.ts +369 -359
  36. package/extension/learning/analystWave.ts +324 -0
  37. package/extension/learning/audit.ts +667 -0
  38. package/extension/learning/capture.ts +92 -0
  39. package/extension/learning/containment.ts +104 -0
  40. package/extension/{waves/dreamWave.ts → learning/dream.ts} +112 -94
  41. package/extension/learning/dreamAnalysis.ts +435 -0
  42. package/extension/{waves/dreamReducerWave.ts → learning/dreamReducer.ts} +46 -41
  43. package/extension/{waves → learning}/dreamReport.ts +35 -31
  44. package/extension/learning/harvest.ts +491 -0
  45. package/extension/learning/prose.ts +66 -0
  46. package/extension/learning/routing.ts +79 -0
  47. package/extension/pi/v1/bashScanTimeout.ts +64 -0
  48. package/extension/{doors/prReview.ts → pi/v1/codeReview/automated.ts} +215 -310
  49. package/extension/{doors/prReviewBrowser.ts → pi/v1/codeReview/browser.ts} +125 -57
  50. package/extension/{doors/hunkHandoff.ts → pi/v1/codeReview/checkout.ts} +12 -8
  51. package/extension/{doors/reviewWaveTools.ts → pi/v1/codeReview/reviewWave.ts} +169 -116
  52. package/extension/pi/v1/codeReview/stack.ts +606 -0
  53. package/extension/pi/v1/codeReview/submit.ts +354 -0
  54. package/extension/{doors/prReviewTerminal.ts → pi/v1/codeReview/terminal.ts} +32 -27
  55. package/extension/pi/v1/contextEvidence.ts +80 -0
  56. package/extension/pi/v1/contextInjection.ts +207 -0
  57. package/extension/{doors → pi/v1/delivery}/address.ts +163 -265
  58. package/extension/pi/v1/delivery/ci.ts +570 -0
  59. package/extension/pi/v1/delivery/commitCompact.ts +201 -0
  60. package/extension/pi/v1/delivery/conflictResolverEngine.ts +425 -0
  61. package/extension/{doors → pi/v1/delivery}/land.ts +128 -60
  62. package/extension/pi/v1/delivery/ready.ts +322 -0
  63. package/extension/pi/v1/delivery/stackConflictResolver.ts +172 -0
  64. package/extension/pi/v1/delivery/stackDrive.ts +120 -0
  65. package/extension/pi/v1/delivery/stackLand.ts +223 -0
  66. package/extension/pi/v1/delivery/stackRecover.ts +265 -0
  67. package/extension/pi/v1/delivery/stackStatus.ts +237 -0
  68. package/extension/pi/v1/delivery/stackSync.ts +658 -0
  69. package/extension/pi/v1/delivery/submit.ts +389 -0
  70. package/extension/pi/v1/delivery/submitConflict.ts +186 -0
  71. package/extension/pi/v1/draftReview.ts +431 -0
  72. package/extension/{doors → pi/v1}/draftReviewWaveTools.ts +141 -151
  73. package/extension/pi/v1/gist.ts +794 -0
  74. package/extension/pi/v1/learning/audit.ts +186 -0
  75. package/extension/pi/v1/learning/dream.ts +207 -0
  76. package/extension/{doors/learnFactory.ts → pi/v1/learning/factory.ts} +18 -65
  77. package/extension/{doors/harvestWaveTools.ts → pi/v1/learning/harvest.ts} +46 -100
  78. package/extension/pi/v1/learning/learn.ts +585 -0
  79. package/extension/{doors → pi/v1}/lifecycleGates.ts +14 -59
  80. package/extension/{factories → pi/v1}/objective.ts +53 -33
  81. package/extension/pi/v1/objectiveAuthoring.ts +672 -0
  82. package/extension/pi/v1/objectiveDreamGate.ts +160 -0
  83. package/extension/pi/v1/objectivePlanning.ts +776 -0
  84. package/extension/pi/v1/objectiveRefinement.ts +1320 -0
  85. package/extension/pi/v1/objectiveReview.ts +451 -0
  86. package/extension/{doors → pi/v1}/objectiveReviewBrowser.ts +259 -172
  87. package/extension/pi/v1/plan.ts +812 -0
  88. package/extension/pi/v1/planReview.ts +820 -0
  89. package/extension/{doors → pi/v1}/planReviewBrowser.ts +228 -152
  90. package/extension/{doors/annotationPush.ts → pi/v1/providers/annotations.ts} +158 -89
  91. package/extension/pi/v1/providers/plannotator.ts +487 -0
  92. package/extension/{doors → pi/v1/providers}/plannotatorHandoff.ts +152 -34
  93. package/extension/pi/v1/providers/selection.ts +43 -0
  94. package/extension/{adapters/planAdapterTombell.ts → pi/v1/providers/tombell.ts} +43 -72
  95. package/extension/pi/v1/review.ts +538 -0
  96. package/extension/pi/v1/reviewOutcome.ts +9 -0
  97. package/extension/pi/v1/scoutWave.ts +318 -0
  98. package/extension/{doors → pi/v1}/selfcheck.ts +4 -4
  99. package/extension/session/branchWorkflowSession.ts +60 -0
  100. package/extension/session/lifecycle.ts +644 -0
  101. package/extension/session/lifecycleGates.ts +64 -0
  102. package/extension/session/saveDestination.ts +87 -0
  103. package/extension/session/workflowSession.ts +971 -0
  104. package/extension/substrate/agentScratch.ts +27 -54
  105. package/extension/substrate/bashScanTimeout.ts +181 -0
  106. package/extension/substrate/bindingDelivery.ts +38 -30
  107. package/extension/substrate/bindings.ts +4 -5
  108. package/extension/substrate/cache.ts +64 -12
  109. package/extension/substrate/childRestrictions.ts +39 -0
  110. package/extension/substrate/coldDoor.ts +17 -1
  111. package/extension/substrate/config.ts +157 -19
  112. package/extension/substrate/git.ts +88 -6
  113. package/extension/substrate/modelVisible.ts +53 -0
  114. package/extension/substrate/paths.ts +2 -7
  115. package/extension/substrate/prompts.ts +22 -0
  116. package/extension/substrate/registry.ts +2 -0
  117. package/extension/substrate/resolverLease.ts +364 -0
  118. package/extension/substrate/sessionData.ts +85 -152
  119. package/extension/substrate/toolGating.ts +279 -84
  120. package/extension/substrate/unifiedDiff.ts +1 -1
  121. package/extension/substrate/workflowState.ts +191 -166
  122. package/extension/substrate/worktreeResolverLock.ts +261 -0
  123. package/extension/surfaces/surfaces.ts +79 -27
  124. package/extension/waves/adversarialReviewWave.ts +103 -48
  125. package/extension/waves/blockedReports.ts +59 -0
  126. package/extension/waves/draftReviewWave.ts +42 -42
  127. package/extension/waves/laneIdentity.ts +77 -0
  128. package/extension/waves/objectiveExplorerWave.ts +24 -24
  129. package/extension/waves/prReviewWave.ts +89 -77
  130. package/extension/waves/reportWave.ts +438 -578
  131. package/extension/waves/reviewClassifierWave.ts +22 -22
  132. package/extension/waves/rpcAdapter.ts +100 -15
  133. package/extension/waves/scoutWave.ts +192 -0
  134. package/extension/waves/transport.ts +480 -0
  135. package/extension/worker/sdkAdapter.ts +494 -0
  136. package/extension/worker/stageExecution.ts +679 -0
  137. package/extension/workerMain.ts +18 -19
  138. package/package.json +6 -4
  139. package/prompts/_fixtures/live.yaml +98 -10
  140. package/prompts/contexts/adapters/plannotator-gist.md +6 -0
  141. package/prompts/contexts/adapters/plannotator-objective.md +6 -0
  142. package/prompts/contexts/adapters/plannotator-plan.md +8 -1
  143. package/prompts/contexts/adapters/plannotator-refinement.md +22 -0
  144. package/prompts/contexts/adapters/tombell-plan.md +4 -0
  145. package/prompts/contexts/objective-refinement.md +17 -0
  146. package/prompts/contexts/plan-authoring.md +6 -5
  147. package/prompts/contexts/read-only.md +1 -1
  148. package/prompts/stages/conflict-resolution-continuation.md +9 -0
  149. package/prompts/stages/conflict-resolution.md +4 -4
  150. package/prompts/stages/objective-author/adopt.md +1 -1
  151. package/prompts/stages/objective-author/file.md +1 -1
  152. package/prompts/stages/objective-author/seed.md +1 -1
  153. package/prompts/stages/objective-plan/guidance.md +2 -2
  154. package/prompts/stages/objective-plan/seed.md +9 -1
  155. package/prompts/stages/objective-reconcile-ready.md +7 -0
  156. package/prompts/stages/objective-reconcile.md +1 -1
  157. package/prompts/stages/objective-refine/seed.md +18 -0
  158. package/prompts/stages/objective-review-browser.md +4 -4
  159. package/prompts/stages/objective-sync.md +1 -1
  160. package/prompts/stages/plan-review-browser.md +4 -4
  161. package/prompts/stages/pr-review-browser/active.md +3 -4
  162. package/prompts/stages/pr-review-browser/foreign.md +3 -4
  163. package/prompts/stages/pr-review-terminal/active.md +3 -3
  164. package/prompts/stages/pr-review-terminal/foreign.md +3 -3
  165. package/prompts/stages/pr-review.md +3 -3
  166. package/prompts/stages/stack-review/cold.md +1 -0
  167. package/prompts/stages/stack-review-browser/stack.md +22 -0
  168. package/shared/README.md +8 -3
  169. package/shared/bindings.yaml +6 -3
  170. package/shared/contracts.md +4499 -2147
  171. package/shared/fixtures/issues-table.json +130 -0
  172. package/shared/registry.yaml +29 -1
  173. package/shared/schemas/outputs/objective-node-engagement.schema.json +318 -0
  174. package/shared/schemas/outputs/objective-stack-status.schema.json +178 -2
  175. package/shared/schemas/outputs/pr-ready.schema.json +110 -2
  176. package/shared/schemas/outputs/pr-review-context.schema.json +54 -9
  177. package/shared/schemas/outputs/pr-review-stack-context.schema.json +196 -0
  178. package/extension/adapters/planAdapterPlannotator.ts +0 -362
  179. package/extension/doors/auditWaveTools.ts +0 -352
  180. package/extension/doors/ciExecutor.ts +0 -756
  181. package/extension/doors/commitCompact.ts +0 -251
  182. package/extension/doors/dreamWaveTools.ts +0 -475
  183. package/extension/doors/learn.ts +0 -655
  184. package/extension/doors/objectiveStack.ts +0 -1143
  185. package/extension/doors/prReviewDynamic.ts +0 -276
  186. package/extension/doors/ready.ts +0 -87
  187. package/extension/doors/submit.ts +0 -347
  188. package/extension/doors/submitPrReview.ts +0 -408
  189. package/extension/factories/gistAuthor.ts +0 -94
  190. package/extension/factories/gistDraft.ts +0 -265
  191. package/extension/factories/gistSave.ts +0 -251
  192. package/extension/factories/implementHere.ts +0 -116
  193. package/extension/factories/objectiveAuthor.ts +0 -98
  194. package/extension/factories/objectiveDraft.ts +0 -466
  195. package/extension/factories/objectivePlan.ts +0 -975
  196. package/extension/factories/objectiveSave.ts +0 -363
  197. package/extension/factories/planDraft.ts +0 -140
  198. package/extension/factories/planMode.ts +0 -205
  199. package/extension/factories/planReview.ts +0 -1237
  200. package/extension/factories/planSave.ts +0 -604
  201. package/extension/factories/planTitle.ts +0 -141
  202. package/extension/substrate/structuredOutput.ts +0 -202
  203. package/extension/waves/auditWave.ts +0 -312
  204. package/extension/waves/harvestWave.ts +0 -399
  205. package/extension/waves/learnWave.ts +0 -155
  206. package/extension/waves/memoryAdapter.ts +0 -139
  207. package/extension/waves/prReviewDynamicWave.ts +0 -777
  208. package/extension/worker/readOnlySession.ts +0 -294
  209. package/extension/worker/worker.ts +0 -899
  210. package/prompts/stages/pr-review-dynamic.md +0 -7
  211. package/shared/contracts-history.md +0 -605
@@ -0,0 +1,207 @@
1
+ // The one marker-dedup'd context-injection mechanism (contracts §8.31 semantics) behind the
2
+ // six injected authoring/adapter contexts: gist, plan, objective-authoring, refinement and the
3
+ // plannotator/tombell plan adapters all register the same `before_agent_start` + `context` hook
4
+ // pair around one injected, marker-dedup'd context. The MECHANICS live here — the live-evidence
5
+ // dedup, the selection-driven retention filter, the guarded reads, the runner fence. Callers keep
6
+ // flavor selection + content construction (the customType/marker vocabulary) in their
7
+ // `InjectedContextSpec` closures; `isPlanGuidanceStage` is the one shared stage predicate (the
8
+ // plan context and the two plan-adapter plan flavors).
9
+ //
10
+ // ONE decision drives both hooks: `spec.select` (the flavor to deliver this turn, or null). It
11
+ // reads the FULL branch (`branchOf` — eligibility/state survive compaction), while the dedup
12
+ // reads Pi's OWN live projection (`contextEvidence.ts`: `buildContextEntries()` → native
13
+ // messages) and asks the typed predicate whether the selected flavor's marker is still delivered
14
+ // — as user content (a cold prompt) or as the owned customType's content (a prior hidden copy).
15
+ // A copy Pi has compacted out of context re-injects on the next turn even though the historical
16
+ // entry still sits on the branch; a summary quoting the marker never counts. The submitting
17
+ // `event.prompt` is checked BEFORE the projection read: at `before_agent_start` a cold launch's
18
+ // prompt is not yet persisted, so only that check sees a cold seed on the launch turn.
19
+ //
20
+ // RETENTION follows SELECTION on the `context` event, and touches ONLY the owned customType: a
21
+ // null selection (ineligible, or eligibility could not be established — a failed branch read or
22
+ // a throwing selector) removes every owned custom message; a selected flavor retains only the
23
+ // owned copies carrying that flavor's marker and removes obsolete sibling flavors (a
24
+ // plan→objective transition under plannotator cannot retain the old plan-adapter instructions).
25
+ // User/task messages are NEVER removed for carrying an owned marker — a cold seed, a quoted
26
+ // marker, an `<untrusted_draft>` body are the human's/the door's input and stay byte-for-byte;
27
+ // assistant/tool messages and other features' custom messages are never inspected. This filters
28
+ // the OUTGOING model context only: persisted transcripts and compaction summaries are never
29
+ // rewritten.
30
+ //
31
+ // Failure semantics: a failed branch read short-circuits INJECTION (no `select` call — an
32
+ // empty-branch fallback would wrongly inject for a selector whose reads pass on `undefined`); a
33
+ // failed projection read also returns without constructing or injecting content (a guessed copy
34
+ // could double-deliver); on the `context` event the same failure fails CLOSED to "nothing
35
+ // selected" — stale owned guidance must never survive an unreadable branch. Retention never
36
+ // reads the projection.
37
+ //
38
+ // Deliberate NON-callers keep their own scan/strip semantics: `substrate/bindingDelivery.ts`
39
+ // (strips only its own customType — never user turns; projection errors escape its hook),
40
+ // `substrate/agentScratch.ts` (requires the exact current custom block, not a marker scan),
41
+ // `substrate/toolGating.ts` (full-branch scan — the strict once-per-selected-branch read-only
42
+ // marker; its `perk:mode-context` retention is independent of every authoring context here),
43
+ // and `hunkFeedback/receiver.ts`.
44
+
45
+ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
46
+ import { GIST_AUTHOR_STAGE } from "../../authoring/gist/draft.ts";
47
+ import { OBJECTIVE_AUTHOR_STAGE, OBJECTIVE_SAVE_STAGE } from "../../authoring/objective/prose.ts";
48
+ import { REFINE_STAGE } from "../../authoring/refinement/context.ts";
49
+ import { type BranchEntry, branchOf } from "../../substrate/workflowState.ts";
50
+ import {
51
+ activeContextMessages,
52
+ type ContextMessage,
53
+ contextCarriesMarker,
54
+ } from "./contextEvidence.ts";
55
+
56
+ /**
57
+ * The stages plan guidance is withheld from, for one of two reasons: another authoring context
58
+ * OWNS the stage (the objective/gist/refinement installers inject their own contexts;
59
+ * `objective-save` rides `plan_review`'s objective-arm routing), or the stage is read-only but
60
+ * authors nothing (`audit`, the dev-only `perk-dev audit judge` door — a literal: it has no
61
+ * authoring module). The registry guard test pins the set against the read-only stages.
62
+ */
63
+ const PLAN_GUIDANCE_EXCLUDED_STAGES: ReadonlySet<string> = new Set([
64
+ OBJECTIVE_AUTHOR_STAGE,
65
+ OBJECTIVE_SAVE_STAGE,
66
+ GIST_AUTHOR_STAGE,
67
+ REFINE_STAGE,
68
+ "audit",
69
+ ]);
70
+
71
+ /**
72
+ * Plan guidance rides the read-only gate in every stage not excluded above: the stage-less warm
73
+ * `/plan` (`undefined`), the cold `plan`/`objective-plan` claims, and any other (unknown or
74
+ * read-write) stage id — fail-open like the stage tool filter.
75
+ */
76
+ export function isPlanGuidanceStage(stage: string | undefined): boolean {
77
+ return stage === undefined || !PLAN_GUIDANCE_EXCLUDED_STAGES.has(stage);
78
+ }
79
+
80
+ /**
81
+ * One marker-dedup'd injected context: the owned customType + the flavor table. Each flavor is
82
+ * declared ONCE — the marker literal keys its content thunk — so the retention key, the dedup
83
+ * key (the selected key), and the injected content cannot drift apart: `select` returns a KEY of
84
+ * the table, never a free-floating marker/content pair.
85
+ */
86
+ export interface InjectedContextSpec<K extends string = string> {
87
+ customType: string;
88
+ /**
89
+ * The flavor table: marker literal → content thunk (plannotator: all three flavor markers).
90
+ * A thunk is invoked ONLY after the dedup scan passes, preserving the scan-before-construct
91
+ * ordering (config reads/renders never run on dedup-suppressed turns). Caller contract: the
92
+ * rendered content carries its own key (the marker rides inside the template bytes — pinned by
93
+ * each caller's content tests); a marker-less content would defeat the dedup (re-inject every
94
+ * turn) AND the retention (an owned copy without its flavor's marker is removed as obsolete).
95
+ */
96
+ flavors: Readonly<Record<K, (ctx: ExtensionContext) => string>>;
97
+ /**
98
+ * Feature policy — the ONE eligibility/retention decision: the flavor to deliver this turn (a
99
+ * `flavors` key), or null (ineligible/defer). Drives injection on `before_agent_start` AND the
100
+ * owned-message retention on `context` (null removes every owned copy; a key retains only that
101
+ * flavor's copies).
102
+ */
103
+ select(ctx: ExtensionContext, branch: readonly BranchEntry[]): K | null;
104
+ }
105
+
106
+ /** The structural message slice the retention filter inspects. */
107
+ interface OwnedMessage {
108
+ customType?: string;
109
+ content?: unknown;
110
+ }
111
+
112
+ /**
113
+ * The owned-message retention filter (module-private — tests drive it through the registered
114
+ * hook): messages of the owned customType survive only when a flavor is selected AND their
115
+ * content carries that flavor's marker (a string-content or text-part scan of the OWNED copy
116
+ * only); every other message — any role, any other customType — passes through untouched, marker
117
+ * or not.
118
+ */
119
+ function retainSelectedFlavor<T>(messages: T[], customType: string, marker: string | null): T[] {
120
+ return messages.filter((m) => {
121
+ const msg = m as OwnedMessage;
122
+ if (msg.customType !== customType) return true;
123
+ return marker !== null && carriesMarker(msg.content, marker);
124
+ });
125
+ }
126
+
127
+ function carriesMarker(content: unknown, marker: string): boolean {
128
+ if (typeof content === "string") return content.includes(marker);
129
+ if (Array.isArray(content)) {
130
+ return content.some(
131
+ (c) =>
132
+ (c as { type?: string; text?: string }).type === "text" &&
133
+ ((c as { text?: string }).text ?? "").includes(marker),
134
+ );
135
+ }
136
+ return false;
137
+ }
138
+
139
+ /**
140
+ * Register the inject/retain hook pair for one marker-dedup'd context (contracts §8.31
141
+ * semantics). Call at the exact registration position the replaced hook pair held — hook
142
+ * ordering is frozen by each installer's internal sequence.
143
+ *
144
+ * - `before_agent_start`: guarded branch read (a failed read short-circuits — no `select` call,
145
+ * no injection) → the runner fence (a runner child selects nothing — no `select` call) →
146
+ * `spec.select` (null → no injection) → the submitting prompt carrying the SELECTED marker
147
+ * suppresses (cold delivery before persistence; another flavor's marker does not) → guarded
148
+ * projection read (a failed read returns — nothing constructed, nothing injected) → a live
149
+ * owned copy of the selected marker suppresses (the content thunk is never invoked) → inject
150
+ * `{ customType, content, display: false }`.
151
+ * - `context`: guarded branch read + the same fence + `spec.select` (a failed read or a throwing
152
+ * selector fails closed to null) → retain the owned customType's copies carrying the selected
153
+ * flavor's marker only; remove every other owned copy (all of them on null). No other message
154
+ * is inspected.
155
+ *
156
+ * `runnerChild` is the composition root's per-`session_start` runner bit (§8.3): suppression
157
+ * only, never a grant — and ahead of `spec.select`, so a runner child performs no selector reads.
158
+ */
159
+ export function installInjectedContext<K extends string>(
160
+ pi: ExtensionAPI,
161
+ spec: InjectedContextSpec<K>,
162
+ runnerChild: () => boolean,
163
+ ): void {
164
+ // Defensive over a widened K (string): an off-table key names no flavor — never inject or
165
+ // retain on it.
166
+ const flavorOf = (key: K | null): string | null =>
167
+ key !== null && spec.flavors[key] !== undefined ? key : null;
168
+ const selectedMarker = (ctx: ExtensionContext, branch: readonly BranchEntry[]): string | null =>
169
+ runnerChild() ? null : flavorOf(spec.select(ctx, branch));
170
+
171
+ pi.on("before_agent_start", async (event, ctx) => {
172
+ let branch: readonly BranchEntry[];
173
+ try {
174
+ branch = branchOf(ctx);
175
+ } catch {
176
+ return;
177
+ }
178
+ const marker = selectedMarker(ctx, branch);
179
+ if (marker === null) return;
180
+ const content = spec.flavors[marker as K];
181
+ if (event.prompt.includes(marker)) return;
182
+ let live: readonly ContextMessage[];
183
+ try {
184
+ live = activeContextMessages(ctx);
185
+ } catch {
186
+ return;
187
+ }
188
+ if (contextCarriesMarker(live, { customType: spec.customType, marker })) return;
189
+ return {
190
+ message: {
191
+ customType: spec.customType,
192
+ content: content(ctx),
193
+ display: false,
194
+ },
195
+ };
196
+ });
197
+
198
+ pi.on("context", async (event, ctx) => {
199
+ let selected: string | null;
200
+ try {
201
+ selected = selectedMarker(ctx, branchOf(ctx));
202
+ } catch {
203
+ selected = null;
204
+ }
205
+ return { messages: retainSelectedFlavor(event.messages, spec.customType, selected) };
206
+ });
207
+ }