super-dsh 0.1.10-e → 0.1.11

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 (38) hide show
  1. package/README.md +18 -2
  2. package/agent-agy/dist/agent.js +19 -1
  3. package/agent-claude/dist/agent.js +23 -5
  4. package/agent-codex/README.md +18 -6
  5. package/agent-codex/dist/adapter.js +3 -2
  6. package/agent-codex/dist/agent.js +41 -7
  7. package/agent-codex/dist/app-server-client.js +7 -1
  8. package/agent-codex/dist/app-server-wire.js +10 -22
  9. package/agent-codex/dist/codex-launch.js +10 -32
  10. package/agent-codex/dist/codex-native-config.js +9 -3
  11. package/agent-codex/dist/codex-oauth.js +153 -0
  12. package/agent-codex/dist/codex-provider-gateway.js +36 -38
  13. package/agent-codex/dist/codex-providers.js +20 -7
  14. package/agent-codex/dist/codex-runtime.js +120 -0
  15. package/agent-codex/dist/index.js +18 -11
  16. package/agent-codex/package.json +1 -1
  17. package/agent-codex-sdk/dist/adapter.js +3 -2
  18. package/agent-codex-sdk/dist/agent.js +35 -1
  19. package/agent-codex-sdk/dist/codex-native-config.js +9 -3
  20. package/agent-codex-sdk/dist/codex-oauth.js +153 -0
  21. package/agent-codex-sdk/dist/codex-provider-gateway.js +36 -38
  22. package/agent-codex-sdk/dist/codex-providers.js +20 -7
  23. package/agent-codex-sdk/dist/codex-sdk-launch.js +5 -38
  24. package/agent-codex-sdk/dist/index.js +18 -11
  25. package/agent-codex-sdk/dist/sdk-runtime.js +2 -2
  26. package/agent-hermes/dist/agent.js +46 -2
  27. package/agent-hermes/dist/hermes-client.js +49 -1
  28. package/agent-omp/dist/agent.js +17 -4
  29. package/agent-omp-sdk/dist/agent.d.ts +11 -0
  30. package/agent-omp-sdk/dist/agent.js +17 -4
  31. package/agent-pi/dist/agent.js +33 -1
  32. package/client/index.js +10 -7
  33. package/package.json +2 -2
  34. package/superd-hub/dist/client/agent-settings.d.ts +1 -1
  35. package/superd-hub/dist/client/agent-settings.js +2 -2
  36. package/superd-hub/dist/world-mount.d.ts +6 -5
  37. package/superd-hub/dist/world-mount.js +6 -6
  38. package/superd-hub/lib/client/index.js +10 -7
@@ -61,7 +61,7 @@
61
61
  * (keep the spawned stdio transport the only transport).
62
62
  */
63
63
  import { spawn } from "node:child_process";
64
- import { existsSync } from "node:fs";
64
+ import { existsSync, readFileSync } from "node:fs";
65
65
  import { homedir } from "node:os";
66
66
  import { delimiter, resolve } from "node:path";
67
67
  import { createInterface } from "node:readline";
