@treeseed/dsh-chapters 0.1.17 → 0.1.18

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/lib/preset.d.ts CHANGED
@@ -1,55 +1,46 @@
1
1
  /**
2
- * The `chapters` agent preset: its composition, and the ONLY sanctioned way this
3
- * plugin makes a preset EXIST on a host that composes presets from declaration
4
- * rows (>= 0.1.7-alpha.1; "Nothing reads $DSH_HOME/.agent-presets any more").
2
+ * The `chapters` agent preset row — our implementation of the host's own
3
+ * `@deepseek-ai/dsh-agent-preset` declaration row, mounted from the bundle patch
4
+ * (`cordis.patch.yml`, row id `preset-chapters`), so the preset is a visible Loader entry
5
+ * exactly like `preset-standard` / `preset-ptc` rather than something a plugin does aside.
5
6
  *
6
- * WHY THIS FILE IS CODE AND NOT A SHIPPED PATCH LAYER
7
- * ---------------------------------------------------
8
- * The host's idiom is a `@deepseek-ai/dsh-agent-preset` row carried by a bundle
9
- * patch layer, listed in `package.json` `dsh.bundle.patch`. Shipping that row as
10
- * a SECOND layer means `dsh.bundle.patch` becomes an ARRAY, and both halves of
11
- * that are fatal on a pre-0.1.7 host — measured on the pinned e2e closure
12
- * (0.1.5-rc.1), not reasoned about:
13
- *
14
- * - array `patch`: `TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must
15
- * be of type string. Received an instance of Array` at
16
- * `dsh-app-boot/lib/index.js:853` (`join(packageDir, declared)`) — the host
17
- * never boots.
18
- * - the row itself, in ANY layer a legacy host reads:
19
- * `dsh: plugin tree failed to load: ... failed to import loader entry
20
- * preset-chapters (@deepseek-ai/dsh-agent-preset): Cannot find package` — a
21
- * legacy host's plugin tree aborts the boot on one unresolvable row.
22
- * - gating it with `disabled: !!js get('agentPresets')?.register === undefined`
23
- * does not work either: the Loader evaluates `disabled` BEFORE services are
24
- * mounted, so the expression is truthy on a 0.2.x host too and the row never
25
- * registers (measured: `preset-v2 … failed to import`, roster without it).
7
+ * WHY OUR ROW AND NOT THE HOST PACKAGE DIRECTLY
8
+ * --------------------------------------------
9
+ * A preset can be unusable in two ways and both are total: one unresolvable child row makes
10
+ * the WHOLE preset `broken` (`mountPreset` throws on any failed row → `record.broken` →
11
+ * `retain()` refuses with `agent-preset/invalid`), which is the same user-visible failure as
12
+ * a missing preset — every chapters session stops composing while its durable log sits there
13
+ * intact. Measured on the stable release with a composition copy-anchored to the alpha:
14
+ * `@deepseek-ai/dsh-tool-schedule` is not published at 0.2.0-rc.2 at all, and the boot said
15
+ * `preset "chapters" is DECLARED BUT BROKEN — tool-schedule … never started`.
26
16
  *
27
- * Registering through `ctx.get('agentPresets').register(definition)` is the same
28
- * call the host's own declaration-row plugin makes
29
- * (`dsh-agent-preset/lib/index.js`: `[Service.init]` → `this.ctx.agentPresets.register(this.config)`),
30
- * made from a row that resolves on EVERY host: this package. The tradeoff is
31
- * honest and stated in docs/contract.md: the composition is not visible in a
32
- * patch-layer dump, so the roster (not a file) is the evidence — which is the
33
- * rule this module enforces anyway.
17
+ * So this row does what the host's does, plus drop-what-will-not-load. It also refuses to
18
+ * brick a host predating the mechanism: a `@deepseek-ai/dsh-agent-preset` row is
19
+ * unresolvable before 0.1.7-alpha.1, and ONE unresolvable row aborts a legacy plugin tree
20
+ * (`plugin tree failed to load: … Cannot find package`), while a row naming our own package
21
+ * resolves wherever our package runs. The support floor is 0.2.0, so that is now
22
+ * belt-and-braces rather than the reason — and it is why the row plugin is ours.
34
23
  *
35
- * WHY ROWS ARE PRUNED AGAINST THE INSTALLED HOST
36
- * ----------------------------------------------
37
- * The host's standard plugin list is not stable across the versions this package
38
- * must serve. Measured against published `@deepseek-ai/dsh-web-app` presets:
39
- * 0.2.0-rc.1/rc.2 lack `dsh-time-context` and `dsh-tool-schedule`;
40
- * `dsh-tool-schedule` is not published at 0.2.0-rc.x AT ALL (first real version
41
- * 0.2.1-alpha.1), and `dsh-plugin-market` exists only at 0.2.1-alpha.4. One
42
- * unresolvable row does not degrade: `mountPreset` throws on any failed row, the
43
- * registry stores it as `record.broken`, and `retain()` then refuses with
44
- * `agent-preset/invalid` — every session choosing `chapters` stops composing,
45
- * while its durable log sits there intact. So a row whose package does not
46
- * resolve is DROPPED here and reported, instead of taking the preset down.
24
+ * WHAT IS COPIED FROM THE HOST'S PLUGIN, LINE FOR LINE
25
+ * ---------------------------------------------------
26
+ * - `static inject = ['agentPresets']` — the registry's shape, never a version test.
27
+ * - `static [EntryGroup.key] = true` — "preserve child expressions until their own plugins
28
+ * activate". Without it the Loader interpolates `!!js` against the HOST context, and
29
+ * `disabled: !!js process.platform === 'win32'` stops belonging to the child row that
30
+ * owns it.
31
+ * - `[Service.init]` `yield`s the registry's disposer, so Cordis owns the registration's
32
+ * lifetime. That also disposes of the trap the first draft fell into: calling
33
+ * `ctx.effect(disposer)` from `apply()` fires when the apply FIBER finishes, which
34
+ * un-registered the preset the instant it had been registered (measured on 0.2.0-rc.2:
35
+ * `register()` reported success while the roster came back without `chapters`).
47
36
  *
48
37
  * @module dsh-chapters/src/preset
49
38
  */
