@lmzhen/dsh-evolution-core 0.16.1 → 0.17.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.
@@ -36,6 +36,8 @@ export declare const EVOLUTION_ERRORS: Readonly<{
36
36
  readonly 'e-305-this-invocation-carries-no-2': "E-305: this invocation carries no agent — `%a1%` needs a session-backed call (run it from a session in the GUI or the CLI).";
37
37
  readonly 'e-317-skill-write-not-confirmed': "E-317: skill \"%a1%\" was not %a2% — the confirmation prompt was declined or dismissed, so nothing was written. The prompt appears on every create and bare delete; repeat the call only if the operator asks for it.";
38
38
  readonly 'e-318-skill-write-without-a-read': "E-318: skill \"%a1%\" was not read in this session, so this write is refused. Read it with the `skill` tool first (a read that failed does not count), then repeat the write.";
39
+ readonly 'e-320-the-profile-home-is-not': "E-320: the profile home is not reported by this host, so the legacy settings document cannot be located. /evolution migrate reads `<profile home>/settings.yaml.imported`; run it from a profile-launched session.";
40
+ readonly 'e-321-no-io-provider-is': "E-321: no evolution-io provider is mounted, so the legacy settings document cannot be read. Mount the evolution-io row (evolution-host/evolution-all) and retry.";
39
41
  readonly 'e-319-skill-write-confirm-timed-out': "E-319: skill \"%a1%\" was not %a2% — the confirmation prompt went unanswered for %a3% second(s), so the write was cancelled. Nobody was there to decide: set `skillWriteConfirm` to `auto` to write without a prompt, keep `ask` to wait until someone answers, or raise `skillWriteConfirmTimeoutSeconds`.";
40
42
  }>;
41
43
  /** One table key. */
@@ -19,6 +19,27 @@
19
19
  * vocabulary — changes this table and nothing else (`evidenceKindIndex` is the only reader).
20
20
  * @module @lmzhen/dsh-evolution-core
21
21
  */
22
+ /**
23
+ * Whether one frame type records a boundary and carries no content.
24
+ *
25
+ * The classification table's SECOND reader: the live projection unit
26
+ * (`session-projection.ts`) folds the same answer per event, so the rule stays one table.
27
+ * @param type - a platform session-event type.
28
+ * @returns true for the turn/step bookends.
29
+ */
30
+ export declare function isBookkeepingFrame(type: string): boolean;
31
+ /**
32
+ * The one question an evidence index answers: does this seq carry content?
33
+ *
34
+ * Structural on purpose — {@link evidenceKindIndex} materializes the dense set of content seqs,
35
+ * while the projection answers by complement over the sparse boundary seqs
36
+ * (`session-projection.ts`). Both satisfy this interface, and it is all the plan path reads
37
+ * (`evolution-plan-validator/src/index.ts:164`).
38
+ */
39
+ export interface EvidenceIndex {
40
+ /** @param seq - a cited evidence seq. @returns true when that frame carries content. */
41
+ has(seq: number): boolean;
42
+ }
22
43
  /** One session frame, as far as the classification reads it. A platform `SessionEvent` is
23
44
  * assignable: both fields are optional here so a stub log can be classified without pretending
24
45
  * to be a full event. */
@@ -24,7 +24,9 @@
24
24
  * tool path and the review's plan path), `drift-signals.ts`, `skill-health.ts`,
25
25
  * `preset-composition.ts`, `prompts.ts`, `learn-prompt.ts`,
26
26
  * `evolution-events.ts`, `evidence.ts` (which session frames count as content, for the plan
27
- * path's evidence-class report), `io.ts` (the ctx.evolutionIo seam itself).
27
+ * path's evidence-class report), `session-projection.ts` (the live per-session units the family's
28
+ * log-shaped readers read instead of re-reading the session log), `io.ts` (the ctx.evolutionIo
29
+ * seam itself).
28
30
  * @module @lmzhen/dsh-evolution-core
29
31
  */
30
32
  export * from './citations.ts';
