super-dsh 0.1.9 → 0.1.10-e
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 +25 -3
- package/agent-agy/cordis.patch.yml +7 -4
- package/agent-agy/dist/agent.d.ts +12 -11
- package/agent-agy/dist/agy-auth-gateway.d.ts +124 -0
- package/agent-agy/dist/agy-auth-gateway.js +233 -0
- package/agent-agy/dist/agy-auth.d.ts +153 -0
- package/agent-agy/dist/agy-auth.js +179 -0
- package/agent-agy/dist/agy-cli-client.d.ts +39 -9
- package/agent-agy/dist/agy-cli-client.js +82 -18
- package/agent-agy/dist/agy-store.d.ts +8 -0
- package/agent-agy/dist/index.d.ts +46 -16
- package/agent-agy/dist/index.js +82 -56
- package/agent-agy/dist/models.d.ts +70 -0
- package/agent-agy/dist/models.js +102 -3
- package/agent-agy/package.json +7 -5
- package/agent-agy-sdk/cordis.patch.yml +58 -0
- package/agent-agy-sdk/dist/adapter.d.ts +15 -0
- package/agent-agy-sdk/dist/adapter.js +42 -0
- package/agent-agy-sdk/dist/agent-preset-agy.d.ts +27 -0
- package/agent-agy-sdk/dist/agent-preset-agy.js +32 -0
- package/agent-agy-sdk/dist/agent.d.ts +138 -0
- package/agent-agy-sdk/dist/agent.js +494 -0
- package/{agent-agy → agent-agy-sdk}/dist/agy-client.d.ts +26 -6
- package/{agent-agy → agent-agy-sdk}/dist/agy-client.js +43 -25
- package/agent-agy-sdk/dist/agy-sessions.d.ts +25 -0
- package/agent-agy-sdk/dist/agy-sessions.js +107 -0
- package/agent-agy-sdk/dist/agy-store.d.ts +39 -0
- package/agent-agy-sdk/dist/agy-store.js +83 -0
- package/agent-agy-sdk/dist/inbox.d.ts +26 -0
- package/agent-agy-sdk/dist/inbox.js +53 -0
- package/agent-agy-sdk/dist/index.d.ts +93 -0
- package/agent-agy-sdk/dist/index.js +476 -0
- package/agent-agy-sdk/dist/models.d.ts +41 -0
- package/agent-agy-sdk/dist/models.js +74 -0
- package/agent-agy-sdk/dist/sdk-venv.d.ts +76 -0
- package/agent-agy-sdk/dist/sdk-venv.js +173 -0
- package/agent-agy-sdk/dist/world-plugin.d.ts +10 -0
- package/agent-agy-sdk/dist/world-plugin.js +82 -0
- package/agent-agy-sdk/package.json +64 -0
- package/agent-claude/dist/adapter.js +106 -31
- package/agent-claude/dist/agent.js +154 -17
- package/agent-claude/dist/claude-client.js +84 -43
- package/agent-claude/dist/claude-launch.js +101 -0
- package/agent-claude/dist/claude-native-config.js +169 -0
- package/agent-claude/dist/claude-provider-gateway.js +340 -0
- package/agent-claude/dist/claude-providers.js +391 -0
- package/agent-claude/dist/claude-route.js +152 -0
- package/agent-claude/dist/index.js +214 -37
- package/agent-claude/dist/models.js +14 -0
- package/agent-claude/dist/sdk-runtime.js +320 -0
- package/agent-claude/package.json +6 -4
- package/agent-codex/README.md +141 -36
- package/agent-codex/cordis.patch.yml +14 -10
- package/agent-codex/dist/adapter.js +64 -28
- package/agent-codex/dist/agent.js +255 -81
- package/agent-codex/dist/app-server-approvals.js +165 -0
- package/agent-codex/dist/app-server-client.js +723 -0
- package/agent-codex/dist/app-server-events.js +432 -0
- package/agent-codex/dist/app-server-protocol.js +95 -0
- package/agent-codex/dist/app-server-wire.js +299 -0
- package/agent-codex/dist/codex-launch.js +78 -0
- package/agent-codex/dist/codex-native-config.js +392 -0
- package/agent-codex/dist/codex-provider-gateway.js +402 -0
- package/agent-codex/dist/codex-providers.js +376 -0
- package/agent-codex/dist/codex-route.js +86 -0
- package/agent-codex/dist/index.js +256 -115
- package/agent-codex/dist/permission.js +75 -1
- package/agent-codex/package.json +1 -2
- package/agent-codex-sdk/README.md +79 -0
- package/agent-codex-sdk/cordis.patch.yml +81 -0
- package/agent-codex-sdk/dist/adapter.js +91 -0
- package/agent-codex-sdk/dist/agent-preset-codex.js +32 -0
- package/agent-codex-sdk/dist/agent.js +1085 -0
- package/agent-codex-sdk/dist/codex-native-config.js +392 -0
- package/agent-codex-sdk/dist/codex-provider-gateway.js +402 -0
- package/agent-codex-sdk/dist/codex-providers.js +376 -0
- package/agent-codex-sdk/dist/codex-route.js +86 -0
- package/{agent-codex/dist/codex-client.js → agent-codex-sdk/dist/codex-sdk-client.js} +253 -32
- package/agent-codex-sdk/dist/codex-sdk-launch.js +92 -0
- package/agent-codex-sdk/dist/codex-store.js +180 -0
- package/agent-codex-sdk/dist/inbox.js +53 -0
- package/agent-codex-sdk/dist/index.js +739 -0
- package/agent-codex-sdk/dist/permission.js +71 -0
- package/agent-codex-sdk/dist/sdk-runtime.js +307 -0
- package/agent-codex-sdk/dist/session-map.js +167 -0
- package/agent-codex-sdk/dist/world-plugin.js +93 -0
- package/agent-codex-sdk/package.json +79 -0
- package/agent-codex-sdk/scripts/fetch-sdk-runtime.mjs +57 -0
- package/agent-omp/README.md +72 -55
- package/agent-omp/dist/agent.js +36 -11
- package/agent-omp/dist/index.js +92 -52
- package/agent-omp/dist/lazy-rpc.js +11 -4
- package/agent-omp/dist/omp-cli.js +114 -97
- package/agent-omp/dist/omp-store.js +12 -4
- package/agent-omp/dist/permission.js +70 -5
- package/agent-omp/dist/rpc-client.js +409 -0
- package/agent-omp/dist/world-plugin.js +36 -3
- package/agent-omp/package.json +2 -6
- package/agent-omp-sdk/README.md +103 -0
- package/agent-omp-sdk/cordis.patch.yml +108 -0
- package/agent-omp-sdk/dist/adapter.d.ts +21 -0
- package/agent-omp-sdk/dist/adapter.js +36 -0
- package/agent-omp-sdk/dist/agent-preset-omp.d.ts +31 -0
- package/agent-omp-sdk/dist/agent-preset-omp.js +32 -0
- package/agent-omp-sdk/dist/agent.d.ts +64 -0
- package/agent-omp-sdk/dist/agent.js +1178 -0
- package/agent-omp-sdk/dist/discovery.d.ts +57 -0
- package/agent-omp-sdk/dist/discovery.js +116 -0
- package/agent-omp-sdk/dist/inbox.d.ts +28 -0
- package/agent-omp-sdk/dist/inbox.js +53 -0
- package/agent-omp-sdk/dist/index.d.ts +60 -0
- package/agent-omp-sdk/dist/index.js +632 -0
- package/agent-omp-sdk/dist/knobs.d.ts +34 -0
- package/agent-omp-sdk/dist/knobs.js +50 -0
- package/agent-omp-sdk/dist/lazy-rpc.d.ts +98 -0
- package/agent-omp-sdk/dist/lazy-rpc.js +178 -0
- package/agent-omp-sdk/dist/mobile/zoom-guard.d.ts +187 -0
- package/agent-omp-sdk/dist/mobile/zoom-guard.js +294 -0
- package/agent-omp-sdk/dist/mobile-boot.d.ts +49 -0
- package/agent-omp-sdk/dist/mobile-boot.js +38 -0
- package/agent-omp-sdk/dist/models.d.ts +39 -0
- package/agent-omp-sdk/dist/models.js +299 -0
- package/agent-omp-sdk/dist/omp-cli.d.ts +67 -0
- package/agent-omp-sdk/dist/omp-cli.js +164 -0
- package/agent-omp-sdk/dist/omp-disk-discovery.d.ts +27 -0
- package/agent-omp-sdk/dist/omp-disk-discovery.js +178 -0
- package/agent-omp-sdk/dist/omp-executable.d.ts +2 -0
- package/agent-omp-sdk/dist/omp-executable.js +30 -0
- package/agent-omp-sdk/dist/omp-store.d.ts +134 -0
- package/agent-omp-sdk/dist/omp-store.js +526 -0
- package/agent-omp-sdk/dist/pairing.d.ts +7 -0
- package/agent-omp-sdk/dist/pairing.js +45 -0
- package/agent-omp-sdk/dist/permission.d.ts +138 -0
- package/agent-omp-sdk/dist/permission.js +161 -0
- package/agent-omp-sdk/dist/protocol.d.ts +189 -0
- package/agent-omp-sdk/dist/rpc-types.d.ts +77 -0
- package/agent-omp-sdk/dist/rpc-types.js +1 -0
- package/agent-omp-sdk/dist/sdk-client.d.ts +160 -0
- package/{agent-omp → agent-omp-sdk}/dist/sdk-client.js +108 -56
- package/agent-omp-sdk/dist/sdk-runtime.d.ts +211 -0
- package/agent-omp-sdk/dist/sdk-runtime.js +418 -0
- package/agent-omp-sdk/dist/sidecar-client.d.ts +87 -0
- package/agent-omp-sdk/dist/sidecar-client.js +197 -0
- package/agent-omp-sdk/dist/store/adapter.d.ts +17 -0
- package/agent-omp-sdk/dist/store/adapter.js +47 -0
- package/agent-omp-sdk/dist/store/db.d.ts +70 -0
- package/agent-omp-sdk/dist/store/db.js +210 -0
- package/agent-omp-sdk/dist/store/index.d.ts +23 -0
- package/agent-omp-sdk/dist/store/index.js +59 -0
- package/agent-omp-sdk/dist/store/reconcile.d.ts +15 -0
- package/agent-omp-sdk/dist/store/reconcile.js +51 -0
- package/agent-omp-sdk/dist/store/schema.d.ts +8 -0
- package/agent-omp-sdk/dist/store/schema.js +40 -0
- package/agent-omp-sdk/dist/transport.d.ts +49 -0
- package/agent-omp-sdk/dist/transport.js +35 -0
- package/agent-omp-sdk/dist/world-home.d.ts +4 -0
- package/agent-omp-sdk/dist/world-home.js +23 -0
- package/agent-omp-sdk/dist/world-plugin.d.ts +10 -0
- package/agent-omp-sdk/dist/world-plugin.js +137 -0
- package/agent-omp-sdk/package.json +87 -0
- package/client/index.js +1204 -93
- package/cordis.patch.yml +64 -0
- package/package.json +17 -8
- package/superd-hub/dist/client/agent-settings.d.ts +210 -0
- package/superd-hub/dist/client/agent-settings.js +300 -0
- package/superd-hub/dist/client/roster.js +6 -0
- package/superd-hub/dist/client/runtime-settings-api.d.ts +76 -0
- package/superd-hub/dist/client/runtime-settings-api.js +137 -0
- package/superd-hub/dist/client/switch.d.ts +165 -0
- package/superd-hub/dist/client/switch.js +295 -0
- package/superd-hub/lib/client/index.js +1204 -93
- package/agent-codex/dist/models.js +0 -152
- package/agent-omp/dist/sidecar-client.js +0 -195
- /package/{agent-agy → agent-agy-sdk}/bridge/agy_bridge.py +0 -0
- /package/{agent-agy → agent-agy-sdk}/bridge/gemini_openai_server.mjs +0 -0
- /package/{agent-codex/dist/codex-events.js → agent-codex-sdk/dist/codex-sdk-events.js} +0 -0
- /package/{agent-omp → agent-omp-sdk}/dist/protocol.js +0 -0
- /package/{agent-omp → agent-omp-sdk}/sidecar/main.ts +0 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* runtime-settings-api.ts — the browser half of a runtime's settings panel.
|
|
3
|
+
*
|
|
4
|
+
* The panel's config belongs to the ADAPTER, so nothing here knows what an auth
|
|
5
|
+
* method is: it posts a `client-request` envelope at the runtime's own world and
|
|
6
|
+
* hands the adapter's answer to `parseAuthView` (agent-settings.ts).
|
|
7
|
+
*
|
|
8
|
+
* POST /hub/api?agent=agy
|
|
9
|
+
* { type:'client-request', rpcId, method:'agyAuth/get'|'agyAuth/set',
|
|
10
|
+
* payload:{ args: {} | { request: { mode } } } }
|
|
11
|
+
*
|
|
12
|
+
* `/hub/api` is the hub family's prefix mount (`hub-route.ts`), which forwards
|
|
13
|
+
* `method` verbatim to the addressed world's `typertGateway`. The path needs no
|
|
14
|
+
* `CONTROL` exemption in fusion-transport.ts: the rewrite only touches same-origin
|
|
15
|
+
* `/api/...` paths, and `/hub/...` is never one of them.
|
|
16
|
+
*
|
|
17
|
+
* Node-safe: no DOM reach at module scope and `fetch` is injectable, so
|
|
18
|
+
* test/client-runtime-settings.test.mjs pins the envelope and every failure
|
|
19
|
+
* branch under `node --test` (the mesh-api.ts precedent).
|
|
20
|
+
*
|
|
21
|
+
* @module superd-hub/client/runtime-settings-api
|
|
22
|
+
*/
|
|
23
|
+
import { type RuntimeAuthView, type RuntimeProvidersView } from './agent-settings.js';
|
|
24
|
+
/** Structural slice of `fetch` — the bundle uses the global, tests inject a stub. */
|
|
25
|
+
export type FetchLike = (input: string, init: RequestInit) => Promise<{
|
|
26
|
+
ok: boolean;
|
|
27
|
+
status: number;
|
|
28
|
+
text(): Promise<string>;
|
|
29
|
+
}>;
|
|
30
|
+
/** One rpcId for request/response correlation. `randomUUID` needs a secure context. */
|
|
31
|
+
export declare function mintRpcId(): string;
|
|
32
|
+
/** The wire body of one unary call. */
|
|
33
|
+
export interface ClientRequestEnvelope {
|
|
34
|
+
type: 'client-request';
|
|
35
|
+
rpcId: string;
|
|
36
|
+
method: string;
|
|
37
|
+
payload: {
|
|
38
|
+
args: Record<string, unknown>;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/** Build the envelope for one panel call (exported so tests pin the exact wire shape). */
|
|
42
|
+
/** The operation names the adapter Remotes accept (auth ladder + providers). */
|
|
43
|
+
export type RuntimeSettingsOp = 'get' | 'set' | 'save' | 'remove';
|
|
44
|
+
export declare function settingsEnvelope(namespace: string, op: RuntimeSettingsOp, args: Record<string, unknown>): ClientRequestEnvelope;
|
|
45
|
+
/** What a caller can do with a failed panel call — kept structural for the tests. */
|
|
46
|
+
declare function messageOf(error: unknown): string;
|
|
47
|
+
/**
|
|
48
|
+
* One unary call at a runtime's world, unwrapped to the RPC's value.
|
|
49
|
+
*
|
|
50
|
+
* Every failure mode is reported as a thrown `Error` carrying the adapter's own
|
|
51
|
+
* message where one exists: a transport failure, a malformed answer, and the
|
|
52
|
+
* gateway's business error (`{ok:false,error}`) must not look alike to the panel.
|
|
53
|
+
*
|
|
54
|
+
* @param agentKey - roster key addressing the world (`?agent=`).
|
|
55
|
+
* @param namespace - the adapter's Remote namespace (`agyAuth`).
|
|
56
|
+
* @param op - the Remote method.
|
|
57
|
+
* @param args - exact named wire arguments.
|
|
58
|
+
* @param doFetch - fetch implementation (tests inject).
|
|
59
|
+
* @returns the raw RPC value.
|
|
60
|
+
* @throws {Error} on transport, framing, or gateway failure.
|
|
61
|
+
*/
|
|
62
|
+
export declare function callRuntimeSettings(agentKey: string, namespace: string, op: RuntimeSettingsOp, args: Record<string, unknown>, doFetch?: FetchLike): Promise<unknown>;
|
|
63
|
+
/** Read one runtime's panel state, parsed. */
|
|
64
|
+
export declare function fetchAuthView(agentKey: string, namespace: string, doFetch?: FetchLike): Promise<RuntimeAuthView>;
|
|
65
|
+
/** Adopt one auth form (`null` clears the explicit choice). */
|
|
66
|
+
export declare function setAuthMode(agentKey: string, namespace: string, mode: string | null, doFetch?: FetchLike): Promise<RuntimeAuthView>;
|
|
67
|
+
/** Read the multi-provider state of one runtime. */
|
|
68
|
+
export declare function fetchProvidersView(agentKey: string, namespace: string, doFetch?: FetchLike): Promise<RuntimeProvidersView>;
|
|
69
|
+
/**
|
|
70
|
+
* Add or edit one provider in the adapter's list. Which provider a session runs
|
|
71
|
+
* on is DSH's own per-session model selection, so this touches no active half.
|
|
72
|
+
*/
|
|
73
|
+
export declare function saveProvider(agentKey: string, namespace: string, provider: unknown, doFetch?: FetchLike): Promise<RuntimeProvidersView>;
|
|
74
|
+
/** Drop one provider from the list. Does not rewrite the runtime's own config. */
|
|
75
|
+
export declare function removeProvider(agentKey: string, namespace: string, id: string, doFetch?: FetchLike): Promise<RuntimeProvidersView>;
|
|
76
|
+
export { messageOf as settingsErrorText };
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* runtime-settings-api.ts — the browser half of a runtime's settings panel.
|
|
3
|
+
*
|
|
4
|
+
* The panel's config belongs to the ADAPTER, so nothing here knows what an auth
|
|
5
|
+
* method is: it posts a `client-request` envelope at the runtime's own world and
|
|
6
|
+
* hands the adapter's answer to `parseAuthView` (agent-settings.ts).
|
|
7
|
+
*
|
|
8
|
+
* POST /hub/api?agent=agy
|
|
9
|
+
* { type:'client-request', rpcId, method:'agyAuth/get'|'agyAuth/set',
|
|
10
|
+
* payload:{ args: {} | { request: { mode } } } }
|
|
11
|
+
*
|
|
12
|
+
* `/hub/api` is the hub family's prefix mount (`hub-route.ts`), which forwards
|
|
13
|
+
* `method` verbatim to the addressed world's `typertGateway`. The path needs no
|
|
14
|
+
* `CONTROL` exemption in fusion-transport.ts: the rewrite only touches same-origin
|
|
15
|
+
* `/api/...` paths, and `/hub/...` is never one of them.
|
|
16
|
+
*
|
|
17
|
+
* Node-safe: no DOM reach at module scope and `fetch` is injectable, so
|
|
18
|
+
* test/client-runtime-settings.test.mjs pins the envelope and every failure
|
|
19
|
+
* branch under `node --test` (the mesh-api.ts precedent).
|
|
20
|
+
*
|
|
21
|
+
* @module superd-hub/client/runtime-settings-api
|
|
22
|
+
*/
|
|
23
|
+
import { parseAuthView, parseProvidersView } from './agent-settings.js';
|
|
24
|
+
/** One rpcId for request/response correlation. `randomUUID` needs a secure context. */
|
|
25
|
+
export function mintRpcId() {
|
|
26
|
+
const webCrypto = globalThis.crypto;
|
|
27
|
+
if (webCrypto !== undefined && typeof webCrypto.randomUUID === 'function')
|
|
28
|
+
return webCrypto.randomUUID();
|
|
29
|
+
if (webCrypto !== undefined && typeof webCrypto.getRandomValues === 'function') {
|
|
30
|
+
const bytes = webCrypto.getRandomValues(new Uint8Array(16));
|
|
31
|
+
return [...bytes].map((b) => b.toString(16).padStart(2, '0')).join('');
|
|
32
|
+
}
|
|
33
|
+
return `rpc-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
34
|
+
}
|
|
35
|
+
export function settingsEnvelope(namespace, op, args) {
|
|
36
|
+
return {
|
|
37
|
+
type: 'client-request',
|
|
38
|
+
rpcId: mintRpcId(),
|
|
39
|
+
method: `${namespace}/${op}`,
|
|
40
|
+
payload: { args },
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/** What a caller can do with a failed panel call — kept structural for the tests. */
|
|
44
|
+
function messageOf(error) {
|
|
45
|
+
if (error !== null && typeof error === 'object' && typeof error.message === 'string') {
|
|
46
|
+
return error.message;
|
|
47
|
+
}
|
|
48
|
+
return String(error);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* One unary call at a runtime's world, unwrapped to the RPC's value.
|
|
52
|
+
*
|
|
53
|
+
* Every failure mode is reported as a thrown `Error` carrying the adapter's own
|
|
54
|
+
* message where one exists: a transport failure, a malformed answer, and the
|
|
55
|
+
* gateway's business error (`{ok:false,error}`) must not look alike to the panel.
|
|
56
|
+
*
|
|
57
|
+
* @param agentKey - roster key addressing the world (`?agent=`).
|
|
58
|
+
* @param namespace - the adapter's Remote namespace (`agyAuth`).
|
|
59
|
+
* @param op - the Remote method.
|
|
60
|
+
* @param args - exact named wire arguments.
|
|
61
|
+
* @param doFetch - fetch implementation (tests inject).
|
|
62
|
+
* @returns the raw RPC value.
|
|
63
|
+
* @throws {Error} on transport, framing, or gateway failure.
|
|
64
|
+
*/
|
|
65
|
+
export async function callRuntimeSettings(agentKey, namespace, op, args, doFetch) {
|
|
66
|
+
const send = doFetch ?? ((input, init) => fetch(input, init));
|
|
67
|
+
const envelope = settingsEnvelope(namespace, op, args);
|
|
68
|
+
const response = await send(`/hub/api?agent=${encodeURIComponent(agentKey)}`, {
|
|
69
|
+
method: 'POST',
|
|
70
|
+
credentials: 'same-origin',
|
|
71
|
+
headers: { 'content-type': 'application/json' },
|
|
72
|
+
body: JSON.stringify(envelope),
|
|
73
|
+
});
|
|
74
|
+
const text = await response.text();
|
|
75
|
+
let body;
|
|
76
|
+
try {
|
|
77
|
+
body = JSON.parse(text);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
throw new Error(`settings ${envelope.method}: unreadable answer (HTTP ${response.status})`);
|
|
81
|
+
}
|
|
82
|
+
const result = body?.result;
|
|
83
|
+
if (result === null || typeof result !== 'object') {
|
|
84
|
+
throw new Error(`settings ${envelope.method}: malformed answer (HTTP ${response.status})`);
|
|
85
|
+
}
|
|
86
|
+
const record = result;
|
|
87
|
+
if (record.ok === true)
|
|
88
|
+
return record.value;
|
|
89
|
+
const error = record.error;
|
|
90
|
+
if (error !== undefined && typeof error.message === 'string') {
|
|
91
|
+
throw new Error(error.message);
|
|
92
|
+
}
|
|
93
|
+
throw new Error(`settings ${envelope.method}: failed (HTTP ${response.status})`);
|
|
94
|
+
}
|
|
95
|
+
/** Read one runtime's panel state, parsed. */
|
|
96
|
+
export async function fetchAuthView(agentKey, namespace, doFetch) {
|
|
97
|
+
const value = await callRuntimeSettings(agentKey, namespace, 'get', {}, doFetch);
|
|
98
|
+
const view = parseAuthView(value);
|
|
99
|
+
if (view === undefined)
|
|
100
|
+
throw new Error('settings: the runtime returned no usable auth options');
|
|
101
|
+
return view;
|
|
102
|
+
}
|
|
103
|
+
/** Adopt one auth form (`null` clears the explicit choice). */
|
|
104
|
+
export async function setAuthMode(agentKey, namespace, mode, doFetch) {
|
|
105
|
+
const value = await callRuntimeSettings(agentKey, namespace, 'set', { request: { mode } }, doFetch);
|
|
106
|
+
const view = parseAuthView(value);
|
|
107
|
+
if (view === undefined)
|
|
108
|
+
throw new Error('settings: the runtime returned no usable auth options');
|
|
109
|
+
return view;
|
|
110
|
+
}
|
|
111
|
+
// --------------------------------- the providers bodies (codex P4, claude)
|
|
112
|
+
// Same transport, same namespace rule (codexProviders, claudeProviders); only
|
|
113
|
+
// the operation names and the argument shapes differ. Every call re-parses the
|
|
114
|
+
// adapter's answer through `parseProvidersView`, so a malformed reply is an
|
|
115
|
+
// error here rather than a half-rendered panel.
|
|
116
|
+
function requireProvidersView(value) {
|
|
117
|
+
const view = parseProvidersView(value);
|
|
118
|
+
if (view === undefined)
|
|
119
|
+
throw new Error('settings: the runtime returned no usable provider list');
|
|
120
|
+
return view;
|
|
121
|
+
}
|
|
122
|
+
/** Read the multi-provider state of one runtime. */
|
|
123
|
+
export async function fetchProvidersView(agentKey, namespace, doFetch) {
|
|
124
|
+
return requireProvidersView(await callRuntimeSettings(agentKey, namespace, 'get', {}, doFetch));
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Add or edit one provider in the adapter's list. Which provider a session runs
|
|
128
|
+
* on is DSH's own per-session model selection, so this touches no active half.
|
|
129
|
+
*/
|
|
130
|
+
export async function saveProvider(agentKey, namespace, provider, doFetch) {
|
|
131
|
+
return requireProvidersView(await callRuntimeSettings(agentKey, namespace, 'save', { request: { provider } }, doFetch));
|
|
132
|
+
}
|
|
133
|
+
/** Drop one provider from the list. Does not rewrite the runtime's own config. */
|
|
134
|
+
export async function removeProvider(agentKey, namespace, id, doFetch) {
|
|
135
|
+
return requireProvidersView(await callRuntimeSettings(agentKey, namespace, 'remove', { request: { id } }, doFetch));
|
|
136
|
+
}
|
|
137
|
+
export { messageOf as settingsErrorText };
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-half switch wiring (native UI fusion, plan Task 4): turns the pure
|
|
3
|
+
* {@link createSwitchController} machine into the page's `switchAgent`.
|
|
4
|
+
*
|
|
5
|
+
* The host face is assembled from exactly the sanctioned surfaces:
|
|
6
|
+
* - the Task-3 page atom `__SUPERD_UI_STATE__` (setAgent = state + NS swap;
|
|
7
|
+
* every later request demuxes through the call-time patches);
|
|
8
|
+
* - the Task-3 namespaced `Storage` for the landing read — `getItem` is
|
|
9
|
+
* read at call time against the ACTIVE label's `'<label>:'` namespace,
|
|
10
|
+
* so the post-`setAgent` read of `dsh.sessions.current` lands in the
|
|
11
|
+
* target world's tree without this module ever prefixing keys itself;
|
|
12
|
+
* - `ctx.connection` (`reconnect()` + the generation observable — the
|
|
13
|
+
* establishment leg of `connection/reset`);
|
|
14
|
+
* - `ctx.uiWorkspace` (`openSession(target)` / `startSession(workspaceId?)`
|
|
15
|
+
* / `openWorkspace(workspaceId)` — the navigation face; T-F executes the
|
|
16
|
+
* restore precedence through these public faces only);
|
|
17
|
+
* - `ctx.workspaces` (`list` — the workspace projection; T-F also reads
|
|
18
|
+
* `initializeDefault` to mirror upstream's default-workspace boot path);
|
|
19
|
+
* - `ctx.sessions` (T-F: the session projection — its `list` store is the
|
|
20
|
+
* second freshness edge the landing gate watches, and its `ids`/`byId`
|
|
21
|
+
* are what upstream `restoreSelection` resolves the pointer against,
|
|
22
|
+
* navigation.ts:324; watching `workspaces.list` alone was the T-A gap
|
|
23
|
+
* that broke native's own last-visited restore);
|
|
24
|
+
* - the control-plane roster fetch (`/api/agent-runtime`, never rewritten)
|
|
25
|
+
* as the probe: the target must still be a live roster entry;
|
|
26
|
+
* - a minimal self-owned toast overlay for the revert feedback (upstream
|
|
27
|
+
* has no public toast service; the overlay reads nothing from any other
|
|
28
|
+
* component's DOM).
|
|
29
|
+
*
|
|
30
|
+
* The client entry (`index.ts`) binds once `connection` + `uiWorkspace`
|
|
31
|
+
* exist. The Hub UI surfaces (HubPage rows + the HubPanelIcon chevron menu —
|
|
32
|
+
* the sole switch surface since the T-B RuntimeSeat retirement) run every
|
|
33
|
+
* click through {@link seatClickDecision} (switch-core): on a fusion page
|
|
34
|
+
* the left click is intercepted with NO navigation — the orchestration bound
|
|
35
|
+
* or not; on a world page the flag global is absent, the fusion arm never
|
|
36
|
+
* runs, and the real hyperlink stays in charge (spec §5 双半边).
|
|
37
|
+
*
|
|
38
|
+
* @module superd-hub/client/switch
|
|
39
|
+
*/
|
|
40
|
+
import { type RestoreWorkspaceView } from '../switch-core.js';
|
|
41
|
+
/** The Task-3 page-level state atom the fused page boots with. */
|
|
42
|
+
export interface SuperdUiState {
|
|
43
|
+
/** The active agent label: `'native'` or a runtime key. */
|
|
44
|
+
readonly activeAgent: string;
|
|
45
|
+
setAgent(label: string): void;
|
|
46
|
+
subscribe(fn: (label: string) => void): () => void;
|
|
47
|
+
}
|
|
48
|
+
/** Structural face of `ctx.connection` — the parts the switch machine needs. */
|
|
49
|
+
export interface SwitchConnection {
|
|
50
|
+
reconnect(): void;
|
|
51
|
+
generation: {
|
|
52
|
+
getSnapshot(): unknown;
|
|
53
|
+
subscribe(listener: () => void): () => void;
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/** Structural face of `ctx.uiWorkspace` — the navigation face (public only). */
|
|
57
|
+
export interface SwitchUiWorkspace {
|
|
58
|
+
openSession(target: string | object): void;
|
|
59
|
+
openWorkspace(workspaceId: string): void | Promise<void>;
|
|
60
|
+
startSession(workspaceId?: string): void;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Structural face of `ctx.workspaces` — the workspace projection whose
|
|
64
|
+
* baseline replacement is one of the TWO freshness edges the landing gate
|
|
65
|
+
* watches, plus `initializeDefault` for the default-workspace mirror (the
|
|
66
|
+
* same public method upstream's private `initializeDefaultWorkspace`
|
|
67
|
+
* calls). Present wherever `uiWorkspace` is: upstream's ui-workspace injects
|
|
68
|
+
* `workspaces` itself (ui-workspace/src/client/index.ts).
|
|
69
|
+
*/
|
|
70
|
+
export interface SwitchWorkspaces {
|
|
71
|
+
readonly list: {
|
|
72
|
+
getSnapshot(): {
|
|
73
|
+
items: readonly RestoreWorkspaceView[];
|
|
74
|
+
phase: unknown;
|
|
75
|
+
};
|
|
76
|
+
subscribe(listener: () => void): () => void;
|
|
77
|
+
};
|
|
78
|
+
initializeDefault(signal?: AbortSignal): Promise<{
|
|
79
|
+
workspaceId: string;
|
|
80
|
+
} | undefined>;
|
|
81
|
+
}
|
|
82
|
+
/** Structural slice of one `ctx.sessions.list` `byId` row the landing reads. */
|
|
83
|
+
export interface SwitchSessionSummary {
|
|
84
|
+
readonly id: string;
|
|
85
|
+
readonly blank?: boolean;
|
|
86
|
+
readonly updatedAt?: number;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Structural face of `ctx.sessions` (T-F) — the session projection the
|
|
90
|
+
* landing pointer resolves against (upstream `restoreSelection` reads
|
|
91
|
+
* `sessions.byId`, navigation.ts:324) and the second freshness edge of the
|
|
92
|
+
* both-lists gate (the T-A gap: without it the gate could fire while `byId`
|
|
93
|
+
* still held the previous plane's rows). T-G: its `byId` is also the gate's
|
|
94
|
+
* membership face — the list is minted incrementally post-reconnect, so the
|
|
95
|
+
* gate holds until the pointer's session joins `byId` (or the list goes
|
|
96
|
+
* quiet) instead of trusting the first partial-list edge.
|
|
97
|
+
*/
|
|
98
|
+
export interface SwitchSessions {
|
|
99
|
+
readonly list: {
|
|
100
|
+
getSnapshot(): {
|
|
101
|
+
ids: readonly string[];
|
|
102
|
+
byId: Readonly<Record<string, SwitchSessionSummary>>;
|
|
103
|
+
phase: unknown;
|
|
104
|
+
};
|
|
105
|
+
subscribe(listener: () => void): () => void;
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
/** Whether this page is a fusion page: the baked flag global is present. */
|
|
109
|
+
export declare function fusionPage(): boolean;
|
|
110
|
+
/** The page atom, when the page carries one (it always does on a fusion page). */
|
|
111
|
+
export declare function uiState(): SuperdUiState | undefined;
|
|
112
|
+
/**
|
|
113
|
+
* Build the `SwitchHost.openDefaultWorkspace` action.
|
|
114
|
+
*
|
|
115
|
+
* dsh 0.2.0-rc.2 removed the request object from the Host's
|
|
116
|
+
* `workspaces.initializeDefault` Remote: the first-use directory and stored
|
|
117
|
+
* title are fixed and language-neutral (`default-workspace` under
|
|
118
|
+
* `<Documents>/deepseek-harness`), so the caller passes only its own lifetime
|
|
119
|
+
* signal. This module shipped the pre-0.2 `{ directoryName, title }` shape for
|
|
120
|
+
* a release cycle; the Host then received a plain object where it feeds
|
|
121
|
+
* `AbortSignal.any(...)`, EVERY empty-plane landing rejected, and the browser
|
|
122
|
+
* surfaced "Unable to create default workspace". Root-caused 2026-10-02 on
|
|
123
|
+
* the Hermes world — the only world whose registry was still empty after the
|
|
124
|
+
* 0.2.0-rc.2 upgrade, hence the only one that took the default-workspace
|
|
125
|
+
* branch. Exported so the call shape is unit-testable without the
|
|
126
|
+
* module-level single-bind guard.
|
|
127
|
+
*
|
|
128
|
+
* @param deps - the workspace projection and the navigation face.
|
|
129
|
+
* @param toast - the feedback sink; a refusal is feedback, never a revert.
|
|
130
|
+
* @returns the host action.
|
|
131
|
+
*/
|
|
132
|
+
export declare function openDefaultWorkspaceFor(deps: {
|
|
133
|
+
workspaces: SwitchWorkspaces;
|
|
134
|
+
uiWorkspace: SwitchUiWorkspace;
|
|
135
|
+
}, toast: (message: string) => void): () => void;
|
|
136
|
+
/**
|
|
137
|
+
* Bind the page's switch agent from the client services. Idempotent; a no-op
|
|
138
|
+
* without the page atom (a non-fusion page never has one).
|
|
139
|
+
* @param deps - the `connection`, `uiWorkspace`, `workspaces` and `sessions`
|
|
140
|
+
* services (structural; the stock client tree always has all four —
|
|
141
|
+
* upstream's ui-workspace injects the same set).
|
|
142
|
+
*/
|
|
143
|
+
export declare function bindSwitchAgent(deps: {
|
|
144
|
+
connection: SwitchConnection;
|
|
145
|
+
uiWorkspace: SwitchUiWorkspace;
|
|
146
|
+
workspaces: SwitchWorkspaces;
|
|
147
|
+
sessions: SwitchSessions;
|
|
148
|
+
}): void;
|
|
149
|
+
/**
|
|
150
|
+
* The Hub switch surfaces' entry point. Attempts the in-page switch for
|
|
151
|
+
* `label`, gated on the target world's readiness (C1): a not-ready world
|
|
152
|
+
* holds the UI on native with starting feedback and a bounded poll; a
|
|
153
|
+
* failed world reports and never switches; a ready/native/unknown target
|
|
154
|
+
* runs the machine (whose roster probe stays the fallback). A not-ready
|
|
155
|
+
* FEDERATED row skips all of that — an immediate machine-error toast
|
|
156
|
+
* (I1(c), `machine-down`), never a wait loop.
|
|
157
|
+
*
|
|
158
|
+
* @returns `true` when the fusion switch took the click over; `false` when
|
|
159
|
+
* this page is not a fusion page or the orchestration is unbound.
|
|
160
|
+
* Informational only — the interception contract (`seatClickDecision`)
|
|
161
|
+
* prevents navigation on every fusion-page left click regardless: a
|
|
162
|
+
* `false` means the click is swallowed, never that the anchor navigates
|
|
163
|
+
* the fused page away (review round 1).
|
|
164
|
+
*/
|
|
165
|
+
export declare function attemptSwitch(label: string): boolean;
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-half switch wiring (native UI fusion, plan Task 4): turns the pure
|
|
3
|
+
* {@link createSwitchController} machine into the page's `switchAgent`.
|
|
4
|
+
*
|
|
5
|
+
* The host face is assembled from exactly the sanctioned surfaces:
|
|
6
|
+
* - the Task-3 page atom `__SUPERD_UI_STATE__` (setAgent = state + NS swap;
|
|
7
|
+
* every later request demuxes through the call-time patches);
|
|
8
|
+
* - the Task-3 namespaced `Storage` for the landing read — `getItem` is
|
|
9
|
+
* read at call time against the ACTIVE label's `'<label>:'` namespace,
|
|
10
|
+
* so the post-`setAgent` read of `dsh.sessions.current` lands in the
|
|
11
|
+
* target world's tree without this module ever prefixing keys itself;
|
|
12
|
+
* - `ctx.connection` (`reconnect()` + the generation observable — the
|
|
13
|
+
* establishment leg of `connection/reset`);
|
|
14
|
+
* - `ctx.uiWorkspace` (`openSession(target)` / `startSession(workspaceId?)`
|
|
15
|
+
* / `openWorkspace(workspaceId)` — the navigation face; T-F executes the
|
|
16
|
+
* restore precedence through these public faces only);
|
|
17
|
+
* - `ctx.workspaces` (`list` — the workspace projection; T-F also reads
|
|
18
|
+
* `initializeDefault` to mirror upstream's default-workspace boot path);
|
|
19
|
+
* - `ctx.sessions` (T-F: the session projection — its `list` store is the
|
|
20
|
+
* second freshness edge the landing gate watches, and its `ids`/`byId`
|
|
21
|
+
* are what upstream `restoreSelection` resolves the pointer against,
|
|
22
|
+
* navigation.ts:324; watching `workspaces.list` alone was the T-A gap
|
|
23
|
+
* that broke native's own last-visited restore);
|
|
24
|
+
* - the control-plane roster fetch (`/api/agent-runtime`, never rewritten)
|
|
25
|
+
* as the probe: the target must still be a live roster entry;
|
|
26
|
+
* - a minimal self-owned toast overlay for the revert feedback (upstream
|
|
27
|
+
* has no public toast service; the overlay reads nothing from any other
|
|
28
|
+
* component's DOM).
|
|
29
|
+
*
|
|
30
|
+
* The client entry (`index.ts`) binds once `connection` + `uiWorkspace`
|
|
31
|
+
* exist. The Hub UI surfaces (HubPage rows + the HubPanelIcon chevron menu —
|
|
32
|
+
* the sole switch surface since the T-B RuntimeSeat retirement) run every
|
|
33
|
+
* click through {@link seatClickDecision} (switch-core): on a fusion page
|
|
34
|
+
* the left click is intercepted with NO navigation — the orchestration bound
|
|
35
|
+
* or not; on a world page the flag global is absent, the fusion arm never
|
|
36
|
+
* runs, and the real hyperlink stays in charge (spec §5 双半边).
|
|
37
|
+
*
|
|
38
|
+
* @module superd-hub/client/switch
|
|
39
|
+
*/
|
|
40
|
+
import { createReadinessWaiter, createSwitchController, decidePreFlight, observeCachesRefreshed, runMachineDownFeedback, SESSIONS_CURRENT_KEY, } from '../switch-core.js';
|
|
41
|
+
import { showSuperdToast } from '../toast-core.js';
|
|
42
|
+
import { fetchMachines, reprobeMachine } from './mesh-api.js';
|
|
43
|
+
import { fetchRoster } from './roster.js';
|
|
44
|
+
/** Whether this page is a fusion page: the baked flag global is present. */
|
|
45
|
+
export function fusionPage() {
|
|
46
|
+
return globalThis.__SUPERD_UI_FUSION__ === true;
|
|
47
|
+
}
|
|
48
|
+
/** The page atom, when the page carries one (it always does on a fusion page). */
|
|
49
|
+
export function uiState() {
|
|
50
|
+
const state = globalThis.__SUPERD_UI_STATE__;
|
|
51
|
+
if (state === undefined)
|
|
52
|
+
return undefined;
|
|
53
|
+
if (typeof state.setAgent !== 'function')
|
|
54
|
+
return undefined;
|
|
55
|
+
if (typeof state.subscribe !== 'function' || typeof state.activeAgent !== 'string')
|
|
56
|
+
return undefined;
|
|
57
|
+
return state;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* One transient message through the official `Toast` primitive (C1 task 4:
|
|
61
|
+
* the platform's top-center banner — dark surface in both themes, warn icon
|
|
62
|
+
* seat — rendered by the host's React tree from the Hub sidebar row's
|
|
63
|
+
* always-mounted owner; see client/toast.ts).
|
|
64
|
+
*/
|
|
65
|
+
function showToast(message) {
|
|
66
|
+
showSuperdToast(message);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* I1(c) feedback + I1(b) light probe (mesh P3 Task 5a): the machine-down
|
|
70
|
+
* branch delegates to the pure {@link runMachineDownFeedback} — the
|
|
71
|
+
* machines face (already part of the client half — the Hub machines panel's
|
|
72
|
+
* own cheap same-origin GET) enriches the toast, bounded at 3s; after
|
|
73
|
+
* toasting, ONE fire-and-forget reprobe POST per failed click refreshes the
|
|
74
|
+
* hub's status table so the NEXT click can succeed without waiting for the
|
|
75
|
+
* 5-minute sweep. Nothing here blocks the UI path.
|
|
76
|
+
*/
|
|
77
|
+
function reportMachineDown(label, row) {
|
|
78
|
+
runMachineDownFeedback(label, row, {
|
|
79
|
+
fetchMachines,
|
|
80
|
+
reprobe: reprobeMachine,
|
|
81
|
+
toast: showToast,
|
|
82
|
+
setTimeout: (fn, ms) => globalThis.setTimeout(fn, ms),
|
|
83
|
+
clearTimeout: (handle) => globalThis.clearTimeout(handle),
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Build the `SwitchHost.openDefaultWorkspace` action.
|
|
88
|
+
*
|
|
89
|
+
* dsh 0.2.0-rc.2 removed the request object from the Host's
|
|
90
|
+
* `workspaces.initializeDefault` Remote: the first-use directory and stored
|
|
91
|
+
* title are fixed and language-neutral (`default-workspace` under
|
|
92
|
+
* `<Documents>/deepseek-harness`), so the caller passes only its own lifetime
|
|
93
|
+
* signal. This module shipped the pre-0.2 `{ directoryName, title }` shape for
|
|
94
|
+
* a release cycle; the Host then received a plain object where it feeds
|
|
95
|
+
* `AbortSignal.any(...)`, EVERY empty-plane landing rejected, and the browser
|
|
96
|
+
* surfaced "Unable to create default workspace". Root-caused 2026-10-02 on
|
|
97
|
+
* the Hermes world — the only world whose registry was still empty after the
|
|
98
|
+
* 0.2.0-rc.2 upgrade, hence the only one that took the default-workspace
|
|
99
|
+
* branch. Exported so the call shape is unit-testable without the
|
|
100
|
+
* module-level single-bind guard.
|
|
101
|
+
*
|
|
102
|
+
* @param deps - the workspace projection and the navigation face.
|
|
103
|
+
* @param toast - the feedback sink; a refusal is feedback, never a revert.
|
|
104
|
+
* @returns the host action.
|
|
105
|
+
*/
|
|
106
|
+
export function openDefaultWorkspaceFor(deps, toast) {
|
|
107
|
+
return () => {
|
|
108
|
+
void deps.workspaces.initializeDefault()
|
|
109
|
+
.then((prepared) => {
|
|
110
|
+
if (prepared !== undefined)
|
|
111
|
+
deps.uiWorkspace.openWorkspace(prepared.workspaceId);
|
|
112
|
+
})
|
|
113
|
+
.catch(() => { toast('Super D: unable to create the default workspace.'); });
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
/** The bound `switchAgent`, or `undefined` until the entry binds it. */
|
|
117
|
+
let bound;
|
|
118
|
+
/**
|
|
119
|
+
* Bind the page's switch agent from the client services. Idempotent; a no-op
|
|
120
|
+
* without the page atom (a non-fusion page never has one).
|
|
121
|
+
* @param deps - the `connection`, `uiWorkspace`, `workspaces` and `sessions`
|
|
122
|
+
* services (structural; the stock client tree always has all four —
|
|
123
|
+
* upstream's ui-workspace injects the same set).
|
|
124
|
+
*/
|
|
125
|
+
export function bindSwitchAgent(deps) {
|
|
126
|
+
if (bound !== undefined)
|
|
127
|
+
return;
|
|
128
|
+
const state = uiState();
|
|
129
|
+
if (state === undefined)
|
|
130
|
+
return;
|
|
131
|
+
const readRestoreCaches = () => {
|
|
132
|
+
const workspaces = deps.workspaces.list.getSnapshot();
|
|
133
|
+
const sessions = deps.sessions.list.getSnapshot();
|
|
134
|
+
return {
|
|
135
|
+
workspaces: { items: workspaces.items, phase: workspaces.phase },
|
|
136
|
+
sessions: { ids: sessions.ids, byId: sessions.byId, phase: sessions.phase },
|
|
137
|
+
};
|
|
138
|
+
};
|
|
139
|
+
// Upstream `initializeDefaultWorkspace` (navigation.ts:349-361) mirrored
|
|
140
|
+
// through the public faces; the call contract and its 2026-10-02 regression
|
|
141
|
+
// live in `openDefaultWorkspaceFor`.
|
|
142
|
+
const openDefaultWorkspace = openDefaultWorkspaceFor(deps, showToast);
|
|
143
|
+
const host = {
|
|
144
|
+
setAgent(label) { state.setAgent(label); },
|
|
145
|
+
readPersistedSelection() {
|
|
146
|
+
// The Task-3 storage patch prefixes the ACTIVE label's namespace at
|
|
147
|
+
// call time — the landing reads long after setAgent, so this read
|
|
148
|
+
// lands in the target world's `'<label>:dsh.sessions.current'`.
|
|
149
|
+
const storage = globalThis.localStorage;
|
|
150
|
+
return storage === undefined ? null : storage.getItem(SESSIONS_CURRENT_KEY);
|
|
151
|
+
},
|
|
152
|
+
openSession(target) { deps.uiWorkspace.openSession(target); },
|
|
153
|
+
startSession(workspaceId) { deps.uiWorkspace.startSession(workspaceId); },
|
|
154
|
+
openDefaultWorkspace,
|
|
155
|
+
readRestoreCaches,
|
|
156
|
+
onCachesRefreshed(listener, gate) {
|
|
157
|
+
// The T-F freshness gate over the real stores, extended T-G with the
|
|
158
|
+
// membership face: the first post-establishment baseline replacement
|
|
159
|
+
// in BOTH `workspaces.list` and `sessions.list` — upstream's
|
|
160
|
+
// watchNavigation ready gate (navigation.ts:292) — held until the
|
|
161
|
+
// persisted pointer's session JOINS the freshly (incrementally) minted
|
|
162
|
+
// list, or the list goes quiet. The sessions store adapts its
|
|
163
|
+
// projection's `ids` array to the freshness token (a fresh array is
|
|
164
|
+
// minted on every projection); membership answers from the LIVE `byId`
|
|
165
|
+
// snapshot — the face upstream's boot restore resolves the pointer
|
|
166
|
+
// against (navigation.ts:324), and the only one that can gain a row
|
|
167
|
+
// without a new `ids` identity (publishRetention, service.ts:565).
|
|
168
|
+
// The machine's gate request (pointer face, quiet window, timers)
|
|
169
|
+
// rides through structurally — it is a full LandingGateOptions.
|
|
170
|
+
const sessionsFace = {
|
|
171
|
+
getSnapshot: () => {
|
|
172
|
+
const snapshot = deps.sessions.list.getSnapshot();
|
|
173
|
+
return { items: snapshot.ids, phase: snapshot.phase };
|
|
174
|
+
},
|
|
175
|
+
subscribe: (fn) => deps.sessions.list.subscribe(fn),
|
|
176
|
+
contains: (id) => deps.sessions.list.getSnapshot().byId[id] !== undefined,
|
|
177
|
+
};
|
|
178
|
+
return observeCachesRefreshed(deps.workspaces.list, sessionsFace, deps.connection.generation, listener, gate);
|
|
179
|
+
},
|
|
180
|
+
reconnect() { deps.connection.reconnect(); },
|
|
181
|
+
onGenerationEstablished(listener) {
|
|
182
|
+
const generation = deps.connection.generation;
|
|
183
|
+
// The observable fires on establishment, replacement, and loss; only
|
|
184
|
+
// an existing generation settles the attempt.
|
|
185
|
+
return generation.subscribe(() => {
|
|
186
|
+
if (generation.getSnapshot() !== undefined)
|
|
187
|
+
listener();
|
|
188
|
+
});
|
|
189
|
+
},
|
|
190
|
+
async probeTarget(label) {
|
|
191
|
+
try {
|
|
192
|
+
// Control plane, never rewritten: the roster always talks to ctx0.
|
|
193
|
+
const roster = await fetchRoster();
|
|
194
|
+
return roster.some((agent) => agent.key === label);
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
toast: showToast,
|
|
201
|
+
setTimeout(fn, ms) { return globalThis.setTimeout(fn, ms); },
|
|
202
|
+
clearTimeout(handle) { globalThis.clearTimeout(handle); },
|
|
203
|
+
};
|
|
204
|
+
const controller = createSwitchController(host);
|
|
205
|
+
bound = (label) => controller.run(label);
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* The Hub switch surfaces' entry point. Attempts the in-page switch for
|
|
209
|
+
* `label`.
|
|
210
|
+
* @returns `true` when the fusion switch took the click over; `false` when
|
|
211
|
+
* this page is not a fusion page or the orchestration is unbound.
|
|
212
|
+
* Informational only — the interception contract (`seatClickDecision`)
|
|
213
|
+
* prevents navigation on every fusion-page left click regardless: a
|
|
214
|
+
* `false` means the click is swallowed, never that the anchor navigates
|
|
215
|
+
* the fused page away (review round 1).
|
|
216
|
+
*/
|
|
217
|
+
let pendingWait;
|
|
218
|
+
/**
|
|
219
|
+
* The Hub switch surfaces' entry point. Attempts the in-page switch for
|
|
220
|
+
* `label`, gated on the target world's readiness (C1): a not-ready world
|
|
221
|
+
* holds the UI on native with starting feedback and a bounded poll; a
|
|
222
|
+
* failed world reports and never switches; a ready/native/unknown target
|
|
223
|
+
* runs the machine (whose roster probe stays the fallback). A not-ready
|
|
224
|
+
* FEDERATED row skips all of that — an immediate machine-error toast
|
|
225
|
+
* (I1(c), `machine-down`), never a wait loop.
|
|
226
|
+
*
|
|
227
|
+
* @returns `true` when the fusion switch took the click over; `false` when
|
|
228
|
+
* this page is not a fusion page or the orchestration is unbound.
|
|
229
|
+
* Informational only — the interception contract (`seatClickDecision`)
|
|
230
|
+
* prevents navigation on every fusion-page left click regardless: a
|
|
231
|
+
* `false` means the click is swallowed, never that the anchor navigates
|
|
232
|
+
* the fused page away (review round 1).
|
|
233
|
+
*/
|
|
234
|
+
export function attemptSwitch(label) {
|
|
235
|
+
const run = bound;
|
|
236
|
+
if (run === undefined || !fusionPage())
|
|
237
|
+
return false;
|
|
238
|
+
// A newer attempt supersedes any pending readiness wait.
|
|
239
|
+
pendingWait?.cancel();
|
|
240
|
+
pendingWait = undefined;
|
|
241
|
+
void (async () => {
|
|
242
|
+
let row;
|
|
243
|
+
try {
|
|
244
|
+
row = (await fetchRoster()).find((agent) => agent.key === label);
|
|
245
|
+
}
|
|
246
|
+
catch {
|
|
247
|
+
row = undefined;
|
|
248
|
+
}
|
|
249
|
+
const decision = decidePreFlight(label, row);
|
|
250
|
+
if (decision === 'failed') {
|
|
251
|
+
showToast(`Super D: ${label} failed to start — see the service log.`);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
// I1(c): a degraded FEDERATED row fails fast — the hub's own probe
|
|
255
|
+
// already knows the machine is unreachable/auth-expired, so "starting…"
|
|
256
|
+
// plus the 120s wait would be a lie. Actionable toast, no wait loop, no
|
|
257
|
+
// revert (the page stays where it is, like the failed branch).
|
|
258
|
+
if (decision === 'machine-down') {
|
|
259
|
+
if (row !== undefined)
|
|
260
|
+
reportMachineDown(label, row);
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
if (decision === 'wait') {
|
|
264
|
+
showToast(`Super D: ${label} is starting…`);
|
|
265
|
+
const waiter = createReadinessWaiter({
|
|
266
|
+
fetchRow: async () => {
|
|
267
|
+
try {
|
|
268
|
+
return (await fetchRoster()).find((agent) => agent.key === label);
|
|
269
|
+
}
|
|
270
|
+
catch {
|
|
271
|
+
return undefined;
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
setTimeout: (fn, ms) => globalThis.setTimeout(fn, ms),
|
|
275
|
+
clearTimeout: (handle) => globalThis.clearTimeout(handle),
|
|
276
|
+
});
|
|
277
|
+
pendingWait = waiter;
|
|
278
|
+
const outcome = await waiter.wait(label);
|
|
279
|
+
if (pendingWait === waiter)
|
|
280
|
+
pendingWait = undefined;
|
|
281
|
+
if (outcome === 'ready') {
|
|
282
|
+
void run(label).catch(() => { });
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
if (outcome === 'failed')
|
|
286
|
+
showToast(`Super D: ${label} failed to start — see the service log.`);
|
|
287
|
+
else if (outcome === 'bound')
|
|
288
|
+
showToast(`Super D: ${label} is still starting — try again in a moment.`);
|
|
289
|
+
// 'cancelled': a newer attempt owns the page — silence.
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
void run(label).catch(() => { });
|
|
293
|
+
})();
|
|
294
|
+
return true;
|
|
295
|
+
}
|