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.
Files changed (178) hide show
  1. package/README.md +25 -3
  2. package/agent-agy/cordis.patch.yml +7 -4
  3. package/agent-agy/dist/agent.d.ts +12 -11
  4. package/agent-agy/dist/agy-auth-gateway.d.ts +124 -0
  5. package/agent-agy/dist/agy-auth-gateway.js +233 -0
  6. package/agent-agy/dist/agy-auth.d.ts +153 -0
  7. package/agent-agy/dist/agy-auth.js +179 -0
  8. package/agent-agy/dist/agy-cli-client.d.ts +39 -9
  9. package/agent-agy/dist/agy-cli-client.js +82 -18
  10. package/agent-agy/dist/agy-store.d.ts +8 -0
  11. package/agent-agy/dist/index.d.ts +46 -16
  12. package/agent-agy/dist/index.js +82 -56
  13. package/agent-agy/dist/models.d.ts +70 -0
  14. package/agent-agy/dist/models.js +102 -3
  15. package/agent-agy/package.json +7 -5
  16. package/agent-agy-sdk/cordis.patch.yml +58 -0
  17. package/agent-agy-sdk/dist/adapter.d.ts +15 -0
  18. package/agent-agy-sdk/dist/adapter.js +42 -0
  19. package/agent-agy-sdk/dist/agent-preset-agy.d.ts +27 -0
  20. package/agent-agy-sdk/dist/agent-preset-agy.js +32 -0
  21. package/agent-agy-sdk/dist/agent.d.ts +138 -0
  22. package/agent-agy-sdk/dist/agent.js +494 -0
  23. package/{agent-agy → agent-agy-sdk}/dist/agy-client.d.ts +26 -6
  24. package/{agent-agy → agent-agy-sdk}/dist/agy-client.js +43 -25
  25. package/agent-agy-sdk/dist/agy-sessions.d.ts +25 -0
  26. package/agent-agy-sdk/dist/agy-sessions.js +107 -0
  27. package/agent-agy-sdk/dist/agy-store.d.ts +39 -0
  28. package/agent-agy-sdk/dist/agy-store.js +83 -0
  29. package/agent-agy-sdk/dist/inbox.d.ts +26 -0
  30. package/agent-agy-sdk/dist/inbox.js +53 -0
  31. package/agent-agy-sdk/dist/index.d.ts +93 -0
  32. package/agent-agy-sdk/dist/index.js +476 -0
  33. package/agent-agy-sdk/dist/models.d.ts +41 -0
  34. package/agent-agy-sdk/dist/models.js +74 -0
  35. package/agent-agy-sdk/dist/sdk-venv.d.ts +76 -0
  36. package/agent-agy-sdk/dist/sdk-venv.js +173 -0
  37. package/agent-agy-sdk/dist/world-plugin.d.ts +10 -0
  38. package/agent-agy-sdk/dist/world-plugin.js +82 -0
  39. package/agent-agy-sdk/package.json +64 -0
  40. package/agent-claude/dist/adapter.js +106 -31
  41. package/agent-claude/dist/agent.js +154 -17
  42. package/agent-claude/dist/claude-client.js +84 -43
  43. package/agent-claude/dist/claude-launch.js +101 -0
  44. package/agent-claude/dist/claude-native-config.js +169 -0
  45. package/agent-claude/dist/claude-provider-gateway.js +340 -0
  46. package/agent-claude/dist/claude-providers.js +391 -0
  47. package/agent-claude/dist/claude-route.js +152 -0
  48. package/agent-claude/dist/index.js +214 -37
  49. package/agent-claude/dist/models.js +14 -0
  50. package/agent-claude/dist/sdk-runtime.js +320 -0
  51. package/agent-claude/package.json +6 -4
  52. package/agent-codex/README.md +141 -36
  53. package/agent-codex/cordis.patch.yml +14 -10
  54. package/agent-codex/dist/adapter.js +64 -28
  55. package/agent-codex/dist/agent.js +255 -81
  56. package/agent-codex/dist/app-server-approvals.js +165 -0
  57. package/agent-codex/dist/app-server-client.js +723 -0
  58. package/agent-codex/dist/app-server-events.js +432 -0
  59. package/agent-codex/dist/app-server-protocol.js +95 -0
  60. package/agent-codex/dist/app-server-wire.js +299 -0
  61. package/agent-codex/dist/codex-launch.js +78 -0
  62. package/agent-codex/dist/codex-native-config.js +392 -0
  63. package/agent-codex/dist/codex-provider-gateway.js +402 -0
  64. package/agent-codex/dist/codex-providers.js +376 -0
  65. package/agent-codex/dist/codex-route.js +86 -0
  66. package/agent-codex/dist/index.js +256 -115
  67. package/agent-codex/dist/permission.js +75 -1
  68. package/agent-codex/package.json +1 -2
  69. package/agent-codex-sdk/README.md +79 -0
  70. package/agent-codex-sdk/cordis.patch.yml +81 -0
  71. package/agent-codex-sdk/dist/adapter.js +91 -0
  72. package/agent-codex-sdk/dist/agent-preset-codex.js +32 -0
  73. package/agent-codex-sdk/dist/agent.js +1085 -0
  74. package/agent-codex-sdk/dist/codex-native-config.js +392 -0
  75. package/agent-codex-sdk/dist/codex-provider-gateway.js +402 -0
  76. package/agent-codex-sdk/dist/codex-providers.js +376 -0
  77. package/agent-codex-sdk/dist/codex-route.js +86 -0
  78. package/{agent-codex/dist/codex-client.js → agent-codex-sdk/dist/codex-sdk-client.js} +253 -32
  79. package/agent-codex-sdk/dist/codex-sdk-launch.js +92 -0
  80. package/agent-codex-sdk/dist/codex-store.js +180 -0
  81. package/agent-codex-sdk/dist/inbox.js +53 -0
  82. package/agent-codex-sdk/dist/index.js +739 -0
  83. package/agent-codex-sdk/dist/permission.js +71 -0
  84. package/agent-codex-sdk/dist/sdk-runtime.js +307 -0
  85. package/agent-codex-sdk/dist/session-map.js +167 -0
  86. package/agent-codex-sdk/dist/world-plugin.js +93 -0
  87. package/agent-codex-sdk/package.json +79 -0
  88. package/agent-codex-sdk/scripts/fetch-sdk-runtime.mjs +57 -0
  89. package/agent-omp/README.md +72 -55
  90. package/agent-omp/dist/agent.js +36 -11
  91. package/agent-omp/dist/index.js +92 -52
  92. package/agent-omp/dist/lazy-rpc.js +11 -4
  93. package/agent-omp/dist/omp-cli.js +114 -97
  94. package/agent-omp/dist/omp-store.js +12 -4
  95. package/agent-omp/dist/permission.js +70 -5
  96. package/agent-omp/dist/rpc-client.js +409 -0
  97. package/agent-omp/dist/world-plugin.js +36 -3
  98. package/agent-omp/package.json +2 -6
  99. package/agent-omp-sdk/README.md +103 -0
  100. package/agent-omp-sdk/cordis.patch.yml +108 -0
  101. package/agent-omp-sdk/dist/adapter.d.ts +21 -0
  102. package/agent-omp-sdk/dist/adapter.js +36 -0
  103. package/agent-omp-sdk/dist/agent-preset-omp.d.ts +31 -0
  104. package/agent-omp-sdk/dist/agent-preset-omp.js +32 -0
  105. package/agent-omp-sdk/dist/agent.d.ts +64 -0
  106. package/agent-omp-sdk/dist/agent.js +1178 -0
  107. package/agent-omp-sdk/dist/discovery.d.ts +57 -0
  108. package/agent-omp-sdk/dist/discovery.js +116 -0
  109. package/agent-omp-sdk/dist/inbox.d.ts +28 -0
  110. package/agent-omp-sdk/dist/inbox.js +53 -0
  111. package/agent-omp-sdk/dist/index.d.ts +60 -0
  112. package/agent-omp-sdk/dist/index.js +632 -0
  113. package/agent-omp-sdk/dist/knobs.d.ts +34 -0
  114. package/agent-omp-sdk/dist/knobs.js +50 -0
  115. package/agent-omp-sdk/dist/lazy-rpc.d.ts +98 -0
  116. package/agent-omp-sdk/dist/lazy-rpc.js +178 -0
  117. package/agent-omp-sdk/dist/mobile/zoom-guard.d.ts +187 -0
  118. package/agent-omp-sdk/dist/mobile/zoom-guard.js +294 -0
  119. package/agent-omp-sdk/dist/mobile-boot.d.ts +49 -0
  120. package/agent-omp-sdk/dist/mobile-boot.js +38 -0
  121. package/agent-omp-sdk/dist/models.d.ts +39 -0
  122. package/agent-omp-sdk/dist/models.js +299 -0
  123. package/agent-omp-sdk/dist/omp-cli.d.ts +67 -0
  124. package/agent-omp-sdk/dist/omp-cli.js +164 -0
  125. package/agent-omp-sdk/dist/omp-disk-discovery.d.ts +27 -0
  126. package/agent-omp-sdk/dist/omp-disk-discovery.js +178 -0
  127. package/agent-omp-sdk/dist/omp-executable.d.ts +2 -0
  128. package/agent-omp-sdk/dist/omp-executable.js +30 -0
  129. package/agent-omp-sdk/dist/omp-store.d.ts +134 -0
  130. package/agent-omp-sdk/dist/omp-store.js +526 -0
  131. package/agent-omp-sdk/dist/pairing.d.ts +7 -0
  132. package/agent-omp-sdk/dist/pairing.js +45 -0
  133. package/agent-omp-sdk/dist/permission.d.ts +138 -0
  134. package/agent-omp-sdk/dist/permission.js +161 -0
  135. package/agent-omp-sdk/dist/protocol.d.ts +189 -0
  136. package/agent-omp-sdk/dist/rpc-types.d.ts +77 -0
  137. package/agent-omp-sdk/dist/rpc-types.js +1 -0
  138. package/agent-omp-sdk/dist/sdk-client.d.ts +160 -0
  139. package/{agent-omp → agent-omp-sdk}/dist/sdk-client.js +108 -56
  140. package/agent-omp-sdk/dist/sdk-runtime.d.ts +211 -0
  141. package/agent-omp-sdk/dist/sdk-runtime.js +418 -0
  142. package/agent-omp-sdk/dist/sidecar-client.d.ts +87 -0
  143. package/agent-omp-sdk/dist/sidecar-client.js +197 -0
  144. package/agent-omp-sdk/dist/store/adapter.d.ts +17 -0
  145. package/agent-omp-sdk/dist/store/adapter.js +47 -0
  146. package/agent-omp-sdk/dist/store/db.d.ts +70 -0
  147. package/agent-omp-sdk/dist/store/db.js +210 -0
  148. package/agent-omp-sdk/dist/store/index.d.ts +23 -0
  149. package/agent-omp-sdk/dist/store/index.js +59 -0
  150. package/agent-omp-sdk/dist/store/reconcile.d.ts +15 -0
  151. package/agent-omp-sdk/dist/store/reconcile.js +51 -0
  152. package/agent-omp-sdk/dist/store/schema.d.ts +8 -0
  153. package/agent-omp-sdk/dist/store/schema.js +40 -0
  154. package/agent-omp-sdk/dist/transport.d.ts +49 -0
  155. package/agent-omp-sdk/dist/transport.js +35 -0
  156. package/agent-omp-sdk/dist/world-home.d.ts +4 -0
  157. package/agent-omp-sdk/dist/world-home.js +23 -0
  158. package/agent-omp-sdk/dist/world-plugin.d.ts +10 -0
  159. package/agent-omp-sdk/dist/world-plugin.js +137 -0
  160. package/agent-omp-sdk/package.json +87 -0
  161. package/client/index.js +1204 -93
  162. package/cordis.patch.yml +64 -0
  163. package/package.json +17 -8
  164. package/superd-hub/dist/client/agent-settings.d.ts +210 -0
  165. package/superd-hub/dist/client/agent-settings.js +300 -0
  166. package/superd-hub/dist/client/roster.js +6 -0
  167. package/superd-hub/dist/client/runtime-settings-api.d.ts +76 -0
  168. package/superd-hub/dist/client/runtime-settings-api.js +137 -0
  169. package/superd-hub/dist/client/switch.d.ts +165 -0
  170. package/superd-hub/dist/client/switch.js +295 -0
  171. package/superd-hub/lib/client/index.js +1204 -93
  172. package/agent-codex/dist/models.js +0 -152
  173. package/agent-omp/dist/sidecar-client.js +0 -195
  174. /package/{agent-agy → agent-agy-sdk}/bridge/agy_bridge.py +0 -0
  175. /package/{agent-agy → agent-agy-sdk}/bridge/gemini_openai_server.mjs +0 -0
  176. /package/{agent-codex/dist/codex-events.js → agent-codex-sdk/dist/codex-sdk-events.js} +0 -0
  177. /package/{agent-omp → agent-omp-sdk}/dist/protocol.js +0 -0
  178. /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
+ }