@@ -124,6 +124,46 @@ export function resolveHermesPython(root) {
124
124
  export function resolveHermesRoot() {
125
125
  return process.env.SUPERD_HERMES_ROOT?.trim() || resolve(homedir(), ".hermes/hermes-agent");
126
126
  }
127
+ /** Parse one `.env` body into entries: `#` comments, optional `export ` prefix,
128
+ * optional single/double quotes, `KEY=VALUE` on one line each. Unparsable
129
+ * lines are skipped (best effort — the gateway tolerates partial envs). */
130
+ export function parseDotEnv(text) {
131
+ const out = {};
132
+ for (const rawLine of text.split(/\r?\n/)) {
133
+ const line = rawLine.trim();
134
+ if (line === "" || line.startsWith("#"))
135
+ continue;
136
+ const body = line.startsWith("export ") ? line.slice("export ".length).trim() : line;
137
+ const eq = body.indexOf("=");
138
+ if (eq <= 0)
139
+ continue;
140
+ const key = body.slice(0, eq).trim();
141
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key))
142
+ continue;
143
+ let value = body.slice(eq + 1).trim();
144
+ if (value.length >= 2 && ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'")))) {
145
+ value = value.slice(1, -1);
146
+ }
147
+ out[key] = value;
148
+ }
149
+ return out;
150
+ }
151
+ /** The native home's `.env` (`~/.hermes/.env`), parsed once per call. The omp
152
+ * and pi runtimes load their own native `.env` on startup; the hermes gateway
153
+ * child does NOT (and the harness env deliberately carries no provider
154
+ * keys), so without this the spawned gateway sees zero provider credentials
155
+ * — every provider request dies as quota/auth regardless of model. */
156
+ export function loadHermesDotEnv() {
157
+ const path = resolve(resolveHermesRoot(), "..", ".env");
158
+ if (!existsSync(path))
159
+ return {};
160
+ try {
161
+ return parseDotEnv(readFileSync(path, "utf8"));
162
+ }
163
+ catch {
164
+ return {};
165
+ }
166
+ }
127
167
  function asRecord(value) {
128
168
  if (value === null || typeof value !== "object" || Array.isArray(value))
129
169
  return null;
@@ -197,6 +237,14 @@ export class HermesGatewayClient {
197
237
  const root = resolveHermesRoot();
198
238
  const python = resolveHermesPython(root);
199
239
  const env = { ...(opts.env ?? process.env) };
240
+ // Native `.env` fill (dotenv convention: existing env WINS). The omp and
241
+ // pi runtimes load their own native `.env`; the hermes gateway child does
242
+ // not, and the harness env carries no provider keys — without this fill
243
+ // every provider request dies as quota/auth regardless of model.
244
+ for (const [dotKey, dotValue] of Object.entries(loadHermesDotEnv())) {
245
+ if (env[dotKey] === undefined || env[dotKey] === "")
246
+ env[dotKey] = dotValue;
247
+ }
200
248
  const prevPath = env.PYTHONPATH?.trim();
201
249
  env.PYTHONPATH = prevPath ? `${root}${delimiter}${prevPath}` : root;
202
250
  env.HERMES_PYTHON_SRC_ROOT = root;
@@ -171,14 +171,23 @@ function userMessageFromOmp(message) {
171
171
  source: { kind: "user" },
172
172
  });
173
173
  }
