@khorsheed/dsh-local-agent-dsh 0.2.0-rc.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/README.en.md +23 -136
- package/README.i18n.yaml +2 -2
- package/README.md +23 -136
- package/lib/client.js +155 -83
- package/lib/{dsh-cli-provider-UhWvivte.js → dsh-cli-provider-B7s3zH0m.js} +108 -27
- package/lib/dsh-cli-provider-DYRuRpVz.js +1920 -0
- package/lib/dsh-cli-provider.js +1 -1
- package/lib/index.js +40 -27
- package/lib/provision.js +327 -152
- package/lib/types/client/SettingsCard.d.ts +41 -13
- package/lib/types/client/SettingsCard.js +22 -6
- package/lib/types/client/icons.d.ts +1 -1
- package/lib/types/client/index.d.ts +5 -27
- package/lib/types/client/index.js +87 -44
- package/lib/types/client/locales.js +2 -2
- package/lib/types/dsh-cli-provider.js +82 -47
- package/lib/types/index.d.ts +9 -1
- package/lib/types/index.js +35 -21
- package/lib/types/live-driver.js +72 -54
- package/lib/types/model-catalog.d.ts +1 -0
- package/lib/types/model-catalog.js +9 -4
- package/lib/types/model-launch.d.ts +14 -0
- package/lib/types/model-launch.js +58 -0
- package/lib/types/provision.d.ts +166 -119
- package/lib/types/provision.js +376 -166
- package/lib/types/session-mirror.d.ts +55 -0
- package/lib/types/session-mirror.js +155 -1
- package/locale/en.json +2 -2
- package/locale/zh.json +2 -2
- package/package.json +27 -25
- package/lib/invariant.js +0 -33
- package/lib/types/invariant.d.ts +0 -16
- package/lib/types/invariant.js +0 -37
|
@@ -43,21 +43,37 @@ export interface DshSettingsCardInjected {
|
|
|
43
43
|
};
|
|
44
44
|
}
|
|
45
45
|
/**
|
|
46
|
-
* Full props of the 0.1.5 settings.plugin.item card
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
46
|
+
* Full props of the retired 0.1.5 settings.plugin.item card. The host removed
|
|
47
|
+
* that seat and client/index.ts no longer registers the component — the live
|
|
48
|
+
* settings surface is the plugins.item entry built on the BundleConfig
|
|
49
|
+
* component below; this export remains for the component spec and direct
|
|
50
|
+
* composers. The removed slot's owner share was intentionally empty and its
|
|
51
|
+
* root scope delivered the global seats; the structural type below is exact
|
|
52
|
+
* about the one seat the auth block consumes.
|
|
51
53
|
*/
|
|
52
54
|
export type DshSettingsCardProps = InjectFace<DshSettingsCardInjected> & PropsLocale<typeof NS> & {
|
|
53
55
|
/** The standing root-scope global seat (ui-session's merge), delivered to the 0.1.5 card by the renderer. */
|
|
54
56
|
useSessions: ProviderAuthBlockProps['useSessions'];
|
|
55
57
|
};
|
|
56
|
-
/** Full props of the
|
|
57
|
-
export type DshBundleConfigProps = PropsRuntime<'plugins.
|
|
58
|
+
/** Full props of the plugins.item entry (owner prop `view`). */
|
|
59
|
+
export type DshBundleConfigProps = PropsRuntime<'plugins.item'> & InjectFace<DshSettingsCardInjected> & PropsLocale<typeof NS>;
|
|
58
60
|
/**
|
|
59
|
-
*
|
|
60
|
-
* same
|
|
61
|
+
* Full props of the harness's section inside the core's「本机 Agent 委派」
|
|
62
|
+
* family card: the same inject face minus the slot-owner share, plus the one
|
|
63
|
+
* global seat the body consumes, forwarded by the family card (the section
|
|
64
|
+
* render bypasses the slot runtime, so its remaining standard seats are not
|
|
65
|
+
* fabricable here and are deliberately absent).
|
|
66
|
+
*/
|
|
67
|
+
export type DshCardSectionProps = InjectFace<DshSettingsCardInjected> & PropsLocale<typeof NS> & {
|
|
68
|
+
/** Whether the section is currently expanded (the family card mounts the body only while it is). */
|
|
69
|
+
readonly active: boolean;
|
|
70
|
+
/** The standing root-scope global seat (ui-session's merge), forwarded by the family card. */
|
|
71
|
+
readonly useSessions: ProviderAuthBlockProps['useSessions'];
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* The dsh harness settings card of the retired 0.1.5 plugin configuration
|
|
75
|
+
* tab (no longer registered; the live surface is the plugins.item entry
|
|
76
|
+
* below): the same collapsible chrome the official plugin cards use, re-implemented
|
|
61
77
|
* locally (the bundle-purity gate forbids value-importing the official chrome
|
|
62
78
|
* — the context-guard pattern), with the at-a-glance credential dot in the
|
|
63
79
|
* header. Explanation copy lives in hover/focus bubbles behind ⓘ anchors —
|
|
@@ -67,12 +83,24 @@ export type DshBundleConfigProps = PropsRuntime<'plugins.bundle.config'> & Injec
|
|
|
67
83
|
*/
|
|
68
84
|
export declare function DshSettingsCard(props: DshSettingsCardProps): import("react").JSX.Element;
|
|
69
85
|
/**
|
|
70
|
-
* The
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
86
|
+
* The provider's card on the Plugins page (the `plugins.item` entry
|
|
87
|
+
* client/index.ts registers while the Host serves the namespace AND the core
|
|
88
|
+
* predates the family card): the page
|
|
89
|
+
* draws the title, the icon, and the crumb itself, so the entry renders the
|
|
90
|
+
* one-liner for the `summary` view and the bare form — with its own save
|
|
91
|
+
* control — for `page`.
|
|
74
92
|
* @param props - the owner view, the injected scope/auth faces, and copy.
|
|
75
93
|
* @returns the entry.
|
|
76
94
|
*/
|
|
77
95
|
export declare function DshBundleConfig({ view, useSettings, scope, auth, authT, renderModelPicker, harnessModel, useSessions, t }: DshBundleConfigProps): import("react").JSX.Element;
|
|
96
|
+
/**
|
|
97
|
+
* The harness's section inside the core's「本机 Agent 委派」family card: the
|
|
98
|
+
* same useCardState + CardBody the plugins.item page view renders — the
|
|
99
|
+
* collapsible chrome belongs to the family card now. The delegation enable
|
|
100
|
+
* switch keeps its semantics: it writes the scope, the host mounts/unmounts
|
|
101
|
+
* the harness on the served value, and the section itself stays put.
|
|
102
|
+
* @param props - the injected scope/auth faces, the forwarded session seat, and copy.
|
|
103
|
+
* @returns the section body.
|
|
104
|
+
*/
|
|
105
|
+
export declare function DshCardSection({ active, useSettings, scope, auth, authT, renderModelPicker, harnessModel, useSessions, t }: DshCardSectionProps): import("react").JSX.Element;
|
|
78
106
|
//# sourceMappingURL=SettingsCard.d.ts.map
|
|
@@ -161,8 +161,9 @@ function CardBody({ state, auth, authT, renderModelPicker, useSessions, t }) {
|
|
|
161
161
|
return (_jsxs("div", { className: css.body, children: [_jsxs("section", { className: css.block, children: [_jsx("h3", { className: css.blockTitle, children: t('auth.title') }), _jsx(ProviderAuthBlock, { harness: { id: 'dsh', label: 'dsh' }, useSessions: useSessions, status: auth.status, runCommand: auth.runCommand, t: authT }), _jsx("span", { className: css.hint, children: t('auth.host') })] }), _jsxs("section", { className: css.block, children: [_jsx("h3", { className: css.blockTitle, children: t('enable.title') }), _jsxs("div", { className: css.row, children: [_jsx("span", { className: css.rowLabel, children: t('enable.switch') }), _jsx("button", { type: "button", role: "switch", "aria-checked": enabled, "aria-label": t('enable.switch.aria'), className: enabled ? `${css.switch} ${css.switchOn}` : css.switch, disabled: !ready, onClick: () => { write('enabled', !enabled); }, children: _jsx("span", { className: css.knob }) })] }), _jsx("span", { className: css.hint, children: t(enabled ? 'enable.on' : 'enable.off') })] }), _jsxs("section", { className: css.block, children: [_jsxs("h3", { className: css.blockTitle, children: [t('model.title'), _jsx(Tooltip, { label: t('model.info'), side: "bottom", maxWidth: 360, children: _jsx("button", { type: "button", className: css.info, "aria-label": t('model.info.aria'), children: "\u24D8" }) })] }), _jsxs("div", { className: css.row, children: [renderModelPicker?.({ value: modelValue, onChange: setModelDraft, disabled: !ready, defaultLabel: modelDefaultItem }) ?? (choices.length > 0 ? (_jsxs("div", { className: css.modelField, ref: modelFieldRef, children: [modelInputElement, _jsx("button", { type: "button", className: css.modelMenuButton, "aria-label": t('model.menu'), "aria-haspopup": "menu", "aria-expanded": modelMenuOpen, disabled: !ready, onClick: () => { toggleModelMenu(); }, children: _jsx(IconChevronDownOutlineMedium, { className: modelMenuOpen ? `${css.modelMenuChevron} ${css.modelMenuChevronOpen}` : css.modelMenuChevron }) }), modelMenuOpen && (_jsxs("div", { className: modelMenuUp ? `${css.modelMenu} ${css.modelMenuUp}` : css.modelMenu, role: "menu", "aria-label": t('model.menu'), children: [_jsxs("button", { type: "button", role: "menuitemradio", "aria-checked": modelValue.trim() === '', className: modelValue.trim() === '' ? `${css.modelItemDefault} ${css.modelItemCurrent}` : css.modelItemDefault, onClick: () => { setModelDraft(''); setModelMenuOpen(false); }, children: [_jsx("span", { className: css.modelItemLabel, children: modelDefaultItem }), _jsx("span", { className: css.modelItemCheck, "aria-hidden": true, children: modelValue.trim() === '' ? '✓' : '' })] }), choices.map(choice => (_jsxs("button", { type: "button", role: "menuitemradio", "aria-checked": choice === modelValue, className: choice === modelValue ? css.modelItemCurrent : css.modelItem, onClick: () => { setModelDraft(choice); setModelMenuOpen(false); }, children: [_jsx("span", { className: css.modelItemLabel, children: choice }), _jsx("span", { className: css.modelItemCheck, "aria-hidden": true, children: choice === modelValue ? '✓' : '' })] }, choice)))] }))] })) : modelInputElement), _jsx("button", { type: "button", className: css.modelSave, disabled: !ready || modelValue.trim() === storedModel, onClick: () => { saveModel(); }, children: t('model.save') })] }), modelSaved && _jsx("span", { className: css.saved, children: t('model.applied') }), modelError && _jsx("span", { className: css.errorText, children: t('model.error') })] }), _jsxs("section", { className: css.block, children: [_jsxs("div", { className: css.row, children: [_jsxs("span", { className: css.rowLabel, children: [t('live.title'), _jsx(Tooltip, { label: t('live.info'), side: "bottom", maxWidth: 360, children: _jsx("button", { type: "button", className: css.info, "aria-label": t('live.info.aria'), children: "\u24D8" }) })] }), _jsx("button", { type: "button", role: "switch", "aria-checked": live, "aria-label": t('live.title'), className: live ? `${css.switch} ${css.switchOn}` : css.switch, disabled: !ready, onClick: () => { write('live', !live); }, children: _jsx("span", { className: css.knob }) })] }), snapshot.status === 'unavailable' && (_jsx("span", { className: css.hint, children: t('live.unavailable') }))] }), saved && _jsx("span", { className: css.saved, children: t('live.applied') }), error && _jsx("span", { className: css.errorText, children: t('live.error') })] }));
|
|
162
162
|
}
|
|
163
163
|
/**
|
|
164
|
-
* The dsh harness settings card
|
|
165
|
-
*
|
|
164
|
+
* The dsh harness settings card of the retired 0.1.5 plugin configuration
|
|
165
|
+
* tab (no longer registered; the live surface is the plugins.item entry
|
|
166
|
+
* below): the same collapsible chrome the official plugin cards use, re-implemented
|
|
166
167
|
* locally (the bundle-purity gate forbids value-importing the official chrome
|
|
167
168
|
* — the context-guard pattern), with the at-a-glance credential dot in the
|
|
168
169
|
* header. Explanation copy lives in hover/focus bubbles behind ⓘ anchors —
|
|
@@ -185,10 +186,12 @@ export function DshSettingsCard(props) {
|
|
|
185
186
|
return (_jsxs("li", { className: open ? `${css.card} ${css.cardOpen}` : css.card, children: [_jsxs("button", { type: "button", className: css.header, "aria-expanded": open, "aria-label": `${t(open ? 'card.collapse' : 'card.expand')}: ${title}`, onClick: () => { setOpen(!open); }, children: [_jsxs("span", { className: css.headText, children: [_jsxs("span", { className: css.nameRow, children: [_jsx("span", { className: css.name, children: title }), _jsx(AuthStatusDot, { status: authStatus, label: dotLabel })] }), _jsx("span", { className: css.description, children: t('card.description') })] }), _jsx(IconChevronDownOutlineMedium, { className: open ? `${css.chevron} ${css.chevronOpen}` : css.chevron })] }), open && _jsx(CardBody, { state: state, auth: auth, authT: authT, renderModelPicker: renderModelPicker, useSessions: useSessions, t: t })] }));
|
|
186
187
|
}
|
|
187
188
|
/**
|
|
188
|
-
* The
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
*
|
|
189
|
+
* The provider's card on the Plugins page (the `plugins.item` entry
|
|
190
|
+
* client/index.ts registers while the Host serves the namespace AND the core
|
|
191
|
+
* predates the family card): the page
|
|
192
|
+
* draws the title, the icon, and the crumb itself, so the entry renders the
|
|
193
|
+
* one-liner for the `summary` view and the bare form — with its own save
|
|
194
|
+
* control — for `page`.
|
|
192
195
|
* @param props - the owner view, the injected scope/auth faces, and copy.
|
|
193
196
|
* @returns the entry.
|
|
194
197
|
*/
|
|
@@ -198,3 +201,16 @@ export function DshBundleConfig({ view, useSettings, scope, auth, authT, renderM
|
|
|
198
201
|
return _jsx("span", { className: css.description, children: t('card.description') });
|
|
199
202
|
return _jsx(CardBody, { state: state, auth: auth, authT: authT, renderModelPicker: renderModelPicker, useSessions: useSessions, t: t });
|
|
200
203
|
}
|
|
204
|
+
/**
|
|
205
|
+
* The harness's section inside the core's「本机 Agent 委派」family card: the
|
|
206
|
+
* same useCardState + CardBody the plugins.item page view renders — the
|
|
207
|
+
* collapsible chrome belongs to the family card now. The delegation enable
|
|
208
|
+
* switch keeps its semantics: it writes the scope, the host mounts/unmounts
|
|
209
|
+
* the harness on the served value, and the section itself stays put.
|
|
210
|
+
* @param props - the injected scope/auth faces, the forwarded session seat, and copy.
|
|
211
|
+
* @returns the section body.
|
|
212
|
+
*/
|
|
213
|
+
export function DshCardSection({ active, useSettings, scope, auth, authT, renderModelPicker, harnessModel, useSessions, t }) {
|
|
214
|
+
const state = useCardState(active, useSettings, scope, harnessModel, t);
|
|
215
|
+
return _jsx(CardBody, { state: state, auth: auth, authT: authT, renderModelPicker: renderModelPicker, useSessions: useSessions, t: t });
|
|
216
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Generated by scripts/sync-icon-artwork.mts from @deepseek-ai/dsh-client-ui-primitives
|
|
3
|
-
* (v0.1
|
|
3
|
+
* (v0.2.1-rc.1, harness 1600d8a1e1) — do not edit by hand. The 0.1.5 and rc.1 host lines
|
|
4
4
|
* share no icon export name, so the rc.1 artwork is inlined here: pure SVG,
|
|
5
5
|
* no host runtime identity, identical on every line. Re-run the generator
|
|
6
6
|
* after adding an icon import or bumping the harness checkout.
|
|
@@ -1,30 +1,7 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Local-agent-dsh plugin, browser half: the dsh settings surface, one face
|
|
3
|
-
* per install shape and host line — a standalone install renders the
|
|
4
|
-
* bundle's own configuration on its Plugins-page detail view
|
|
5
|
-
* (`plugins.bundle.config`, keyed by package name), a family-bundle install
|
|
6
|
-
* renders the same card through the row-level configure entry on the
|
|
7
|
-
* BUNDLE's detail view (`plugins.row.config`, keyed
|
|
8
|
-
* `@khorsheed/dsh-bundle-local-agent#local-agent-dsh`), and 0.1.5 renders
|
|
9
|
-
* the `settings.plugin.item` card (keyed to the `local-agent-dsh`
|
|
10
|
-
* settings namespace the host half registers) in the official
|
|
11
|
-
* Plugins → 可配置插件 tab. The card carries the family core's shared
|
|
12
|
-
* ProviderAuthBlock (dsh authenticates through the host credentials, so the
|
|
13
|
-
* block renders status only), the DeepSeek delegation switch — migrated from
|
|
14
|
-
* the core section's `local-agent.settings.row-action` seat, which this
|
|
15
|
-
* plugin no longer contributes to — and the resident-mode block (live switch)
|
|
16
|
-
* plus the default-model block (free-text input, the broker's effective-model
|
|
17
|
-
* line and suggestion vocabulary), whose writes ride the bound settingsScope:
|
|
18
|
-
* the host watcher hot-applies them, no reload. Every read degrades: an
|
|
19
|
-
* absent gateway renders the auth block's 'unavailable' state and drops the
|
|
20
|
-
* model surface to the bare input, an unregistered namespace disables the
|
|
21
|
-
* controls.
|
|
22
|
-
* @module @khorsheed/dsh-local-agent-dsh/client
|
|
23
|
-
*/
|
|
24
1
|
import type { Context } from '@deepseek-ai/cordis';
|
|
25
2
|
import { en, NS, zh, type LocalAgentDshKey } from './locales.ts';
|
|
26
|
-
export { DshBundleConfig, DshSettingsCard } from './SettingsCard.tsx';
|
|
27
|
-
export type { DshCardSettings, DshBundleConfigProps, DshSettingsCardInjected, DshSettingsCardProps, } from './SettingsCard.tsx';
|
|
3
|
+
export { DshBundleConfig, DshCardSection, DshSettingsCard } from './SettingsCard.tsx';
|
|
4
|
+
export type { DshCardSettings, DshBundleConfigProps, DshCardSectionProps, DshSettingsCardInjected, DshSettingsCardProps, } from './SettingsCard.tsx';
|
|
28
5
|
export { en, NS, zh };
|
|
29
6
|
export type { LocalAgentDshKey };
|
|
30
7
|
/**
|
|
@@ -33,11 +10,12 @@ export type { LocalAgentDshKey };
|
|
|
33
10
|
* line (`settingsScope` on 0.1.5, `configForms` on rc.1), so a static entry
|
|
34
11
|
* would pend the whole client plugin on the other line; bindSettingsScope
|
|
35
12
|
* probes the serving one instead (the settings client package is a declared
|
|
36
|
-
* client inject, so one of them is always already up).
|
|
13
|
+
* client inject, so one of them is always already up). The card track probes
|
|
14
|
+
* `configForms` through the same kind of deferred inject (see apply).
|
|
37
15
|
*/
|
|
38
16
|
export declare const inject: string[];
|
|
39
17
|
/**
|
|
40
|
-
* Client plugin body: register the dictionaries and the dsh settings
|
|
18
|
+
* Client plugin body: register the dictionaries and the dsh settings card
|
|
41
19
|
* bound to the local-agent-dsh namespace.
|
|
42
20
|
* @param ctx - client root context.
|
|
43
21
|
*/
|
|
@@ -1,30 +1,50 @@
|
|
|
1
1
|
import { bindSettingsScope } from '@khorsheed/dsh-local-agent/src/client/settings-scope.ts';
|
|
2
|
-
import { en, NS, zh } from './locales.js';
|
|
3
|
-
import { DshBundleConfig, DshSettingsCard, } from './SettingsCard.js';
|
|
4
|
-
export { DshBundleConfig, DshSettingsCard } from './SettingsCard.js';
|
|
5
|
-
export { en, NS, zh };
|
|
6
2
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
3
|
+
* Local-agent-dsh plugin, browser half: the dsh settings card — a section of
|
|
4
|
+
* the family core's「本机 Agent 委派」family card on the Plugins page
|
|
5
|
+
* (registered through the core's `localAgentUi.registerHarnessCard` while the
|
|
6
|
+
* Host serves the `local-agent-dsh` namespace), or this package's own
|
|
7
|
+
* `plugins.item` entry when the core predates the family card (the
|
|
8
|
+
* declare-and-degrade fallback). Every
|
|
9
|
+
* install shape serves the row's volatile Config under the row id —
|
|
10
|
+
* standalone install, family bundle, or mode pack — so the card follows the
|
|
11
|
+
* served namespace rather than any carrier bundle, and a composition without
|
|
12
|
+
* the row shows no trace of it. The card carries the family core's shared
|
|
13
|
+
* ProviderAuthBlock (dsh authenticates through the host credentials, so the
|
|
14
|
+
* block renders status only), the DeepSeek delegation switch — migrated from
|
|
15
|
+
* the core section's `local-agent.settings.row-action` seat, which this
|
|
16
|
+
* plugin no longer contributes to — and the resident-mode block (live switch)
|
|
17
|
+
* plus the default-model block (free-text input, the broker's effective-model
|
|
18
|
+
* line and suggestion vocabulary), whose writes ride the bound settingsScope:
|
|
19
|
+
* the host watcher hot-applies them, no reload. Every read degrades: an
|
|
20
|
+
* absent gateway renders the auth block's 'unavailable' state and drops the
|
|
21
|
+
* model surface to the bare input, an unregistered namespace disables the
|
|
22
|
+
* controls.
|
|
23
|
+
* @module @khorsheed/dsh-local-agent-dsh/client
|
|
10
24
|
*/
|
|
11
|
-
|
|
25
|
+
import { createElement, useSyncExternalStore } from 'react';
|
|
26
|
+
import { en, NS, zh } from './locales.js';
|
|
27
|
+
import { DshBundleConfig, DshCardSection, } from './SettingsCard.js';
|
|
28
|
+
export { DshBundleConfig, DshCardSection, DshSettingsCard } from './SettingsCard.js';
|
|
29
|
+
export { en, NS, zh };
|
|
12
30
|
/**
|
|
13
31
|
* Required services: slot registry, command Remote, and locale registry. The
|
|
14
32
|
* settings scope is NOT a static inject — its service name differs per host
|
|
15
33
|
* line (`settingsScope` on 0.1.5, `configForms` on rc.1), so a static entry
|
|
16
34
|
* would pend the whole client plugin on the other line; bindSettingsScope
|
|
17
35
|
* probes the serving one instead (the settings client package is a declared
|
|
18
|
-
* client inject, so one of them is always already up).
|
|
36
|
+
* client inject, so one of them is always already up). The card track probes
|
|
37
|
+
* `configForms` through the same kind of deferred inject (see apply).
|
|
19
38
|
*/
|
|
20
39
|
export const inject = ['slots', 'remote', 'remote.commands', 'locale'];
|
|
21
40
|
/**
|
|
22
|
-
* Client plugin body: register the dictionaries and the dsh settings
|
|
41
|
+
* Client plugin body: register the dictionaries and the dsh settings card
|
|
23
42
|
* bound to the local-agent-dsh namespace.
|
|
24
43
|
* @param ctx - client root context.
|
|
25
44
|
*/
|
|
26
45
|
export function apply(ctx) {
|
|
27
46
|
ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'local-agent-dsh: dictionaries');
|
|
47
|
+
const t = ctx.locale.bind(NS);
|
|
28
48
|
const scope = bindSettingsScope(ctx, 'local-agent-dsh');
|
|
29
49
|
// The auth block's copy lives in the family core's dictionary; binding is
|
|
30
50
|
// stable per namespace and late dictionary registration still resolves.
|
|
@@ -34,10 +54,11 @@ export function apply(ctx) {
|
|
|
34
54
|
// never matters, and degrade to the auth block's 'unavailable' state when
|
|
35
55
|
// the core client is absent.
|
|
36
56
|
const gateway = () => ctx.get('remote.localAgentGateway');
|
|
37
|
-
//
|
|
38
|
-
|
|
57
|
+
// The card's inject face minus the hooks compartment — the family-card
|
|
58
|
+
// section consumes this verbatim; the standalone card's slot registration
|
|
59
|
+
// adds the hooks compartment back below.
|
|
60
|
+
const cardFace = () => ({
|
|
39
61
|
scope,
|
|
40
|
-
hooks: { settings: scope },
|
|
41
62
|
authT,
|
|
42
63
|
renderModelPicker: props => ctx.get('localAgentUi')?.renderHarnessModelPicker('dsh', props),
|
|
43
64
|
// The harness's memberless model surface; absent gateway/broker
|
|
@@ -54,35 +75,57 @@ export function apply(ctx) {
|
|
|
54
75
|
.then(result => (result.ok ? result.value?.result.text : undefined)),
|
|
55
76
|
},
|
|
56
77
|
});
|
|
57
|
-
// The settings
|
|
58
|
-
|
|
59
|
-
//
|
|
60
|
-
//
|
|
61
|
-
//
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
//
|
|
68
|
-
// the
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
78
|
+
// The settings card injects this face.
|
|
79
|
+
const cardInject = () => ({ ...cardFace(), hooks: { settings: scope } });
|
|
80
|
+
// The family-card section render bypasses the slot renderer's inject.hooks
|
|
81
|
+
// materialization, so bind the scope's selector hook here with the same
|
|
82
|
+
// semantics (a uSES subscription over the scope's own subscribe/getSnapshot).
|
|
83
|
+
const subscribeScope = (listener) => scope.subscribe(listener);
|
|
84
|
+
const readScope = () => scope.getSnapshot();
|
|
85
|
+
const useSettings = (select) => select(useSyncExternalStore(subscribeScope, readScope));
|
|
86
|
+
// One Plugins-page seat gated on the SERVED NAMESPACE, never on the install
|
|
87
|
+
// shape: whichever composition carries the `local-agent-dsh` row —
|
|
88
|
+
// standalone install, family bundle, or mode pack — serves the row's
|
|
89
|
+
// volatile Config under the row id, and `whileServed` keeps the
|
|
90
|
+
// registration alive for exactly that span (the official companion
|
|
91
|
+
// convention), so a composition without the row shows no trace of the card.
|
|
92
|
+
// This replaces the retired per-shape seats: `plugins.row.config` hardcoded
|
|
93
|
+
// one carrier bundle into its key, and the host deleted
|
|
94
|
+
// `settings.plugin.item`. The probe rides a deferred inject because
|
|
95
|
+
// configForms exists only on the alpha.2+ host line; a 0.1.5 host never
|
|
96
|
+
// fires it, and a configForms predating whileServed (0.1.6-alpha.2) skips
|
|
97
|
+
// the card.
|
|
98
|
+
ctx.inject(['configForms'], (formsCtx) => {
|
|
99
|
+
const forms = formsCtx.configForms;
|
|
100
|
+
if (typeof forms.whileServed !== 'function')
|
|
101
|
+
return;
|
|
102
|
+
return forms.whileServed(['local-agent-dsh'], () => {
|
|
103
|
+
// The family card owns the Plugins-page seat when the core's client
|
|
104
|
+
// provides it: a core new enough answers registerHarnessCard, and this
|
|
105
|
+
// package renders as a section of the「本机 Agent 委派」card instead of
|
|
106
|
+
// its own entry. The probe runs at serve time — a host round-trip past
|
|
107
|
+
// client plugin boot — so a composed core has already provided the
|
|
108
|
+
// service; an old or absent core keeps the standalone card below.
|
|
109
|
+
const ui = ctx.get('localAgentUi');
|
|
110
|
+
if (typeof ui?.registerHarnessCard === 'function') {
|
|
111
|
+
return ui.registerHarnessCard({
|
|
112
|
+
name: 'dsh',
|
|
113
|
+
// The retired standalone card's order, kept as the section order.
|
|
114
|
+
order: 140,
|
|
115
|
+
locale: NS,
|
|
116
|
+
label: () => t('card.title'),
|
|
117
|
+
render: ({ active, useSessions }) => createElement(DshCardSection, { active, useSessions, ...cardFace(), useSettings, t }),
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
return ctx.slots.inject('plugins.item', () => ctx.slots.register({
|
|
121
|
+
name: 'plugins.item',
|
|
122
|
+
id: 'local-agent-dsh',
|
|
123
|
+
// Community cards sort after the official settings pages (orders 10-40).
|
|
124
|
+
order: 140,
|
|
125
|
+
label: () => t('card.title'),
|
|
126
|
+
locale: NS,
|
|
127
|
+
inject: cardInject,
|
|
128
|
+
}, DshBundleConfig));
|
|
129
|
+
});
|
|
130
|
+
});
|
|
88
131
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
export const NS = 'local-agent-dsh';
|
|
4
4
|
/** Simplified Chinese dictionary (the key-set source of truth). */
|
|
5
5
|
export const zh = {
|
|
6
|
-
'card.title': '
|
|
6
|
+
'card.title': '本机 DSH 委派',
|
|
7
7
|
'card.description': 'dsh 自委派:开关、认证状态与常驻模式(live)',
|
|
8
8
|
'card.expand': '展开',
|
|
9
9
|
'card.collapse': '收起',
|
|
@@ -35,7 +35,7 @@ export const zh = {
|
|
|
35
35
|
};
|
|
36
36
|
/** English dictionary, checked complete against the zh key set. */
|
|
37
37
|
export const en = {
|
|
38
|
-
'card.title': 'Local
|
|
38
|
+
'card.title': 'Local DSH Delegation',
|
|
39
39
|
'card.description': 'dsh self-delegation: toggle, credential status, and resident (live) mode',
|
|
40
40
|
'card.expand': 'Expand',
|
|
41
41
|
'card.collapse': 'Collapse',
|
|
@@ -10,15 +10,16 @@
|
|
|
10
10
|
* that session. No stdout parsing — the sub-dsh session id is known a priori.
|
|
11
11
|
* @module @khorsheed/dsh-local-agent-dsh/dsh-cli-provider
|
|
12
12
|
*/
|
|
13
|
+
import { localModelLaunch } from './model-launch.js';
|
|
13
14
|
import { randomUUID } from 'node:crypto';
|
|
14
15
|
import { credentialRef } from '@deepseek-ai/dsh-credentials';
|
|
15
16
|
import { SessionId } from '@deepseek-ai/dsh-session';
|
|
16
|
-
import { NO_START_CAPABILITIES, settleRunResult, subprocessRunHandle, } from '@deepseek-ai/dsh-subagent';
|
|
17
|
-
import { assertResumeCwdUnchanged, assertResumeScopeUnchanged, resolveRoundModel, containerExecSpawn, containerScopedHome, delegationEnv, establishSubagentCatalogChild, probeCliVersion, resolveChildCwd, subagentDelegationLabel, } from '@khorsheed/dsh-local-agent';
|
|
17
|
+
import { NO_START_CAPABILITIES, SUBAGENT_DESCRIPTOR_VERSION, settleRunResult, subprocessRunHandle, } from '@deepseek-ai/dsh-subagent';
|
|
18
|
+
import { assertResumeCwdUnchanged, assertResumeScopeUnchanged, resolveRoundModel, containerExecSpawn, containerScopedHome, delegationEnv, establishSubagentCatalogChild, persistChildSession, probeCliVersion, resolveChildCwd, subagentDelegationLabel, } from '@khorsheed/dsh-local-agent';
|
|
18
19
|
import { MEMBER_BRIDGE_SOCKET_ENV, MEMBER_BRIDGE_TOKEN_ENV } from '@khorsheed/dsh-local-agent/types';
|
|
19
20
|
import { LiveChannelUnavailableError } from './live-driver.js';
|
|
20
|
-
import { DEFAULT_SUB_PROFILE_NAME, provisionDshScope } from './provision.js';
|
|
21
|
-
import { mirrorDshSession } from './session-mirror.js';
|
|
21
|
+
import { DEFAULT_SUB_PROFILE_NAME, provisionDshScope, unitSubProfileName } from './provision.js';
|
|
22
|
+
import { closeMirroredOpenStep, mirrorDshSession } from './session-mirror.js';
|
|
22
23
|
/**
|
|
23
24
|
* Spawn-env name carrying the bridge entry path into the sub-dsh process,
|
|
24
25
|
* where the headless bundle's mcp-client row reads it (`!!js`). The name is a
|
|
@@ -168,7 +169,7 @@ export class DshCliProvider {
|
|
|
168
169
|
...record?.configurationLock === undefined ? {} : { configurationLock: record.configurationLock },
|
|
169
170
|
}, configuration => this.startDshResume(request, intent, cwd, homeDir, exec, scope, record?.model, configuration), request.signal, intent?.onAdmitted);
|
|
170
171
|
}
|
|
171
|
-
const childSessionId = SessionId(intent?.preparedMemberId ?? randomUUID());
|
|
172
|
+
const childSessionId = SessionId(intent?.preparedMemberId ?? intent?.childSessionId ?? randomUUID());
|
|
172
173
|
if (typeof this.ctx.localAgent.withMemberConfigurationRound !== 'function') {
|
|
173
174
|
if (intent?.effort !== undefined)
|
|
174
175
|
throw new Error('DSH effort requires the configuration admission core');
|
|
@@ -202,20 +203,25 @@ export class DshCliProvider {
|
|
|
202
203
|
delegationDepth: (request.parent.session.header.delegationDepth ?? 0) + 1,
|
|
203
204
|
},
|
|
204
205
|
});
|
|
205
|
-
// The durable one-shot descriptor
|
|
206
|
-
//
|
|
207
|
-
//
|
|
208
|
-
//
|
|
209
|
-
//
|
|
206
|
+
// The durable one-shot descriptor marks this child as session-backed;
|
|
207
|
+
// without it the 子代理 projection cannot classify the session and the
|
|
208
|
+
// delegation stays invisible. Host 0.2.1 no longer resolves a descriptor
|
|
209
|
+
// onto the request (the runtime's one-shot snapshot is gone), so the
|
|
210
|
+
// provider stamps the same payload itself. The label carries the harness
|
|
211
|
+
// display name as the source marker so the dropdown shows which local
|
|
212
|
+
// agent produced the conversation; the activation spec's required label
|
|
213
|
+
// arrives as the empty string when the caller named none.
|
|
210
214
|
const harness = this.ctx.localAgent.get('dsh');
|
|
211
|
-
const label = subagentDelegationLabel(harness?.displayName ?? 'dsh', request.
|
|
215
|
+
const label = subagentDelegationLabel(harness?.displayName ?? 'dsh', request.label || undefined);
|
|
212
216
|
childSession.append('subagent/descriptor', {
|
|
213
|
-
|
|
217
|
+
version: SUBAGENT_DESCRIPTOR_VERSION,
|
|
218
|
+
mode: 'one-shot',
|
|
219
|
+
provider: this.name,
|
|
214
220
|
label,
|
|
215
221
|
});
|
|
216
|
-
// The
|
|
217
|
-
//
|
|
218
|
-
//
|
|
222
|
+
// The 0.2.1 manager appends the parent-side subagent/catalog discovery
|
|
223
|
+
// row only for parent-delivered children; the family runs caller
|
|
224
|
+
// delivery, so the provider appends it here — once per child, where
|
|
219
225
|
// the descriptor lands. A failure degrades to the warn below; a missing
|
|
220
226
|
// row never blocks the delegation.
|
|
221
227
|
establishSubagentCatalogChild(request.parent.session, childSession.header, label);
|
|
@@ -488,20 +494,25 @@ export async function startDshCliRun(request, spec) {
|
|
|
488
494
|
if (request.signal.aborted) {
|
|
489
495
|
throw new Error('subagent-dsh: request was aborted before the CLI started');
|
|
490
496
|
}
|
|
491
|
-
|
|
497
|
+
// A container round launches the `-unit` twin when one is configured: its
|
|
498
|
+
// headless link names the image's own install, while the host profile
|
|
499
|
+
// links this instance's (`unitHeadlessBundleDir`).
|
|
500
|
+
const profileName = spec.exec === undefined ? config.profileName ?? DEFAULT_SUB_PROFILE_NAME : unitSubProfileName(config);
|
|
492
501
|
// Provisioning is idempotent and cheap; re-running heals a deleted or
|
|
493
502
|
// drifted sub-profile before every round. A CONTAINER round skips it: the
|
|
494
|
-
// sub-profile's node_modules
|
|
495
|
-
//
|
|
503
|
+
// sub-profile's node_modules carries the HOST installation's headless
|
|
504
|
+
// bundle (a copy of the host line's build, or a link that resolves to
|
|
505
|
+
// nothing inside a unit) — and the scoped
|
|
496
506
|
// home is bind-mounted, so writing it would plant a broken profile in the
|
|
497
507
|
// directory the unit actually reads. A containerized caller names the
|
|
498
508
|
// unit's own profile through `profileName` (the image's in-box `headless`)
|
|
499
509
|
// and its own entry through `cliLaunch`.
|
|
500
510
|
if (spec.exec === undefined)
|
|
501
|
-
provisionDshScope(spec.homeDir, config);
|
|
511
|
+
provisionDshScope(spec.homeDir, { ...config, inheritModelConfiguration: true });
|
|
502
512
|
// Resolve the sub-dsh credential BEFORE spawning: the key travels in the
|
|
503
513
|
// explicit env layer, which is the only way past the shared env scrub.
|
|
504
|
-
const
|
|
514
|
+
const modelEnv = spec.exec === undefined ? (await localModelLaunch(ctx, config, spec.model)).env
|
|
515
|
+
: { DEEPSEEK_API_KEY: await resolveApiKey(ctx, config) };
|
|
505
516
|
const turn = spec.resume?.turn ?? 1;
|
|
506
517
|
// The turn opens at the real spawn moment so the timing projection
|
|
507
518
|
// measures actual CLI runtime.
|
|
@@ -525,7 +536,7 @@ export async function startDshCliRun(request, spec) {
|
|
|
525
536
|
// sessions into the parent instance's store.
|
|
526
537
|
const env = delegationEnv({
|
|
527
538
|
DSH_HOME: spec.homeDir,
|
|
528
|
-
|
|
539
|
+
...modelEnv,
|
|
529
540
|
// The launch replicates the parent's tsx ESM hook (dshLaunchArgv), so
|
|
530
541
|
// the hook's tsconfig path is a hard launch dependency: without it tsx
|
|
531
542
|
// compiles the harness source with default options and the child dies
|
|
@@ -674,47 +685,71 @@ export async function startDshCliRun(request, spec) {
|
|
|
674
685
|
// reached its endpoint and produced an answer.
|
|
675
686
|
if (settled.stopReason === 'completed')
|
|
676
687
|
markCredentialVerified(ctx, spec.scope);
|
|
677
|
-
// The turn
|
|
678
|
-
//
|
|
679
|
-
//
|
|
680
|
-
//
|
|
681
|
-
if (spec.childSession !== undefined) {
|
|
682
|
-
if (settled.stopReason === 'completed') {
|
|
683
|
-
spec.childSession.append('turn/end', { turn, reason: { kind: 'completed' } });
|
|
684
|
-
}
|
|
685
|
-
else if (settled.stopReason === 'aborted') {
|
|
686
|
-
spec.childSession.append('turn/end', { turn, reason: { kind: 'aborted', reason: { kind: 'parent' } } });
|
|
687
|
-
}
|
|
688
|
-
else {
|
|
689
|
-
spec.childSession.append('turn/end', {
|
|
690
|
-
turn,
|
|
691
|
-
reason: { kind: 'error', error: { message: 'dsh exited before the run completed', code: 'UNKNOWN' } },
|
|
692
|
-
});
|
|
693
|
-
}
|
|
694
|
-
}
|
|
688
|
+
// The turn does NOT close here: turn/end lands in the mirror queue after
|
|
689
|
+
// the settle pass (see below), so every mirrored event precedes it in the
|
|
690
|
+
// stored log — the released-log reader rejects content outside an open
|
|
691
|
+
// turn on every cold read.
|
|
695
692
|
return settled;
|
|
696
693
|
});
|
|
697
694
|
// After the child EXITS — however it ended (completed, killed by dispose, or
|
|
698
695
|
// crashed) — mirror whatever the sub-dsh session already produced into the
|
|
699
696
|
// dsh subagent session, so every round preserves its conversation and real
|
|
700
697
|
// token usage instead of showing turn boundaries only. Waits for the settle
|
|
701
|
-
// chain first
|
|
702
|
-
//
|
|
703
|
-
//
|
|
698
|
+
// chain first AND for the process to actually exit (the sub-dsh flushed its
|
|
699
|
+
// session before exiting) before reading. The turn closes INSIDE the mirror
|
|
700
|
+
// queue, after the settle pass: the copied tail's open step first receives
|
|
701
|
+
// its closure (an interrupted sub-dsh leaves its step — and any advertised
|
|
702
|
+
// tool calls — open, and the released-log reader rejects a turn/end over
|
|
703
|
+
// it), then turn/end lands and is persisted explicitly (the mirror's own
|
|
704
|
+
// persist skips a no-op pass, which is exactly the case that needs the
|
|
705
|
+
// boundary written). Every terminal path closes the window — a failed or
|
|
706
|
+
// cancelled run settles 'error'/'aborted' instead of leaving the turn open.
|
|
704
707
|
void result.then(() => child.done).then(() => {
|
|
705
708
|
if (spec.childSession !== undefined) {
|
|
706
709
|
const childSession = spec.childSession;
|
|
707
710
|
// The settle pass reports the final mirror count even when polling
|
|
708
711
|
// already covered the round (empty delta): it is authoritative.
|
|
709
|
-
return enqueueMirror(() =>
|
|
712
|
+
return enqueueMirror(async () => {
|
|
713
|
+
const settled = await result;
|
|
714
|
+
try {
|
|
715
|
+
await mirrorAndReport(childSession, true);
|
|
716
|
+
}
|
|
717
|
+
catch (error) {
|
|
718
|
+
// The mirror degrades to a warn; the turn boundary still closes —
|
|
719
|
+
// an open turn at EOF reads as an interrupted tail only until the
|
|
720
|
+
// next resume round tries to open its turn over it.
|
|
721
|
+
ctx.logger.warn(`subagent-dsh: settle mirror failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
722
|
+
}
|
|
723
|
+
closeMirroredOpenStep(childSession);
|
|
724
|
+
// The turn closes at the real settle moment (plus the settle-mirror
|
|
725
|
+
// quiescence), so the timing projection's duration equals the actual
|
|
726
|
+
// CLI runtime.
|
|
727
|
+
if (settled.stopReason === 'completed') {
|
|
728
|
+
childSession.append('turn/end', { turn, reason: { kind: 'completed' } });
|
|
729
|
+
}
|
|
730
|
+
else if (settled.stopReason === 'aborted') {
|
|
731
|
+
childSession.append('turn/end', { turn, reason: { kind: 'aborted', reason: { kind: 'parent' } } });
|
|
732
|
+
}
|
|
733
|
+
else {
|
|
734
|
+
childSession.append('turn/end', {
|
|
735
|
+
turn,
|
|
736
|
+
reason: { kind: 'error', error: { message: 'dsh exited before the run completed', code: 'UNKNOWN' } },
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
await persistChildSession(ctx, childSession).catch((error) => {
|
|
740
|
+
ctx.logger.warn(`subagent-dsh: turn/end persistence failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
741
|
+
});
|
|
742
|
+
});
|
|
710
743
|
}
|
|
711
744
|
return undefined;
|
|
712
745
|
}, () => { });
|
|
713
746
|
return Promise.resolve(subprocessRunHandle({
|
|
714
|
-
//
|
|
715
|
-
//
|
|
716
|
-
//
|
|
717
|
-
id
|
|
747
|
+
// Host 0.2.1: the published run id is a fresh PER-EXECUTION identity —
|
|
748
|
+
// the activation manager rejects a run id naming a live Session (the
|
|
749
|
+
// mirrored child session is exactly that), and resume rounds reuse the
|
|
750
|
+
// child session id. The durable child identity the family tracks rides
|
|
751
|
+
// the staged intent instead (spec.childSession.id == its childSessionId).
|
|
752
|
+
id: SessionId(randomUUID()),
|
|
718
753
|
result,
|
|
719
754
|
signal: request.signal,
|
|
720
755
|
onAbort,
|
package/lib/types/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* namespace (default off); while ON, the controller registers the `dsh`
|
|
5
5
|
* harness into the core registry (scoped home under the shared homes root,
|
|
6
6
|
* `$DSH_HOME`), the `dsh-cli` delegation provider (sub-dsh headless runs with
|
|
7
|
-
* the parent's
|
|
7
|
+
* the parent's selected API-key model route), and the family delegation tool for it.
|
|
8
8
|
* While OFF — the default — nothing model-visible mounts, so the instance
|
|
9
9
|
* keeps exactly the current behavior (official in-process subagent tools) and
|
|
10
10
|
* the model never sees two overlapping delegation tools at once.
|
|
@@ -42,6 +42,14 @@ export interface LocalAgentDshConfig {
|
|
|
42
42
|
cliLaunch?: string[];
|
|
43
43
|
/** Override the headless bundle directory the sub-profile symlinks to. */
|
|
44
44
|
headlessBundleDir?: string;
|
|
45
|
+
/**
|
|
46
|
+
* The headless bundle directory as an evaluation unit (container round)
|
|
47
|
+
* sees it — the image's own install. Present, provisioning also writes a
|
|
48
|
+
* `<profileName>-unit` sub-profile linking it, and container rounds launch
|
|
49
|
+
* that one, while host rounds keep `headlessBundleDir` (default: this
|
|
50
|
+
* instance's own headless). Absent, both share one sub-profile.
|
|
51
|
+
*/
|
|
52
|
+
unitHeadlessBundleDir?: string;
|
|
45
53
|
/**
|
|
46
54
|
* The sub-dsh's permission boundary: the sandbox mode every confined call
|
|
47
55
|
* runs under, paired with the approval policy `dsh-base`'s own preset table
|