super-dsh 0.1.10-e → 0.1.11-a
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 +38 -2
- package/agent-agy/dist/agent.js +19 -1
- package/agent-claude/dist/agent.js +23 -5
- package/agent-codex/dist/adapter.js +3 -2
- package/agent-codex/dist/agent.js +41 -7
- package/agent-codex/dist/app-server-client.js +9 -2
- package/agent-codex/dist/app-server-protocol.js +6 -2
- package/agent-codex/dist/app-server-wire.js +29 -24
- package/agent-codex/dist/codex-launch.js +10 -32
- package/agent-codex/dist/codex-native-config.js +9 -3
- package/agent-codex/dist/codex-oauth.js +153 -0
- package/agent-codex/dist/codex-provider-gateway.js +36 -38
- package/agent-codex/dist/codex-providers.js +20 -7
- package/agent-codex/dist/codex-runtime-fetch.js +293 -0
- package/agent-codex/dist/codex-runtime.js +120 -0
- package/agent-codex/dist/index.js +20 -12
- package/agent-codex/package.json +8 -3
- package/agent-codex-sdk/dist/adapter.js +3 -2
- package/agent-codex-sdk/dist/agent.js +35 -1
- package/agent-codex-sdk/dist/codex-native-config.js +9 -3
- package/agent-codex-sdk/dist/codex-oauth.js +153 -0
- package/agent-codex-sdk/dist/codex-provider-gateway.js +36 -38
- package/agent-codex-sdk/dist/codex-providers.js +20 -7
- package/agent-codex-sdk/dist/codex-sdk-launch.js +5 -38
- package/agent-codex-sdk/dist/index.js +18 -11
- package/agent-codex-sdk/dist/sdk-runtime.js +3 -3
- package/agent-hermes/dist/agent.js +46 -2
- package/agent-hermes/dist/hermes-client.js +49 -1
- package/agent-omp/dist/agent.js +17 -4
- package/agent-omp-sdk/dist/agent.d.ts +11 -0
- package/agent-omp-sdk/dist/agent.js +17 -4
- package/agent-pi/dist/agent.js +33 -1
- package/client/index.js +10 -7
- package/package.json +7 -5
- package/superd-hub/dist/client/agent-settings.d.ts +1 -1
- package/superd-hub/dist/client/agent-settings.js +2 -2
- package/superd-hub/dist/connection-trust.d.ts +8 -0
- package/superd-hub/dist/connection-trust.js +16 -0
- package/superd-hub/dist/web-trust.d.ts +14 -42
- package/superd-hub/dist/web-trust.js +18 -43
- package/superd-hub/dist/world-mount.d.ts +6 -5
- package/superd-hub/dist/world-mount.js +6 -6
- package/superd-hub/lib/client/index.js +10 -7
- package/superd-hub/package.json +5 -1
- package/agent-codex/README.md +0 -179
- package/agent-codex-sdk/README.md +0 -79
- package/agent-hermes/README.md +0 -145
- package/agent-omp/README.md +0 -94
- package/agent-omp-sdk/README.md +0 -103
- package/agent-pi/README.md +0 -67
|
@@ -1,41 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* codex-sdk-launch.ts — how a provider reaches the in-process Codex SDK
|
|
3
|
-
* (2026-10-04 multi-provider rework).
|
|
4
|
-
*
|
|
5
|
-
* This is the SDK half of `agent-codex/codex-launch.ts`. That module turns a
|
|
6
|
-
* provider into argv for a `codex app-server` child; this one turns the same
|
|
7
|
-
* provider into the structured object the SDK's `CodexOptions.config` takes —
|
|
8
|
-
* and underneath they are the same mechanism. The SDK's own type docs say it
|
|
9
|
-
* "flatten[s] it into dotted paths and serialize[s] values as TOML literals so
|
|
10
|
-
* they are compatible with the CLI's `--config` parsing", which is exactly the
|
|
11
|
-
* `--config key=value` channel the app-server line drives by hand. A provider
|
|
12
|
-
* therefore costs nothing on disk on this path either, and is scoped to the
|
|
13
|
-
* process that builds the config, which is what a per-session provider choice
|
|
14
|
-
* needs.
|
|
15
|
-
*
|
|
16
|
-
* The one transport difference is env. A spawned app-server child inherits the
|
|
17
|
-
* process environment, so `codex-launch.ts` hands back ADDITIONS and its caller
|
|
18
|
-
* spreads them (`env: { ...process.env, ...launch.env }`). The SDK's `env` is
|
|
19
|
-
* REPLACE semantics — "When provided, the SDK will not inherit variables from
|
|
20
|
-
* `process.env`" — so the caller must spread the same way before constructing
|
|
21
|
-
* `Codex`. This module still hands back additions only: composing the full map
|
|
22
|
-
* belongs to the caller, and keeping the addition set small is what makes the
|
|
23
|
-
* synthetic-key branch legible in tests.
|
|
24
|
-
*
|
|
25
|
-
* The measured constraints `codex-launch.ts` encodes carry over unchanged (see
|
|
26
|
-
* `docs/test-reports/2026-10-03-codex-provider-model-switch-probe.md`):
|
|
27
|
-
*
|
|
28
|
-
* - a HYPHENATED id is fine in a dotted path (`model_providers.zhipu-cn.…`),
|
|
29
|
-
* so native table names survive verbatim as provider ids;
|
|
30
|
-
* - `model_catalog_json` is resolved against the process CWD when it arrives
|
|
31
|
-
* as an override, so the path is always absolute;
|
|
32
|
-
* - `env_key` is the one auth shape that never consults the auth manager, so a
|
|
33
|
-
* stored API key rides the child's environment under a synthetic variable
|
|
34
|
-
* name. That is also what makes a keyed provider usable behind a ChatGPT
|
|
35
|
-
* login, where `requires_openai_auth` is silently shadowed.
|
|
36
|
-
*
|
|
37
|
-
* @module agent-codex-sdk/codex-sdk-launch
|
|
38
|
-
*/
|
|
1
|
+
import { officialCatalogForLaunch } from "./codex-oauth.js";
|
|
39
2
|
import { writeProviderCatalog } from "./codex-native-config.js";
|
|
40
3
|
/** The environment variable prefix for a key this adapter injects itself. */
|
|
41
4
|
const SYNTHETIC_KEY_PREFIX = "SUPERD_CODEX_KEY_";
|
|
@@ -54,6 +17,10 @@ export function syntheticKeyName(providerId) {
|
|
|
54
17
|
* @returns the structured config, the environment additions, and the provider id they pin.
|
|
55
18
|
*/
|
|
56
19
|
export function providerSdkLaunch(worldHome, codexHome, provider) {
|
|
20
|
+
if (provider.kind === "openai-oauth") {
|
|
21
|
+
const catalog = officialCatalogForLaunch(codexHome);
|
|
22
|
+
return { config: { model_provider: "openai", openai_base_url: "", ...(catalog === undefined ? {} : { model_catalog_json: catalog }) }, env: {}, providerId: "openai" };
|
|
23
|
+
}
|
|
57
24
|
// The SDK serializes these values as TOML literals itself, so unlike the argv
|
|
58
25
|
// line there is no hand-quoting here: a bare object is the whole override.
|
|
59
26
|
const table = {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { OPENAI_OAUTH_ID, readEffectiveProviderStore } from "./codex-oauth.js";
|
|
1
2
|
/**
|
|
2
3
|
* agent-codex provider — Codex provider plugin for DeepSeek Harness
|
|
3
4
|
* (Super D, SDK line).
|
|
@@ -30,7 +31,6 @@ import { CodexLlmAdapter, routeIds } from "./adapter.js";
|
|
|
30
31
|
import { registerCodexPreset } from "./agent-preset-codex.js";
|
|
31
32
|
import { readRolloutHead, resolveCodexHome } from "./codex-store.js";
|
|
32
33
|
import { CodexResumeError, sessionRecord, upsertSession } from "./session-map.js";
|
|
33
|
-
import { readProviderStore } from "./codex-providers.js";
|
|
34
34
|
import { readNativeSelection } from "./codex-native-config.js";
|
|
35
35
|
import { CodexProvidersGateway, syncFromNative } from "./codex-provider-gateway.js";
|
|
36
36
|
import { seedRoute } from "./codex-route.js";
|
|
@@ -157,12 +157,12 @@ export class CodexProvider extends Service {
|
|
|
157
157
|
*/
|
|
158
158
|
syncNow() {
|
|
159
159
|
try {
|
|
160
|
-
const {
|
|
160
|
+
const { added, updated } = syncFromNative(this.home, resolveCodexHome());
|
|
161
161
|
if (added.length > 0 || updated.length > 0) {
|
|
162
162
|
this.runtime.ctx.logger.info(`codex-provider: config.toml sync — added ${added.join(", ") || "none"}, refreshed ${updated.join(", ") || "none"}`);
|
|
163
163
|
}
|
|
164
164
|
this.syncedAt = Date.now();
|
|
165
|
-
this.#applyRoutes(
|
|
165
|
+
this.#applyRoutes(readEffectiveProviderStore(this.home, resolveCodexHome()).providers);
|
|
166
166
|
}
|
|
167
167
|
catch (error) {
|
|
168
168
|
this.runtime.ctx.logger.warn(`codex-provider: provider sync failed: ${String(error)}`);
|
|
@@ -171,7 +171,7 @@ export class CodexProvider extends Service {
|
|
|
171
171
|
/** Re-register the `ctx.llm` routes from the current store (panel mutation path). */
|
|
172
172
|
refreshRoutes() {
|
|
173
173
|
try {
|
|
174
|
-
this.#applyRoutes(
|
|
174
|
+
this.#applyRoutes(readEffectiveProviderStore(this.home, resolveCodexHome()).providers);
|
|
175
175
|
}
|
|
176
176
|
catch (error) {
|
|
177
177
|
this.runtime.ctx.logger.warn(`codex-provider: route refresh failed: ${String(error)}`);
|
|
@@ -186,14 +186,14 @@ export class CodexProvider extends Service {
|
|
|
186
186
|
nativeProviderId() {
|
|
187
187
|
const native = readNativeSelection(resolveCodexHome());
|
|
188
188
|
const wanted = native.providerId;
|
|
189
|
-
if (wanted === undefined)
|
|
190
|
-
return undefined;
|
|
191
|
-
const providers =
|
|
192
|
-
return providers.find((entry) => entry.id === wanted)?.id ?? undefined;
|
|
189
|
+
if (wanted === undefined || wanted === "openai")
|
|
190
|
+
return readEffectiveProviderStore(this.home, resolveCodexHome()).providers.some(entry => entry.kind === "openai-oauth") ? OPENAI_OAUTH_ID : undefined;
|
|
191
|
+
const providers = readEffectiveProviderStore(this.home, resolveCodexHome()).providers;
|
|
192
|
+
return providers.find((entry) => entry.kind !== "openai-oauth" && entry.id === wanted)?.id ?? undefined;
|
|
193
193
|
}
|
|
194
194
|
/** The store entry a DSH route id names. */
|
|
195
195
|
providerEntry(providerId) {
|
|
196
|
-
return
|
|
196
|
+
return readEffectiveProviderStore(this.home, resolveCodexHome()).providers.find((entry) => entry.id === providerId);
|
|
197
197
|
}
|
|
198
198
|
/**
|
|
199
199
|
* The factory's session-level route fallback — always ONE coherent pair.
|
|
@@ -215,8 +215,13 @@ export class CodexProvider extends Service {
|
|
|
215
215
|
sessionAgentOptions(requested) {
|
|
216
216
|
const native = readNativeSelection(resolveCodexHome());
|
|
217
217
|
const wanted = requested?.provider !== undefined && typeof requested.model === "string" ? requested : undefined;
|
|
218
|
+
const requestedEntry = wanted === undefined ? undefined : this.providerEntry(wanted.provider);
|
|
219
|
+
// An explicitly selected native identity must survive unavailable auth or
|
|
220
|
+
// metadata. Its launch fails honestly instead of seeding a third-party route.
|
|
221
|
+
if (wanted?.provider === OPENAI_OAUTH_ID && (requestedEntry === undefined || requestedEntry.kind === "openai-oauth"))
|
|
222
|
+
return { ...wanted };
|
|
218
223
|
const serves = wanted !== undefined
|
|
219
|
-
&&
|
|
224
|
+
&& requestedEntry?.models.some((model) => model.id === wanted.model) === true;
|
|
220
225
|
if (wanted !== undefined && serves)
|
|
221
226
|
return { ...wanted, provider: wanted.provider, model: wanted.model };
|
|
222
227
|
const provider = this.nativeProviderId();
|
|
@@ -272,6 +277,8 @@ export class CodexProvider extends Service {
|
|
|
272
277
|
// the user's home (the per-provider model catalog is ours).
|
|
273
278
|
launchFor: (providerId) => {
|
|
274
279
|
const entry = this.providerEntry(providerId);
|
|
280
|
+
if (entry === undefined && providerId === OPENAI_OAUTH_ID)
|
|
281
|
+
throw new Error("OpenAI OAuth authentication is unavailable; sign in using Codex");
|
|
275
282
|
return entry === undefined ? undefined : providerSdkLaunch(this.home, codexHome, entry);
|
|
276
283
|
},
|
|
277
284
|
contextWindowFor: (providerId, model) => {
|
|
@@ -374,7 +381,7 @@ export class CodexProvider extends Service {
|
|
|
374
381
|
* picker would be noise.
|
|
375
382
|
*/
|
|
376
383
|
#registerModelCatalog() {
|
|
377
|
-
this.#applyRoutes(
|
|
384
|
+
this.#applyRoutes(readEffectiveProviderStore(this.home, resolveCodexHome()).providers);
|
|
378
385
|
}
|
|
379
386
|
/** Register or atomically re-point the codex routes at the given providers. */
|
|
380
387
|
#applyRoutes(providers) {
|
|
@@ -52,8 +52,8 @@ export const SDK_PACKAGE = "@openai/codex-sdk";
|
|
|
52
52
|
/** The native binary package the SDK resolves at run time; pinned with the SDK. */
|
|
53
53
|
export const SDK_BINARY_PACKAGE = "@openai/codex";
|
|
54
54
|
/**
|
|
55
|
-
* Pin of BOTH packages
|
|
56
|
-
*
|
|
55
|
+
* Pin of BOTH SDK runtime packages; the optional SDK line keeps its tested 0.154.0 pair,
|
|
56
|
+
* while the primary app-server line independently discovers installed native cores.
|
|
57
57
|
*/
|
|
58
58
|
export const SDK_PACKAGE_VERSION = "0.154.0";
|
|
59
59
|
/** The entry `loadCodexSdk` imports, relative to the runtime dir. */
|
|
@@ -110,7 +110,7 @@ const LOCK_STALE_MS = 30 * 60 * 1000;
|
|
|
110
110
|
export function sdkInstallArgv(packageVersion = SDK_PACKAGE_VERSION) {
|
|
111
111
|
return [
|
|
112
112
|
"install",
|
|
113
|
-
"--exact",
|
|
113
|
+
"--save-exact",
|
|
114
114
|
"--ignore-scripts",
|
|
115
115
|
"--omit=dev",
|
|
116
116
|
`${SDK_PACKAGE}@${packageVersion}`,
|
|
@@ -291,6 +291,12 @@ export class HermesAgent {
|
|
|
291
291
|
*/
|
|
292
292
|
#remoteQueue = [];
|
|
293
293
|
#bridgedToolResults = new Set();
|
|
294
|
+
/** callIds already advertised by an appended assistant/message frame — the
|
|
295
|
+
* strict validator refuses any `tool/call` whose callId no assistant frame
|
|
296
|
+
* advertised (2026-10-04 dev3 case C: the hermes wire never delivers the
|
|
297
|
+
* tool_use-bearing intermediate assistant messages, so execution frames
|
|
298
|
+
* arrived with no advertisement anchor). */
|
|
299
|
+
#advertisedToolCalls = new Set();
|
|
294
300
|
/** Fires when the agent has been idle past HERMES_IDLE_EXIT_MS (tears down the client). */
|
|
295
301
|
#onIdleExit;
|
|
296
302
|
#idleExitTimer = undefined;
|
|
@@ -495,7 +501,8 @@ export class HermesAgent {
|
|
|
495
501
|
return;
|
|
496
502
|
this.session.append("turn/start", { turn: this.#dashTurn });
|
|
497
503
|
await this.#emitRequestContext();
|
|
498
|
-
|
|
504
|
+
const event = this.session.append("user/message", message, { surfaceOp: "append" });
|
|
505
|
+
this.#titleSourceSeq ??= event.seq;
|
|
499
506
|
this.#armTurnDeadline();
|
|
500
507
|
void this.#client
|
|
501
508
|
.prompt(userMessageText(message))
|
|
@@ -525,6 +532,12 @@ export class HermesAgent {
|
|
|
525
532
|
await this.#handshake(`session.create`, () => this.#client.createSession({
|
|
526
533
|
cwd: sessionCtx?.cwd ?? process.cwd(),
|
|
527
534
|
...(route?.model !== undefined ? { model: route.model } : {}),
|
|
535
|
+
// The route's provider slug MUST ride with the model: without it the
|
|
536
|
+
// gateway pins the session to its default provider (deepseek) and any
|
|
537
|
+
// foreign-family model id (z-ai/glm-*, anthropic/*, …) dies at
|
|
538
|
+
// session.create with `Model … is not served by provider 'deepseek'`
|
|
539
|
+
// (-32602). The gateway normalizes aliases (`z-ai` → `zai`) itself.
|
|
540
|
+
...(route?.provider !== undefined ? { provider: route.provider } : {}),
|
|
528
541
|
...(sessionCtx?.title !== undefined && sessionCtx.title !== "" ? { title: sessionCtx.title } : {}),
|
|
529
542
|
}));
|
|
530
543
|
}
|
|
@@ -892,13 +905,40 @@ export class HermesAgent {
|
|
|
892
905
|
break;
|
|
893
906
|
const callId = String(event.toolCallId ?? "");
|
|
894
907
|
const name = String(event.toolName ?? "");
|
|
908
|
+
const argumentsJson = String(event.argumentsJson ?? JSON.stringify(event.args ?? {}));
|
|
895
909
|
this.#commitStepStart();
|
|
910
|
+
// Advertisement-before-execution (validator invariant): the hermes wire
|
|
911
|
+
// never delivers the tool_use-bearing intermediate assistant messages,
|
|
912
|
+
// so unless a real assistant frame already advertised this callId,
|
|
913
|
+
// bridge a minimal assistant/message carrying the tool-call block —
|
|
914
|
+
// arguments copied verbatim into both frames (validator: lifecycle
|
|
915
|
+
// arguments ≡ advertised, strict string equality; 2026-10-04 dev3
|
|
916
|
+
// case C). A live stream bridge is left untouched — the final real
|
|
917
|
+
// assistant message settles it.
|
|
918
|
+
if (!this.#advertisedToolCalls.has(callId)) {
|
|
919
|
+
const advertisement = createAssistantMessage({
|
|
920
|
+
content: [{ type: "tool-call", id: ToolCallId(callId), name, arguments: argumentsJson }],
|
|
921
|
+
source: {
|
|
922
|
+
provider: String(this.options.provider ?? ""),
|
|
923
|
+
model: String(this.options.model ?? ""),
|
|
924
|
+
},
|
|
925
|
+
});
|
|
926
|
+
this.session.append("assistant/message", {
|
|
927
|
+
turn: this.#dashTurn,
|
|
928
|
+
step: this.#step,
|
|
929
|
+
message: advertisement,
|
|
930
|
+
stream: [],
|
|
931
|
+
}, {
|
|
932
|
+
surfaceOp: "append",
|
|
933
|
+
});
|
|
934
|
+
this.#advertisedToolCalls.add(callId);
|
|
935
|
+
}
|
|
896
936
|
this.session.append("tool/call", {
|
|
897
937
|
turn: this.#dashTurn,
|
|
898
938
|
step: this.#step,
|
|
899
939
|
callId: ToolCallId(callId),
|
|
900
940
|
name,
|
|
901
|
-
arguments:
|
|
941
|
+
arguments: argumentsJson,
|
|
902
942
|
});
|
|
903
943
|
break;
|
|
904
944
|
}
|
|
@@ -1016,6 +1056,10 @@ export class HermesAgent {
|
|
|
1016
1056
|
}
|
|
1017
1057
|
#appendAssistantMessage(message, usage) {
|
|
1018
1058
|
const content = convertContent(message.content);
|
|
1059
|
+
for (const block of content) {
|
|
1060
|
+
if (block.type === "tool-call")
|
|
1061
|
+
this.#advertisedToolCalls.add(block.id);
|
|
1062
|
+
}
|
|
1019
1063
|
const assistant = createAssistantMessage({
|
|
1020
1064
|
content,
|
|
1021
1065
|
source: {
|
|
@@ -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;
|
package/agent-omp/dist/agent.js
CHANGED
|
@@ -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
|
-
|
|
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({
|
|
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
|
-
|
|
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({
|
|
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);
|
package/agent-pi/dist/agent.js
CHANGED
|
@@ -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:
|
|
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,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "super-dsh",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Super D for the DeepSeek Harness (DSH)
|
|
3
|
+
"version": "0.1.11-a",
|
|
4
|
+
"description": "Super D for the DeepSeek Harness (DSH) — one dsh plugin that mounts the superd hub (runtime selector, roster, per-runtime agents) and joins local foreign-agent runtimes (OMP, Codex, Claude, Pi, Hermes, Antigravity) into the DSH Web UI. Each runtime keeps its native home and session store; the hub only bridges.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
@@ -80,8 +80,6 @@
|
|
|
80
80
|
},
|
|
81
81
|
"dependencies": {
|
|
82
82
|
"@earendil-works/pi-coding-agent": "0.84.2",
|
|
83
|
-
"@openai/codex": "0.154.0",
|
|
84
|
-
"ws": "^8.21.0",
|
|
85
83
|
"zod": "4.4.3",
|
|
86
84
|
"@modelcontextprotocol/sdk": "^1.29.0"
|
|
87
85
|
},
|
|
@@ -100,7 +98,8 @@
|
|
|
100
98
|
"@deepseek-ai/dsh-tools": ">=0.1.5-0 <0.3.0-0",
|
|
101
99
|
"@deepseek-ai/dsh-typert-protocol": ">=0.1.5-0 <0.3.0-0",
|
|
102
100
|
"@deepseek-ai/dsh-user-approval": ">=0.1.5-0 <0.3.0-0",
|
|
103
|
-
"@deepseek-ai/dsh-user-questions": ">=0.1.5-0 <0.3.0-0"
|
|
101
|
+
"@deepseek-ai/dsh-user-questions": ">=0.1.5-0 <0.3.0-0",
|
|
102
|
+
"@deepseek-ai/dsh-client-connection": ">=0.1.5-0 <0.3.0-0"
|
|
104
103
|
},
|
|
105
104
|
"peerDependenciesMeta": {
|
|
106
105
|
"@deepseek-ai/cordis": {
|
|
@@ -147,6 +146,9 @@
|
|
|
147
146
|
},
|
|
148
147
|
"@deepseek-ai/dsh-user-questions": {
|
|
149
148
|
"optional": true
|
|
149
|
+
},
|
|
150
|
+
"@deepseek-ai/dsh-client-connection": {
|
|
151
|
+
"optional": true
|
|
150
152
|
}
|
|
151
153
|
},
|
|
152
154
|
"publishConfig": {
|
|
@@ -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)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { HostConnectionHandle } from '@deepseek-ai/dsh-client-connection';
|
|
2
|
+
/**
|
|
3
|
+
* Use the native index authenticator as a cookie-only admission delegate.
|
|
4
|
+
* A fixed, query-free index path prevents token exchange or redirects here;
|
|
5
|
+
* original Host and Cookie headers preserve the native authority binding and
|
|
6
|
+
* lifetime. No upstream private fields or cookie-format copies are needed.
|
|
7
|
+
*/
|
|
8
|
+
export declare function installAuthenticationOnlyAdmission(connection: HostConnectionHandle): () => void;
|