@xiaoso/dsh-tool-plus 0.1.9 → 0.1.10-beta.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.
Files changed (40) hide show
  1. package/README.en.md +12 -5
  2. package/README.md +11 -4
  3. package/THIRD_PARTY_NOTICES.md +36 -0
  4. package/lib/client.js +272 -181
  5. package/lib/index.mjs +875 -1081
  6. package/lib/{scrapers-mhvFrvjH.mjs → scrapers-DODCPcZW.mjs} +1 -1
  7. package/lib/types/client/BashPlusCard.d.ts +6 -6
  8. package/lib/types/client/ToolPlusSection.d.ts +3 -3
  9. package/lib/types/client/forms.d.ts +6 -6
  10. package/lib/types/client/index.d.ts +25 -12
  11. package/lib/types/client/locales.d.ts +1 -1
  12. package/lib/types/client/preset-panel.d.ts +54 -42
  13. package/lib/types/config/settings.d.ts +202 -21
  14. package/lib/types/host/browser-probe-rpc.d.ts +2 -1
  15. package/lib/types/host/preset-host.d.ts +103 -0
  16. package/lib/types/host/preset-rpc.d.ts +37 -72
  17. package/lib/types/presets/compare.d.ts +14 -37
  18. package/lib/types/presets/conflicts.d.ts +59 -0
  19. package/lib/types/presets/delta.d.ts +23 -18
  20. package/lib/types/presets/plan.d.ts +55 -0
  21. package/lib/types/presets/rows.d.ts +84 -0
  22. package/lib/types/presets/status.d.ts +56 -47
  23. package/lib/types/tools/bash/background.d.ts +20 -5
  24. package/lib/types/tools/bash/streaming-output.d.ts +4 -2
  25. package/lib/types/tools/shared/browser-rpc-channel.d.ts +84 -42
  26. package/lib/types/web/client/labels.d.ts +1 -1
  27. package/lib/types/web/client/row-utils.d.ts +26 -1
  28. package/lib/{utils-eiUz9n8I.mjs → utils-D80w8vry.mjs} +1 -1
  29. package/package.json +61 -46
  30. package/presets/tool-plus-ptc.patch.yml +164 -0
  31. package/presets/tool-plus-standard.patch.yml +158 -0
  32. package/lib/types/presets/install.d.ts +0 -44
  33. package/lib/types/presets/paths.d.ts +0 -41
  34. package/lib/types/presets/rewrite.d.ts +0 -86
  35. package/presets/install-presets.mjs +0 -143
  36. package/presets/package.json +0 -42
  37. package/presets/tool-plus-ptc/agent.cordis.yml +0 -284
  38. package/presets/tool-plus-ptc/preset.yml +0 -3
  39. package/presets/tool-plus-standard/agent.cordis.yml +0 -269
  40. package/presets/tool-plus-standard/preset.yml +0 -3
@@ -1,16 +1,35 @@
1
1
  /**
2
- * Plugin-owned settings surface for the ported bash tool: the `bash-plus`
3
- * namespace, a FLAT settings schema (OMP `bash*`-style scalar keys — the
4
- * client scope writes scalar fields only), its defaults, the mapping onto the
5
- * nested {@link RuntimeConfig} the runtime consumes, and the optional-settings
6
- * consumer wiring. Keeping the whole config-export surface here lets the
7
- * ported OMP runtime stay pristine — the entry only calls
2
+ * Plugin-owned settings surface for the ported bash tool: the `tool-plus`
3
+ * namespace, a FLAT settings schema (OMP `bash*`-style scalar keys — the shared
4
+ * client configuration form writes scalar fields only), its defaults, the
5
+ * mapping onto the nested {@link RuntimeConfig} the runtime consumes, and the
6
+ * optional-settings consumer wiring. Keeping the whole config-export surface
7
+ * here lets the ported OMP runtime stay pristine — the entry only calls
8
8
  * {@link installBashPlusSettings} and re-exports this module's `Config`.
9
9
  * @module @xiaoso/dsh-tool-plus/settings
10
10
  */
11
11
  import type { Context } from '@deepseek-ai/cordis';
12
12
  import z from '@deepseek-ai/schemastery';
13
13
  import type { MinimizerConfig } from '../tools/bash/types.ts';
14
+ /**
15
+ * The Loader commits a settings write into this plugin's live config
16
+ * references and announces the changed paths on the owning fiber
17
+ * (`@deepseek-ai/cordis-plugin-loader` declares this event). The declaration
18
+ * lives in the Loader package, which a business plugin does not depend on, so
19
+ * the one event this module consumes is spelled here — copied verbatim from the
20
+ * Loader's own declaration, so a future signature change fails loudly here
21
+ * instead of silently missing the notification.
22
+ */
23
+ declare module '@deepseek-ai/cordis' {
24
+ interface Events {
25
+ /**
26
+ * Volatile config values were committed into the running fiber without a remount; dispatched to the owning fiber only.
27
+ * @param paths - changed config paths as key arrays; every value is committed before dispatch.
28
+ * @mode emit
29
+ */
30
+ 'loader/volatile-update'(paths: readonly (readonly string[])[]): void;
31
+ }
32
+ }
14
33
  /** Truncation strategy for background→foreground completion messages (OMP config parity). */
