super-dsh 0.1.11 → 0.1.12
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 +54 -6
- package/agent-agy/dist/agent.d.ts +4 -0
- package/agent-agy/dist/agent.js +41 -7
- package/agent-agy/dist/agy-cli-client.d.ts +12 -0
- package/agent-agy/dist/agy-cli-client.js +81 -13
- package/agent-agy/dist/index.js +13 -7
- package/agent-agy/package.json +5 -1
- package/agent-claude/dist/agent.js +67 -4
- package/agent-claude/dist/claude-client.js +46 -1
- package/agent-claude/dist/index.js +3 -0
- package/agent-claude/dist/sdk-runtime.js +2 -2
- package/agent-codex/dist/agent.js +33 -8
- package/agent-codex/dist/app-server-client.js +26 -3
- package/agent-codex/dist/app-server-protocol.js +6 -2
- package/agent-codex/dist/app-server-wire.js +23 -6
- package/agent-codex/dist/codex-native-config.js +6 -1
- package/agent-codex/dist/codex-runtime-fetch.js +293 -0
- package/agent-codex/dist/codex-runtime.js +12 -12
- package/agent-codex/dist/index.js +10 -4
- package/agent-codex/package.json +8 -3
- package/agent-codex-sdk/dist/sdk-runtime.js +1 -1
- package/agent-hermes/dist/hermes-client.js +44 -5
- package/agent-hermes/dist/index.js +10 -5
- package/agent-omp/dist/agent.js +36 -5
- package/agent-omp/dist/index.js +29 -6
- package/agent-omp/dist/lazy-rpc.js +11 -2
- package/agent-omp/dist/rpc-client.js +6 -1
- package/agent-pi/dist/agent.js +36 -9
- package/agent-pi/dist/index.js +6 -2
- package/agent-pi/dist/pi-client.js +13 -3
- package/cordis.patch.yml +10 -9
- package/package.json +6 -4
- package/superd-hub/dist/a2a-row.js +20 -3
- package/superd-hub/dist/connection-trust.d.ts +8 -0
- package/superd-hub/dist/connection-trust.js +16 -0
- package/superd-hub/dist/foreign-agy.d.ts +3 -0
- package/superd-hub/dist/foreign-agy.js +110 -0
- package/superd-hub/dist/foreign-claude.d.ts +4 -0
- package/superd-hub/dist/foreign-claude.js +160 -0
- package/superd-hub/dist/foreign-extension.d.ts +10 -0
- package/superd-hub/dist/foreign-extension.js +27 -0
- package/superd-hub/dist/foreign-hub.d.ts +27 -0
- package/superd-hub/dist/foreign-hub.js +243 -0
- package/superd-hub/dist/foreign-native.d.ts +4 -0
- package/superd-hub/dist/foreign-native.js +115 -0
- package/superd-hub/dist/web-trust.d.ts +14 -42
- package/superd-hub/dist/web-trust.js +18 -43
- package/superd-hub/package.json +14 -2
- package/agent-codex/README.md +0 -191
- 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
|
@@ -286,6 +286,8 @@ export class ClaudeAgent {
|
|
|
286
286
|
observedDefaultModel = "";
|
|
287
287
|
/** Deliveries parked while idle without a wakeup; flushed at the next prompt. */
|
|
288
288
|
remoteQueue = [];
|
|
289
|
+
/** Busy peer deliveries start their own turn inside the native Mod. */
|
|
290
|
+
pendingHubTurns = [];
|
|
289
291
|
/** Serialization chain for async prompt deliveries (attachment prefetch). */
|
|
290
292
|
deliveryChain = Promise.resolve();
|
|
291
293
|
sessionIdentityCommitted = false;
|
|
@@ -456,13 +458,20 @@ export class ClaudeAgent {
|
|
|
456
458
|
}
|
|
457
459
|
}
|
|
458
460
|
// ── internals ────────────────────────────────────────────────────────────
|
|
461
|
+
isHubMessage(message) {
|
|
462
|
+
const source = message.source;
|
|
463
|
+
return source?.kind === "a2a-relay" || source?.kind === "a2a-notice"
|
|
464
|
+
|| ((source?.kind === "user" || source?.kind === "user-rpc") && source.rpcId?.startsWith("a2a-") === true);
|
|
465
|
+
}
|
|
459
466
|
deliver(message, target, wakeup) {
|
|
460
467
|
if (this.disposed)
|
|
461
468
|
return;
|
|
462
469
|
const busy = this.turnOpen || this.streaming;
|
|
463
470
|
trace(`deliver target=${target} wakeup=${wakeup} busy=${busy}`);
|
|
464
471
|
if (busy) {
|
|
465
|
-
this.
|
|
472
|
+
const peer = this.isHubMessage(message);
|
|
473
|
+
if (!peer)
|
|
474
|
+
this.appendUserMessage(message);
|
|
466
475
|
this.scheduleDelivery(async () => {
|
|
467
476
|
const payload = await this.promptContent(message);
|
|
468
477
|
if (payload.kind === "empty") {
|
|
@@ -473,8 +482,22 @@ export class ClaudeAgent {
|
|
|
473
482
|
trace(`busy ${target} delivery for turn ${this.dashTurn} has no attachable content — not pushed`);
|
|
474
483
|
return;
|
|
475
484
|
}
|
|
476
|
-
|
|
477
|
-
|
|
485
|
+
if (peer) {
|
|
486
|
+
const entry = { message, id: undefined, started: false };
|
|
487
|
+
this.pendingHubTurns.push(entry);
|
|
488
|
+
try {
|
|
489
|
+
entry.id = await this.client.receiveHub(payload.content);
|
|
490
|
+
}
|
|
491
|
+
catch (error) {
|
|
492
|
+
const index = this.pendingHubTurns.indexOf(entry);
|
|
493
|
+
if (index >= 0)
|
|
494
|
+
this.pendingHubTurns.splice(index, 1);
|
|
495
|
+
throw error;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
else {
|
|
499
|
+
await (target === "next-turn" ? this.client.followUp(payload.content) : this.client.steer(payload.content));
|
|
500
|
+
}
|
|
478
501
|
});
|
|
479
502
|
return;
|
|
480
503
|
}
|
|
@@ -687,7 +710,7 @@ export class ClaudeAgent {
|
|
|
687
710
|
this.fail(new Error(payload.reason));
|
|
688
711
|
return;
|
|
689
712
|
}
|
|
690
|
-
void this.client.prompt(payload.content).catch((error) => this.fail(error));
|
|
713
|
+
void (this.isHubMessage(message) ? this.client.receiveHub(payload.content) : this.client.prompt(payload.content)).catch((error) => this.fail(error));
|
|
691
714
|
}
|
|
692
715
|
/**
|
|
693
716
|
* One-shot first-turn identity commit: the agent-preset stamp plus, when
|
|
@@ -1163,8 +1186,47 @@ export class ClaudeAgent {
|
|
|
1163
1186
|
trace(`forwarding "${line}" to Claude`);
|
|
1164
1187
|
this.prompt(createUserMessage({ content: [{ type: "text", text: line }], source: { kind: "user" } }));
|
|
1165
1188
|
}
|
|
1189
|
+
openStartedHubTurn() {
|
|
1190
|
+
if (this.turnOpen)
|
|
1191
|
+
return;
|
|
1192
|
+
const index = this.pendingHubTurns.findIndex(entry => entry.started);
|
|
1193
|
+
if (index < 0)
|
|
1194
|
+
return;
|
|
1195
|
+
const { message } = this.pendingHubTurns.splice(index, 1)[0];
|
|
1196
|
+
this.openNativeTurn(message);
|
|
1197
|
+
}
|
|
1198
|
+
openNativeTurn(message) {
|
|
1199
|
+
this.reserveTurn();
|
|
1200
|
+
clearTimeout(this.idleExitTimer);
|
|
1201
|
+
this.idleExitTimer = undefined;
|
|
1202
|
+
this.beginActivity();
|
|
1203
|
+
this.markRunning();
|
|
1204
|
+
this.session.append("turn/start", { turn: this.dashTurn });
|
|
1205
|
+
if (this.turnRoute !== undefined)
|
|
1206
|
+
this.commitRequestHeader(this.turnRoute);
|
|
1207
|
+
this.emitRequestContext(this.turnRoute);
|
|
1208
|
+
if (message !== undefined)
|
|
1209
|
+
this.appendUserMessage(message);
|
|
1210
|
+
}
|
|
1166
1211
|
handleEvent(event) {
|
|
1212
|
+
// Any native plugin may start a model turn without an SDK input. Keep its
|
|
1213
|
+
// observed output inside a valid turn even if its input is absent from
|
|
1214
|
+
// the SDK stream. An explicit Hub receipt alone establishes peer source.
|
|
1215
|
+
if (!this.turnOpen && (event.parentToolUseId ?? null) === null
|
|
1216
|
+
&& ["assistant_reasoning", "assistant_text", "assistant_message"].includes(event.type)) {
|
|
1217
|
+
this.openStartedHubTurn();
|
|
1218
|
+
if (!this.turnOpen)
|
|
1219
|
+
this.openNativeTurn();
|
|
1220
|
+
}
|
|
1167
1221
|
switch (event.type) {
|
|
1222
|
+
case "hub_turn_start": {
|
|
1223
|
+
const entry = this.pendingHubTurns.find(item => item.id === event.messageId);
|
|
1224
|
+
if (entry !== undefined) {
|
|
1225
|
+
entry.started = true;
|
|
1226
|
+
this.openStartedHubTurn();
|
|
1227
|
+
}
|
|
1228
|
+
break;
|
|
1229
|
+
}
|
|
1168
1230
|
case "session_init":
|
|
1169
1231
|
trace(`session_init model=${String(event.model)}`);
|
|
1170
1232
|
this.observeModelCatalog(event);
|
|
@@ -1242,6 +1304,7 @@ export class ClaudeAgent {
|
|
|
1242
1304
|
: { kind: "completed" });
|
|
1243
1305
|
this.markIdle();
|
|
1244
1306
|
this.endActivity();
|
|
1307
|
+
this.openStartedHubTurn();
|
|
1245
1308
|
break;
|
|
1246
1309
|
}
|
|
1247
1310
|
case "todo_write": {
|
|
@@ -67,6 +67,8 @@ import { loadClaudeQuery, resolveClaudeBinary } from "./sdk-runtime.js";
|
|
|
67
67
|
import { InputQueue } from "./input-queue.js";
|
|
68
68
|
import { projectClaudeEvent } from "./claude-events.js";
|
|
69
69
|
import { NATIVE_LAUNCH } from "./claude-launch.js";
|
|
70
|
+
import { resolveClaudeHome } from "./claude-home.js";
|
|
71
|
+
import { prepareClaudeNativePlugin } from "../../superd-hub/dist/foreign-claude.js";
|
|
70
72
|
/** The default executable resolution: Task 1's `resolveClaudeBinary` —
|
|
71
73
|
* `$CLAUDE_EXECUTABLE` (only while it exists) > `claude` on PATH >
|
|
72
74
|
* `~/.local/bin/claude` > `/usr/local/bin/claude`. `undefined` means "no
|
|
@@ -158,6 +160,10 @@ export class ClaudeSdkClient {
|
|
|
158
160
|
launch;
|
|
159
161
|
/** True while a user turn is in flight (set on push, cleared on `turn_end`/`agent_end`). */
|
|
160
162
|
turnActive = false;
|
|
163
|
+
hub;
|
|
164
|
+
prepareHub;
|
|
165
|
+
preparation;
|
|
166
|
+
hubSession;
|
|
161
167
|
queue;
|
|
162
168
|
query;
|
|
163
169
|
pendingControls = [];
|
|
@@ -177,6 +183,15 @@ export class ClaudeSdkClient {
|
|
|
177
183
|
this.canUseTool = config.canUseTool;
|
|
178
184
|
this.allowDangerouslySkipPermissions = config.allowDangerouslySkipPermissions;
|
|
179
185
|
this.launch = config.launch ?? NATIVE_LAUNCH;
|
|
186
|
+
this.hub = config.hub;
|
|
187
|
+
this.hub?.onTurnStarted(id => {
|
|
188
|
+
if (!this.closed) {
|
|
189
|
+
this.turnActive = true;
|
|
190
|
+
for (const listener of this.listeners)
|
|
191
|
+
listener({ type: "hub_turn_start", messageId: id });
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
this.prepareHub = config.prepareHub ?? (config.hub === undefined ? undefined : () => prepareClaudeNativePlugin(config.hub, resolveClaudeHome()));
|
|
180
195
|
refcount += 1;
|
|
181
196
|
}
|
|
182
197
|
/**
|
|
@@ -259,7 +274,13 @@ export class ClaudeSdkClient {
|
|
|
259
274
|
async ensureStarted() {
|
|
260
275
|
if (this.query !== undefined)
|
|
261
276
|
return;
|
|
277
|
+
this.ensureUsable();
|
|
278
|
+
if (this.prepareHub !== undefined) {
|
|
279
|
+
this.preparation ??= this.prepareHub().then(value => { this.hubSession = value; });
|
|
280
|
+
await this.preparation;
|
|
281
|
+
}
|
|
262
282
|
const f = await resolveFactory(this.worldHome); // await BEFORE the sync section
|
|
283
|
+
this.ensureUsable();
|
|
263
284
|
if (this.query !== undefined)
|
|
264
285
|
return; // lost the race to a concurrent first prompt — reuse its query
|
|
265
286
|
const queue = new InputQueue();
|
|
@@ -282,6 +303,22 @@ export class ClaudeSdkClient {
|
|
|
282
303
|
await this.ensureStarted();
|
|
283
304
|
this.push(content, undefined);
|
|
284
305
|
}
|
|
306
|
+
/** Peer messages enter the real Claude runtime through the native Mod inbox. */
|
|
307
|
+
async receiveHub(content) {
|
|
308
|
+
this.ensureUsable();
|
|
309
|
+
await this.ensureStarted();
|
|
310
|
+
if (this.hub === undefined || this.hubSession === undefined) {
|
|
311
|
+
// Standalone adapter has no Hub host and keeps its usual input channel.
|
|
312
|
+
this.push(content, undefined);
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
const text = typeof content === "string" ? content : content.map(block => block.type === "text" ? block.text : "").join("\n\n");
|
|
316
|
+
if (!text)
|
|
317
|
+
throw new Error("[E_HUB_INPUT] Hub messages require text");
|
|
318
|
+
const id = await this.hub.enqueue(text);
|
|
319
|
+
this.turnActive = true;
|
|
320
|
+
return id;
|
|
321
|
+
}
|
|
285
322
|
/** Surface parity with prompt: a plain queued user turn. */
|
|
286
323
|
async followUp(content) {
|
|
287
324
|
this.ensureUsable();
|
|
@@ -328,18 +365,23 @@ export class ClaudeSdkClient {
|
|
|
328
365
|
this.closed = true;
|
|
329
366
|
this.queue?.close();
|
|
330
367
|
this.query?.close();
|
|
368
|
+
this.hub?.dispose();
|
|
331
369
|
refcount = Math.max(0, refcount - 1);
|
|
332
370
|
}
|
|
333
371
|
// --- internals ------------------------------------------------------------
|
|
334
372
|
buildOptions() {
|
|
335
373
|
const launchExecutable = this.launch.pathToClaudeCodeExecutable;
|
|
336
374
|
const executable = launchExecutable !== undefined && launchExecutable !== "" ? launchExecutable : executableResolver();
|
|
375
|
+
const env = { ...process.env, ...this.launch.env };
|
|
376
|
+
delete env.SUPERD_HUB_SESSION;
|
|
377
|
+
if (this.hubSession !== undefined)
|
|
378
|
+
env.SUPERD_HUB_SESSION = this.hubSession;
|
|
337
379
|
return {
|
|
338
380
|
cwd: this.cwdPath,
|
|
339
381
|
// The launch env holds ADDITIONS only; spreading process.env first keeps
|
|
340
382
|
// PATH/HOME and the user's own key alive (NATIVE_LAUNCH carries an empty
|
|
341
383
|
// env, so the native shape is exactly `{ ...process.env }`).
|
|
342
|
-
env
|
|
384
|
+
env,
|
|
343
385
|
// The `--settings` command-line tier (a JSON string) outranks the user's
|
|
344
386
|
// native settings.json env, so a provider-pinned launch rides it here
|
|
345
387
|
// (2026-10-04 F-B: without it the native env pins the endpoint).
|
|
@@ -387,6 +429,9 @@ export class ClaudeSdkClient {
|
|
|
387
429
|
if (event.type === "session_init" && typeof event.sessionId === "string") {
|
|
388
430
|
this.threadIdValue = event.sessionId;
|
|
389
431
|
}
|
|
432
|
+
if ((event.parentToolUseId ?? null) === null
|
|
433
|
+
&& ["assistant_text", "assistant_reasoning", "assistant_message"].includes(event.type))
|
|
434
|
+
this.turnActive = true;
|
|
390
435
|
if (event.type === "turn_end")
|
|
391
436
|
this.turnActive = false;
|
|
392
437
|
this.emit(event);
|
|
@@ -27,6 +27,7 @@ import { ClaudeSdkClient, probeClaudeModels } from "./claude-client.js";
|
|
|
27
27
|
import { ClaudeAgent } from "./agent.js";
|
|
28
28
|
import { ClaudeLlmAdapter, CLAUDE_PROVIDER_ID, routeIds } from "./adapter.js";
|
|
29
29
|
import { resolveClaudeHome } from "./claude-home.js";
|
|
30
|
+
import { bindForeignHub } from "../../superd-hub/dist/foreign-hub.js";
|
|
30
31
|
import { resetProjectionState } from "./claude-events.js";
|
|
31
32
|
import { claudeSessionIdFromDsh } from "./session-id.js";
|
|
32
33
|
import { claudePermissionMode, defaultPermissionPreset, modeChangePatch, presetFromEvents, } from "./permission.js";
|
|
@@ -308,6 +309,7 @@ export class ClaudeProvider extends Service {
|
|
|
308
309
|
cwd: spawnCwd,
|
|
309
310
|
worldHome: this.home,
|
|
310
311
|
canUseTool,
|
|
312
|
+
hub: bindForeignHub("claude", String(id), this.home, () => agentRef.current),
|
|
311
313
|
allowDangerouslySkipPermissions: claudeMode === "bypassPermissions",
|
|
312
314
|
...overrides,
|
|
313
315
|
});
|
|
@@ -419,6 +421,7 @@ export class ClaudeProvider extends Service {
|
|
|
419
421
|
cwd: spawnCwd,
|
|
420
422
|
worldHome: this.home,
|
|
421
423
|
canUseTool,
|
|
424
|
+
hub: bindForeignHub("claude", String(id), this.home, () => agentRef.current),
|
|
422
425
|
allowDangerouslySkipPermissions: claudeMode === "bypassPermissions",
|
|
423
426
|
...overrides,
|
|
424
427
|
});
|
|
@@ -49,7 +49,7 @@ import { promisify } from "node:util";
|
|
|
49
49
|
/** The coding-agent SDK this line drives (a plain npm package, never declared). */
|
|
50
50
|
export const SDK_PACKAGE = "@anthropic-ai/claude-agent-sdk";
|
|
51
51
|
/** The pin, written here rather than in `package.json` — the marker carries it. */
|
|
52
|
-
export const SDK_PACKAGE_VERSION = "0.3.
|
|
52
|
+
export const SDK_PACKAGE_VERSION = "0.3.291";
|
|
53
53
|
export function sdkRuntimeLayout(worldHome) {
|
|
54
54
|
return {
|
|
55
55
|
dir: join(worldHome, "sdk-runtime"),
|
|
@@ -139,7 +139,7 @@ export function resolveClaudeBinary() {
|
|
|
139
139
|
export function sdkInstallArgv(version, mode) {
|
|
140
140
|
const argv = [
|
|
141
141
|
"install",
|
|
142
|
-
"--exact",
|
|
142
|
+
"--save-exact",
|
|
143
143
|
"--ignore-scripts",
|
|
144
144
|
"--omit=dev",
|
|
145
145
|
"--no-audit",
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ToolCallRecovery } from "@deepseek-ai/dsh-session";
|
|
1
2
|
import { agentEvents } from "@deepseek-ai/dsh-agent";
|
|
2
3
|
import { Inbox } from "./inbox.js";
|
|
3
4
|
import { canonicalHeader, headerEquals } from "@deepseek-ai/dsh-session";
|
|
@@ -267,6 +268,8 @@ export class CodexAgent {
|
|
|
267
268
|
#step = 0;
|
|
268
269
|
#lastTurn = 0;
|
|
269
270
|
#turnOpen = false;
|
|
271
|
+
#turnCommitted = false;
|
|
272
|
+
#stepOpen = false;
|
|
270
273
|
#cancelCause = null;
|
|
271
274
|
/** Terminal model failure of the open turn (`message_end` stopReason "error"); consumed by `agent_end`. */
|
|
272
275
|
#pendingFailure = null;
|
|
@@ -526,7 +529,7 @@ export class CodexAgent {
|
|
|
526
529
|
catch (error) {
|
|
527
530
|
trace(`#startTurn cold start failed: ${String(error)}`);
|
|
528
531
|
if (this.#turnOpen) {
|
|
529
|
-
this
|
|
532
|
+
this.#commitTurnStart();
|
|
530
533
|
this.session.append("user/message", message, { surfaceOp: "append" });
|
|
531
534
|
this.#closeTurn({ kind: "error", error: { message: String(error), code: "UNKNOWN" } });
|
|
532
535
|
}
|
|
@@ -535,7 +538,7 @@ export class CodexAgent {
|
|
|
535
538
|
}
|
|
536
539
|
if (this.#disposed)
|
|
537
540
|
return;
|
|
538
|
-
this
|
|
541
|
+
this.#commitTurnStart();
|
|
539
542
|
if (route !== undefined)
|
|
540
543
|
this.#commitRequestHeader(route);
|
|
541
544
|
this.#emitRequestContext(route);
|
|
@@ -812,7 +815,7 @@ export class CodexAgent {
|
|
|
812
815
|
return;
|
|
813
816
|
this.#reserveTurn();
|
|
814
817
|
this.#localUserPending = localUser;
|
|
815
|
-
this
|
|
818
|
+
this.#commitTurnStart();
|
|
816
819
|
// This path opens a turn for a message the CLIENT queued and is now
|
|
817
820
|
// generating, so the child is already running: resolve the route for the
|
|
818
821
|
// log's benefit only. It cannot have moved since the turn it belongs to
|
|
@@ -824,9 +827,32 @@ export class CodexAgent {
|
|
|
824
827
|
this.#emitRequestContext(route);
|
|
825
828
|
this.session.append("user/message", message, { surfaceOp: "append" });
|
|
826
829
|
}
|
|
830
|
+
#commitTurnStart() {
|
|
831
|
+
if (this.#turnCommitted)
|
|
832
|
+
return;
|
|
833
|
+
this.session.append("turn/start", { turn: this.#dashTurn });
|
|
834
|
+
this.#turnCommitted = true;
|
|
835
|
+
}
|
|
827
836
|
#closeTurn(reason) {
|
|
828
837
|
if (!this.#turnOpen)
|
|
829
838
|
return;
|
|
839
|
+
this.#commitTurnStart();
|
|
840
|
+
if (this.#stepOpen) {
|
|
841
|
+
const events = this.session.snapshotEvents();
|
|
842
|
+
const start = events.findLastIndex(event => event.type === "step/start");
|
|
843
|
+
const recovery = new ToolCallRecovery();
|
|
844
|
+
for (const event of events.slice(start))
|
|
845
|
+
recovery.observe(event);
|
|
846
|
+
for (const result of recovery.results()) {
|
|
847
|
+
this.session.append("tool/result", result.data, {
|
|
848
|
+
surfaceOp: "append",
|
|
849
|
+
...(result.sourceEventSeqs === undefined ? {} : { sourceEventSeqs: result.sourceEventSeqs }),
|
|
850
|
+
});
|
|
851
|
+
}
|
|
852
|
+
this.session.append("step/end", { turn: this.#dashTurn, step: this.#step });
|
|
853
|
+
this.#stepOpen = false;
|
|
854
|
+
}
|
|
855
|
+
this.#turnCommitted = false;
|
|
830
856
|
this.#turnOpen = false;
|
|
831
857
|
this.#localUserPending = false;
|
|
832
858
|
this.session.append("turn/end", { turn: this.#dashTurn, reason });
|
|
@@ -848,6 +874,7 @@ export class CodexAgent {
|
|
|
848
874
|
this.#stepStartPending = false;
|
|
849
875
|
this.#step += 1;
|
|
850
876
|
this.session.append("step/start", { turn: this.#dashTurn, step: this.#step });
|
|
877
|
+
this.#stepOpen = true;
|
|
851
878
|
trace(`step/start turn=${this.#dashTurn} step=${this.#step}`);
|
|
852
879
|
}
|
|
853
880
|
/**
|
|
@@ -986,10 +1013,6 @@ export class CodexAgent {
|
|
|
986
1013
|
trace("event turn_start");
|
|
987
1014
|
this.#stepStartPending = true;
|
|
988
1015
|
this.#pendingFailure = null;
|
|
989
|
-
// Open the step immediately (the SDK may never send message_start)
|
|
990
|
-
// and try the system prompt now that the thread's rollout exists.
|
|
991
|
-
this.#commitStepStart();
|
|
992
|
-
this.#emitSystemMessage();
|
|
993
1016
|
break;
|
|
994
1017
|
case "message_start": {
|
|
995
1018
|
const message = event.message;
|
|
@@ -1046,6 +1069,7 @@ export class CodexAgent {
|
|
|
1046
1069
|
break;
|
|
1047
1070
|
}
|
|
1048
1071
|
case "tool_execution_start": {
|
|
1072
|
+
this.#flushDeferredAssistant();
|
|
1049
1073
|
const callId = String(event.toolCallId ?? "");
|
|
1050
1074
|
const name = String(event.toolName ?? "");
|
|
1051
1075
|
const argumentsJson = String(event.argumentsJson ?? JSON.stringify(event.args ?? {}));
|
|
@@ -1112,8 +1136,9 @@ export class CodexAgent {
|
|
|
1112
1136
|
// DSH turn-token fold requires the sample on a message of the SAME
|
|
1113
1137
|
// turn. Flushed inside the still-open step, BEFORE step/end.
|
|
1114
1138
|
this.#flushDeferredAssistant(convertUsage(event.data?.usage));
|
|
1115
|
-
if (this.#turnOpen && this.#
|
|
1139
|
+
if (this.#turnOpen && this.#stepOpen) {
|
|
1116
1140
|
this.session.append("step/end", { turn: this.#dashTurn, step: this.#step });
|
|
1141
|
+
this.#stepOpen = false;
|
|
1117
1142
|
}
|
|
1118
1143
|
break;
|
|
1119
1144
|
case "agent_end": {
|
|
@@ -406,6 +406,7 @@ export class CodexAppServerClient {
|
|
|
406
406
|
if (this.#closed)
|
|
407
407
|
return;
|
|
408
408
|
this.#closed = true;
|
|
409
|
+
this.#options.hub?.dispose();
|
|
409
410
|
this.#sessionEpoch += 1;
|
|
410
411
|
this.#transport?.close();
|
|
411
412
|
this.#transport = undefined;
|
|
@@ -449,6 +450,7 @@ export class CodexAppServerClient {
|
|
|
449
450
|
transport = transportFactory({
|
|
450
451
|
...(this.#options.cwd === undefined ? {} : { cwd: this.#options.cwd }),
|
|
451
452
|
...(this.#options.requestTimeoutMs === undefined ? {} : { requestTimeoutMs: this.#options.requestTimeoutMs }),
|
|
453
|
+
...(this.#options.worldHome === undefined ? {} : { worldHome: this.#options.worldHome }),
|
|
452
454
|
args: ["app-server", ...launch.argv],
|
|
453
455
|
env: childEnv,
|
|
454
456
|
onNotification: (notification) => this.#onNotification(notification),
|
|
@@ -462,7 +464,7 @@ export class CodexAppServerClient {
|
|
|
462
464
|
}
|
|
463
465
|
this.#transport = transport;
|
|
464
466
|
if (first) {
|
|
465
|
-
transport.start();
|
|
467
|
+
await transport.start();
|
|
466
468
|
const info = asRecord(await transport.request("initialize", {
|
|
467
469
|
clientInfo: { name: "superd-agent-codex", title: "Super D agent-codex", version: "0.1.0" },
|
|
468
470
|
capabilities: { experimentalApi: true, requestAttestation: false },
|
|
@@ -488,7 +490,13 @@ export class CodexAppServerClient {
|
|
|
488
490
|
...(this.#options.sandboxMode === undefined ? {} : { sandbox: this.#options.sandboxMode }),
|
|
489
491
|
};
|
|
490
492
|
const response = asRecord(resumeId === null
|
|
491
|
-
? await transport.request("thread/start",
|
|
493
|
+
? await transport.request("thread/start", {
|
|
494
|
+
...shared,
|
|
495
|
+
...(() => {
|
|
496
|
+
const definition = this.#options.hub?.definition();
|
|
497
|
+
return definition === undefined ? {} : { dynamicTools: [{ type: 'function', ...definition }] };
|
|
498
|
+
})(),
|
|
499
|
+
})
|
|
492
500
|
: await transport.request("thread/resume", { threadId: resumeId, ...shared }));
|
|
493
501
|
const thread = asRecord(response?.["thread"]);
|
|
494
502
|
const threadId = asString(thread?.["id"]) ?? resumeId;
|
|
@@ -658,7 +666,22 @@ export class CodexAppServerClient {
|
|
|
658
666
|
// closes the just-finished Dash turn and opens a fresh one.
|
|
659
667
|
void this.#startTurn(next).catch((error) => this.#fail(error));
|
|
660
668
|
}
|
|
661
|
-
#onServerRequest(request) {
|
|
669
|
+
async #onServerRequest(request) {
|
|
670
|
+
if (request.method === 'item/tool/call') {
|
|
671
|
+
const params = asRecord(request.params);
|
|
672
|
+
try {
|
|
673
|
+
const hub = this.#options.hub;
|
|
674
|
+
if (hub === undefined || params?.tool !== 'superd-hub' ||
|
|
675
|
+
params.threadId !== this.#threadId || (params.namespace !== undefined && params.namespace !== null)) {
|
|
676
|
+
throw new Error('[E_HUB_BINDING] Tool call does not belong to this Super D session; reconnect through Super D');
|
|
677
|
+
}
|
|
678
|
+
const result = await hub.execute(params.arguments);
|
|
679
|
+
return { result: { success: true, contentItems: [{ type: 'inputText', text: JSON.stringify(result) }] } };
|
|
680
|
+
}
|
|
681
|
+
catch (error) {
|
|
682
|
+
return { result: { success: false, contentItems: [{ type: 'inputText', text: error instanceof Error ? error.message : String(error) }] } };
|
|
683
|
+
}
|
|
684
|
+
}
|
|
662
685
|
const handler = this.#serverRequestHandler;
|
|
663
686
|
if (handler === undefined)
|
|
664
687
|
return failClosedResponse(request.method);
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Narrow hand-written types for the codex `app-server` JSONL JSON-RPC wire.
|
|
3
3
|
*
|
|
4
|
-
* DERIVED FROM: the `codex app-server generate-ts --out <dir>` output for
|
|
5
|
-
* `@openai/codex`
|
|
4
|
+
* DERIVED FROM: the `codex app-server generate-ts --out <dir>` output for
|
|
5
|
+
* `@openai/codex@0.154.0` (the SDK line's tested pair). The app-server line's
|
|
6
|
+
* runtime target is the exact-pin `@openai/codex@0.160.0`, lazily fetched into
|
|
7
|
+
* the world home (codex-runtime-fetch.ts) — never an install-time dependency.
|
|
8
|
+
* That tree is ~617 files;
|
|
9
|
+
|
|
6
10
|
* this module is a hand-written, deliberately narrow subset covering exactly
|
|
7
11
|
* the envelopes, requests, notifications and item shapes
|
|
8
12
|
* `src/app-server-client.ts` and `src/app-server-events.ts` consume. Each
|
|
@@ -7,7 +7,10 @@
|
|
|
7
7
|
* `{"id":…,"result":…}` / `{"id":…,"error":{…}}`).
|
|
8
8
|
*
|
|
9
9
|
* This module owns exactly four things and nothing else:
|
|
10
|
-
* 1. runtime selection (override →
|
|
10
|
+
* 1. runtime selection (override → PATH CLI → desktop core → fetched world-home
|
|
11
|
+
* runtime — codex-runtime-fetch.ts; the pinned package is NEVER an
|
|
12
|
+
* install-time dependency, 2026-10-06 ruling),
|
|
13
|
+
|
|
11
14
|
* 2. process spawn + line framing + per-id request/response correlation,
|
|
12
15
|
* 3. dispatch of server-initiated notifications and server-requests,
|
|
13
16
|
* 4. teardown (process GROUP kill — the app-server may spawn stdio MCP
|
|
@@ -22,6 +25,7 @@
|
|
|
22
25
|
* process, and the client calls it on first real work.
|
|
23
26
|
*/
|
|
24
27
|
import { spawn } from "node:child_process";
|
|
28
|
+
import { createCodexRuntimeDeps, ensureCodexRuntime } from "./codex-runtime-fetch.js";
|
|
25
29
|
import { resolveCodexRuntime } from "./codex-runtime.js";
|
|
26
30
|
export { resolveCodexCommand, resolveCodexRuntime } from "./codex-runtime.js";
|
|
27
31
|
/** Per-request protocol error surfaced by the server (JSON-RPC `error` object). */
|
|
@@ -69,15 +73,14 @@ export class CodexAppServerTransport {
|
|
|
69
73
|
}
|
|
70
74
|
/**
|
|
71
75
|
* Spawn the child exactly once. Idempotent.
|
|
72
|
-
* @throws when
|
|
76
|
+
* @throws when no runtime can be discovered or fetched, or the child cannot
|
|
77
|
+
* be spawned at all (bad executable).
|
|
73
78
|
*/
|
|
74
|
-
start() {
|
|
79
|
+
async start() {
|
|
75
80
|
if (this.#child !== undefined)
|
|
76
81
|
return;
|
|
77
82
|
const env = this.#options.env ?? { ...process.env };
|
|
78
|
-
const runtime =
|
|
79
|
-
env: this.#options.command === undefined ? env : { ...env, CODEX_BIN: this.#options.command },
|
|
80
|
-
});
|
|
83
|
+
const runtime = await this.#resolveRuntime(env);
|
|
81
84
|
const args = [...(this.#options.args ?? ["app-server"])];
|
|
82
85
|
// `detached` gives the child its own process group so teardown can kill
|
|
83
86
|
// the whole tree (stdio MCP descendants included), not just the root.
|
|
@@ -99,6 +102,20 @@ export class CodexAppServerTransport {
|
|
|
99
102
|
child.on("error", (error) => this.#onExit(null, null, error));
|
|
100
103
|
child.on("exit", (code, signal) => this.#onExit(code, signal));
|
|
101
104
|
}
|
|
105
|
+
/**
|
|
106
|
+
|
|
107
|
+
* Resolve the child's runtime. An explicit `command` and world-home-less
|
|
108
|
+
* calls stay on the pure sync ladder; only a world home arms the lazy fetch
|
|
109
|
+
* of the pinned package (2026-10-06 ruling — never an install-time dep).
|
|
110
|
+
*/
|
|
111
|
+
async #resolveRuntime(env) {
|
|
112
|
+
if (this.#options.command !== undefined || this.#options.worldHome === undefined) {
|
|
113
|
+
return resolveCodexRuntime({
|
|
114
|
+
env: this.#options.command === undefined ? env : { ...env, CODEX_BIN: this.#options.command },
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
return ensureCodexRuntime(this.#options.worldHome, createCodexRuntimeDeps({ env }));
|
|
118
|
+
}
|
|
102
119
|
/** Send a request and await its result. Rejects on protocol error, timeout, or EOF. */
|
|
103
120
|
request(method, params, options = {}) {
|
|
104
121
|
if (this.#closed || this.#child === undefined) {
|
|
@@ -127,7 +127,7 @@ export function mergeModelCatalog(existing, absorbed, models, template) {
|
|
|
127
127
|
if (seen.has(model.id))
|
|
128
128
|
continue;
|
|
129
129
|
seen.add(model.id);
|
|
130
|
-
const base = bySlug.get(model.id)
|
|
130
|
+
const base = { ...fallback, ...bySlug.get(model.id) };
|
|
131
131
|
const entry = {
|
|
132
132
|
...base,
|
|
133
133
|
slug: model.id,
|
|
@@ -138,6 +138,10 @@ export function mergeModelCatalog(existing, absorbed, models, template) {
|
|
|
138
138
|
// carry verified hashes; remove stale generated values too. Context-limit
|
|
139
139
|
// compaction and its window/threshold metadata remain unchanged.
|
|
140
140
|
delete entry["comp_hash"];
|
|
141
|
+
// OpenAI's code-mode-only catalog entry cannot establish another
|
|
142
|
+
// endpoint's tool protocol. Omit it so Codex uses its normal tool mode.
|
|
143
|
+
// Also repair stale provider entries previously cloned from that cache.
|
|
144
|
+
delete entry["tool_mode"];
|
|
141
145
|
if (model.contextWindow !== undefined) {
|
|
142
146
|
// Codex reads context_window and caps against max_context_window; a stale
|
|
143
147
|
// template value must not silently cap a larger real window.
|
|
@@ -359,6 +363,7 @@ export function catalogTemplate(codexHome, worldHome) {
|
|
|
359
363
|
readIfExists(providerCatalogPath(worldHome, "superd-model-catalog")),
|
|
360
364
|
absorbedPath === undefined ? undefined : readIfExists(absorbedPath),
|
|
361
365
|
readIfExists(join(codexHome, SUPERD_CATALOG_FILE)),
|
|
366
|
+
readIfExists(join(codexHome, "models_cache.json")),
|
|
362
367
|
];
|
|
363
368
|
for (const text of sources) {
|
|
364
369
|
const first = catalogEntries(text)[0];
|