174
- /** Extract every tool call an OMP assistant message names, in order, for approval correlation. */
175
- function toolCalls(blocks) {
174
+ /** Extract every tool call an OMP assistant message names, in order, for approval correlation.
175
+ * `arguments` is the ADVERTISED args原文 (same normalization `convertContent` applies): dsh's
176
+ * strict validator requires `tool/call.arguments` ≡ the assistant frame's advertised arguments
177
+ * byte-for-byte, while `tool_execution_*` events carry runtime-parsed (schema-stripped) args —
178
+ * models that add unknown fields (e.g. an intent string) make the two diverge and the stored
179
+ * log fail to load (2026-10-04 dev3 case A). Exported for the regression test. */
180
+ export function toolCalls(blocks) {
176
181
  if (blocks === undefined)
177
182
  return [];
178
183
  const result = [];
179
184
  for (const block of blocks) {
180
185
  if (block.type === "toolCall" && typeof block.id === "string") {
181
- result.push({ callId: block.id, name: typeof block.name === "string" ? block.name : "" });
186
+ result.push({
187
+ callId: block.id,
188
+ name: typeof block.name === "string" ? block.name : "",
189
+ arguments: typeof block.arguments === "string" ? block.arguments : JSON.stringify(block.arguments ?? {}),
190
+ });
182
191
  }
183
192
  }
184
193
  return result;
@@ -954,12 +963,16 @@ export class OmpAgent {
954
963
  case "tool_execution_start": {
955
964
  const callId = String(event.toolCallId ?? "");
956
965
  const name = String(event.toolName ?? "");
966
+ // Advertised args win (strict validator: tool/call.arguments ≡ the
967
+ // assistant frame's advertised arguments); execution args are the
968
+ // fallback for calls no assistant frame ever carried.
969
+ const advertised = this.#pendingToolCalls.find((pending) => pending.callId === callId);
957
970
  this.session.append("tool/call", {
958
971
  turn: this.#dashTurn,
959
972
  step: this.#step,
960
973
  callId: ToolCallId(callId),
961
974
  name,
962
- arguments: JSON.stringify(event.args ?? {}),
975
+ arguments: advertised?.arguments ?? JSON.stringify(event.args ?? {}),
963
976
  });
964
977
  // The assistant's pending tool call is now materialized as a running call.
965
978
  this.#pendingToolCalls = this.#pendingToolCalls.filter((pending) => pending.callId !== callId);
@@ -43,6 +43,17 @@ export declare function ompFailure(message: OmpMessage): {
43
43
  } | undefined;
44
44
  /** Map OMP's usage accounting into Dash `TokenUsage` (disjoint field names). */
45
45
  export declare function convertUsage(usage: unknown): TokenUsage | undefined;
46
+ /** Extract every tool call an OMP assistant message names, in order, for approval correlation.
47
+ * `arguments` is the ADVERTISED args原文 (same normalization `convertContent` applies): dsh's
48
+ * strict validator requires `tool/call.arguments` ≡ the assistant frame's advertised arguments
49
+ * byte-for-byte, while `tool_execution_*` events carry runtime-parsed (schema-stripped) args —
50
+ * models that add unknown fields (e.g. an intent string) make the two diverge and the stored
51
+ * log fail to load (2026-10-04 dev3 case A). Exported for the regression test. */
52
+ export declare function toolCalls(blocks: OmpContentBlock[] | undefined): {
53
+ callId: string;
54
+ name: string;
55
+ arguments: string;
56
+ }[];
46
57
  export declare class OmpAgent implements Agent {
47
58
  #private;
48
59
  readonly id: SessionId;
@@ -171,14 +171,23 @@ function userMessageFromOmp(message) {
171
171
  source: { kind: "user" },
172
172
  });
173
173
  }
174
- /** Extract every tool call an OMP assistant message names, in order, for approval correlation. */
175
- function toolCalls(blocks) {
174
+ /** Extract every tool call an OMP assistant message names, in order, for approval correlation.
175
+ * `arguments` is the ADVERTISED args原文 (same normalization `convertContent` applies): dsh's
176
+ * strict validator requires `tool/call.arguments` ≡ the assistant frame's advertised arguments
177
+ * byte-for-byte, while `tool_execution_*` events carry runtime-parsed (schema-stripped) args —
178
+ * models that add unknown fields (e.g. an intent string) make the two diverge and the stored
179
+ * log fail to load (2026-10-04 dev3 case A). Exported for the regression test. */
180
+ export function toolCalls(blocks) {
176
181
  if (blocks === undefined)
177
182
  return [];
178
183
  const result = [];
179
184
  for (const block of blocks) {
180
185
  if (block.type === "toolCall" && typeof block.id === "string") {
181
- result.push({ callId: block.id, name: typeof block.name === "string" ? block.name : "" });
186
+ result.push({
187
+ callId: block.id,
188
+ name: typeof block.name === "string" ? block.name : "",
189
+ arguments: typeof block.arguments === "string" ? block.arguments : JSON.stringify(block.arguments ?? {}),
190
+ });
182
191
  }
183
192
  }
184
193
  return result;
@@ -954,12 +963,16 @@ export class OmpAgent {
954
963
  case "tool_execution_start": {
955
964
  const callId = String(event.toolCallId ?? "");
956
965
  const name = String(event.toolName ?? "");
966
+ // Advertised args win (strict validator: tool/call.arguments ≡ the
967
+ // assistant frame's advertised arguments); execution args are the
968
+ // fallback for calls no assistant frame ever carried.
969
+ const advertised = this.#pendingToolCalls.find((pending) => pending.callId === callId);
957
970
  this.session.append("tool/call", {
958
971
  turn: this.#dashTurn,
959
972
  step: this.#step,
960
973
  callId: ToolCallId(callId),
961
974
  name,
962
- arguments: JSON.stringify(event.args ?? {}),
975
+ arguments: advertised?.arguments ?? JSON.stringify(event.args ?? {}),
963
976
  });
964
977
  // The assistant's pending tool call is now materialized as a running call.
965
978
  this.#pendingToolCalls = this.#pendingToolCalls.filter((pending) => pending.callId !== callId);
@@ -206,6 +206,11 @@ export class PiAgent {
206
206
  */
207
207
  #remoteQueue = [];
208
208
  #bridgedToolResults = new Set();
209
+ /** callIds already advertised by an appended assistant/message frame — the
210
+ * strict validator refuses any `tool/call` whose callId no assistant frame
211
+ * advertised, and requires the execution arguments to equal the advertised
212
+ * string (2026-10-04 dev3 omp/hermes repair: same write-side family). */
213
+ #advertisedToolCalls = new Set();
209
214
  /** The last title already mirrored into the log (dedupe). */
210
215
  #mirroredTitle = null;
211
216
  /** The open compaction lifecycle's id (`compaction/start` … `compaction/end`). */
@@ -750,7 +755,30 @@ export class PiAgent {
750
755
  case "tool_execution_start": {
751
756
  const callId = String(event.toolCallId ?? "");
752
757
  const name = String(event.toolName ?? "");
758
+ const argumentsJson = typeof event.args === "string" ? event.args : JSON.stringify(event.args ?? {});
753
759
  this.#commitStepStart();
760
+ // Advertisement-before-execution (validator invariant, 2026-10-04 dev3
761
+ // omp/hermes repair family): unless a real assistant frame advertised
762
+ // this callId, bridge a minimal assistant/message carrying the
763
+ // tool-call block — arguments copied verbatim into both frames.
764
+ if (!this.#advertisedToolCalls.has(callId)) {
765
+ const advertisement = createAssistantMessage({
766
+ content: [{ type: "tool-call", id: ToolCallId(callId), name, arguments: argumentsJson }],
767
+ source: {
768
+ provider: String(this.options.provider ?? ""),
769
+ model: String(this.options.model ?? ""),
770
+ },
771
+ });
772
+ this.session.append("assistant/message", {
773
+ turn: this.#dashTurn,
774
+ step: this.#step,
775
+ message: advertisement,
776
+ stream: [],
777
+ }, {
778
+ surfaceOp: "append",
779
+ });
780
+ this.#advertisedToolCalls.add(callId);
781
+ }
754
782
  this.session.append("tool/call", {
755
783
  turn: this.#dashTurn,
756
784
  step: this.#step,
@@ -758,7 +786,7 @@ export class PiAgent {
758
786
  name,
759
787
  // The DSH payload requires the RAW JSON string the model produced;
760
788
  // the projection stringified pi's parsed args object.
761
- arguments: typeof event.args === "string" ? event.args : JSON.stringify(event.args ?? {}),
789
+ arguments: argumentsJson,
762
790
  });
763
791
  break;
764
792
  }
@@ -882,6 +910,10 @@ export class PiAgent {
882
910
  }
883
911
  #appendAssistantMessage(message) {
884
912
  const content = convertContent(message.content);
913
+ for (const block of content) {
914
+ if (block.type === "tool-call")
915
+ this.#advertisedToolCalls.add(block.id);
916
+ }
885
917
  // pi reports usage on every assistant message — same-event attachment,
886
918
  // no lag.
887
919
  const usage = convertUsage(message.usage);
package/client/index.js CHANGED
@@ -1738,14 +1738,14 @@ function modelOption(raw) {
1738
1738
  };
1739
1739
  }
1740
1740
  function providerAuthKind(raw) {
1741
- return raw === "env" ? "env" : raw === "authToken" ? "authToken" : "apiKey";
1741
+ return raw === "oauth" ? "oauth" : raw === "env" ? "env" : raw === "authToken" ? "authToken" : "apiKey";
1742
1742
  }
1743
1743
  function providerOption(raw) {
1744
1744
  if (typeof raw !== "object" || raw === null) return void 0;
1745
1745
  const record = raw;
1746
1746
  const id = nonEmpty(record.id);
1747
1747
  const label = nonEmpty(record.label);
1748
- const baseUrl = nonEmpty(record.baseUrl);
1748
+ const baseUrl = nonEmpty(record.baseUrl) ?? (record.authKind === "oauth" ? "" : void 0);
1749
1749
  if (id === void 0 || label === void 0 || baseUrl === void 0) return void 0;
1750
1750
  const models = Array.isArray(record.models) ? record.models.map(modelOption).filter((entry) => entry !== void 0) : [];
1751
1751
  const envName = nonEmpty(record.envName);
@@ -2650,6 +2650,10 @@ function ProvidersPanel({ agent, namespace }) {
2650
2650
  style: chip,
2651
2651
  children: "token"
2652
2652
  }),
2653
+ provider.authKind === "oauth" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2654
+ style: chip,
2655
+ children: "native OAuth"
2656
+ }),
2653
2657
  !provider.hasKey && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2654
2658
  style: chip,
2655
2659
  children: "no key"
@@ -2668,13 +2672,13 @@ function ProvidersPanel({ agent, namespace }) {
2668
2672
  }),
2669
2673
  provider.models.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2670
2674
  style: chipWarn,
2671
- children: "no models yet — add one"
2675
+ children: provider.authKind === "oauth" ? "model cache unavailable — open Codex to refresh" : "no models yet — add one"
2672
2676
  }),
2673
2677
  busy === `remove:${provider.id}` && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2674
2678
  style: chip,
2675
2679
  children: "removing…"
2676
2680
  }),
2677
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2681
+ provider.authKind !== "oauth" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2678
2682
  type: "button",
2679
2683
  style: buttonStyle,
2680
2684
  disabled: busy !== null,
@@ -2682,8 +2686,7 @@ function ProvidersPanel({ agent, namespace }) {
2682
2686
  openEdit(provider);
2683
2687
  },
2684
2688
  children: "Edit"
2685
- }),
2686
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2689
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2687
2690
  type: "button",