15
34
  export type OutputTruncateStrategy = 'bytes' | 'lines';
16
35
  /** Retention mode for a truncated completion message: head, tail, or middle (head+tail). */
@@ -125,8 +144,9 @@ export declare const DEFAULT_AUTO_BACKGROUND_MS = 60000;
125
144
  export declare const DEFAULT_MAX_BACKGROUND_JOBS = 15;
126
145
  /**
127
146
  * Plugin settings, one scalar key per option — every field is writable through
128
- * the client `SettingsScope.set`/`unset`. Defaults live in the {@link Config}
129
- * schema and in {@link resolveConfig}.
147
+ * the shared client configuration form (`ctx.configForms.get('tool-plus')`
148
+ * `.set`/`.unset`). Defaults live in the {@link Config} schema and in
149
+ * {@link resolveConfig}.
130
150
  */
131
151
  export interface Config {
132
152
  enableRunInBackground?: boolean;
@@ -193,28 +213,189 @@ export interface Config {
193
213
  imagesResizeJpegQuality?: number;
194
214
  }
195
215
  /**
196
- * Runtime configuration schema for the plugin. The namespace resolves through
197
- * this flat surface; {@link resolveConfig} maps it onto {@link RuntimeConfig}.
216
+ * Runtime configuration schema for the plugin, in the dsh 0.1.7 settings shape:
217
+ * EVERY field is declared `.volatile()`, because a settings namespace is the
218
+ * plugin's own entry and `SettingsForms.describe()` serves exactly the fields
219
+ * whose schema declares them live. {@link resolveConfig} maps the flat surface
220
+ * onto {@link RuntimeConfig}.
221
+ *
222
+ * The annotation is deliberately inferred rather than written as `z<Config>`:
223
+ * the schema's parsed output is {@link LiveConfig} (one stable reference per
224
+ * field), while {@link Config} stays the plain settings DOCUMENT the browser
225
+ * edits and the specs pass in.
226
+ */
227
+ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
228
+ enableRunInBackground: z<boolean, boolean, "volatile-defined">;
229
+ autoBackgroundMs: z<number, number, "volatile-defined">;
230
+ defaultTimeoutMs: z<number, number, "volatile-defined">;
231
+ maxTimeoutMs: z<number, number, "volatile-defined">;
232
+ outputMaxBytes: z<number, number, "volatile-defined">;
233
+ outputSinkTailBytes: z<number, number, "volatile-defined">;
234
+ outputSinkHeadBytes: z<number, number, "volatile-defined">;
235
+ minimizerEnabled: z<boolean, boolean, "volatile-defined">;
236
+ interceptorEnabled: z<boolean, boolean, "volatile-defined">;
237
+ nonInteractiveEnv: z<boolean, boolean, "volatile-defined">;
238
+ snapshotEnabled: z<boolean, boolean, "volatile-defined">;
239
+ rmSafe: z<boolean, boolean, "volatile-defined">;
240
+ useShellCommandWrapper: z<boolean, boolean, "volatile-defined">;
241
+ webCards: z<boolean, boolean, "volatile-defined">;
242
+ maxBackgroundJobs: z<number, number, "volatile-defined">;
243
+ outputTruncateStrategy: z<"lines" | "bytes", "lines" | "bytes", "volatile-defined">;
244
+ outputTruncateTriggerBytes: z<number, number, "volatile-defined">;
245
+ outputTruncateTriggerLines: z<number, number, "volatile-defined">;
246
+ outputTruncateByteMode: z<"middle" | "head" | "tail", "middle" | "head" | "tail", "volatile-defined">;
247
+ outputTruncateByteHeadBytes: z<number, number, "volatile-defined">;
248
+ outputTruncateByteTailBytes: z<number, number, "volatile-defined">;
249
+ outputTruncateLineMode: z<"middle" | "head" | "tail", "middle" | "head" | "tail", "volatile-defined">;
250
+ outputTruncateLineHeadLines: z<number, number, "volatile-defined">;
251
+ outputTruncateLineTailLines: z<number, number, "volatile-defined">;
252
+ editBlockAutoGenerated: z<boolean, boolean, "volatile-defined">;
253
+ readSummarizeEnabled: z<boolean, boolean, "volatile-defined">;
254
+ readSummarizeProse: z<boolean, boolean, "volatile-defined">;
255
+ readSummarizeMinBodyLines: z<number, number, "volatile-defined">;
256
+ readSummarizeMinCommentLines: z<number, number, "volatile-defined">;
257
+ readSummarizeMinTotalLines: z<number, number, "volatile-defined">;
258
+ readSummarizeUnfoldUntil: z<number, number, "volatile-defined">;
259
+ readSummarizeUnfoldLimit: z<number, number, "volatile-defined">;
260
+ editMode: z<"replace" | "patch" | "hashline" | "apply_patch", "replace" | "patch" | "hashline" | "apply_patch", "volatile-defined">;
261
+ editFuzzyMatch: z<boolean, boolean, "volatile-defined">;
262
+ editFuzzyThreshold: z<number, number, "volatile-defined">;
263
+ editEnforceSeenLines: z<boolean, boolean, "volatile-defined">;
264
+ readDefaultLimit: z<number, number, "volatile-defined">;
265
+ readLineNumbers: z<boolean, boolean, "volatile-defined">;
266
+ readRenderMarkdown: z<boolean, boolean, "volatile-defined">;
267
+ readConcurrentSafe: z<boolean, boolean, "volatile-defined">;
268
+ grepContextBefore: z<number, number, "volatile-defined">;
269
+ grepContextAfter: z<number, number, "volatile-defined">;
270
+ grepCaseDefault: z<boolean, boolean, "volatile-defined">;
271
+ grepGitignoreDefault: z<boolean, boolean, "volatile-defined">;
272
+ globGitignoreDefault: z<boolean, boolean, "volatile-defined">;
273
+ globHiddenDefault: z<boolean, boolean, "volatile-defined">;
274
+ astGrepEnabled: z<boolean, boolean, "volatile-defined">;
275
+ astEditEnabled: z<boolean, boolean, "volatile-defined">;
276
+ fetchEnabled: z<boolean, boolean, "volatile-defined">;
277
+ fetchMaxTimeoutSeconds: z<number, number, "volatile-defined">;
278
+ fetchReader: z<"trafilatura" | "lynx" | "auto" | "native" | "parallel" | "jina" | "browser", "trafilatura" | "lynx" | "auto" | "native" | "parallel" | "jina" | "browser", "volatile-defined">;
279
+ browserReaderEnabled: z<boolean, boolean, "volatile-defined">;
280
+ imagesAutoResize: z<boolean, boolean, "volatile-defined">;
281
+ imagesBlockImages: z<boolean, boolean, "volatile-defined">;
282
+ imagesExcludeWebp: z<boolean, boolean, "volatile-defined">;
283
+ imagesInputMaxBytes: z<number, number, "volatile-defined">;
284
+ imagesResizeMaxSide: z<number, number, "volatile-defined">;
285
+ imagesResizeMaxBytes: z<number, number, "volatile-defined">;
286
+ imagesResizeMinSide: z<number, number, "volatile-defined">;
287
+ imagesResizeJpegQuality: z<number, number, "volatile-defined">;
288
+ }>>, Schemastery.ObjectT<NoInfer<{
289
+ enableRunInBackground: z<boolean, boolean, "volatile-defined">;
290
+ autoBackgroundMs: z<number, number, "volatile-defined">;
291
+ defaultTimeoutMs: z<number, number, "volatile-defined">;
292
+ maxTimeoutMs: z<number, number, "volatile-defined">;
293
+ outputMaxBytes: z<number, number, "volatile-defined">;
294
+ outputSinkTailBytes: z<number, number, "volatile-defined">;
295
+ outputSinkHeadBytes: z<number, number, "volatile-defined">;
296
+ minimizerEnabled: z<boolean, boolean, "volatile-defined">;
297
+ interceptorEnabled: z<boolean, boolean, "volatile-defined">;
298
+ nonInteractiveEnv: z<boolean, boolean, "volatile-defined">;
299
+ snapshotEnabled: z<boolean, boolean, "volatile-defined">;
300
+ rmSafe: z<boolean, boolean, "volatile-defined">;
301
+ useShellCommandWrapper: z<boolean, boolean, "volatile-defined">;
302
+ webCards: z<boolean, boolean, "volatile-defined">;
303
+ maxBackgroundJobs: z<number, number, "volatile-defined">;
304
+ outputTruncateStrategy: z<"lines" | "bytes", "lines" | "bytes", "volatile-defined">;
305
+ outputTruncateTriggerBytes: z<number, number, "volatile-defined">;
306
+ outputTruncateTriggerLines: z<number, number, "volatile-defined">;
307
+ outputTruncateByteMode: z<"middle" | "head" | "tail", "middle" | "head" | "tail", "volatile-defined">;
308
+ outputTruncateByteHeadBytes: z<number, number, "volatile-defined">;
309
+ outputTruncateByteTailBytes: z<number, number, "volatile-defined">;
310
+ outputTruncateLineMode: z<"middle" | "head" | "tail", "middle" | "head" | "tail", "volatile-defined">;
311
+ outputTruncateLineHeadLines: z<number, number, "volatile-defined">;
312
+ outputTruncateLineTailLines: z<number, number, "volatile-defined">;
313
+ editBlockAutoGenerated: z<boolean, boolean, "volatile-defined">;
314
+ readSummarizeEnabled: z<boolean, boolean, "volatile-defined">;
315
+ readSummarizeProse: z<boolean, boolean, "volatile-defined">;
316
+ readSummarizeMinBodyLines: z<number, number, "volatile-defined">;
317
+ readSummarizeMinCommentLines: z<number, number, "volatile-defined">;
318
+ readSummarizeMinTotalLines: z<number, number, "volatile-defined">;
319
+ readSummarizeUnfoldUntil: z<number, number, "volatile-defined">;
320
+ readSummarizeUnfoldLimit: z<number, number, "volatile-defined">;
321
+ editMode: z<"replace" | "patch" | "hashline" | "apply_patch", "replace" | "patch" | "hashline" | "apply_patch", "volatile-defined">;
322
+ editFuzzyMatch: z<boolean, boolean, "volatile-defined">;
323
+ editFuzzyThreshold: z<number, number, "volatile-defined">;
324
+ editEnforceSeenLines: z<boolean, boolean, "volatile-defined">;
325
+ readDefaultLimit: z<number, number, "volatile-defined">;
326
+ readLineNumbers: z<boolean, boolean, "volatile-defined">;
327
+ readRenderMarkdown: z<boolean, boolean, "volatile-defined">;
328
+ readConcurrentSafe: z<boolean, boolean, "volatile-defined">;
329
+ grepContextBefore: z<number, number, "volatile-defined">;
330
+ grepContextAfter: z<number, number, "volatile-defined">;
331
+ grepCaseDefault: z<boolean, boolean, "volatile-defined">;
332
+ grepGitignoreDefault: z<boolean, boolean, "volatile-defined">;
333
+ globGitignoreDefault: z<boolean, boolean, "volatile-defined">;
334
+ globHiddenDefault: z<boolean, boolean, "volatile-defined">;
335
+ astGrepEnabled: z<boolean, boolean, "volatile-defined">;
336
+ astEditEnabled: z<boolean, boolean, "volatile-defined">;
337
+ fetchEnabled: z<boolean, boolean, "volatile-defined">;
338
+ fetchMaxTimeoutSeconds: z<number, number, "volatile-defined">;
339
+ fetchReader: z<"trafilatura" | "lynx" | "auto" | "native" | "parallel" | "jina" | "browser", "trafilatura" | "lynx" | "auto" | "native" | "parallel" | "jina" | "browser", "volatile-defined">;
340
+ browserReaderEnabled: z<boolean, boolean, "volatile-defined">;
341
+ imagesAutoResize: z<boolean, boolean, "volatile-defined">;
342
+ imagesBlockImages: z<boolean, boolean, "volatile-defined">;
343
+ imagesExcludeWebp: z<boolean, boolean, "volatile-defined">;
344
+ imagesInputMaxBytes: z<number, number, "volatile-defined">;
345
+ imagesResizeMaxSide: z<number, number, "volatile-defined">;
346
+ imagesResizeMaxBytes: z<number, number, "volatile-defined">;
347
+ imagesResizeMinSide: z<number, number, "volatile-defined">;
348
+ imagesResizeJpegQuality: z<number, number, "volatile-defined">;
349
+ }>>, "plain">;
350
+ /**
351
+ * Live entry config: what the Loader hands `apply` for this entry, and what
352
+ * {@link installBashPlusSettings} re-reads after a settings write commits. One
353
+ * stable reference per schema field — read it through `.get()`, never as a
354
+ * plain value.
198
355
  */