@@ -58,6 +60,7 @@ export * from './skill-reads.ts';
58
60
  export * from './skill-row-cells.ts';
59
61
  export * from './document-body.ts';
60
62
  export * from './evidence.ts';
63
+ export * from './session-projection.ts';
61
64
  export * from './drift-signals.ts';
62
65
  export * from './skill-history.ts';
63
66
  export * from './skill-store.ts';
@@ -67,6 +70,7 @@ export * from './tool-dispatch.ts';
67
70
  export * from './usage.ts';
68
71
  export * from './constants.ts';
69
72
  export * from './params.ts';
73
+ export * from './namespace-migration.ts';
70
74
  export * from './numeric.ts';
71
75
  export * from './opt-in.ts';
72
76
  //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,133 @@
1
+ /** One value a legacy section carried, with the spelling it used. */
2
+ export interface NamespaceMigrationValue {
3
+ /** The key as the legacy document spelled it (canonical or deprecated alias). */
4
+ readonly source: string;
5
+ /** The canonical registry id the value is written under. */
6
+ readonly key: string;
7
+ /** The parsed value. */
8
+ readonly value: unknown;
9
+ }
10
+ /** One row's migration target. */
11
+ export interface NamespaceMigrationEntry {
12
+ /** The legacy section the values sit in. */
13
+ readonly namespace: string;
14
+ /** The row id the platform keys the settings by now. */
15
+ readonly rowId: string;
16
+ /** The values to merge into that row, in document order. */
17
+ readonly values: readonly NamespaceMigrationValue[];
18
+ }
19
+ /** One legacy key no row may take. */
20
+ export interface NamespaceMigrationSkip {
21
+ /** The legacy section it sat in. */
22
+ readonly namespace: string;
23
+ /** The key as spelled. */
24
+ readonly key: string;
25
+ /** Why it is not migrated. */
26
+ readonly reason: string;
27
+ }
28
+ /** What a legacy document asks for. */
29
+ export interface NamespaceMigrationPlan {
30
+ /** One entry per legacy section that carried values a row can take. */
31
+ readonly entries: readonly NamespaceMigrationEntry[];
32
+ /** Legacy sections the family knows but that carried nothing mappable. */
33
+ readonly empty: readonly string[];
34
+ /** Top-level sections that are not family legacy sections (the platform's own). */
35
+ readonly foreign: readonly string[];
36
+ /** Keys that were found but not migrated. */
37
+ readonly skipped: readonly NamespaceMigrationSkip[];
38
+ }
39
+ /**
40
+ * Read one legacy settings document into a migration plan.
41
+ *
42
+ * Pure: the caller reads the file (the platform's renamed `settings.yaml.imported` first,
43
+ * then a `settings.yaml` the platform has not imported yet) and hands the text over.
44
+ * @param documentText - the YAML document text.
45
+ * @returns what to write, what the family knows but need not touch, and what it refused.
46
+ * @throws {Error} when the document is not a mapping, or a family section is not one.
47
+ */
48
+ export declare function planNamespaceMigration(documentText: string): NamespaceMigrationPlan;
49
+ /** One key the target row already carried. */
50
+ export interface NamespaceMigrationConflict {
51
+ /** The row id. */
52
+ readonly rowId: string;
53
+ /** The canonical key. */
54
+ readonly key: string;
55
+ /** The value the stored row already holds — it wins. */
56
+ readonly current: unknown;
57
+ /** The legacy value that was NOT applied. */
58
+ readonly legacy: unknown;
59
+ }
60
+ /** What one run did. */
61
+ export interface NamespaceMigrationReport {
62
+ /** Rows the run wrote, with the keys it merged. */
63
+ readonly written: readonly {
64
+ readonly rowId: string;
65
+ readonly keys: readonly string[];
66
+ }[];
67
+ /** Keys whose stored value won over the legacy one. */
68
+ readonly conflicts: readonly NamespaceMigrationConflict[];
69
+ /** Keys the target row already held the same value for: no write was needed. */
70
+ readonly unchanged: readonly {
71
+ readonly rowId: string;
72
+ readonly key: string;
73
+ }[];
74
+ /** Keys the plan refused (see {@link NamespaceMigrationSkip}). */
75
+ readonly skipped: readonly NamespaceMigrationSkip[];
76
+ }
77
+ /** Everything {@link applyNamespaceMigration} needs from its caller. */
78
+ export interface NamespaceMigrationDeps {
79
+ /**
80
+ * The row's CURRENT user layer, as `settings.describe()` reports it — the raw user
81
+ * section, not the resolved value: presence of a key is what marks an override.
82
+ * @param rowId - the row id.
83
+ * @returns the describe view, or undefined when the platform serves no such row.
84
+ */
85
+ read(rowId: string): {
86
+ readonly user?: Record<string, unknown> | undefined;
87
+ } | undefined;
88
+ /**
89
+ * Merge one row's values (the platform's `settings.update` — merge semantics).
90
+ * @param rowId - the row id.
91
+ * @param patch - canonical key to value, the keys the row does not already carry.
92
+ * @returns settlement after the write is accepted.
93
+ */
94
+ update(rowId: string, patch: Readonly<Record<string, unknown>>): Promise<void>;
95
+ /**
96
+ * Report one value that was found and not applied.
97
+ * @param message - the warning text.
98
+ */
99
+ warn(message: string): void;
100
+ }
101
+ /**
102
+ /** One row's pending write: the keys that row does not carry yet. */
103
+ export interface NamespaceMigrationWrite {
104
+ /** The row id. */
105
+ readonly rowId: string;
106
+ /** Canonical key to legacy value, for the keys the row does not already hold. */
107
+ readonly patch: Readonly<Record<string, unknown>>;
108
+ }
109
+ /** What a plan would do against the live document, decided without writing anything. */
110
+ export interface NamespaceMigrationClassification {
111
+ /** The rows to write, one entry per row. */
112
+ readonly write: readonly NamespaceMigrationWrite[];
113
+ /** Keys whose stored value won over the legacy one. */
114
+ readonly conflicts: readonly NamespaceMigrationConflict[];
115
+ /** Keys the row already held the same value for. */
116
+ readonly unchanged: readonly {
117
+ readonly rowId: string;
118
+ readonly key: string;
119
+ }[];
120
+ /** The plan's refusals, plus the keys of a row this composition does not serve. */
121
+ readonly skipped: readonly NamespaceMigrationSkip[];
122
+ }
123
+ /**
124
+ * Decide what a plan would do: the rows it would write, the keys already equal, the stored
125
+ * values that win, and the refusals. The write half and the read-only doctor read the SAME
126
+ * decision here — one rule, two consumers, so a report can never disagree with a run.
127
+ * @param plan - the plan from {@link planNamespaceMigration}.
128
+ * @param read - the row's current user layer (see {@link NamespaceMigrationDeps.read}).
129
+ * @returns the decision; nothing is written.
130
+ */
131
+ export declare function classifyNamespaceMigration(plan: NamespaceMigrationPlan, read: NamespaceMigrationDeps['read']): NamespaceMigrationClassification;
132
+ export declare function applyNamespaceMigration(plan: NamespaceMigrationPlan, deps: NamespaceMigrationDeps): Promise<NamespaceMigrationReport>;
133
+ //# sourceMappingURL=namespace-migration.d.ts.map
@@ -15,8 +15,9 @@
15
15
  * model tools are visible in its scope. Mounted at profile root they are visible
