super-dsh 0.3.1 → 0.3.2
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/client/index.js +135 -14
- package/package.json +1 -1
- package/remote/helper-bin/MANIFEST.json +35 -0
- package/remote/helper-bin/aarch64-apple-darwin/BUILT-VER +1 -0
- package/remote/helper-bin/aarch64-apple-darwin/superd-run +0 -0
- package/remote/helper-bin/aarch64-unknown-linux-musl/BUILT-VER +1 -0
- package/remote/helper-bin/aarch64-unknown-linux-musl/superd-run +0 -0
- package/remote/helper-bin/x86_64-pc-windows-gnu/BUILT-VER +1 -0
- package/remote/helper-bin/x86_64-pc-windows-gnu/superd-run.exe +0 -0
- package/remote/helper-bin/x86_64-unknown-linux-musl/BUILT-VER +1 -0
- package/remote/helper-bin/x86_64-unknown-linux-musl/superd-run +0 -0
- package/remote/plugin.d.ts +172 -51
- package/remote/plugin.js +2161 -711
- package/remote/provenance.json +32 -20
- package/superd-hub/dist/a2a-host.d.ts +11 -3
- package/superd-hub/dist/a2a-host.js +7 -7
- package/superd-hub/dist/a2a-row.js +7 -1
- package/superd-hub/dist/carrier.d.ts +9 -7
- package/superd-hub/dist/carrier.js +3 -3
- package/superd-hub/dist/client/pill-geometry.d.ts +70 -0
- package/superd-hub/dist/client/pill-geometry.js +49 -0
- package/superd-hub/dist/client/switch.d.ts +15 -0
- package/superd-hub/dist/client/switch.js +54 -1
- package/superd-hub/dist/companions.js +8 -6
- package/superd-hub/dist/fusion-transport.d.ts +18 -1
- package/superd-hub/dist/fusion-transport.js +44 -5
- package/superd-hub/dist/gateway.js +8 -1
- package/superd-hub/dist/hub-route.d.ts +5 -2
- package/superd-hub/dist/hub-route.js +6 -6
- package/superd-hub/dist/rpc.d.ts +5 -0
- package/superd-hub/dist/rpc.js +20 -1
- package/superd-hub/dist/switch-core.d.ts +41 -0
- package/superd-hub/dist/switch-core.js +40 -0
- package/superd-hub/dist/targets.d.ts +7 -0
- package/superd-hub/dist/world-lifecycle.js +1 -1
- package/superd-hub/lib/client/index.js +135 -14
package/client/index.js
CHANGED
|
@@ -898,6 +898,16 @@ function machineLabelsFrom(list) {
|
|
|
898
898
|
* roster-probe failure → revert to native + a visible toast, never a page
|
|
899
899
|
* lock).
|
|
900
900
|
*
|
|
901
|
+
* Live-install staleness (2026-10-18): the fusion flag global and the
|
|
902
|
+
* transport script are baked into the SERVED index, so a page that booted
|
|
903
|
+
* before super-dsh was installed runs the freshly hot-loaded client half
|
|
904
|
+
* against a stale page — `fusionPage()` reads false and every switch
|
|
905
|
+
* affordance stays hidden until a manual restart. {@link fusionReloadDecision}
|
|
906
|
+
* is the pure decision behind the client half's heal: one roster answer later,
|
|
907
|
+
* reload the page once. The transport must precede app boot (its upload hook
|
|
908
|
+
* `__DSH_FILE_UPLOAD__` is read at construction), so a client-side flag flip
|
|
909
|
+
* can never arm a stale page honestly — the page must be re-served.
|
|
910
|
+
*
|
|
901
911
|
* Host-half on purpose: plain TS with every effect injected through
|
|
902
912
|
* {@link SwitchHost} — no DOM, no client imports — so `test/client-switch.test.mjs`
|
|
903
913
|
* imports it from `dist/` like every other host module. The client half
|
|
@@ -1343,6 +1353,22 @@ function switchAffordance(fusion) {
|
|
|
1343
1353
|
hint: FUSION_OFF_HINT
|
|
1344
1354
|
};
|
|
1345
1355
|
}
|
|
1356
|
+
/** The sessionStorage marker guarding the stale-page reload: one per tab session. */
|
|
1357
|
+
const FUSION_RELOAD_MARKER = "superd.fusion-armed.reload";
|
|
1358
|
+
/**
|
|
1359
|
+
* The live-install staleness decision (host-half pure so the node tests pin
|
|
1360
|
+
* the table from `dist/`). The host's `hub.fusion` field is the ONLY thing
|
|
1361
|
+
* that distinguishes a page-booted-before-install from a deliberately
|
|
1362
|
+
* flag-off deployment — without it the reload would loop forever on every
|
|
1363
|
+
* `enabled: false` composition (the client half loads there too).
|
|
1364
|
+
*/
|
|
1365
|
+
function fusionReloadDecision(input) {
|
|
1366
|
+
if (input.flagPresent) return "armed";
|
|
1367
|
+
if (input.markerPresent) return "already-reloaded";
|
|
1368
|
+
if (input.hubFusion === true) return "reload";
|
|
1369
|
+
if (input.hubFusion === false) return "row-off";
|
|
1370
|
+
return "host-silent";
|
|
1371
|
+
}
|
|
1346
1372
|
/**
|
|
1347
1373
|
* The active agent's display name for the Hub row's right-edge selector: the
|
|
1348
1374
|
* roster label of the atom's `activeAgent` key — the name the selector's
|
|
@@ -1621,6 +1647,53 @@ function createReadinessWaiter(faces, options = {}) {
|
|
|
1621
1647
|
function fusionPage() {
|
|
1622
1648
|
return globalThis.__SUPERD_UI_FUSION__ === true;
|
|
1623
1649
|
}
|
|
1650
|
+
/**
|
|
1651
|
+
* The sessionStorage marker read (undefined-safe: a storage failure is
|
|
1652
|
+
* `false` — the probe then reloads without the loop guard, which only
|
|
1653
|
+
* matters for a host that reports fusion but never bakes the flag).
|
|
1654
|
+
*/
|
|
1655
|
+
function staleReloadMarker() {
|
|
1656
|
+
try {
|
|
1657
|
+
return globalThis.sessionStorage?.getItem(FUSION_RELOAD_MARKER) !== null;
|
|
1658
|
+
} catch {
|
|
1659
|
+
return false;
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
/**
|
|
1663
|
+
* Live-install staleness probe (2026-10-18): the ONE call the client entry
|
|
1664
|
+
* makes when the page does not carry the fusion flag. A flag-absent page is
|
|
1665
|
+
* either a pre-install page (the client half hot-loaded into HTML served
|
|
1666
|
+
* before the row mounted — the fresh-install case where the Hub row renders
|
|
1667
|
+
* but every switch affordance stays hidden until a manual restart) or a
|
|
1668
|
+
* deliberate flag-off deployment (T-E). The roster's additive `hub.fusion`
|
|
1669
|
+
* answers which; on an armed host the page re-boots ONCE — the marker keeps
|
|
1670
|
+
* a misbehaving host from ever looping the tab. Every failure mode (fetch
|
|
1671
|
+
* error, non-OK, old host without the field) degrades to no action: the
|
|
1672
|
+
* flag-off shapes below are the honest rendering of an unanswered probe.
|
|
1673
|
+
*
|
|
1674
|
+
* Fire-and-forget by contract — never throws, never blocks the apply.
|
|
1675
|
+
*/
|
|
1676
|
+
function probeStaleFusionPage() {
|
|
1677
|
+
(async () => {
|
|
1678
|
+
let hubFusion;
|
|
1679
|
+
try {
|
|
1680
|
+
const response = await fetch("/api/agent-runtime", { credentials: "same-origin" });
|
|
1681
|
+
if (!response.ok) return;
|
|
1682
|
+
hubFusion = (await response.json()).hub?.fusion;
|
|
1683
|
+
} catch {
|
|
1684
|
+
return;
|
|
1685
|
+
}
|
|
1686
|
+
if (fusionReloadDecision({
|
|
1687
|
+
flagPresent: fusionPage(),
|
|
1688
|
+
markerPresent: staleReloadMarker(),
|
|
1689
|
+
hubFusion
|
|
1690
|
+
}) !== "reload") return;
|
|
1691
|
+
try {
|
|
1692
|
+
globalThis.sessionStorage?.setItem(FUSION_RELOAD_MARKER, "1");
|
|
1693
|
+
} catch {}
|
|
1694
|
+
globalThis.location?.reload();
|
|
1695
|
+
})();
|
|
1696
|
+
}
|
|
1624
1697
|
/** The page atom, when the page carries one (it always does on a fusion page). */
|
|
1625
1698
|
function uiState() {
|
|
1626
1699
|
const state = globalThis.__SUPERD_UI_STATE__;
|
|
@@ -3880,6 +3953,38 @@ function HubPage() {
|
|
|
3880
3953
|
});
|
|
3881
3954
|
}
|
|
3882
3955
|
|
|
3956
|
+
//#endregion
|
|
3957
|
+
//#region src/client/pill-geometry.ts
|
|
3958
|
+
/**
|
|
3959
|
+
* Derive the scroll-invariant offsets from one same-frame measurement.
|
|
3960
|
+
* Measuring the DELTAS (not absolute coordinates) is the whole point: they
|
|
3961
|
+
* survive any scroll of any ancestor, so nothing needs to re-run while the
|
|
3962
|
+
* sidebar scrolls.
|
|
3963
|
+
*/
|
|
3964
|
+
function measurePillBox(input) {
|
|
3965
|
+
return {
|
|
3966
|
+
dx: input.row.right - input.wrap.left,
|
|
3967
|
+
cy: (input.row.top + input.row.bottom) / 2 - input.wrap.top
|
|
3968
|
+
};
|
|
3969
|
+
}
|
|
3970
|
+
/** The row's own horizontal padding (native `.panelRow`): the selector's inset. */
|
|
3971
|
+
const PILL_ROW_INSET = 8;
|
|
3972
|
+
/**
|
|
3973
|
+
* The pill's placement, wrapper-relative: its right edge sits `inset` px
|
|
3974
|
+
* inside the row's right edge, vertically centered on the row
|
|
3975
|
+
* (`translateY(-50%)`). `selectorWidth` is the render-time pill width (the
|
|
3976
|
+
* mark is present only while an agent is active), so it stays a render
|
|
3977
|
+
* concern; the measured box is not.
|
|
3978
|
+
*/
|
|
3979
|
+
function pillStyle(box, selectorWidth, inset = PILL_ROW_INSET) {
|
|
3980
|
+
return {
|
|
3981
|
+
position: "absolute",
|
|
3982
|
+
left: box.dx - inset - selectorWidth,
|
|
3983
|
+
top: box.cy,
|
|
3984
|
+
transform: "translateY(-50%)"
|
|
3985
|
+
};
|
|
3986
|
+
}
|
|
3987
|
+
|
|
3883
3988
|
//#endregion
|
|
3884
3989
|
//#region src/client/HubPanelIcon.tsx
|
|
3885
3990
|
/** Runtime-mark edge inside the selector, and the chevron's own default edge. */
|
|
@@ -3893,8 +3998,6 @@ const CHEVRON_SIZE = 14;
|
|
|
3893
3998
|
const SELECTOR_HEIGHT = 28;
|
|
3894
3999
|
const SELECTOR_PAD = 8;
|
|
3895
4000
|
const SELECTOR_GAP = 4;
|
|
3896
|
-
/** The row's own horizontal padding (native `.panelRow`): the selector's inset. */
|
|
3897
|
-
const ROW_INSET = 8;
|
|
3898
4001
|
function HubPanelIcon({ size }) {
|
|
3899
4002
|
const wide = size <= 16;
|
|
3900
4003
|
const rootRef = react.useRef(null);
|
|
@@ -3917,16 +4020,25 @@ function HubPanelIcon({ size }) {
|
|
|
3917
4020
|
}, [fusion]);
|
|
3918
4021
|
const measure = react.useCallback(() => {
|
|
3919
4022
|
const row = rootRef.current?.closest("button") ?? null;
|
|
3920
|
-
|
|
4023
|
+
const wrap = selectorRef.current?.parentElement ?? null;
|
|
4024
|
+
if (row === null || wrap === null) {
|
|
3921
4025
|
setBox(null);
|
|
3922
4026
|
return;
|
|
3923
4027
|
}
|
|
3924
|
-
const
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
4028
|
+
const rowRect = row.getBoundingClientRect();
|
|
4029
|
+
const wrapRect = wrap.getBoundingClientRect();
|
|
4030
|
+
const next = measurePillBox({
|
|
4031
|
+
row: {
|
|
4032
|
+
top: rowRect.top,
|
|
4033
|
+
bottom: rowRect.bottom,
|
|
4034
|
+
right: rowRect.right
|
|
4035
|
+
},
|
|
4036
|
+
wrap: {
|
|
4037
|
+
left: wrapRect.left,
|
|
4038
|
+
top: wrapRect.top
|
|
4039
|
+
}
|
|
3929
4040
|
});
|
|
4041
|
+
setBox((prev) => prev !== null && prev.dx === next.dx && prev.cy === next.cy ? prev : next);
|
|
3930
4042
|
}, []);
|
|
3931
4043
|
react.useLayoutEffect(() => {
|
|
3932
4044
|
if (!wide || !switchUi.available) return;
|
|
@@ -4036,7 +4148,7 @@ function HubPanelIcon({ size }) {
|
|
|
4036
4148
|
children: [
|
|
4037
4149
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(HubGlyph, { size }),
|
|
4038
4150
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SuperdToastHost, {}),
|
|
4039
|
-
wide &&
|
|
4151
|
+
wide && switchUi.available && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Menu, {
|
|
4040
4152
|
open,
|
|
4041
4153
|
onClose: () => {
|
|
4042
4154
|
setOpen(false);
|
|
@@ -4057,10 +4169,11 @@ function HubPanelIcon({ size }) {
|
|
|
4057
4169
|
"aria-expanded": open,
|
|
4058
4170
|
title: activeLabel === void 0 ? "Agent selector" : `Agent selector: ${activeLabel}`,
|
|
4059
4171
|
style: {
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4172
|
+
...pillStyle(box ?? {
|
|
4173
|
+
dx: 0,
|
|
4174
|
+
cy: 0
|
|
4175
|
+
}, selectorWidth),
|
|
4176
|
+
visibility: box === null ? "hidden" : "visible",
|
|
4064
4177
|
display: "inline-flex",
|
|
4065
4178
|
alignItems: "center",
|
|
4066
4179
|
justifyContent: "center",
|
|
@@ -4105,9 +4218,17 @@ const inject = ["slots"];
|
|
|
4105
4218
|
* absent (a composition without the sidebar renders nothing — every seat
|
|
4106
4219
|
* here is an optional occupant by contract).
|
|
4107
4220
|
*
|
|
4108
|
-
*
|
|
4221
|
+
* Live-install staleness: the fusion flag is baked into the SERVED index, so
|
|
4222
|
+
* a page that booted before super-dsh was installed carries no flag even
|
|
4223
|
+
* though this very apply is the hot-loaded client half — the Hub row would
|
|
4224
|
+
* render with every switch affordance hidden until a manual restart. When
|
|
4225
|
+
* the flag is absent the entry probes the roster's `hub.fusion` once and
|
|
4226
|
+
* re-boots the page on an armed host (switch.ts `probeStaleFusionPage`; the
|
|
4227
|
+
* sessionStorage marker bounds it to one reload per tab session). A flag-off
|
|
4228
|
+
* deployment answers `false` and keeps the T-E inert shape with no reload.
|
|
4109
4229
|
*/
|
|
4110
4230
|
function apply(ctx) {
|
|
4231
|
+
if (!fusionPage()) probeStaleFusionPage();
|
|
4111
4232
|
ctx.inject(["slots"], (raw) => {
|
|
4112
4233
|
const scope = raw;
|
|
4113
4234
|
scope.effect(() => scope.slots.inject("main", () => scope.slots.register({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "super-dsh",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "Super D for the DeepSeek Harness (DSH) — one dsh plugin that mounts the superd hub (runtime selector, roster, per-runtime agents) and joins local foreign-agent runtimes (OMP, Codex, Claude, Pi, Hermes, Antigravity) into the DSH Web UI. Each runtime keeps its native home and session store; the hub only bridges.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"format": 1,
|
|
3
|
+
"name": "superd-run",
|
|
4
|
+
"ver": "0.3.1-d.b596",
|
|
5
|
+
"protocol": 1,
|
|
6
|
+
"targets": {
|
|
7
|
+
"aarch64-apple-darwin": {
|
|
8
|
+
"file": "superd-run",
|
|
9
|
+
"bytes": 337808,
|
|
10
|
+
"sha256": "a8fd11a5e6a2cb146deb599414434a299ebf78c53cfc1d3e5f42617ee11c1706"
|
|
11
|
+
},
|
|
12
|
+
"aarch64-unknown-linux-musl": {
|
|
13
|
+
"file": "superd-run",
|
|
14
|
+
"bytes": 397592,
|
|
15
|
+
"sha256": "3a8660fa099f1e71de6e08313ee2aaace00503f74365475ee46e2d2a89228397"
|
|
16
|
+
},
|
|
17
|
+
"x86_64-pc-windows-gnu": {
|
|
18
|
+
"file": "superd-run.exe",
|
|
19
|
+
"bytes": 337408,
|
|
20
|
+
"sha256": "1372450238c5bda702b4529f4148aa79e80a255c3c125d1a6229afd4693e9d90"
|
|
21
|
+
},
|
|
22
|
+
"x86_64-unknown-linux-musl": {
|
|
23
|
+
"file": "superd-run",
|
|
24
|
+
"bytes": 447152,
|
|
25
|
+
"sha256": "05b428657942abf22a0ad609433d5cab6b6182fe389851345459a0274b5a6533"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"sources": {
|
|
29
|
+
"Cargo.toml": "129ab8360e9da872f9dfd6e713d81fe1389735a74d781f3d9c15e110f92d2757",
|
|
30
|
+
"build.rs": "c4fb356956b1ec700e99bdeca07d657a7d3b2a4026582db136411676da3d125e",
|
|
31
|
+
"src/main.rs": "47f1648ea1abc30583839edab219a9062c83d57be981c0505ab6bf79c6fbff54",
|
|
32
|
+
"src/unix.rs": "56c5f523b7b1a9efd50ef24ccdabc024bc065dd2f904fdbecdf0ca49fe7f3393",
|
|
33
|
+
"src/windows.rs": "6c1537424b1adc05ed17c7cf8bf376fb5711602b9da1697d596155bf21d541da"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.3.1-d.b596
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.3.1-d.b596
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.3.1-d.b596
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.3.1-d.b596
|
|
Binary file
|
package/remote/plugin.d.ts
CHANGED
|
@@ -90,16 +90,10 @@ interface SshConnection {
|
|
|
90
90
|
readonly key?: string;
|
|
91
91
|
readonly proxyJump?: string;
|
|
92
92
|
}
|
|
93
|
-
/** One instance per Agent-owned driver; destroyed with its Agent/plugin owners. */
|
|
94
|
-
declare class SshTargets {
|
|
95
|
-
private readonly targets;
|
|
96
|
-
register(label: string, connection: SshConnection): SshConnection;
|
|
97
|
-
get(label: string): SshConnection | undefined;
|
|
98
|
-
entries(): ReadonlyArray<readonly [string, SshConnection]>;
|
|
99
|
-
clear(): void;
|
|
100
|
-
}
|
|
101
93
|
/** Shared by execution, probes and file/relay consumers. extraArgs is trusted
|
|
102
|
-
* internal SSH argv (e.g. forwarding), never a model-provided shell string.
|
|
94
|
+
* internal SSH argv (e.g. forwarding), never a model-provided shell string.
|
|
95
|
+
* Identity assembly lives in ssh-argv.ts (the ONE policy — 2026-10-17 ruling);
|
|
96
|
+
* this wrapper only picks the tty/batch dialect base. */
|
|
103
97
|
declare function sshArgv(connection: SshConnection, options?: {
|
|
104
98
|
tty?: boolean;
|
|
105
99
|
extraArgs?: readonly string[];
|
|
@@ -117,7 +111,12 @@ interface TransferReceipt {
|
|
|
117
111
|
action: 'copy';
|
|
118
112
|
src: string;
|
|
119
113
|
dst: string;
|
|
120
|
-
|
|
114
|
+
route: 'hub-staged';
|
|
115
|
+
/** S4: tier actually performed — sha256 digests ran and matched, or explicitly skipped. */
|
|
116
|
+
verification: 'sha256' | 'not-applicable';
|
|
117
|
+
/** S3 unified failure envelope. */
|
|
118
|
+
ok: boolean;
|
|
119
|
+
reason?: 'digest-mismatch' | 'staging-error' | 'aborted';
|
|
121
120
|
sourceDigest?: string;
|
|
122
121
|
destinationDigest?: string;
|
|
123
122
|
bytes: number;
|
|
@@ -154,8 +153,16 @@ interface NativeCopyReceipt {
|
|
|
154
153
|
stdout: string[];
|
|
155
154
|
stderr: string[];
|
|
156
155
|
outputTruncated: boolean;
|
|
157
|
-
/**
|
|
158
|
-
|
|
156
|
+
/** S3/S4 (spec §3.3): verification names the tier actually performed — native routes are exit-code-only (D1=A). */
|
|
157
|
+
verification: 'native-exit-only';
|
|
158
|
+
/** S3 unified failure envelope: all native exits zero. */
|
|
159
|
+
ok: boolean;
|
|
160
|
+
/** Failure class when ok is false. */
|
|
161
|
+
reason?: 'non-zero-exit' | 'aborted';
|
|
162
|
+
/** Human context, e.g. two-segment 'push not started: pull failed' (prose moved out of route, S8). */
|
|
163
|
+
note?: string;
|
|
164
|
+
/** D3 mkdir pre-hop outcome: true = created this call, false = already existed, 'n-a' = skipped or unavailable (detail in note). */
|
|
165
|
+
created?: true | false | 'n-a';
|
|
159
166
|
/** Tool-owned staging/config release: released on normal/observed exit, unknown when a channel was lost. */
|
|
160
167
|
cleanup: 'released' | 'unknown' | 'none-required';
|
|
161
168
|
}
|
|
@@ -203,6 +210,10 @@ interface RsyncReceipt extends RunResult {
|
|
|
203
210
|
transferredBytes?: number;
|
|
204
211
|
/** Two-SSH route actually used, when applicable. */
|
|
205
212
|
route?: 'local-ssh' | 'ssh-ssh-forward' | 'ssh-ssh-stdio';
|
|
213
|
+
/** S3/S4: exit-code-only tier (D1=A) + unified failure envelope. */
|
|
214
|
+
verification: 'native-exit-only';
|
|
215
|
+
ok: boolean;
|
|
216
|
+
reason?: 'non-zero-exit' | 'aborted';
|
|
206
217
|
}
|
|
207
218
|
/**
|
|
208
219
|
* Native rsync with ordered caller options. Local↔SSH runs rsync directly with
|
|
@@ -213,6 +224,58 @@ interface RsyncReceipt extends RunResult {
|
|
|
213
224
|
*/
|
|
214
225
|
declare function runRsync(request: RsyncRequest, source: RsyncEndpoint, destination: RsyncEndpoint, signal: AbortSignal, options?: RelayRsyncOptions): Promise<RsyncReceipt>;
|
|
215
226
|
//#endregion
|
|
227
|
+
//#region src/remote/helper.d.ts
|
|
228
|
+
interface HelperManifest {
|
|
229
|
+
format: 1;
|
|
230
|
+
name: string;
|
|
231
|
+
ver: string;
|
|
232
|
+
protocol: number;
|
|
233
|
+
targets: Record<string, {
|
|
234
|
+
file: string;
|
|
235
|
+
bytes: number;
|
|
236
|
+
sha256: string;
|
|
237
|
+
}>;
|
|
238
|
+
}
|
|
239
|
+
interface HelperAssets {
|
|
240
|
+
dir: string;
|
|
241
|
+
manifest: HelperManifest;
|
|
242
|
+
}
|
|
243
|
+
//#endregion
|
|
244
|
+
//#region src/remote/nonce-framing.d.ts
|
|
245
|
+
interface EnvDelta {
|
|
246
|
+
added: string[];
|
|
247
|
+
changed: string[];
|
|
248
|
+
removed: string[];
|
|
249
|
+
}
|
|
250
|
+
//#endregion
|
|
251
|
+
//#region src/remote/pty-session.d.ts
|
|
252
|
+
/** detached job 的完成结果(frame 或会话死亡时结算;与 dispatch 回执同构)。 */
|
|
253
|
+
interface PtyJobResult {
|
|
254
|
+
output: string;
|
|
255
|
+
outputStats?: StreamOutputStats;
|
|
256
|
+
exit: number | null;
|
|
257
|
+
cwd: string | null;
|
|
258
|
+
timedOut: boolean;
|
|
259
|
+
commandId: string;
|
|
260
|
+
envDelta?: EnvDelta;
|
|
261
|
+
}
|
|
262
|
+
/** P4/P5(2026-10-19)running job 句柄:byte-cursor 增量读 + P8 显式中断 + 完成时帧结算。 */
|
|
263
|
+
interface PtyRunningJob {
|
|
264
|
+
/** 账本键:与 jobId 同构(background 通道预传入 registry id;同步完成命令用同格式生成 id)。 */
|
|
265
|
+
commandId: string;
|
|
266
|
+
/** Byte-cursor incremental read over the command's merged output (tail-ring retention; lossy reports eviction). */
|
|
267
|
+
read: (fromByte: number) => {
|
|
268
|
+
text: string;
|
|
269
|
+
nextOffset: number;
|
|
270
|
+
lossy: boolean;
|
|
271
|
+
};
|
|
272
|
+
stats: () => OutputStats;
|
|
273
|
+
/** P8 两段式 ^C 中断(会话存活;trap-INT 走宽限 group-kill 兜底)。 */
|
|
274
|
+
interrupt: () => void;
|
|
275
|
+
/** Settles on the exit frame, or with exit null when the session dies. */
|
|
276
|
+
done: Promise<PtyJobResult>;
|
|
277
|
+
}
|
|
278
|
+
//#endregion
|
|
216
279
|
//#region src/remote/process.d.ts
|
|
217
280
|
declare const PROCESS_CLEANUP_BOUND_MS: number;
|
|
218
281
|
interface ProcessOptions {
|
|
@@ -260,7 +323,6 @@ interface ProbeOptions {
|
|
|
260
323
|
//#endregion
|
|
261
324
|
//#region src/remote/roster.d.ts
|
|
262
325
|
interface RosterOptions {
|
|
263
|
-
byosshLabels?: readonly string[];
|
|
264
326
|
/** 测试缝:替代 runOneShot 的容器扫描执行器。 */
|
|
265
327
|
runner?: (argv: string[], opts?: OneShotOptions) => Promise<OneShotResult>;
|
|
266
328
|
/** 测试缝:替代 ~/.ssh/config 读取。 */
|
|
@@ -283,10 +345,25 @@ interface RemoteCallResult {
|
|
|
283
345
|
truncated?: number;
|
|
284
346
|
outputStats?: StreamOutputStats;
|
|
285
347
|
outputStatsUnavailable?: boolean;
|
|
286
|
-
/**
|
|
348
|
+
/** §5.2 回执字段序:session(+label)→ connection → commandId → state 字段(cwd/envDelta/ttlSec/exit)。 */
|
|
287
349
|
session?: string;
|
|
288
|
-
|
|
350
|
+
label?: string;
|
|
351
|
+
connection?: string;
|
|
352
|
+
/** (byo-pty)/(byo-ssh) 来源标注(创建回执与 roster/card;native 回执省略)。 */
|
|
353
|
+
byoTag?: 'byo-pty' | 'byo-ssh';
|
|
354
|
+
commandId?: string;
|
|
355
|
+
envDelta?: EnvDelta;
|
|
356
|
+
/** 引导/教学行;无则省略。 */
|
|
289
357
|
notice?: string;
|
|
358
|
+
/** oneshot 监督路径的如实标注:`superd-run <ver> (<triple>)` 或 `posix-shim`(spec remote-rescope §5)。 */
|
|
359
|
+
runner?: string;
|
|
360
|
+
/** P4/P5(2026-10-19):true = pty 命令仍在前台运行(回执 = handoff),ptyJob 随行供 job 通道接管。 */
|
|
361
|
+
running?: boolean;
|
|
362
|
+
ptyJob?: PtyRunningJob;
|
|
363
|
+
/** 本会话 idle TTL 剩余(秒);回执即状态快照(P2/P3:状态留在上下文里)。 */
|
|
364
|
+
ttlSec?: number;
|
|
365
|
+
/** 预热(创建形无 cmd)回执 = true。 */
|
|
366
|
+
created?: boolean;
|
|
290
367
|
}
|
|
291
368
|
interface RemoteAuditRecord {
|
|
292
369
|
target: string;
|
|
@@ -311,31 +388,39 @@ interface RemoteDriverOptions {
|
|
|
311
388
|
ptyArgvFor?: (plan: TargetPlan) => string[];
|
|
312
389
|
spawnArgvFor?: (spawnCommand: string) => string[];
|
|
313
390
|
probeRunner?: ProbeOptions['runner'];
|
|
314
|
-
/**
|
|
391
|
+
/** 测试/验收缝:helper 资产来源(默认 lib 旁挂 helper-bin);显式 null 强制 shim 路径。 */
|
|
392
|
+
helperAssetsFor?: () => HelperAssets | null;
|
|
393
|
+
/** 测试缝:roster 扫描执行器(裸名解析复用)。 */
|
|
315
394
|
rosterRunner?: RosterOptions['runner'];
|
|
316
|
-
/** 测试缝:ssh config
|
|
395
|
+
/** 测试缝:ssh config 读取(裸名解析复用)。 */
|
|
317
396
|
sshConfigReader?: () => Promise<string>;
|
|
318
397
|
}
|
|
319
|
-
/**
|
|
398
|
+
/** 双轨调度器:参数校验 → 目标语法(parseTargetForm)→ 统一 label 池 → oneshot 子进程 / pty 会话池。 */
|
|
320
399
|
declare class RemoteDriver {
|
|
321
400
|
private readonly opts;
|
|
322
|
-
private readonly sshTargets;
|
|
323
401
|
private readonly pool;
|
|
402
|
+
private readonly labels;
|
|
324
403
|
private readonly hubOwner;
|
|
325
404
|
private readonly hubProviders;
|
|
326
405
|
private readonly processes;
|
|
327
406
|
private readonly starting;
|
|
328
407
|
private readonly fileAbort;
|
|
329
408
|
private disposed;
|
|
409
|
+
/** undefined = 未尝试;null = 资产缺失(shim-only);registry = 每 target sticky 状态机。 */
|
|
410
|
+
private helpers;
|
|
411
|
+
private readonly runnerOf;
|
|
330
412
|
private disposal;
|
|
413
|
+
private readonly ptyIdleTtlSec;
|
|
331
414
|
constructor(opts?: RemoteDriverOptions);
|
|
332
415
|
call(params: RemoteCallParams, callCtx?: {
|
|
333
416
|
sessionKey?: string;
|
|
417
|
+
ptyBackground?: boolean;
|
|
418
|
+
commandId?: string;
|
|
334
419
|
}): Promise<RemoteCallResult>;
|
|
335
|
-
/**
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
420
|
+
/** 参数互斥矩阵(§2):spawn/ssh 是 BYO 入口(参数在场即 BYO),只与对应形态的 target 配对。 */
|
|
421
|
+
private validate;
|
|
422
|
+
private execute;
|
|
423
|
+
/** Native-first copy routing (spec: remote-native-shortcut) — unchanged by the workbench round. */
|
|
339
424
|
copy(request: CopyRequest & {
|
|
340
425
|
argv?: unknown;
|
|
341
426
|
pullArgv?: unknown;
|
|
@@ -347,44 +432,80 @@ declare class RemoteDriver {
|
|
|
347
432
|
sessionKey?: string;
|
|
348
433
|
}): Promise<RsyncReceipt>;
|
|
349
434
|
/** Only called from JobSpec.run: admission precedes local file reads and discovery. */
|
|
350
|
-
start(input: RemoteInput, signal: AbortSignal
|
|
435
|
+
start(input: RemoteInput, signal: AbortSignal, callCtx?: {
|
|
436
|
+
sessionKey?: string;
|
|
437
|
+
}): Promise<RemoteProcess>;
|
|
351
438
|
private startPrepared;
|
|
352
439
|
private nativeProcess;
|
|
440
|
+
/** Helper bootstrap is best-effort and sticky: any failure parks null for this driver
|
|
441
|
+
* lifetime and the shim stays the path. First call to a target pays probe (+ upload). */
|
|
442
|
+
private ensureHelper;
|
|
443
|
+
private helperExec;
|
|
353
444
|
private own;
|
|
354
|
-
/**
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
private
|
|
361
|
-
/**
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
445
|
+
/** 现场采集机器面并 merge 进本 session 注册表(ssh → docker → incus → hub,迟到者加前缀)。错误逐段吞掉(roster 另行如实呈现)。 */
|
|
446
|
+
private refreshMachines;
|
|
447
|
+
/** 池寻址:命中即回;miss 现场采集一次再寻址(docker 容器名不经 roster 也可达)。 */
|
|
448
|
+
private resolvePoolEntry;
|
|
449
|
+
private planOf;
|
|
450
|
+
/** ssh:<name> 的池感知解析:BYO 身份 → 其 connection;否则按 config 别名/主机名。 */
|
|
451
|
+
private sshPlanFor;
|
|
452
|
+
/** BYO ssh 注册(v2:无 spec 对比——label 占用即拒;注册成功才认领)。返回实际名(label 或 s-id)。 */
|
|
453
|
+
private registerByoSsh;
|
|
454
|
+
/** PTY 会话创建(native/spawn 同一形状):上限 → label 认领 → 惰性会话(失败由 onDead 释放 label)。 */
|
|
455
|
+
private createSession;
|
|
456
|
+
private ttlMinutes;
|
|
457
|
+
/** connection 由 BYO ssh 身份建立 → card 面标 `(built on)`(与 roster 同词,R2 复核 P2-10)。 */
|
|
458
|
+
private isBuiltOn;
|
|
459
|
+
/** PTY 创建形唯一实现(execute 与 background 预建共用):spawn BYO / ssh BYO / native `<transport>:pty`。 */
|
|
460
|
+
private createPtyLane;
|
|
461
|
+
/** background PTY lane:会话先占位(不拨号)再进官方 jobs registry——首回执必须能寻址;
|
|
462
|
+
* registry 拒绝时把新建的会话拆掉(复用形的会话绝不拆)。返回 undefined = 走普通 oneshot job。 */
|
|
463
|
+
preparePtyLane(params: RemoteCallParams, callCtx?: {
|
|
464
|
+
sessionKey?: string;
|
|
465
|
+
}): Promise<{
|
|
466
|
+
input: RemoteInput;
|
|
467
|
+
session: string;
|
|
468
|
+
connection: string;
|
|
469
|
+
label?: string;
|
|
470
|
+
cleanup: () => void;
|
|
471
|
+
} | undefined>;
|
|
472
|
+
/** `pty:<id|label>` 寻址:先 id 后 label;miss 给重建形式。 */
|
|
473
|
+
private resolvePty;
|
|
474
|
+
/** §6 close:默认撞 detached job 报错(列杀法);force = 杀组+关闭(披露 killed)。幂等 not-found。 */
|
|
475
|
+
closePty(selector: string, sessionKey: string, force?: boolean): {
|
|
476
|
+
session: string;
|
|
477
|
+
connection: string;
|
|
478
|
+
closed: boolean;
|
|
479
|
+
reason?: string;
|
|
480
|
+
killed: string[];
|
|
481
|
+
message?: string;
|
|
482
|
+
};
|
|
483
|
+
/** job 通道收养回填(timeout handoff 时 registry id ≠ commandId,账本记录两者)。 */
|
|
484
|
+
attachPtyJob(commandId: string, jobId: string, sessionKey: string): void;
|
|
485
|
+
private dispatchPty;
|
|
486
|
+
/** 预热(创建形无 cmd)回执:§3——无 commandId/envDelta;timeout 忽略。 */
|
|
487
|
+
private warmReceipt;
|
|
488
|
+
/** session card 回执(`pty:<id|label>` 无 cmd):connection/state/cwd + 最近 20 条账本(不带输出尾,§9 Q7)。 */
|
|
489
|
+
private cardReceipt;
|
|
490
|
+
private oneshot;
|
|
491
|
+
private hubOneshot;
|
|
492
|
+
private hubOneshotProcess;
|
|
493
|
+
/** Ruling P19 + §2.5:roster = 注意力入口——本地扫描刷新统一池,零拨号;机器面公共,会话面 caller-scoped。 */
|
|
365
494
|
roster(callCtx?: {
|
|
366
495
|
sessionKey?: string;
|
|
367
496
|
}): Promise<string>;
|
|
497
|
+
/** status probe(target 无 cmd):机器面 = probe + 该 connection 上的会话;pty selector = session card。 */
|
|
368
498
|
status(target: string, callCtx?: {
|
|
369
499
|
sessionKey?: string;
|
|
370
500
|
}): Promise<string>;
|
|
371
|
-
/**
|
|
372
|
-
private ptyStatus;
|
|
373
|
-
/** Ruling 13:审计 best-effort——钩子抛错不得影响命令路径(成功被毒化成失败+双重审计)。 */
|
|
501
|
+
/** Ruling 13:审计 best-effort——钩子抛错不得影响命令路径。 */
|
|
374
502
|
private audit;
|
|
375
|
-
/**
|
|
376
|
-
registerSsh(target: string, connection: SshConnection): string;
|
|
377
|
-
private byosshPlan;
|
|
378
|
-
/** Single target resolver for later copy/rsync; BYO identities cannot be
|
|
379
|
-
* independently re-resolved through the origin's alias table. */
|
|
503
|
+
/** Single target resolver for later copy/rsync; PTY sessions are not filesystem connections. */
|
|
380
504
|
resolveConnectionTarget(target: string, callCtx?: {
|
|
381
505
|
sessionKey?: string;
|
|
382
506
|
}): Promise<TargetPlan>;
|
|
383
|
-
|
|
384
|
-
private
|
|
385
|
-
/** BYO-PTY 通道:label 即池键(agent 前缀之外),spawn 只在 label 尚不存在时被接受。
|
|
386
|
-
* 三分支 —— 建(无会话)/ 复用(无 spawn,或有 spawn 且与创建来源相同)/ **冲突(有 spawn 且来源不同:不执行任何命令)**。 */
|
|
387
|
-
private ptyCall;
|
|
507
|
+
/** P2/P3:回执即状态快照——idle TTL 剩余随每次 pty 回执带回。 */
|
|
508
|
+
private ptyTtl;
|
|
388
509
|
hasHub(): boolean;
|
|
389
510
|
private requireHub;
|
|
390
511
|
private assertOpen;
|
|
@@ -397,13 +518,13 @@ interface RemoteInput {
|
|
|
397
518
|
spawn?: string;
|
|
398
519
|
ssh?: SshConnection;
|
|
399
520
|
cmd?: string;
|
|
400
|
-
|
|
521
|
+
scriptPath?: string;
|
|
401
522
|
stdin?: string;
|
|
402
523
|
mode?: 'oneshot' | 'pty';
|
|
403
524
|
timeout?: number;
|
|
404
525
|
}
|
|
405
526
|
interface PreparedRemoteInput {
|
|
406
|
-
target
|
|
527
|
+
target?: string;
|
|
407
528
|
spawn?: string;
|
|
408
529
|
ssh?: SshConnection;
|
|
409
530
|
cmd: string;
|
|
@@ -552,4 +673,4 @@ declare const _default: {
|
|
|
552
673
|
apply: (ctx: Context, config: RemoteRowConfig) => (() => void);
|
|
553
674
|
};
|
|
554
675
|
//#endregion
|
|
555
|
-
export { type BinaryFileEndpoint, ByteTailRing, Config, FILE_FRAME_BYTES, FILE_MAX_ENTRIES, FILE_MAX_METADATA, type HubRemoteTransport, type HubTarget, type OutputStats, PROCESS_CLEANUP_BOUND_MS, type PreparedRemoteInput, RemoteConfigValidator, type RemoteInput, type RemoteProcess, RemoteRowConfig, type RsyncEndpoint, type RsyncReceipt, type RsyncRequest, type SshConnection,
|
|
676
|
+
export { type BinaryFileEndpoint, ByteTailRing, Config, FILE_FRAME_BYTES, FILE_MAX_ENTRIES, FILE_MAX_METADATA, type HubRemoteTransport, type HubTarget, type OutputStats, PROCESS_CLEANUP_BOUND_MS, type PreparedRemoteInput, RemoteConfigValidator, type RemoteInput, type RemoteProcess, RemoteRowConfig, type RsyncEndpoint, type RsyncReceipt, type RsyncRequest, type SshConnection, type StreamOutputStats, Utf16TailRing, apply, checkAbsolute, checkSelection, _default as default, inject, localFileEndpoint, name, readUtf8Tail, runFileWorker, runRsync, sshArgv, startBinaryProcess, startOneShot, startRemoteJob };
|