199
- export declare const Config: z<Config>;
356
+ export type LiveConfig = ReturnType<typeof Config>;
200
357
  /**
201
358
  * Resolve the flat settings (composition entry or the settings document) to
202
359
  * the nested runtime config the ported OMP runtime consumes. A direct mount
203
360
  * (`ctx.plugin`) skips the Loader's schema parse, so the `?? default` pass
204
361
  * mirrors the schema; the settings namespace resolution rides the same path.
205
- * @param config - flat settings, possibly partial and not schema-parsed.
362
+ * Accepts both shapes a caller can hold: the Loader's live entry config (one
363
+ * reference per field) and a plain settings document.
364
+ * @param input - the entry's live config, or a plain (possibly partial) document.
206
365
  * @returns the fully-defaulted nested runtime config.
207
366
  */
208
- export declare function resolveConfig(config: Config): RuntimeConfig;
367
+ export declare function resolveConfig(input: Config | LiveConfig): RuntimeConfig;
209
368
  /**
210
- * Install the optional-settings consumer wiring for this plugin: while a
211
- * settings provider is mounted, resolve through the `tool-plus` namespace
212
- * (schema defaults → composition entry `base` → user document) and forward the
213
- * scope to `onSource`; when none is mounted, keep the composition entry, so
214
- * the tool keeps working exactly as composed. Mirrors the official bash-local
215
- * pattern so a committed change applies without a reload.
369
+ * Install the optional-settings consumer wiring for this plugin, in the dsh
370
+ * 0.1.7 shape.
371
+ *
372
+ * 0.1.7 deleted the namespace-registration API (`SettingsForms` no longer has
373
+ * `installSection`): a settings namespace is not something a plugin installs
374
+ * any more — it IS the plugin's own profile entry, and the service serves
375
+ * exactly the Config fields whose schema declares them live
376
+ * ({@link Config} declares every field `.volatile()`). The plugin therefore
377
+ * keeps one authoritative source, its own entry config:
378
+ *
379
+ * - the Loader merges the composition layers and the profile override before
380
+ * `apply` runs, so the entry already carries the effective settings;
381
+ * - a settings write is committed INTO that entry's live references without a
382
+ * remount, and the Loader then announces the changed paths on this fiber
383
+ * (`loader/volatile-update`) — which is the signal for refreshing
384
+ * registration facts (the `editMode`-sensitive descriptions and the AST
385
+ * switches), exactly as the settings docs prescribe.
386
+ *
387
+ * The plugin ships its own settings pages in the browser half, so it owns its
388
+ * page policy: `configure({ auto: false })` tells the settings service not to
389
+ * auto-generate a schema page for this entry. That call rides an optional
390
+ * `ctx.inject(['settings'], ...)` child, so the plugin runs unchanged in a
391
+ * deployment that composes no settings service at all.
392
+ *
393
+ * Mirrors the official bash-local pattern (`packages/shell/bash-local`): the
394
+ * business plugin reads its own Config references and registers only its page
395
+ * policy; nothing re-registers the namespace.
216
396
  * @param ctx - plugin context owning the wiring.
217
- * @param entry - composition entry config declared by the caller.
397
+ * @param entry - the entry's config as the Loader resolved it (live references
398
+ * over the composition layers and the profile override), or a plain document.
218
399
  * @param onSource - receives a thunk of the currently authoritative runtime config.
219
400
  */
