dsh-advisor 0.1.4 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,144 @@
1
+ /**
2
+ * dsh-tui settings-section seam (plan dsh-advisor-tui-settings-n9, T1) — the
3
+ * `tuiSettingsSections` Advisor section.
4
+ *
5
+ * dsh-tui ≥ v0.8.0 ships a `/settings` screen; optional plugins declare what
6
+ * is editable there by registering a SECTION over their settings namespace on
7
+ * the optional `tuiSettingsSections` host service
8
+ * (`src/dsh-adapter/settings-sections.ts` in dsh-TUI — a small host-only
9
+ * registry; storage + validation stay with the dsh settings service). The
10
+ * screen then renders the section's fields, stages edits, and writes them on
11
+ * save through the revision-fenced `settings.mutate` into the section's
12
+ * namespace user layer — for the advisor that is the already-registered
13
+ * `advisor` namespace (`src/settings.ts` `installAdvisorSettings`), so the
14
+ * section is fully writable.
15
+ *
16
+ * This module is the advisor's settings-seam surface:
17
+ * `installTuiSettingsSection` conditionally injects `tuiSettingsSections` and
18
+ * registers {@link ADVISOR_TUI_SETTINGS_SECTION} when the service exists; a
19
+ * profile without the `dsh-tui-settings-sections` row (dsh-tui < v0.8.0, or
20
+ * any non-TUI host) gets a clean no-op. The provider shapes are minimal LOCAL
21
+ * structural copies of the dsh-TUI types — the advisor MUST NOT import
22
+ * `@deepseek-harness-tui/dsh-tui` (zero new peers, plan Global Constraint),
23
+ * so drift against the upstream shape is bounded to the structural cast at
24
+ * the inject boundary and pinned by `tests/tui-settings.test.ts`.
25
+ *
26
+ * Upstream revision pin: the structural shapes mirror
27
+ * `@deepseek-harness-tui/dsh-tui` v0.8.0 (`src/dsh-adapter/settings-sections.ts`,
28
+ * dsh-TUI commit `02ff08e`). Residual drift window: an upstream
29
+ * addition/rename of a REQUIRED field would NOT fail the structural cast (the
30
+ * cast goes through `unknown`) and could cause host-side misrender rather than
31
+ * a compile error — re-verify against the dsh-TUI repo when bumping.
32
+ *
33
+ * Field subset (grill-me locked): the section covers the five SAFE §5.1 keys
34
+ * (`enabled` / `provider` / `model` / `immuneTurns` / `maxDeltaMessages`).
35
+ * `systemPrompt` is intentionally NOT a field — the TUI `text` control is
36
+ * single-line, and editing a multi-line prompt there would truncate/replace
37
+ * it (data loss). It stays editable via the web card or
38
+ * `$DSH_HOME/settings.yaml`. The TUI seam has no cross-field validation
39
+ * (upstream behavior, recorded): a save may set `enabled: true` with empty
40
+ * `provider`/`model`, which the S4 explicit model gate (spec §5.2) resolves
41
+ * to disabled-with-reason at runtime; the settings-service schema
42
+ * re-validation on mutate is the value-level backstop (a non-schema value
43
+ * fails the whole save before persist).
44
+ *
45
+ * @module dsh-advisor/tui-settings
46
+ */
47
+ import type { Context } from '@deepseek-ai/cordis';
48
+ /** Localized (zh/en) descriptions — structural mirror of dsh-TUI's
49
+ * `LocalizedDescriptions` (the `/settings` section + field labels/hints). */
50
+ export type TuiLocalizedDescriptions = Readonly<Partial<Record<'zh' | 'en', string>>>;
51
+ /** One `kind: 'select'` choice — structural mirror of dsh-TUI's
52
+ * `TuiSettingsFieldOption`. */
53
+ export interface TuiSettingsFieldOption {
54
+ /** Stored value. */
55
+ value: string;
56
+ /** Display label (English; also the fallback). */
57
+ label: string;
58
+ /** Provider-owned translations for the label. */
59
+ descriptions?: TuiLocalizedDescriptions;
60
+ }
61
+ /** The write one field's draft stages when the section is saved — structural
62
+ * mirror of dsh-TUI's `TuiSettingsFieldWrite`. */
63
+ export type TuiSettingsFieldWrite = {
64
+ kind: 'set';
65
+ value: unknown;
66
+ } | {
67
+ kind: 'clear';
68
+ };
69
+ /** One editable field inside a section — structural mirror of dsh-TUI's
70
+ * `TuiSettingsField`. `path` uses the settings-service `mutate` path
71
+ * vocabulary (object keys); the advisor's §5.1 flat keys map directly as
72
+ * single-element paths. */
73
+ export interface TuiSettingsField {
74
+ /** Key path from the section root, in the settings service's `mutate` path
75
+ * vocabulary (object keys; dict keys name their entry directly). */
76
+ path: readonly string[];
77
+ /** Short field label (English; also the fallback). */
78
+ label: string;
79
+ /** Provider-owned translations for the label. */
80
+ descriptions?: TuiLocalizedDescriptions;
81
+ /** Optional one-line help rendered under the field. */
82
+ hint?: string;
83
+ /** Provider-owned translations for the hint. */
84
+ hintDescriptions?: TuiLocalizedDescriptions;
85
+ kind: 'text' | 'number' | 'boolean' | 'select';
86
+ /** Choices for `kind: 'select'` (ignored otherwise). */
87
+ options?: readonly TuiSettingsFieldOption[];
88
+ /** Input placeholder for `kind: 'text' | 'number'`. */
89
+ placeholder?: string;
90
+ /** Credential control (mirrors the web cards' CardSecretSpec). */
91
+ secret?: {
92
+ ref: string;
93
+ };
94
+ /** Render a stored value as draft text (defaults to the kind's conversion). */
95
+ format?(value: unknown): string;
96
+ /** The write this draft text stages, or `undefined` when the text is not a
97
+ * value this field accepts (defaults to the kind's conversion — an empty
98
+ * text/number draft stages a clear, re-inheriting the composition layer). */
99
+ parse?(text: string): TuiSettingsFieldWrite | undefined;
100
+ }
101
+ /** One plugin's section inside the TUI `/settings` screen — structural mirror
102
+ * of dsh-TUI's `TuiSettingsSection`. */
103
+ export interface TuiSettingsSection {
104
+ /** Settings namespace this section edits; the screen marks the section
105
+ * unavailable when the composition serves no such namespace. */
106
+ ns: string;
107
+ /** Section title (English; also the fallback). */
108
+ title: string;
109
+ /** Provider-owned translations for the title. */
110
+ descriptions?: TuiLocalizedDescriptions;
111
+ /** Editable fields, in display order. */
112
+ fields: readonly TuiSettingsField[];
113
+ }
114
+ /** The `tuiSettingsSections` host service key — the ONE name the T1
115
+ * registration condition (`installTuiSettingsSection`'s inject) and the T2
116
+ * hint truthfulness probe (`getConfig`'s `ctx.get`) are both keyed on. Shared
117
+ * so the registration condition and the render-time hint can never drift
118
+ * apart (S-001, plan QC fix wave). */
119
+ export declare const TUI_SETTINGS_SECTIONS = "tuiSettingsSections";
120
+ /** Test-friendly alias for the section namespace (`'advisor'`). The section
121
+ * itself reuses the shared {@link ADVISOR_SETTINGS_NAMESPACE} brand — a
122
+ * mismatched ns would silently render the section "unavailable" in the host
123
+ * screen (the alias exists so tests can pin the value without importing the
124
+ * settings module's brand). */
125
+ export declare const ADVISOR_TUI_SETTINGS_NS = "advisor";
126
+ /** The declared "Advisor" section for the dsh-tui `/settings` screen: the
127
+ * five safe §5.1 keys (enabled/provider/model/immuneTurns/maxDeltaMessages)
128
+ * with zh/en labels + hints. Field paths are single-element §5.1 flat keys,
129
+ * so staged edits map 1:1 onto the namespace `mutate` paths. */
130
+ export declare const ADVISOR_TUI_SETTINGS_SECTION: TuiSettingsSection;
131
+ /**
132
+ * Install the advisor's TUI settings-section surface: register the "Advisor"
133
+ * section on `tuiSettingsSections` when the host service exists (conditional
134
+ * inject; absent service → clean no-op). Called from `apply()` AFTER the
135
+ * single-reviewer claim (next to `installTuiClient`), so the section registers
136
+ * at most once per process. The structural accessor keeps the inject key in
137
+ * the standard position: the cordis Context has no `tuiSettingsSections`
138
+ * augmentation in this repo, so the service is read through a local
139
+ * structural cast. The inject child's return value (the registry disposer)
140
+ * is the child's own effect disposer, so the section withdraws when this
141
+ * fiber (or the service) goes away; a duplicate-ns registration is contained
142
+ * (debug log + no-op disposer, never throws — multi-fiber dedupe).
143
+ */
144
+ export declare function installTuiSettingsSection(ctx: Context): void;
@@ -0,0 +1,145 @@
1
+ /**
2
+ * dsh-tui settings-section seam (plan dsh-advisor-tui-settings-n9, T1) — the
3
+ * `tuiSettingsSections` Advisor section.
4
+ *
5
+ * dsh-tui ≥ v0.8.0 ships a `/settings` screen; optional plugins declare what
6
+ * is editable there by registering a SECTION over their settings namespace on
7
+ * the optional `tuiSettingsSections` host service
8
+ * (`src/dsh-adapter/settings-sections.ts` in dsh-TUI — a small host-only
9
+ * registry; storage + validation stay with the dsh settings service). The
10
+ * screen then renders the section's fields, stages edits, and writes them on
11
+ * save through the revision-fenced `settings.mutate` into the section's
12
+ * namespace user layer — for the advisor that is the already-registered
13
+ * `advisor` namespace (`src/settings.ts` `installAdvisorSettings`), so the
14
+ * section is fully writable.
15
+ *
16
+ * This module is the advisor's settings-seam surface:
17
+ * `installTuiSettingsSection` conditionally injects `tuiSettingsSections` and
18
+ * registers {@link ADVISOR_TUI_SETTINGS_SECTION} when the service exists; a
19
+ * profile without the `dsh-tui-settings-sections` row (dsh-tui < v0.8.0, or
20
+ * any non-TUI host) gets a clean no-op. The provider shapes are minimal LOCAL
21
+ * structural copies of the dsh-TUI types — the advisor MUST NOT import
22
+ * `@deepseek-harness-tui/dsh-tui` (zero new peers, plan Global Constraint),
23
+ * so drift against the upstream shape is bounded to the structural cast at
24
+ * the inject boundary and pinned by `tests/tui-settings.test.ts`.
25
+ *
26
+ * Upstream revision pin: the structural shapes mirror
27
+ * `@deepseek-harness-tui/dsh-tui` v0.8.0 (`src/dsh-adapter/settings-sections.ts`,
28
+ * dsh-TUI commit `02ff08e`). Residual drift window: an upstream
29
+ * addition/rename of a REQUIRED field would NOT fail the structural cast (the
30
+ * cast goes through `unknown`) and could cause host-side misrender rather than
31
+ * a compile error — re-verify against the dsh-TUI repo when bumping.
32
+ *
33
+ * Field subset (grill-me locked): the section covers the five SAFE §5.1 keys
34
+ * (`enabled` / `provider` / `model` / `immuneTurns` / `maxDeltaMessages`).
35
+ * `systemPrompt` is intentionally NOT a field — the TUI `text` control is
36
+ * single-line, and editing a multi-line prompt there would truncate/replace
37
+ * it (data loss). It stays editable via the web card or
38
+ * `$DSH_HOME/settings.yaml`. The TUI seam has no cross-field validation
39
+ * (upstream behavior, recorded): a save may set `enabled: true` with empty
40
+ * `provider`/`model`, which the S4 explicit model gate (spec §5.2) resolves
41
+ * to disabled-with-reason at runtime; the settings-service schema
42
+ * re-validation on mutate is the value-level backstop (a non-schema value
43
+ * fails the whole save before persist).
44
+ *
45
+ * @module dsh-advisor/tui-settings
46
+ */
47
+ import { ADVISOR_SETTINGS_NAMESPACE } from './settings.js';
48
+ /** The `tuiSettingsSections` host service key — the ONE name the T1
49
+ * registration condition (`installTuiSettingsSection`'s inject) and the T2
50
+ * hint truthfulness probe (`getConfig`'s `ctx.get`) are both keyed on. Shared
51
+ * so the registration condition and the render-time hint can never drift
52
+ * apart (S-001, plan QC fix wave). */
53
+ export const TUI_SETTINGS_SECTIONS = 'tuiSettingsSections';
54
+ /** Test-friendly alias for the section namespace (`'advisor'`). The section
55
+ * itself reuses the shared {@link ADVISOR_SETTINGS_NAMESPACE} brand — a
56
+ * mismatched ns would silently render the section "unavailable" in the host
57
+ * screen (the alias exists so tests can pin the value without importing the
58
+ * settings module's brand). */
59
+ export const ADVISOR_TUI_SETTINGS_NS = 'advisor';
60
+ /** The declared "Advisor" section for the dsh-tui `/settings` screen: the
61
+ * five safe §5.1 keys (enabled/provider/model/immuneTurns/maxDeltaMessages)
62
+ * with zh/en labels + hints. Field paths are single-element §5.1 flat keys,
63
+ * so staged edits map 1:1 onto the namespace `mutate` paths. */
64
+ export const ADVISOR_TUI_SETTINGS_SECTION = {
65
+ ns: ADVISOR_SETTINGS_NAMESPACE,
66
+ title: 'Advisor',
67
+ descriptions: {
68
+ zh: '顾问评审设置',
69
+ en: 'Advisor settings',
70
+ },
71
+ fields: [
72
+ {
73
+ path: ['enabled'],
74
+ kind: 'boolean',
75
+ label: 'Enabled',
76
+ descriptions: { zh: '启用', en: 'Enabled' },
77
+ hint: 'Master switch for the advisor.',
78
+ hintDescriptions: { zh: '顾问总开关。', en: 'Master switch for the advisor.' },
79
+ },
80
+ {
81
+ path: ['provider'],
82
+ kind: 'text',
83
+ label: 'Provider',
84
+ descriptions: { zh: 'Provider', en: 'Provider' },
85
+ hint: 'Provider route; required (non-empty) when enabled.',
86
+ hintDescriptions: { zh: 'Provider 路由;启用时必须非空。', en: 'Provider route; required (non-empty) when enabled.' },
87
+ placeholder: 'e.g. deepseek-official',
88
+ },
89
+ {
90
+ path: ['model'],
91
+ kind: 'text',
92
+ label: 'Model',
93
+ descriptions: { zh: 'Model', en: 'Model' },
94
+ hint: 'Model id; required (non-empty) when enabled.',
95
+ hintDescriptions: { zh: '模型 ID;启用时必须非空。', en: 'Model id; required (non-empty) when enabled.' },
96
+ placeholder: 'e.g. deepseek-v4-flash',
97
+ },
98
+ {
99
+ path: ['immuneTurns'],
100
+ kind: 'number',
101
+ label: 'Immune turns',
102
+ descriptions: { zh: '免疫轮数', en: 'Immune turns' },
103
+ hint: 'Cooldown after a delivered interrupt (integer ≥ 0).',
104
+ hintDescriptions: { zh: '投递一次建议后的冷却轮数(整数 ≥ 0)。', en: 'Cooldown after a delivered interrupt (integer ≥ 0).' },
105
+ },
106
+ {
107
+ path: ['maxDeltaMessages'],
108
+ kind: 'number',
109
+ label: 'Max delta messages',
110
+ descriptions: { zh: '最大增量消息数', en: 'Max delta messages' },
111
+ hint: 'Delta window (integer ≥ 0; 0 = unbounded).',
112
+ hintDescriptions: { zh: '增量窗口(整数 ≥ 0;0 = 不限)。', en: 'Delta window (integer ≥ 0; 0 = unbounded).' },
113
+ },
114
+ ],
115
+ };
116
+ /**
117
+ * Install the advisor's TUI settings-section surface: register the "Advisor"
118
+ * section on `tuiSettingsSections` when the host service exists (conditional
119
+ * inject; absent service → clean no-op). Called from `apply()` AFTER the
120
+ * single-reviewer claim (next to `installTuiClient`), so the section registers
121
+ * at most once per process. The structural accessor keeps the inject key in
122
+ * the standard position: the cordis Context has no `tuiSettingsSections`
123
+ * augmentation in this repo, so the service is read through a local
124
+ * structural cast. The inject child's return value (the registry disposer)
125
+ * is the child's own effect disposer, so the section withdraws when this
126
+ * fiber (or the service) goes away; a duplicate-ns registration is contained
127
+ * (debug log + no-op disposer, never throws — multi-fiber dedupe).
128
+ */
129
+ export function installTuiSettingsSection(ctx) {
130
+ ctx.inject([TUI_SETTINGS_SECTIONS], (tctx) => {
131
+ const sections = tctx.tuiSettingsSections;
132
+ if (sections === undefined)
133
+ return;
134
+ try {
135
+ return sections.register(ADVISOR_TUI_SETTINGS_SECTION);
136
+ }
137
+ catch (error) {
138
+ if (!(error instanceof Error) || !error.message.includes('already registered'))
139
+ throw error;
140
+ tctx.logger('advisor').debug('advisor tui settings section already registered — no section on this fiber (multi-fiber dedupe)');
141
+ return () => { };
142
+ }
143
+ });
144
+ }
145
+ //# sourceMappingURL=tui-settings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tui-settings.js","sourceRoot":"","sources":["../src/tui-settings.ts"],"names":[],"mappings":"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"}
package/lib/tui.d.ts ADDED
@@ -0,0 +1,60 @@
1
+ /**
2
+ * dsh-tui client surface (plan dsh-advisor-tui-client-n8, T1) — the
3
+ * `tuiCommandTrees` /advisor provider.
4
+ *
5
+ * In a `dsh --profile dsh-tui` terminal session the plugin's `/advisor`
6
+ * command (registered through the conditional `ctx.inject(['commands'], ...)`
7
+ * child) already lands in the TUI `/` menu via the command-registry merge.
8
+ * What the TUI cannot infer is the LOCALIZED row description and the typed
9
+ * subcommand COMPLETION — those come from the optional `tuiCommandTrees`
10
+ * host service (`src/dsh-adapter/command-trees.ts` in dsh-TUI): a provider
11
+ * declares `root`, zh/en `descriptions`, and a `children(canonicalPath)`
12
+ * completion tree (root at index 0; the TUI asks at depth 2 when completing
13
+ * `/advisor <sub> ⋯`).
14
+ *
15
+ * This module is the advisor's TUI seam: `installTuiClient` conditionally
16
+ * injects `tuiCommandTrees` and registers the `/advisor` tree when the
17
+ * service exists; a profile without the `dsh-tui-command-trees` row (or any
18
+ * non-TUI host) gets a clean no-op. The provider shapes are minimal LOCAL
19
+ * structural copies of the dsh-TUI types — the advisor MUST NOT import
20
+ * `@deepseek-harness-tui/dsh-tui` (zero new peers, plan Global Constraint),
21
+ * so drift against the upstream shape is bounded to the structural cast at
22
+ * the inject boundary and pinned by `tests/tui-client.test.ts`.
23
+ *
24
+ * @module dsh-advisor/tui
25
+ */
26
+ import type { Context } from '@deepseek-ai/cordis';
27
+ /** Localized (zh/en) descriptions — structural mirror of dsh-TUI's
28
+ * `LocalizedDescriptions` (the TUI `/` row + completion descriptions). */
29
+ export type TuiLocalizedDescriptions = Readonly<Partial<Record<'zh' | 'en', string>>>;
30
+ /** One completion node — structural mirror of dsh-TUI's
31
+ * `CommandCompletionNode` (`src/commands.ts` in dsh-TUI). */
32
+ export interface TuiCommandCompletionNode {
33
+ name: string;
34
+ aliases?: readonly string[];
35
+ description: string;
36
+ descriptions?: TuiLocalizedDescriptions;
37
+ tag?: string;
38
+ descriptionKey?: string;
39
+ }
40
+ /** A `/`-menu command tree provider — structural mirror of dsh-TUI's
41
+ * `TuiCommandTreeProvider`. `children` receives the canonical path with the
42
+ * root at index 0. */
43
+ export interface TuiCommandTreeProvider {
44
+ root: string;
45
+ descriptions?: TuiLocalizedDescriptions;
46
+ children(canonicalPath: readonly string[]): readonly TuiCommandCompletionNode[];
47
+ }
48
+ /** The `/advisor` tree root (matches the command registry name). */
49
+ export declare const ADVISOR_TUI_ROOT = "advisor";
50
+ /**
51
+ * Install the advisor's TUI client surface: register the `/advisor`
52
+ * `tuiCommandTrees` provider when the host service exists (conditional
53
+ * inject; absent service → clean no-op). Called from `apply()` AFTER the
54
+ * single-reviewer claim, so the tree registers at most once per process
55
+ * (a duplicate-root registration would throw in the host registry). The
56
+ * structural accessor keeps the inject key in the standard position: the
57
+ * cordis Context has no `tuiCommandTrees` augmentation in this repo, so the
58
+ * service is read through a local structural cast.
59
+ */
60
+ export declare function installTuiClient(ctx: Context): void;
package/lib/tui.js ADDED
@@ -0,0 +1,113 @@
1
+ /**
2
+ * dsh-tui client surface (plan dsh-advisor-tui-client-n8, T1) — the
3
+ * `tuiCommandTrees` /advisor provider.
4
+ *
5
+ * In a `dsh --profile dsh-tui` terminal session the plugin's `/advisor`
6
+ * command (registered through the conditional `ctx.inject(['commands'], ...)`
7
+ * child) already lands in the TUI `/` menu via the command-registry merge.
8
+ * What the TUI cannot infer is the LOCALIZED row description and the typed
9
+ * subcommand COMPLETION — those come from the optional `tuiCommandTrees`
10
+ * host service (`src/dsh-adapter/command-trees.ts` in dsh-TUI): a provider
11
+ * declares `root`, zh/en `descriptions`, and a `children(canonicalPath)`
12
+ * completion tree (root at index 0; the TUI asks at depth 2 when completing
13
+ * `/advisor <sub> ⋯`).
14
+ *
15
+ * This module is the advisor's TUI seam: `installTuiClient` conditionally
16
+ * injects `tuiCommandTrees` and registers the `/advisor` tree when the
17
+ * service exists; a profile without the `dsh-tui-command-trees` row (or any
18
+ * non-TUI host) gets a clean no-op. The provider shapes are minimal LOCAL
19
+ * structural copies of the dsh-TUI types — the advisor MUST NOT import
20
+ * `@deepseek-harness-tui/dsh-tui` (zero new peers, plan Global Constraint),
21
+ * so drift against the upstream shape is bounded to the structural cast at
22
+ * the inject boundary and pinned by `tests/tui-client.test.ts`.
23
+ *
24
+ * @module dsh-advisor/tui
25
+ */
26
+ /** The `/advisor` tree root (matches the command registry name). */
27
+ export const ADVISOR_TUI_ROOT = 'advisor';
28
+ /** The four typed `/advisor` subcommands surfaced as completion children.
29
+ * Bare `/advisor` (toggle) is the empty-argument default, not a completion
30
+ * child (compass S1); `USAGE` is the unknown-subcommand fallback, not a
31
+ * named command. */
32
+ const ADVISOR_SUBCOMMANDS = ['on', 'off', 'status', 'config'];
33
+ /** zh/en copy for the `/` menu row (shown via the host's `descriptions(root)`). */
34
+ const ADVISOR_TUI_DESCRIPTIONS = {
35
+ zh: '按会话运行的评审顾问:开启 / 关闭 / 状态 / 配置',
36
+ en: 'Per-session advisor: enable, disable, status, or config',
37
+ };
38
+ /** zh/en copy per completion node. `description` is the plain fallback the
39
+ * node carries; `descriptions` is the localized map the TUI prefers. */
40
+ const SUBCOMMAND_DESCRIPTIONS = {
41
+ on: {
42
+ description: 'Enable the advisor for this session',
43
+ descriptions: {
44
+ zh: '为本会话启用顾问',
45
+ en: 'Enable the advisor for this session',
46
+ },
47
+ },
48
+ off: {
49
+ description: 'Disable the advisor for this session',
50
+ descriptions: {
51
+ zh: '为本会话禁用顾问',
52
+ en: 'Disable the advisor for this session',
53
+ },
54
+ },
55
+ status: {
56
+ description: 'Show per-session advisor status (state, model, runtime, pending, last activity)',
57
+ descriptions: {
58
+ zh: '查看本会话顾问状态(开关、模型、运行态、待处理、最近活动)',
59
+ en: 'Show per-session advisor status (state, model, runtime, pending, last activity)',
60
+ },
61
+ },
62
+ config: {
63
+ description: 'Show the composed advisor config (settings readback)',
64
+ descriptions: {
65
+ zh: '查看组合后的顾问配置(设置回读)',
66
+ en: 'Show the composed advisor config (settings readback)',
67
+ },
68
+ },
69
+ };
70
+ /** The `/advisor` completion tree. `children` NEVER throws: unknown paths and
71
+ * a bare `[]` return an empty list (leaves have no deeper completion — the
72
+ * TUI asks at depth 2). */
73
+ const advisorTree = {
74
+ root: ADVISOR_TUI_ROOT,
75
+ descriptions: ADVISOR_TUI_DESCRIPTIONS,
76
+ children(canonicalPath) {
77
+ // Root at index 0: only `['advisor']` asks for the subcommand list.
78
+ if (canonicalPath.length !== 1 || canonicalPath[0] !== ADVISOR_TUI_ROOT)
79
+ return [];
80
+ return ADVISOR_SUBCOMMANDS.map((name) => ({
81
+ name,
82
+ description: SUBCOMMAND_DESCRIPTIONS[name].description,
83
+ descriptions: SUBCOMMAND_DESCRIPTIONS[name].descriptions,
84
+ }));
85
+ },
86
+ };
87
+ /**
88
+ * Install the advisor's TUI client surface: register the `/advisor`
89
+ * `tuiCommandTrees` provider when the host service exists (conditional
90
+ * inject; absent service → clean no-op). Called from `apply()` AFTER the
91
+ * single-reviewer claim, so the tree registers at most once per process
92
+ * (a duplicate-root registration would throw in the host registry). The
93
+ * structural accessor keeps the inject key in the standard position: the
94
+ * cordis Context has no `tuiCommandTrees` augmentation in this repo, so the
95
+ * service is read through a local structural cast.
96
+ */
97
+ export function installTuiClient(ctx) {
98
+ ctx.inject(['tuiCommandTrees'], (tctx) => {
99
+ const trees = tctx.tuiCommandTrees;
100
+ if (trees === undefined)
101
+ return;
102
+ try {
103
+ return trees.register(advisorTree);
104
+ }
105
+ catch (error) {
106
+ if (!(error instanceof Error) || !error.message.includes('already registered'))
107
+ throw error;
108
+ tctx.logger('advisor').debug('advisor tui tree already registered — no tree on this fiber (multi-fiber dedupe)');
109
+ return () => { };
110
+ }
111
+ });
112
+ }
113
+ //# sourceMappingURL=tui.js.map
package/lib/tui.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tui.js","sourceRoot":"","sources":["../src/tui.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AA4BH,oEAAoE;AACpE,MAAM,CAAC,MAAM,gBAAgB,GAAG,SAAS,CAAA;AAEzC;;;oBAGoB;AACpB,MAAM,mBAAmB,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,CAAU,CAAA;AAItE,mFAAmF;AACnF,MAAM,wBAAwB,GAA6B;IACzD,EAAE,EAAE,8BAA8B;IAClC,EAAE,EAAE,yDAAyD;CAC9D,CAAA;AAED;wEACwE;AACxE,MAAM,uBAAuB,GAAyG;IACpI,EAAE,EAAE;QACF,WAAW,EAAE,qCAAqC;QAClD,YAAY,EAAE;YACZ,EAAE,EAAE,UAAU;YACd,EAAE,EAAE,qCAAqC;SAC1C;KACF;IACD,GAAG,EAAE;QACH,WAAW,EAAE,sCAAsC;QACnD,YAAY,EAAE;YACZ,EAAE,EAAE,UAAU;YACd,EAAE,EAAE,sCAAsC;SAC3C;KACF;IACD,MAAM,EAAE;QACN,WAAW,EAAE,iFAAiF;QAC9F,YAAY,EAAE;YACZ,EAAE,EAAE,+BAA+B;YACnC,EAAE,EAAE,iFAAiF;SACtF;KACF;IACD,MAAM,EAAE;QACN,WAAW,EAAE,sDAAsD;QACnE,YAAY,EAAE;YACZ,EAAE,EAAE,kBAAkB;YACtB,EAAE,EAAE,sDAAsD;SAC3D;KACF;CACF,CAAA;AAED;;2BAE2B;AAC3B,MAAM,WAAW,GAA2B;IAC1C,IAAI,EAAE,gBAAgB;IACtB,YAAY,EAAE,wBAAwB;IACtC,QAAQ,CAAC,aAAgC;QACvC,oEAAoE;QACpE,IAAI,aAAa,CAAC,MAAM,KAAK,CAAC,IAAI,aAAa,CAAC,CAAC,CAAC,KAAK,gBAAgB;YAAE,OAAO,EAAE,CAAA;QAClF,OAAO,mBAAmB,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YACxC,IAAI;YACJ,WAAW,EAAE,uBAAuB,CAAC,IAAI,CAAC,CAAC,WAAW;YACtD,YAAY,EAAE,uBAAuB,CAAC,IAAI,CAAC,CAAC,YAAY;SACzD,CAAC,CAAC,CAAA;IACL,CAAC;CACF,CAAA;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,gBAAgB,CAAC,GAAY;IAC3C,GAAG,CAAC,MAAM,CAAC,CAAC,iBAAiB,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE;QACvC,MAAM,KAAK,GAAI,IAA6F,CAAC,eAAe,CAAA;QAC5H,IAAI,KAAK,KAAK,SAAS;YAAE,OAAM;QAC/B,IAAI,CAAC;YACH,OAAO,KAAK,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAA;QACpC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,CAAC,KAAK,YAAY,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAC;gBAAE,MAAM,KAAK,CAAA;YAC3F,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,kFAAkF,CAAC,CAAA;YAChH,OAAO,GAAG,EAAE,GAAE,CAAC,CAAA;QACjB,CAAC;IACH,CAAC,CAAC,CAAA;AACJ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-advisor",
3
- "version": "0.1.4",
3
+ "version": "0.2.1",
4
4
  "type": "module",
5
5
  "description": "dsh plugin bundle porting the omp advisor subsystem: a per-session reviewer model that observes the primary transcript and injects severity-ranked advice (nit/concern/blocker).",
6
6
  "repository": {