16
16
  * to every session; mounted inside a variant preset they are visible only to the
17
17
  * agents that joined that preset's standing scope — every registration inside a
18
- * preset files into that agent's layer
19
- * (packages/preset/agent-presets/src/mount.ts:4-13).
18
+ * preset files into that agent's layer — in 0.2.x the preset is an ordinary row and its
19
+ * child rows stay in that agent's own layer (`packages/preset/agent-preset/src/index.ts`,
20
+ * `static [EntryGroup.key] = true`).
20
21
  *
21
22
  * `sessionScoped` is what a deployment declares: true means "act only on a
22
23
  * session that carries the family's model tools", which is the right question in
@@ -119,32 +119,62 @@ export declare const PARAM_EXPOSURE: readonly ParamExposure[];
119
119
  * user layer (its knobs stay deployment-only). */
120
120
  export declare const PARAM_NAMESPACES: Readonly<Record<string, string>>;
121
121
  /**
122
- * The settings namespace one owner package registers. The registry's map is the only
123
- * place a namespace is spelled: a package without an entry has no user layer, so a
124
- * missing entry must fail loud here instead of letting a package fall back to a
125
- * private constant that the registry never sees.
122
+ * The rows whose settings namespace CHANGED with the platform-line move (G3): the
123
+ * platform keys a settings section by the Loader entry id now, while 0.1.x let each
124
+ * plugin register a free-form namespace — this table is the difference, DERIVED from
125
+ * {@link PARAM_NAMESPACES} so freezing a row id renames it in one place.
126
+ *
127
+ * It is the migration's TARGET (`rowId`) and its SOURCE (`namespace`, the section a
128
+ * stored value sits in). Rows whose two names already agree are absent: there is
129
+ * nothing to move for them.
130
+ */
131
+ export declare const NAMESPACE_MIGRATIONS: readonly {
132
+ readonly namespace: string;
133
+ readonly rowId: string;
134
+ }[];
135
+ /**
136
+ * The LEGACY settings namespace one owner package used to register through the family's
137
+ * own seam (0.1.x). It is no longer what the platform calls the section: the platform
138
+ * derives a settings namespace from the Loader entry id, and this registry's `owner` field
139
+ * IS that row id, so a live lookup uses {@link paramRowId}. What remains here is the
140
+ * migration SOURCE (G3 moves a user layer out of this string and into the row id).
126
141
  * @param owner - owner package directory name (the map's key).
127
- * @returns the namespace that owner registers.
142
+ * @returns the legacy namespace that owner registered.
128
143
  * @throws when the owner has no entry in {@link PARAM_NAMESPACES}.
129
144
  */
