@khorsheed/dsh-local-agent 0.1.0-rc.6

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 (99) hide show
  1. package/README.en.md +181 -0
  2. package/README.i18n.yaml +2 -0
  3. package/README.md +181 -0
  4. package/cordis.patch.yml +21 -0
  5. package/lib/client.js +8832 -0
  6. package/lib/index.js +5281 -0
  7. package/lib/invariant.js +85 -0
  8. package/lib/member-bridge.js +298 -0
  9. package/lib/typert.host.d.ts +3 -0
  10. package/lib/typert.host.js +3137 -0
  11. package/lib/typert.remote-client.d.ts +67 -0
  12. package/lib/typert.remote-client.js +1971 -0
  13. package/lib/types/cli-version.d.ts +82 -0
  14. package/lib/types/cli-version.js +165 -0
  15. package/lib/types/client/AuthStatusDot.d.ts +17 -0
  16. package/lib/types/client/AuthStatusDot.js +9 -0
  17. package/lib/types/client/HarnessModelPicker.d.ts +17 -0
  18. package/lib/types/client/HarnessModelPicker.js +48 -0
  19. package/lib/types/client/LocalAgentRecordsAction.d.ts +41 -0
  20. package/lib/types/client/LocalAgentRecordsAction.js +74 -0
  21. package/lib/types/client/MemberComposer.d.ts +79 -0
  22. package/lib/types/client/MemberComposer.js +308 -0
  23. package/lib/types/client/MemberConfiguration.d.ts +9 -0
  24. package/lib/types/client/MemberConfiguration.js +46 -0
  25. package/lib/types/client/MemberInboxView.d.ts +12 -0
  26. package/lib/types/client/MemberInboxView.js +38 -0
  27. package/lib/types/client/MemberLiveNode.d.ts +14 -0
  28. package/lib/types/client/MemberLiveNode.js +74 -0
  29. package/lib/types/client/MemberPaintDiagnostics.d.ts +7 -0
  30. package/lib/types/client/MemberPaintDiagnostics.js +20 -0
  31. package/lib/types/client/ModelConfigurationFields.d.ts +15 -0
  32. package/lib/types/client/ModelConfigurationFields.js +23 -0
  33. package/lib/types/client/ProviderAuthBlock.d.ts +59 -0
  34. package/lib/types/client/ProviderAuthBlock.js +164 -0
  35. package/lib/types/client/auth-status.d.ts +28 -0
  36. package/lib/types/client/auth-status.js +56 -0
  37. package/lib/types/client/icons.d.ts +22 -0
  38. package/lib/types/client/icons.js +6 -0
  39. package/lib/types/client/index.d.ts +62 -0
  40. package/lib/types/client/index.js +135 -0
  41. package/lib/types/client/live-node.d.ts +14 -0
  42. package/lib/types/client/live-node.js +34 -0
  43. package/lib/types/client/live-output.d.ts +34 -0
  44. package/lib/types/client/live-output.js +94 -0
  45. package/lib/types/client/live-paint.d.ts +40 -0
  46. package/lib/types/client/live-paint.js +74 -0
  47. package/lib/types/client/locales.d.ts +120 -0
  48. package/lib/types/client/locales.js +227 -0
  49. package/lib/types/client/member-configuration.d.ts +41 -0
  50. package/lib/types/client/member-configuration.js +114 -0
  51. package/lib/types/client/member-dock.d.ts +94 -0
  52. package/lib/types/client/member-dock.js +102 -0
  53. package/lib/types/client/member-feed.d.ts +24 -0
  54. package/lib/types/client/member-feed.js +134 -0
  55. package/lib/types/client/request-id.d.ts +3 -0
  56. package/lib/types/client/request-id.js +6 -0
  57. package/lib/types/client/settings-scope.d.ts +74 -0
  58. package/lib/types/client/settings-scope.js +98 -0
  59. package/lib/types/client/use-configuration-menu.d.ts +10 -0
  60. package/lib/types/client/use-configuration-menu.js +61 -0
  61. package/lib/types/container.d.ts +69 -0
  62. package/lib/types/container.js +129 -0
  63. package/lib/types/env.d.ts +17 -0
  64. package/lib/types/env.js +89 -0
  65. package/lib/types/gateway.d.ts +162 -0
  66. package/lib/types/gateway.js +470 -0
  67. package/lib/types/index.d.ts +1463 -0
  68. package/lib/types/index.js +2704 -0
  69. package/lib/types/invariant.d.ts +16 -0
  70. package/lib/types/invariant.js +105 -0
  71. package/lib/types/live-flush.d.ts +21 -0
  72. package/lib/types/live-flush.js +59 -0
  73. package/lib/types/live-stream.d.ts +41 -0
  74. package/lib/types/live-stream.js +178 -0
  75. package/lib/types/member-bridge.d.ts +50 -0
  76. package/lib/types/member-bridge.js +216 -0
  77. package/lib/types/member-channel.d.ts +86 -0
  78. package/lib/types/member-channel.js +232 -0
  79. package/lib/types/member-control-storage.d.ts +10 -0
  80. package/lib/types/member-control-storage.js +100 -0
  81. package/lib/types/member-control.d.ts +62 -0
  82. package/lib/types/member-control.js +359 -0
  83. package/lib/types/member-controls.d.ts +23 -0
  84. package/lib/types/member-controls.js +143 -0
  85. package/lib/types/member-feed.d.ts +4 -0
  86. package/lib/types/member-feed.js +45 -0
  87. package/lib/types/member-inbox.d.ts +30 -0
  88. package/lib/types/member-inbox.js +225 -0
  89. package/lib/types/model-directory.d.ts +43 -0
  90. package/lib/types/model-directory.js +192 -0
  91. package/lib/types/prepared-members.d.ts +15 -0
  92. package/lib/types/prepared-members.js +61 -0
  93. package/lib/types/settings-face.d.ts +61 -0
  94. package/lib/types/settings-face.js +61 -0
  95. package/lib/types/types.d.ts +959 -0
  96. package/lib/types/types.js +24 -0
  97. package/locale/en.json +6 -0
  98. package/locale/zh.json +6 -0
  99. package/package.json +112 -0