220
- export declare function installBashPlusSettings(ctx: Context, entry: Config, onSource: (current: () => RuntimeConfig) => void): void;
401
+ export declare function installBashPlusSettings(ctx: Context, entry: Config | LiveConfig, onSource: (current: () => RuntimeConfig) => void): void;
@@ -6,7 +6,8 @@
6
6
  *
7
7
  * **Why this registers its own route instead of calling `connection.rpc.handle`.**
8
8
  * The documented helper is unusable from a plugin in a real web deployment on
9
- * dsh 0.1.5-rc.1: `HostConnectionService.rpc` builds its handle against
9
+ * dsh 0.1.5-rc.1 — and still on 0.1.7-rc.1, re-checked for this migration:
10
+ * `HostConnectionService.rpc` builds its handle against
10
11
  * `owner = this.ctx` (`rpc-host.ts: "Generic channel registry scoped to the
11
12
  * Context reading this service"`), and `register()` then evaluates
12
13
  * `owner.webServer.register(route)`. The client-connection row declares
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Host half of the preset surface: read the deployment's declared presets and
3
+ * apply the panel's three actions through the harness's own config editor.
4
+ *
5
+ * WHY WE DO NOT WRITE FILES OURSELVES: dsh 0.1.7 removed the directory preset
6
+ * mechanism and the `agent_preset` tool with it. The only supported programmatic
7
+ * write path is `ctx.configEditor.edit()`, and it is a better one than we had —
8
+ * it takes the profile lock, serializes with HMR, validates the next config
9
+ * through the Loader's own waterfall, writes atomically, rolls back on a failed
10
+ * reconcile, and refuses when a home patch or `--patch` overlay would defeat the
11
+ * edit. It also gives us "revert to the bundled declaration" for free: handing
12
+ * back the inherited config makes it delete the override's `config` key.
13
+ *
14
+ * The service is resolved per call, so a deployment that mounts the editor
15
+ * after this plugin still works instead of caching an early `undefined`.
16
+ *
17
+ * Everything here is a thin adapter; the decisions live in `src/presets/**`
18
+ * (pure, fs-free, unit-testable) and this module only resolves services and
19
+ * shapes results.
20
+ * @module @xiaoso/dsh-tool-plus/host/preset-host
21
+ */
22
+ import { type PresetActionResultValue, type PresetCompareValue, type PresetStatusListValue } from '../tools/shared/browser-rpc-channel.ts';
23
+ import { type PresetActionKind } from '../presets/plan.ts';
24
+ /** The context slice this module needs: Cordis service lookup only. */
25
+ export interface PresetHostCtx {
26
+ get(name: string): unknown;
27
+ }
28
+ /** Structural view of one addressable Loader entry. */
29
+ interface EntryLike {
30
+ options: {
31
+ id: string;
32
+ name?: string;
33
+ config?: unknown;
34
+ };
35
+ }
36
+ /** One `configEditor.configuration()` row. */
37
+ interface ConfigurationRowLike {
38
+ entry: EntryLike;
39
+ /** The bundled layer's config (our shipped declaration). */
40
+ inherited: Record<string, unknown>;
41
+ /** The profile layer's config; `{}` when the user never overrode the row. */
42
+ override: Record<string, unknown>;
43
+ }
44
+ /**
45
+ * The slice of `@deepseek-ai/dsh-config-editor` this plugin uses. Declared
46
+ * structurally so the plugin keeps zero build-time coupling to a package it
47
+ * never bundles.
48
+ */
49
+ export interface ConfigEditorLike {
50
+ /** Absolute path of the profile patch this editor writes. */
51
+ readonly documentPath: string;
52
+ entries(): readonly EntryLike[];
53
+ configuration(): readonly ConfigurationRowLike[];
54
+ edit(entry: EntryLike, change: (current: Record<string, unknown>, inherited: Record<string, unknown>) => Record<string, unknown>): Promise<void>;
55
+ }
56
+ /** One roster entry as the registry reports it. */
57
+ interface RosterRowLike {
58
+ readonly id?: unknown;
59
+ readonly name?: unknown;
60
+ readonly description?: unknown;
61
+ readonly broken?: unknown;
62
+ readonly isDefault?: unknown;
63
+ }
64
+ /**
65
+ * The slice of the preset registry this plugin uses.
66
+ *
67
+ * `remoteExportList()` is preferred: it is the roster the official client reads,
68
+ * and unlike `list()` it also carries `isDefault`. `list()` remains the fallback
69
+ * for a deployment whose registry predates the exported roster.
70
+ */
71
+ export interface AgentPresetsLike {
72
+ list?(): Promise<readonly RosterRowLike[]>;
73
+ remoteExportList?(): Promise<{
74
+ readonly presets?: readonly RosterRowLike[];
75
+ } | undefined>;
76
+ }
77
+ /**
78
+ * Answer `presets/status`.
79
+ * @param ctx - context used to resolve the config editor and the registry.
80
+ * @returns the panel's full status payload.
81
+ */
82
+ export declare function listPresetStatuses(ctx: PresetHostCtx): Promise<PresetStatusListValue>;
83
+ /**
84
+ * Answer `presets/apply` by planning the action and, when it is worth writing,
85
+ * delegating the write to the harness's own config editor.
86
+ * @param ctx - context used to resolve the services.
87
+ * @param presetId - the preset to act on.
88
+ * @param action - `upgrade`, `align` or `revert`.
89
+ * @param templateId - the align target (required for `align`).
90
+ * @returns the action result, never throwing.
91
+ */
92
+ export declare function applyPresetAction(ctx: PresetHostCtx, presetId: string, action: PresetActionKind, templateId?: string): Promise<PresetActionResultValue>;
93
+ /**
94
+ * Answer `presets/compare`: one preset's effective plugins against one of our
95
+ * declared templates. Never throws; an unresolvable side reports `unreadable`
96
+ * (that is a state the panel renders as a red dot, not a failed call).
97
+ * @param ctx - context used to resolve the services.
98
+ * @param presetId - the preset being inspected.
99
+ * @param templateId - the template to compare it against.
100
+ * @returns the comparison.
101
+ */
102
+ export declare function comparePresetAgainstTemplate(ctx: PresetHostCtx, presetId: string, templateId: string): Promise<PresetCompareValue>;
103
+ export {};
@@ -1,102 +1,67 @@
1
1
  /**
2
2
  * Host half of the preset endpoints: answers `presets/status` (roster +
3
- * conflicts + template diff) and `presets/apply` (disable conflicting rows /
4
- * reset from our template) for the settings panel's 「全局」tab.
3
+ * conflicts + template diff), `presets/apply` (upgrade / align / revert) and
4
+ * `presets/compare` for the settings panel's 「全局」tab.
5
5
  *
6
- * These endpoints live on the **same** `/tool-plus` channel registration as
7
- * `browser/detect` and `rmSafe/status` (see `browser-probe-rpc.ts`): a
8
- * Connection channel has exactly one handler, so a second
9
- * `connection.rpc.handle('/tool-plus', …)` never takes effect. That is why this
10
- * module exports an endpoint handler instead of installing a channel of its
11
- * own.
6
+ * These endpoints live on the **same** `/tool-plus` route as `browser/detect`
7
+ * and `rmSafe/status` (see `browser-probe-rpc.ts`). That route is a plain
8
+ * `webServer.register({ kind: 'prefix', path: '/tool-plus' })` registration, NOT
9
+ * `connection.rpc.handle`: in this deployment the Connection handler resolves
10
+ * its owner through `this.ctx` without the `webServer` injection, so a
11
+ * `rpc.handle` registration never takes effect. The Connection service is still
12
+ * used for its loopback trust fence (`connection.requestRejection`).
12
13
  *
13
- * The preset analysis itself lives in `src/presets/**` and is **injected**, so
14
- * this module stays a thin wire adapter and its helpers are testable without a
15
- * Connection service or a filesystem.
16
- *
17
- * Trust policy is `loopback` (the channel's own policy): these endpoints read
18
- * and write files under the user's DSH home, so they must never be reachable
19
- * from a non-loopback host.
14
+ * This module is a wire adapter only: payload validation plus dispatch into an
15
+ * injected {@link PresetRpcDeps}. The service lookup, the configuration reads
16
+ * and the writes live in `../host/preset-host.ts`; the decision logic lives in
17
+ * `src/presets/**`. Trust policy is `loopback` — these endpoints read the user's
18
+ * profile configuration and the write path edits it, so they must never be
19
+ * reachable from a non-loopback host.
20
20
  * @module @xiaoso/dsh-tool-plus/host/preset-rpc
21
21
  */
