@khorsheed/dsh-local-agent 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.
Files changed (57) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.en.md +23 -164
  3. package/README.i18n.yaml +2 -2
  4. package/README.md +23 -164
  5. package/lib/client.js +23707 -4768
  6. package/lib/index.js +827 -62
  7. package/lib/member-bridge.js +195 -25
  8. package/lib/tool-factory.js +52 -19
  9. package/lib/typert.host.js +1440 -353
  10. package/lib/typert.remote-client.d.ts +43 -3
  11. package/lib/typert.remote-client.js +973 -335
  12. package/lib/types/client/FamilyCard.d.ts +37 -0
  13. package/lib/types/client/FamilyCard.js +55 -0
  14. package/lib/types/client/HarnessModelPicker.d.ts +21 -1
  15. package/lib/types/client/HarnessModelPicker.js +31 -3
  16. package/lib/types/client/MemberLiveNode.js +38 -4
  17. package/lib/types/client/ModelListSection.d.ts +49 -0
  18. package/lib/types/client/ModelListSection.js +132 -0
  19. package/lib/types/client/ProviderAuthBlock.d.ts +10 -2
  20. package/lib/types/client/ProviderAuthBlock.js +13 -6
  21. package/lib/types/client/auth-status.d.ts +6 -0
  22. package/lib/types/client/auth-status.js +8 -0
  23. package/lib/types/client/harness-cards.d.ts +72 -0
  24. package/lib/types/client/harness-cards.js +57 -0
  25. package/lib/types/client/icons.d.ts +2 -1
  26. package/lib/types/client/icons.js +1 -0
  27. package/lib/types/client/index.d.ts +32 -7
  28. package/lib/types/client/index.js +66 -9
  29. package/lib/types/client/locales.d.ts +25 -1
  30. package/lib/types/client/locales.js +50 -2
  31. package/lib/types/client/model-directory-scope.d.ts +14 -0
  32. package/lib/types/client/model-directory-scope.js +20 -0
  33. package/lib/types/gateway.d.ts +49 -1
  34. package/lib/types/gateway.js +89 -0
  35. package/lib/types/index.d.ts +91 -38
  36. package/lib/types/index.js +190 -58
  37. package/lib/types/live-stream.d.ts +11 -0
  38. package/lib/types/live-stream.js +26 -0
  39. package/lib/types/member-bridge.d.ts +11 -1
  40. package/lib/types/member-bridge.js +81 -13
  41. package/lib/types/member-channel.d.ts +44 -1
  42. package/lib/types/member-channel.js +65 -2
  43. package/lib/types/member-controls.d.ts +8 -0
  44. package/lib/types/member-controls.js +15 -2
  45. package/lib/types/mode-skills.d.ts +18 -0
  46. package/lib/types/mode-skills.js +56 -0
  47. package/lib/types/model-list.d.ts +55 -0
  48. package/lib/types/model-list.js +101 -0
  49. package/lib/types/preset-tool-host.d.ts +70 -0
  50. package/lib/types/preset-tool-host.js +210 -0
  51. package/lib/types/tool-factory.d.ts +9 -3
  52. package/lib/types/tool-factory.js +55 -18
  53. package/lib/types/types.d.ts +62 -5
  54. package/package.json +27 -23
  55. package/lib/invariant.js +0 -85
  56. package/lib/types/invariant.d.ts +0 -16
  57. package/lib/types/invariant.js +0 -105
@@ -0,0 +1,57 @@
1
+ /**
2
+ * The harness-card registry behind the「本机 Agent 委派」family card: each
3
+ * delegation companion's client registers one section (its settings card
4
+ * body) through the localAgentUi service's `registerHarnessCard`, and the
5
+ * core's single `plugins.item` entry renders every registered section sorted
6
+ * by order. Sections come from THIS registry, never the gateway roster — a
7
+ * registered harness absent host-side still renders, its body degrading
8
+ * exactly like the retired standalone card did.
9
+ *
10
+ * The registry lives on the cordis service (not a module export) because every
11
+ * plugin's client bundle inlines its own copy of the core's sources: module
12
+ * identity is per bundle, the service is the one shared instance.
13
+ * @module @khorsheed/dsh-local-agent/client — harness card registry
14
+ */
15
+ /**
16
+ * In-memory registry with the useSyncExternalStore face the family card
17
+ * subscribes through: a version counter as the snapshot (mutations bump it)
18
+ * and a cached sorted list readers re-read after a bump.
19
+ */
20
+ export class HarnessCardRegistry {
21
+ entries = new Map();
22
+ listeners = new Set();
23
+ version = 0;
24
+ sorted = [];
25
+ /**
26
+ * Register one harness's section; re-registering the same name replaces the
27
+ * entry. The returned disposer removes only the entry it registered (a stale
28
+ * disposer never evicts a newer registration of the same name).
29
+ * @param entry - the section registration.
30
+ * @returns the unregister.
31
+ */
32
+ register = (entry) => {
33
+ this.entries.set(entry.name, entry);
34
+ this.bump();
35
+ return () => {
36
+ if (this.entries.get(entry.name) !== entry)
37
+ return;
38
+ this.entries.delete(entry.name);
39
+ this.bump();
40
+ };
41
+ };
42
+ /** The registered sections in display order (order, then name). */
43
+ cards = () => this.sorted;
44
+ /** The mutation counter — the useSyncExternalStore snapshot. */
45
+ snapshotVersion = () => this.version;
46
+ /** Subscribe a listener fired after every mutation; returns the unsubscribe. */
47
+ subscribe = (listener) => {
48
+ this.listeners.add(listener);
49
+ return () => { this.listeners.delete(listener); };
50
+ };
51
+ bump() {
52
+ this.version += 1;
53
+ this.sorted = [...this.entries.values()].sort((a, b) => a.order - b.order || (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
54
+ for (const listener of [...this.listeners])
55
+ listener();
56
+ }
57
+ }
@@ -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.7-rc.1, harness 46a7f68b09) — do not edit by hand. The 0.1.5 and rc.1 host lines
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.
@@ -18,5 +18,6 @@ export declare const IconCheckOutlineMedium: ({ size, className }: IconProps) =>
18
18
  export declare const IconChevronDownOutlineMedium: ({ size, className }: IconProps) => import("react").JSX.Element;
19
19
  export declare const IconChevronUpOutlineMedium: ({ size, className }: IconProps) => import("react").JSX.Element;
20
20
  export declare const IconQueueOutlineMedium: ({ size, className }: IconProps) => import("react").JSX.Element;
21
+ export declare const IconThinkOutlineRegular: ({ size, className }: IconProps) => import("react").JSX.Element;
21
22
  export declare const IconTrashOutlineMedium: ({ size, className }: IconProps) => import("react").JSX.Element;
22
23
  //# sourceMappingURL=icons.d.ts.map
@@ -3,4 +3,5 @@ export const IconCheckOutlineMedium = ({ size = 16, className }) => (_jsx("svg",
3
3
  export const IconChevronDownOutlineMedium = ({ size = 14, className }) => (_jsx("svg", { width: size, height: size, className: className, viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", strokeWidth: 1.3, children: _jsx("path", { d: "M4 6L7.29289 9.29289C7.68342 9.68342 8.31658 9.68342 8.70711 9.29289L12 6", stroke: "currentColor" }) }));
4
4
  export const IconChevronUpOutlineMedium = ({ size = 14, className }) => (_jsx("svg", { width: size, height: size, className: className, viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", strokeWidth: 1.3, children: _jsx("path", { d: "M12 10L8.70711 6.70711C8.31658 6.31658 7.68342 6.31658 7.29289 6.70711L4 10", stroke: "currentColor" }) }));
5
5
  export const IconQueueOutlineMedium = ({ size = 14, className }) => (_jsxs("svg", { width: size, height: size, className: className, viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", strokeWidth: 1.3, children: [_jsx("path", { d: "M5 6.75H11", stroke: "currentColor" }), _jsx("path", { d: "M5 9H8", stroke: "currentColor" }), _jsx("path", { d: "M2.37067 11.2497C1.5872 9.89252 1.32042 8.29798 1.61945 6.7597C1.91847 5.22141 2.76317 3.84293 3.99801 2.87809C5.23285 1.91325 6.7747 1.427 8.33964 1.50888C9.90458 1.59076 11.3873 2.23526 12.5147 3.32369C13.6422 4.41232 14.3384 5.8717 14.4751 7.43304C14.6118 8.99438 14.1797 10.5525 13.2585 11.8205C12.3372 13.0885 10.9889 13.9809 9.4617 14.3334C8.18666 14.6277 6.8587 14.529 5.64964 14.0601C5.17095 13.8745 4.76937 13.4929 4.26509 13.3963C3.67389 13.2832 2.95232 13.5595 2.0377 14.3334", stroke: "currentColor" })] }));
6
+ export const IconThinkOutlineRegular = ({ size = 16, className }) => (_jsxs("svg", { width: size, height: size, className: className, viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", strokeWidth: 1, children: [_jsx("path", { d: "M10.2854 5.71481C12.9673 8.39663 14.1182 11.5938 12.8562 12.8559C11.5942 14.1179 8.39706 12.9669 5.71518 10.2851C3.03333 7.60323 1.88236 4.40608 3.14441 3.14403C4.40644 1.882 7.6036 3.03297 10.2854 5.71481Z", stroke: "currentColor" }), _jsx("path", { d: "M10.2854 10.2851C7.6036 12.9669 4.40644 14.1179 3.14441 12.8559C1.88236 11.5938 3.03333 8.39663 5.71518 5.71481C8.39706 3.03297 11.5942 1.882 12.8562 3.14403C14.1182 4.40608 12.9673 7.60323 10.2854 10.2851Z", stroke: "currentColor" }), _jsx("path", { d: "M8.86291 8.0002C8.86291 8.47549 8.47762 8.86087 8.00224 8.86087C7.52694 8.86087 7.1416 8.47549 7.1416 8.0002C7.1416 7.52485 7.52694 7.13953 8.00224 7.13953C8.47762 7.13953 8.86291 7.52485 8.86291 8.0002Z", fill: "currentColor" })] }));
6
7
  export const IconTrashOutlineMedium = ({ size = 16, className }) => (_jsxs("svg", { width: size, height: size, className: className, viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", strokeWidth: 1.3, children: [_jsx("path", { d: "M1.28149 3.88831H14.7187", stroke: "currentColor" }), _jsx("path", { d: "M5.41602 3.88833V2.47962C5.41602 2.29282 5.52492 2.11366 5.71876 1.98157C5.9126 1.84948 6.17551 1.77527 6.44964 1.77527H9.55053C9.82466 1.77527 10.0876 1.84948 10.2814 1.98157C10.4753 2.11366 10.5842 2.29282 10.5842 2.47962V3.88833", stroke: "currentColor" }), _jsx("path", { d: "M2.57349 3.88831L3.19366 13.2943C3.21937 13.5502 3.33952 13.7872 3.53065 13.9593C3.72178 14.1313 3.97016 14.2259 4.22729 14.2246H11.7728C12.0299 14.2259 12.2783 14.1313 12.4694 13.9593C12.6605 13.7872 12.7807 13.5502 12.8064 13.2943L13.4266 3.88831", stroke: "currentColor" }), _jsx("path", { d: "M6.44946 6.98926V11.1238", stroke: "currentColor" }), _jsx("path", { d: "M9.55054 6.98926V11.1238", stroke: "currentColor" })] }));
@@ -1,12 +1,18 @@
1
1
  /**
2
2
  * Local-agent records plugin, browser half: the member composer for delegated
3
- * CLI sessions, plus the shared settings-card building blocks the provider
4
- * packages compose (ProviderAuthBlock, the auth-status bus, AuthStatusDot).
5
- * The per-harness auth/live settings live in each provider's own settings
6
- * card — on alpha.2 the bundle's configuration on its Plugins-page detail
7
- * view (`plugins.bundle.config`), on 0.1.5 the `settings.plugin.item` card
8
- * (Plugins → 可配置插件); the standalone「本地 Agent」section was retired
9
- * once the cards carried auth (the live-settings proposal's M3). Roster and status ride the read-only local-agent Remote
3
+ * CLI sessions, the shared settings-card building blocks the provider
4
+ * packages compose (ProviderAuthBlock, the auth-status bus, AuthStatusDot),
5
+ * and the「本机 Agent 委派」family card — ONE `plugins.item` entry on the
6
+ * Plugins page that renders each harness's settings section, registered by
7
+ * the provider packages through the localAgentUi service's
8
+ * `registerHarnessCard` (a provider on a core predating the family card
9
+ * keeps its own standalone card; this core alone shows the card with an
10
+ * empty state). The card registers with the core client bundle itself —
11
+ * no served-namespace gate: the whileServed convention only lists
12
+ * namespaces whose Config has a volatile field, and this core's Config
13
+ * has none, so its namespace never serves (the companions' sections keep
14
+ * their own namespace gates).
15
+ * Roster and status ride the read-only local-agent Remote
10
16
  * channel (no session events); only the user-initiated login/logout/preset
11
17
  * commands go through the commands Remote, whose visible command node is the
12
18
  * expected feedback. Delegation records live with the shared subagent
@@ -16,16 +22,23 @@
16
22
  */
17
23
  import { type ReactNode } from 'react';
18
24
  import { type HarnessModelPickerProps } from './HarnessModelPicker.tsx';
25
+ import { type HarnessCardRegistration } from './harness-cards.ts';
19
26
  import type { Context } from '@deepseek-ai/cordis';
20
27
  import type { TypertRemoteNamespaceMap } from '@deepseek-ai/dsh-typert-protocol';
21
28
  import { type LocalAgentKey } from './locales.ts';
22
29
  export type { LocalAgentHarnessView } from './LocalAgentRecordsAction.tsx';
23
30
  export { ProviderAuthBlock } from './ProviderAuthBlock.tsx';
24
31
  export type { ProviderAuthBlockProps, ProviderAuthInjected } from './ProviderAuthBlock.tsx';
32
+ export { ModelListSection } from './ModelListSection.tsx';
33
+ export type { ModelListSectionListFace, ModelListSectionProps } from './ModelListSection.tsx';
25
34
  export { AuthStatusDot } from './AuthStatusDot.tsx';
26
35
  export type { AuthStatusDotProps } from './AuthStatusDot.tsx';
27
36
  export { publishAuthStatus, useHarnessAuthStatus, resetAuthStatuses, readAuthStatus } from './auth-status.ts';
28
37
  export type { HarnessAuthStatusKind } from './auth-status.ts';
38
+ export { HarnessCardRegistry } from './harness-cards.ts';
39
+ export type { HarnessCardRegistration, HarnessCardRenderProps } from './harness-cards.ts';
40
+ export { LocalAgentFamilyCard } from './FamilyCard.tsx';
41
+ export type { FamilyCardInjected, LocalAgentFamilyCardProps } from './FamilyCard.tsx';
29
42
  export type { MemberComposerInjected, MemberComposerMatch, MemberComposerProps } from './MemberComposer.tsx';
30
43
  export { selectCliMember } from './MemberComposer.tsx';
31
44
  /** The mounted local-agent gateway namespace, read back from the global store. */
@@ -37,6 +50,18 @@ export interface LocalAgentUi {
37
50
  renderMemberInbox(childSessionId: string): ReactNode;
38
51
  renderHarnessModelPicker(name: string, props: HarnessModelPickerInput): ReactNode;
39
52
  renderMemberConfiguration(childSessionId: string): ReactNode;
53
+ /**
54
+ * Register one harness's section in the family card; returns the
55
+ * unregister. Optional on the TYPE because a companion may run against a
56
+ * core that predates the family card: companions probe
57
+ * `typeof ui.registerHarnessCard === 'function'` and keep their standalone
58
+ * plugins.item card when it is absent. This core always provides it.
59
+ */
60
+ registerHarnessCard?(entry: HarnessCardRegistration): () => void;
61
+ /** The registered harness sections in display order (the family card's reading). */
62
+ harnessCards?(): readonly HarnessCardRegistration[];
63
+ /** The registry's useSyncExternalStore subscribe. */
64
+ subscribeHarnessCards?(listener: () => void): () => void;
40
65
  }
41
66
  declare module '@deepseek-ai/cordis' {
42
67
  interface Context {
@@ -1,13 +1,19 @@
1
1
  import { MemberFeeds } from './member-feed.js';
2
2
  /**
3
3
  * Local-agent records plugin, browser half: the member composer for delegated
4
- * CLI sessions, plus the shared settings-card building blocks the provider
5
- * packages compose (ProviderAuthBlock, the auth-status bus, AuthStatusDot).
6
- * The per-harness auth/live settings live in each provider's own settings
7
- * card — on alpha.2 the bundle's configuration on its Plugins-page detail
8
- * view (`plugins.bundle.config`), on 0.1.5 the `settings.plugin.item` card
9
- * (Plugins → 可配置插件); the standalone「本地 Agent」section was retired
10
- * once the cards carried auth (the live-settings proposal's M3). Roster and status ride the read-only local-agent Remote
4
+ * CLI sessions, the shared settings-card building blocks the provider
5
+ * packages compose (ProviderAuthBlock, the auth-status bus, AuthStatusDot),
6
+ * and the「本机 Agent 委派」family card — ONE `plugins.item` entry on the
7
+ * Plugins page that renders each harness's settings section, registered by
8
+ * the provider packages through the localAgentUi service's
9
+ * `registerHarnessCard` (a provider on a core predating the family card
10
+ * keeps its own standalone card; this core alone shows the card with an
11
+ * empty state). The card registers with the core client bundle itself —
12
+ * no served-namespace gate: the whileServed convention only lists
13
+ * namespaces whose Config has a volatile field, and this core's Config
14
+ * has none, so its namespace never serves (the companions' sections keep
15
+ * their own namespace gates).
16
+ * Roster and status ride the read-only local-agent Remote
11
17
  * channel (no session events); only the user-initiated login/logout/preset
12
18
  * commands go through the commands Remote, whose visible command node is the
13
19
  * expected feedback. Delegation records live with the shared subagent
@@ -17,6 +23,9 @@ import { MemberFeeds } from './member-feed.js';
17
23
  */
18
24
  import { createElement } from 'react';
19
25
  import { HarnessModelPicker } from './HarnessModelPicker.js';
26
+ import { scopeDirectoryToList } from './model-directory-scope.js';
27
+ import { LocalAgentFamilyCard } from './FamilyCard.js';
28
+ import { HarnessCardRegistry } from './harness-cards.js';
20
29
  import { MemberConfiguration } from './MemberConfiguration.js';
21
30
  import { MemberConfigurationStores } from './member-configuration.js';
22
31
  import localAgentRemote from '@khorsheed/dsh-local-agent/remote';
@@ -26,8 +35,11 @@ import { memberLiveDefinition } from './live-node.js';
26
35
  import { MemberLiveNode, MemberLiveOutputView } from './MemberLiveNode.js';
27
36
  import { MemberLiveOutputs } from './live-output.js';
28
37
  export { ProviderAuthBlock } from './ProviderAuthBlock.js';
38
+ export { ModelListSection } from './ModelListSection.js';
29
39
  export { AuthStatusDot } from './AuthStatusDot.js';
30
40
  export { publishAuthStatus, useHarnessAuthStatus, resetAuthStatuses, readAuthStatus } from './auth-status.js';
41
+ export { HarnessCardRegistry } from './harness-cards.js';
42
+ export { LocalAgentFamilyCard } from './FamilyCard.js';
31
43
  export { selectCliMember } from './MemberComposer.js';
32
44
  import { MemberInboxView } from './MemberInboxView.js';
33
45
  export { ModelConfigurationFields } from './ModelConfigurationFields.js';
@@ -76,8 +88,21 @@ export async function apply(ctx) {
76
88
  let face = directoryFaces.get(name);
77
89
  if (face === undefined)
78
90
  directoryFaces.set(name, face = {
79
- read: async (refresh) => unwrap(await gateway.modelDirectory(name, undefined, refresh)),
80
- follow: signal => gateway.followModelDirectory(name, undefined, signal),
91
+ read: async (refresh) => {
92
+ const directory = unwrap(await gateway.modelDirectory(name, undefined, refresh));
93
+ return directory === null ? null : scopeDirectoryToList(directory, unwrap(await gateway.modelList(name)));
94
+ },
95
+ follow: async function* (signal) {
96
+ // The invite picker faces the curated list, not the raw served
97
+ // catalog; re-read the list on each directory revision.
98
+ let list = unwrap(await gateway.modelList(name));
99
+ for await (const directory of gateway.followModelDirectory(name, undefined, signal)) {
100
+ if (signal.aborted)
101
+ return;
102
+ list = unwrap(await gateway.modelList(name));
103
+ yield scopeDirectoryToList(directory, list);
104
+ }
105
+ },
81
106
  });
82
107
  return face;
83
108
  };
@@ -92,11 +117,20 @@ export async function apply(ctx) {
92
117
  return face;
93
118
  };
94
119
  const outputs = new MemberLiveOutputs((id, signal) => feeds.follow(id, 'output', signal));
120
+ // The family card's section registry: companions register through the
121
+ // service below, the plugins.item entry reads it reactively. Service-held,
122
+ // never module-held — each client bundle inlines its own copy of these
123
+ // sources, so only the cordis service is the shared instance.
124
+ const harnessCards = new HarnessCardRegistry();
125
+ const t = ctx.locale.bind(NS);
95
126
  const configurationUi = {
96
127
  renderMemberOutput: (id, startedAt) => createElement(MemberLiveOutputView, { key: `${id}:${startedAt}`, sessionId: id, startedAt, outputs, t: ctx.locale.bind(NS) }),
97
128
  renderMemberInbox: id => createElement(MemberInboxView, { key: id, face: inboxFace(id), t: ctx.locale.bind(NS) }),
98
129
  renderHarnessModelPicker: (name, props) => createElement(HarnessModelPicker, { ...props, key: name, face: directoryFace(name), t: ctx.locale.bind(NS) }),
99
130
  renderMemberConfiguration: id => createElement(MemberConfiguration, { key: id, store: configurations.get(id), diagnostics: outputs.diagnostics, t: ctx.locale.bind(NS) }),
131
+ registerHarnessCard: entry => harnessCards.register(entry),
132
+ harnessCards: () => harnessCards.cards(),
133
+ subscribeHarnessCards: listener => harnessCards.subscribe(listener),
100
134
  };
101
135
  ctx.provide('localAgentUi', configurationUi);
102
136
  ctx.inject(['uiConversation'], lctx => { lctx.uiConversation.events.register(memberLiveDefinition); });
@@ -128,6 +162,29 @@ export async function apply(ctx) {
128
162
  setMemberModel: (childSessionId, model) => gateway.setMemberModel(childSessionId, model).then(result => (result.ok ? result.value : undefined)),
129
163
  }),
130
164
  }, MemberComposer));
165
+ // The family card: ONE Plugins-page entry rendering every registered
166
+ // harness section. Registered UNCONDITIONALLY, not gated on the core row's
167
+ // served namespace like the companions' retired standalone cards: the
168
+ // whileServed list only carries namespaces whose Config schema has a
169
+ // volatile field, and this core's Config has none — gating on it meant the
170
+ // card never mounted (and the new-core companions had already given up
171
+ // their standalone seats, so no local-agent card rendered at all). The
172
+ // client bundle loads only with the core row in the composition, so the
173
+ // bundle's presence is the gate; a core whose server half failed simply
174
+ // renders the sections' degraded states.
175
+ ctx.slots.inject('plugins.item', () => ctx.slots.register({
176
+ name: 'plugins.item',
177
+ id: 'local-agent',
178
+ // Ahead of the harnesses' retired standalone cards (orders 110-140).
179
+ order: 100,
180
+ label: () => t('family.title'),
181
+ locale: NS,
182
+ inject: () => ({
183
+ harnessCards,
184
+ harnessStatus: name => gateway?.status(name, undefined).then(result => (result.ok ? result.value : undefined))
185
+ ?? Promise.resolve(undefined),
186
+ }),
187
+ }, LocalAgentFamilyCard));
131
188
  return async () => {
132
189
  for (const dispose of disposers.reverse())
133
190
  await dispose();
@@ -24,6 +24,7 @@ export declare const zh: {
24
24
  readonly "configuration.details": "模型目录详情";
25
25
  readonly "configuration.model": "模型";
26
26
  readonly "configuration.effort": "推理强度";
27
+ readonly "configuration.effortDefault": "默认";
27
28
  readonly "configuration.inherit": "继承成员创建配置";
28
29
  readonly "configuration.default": "跟随 harness 默认值";
29
30
  readonly "configuration.search": "搜索模型名称或 ID";
@@ -55,7 +56,6 @@ export declare const zh: {
55
56
  readonly "configuration.cancelled": "已接收撤销请求,正在恢复配置。";
56
57
  readonly "configuration.failed": "配置操作失败,请检查原因后重试。";
57
58
  readonly 'stream.thinking': '思考中';
58
- readonly 'stream.writing': '正在生成';
59
59
  readonly 'stream.recovering': '部分记录,等待实时连接';
60
60
  readonly 'stream.copy': '复制';
61
61
  readonly 'stream.copied': '已复制';
@@ -87,6 +87,30 @@ export declare const zh: {
87
87
  readonly 'settings.unsupported': '待支持';
88
88
  readonly 'settings.rosterFailed': '无法读取本地 Agent 列表,请重试';
89
89
  readonly 'settings.retry': '重试';
90
+ readonly 'family.title': '本机 Agent 委派';
91
+ readonly 'family.description': '本机安装的编码 agent CLI 的委派通道:端点凭据、默认模型与常驻模式';
92
+ readonly 'family.cliMissing': '未检测到 CLI';
93
+ readonly 'family.empty': '还没有已注册的委派通道';
94
+ readonly 'family.expand': '展开';
95
+ readonly 'family.collapse': '收起';
96
+ readonly 'models.title': '可用模型';
97
+ readonly 'models.info.aria': '可用模型说明';
98
+ readonly 'models.info': '列表从上到下是优先级:每轮委派默认使用第一个模型。点「添加模型」从端点当前供应的目录里勾选加入,所以列表里只会有端点真实供应的模型;拖动行调整顺序;留空则跟随原有默认(设置值或 CLI 内置默认)。列表保存在作用域目录(models.json),保存对下一轮委派生效。';
99
+ readonly 'models.empty': '列表为空 —— 跟随原有默认(设置值或 CLI 内置默认)';
100
+ readonly 'models.add': '+ 添加模型';
101
+ readonly 'models.delete': '移除';
102
+ readonly 'models.defaultChip': '默认';
103
+ readonly 'models.orderHint': '拖动行调整顺序,第一个为默认';
104
+ readonly 'models.applied': '已保存,下一轮委派生效;进行中的轮次不受影响';
105
+ readonly 'models.error': '保存失败,请重试';
106
+ readonly 'models.dialog.title': '选择要添加的模型';
107
+ readonly 'models.dialog.hint': '以下是端点当前供应的模型,勾选要添加的模型。';
108
+ readonly 'models.dialog.search': '搜索模型';
109
+ readonly 'models.dialog.selectAll': '全选';
110
+ readonly 'models.dialog.cancel': '取消';
111
+ readonly 'models.dialog.confirm': '添加所选';
112
+ readonly 'models.dialog.loading': '正在加载端点目录…';
113
+ readonly 'models.dialog.empty': '端点目录为空 —— 先保存凭据';
90
114
  readonly 'member.checking': '正在确认成员身份…';
91
115
  readonly 'member.readonly.title': '一次性子代理记录';
92
116
  readonly 'member.readonly.body': '一次性任务不支持后续消息,可在这里查看完整执行记录。';
@@ -24,6 +24,7 @@ export const zh = {
24
24
  "configuration.details": "模型目录详情",
25
25
  "configuration.model": "模型",
26
26
  "configuration.effort": "推理强度",
27
+ "configuration.effortDefault": "默认",
27
28
  "configuration.inherit": "继承成员创建配置",
28
29
  "configuration.default": "跟随 harness 默认值",
29
30
  "configuration.search": "搜索模型名称或 ID",
@@ -55,7 +56,6 @@ export const zh = {
55
56
  "configuration.cancelled": "已接收撤销请求,正在恢复配置。",
56
57
  "configuration.failed": "配置操作失败,请检查原因后重试。",
57
58
  'stream.thinking': '思考中',
58
- 'stream.writing': '正在生成',
59
59
  'stream.recovering': '部分记录,等待实时连接',
60
60
  'stream.copy': '复制',
61
61
  'stream.copied': '已复制',
@@ -87,6 +87,30 @@ export const zh = {
87
87
  'settings.unsupported': '待支持',
88
88
  'settings.rosterFailed': '无法读取本地 Agent 列表,请重试',
89
89
  'settings.retry': '重试',
90
+ 'family.title': '本机 Agent 委派',
91
+ 'family.description': '本机安装的编码 agent CLI 的委派通道:端点凭据、默认模型与常驻模式',
92
+ 'family.cliMissing': '未检测到 CLI',
93
+ 'family.empty': '还没有已注册的委派通道',
94
+ 'family.expand': '展开',
95
+ 'family.collapse': '收起',
96
+ 'models.title': '可用模型',
97
+ 'models.info.aria': '可用模型说明',
98
+ 'models.info': '列表从上到下是优先级:每轮委派默认使用第一个模型。点「添加模型」从端点当前供应的目录里勾选加入,所以列表里只会有端点真实供应的模型;拖动行调整顺序;留空则跟随原有默认(设置值或 CLI 内置默认)。列表保存在作用域目录(models.json),保存对下一轮委派生效。',
99
+ 'models.empty': '列表为空 —— 跟随原有默认(设置值或 CLI 内置默认)',
100
+ 'models.add': '+ 添加模型',
101
+ 'models.delete': '移除',
102
+ 'models.defaultChip': '默认',
103
+ 'models.orderHint': '拖动行调整顺序,第一个为默认',
104
+ 'models.applied': '已保存,下一轮委派生效;进行中的轮次不受影响',
105
+ 'models.error': '保存失败,请重试',
106
+ 'models.dialog.title': '选择要添加的模型',
107
+ 'models.dialog.hint': '以下是端点当前供应的模型,勾选要添加的模型。',
108
+ 'models.dialog.search': '搜索模型',
109
+ 'models.dialog.selectAll': '全选',
110
+ 'models.dialog.cancel': '取消',
111
+ 'models.dialog.confirm': '添加所选',
112
+ 'models.dialog.loading': '正在加载端点目录…',
113
+ 'models.dialog.empty': '端点目录为空 —— 先保存凭据',
90
114
  'member.checking': '正在确认成员身份…',
91
115
  'member.readonly.title': '一次性子代理记录',
92
116
  'member.readonly.body': '一次性任务不支持后续消息,可在这里查看完整执行记录。',
@@ -136,6 +160,7 @@ export const en = {
136
160
  "configuration.details": "Model directory details",
137
161
  "configuration.model": "Model",
138
162
  "configuration.effort": "Reasoning effort",
163
+ "configuration.effortDefault": "Default",
139
164
  "configuration.inherit": "Inherit member configuration",
140
165
  "configuration.default": "Follow harness defaults",
141
166
  "configuration.search": "Search model names or IDs",
@@ -167,7 +192,6 @@ export const en = {
167
192
  "configuration.cancelled": "Cancellation accepted; restoring configuration.",
168
193
  "configuration.failed": "Configuration failed; check the reason before retrying.",
169
194
  'stream.thinking': 'Thinking',
170
- 'stream.writing': 'Writing',
171
195
  'stream.recovering': 'Partial record, awaiting live connection',
172
196
  'stream.copy': 'Copy',
173
197
  'stream.copied': 'Copied',
@@ -199,6 +223,30 @@ export const en = {
199
223
  'settings.unsupported': 'Coming soon',
200
224
  'settings.rosterFailed': 'Could not read the local-agent roster; retry',
201
225
  'settings.retry': 'Retry',
226
+ 'family.title': 'Local Agent Delegation',
227
+ 'family.description': 'Delegation channels of the coding-agent CLIs installed on this machine: endpoint credentials, default model, and resident mode',
228
+ 'family.cliMissing': 'CLI not detected',
229
+ 'family.empty': 'No delegation harnesses registered yet',
230
+ 'family.expand': 'Expand',
231
+ 'family.collapse': 'Collapse',
232
+ 'models.title': 'Available models',
233
+ 'models.info.aria': 'About the model list',
234
+ 'models.info': "Order is priority: every delegation round starts with the FIRST model. \"Add model\" checks entries off the endpoint's served catalog, so the list can only hold what the endpoint actually serves; drag rows to reorder; leave empty to follow the previous default (the settings value or the CLI's built-in default). The list persists in the scoped home (models.json) and applies to the next delegation round.",
235
+ 'models.empty': 'List is empty — following the previous default (settings value or the CLI built-in default)',
236
+ 'models.add': '+ Add model',
237
+ 'models.delete': 'Remove',
238
+ 'models.defaultChip': 'Default',
239
+ 'models.orderHint': 'Drag rows to reorder; the first is the default',
240
+ 'models.applied': 'Saved — takes effect on the next delegation round; rounds in flight are unaffected',
241
+ 'models.error': 'Save failed, please retry',
242
+ 'models.dialog.title': 'Choose models to add',
243
+ 'models.dialog.hint': 'These are the models the endpoint currently serves — check the ones to add.',
244
+ 'models.dialog.search': 'Search models',
245
+ 'models.dialog.selectAll': 'Select all',
246
+ 'models.dialog.cancel': 'Cancel',
247
+ 'models.dialog.confirm': 'Add selected',
248
+ 'models.dialog.loading': 'Loading the endpoint catalog…',
249
+ 'models.dialog.empty': 'The endpoint catalog is empty — save a credential first',
202
250
  'member.checking': 'Checking membership…',
203
251
  'member.readonly.title': 'One-shot subagent record',
204
252
  'member.readonly.body': 'One-shot tasks do not accept follow-up messages; the full run record is available here.',
@@ -0,0 +1,14 @@
1
+ import type { LocalAgentModelDirectory } from '../types.ts';
2
+ /**
3
+ * Scope a harness's served directory to its curated model list (models.json):
4
+ * the list IS the invite picker's vocabulary, in the list's own order — a
5
+ * listed value the endpoint no longer serves stays as a bare row (the member
6
+ * dialog shows it the same way). No list support, or none configured, keeps
7
+ * the directory as served. Mirrors the member dialog's scoping rule so the
8
+ * invite picker and the member configuration show the same candidates.
9
+ * @param directory - the harness's served directory.
10
+ * @param list - the curated shortlist values, or null when unsupported.
11
+ * @returns the scoped directory.
12
+ */
13
+ export declare function scopeDirectoryToList(directory: LocalAgentModelDirectory, list: readonly string[] | null | undefined): LocalAgentModelDirectory;
14
+ //# sourceMappingURL=model-directory-scope.d.ts.map
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Scope a harness's served directory to its curated model list (models.json):
3
+ * the list IS the invite picker's vocabulary, in the list's own order — a
4
+ * listed value the endpoint no longer serves stays as a bare row (the member
5
+ * dialog shows it the same way). No list support, or none configured, keeps
6
+ * the directory as served. Mirrors the member dialog's scoping rule so the
7
+ * invite picker and the member configuration show the same candidates.
8
+ * @param directory - the harness's served directory.
9
+ * @param list - the curated shortlist values, or null when unsupported.
10
+ * @returns the scoped directory.
11
+ */
12
+ export function scopeDirectoryToList(directory, list) {
13
+ if (list === undefined || list === null || list.length === 0)
14
+ return directory;
15
+ const byValue = new Map(directory.entries.flatMap(entry => entry.resolvedModel === undefined
16
+ ? [[entry.value, entry]]
17
+ : [[entry.value, entry], [entry.resolvedModel, entry]]));
18
+ const entries = list.map(value => byValue.get(value) ?? { value, label: value, source: 'configuration' });
19
+ return { ...directory, entries };
20
+ }
@@ -10,7 +10,7 @@
10
10
  import type { Context } from '@deepseek-ai/cordis';
11
11
  import { TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
12
12
  import type { LocalAgentDelegationView, LocalAgentModelInfo, LocalAgentModelDirectory, LocalAgentPromptResult, LocalAgentSessionRecord, LocalAgentStreamFrame, LocalAgentMemberControlState, LocalAgentMemberInbox, LocalAgentMemberConfiguration, LocalAgentControlReceipt, LocalAgentMemberFeedRequest, LocalAgentMemberFeedEvent } from './types.ts';
13
- import type { LocalAgentRosterRow, LocalAgentStatus } from './types.ts';
13
+ import type { LocalAgentRosterRow, LocalAgentStatus, LocalAgentEndpointAuthFields, LocalAgentEndpointAuthState } from './types.ts';
14
14
  export default class LocalAgentGateway extends TypertRemoteService {
15
15
  static inject: string[];
16
16
  constructor(ctx: Context);
@@ -103,6 +103,54 @@ export default class LocalAgentGateway extends TypertRemoteService {
103
103
  * @returns the layer-by-layer surface, or null.
104
104
  */
105
105
  harnessModel(name: string): Promise<LocalAgentModelInfo | null>;
106
+ /**
107
+ * Verify one harness's saved endpoint from the host — the settings card's
108
+ * verify button. The answer is the harness's human-readable verdict text.
109
+ * Session-less by design: the commands channel needs a main agent session,
110
+ * which a settings card may not have. Null when the harness is unknown or
111
+ * declares no probe (the card then hides the button — declare-and-degrade).
112
+ * @param name - the harness name.
113
+ * @returns the verdict text, or null.
114
+ */
115
+ checkEndpoint(name: string): Promise<string | null>;
116
+ /**
117
+ * The settings card's read of one harness's resolved endpoint state —
118
+ * baseUrl verbatim, credentials as presence booleans (secrets never cross).
119
+ * Session-less like the rest of the gateway. Null when the harness is
120
+ * unknown or declares no endpoint face (the card then hides the form).
121
+ * @param name - the harness name.
122
+ * @returns the resolved per-field state, or null.
123
+ */
124
+ endpointAuth(name: string): Promise<LocalAgentEndpointAuthState | null>;
125
+ /**
126
+ * Apply one endpoint-credential edit into the harness's own credential
127
+ * store (a string sets, null clears, absent leaves), answering the
128
+ * post-write resolved state so the card can flip immediately. The write
129
+ * deliberately bypasses the volatile row-config channel — a credential
130
+ * never lands in the profile patch document. Null when the harness is
131
+ * unknown or declares no endpoint face.
132
+ * @param name - the harness name.
133
+ * @param fields - the field edits.
134
+ * @returns the post-write resolved state, or null.
135
+ */
136
+ configureEndpoint(name: string, fields: LocalAgentEndpointAuthFields): Promise<LocalAgentEndpointAuthState | null>;
137
+ /**
138
+ * The settings card's read of one harness's curated model list — ordered
139
+ * identifiers, first is the default. Null when the harness is unknown or
140
+ * declares no list face (the card then hides the editor).
141
+ * @param name - the harness name.
142
+ * @returns the ordered list, or null.
143
+ */
144
+ modelList(name: string): Promise<readonly string[] | null>;
145
+ /**
146
+ * Replace one harness's curated model list wholesale (reorders, removals
147
+ * and additions all ride the same write), answering the list as persisted.
148
+ * Null when the harness is unknown or declares no list face.
149
+ * @param name - the harness name.
150
+ * @param models - the new ordered list.
151
+ * @returns the persisted list, or null.
152
+ */
153
+ configureModelList(name: string, models: readonly string[]): Promise<readonly string[] | null>;
106
154
  /**
107
155
  * The model surface of one member — the composer's picker read. The
108
156
  * delegation record's requested model rides in from the core so the broker