@@ -0,0 +1,82 @@
1
+ /**
2
+ * The family's CLI version probe: one bounded `<cli> --version` spawn whose
3
+ * answer is cached against the executable's own identity (resolved path +
4
+ * mtime + size), so a status surface can report `cliVersion` without paying a
5
+ * spawn per call and without ever reporting a stale version across an upgrade.
6
+ *
7
+ * The probe exists because `cliVersion` is a condition-hash input for the
8
+ * evaluation (`LocalAgentEffectiveSettings`), and the CLI's self-reported
9
+ * version is the only value a harness can state honestly: the scoped config
10
+ * never names it, and a package-manifest read would name whatever the host
11
+ * happens to resolve, not the binary a delegation round actually spawns.
12
+ *
13
+ * Degrade, never explode: an unresolvable executable, a non-zero exit, a
14
+ * timeout, or output with no version-shaped token all resolve `undefined` —
15
+ * the absent field is the honest condition input, exactly as
16
+ * `LocalAgentEffectiveSettings` specifies for a knob a harness cannot read.
17
+ * @module @khorsheed/dsh-local-agent/cli-version
18
+ */
19
+ import type { SubprocessHandle, SubprocessSpawnSpec } from '@deepseek-ai/dsh-subprocess';
20
+ /** How long one `--version` probe may run before it is terminated. */
21
+ export declare const CLI_VERSION_PROBE_TIMEOUT_MS = 5000;
22
+ /**
23
+ * How long a FAILED probe is remembered. A success is cached against the
24
+ * executable's identity and never expires (a new binary is a new key), but a
25
+ * failure may be transient (a machine under load hitting the timeout), so it
26
+ * expires instead of poisoning the field until the next CLI upgrade.
27
+ */
28
+ export declare const CLI_VERSION_FAILURE_TTL_MS = 60000;
29
+ /**
30
+ * The version token inside a `--version` banner.
31
+ * @param output - the probe's collected output.
32
+ * @returns the first version-shaped token, or undefined when there is none.
33
+ */
34
+ export declare function parseCliVersion(output: string): string | undefined;
35
+ /** One `<cli> --version` probe request. */
36
+ export interface CliVersionProbe {
37
+ /**
38
+ * The probe argv. `argv[0]` is the executable (resolved against `PATH` when
39
+ * it carries no separator); the rest are the version flags, plus — for a
40
+ * launcher-style argv such as the sub-dsh's `node … bin.js --version` — the
41
+ * entry script, which is what actually carries the version.
42
+ */
43
+ readonly argv: readonly string[];
44
+ /** Working directory for the probe; the harness's scoped home is always safe. */
45
+ readonly cwd: string;
46
+ /** The shared subprocess seam (`ctx.subprocess.spawn`). */
47
+ readonly spawn: (spec: SubprocessSpawnSpec) => SubprocessHandle;
48
+ /**
49
+ * Explicit environment layer, normally `delegationEnv({ <HOME_VAR>: homeDir })`
50
+ * so the probe reads the SCOPED home like a delegation round would, never
51
+ * the user's own installation.
52
+ */
53
+ readonly env?: NodeJS.ProcessEnv | undefined;
54
+ /** Probe deadline; defaults to {@link CLI_VERSION_PROBE_TIMEOUT_MS}. */
55
+ readonly timeoutMs?: number | undefined;
56
+ }
57
+ /**
58
+ * Forget every cached probe. Exists for tests (whose stub executables and stub
59
+ * spawns must not inherit another test's answer) and for a caller that must
60
+ * force a re-probe; ordinary callers never need it, because a CLI upgrade
61
+ * changes the cache key by itself.
62
+ */
63
+ export declare function clearCliVersionCache(): void;
64
+ /**
65
+ * The cache key: the identity of every argv entry that names an existing file
66
+ * — the executable, plus a launcher argv's entry script. An upgrade rewrites
67
+ * one of those files, which changes the key, which re-probes. An executable
68
+ * that resolves to nothing keys on the raw argv instead, so a CLI that is not
69
+ * installed is not probed once per status call either.
70
+ */
71
+ export declare function cliExecutableIdentity(argv: readonly string[]): string;
72
+ /**
73
+ * The CLI's own version, probed at most once per executable identity.
74
+ * Concurrent callers share one in-flight probe; a success is remembered until
75
+ * the binary changes; a failure is remembered for
76
+ * {@link CLI_VERSION_FAILURE_TTL_MS} so a transient miss recovers without
77
+ * spawning per status call.
78
+ * @param probe - the probe request.
79
+ * @returns the version, or undefined when the CLI cannot be asked.
80
+ */
81
+ export declare function probeCliVersion(probe: CliVersionProbe): Promise<string | undefined>;
82
+ //# sourceMappingURL=cli-version.d.ts.map
@@ -0,0 +1,165 @@
1
+ /**
2
+ * The family's CLI version probe: one bounded `<cli> --version` spawn whose
3
+ * answer is cached against the executable's own identity (resolved path +
4
+ * mtime + size), so a status surface can report `cliVersion` without paying a
5
+ * spawn per call and without ever reporting a stale version across an upgrade.
6
+ *
7
+ * The probe exists because `cliVersion` is a condition-hash input for the
8
+ * evaluation (`LocalAgentEffectiveSettings`), and the CLI's self-reported
9
+ * version is the only value a harness can state honestly: the scoped config
10
+ * never names it, and a package-manifest read would name whatever the host
11
+ * happens to resolve, not the binary a delegation round actually spawns.
12
+ *
13
+ * Degrade, never explode: an unresolvable executable, a non-zero exit, a
14
+ * timeout, or output with no version-shaped token all resolve `undefined` —
15
+ * the absent field is the honest condition input, exactly as
16
+ * `LocalAgentEffectiveSettings` specifies for a knob a harness cannot read.
17
+ * @module @khorsheed/dsh-local-agent/cli-version
18
+ */
19
+ import { statSync } from 'node:fs';
20
+ import { delimiter, isAbsolute, join, resolve } from 'node:path';
21
+ /** How long one `--version` probe may run before it is terminated. */
22
+ export const CLI_VERSION_PROBE_TIMEOUT_MS = 5_000;
23
+ /**
24
+ * How long a FAILED probe is remembered. A success is cached against the
25
+ * executable's identity and never expires (a new binary is a new key), but a
26
+ * failure may be transient (a machine under load hitting the timeout), so it
27
+ * expires instead of poisoning the field until the next CLI upgrade.
28
+ */
29
+ export const CLI_VERSION_FAILURE_TTL_MS = 60_000;
30
+ /** SIGTERM to SIGKILL grace for a probe that ignores the timeout's terminate. */
31
+ const PROBE_GRACE_MS = 2_000;
32
+ /** In-memory cap per collected probe stream; a version banner is a few bytes. */
33
+ const PROBE_OUTPUT_MAX_BYTES = 4 * 1024;
34
+ /**
35
+ * A version-shaped token: `1.2.3` with an optional prerelease/build tail.
36
+ * Every family CLI prints one inside a banner of its own shape — codex
37
+ * `codex-cli 0.144.0`, claude `2.1.263 (Claude Code)`, kimi a bare `0.39.1`,
38
+ * dsh `0.1.1-rc.2` — so the token, not the banner, is what this parses.
39
+ */
40
+ const VERSION_TOKEN = /\d+\.\d+\.\d+(?:[-+][0-9A-Za-z][0-9A-Za-z.-]*)?/;
41
+ /**
42
+ * The version token inside a `--version` banner.
43
+ * @param output - the probe's collected output.
44
+ * @returns the first version-shaped token, or undefined when there is none.
45
+ */
46
+ export function parseCliVersion(output) {
47
+ return VERSION_TOKEN.exec(output)?.[0];
48
+ }
49
+ const cache = new Map();
50
+ /**
51
+ * Forget every cached probe. Exists for tests (whose stub executables and stub
52
+ * spawns must not inherit another test's answer) and for a caller that must
53
+ * force a re-probe; ordinary callers never need it, because a CLI upgrade
54
+ * changes the cache key by itself.
55
+ */
56
+ export function clearCliVersionCache() {
57
+ cache.clear();
58
+ }
59
+ /** `path:mtimeMs:size` for an existing file, undefined when it is not one. */
60
+ function stampFile(path) {
61
+ try {
62
+ const stats = statSync(path);
63
+ return stats.isFile() ? `${path}:${stats.mtimeMs}:${stats.size}` : undefined;
64
+ }
65
+ catch {
66
+ return undefined;
67
+ }
68
+ }
69
+ /** Whether an argv entry names a path rather than a bare command or a flag. */
70
+ function looksLikePath(entry) {
71
+ return entry.includes('/') || (process.platform === 'win32' && entry.includes('\\'));
72
+ }
73
+ /**
74
+ * Resolve `argv[0]` to a real file: used as-is when it names a path,
75
+ * otherwise searched along `PATH` (with `PATHEXT` on Windows) exactly as the
76
+ * spawn seam will resolve it.
77
+ */
78
+ function resolveExecutable(command) {
79
+ if (command === '')
80
+ return undefined;
81
+ if (looksLikePath(command))
82
+ return stampFile(isAbsolute(command) ? command : resolve(command));
83
+ const extensions = process.platform === 'win32'
84
+ ? (process.env['PATHEXT'] ?? '.EXE;.CMD;.BAT').split(';')
85
+ : [''];
86
+ for (const dir of (process.env['PATH'] ?? '').split(delimiter)) {
87
+ if (dir === '')
88
+ continue;
89
+ for (const extension of extensions) {
90
+ const stamped = stampFile(join(dir, command + extension));
91
+ if (stamped !== undefined)
92
+ return stamped;
93
+ }
94
+ }
95
+ return undefined;
96
+ }
97
+ /**
98
+ * The cache key: the identity of every argv entry that names an existing file
99
+ * — the executable, plus a launcher argv's entry script. An upgrade rewrites
100
+ * one of those files, which changes the key, which re-probes. An executable
101
+ * that resolves to nothing keys on the raw argv instead, so a CLI that is not
102
+ * installed is not probed once per status call either.
103
+ */
104
+ export function cliExecutableIdentity(argv) {
105
+ const parts = [resolveExecutable(argv[0] ?? '') ?? `argv:${argv[0] ?? ''}`];
106
+ for (const entry of argv.slice(1)) {
107
+ if (!looksLikePath(entry))
108
+ continue;
109
+ const stamped = stampFile(isAbsolute(entry) ? entry : resolve(entry));
110
+ if (stamped !== undefined)
111
+ parts.push(stamped);
112
+ }
113
+ return parts.join(' ');
114
+ }
115
+ /** Spawn the probe once and map its output to a version; never throws. */
116
+ async function runProbe(probe) {
117
+ try {
118
+ const child = probe.spawn({
119
+ argv: [...probe.argv],
120
+ cwd: probe.cwd,
121
+ stdio: {
122
+ stdin: 'ignore',
123
+ stdout: { maxBytes: PROBE_OUTPUT_MAX_BYTES },
124
+ stderr: { maxBytes: PROBE_OUTPUT_MAX_BYTES },
125
+ },
126
+ graceMs: PROBE_GRACE_MS,
127
+ signal: AbortSignal.timeout(probe.timeoutMs ?? CLI_VERSION_PROBE_TIMEOUT_MS),
128
+ ...probe.env === undefined ? {} : { env: probe.env },
129
+ });
130
+ const outcome = await child.done;
131
+ // A non-zero exit (or a signal from the timeout) means the CLI did not
132
+ // answer the question asked. Parsing its diagnostics would invent a
133
+ // version out of an error message, so the honest answer is absence.
134
+ if (outcome.exitCode !== 0)
135
+ return undefined;
136
+ const stdout = child.collected.stdout?.readFrom(0).text ?? '';
137
+ const stderr = child.collected.stderr?.readFrom(0).text ?? '';
138
+ return parseCliVersion(`${stdout}\n${stderr}`);
139
+ }
140
+ catch {
141
+ return undefined;
142
+ }
143
+ }
144
+ /**
145
+ * The CLI's own version, probed at most once per executable identity.
146
+ * Concurrent callers share one in-flight probe; a success is remembered until
147
+ * the binary changes; a failure is remembered for
148
+ * {@link CLI_VERSION_FAILURE_TTL_MS} so a transient miss recovers without
149
+ * spawning per status call.
150
+ * @param probe - the probe request.
151
+ * @returns the version, or undefined when the CLI cannot be asked.
152
+ */
153
+ export async function probeCliVersion(probe) {
154
+ const key = cliExecutableIdentity(probe.argv);
155
+ const cached = cache.get(key);
156
+ if (cached !== undefined && (cached.failedAt === undefined || Date.now() - cached.failedAt < CLI_VERSION_FAILURE_TTL_MS)) {
157
+ return cached.promise;
158
+ }
159
+ const entry = { promise: runProbe(probe) };
160
+ cache.set(key, entry);
161
+ const version = await entry.promise;
162
+ if (version === undefined)
163
+ entry.failedAt = Date.now();
164
+ return version;
165
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * The at-a-glance credential dot for the settings-card headers: the same
3
+ * visual as the family settings section's per-harness row dot (green =
4
+ * authenticated, red = not, grey = checking/unavailable).
5
+ * @module @khorsheed/dsh-local-agent/client — auth status dot
6
+ */
7
+ import type { HarnessAuthStatusKind } from './auth-status.ts';
8
+ /** Props for the credential dot. */
9
+ export interface AuthStatusDotProps {
10
+ /** The latest known status kind. */
11
+ status: HarnessAuthStatusKind;
12
+ /** The localized aria label (the card's auth copy namespace). */
13
+ label: string;
14
+ }
15
+ /** One 8px status dot with an accessible label. */
16
+ export declare function AuthStatusDot({ status, label }: AuthStatusDotProps): import("react").JSX.Element;
17
+ //# sourceMappingURL=AuthStatusDot.d.ts.map
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import css from './AuthStatusDot.module.css';
3
+ /** One 8px status dot with an accessible label. */
4
+ export function AuthStatusDot({ status, label }) {
5
+ const modifier = status === 'authenticated' ? css.dotOn
6
+ : status === 'anonymous' ? css.dotOff
7
+ : css.dotPending;
8
+ return _jsx("span", { className: `${css.credentialDot} ${modifier}`, role: "img", "aria-label": label, "data-auth-status": status });
9
+ }
@@ -0,0 +1,17 @@
1
+ import type { LocalAgentModelDirectory } from '../types.ts';
2
+ import { type ConfigurationTranslate } from './ModelConfigurationFields.tsx';
3
+ export interface ModelDirectoryFace {
4
+ read(refresh: boolean): Promise<LocalAgentModelDirectory | null>;
5
+ follow(signal: AbortSignal): AsyncIterable<LocalAgentModelDirectory>;
6
+ }
7
+ export interface HarnessModelPickerProps {
8
+ face: ModelDirectoryFace;
9
+ value: string;
10
+ onChange(value: string): void;
11
+ disabled?: boolean | undefined;
12
+ defaultLabel?: string | undefined;
13
+ t: ConfigurationTranslate;
14
+ }
15
+ /** Discover while expanded or resolving an empty choice; settings remain caller-owned drafts. */
16
+ export declare function HarnessModelPicker({ face, value, onChange, disabled, t }: HarnessModelPickerProps): import("react").JSX.Element;
17
+ //# sourceMappingURL=HarnessModelPicker.d.ts.map
@@ -0,0 +1,48 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useRef, useState } from 'react';
3
+ import { ModelConfigurationFields } from './ModelConfigurationFields.js';
4
+ import css from './MemberConfiguration.module.css';
5
+ import { useConfigurationMenu } from './use-configuration-menu.js';
6
+ /** Discover while expanded or resolving an empty choice; settings remain caller-owned drafts. */
7
+ export function HarnessModelPicker({ face, value, onChange, disabled, t }) {
8
+ const menu = useConfigurationMenu('below');
9
+ const { open, setOpen } = menu;
10
+ const [surface, setSurface] = useState();
11
+ const directory = surface?.face === face ? surface.directory : undefined;
12
+ const currentFace = useRef(face);
13
+ currentFace.current = face;
14
+ const setDirectory = (directory) => { if (currentFace.current === face)
15
+ setSurface({ face, directory }); };
16
+ const [error, setError] = useState();
17
+ useEffect(() => {
18
+ if (!open && value)
19
+ return;
20
+ const controller = new AbortController();
21
+ let timer;
22
+ const follow = async () => {
23
+ try {
24
+ for await (const result of face.follow(controller.signal)) {
25
+ if (controller.signal.aborted)
26
+ return;
27
+ setDirectory(result);
28
+ setError(undefined);
29
+ }
30
+ }
31
+ catch (error) {
32
+ if (!controller.signal.aborted)
33
+ setError(error instanceof Error ? error.message : String(error));
34
+ }
35
+ if (!controller.signal.aborted)
36
+ timer = setTimeout(() => { void follow(); }, 1_000);
37
+ };
38
+ void follow();
39
+ return () => { controller.abort(); clearTimeout(timer); };
40
+ }, [face, open, Boolean(value)]);
41
+ useEffect(() => {
42
+ const first = directory?.entries.find(entry => !entry.hidden);
43
+ if (!value && !disabled && first)
44
+ onChange(first.value);
45
+ }, [directory, value, disabled, onChange]);
46
+ const entry = directory?.entries.find(entry => entry.value === value);
47
+ return _jsxs("details", { ref: menu.root, className: `${css.root} ${css.settings}`, open: open, onToggle: event => setOpen(event.currentTarget.open), children: [_jsx("summary", { "aria-label": t('configuration.model'), "aria-expanded": open, children: _jsx("span", { className: css.triggerLabel, children: entry?.label ?? (value || t('configuration.choose')) }) }), open && _jsxs("div", { ref: menu.panel, className: css.panel, style: menu.style, children: [error && _jsx("div", { role: "alert", children: error }), !directory && _jsx("span", { role: "status", children: t('loading') }), _jsx(ModelConfigurationFields, { directory: directory, value: { model: value ? { mode: 'value', value } : { mode: 'default' }, effort: { mode: 'default' } }, onChange: selection => { onChange(selection.model.mode === 'value' ? selection.model.value : ''); setOpen(false); }, showEffort: false, disabled: disabled, t: t })] })] });
48
+ }
@@ -0,0 +1,41 @@
1
+ import type { SessionId } from '@deepseek-ai/dsh-session/types';
2
+ import type { LocalAgentRosterRow, LocalAgentSessionRecord } from '@khorsheed/dsh-local-agent/types';
3
+ import type { PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
4
+ import { NS } from './locales.ts';
5
+ /** One harness the family renders a trigger for. */
6
+ export interface LocalAgentHarnessView {
7
+ /** Harness command prefix, e.g. `kimi`. */
8
+ id: string;
9
+ /** Trigger label, e.g. `Kimi`. */
10
+ label: string;
11
+ }
12
+ /** Injected business face of the session-header records action. */
13
+ export interface LocalAgentRecordsActionInjected {
14
+ /** Registered harnesses, in registration order. */
15
+ roster: () => Promise<readonly LocalAgentRosterRow[] | undefined>;
16
+ /**
17
+ * One harness's scoped sessions, narrowed to the given dsh session's cwd.
18
+ * @param name - the harness name.
19
+ * @param sessionId - the dsh session whose workspace the records must match.
20
+ */
21
+ sessions: (name: string, sessionId: SessionId) => Promise<readonly LocalAgentSessionRecord[] | undefined>;
22
+ }
23
+ /** Full props for the session-header records action. */
24
+ export type LocalAgentRecordsActionProps = PropsRuntime<'conversation.session.header.actions'> & PropsLocale<typeof NS> & LocalAgentRecordsActionInjected;
25
+ /** One harness's records dropdown: trigger and popover. */
26
+ export declare function RecordsDropdown({ harness, sessionId, listRecords, t }: {
27
+ harness: LocalAgentHarnessView;
28
+ sessionId: SessionId;
29
+ listRecords: (sessionId: SessionId) => Promise<readonly LocalAgentSessionRecord[] | undefined>;
30
+ t: LocalAgentRecordsActionProps['t'];
31
+ }): import("react").JSX.Element;
32
+ /**
33
+ * Session-header entry point for the local-agent family: fetches the harness
34
+ * roster through the local-agent Remote channel on mount and renders one
35
+ * records dropdown per harness. The Remote channel is read-only and emits no
36
+ * session events, so these UI refreshes never leave command nodes in the log.
37
+ * @param props - runtime slot currency plus the injected query face.
38
+ * @returns the per-harness triggers and their popovers.
39
+ */
40
+ export declare function LocalAgentRecordsAction({ sessionId, roster, sessions, t }: LocalAgentRecordsActionProps): import("react").JSX.Element;
41
+ //# sourceMappingURL=LocalAgentRecordsAction.d.ts.map
@@ -0,0 +1,74 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useMemo, useRef, useState } from 'react';
3
+ import { IconChevronDownOutlineMedium } from './icons.js';
4
+ import css from './LocalAgentRecordsAction.module.css';
5
+ /** One harness's records dropdown: trigger and popover. */
6
+ export function RecordsDropdown({ harness, sessionId, listRecords, t }) {
7
+ const [open, setOpen] = useState(false);
8
+ const [load, setLoad] = useState({ phase: 'idle' });
9
+ const rootRef = useRef(null);
10
+ const triggerRef = useRef(null);
11
+ useEffect(() => {
12
+ if (!open)
13
+ return;
14
+ setLoad({ phase: 'loading' });
15
+ let cancelled = false;
16
+ void listRecords(sessionId).then((records) => {
17
+ if (cancelled)
18
+ return;
19
+ setLoad(records === undefined
20
+ ? { phase: 'unavailable' }
21
+ : { phase: 'loaded', records });
22
+ }).catch(() => {
23
+ if (!cancelled)
24
+ setLoad({ phase: 'error' });
25
+ });
26
+ return () => { cancelled = true; };
27
+ }, [open, listRecords, sessionId]);
28
+ useEffect(() => {
29
+ if (!open)
30
+ return;
31
+ const closeOutside = (event) => {
32
+ if (event.target instanceof Node && !rootRef.current?.contains(event.target)) {
33
+ setOpen(false);
34
+ }
35
+ };
36
+ document.addEventListener('pointerdown', closeOutside);
37
+ return () => { document.removeEventListener('pointerdown', closeOutside); };
38
+ }, [open]);
39
+ const rows = useMemo(() => (load.phase === 'loaded' ? load.records : []), [load]);
40
+ const empty = load.phase === 'loaded' && rows.length === 0;
41
+ const onKeyDown = (event) => {
42
+ if (event.key !== 'Escape' || !open)
43
+ return;
44
+ event.preventDefault();
45
+ setOpen(false);
46
+ triggerRef.current?.focus();
47
+ };
48
+ return (_jsxs("div", { ref: rootRef, className: css.root, onKeyDown: onKeyDown, children: [_jsxs("button", { ref: triggerRef, type: "button", className: css.trigger, "aria-haspopup": "listbox", "aria-expanded": open, "aria-label": t('list.aria', { harness: harness.label }), onClick: () => { setOpen(value => !value); }, children: [harness.label, _jsx(IconChevronDownOutlineMedium, { className: open ? css.triggerOpen : undefined })] }), open && (_jsxs("ul", { className: css.menu, role: "listbox", "aria-label": t('list.title', { harness: harness.label }), children: [load.phase === 'loading' && _jsx("li", { className: css.row, children: t('loading') }), load.phase === 'unavailable' && _jsx("li", { className: css.row, children: t('unknown.command', { harness: harness.label, command: harness.id }) }), load.phase === 'error' && _jsx("li", { className: css.row, children: t('error', { harness: harness.label }) }), empty && (_jsx("li", { className: css.row, children: _jsx("span", { className: css.label, children: t('empty', { harness: harness.label }) }) })), empty && _jsx("li", { className: css.row, children: t('login.hint', { command: harness.id }) }), rows.map(row => (_jsxs("li", { className: css.row, role: "option", "aria-label": t('row.aria', { harness: harness.label, sessionId: row.id }), children: [_jsx("span", { className: css.label, title: row.id, children: row.id }), _jsx("span", { className: css.kind, children: t('workdir') }), _jsx("span", { className: css.status, title: row.workDir, children: row.workDir })] }, row.id)))] }))] }));
49
+ }
50
+ /**
51
+ * Session-header entry point for the local-agent family: fetches the harness
52
+ * roster through the local-agent Remote channel on mount and renders one
53
+ * records dropdown per harness. The Remote channel is read-only and emits no
54
+ * session events, so these UI refreshes never leave command nodes in the log.
55
+ * @param props - runtime slot currency plus the injected query face.
56
+ * @returns the per-harness triggers and their popovers.
57
+ */
58
+ export function LocalAgentRecordsAction({ sessionId, roster, sessions, t }) {
59
+ const [harnesses, setHarnesses] = useState([]);
60
+ useEffect(() => {
61
+ let cancelled = false;
62
+ void roster().then((rows) => {
63
+ if (cancelled || rows === undefined)
64
+ return;
65
+ setHarnesses(rows.map(row => ({ id: row.name, label: row.displayName })));
66
+ }).catch(() => {
67
+ // A failed roster fetch leaves the header without triggers; the settings
68
+ // section surfaces the failure with a retry instead.
69
+ });
70
+ return () => { cancelled = true; };
71
+ }, [roster]);
72
+ const listRecords = (id) => (target) => sessions(id, target);
73
+ return (_jsx("div", { className: css.family, children: harnesses.map(harness => (_jsx(RecordsDropdown, { harness: harness, sessionId: sessionId, listRecords: listRecords(harness.id), t: t }, harness.id))) }));
74
+ }
@@ -0,0 +1,79 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { ComposerChainProps } from '@deepseek-ai/dsh-client-ui-conversation/client';
3
+ import type { PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
4
+ import type { LocalAgentDelegationView, LocalAgentModelInfo, LocalAgentPromptResult } from '@khorsheed/dsh-local-agent/types';
5
+ import { NS } from './locales.ts';
6
+ /** What the chain selector hands the elected component. */
7
+ export interface MemberComposerMatch {
8
+ /**
9
+ * The elected session's id — a one-shot subagent session, possibly a family
10
+ * member (the component confirms through the Remote).
11
+ */
12
+ childSessionId: string;
13
+ }
14
+ /**
15
+ * Pure coarse filter for the composer chain: elect any one-shot subagent
16
+ * session. Deliberately wide (selector purity allows no async membership
17
+ * check) — the component degrades to the read-only panel when the delegation
18
+ * registry has no record for the session.
19
+ * @param owner - the composer chain currency dispatched by ConversationRoot.
20
+ * @returns the match carrying the child session id, or null to pass the
21
+ * election down the chain.
22
+ */
23
+ export declare function selectCliMember(owner: ComposerChainProps): MemberComposerMatch | null;
24
+ /**
25
+ * Injected business face: the member channel's gateway remotes, narrowed to
26
+ * plain values (an RPC failure surfaces as undefined, which the component
27
+ * renders through its own copy rather than throwing into the slot tree).
28
+ */
29
+ export interface MemberComposerInjected {
30
+ renderMemberInbox?: ((childSessionId: string) => ReactNode) | undefined;
31
+ renderMemberConfiguration?: ((childSessionId: string) => ReactNode) | undefined;
32
+ /** The delegation view for a child session; null = not a family member. */
33
+ memberOf: (childSessionId: string) => Promise<LocalAgentDelegationView | null | undefined>;
34
+ /** Send one human follow-up to the member (facade resume host-side). */
35
+ promptMember: (childSessionId: string, text: string, requestId?: string) => Promise<LocalAgentPromptResult | undefined>;
36
+ /** Interrupt the member's in-flight run. */
37
+ stopMember: (childSessionId: string) => Promise<boolean | undefined>;
38
+ /**
39
+ * The in-flight delegation child session ids (the second running source:
40
+ * the official summary `running` flag is agent-based and stays false for
41
+ * external CLI runs, which have no live host agent). undefined = RPC
42
+ * failure; the component keeps the last known bit rather than flapping.
43
+ */
44
+ activeDelegations: () => Promise<readonly string[] | undefined>;
45
+ /**
46
+ * The member's model surface; null = no picker (a brokerless harness keeps
47
+ * its pre-broker behavior exactly), undefined = RPC failure (keep the last
48
+ * known surface rather than flapping the picker off).
49
+ */
50
+ memberModel: (childSessionId: string) => Promise<LocalAgentModelInfo | null | undefined>;
51
+ /**
52
+ * Set (undefined = clear) the member's session-level model override; the
53
+ * structured error renders inline like a promptMember failure.
54
+ */
55
+ setMemberModel: (childSessionId: string, model?: string) => Promise<LocalAgentPromptResult | undefined>;
56
+ }
57
+ /** Full chain props after the member selector accepts the owner currency. */
58
+ export type MemberComposerProps = PropsRuntime<'conversation.composer'> & {
59
+ matched: MemberComposerMatch;
60
+ } & PropsLocale<typeof NS> & MemberComposerInjected;
61
+ /** Test hook: drop every cached membership answer. */
62
+ export declare function resetMembershipCache(): void;
63
+ /**
64
+ * The member composer: a writable box for family member sessions and the
65
+ * official-looking read-only panel for every other one-shot session. Drafts
66
+ * stay in local state (the official input machine is deliberately NOT wired —
67
+ * its send path is hardwired to the host prompt pipeline this channel must
68
+ * bypass). A run in flight disables the input and swaps Send for Stop — the
69
+ * running bit is DUAL-SOURCE: the session summary's flag (agent-based; the
70
+ * dsh member's sub-instance drives it natively) OR the family's in-flight
71
+ * delegation registry polled through `activeDelegations` (the only source
72
+ * that lights up for external CLI runs, which have no live host agent).
73
+ * @param props - selector match, standard slot props, locale seat, and the
74
+ * injected gateway face.
75
+ * @returns the composer, the neutral checking state while probing, or the
76
+ * read-only panel when not a member.
77
+ */
78
+ export declare function MemberComposer({ matched, useSession, useProjection, memberOf, promptMember, stopMember, activeDelegations, memberModel, setMemberModel, renderMemberConfiguration, renderMemberInbox, t }: MemberComposerProps): import("react").JSX.Element;
79
+ //# sourceMappingURL=MemberComposer.d.ts.map