22
22
  import type { ConnectionRpcHandler } from '@deepseek-ai/dsh-client-connection';
23
- import { type PresetActionPayload, type PresetComparePayloadValue } from '../tools/shared/browser-rpc-channel.ts';
24
- import type { PresetInstallResult } from '../presets/install.ts';
25
- import type { PresetComparison, PresetTemplate } from '../presets/compare.ts';
26
- import type { PresetRosterEntry, PresetStatus } from '../presets/status.ts';
27
- /** Payload shape of a `presets/compare` call (shared contract). */
28
- type PresetComparePayload = PresetComparePayloadValue;
23
+ import { type PresetActionPayload, type PresetActionResultValue, type PresetActionValue, type PresetComparePayloadValue, type PresetCompareValue, type PresetStatusListValue } from '../tools/shared/browser-rpc-channel.ts';
29
24
  /** Result shape of one Connection RPC call (official pattern in the gateway). */
30
25
  type ConnectionRpcResult = Awaited<ReturnType<ConnectionRpcHandler>>;
26
+ /** The context slice this handler needs: pass-through to the injected deps. */
27
+ export interface PresetEndpointCtx {
28
+ /** Cordis service lookup, forwarded to the host implementation. */
29
+ get(name: string): unknown;
30
+ }
31
31
  /**
32
- * Structural view of one official `ctx.agentPresets.list()` entry. Declared
33
- * here instead of importing `@deepseek-ai/dsh-agent-presets` so the plugin
34
- * keeps zero build-time coupling to a package it never bundles — the service
35
- * is reached through the context, and every field is validated on the way in.
32
+ * Everything the endpoints need from the host plane. Injected so this module
33
+ * stays a thin wire adapter whose parsing helpers are testable without a
34
+ * Connection service, a Loader, or a profile.
36
35
  */
37
- interface RosterPresetLike {
38
- readonly id?: unknown;
39
- readonly trust?: unknown;
40
- readonly path?: unknown;
41
- readonly name?: unknown;
42
- readonly description?: unknown;
43
- readonly broken?: unknown;
44
- }
45
- /** The only slice of the official agent-presets service this plugin uses. */
46
- export interface AgentPresetsServiceLike {
47
- list(): Promise<readonly RosterPresetLike[]>;
48
- }
49
- /** Install-time dependencies: the preset analysis owned by the host half. */
50
36
  export interface PresetRpcDeps {
51
- /** Build one status row per roster entry (reads the user root, never writes). */
52
- listStatuses: (roster: readonly PresetRosterEntry[]) => PresetStatus[];
53
- /** Apply one action to one preset (the only endpoint that writes). */
54
- applyAction: (id: string, action: PresetActionPayload['action'], templateId?: string) => PresetInstallResult;
55
- /** Templates this package ships (the panel's "compare with" options). */
56
- listTemplates: () => PresetTemplate[];
57
- /**
58
- * Read-only comparison between one roster preset and one of our templates.
59
- * Returns `status: 'unreadable'` instead of throwing when a file cannot be
60
- * read — that is a state the panel renders (red dot), not a failed call.
61
- */
62
- comparePreset: (preset: PresetRosterEntry, templateId: string) => PresetComparison;
37
+ /** Read every declared preset with its conflicts and template diff. */
38
+ listPresets: (ctx: PresetEndpointCtx) => Promise<PresetStatusListValue>;
39
+ /** Apply one action to one preset. The only path that writes. */
40
+ applyAction: (ctx: PresetEndpointCtx, id: string, action: PresetActionValue, templateId?: string) => Promise<PresetActionResultValue>;
41
+ /** Read-only comparison between one preset and one of our declarations. */
42
+ comparePreset: (ctx: PresetEndpointCtx, presetId: string, templateId: string) => Promise<PresetCompareValue>;
63
43
  }