130
145
  export declare function paramNamespace(owner: string): string;
131
- /** Structural view of one registered settings scope (platform Service Definition).
132
- * Declared locally so this module keeps its zero-import, zero-dependency shape. */
133
- interface SettingsScopeLike {
134
- get(): unknown;
135
- watch(callback: (next: unknown, prev: unknown) => void): () => void;
136
- }
137
- /** Structural view of the platform settings provider; only the members the
138
- * family uses are named. A missing `describe` disables the user layer loudly
139
- * (see {@link paramSectionOverrides}) instead of reading as 'no overrides'. */
146
+ /**
147
+ * The settings id one row's user layer lives under, or undefined when the owner publishes
148
+ * no user layer at all (a deployment-only row: its knobs are E2 and never reach a settings
149
+ * section). The platform derives a settings namespace from the Loader entry id and reports
150
+ * it in `settings.describe()` / `configForms.get(entryId)`, and an owner package's row id is
151
+ * its own name — spelled the same in every bundle patch.
152
+ *
153
+ * This is the id to LOOK UP with; {@link paramNamespace} keeps the legacy string the 0.1.x
154
+ * seam registered, which is the G3 migration source and never a live lookup key.
155
+ * @param owner - owner package directory name (the row id).
156
+ * @returns the entry id the platform reports for that row, or undefined for a row without
157
+ * a user layer.
158
+ */
159
+ export declare function paramSettingsId(owner: string): string | undefined;
160
+ /**
161
+ * The settings id of a row that MUST have a user layer (an owner reading its own section).
162
+ * @param owner - owner package directory name (the row id).
163
+ * @returns the entry id the platform reports for that row.
164
+ * @throws when the owner publishes no user layer.
165
+ */
166
+ export declare function paramRowId(owner: string): string;
167
+ /**
168
+ * Structural view of the platform settings provider: only the members the family
169
+ * still uses are named.
170
+ *
171
+ * G1 removed the family's own settings seam (`installParamSection`): a volatile row
172
+ * field carries the user layer by itself, so what remains is the WRITE face
173
+ * (`update`) and the one read the precedence rule needs — `describe()`, for the KEY
174
+ * NAMES the user set. A value is never read from here. Declared locally so this module
175
+ * keeps its zero-import, zero-dependency shape.
176
+ */
140
177
  export interface SettingsProviderLike {
141
- register(namespace: string, schema: unknown, options: {
142
- base: unknown;
143
- applies?: 'live' | 'restart';
144
- /** Owner-side refusal of a resolved section (cross-field rules the schema
145
- * cannot express); throwing refuses the WRITE that produced the value. */
146
- validate?: (value: unknown) => void;
147
- }): SettingsScopeLike;
148
178
  /** Merge a patch into one namespace's user layer. A stale `expectedRevision`
149
179
  * rejects with the platform's SETTINGS_CONFLICT error. */
150
180
  update?(namespace: string, patch: object, expectedRevision?: number): Promise<void>;
@@ -162,69 +192,6 @@ export interface SettingsProviderLike {
162
192
  applies?: 'live' | 'restart';
163
193
  }[];
164
194
  }
