@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
@@ -0,0 +1,59 @@
1
+ /**
2
+ * 冲突不变式:一个 preset「接上了本插件的工具」当且仅当官方那 4 个工具行
3
+ * 处于**不存在**或 **`disabled: true`** 状态。
4
+ *
5
+ * 原因(这套设计的地基,别丢):`@xiaoso/dsh-tool-plus` 作为包在**宿主面**
6
+ * 加载时,它自己的 profile 补丁已经禁用了这 4 行并插入**一行全局** `tool-plus`;
7
+ * 注册表按 scope 分层、就近优先,所以宿主面那一份已经服务所有会话。若某个
8
+ * preset 又在 **agent 面**挂一份同名工具行,就会用**第二个 `apply()` 影子实例**
9
+ * 盖掉健康的宿主实例,而那个实例的 settings 注入永不触发(cfg 退化成
10
+ * replace-mode 读取 —— 2026-08-25 hashline 事故的根因)。
11
+ *
12
+ * 与旧文本实现的唯一区别是**载体**:以前逐行读 `agent.cordis.yml` 的文本,
13
+ * 现在直接看宿主交给我们的行对象。判定规则一字不改:
14
+ * - 只认**顶层**行;group 里嵌套的同名行不碰;
15
+ * - `disabled` 严格等于布尔 `true` 才算已处理 —— 缺席、`false`、字符串、
16
+ * `!!js` 表达式统统算冲突(`!!js` 在挂载时才求值,我们要的是无条件关闭,
17
+ * 所以和旧实现一样整值翻成 `true`);
18
+ * - 输出恒为新数组,输入不变;结果与输入逐值相同时 `changes` 为空(调用方以
19
+ * 此判定"不写盘")。
20
+ * @module @xiaoso/dsh-tool-plus/presets/conflicts
21
+ */
22
+ import { type PresetRow } from './rows.ts';
23
+ /** 官方 standard / ptc / cordis 会挂的 4 个工具行 id。 */
24
+ export declare const PRESET_CONFLICT_IDS: readonly string[];
25
+ /** 一次改写实际发生的一处改动。 */
26
+ export interface PresetChange {
27
+ /** 行 id(顶层 `- id: <id>`)。 */
28
+ id: string;
29
+ /** `disabled` = 该行原本没有 `disabled` 键,现在补上;`flipped` = 既有值被翻成 `true`。 */
30
+ action: 'disabled' | 'flipped';
31
+ }
32
+ /** 只读判定:给设置页状态用。 */
33
+ export interface PresetAnalysis {
34
+ /** 仍挂着冲突行的 id(缺 `disabled` 或值不是布尔 `true`),按 conflictIds 顺序。 */
35
+ conflicts: string[];
36
+ /** 无冲突且形状可识别。 */
37
+ clean: boolean;
38
+ /** `unrecognized` = 不是合法的顶层行列表,一律不处理也不改写。 */
39
+ shape: 'ok' | 'unrecognized';
40
+ }
41
+ /**
42
+ * 只读分析一份 `plugins` 值。
43
+ * @param plugins - 声明的子插件列表(来自宿主配置,形状未知)。
44
+ * @param conflictIds - 冲突行清单,默认 {@link PRESET_CONFLICT_IDS}。
45
+ * @returns 冲突清单、是否干净、形状是否可识别。
46
+ */
47
+ export declare function analyzePlugins(plugins: unknown, conflictIds?: readonly string[]): PresetAnalysis;
48
+ /** 改写结果:新列表 + 实际改动清单 + 改写后仍未解决的冲突(理论上为空)。 */
49
+ export interface PresetConflictRewrite {
50
+ plugins: PresetRow[];
51
+ changes: PresetChange[];
52
+ }
53
+ /**
54
+ * 把那 4 个仍挂着的冲突行翻成 `disabled: true`(不改别的行、不删行)。
55
+ * @param plugins - 合法顶层行列表。
56
+ * @param conflictIds - 冲突行清单,默认 {@link PRESET_CONFLICT_IDS}。
57
+ * @returns 新列表与实际改动清单(无改动时 `changes` 为空且列表为原引用)。
58
+ */
59
+ export declare function disableConflicts(plugins: readonly PresetRow[], conflictIds?: readonly string[]): PresetConflictRewrite;
@@ -1,16 +1,18 @@
1
1
  /**
2
- * 预设差异清单:只读地说清"用户这份和随包模板差在哪"。
2
+ * 差异清单:只读地说清"这份 preset 与另一份(通常是随包声明)差在哪"。
3
3
  *
4
4
  * 为什么需要它:`templateDiffers: boolean` 只能说"不一样",而面板上紧挨着的
5
- * 是"工具冲突行都已禁用、无需调整"。两句都对,但读起来打架 —— 前者说的是
6
- * **你改过内容**,后者说的是**我们自己那 4 个冲突行**。把差异逐条列出来,
7
- * 面板才不需要用户自己猜"到底哪里不同、更新会不会动它"。
5
+ * 是"冲突行都已禁用、无需调整"。两句都对,读起来却打架 —— 前者说的是
6
+ * **内容被改过**,后者说的是**我们自己那 4 个冲突行**。把差异逐条列出来,
7
+ * 用户才不用猜"到底哪里不同、对齐会不会动它"。
8
8
  *
9
- * 契约:①比**展开到叶子的点号路径**(`config.thresholdRatio`,列表项带 `[i]`)——
10
- * 真实漂移常藏在嵌套键里,只比顶层键会得出"没有差异"的错误结论;②冲突行的
11
- * `disabled` 由 `conflicts` 负责,不在差异里重复报;③形状不可识别 → 空清单,
12
- * 绝不猜;④顺序=用户这份的行序在前,模板独有的行补在后;⑤`total` 是全量,
13
- * `items` 可被 `limit` 截断(面板据此说"等 N 项")。
9
+ * 契约(自旧文本实现原样继承,语义未变):
10
+ * - 比**展开到叶子的点号路径**(`config.thresholdRatio`、`config.models[0].id`)——
11
+ * 真实漂移常藏在嵌套键里,只比顶层键会得出"没有差异"的错误结论;
12
+ * - 冲突行的 `disabled` 由 `conflicts` 负责,不在差异里重复报;
13
+ * - 形状不可识别 → 空清单,绝不猜;
14
+ * - 顺序 = 你这份的行序在前(含 group 内子行),对方独有的行补在后;
15
+ * - `total` 是全量,`items` 可被 `limit` 截断(面板据此说"等 N 项")。
14
16
  * @module @xiaoso/dsh-tool-plus/presets/delta
15
17
  */
16
18
  /** 一条差异:整行只在一侧、某个叶子只在一侧、或同一个叶子两边值不同。 */
@@ -18,13 +20,16 @@ export type PresetDeltaKind = 'changed' | 'only-yours' | 'only-template' | 'row-
18
20
  /** 差异清单的一项。`path` 为空串表示整行层面的差异。 */
19
21
  export interface PresetDeltaItem {
20
22
  kind: PresetDeltaKind;
21
- /** 顶层行的 id。 */
23
+ /**
24
+ * 行路径:顶层行的 id(`tool-web`),或 group 内子行(`delegation/tool-ralph`)。
25
+ * 用路径而不是裸 id,是因为嵌套行重名时裸 id 会指错行。
26
+ */
22
27
  row: string;
23
28
  /** 行内点号路径(如 `config.thresholdRatio`);整行差异时为空串。 */
24
29
  path: string;
25
30
  /** 你这份里的值(`only-template` / `row-only-template` 时不带)。 */
26
31
  yours?: string;
27
- /** 模板里的值(`only-yours` / `row-only-yours` 时不带)。 */
32
+ /** 对方的同名值(`only-yours` / `row-only-yours` 时不带)。 */
28
33
  template?: string;
29
34
  }
30
35
  /** 差异清单:`items` 已按 `limit` 截断,`total` 是全量条数。 */
@@ -32,21 +37,21 @@ export interface PresetDelta {
32
37
  items: PresetDeltaItem[];
33
38
  total: number;
34
39
  /**
35
- * 全量口径的两个分组:`yoursCount` = 你自己加/改的(更新不覆盖,重置会覆盖),
36
- * `behindCount` = 模板有而你没有的(模板已更新,重置可对齐)。
40
+ * 全量口径的两个分组:`yoursCount` = 你自己加/改的(对齐会覆盖它),
41
+ * `behindCount` = 对方有而你没有的(对方已更新,对齐可补上)。
37
42
  * 分组计数必须按**全量**算:`items` 被 `limit` 截断后数它会把"等 N 项"说错。
38
43
  */
39
44
  yoursCount: number;
40
45
  behindCount: number;
41
46
  }
42
47
  /**
43
- * 比较两份组合文件,列出差异(只读,不改任何字节)。
44
- * @param userText - 用户这份的 `agent.cordis.yml` 原文。
45
- * @param templateText - 随包模板的同一文件原文。
48
+ * 比较两份 `config.plugins`,列出差异(只读,不改任何值)。
49
+ * @param yours - 你这一侧的 plugins(形状未知)。
50
+ * @param theirs - 对方的 plugins(形状未知)。
46
51
  * @param options - `conflictIds`(默认 {@link PRESET_CONFLICT_IDS})与 `limit`(默认 6)。
47
- * @returns 差异清单;两侧任一不可识别时为空清单。
52
+ * @returns 差异清单;两侧任一形状不可识别时为空清单。
48
53
  */