64
44
  /**
65
45
  * Validate a `presets/compare` payload: two non-empty ids, nothing else.
66
46
  * @param payload - the raw RPC payload.
67
47
  * @returns the narrowed payload, or undefined when invalid.
68
48
  */
69
- export declare function parsePresetComparePayload(payload: unknown): PresetComparePayload | undefined;
49
+ export declare function parsePresetComparePayload(payload: unknown): PresetComparePayloadValue | undefined;
70
50
  /**
71
51
  * Validate a `presets/apply` payload. Anything that is not exactly
72
- * `{ id: non-empty string, action: 'upgrade' | 'reset' }` is rejected, so the
73
- * write endpoint never receives a half-formed request.
52
+ * `{ id: non-empty string, action: 'upgrade' | 'align' | 'revert', templateId?: non-empty string }`
53
+ * is rejected, so the write endpoint never receives a half-formed request.
74
54
  * @param payload - the raw RPC payload.
75
55
  * @returns the narrowed payload, or undefined when invalid.
76
56
  */
77
57
  export declare function parsePresetActionPayload(payload: unknown): PresetActionPayload | undefined;
78
58
  /**
79
- * Narrow one roster entry coming from the official service. Unknown trust is
80
- * treated as `user` (the conservative side: it is a writable preset only when
81
- * the caller proves otherwise — the caller matches ids against our templates).
82
- * @param preset - raw entry from `ctx.agentPresets.list()`.
83
- * @returns the entry in our own shape.
84
- */
85
- export declare function toRosterEntry(preset: RosterPresetLike): PresetRosterEntry;
86
- /** The context slice this handler needs: service lookup only. */
87
- export interface PresetEndpointCtx {
88
- /** Cordis service lookup; `agentPresets` is mounted by the harness. */
89
- get(name: string): unknown;
90
- }
91
- /**
92
- * Answer one preset endpoint on the shared `/tool-plus` channel.
59
+ * Answer one preset endpoint on the shared `/tool-plus` route.
93
60
  *
94
61
  * Returns `undefined` for an endpoint this module does not own, so the caller
95
- * (the single channel handler) can keep serving its own families. The service
96
- * is resolved per call: a deployment that mounts `agent-presets` after this
97
- * plugin still gets a working endpoint instead of a stale `undefined`.
98
- * @param ctx - context used to resolve the `agentPresets` service.
99
- * @param deps - injected preset analysis.
62
+ * (the single route handler) can keep serving its own families.
63
+ * @param ctx - context forwarded to the injected host implementation.
64
+ * @param deps - injected host implementation.
100
65
  * @param endpoint - the requested endpoint name.
101
66
  * @param payload - the raw RPC payload.
102
67
  * @returns the RPC result, or undefined when the endpoint is not ours.
@@ -1,28 +1,22 @@
1
1
  /**
2
- * 预设比较:把"某份预设"和"我们随包的某个模板"放在一起看。
2
+ * 预设比较:把"某份 preset 生效的 plugins"和"某个随包声明的 plugins"放在一起看。
3
3
  *
4
- * 面板现在是两块信息、两个状态点:
5
- * ①**工具行**:这份预设里我们那 4 个冲突行都禁用了吗(=`conflicts`);
6
- * ②**完整比较**:内容上和我们选的模板差多少(=`items`,弹窗里逐条展示)。
7
- * 这是只读操作,绝不写盘。比较一律**不跳过**冲突行的 `disabled` —— 第二个点
8
- * 要的是"完整的比较",工具开关的不同也必须出现在差异清单里,否则会出现
9
- * "说内容一致、却说工具行没配好"这种自相矛盾的读数。
4
+ * 面板是两块信息、两个状态点:
5
+ * ①**工具行**:这份 preset 里那 4 个冲突行都禁用了吗(=`conflicts`);
6
+ * ②**完整比较**:内容上和选中的模板差多少(=`items`,弹窗里逐条展示)。
7
+ * 只读,绝不写盘。比较一律**不跳过**冲突行的 `disabled` —— 第二个点要的是
8
+ * "完整的比较",工具开关的不同也必须出现在差异清单里,否则会出现"说内容一致、
9
+ * 却说工具行没配好"这种自相矛盾的读数。
10
10
  * @module @xiaoso/dsh-tool-plus/presets/compare
11
11
  */