165
- /** Hooks a caller may supply when a section attaches to the settings service.
166
- * @typeParam T - the section's value type (what `validate` inspects). */
167
- export interface ParamSectionOptions<T extends object = object> {
168
- /** Called once when the user layer turns unreadable (a warning, never silent). */
169
- warn?: (message: string) => void;
170
- /** Called after every committed change that the reader can observe — the place
171
- * to REBUILD registration-level facts (the platform's own `installSection`
172
- * documents the same hook shape). Consumers that read at use time pass nothing. */
173
- onChange?: () => void;
174
- /** Refuse a resolved section the owner could not act on: a cross-field rule the
175
- * schema cannot express (the platform applies this hook to the RESOLVED section,
176
- * so a user value is judged together with the deployment layer beneath it).
177
- * Throwing refuses the write that produced the value. */
178
- validate?: (value: T) => void;
179
- }
180
- /** Reader for one parameter section: presence-aware user overrides. */
181
- export interface ParamOverrides<T extends object> {
182
- /** The namespace this reader is bound to. */
183
- readonly namespace: string;
184
- /** The user-set value for one key, or undefined when the user never set it. */
185
- get<K extends keyof T & string>(key: K): T[K] | undefined;
186
- /** The resolved section (defaults < base < user) — display and tests. */
187
- resolved(): T;
188
- }
189
- /**
190
- * G3: expose one parameter section to the user layer.
191
- *
192
- * Precedence stays 'user > deployment > default': the caller keeps reading its
193
- * deployment carriers (policy snapshot, then the plugin row) and consults
194
- * {@link ParamOverrides.get} FIRST — an unset key returns undefined, so the
195
- * deployment value keeps winning and the family's shadowing rules survive.
196
- *
197
- * Failure posture: a provider without `describe` (or one whose describe throws)
198
- * leaves the user layer UNAVAILABLE and warns once — deployment values then
199
- * apply. Treating an unreadable user layer as 'no overrides' would silently
200
- * ignore a setting the user did write, so the warning names it.
201
- * @typeParam T - the section's value type.
202
- * @param provider - the platform settings provider, or undefined when absent.
203
- * @param namespace - namespace to register.
204
- * @param schema - schemastery schema the platform validates against.
205
- * @param base - composition base layer (the plugin row's values).
206
- * @param options - warning sink and the change hook.
207
- * @returns a reader bound to the namespace.
208
- */
209
- export declare function paramSectionOverrides<T extends object>(provider: SettingsProviderLike | undefined, namespace: string, schema: unknown, base: T, options?: ParamSectionOptions<T>): ParamOverrides<T>;
210
- /** Minimal structural view of the cordis context used to attach a section.
211
- * The callback takes `unknown` on purpose: cordis's own `inject` declares a
212
- * `Context` parameter, and a callback accepting `unknown` is assignable to it
213
- * (parameter contravariance) while a narrower shape is not. */
214
- export interface SettingsHostLike {
215
- inject(names: string[], callback: (ctx: unknown) => void): unknown;
216
- }
217
- /**
218
- * Attach a parameter section through the optional settings service.
219
- * @typeParam T - the section's value type.
220
- * @param host - the plugin context (structurally typed).
221
- * @param namespace - namespace to register.
222
- * @param schema - schemastery schema the platform validates against.
223
- * @param base - composition base layer (the plugin row's values).
224
- * @param options - warning sink and the change hook.
225
- * @returns a reader that follows the provider when it appears.
226
- */
227
- export declare function installParamSection<T extends object>(host: SettingsHostLike, namespace: string, schema: unknown, base: T, options?: ParamSectionOptions<T>): ParamOverrides<T>;
228
195
  /**
229
196
  * Number-typed read over {@link readParam}: the family's tunables are numbers,
230
197
  * and a value of another type reads as absent so the caller's default applies
@@ -234,5 +201,4 @@ export declare function installParamSection<T extends object>(host: SettingsHost
234
201
  * @returns the resolved number, or undefined when absent or not a number.
235
202
  */
