@yanlinglabs/winter-agent-sdk 0.0.1

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.
@@ -0,0 +1,50 @@
1
+ import { type SessionStoreEntry } from "./store/session-store.js";
2
+ import { type BrandProfile } from "./brand.js";
3
+ interface SessionQueryOptions {
4
+ directory?: string;
5
+ winterHome?: string;
6
+ /**
7
+ * P7a fix wave (item 5, whole-branch review I-1): THE HOST'S OWN BRAND.
8
+ *
9
+ * These nine functions run OUTSIDE a query -- no `RuntimeConfig`, no wiring -- so the only way
10
+ * they can learn which product's store to open is to be told. Without this field `resolveHome`
11
+ * fell through to `resolveWinterHome()` with NO brand, which reads `WINTER_HOME`, `WINTER_PROFILE`
12
+ * and `~/.winter`: a D19 tier-1 reuser calling `listSessions()` silently addressed WINTER's store,
13
+ * and on a machine where Winter is also installed `deleteSession(id)` from the reuser's app
14
+ * deleted a Winter session.
15
+ *
16
+ * `winterHome` still wins outright -- it is an explicit path, and a caller that computed one has
17
+ * already made this decision. `brand` is what a caller uses INSTEAD of recomputing the path: the
18
+ * profile is folded onto Winter's defaults by the same `resolveBrand` `query()` uses, so a partial
19
+ * `{ homeDirName, envPrefix }` is enough and an invalid one refuses here rather than silently
20
+ * addressing the wrong store.
21
+ */
22
+ brand?: Partial<BrandProfile>;
23
+ }
24
+ export declare function listSessions(opts?: SessionQueryOptions): Promise<Array<{
25
+ sessionId: string;
26
+ projectKey: string;
27
+ mtime: number;
28
+ name?: string;
29
+ tags?: string[];
30
+ }>>;
31
+ export declare function getSessionInfo(sessionId: string, opts?: SessionQueryOptions): Promise<{
32
+ sessionId: string;
33
+ projectKey: string;
34
+ mtime: number;
35
+ entryCount: number;
36
+ name?: string;
37
+ tags?: string[];
38
+ }>;
39
+ export declare function getSessionMessages(sessionId: string, opts?: SessionQueryOptions): Promise<SessionStoreEntry[]>;
40
+ export declare function renameSession(sessionId: string, name: string, opts?: SessionQueryOptions): Promise<void>;
41
+ export declare function tagSession(sessionId: string, tags: string[], opts?: SessionQueryOptions): Promise<void>;
42
+ export declare function deleteSession(sessionId: string, opts?: SessionQueryOptions): Promise<void>;
43
+ export declare function forkSession(sessionId: string, opts?: SessionQueryOptions): Promise<{
44
+ sessionId: string;
45
+ }>;
46
+ export declare function listSubagents(sessionId: string, opts?: SessionQueryOptions): Promise<Array<{
47
+ agentId: string;
48
+ }>>;
49
+ export declare function getSubagentMessages(sessionId: string, agentId: string, opts?: SessionQueryOptions): Promise<SessionStoreEntry[]>;
50
+ export {};
@@ -0,0 +1,33 @@
1
+ import type { ModelSlotSetting } from "../protocol/config.js";
2
+ /**
3
+ * The catalog questions validation needs, injected.
4
+ *
5
+ * `model` in a `ModelSlotSetting` may be a `canonicalModelId` OR a catalog key (both are things a
6
+ * user has to hand), so validation needs to go both ways: canonical id -> the rows serving it, and
7
+ * key -> its canonical id.
8
+ */
9
+ export interface ModelSlotsLookup {
10
+ rowsForCanonicalId(id: string): Array<{
11
+ key: string;
12
+ providerId: string;
13
+ }>;
14
+ keyToCanonicalId(key: string): string | undefined;
15
+ }
16
+ export type ModelSlotsValidation = {
17
+ ok: true;
18
+ slots: ModelSlotSetting[];
19
+ } | {
20
+ ok: false;
21
+ reason: string;
22
+ };
23
+ /**
24
+ * Validates a raw `settings.modelSlots` value (WS-13c §5).
25
+ *
26
+ * Lane B's obligations, from the spec: 1–4 entries; names match the slot token grammar and are
27
+ * unique; NO name is a reserved Claude name (D25); every `model` resolves in the catalog; a given
28
+ * `provider` actually serves it; `description` is ≤ 200 characters and carries no currency amount.
29
+ *
30
+ * WHOLE-SET: the first invalid entry (in array order) is where this returns, with a `reason` that
31
+ * names what failed. It never validates some entries and drops others.
32
+ */
33
+ export declare function validateModelSlots(raw: unknown, lookup: ModelSlotsLookup): ModelSlotsValidation;
@@ -0,0 +1,79 @@
1
+ import { type DetailedResolvedSettings, type ResolvedSettings, type ResolveSettingsDetailedOptions, type ResolveSettingsOptions, type Settings } from "./types.js";
2
+ export declare function validateProjectPlansDirectory(value: unknown): {
3
+ ok: true;
4
+ } | {
5
+ ok: false;
6
+ reason: string;
7
+ };
8
+ /**
9
+ * The Winter-side resolver. `resolveSettings` (the pinned export, below) is a thin projection of
10
+ * this onto the pinned three fields.
11
+ *
12
+ * Precedence, highest first (R5-8): managed (server, then programmatic) > flag (inline/sdk) >
13
+ * local > project > user. `local` above `project` is the pinned ordering capture (1) proves
14
+ * behaviourally (cell J vs I: only a rule in the LOCAL file silences a prompt).
15
+ *
16
+ * `trustedWorkspace` lives on the pinned `ResolveSettingsDetailedOptions` itself (R-6c-16, folded by
17
+ * the controller after Lane B merged; the lane had widened it by intersection because `types.ts` was
18
+ * the spine's frozen surface during the phase). `WorkspaceTrustFilterOptions` below carries the
19
+ * identical field for `applyWorkspaceTrust`. Absent = untrusted (fail-safe); production-wiring
20
+ * threads the host's RULING P5-A bit.
21
+ */
22
+ export declare function resolveSettingsDetailed(opts?: ResolveSettingsDetailedOptions): Promise<DetailedResolvedSettings>;
23
+ /**
24
+ * PINNED EXPORT (`sdk.d.ts:2809`): `resolveSettings(_opts?: ResolveSettingsOptions): Promise<ResolvedSettings>`.
25
+ *
26
+ * Task 1's item (a) found this is a MIRROR of a pinned function, not the "disclosed Winter
27
+ * extension" R5-8 originally described -- one options object (never four positional parameters),
28
+ * and exactly three result fields. Winter-side detail (per-source raw values, load errors, the
29
+ * inline/`flag` tier, an explicit `winterHome`) lives on `resolveSettingsDetailed` above, which this
30
+ * wraps: a caller writing against the pinned surface sees nothing extra.
31
+ *
32
+ * WS-03 disclosure: the pinned options object carries no environment/home injection point, so an
33
+ * omitted `settingSources` (or one including `"user"`) reads the REAL resolved WINTER_HOME. Tests
34
+ * must therefore either exclude the `user` tier or call `resolveSettingsDetailed` with an explicit
35
+ * `winterHome`.
36
+ */
37
+ export declare function resolveSettings(opts?: ResolveSettingsOptions): Promise<ResolvedSettings>;
38
+ /**
39
+ * PINNED EXPORT (`sdk.d.ts:694`, doc `686-694`): drops `permissions.defaultMode` from the resolved
40
+ * settings iff it is ESCALATING (`bypassPermissions`/`auto`/`acceptEdits`) AND was set by the
41
+ * `project` tier -- the repo-committed one. Every other key, including `allow`/`deny`/`ask`, is
42
+ * returned untouched, and a non-escalating project `defaultMode` (`plan`) survives. Verified against
43
+ * the pinned runtime in capture (1)'s declared-API table, all four rows.
44
+ *
45
+ * The "which tier set it" question is answered by walking `sources`, NOT by reading
46
+ * `provenance.permissions` -- the two disagree in one observable case, and only one of them is
47
+ * safe: with an escalating `defaultMode` in project and an `allow` in local, the coarse per-top-
48
+ * level-key provenance reports `local` (so a provenance-driven filter would RETAIN the escalating
49
+ * repo-committed mode) while the walk correctly attributes `defaultMode` to `project` and drops it.
50
+ * That specific combination was not captured against the pinned runtime; the walk is the fail-safe
51
+ * direction and is what Winter ships. Recorded in the Task 2 report.
52
+ *
53
+ * Never mutates its input.
54
+ */
55
+ export declare function filterEscalatingDefaultMode(resolved: ResolvedSettings): Settings;
56
+ export interface WorkspaceTrustFilterOptions {
57
+ /** Host-declared workspace trust (RULING P5-A). Default false -- a repository never self-trusts (WS-07 §3.2). */
58
+ trustedWorkspace?: boolean;
59
+ }
60
+ /**
61
+ * RULING P5-A, the whole tier filter in one call -- what a lane should use.
62
+ *
63
+ * Capture (1)'s finding, verbatim: the pinned SDK's trust is a per-TIER filter on PERMISSIVE rules,
64
+ * not a per-directory bit. A PROJECT-tier `allow`/`additionalDirectories` LOADS but does not widen;
65
+ * `local`- and `user`-tier permissive rules do widen; `deny`/`ask` from every tier apply regardless
66
+ * (cells K/L: a project `deny` is honored and beats a local `allow`).
67
+ *
68
+ * Winter layers ONE product extension above that: a host may declare the workspace trusted
69
+ * (`RuntimeConfig.trustedWorkspace`), which lifts the project-tier restriction. The filter is never
70
+ * DERIVED from that bit in the other direction -- an untrusted repo's project-tier `deny` stays
71
+ * enforced, which is the thing capture (1) explicitly forbids losing.
72
+ *
73
+ * Also applies the pinned `filterEscalatingDefaultMode`, which is tier-based and therefore fires
74
+ * even in a trusted workspace: a repo-committed escalating `defaultMode` never survives, trust or
75
+ * no trust.
76
+ *
77
+ * Never mutates its input.
78
+ */
79
+ export declare function applyWorkspaceTrust(resolved: ResolvedSettings, opts?: WorkspaceTrustFilterOptions): Settings;
@@ -0,0 +1,29 @@
1
+ import { type BrandProfile } from "../brand.js";
2
+ import { type HomeBrand } from "../paths/home.js";
3
+ import type { Settings, SettingSource } from "./types.js";
4
+ export interface SettingsPathOptions {
5
+ cwd: string;
6
+ /** Explicit resolved home root; when omitted it is resolved from `env` and `brand`. */
7
+ winterHome?: string;
8
+ env?: Record<string, string | undefined>;
9
+ /**
10
+ * P7a (D19): the session's resolved brand profile. Omitted means Winter's own
11
+ * (`WINTER_BRAND`), so every caller predating the profile keeps exactly today's paths.
12
+ */
13
+ brand?: HomeBrand & Pick<BrandProfile, "projectDirName">;
14
+ }
15
+ export declare function settingsPathFor(source: SettingSource, opts: SettingsPathOptions): string;
16
+ export interface LoadedSettingsFile {
17
+ /** Absent entirely when the file does not exist -- an absent file contributes NO source entry. */
18
+ present: boolean;
19
+ loaded: boolean;
20
+ error?: string;
21
+ values: Settings;
22
+ }
23
+ /**
24
+ * Reads one settings file. NEVER throws: a missing file reports `present: false`; anything that
25
+ * exists but cannot be used (unreadable, unparseable, or a non-object top level) reports
26
+ * `present: true, loaded: false` with a human-readable `error` and an EMPTY value map, so a
27
+ * malformed repo-committed file can never silently contribute a partial document.
28
+ */
29
+ export declare function loadSettingsFile(path: string): Promise<LoadedSettingsFile>;
@@ -0,0 +1,291 @@
1
+ import type { BrandProfile } from "../brand.js";
2
+ /**
3
+ * P7a (D19): the brand fields the settings cascade needs — the project dot-dir it looks in, and the
4
+ * env prefix + home dir the user tier resolves through. A `Pick`, not the whole profile, so a
5
+ * caller can thread three fields rather than construct one.
6
+ */
7
+ export type SettingsBrand = Pick<BrandProfile, "envPrefix" | "homeDirName" | "projectDirName">;
8
+ import type { ModelSlotSetting } from "../protocol/config.js";
9
+ /** The three settings FILE tiers a session may load. `sdk.d.ts:7917`, verbatim and in pinned order. */
10
+ export type SettingSource = "user" | "project" | "local";
11
+ /** Pinned order (`sdk.d.ts:7917`). Also the value `settingSources: undefined` means (all three). */
12
+ export declare const SETTING_SOURCES: readonly SettingSource[];
13
+ /** `sdk.d.ts:2783`: `SettingSource | 'managed' | 'flag'`. `'flag'` is R5-8's "inline/sdk" position. */
14
+ export type ResolvedSettingSource = SettingSource | "managed" | "flag";
15
+ /** `sdk.d.ts:2310`, verbatim -- how a `managed` value reached this process. */
16
+ export type PolicySettingsOrigin = "helper" | "remote" | "plist" | "hklm" | "file" | "parent" | "hkcu";
17
+ export interface SettingsHookHandler {
18
+ /** `"command"` is the only shape a settings file can express; see buildHookEntriesFromSettings. */
19
+ type?: string;
20
+ command?: string;
21
+ /** SECONDS (HookCallbackMatcher.timeout's pinned unit) -- converted to ms exactly once, at entry-build time. */
22
+ timeout?: number;
23
+ }
24
+ export interface SettingsHookMatcherGroup {
25
+ matcher?: string;
26
+ hooks?: SettingsHookHandler[];
27
+ }
28
+ /** Open-keyed for the same reason RuntimeHooksConfig is (protocol/config.ts): unknown event names are accepted, preserved and inert. */
29
+ export type SettingsHooksConfig = Partial<Record<string, SettingsHookMatcherGroup[]>>;
30
+ export interface SettingsPermissionsBlock {
31
+ allow?: string[];
32
+ ask?: string[];
33
+ deny?: string[];
34
+ /** An OPEN string at this layer: a settings file is JSON, so an invalid mode must degrade at the consumer, never be assumed pre-validated. */
35
+ defaultMode?: string;
36
+ disableBypassPermissionsMode?: boolean;
37
+ additionalDirectories?: string[];
38
+ [key: string]: unknown;
39
+ }
40
+ export interface Settings {
41
+ permissions?: SettingsPermissionsBlock;
42
+ hooks?: SettingsHooksConfig;
43
+ env?: Record<string, string>;
44
+ apiKeyHelper?: string;
45
+ /** `sdk.d.ts:7270` */
46
+ outputStyle?: string;
47
+ /** `sdk.d.ts:7734` */
48
+ autoMemoryEnabled?: boolean;
49
+ /**
50
+ * `sdk.d.ts:7738`. Its own pinned doc (`7736`) says a PROJECT-set value is ignored for security --
51
+ * the one per-key project-source restriction the declaration actually states. See OVERLAY_NEVER_KEYS.
52
+ */
53
+ autoMemoryDirectory?: string;
54
+ /** `sdk.d.ts:7693` */
55
+ plansDirectory?: string;
56
+ /** `sdk.d.ts:6047`: the value is `string[] | boolean | object`, NOT a boolean map. */
57
+ enabledPlugins?: Record<string, string[] | boolean | Record<string, unknown>>;
58
+ /**
59
+ * `sdk.d.ts:7755`. NEGATIVE sense, one-member literal -- there is NO `autoMode` key in the pinned
60
+ * declaration (OQ-P5-2). Restrictive, so it is safe from every tier and is NOT an overlay-never key.
61
+ */
62
+ disableAutoMode?: "disable";
63
+ /**
64
+ * WINTER-DEFINED (disclosed): WS-07 §3.2's "`autoMode` is never taken from project/local" names a
65
+ * key the pin does not have. Winter keeps the key and the restriction, narrowed per RULING P5-A to
66
+ * the PROJECT tier only (the pinned analogue at `autoMemoryDirectory` is project-only).
67
+ */
68
+ autoMode?: string;
69
+ /** `sdk.d.ts:5651`. Per-skill visibility: `on` | `name-only` | `user-invocable-only` | `off`; an unrecognised value reads as `on`. */
70
+ skillOverrides?: Record<string, string>;
71
+ /** `sdk.d.ts:5657`. Removes the BUILTIN skill tier and nothing else. */
72
+ disableBundledSkills?: boolean;
73
+ /** `sdk.d.ts:5988`. `true`, or the areas (`skills`/`agents`/`hooks`/`mcp`) restricted to plugin contributions only. */
74
+ strictPluginOnlyCustomization?: boolean | string[];
75
+ /** `sdk.d.ts:5499`. Per-description cap in the model-facing skill listing (default 1536 chars). */
76
+ skillListingMaxDescChars?: number;
77
+ /** `sdk.d.ts:5503`. The listing's share of the context window (default 0.01). */
78
+ skillListingBudgetFraction?: number;
79
+ /**
80
+ * A settings-tier MCP server block. Deliberately `Record<string, unknown>` rather than a typed
81
+ * server union: `settings/loaders/mcp-config.ts` validates each entry and `resolveMcpServerSources`
82
+ * is the sole authority on the shapes, so a type here would be a second, drift-prone declaration
83
+ * of a contract that already has one.
84
+ */
85
+ mcpServers?: Record<string, unknown>;
86
+ /**
87
+ * WINTER-DEFINED (WS-13b R6b-7, disclosed): per-provider enablement, keyed by catalog provider id.
88
+ *
89
+ * Its whole reason for existing is the reversion condition. `xai-oauth` ships on prong 2 of the
90
+ * admission rule -- a vendor's public product client used with an honest Winter identity -- and
91
+ * WS-13b §4 requires that a vendor rejecting that identity can be answered WITHOUT a release. A
92
+ * setting is that answer; a compile-time constant is not.
93
+ *
94
+ * ABSENT MEANS ENABLED. Silence is not a disablement, so an unlisted provider resolves normally
95
+ * and only an explicit `enabled: false` refuses.
96
+ *
97
+ * RESTRICTIVE-ONLY ACROSS TIERS (RULING R6b-9), enforced by `restrictProviderEnables` in
98
+ * `resolve.ts` rather than by the ordinary merge: the effective value is `false` if ANY tier says
99
+ * `false`, and a lower tier's `true` never re-enables what a higher one disabled. Without that,
100
+ * a cloned repository's PROJECT settings file could put back a provider its operator withdrew —
101
+ * and this key IS the reversion switch (R6b-7 / WS-13b §4), so a switch a repository can flip back
102
+ * would not be one.
103
+ *
104
+ * NOT an OVERLAY_NEVER_KEY, deliberately: a never-key drops the project tier's value entirely,
105
+ * which would also drop a project's legitimate `false`. Disabling is a tightening every tier may
106
+ * make; only the enabling direction is restricted — the same asymmetry `permissions.deny` and
107
+ * `permissions.allow` already carry.
108
+ */
109
+ providers?: Record<string, {
110
+ enabled?: boolean;
111
+ }>;
112
+ /**
113
+ * WINTER-DEFINED (WS-13c §5, D27, disclosed): the user's OWN four options, with the facing names
114
+ * that show on the Agent tool and the default model switcher.
115
+ *
116
+ * 1–4 entries. Honoured from the USER tier and the TRUSTED project tier only (R13c-7) — an
117
+ * untrusted project's set is ignored whole and recorded as `modelSlotsIgnored:
118
+ * "untrusted-project"`. That gate is the point of the key, not a precaution around it: a cloned
119
+ * repository that could map `cheap` to Astra would be choosing what the user's agent spends.
120
+ *
121
+ * Validated WHOLE (never partially): a set with one bad entry is ignored entirely and the failing
122
+ * entry recorded, because a partially-applied set is a lineup the user did not ask for.
123
+ */
124
+ modelSlots?: ModelSlotSetting[];
125
+ /**
126
+ * WINTER-DEFINED (WS-13c §4 step 3-ii, disclosed): an ordered list of provider ids, consulted
127
+ * after the family's own `vendorProviders` and before the admission-tier fallback.
128
+ *
129
+ * A PREFERENCE, never an admission: a provider named here still needs a credential and still
130
+ * obeys `providers.<id>.enabled`. Same tiers and same hot-reload as `modelSlots`.
131
+ */
132
+ preferredProviders?: string[];
133
+ /**
134
+ * WINTER-DEFINED (D30, WS-06 §4's advisor amendment, disclosed): which model the ADVISOR consults.
135
+ *
136
+ * `model` names a slot name, a canonical model id or a catalog key, resolved through WS-13c §4 —
137
+ * the same path the session model takes, so it obeys credentials, `providers.<id>.enabled` and the
138
+ * vendor-first order, and an unresolvable value is a typed refusal rather than a substitution.
139
+ *
140
+ * SAME TIERS AS `modelSlots`, deliberately: the user tier and the TRUSTED project tier only. The
141
+ * advisor sends this session's conversation to whatever this names, so a cloned repository that
142
+ * could set it would be choosing where the user's transcript goes — the identical argument that
143
+ * gates `modelSlots`, with a higher price for getting it wrong.
144
+ *
145
+ * HOT (a Global Constraint of this phase): it takes effect at the next quiescent boundary through
146
+ * the live settings getter, never at a restart. Unset means the per-family default (D30: a gpt
147
+ * session -> `astra`, a claude session -> `fable`, any other family -> its slot 1, a family with
148
+ * no slots -> the session's own model). `Options.advisor.model` outranks it.
149
+ *
150
+ * DECLARED AT P7a'S SPINE; the resolution and the trust gate are Lane B's.
151
+ */
152
+ advisor?: {
153
+ model?: string;
154
+ };
155
+ /**
156
+ * DERIVED provenance — written by `resolve.ts` / the runtime, NEVER read from a settings file.
157
+ *
158
+ * It records WHY a `modelSlots` set did not take effect, so a host can say so instead of showing
159
+ * the default lineup with no explanation: the set came from an untrusted project tier, it failed
160
+ * whole-set validation, or the session's effective main model is a Claude model and D25 pins that
161
+ * enum. A file that sets this key is stating a conclusion it does not get to draw; the resolver
162
+ * overwrites it.
163
+ */
164
+ modelSlotsIgnored?: "untrusted-project" | "invalid" | "claude-pinned";
165
+ [key: string]: unknown;
166
+ }
167
+ /**
168
+ * Narrows `Settings.providers` into the shape selection consumes: every declared id present, with an
169
+ * explicit boolean.
170
+ *
171
+ * ONE narrowing, at one place (the rider-26 pattern the six P5 keys established), so a typo'd key or
172
+ * a JSON file saying `"enabled": "false"` is handled here rather than at each reader with a cast.
173
+ *
174
+ * Total by construction: a settings file is JSON and may say anything. A non-object entry, an empty
175
+ * provider id and a non-boolean `enabled` are all DROPPED rather than coerced -- coercing `"false"`
176
+ * to `false` would disable a provider on the strength of a typo, and coercing it to `true` would
177
+ * pretend the user said something they did not. Only a literal `false` disables.
178
+ */
179
+ export declare function providerSettingsFrom(settings: Settings | undefined): Record<string, {
180
+ enabled: boolean;
181
+ }>;
182
+ /** `sdk.d.ts:2413-2419`, verbatim. */
183
+ export interface ProvenanceEntry {
184
+ source: ResolvedSettingSource;
185
+ path?: string;
186
+ policyOrigin?: PolicySettingsOrigin;
187
+ }
188
+ /** One tier's contribution, RAW (never overlay-filtered) -- the pinned `sources` escape hatch (`sdk.d.ts:2764-2767`). */
189
+ export interface ResolvedSettingsSourceEntry {
190
+ source: ResolvedSettingSource;
191
+ settings: Settings;
192
+ path?: string;
193
+ policyOrigin?: PolicySettingsOrigin;
194
+ }
195
+ /**
196
+ * `sdk.d.ts:2759-2774`, verbatim three fields.
197
+ *
198
+ * `provenance` is per TOP-LEVEL key only (`2762`) -- runtime-confirmed by capture (1): with
199
+ * `permissions.allow` from project and `permissions.defaultMode` from local, the merged
200
+ * `effective.permissions` carried both and `provenance.permissions.source` reported `local` alone.
201
+ * A consumer that needs finer attribution reads `sources` (which this implementation orders
202
+ * highest-precedence first), exactly as the pinned field's own doc directs.
203
+ */
204
+ export interface ResolvedSettings {
205
+ effective: Settings;
206
+ provenance: Partial<Record<string, ProvenanceEntry>>;
207
+ sources: ResolvedSettingsSourceEntry[];
208
+ }
209
+ /** `sdk.d.ts:2815-2843`, verbatim four fields -- ONE options object, not four positional parameters. */
210
+ export interface ResolveSettingsOptions {
211
+ cwd?: string;
212
+ settingSources?: SettingSource[];
213
+ /**
214
+ * Programmatic policy tier. The pinned doc (`2018-2040`) says the pinned runtime filters this tier
215
+ * RESTRICTIVE-ONLY.
216
+ *
217
+ * WINTER DOES NOT APPLY THAT FILTER (Phase 5 fix wave, A-4 -- said plainly here, where the old
218
+ * one-liner quoted the pin's behaviour in a way a reader could take for Winter's). A managed tier
219
+ * is merged like any other, at the top of the precedence order, so a managed PERMISSIVE rule
220
+ * widens where the pin would drop it. That is a DISCLOSED DIVERGENCE, not an oversight: the pinned
221
+ * filter's exact rule is not stated anywhere in scope, no capture exercised it, and inventing one
222
+ * would be Winter guessing at a security-relevant transformation. The direction of the divergence
223
+ * is the permissive one, which is why it is disclosed here rather than buried in a report.
224
+ */
225
+ managedSettings?: Settings;
226
+ /** Remote policy payload -- pinned doc `2838-2839`: explicitly UNfiltered where `managedSettings` is filtered. */
227
+ serverManagedSettings?: Settings;
228
+ }
229
+ /** A per-tier record carrying what the pinned `sources` entry cannot: whether the file loaded, and why not. */
230
+ export interface DetailedSettingsSourceEntry extends ResolvedSettingsSourceEntry {
231
+ /** True iff a file/inline value existed AND parsed to a JSON object. */
232
+ loaded: boolean;
233
+ /** Present iff `loaded` is false because something existed but could not be used (parse error, wrong shape, unreadable). */
234
+ error?: string;
235
+ /** Alias of `settings`, kept under the brief's own field name so lane code can use either. */
236
+ values: Settings;
237
+ }
238
+ export interface DetailedResolvedSettings extends ResolvedSettings {
239
+ /** Highest-precedence first, same order as `sources`; a superset of it. */
240
+ perSource: DetailedSettingsSourceEntry[];
241
+ }
242
+ export interface ResolveSettingsDetailedOptions extends ResolveSettingsOptions {
243
+ /** RULING P5-A's host-declared workspace-trust bit, threaded by production-wiring. Absent = untrusted (fail-safe): the project tier's `modelSlots`/`preferredProviders` are dropped (WS-13c §5, R-6c-16). */
244
+ trustedWorkspace?: boolean;
245
+ /** Explicit resolved home root. Tests MUST pass this rather than mutating process.env (a shared-process `bun test` run would race). */
246
+ winterHome?: string;
247
+ /** Injectable environment for home resolution; defaults to `process.env`. */
248
+ env?: Record<string, string | undefined>;
249
+ /**
250
+ * P7a (D19): the session's resolved brand profile, which decides the PROJECT tier's directory
251
+ * (`<cwd>/<projectDirName>/settings.json`) and the env name the user tier's home is read from.
252
+ * Absent means Winter's own profile — every caller predating it keeps today's paths exactly.
253
+ */
254
+ brand?: SettingsBrand;
255
+ /** The `'flag'` tier -- R5-8's "inline/sdk" position. Unreachable from the pinned options object, which has no inline input. */
256
+ inline?: Settings;
257
+ }
258
+ /**
259
+ * Keys that are NEVER taken from PROJECT settings.
260
+ *
261
+ * Scope correction vs the Task 2 brief: the brief said "project/local"; RULING P5-A (and the one
262
+ * pinned per-key restriction this can mirror, `autoMemoryDirectory`'s own doc at `sdk.d.ts:7736`)
263
+ * make it PROJECT-only. `local` is gitignored and personal -- it carries the same authority as
264
+ * `user` under the captured per-tier filter; only the repo-committed tier is restricted.
265
+ *
266
+ * `disableAutoMode` is deliberately ABSENT: it is restrictive (`'disable'` is its only value), so a
267
+ * repo-committed file setting it can only ever tighten, which every tier is allowed to do.
268
+ *
269
+ * `outputStyle` JOINED IN THE PHASE 5 FIX WAVE (whole-branch m1). RULING P5-G gates a project-tier
270
+ * style FILE to append-only -- a checked-in project-tier output-style file may add to the prompt but
271
+ * never replace it. It says nothing about SELECTION, and selection is the other half of the same
272
+ * power: a project `settings.json` naming one of the USER's own styles -- one the user wrote with
273
+ * `keep-coding-instructions: false` -- would replace the authored prompt on the strength of a
274
+ * repository's choice. The style file is the user's; the decision to apply it was not. Same
275
+ * self-grant shape P5-A closes, arriving through selection rather than through content.
276
+ *
277
+ * A host that genuinely wants a per-repository style still has one: `Options.outputStyle`, which is
278
+ * the host's own configuration and outranks every file tier.
279
+ */
280
+ export declare const OVERLAY_NEVER_KEYS: readonly string[];
281
+ /**
282
+ * The permission modes `filterEscalatingDefaultMode` treats as escalating (pinned doc `686-694`).
283
+ * `plan`/`default` are non-escalating and survive from any tier.
284
+ */
285
+ export declare const ESCALATING_PERMISSION_MODES: readonly string[];
286
+ /**
287
+ * PROJECT-tier permission keys that LOAD but never WIDEN in an untrusted workspace (RULING P5-A,
288
+ * capture (1) cells B/I/O). `deny`/`ask` are deliberately absent: they only ever tighten, and
289
+ * capture (1) cells K/L prove a project `deny` is honored and beats a local `allow`.
290
+ */
291
+ export declare const PROJECT_PERMISSIVE_KEYS: readonly string[];
@@ -0,0 +1,19 @@
1
+ import { type SessionKey, type SessionStore } from "./session-store.js";
2
+ /**
3
+ * `resume + forkSession: true` (WS-05 §7) — copies `src`'s entries into a brand-new lowercase
4
+ * RFC4122 v4 session id, with each copied entry's OWN `sessionId` field rewritten to the fork's
5
+ * id (uuid/parentUuid/content are otherwise untouched — the fork's conversational identity is
6
+ * byte-for-byte the source's, just re-owned). `src` is never written to: this only reads it (via
7
+ * store.load) and appends to the NEW key, so the source stays byte-identical on disk by
8
+ * construction. No undo/file-history copies (WS-05 §7) — neither exists in this codebase yet, so
9
+ * there is nothing to carry forward or omit; revisit when either lands.
10
+ *
11
+ * Throws `SessionNotFoundError("not_found", ...)` — not resume.ts's ResumeTargetError, which this
12
+ * primitive no longer has access to post-relocation — when `src` doesn't exist. The public
13
+ * `forkSession(sessionId, opts)` wrapper in ../sessions.ts already validates existence before
14
+ * calling this, so that branch is a defensive backstop (a TOCTOU race, or a direct caller) rather
15
+ * than the primary path a normal caller hits.
16
+ */
17
+ export declare function forkSessionByKey(store: SessionStore, src: SessionKey): Promise<{
18
+ sessionId: string;
19
+ }>;
@@ -0,0 +1,15 @@
1
+ export declare class WinterStoreError extends Error {
2
+ constructor(message: string);
3
+ }
4
+ export declare class WinterStoreLeaseError extends WinterStoreError {
5
+ readonly heldByPid: number;
6
+ constructor(message: string, heldByPid: number);
7
+ }
8
+ export interface LeaseInfo {
9
+ pid: number;
10
+ startTimeMs: number;
11
+ }
12
+ export declare function isPidAlive(pid: number): boolean;
13
+ export declare function writeAllSync(fd: number, buf: Buffer): void;
14
+ export declare function readLeaseInfo(lockPath: string): LeaseInfo | null;
15
+ export declare function acquireLease(lockPath: string): LeaseInfo;
@@ -0,0 +1,111 @@
1
+ export { WinterStoreError, WinterStoreLeaseError } from "./leases.js";
2
+ export type SessionKey = {
3
+ projectKey: string;
4
+ sessionId: string;
5
+ subpath?: string;
6
+ };
7
+ export type SessionStoreEntry = {
8
+ type: string;
9
+ uuid?: string;
10
+ timestamp?: string;
11
+ [key: string]: unknown;
12
+ };
13
+ export type SessionSummaryEntry = {
14
+ sessionId: string;
15
+ entryCount: number;
16
+ mtime: number;
17
+ lastEntryType?: string;
18
+ lastTimestamp?: string;
19
+ producerRuntime?: "claude-agent" | "winter-agent";
20
+ producerEngineVersion?: string;
21
+ dialectFamily?: "claude-code-jsonl";
22
+ projectDirName?: string;
23
+ name?: string;
24
+ tags?: string[];
25
+ [key: string]: unknown;
26
+ };
27
+ export declare const DIALECT_RECORD_ENTRY_TYPE = "winter_dialect_record";
28
+ /**
29
+ * The provider-state sidecar's filename suffix (P6 R6-7): `<sessionId>.provider-state.jsonl`.
30
+ *
31
+ * DECLARED HERE, imported by the runtime -- one declaration, and this is the package that can own it:
32
+ * the sdk cannot import the runtime (WS-02 §3's dependency inversion), and `delete()` below must
33
+ * name the file to remove it. The runtime owns the RECORD SEMANTICS (`store/provider-state.ts`: the
34
+ * envelope, the write-ahead ordering, the chain); this package owns the sidecar's LIFECYCLE at the
35
+ * path level, because that is where the deletion transaction lives.
36
+ */
37
+ export declare const PROVIDER_STATE_FILE_SUFFIX = ".provider-state.jsonl";
38
+ export type SessionStore = {
39
+ append(key: SessionKey, entries: SessionStoreEntry[]): Promise<void>;
40
+ load(key: SessionKey): Promise<SessionStoreEntry[] | null>;
41
+ listSessions?(projectKey: string): Promise<Array<{
42
+ sessionId: string;
43
+ mtime: number;
44
+ }>>;
45
+ listSessionSummaries?(projectKey: string): Promise<SessionSummaryEntry[]>;
46
+ delete?(key: SessionKey): Promise<void>;
47
+ listSubkeys?(key: {
48
+ projectKey: string;
49
+ sessionId: string;
50
+ }): Promise<string[]>;
51
+ };
52
+ export declare class WinterCompatibilitySessionStore implements SessionStore {
53
+ private readonly winterHome;
54
+ constructor(opts: {
55
+ winterHome: string;
56
+ });
57
+ append(key: SessionKey, entries: SessionStoreEntry[]): Promise<void>;
58
+ load(key: SessionKey): Promise<SessionStoreEntry[] | null>;
59
+ listSessions(projectKey: string): Promise<Array<{
60
+ sessionId: string;
61
+ mtime: number;
62
+ }>>;
63
+ listSessionSummaries(projectKey: string): Promise<SessionSummaryEntry[]>;
64
+ delete(key: SessionKey): Promise<void>;
65
+ listSubkeys(key: {
66
+ projectKey: string;
67
+ sessionId: string;
68
+ }): Promise<string[]>;
69
+ listProjectKeys(): Promise<string[]>;
70
+ /**
71
+ * ONE session's folded summary, read by its own path.
72
+ *
73
+ * Phase 6 Task 3 (review round 2, M2). `listSessionSummaries` reads and parses EVERY
74
+ * `*.summary.json` in the project directory, which is O(#sessions) -- fine for enumerating a picker,
75
+ * wrong for the one question a RESUME asks about ITSELF ("did this session record a provider
76
+ * identity?"), which a busy project would pay for on every resume.
77
+ *
78
+ * Deliberately NOT part of the exported `SessionStore` type -- same posture as `listProjectKeys`/
79
+ * `mergeSessionMetadata`/`claimWriterLease` above: WS-03 §10 pins that surface as exactly its six
80
+ * members, and a caller reaches this through a LOCAL intersection type instead. `null` for a session
81
+ * with no summary yet, which is indistinguishable from one that never existed and is the honest
82
+ * answer to both.
83
+ */
84
+ /**
85
+ * P6 fix wave (the public `forkSession()` door): copies a session's provider-state sidecar onto a
86
+ * FORK. Winter-only, on the concrete class like `readSessionSummary`, and generic by design -- the
87
+ * sdk cannot import the runtime's record codec (WS-02 §3), so this is a LINE rewrite: every parseable
88
+ * record is re-owned to the fork's `sessionId` and given a FRESH `uuid` (the store's idempotency key;
89
+ * two sessions sharing record uuids would collide into one upserted row) while its `anchorUuid` stays
90
+ * -- `forkSessionByKey` keeps entry uuids, which is exactly what makes the copied anchors meaningful.
91
+ * An unparseable line (a torn tail) is skipped, never fatal; a source with no sidecar copies nothing.
92
+ * Written with the same 0600 / O_APPEND / O_NOFOLLOW / fsync posture the transcript gets. Returns the
93
+ * number of records copied.
94
+ */
95
+ copyProviderStateForFork(src: SessionKey, dest: SessionKey): Promise<number>;
96
+ readSessionSummary(key: {
97
+ projectKey: string;
98
+ sessionId: string;
99
+ }): Promise<SessionSummaryEntry | null>;
100
+ mergeSessionMetadata(key: {
101
+ projectKey: string;
102
+ sessionId: string;
103
+ }, patch: {
104
+ name?: string;
105
+ tags?: string[];
106
+ }): Promise<void>;
107
+ acquireSessionLease(key: {
108
+ projectKey: string;
109
+ sessionId: string;
110
+ }): Promise<void>;
111
+ }
@@ -0,0 +1,26 @@
1
+ export interface SpawnedRuntimeProcess {
2
+ stdin: {
3
+ write(chunk: string): void;
4
+ end(): void;
5
+ };
6
+ stdout: AsyncIterable<string>;
7
+ stderr?: AsyncIterable<string>;
8
+ kill(signal?: string): void;
9
+ readonly exited: Promise<{
10
+ code: number | null;
11
+ signal: string | null;
12
+ }>;
13
+ readonly pid: number | null;
14
+ }
15
+ export interface SpawnRuntimeOptions {
16
+ command: string;
17
+ args: string[];
18
+ cwd: string;
19
+ env: Record<string, string>;
20
+ signal?: AbortSignal;
21
+ }
22
+ export type SpawnClaudeCodeProcess = (opts: SpawnRuntimeOptions) => SpawnedRuntimeProcess;
23
+ export declare function resolveRuntimeExecutable(opts: {
24
+ pathToClaudeCodeExecutable?: string;
25
+ }): string;
26
+ export declare function defaultSpawn(opts: SpawnRuntimeOptions): SpawnedRuntimeProcess;