@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,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
@@ -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