49
- export declare function presetDelta(userText: string, templateText: string, options?: {
54
+ export declare function presetDelta(yours: unknown, theirs: unknown, options?: {
50
55
  conflictIds?: readonly string[];
51
56
  limit?: number;
52
57
  }): PresetDelta;
@@ -0,0 +1,55 @@
1
+ /**
2
+ * 动作判定:三个面板动作各自"要不要动手、动手算不算真改动"。**纯函数**。
3
+ *
4
+ * 这一层存在的理由:真正落盘的是宿主自己的 `ctx.configEditor.edit()`,它**每次
5
+ * 调用都会重写 profile 补丁文件**(哪怕内容一个字没变)。所以"是否值得写"必须
6
+ * 在调用它**之前**判定,否则面板每点一次都会白改一次 mtime、白触发一轮 HMR 重载。
7
+ * 判定所需的事实全部来自 {@link PresetFacts},因此这里可以纯函数测试,不需要
8
+ * 真的 profile、也不需要 cordis。
9
+ *
10
+ * 三个动作的语义(自旧目录机制映射而来,见 CHANGELOG):
11
+ * - `upgrade`(最小更新):只把仍挂着的官方冲突行翻成 `disabled: true`。不改别的行,
12
+ * 不动显示元数据 —— 幂等,已干净时一个字节都不写。
13
+ * - `align`(对齐模板):把生效的 plugins 整份换成所选模板声明的 plugins。
14
+ * *对齐到自己的随包声明* = 撤销覆盖,等价于 `revert`,这里直接归约过去。
15
+ * - `revert`(恢复随包):让宿主删掉 profile 补丁里那一行的 `config`,
16
+ * 于是这一行回落到随包声明。没有覆盖时是 no-op。
17
+ * @module @xiaoso/dsh-tool-plus/presets/plan
18
+ */
19
+ import type { PresetChange } from './conflicts.ts';
20
+ /** 面板可以请求的三个动作。 */
21
+ export type PresetActionKind = 'upgrade' | 'align' | 'revert';
22
+ /** 判定所需的全部事实。 */
23
+ export interface PresetPlanInput {
24
+ action: PresetActionKind;
25
+ /** 这个 preset 是不是本插件声明的(只有 ours 允许 align / revert)。 */
26
+ ours: boolean;
27
+ /** 用户是否在 profile 层写过覆盖。 */
28
+ customized: boolean;
29
+ /** 生效的完整 config(宿主的 `entry.options.config`);取不到时 undefined。 */
30
+ effective: Record<string, unknown> | undefined;
31
+ /** 随包声明那一层的完整 config(宿主的 `inherited`)。 */
32
+ inherited: Record<string, unknown> | undefined;
33
+ /** `align` 的目标模板 plugins。 */
34
+ templatePlugins?: unknown;
35
+ /** `align` 请求的模板 id(仅用于文案与校验)。 */
36
+ templateId?: string;
37
+ }
38
+ /** 判定结果。 */
39
+ export type PresetPlan = {
40
+ kind: 'noop';
41
+ reason: string;
42
+ } | {
43
+ kind: 'upgrade';
44
+ changes: PresetChange[];
45
+ } | {
46
+ kind: 'align';
47
+ } | {
48
+ kind: 'revert';
49
+ };
50
+ /**
51
+ * 判定一个动作该不该写、会改什么。
52
+ * @param input - 判定所需的事实。
53
+ * @returns 计划;`noop` 时调用方**不要**调用 `configEditor.edit`。
54
+ */
55
+ export declare function planPresetAction(input: PresetPlanInput): PresetPlan;
@@ -0,0 +1,84 @@
1
+ /**
2
+ * preset 组合的**行模型**:官方声明式 preset 的 `config.plugins` 就是一棵
3
+ * Cordis entry list,本模块是它唯一的读写口径。
4
+ *
5
+ * 为什么不再有 YAML 文本层:旧机制里预设是磁盘上的 `agent.cordis.yml`,我们
6
+ * 只能逐行做文本手术(保缩进、保注释、保 CRLF)。0.1.7 起预设是 profile 配置
7
+ * 里的一条声明,宿主给我们的**已经是解析好的对象树**(`configEditor` 的
8
+ * `inherited` / `override`,以及 `entry.options.config`),再走文本就是自找
9
+ * 麻烦:`!!js` 标记、块标量、引号风格全都不该由我们操心。
10
+ *
11
+ * 三条纪律:
12
+ * - **形状判定照抄官方**:顶层必须是行列表,且每一行是带非空字符串 `name`
13
+ * 的映射(官方 `entryListProblem` 同款)。不满足 → `undefined`,一律不处理。
14
+ * - **只认顶层行**:`tool-bash`/`tool-fs` 这类 id 只按顶层匹配;group 里嵌套的
15
+ * 同名行属于那个 group 的私有组成,绝不被外层动作波及。
16
+ * - **绝不原地改**:所有"改写"都返回新对象,输入保持可比较(宿主按深相等判定
17
+ * 是否落盘,原地改会让幂等判定失去意义)。
18
+ * @module @xiaoso/dsh-tool-plus/presets/rows
19
+ */
20
+ /** 一行 Cordis 配置项的最小子集(其余键原样透传)。 */
21
+ export interface PresetRow {
22
+ id?: unknown;
23
+ name?: unknown;
24
+ group?: unknown;
25
+ disabled?: unknown;
26
+ isolate?: unknown;
27
+ config?: unknown;
28
+ [key: string]: unknown;
29
+ }
30
+ /** 行 id 的取值(非空字符串才认,与官方对 `name` 的要求同款)。 */
31
+ export declare function rowId(row: PresetRow): string | undefined;
32
+ /** 行模块名(官方 entry-list 要求每行都有非空 `name`)。 */
33
+ export declare function rowName(row: PresetRow): string | undefined;
34
+ /** 这一行是不是 group 载体(group 的子行在 `config` 里)。 */
35
+ export declare function isGroupRow(row: PresetRow): boolean;
36
+ /**
37
+ * 判定一份 `plugins` 值是不是合法的顶层行列表。规则与官方
38
+ * `entryListProblem` 一致:必须是数组,每一项必须是带非空 `name` 的映射。
39
+ * @param value - 待判定的值(来自宿主配置或测试注入)。
40
+ * @returns 合法时为 true。
41
+ */
42
+ export declare function isRowList(value: unknown): value is PresetRow[];
43
+ /**
44
+ * 展平一棵行树(含 group 嵌套),键为 `父/子` 形式的路径。
45
+ *
46
+ * 路径是**给面板看的标识**,也用于我们自己的差异比较;官方
47
+ * `compositionInventory()` 回的行没有路径(它压平后只剩 entryId/moduleName),
48
+ * 所以需要分组结构时只能从声明本身取。
49
+ * @param rows - 顶层行列表。
50
+ * @param prefix - 递归时的父路径。
51
+ * @returns 路径 → 行。
52
+ */
53
+ export declare function flattenRows(rows: readonly PresetRow[], prefix?: string): Map<string, PresetRow>;
54
+ /** 一个叶子:行内点号路径 → 规范化后的值字符串。 */
55
+ export interface RowLeaf {
56
+ /** 行内路径,如 `config.thresholdRatio` 或 `config.models[0].id`。 */
57
+ path: string;
58
+ /** 规范化值:标量走 `JSON.stringify`,所以 `true`/`"true"`/`1` 不会互相混淆。 */
59
+ value: string;
60
+ }
61
+ /**
62
+ * `!!js` 表达式标记:entry-list 方言把它解析成 `{ __jsExpr: <源码> }`(见
63
+ * `vendor/include/src/index.ts` 的 `JsExpr`)。它对我们是**标量**——一整段待求值的
64
+ * 源码,不是可以逐键比较的结构。漏了这一步会造出 `disabled.__jsExpr` 这种假叶子,
65
+ * 顺带让"冲突行的 disabled 归 conflicts 管"这条跳过规则失效。
66
+ * @param value - 待判定的值。
67
+ * @returns 是表达式标记时为 true。
68
+ */
69
+ export declare function isJsExpr(value: unknown): value is {
70
+ __jsExpr: string;
71
+ };
72
+ /**
73
+ * 收集一行自己的叶子路径(不含 `id`,不含 group 的子行)。
74
+ *
75
+ * 用规范化 JSON 而不是 `String()`:Loader 对 `disabled` 的语义是
76
+ * "布尔取 Boolean(value)",所以字符串 `"false"` 与布尔 `false` **语义相反**;
77
+ * 两者若都显示成 `false`,面板就会把一处真实差异藏起来。
78
+ * @param row - 目标行。
79
+ * @param base - 递归时的前缀。
80
+ * @returns 该行的叶子清单(顺序为键的声明顺序)。
81
+ */
82
+ export declare function rowLeaves(row: PresetRow, base?: string): RowLeaf[];
83
+ /** 规范化的深比较(两侧都是 YAML/JSON 值,无函数与原型差异)。 */
84
+ export declare function sameValue(a: unknown, b: unknown): boolean;
@@ -1,56 +1,65 @@
1
- import { type PresetDeps } from './paths.ts';
2
- import { type PresetDelta } from './delta.ts';
3
- /** 预设来源:我们随包的两份 / 用户自建 / 官方随附。 */
4
- export type PresetSource = 'ours' | 'user' | 'shipped';
5
1
  /**
6
- * roster 的一行(调用方从 `ctx.agentPresets.list()` 映射而来,字段名对齐
7
- * `dsh-agent-presets` 的 `AgentPreset`:`path` 是**组合文件**的绝对路径)。
2
+ * preset 状态:把宿主交给我们的事实折成设置页要的清单。同步、只读。
3
+ *
4
+ * 事实从哪来(0.1.7 起官方只提供这两处,`path` / `trust` 都已不存在):
5
+ * - `ctx.configEditor.configuration()` —— 每个**可寻址**的 profile 行给
6
+ * `{entry, inherited, override}`。`inherited` 是随包声明那一层(我们的
7
+ * 模板),`override` 是用户写进 profile 补丁那一层。bundle 补丁 insert 的
8
+ * `preset-*` 行也是根 include 的子行,所以这里拿得到。
9
+ * - `ctx.agentPresets.list()` —— 名册身份与激活诊断(`broken`),**没有**
10
+ * `path`,也**没有** `trust`。
11
+ *
12
+ * 三种来源的旧口径已失效,现在的区分只剩"是不是本插件声明的":
13
+ * - `ours`:id 在 {@link BUNDLED_PRESET_IDS} 里 → 三个动作齐全;
14
+ * - `other`:别人的声明(官方随附,或用户装的别的 bundle)→ 只给"最小更新",
15
+ * 因为我们只该修冲突行,不该替别人重写整份内容。
16
+ * @module @xiaoso/dsh-tool-plus/presets/status
8
17
  */
9
- export interface PresetRosterEntry {
10
- id: string;
11
- trust: 'system' | 'user';
12
- path: string;
13
- name?: string;
14
- description?: string;
15
- broken?: string;
16
- }
17
- /** 一个预设的设置页状态。 */
18
- export interface PresetStatus {
18
+ import { type PresetStatusListValue, type PresetStatusValue } from '../tools/shared/browser-rpc-channel.ts';
19
+ /**
20
+ * 一个 preset 的原始事实(宿主层收集,本模块纯消费)。
21
+ *
22
+ * `inherited` / `override` 是**完整 config 对象**(`{id,name,description,order,plugins}`),
23
+ * 不是 plugins 列表 —— 因为 profile 补丁按行 id 覆盖时替换的是**整份 `config`**,
24
+ * 判断"改没改过"必须看整份,不能只看 plugins。
25
+ */
26
+ export interface PresetFacts {
27
+ /** 预设身份(`config.id`)。 */
19
28
  id: string;
29
+ /** profile 补丁里那一行的 id(`preset-<id>`);本 profile 没有这一行时为 undefined。 */
30
+ entryId?: string;
20
31
  name?: string;
21
32
  description?: string;
22
- source: PresetSource;
23
- /** 组合文件绝对路径(`agent.cordis.yml`)。 */
24
- path: string;
25
- /** 仍挂着的官方冲突行(空 = 本插件的工具已生效)。 */
26
- conflicts: string[];
27
- /** 无冲突且形状可识别。 */
28
- clean: boolean;
29
- /** 组合文件**可读但**不成顶层行列表 → 一律不处理(内容问题,§5.5)。 */
30
- unrecognized: boolean;
31
- /** roster 报告的不可挂载原因,原样带出。 */
33
+ isDefault: boolean;
34
+ /** 宿主报告的不可挂载原因(行名解析不到、缺服务…),原样带出。 */
32
35
  broken?: string;
33
- /**
34
- * 用户根(`<dshHome>/.agent-presets/<id>`)里有这个预设的副本。
35
- * 设置页对我们随包的两份据此显示"已安装 / 未安装";官方随附的预设不在用户
36
- * 根,故恒为 false。
37
- */
38
- installed: boolean;
39
- /** 包内存在这个 id 的模板(只有我们随包的两份才有)。 */
40
- templatePresent: boolean;
41
- /** 已安装且与模板逐字节不同 → 本地有改动(重置会覆盖它)。 */
42
- templateDiffers: boolean;
43
- /**
44
- * 与模板**具体**差在哪些键(只读列出;`templateDiffers` 只说明"有差异",
45
- * 面板需要能说清"差在哪",否则和"工具行无需调整"并列时会自相矛盾)。
46
- * 无模板或不可比较时为 undefined。
47
- */
48
- templateDelta?: PresetDelta;
36
+ /** 生效的完整 config(宿主的 `entry.options.config`);取不到时为 undefined。 */
37
+ effective?: Record<string, unknown>;
38
+ /** 随包声明那一层的 config(`configEditor` 的 `inherited`);取不到时为 undefined。 */
39
+ inherited?: Record<string, unknown>;
40
+ /** 用户在 profile 层写的 config(`configEditor` 的 `override`);空对象 = 没有覆盖。 */
41
+ override?: Record<string, unknown>;
42
+ /** 这个 preset 是不是本插件声明的(id ∈ {@link BUNDLED_PRESET_IDS})。 */
43
+ ours: boolean;
49
44
  }
50
45
  /**
51
- * 列出每个 roster 行的状态(顺序与入参一致)。
52
- * @param roster - 调用方映射好的预设清单(`path` = 组合文件路径)。
53
- * @param deps - 路径注入(fs 层测试用)。
54
- * @returns 逐行的设置页状态。
46
+ * 把一个 preset 折成面板状态。
47
+ * @param facts - 宿主收集的原始事实。
48
+ * @param templateIds - 随包声明的模板清单(用于"与随包声明是否一致")。
49
+ * @returns 面板状态行。
50
+ */
51
+ export declare function toPresetStatus(facts: PresetFacts, templateIds: readonly string[]): PresetStatusValue;
52
+ /**
53
+ * 列出每个事实的状态,并给出模板清单。
54
+ * @param facts - 宿主收集的原始事实(顺序即面板顺序)。
55
+ * @param writable - 本部署有没有可编辑的 profile(`configEditor` 是否在场)。
56
+ * @returns `presets/status` 的完整返回值。
57
+ */
58
+ export declare function listPresetStatuses(facts: readonly PresetFacts[], writable: boolean): PresetStatusListValue;
59
+ /**
60
+ * 取某个模板声明的 plugins(面板"对比 / 对齐"的右侧)。
61
+ * @param facts - 同一个事实集合。
62
+ * @param templateId - 模板 id(本插件声明的某个 preset)。
63
+ * @returns 该模板的 plugins;不是我们的模板时 undefined。
55
64
  */
56
- export declare function listPresetStatuses(roster: readonly PresetRosterEntry[], deps?: PresetDeps): PresetStatus[];
65
+ export declare function templatePlugins(facts: readonly PresetFacts[], templateId: string): unknown;
@@ -2,11 +2,13 @@
2
2
  * Background-job adaptation for the `ctx.jobs` seam: one managed job runs its
3
3
  * command on its OWN `:async:` Shell (never the session's persistent shell),
4
4
  * so background work never blocks the foreground session. The registry owns
5
- * identity, lifecycle state, and completion notices (`dsh-tool-jobs` injects
6
- * them); this module owns the execution resources and their hooks.
5
+ * identity, lifecycle state, the output ring, and completion notices
6
+ * (`dsh-tool-jobs` injects them); this module owns the execution resources,
7
+ * streams the run into that ring, and appends the plugin's completion message
8
+ * once the run ends.
7
9
  * @module @xiaoso/dsh-tool-plus/background
8
10
  */
9
- import type { JobHooks } from '@deepseek-ai/dsh-jobs';
11
+ import type { JobHandle, JobHooks } from '@deepseek-ai/dsh-jobs';
10
12
  import type { BashForegroundOutput, ResolvedConfig } from './types.ts';
11
13
  export interface ManagedBashJob {
12
14
  /** Hooks handed to `ctx.jobs.start`'s `run()`. */
@@ -29,11 +31,24 @@ export interface StartBashJobOptions {
29
31
  /** Command-scoped environment (already merged with dshEnv). */
30
32
  env: Record<string, string> | undefined;
31
33
  config: ResolvedConfig;
34
+ /**
35
+ * The registry's producer face, when this run is a registered job: every
36
+ * streamed chunk lands in the job's output ring, and so does the settled
37
+ * completion message. Absent for a run no registry owns (unit callers).
38
+ */
39
+ job?: JobHandle;
40
+ /**
41
+ * Builds the settled completion message from the bounded preview and the
42
+ * run's spill file (the plugin's `outputTruncate` policy). Absent keeps the
43
+ * preview verbatim.
44
+ */
45
+ formatCompletion?: (previewText: string, spillPath: string | undefined) => string;
32
46
  }
33
47
  /**
34
48
  * Start one managed background bash job. The producer owns a per-job
35
- * `AbortController` (cancellation kills only this job's shell), a bounded
36
- * preview tail, and a consuming delta cursor for `readOutput`.
49
+ * `AbortController` (cancellation kills only this job's shell) and a bounded
50
+ * preview tail; the registry owns the model's consuming cursor over the ring
51
+ * this job writes to.
37
52
  * @param options - job identity and execution parameters.
38
53
  * @returns the registry hooks plus the tool-result completion promise.
39
54
  */
@@ -17,8 +17,10 @@ export declare class OutputSink extends VerbatimOutputSink {
17
17
  }
18
18
  /**
19
19
  * Composition wrapper over the verbatim {@link TailBuffer}, adding the DSH
20
- * `reset()` increment. The verbatim class keeps its state in private fields,
21
- * so reset is modeled as a fresh window with the same budget.
20
+ * `reset()` increment: the consuming-read cursor reset the bash background
21
+ * producer used while `JobHooks.readOutput` existed. 0.1.7 moves the model's
22
+ * cursor onto the job registry's output ring, so the increment is no longer
23
+ * part of that path; the preview buffer still offers it.
22
24
  */
23
25
  export declare class TailBuffer {
24
26
  #private;