@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.
- package/README.md +5 -0
- package/extension/authoring/gist/draft.ts +198 -0
- package/extension/authoring/gist/prose.ts +46 -0
- package/extension/authoring/gist/review.ts +133 -0
- package/extension/authoring/gist/save.ts +118 -0
- package/extension/authoring/objective/draft.ts +345 -0
- package/extension/{factories/objectiveDreamReport.ts → authoring/objective/dreamReportGate.ts} +74 -131
- package/extension/authoring/objective/planning.ts +124 -0
- package/extension/authoring/objective/prose.ts +103 -0
- package/extension/authoring/objective/review.ts +128 -0
- package/extension/authoring/objective/save.ts +224 -0
- package/extension/authoring/plan/draft.ts +84 -0
- package/extension/authoring/plan/prose.ts +41 -0
- package/extension/authoring/plan/review.ts +269 -0
- package/extension/authoring/plan/save.ts +256 -0
- package/extension/authoring/plan/source.ts +82 -0
- package/extension/authoring/refinement/context.ts +468 -0
- package/extension/authoring/refinement/draft.ts +261 -0
- package/extension/authoring/refinement/prose.ts +79 -0
- package/extension/authoring/refinement/review.ts +111 -0
- package/extension/authoring/refinement/save.ts +119 -0
- package/extension/authoring/review/approvalGate.ts +34 -0
- package/extension/authoring/review/draftContext.ts +68 -0
- package/extension/codeReview/automated.ts +352 -0
- package/extension/codeReview/submission.ts +229 -0
- package/extension/delivery/address.ts +295 -0
- package/extension/delivery/ci.ts +355 -0
- package/extension/delivery/commitCompact.ts +93 -0
- package/extension/delivery/conflictResolution.ts +247 -0
- package/extension/delivery/ready.ts +193 -0
- package/extension/delivery/stackConflict.ts +361 -0
- package/extension/delivery/stackObjective.ts +16 -0
- package/extension/delivery/stackReconcile.ts +165 -0
- package/extension/delivery/submit.ts +171 -0
- package/extension/index.ts +369 -359
- package/extension/learning/analystWave.ts +324 -0
- package/extension/learning/audit.ts +667 -0
- package/extension/learning/capture.ts +92 -0
- package/extension/learning/containment.ts +104 -0
- package/extension/{waves/dreamWave.ts → learning/dream.ts} +112 -94
- package/extension/learning/dreamAnalysis.ts +435 -0
- package/extension/{waves/dreamReducerWave.ts → learning/dreamReducer.ts} +46 -41
- package/extension/{waves → learning}/dreamReport.ts +35 -31
- package/extension/learning/harvest.ts +491 -0
- package/extension/learning/prose.ts +66 -0
- package/extension/learning/routing.ts +79 -0
- package/extension/pi/v1/bashScanTimeout.ts +64 -0
- package/extension/{doors/prReview.ts → pi/v1/codeReview/automated.ts} +215 -310
- package/extension/{doors/prReviewBrowser.ts → pi/v1/codeReview/browser.ts} +125 -57
- package/extension/{doors/hunkHandoff.ts → pi/v1/codeReview/checkout.ts} +12 -8
- package/extension/{doors/reviewWaveTools.ts → pi/v1/codeReview/reviewWave.ts} +169 -116
- package/extension/pi/v1/codeReview/stack.ts +606 -0
- package/extension/pi/v1/codeReview/submit.ts +354 -0
- package/extension/{doors/prReviewTerminal.ts → pi/v1/codeReview/terminal.ts} +32 -27
- package/extension/pi/v1/contextEvidence.ts +80 -0
- package/extension/pi/v1/contextInjection.ts +207 -0
- package/extension/{doors → pi/v1/delivery}/address.ts +163 -265
- package/extension/pi/v1/delivery/ci.ts +570 -0
- package/extension/pi/v1/delivery/commitCompact.ts +201 -0
- package/extension/pi/v1/delivery/conflictResolverEngine.ts +425 -0
- package/extension/{doors → pi/v1/delivery}/land.ts +128 -60
- package/extension/pi/v1/delivery/ready.ts +322 -0
- package/extension/pi/v1/delivery/stackConflictResolver.ts +172 -0
- package/extension/pi/v1/delivery/stackDrive.ts +120 -0
- package/extension/pi/v1/delivery/stackLand.ts +223 -0
- package/extension/pi/v1/delivery/stackRecover.ts +265 -0
- package/extension/pi/v1/delivery/stackStatus.ts +237 -0
- package/extension/pi/v1/delivery/stackSync.ts +658 -0
- package/extension/pi/v1/delivery/submit.ts +389 -0
- package/extension/pi/v1/delivery/submitConflict.ts +186 -0
- package/extension/pi/v1/draftReview.ts +431 -0
- package/extension/{doors → pi/v1}/draftReviewWaveTools.ts +141 -151
- package/extension/pi/v1/gist.ts +794 -0
- package/extension/pi/v1/learning/audit.ts +186 -0
- package/extension/pi/v1/learning/dream.ts +207 -0
- package/extension/{doors/learnFactory.ts → pi/v1/learning/factory.ts} +18 -65
- package/extension/{doors/harvestWaveTools.ts → pi/v1/learning/harvest.ts} +46 -100
- package/extension/pi/v1/learning/learn.ts +585 -0
- package/extension/{doors → pi/v1}/lifecycleGates.ts +14 -59
- package/extension/{factories → pi/v1}/objective.ts +53 -33
- package/extension/pi/v1/objectiveAuthoring.ts +672 -0
- package/extension/pi/v1/objectiveDreamGate.ts +160 -0
- package/extension/pi/v1/objectivePlanning.ts +776 -0
- package/extension/pi/v1/objectiveRefinement.ts +1320 -0
- package/extension/pi/v1/objectiveReview.ts +451 -0
- package/extension/{doors → pi/v1}/objectiveReviewBrowser.ts +259 -172
- package/extension/pi/v1/plan.ts +812 -0
- package/extension/pi/v1/planReview.ts +820 -0
- package/extension/{doors → pi/v1}/planReviewBrowser.ts +228 -152
- package/extension/{doors/annotationPush.ts → pi/v1/providers/annotations.ts} +158 -89
- package/extension/pi/v1/providers/plannotator.ts +487 -0
- package/extension/{doors → pi/v1/providers}/plannotatorHandoff.ts +152 -34
- package/extension/pi/v1/providers/selection.ts +43 -0
- package/extension/{adapters/planAdapterTombell.ts → pi/v1/providers/tombell.ts} +43 -72
- package/extension/pi/v1/review.ts +538 -0
- package/extension/pi/v1/reviewOutcome.ts +9 -0
- package/extension/pi/v1/scoutWave.ts +318 -0
- package/extension/{doors → pi/v1}/selfcheck.ts +4 -4
- package/extension/session/branchWorkflowSession.ts +60 -0
- package/extension/session/lifecycle.ts +644 -0
- package/extension/session/lifecycleGates.ts +64 -0
- package/extension/session/saveDestination.ts +87 -0
- package/extension/session/workflowSession.ts +971 -0
- package/extension/substrate/agentScratch.ts +27 -54
- package/extension/substrate/bashScanTimeout.ts +181 -0
- package/extension/substrate/bindingDelivery.ts +38 -30
- package/extension/substrate/bindings.ts +4 -5
- package/extension/substrate/cache.ts +64 -12
- package/extension/substrate/childRestrictions.ts +39 -0
- package/extension/substrate/coldDoor.ts +17 -1
- package/extension/substrate/config.ts +157 -19
- package/extension/substrate/git.ts +88 -6
- package/extension/substrate/modelVisible.ts +53 -0
- package/extension/substrate/paths.ts +2 -7
- package/extension/substrate/prompts.ts +22 -0
- package/extension/substrate/registry.ts +2 -0
- package/extension/substrate/resolverLease.ts +364 -0
- package/extension/substrate/sessionData.ts +85 -152
- package/extension/substrate/toolGating.ts +279 -84
- package/extension/substrate/unifiedDiff.ts +1 -1
- package/extension/substrate/workflowState.ts +191 -166
- package/extension/substrate/worktreeResolverLock.ts +261 -0
- package/extension/surfaces/surfaces.ts +79 -27
- package/extension/waves/adversarialReviewWave.ts +103 -48
- package/extension/waves/blockedReports.ts +59 -0
- package/extension/waves/draftReviewWave.ts +42 -42
- package/extension/waves/laneIdentity.ts +77 -0
- package/extension/waves/objectiveExplorerWave.ts +24 -24
- package/extension/waves/prReviewWave.ts +89 -77
- package/extension/waves/reportWave.ts +438 -578
- package/extension/waves/reviewClassifierWave.ts +22 -22
- package/extension/waves/rpcAdapter.ts +100 -15
- package/extension/waves/scoutWave.ts +192 -0
- package/extension/waves/transport.ts +480 -0
- package/extension/worker/sdkAdapter.ts +494 -0
- package/extension/worker/stageExecution.ts +679 -0
- package/extension/workerMain.ts +18 -19
- package/package.json +6 -4
- package/prompts/_fixtures/live.yaml +98 -10
- package/prompts/contexts/adapters/plannotator-gist.md +6 -0
- package/prompts/contexts/adapters/plannotator-objective.md +6 -0
- package/prompts/contexts/adapters/plannotator-plan.md +8 -1
- package/prompts/contexts/adapters/plannotator-refinement.md +22 -0
- package/prompts/contexts/adapters/tombell-plan.md +4 -0
- package/prompts/contexts/objective-refinement.md +17 -0
- package/prompts/contexts/plan-authoring.md +6 -5
- package/prompts/contexts/read-only.md +1 -1
- package/prompts/stages/conflict-resolution-continuation.md +9 -0
- package/prompts/stages/conflict-resolution.md +4 -4
- package/prompts/stages/objective-author/adopt.md +1 -1
- package/prompts/stages/objective-author/file.md +1 -1
- package/prompts/stages/objective-author/seed.md +1 -1
- package/prompts/stages/objective-plan/guidance.md +2 -2
- package/prompts/stages/objective-plan/seed.md +9 -1
- package/prompts/stages/objective-reconcile-ready.md +7 -0
- package/prompts/stages/objective-reconcile.md +1 -1
- package/prompts/stages/objective-refine/seed.md +18 -0
- package/prompts/stages/objective-review-browser.md +4 -4
- package/prompts/stages/objective-sync.md +1 -1
- package/prompts/stages/plan-review-browser.md +4 -4
- package/prompts/stages/pr-review-browser/active.md +3 -4
- package/prompts/stages/pr-review-browser/foreign.md +3 -4
- package/prompts/stages/pr-review-terminal/active.md +3 -3
- package/prompts/stages/pr-review-terminal/foreign.md +3 -3
- package/prompts/stages/pr-review.md +3 -3
- package/prompts/stages/stack-review/cold.md +1 -0
- package/prompts/stages/stack-review-browser/stack.md +22 -0
- package/shared/README.md +8 -3
- package/shared/bindings.yaml +6 -3
- package/shared/contracts.md +4499 -2147
- package/shared/fixtures/issues-table.json +130 -0
- package/shared/registry.yaml +29 -1
- package/shared/schemas/outputs/objective-node-engagement.schema.json +318 -0
- package/shared/schemas/outputs/objective-stack-status.schema.json +178 -2
- package/shared/schemas/outputs/pr-ready.schema.json +110 -2
- package/shared/schemas/outputs/pr-review-context.schema.json +54 -9
- package/shared/schemas/outputs/pr-review-stack-context.schema.json +196 -0
- package/extension/adapters/planAdapterPlannotator.ts +0 -362
- package/extension/doors/auditWaveTools.ts +0 -352
- package/extension/doors/ciExecutor.ts +0 -756
- package/extension/doors/commitCompact.ts +0 -251
- package/extension/doors/dreamWaveTools.ts +0 -475
- package/extension/doors/learn.ts +0 -655
- package/extension/doors/objectiveStack.ts +0 -1143
- package/extension/doors/prReviewDynamic.ts +0 -276
- package/extension/doors/ready.ts +0 -87
- package/extension/doors/submit.ts +0 -347
- package/extension/doors/submitPrReview.ts +0 -408
- package/extension/factories/gistAuthor.ts +0 -94
- package/extension/factories/gistDraft.ts +0 -265
- package/extension/factories/gistSave.ts +0 -251
- package/extension/factories/implementHere.ts +0 -116
- package/extension/factories/objectiveAuthor.ts +0 -98
- package/extension/factories/objectiveDraft.ts +0 -466
- package/extension/factories/objectivePlan.ts +0 -975
- package/extension/factories/objectiveSave.ts +0 -363
- package/extension/factories/planDraft.ts +0 -140
- package/extension/factories/planMode.ts +0 -205
- package/extension/factories/planReview.ts +0 -1237
- package/extension/factories/planSave.ts +0 -604
- package/extension/factories/planTitle.ts +0 -141
- package/extension/substrate/structuredOutput.ts +0 -202
- package/extension/waves/auditWave.ts +0 -312
- package/extension/waves/harvestWave.ts +0 -399
- package/extension/waves/learnWave.ts +0 -155
- package/extension/waves/memoryAdapter.ts +0 -139
- package/extension/waves/prReviewDynamicWave.ts +0 -777
- package/extension/worker/readOnlySession.ts +0 -294
- package/extension/worker/worker.ts +0 -899
- package/prompts/stages/pr-review-dynamic.md +0 -7
- 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
|
+
}
|