12
- import { type PresetDeps } from './paths.ts';
13
12
  import { type PresetDeltaItem } from './delta.ts';
14
- /** 面板"对比模板"下拉的一项:模板 id(可读性靠 name)。 */
15
- export interface PresetTemplate {
16
- id: string;
17
- name?: string;
18
- }
19
13
  /** 一次比较的结果(面板两个状态点 + 弹窗的全部数据)。 */
20
14
  export interface PresetComparison {
21
- /** 任一侧读不到或形状不可识别 → `unreadable`,此时 `items` 为空。 */
15
+ /** 任一侧不是合法行列表 → `unreadable`,此时 `items` 为空。 */
22
16
  status: 'ok' | 'unreadable';
23
- /** 这份预设里还没禁用的冲突行(空 = 我们的工具已生效)。 */
17
+ /** 这份 preset 里还没禁用的冲突行(空 = 本插件的工具已生效)。 */
24
18
  conflicts: string[];
25
- /** 内容层面逐项一致(注释与排版差异不算,逐字节相同也不额外判断)。 */
19
+ /** 内容层面逐项一致(顺序不同不算差异,逐值比较)。 */
26
20
  identical: boolean;
27
21
  yoursCount: number;
28
22
  behindCount: number;
@@ -30,26 +24,9 @@ export interface PresetComparison {
30
24
  items: PresetDeltaItem[];
31
25
  }
32
26
  /**
33
- * 列出包里带模板的预设 id(面板"对比模板"下拉的选项)。只读目录,不抛。
34
- * @param deps - 路径注入(测试用)。
35
- * @returns 按 id 排序的模板清单(读不到目录时为空数组)。
36
- */
37
- export declare function listTemplates(deps?: PresetDeps): PresetTemplate[];
38
- /** 读一份模板的组合文件;不存在返回 undefined。 */
39
- export declare function readTemplateComposition(templateId: string, deps?: PresetDeps): string | undefined;
40
- /**
41
- * 比较两份组合文件文本(纯函数,只读)。
42
- * @param presetText - 所选预设的原文。
43
- * @param templateText - 所选模板的原文。
27
+ * 比较两份 `config.plugins`(纯函数,只读)。
28
+ * @param presetPlugins - 所选 preset 生效的 plugins。
29
+ * @param templatePlugins - 所选模板的 plugins(随包声明)。
44
30
  * @returns 两个状态点与弹窗需要的全部数据。
45
31
  */
46
- export declare function compareCompositions(presetText: string, templateText: string): PresetComparison;
47
- /**
48
- * 读盘版比较:预设一侧用 roster 给的组合文件路径,模板一侧按 id 去包里找。
49
- * 任一侧读不到都返回 `unreadable` —— 那是面板要渲染的红点,不是调用失败。
50
- * @param presetPath - 所选预设的组合文件绝对路径(来自 roster)。
51
- * @param templateId - 所选模板的 id。
52
- * @param deps - 路径注入(测试用)。
53
- * @returns 比较结果。
54
- */
55
- export declare function comparePresetFile(presetPath: string, templateId: string, deps?: PresetDeps): PresetComparison;
32
+ export declare function comparePlugins(presetPlugins: unknown, templatePlugins: unknown): PresetComparison;