@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.
- package/README.en.md +181 -0
- package/README.i18n.yaml +2 -0
- package/README.md +181 -0
- package/cordis.patch.yml +21 -0
- package/lib/client.js +8832 -0
- package/lib/index.js +5281 -0
- package/lib/invariant.js +85 -0
- package/lib/member-bridge.js +298 -0
- package/lib/typert.host.d.ts +3 -0
- package/lib/typert.host.js +3137 -0
- package/lib/typert.remote-client.d.ts +67 -0
- package/lib/typert.remote-client.js +1971 -0
- package/lib/types/cli-version.d.ts +82 -0
- package/lib/types/cli-version.js +165 -0
- package/lib/types/client/AuthStatusDot.d.ts +17 -0
- package/lib/types/client/AuthStatusDot.js +9 -0
- package/lib/types/client/HarnessModelPicker.d.ts +17 -0
- package/lib/types/client/HarnessModelPicker.js +48 -0
- package/lib/types/client/LocalAgentRecordsAction.d.ts +41 -0
- package/lib/types/client/LocalAgentRecordsAction.js +74 -0
- package/lib/types/client/MemberComposer.d.ts +79 -0
- package/lib/types/client/MemberComposer.js +308 -0
- package/lib/types/client/MemberConfiguration.d.ts +9 -0
- package/lib/types/client/MemberConfiguration.js +46 -0
- package/lib/types/client/MemberInboxView.d.ts +12 -0
- package/lib/types/client/MemberInboxView.js +38 -0
- package/lib/types/client/MemberLiveNode.d.ts +14 -0
- package/lib/types/client/MemberLiveNode.js +74 -0
- package/lib/types/client/MemberPaintDiagnostics.d.ts +7 -0
- package/lib/types/client/MemberPaintDiagnostics.js +20 -0
- package/lib/types/client/ModelConfigurationFields.d.ts +15 -0
- package/lib/types/client/ModelConfigurationFields.js +23 -0
- package/lib/types/client/ProviderAuthBlock.d.ts +59 -0
- package/lib/types/client/ProviderAuthBlock.js +164 -0
- package/lib/types/client/auth-status.d.ts +28 -0
- package/lib/types/client/auth-status.js +56 -0
- package/lib/types/client/icons.d.ts +22 -0
- package/lib/types/client/icons.js +6 -0
- package/lib/types/client/index.d.ts +62 -0
- package/lib/types/client/index.js +135 -0
- package/lib/types/client/live-node.d.ts +14 -0
- package/lib/types/client/live-node.js +34 -0
- package/lib/types/client/live-output.d.ts +34 -0
- package/lib/types/client/live-output.js +94 -0
- package/lib/types/client/live-paint.d.ts +40 -0
- package/lib/types/client/live-paint.js +74 -0
- package/lib/types/client/locales.d.ts +120 -0
- package/lib/types/client/locales.js +227 -0
- package/lib/types/client/member-configuration.d.ts +41 -0
- package/lib/types/client/member-configuration.js +114 -0
- package/lib/types/client/member-dock.d.ts +94 -0
- package/lib/types/client/member-dock.js +102 -0
- package/lib/types/client/member-feed.d.ts +24 -0
- package/lib/types/client/member-feed.js +134 -0
- package/lib/types/client/request-id.d.ts +3 -0
- package/lib/types/client/request-id.js +6 -0
- package/lib/types/client/settings-scope.d.ts +74 -0
- package/lib/types/client/settings-scope.js +98 -0
- package/lib/types/client/use-configuration-menu.d.ts +10 -0
- package/lib/types/client/use-configuration-menu.js +61 -0
- package/lib/types/container.d.ts +69 -0
- package/lib/types/container.js +129 -0
- package/lib/types/env.d.ts +17 -0
- package/lib/types/env.js +89 -0
- package/lib/types/gateway.d.ts +162 -0
- package/lib/types/gateway.js +470 -0
- package/lib/types/index.d.ts +1463 -0
- package/lib/types/index.js +2704 -0
- package/lib/types/invariant.d.ts +16 -0
- package/lib/types/invariant.js +105 -0
- package/lib/types/live-flush.d.ts +21 -0
- package/lib/types/live-flush.js +59 -0
- package/lib/types/live-stream.d.ts +41 -0
- package/lib/types/live-stream.js +178 -0
- package/lib/types/member-bridge.d.ts +50 -0
- package/lib/types/member-bridge.js +216 -0
- package/lib/types/member-channel.d.ts +86 -0
- package/lib/types/member-channel.js +232 -0
- package/lib/types/member-control-storage.d.ts +10 -0
- package/lib/types/member-control-storage.js +100 -0
- package/lib/types/member-control.d.ts +62 -0
- package/lib/types/member-control.js +359 -0
- package/lib/types/member-controls.d.ts +23 -0
- package/lib/types/member-controls.js +143 -0
- package/lib/types/member-feed.d.ts +4 -0
- package/lib/types/member-feed.js +45 -0
- package/lib/types/member-inbox.d.ts +30 -0
- package/lib/types/member-inbox.js +225 -0
- package/lib/types/model-directory.d.ts +43 -0
- package/lib/types/model-directory.js +192 -0
- package/lib/types/prepared-members.d.ts +15 -0
- package/lib/types/prepared-members.js +61 -0
- package/lib/types/settings-face.d.ts +61 -0
- package/lib/types/settings-face.js +61 -0
- package/lib/types/types.d.ts +959 -0
- package/lib/types/types.js +24 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- 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
|