2688
2691
  style: {
2689
2692
  ...buttonStyle,
@@ -2695,7 +2698,7 @@ function ProvidersPanel({ agent, namespace }) {
2695
2698
  run(`remove:${provider.id}`, () => removeProvider(agent.key, namespace, provider.id));
2696
2699
  },
2697
2700
  children: "Delete"
2698
- })
2701
+ })] })
2699
2702
  ]
2700
2703
  }, provider.id)),
2701
2704
  editing === null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "super-dsh",
3
- "version": "0.1.10-e",
3
+ "version": "0.1.11",
4
4
  "description": "Super D for the DeepSeek Harness (DSH) \u2014 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",
@@ -80,7 +80,7 @@
80
80
  },
81
81
  "dependencies": {
82
82
  "@earendil-works/pi-coding-agent": "0.84.2",
83
- "@openai/codex": "0.154.0",
83
+ "@openai/codex": "0.160.0",
84
84
  "ws": "^8.21.0",
85
85
  "zod": "4.4.3",
86
86
  "@modelcontextprotocol/sdk": "^1.29.0"
@@ -108,7 +108,7 @@ export interface ProviderModelOption {
108
108
  * addition on save, which is why the form offers kinds per view
109
109
  * (`authKindOptions`) instead of one fixed list.
110
110
  */
111
- export type ProviderAuthKind = 'apiKey' | 'env' | 'authToken';
111
+ export type ProviderAuthKind = 'apiKey' | 'env' | 'authToken' | 'oauth';
112
112
  /** One configured provider, listed but never carrying its key. */
113
113
  export interface ProviderOption {
114
114
  readonly id: string;
@@ -138,7 +138,7 @@ function modelOption(raw) {
138
138
  function providerAuthKind(raw) {
139
139
  // An unstated or unknown kind reads as the shared conservative default,
140
140
  // never as the other face's shape.
141
- return raw === 'env' ? 'env' : raw === 'authToken' ? 'authToken' : 'apiKey';
141
+ return raw === 'oauth' ? 'oauth' : raw === 'env' ? 'env' : raw === 'authToken' ? 'authToken' : 'apiKey';
142
142
  }
143
143
  function providerOption(raw) {
144
144
  if (typeof raw !== 'object' || raw === null)
@@ -146,7 +146,7 @@ function providerOption(raw) {
146
146
  const record = raw;
147
147
  const id = nonEmpty(record.id);
148
148
  const label = nonEmpty(record.label);
149
- const baseUrl = nonEmpty(record.baseUrl);
149
+ const baseUrl = nonEmpty(record.baseUrl) ?? (record.authKind === 'oauth' ? '' : undefined);
150
150
  if (id === undefined || label === undefined || baseUrl === undefined)
151
151
  return undefined;
152
152
  const models = Array.isArray(record.models)
@@ -93,13 +93,14 @@ export declare const WORLD_WAVE1_DISABLED_ROWS: readonly string[];
93
93
  * Wave-2 of world composition minimization: the CLIENT lane. The
94
94
  * multi-ctx-multi-ui → native-ui-only transition (0.1.5 / T-D) retired the
95
95
  * world's own web UI (webserver/hmr/web-runtime/client-hmr above); wave-2
96
- * completes it by stripping the browser-half rows of the web-app bundle —
97
- * every dsh-client-* row plus the cordis client runner. In a world these
98
- * only registered modules into a table no browser ever reads.
96
+ * completes it by stripping the browser-only rows of the web-app bundle
97
+ * plus the cordis client runner. A dsh-client-* package can also provide
98
+ * host services, so its name alone does not make it safe to disable.
99
99
  *
100
100
  * Dependency-cleared 2026-09-27: the KEEP side (api controllers, gateway,
101
- * session core, adapters) injects none of their services with ONE
102
- * exceptions — `connection` (dsh-client-connection: the world's typert
101
+ * session core, adapters) keeps the required host services:
102
+ * `file-upload` (dsh-client-file-upload: sessionController injects its
103
+ * fileUploads service), `connection` (dsh-client-connection: the world's typert
103
104
  * gateway injects it) and `modules` (dsh-client-modules: its activation
104
105
  * work fails in world trees ("client bundles not found") and at least one
105
106
  * fiber treats it as required — the load-bearing parking restatement
@@ -95,13 +95,14 @@ export const WORLD_WAVE1_DISABLED_ROWS = [
95
95
  * Wave-2 of world composition minimization: the CLIENT lane. The
96
96
  * multi-ctx-multi-ui → native-ui-only transition (0.1.5 / T-D) retired the
97
97
  * world's own web UI (webserver/hmr/web-runtime/client-hmr above); wave-2
98
- * completes it by stripping the browser-half rows of the web-app bundle —
99
- * every dsh-client-* row plus the cordis client runner. In a world these
100
- * only registered modules into a table no browser ever reads.
98
+ * completes it by stripping the browser-only rows of the web-app bundle
99
+ * plus the cordis client runner. A dsh-client-* package can also provide
100
+ * host services, so its name alone does not make it safe to disable.
101
101
  *
102
102
  * Dependency-cleared 2026-09-27: the KEEP side (api controllers, gateway,
103
- * session core, adapters) injects none of their services with ONE
104
- * exceptions — `connection` (dsh-client-connection: the world's typert
103
+ * session core, adapters) keeps the required host services:
104
+ * `file-upload` (dsh-client-file-upload: sessionController injects its
105
+ * fileUploads service), `connection` (dsh-client-connection: the world's typert
105
106
  * gateway injects it) and `modules` (dsh-client-modules: its activation
106
107
  * work fails in world trees ("client bundles not found") and at least one
107
108
  * fiber treats it as required — the load-bearing parking restatement
@@ -110,7 +111,6 @@ export const WORLD_WAVE1_DISABLED_ROWS = [
110
111
  export const WORLD_WAVE2_DISABLED_ROWS = [
111
112
  'cordis-client-runner',
112
113
  'directory-picker-browse-surface',
113
- 'file-upload',
114
114
  'locale',
115
115
  'resources',
116
116
  'ui-agent-preset', 'ui-approval', 'ui-attachment', 'ui-brand-official',
@@ -1738,14 +1738,14 @@ function modelOption(raw) {
1738
1738
  };
1739
1739
  }
1740
1740
  function providerAuthKind(raw) {
1741
- return raw === "env" ? "env" : raw === "authToken" ? "authToken" : "apiKey";
1741
+ return raw === "oauth" ? "oauth" : raw === "env" ? "env" : raw === "authToken" ? "authToken" : "apiKey";
1742
1742
  }
1743
1743
  function providerOption(raw) {
1744
1744
  if (typeof raw !== "object" || raw === null) return void 0;
1745
1745
  const record = raw;
1746
1746
  const id = nonEmpty(record.id);
1747
1747
  const label = nonEmpty(record.label);
1748
- const baseUrl = nonEmpty(record.baseUrl);
1748
+ const baseUrl = nonEmpty(record.baseUrl) ?? (record.authKind === "oauth" ? "" : void 0);
1749
1749
  if (id === void 0 || label === void 0 || baseUrl === void 0) return void 0;
1750
1750
  const models = Array.isArray(record.models) ? record.models.map(modelOption).filter((entry) => entry !== void 0) : [];
1751
1751
  const envName = nonEmpty(record.envName);
@@ -2650,6 +2650,10 @@ function ProvidersPanel({ agent, namespace }) {
2650
2650
  style: chip,
2651
2651
  children: "token"
2652
2652
  }),
2653
+ provider.authKind === "oauth" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2654
+ style: chip,
2655
+ children: "native OAuth"
2656
+ }),
2653
2657
  !provider.hasKey && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2654
2658
  style: chip,
2655
2659
  children: "no key"
@@ -2668,13 +2672,13 @@ function ProvidersPanel({ agent, namespace }) {
2668
2672
  }),
2669
2673
  provider.models.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2670
2674
  style: chipWarn,
2671
- children: "no models yet — add one"
2675
+ children: provider.authKind === "oauth" ? "model cache unavailable — open Codex to refresh" : "no models yet — add one"
2672
2676
  }),
2673
2677
  busy === `remove:${provider.id}` && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2674
2678
  style: chip,
2675
2679
  children: "removing…"
2676
2680
  }),
2677
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2681
+ provider.authKind !== "oauth" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2678
2682
  type: "button",
2679
2683
  style: buttonStyle,
2680
2684
  disabled: busy !== null,
@@ -2682,8 +2686,7 @@ function ProvidersPanel({ agent, namespace }) {
2682
2686
  openEdit(provider);
2683
2687
  },
2684
2688
  children: "Edit"
2685
- }),
2686
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2689
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2687
2690
  type: "button",
2688
2691
  style: {
2689
2692
  ...buttonStyle,
@@ -2695,7 +2698,7 @@ function ProvidersPanel({ agent, namespace }) {
2695
2698
  run(`remove:${provider.id}`, () => removeProvider(agent.key, namespace, provider.id));
2696
2699
  },
2697
2700
  children: "Delete"
2698
- })
2701
+ })] })
2699
2702
  ]
2700
2703
  }, provider.id)),
2701
2704
  editing === null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {