@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,61 @@
|
|
|
1
|
+
import { useEffect, useLayoutEffect, useRef, useState } from 'react';
|
|
2
|
+
/** Shared dismissal and placement for settings and member configuration menus. */
|
|
3
|
+
export function useConfigurationMenu(preferred) {
|
|
4
|
+
const root = useRef(null);
|
|
5
|
+
const panel = useRef(null);
|
|
6
|
+
const [open, setOpen] = useState(false);
|
|
7
|
+
const [style, setStyle] = useState();
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
const dismiss = (event) => {
|
|
10
|
+
if (root.current?.open && event.target instanceof Node && !root.current.contains(event.target)) {
|
|
11
|
+
root.current.open = false;
|
|
12
|
+
setOpen(false);
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
const escape = (event) => {
|
|
16
|
+
if (event.key !== 'Escape' || !root.current?.open)
|
|
17
|
+
return;
|
|
18
|
+
event.preventDefault();
|
|
19
|
+
event.stopPropagation();
|
|
20
|
+
root.current.open = false;
|
|
21
|
+
setOpen(false);
|
|
22
|
+
root.current.querySelector('summary')?.focus();
|
|
23
|
+
};
|
|
24
|
+
document.addEventListener('pointerdown', dismiss, true);
|
|
25
|
+
document.addEventListener('focusin', dismiss);
|
|
26
|
+
document.addEventListener('keydown', escape, true);
|
|
27
|
+
return () => {
|
|
28
|
+
document.removeEventListener('pointerdown', dismiss, true);
|
|
29
|
+
document.removeEventListener('focusin', dismiss);
|
|
30
|
+
document.removeEventListener('keydown', escape, true);
|
|
31
|
+
};
|
|
32
|
+
}, []);
|
|
33
|
+
useLayoutEffect(() => {
|
|
34
|
+
if (!open)
|
|
35
|
+
return;
|
|
36
|
+
const place = () => {
|
|
37
|
+
if (!root.current || !panel.current)
|
|
38
|
+
return;
|
|
39
|
+
const anchor = root.current.getBoundingClientRect();
|
|
40
|
+
const dialog = root.current.closest('[role="dialog"], dialog')?.getBoundingClientRect();
|
|
41
|
+
const bounds = { left: Math.max(8, (dialog?.left ?? 0) + 8), right: Math.min(window.innerWidth - 8, (dialog?.right ?? window.innerWidth) - 8), top: Math.max(8, (dialog?.top ?? 0) + 8), bottom: Math.min(window.innerHeight - 8, (dialog?.bottom ?? window.innerHeight) - 8) };
|
|
42
|
+
const above = Math.max(0, anchor.top - bounds.top - 6);
|
|
43
|
+
const below = Math.max(0, bounds.bottom - anchor.bottom - 6);
|
|
44
|
+
const needed = Math.min(panel.current.scrollHeight, 400);
|
|
45
|
+
const up = preferred === 'above' ? above >= needed || above >= below : below < needed && above > below;
|
|
46
|
+
const width = Math.max(0, Math.min(300, bounds.right - bounds.left));
|
|
47
|
+
const left = Math.max(bounds.left, Math.min(preferred === 'above' ? anchor.right - width : anchor.left, bounds.right - width)) - anchor.left;
|
|
48
|
+
setStyle({ width, left, right: 'auto', top: up ? 'auto' : 'calc(100% + 6px)', bottom: up ? 'calc(100% + 6px)' : 'auto', maxHeight: Math.min(400, up ? above : below) });
|
|
49
|
+
};
|
|
50
|
+
place();
|
|
51
|
+
const observer = typeof ResizeObserver === 'undefined' ? undefined : new ResizeObserver(place);
|
|
52
|
+
if (panel.current)
|
|
53
|
+
observer?.observe(panel.current);
|
|
54
|
+
window.addEventListener('resize', place);
|
|
55
|
+
const scroll = (event) => { if (!(event.target instanceof Node) || !root.current?.contains(event.target))
|
|
56
|
+
place(); };
|
|
57
|
+
document.addEventListener('scroll', scroll, true);
|
|
58
|
+
return () => { observer?.disconnect(); window.removeEventListener('resize', place); document.removeEventListener('scroll', scroll, true); };
|
|
59
|
+
}, [open, preferred]);
|
|
60
|
+
return { root, panel, open, setOpen, style };
|
|
61
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Container exec transport for one delegation round: rewrite a CLI launch
|
|
3
|
+
* (argv + explicit env layer) into the `docker exec` form that runs the SAME
|
|
4
|
+
* argv inside an already-acquired container. Nothing downstream of the spawn
|
|
5
|
+
* changes — stdio stays piped, the stream parsers, settle chain, readback and
|
|
6
|
+
* delegation recording are byte-for-byte the host path's.
|
|
7
|
+
*
|
|
8
|
+
* The transport owns exactly one docker verb, `exec`. Acquiring, inspecting,
|
|
9
|
+
* mounting and destroying a container belong to the caller (lab); a provider
|
|
10
|
+
* that reached for another verb would be running an experiment nobody
|
|
11
|
+
* declared.
|
|
12
|
+
* @module @khorsheed/dsh-local-agent/container
|
|
13
|
+
*/
|
|
14
|
+
import type { DelegationExecTarget } from './types.ts';
|
|
15
|
+
/** One CLI launch: the argv a provider would spawn and its explicit env layer. */
|
|
16
|
+
export interface ContainerExecLaunch {
|
|
17
|
+
/** The CLI argv (`['codex', 'exec', …]`), unchanged by the transport. */
|
|
18
|
+
readonly argv: readonly string[];
|
|
19
|
+
/**
|
|
20
|
+
* The provider's explicit env layer as {@link delegationEnv} built it:
|
|
21
|
+
* defined entries are the round's own variables, `undefined` entries are
|
|
22
|
+
* tombstones for the shared scrub and carry no value to forward.
|
|
23
|
+
*/
|
|
24
|
+
readonly env: Readonly<NodeJS.ProcessEnv>;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Rewrite a host CLI launch into its container form:
|
|
28
|
+
* `docker exec -w <workdir> [-e NAME…] <container> <original argv>`.
|
|
29
|
+
*
|
|
30
|
+
* **Values never ride the argv.** Each forwarded variable appears as a
|
|
31
|
+
* NAME-only `-e` flag, and the docker CLI resolves it from its OWN
|
|
32
|
+
* environment — which this function also returns. A credential the provider
|
|
33
|
+
* resolved (the sub-dsh API key) therefore stays out of the host process
|
|
34
|
+
* table, exactly as it does on the host path. Names are sorted, so one launch
|
|
35
|
+
* always produces one argv.
|
|
36
|
+
*
|
|
37
|
+
* What gets forwarded: every DEFINED entry of the provider's explicit env
|
|
38
|
+
* layer (tombstones carry no value and are dropped), overridden per key by
|
|
39
|
+
* the target's own `env` — the caller's entries are the in-container truth
|
|
40
|
+
* (`CODEX_HOME=/creds/codex` replacing the host scoped-home path). The
|
|
41
|
+
* ambient allowlist (`PATH`, `HOME`, the proxy variables) is deliberately NOT
|
|
42
|
+
* forwarded: inside the container those belong to the image and the `docker
|
|
43
|
+
* run` that created it, not to the host that is driving it.
|
|
44
|
+
* @param target - the container, in-container workdir, and env overrides.
|
|
45
|
+
* @param launch - the argv and explicit env layer of the host launch.
|
|
46
|
+
* @param who - the provider name, for error messages.
|
|
47
|
+
* @returns the argv and env for `ctx.subprocess.spawn` (the docker client).
|
|
48
|
+
*/
|
|
49
|
+
export declare function containerExecSpawn(target: DelegationExecTarget, launch: ContainerExecLaunch, who: string): {
|
|
50
|
+
argv: string[];
|
|
51
|
+
env: NodeJS.ProcessEnv;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Require the caller to name the CLI's scoped home as an IN-CONTAINER path.
|
|
55
|
+
* Every harness points its CLI at a scoped home through one environment
|
|
56
|
+
* variable, and the provider's own value is a host path: forwarded verbatim
|
|
57
|
+
* it would send the CLI to a directory that does not exist inside the unit,
|
|
58
|
+
* where it would silently start from an empty state — no credentials, no
|
|
59
|
+
* rollout to read back, and a delegation that fails for a reason nothing in
|
|
60
|
+
* the output names. The caller bind-mounts the host scoped home and declares
|
|
61
|
+
* where it landed; a target that forgets fails before anything spawns.
|
|
62
|
+
* @param target - the caller's exec target.
|
|
63
|
+
* @param key - the harness's scoped-home variable (`CODEX_HOME`, …).
|
|
64
|
+
* @param who - the provider name, for the error message.
|
|
65
|
+
* @returns the declared in-container scoped home.
|
|
66
|
+
* @throws when the target does not declare the variable.
|
|
67
|
+
*/
|
|
68
|
+
export declare function containerScopedHome(target: DelegationExecTarget, key: string, who: string): string;
|
|
69
|
+
//# sourceMappingURL=container.d.ts.map
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Container exec transport for one delegation round: rewrite a CLI launch
|
|
3
|
+
* (argv + explicit env layer) into the `docker exec` form that runs the SAME
|
|
4
|
+
* argv inside an already-acquired container. Nothing downstream of the spawn
|
|
5
|
+
* changes — stdio stays piped, the stream parsers, settle chain, readback and
|
|
6
|
+
* delegation recording are byte-for-byte the host path's.
|
|
7
|
+
*
|
|
8
|
+
* The transport owns exactly one docker verb, `exec`. Acquiring, inspecting,
|
|
9
|
+
* mounting and destroying a container belong to the caller (lab); a provider
|
|
10
|
+
* that reached for another verb would be running an experiment nobody
|
|
11
|
+
* declared.
|
|
12
|
+
* @module @khorsheed/dsh-local-agent/container
|
|
13
|
+
*/
|
|
14
|
+
import { delegationEnv } from './env.js';
|
|
15
|
+
/**
|
|
16
|
+
* Docker client configuration a delegation must not lose when the CLI moves
|
|
17
|
+
* into a container: without them the `docker exec` would talk to the wrong
|
|
18
|
+
* daemon (or none). They are read from the HOST process environment, apply to
|
|
19
|
+
* the docker CLI itself, and never reach the container.
|
|
20
|
+
*/
|
|
21
|
+
const DOCKER_CLIENT_ENV = [
|
|
22
|
+
'DOCKER_HOST',
|
|
23
|
+
'DOCKER_CONTEXT',
|
|
24
|
+
'DOCKER_CONFIG',
|
|
25
|
+
'DOCKER_API_VERSION',
|
|
26
|
+
'DOCKER_CERT_PATH',
|
|
27
|
+
'DOCKER_TLS_VERIFY',
|
|
28
|
+
];
|
|
29
|
+
/** A valid POSIX environment variable name (no `=`, no NUL, not empty). */
|
|
30
|
+
const ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
31
|
+
/**
|
|
32
|
+
* Validate one exec target, failing loud on the shapes docker would reject
|
|
33
|
+
* later (or, worse, misread as its own flags).
|
|
34
|
+
* @param target - the caller's exec target.
|
|
35
|
+
* @param who - the provider name, for the error message.
|
|
36
|
+
* @throws when the container name or workdir is empty, flag-shaped, or (for
|
|
37
|
+
* the workdir) not absolute, or when an env key is not a variable name.
|
|
38
|
+
*/
|
|
39
|
+
function assertTarget(target, who) {
|
|
40
|
+
if (target.container === '' || target.container.startsWith('-')) {
|
|
41
|
+
throw new Error(`${who}: the container exec target needs a container name, got ${JSON.stringify(target.container)}`);
|
|
42
|
+
}
|
|
43
|
+
if (!target.workdir.startsWith('/')) {
|
|
44
|
+
throw new Error(`${who}: the container exec target's workdir must be an absolute in-container path, got ${JSON.stringify(target.workdir)}`);
|
|
45
|
+
}
|
|
46
|
+
for (const key of Object.keys(target.env ?? {})) {
|
|
47
|
+
if (!ENV_NAME.test(key)) {
|
|
48
|
+
throw new Error(`${who}: ${JSON.stringify(key)} is not a usable environment variable name for the container exec target`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Rewrite a host CLI launch into its container form:
|
|
54
|
+
* `docker exec -w <workdir> [-e NAME…] <container> <original argv>`.
|
|
55
|
+
*
|
|
56
|
+
* **Values never ride the argv.** Each forwarded variable appears as a
|
|
57
|
+
* NAME-only `-e` flag, and the docker CLI resolves it from its OWN
|
|
58
|
+
* environment — which this function also returns. A credential the provider
|
|
59
|
+
* resolved (the sub-dsh API key) therefore stays out of the host process
|
|
60
|
+
* table, exactly as it does on the host path. Names are sorted, so one launch
|
|
61
|
+
* always produces one argv.
|
|
62
|
+
*
|
|
63
|
+
* What gets forwarded: every DEFINED entry of the provider's explicit env
|
|
64
|
+
* layer (tombstones carry no value and are dropped), overridden per key by
|
|
65
|
+
* the target's own `env` — the caller's entries are the in-container truth
|
|
66
|
+
* (`CODEX_HOME=/creds/codex` replacing the host scoped-home path). The
|
|
67
|
+
* ambient allowlist (`PATH`, `HOME`, the proxy variables) is deliberately NOT
|
|
68
|
+
* forwarded: inside the container those belong to the image and the `docker
|
|
69
|
+
* run` that created it, not to the host that is driving it.
|
|
70
|
+
* @param target - the container, in-container workdir, and env overrides.
|
|
71
|
+
* @param launch - the argv and explicit env layer of the host launch.
|
|
72
|
+
* @param who - the provider name, for error messages.
|
|
73
|
+
* @returns the argv and env for `ctx.subprocess.spawn` (the docker client).
|
|
74
|
+
*/
|
|
75
|
+
export function containerExecSpawn(target, launch, who) {
|
|
76
|
+
assertTarget(target, who);
|
|
77
|
+
const forwarded = {};
|
|
78
|
+
for (const [key, value] of Object.entries(launch.env)) {
|
|
79
|
+
if (value !== undefined && ENV_NAME.test(key))
|
|
80
|
+
forwarded[key] = value;
|
|
81
|
+
}
|
|
82
|
+
for (const [key, value] of Object.entries(target.env ?? {}))
|
|
83
|
+
forwarded[key] = value;
|
|
84
|
+
const names = Object.keys(forwarded).sort();
|
|
85
|
+
const client = {};
|
|
86
|
+
for (const key of DOCKER_CLIENT_ENV) {
|
|
87
|
+
const value = process.env[key];
|
|
88
|
+
if (value !== undefined)
|
|
89
|
+
client[key] = value;
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
argv: [
|
|
93
|
+
'docker',
|
|
94
|
+
'exec',
|
|
95
|
+
'-w',
|
|
96
|
+
target.workdir,
|
|
97
|
+
...names.flatMap(name => ['-e', name]),
|
|
98
|
+
target.container,
|
|
99
|
+
...launch.argv,
|
|
100
|
+
],
|
|
101
|
+
// The docker client is the process being spawned, so it carries the
|
|
102
|
+
// forwarded VALUES (the `-e NAME` flags read them here) plus its own
|
|
103
|
+
// daemon coordinates, under the same scrub the host path uses.
|
|
104
|
+
env: delegationEnv({ ...client, ...forwarded }),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Require the caller to name the CLI's scoped home as an IN-CONTAINER path.
|
|
109
|
+
* Every harness points its CLI at a scoped home through one environment
|
|
110
|
+
* variable, and the provider's own value is a host path: forwarded verbatim
|
|
111
|
+
* it would send the CLI to a directory that does not exist inside the unit,
|
|
112
|
+
* where it would silently start from an empty state — no credentials, no
|
|
113
|
+
* rollout to read back, and a delegation that fails for a reason nothing in
|
|
114
|
+
* the output names. The caller bind-mounts the host scoped home and declares
|
|
115
|
+
* where it landed; a target that forgets fails before anything spawns.
|
|
116
|
+
* @param target - the caller's exec target.
|
|
117
|
+
* @param key - the harness's scoped-home variable (`CODEX_HOME`, …).
|
|
118
|
+
* @param who - the provider name, for the error message.
|
|
119
|
+
* @returns the declared in-container scoped home.
|
|
120
|
+
* @throws when the target does not declare the variable.
|
|
121
|
+
*/
|
|
122
|
+
export function containerScopedHome(target, key, who) {
|
|
123
|
+
const value = target.env?.[key];
|
|
124
|
+
if (value === undefined || value === '') {
|
|
125
|
+
throw new Error(`${who}: a container exec target must declare ${key} as the in-container path of the scoped home `
|
|
126
|
+
+ '(bind-mount the host scoped home read-write and name its mount point) — the host path means nothing inside the unit');
|
|
127
|
+
}
|
|
128
|
+
return value;
|
|
129
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build the explicit `env` layer of a delegation spawn spec: the caller's
|
|
3
|
+
* entries (scoped home variable, credentials, endpoint overrides) plus an
|
|
4
|
+
* `undefined` tombstone for every ambient key outside the platform's
|
|
5
|
+
* inheritance allowlist. The result only makes sense as a
|
|
6
|
+
* `SubprocessSpawnSpec.env` — the subprocess seam merges it over
|
|
7
|
+
* `scrubbedParentEnv()`, so allowlisted keys arrive by inheritance and never
|
|
8
|
+
* appear here, while tombstoned keys are removed from the child. Caller's
|
|
9
|
+
* entries always win, including over the allowlist. Anything the child truly
|
|
10
|
+
* needs beyond the allowlist must be passed explicitly by the provider, which
|
|
11
|
+
* is the point: the delegation env is reviewed, not inherited by accident.
|
|
12
|
+
* @param extra - the provider's explicit entries (and any deliberate
|
|
13
|
+
* tombstones) for this spawn.
|
|
14
|
+
* @returns the explicit env layer to hand to `ctx.subprocess.spawn`.
|
|
15
|
+
*/
|
|
16
|
+
export declare function delegationEnv(extra: Readonly<NodeJS.ProcessEnv>): NodeJS.ProcessEnv;
|
|
17
|
+
//# sourceMappingURL=env.d.ts.map
|
package/lib/types/env.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { scrubbedParentEnv } from '@deepseek-ai/dsh-subprocess';
|
|
2
|
+
/**
|
|
3
|
+
* Ambient keys a delegated CLI child may inherit on POSIX. Everything else the
|
|
4
|
+
* harness process carries is tombstoned: the CLIs read far more of the
|
|
5
|
+
* environment than they document (claude 2.1.236's credential resolution
|
|
6
|
+
* breaks when `USER` is present — bisected on a live host), so inheriting by
|
|
7
|
+
* default turns every host-specific variable into a per-harness incident.
|
|
8
|
+
* `USER`/`LOGNAME` are deliberately absent; git resolves the author from
|
|
9
|
+
* `HOME`'s gitconfig. Proxy variables pass through in both casings.
|
|
10
|
+
*/
|
|
11
|
+
const POSIX_INHERITED = new Set([
|
|
12
|
+
'PATH',
|
|
13
|
+
'HOME',
|
|
14
|
+
'TMPDIR',
|
|
15
|
+
'SHELL',
|
|
16
|
+
'TERM',
|
|
17
|
+
'LANG',
|
|
18
|
+
'LC_ALL',
|
|
19
|
+
'LC_CTYPE',
|
|
20
|
+
'LC_MESSAGES',
|
|
21
|
+
'XDG_CONFIG_HOME',
|
|
22
|
+
'XDG_DATA_HOME',
|
|
23
|
+
'XDG_CACHE_HOME',
|
|
24
|
+
'XDG_RUNTIME_DIR',
|
|
25
|
+
'SSH_AUTH_SOCK',
|
|
26
|
+
'NO_COLOR',
|
|
27
|
+
'GIT_TERMINAL_PROMPT',
|
|
28
|
+
'http_proxy',
|
|
29
|
+
'https_proxy',
|
|
30
|
+
'all_proxy',
|
|
31
|
+
'no_proxy',
|
|
32
|
+
'HTTP_PROXY',
|
|
33
|
+
'HTTPS_PROXY',
|
|
34
|
+
'ALL_PROXY',
|
|
35
|
+
'NO_PROXY',
|
|
36
|
+
]);
|
|
37
|
+
/**
|
|
38
|
+
* The Windows counterpart, matched case-insensitively per the platform's
|
|
39
|
+
* environment semantics. `USERNAME` is excluded for symmetry with POSIX
|
|
40
|
+
* `USER`.
|
|
41
|
+
*/
|
|
42
|
+
const WINDOWS_INHERITED = new Set([
|
|
43
|
+
'PATH',
|
|
44
|
+
'PATHEXT',
|
|
45
|
+
'SYSTEMROOT',
|
|
46
|
+
'SYSTEMDRIVE',
|
|
47
|
+
'WINDIR',
|
|
48
|
+
'COMSPEC',
|
|
49
|
+
'TEMP',
|
|
50
|
+
'TMP',
|
|
51
|
+
'USERPROFILE',
|
|
52
|
+
'HOMEDRIVE',
|
|
53
|
+
'HOMEPATH',
|
|
54
|
+
'APPDATA',
|
|
55
|
+
'LOCALAPPDATA',
|
|
56
|
+
'PROGRAMDATA',
|
|
57
|
+
'HTTP_PROXY',
|
|
58
|
+
'HTTPS_PROXY',
|
|
59
|
+
'ALL_PROXY',
|
|
60
|
+
'NO_PROXY',
|
|
61
|
+
]);
|
|
62
|
+
/**
|
|
63
|
+
* Build the explicit `env` layer of a delegation spawn spec: the caller's
|
|
64
|
+
* entries (scoped home variable, credentials, endpoint overrides) plus an
|
|
65
|
+
* `undefined` tombstone for every ambient key outside the platform's
|
|
66
|
+
* inheritance allowlist. The result only makes sense as a
|
|
67
|
+
* `SubprocessSpawnSpec.env` — the subprocess seam merges it over
|
|
68
|
+
* `scrubbedParentEnv()`, so allowlisted keys arrive by inheritance and never
|
|
69
|
+
* appear here, while tombstoned keys are removed from the child. Caller's
|
|
70
|
+
* entries always win, including over the allowlist. Anything the child truly
|
|
71
|
+
* needs beyond the allowlist must be passed explicitly by the provider, which
|
|
72
|
+
* is the point: the delegation env is reviewed, not inherited by accident.
|
|
73
|
+
* @param extra - the provider's explicit entries (and any deliberate
|
|
74
|
+
* tombstones) for this spawn.
|
|
75
|
+
* @returns the explicit env layer to hand to `ctx.subprocess.spawn`.
|
|
76
|
+
*/
|
|
77
|
+
export function delegationEnv(extra) {
|
|
78
|
+
const windows = process.platform === 'win32';
|
|
79
|
+
const normalize = (key) => (windows ? key.toUpperCase() : key);
|
|
80
|
+
const explicit = new Set(Object.keys(extra).map(normalize));
|
|
81
|
+
const inherited = windows ? WINDOWS_INHERITED : POSIX_INHERITED;
|
|
82
|
+
const env = {};
|
|
83
|
+
for (const key of Object.keys(scrubbedParentEnv())) {
|
|
84
|
+
if (explicit.has(normalize(key)) || inherited.has(normalize(key)))
|
|
85
|
+
continue;
|
|
86
|
+
env[key] = undefined;
|
|
87
|
+
}
|
|
88
|
+
return { ...env, ...extra };
|
|
89
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read-only Remote channel for the local-agent family: roster, per-harness
|
|
3
|
+
* auth status, and scoped session records. The web client polls these through
|
|
4
|
+
* Typert Gateway instead of the slash-command channel, so UI refreshes never
|
|
5
|
+
* leave `command/run`/`command/done` lifecycle nodes in the session log.
|
|
6
|
+
* Sign-out and login stay on the command channel: they are user-initiated
|
|
7
|
+
* actions whose visible command node is the expected feedback.
|
|
8
|
+
* @module @khorsheed/dsh-local-agent/gateway
|
|
9
|
+
*/
|
|
10
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
11
|
+
import { TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
|
|
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';
|
|
14
|
+
export default class LocalAgentGateway extends TypertRemoteService {
|
|
15
|
+
static inject: string[];
|
|
16
|
+
constructor(ctx: Context);
|
|
17
|
+
/**
|
|
18
|
+
* Follow one member's transient output with a complete reconnect baseline.
|
|
19
|
+
* @param childSessionId - mirrored session identity.
|
|
20
|
+
* @param signal - Remote-owned cancellation, including browser disconnects.
|
|
21
|
+
* @returns bounded incremental updates; final content remains in session history.
|
|
22
|
+
*/
|
|
23
|
+
followMemberOutput(childSessionId: string, signal: AbortSignal): AsyncIterable<LocalAgentStreamFrame>;
|
|
24
|
+
/** Shared browser stream: adding visible members does not consume more HTTP connections. */
|
|
25
|
+
followMembers(requests: readonly LocalAgentMemberFeedRequest[], signal: AbortSignal): AsyncIterable<LocalAgentMemberFeedEvent>;
|
|
26
|
+
private memberFeed;
|
|
27
|
+
private directoryBroker;
|
|
28
|
+
/**
|
|
29
|
+
* Read or explicitly refresh native model discovery, without changing a member.
|
|
30
|
+
* @param name - owning harness.
|
|
31
|
+
* @param childSessionId - member context, or undefined for the harness default.
|
|
32
|
+
* @param refresh - bypass the discovery TTL.
|
|
33
|
+
* @returns source-labelled discovery state, or null for an unavailable broker.
|
|
34
|
+
*/
|
|
35
|
+
modelDirectory(name: string, childSessionId: string | undefined, refresh: boolean): Promise<LocalAgentModelDirectory | null>;
|
|
36
|
+
/**
|
|
37
|
+
* Keep a currently open model picker synchronized with discovery completion.
|
|
38
|
+
* @param name - owning harness.
|
|
39
|
+
* @param childSessionId - member context, or undefined for the harness default.
|
|
40
|
+
* @param signal - Remote-owned cancellation.
|
|
41
|
+
* @returns directory baselines and subsequent snapshots.
|
|
42
|
+
*/
|
|
43
|
+
followModelDirectory(name: string, childSessionId: string | undefined, signal: AbortSignal): AsyncIterable<LocalAgentModelDirectory>;
|
|
44
|
+
/** Resolve directory ownership from the recorded member, never a UI provider guess. */
|
|
45
|
+
memberDirectory(childSessionId: string, refresh: boolean): Promise<LocalAgentModelDirectory | null>;
|
|
46
|
+
followMemberDirectory(childSessionId: string, signal: AbortSignal): AsyncIterable<LocalAgentModelDirectory>;
|
|
47
|
+
/**
|
|
48
|
+
* Registered harnesses in registration order.
|
|
49
|
+
* @returns the roster rows.
|
|
50
|
+
*/
|
|
51
|
+
roster(): readonly LocalAgentRosterRow[];
|
|
52
|
+
/**
|
|
53
|
+
* The child session ids with an in-flight delegation run (the
|
|
54
|
+
* active-delegation registry's key set). Surfaces that render one-shot
|
|
55
|
+
* subagent rows poll this to mark a row as actually running — the official
|
|
56
|
+
* session summary's `running` flag is agent-based and stays false for
|
|
57
|
+
* external CLI one-shots, which have no live agent — and to offer the
|
|
58
|
+
* stop verb that dispatches `/local-agent stop <childSessionId>`.
|
|
59
|
+
* @returns the in-flight child session ids.
|
|
60
|
+
*/
|
|
61
|
+
activeDelegations(): readonly string[];
|
|
62
|
+
/**
|
|
63
|
+
* One harness's auth status, in the default scope or in a named one.
|
|
64
|
+
* @param name - the harness name.
|
|
65
|
+
* @param scope - the named scope to report on; absent means the default
|
|
66
|
+
* scope, so a client written before scopes existed gets exactly the
|
|
67
|
+
* snapshot it always got.
|
|
68
|
+
* @returns the status snapshot.
|
|
69
|
+
*/
|
|
70
|
+
status(name: string, scope?: string): Promise<LocalAgentStatus>;
|
|
71
|
+
/**
|
|
72
|
+
* One harness's scoped session records, optionally narrowed to the sessions
|
|
73
|
+
* whose working directory is a given dsh session's cwd — the delegations a
|
|
74
|
+
* session's workspace actually produced, so one project's kimi sessions do
|
|
75
|
+
* not surface in another project's session.
|
|
76
|
+
* @param name - the harness name.
|
|
77
|
+
* @param sessionId - optional dsh session whose cwd filters the records.
|
|
78
|
+
* @returns the matching session records.
|
|
79
|
+
*/
|
|
80
|
+
sessions(name: string, sessionId?: string): Promise<readonly LocalAgentSessionRecord[]>;
|
|
81
|
+
/**
|
|
82
|
+
* The member channel's membership check: the delegation view for one dsh
|
|
83
|
+
* child session, or null when the family never delegated it. The composer
|
|
84
|
+
* chain elects on the session snapshot alone (one-shot subagent), then
|
|
85
|
+
* confirms membership through this Remote — a null answer renders the same
|
|
86
|
+
* read-only panel the official composer shows, never a writable box.
|
|
87
|
+
* The CLI-session resume handle (`cliSessionId`) deliberately stays off the
|
|
88
|
+
* wire: the resume facade resolves it host-side from the record.
|
|
89
|
+
* @param childSessionId - the dsh child session id being viewed.
|
|
90
|
+
* @returns the delegation view, or null for a non-member session.
|
|
91
|
+
*/
|
|
92
|
+
memberOf(childSessionId: string): LocalAgentDelegationView | null;
|
|
93
|
+
/**
|
|
94
|
+
* The model surface of one harness without a member — the settings card's
|
|
95
|
+
* "what would a round run with" read. Null when the harness is unknown or
|
|
96
|
+
* exposes no model broker: the card then keeps its free-text field exactly
|
|
97
|
+
* as before brokers existed. The core fills `lastObserved` from the
|
|
98
|
+
* delegation records (the harness's own provider) when the broker's answer
|
|
99
|
+
* carries none — display context for the cli-builtin layer, never a layer —
|
|
100
|
+
* and appends it to `choices` when no broker choice lists it, so the card's
|
|
101
|
+
* menu offers what last ran instead of staying empty.
|
|
102
|
+
* @param name - the harness name.
|
|
103
|
+
* @returns the layer-by-layer surface, or null.
|
|
104
|
+
*/
|
|
105
|
+
harnessModel(name: string): Promise<LocalAgentModelInfo | null>;
|
|
106
|
+
/**
|
|
107
|
+
* The model surface of one member — the composer's picker read. The
|
|
108
|
+
* delegation record's requested model rides in from the core so the broker
|
|
109
|
+
* can rank it between the override and the settings layer; the record's
|
|
110
|
+
* OBSERVED model fills `lastObserved` when the broker's answer carries none
|
|
111
|
+
* (a broker that names one itself always wins — spread order: the broker's
|
|
112
|
+
* answer first, the core's fill only over an absent field) and joins
|
|
113
|
+
* `choices` as the last entry when no broker choice lists it, so a member
|
|
114
|
+
* that ran once has a one-item pick list instead of an empty menu. Null for a
|
|
115
|
+
* non-member session or a brokerless harness.
|
|
116
|
+
* @param childSessionId - the dsh child session id of the member.
|
|
117
|
+
* @returns the layer-by-layer surface, or null.
|
|
118
|
+
*/
|
|
119
|
+
memberModel(childSessionId: string): Promise<LocalAgentModelInfo | null>;
|
|
120
|
+
/**
|
|
121
|
+
* Switch a member's session-level model (the composer's picker write). The
|
|
122
|
+
* override outranks the delegation's recorded model and the settings
|
|
123
|
+
* layer; with the live driver on it retires the member's resident runtime
|
|
124
|
+
* so the NEXT round respawns onto the new model — the CLI session itself
|
|
125
|
+
* (its rollout) carries over. Failures arrive structured, never raw.
|
|
126
|
+
* @param childSessionId - the dsh child session id of the member.
|
|
127
|
+
* @param model - the model identifier, or undefined to clear the override
|
|
128
|
+
* (the member then follows the settings layer again).
|
|
129
|
+
* @returns `{ ok: true }` once applied, or a structured error.
|
|
130
|
+
*/
|
|
131
|
+
setMemberModel(childSessionId: string, model?: string): Promise<LocalAgentPromptResult>;
|
|
132
|
+
memberConfiguration(childSessionId: string): Promise<LocalAgentMemberControlState>;
|
|
133
|
+
selectMemberConfiguration(childSessionId: string, requestId: string, expectedRevision: number, selection: LocalAgentMemberConfiguration): Promise<LocalAgentControlReceipt>;
|
|
134
|
+
cancelMemberConfiguration(childSessionId: string, requestId: string, expectedRevision: number): Promise<LocalAgentControlReceipt>;
|
|
135
|
+
retryMemberConfiguration(childSessionId: string, expectedRevision: number): Promise<void>;
|
|
136
|
+
followMemberConfiguration(childSessionId: string, signal: AbortSignal): AsyncIterable<LocalAgentMemberControlState>;
|
|
137
|
+
/**
|
|
138
|
+
* Send one human-authored follow-up to a family member: continue the
|
|
139
|
+
* member's SAME CLI session inside the SAME dsh child session. The child
|
|
140
|
+
* session id is the membership proof — the user opened the real child
|
|
141
|
+
* session, and the recorded delegation is the authorization source — so
|
|
142
|
+
* there is no model-forged handle surface; the resume handle never enters
|
|
143
|
+
* the prompt text. The prompt goes through the facade
|
|
144
|
+
* ({@link LocalAgentRegistry.resume}) unchanged: ownership check, resume
|
|
145
|
+
* lock, reattach, and run tracking all apply.
|
|
146
|
+
* @param childSessionId - the dsh child session id of the member.
|
|
147
|
+
* @param text - the human's follow-up message.
|
|
148
|
+
* @returns `{ ok: true }` once the run started, or a structured error
|
|
149
|
+
* (unknown member, parent not live, resume in flight) for the composer to
|
|
150
|
+
* render inline — raw exceptions never cross the wire.
|
|
151
|
+
*/
|
|
152
|
+
promptMember(childSessionId: string, text: string, requestId?: string): Promise<LocalAgentPromptResult>;
|
|
153
|
+
memberInbox(childSessionId: string): LocalAgentMemberInbox;
|
|
154
|
+
controlMemberInbox(childSessionId: string, action: 'pause' | 'resume' | 'cancel' | 'reconcile', requestId?: string, outcome?: 'done' | 'cancelled', evidence?: string): LocalAgentPromptResult;
|
|
155
|
+
/**
|
|
156
|
+
* Interrupt a member's in-flight run (the composer's Stop).
|
|
157
|
+
* @param childSessionId - the dsh child session id of the member.
|
|
158
|
+
* @returns whether an in-flight run was found and signalled.
|
|
159
|
+
*/
|
|
160
|
+
stopMember(childSessionId: string): boolean;
|
|
161
|
+
}
|
|
162
|
+
//# sourceMappingURL=gateway.d.ts.map
|