super-dsh 0.3.0-e → 0.3.0-f
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.md +13 -55
- package/client/index.js +141 -10
- package/cordis.patch.yml +3 -3
- package/package.json +2 -4
- package/superd-hub/dist/bundle-setup.d.ts +16 -6
- package/superd-hub/dist/bundle-setup.js +84 -148
- package/superd-hub/dist/client/companion-api.d.ts +5 -0
- package/superd-hub/dist/client/companion-api.js +30 -0
- package/superd-hub/dist/companion-contract.d.ts +9 -0
- package/superd-hub/dist/companion-contract.js +1 -0
- package/superd-hub/dist/companions.d.ts +30 -0
- package/superd-hub/dist/companions.js +199 -0
- package/superd-hub/dist/fusion-transport.js +1 -0
- package/superd-hub/dist/gateway.js +17 -11
- package/superd-hub/lib/client/index.js +141 -10
- package/superd-hub/package.json +1 -2
- package/superd-hub/scripts/register-companion.mjs +0 -100
package/README.md
CHANGED
|
@@ -22,62 +22,20 @@ six primary `@pgmi-builds/agent-*` packages and three OPTIONAL SDK halves).
|
|
|
22
22
|
Restart the profile after replacing a loaded package. Each foreign world's
|
|
23
23
|
DSH home is created under `<DSH_HOME>/agents/<label>/` on first boot.
|
|
24
24
|
|
|
25
|
-
## Better Dash delivery
|
|
26
|
-
|
|
27
|
-
Super
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
fetch. This automatic local registration is separate from the user's single
|
|
37
|
-
installation command. Missing cached data reports a failure instead of falling
|
|
38
|
-
back to online fetching. Replacing loaded code or promoting loaded provenance
|
|
39
|
-
requires the documented cold restart.
|
|
40
|
-
|
|
41
|
-
The Plugin Manager shows both packages as parallel installed bundles with their
|
|
42
|
-
own YAML and independent switches. Better's Code source is `better-dsh@<version>`.
|
|
43
|
-
Updates retain disablement and same-version explicit uninstall; removing Super
|
|
44
|
-
retains the directly registered Better. Old `bundle-artifacts/` caches are left
|
|
45
|
-
intact for previous installations, but new releases do not write them. With Super
|
|
46
|
-
disabled, registration waits until Super is enabled. dev WebMCP is
|
|
47
|
-
separate and test-only.
|
|
48
|
-
|
|
49
|
-
Both packages use the same local dual-build pipeline. Tests serve the exact
|
|
50
|
-
unpublished artifact pair through a read-only local registry, without modifying
|
|
51
|
-
the release manifest or preinstalling Better. Current iteration: **0.3.0-f**.
|
|
52
|
-
|
|
53
|
-
For a tarball URL installation in the native Web UI, put the `.tgz` URL in the
|
|
54
|
-
main Add plugin input and keep the separate Registry selection on the official
|
|
55
|
-
npm registry. A tarball file server is not an npm registry.
|
|
56
|
-
|
|
57
|
-
Disabling Better restores native Web binding, trusted-host, login and onboarding
|
|
58
|
-
policies. Remote deployments must supply native DSH trusted-host configuration;
|
|
59
|
-
Super does not take ownership of those Better policies.
|
|
60
|
-
|
|
61
|
-
Better Dash owns the native DSH policies: `dashr-web-trust` retains cookie
|
|
62
|
-
authentication while accepting authenticated serving Host/Origin values; its
|
|
63
|
-
password page provides new-client login. Cookie expiry and authority binding remain.
|
|
64
|
-
`dashr-webserver` defaults to `0.0.0.0`; native `--port` and explicit loopback
|
|
65
|
-
`--host` overrides are preserved. Super Dash carries no duplicate WebTrust.
|
|
66
|
-
To restore the native address fence, set `dashr-web-trust`'s `trustAllHosts: false`.
|
|
67
|
-
|
|
68
|
-
Better Dash also disables automatic credential onboarding and acknowledges the
|
|
69
|
-
installed Models client's current welcome version before the listener opens.
|
|
70
|
-
Manual model configuration remains available. To restore native welcome behavior:
|
|
71
|
-
|
|
72
|
-
```yaml
|
|
73
|
-
- id: dashr-welcome-notice
|
|
74
|
-
disabled: true
|
|
75
|
-
```
|
|
25
|
+
## Better Dash delivery
|
|
26
|
+
|
|
27
|
+
Super installs [Better Dash](../better-dsh/) at runtime through the native DSH
|
|
28
|
+
Plugin Manager, requesting its installed Super version first, then `better-dsh@latest`, then bare
|
|
29
|
+
`better-dsh` after each preceding failure. The packages have no dependency edge or shared physical payload;
|
|
30
|
+
both retain independent native Plugin Manager rows and component switches.
|
|
31
|
+
|
|
32
|
+
First-use intent lives under `$DSH_HOME/superd/companions/`, so manual removal
|
|
33
|
+
remains removed across restarts and upgrades. Hub Settings offers explicit
|
|
34
|
+
reinstallation. Both packages ship at synchronized versions; publish Better
|
|
35
|
+
before Super. Pi's prebuilt SDK retains its separate bundled delivery.
|
|
76
36
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
entry. Dev WebMCP lives in the root `dev-webmcp/` directory and is included
|
|
80
|
-
only as an extra development profile bundle, never in either release tarball.
|
|
37
|
+
Better's welcome policy uses the official client onboarding slot. Native
|
|
38
|
+
WebServer and HMR stay in place; listener configuration belongs to deployment.
|
|
81
39
|
|
|
82
40
|
## Runtimes
|
|
83
41
|
|
package/client/index.js
CHANGED
|
@@ -2198,6 +2198,145 @@ async function removeProvider(agentKey, namespace, id, doFetch) {
|
|
|
2198
2198
|
return requireProvidersView(await callRuntimeSettings(agentKey, namespace, "remove", { request: { id } }, doFetch));
|
|
2199
2199
|
}
|
|
2200
2200
|
|
|
2201
|
+
//#endregion
|
|
2202
|
+
//#region src/client/companion-api.ts
|
|
2203
|
+
async function callCompanion(operation, doFetch) {
|
|
2204
|
+
const method = `superdCompanions/${operation}`;
|
|
2205
|
+
const response = await (doFetch ?? ((path, init) => fetch(path, init)))(`/api/${method}`, {
|
|
2206
|
+
method: "POST",
|
|
2207
|
+
credentials: "same-origin",
|
|
2208
|
+
headers: { "content-type": "application/json" },
|
|
2209
|
+
body: JSON.stringify({
|
|
2210
|
+
type: "client-request",
|
|
2211
|
+
rpcId: mintRpcId(),
|
|
2212
|
+
method,
|
|
2213
|
+
payload: { args: {} }
|
|
2214
|
+
})
|
|
2215
|
+
});
|
|
2216
|
+
if (!response.ok) throw new Error(`Better Dash: HTTP ${response.status}`);
|
|
2217
|
+
let body;
|
|
2218
|
+
try {
|
|
2219
|
+
body = JSON.parse(await response.text());
|
|
2220
|
+
} catch {
|
|
2221
|
+
throw new Error("Better Dash: unreadable response");
|
|
2222
|
+
}
|
|
2223
|
+
const result = body?.result;
|
|
2224
|
+
if (result?.ok !== true) throw new Error(result?.error?.message ?? "Better Dash: request failed");
|
|
2225
|
+
const value = result.value;
|
|
2226
|
+
if (value === null || typeof value !== "object" || typeof value.installed !== "boolean" || typeof value.installing !== "boolean" || typeof value.requestedVersion !== "string" || value.version !== void 0 && typeof value.version !== "string" || value.enabled !== void 0 && typeof value.enabled !== "boolean" || value.error !== void 0 && typeof value.error !== "string") throw new Error("Better Dash: invalid status");
|
|
2227
|
+
return value;
|
|
2228
|
+
}
|
|
2229
|
+
|
|
2230
|
+
//#endregion
|
|
2231
|
+
//#region src/client/CompanionSettings.tsx
|
|
2232
|
+
/** Native plugin switches remain in Plugin Manager; this row provides explicit recovery. */
|
|
2233
|
+
function CompanionSettings() {
|
|
2234
|
+
const [status, setStatus] = react.default.useState();
|
|
2235
|
+
const [error, setError] = react.default.useState();
|
|
2236
|
+
const [busy, setBusy] = react.default.useState(false);
|
|
2237
|
+
const live = react.default.useRef(false);
|
|
2238
|
+
const operation = react.default.useRef(false);
|
|
2239
|
+
react.default.useEffect(() => {
|
|
2240
|
+
live.current = true;
|
|
2241
|
+
let reading = false;
|
|
2242
|
+
const refresh = async () => {
|
|
2243
|
+
if (reading || operation.current) return;
|
|
2244
|
+
reading = true;
|
|
2245
|
+
try {
|
|
2246
|
+
const next = await callCompanion("getBetterDash");
|
|
2247
|
+
if (live.current) {
|
|
2248
|
+
setStatus(next);
|
|
2249
|
+
setError(next.error);
|
|
2250
|
+
}
|
|
2251
|
+
} catch (cause) {
|
|
2252
|
+
if (live.current) setError(cause instanceof Error ? cause.message : String(cause));
|
|
2253
|
+
} finally {
|
|
2254
|
+
reading = false;
|
|
2255
|
+
}
|
|
2256
|
+
};
|
|
2257
|
+
refresh();
|
|
2258
|
+
const timer = window.setInterval(() => {
|
|
2259
|
+
refresh();
|
|
2260
|
+
}, 5e3);
|
|
2261
|
+
return () => {
|
|
2262
|
+
live.current = false;
|
|
2263
|
+
window.clearInterval(timer);
|
|
2264
|
+
};
|
|
2265
|
+
}, []);
|
|
2266
|
+
const install = async () => {
|
|
2267
|
+
if (operation.current) return;
|
|
2268
|
+
operation.current = true;
|
|
2269
|
+
setBusy(true);
|
|
2270
|
+
setError(void 0);
|
|
2271
|
+
try {
|
|
2272
|
+
const next = await callCompanion("installBetterDash");
|
|
2273
|
+
if (live.current) {
|
|
2274
|
+
setStatus(next);
|
|
2275
|
+
setError(next.error);
|
|
2276
|
+
}
|
|
2277
|
+
} catch (cause) {
|
|
2278
|
+
if (live.current) setError(cause instanceof Error ? cause.message : String(cause));
|
|
2279
|
+
} finally {
|
|
2280
|
+
operation.current = false;
|
|
2281
|
+
if (live.current) setBusy(false);
|
|
2282
|
+
}
|
|
2283
|
+
};
|
|
2284
|
+
const installing = busy || status?.installing === true;
|
|
2285
|
+
const label = status === void 0 ? "Loading…" : status.installed ? `Installed${status.version ? " · " + status.version : ""}${status.enabled === false ? " · Disabled" : ""}` : "Not installed";
|
|
2286
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2287
|
+
style: {
|
|
2288
|
+
borderRadius: 12,
|
|
2289
|
+
padding: "12px 14px",
|
|
2290
|
+
background: "var(--dsw-alias-interactive-bg-hover)"
|
|
2291
|
+
},
|
|
2292
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2293
|
+
style: {
|
|
2294
|
+
display: "flex",
|
|
2295
|
+
alignItems: "center",
|
|
2296
|
+
gap: 16
|
|
2297
|
+
},
|
|
2298
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2299
|
+
style: { flex: 1 },
|
|
2300
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2301
|
+
style: {
|
|
2302
|
+
fontSize: 14,
|
|
2303
|
+
fontWeight: 600
|
|
2304
|
+
},
|
|
2305
|
+
children: "Better Dash"
|
|
2306
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2307
|
+
role: "status",
|
|
2308
|
+
style: {
|
|
2309
|
+
fontSize: 12,
|
|
2310
|
+
color: "var(--dsw-alias-label-secondary)",
|
|
2311
|
+
marginTop: 4
|
|
2312
|
+
},
|
|
2313
|
+
children: installing ? "Installing…" : label
|
|
2314
|
+
})]
|
|
2315
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2316
|
+
type: "button",
|
|
2317
|
+
disabled: installing || status?.installed === true,
|
|
2318
|
+
onClick: () => {
|
|
2319
|
+
install();
|
|
2320
|
+
},
|
|
2321
|
+
style: {
|
|
2322
|
+
padding: "6px 12px",
|
|
2323
|
+
borderRadius: 8,
|
|
2324
|
+
cursor: installing || status?.installed ? "default" : "pointer"
|
|
2325
|
+
},
|
|
2326
|
+
children: installing ? "Installing…" : status?.installed ? "Installed" : "Install"
|
|
2327
|
+
})]
|
|
2328
|
+
}), error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2329
|
+
role: "alert",
|
|
2330
|
+
style: {
|
|
2331
|
+
fontSize: 12,
|
|
2332
|
+
margin: "8px 0 0",
|
|
2333
|
+
color: "var(--dsw-alias-state-error-primary)"
|
|
2334
|
+
},
|
|
2335
|
+
children: error
|
|
2336
|
+
})]
|
|
2337
|
+
});
|
|
2338
|
+
}
|
|
2339
|
+
|
|
2201
2340
|
//#endregion
|
|
2202
2341
|
//#region src/client/HubPage.tsx
|
|
2203
2342
|
const EMPTY_DRAFT = {
|
|
@@ -3415,16 +3554,8 @@ function HubPage() {
|
|
|
3415
3554
|
},
|
|
3416
3555
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(GroupHead, {
|
|
3417
3556
|
title: "Settings",
|
|
3418
|
-
count:
|
|
3419
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
3420
|
-
style: {
|
|
3421
|
-
margin: 0,
|
|
3422
|
-
fontSize: 13,
|
|
3423
|
-
lineHeight: "20px",
|
|
3424
|
-
color: "var(--dsw-alias-label-tertiary)"
|
|
3425
|
-
},
|
|
3426
|
-
children: "Hub preferences — pairing, runtime visibility, and orchestration defaults land with the hub service."
|
|
3427
|
-
})]
|
|
3557
|
+
count: 1
|
|
3558
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CompanionSettings, {})]
|
|
3428
3559
|
}),
|
|
3429
3560
|
tab === "home" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
3430
3561
|
style: {
|
package/cordis.patch.yml
CHANGED
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
# zero-port sibling root, mounted under /<label>
|
|
13
13
|
# - super-dsh/join the generic hub joiner for the standalone
|
|
14
14
|
# claude adapter (R2)
|
|
15
|
-
# DSH-only WebTrust
|
|
16
|
-
# Better Dash
|
|
15
|
+
# DSH-only WebTrust and onboarding policies belong to independently installed
|
|
16
|
+
# Better Dash; the native Plugin Manager owns its package and enablement.
|
|
17
17
|
# - super-dsh/a2a the agent-facing A2A tool face (mesh P3): the
|
|
18
18
|
# single `superd-hub` model tool on ctx0's tools
|
|
19
19
|
# service (dormant without tools; world trees
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
# Profile-layer values (webserver port/host, settings path) stay in the USER's
|
|
23
23
|
# profile patch layer, never here.
|
|
24
24
|
- insert:
|
|
25
|
-
#
|
|
25
|
+
# Expose native companion management and perform first-use installation.
|
|
26
26
|
- id: superd-bundle-setup
|
|
27
27
|
name: 'super-dsh/setup'
|
|
28
28
|
# BARE package row: subpath rows (…/agents/omp) are permanently not client
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "super-dsh",
|
|
3
|
-
"version": "0.3.0-
|
|
3
|
+
"version": "0.3.0-f",
|
|
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",
|
|
@@ -68,8 +68,7 @@
|
|
|
68
68
|
"agent-agy-sdk/dist",
|
|
69
69
|
"agent-agy-sdk/package.json",
|
|
70
70
|
"agent-agy-sdk/cordis.patch.yml",
|
|
71
|
-
"agent-agy-sdk/bridge"
|
|
72
|
-
"superd-hub/scripts/register-companion.mjs"
|
|
71
|
+
"agent-agy-sdk/bridge"
|
|
73
72
|
],
|
|
74
73
|
"dsh": {
|
|
75
74
|
"bundle": {
|
|
@@ -83,7 +82,6 @@
|
|
|
83
82
|
}
|
|
84
83
|
},
|
|
85
84
|
"dependencies": {
|
|
86
|
-
"better-dsh": "0.3.0-e",
|
|
87
85
|
"@earendil-works/pi-coding-agent": "0.84.2",
|
|
88
86
|
"zod": "4.4.3",
|
|
89
87
|
"@modelcontextprotocol/sdk": "^1.29.0"
|
|
@@ -1,13 +1,23 @@
|
|
|
1
|
+
/** Better Dash is installed by the current profile's native Plugin Manager. */
|
|
1
2
|
import type { Context } from '@deepseek-ai/cordis';
|
|
2
|
-
import
|
|
3
|
+
import { TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
|
|
4
|
+
import { BetterDashCompanion } from './companions.js';
|
|
5
|
+
import type { BetterDashStatus } from './companion-contract.js';
|
|
6
|
+
export { BetterDashCompanion } from './companions.js';
|
|
7
|
+
export type { BetterDashStatus } from './companion-contract.js';
|
|
3
8
|
export declare const name = "superd-bundle-setup";
|
|
4
9
|
export declare const inject: string[];
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
10
|
+
declare module '@deepseek-ai/cordis' {
|
|
11
|
+
interface Context {
|
|
12
|
+
superdCompanions: SuperdCompanions;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export declare class SuperdCompanions extends TypertRemoteService {
|
|
16
|
+
private readonly companion;
|
|
17
|
+
constructor(ctx: Context, companion: BetterDashCompanion);
|
|
18
|
+
getBetterDash(): Promise<BetterDashStatus>;
|
|
19
|
+
installBetterDash(): Promise<BetterDashStatus>;
|
|
8
20
|
}
|
|
9
|
-
/** First registration enables a companion; native reconciliation preserves retained disablement. */
|
|
10
|
-
export declare function reconcileRegistryCompanions(profile: ProfileContext, signal?: AbortSignal): Promise<DeliveryResult>;
|
|
11
21
|
export declare function apply(ctx: Context): void;
|
|
12
22
|
declare const _default: {
|
|
13
23
|
name: string;
|
|
@@ -1,158 +1,94 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import { join } from 'node:path';
|
|
6
|
-
import { fileURLToPath } from 'node:url';
|
|
7
|
-
import { withFileLock, writeFileAtomic } from '@deepseek-ai/dsh-atomic-write';
|
|
8
|
-
import * as AppBoot from '@deepseek-ai/dsh-app-boot';
|
|
9
|
-
export const name = 'superd-bundle-setup';
|
|
10
|
-
export const inject = ['profileContext', 'appReady'];
|
|
11
|
-
const hash = (bytes) => createHash('sha256').update(bytes).digest('hex');
|
|
12
|
-
// Refresh-capable hosts retain loaded package provenance: promoting the
|
|
13
|
-
// companion from its containing pack to a direct dependency needs a restart.
|
|
14
|
-
// Older hosts without this API keep their first-registration behavior.
|
|
15
|
-
const nativePackages = AppBoot.PluginPackages?.prototype;
|
|
16
|
-
const restartForPromotion = nativePackages !== undefined && typeof Reflect.get(nativePackages, 'refresh') === 'function';
|
|
17
|
-
const json = async (path) => JSON.parse(await readFile(path, 'utf8'));
|
|
18
|
-
async function optionalJson(path) {
|
|
19
|
-
try {
|
|
20
|
-
return await json(path);
|
|
1
|
+
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
|
|
2
|
+
var useValue = arguments.length > 2;
|
|
3
|
+
for (var i = 0; i < initializers.length; i++) {
|
|
4
|
+
value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
|
21
5
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
clearTimeout(timeout);
|
|
43
|
-
signal?.removeEventListener('abort', abort);
|
|
44
|
-
if (signal?.aborted) {
|
|
45
|
-
fail(signal.reason);
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
if (code !== 0) {
|
|
49
|
-
fail(new Error(`Native companion registration failed (${code}): ${diagnostic.trim() || output.trim()}`));
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
try {
|
|
53
|
-
const result = JSON.parse(output);
|
|
54
|
-
if (result.exitCode !== 0)
|
|
55
|
-
fail(new Error(`Native companion registration failed (${result.exitCode}); diagnostics: ${result.logPath ?? 'unavailable'}`));
|
|
56
|
-
else if (!['installed', 'owner-changed', 'companion-changed', 'companion-removed'].includes(result.disposition))
|
|
57
|
-
fail(new Error('Native companion registration returned an invalid disposition'));
|
|
58
|
-
else
|
|
59
|
-
done(result.disposition);
|
|
60
|
-
}
|
|
61
|
-
catch (error) {
|
|
62
|
-
fail(error);
|
|
63
|
-
}
|
|
64
|
-
});
|
|
65
|
-
child.stdin.on('error', () => { });
|
|
66
|
-
child.stdin.end(JSON.stringify({ profile, expected }));
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
/** First registration enables a companion; native reconciliation preserves retained disablement. */
|
|
70
|
-
export async function reconcileRegistryCompanions(profile, signal) {
|
|
71
|
-
const directory = join(profile.home, 'companion-registration');
|
|
72
|
-
await mkdir(directory, { recursive: true, mode: 0o700 });
|
|
73
|
-
const key = hash(profile.dir);
|
|
74
|
-
return withFileLock(join(directory, key + '.delivery'), async () => {
|
|
75
|
-
signal?.throwIfAborted();
|
|
76
|
-
const receiptPath = join(directory, key + '.json');
|
|
77
|
-
const receipts = await optionalJson(receiptPath) ?? {};
|
|
78
|
-
const manifest = await json(join(profile.dir, 'package.json'));
|
|
79
|
-
const result = { installed: [], restartRequired: false };
|
|
80
|
-
for (const owner of Object.keys(manifest.dependencies ?? {})) {
|
|
81
|
-
const ownerDir = join(profile.dir, 'node_modules', owner);
|
|
82
|
-
const ownerManifest = await optionalJson(join(ownerDir, 'package.json'));
|
|
83
|
-
if (owner !== 'super-dsh' || ownerManifest?.name !== owner)
|
|
84
|
-
continue;
|
|
85
|
-
const version = ownerManifest.dependencies?.['better-dsh'];
|
|
86
|
-
if (version === undefined || version !== ownerManifest.version)
|
|
87
|
-
throw new Error('Super Dash requires its synchronized exact Better registry dependency');
|
|
88
|
-
const item = { name: 'better-dsh', version };
|
|
89
|
-
const current = await json(join(profile.dir, 'package.json'));
|
|
90
|
-
const ownerSpec = current.dependencies?.[owner];
|
|
91
|
-
if (ownerSpec === undefined)
|
|
92
|
-
continue;
|
|
93
|
-
const priorSpec = current.dependencies?.[item.name];
|
|
94
|
-
const receiptKey = `${owner}:${item.name}`;
|
|
95
|
-
if (priorSpec === undefined && receipts[receiptKey] === version)
|
|
96
|
-
continue; // same-version explicit removal
|
|
97
|
-
const installed = await optionalJson(join(profile.dir, 'node_modules', item.name, 'package.json'));
|
|
98
|
-
if (priorSpec === version && installed?.name === item.name && installed.version === version) {
|
|
99
|
-
receipts[receiptKey] = version;
|
|
100
|
-
await writeFileAtomic(receiptPath, JSON.stringify(receipts) + '\n', { mode: 0o600 });
|
|
101
|
-
continue;
|
|
102
|
-
}
|
|
103
|
-
signal?.throwIfAborted();
|
|
104
|
-
const disposition = await installNative(profile, { owner, ownerSpec, companion: item, ...priorSpec === undefined ? {} : { priorSpec } }, signal);
|
|
105
|
-
if (disposition === 'owner-changed' || disposition === 'companion-changed')
|
|
106
|
-
continue;
|
|
107
|
-
receipts[receiptKey] = version;
|
|
108
|
-
await writeFileAtomic(receiptPath, JSON.stringify(receipts) + '\n', { mode: 0o600 });
|
|
109
|
-
if (disposition === 'companion-removed')
|
|
110
|
-
continue;
|
|
111
|
-
result.installed.push(`${item.name}@${item.version}`);
|
|
112
|
-
result.restartRequired ||= priorSpec !== undefined || restartForPromotion;
|
|
6
|
+
return useValue ? value : void 0;
|
|
7
|
+
};
|
|
8
|
+
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
|
9
|
+
function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
|
|
10
|
+
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
|
11
|
+
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
|
|
12
|
+
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
|
|
13
|
+
var _, done = false;
|
|
14
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
15
|
+
var context = {};
|
|
16
|
+
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
|
17
|
+
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
|
18
|
+
context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
|
|
19
|
+
var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
|
|
20
|
+
if (kind === "accessor") {
|
|
21
|
+
if (result === void 0) continue;
|
|
22
|
+
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
|
23
|
+
if (_ = accept(result.get)) descriptor.get = _;
|
|
24
|
+
if (_ = accept(result.set)) descriptor.set = _;
|
|
25
|
+
if (_ = accept(result.init)) initializers.unshift(_);
|
|
113
26
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
export function apply(ctx) {
|
|
118
|
-
const signal = new AbortController();
|
|
119
|
-
let started = false, running = false, again = false;
|
|
120
|
-
const reconcile = async () => {
|
|
121
|
-
if (!started || signal.signal.aborted)
|
|
122
|
-
return;
|
|
123
|
-
if (running) {
|
|
124
|
-
again = true;
|
|
125
|
-
return;
|
|
27
|
+
else if (_ = accept(result)) {
|
|
28
|
+
if (kind === "field") initializers.unshift(_);
|
|
29
|
+
else descriptor[key] = _;
|
|
126
30
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
31
|
+
}
|
|
32
|
+
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
33
|
+
done = true;
|
|
34
|
+
};
|
|
35
|
+
import { Remote, TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
|
|
36
|
+
import { BetterDashCompanion } from './companions.js';
|
|
37
|
+
import { PACK_VERSION } from './pack-version.js';
|
|
38
|
+
export { BetterDashCompanion } from './companions.js';
|
|
39
|
+
export const name = 'superd-bundle-setup';
|
|
40
|
+
export const inject = ['profileContext', 'appReady'];
|
|
41
|
+
let SuperdCompanions = (() => {
|
|
42
|
+
let _classSuper = TypertRemoteService;
|
|
43
|
+
let _instanceExtraInitializers = [];
|
|
44
|
+
let _getBetterDash_decorators;
|
|
45
|
+
let _installBetterDash_decorators;
|
|
46
|
+
return class SuperdCompanions extends _classSuper {
|
|
47
|
+
static {
|
|
48
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
49
|
+
_getBetterDash_decorators = [Remote];
|
|
50
|
+
_installBetterDash_decorators = [Remote];
|
|
51
|
+
__esDecorate(this, null, _getBetterDash_decorators, { kind: "method", name: "getBetterDash", static: false, private: false, access: { has: obj => "getBetterDash" in obj, get: obj => obj.getBetterDash }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
52
|
+
__esDecorate(this, null, _installBetterDash_decorators, { kind: "method", name: "installBetterDash", static: false, private: false, access: { has: obj => "installBetterDash" in obj, get: obj => obj.installBetterDash }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
53
|
+
if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
138
54
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
void reconcile();
|
|
144
|
-
}
|
|
55
|
+
companion = __runInitializers(this, _instanceExtraInitializers);
|
|
56
|
+
constructor(ctx, companion) {
|
|
57
|
+
super(ctx, 'superdCompanions');
|
|
58
|
+
this.companion = companion;
|
|
145
59
|
}
|
|
60
|
+
getBetterDash() { return this.companion.getBetterDash(); }
|
|
61
|
+
installBetterDash() { return this.companion.installBetterDash(); }
|
|
146
62
|
};
|
|
63
|
+
})();
|
|
64
|
+
export { SuperdCompanions };
|
|
65
|
+
export function apply(ctx) {
|
|
66
|
+
const companion = new BetterDashCompanion(ctx.profileContext, PACK_VERSION, () => ctx.get('pluginManager'));
|
|
67
|
+
new SuperdCompanions(ctx, companion);
|
|
147
68
|
ctx.effect(() => {
|
|
148
|
-
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
// Never await a child here: manager HMR may be holding the profile lock it needs.
|
|
69
|
+
let ready = false, registering = true;
|
|
70
|
+
const start = () => {
|
|
71
|
+
void companion.ensureAutomatic().then(status => {
|
|
72
|
+
if (status.error !== undefined)
|
|
73
|
+
ctx.logger.warn(status.error);
|
|
74
|
+
}).catch(error => ctx.logger.warn('Better Dash companion status failed', error));
|
|
155
75
|
};
|
|
156
|
-
|
|
76
|
+
const offReady = ctx.appReady.onReady(() => {
|
|
77
|
+
ready = true;
|
|
78
|
+
// Cold readiness occurs after boot, outside HMR. A live mount gets an
|
|
79
|
+
// immediate callback while its parent HMR transaction is still active:
|
|
80
|
+
// wait for the native changed notification below instead.
|
|
81
|
+
if (!registering)
|
|
82
|
+
start();
|
|
83
|
+
});
|
|
84
|
+
registering = false;
|
|
85
|
+
const offChange = ctx.on('plugin-manager/changed', () => {
|
|
86
|
+
// Native configure/HMR has finished here, but the parent's profile lock
|
|
87
|
+
// is still held. Return immediately; child install queues that lock.
|
|
88
|
+
if (ready)
|
|
89
|
+
start();
|
|
90
|
+
});
|
|
91
|
+
return () => { offReady(); offChange(); companion.dispose(); };
|
|
92
|
+
}, 'bundle-delivery: native companions');
|
|
157
93
|
}
|
|
158
94
|
export default { name, inject, apply };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Local ctx0 companion management, independent of the selected agent world. */
|
|
2
|
+
import { type FetchLike } from './runtime-settings-api.js';
|
|
3
|
+
import type { BetterDashStatus } from '../companion-contract.js';
|
|
4
|
+
export type CompanionOperation = 'getBetterDash' | 'installBetterDash';
|
|
5
|
+
export declare function callCompanion(operation: CompanionOperation, doFetch?: FetchLike): Promise<BetterDashStatus>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** Local ctx0 companion management, independent of the selected agent world. */
|
|
2
|
+
import { mintRpcId } from './runtime-settings-api.js';
|
|
3
|
+
export async function callCompanion(operation, doFetch) {
|
|
4
|
+
const method = `superdCompanions/${operation}`;
|
|
5
|
+
const send = doFetch ?? ((path, init) => fetch(path, init));
|
|
6
|
+
const response = await send(`/api/${method}`, {
|
|
7
|
+
method: 'POST', credentials: 'same-origin', headers: { 'content-type': 'application/json' },
|
|
8
|
+
body: JSON.stringify({ type: 'client-request', rpcId: mintRpcId(), method, payload: { args: {} } }),
|
|
9
|
+
});
|
|
10
|
+
if (!response.ok)
|
|
11
|
+
throw new Error(`Better Dash: HTTP ${response.status}`);
|
|
12
|
+
let body;
|
|
13
|
+
try {
|
|
14
|
+
body = JSON.parse(await response.text());
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
throw new Error('Better Dash: unreadable response');
|
|
18
|
+
}
|
|
19
|
+
const result = body?.result;
|
|
20
|
+
if (result?.ok !== true)
|
|
21
|
+
throw new Error(result?.error?.message ?? 'Better Dash: request failed');
|
|
22
|
+
const value = result.value;
|
|
23
|
+
if (value === null || typeof value !== 'object' || typeof value.installed !== 'boolean'
|
|
24
|
+
|| typeof value.installing !== 'boolean' || typeof value.requestedVersion !== 'string'
|
|
25
|
+
|| (value.version !== undefined && typeof value.version !== 'string')
|
|
26
|
+
|| (value.enabled !== undefined && typeof value.enabled !== 'boolean')
|
|
27
|
+
|| (value.error !== undefined && typeof value.error !== 'string'))
|
|
28
|
+
throw new Error('Better Dash: invalid status');
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ProfileContext } from '@deepseek-ai/dsh-app-boot';
|
|
2
|
+
import type { PluginManager } from '@deepseek-ai/dsh-plugin-manager';
|
|
3
|
+
import type { BetterDashStatus } from './companion-contract.js';
|
|
4
|
+
type Manager = Pick<PluginManager, 'listBundles' | 'installBundle'>;
|
|
5
|
+
/** One controller per native profile; no package, registry, HMR or selection writer of its own. */
|
|
6
|
+
export declare class BetterDashCompanion {
|
|
7
|
+
private readonly version;
|
|
8
|
+
private readonly manager;
|
|
9
|
+
private readonly directory;
|
|
10
|
+
private readonly receipt;
|
|
11
|
+
private readonly legacy;
|
|
12
|
+
private initialized?;
|
|
13
|
+
private intent;
|
|
14
|
+
private active?;
|
|
15
|
+
private stopped;
|
|
16
|
+
private restartNotice?;
|
|
17
|
+
constructor(profile: Pick<ProfileContext, 'home' | 'dir'>, version: string, manager: () => Manager | undefined);
|
|
18
|
+
private initialize;
|
|
19
|
+
private cleanLegacy;
|
|
20
|
+
private save;
|
|
21
|
+
getBetterDash(): Promise<BetterDashStatus>;
|
|
22
|
+
ensureAutomatic(): Promise<BetterDashStatus>;
|
|
23
|
+
installBetterDash(): Promise<BetterDashStatus>;
|
|
24
|
+
private run;
|
|
25
|
+
private install;
|
|
26
|
+
/** Read-only observation: an intermediate native event must never trigger reinstatement. */
|
|
27
|
+
observeNativeChange(): Promise<BetterDashStatus>;
|
|
28
|
+
dispose(): void;
|
|
29
|
+
}
|
|
30
|
+
export {};
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/** Companion intent and the single native installation operation. */
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { readFile, mkdir, unlink, rmdir } from 'node:fs/promises';
|
|
4
|
+
import { join, dirname } from 'node:path';
|
|
5
|
+
import { writeFileAtomic } from '@deepseek-ai/dsh-atomic-write';
|
|
6
|
+
const message = (error) => error instanceof Error ? error.message : String(error);
|
|
7
|
+
async function optionalJson(path) {
|
|
8
|
+
try {
|
|
9
|
+
return JSON.parse(await readFile(path, 'utf8'));
|
|
10
|
+
}
|
|
11
|
+
catch (error) {
|
|
12
|
+
if (error.code === 'ENOENT')
|
|
13
|
+
return undefined;
|
|
14
|
+
throw error;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function nativeFailure(result) {
|
|
18
|
+
if ((result.application === 'applied' || result.application === 'restart-required') && result.error === undefined
|
|
19
|
+
&& (result.packageResult === undefined || result.packageResult.exitCode === 0))
|
|
20
|
+
return undefined;
|
|
21
|
+
return [result.application, result.error?.code, result.error?.diagnostic,
|
|
22
|
+
result.packageResult?.kind, result.packageResult?.logPath].filter(Boolean).join(': ');
|
|
23
|
+
}
|
|
24
|
+
/** One controller per native profile; no package, registry, HMR or selection writer of its own. */
|
|
25
|
+
export class BetterDashCompanion {
|
|
26
|
+
version;
|
|
27
|
+
manager;
|
|
28
|
+
directory;
|
|
29
|
+
receipt;
|
|
30
|
+
legacy;
|
|
31
|
+
initialized;
|
|
32
|
+
intent = { attempted: false };
|
|
33
|
+
active;
|
|
34
|
+
stopped = false;
|
|
35
|
+
restartNotice;
|
|
36
|
+
constructor(profile, version, manager) {
|
|
37
|
+
this.version = version;
|
|
38
|
+
this.manager = manager;
|
|
39
|
+
const key = createHash('sha256').update(profile.dir).digest('hex') + '.json';
|
|
40
|
+
this.directory = join(profile.home, 'superd', 'companions');
|
|
41
|
+
this.receipt = join(this.directory, key);
|
|
42
|
+
this.legacy = join(profile.home, 'companion-registration', key);
|
|
43
|
+
}
|
|
44
|
+
initialize() {
|
|
45
|
+
return this.initialized ??= (async () => {
|
|
46
|
+
const saved = await optionalJson(this.receipt);
|
|
47
|
+
if (saved !== undefined) {
|
|
48
|
+
this.intent = saved;
|
|
49
|
+
await this.cleanLegacy();
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const old = await optionalJson(this.legacy);
|
|
53
|
+
// Any old registration was first use. An absent Better now means removal,
|
|
54
|
+
// including when Super has since changed version; never resurrect it.
|
|
55
|
+
if (old?.['super-dsh:better-dsh'] !== undefined) {
|
|
56
|
+
this.intent = { attempted: true };
|
|
57
|
+
await this.save();
|
|
58
|
+
await this.cleanLegacy();
|
|
59
|
+
}
|
|
60
|
+
})();
|
|
61
|
+
}
|
|
62
|
+
async cleanLegacy() {
|
|
63
|
+
try {
|
|
64
|
+
await unlink(this.legacy);
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
if (error.code !== 'ENOENT')
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
await rmdir(dirname(this.legacy));
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
if (!['ENOENT', 'ENOTEMPTY'].includes(error.code ?? ''))
|
|
75
|
+
throw error;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
async save() {
|
|
79
|
+
await mkdir(this.directory, { recursive: true, mode: 0o700 });
|
|
80
|
+
await writeFileAtomic(this.receipt, JSON.stringify(this.intent) + '\n', { mode: 0o600 });
|
|
81
|
+
}
|
|
82
|
+
async getBetterDash() {
|
|
83
|
+
const status = { installed: false, installing: this.active !== undefined, requestedVersion: this.version };
|
|
84
|
+
try {
|
|
85
|
+
await this.initialize();
|
|
86
|
+
const manager = this.manager();
|
|
87
|
+
if (manager === undefined)
|
|
88
|
+
throw new Error('Native Plugin Manager is unavailable; enable it and retry Install in Settings.');
|
|
89
|
+
const better = (await manager.listBundles()).find(bundle => bundle.name === 'better-dsh' && bundle.installed);
|
|
90
|
+
if (better !== undefined) {
|
|
91
|
+
status.installed = true;
|
|
92
|
+
if (better.version !== undefined)
|
|
93
|
+
status.version = better.version;
|
|
94
|
+
status.enabled = better.enabled;
|
|
95
|
+
// An installed dependency alone does not prove application succeeded:
|
|
96
|
+
// native configure can fail after writing that dependency. Only a
|
|
97
|
+
// missing -> installed recovery clears a previous failure here.
|
|
98
|
+
if (this.active === undefined && this.intent.error !== undefined && this.intent.missingAfterFailure === true) {
|
|
99
|
+
delete this.intent.error;
|
|
100
|
+
delete this.intent.missingAfterFailure;
|
|
101
|
+
await this.save();
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
this.restartNotice = undefined;
|
|
106
|
+
if (this.active === undefined && this.intent.error !== undefined && this.intent.missingAfterFailure !== true) {
|
|
107
|
+
this.intent.missingAfterFailure = true;
|
|
108
|
+
await this.save();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const error = this.intent.error ?? this.restartNotice;
|
|
112
|
+
if (error !== undefined)
|
|
113
|
+
status.error = error;
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
status.error = message(error);
|
|
117
|
+
}
|
|
118
|
+
return status;
|
|
119
|
+
}
|
|
120
|
+
ensureAutomatic() { return this.run(false); }
|
|
121
|
+
installBetterDash() { return this.run(true); }
|
|
122
|
+
run(manual) {
|
|
123
|
+
if (this.active !== undefined)
|
|
124
|
+
return this.active;
|
|
125
|
+
const task = this.install(manual).catch(async (error) => {
|
|
126
|
+
this.intent.error = `Better Dash installation failed: ${message(error)}. Check Better Dash in Plugin Manager; if missing, retry Install in Settings.`;
|
|
127
|
+
const status = await this.getBetterDash();
|
|
128
|
+
this.intent.missingAfterFailure = !status.installed;
|
|
129
|
+
try {
|
|
130
|
+
await this.save();
|
|
131
|
+
}
|
|
132
|
+
catch { /* Report the operation error even when persistence is unavailable. */ }
|
|
133
|
+
return status;
|
|
134
|
+
}).then(status => ({ ...status, installing: false })).finally(() => { this.active = undefined; });
|
|
135
|
+
this.active = task;
|
|
136
|
+
return task;
|
|
137
|
+
}
|
|
138
|
+
async install(manual) {
|
|
139
|
+
await this.initialize();
|
|
140
|
+
const before = await this.getBetterDash();
|
|
141
|
+
if (this.stopped)
|
|
142
|
+
return before;
|
|
143
|
+
if (before.installed) {
|
|
144
|
+
this.intent.attempted = true;
|
|
145
|
+
if (this.intent.missingAfterFailure === true) {
|
|
146
|
+
delete this.intent.error;
|
|
147
|
+
delete this.intent.missingAfterFailure;
|
|
148
|
+
}
|
|
149
|
+
await this.save();
|
|
150
|
+
return this.getBetterDash(); // Native disablement and installed versions belong to the user.
|
|
151
|
+
}
|
|
152
|
+
// Native changed notifications enter through this operation, so its
|
|
153
|
+
// read is active and deliberately cannot update the receipt itself.
|
|
154
|
+
// Record removal here even when first-use installation is already done.
|
|
155
|
+
if (this.intent.error !== undefined && this.intent.missingAfterFailure !== true) {
|
|
156
|
+
this.intent.missingAfterFailure = true;
|
|
157
|
+
await this.save();
|
|
158
|
+
}
|
|
159
|
+
if (!manual && this.intent.attempted)
|
|
160
|
+
return before;
|
|
161
|
+
this.intent = { attempted: true };
|
|
162
|
+
await this.save(); // First use is durable before native HMR can unload this row.
|
|
163
|
+
const manager = this.manager();
|
|
164
|
+
if (manager === undefined)
|
|
165
|
+
throw new Error('Native Plugin Manager is unavailable');
|
|
166
|
+
if (this.version === 'unknown' || this.version.trim() === '')
|
|
167
|
+
throw new Error('The installed Super Dash package version could not be read');
|
|
168
|
+
const failures = [];
|
|
169
|
+
for (const spec of [`better-dsh@${this.version}`, 'better-dsh@latest', 'better-dsh']) {
|
|
170
|
+
if (this.stopped)
|
|
171
|
+
break;
|
|
172
|
+
try {
|
|
173
|
+
const result = await manager.installBundle(spec);
|
|
174
|
+
const failure = nativeFailure(result);
|
|
175
|
+
if (failure === undefined) {
|
|
176
|
+
const after = await this.getBetterDash();
|
|
177
|
+
if (after.installed) {
|
|
178
|
+
delete this.intent.error;
|
|
179
|
+
delete this.intent.missingAfterFailure;
|
|
180
|
+
this.restartNotice = result.application === 'restart-required'
|
|
181
|
+
? 'Better Dash is installed; restart DSH to apply the native change.' : undefined;
|
|
182
|
+
await this.save();
|
|
183
|
+
return this.getBetterDash();
|
|
184
|
+
}
|
|
185
|
+
failures.push(`${spec}: native manager reported ${result.application}, but Better Dash is not installed`);
|
|
186
|
+
}
|
|
187
|
+
else
|
|
188
|
+
failures.push(`${spec}: ${failure}`);
|
|
189
|
+
}
|
|
190
|
+
catch (error) {
|
|
191
|
+
failures.push(`${spec}: ${message(error)}`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
throw new Error(failures.join('; ') || 'Super Dash companion component was unloaded');
|
|
195
|
+
}
|
|
196
|
+
/** Read-only observation: an intermediate native event must never trigger reinstatement. */
|
|
197
|
+
async observeNativeChange() { return this.getBetterDash(); }
|
|
198
|
+
dispose() { this.stopped = true; }
|
|
199
|
+
}
|
|
@@ -132,6 +132,7 @@ try {
|
|
|
132
132
|
// is active.
|
|
133
133
|
const isExempt = (path) => path === CONTROL || path.startsWith(CONTROL + '/')
|
|
134
134
|
|| path === CONTROL_PREFIX || path.startsWith(CONTROL_PREFIX + '/')
|
|
135
|
+
|| path === '/api/superdCompanions' || path.startsWith('/api/superdCompanions/')
|
|
135
136
|
// Key compare matches the server demux shape: searchParams.get('agent')
|
|
136
137
|
// looks up PERCENT-DECODED keys, so '%61gent=x' demuxes as agent=x and the
|
|
137
138
|
// surgery must strip those spellings too (+' ' wins the '+' spelling).
|
|
@@ -82,24 +82,30 @@ export function apply(ctx, config) {
|
|
|
82
82
|
// would multiply probes against every paired remote. The effect's
|
|
83
83
|
// disposer IS the sweep disposer — the timer dies with the row
|
|
84
84
|
// (unref'd, overlap-skipping, per-machine errors swallowed; mesh-link).
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
host.effect(() => startReprobeSweep({ home, intervalSec: config?.reprobeIntervalSec, immediate: true }), 'superd-hub: mesh reprobe sweep');
|
|
86
|
+
host.effect(() => registerHubRoute({
|
|
87
87
|
real: real,
|
|
88
88
|
authorize: (req, res) => connection.authorizeIndex(req, res),
|
|
89
89
|
requestRejection: (req) => connection.requestRejection(req),
|
|
90
90
|
gatewayOf: (label) => (federatedGatewayOf(label) ?? getTarget(label)?.gateway),
|
|
91
|
-
});
|
|
92
|
-
ctx.effect(() => dispose, 'superd-hub: /hub route family');
|
|
91
|
+
}), 'superd-hub: /hub route family');
|
|
93
92
|
// P4 §8.2: the PTY executor's upgrade route — same gate as the /hub family
|
|
94
93
|
// (authorizeIndex cookie auth first, then the browser-trust fence). The
|
|
95
94
|
// dumb pump lives in exec-pty-rpc.ts; a2a-row owns the initiator half.
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
95
|
+
// The async effect belongs to this Connection injection: disposal waits
|
|
96
|
+
// for setup and releases its route even when setup finishes during unload.
|
|
97
|
+
host.effect(async () => {
|
|
98
|
+
try {
|
|
99
|
+
return await registerPtyExecutor(real, {
|
|
100
|
+
authorize: (req, res) => connection.authorizeIndex(req, res),
|
|
101
|
+
requestRejection: (req) => connection.requestRejection(req),
|
|
102
|
+
log: consoleA2aLog(),
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return () => { };
|
|
107
|
+
}
|
|
108
|
+
}, 'superd-hub: PTY executor upgrade route');
|
|
103
109
|
});
|
|
104
110
|
}
|
|
105
111
|
export default apply;
|
|
@@ -2198,6 +2198,145 @@ async function removeProvider(agentKey, namespace, id, doFetch) {
|
|
|
2198
2198
|
return requireProvidersView(await callRuntimeSettings(agentKey, namespace, "remove", { request: { id } }, doFetch));
|
|
2199
2199
|
}
|
|
2200
2200
|
|
|
2201
|
+
//#endregion
|
|
2202
|
+
//#region src/client/companion-api.ts
|
|
2203
|
+
async function callCompanion(operation, doFetch) {
|
|
2204
|
+
const method = `superdCompanions/${operation}`;
|
|
2205
|
+
const response = await (doFetch ?? ((path, init) => fetch(path, init)))(`/api/${method}`, {
|
|
2206
|
+
method: "POST",
|
|
2207
|
+
credentials: "same-origin",
|
|
2208
|
+
headers: { "content-type": "application/json" },
|
|
2209
|
+
body: JSON.stringify({
|
|
2210
|
+
type: "client-request",
|
|
2211
|
+
rpcId: mintRpcId(),
|
|
2212
|
+
method,
|
|
2213
|
+
payload: { args: {} }
|
|
2214
|
+
})
|
|
2215
|
+
});
|
|
2216
|
+
if (!response.ok) throw new Error(`Better Dash: HTTP ${response.status}`);
|
|
2217
|
+
let body;
|
|
2218
|
+
try {
|
|
2219
|
+
body = JSON.parse(await response.text());
|
|
2220
|
+
} catch {
|
|
2221
|
+
throw new Error("Better Dash: unreadable response");
|
|
2222
|
+
}
|
|
2223
|
+
const result = body?.result;
|
|
2224
|
+
if (result?.ok !== true) throw new Error(result?.error?.message ?? "Better Dash: request failed");
|
|
2225
|
+
const value = result.value;
|
|
2226
|
+
if (value === null || typeof value !== "object" || typeof value.installed !== "boolean" || typeof value.installing !== "boolean" || typeof value.requestedVersion !== "string" || value.version !== void 0 && typeof value.version !== "string" || value.enabled !== void 0 && typeof value.enabled !== "boolean" || value.error !== void 0 && typeof value.error !== "string") throw new Error("Better Dash: invalid status");
|
|
2227
|
+
return value;
|
|
2228
|
+
}
|
|
2229
|
+
|
|
2230
|
+
//#endregion
|
|
2231
|
+
//#region src/client/CompanionSettings.tsx
|
|
2232
|
+
/** Native plugin switches remain in Plugin Manager; this row provides explicit recovery. */
|
|
2233
|
+
function CompanionSettings() {
|
|
2234
|
+
const [status, setStatus] = react.default.useState();
|
|
2235
|
+
const [error, setError] = react.default.useState();
|
|
2236
|
+
const [busy, setBusy] = react.default.useState(false);
|
|
2237
|
+
const live = react.default.useRef(false);
|
|
2238
|
+
const operation = react.default.useRef(false);
|
|
2239
|
+
react.default.useEffect(() => {
|
|
2240
|
+
live.current = true;
|
|
2241
|
+
let reading = false;
|
|
2242
|
+
const refresh = async () => {
|
|
2243
|
+
if (reading || operation.current) return;
|
|
2244
|
+
reading = true;
|
|
2245
|
+
try {
|
|
2246
|
+
const next = await callCompanion("getBetterDash");
|
|
2247
|
+
if (live.current) {
|
|
2248
|
+
setStatus(next);
|
|
2249
|
+
setError(next.error);
|
|
2250
|
+
}
|
|
2251
|
+
} catch (cause) {
|
|
2252
|
+
if (live.current) setError(cause instanceof Error ? cause.message : String(cause));
|
|
2253
|
+
} finally {
|
|
2254
|
+
reading = false;
|
|
2255
|
+
}
|
|
2256
|
+
};
|
|
2257
|
+
refresh();
|
|
2258
|
+
const timer = window.setInterval(() => {
|
|
2259
|
+
refresh();
|
|
2260
|
+
}, 5e3);
|
|
2261
|
+
return () => {
|
|
2262
|
+
live.current = false;
|
|
2263
|
+
window.clearInterval(timer);
|
|
2264
|
+
};
|
|
2265
|
+
}, []);
|
|
2266
|
+
const install = async () => {
|
|
2267
|
+
if (operation.current) return;
|
|
2268
|
+
operation.current = true;
|
|
2269
|
+
setBusy(true);
|
|
2270
|
+
setError(void 0);
|
|
2271
|
+
try {
|
|
2272
|
+
const next = await callCompanion("installBetterDash");
|
|
2273
|
+
if (live.current) {
|
|
2274
|
+
setStatus(next);
|
|
2275
|
+
setError(next.error);
|
|
2276
|
+
}
|
|
2277
|
+
} catch (cause) {
|
|
2278
|
+
if (live.current) setError(cause instanceof Error ? cause.message : String(cause));
|
|
2279
|
+
} finally {
|
|
2280
|
+
operation.current = false;
|
|
2281
|
+
if (live.current) setBusy(false);
|
|
2282
|
+
}
|
|
2283
|
+
};
|
|
2284
|
+
const installing = busy || status?.installing === true;
|
|
2285
|
+
const label = status === void 0 ? "Loading…" : status.installed ? `Installed${status.version ? " · " + status.version : ""}${status.enabled === false ? " · Disabled" : ""}` : "Not installed";
|
|
2286
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2287
|
+
style: {
|
|
2288
|
+
borderRadius: 12,
|
|
2289
|
+
padding: "12px 14px",
|
|
2290
|
+
background: "var(--dsw-alias-interactive-bg-hover)"
|
|
2291
|
+
},
|
|
2292
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2293
|
+
style: {
|
|
2294
|
+
display: "flex",
|
|
2295
|
+
alignItems: "center",
|
|
2296
|
+
gap: 16
|
|
2297
|
+
},
|
|
2298
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2299
|
+
style: { flex: 1 },
|
|
2300
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2301
|
+
style: {
|
|
2302
|
+
fontSize: 14,
|
|
2303
|
+
fontWeight: 600
|
|
2304
|
+
},
|
|
2305
|
+
children: "Better Dash"
|
|
2306
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2307
|
+
role: "status",
|
|
2308
|
+
style: {
|
|
2309
|
+
fontSize: 12,
|
|
2310
|
+
color: "var(--dsw-alias-label-secondary)",
|
|
2311
|
+
marginTop: 4
|
|
2312
|
+
},
|
|
2313
|
+
children: installing ? "Installing…" : label
|
|
2314
|
+
})]
|
|
2315
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2316
|
+
type: "button",
|
|
2317
|
+
disabled: installing || status?.installed === true,
|
|
2318
|
+
onClick: () => {
|
|
2319
|
+
install();
|
|
2320
|
+
},
|
|
2321
|
+
style: {
|
|
2322
|
+
padding: "6px 12px",
|
|
2323
|
+
borderRadius: 8,
|
|
2324
|
+
cursor: installing || status?.installed ? "default" : "pointer"
|
|
2325
|
+
},
|
|
2326
|
+
children: installing ? "Installing…" : status?.installed ? "Installed" : "Install"
|
|
2327
|
+
})]
|
|
2328
|
+
}), error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2329
|
+
role: "alert",
|
|
2330
|
+
style: {
|
|
2331
|
+
fontSize: 12,
|
|
2332
|
+
margin: "8px 0 0",
|
|
2333
|
+
color: "var(--dsw-alias-state-error-primary)"
|
|
2334
|
+
},
|
|
2335
|
+
children: error
|
|
2336
|
+
})]
|
|
2337
|
+
});
|
|
2338
|
+
}
|
|
2339
|
+
|
|
2201
2340
|
//#endregion
|
|
2202
2341
|
//#region src/client/HubPage.tsx
|
|
2203
2342
|
const EMPTY_DRAFT = {
|
|
@@ -3415,16 +3554,8 @@ function HubPage() {
|
|
|
3415
3554
|
},
|
|
3416
3555
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(GroupHead, {
|
|
3417
3556
|
title: "Settings",
|
|
3418
|
-
count:
|
|
3419
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
3420
|
-
style: {
|
|
3421
|
-
margin: 0,
|
|
3422
|
-
fontSize: 13,
|
|
3423
|
-
lineHeight: "20px",
|
|
3424
|
-
color: "var(--dsw-alias-label-tertiary)"
|
|
3425
|
-
},
|
|
3426
|
-
children: "Hub preferences — pairing, runtime visibility, and orchestration defaults land with the hub service."
|
|
3427
|
-
})]
|
|
3557
|
+
count: 1
|
|
3558
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CompanionSettings, {})]
|
|
3428
3559
|
}),
|
|
3429
3560
|
tab === "home" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
3430
3561
|
style: {
|
package/superd-hub/package.json
CHANGED
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
// Separate-process entry for the SAME native package operations used by dsh plugin.
|
|
2
|
-
// No shell interpolation, package scripts, upstream edits, or online companion lookup.
|
|
3
|
-
import { createRequire } from 'node:module'
|
|
4
|
-
import { readFile } from 'node:fs/promises'
|
|
5
|
-
import { join } from 'node:path'
|
|
6
|
-
import { pathToFileURL } from 'node:url'
|
|
7
|
-
let input = ''
|
|
8
|
-
for await (const bytes of process.stdin) input += bytes
|
|
9
|
-
const {profile,expected} = JSON.parse(input)
|
|
10
|
-
const require = createRequire(profile.installAnchor)
|
|
11
|
-
const {runProfilePnpm,saveManifest} = await import(pathToFileURL(require.resolve('@deepseek-ai/dsh-plugin-manager/operations')).href)
|
|
12
|
-
const {withFileLock,writeFileAtomic} = await import(pathToFileURL(require.resolve('@deepseek-ai/dsh-atomic-write')).href)
|
|
13
|
-
const abort = new AbortController()
|
|
14
|
-
process.once('SIGTERM', () => abort.abort())
|
|
15
|
-
const json = async path => JSON.parse(await readFile(path, 'utf8'))
|
|
16
|
-
async function optionalJson(path) { try { return await json(path) } catch(error) { if(error.code === 'ENOENT') return undefined; throw error } }
|
|
17
|
-
const skipped = disposition => ({ exitCode: 0, disposition })
|
|
18
|
-
// Complete each native acquisition/release before observing cancellation;
|
|
19
|
-
// exiting during its async lock-file write can strand incomplete lock metadata.
|
|
20
|
-
async function withCancellableProfileLock(operation) {
|
|
21
|
-
const path = join(profile.dir, 'package.json'), deadline = Date.now() + 120000
|
|
22
|
-
for (;;) {
|
|
23
|
-
abort.signal.throwIfAborted()
|
|
24
|
-
let acquired = false
|
|
25
|
-
try {
|
|
26
|
-
return await withFileLock(path, async () => {
|
|
27
|
-
acquired = true
|
|
28
|
-
abort.signal.throwIfAborted()
|
|
29
|
-
return operation()
|
|
30
|
-
}, { waitMs: Math.min(250, Math.max(0, deadline - Date.now())) })
|
|
31
|
-
} catch (error) {
|
|
32
|
-
abort.signal.throwIfAborted()
|
|
33
|
-
if (acquired || Date.now() >= deadline
|
|
34
|
-
|| error.message !== `atomic-write: timed out waiting for the writer lock at ${path}.lock`) throw error
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
const result = await withCancellableProfileLock(async () => {
|
|
39
|
-
abort.signal.throwIfAborted()
|
|
40
|
-
const current = await json(join(profile.dir, 'package.json'))
|
|
41
|
-
if (typeof expected.ownerSpec !== 'string' || current.dependencies?.[expected.owner] !== expected.ownerSpec) return skipped('owner-changed')
|
|
42
|
-
const ownerDir = join(profile.dir, 'node_modules', expected.owner)
|
|
43
|
-
let owner
|
|
44
|
-
try { owner = await json(join(ownerDir, 'package.json')) }
|
|
45
|
-
catch (error) { if (error.code === 'ENOENT') return skipped('owner-changed'); throw error }
|
|
46
|
-
const item=expected.companion
|
|
47
|
-
if(owner.name !== 'super-dsh' || owner.version !== item.version || owner.dependencies?.[item.name] !== item.version) return skipped('owner-changed')
|
|
48
|
-
// Resolve from the owner: a retained older direct Better may coexist with the new transitive version.
|
|
49
|
-
const ownerRequire=createRequire(join(ownerDir,'package.json'))
|
|
50
|
-
const downloaded=await json(ownerRequire.resolve(item.name+'/package.json'))
|
|
51
|
-
if(downloaded.name !== item.name || downloaded.version !== item.version) throw new Error('The user installation did not fetch the exact companion dependency')
|
|
52
|
-
const prior = current.dependencies?.[item.name]
|
|
53
|
-
if (prior !== expected.priorSpec) return skipped(prior === undefined && expected.priorSpec !== undefined ? 'companion-removed' : 'companion-changed')
|
|
54
|
-
abort.signal.throwIfAborted()
|
|
55
|
-
// A plain npm consumer already has the exact transitive files but not a pnpm
|
|
56
|
-
// store. Fresh hoisted registration changes metadata only, preserving npm ci.
|
|
57
|
-
const npmLockPath=join(profile.dir,'package-lock.json')
|
|
58
|
-
const npmLock=await optionalJson(npmLockPath)
|
|
59
|
-
const staged=structuredClone(current)
|
|
60
|
-
staged.dependencies={...staged.dependencies,[item.name]:item.version}
|
|
61
|
-
const selectNew=manifest=>{
|
|
62
|
-
if(prior !== undefined)return
|
|
63
|
-
const bundles=manifest.dsh?.profile?.bundles??[]
|
|
64
|
-
manifest.dsh={...manifest.dsh,profile:{...manifest.dsh?.profile,bundles:[...bundles,...bundles.includes(item.name)?[]:[item.name]]}}
|
|
65
|
-
}
|
|
66
|
-
const installedBefore=await optionalJson(join(profile.dir,'node_modules',item.name,'package.json'))
|
|
67
|
-
if(npmLock?.packages?.[''] && installedBefore?.version === item.version) {
|
|
68
|
-
const beforeLock=await readFile(npmLockPath,'utf8')
|
|
69
|
-
npmLock.packages[''].dependencies={...npmLock.packages[''].dependencies,[item.name]:item.version}
|
|
70
|
-
try {
|
|
71
|
-
selectNew(staged)
|
|
72
|
-
await writeFileAtomic(npmLockPath,JSON.stringify(npmLock,null,2)+'\n',{mode:0o600})
|
|
73
|
-
await saveManifest(profile.dir,staged)
|
|
74
|
-
} catch(error) { await writeFileAtomic(npmLockPath,beforeLock,{mode:0o600});await saveManifest(profile.dir,current);throw error }
|
|
75
|
-
return {exitCode:0,disposition:'installed'}
|
|
76
|
-
}
|
|
77
|
-
// No named spec: native add --offline still does online pnpm view preflight.
|
|
78
|
-
// Stage the exact dependency under the same lock, then re-layout cached files
|
|
79
|
-
// with the package manager that actually produced the consumer's lockfile.
|
|
80
|
-
await saveManifest(profile.dir,staged)
|
|
81
|
-
let operation
|
|
82
|
-
try {
|
|
83
|
-
operation=await runProfilePnpm({profile:profile.name,dir:profile.dir,installAnchor:profile.installAnchor,cwd:profile.dir,home:profile.home},
|
|
84
|
-
npmLock ? ['install','--offline','--ignore-scripts','--no-audit','--no-fund'] : ['install','--offline','--ignore-scripts','--no-frozen-lockfile'], {
|
|
85
|
-
...profile.packageManager, ...npmLock?{command:'npm',args:[]}: {},
|
|
86
|
-
execution:'service', signal:abort.signal, outputBytes:16384,
|
|
87
|
-
lookupTimeoutMs:120000, idleTimeoutMs:120000,
|
|
88
|
-
})
|
|
89
|
-
if(operation.exitCode !== 0) { await saveManifest(profile.dir,current) }
|
|
90
|
-
else {
|
|
91
|
-
const installed=await json(join(profile.dir,'node_modules',item.name,'package.json'))
|
|
92
|
-
const after=await json(join(profile.dir,'package.json'))
|
|
93
|
-
if(installed.name !== item.name || installed.version !== item.version || after.dependencies?.[item.name] !== item.version) throw new Error('Native companion registration did not resolve the exact cached registry version')
|
|
94
|
-
selectNew(after)
|
|
95
|
-
await saveManifest(profile.dir,after)
|
|
96
|
-
}
|
|
97
|
-
} catch(error) { await saveManifest(profile.dir,current);throw error }
|
|
98
|
-
return { exitCode: operation.exitCode, logPath: operation.logPath, disposition: 'installed' }
|
|
99
|
-
})
|
|
100
|
-
process.stdout.write(JSON.stringify(result)+'\n')
|