@khorsheed/dsh-local-agent-claude-code 0.2.0-rc.1 → 0.2.0-rc.3
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 +11 -0
- package/README.en.md +35 -29
- package/README.i18n.yaml +2 -2
- package/README.md +31 -29
- package/lib/client.js +843 -316
- package/lib/index.js +1491 -1520
- package/lib/types/claude-cli-provider.d.ts +13 -4
- package/lib/types/claude-cli-provider.js +61 -49
- package/lib/types/client/SettingsCard.d.ts +75 -24
- package/lib/types/client/SettingsCard.js +106 -128
- package/lib/types/client/index.d.ts +12 -25
- package/lib/types/client/index.js +135 -50
- package/lib/types/client/locales.d.ts +36 -24
- package/lib/types/client/locales.js +40 -28
- package/lib/types/endpoint-auth.d.ts +181 -0
- package/lib/types/endpoint-auth.js +281 -0
- package/lib/types/index.d.ts +32 -16
- package/lib/types/index.js +128 -62
- package/lib/types/live-driver.d.ts +9 -1
- package/lib/types/live-driver.js +18 -18
- package/lib/types/live-switch.d.ts +4 -1
- package/lib/types/model-broker.d.ts +4 -2
- package/lib/types/model-broker.js +19 -8
- package/lib/types/model-catalog.d.ts +35 -6
- package/lib/types/model-catalog.js +58 -68
- package/lib/types/provision.d.ts +34 -27
- package/lib/types/provision.js +49 -31
- package/lib/types/records.d.ts +4 -62
- package/lib/types/records.js +5 -382
- package/locale/en.json +2 -2
- package/locale/zh.json +2 -2
- package/package.json +20 -21
- package/lib/invariant.js +0 -31
- package/lib/types/invariant.d.ts +0 -16
- package/lib/types/invariant.js +0 -33
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect,
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
3
|
import { Tooltip } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
4
4
|
import { IconChevronDownOutlineMedium } from './icons.js';
|
|
5
5
|
// The shared auth block is bundled from the family core's source — the
|
|
@@ -7,10 +7,9 @@ import { IconChevronDownOutlineMedium } from './icons.js';
|
|
|
7
7
|
// the block carries no runtime identity to share.
|
|
8
8
|
import { ProviderAuthBlock, } from '@khorsheed/dsh-local-agent/src/client/ProviderAuthBlock.tsx';
|
|
9
9
|
import { AuthStatusDot } from '@khorsheed/dsh-local-agent/src/client/AuthStatusDot.tsx';
|
|
10
|
-
import { useHarnessAuthStatus } from '@khorsheed/dsh-local-agent/src/client/auth-status.ts';
|
|
10
|
+
import { publishAuthStatus, useHarnessAuthStatus } from '@khorsheed/dsh-local-agent/src/client/auth-status.ts';
|
|
11
|
+
import { ModelListSection } from '@khorsheed/dsh-local-agent/src/client/ModelListSection.tsx';
|
|
11
12
|
import css from './SettingsCard.module.css';
|
|
12
|
-
/** How many previously-saved model identifiers the input suggests. */
|
|
13
|
-
const RECENT_MODEL_LIMIT = 5;
|
|
14
13
|
/**
|
|
15
14
|
* The staged card state both settings surfaces share: the resident-mode and
|
|
16
15
|
* model edits write through the bound settingsScope immediately
|
|
@@ -22,145 +21,111 @@ const RECENT_MODEL_LIMIT = 5;
|
|
|
22
21
|
* @param useSettings - the bound settings hook from the inject hooks compartment.
|
|
23
22
|
* @param scope - the bound settings scope the writes go through.
|
|
24
23
|
* @param modelInfo - the harness's memberless model surface read; absent degrades to the bare input.
|
|
24
|
+
* @param endpointAuth - the harness's endpoint credential face; absent hides the credential form.
|
|
25
25
|
* @param t - copy.
|
|
26
26
|
*/
|
|
27
|
-
function useCardState(active, useSettings, scope,
|
|
27
|
+
function useCardState(active, useSettings, scope, endpointAuth) {
|
|
28
28
|
const snapshot = useSettings(value => value);
|
|
29
29
|
const [saved, setSaved] = useState(false);
|
|
30
30
|
const [error, setError] = useState(false);
|
|
31
31
|
const ready = snapshot.status === 'ready' && snapshot.writable;
|
|
32
32
|
const live = snapshot.value?.live ?? false;
|
|
33
|
-
// The
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const [
|
|
40
|
-
const [
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const
|
|
33
|
+
// The endpoint credential form talks to the harness's OWN credential store
|
|
34
|
+
// through the endpointAuth face — never the settings scope, so no key ever
|
|
35
|
+
// lands in the profile patch document. baseUrl echoes (it is not a
|
|
36
|
+
// secret); the two keys never do — a set key shows as a "configured"
|
|
37
|
+
// placeholder and the input holds only the draft of a REPLACEMENT. A blank
|
|
38
|
+
// write clears the field (the config layer then answers for it again).
|
|
39
|
+
const [endpointState, setEndpointState] = useState(null);
|
|
40
|
+
const [baseUrlDraft, setBaseUrlDraft] = useState(null);
|
|
41
|
+
const [apiKeyDraft, setApiKeyDraft] = useState(null);
|
|
42
|
+
const [authTokenDraft, setAuthTokenDraft] = useState(null);
|
|
43
|
+
const [endpointSaved, setEndpointSaved] = useState(false);
|
|
44
|
+
const [endpointError, setEndpointError] = useState(false);
|
|
45
|
+
const endpointDirty = baseUrlDraft !== null || apiKeyDraft !== null || authTokenDraft !== null;
|
|
46
|
+
const endpointUnavailable = endpointAuth === undefined || endpointState === 'unavailable';
|
|
47
|
+
const endpointLoading = !endpointUnavailable && endpointState === null;
|
|
48
|
+
const apiKeyConfigured = endpointState !== null && endpointState !== 'unavailable' && endpointState.apiKeySet;
|
|
49
|
+
const authTokenConfigured = endpointState !== null && endpointState !== 'unavailable' && endpointState.authTokenSet;
|
|
48
50
|
useEffect(() => {
|
|
49
|
-
if (!active ||
|
|
51
|
+
if (!active || endpointAuth === undefined)
|
|
50
52
|
return;
|
|
51
53
|
let stale = false;
|
|
52
|
-
void
|
|
53
|
-
|
|
54
|
-
|
|
54
|
+
void endpointAuth.read().then((state) => { if (!stale)
|
|
55
|
+
setEndpointState(state ?? 'unavailable'); }, () => { if (!stale)
|
|
56
|
+
setEndpointState('unavailable'); });
|
|
55
57
|
return () => { stale = true; };
|
|
56
|
-
}, [active,
|
|
57
|
-
/**
|
|
58
|
-
* Commit the model field. A blank value UNSETS the key rather than storing
|
|
59
|
-
* an empty string, so the field re-inherits the YAML composition base — and
|
|
60
|
-
* with no base, the harness is back to passing no model at all.
|
|
61
|
-
*/
|
|
62
|
-
const saveModel = () => {
|
|
63
|
-
const next = modelValue.trim();
|
|
64
|
-
setModelSaved(false);
|
|
65
|
-
setModelError(false);
|
|
66
|
-
const write = next === '' ? scope.unset('model') : scope.set('model', next);
|
|
67
|
-
void write.then(async () => {
|
|
68
|
-
// Most-recent-first, deduplicated, capped: the suggestion list is a
|
|
69
|
-
// memory of what this instance has actually run, never a catalog.
|
|
70
|
-
if (next !== '') {
|
|
71
|
-
await scope.set('recentModels', [next, ...recentModels.filter(value => value !== next)].slice(0, RECENT_MODEL_LIMIT));
|
|
72
|
-
}
|
|
73
|
-
setModelDraft(null);
|
|
74
|
-
setModelSaved(true);
|
|
75
|
-
}, () => { setModelError(true); });
|
|
76
|
-
};
|
|
58
|
+
}, [active, endpointAuth, endpointSaved]);
|
|
77
59
|
const write = (field, value) => {
|
|
78
60
|
setSaved(false);
|
|
79
61
|
setError(false);
|
|
80
62
|
void scope.set(field, value).then(() => { setSaved(true); }, () => { setError(true); });
|
|
81
63
|
};
|
|
82
|
-
// The
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
//
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
const [modelMenuUp, setModelMenuUp] = useState(false);
|
|
89
|
-
const modelFieldRef = useRef(null);
|
|
90
|
-
// Click-outside and Esc close the model menu (the member composer's picker
|
|
91
|
-
// pattern); the upward flip is measured when the menu opens.
|
|
92
|
-
useEffect(() => {
|
|
93
|
-
if (!modelMenuOpen)
|
|
64
|
+
// The credential save: one `configureEndpoint` call carrying only the
|
|
65
|
+
// dirty fields (a blank draft clears its field); the answer is the
|
|
66
|
+
// post-write resolved state, so the chips and the auth probe flip without
|
|
67
|
+
// a re-read.
|
|
68
|
+
const saveEndpoint = () => {
|
|
69
|
+
if (endpointAuth === undefined)
|
|
94
70
|
return;
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
if (event.key === 'Escape')
|
|
102
|
-
setModelMenuOpen(false);
|
|
103
|
-
};
|
|
104
|
-
document.addEventListener('mousedown', onPointerDown);
|
|
105
|
-
document.addEventListener('keydown', onKeyDown);
|
|
106
|
-
return () => {
|
|
107
|
-
document.removeEventListener('mousedown', onPointerDown);
|
|
108
|
-
document.removeEventListener('keydown', onKeyDown);
|
|
71
|
+
setEndpointSaved(false);
|
|
72
|
+
setEndpointError(false);
|
|
73
|
+
const fields = {
|
|
74
|
+
...baseUrlDraft !== null ? { baseUrl: baseUrlDraft.trim() === '' ? null : baseUrlDraft.trim() } : {},
|
|
75
|
+
...apiKeyDraft !== null ? { apiKey: apiKeyDraft.trim() === '' ? null : apiKeyDraft.trim() } : {},
|
|
76
|
+
...authTokenDraft !== null ? { authToken: authTokenDraft.trim() === '' ? null : authTokenDraft.trim() } : {},
|
|
109
77
|
};
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
setModelMenuUp(spaceBelow < 260 && rect.top > spaceBelow);
|
|
78
|
+
void endpointAuth.write(fields).then((state) => {
|
|
79
|
+
if (state !== undefined && state !== null) {
|
|
80
|
+
setEndpointState(state);
|
|
81
|
+
// Flip the status dot with the write's own answer — the next
|
|
82
|
+
// scheduled probe confirms it, but the save reads as done NOW.
|
|
83
|
+
publishAuthStatus('claude-code', state.apiKeySet || state.authTokenSet ? 'authenticated' : 'anonymous');
|
|
117
84
|
}
|
|
118
|
-
|
|
119
|
-
|
|
85
|
+
setBaseUrlDraft(null);
|
|
86
|
+
setApiKeyDraft(null);
|
|
87
|
+
setAuthTokenDraft(null);
|
|
88
|
+
setEndpointSaved(true);
|
|
89
|
+
}, () => { setEndpointError(true); });
|
|
120
90
|
};
|
|
121
|
-
// An unset field DISPLAYS the default it follows (dimmed, as the input's
|
|
122
|
-
// placeholder — inherited, never pinned), down the chain the broker
|
|
123
|
-
// reports: the scoped config's model, then a cli-builtin source that still
|
|
124
|
-
// names its default (a harness catalog's isDefault entry), then the last
|
|
125
|
-
// observed model the records know, and last the generic copy.
|
|
126
|
-
const inheritedModel = modelSurface !== null && modelSurface.cliDefault !== undefined && modelSurface.cliDefault !== ''
|
|
127
|
-
? modelSurface.cliDefault
|
|
128
|
-
: modelSurface !== null && modelSurface.source === 'cli-builtin' && modelSurface.effective !== undefined && modelSurface.effective !== ''
|
|
129
|
-
? modelSurface.effective
|
|
130
|
-
: modelSurface !== null && modelSurface.lastObserved !== undefined && modelSurface.lastObserved !== ''
|
|
131
|
-
? modelSurface.lastObserved
|
|
132
|
-
: undefined;
|
|
133
|
-
const modelPlaceholder = inheritedModel ?? t('model.placeholder');
|
|
134
|
-
// The menu's leading item: the same chain, spelled as the follow-default
|
|
135
|
-
// choice. It reads checked while the field is unset, and picking it clears
|
|
136
|
-
// the draft back to follow-default (a save then unsets the key).
|
|
137
|
-
const modelDefaultItem = modelSurface !== null && modelSurface.cliDefault !== undefined && modelSurface.cliDefault !== ''
|
|
138
|
-
? t('model.menuDefaultCliConfig', { model: modelSurface.cliDefault })
|
|
139
|
-
: modelSurface !== null && modelSurface.source === 'cli-builtin' && modelSurface.effective !== undefined && modelSurface.effective !== ''
|
|
140
|
-
? t('model.menuDefaultCliBuiltin', { model: modelSurface.effective })
|
|
141
|
-
: modelSurface !== null && modelSurface.lastObserved !== undefined && modelSurface.lastObserved !== ''
|
|
142
|
-
? t('model.menuDefaultLastObserved', { model: modelSurface.lastObserved })
|
|
143
|
-
: t('model.menuDefault');
|
|
144
91
|
return {
|
|
145
|
-
snapshot, saved, error, ready, live,
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
92
|
+
snapshot, saved, error, ready, live, write,
|
|
93
|
+
endpointState, endpointUnavailable, endpointLoading,
|
|
94
|
+
baseUrlValue: baseUrlDraft ?? (endpointState !== null && endpointState !== 'unavailable' ? endpointState.baseUrl ?? '' : ''),
|
|
95
|
+
apiKeyConfigured, authTokenConfigured, apiKeyDraft, authTokenDraft,
|
|
96
|
+
endpointSaved, endpointError, endpointDirty,
|
|
97
|
+
saveEndpoint, setBaseUrlDraft, setApiKeyDraft, setAuthTokenDraft,
|
|
149
98
|
};
|
|
150
99
|
}
|
|
151
|
-
/** The card body both settings surfaces share: the auth block, the
|
|
152
|
-
function CardBody({ state, auth, authT,
|
|
153
|
-
const { snapshot, saved, error, ready, live,
|
|
154
|
-
// The
|
|
155
|
-
//
|
|
156
|
-
//
|
|
157
|
-
//
|
|
158
|
-
const
|
|
159
|
-
|
|
100
|
+
/** The card body both settings surfaces share: the auth block, the model-list section, and the resident-mode block. */
|
|
101
|
+
function CardBody({ state, active, auth, authT, modelList, modelDirectory, checkEndpoint, useSessions, t }) {
|
|
102
|
+
const { snapshot, saved, error, ready, live, write, endpointUnavailable, endpointLoading, baseUrlValue, apiKeyConfigured, authTokenConfigured, apiKeyDraft, authTokenDraft, endpointSaved, endpointError, endpointDirty, saveEndpoint, setBaseUrlDraft, setApiKeyDraft, setAuthTokenDraft, } = state;
|
|
103
|
+
// The verify button: runs the gateway's `checkEndpoint` Remote against the
|
|
104
|
+
// SAVED endpoint (unsaved drafts deliberately disable it — what it verifies
|
|
105
|
+
// is the saved configuration) and shows the verdict inline. Session-less:
|
|
106
|
+
// a settings card may have no main agent session to run a slash command.
|
|
107
|
+
const [checking, setChecking] = useState(false);
|
|
108
|
+
const [checkText, setCheckText] = useState(null);
|
|
109
|
+
const runCheck = () => {
|
|
110
|
+
if (checkEndpoint === undefined)
|
|
111
|
+
return;
|
|
112
|
+
setChecking(true);
|
|
113
|
+
setCheckText(null);
|
|
114
|
+
void checkEndpoint().then((text) => {
|
|
115
|
+
setCheckText(text ?? t('endpoint.checkUnavailable'));
|
|
116
|
+
setChecking(false);
|
|
117
|
+
}, () => {
|
|
118
|
+
setCheckText(t('endpoint.checkFailed'));
|
|
119
|
+
setChecking(false);
|
|
120
|
+
});
|
|
121
|
+
};
|
|
122
|
+
return (_jsxs("div", { className: css.body, children: [_jsxs("section", { className: css.block, children: [_jsxs("h3", { className: css.blockTitle, children: [t('auth.title'), _jsx(Tooltip, { label: t('endpoint.info'), side: "bottom", maxWidth: 360, children: _jsx("button", { type: "button", className: css.info, "aria-label": t('endpoint.info.aria'), children: "\u24D8" }) })] }), _jsx(ProviderAuthBlock, { harness: { id: 'claude-code', label: 'Claude Code' }, useSessions: useSessions, status: auth.status, runCommand: auth.runCommand, t: authT }), endpointUnavailable && (_jsx("span", { className: css.hint, children: t('endpoint.unavailable') })), !endpointUnavailable && (_jsxs("div", { className: css.endpointFields, children: [_jsxs("label", { className: css.endpointField, children: [_jsx("span", { className: css.endpointLabel, children: t('endpoint.baseUrl') }), _jsx("input", { type: "text", className: css.modelInput, value: baseUrlValue, placeholder: t('endpoint.baseUrl.placeholder'), "aria-label": t('endpoint.baseUrl'), disabled: endpointLoading, onChange: (event) => { setBaseUrlDraft(event.currentTarget.value); } })] }), _jsxs("label", { className: css.endpointField, children: [_jsxs("span", { className: css.endpointLabel, children: [t('endpoint.apiKey'), apiKeyConfigured && _jsx("span", { className: css.configuredChip, children: t('endpoint.configured') })] }), _jsx("input", { type: "password", className: css.modelInput, value: apiKeyDraft ?? '', placeholder: apiKeyConfigured ? t('endpoint.secret.configured') : t('endpoint.secret.placeholder'), "aria-label": t('endpoint.apiKey'), autoComplete: "off", disabled: endpointLoading, onChange: (event) => { setApiKeyDraft(event.currentTarget.value); } })] }), _jsxs("label", { className: css.endpointField, children: [_jsxs("span", { className: css.endpointLabel, children: [t('endpoint.authToken'), authTokenConfigured && _jsx("span", { className: css.configuredChip, children: t('endpoint.configured') })] }), _jsx("input", { type: "password", className: css.modelInput, value: authTokenDraft ?? '', placeholder: authTokenConfigured ? t('endpoint.secret.configured') : t('endpoint.secret.placeholder'), "aria-label": t('endpoint.authToken'), autoComplete: "off", disabled: endpointLoading, onChange: (event) => { setAuthTokenDraft(event.currentTarget.value); } })] }), _jsxs("div", { className: css.row, children: [_jsx("button", { type: "button", className: css.modelSave, disabled: endpointLoading || !endpointDirty, onClick: () => { saveEndpoint(); }, children: t('endpoint.save') }), checkEndpoint !== undefined && (_jsx("button", { type: "button", className: css.modelSave, disabled: endpointLoading || endpointDirty || checking, title: endpointDirty ? t('endpoint.checkDirtyHint') : undefined, onClick: () => { runCheck(); }, children: checking ? t('endpoint.checking') : t('endpoint.check') })), endpointSaved && _jsx("span", { className: css.saved, children: t('endpoint.applied') }), endpointError && _jsx("span", { className: css.errorText, children: t('endpoint.error') })] }), checkText !== null && _jsx("span", { className: css.hint, children: checkText })] }))] }), _jsx(ModelListSection, { active: active, modelList: modelList, modelDirectory: modelDirectory, directoryRefresh: endpointSaved, t: authT }), _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 }) })] }), saved && _jsx("span", { className: css.saved, children: t('live.applied') }), error && _jsx("span", { className: css.errorText, children: t('live.error') }), snapshot.status === 'unavailable' && (_jsx("span", { className: css.hint, children: t('live.unavailable') }))] })] }));
|
|
160
123
|
}
|
|
161
124
|
/**
|
|
162
|
-
* The claude-code harness settings card
|
|
163
|
-
* tab
|
|
125
|
+
* The claude-code harness settings card of the retired 0.1.5 plugin
|
|
126
|
+
* configuration tab (no longer registered; the live surface is the
|
|
127
|
+
* plugins.item entry below): the same collapsible chrome the official plugin
|
|
128
|
+
* cards use,
|
|
164
129
|
* re-implemented locally (the bundle-purity gate forbids value-importing the
|
|
165
130
|
* official chrome — the context-guard pattern), with the at-a-glance
|
|
166
131
|
* credential dot in the header. Explanation copy lives in hover/focus bubbles
|
|
@@ -169,9 +134,9 @@ function CardBody({ state, auth, authT, renderModelPicker, useSessions, t }) {
|
|
|
169
134
|
* @returns the card.
|
|
170
135
|
*/
|
|
171
136
|
export function ClaudeCodeSettingsCard(props) {
|
|
172
|
-
const { useSettings, scope, auth, authT,
|
|
137
|
+
const { useSettings, scope, auth, authT, modelList, modelDirectory, checkEndpoint, endpointAuth, useSessions, t } = props;
|
|
173
138
|
const [open, setOpen] = useState(false);
|
|
174
|
-
const state = useCardState(open, useSettings, scope,
|
|
139
|
+
const state = useCardState(open, useSettings, scope, endpointAuth);
|
|
175
140
|
// The at-a-glance credential dot in the collapsed header: every mount
|
|
176
141
|
// re-probes (a credential expiring mid-session must flip the dot at the
|
|
177
142
|
// next view); the bus dedupes identical results, so no re-render storm.
|
|
@@ -180,19 +145,32 @@ export function ClaudeCodeSettingsCard(props) {
|
|
|
180
145
|
: authStatus === 'anonymous' ? 'settings.notAuthenticated'
|
|
181
146
|
: authStatus === 'checking' ? 'loading' : 'error');
|
|
182
147
|
const title = t('card.title');
|
|
183
|
-
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,
|
|
148
|
+
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, active: open, auth: auth, authT: authT, modelList: modelList, modelDirectory: modelDirectory, checkEndpoint: checkEndpoint, useSessions: useSessions, t: t })] }));
|
|
184
149
|
}
|
|
185
150
|
/**
|
|
186
|
-
* The
|
|
187
|
-
*
|
|
188
|
-
*
|
|
189
|
-
*
|
|
151
|
+
* The provider's card on the Plugins page (the `plugins.item` entry
|
|
152
|
+
* client/index.ts registers while the Host serves the namespace AND the core
|
|
153
|
+
* predates the family card): the page
|
|
154
|
+
* draws the title, the icon, and the crumb itself, so the entry renders the
|
|
155
|
+
* one-liner for the `summary` view and the bare form — with its own save
|
|
156
|
+
* control — for `page`.
|
|
190
157
|
* @param props - the owner view, the injected scope/auth faces, and copy.
|
|
191
158
|
* @returns the entry.
|
|
192
159
|
*/
|
|
193
|
-
export function ClaudeCodeBundleConfig({ view, useSettings, scope, auth, authT,
|
|
194
|
-
const state = useCardState(view === 'page', useSettings, scope,
|
|
160
|
+
export function ClaudeCodeBundleConfig({ view, useSettings, scope, auth, authT, modelList, modelDirectory, checkEndpoint, endpointAuth, useSessions, t }) {
|
|
161
|
+
const state = useCardState(view === 'page', useSettings, scope, endpointAuth);
|
|
195
162
|
if (view === 'summary')
|
|
196
163
|
return _jsx("span", { className: css.description, children: t('card.description') });
|
|
197
|
-
return _jsx(CardBody, { state: state, auth: auth, authT: authT,
|
|
164
|
+
return _jsx(CardBody, { state: state, active: view === 'page', auth: auth, authT: authT, modelList: modelList, modelDirectory: modelDirectory, checkEndpoint: checkEndpoint, useSessions: useSessions, t: t });
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* The harness's section inside the core's「本机 Agent 委派」family card: the
|
|
168
|
+
* same useCardState + CardBody the plugins.item page view renders — the
|
|
169
|
+
* collapsible chrome belongs to the family card now.
|
|
170
|
+
* @param props - the injected scope/auth faces, the forwarded session seat, and copy.
|
|
171
|
+
* @returns the section body.
|
|
172
|
+
*/
|
|
173
|
+
export function ClaudeCodeCardSection({ active, useSettings, scope, auth, authT, modelList, modelDirectory, checkEndpoint, endpointAuth, useSessions, t }) {
|
|
174
|
+
const state = useCardState(active, useSettings, scope, endpointAuth);
|
|
175
|
+
return _jsx(CardBody, { state: state, active: active, auth: auth, authT: authT, modelList: modelList, modelDirectory: modelDirectory, checkEndpoint: checkEndpoint, useSessions: useSessions, t: t });
|
|
198
176
|
}
|
|
@@ -1,35 +1,22 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Local-agent-claude-code plugin, browser half: the claude-code settings
|
|
3
|
-
* surface, one face per install shape and host line — a standalone install
|
|
4
|
-
* renders the 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-claude-code`, the row id the
|
|
9
|
-
* bundle's patch declares), and 0.1.5 renders the `settings.plugin.item`
|
|
10
|
-
* card (keyed to the `local-agent-claude-code` settings namespace the host
|
|
11
|
-
* half registers) in the official Plugins → 可配置插件 tab. The card carries the family core's
|
|
12
|
-
* shared ProviderAuthBlock (login/logout/manual-handoff, backed by the core's
|
|
13
|
-
* localAgentGateway Remote and the commands channel), the default-model block
|
|
14
|
-
* (free-text write plus the gateway's model surface: the effective-model line
|
|
15
|
-
* and the suggestion vocabulary), and the resident-mode switch, whose writes
|
|
16
|
-
* ride the bound settingsScope — the host watcher hot-applies them, no
|
|
17
|
-
* reload. Every read degrades: an absent gateway renders the auth block's
|
|
18
|
-
* 'unavailable' state and drops the model surface, an unregistered namespace
|
|
19
|
-
* disables the live controls.
|
|
20
|
-
* @module @khorsheed/dsh-local-agent-claude-code/client
|
|
21
|
-
*/
|
|
22
1
|
import type { Context } from '@deepseek-ai/cordis';
|
|
23
2
|
import { en, NS, zh, type LocalAgentClaudeCodeKey } from './locales.ts';
|
|
24
|
-
export { ClaudeCodeBundleConfig, ClaudeCodeSettingsCard } from './SettingsCard.tsx';
|
|
25
|
-
export type { ClaudeLiveSettings, ClaudeCodeBundleConfigProps, ClaudeCodeSettingsCardInjected, ClaudeCodeSettingsCardProps, } from './SettingsCard.tsx';
|
|
3
|
+
export { ClaudeCodeBundleConfig, ClaudeCodeCardSection, ClaudeCodeSettingsCard } from './SettingsCard.tsx';
|
|
4
|
+
export type { ClaudeLiveSettings, ClaudeCodeBundleConfigProps, ClaudeCodeCardSectionProps, ClaudeCodeSettingsCardInjected, ClaudeCodeSettingsCardProps, } from './SettingsCard.tsx';
|
|
26
5
|
export { en, NS, zh };
|
|
27
6
|
export type { LocalAgentClaudeCodeKey };
|
|
28
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* Required services: slot registry, command Remote, and locale registry. The
|
|
9
|
+
* settings scope is NOT a static inject — its service name differs per host
|
|
10
|
+
* line (`settingsScope` on 0.1.5, `configForms` on rc.1), so a static entry
|
|
11
|
+
* would pend the whole client plugin on the other line; bindSettingsScope
|
|
12
|
+
* probes the serving one instead (the settings client package is a declared
|
|
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).
|
|
15
|
+
*/
|
|
29
16
|
export declare const inject: string[];
|
|
30
17
|
/**
|
|
31
18
|
* Client plugin body: register the dictionaries and the claude-code settings
|
|
32
|
-
*
|
|
19
|
+
* card bound to the local-agent-claude-code namespace.
|
|
33
20
|
* @param ctx - client root context.
|
|
34
21
|
*/
|
|
35
22
|
export declare function apply(ctx: Context): void;
|
|
@@ -1,23 +1,49 @@
|
|
|
1
1
|
import { bindSettingsScope } from '@khorsheed/dsh-local-agent/src/client/settings-scope.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Local-agent-claude-code plugin, browser half: the claude-code settings card
|
|
4
|
+
* — a section of the family core's「本机 Agent 委派」family card on the
|
|
5
|
+
* Plugins page (registered through the core's
|
|
6
|
+
* `localAgentUi.registerHarnessCard` while the Host serves the
|
|
7
|
+
* `local-agent-claude-code` namespace), or this package's own `plugins.item`
|
|
8
|
+
* entry when the core predates the family card (the declare-and-degrade
|
|
9
|
+
* fallback). Every install shape serves the row's
|
|
10
|
+
* volatile Config under the row id — standalone install, family bundle, or
|
|
11
|
+
* mode pack — so the card follows the served namespace rather than any
|
|
12
|
+
* carrier bundle, and a composition without the row shows no trace of it. The
|
|
13
|
+
* card carries the family core's shared ProviderAuthBlock
|
|
14
|
+
* (login/logout/manual-handoff, backed by the core's localAgentGateway Remote
|
|
15
|
+
* and the commands channel), the default-model block (free-text write plus
|
|
16
|
+
* the gateway's model surface: the effective-model line and the suggestion
|
|
17
|
+
* vocabulary), and the resident-mode switch, whose writes ride the bound
|
|
18
|
+
* settingsScope — the host watcher hot-applies them, no reload. Every read
|
|
19
|
+
* degrades: an absent gateway renders the auth block's 'unavailable' state
|
|
20
|
+
* and drops the model surface, an unregistered namespace disables the live
|
|
21
|
+
* controls.
|
|
22
|
+
* @module @khorsheed/dsh-local-agent-claude-code/client
|
|
23
|
+
*/
|
|
24
|
+
import { createElement, useSyncExternalStore } from 'react';
|
|
2
25
|
import { en, NS, zh } from './locales.js';
|
|
3
|
-
import { ClaudeCodeBundleConfig,
|
|
4
|
-
export { ClaudeCodeBundleConfig, ClaudeCodeSettingsCard } from './SettingsCard.js';
|
|
26
|
+
import { ClaudeCodeBundleConfig, ClaudeCodeCardSection, } from './SettingsCard.js';
|
|
27
|
+
export { ClaudeCodeBundleConfig, ClaudeCodeCardSection, ClaudeCodeSettingsCard } from './SettingsCard.js';
|
|
5
28
|
export { en, NS, zh };
|
|
6
29
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
30
|
+
* Required services: slot registry, command Remote, and locale registry. The
|
|
31
|
+
* settings scope is NOT a static inject — its service name differs per host
|
|
32
|
+
* line (`settingsScope` on 0.1.5, `configForms` on rc.1), so a static entry
|
|
33
|
+
* would pend the whole client plugin on the other line; bindSettingsScope
|
|
34
|
+
* probes the serving one instead (the settings client package is a declared
|
|
35
|
+
* client inject, so one of them is always already up). The card track probes
|
|
36
|
+
* `configForms` through the same kind of deferred inject (see apply).
|
|
10
37
|
*/
|
|
11
|
-
const PACKAGE_NAME = '@khorsheed/dsh-local-agent-claude-code';
|
|
12
|
-
/** Required services: slot registry, settings scope, command Remote, and locale registry. */
|
|
13
38
|
export const inject = ['slots', 'remote', 'remote.commands', 'locale'];
|
|
14
39
|
/**
|
|
15
40
|
* Client plugin body: register the dictionaries and the claude-code settings
|
|
16
|
-
*
|
|
41
|
+
* card bound to the local-agent-claude-code namespace.
|
|
17
42
|
* @param ctx - client root context.
|
|
18
43
|
*/
|
|
19
44
|
export function apply(ctx) {
|
|
20
45
|
ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'local-agent-claude-code: dictionaries');
|
|
46
|
+
const t = ctx.locale.bind(NS);
|
|
21
47
|
const scope = bindSettingsScope(ctx, 'local-agent-claude-code');
|
|
22
48
|
// The auth block's copy lives in the family core's dictionary; binding is
|
|
23
49
|
// stable per namespace and late dictionary registration still resolves.
|
|
@@ -27,21 +53,58 @@ export function apply(ctx) {
|
|
|
27
53
|
// never matters, and degrade to the auth block's 'unavailable' state when
|
|
28
54
|
// the core client is absent.
|
|
29
55
|
const gateway = () => ctx.get('remote.localAgentGateway');
|
|
30
|
-
//
|
|
31
|
-
|
|
56
|
+
// The card's inject face minus the hooks compartment — the family-card
|
|
57
|
+
// section consumes this verbatim; the standalone card's slot registration
|
|
58
|
+
// adds the hooks compartment back below.
|
|
59
|
+
const cardFace = () => ({
|
|
32
60
|
scope,
|
|
33
|
-
hooks: { settings: scope },
|
|
34
61
|
authT,
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
62
|
+
// The curated model list's read/write faces, over the same Remotes.
|
|
63
|
+
modelList: {
|
|
64
|
+
read: () => {
|
|
65
|
+
const gw = gateway();
|
|
66
|
+
if (gw === undefined || typeof gw.modelList !== 'function')
|
|
67
|
+
return Promise.resolve(undefined);
|
|
68
|
+
return gw.modelList('claude-code').then(result => (result.ok ? result.value : undefined));
|
|
69
|
+
},
|
|
70
|
+
write: (models) => {
|
|
71
|
+
const gw = gateway();
|
|
72
|
+
if (gw === undefined || typeof gw.configureModelList !== 'function')
|
|
73
|
+
return Promise.resolve(undefined);
|
|
74
|
+
return gw.configureModelList('claude-code', models).then(result => (result.ok ? result.value : undefined));
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
// The endpoint's served catalog for the add dialog. A core predating
|
|
78
|
+
// modelDirectory leaves the dialog with its reason.
|
|
79
|
+
modelDirectory: (refresh) => {
|
|
80
|
+
const gw = gateway();
|
|
81
|
+
if (gw === undefined || typeof gw.modelDirectory !== 'function')
|
|
82
|
+
return Promise.resolve(undefined);
|
|
83
|
+
return gw.modelDirectory('claude-code', undefined, refresh ?? false).then(result => (result.ok ? result.value : undefined));
|
|
84
|
+
},
|
|
85
|
+
// The verify button's probe, over the session-less gateway Remote. A core
|
|
86
|
+
// predating `checkEndpoint` hides the button (declare-and-degrade).
|
|
87
|
+
checkEndpoint: () => {
|
|
41
88
|
const gw = gateway();
|
|
42
|
-
if (gw === undefined || typeof gw.
|
|
89
|
+
if (gw === undefined || typeof gw.checkEndpoint !== 'function')
|
|
43
90
|
return Promise.resolve(undefined);
|
|
44
|
-
return gw.
|
|
91
|
+
return gw.checkEndpoint('claude-code').then(result => (result.ok ? result.value : undefined));
|
|
92
|
+
},
|
|
93
|
+
// The credential form's read/write face, over the same session-less
|
|
94
|
+
// Remotes. Absent on a core that predates them — the card hides the form.
|
|
95
|
+
endpointAuth: {
|
|
96
|
+
read: () => {
|
|
97
|
+
const gw = gateway();
|
|
98
|
+
if (gw === undefined || typeof gw.endpointAuth !== 'function')
|
|
99
|
+
return Promise.resolve(undefined);
|
|
100
|
+
return gw.endpointAuth('claude-code').then(result => (result.ok ? result.value : undefined));
|
|
101
|
+
},
|
|
102
|
+
write: (fields) => {
|
|
103
|
+
const gw = gateway();
|
|
104
|
+
if (gw === undefined || typeof gw.configureEndpoint !== 'function')
|
|
105
|
+
return Promise.resolve(undefined);
|
|
106
|
+
return gw.configureEndpoint('claude-code', fields).then(result => (result.ok ? result.value : undefined));
|
|
107
|
+
},
|
|
45
108
|
},
|
|
46
109
|
auth: {
|
|
47
110
|
status: name =>
|
|
@@ -53,35 +116,57 @@ export function apply(ctx) {
|
|
|
53
116
|
.then(result => (result.ok ? result.value?.result.text : undefined)),
|
|
54
117
|
},
|
|
55
118
|
});
|
|
56
|
-
// The settings
|
|
57
|
-
|
|
58
|
-
//
|
|
59
|
-
//
|
|
60
|
-
//
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
//
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
// the
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
119
|
+
// The settings card injects this face.
|
|
120
|
+
const cardInject = () => ({ ...cardFace(), hooks: { settings: scope } });
|
|
121
|
+
// The family-card section render bypasses the slot renderer's inject.hooks
|
|
122
|
+
// materialization, so bind the scope's selector hook here with the same
|
|
123
|
+
// semantics (a uSES subscription over the scope's own subscribe/getSnapshot).
|
|
124
|
+
const subscribeScope = (listener) => scope.subscribe(listener);
|
|
125
|
+
const readScope = () => scope.getSnapshot();
|
|
126
|
+
const useSettings = (select) => select(useSyncExternalStore(subscribeScope, readScope));
|
|
127
|
+
// One Plugins-page seat gated on the SERVED NAMESPACE, never on the install
|
|
128
|
+
// shape: whichever composition carries the `local-agent-claude-code` row —
|
|
129
|
+
// standalone install, family bundle, or mode pack — serves the row's
|
|
130
|
+
// volatile Config under the row id, and `whileServed` keeps the
|
|
131
|
+
// registration alive for exactly that span (the official companion
|
|
132
|
+
// convention), so a composition without the row shows no trace of the card.
|
|
133
|
+
// This replaces the retired per-shape seats: `plugins.row.config` hardcoded
|
|
134
|
+
// one carrier bundle into its key, and the host deleted
|
|
135
|
+
// `settings.plugin.item`. The probe rides a deferred inject because
|
|
136
|
+
// configForms exists only on the alpha.2+ host line; a 0.1.5 host never
|
|
137
|
+
// fires it, and a configForms predating whileServed (0.1.6-alpha.2) skips
|
|
138
|
+
// the card.
|
|
139
|
+
ctx.inject(['configForms'], (formsCtx) => {
|
|
140
|
+
const forms = formsCtx.configForms;
|
|
141
|
+
if (typeof forms.whileServed !== 'function')
|
|
142
|
+
return;
|
|
143
|
+
return forms.whileServed(['local-agent-claude-code'], () => {
|
|
144
|
+
// The family card owns the Plugins-page seat when the core's client
|
|
145
|
+
// provides it: a core new enough answers registerHarnessCard, and this
|
|
146
|
+
// package renders as a section of the「本机 Agent 委派」card instead of
|
|
147
|
+
// its own entry. The probe runs at serve time — a host round-trip past
|
|
148
|
+
// client plugin boot — so a composed core has already provided the
|
|
149
|
+
// service; an old or absent core keeps the standalone card below.
|
|
150
|
+
const ui = ctx.get('localAgentUi');
|
|
151
|
+
if (typeof ui?.registerHarnessCard === 'function') {
|
|
152
|
+
return ui.registerHarnessCard({
|
|
153
|
+
name: 'claude-code',
|
|
154
|
+
// The retired standalone card's order, kept as the section order.
|
|
155
|
+
order: 130,
|
|
156
|
+
locale: NS,
|
|
157
|
+
label: () => t('card.title'),
|
|
158
|
+
render: ({ active, useSessions }) => createElement(ClaudeCodeCardSection, { active, useSessions, ...cardFace(), useSettings, t }),
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
return ctx.slots.inject('plugins.item', () => ctx.slots.register({
|
|
162
|
+
name: 'plugins.item',
|
|
163
|
+
id: 'local-agent-claude-code',
|
|
164
|
+
// Community cards sort after the official settings pages (orders 10-40).
|
|
165
|
+
order: 130,
|
|
166
|
+
label: () => t('card.title'),
|
|
167
|
+
locale: NS,
|
|
168
|
+
inject: cardInject,
|
|
169
|
+
}, ClaudeCodeBundleConfig));
|
|
170
|
+
});
|
|
171
|
+
});
|
|
87
172
|
}
|