50
- /** One mounted plugin row of a preset composition (the Loader's own shape).
51
- * A group row carries its children in `config`; an ordinary row carries a
52
- * config map — the Loader accepts both under the same key. */
39
+ import { Service } from '@deepseek-ai/cordis';
40
+ import { EntryGroup } from '@deepseek-ai/cordis-plugin-loader';
41
+ import z from '@deepseek-ai/schemastery';
42
+ /** One mounted plugin row of a preset composition (the Loader's own shape). A group row
43
+ * carries its children in `config`; an ordinary row carries a config map. */
53
44
  export interface PresetRow {
54
45
  id: string;
55
46
  name: string;
@@ -58,99 +49,75 @@ export interface PresetRow {
58
49
  group?: boolean;
59
50
  isolate?: Record<string, boolean>;
60
51
  }
61
- /** The `AgentPreset` Config the host registers (`dsh-agent-preset/lib/index.js:13-20`). */
52
+ /** The preset definition, in the shape `AgentPresetRegistry.register()` accepts — the host
53
+ * plugin's own Config: id, name?, description?, order, plugins. */
62
54
  export interface PresetDefinition {
63
55
  id: string;
64
- name: string;
65
- description: string;
66
- order: number;
56
+ name?: string;
57
+ description?: string;
58
+ order?: number;
67
59
  plugins: PresetRow[];
68
60
  }
69
- /**
70
- * WHY THE DELEGATION ROWS CARRY NO `toolFilter`, although the host's own alpha.1/alpha.2
71
- * list denied `schedule_*` there.
72
- *
73
- * Measured on a real 0.2.1-alpha.4 boot: `tool-subagent` does NOT strip an unknown key —
74
- * it rejects the removed one outright, and the WHOLE preset goes broken:
75
- * `tool-subagent: toolFilter is no longer supported; configure persona and tool plugins
76
- * in an Agent Preset and select it with agentPreset (spawn only)`
77
- * while the same composition on 0.2.0-rc.2 (which still accepts `toolFilter`) mounts. So a
78
- * key that one supported host accepts and the next one THROWS on is worse than dropping it:
79
- * the deny list protected nothing on rc.2 (no schedule tools exist there at all) and would
80
- * break every chapters session on the alpha. The general rule this incident writes down:
81
- * carry only config keys EVERY supported host accepts, and let the roster line — not a
82
- * file on disk — be the evidence that the composition is usable.
83
- */
84
- /**
85
- * The chapters preset: the host's standard coding-agent composition with EXACTLY
86
- * four deltas, each one a product decision of ours.
87
- *
88
- * 1. `compaction-basic` → `@treeseed/dsh-chapters/engine` (thresholdRatio 0.9):
89
- * the zero-inference `summarize()` — chapters from the log, no model tokens.
90
- * 2. `tool-result-pruner.thresholdChars` 8192 → 1000000, deliberately dormant:
91
- * chapters defer oversized results at ARCHIVE time (verbatim bodies +
92
- * artifact references), never in-surface, where a prune is a prefix cliff on
93
- * local hardware.
94
- * 3. the persona gains the PLOT: suffix (plot carriage through in-place
95
- * checkpoints) and keeps no `{{cwd}}` (see PLOT_SUFFIX).
96
- * 4. `tool-ralph` mounted ENABLED — the bounded goal loop the continuation
97
- * children run. The host ships it disabled; enabling it here is ours.
98
- *
99
- * @param platform the platform the shell-gated rows are decided against; a
100
- * parameter so the rows are testable on any machine.
101
- */
102
- export declare function chaptersPresetRows(platform?: NodeJS.Platform, thresholdRatio?: number): PresetRow[];
103
- /** The preset identity. `config.id` is DURABLE STATE: every continuation child's
104
- * log records it as `agent-preset/selected`, so renaming it orphans those
105
- * sessions (`agent-preset/not-found` on open). */
61
+ /** The preset identity. `config.id` is DURABLE STATE: every continuation child's log
62
+ * records it as `agent-preset/selected`, so renaming it orphans those sessions. */
106
63
  export declare const CHAPTERS_PRESET_ID = "chapters";
107
- export declare function chaptersPresetDefinition(platform?: NodeJS.Platform, thresholdRatio?: number): PresetDefinition;
108
- /** A row whose own module cannot be imported would activate as failed, and one
109
- * failed row makes the WHOLE preset broken (`mountPreset` throws) — so it is
110
- * dropped, with the reason, instead of taking every chapters session down.
64
+ /**
65
+ * Drop rows whose module cannot be imported, keeping the group structure intact.
111
66
  *
112
- * The probe is asked with the row's FULL specifier, so a package that exists but
113
- * no longer exports the subpath (`.../tools`, `.../list-agents`) is caught too —
114
- * those are the renames that eat presets.
67
+ * The probe is asked with the row's FULL specifier, so a package that still exists but no
68
+ * longer exports the subpath a row names (`…/tools`, `…/list-agents`) is caught too — those
69
+ * renames are what eat presets between host versions.
115
70
  *
116
71
  * @param rows the composition, groups included.
117
72
  * @param resolvable asked with each row's module specifier. MUST NOT throw.
73
+ * @returns the composition to register, and every drop NAMED — a silent drop is how a
74
+ * preset loses a tool nobody notices.
118
75
  */
119
- export declare function pruneResolvableRows(rows: PresetRow[], resolvable: (specifier: string) => boolean): {
76
+ export declare function pruneUnresolvableRows(rows: PresetRow[], resolvable: (specifier: string) => boolean): {
120
77
  kept: PresetRow[];
121
78
  dropped: string[];
122
79
  };
123
- /** What registering a preset can end up as. Every arm has its own log line and
124
- * its own remedy, because the silent case is the one that cost a human hours. */
80
+ /** What registering a preset can end up as. Every arm has its own log line and its own
81
+ * remedy, because the silent case is the one that cost a human hours. */
125
82
  export type RegisterOutcome = {
126
83
  state: 'registered';
127
- }
128
- /** config.installChaptersPreset=false: the operator owns the preset, we do not register. */
129
- | {
130
- state: 'disabled';
131
- } | {
132
- state: 'already-declared';
133
84
  } | {
134
85
  state: 'pruned';
135
86
  dropped: string[];
87
+ } | {
88
+ state: 'already-declared';
136
89
  } | {
137
90
  state: 'unsupported';
138
91
  } | {
139
92
  state: 'failed';
140
93
  reason: string;
141
94
  };
142
- /** The narrow surface of `ctx.get('agentPresets')` this module uses. */
95
+ /** The narrow surface of `ctx.get('agentPresets')` this row uses. */
143
96
  export interface PresetRegistryLike {
144
97
  register?: (definition: PresetDefinition) => Promise<() => Promise<void>>;
145
98
  list?: () => Promise<unknown>;
146
- /** Host-side inventory of every declared preset and its mounted rows —
147
- * `compositionInventory()` in `@deepseek-ai/dsh-agent-preset-registry`. */
99
+ /** `compositionInventory()` in `@deepseek-ai/dsh-agent-preset-registry`: every declared
100
+ * preset with its mounted rows and their Loader states. */
148
101
  compositionInventory?: () => Promise<unknown>;
149
102
  }
150
- /** One mounted row of a live preset composition, as the host's own inventory reports it.
103
+ /**
104
+ * Register a declared preset, dropping rows this host cannot import.
105
+ *
106
+ * Split out of the plugin class so every arm is unit-testable without a host: the caller
107
+ * hands in the registry surface and a resolver.
108
+ *
109
+ * @returns the outcome AND the disposer the registry hands back — the caller must keep it
110
+ * alive for the row's lifetime, which is what yielding it from `[Service.init]`
111
+ * does; nothing else has the right lifetime (see the module header).
112
+ */
113
+ export declare function registerPrunedPreset(presets: PresetRegistryLike | undefined, definition: PresetDefinition, resolvable: (specifier: string) => boolean): Promise<{
114
+ outcome: RegisterOutcome;
115
+ disposer?: () => Promise<void>;
116
+ }>;
117
+ /** One mounted row of a live preset composition, as the host's inventory reports it.
151
118
  * `fiberState` is the Loader's state for that row; an enabled row that is not `active`
152
119
  * cannot serve a session, and the roster's `broken` flag reports that only for the whole
153
- * mount — which is why the witness asks for the rows too. */
120
+ * mount — which is why the witness asks for the rows as well. */
154
121
  export interface InventoryRow {
155
122
  entryId?: string;
156
123
  moduleName?: string;
@@ -165,27 +132,55 @@ export interface InventoryEntry {
165
132
  }
166
133
  /** Find one preset in an inventory response, tolerating a missing or odd shape. */
167
134
  export declare function inventoryEntry(raw: unknown, presetId: string): InventoryEntry | undefined;
168
- /** Enabled rows of one preset that are not active. A row `pending` a host service can
169
- * still activate later, so this is reported apart from `broken`, never as a refusal. */
135
+ /** Enabled rows of one preset that are not active. A row `pending` a host service can still
136
+ * activate later, so this is reported apart from `broken` and never as a refusal. */
170
137
  export declare function unusableRows(entry: InventoryEntry | undefined): string[];
171
138
  /**
172
- * Make the `chapters` preset exist on a host that composes presets from
173
- * declaration rows, and say what happened.
139
+ * Whether a module specifier can be imported from THIS package's resolution family.
174
140
  *
175
- * @param presets the host's `agentPresets` service, or undefined on a host that
176
- * has no preset registry at all (then the legacy directory copy is the
177
- * delivery, and this returns 'unsupported').
178
- * @param dispose owner for the disposer the registry hands back — registering
179
- * without it would leave a standing preset mount after unmount.
141
+ * Two limits are the host's, not ours to paper over: a RELATIVE specifier resolves without
142
+ * touching the filesystem (so a file-URL row would always be reported resolvable — the
143
+ * composition uses package specifiers only, and the drift test pins that), and a name that
144
+ * resolves is not yet a name that ACTIVATES, which is what the roster line reports as
145
+ * `DECLARED BUT BROKEN`. Never throws: an unresolvable specifier is the answer.
180
146
  */
181
- export declare function registerChaptersPreset(presets: PresetRegistryLike | undefined, opts: {
182
- resolvable: (specifier: string) => boolean;
183
- platform?: NodeJS.Platform;
184
- /** Engine pressure threshold for the realm row; the host plane's
185
- * `config.compactionThresholdRatio` is what feeds it (no hardcoded tunables). */
186
- thresholdRatio?: number;
187
- dispose: (disposer: () => Promise<void>) => void;
188
- }): Promise<{
189
- outcome: RegisterOutcome;
190
- definition: PresetDefinition;
191
- }>;
147
+ export declare function resolvableFromThisPackage(specifier: string): boolean;
148
+ /** The narrow slice of the Cordis context this row needs. */
149
+ interface RowCtx {
150
+ agentPresets: PresetRegistryLike;
151
+ logger?: {
152
+ info?: (m: string) => void;
153
+ warn?: (m: string) => void;
154
+ };
155
+ }
156
+ /**
157
+ * The declaration row. `config` IS the preset definition, exactly as the host's
158
+ * `AgentPreset` plugin consumes it, so a profile patch can override `preset-chapters`
159
+ * wholesale or `disabled: true` it — the two things a runtime registration done in
160
+ * `apply()` cannot offer, and the reason this is a row.
161
+ */
162
+ export default class ChaptersPresetRow {
163
+ static inject: readonly ["agentPresets"];
164
+ /** Preserve child `!!js` expressions until their own plugins activate. */
165
+ static [EntryGroup.key]: boolean;
166
+ static Config: z<Schemastery.ObjectS<{
167
+ id: z<string, string>;
168
+ name: z<string, string>;
169
+ description: z<string, string>;
170
+ order: z<number, number>;
171
+ plugins: z<any[], any[]>;
172
+ }>, Schemastery.ObjectT<{
173
+ id: z<string, string>;
174
+ name: z<string, string>;
175
+ description: z<string, string>;
176
+ order: z<number, number>;
177
+ plugins: z<any[], any[]>;
178
+ }>>;
179
+ ctx: RowCtx;
180
+ config: PresetDefinition;
181
+ constructor(ctx: RowCtx, config: PresetDefinition);
182
+ /** Registers, then yields the disposer so Cordis owns the registration's lifetime — the
183
+ * host's own row plugin does exactly this (`yield await this.ctx.agentPresets.register(…)`). */
184
+ [Service.init](): AsyncGenerator<() => Promise<void>, void>;
185
+ }
186
+ export {};