super-dsh 0.1.11-a → 0.1.11-b
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 +34 -1
- package/agent-agy/dist/agent.d.ts +2 -0
- package/agent-agy/dist/agent.js +14 -0
- package/agent-agy/dist/agy-cli-client.d.ts +9 -0
- package/agent-agy/dist/agy-cli-client.js +56 -8
- 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 +24 -2
- package/agent-codex/dist/codex-native-config.js +6 -1
- package/agent-codex/dist/index.js +6 -1
- package/agent-hermes/dist/hermes-client.js +44 -5
- package/agent-hermes/dist/index.js +6 -2
- package/agent-omp/dist/agent.js +36 -5
- package/agent-omp/dist/index.js +23 -3
- 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/package.json +2 -2
- package/superd-hub/dist/a2a-row.js +20 -3
- 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/package.json +9 -1
package/README.md
CHANGED
|
@@ -45,12 +45,45 @@ and a LAN relay. The test launcher enables this mode without `--host` or
|
|
|
45
45
|
| Label | Runtime | Integration |
|
|
46
46
|
|---|---|---|
|
|
47
47
|
| OMP | OMP (`~/.omp`) | host `omp` binary over RPC (`omp --mode rpc`, NDJSON v1), lazy per session |
|
|
48
|
-
| Codex | Codex app-server (`~/.codex`) |
|
|
48
|
+
| Codex | Codex app-server (`~/.codex`) | CLI first, desktop core and lazy world-home fetch fallback |
|
|
49
49
|
| Claude | Claude Code (`~/.claude`) | standalone adapter, joined by the hub |
|
|
50
50
|
| Pi | pi coding agent (`~/.pi`) | in-process SDK |
|
|
51
51
|
| Hermes | Hermes (`~/.hermes`) | gateway child |
|
|
52
52
|
| Antigravity | agy SDK | Python bridge (`bridge/agy_bridge.py`) |
|
|
53
53
|
|
|
54
|
+
### Foreign-agent Hub facility
|
|
55
|
+
|
|
56
|
+
Super D sessions in Pi, OMP, and Codex can use the same `superd-hub` facility
|
|
57
|
+
for agent roster/status, messaging, and shell execution. Tool descriptions,
|
|
58
|
+
argument validation, receipts, and reply guidance come from the existing Hub.
|
|
59
|
+
Replies wake the initiating session automatically; do not poll for them.
|
|
60
|
+
|
|
61
|
+
Pi and OMP share one native extension. It is loaded incrementally on the first
|
|
62
|
+
prompt, preserving normal extension discovery and native tools. Its per-session
|
|
63
|
+
wrapper lives under the Super D world home (`hub-extensions`, mode 0600), with a
|
|
64
|
+
session capability for a loopback-only HTTP listener. No global extension is
|
|
65
|
+
installed in `~/.pi` or `~/.omp`. Some OMP versions present registered extension
|
|
66
|
+
tools as devices: read `xd://superd-hub` for instructions and write action JSON
|
|
67
|
+
to that device.
|
|
68
|
+
|
|
69
|
+
Codex uses the official experimental app-server `dynamicTools` interface on
|
|
70
|
+
new threads. Callbacks bind to that thread and the Super D session; existing
|
|
71
|
+
native tools/plugins remain available. No global plugin is installed in
|
|
72
|
+
`~/.codex`. Resuming a thread created through this integration restores its
|
|
73
|
+
saved tool definition. Older threads without that definition need a new
|
|
74
|
+
Super D conversation to gain the facility. A native Codex client resuming one
|
|
75
|
+
of these threads does not supply Super D's callback host.
|
|
76
|
+
|
|
77
|
+
Pi/OMP extension closures cannot transparently renew an already-loaded listener
|
|
78
|
+
capability when the Hub row is unmounted/remounted. Restart Super D and resume
|
|
79
|
+
the conversation after that operation; the tool reports this recovery path
|
|
80
|
+
when its connection becomes unavailable. Whole-app restart/resume is tested.
|
|
81
|
+
|
|
82
|
+
Draft creation and passive history viewing create no extension file, listener,
|
|
83
|
+
or agent process. Closing a session invalidates its capability. Interrupted
|
|
84
|
+
tool operations retain conservative upstream recovery guidance about unknown
|
|
85
|
+
outcomes rather than encouraging blind retries.
|
|
86
|
+
|
|
54
87
|
### Codex in 0.1.11
|
|
55
88
|
|
|
56
89
|
Codex discovers the local OpenAI OAuth login as a read-only provider in the
|
|
@@ -35,6 +35,7 @@ import { type WithSystemHead } from "../../superd-hub/dist/index.js";
|
|
|
35
35
|
/** The CLI stream transport the agent drives (SDK bridge moved to agent-agy-sdk). */
|
|
36
36
|
export type LiveAgyClient = Pick<AgyCliClient, "conversationId" | "lastUsage" | "onThinkingDelta" | "onToolEvent" | "setApprovalHandler" | "turn" | "close"> & {
|
|
37
37
|
setSystemMessageHandler?(handler: (text: string) => void): void;
|
|
38
|
+
interrupt?: () => Promise<void>;
|
|
38
39
|
};
|
|
39
40
|
/** Classify a bridge turn failure into the stable {message, code} UI chip. */
|
|
40
41
|
export declare function classifyTurnError(error: unknown): {
|
|
@@ -119,6 +120,7 @@ export declare class AgyAgent implements Agent {
|
|
|
119
120
|
/** Flush ONE parked follow-up as a fresh prompt (never submit while busy). */
|
|
120
121
|
private flushFollowUps;
|
|
121
122
|
private pendingThinking;
|
|
123
|
+
private stopInFlight?;
|
|
122
124
|
/** Calls from this turn still lacking a result (SDK 0.1.17 exposes none). */
|
|
123
125
|
private unpairedCalls;
|
|
124
126
|
private armTurnDeadline;
|
package/agent-agy/dist/agent.js
CHANGED
|
@@ -293,6 +293,8 @@ export class AgyAgent {
|
|
|
293
293
|
this.ensureStreamBridge().push({ type: "text-delta", index: full.length, text: chunk });
|
|
294
294
|
full += chunk;
|
|
295
295
|
}
|
|
296
|
+
await this.stopInFlight;
|
|
297
|
+
this.stopInFlight = undefined;
|
|
296
298
|
this.clearTurnDeadline();
|
|
297
299
|
// SDK 0.1.17 streams ToolCall but NEVER ToolResult (verified live and
|
|
298
300
|
// in SDK source: receive_chunks yields Thought/Text/ToolCall only; the
|
|
@@ -327,6 +329,8 @@ export class AgyAgent {
|
|
|
327
329
|
this.flushFollowUps();
|
|
328
330
|
}
|
|
329
331
|
catch (error) {
|
|
332
|
+
await this.stopInFlight;
|
|
333
|
+
this.stopInFlight = undefined;
|
|
330
334
|
this.clearTurnDeadline();
|
|
331
335
|
this.abandonStreamBridge();
|
|
332
336
|
const failure = classifyTurnError(error);
|
|
@@ -345,8 +349,10 @@ export class AgyAgent {
|
|
|
345
349
|
if (this.turnOpen && this.step > 0)
|
|
346
350
|
this.closeStep();
|
|
347
351
|
this.closeTurn({ kind: "error", error: failure });
|
|
352
|
+
this.cancelCause = null;
|
|
348
353
|
this.markIdle();
|
|
349
354
|
this.endActivity();
|
|
355
|
+
this.flushFollowUps();
|
|
350
356
|
}
|
|
351
357
|
}
|
|
352
358
|
appendSystemNotice(text) {
|
|
@@ -452,6 +458,7 @@ export class AgyAgent {
|
|
|
452
458
|
this.deliverPrompt(entry.message);
|
|
453
459
|
}
|
|
454
460
|
pendingThinking = [];
|
|
461
|
+
stopInFlight;
|
|
455
462
|
/** Calls from this turn still lacking a result (SDK 0.1.17 exposes none). */
|
|
456
463
|
unpairedCalls = [];
|
|
457
464
|
armTurnDeadline() {
|
|
@@ -462,6 +469,13 @@ export class AgyAgent {
|
|
|
462
469
|
if (!this.turnOpen)
|
|
463
470
|
return;
|
|
464
471
|
trace(`turn deadline exceeded after ${AGY_TURN_DEADLINE_MS}ms — failing open turn`);
|
|
472
|
+
if (this.client.interrupt !== undefined) {
|
|
473
|
+
// Keep ownership until the old CLI stops. Its catch pairs unresolved
|
|
474
|
+
// calls and only then starts the next queued message.
|
|
475
|
+
this.cancelCause = { kind: "hook", reason: `agy turn deadline exceeded after ${AGY_TURN_DEADLINE_MS}ms` };
|
|
476
|
+
this.stopInFlight = this.client.interrupt().catch(error => trace(`deadline interrupt failed: ${String(error)}`));
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
465
479
|
this.abandonStreamBridge();
|
|
466
480
|
if (this.step > 0)
|
|
467
481
|
this.closeStep();
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
*
|
|
16
16
|
* Same surface as AgyBridgeClient so AgyAgent needs no changes.
|
|
17
17
|
*/
|
|
18
|
+
import type { ForeignHubBinding } from "@pgmi-builds/superd-hub/foreign-hub";
|
|
18
19
|
import { type AgyAuthMode, type AgySettingsMutation } from "./agy-auth.js";
|
|
19
20
|
export interface AgyCliOptions {
|
|
20
21
|
/** Key or a resolver (onboarding-saved keys become visible at first spawn). */
|
|
@@ -49,6 +50,9 @@ export interface AgyCliOptions {
|
|
|
49
50
|
authMode?: AgyAuthMode;
|
|
50
51
|
spawnAttempts?: number;
|
|
51
52
|
trace?: (...parts: unknown[]) => void;
|
|
53
|
+
hubBinding?: ForeignHubBinding;
|
|
54
|
+
hubStagingDirectory?: string;
|
|
55
|
+
prepareHub?: () => Promise<string | undefined>;
|
|
52
56
|
}
|
|
53
57
|
export type CliEvent = {
|
|
54
58
|
event: "ready";
|
|
@@ -110,6 +114,9 @@ export declare class AgyCliClient {
|
|
|
110
114
|
private dead;
|
|
111
115
|
private conversationIdValue;
|
|
112
116
|
private toolsSeen;
|
|
117
|
+
private hubPreparation?;
|
|
118
|
+
private hubSession?;
|
|
119
|
+
private prepare;
|
|
113
120
|
onThinkingDelta?: (text: string) => void;
|
|
114
121
|
onToolEvent?: (ev: {
|
|
115
122
|
event: "tool_call";
|
|
@@ -163,5 +170,7 @@ export declare class AgyCliClient {
|
|
|
163
170
|
private readyReceived;
|
|
164
171
|
private expectReady;
|
|
165
172
|
ping(): Promise<void>;
|
|
173
|
+
/** Stop one CLI execution, preserving its conversation and Hub capability. */
|
|
174
|
+
interrupt(): Promise<void>;
|
|
166
175
|
close(): Promise<void>;
|
|
167
176
|
}
|
|
@@ -16,6 +16,9 @@
|
|
|
16
16
|
* Same surface as AgyBridgeClient so AgyAgent needs no changes.
|
|
17
17
|
*/
|
|
18
18
|
import { spawn } from "node:child_process";
|
|
19
|
+
import { execFile } from "node:child_process";
|
|
20
|
+
import { promisify } from "node:util";
|
|
21
|
+
import { prepareAgyNativePlugin } from "@pgmi-builds/superd-hub/foreign-agy";
|
|
19
22
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
20
23
|
import { join } from "node:path";
|
|
21
24
|
import { authChildEnv, authSettingsMutation, nativeSettingsPath, } from "./agy-auth.js";
|
|
@@ -63,6 +66,24 @@ export class AgyCliClient {
|
|
|
63
66
|
dead = false;
|
|
64
67
|
conversationIdValue = "";
|
|
65
68
|
toolsSeen = 0;
|
|
69
|
+
hubPreparation;
|
|
70
|
+
hubSession;
|
|
71
|
+
async prepare() {
|
|
72
|
+
if (this.dead)
|
|
73
|
+
throw new Error("agy client is closed");
|
|
74
|
+
this.hubPreparation ??= (async () => {
|
|
75
|
+
if (this.opts.hubBinding === undefined)
|
|
76
|
+
return;
|
|
77
|
+
this.hubSession = await (this.opts.prepareHub?.() ?? prepareAgyNativePlugin(this.opts.hubBinding, join(this.realHome(), ".gemini", "antigravity-cli"), this.opts.hubStagingDirectory, async (directory) => {
|
|
78
|
+
const exec = promisify(execFile);
|
|
79
|
+
await exec(this.opts.agyBin ?? "agy", ["plugin", "install", directory], { timeout: 60_000 });
|
|
80
|
+
await exec(this.opts.agyBin ?? "agy", ["plugin", "enable", "superd-hub"], { timeout: 60_000 });
|
|
81
|
+
}));
|
|
82
|
+
})();
|
|
83
|
+
await this.hubPreparation;
|
|
84
|
+
if (this.dead)
|
|
85
|
+
throw new Error("agy client is closed");
|
|
86
|
+
}
|
|
66
87
|
onThinkingDelta;
|
|
67
88
|
onToolEvent;
|
|
68
89
|
setApprovalHandler(_handler) {
|
|
@@ -90,6 +111,8 @@ export class AgyCliClient {
|
|
|
90
111
|
spawnCli() {
|
|
91
112
|
if (this.child || this.dead)
|
|
92
113
|
return;
|
|
114
|
+
this.pending.length = 0;
|
|
115
|
+
this.buffer = "";
|
|
93
116
|
const mode = this.opts.authMode ?? "apiKey";
|
|
94
117
|
// Only key mode patches settings (`modelProvider: "gemini"`); ADC and OAuth
|
|
95
118
|
// need the DEFAULT backend, where that key must be ABSENT — the binary
|
|
@@ -107,8 +130,9 @@ export class AgyCliClient {
|
|
|
107
130
|
"--input-format=stream-json", "--output-format=stream-json",
|
|
108
131
|
"--disable-slash-commands",
|
|
109
132
|
];
|
|
110
|
-
|
|
111
|
-
|
|
133
|
+
const conversationId = this.conversationIdValue || this.opts.conversationId;
|
|
134
|
+
if (conversationId)
|
|
135
|
+
args.push(`--conversation=${conversationId}`);
|
|
112
136
|
if (this.opts.model)
|
|
113
137
|
args.push(`--model=${this.opts.model}`);
|
|
114
138
|
if (this.opts.effort)
|
|
@@ -127,6 +151,9 @@ export class AgyCliClient {
|
|
|
127
151
|
// ADC, the key for key mode, and the auto-update pin on EVERY mode.
|
|
128
152
|
// NO_PROXY keeps the dev3 loopback bridge reachable.
|
|
129
153
|
const env = { ...process.env, NO_PROXY: "127.0.0.1,localhost", ...authChildEnv(mode, key) };
|
|
154
|
+
delete env.SUPERD_HUB_SESSION;
|
|
155
|
+
if (this.hubSession !== undefined)
|
|
156
|
+
env.SUPERD_HUB_SESSION = this.hubSession;
|
|
130
157
|
// No HOME override, ever: the settings we just maintained live at
|
|
131
158
|
// `$HOME/.gemini`, ADC at `$HOME/.config/gcloud`, and the sign-in token
|
|
132
159
|
// in the keyring — isolating HOME destroys all three. That was the
|
|
@@ -155,6 +182,8 @@ export class AgyCliClient {
|
|
|
155
182
|
});
|
|
156
183
|
child.on("exit", (code) => {
|
|
157
184
|
this.child = undefined;
|
|
185
|
+
this.readyReceived = false;
|
|
186
|
+
this.buffer = "";
|
|
158
187
|
// The CLI reports its refusals on stderr / as non-JSON stdout lines and
|
|
159
188
|
// then exits non-zero. Those lines ARE the diagnosis ("--effort is not
|
|
160
189
|
// supported for model …", "Eligibility check failed: …"), so the exit
|
|
@@ -206,6 +235,8 @@ export class AgyCliClient {
|
|
|
206
235
|
continue;
|
|
207
236
|
}
|
|
208
237
|
if (parsed.event === "init") {
|
|
238
|
+
if (typeof parsed.conversation_id === "string")
|
|
239
|
+
this.conversationIdValue = parsed.conversation_id;
|
|
209
240
|
const init = parsed.init;
|
|
210
241
|
this.toolsSeen = Array.isArray(init?.tools) ? init.tools.length : 0;
|
|
211
242
|
this.dispatch({ event: "ready", tools: this.toolsSeen, permissionMode: init?.permission_mode });
|
|
@@ -262,7 +293,6 @@ export class AgyCliClient {
|
|
|
262
293
|
event: "tool_call", id, name,
|
|
263
294
|
args: JSON.stringify(su.tool_info?.parameters ?? {}),
|
|
264
295
|
});
|
|
265
|
-
this.onToolEvent?.({ event: "tool_call", id, name, args: JSON.stringify(su.tool_info?.parameters ?? {}) });
|
|
266
296
|
}
|
|
267
297
|
else if (su.state === "DONE" || su.state === "ERROR") {
|
|
268
298
|
const output = su.tool_info?.output ?? "";
|
|
@@ -271,10 +301,6 @@ export class AgyCliClient {
|
|
|
271
301
|
event: "tool_result", id, name, is_error: isError,
|
|
272
302
|
result: output || (isError ? "tool failed" : "[agy CLI reported no output summary]"),
|
|
273
303
|
});
|
|
274
|
-
this.onToolEvent?.({
|
|
275
|
-
event: "tool_result", id, name, is_error: isError,
|
|
276
|
-
result: output || (isError ? "tool failed" : "[agy CLI reported no output summary]"),
|
|
277
|
-
});
|
|
278
304
|
}
|
|
279
305
|
}
|
|
280
306
|
}
|
|
@@ -298,6 +324,7 @@ export class AgyCliClient {
|
|
|
298
324
|
}
|
|
299
325
|
/** One turn. First call spawns the CLI (lazy per §14-i) and keeps it resident. */
|
|
300
326
|
async *turn(text) {
|
|
327
|
+
await this.prepare();
|
|
301
328
|
this.spawnCli();
|
|
302
329
|
await this.expectReady();
|
|
303
330
|
this.send({ event: "user", message: { content: text } });
|
|
@@ -343,13 +370,34 @@ export class AgyCliClient {
|
|
|
343
370
|
}
|
|
344
371
|
}
|
|
345
372
|
async ping() {
|
|
373
|
+
await this.prepare();
|
|
346
374
|
this.spawnCli();
|
|
347
375
|
await this.expectReady();
|
|
348
376
|
}
|
|
377
|
+
/** Stop one CLI execution, preserving its conversation and Hub capability. */
|
|
378
|
+
async interrupt() {
|
|
379
|
+
const child = this.child;
|
|
380
|
+
if (child === undefined)
|
|
381
|
+
return;
|
|
382
|
+
this.readyReceived = false;
|
|
383
|
+
const exited = new Promise(resolve => child.once("exit", () => resolve()));
|
|
384
|
+
child.kill("SIGTERM");
|
|
385
|
+
const timer = setTimeout(() => child.kill("SIGKILL"), 2000);
|
|
386
|
+
timer.unref();
|
|
387
|
+
try {
|
|
388
|
+
await exited;
|
|
389
|
+
}
|
|
390
|
+
finally {
|
|
391
|
+
clearTimeout(timer);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
349
394
|
async close() {
|
|
350
|
-
if (
|
|
395
|
+
if (this.dead)
|
|
351
396
|
return;
|
|
352
397
|
this.dead = true;
|
|
398
|
+
await this.opts.hubBinding?.dispose();
|
|
399
|
+
if (!this.child)
|
|
400
|
+
return;
|
|
353
401
|
const child = this.child;
|
|
354
402
|
try {
|
|
355
403
|
child.stdin.end();
|
package/agent-agy/dist/index.js
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
* `sessionPersistence` service (hermes create/resume transaction shape).
|
|
19
19
|
*/
|
|
20
20
|
import { realpathSync, statSync } from "node:fs";
|
|
21
|
+
import { bindForeignHub } from "@pgmi-builds/superd-hub/foreign-hub";
|
|
21
22
|
import { join } from "node:path";
|
|
22
23
|
import { Service } from "@deepseek-ai/cordis";
|
|
23
24
|
import { installModelSelection } from "@deepseek-ai/dsh-agent";
|
|
@@ -149,6 +150,8 @@ export class AgyProvider extends Service {
|
|
|
149
150
|
hasAuthEvidence() {
|
|
150
151
|
if (this.resolveApiKey() !== undefined)
|
|
151
152
|
return true;
|
|
153
|
+
if (hasSigninCredential(this.opts.realHome ?? process.env.HOME ?? ""))
|
|
154
|
+
return true;
|
|
152
155
|
return hasAdcEvidence(process.env, process.env.HOME ?? "");
|
|
153
156
|
}
|
|
154
157
|
/** Default model id: adapter state → native TUI default (slug-normalized) → catalog head. */
|
|
@@ -260,7 +263,8 @@ export class AgyProvider extends Service {
|
|
|
260
263
|
trace(`create id=${id} spawnCwd=${spawnCwd}`);
|
|
261
264
|
// Full-lazy (§14-i): construct the client (spawns NOTHING); the bridge
|
|
262
265
|
// child materializes on the first real prompt.
|
|
263
|
-
const
|
|
266
|
+
const agentRef = {};
|
|
267
|
+
const client = this.createLiveClient(spawnCwd, agentOptionsModel(options.agentOptions), options.agentOptions?.reasoningEffort, bindForeignHub("agy", id, this.home, () => agentRef.current));
|
|
264
268
|
const preparation = SessionPreparation.create(loopCtx.sessions.prepare(id, {
|
|
265
269
|
...(options.seed === undefined ? {} : { seed: options.seed }),
|
|
266
270
|
...(meta === undefined ? {} : { meta }),
|
|
@@ -273,7 +277,7 @@ export class AgyProvider extends Service {
|
|
|
273
277
|
upsertSession(this.home, String(id), { conversationId: null, cwd: spawnCwd, createdAt: Date.now(), preset: null });
|
|
274
278
|
handedOff = true;
|
|
275
279
|
const agentOptions = await this.sessionAgentOptions(options.agentOptions);
|
|
276
|
-
return await setupAndPublish(loopCtx, ownerCtx, id, agentOptions, options.setup, preparation.session, client, options.parentAgent, "startup", { enterSession: true, preparation }, stored, this.agentRuntime(spawnCwd), () => this.followConversationId(id, client));
|
|
280
|
+
return await setupAndPublish(loopCtx, ownerCtx, id, agentOptions, options.setup, preparation.session, client, options.parentAgent, "startup", { enterSession: true, preparation }, stored, this.agentRuntime(spawnCwd), () => this.followConversationId(id, client), agentRef);
|
|
277
281
|
}
|
|
278
282
|
catch (error) {
|
|
279
283
|
trace(`create ${id} THREW: ${String(error)}`);
|
|
@@ -318,7 +322,8 @@ export class AgyProvider extends Service {
|
|
|
318
322
|
}
|
|
319
323
|
return verified;
|
|
320
324
|
})();
|
|
321
|
-
const
|
|
325
|
+
const agentRef = {};
|
|
326
|
+
const client = this.createLiveClient(spawnCwd, agentOptionsModel(options.agentOptions), options.agentOptions?.reasoningEffort, bindForeignHub("agy", id, this.home, () => agentRef.current), blank ? undefined : record.conversationId);
|
|
322
327
|
let handle;
|
|
323
328
|
let stored;
|
|
324
329
|
let preparation;
|
|
@@ -340,7 +345,7 @@ export class AgyProvider extends Service {
|
|
|
340
345
|
handle = undefined;
|
|
341
346
|
handedOff = true;
|
|
342
347
|
const agentOptions = await this.sessionAgentOptions(options.agentOptions);
|
|
343
|
-
return await setupAndPublish(loopCtx, ownerCtx, id, agentOptions, options.setup, preparation.session, client, options.parentAgent, "resume", { enterSession: true, preparation }, stored, this.agentRuntime(spawnCwd), () => this.followConversationId(id, client));
|
|
348
|
+
return await setupAndPublish(loopCtx, ownerCtx, id, agentOptions, options.setup, preparation.session, client, options.parentAgent, "resume", { enterSession: true, preparation }, stored, this.agentRuntime(spawnCwd), () => this.followConversationId(id, client), agentRef);
|
|
344
349
|
}
|
|
345
350
|
catch (error) {
|
|
346
351
|
trace(`resume ${id} THREW: ${String(error)}`);
|
|
@@ -363,8 +368,8 @@ export class AgyProvider extends Service {
|
|
|
363
368
|
* environment for free while the SDK is an opt-in extra. Auth-form selection
|
|
364
369
|
* still rides the agy-auth ladder.
|
|
365
370
|
*/
|
|
366
|
-
createLiveClient(cwd, modelId, reasoningEffort) {
|
|
367
|
-
return new AgyCliClient(this.cliOptions(cwd, modelId, reasoningEffort));
|
|
371
|
+
createLiveClient(cwd, modelId, reasoningEffort, hubBinding, conversationId) {
|
|
372
|
+
return new AgyCliClient({ ...this.cliOptions(cwd, modelId, reasoningEffort), conversationId, hubBinding, hubStagingDirectory: join(this.home, "hub-native-agy") });
|
|
368
373
|
}
|
|
369
374
|
/**
|
|
370
375
|
* The auth form this machine should use: an explicitly selected, actually
|
|
@@ -457,7 +462,7 @@ async function appendUnstoredSuffix(stored, session) {
|
|
|
457
462
|
stored.storedCount += suffix.length;
|
|
458
463
|
}
|
|
459
464
|
/** Shared create/resume transaction (module-level: Cordis tracing proxy). */
|
|
460
|
-
async function setupAndPublish(loopCtx, ownerCtx, id, agentOptions, setup, session, client, parentAgent, source, opts, stored, runtimeInfo, onTurnDone) {
|
|
465
|
+
async function setupAndPublish(loopCtx, ownerCtx, id, agentOptions, setup, session, client, parentAgent, source, opts, stored, runtimeInfo, onTurnDone, agentRef) {
|
|
461
466
|
const runtime = { ...runtimeInfo, onTurnDone };
|
|
462
467
|
let detachSession;
|
|
463
468
|
let detachAgent;
|
|
@@ -465,6 +470,7 @@ async function setupAndPublish(loopCtx, ownerCtx, id, agentOptions, setup, sessi
|
|
|
465
470
|
let idleExit;
|
|
466
471
|
try {
|
|
467
472
|
agent = new AgyAgent(loopCtx, id, agentOptions, session, client, () => idleExit?.(), runtime);
|
|
473
|
+
agentRef.current = agent;
|
|
468
474
|
// Pin THIS session to the agy route (static resolution — synchronous).
|
|
469
475
|
try {
|
|
470
476
|
const selection = { provider: agentOptions.provider ?? "agy", model: agentOptions.model ?? "" };
|
package/agent-agy/package.json
CHANGED
|
@@ -30,7 +30,8 @@
|
|
|
30
30
|
"@deepseek-ai/dsh-scope": ">=0.1.5-0 <0.3.0-0",
|
|
31
31
|
"@deepseek-ai/dsh-session": ">=0.1.5-0 <0.3.0-0",
|
|
32
32
|
"@deepseek-ai/dsh-session-persistence": ">=0.1.5-0 <0.3.0-0",
|
|
33
|
-
"@deepseek-ai/dsh-typert-protocol": ">=0.1.5-0 <0.3.0-0"
|
|
33
|
+
"@deepseek-ai/dsh-typert-protocol": ">=0.1.5-0 <0.3.0-0",
|
|
34
|
+
"@pgmi-builds/superd-hub": "*"
|
|
34
35
|
},
|
|
35
36
|
"peerDependenciesMeta": {
|
|
36
37
|
"@deepseek-ai/cordis": {
|
|
@@ -51,6 +52,9 @@
|
|
|
51
52
|
"@deepseek-ai/dsh-session-persistence": {
|
|
52
53
|
"optional": true
|
|
53
54
|
},
|
|
55
|
+
"@pgmi-builds/superd-hub": {
|
|
56
|
+
"optional": true
|
|
57
|
+
},
|
|
54
58
|
"@deepseek-ai/dsh-typert-protocol": {
|
|
55
59
|
"optional": true
|
|
56
60
|
}
|
|
@@ -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",
|