236
203
  export declare function readNumberParam(carrier: object | undefined, id: string): number | undefined;
237
- export {};
238
204
  //# sourceMappingURL=params.d.ts.map
@@ -63,5 +63,162 @@ interface RowOverride {
63
63
  * default while nothing downstream reported it.
64
64
  */
65
65
  export declare function loadRowOverrides(): RowOverride[];
66
+ /** The platform package a DECLARATIVE preset row names (0.2.x replaced preset directories with it). */
67
+ export declare const AGENT_PRESET_PACKAGE = "@deepseek-ai/dsh-agent-preset";
68
+ /** Identity and display metadata of one generated preset row. */
69
+ export interface PresetRowIdentity {
70
+ /** The composition row id (`- id:`); what {@link mergePresetRow} replaces by. */
71
+ readonly rowId: string;
72
+ /** The preset id the platform registry keys on (`evolution` / `evolution-ptc`). */
73
+ readonly id: string;
74
+ /** Display name in the preset picker. */
75
+ readonly name: string;
76
+ /** One-line description in the preset picker. */
77
+ readonly description: string;
78
+ /** Sort order among the registered presets. */
79
+ readonly order: number;
80
+ }
81
+ export declare function composePresetRow(baseComposition: string, deltaComposition: string, identity: PresetRowIdentity): string;
82
+ /**
83
+ * Wrap a composed row as the patch entry a profile patch needs.
84
+ *
85
+ * The row {@link composePresetRow} returns starts at column 0; a patch entry's list nests it four
86
+ * spaces deeper. Wrapping is a separate step because the same row text is also what
87
+ * {@link mergePresetRow} matches by id, and because the composer's override anchors are defined
88
+ * against column-0 rows.
89
+ * @param row - the row text, as {@link composePresetRow} returns it.
90
+ * @returns the patch entry text, newline-terminated.
91
+ */
92
+ export declare function composePresetInsert(row: string): string;
93
+ /**
94
+ * The row list a platform base preset patch carries under `config.plugins`, dedented to column 0.
95
+ *
96
+ * A base preset arrives as a whole patch entry — `- insert:` / `- id: preset-standard` /
97
+ * `config:` / `plugins:` — because that is how the platform ships it
98
+ * (`packages/bundle/web-app/presets/standard.patch.yml`). The preset's own list is the
99
+ * SHALLOWEST `plugins:` line: a child row may carry a key by that name far deeper.
100
+ * @param patchText - the base preset patch file's text.
101
+ * @returns the plugin rows, dedented and newline-terminated.
102
+ * @throws {Error} when the patch carries no `plugins:` list.
103
+ */
104
+ export declare function basePresetPlugins(patchText: string): string;
105
+ /** The row id a family preset occupies: the platform ships \`preset-standard\` for id `standard`
106
+ * (`packages/bundle/web-app/presets/standard.patch.yml`), so a family preset id mirrors it. */
107
+ export declare function presetRowId(id: string): string;
108
+ /**
109
+ * The patch entry a profile patch receives for one preset: base rows plus the family delta, wrapped
110
+ * as \`- insert:\`.
111
+ *
112
+ * The two halves are also exposed on their own — {@link composePresetRow} for the row, so
113
+ * {@link mergePresetRow} can match it by id, and {@link basePresetPlugins} to read the platform
114
+ * base out of a shipped patch.
115
+ * @param basePatchText - the platform base preset patch's text.
116
+ * @param deltaComposition - the family delta rows.
117
+ * @param identity - row id, preset id, display fields and order.
118
+ * @returns the patch entry text, newline-terminated.
119
+ */
120
+ export declare function composePresetEntry(basePatchText: string, deltaComposition: string, identity: PresetRowIdentity): string;
121
+ /**
122
+ * The text of one row's own patch item, or null when the patch carries no such row.
123
+ *
124
+ * The comparison unit for a freshness check: this is byte-for-byte what {@link composePresetEntry}
125
+ * writes for the same row id, whether the row sits inside an \`- insert:\` entry (installer-written)
126
+ * or at column 0 (saved by the Web editor).
127
+ * @param patchText - the patch text.
128
+ * @param rowId - the row id to read.
129
+ * @returns the item's text, newline-terminated, or null.
130
+ */
131
+ export declare function presetRowBody(block: string): string;
132
+ /**
133
+ * The row BODY of a patch block, whichever form it is in.
134
+ *
135
+ * {@link presetRowBlock} returns the enclosing item verbatim: an installer-written row sits inside
136
+ * `- insert:` while a row the platform's Web editor saved IS that column-0 item. A comparison must
137
+ * compare like with like — comparing the raw block against `composePresetEntry()` (which always
138
+ * starts with `- insert:`) reported DIFFERS for every editor-saved row, forever (G6 review, P2), and
139
+ * the remedy it printed (`re-run the installer`) was refused by the `exists; use --force` guard.
140
+ * @param block - a block as {@link presetRowBlock} returns it.
141
+ * @returns the row text, `- insert:` unwrapped and its four-space indent stripped.
142
+ */
143
+ export declare function presetRowBlock(patchText: string, rowId: string): string | null;
144
+ /**
145
+ * What `DSH_AGENT_PRESET_ROOT` must name: a directory holding the platform base preset patches
146
+ * under their shipped file names (`<base>.patch.yml`) — e.g. a copy of a same-version platform
147
+ * tree's `packages/bundle/web-app/presets/`. The desktop application is the shape that needs it:
148
+ * its platform packages live inside `resources/app.asar`, so there is no plain
149
+ * `node_modules/@deepseek-ai/dsh-web-app/presets/` on disk for a source installer to read.
150
+ */
151
+ export declare const AGENT_PRESET_ROOT_HINT = "point DSH_AGENT_PRESET_ROOT at a directory holding <base>.patch.yml (copy it out of a same-version dsh tree: packages/bundle/web-app/presets/)";
152
+ /** Where {@link resolveBasePresetPatch} looks, in order. */
153
+ export interface BasePresetPatchSources {
154
+ /** Explicit preset root (`DSH_AGENT_PRESET_ROOT`); an ANSWER, never a fallback head. */
155
+ readonly root?: string | undefined;
156
+ /** Module-graph probe for the bundle package — the only candidate that reaches inside an Electron
157
+ * `app.asar`. Receives `@deepseek-ai/dsh-web-app/package.json` and returns its path. */
158
+ readonly resolve?: ((specifier: string) => string) | undefined;
159
+ /** Target profile directory; its `node_modules` holds the bundles it mounts. */
160
+ readonly profileDir?: string | undefined;
161
+ /** Bundle package names to probe under `<profileDir>/node_modules`. */
162
+ readonly bundles?: readonly string[] | undefined;
163
+ /** Directory the ancestor walk starts from — the caller's own file location. */
164
+ readonly fromDir: string;
165
+ }
166
+ /**
167
+ * Resolve the platform patch file one `--base` names.
168
+ *
169
+ * 0.2.x ships each base preset as a bundle patch layer
170
+ * (`packages/bundle/web-app/presets/<base>.patch.yml`, declared in that package's
171
+ * `dsh.bundle.patch`), so the FILE — not a preset directory — is the base a family row is composed
172
+ * from. Candidates, in order: an explicit `DSH_AGENT_PRESET_ROOT`; a module-graph probe when the
173
+ * caller supplies one (a desktop/Electron host resolves the bundle inside `app.asar`, where no file
174
+ * walk can reach); the target profile's own `node_modules`; then, walking up from the caller,
175
+ * every `presets/` directory of a checkout's bundle packages, then every
176
+ * `@deepseek-ai/dsh-<name>/presets/` directory of an installed tree.
177
+ *
178
+ * A base with no patch file is REFUSED with every probed candidate listed: composing a family preset
179
+ * from another base's rows would mount rows the user did not ask for, with nothing saying so.
180
+ * @param base - the base name, already validated against `evolution-agent/bases.json`.
181
+ * @param sources - the roots to probe.
182
+ * @returns the patch file's absolute path.
183
+ * @throws {Error} when no candidate exists.
184
+ */
185
+ export declare function resolveBasePresetPatch(base: string, sources: BasePresetPatchSources): string;
186
+ /**
187
+ * Remove one row from a patch, taking its `- insert:` item with it when that item holds nothing
188
+ * else (an empty insert entry is left-over structure the platform would keep parsing).
189
+ * @param patchText - the patch text to edit.
190
+ * @param rowId - the row id to remove.
191
+ * @returns the patch text, newline-terminated; byte-identical when the id is absent.
192
+ */
193
+ export declare function removePresetRow(patchText: string, rowId: string): string;
194
+ /**
195
+ * Merge one generated row into a composition (a profile patch) by row id.
196
+ *
197
+ * Idempotent by construction: an existing row with the same `- id:` is REPLACED whole — its block
198
+ * is the enclosing column-0 list item, so an installer-written row inside `- insert:` and a
199
+ * Web-editor-saved row at column 0 are both matched by {@link rowBlockSpan} — and anything else in
200
+ * the patch stays byte-identical, because the patch usually carries rows this installer knows
201
+ * nothing about. A missing row is appended.
202
+ *
203
+ * The platform seeds a fresh profile patch with the empty list `[]`
204
+ * (`packages/boot/app-boot/src/profile.ts`, `PROFILE_PATCH_FILENAME`), which is a COMPLETE YAML
205
+ * document: a block sequence appended after it is not parsable. That seed is therefore replaced, not
206
+ * extended.
207
+ * @param patchText - the current patch text (`''` for a fresh one).
208
+ * @param row - the row text, as {@link composePresetRow} returns it.
209
+ * @param rowId - the row id to replace or append.
210
+ * @returns the merged patch text, newline-terminated.
211
+ */
212
+ export declare function mergePresetRow(patchText: string, row: string, rowId: string): string;
213
+ /** The empty patch list the platform seeds a profile patch with; see {@link mergePresetRow}. */
214
+ export declare const EMPTY_PATCH_SEED = "[]";
215
+ /**
216
+ * The text to write when a patch is left with no entries.
217
+ *
218
+ * A zero-byte patch file is not what the platform writes or parses; its own seed is the empty list.
219
+ * @param patchText - the text {@link removePresetRow} returned.
220
+ * @returns the platform's empty-list seed when nothing is left, the text otherwise.
221
+ */
222
+ export declare function presetPatchText(patchText: string): string;
66
223
  export {};
67
224
  //# sourceMappingURL=preset-composition.d.ts.map