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.
Files changed (54) hide show
  1. package/README.md +54 -6
  2. package/agent-agy/dist/agent.d.ts +4 -0
  3. package/agent-agy/dist/agent.js +41 -7
  4. package/agent-agy/dist/agy-cli-client.d.ts +12 -0
  5. package/agent-agy/dist/agy-cli-client.js +81 -13
  6. package/agent-agy/dist/index.js +13 -7
  7. package/agent-agy/package.json +5 -1
  8. package/agent-claude/dist/agent.js +67 -4
  9. package/agent-claude/dist/claude-client.js +46 -1
  10. package/agent-claude/dist/index.js +3 -0
  11. package/agent-claude/dist/sdk-runtime.js +2 -2
  12. package/agent-codex/dist/agent.js +33 -8
  13. package/agent-codex/dist/app-server-client.js +26 -3
  14. package/agent-codex/dist/app-server-protocol.js +6 -2
  15. package/agent-codex/dist/app-server-wire.js +23 -6
  16. package/agent-codex/dist/codex-native-config.js +6 -1
  17. package/agent-codex/dist/codex-runtime-fetch.js +293 -0
  18. package/agent-codex/dist/codex-runtime.js +12 -12
  19. package/agent-codex/dist/index.js +10 -4
  20. package/agent-codex/package.json +8 -3
  21. package/agent-codex-sdk/dist/sdk-runtime.js +1 -1
  22. package/agent-hermes/dist/hermes-client.js +44 -5
  23. package/agent-hermes/dist/index.js +10 -5
  24. package/agent-omp/dist/agent.js +36 -5
  25. package/agent-omp/dist/index.js +29 -6
  26. package/agent-omp/dist/lazy-rpc.js +11 -2
  27. package/agent-omp/dist/rpc-client.js +6 -1
  28. package/agent-pi/dist/agent.js +36 -9
  29. package/agent-pi/dist/index.js +6 -2
  30. package/agent-pi/dist/pi-client.js +13 -3
  31. package/cordis.patch.yml +10 -9
  32. package/package.json +6 -4
  33. package/superd-hub/dist/a2a-row.js +20 -3
  34. package/superd-hub/dist/connection-trust.d.ts +8 -0
  35. package/superd-hub/dist/connection-trust.js +16 -0
  36. package/superd-hub/dist/foreign-agy.d.ts +3 -0
  37. package/superd-hub/dist/foreign-agy.js +110 -0
  38. package/superd-hub/dist/foreign-claude.d.ts +4 -0
  39. package/superd-hub/dist/foreign-claude.js +160 -0
  40. package/superd-hub/dist/foreign-extension.d.ts +10 -0
  41. package/superd-hub/dist/foreign-extension.js +27 -0
  42. package/superd-hub/dist/foreign-hub.d.ts +27 -0
  43. package/superd-hub/dist/foreign-hub.js +243 -0
  44. package/superd-hub/dist/foreign-native.d.ts +4 -0
  45. package/superd-hub/dist/foreign-native.js +115 -0
  46. package/superd-hub/dist/web-trust.d.ts +14 -42
  47. package/superd-hub/dist/web-trust.js +18 -43
  48. package/superd-hub/package.json +14 -2
  49. package/agent-codex/README.md +0 -191
  50. package/agent-codex-sdk/README.md +0 -79
  51. package/agent-hermes/README.md +0 -145
  52. package/agent-omp/README.md +0 -94
  53. package/agent-omp-sdk/README.md +0 -103
  54. package/agent-pi/README.md +0 -67
@@ -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 { LlmAttemptId, ToolCallId, QUOTA_EXCEEDED_CODE, createAssistantMessage, createSystemMessage, createToolResultMessage, createUserMessage } from "@deepseek-ai/dsh-llm";
@@ -265,6 +266,8 @@ export class OmpAgent {
265
266
  #step = 0;
266
267
  #lastTurn = 0;
267
268
  #turnOpen = false;
269
+ #turnCommitted = false;
270
+ #stepOpen = false;
268
271
  #cancelCause = null;
269
272
  /** Terminal model failure of the open turn (`message_end` stopReason "error"); consumed by `agent_end`. */
270
273
  #pendingFailure = null;
@@ -486,7 +489,7 @@ export class OmpAgent {
486
489
  catch (error) {
487
490
  trace(`#startTurn cold start failed: ${String(error)}`);
488
491
  if (this.#turnOpen) {
489
- this.session.append("turn/start", { turn: this.#dashTurn });
492
+ this.#commitTurnStart();
490
493
  this.session.append("user/message", message, { surfaceOp: "append" });
491
494
  this.#closeTurn({ kind: "error", error: { message: String(error), code: "UNKNOWN" } });
492
495
  }
@@ -495,7 +498,7 @@ export class OmpAgent {
495
498
  }
496
499
  if (this.#disposed)
497
500
  return;
498
- this.session.append("turn/start", { turn: this.#dashTurn });
501
+ this.#commitTurnStart();
499
502
  this.session.append("user/message", message, { surfaceOp: "append" });
500
503
  void this.#rpc.prompt(userMessageText(message)).catch((error) => this.#fail(error));
501
504
  }
@@ -709,12 +712,35 @@ export class OmpAgent {
709
712
  return;
710
713
  this.#reserveTurn();
711
714
  this.#localUserPending = localUser;
712
- this.session.append("turn/start", { turn: this.#dashTurn });
715
+ this.#commitTurnStart();
713
716
  this.session.append("user/message", message, { surfaceOp: "append" });
714
717
  }
718
+ #commitTurnStart() {
719
+ if (this.#turnCommitted)
720
+ return;
721
+ this.session.append("turn/start", { turn: this.#dashTurn });
722
+ this.#turnCommitted = true;
723
+ }
715
724
  #closeTurn(reason) {
716
725
  if (!this.#turnOpen)
717
726
  return;
727
+ this.#commitTurnStart();
728
+ if (this.#stepOpen) {
729
+ const events = this.session.snapshotEvents();
730
+ const start = events.findLastIndex(event => event.type === "step/start");
731
+ const recovery = new ToolCallRecovery();
732
+ for (const event of events.slice(start))
733
+ recovery.observe(event);
734
+ for (const result of recovery.results()) {
735
+ this.session.append("tool/result", result.data, {
736
+ surfaceOp: "append",
737
+ ...(result.sourceEventSeqs === undefined ? {} : { sourceEventSeqs: result.sourceEventSeqs }),
738
+ });
739
+ }
740
+ this.session.append("step/end", { turn: this.#dashTurn, step: this.#step });
741
+ this.#stepOpen = false;
742
+ }
743
+ this.#turnCommitted = false;
718
744
  this.#turnOpen = false;
719
745
  this.#localUserPending = false;
720
746
  this.session.append("turn/end", { turn: this.#dashTurn, reason });
@@ -751,9 +777,12 @@ export class OmpAgent {
751
777
  return;
752
778
  this.#mirroredTitle = title;
753
779
  try {
780
+ const input = this.session.snapshotEvents().find(event => event.type === "user/message" && event.data.source.kind === "user");
781
+ if (input === undefined)
782
+ return;
754
783
  this.session.append("session/title", {
755
784
  title,
756
- messageSeqs: [],
785
+ messageSeqs: [input.seq],
757
786
  source: { kind: "provider", provider: "omp" },
758
787
  });
759
788
  }
@@ -773,6 +802,7 @@ export class OmpAgent {
773
802
  return;
774
803
  this.#step += 1;
775
804
  this.session.append("step/start", { turn: this.#dashTurn, step: this.#step });
805
+ this.#stepOpen = true;
776
806
  }
777
807
  /**
778
808
  * Bridge an OMP `message_start(role=user)` that was NOT appended locally.
@@ -986,8 +1016,9 @@ export class OmpAgent {
986
1016
  break;
987
1017
  case "turn_end":
988
1018
  trace("event turn_end");
989
- if (this.#turnOpen) {
1019
+ if (this.#turnOpen && this.#stepOpen) {
990
1020
  this.session.append("step/end", { turn: this.#dashTurn, step: this.#step });
1021
+ this.#stepOpen = false;
991
1022
  }
992
1023
  break;
993
1024
  case "agent_end": {
@@ -19,11 +19,12 @@
19
19
  * exact transcript it rendered before the restart. The OMP transcript is
20
20
  * replayed (`get_messages`) only when no Dash log was persisted.
21
21
  */
22
+ import { bindForeignHub } from '@pgmi-builds/superd-hub/foreign-hub';
22
23
  import { realpathSync, statSync } from "node:fs";
23
24
  import { Service } from "@deepseek-ai/cordis";
24
25
  import { homedir } from "node:os";
25
26
  import { join } from "node:path";
26
- import { SessionPreparation, SessionLogOffset } from "@deepseek-ai/dsh-session";
27
+ import { interruptedTurnClosers, SessionPreparation, SessionLogOffset } from "@deepseek-ai/dsh-session";
27
28
  import { LazyOmpRpc } from "./lazy-rpc.js";
28
29
  import { OmpAgent } from "./agent.js";
29
30
  import { OmpLlmAdapter } from "./adapter.js";
@@ -311,6 +312,13 @@ export class OmpProvider extends Service {
311
312
  }
312
313
  llm.registerAdapter(providers, new OmpLlmAdapter());
313
314
  }
315
+ hubFor(sessionId) {
316
+ const fromBoot = this.runtime.ctx.get("dshHomePath");
317
+ const home = typeof fromBoot === "function" ? fromBoot() : fromBoot;
318
+ if (typeof home !== "string" || home === "")
319
+ throw new Error("OMP world has no boot home path");
320
+ return bindForeignHub('omp', sessionId, home, () => this.runtime.ctx.agents.get(sessionId));
321
+ }
314
322
  async createAgent(ownerCtx, options) {
315
323
  const loopCtx = this.runtime.ctx;
316
324
  const id = options.sessionId;
@@ -347,7 +355,11 @@ export class OmpProvider extends Service {
347
355
  }));
348
356
  const session = preparation.session;
349
357
  const spawnPermission = () => resolveSpawnPermission(session.snapshotEvents(), defaultPermissionPreset(loopCtx), envApprovalMode());
350
- const rpc = new LazyOmpRpc(() => ["--approval-mode", spawnPermission().approvalMode], cwd);
358
+ const hub = this.hubFor(id);
359
+ const rpc = new LazyOmpRpc(async () => {
360
+ const extension = await hub.extensionPath();
361
+ return ["--approval-mode", spawnPermission().approvalMode, ...(extension === undefined ? [] : ["-e", extension])];
362
+ }, cwd, () => hub.dispose());
351
363
  // The prepared header is the single source of truth for the row the
352
364
  // first-spawn adoption writes: v2 folds header identity across
353
365
  // live/listed/loaded observations, so created_at/cwd must be copied from
@@ -426,10 +438,13 @@ export class OmpProvider extends Service {
426
438
  // Both branches are LAZY and both resolve their launch flag at spawn.
427
439
  let rpc;
428
440
  try {
441
+ const closers = interruptedTurnClosers(coldRead.events);
442
+ if (closers.length > 0)
443
+ await stored.append(closers);
429
444
  // Fresh attach: seed the dsh session from the dsh log (native replay),
430
445
  // header identity folded from the stored header verbatim.
431
446
  const preparation = SessionPreparation.create(loopCtx.sessions.prepare(id, {
432
- seed: [...coldRead.events],
447
+ seed: [...coldRead.events, ...closers],
433
448
  meta: structuredClone(stored.header),
434
449
  inheritedEventCount: stored.inheritedEventCount,
435
450
  eventState: coldRead.eventState,
@@ -438,14 +453,22 @@ export class OmpProvider extends Service {
438
453
  trace(`resume id=${id} seeded from dsh session log (${coldRead.events.length} events), approval=resolve-at-spawn (row fallback ${rowPreset() ?? "none"})${freshOmp ? " fresh-omp" : ""}`);
439
454
  const spawnPermission = () => resolveSpawnPermission(preparation.session.snapshotEvents(), rowPreset(), envApprovalMode());
440
455
  if (ompFile === undefined) {
441
- const lazy = new LazyOmpRpc(() => ["--approval-mode", spawnPermission().approvalMode], spawnCwd);
456
+ const hub = this.hubFor(id);
457
+ const lazy = new LazyOmpRpc(async () => {
458
+ const extension = await hub.extensionPath();
459
+ return ["--approval-mode", spawnPermission().approvalMode, ...(extension === undefined ? [] : ["-e", extension])];
460
+ }, spawnCwd, () => hub.dispose());
442
461
  lazy.onSpawned((client) => { void adoptSpawnedChild(id, stored.header, client, spawnPermission().preset); });
443
462
  rpc = lazy;
444
463
  }
445
464
  else {
446
- rpc = new LazyOmpRpc(() => ["--approval-mode", spawnPermission().approvalMode, "--resume", ompFile], spawnCwd);
465
+ const hub = this.hubFor(id);
466
+ rpc = new LazyOmpRpc(async () => {
467
+ const extension = await hub.extensionPath();
468
+ return ["--approval-mode", spawnPermission().approvalMode, "--resume", ompFile, ...(extension === undefined ? [] : ["-e", extension])];
469
+ }, spawnCwd, () => hub.dispose());
447
470
  }
448
- const handle = await setupAndPublish(loopCtx, ownerCtx, id, options.agentOptions ?? {}, options.setup, preparation.session, rpc, options.parentAgent, "resume", { enterSession: true, preparation, stored: { handle: stored, written: coldRead.events.length } });
471
+ const handle = await setupAndPublish(loopCtx, ownerCtx, id, options.agentOptions ?? {}, options.setup, preparation.session, rpc, options.parentAgent, "resume", { enterSession: true, preparation, stored: { handle: stored, written: coldRead.events.length + closers.length } });
449
472
  return this.registerHeld(id, ompFile ?? "", handle, rpc);
450
473
  }
451
474
  catch (error) {
@@ -2,6 +2,7 @@ import { OmpRpcClient } from "./rpc-client.js";
2
2
  /** Synthetic state for a not-yet-spawned child: quiescent by definition. */
3
3
  const IDLE_STATE = { isStreaming: false };
4
4
  export class LazyOmpRpc {
5
+ onClose;
5
6
  #spawnArgs;
6
7
  #cwd;
7
8
  #client;
@@ -18,7 +19,8 @@ export class LazyOmpRpc {
18
19
  * construction and the first prompt — the new-chat permission pick does.
19
20
  * @param cwd - the spawn cwd.
20
21
  */
21
- constructor(spawnArgs, cwd) {
22
+ constructor(spawnArgs, cwd, onClose) {
23
+ this.onClose = onClose;
22
24
  this.#spawnArgs = spawnArgs;
23
25
  this.#cwd = cwd;
24
26
  }
@@ -42,7 +44,11 @@ export class LazyOmpRpc {
42
44
  return Promise.resolve(this.#client);
43
45
  if (this.#spawn === undefined) {
44
46
  const args = typeof this.#spawnArgs === "function" ? this.#spawnArgs() : this.#spawnArgs;
45
- this.#spawn = OmpRpcClient.spawn(args, this.#cwd).then((client) => {
47
+ this.#spawn = Promise.resolve(args).then((resolved) => {
48
+ if (this.#closed)
49
+ throw new Error('OMP client closed during extension preparation');
50
+ return OmpRpcClient.spawn(resolved, this.#cwd);
51
+ }).then((client) => {
46
52
  // A close() that raced the in-flight spawn wins: dispose immediately.
47
53
  if (this.#closed) {
48
54
  client.close();
@@ -160,7 +166,10 @@ export class LazyOmpRpc {
160
166
  this.#client.sendRaw(command);
161
167
  }
162
168
  close() {
169
+ if (this.#closed)
170
+ return;
163
171
  this.#closed = true;
172
+ this.onClose?.();
164
173
  const client = this.#client;
165
174
  this.#client = undefined;
166
175
  client?.close();
@@ -264,7 +264,12 @@ export class OmpRpcClient {
264
264
  async getSystemPrompt() {
265
265
  try {
266
266
  const state = await this.getState();
267
- return state.systemPrompt;
267
+ const prompt = state.systemPrompt;
268
+ if (typeof prompt === "string")
269
+ return prompt;
270
+ if (Array.isArray(prompt) && prompt.every(part => typeof part === "string"))
271
+ return prompt.join("\n\n");
272
+ return undefined;
268
273
  }
269
274
  catch {
270
275
  return undefined;
@@ -1,3 +1,4 @@
1
+ import { ToolCallRecovery } from "@deepseek-ai/dsh-session";
1
2
  import { randomUUID } from "node:crypto";
2
3
  import { agentEvents } from "@deepseek-ai/dsh-agent";
3
4
  import { Inbox } from "./inbox.js";
@@ -186,6 +187,8 @@ export class PiAgent {
186
187
  #step = 0;
187
188
  #lastTurn = 0;
188
189
  #turnOpen = false;
190
+ #turnCommitted = false;
191
+ #stepOpen = false;
189
192
  #cancelCause = null;
190
193
  /** Terminal model failure of the open turn (`message_end` stopReason "error"); consumed by `agent_end`. */
191
194
  #pendingFailure = null;
@@ -402,7 +405,7 @@ export class PiAgent {
402
405
  catch (error) {
403
406
  trace(`#startTurn cold start failed: ${String(error)}`);
404
407
  if (this.#turnOpen) {
405
- this.session.append("turn/start", { turn: this.#dashTurn });
408
+ this.#commitTurnStart();
406
409
  this.session.append("user/message", message, { surfaceOp: "append" });
407
410
  this.#closeTurn({ kind: "error", error: { message: String(error), code: "UNKNOWN" } });
408
411
  }
@@ -411,7 +414,7 @@ export class PiAgent {
411
414
  }
412
415
  if (this.#disposed)
413
416
  return;
414
- this.session.append("turn/start", { turn: this.#dashTurn });
417
+ this.#commitTurnStart();
415
418
  this.#emitRequestContext();
416
419
  this.session.append("user/message", message, { surfaceOp: "append" });
417
420
  void this.#client.prompt(userMessageText(message)).catch((error) => this.#fail(error));
@@ -562,13 +565,36 @@ export class PiAgent {
562
565
  return;
563
566
  this.#reserveTurn();
564
567
  this.#localUserPending = localUser;
565
- this.session.append("turn/start", { turn: this.#dashTurn });
568
+ this.#commitTurnStart();
566
569
  this.#emitRequestContext();
567
570
  this.session.append("user/message", message, { surfaceOp: "append" });
568
571
  }
572
+ #commitTurnStart() {
573
+ if (this.#turnCommitted)
574
+ return;
575
+ this.session.append("turn/start", { turn: this.#dashTurn });
576
+ this.#turnCommitted = true;
577
+ }
569
578
  #closeTurn(reason) {
570
579
  if (!this.#turnOpen)
571
580
  return;
581
+ this.#commitTurnStart();
582
+ if (this.#stepOpen) {
583
+ const events = this.session.snapshotEvents();
584
+ const start = events.findLastIndex(event => event.type === "step/start");
585
+ const recovery = new ToolCallRecovery();
586
+ for (const event of events.slice(start))
587
+ recovery.observe(event);
588
+ for (const result of recovery.results()) {
589
+ this.session.append("tool/result", result.data, {
590
+ surfaceOp: "append",
591
+ ...(result.sourceEventSeqs === undefined ? {} : { sourceEventSeqs: result.sourceEventSeqs }),
592
+ });
593
+ }
594
+ this.session.append("step/end", { turn: this.#dashTurn, step: this.#step });
595
+ this.#stepOpen = false;
596
+ }
597
+ this.#turnCommitted = false;
572
598
  this.#turnOpen = false;
573
599
  this.#localUserPending = false;
574
600
  this.session.append("turn/end", { turn: this.#dashTurn, reason });
@@ -585,6 +611,7 @@ export class PiAgent {
585
611
  this.#stepStartPending = false;
586
612
  this.#step += 1;
587
613
  this.session.append("step/start", { turn: this.#dashTurn, step: this.#step });
614
+ this.#stepOpen = true;
588
615
  trace(`step/start turn=${this.#dashTurn} step=${this.#step}`);
589
616
  }
590
617
  /**
@@ -695,10 +722,6 @@ export class PiAgent {
695
722
  trace("event turn_start");
696
723
  this.#stepStartPending = true;
697
724
  this.#pendingFailure = null;
698
- // Open the step immediately (pi may never send message_start)
699
- // and try the system prompt now that the session exists.
700
- this.#commitStepStart();
701
- this.#emitSystemMessage();
702
725
  break;
703
726
  case "message_start": {
704
727
  const message = event.message;
@@ -798,8 +821,9 @@ export class PiAgent {
798
821
  // Last chance for the system prompt. Appended inside the still-open
799
822
  // step (before step/end) so the step-scope invariant holds.
800
823
  this.#emitSystemMessage();
801
- if (this.#turnOpen && this.#step > 0) {
824
+ if (this.#turnOpen && this.#stepOpen) {
802
825
  this.session.append("step/end", { turn: this.#dashTurn, step: this.#step });
826
+ this.#stepOpen = false;
803
827
  }
804
828
  break;
805
829
  case "agent_end": {
@@ -839,9 +863,12 @@ export class PiAgent {
839
863
  return;
840
864
  this.#mirroredTitle = name;
841
865
  try {
866
+ const input = this.session.snapshotEvents().find(event => event.type === "user/message" && event.data.source.kind === "user");
867
+ if (input === undefined)
868
+ return;
842
869
  this.session.append("session/title", {
843
870
  title: name,
844
- messageSeqs: [],
871
+ messageSeqs: [input.seq],
845
872
  source: { kind: "provider", provider: "pi" },
846
873
  });
847
874
  trace(`session/title ← pi: "${name}"`);
@@ -25,6 +25,7 @@
25
25
  * duplicated by design: pi's native JSONL stays pi's authority; the DSH log
26
26
  * is the WebUI copy.
27
27
  */
28
+ import { bindForeignHub } from '@pgmi-builds/superd-hub/foreign-hub';
28
29
  import { realpathSync, statSync } from "node:fs";
29
30
  import { join } from "node:path";
30
31
  import { Service } from "@deepseek-ai/cordis";
@@ -288,6 +289,9 @@ export class PiProvider extends Service {
288
289
  });
289
290
  });
290
291
  }
292
+ hubFor(sessionId) {
293
+ return bindForeignHub('pi', sessionId, this.home, () => this.runtime.ctx.agents.get(sessionId));
294
+ }
291
295
  async createAgent(ownerCtx, options) {
292
296
  const loopCtx = this.runtime.ctx;
293
297
  const id = options.sessionId;
@@ -315,7 +319,7 @@ export class PiProvider extends Service {
315
319
  const client = await PiSessionClient.spawn([], spawnCwd, {
316
320
  kind: "create",
317
321
  ...(toolset === undefined ? {} : { tools: toolset }),
318
- });
322
+ }, this.hubFor(id));
319
323
  const preparation = SessionPreparation.create(loopCtx.sessions.prepare(id, {
320
324
  ...(options.seed === undefined ? {} : { seed: options.seed }),
321
325
  ...(meta === undefined ? {} : { meta }),
@@ -398,7 +402,7 @@ export class PiProvider extends Service {
398
402
  // CREATE-shaped lazy shell — zero pi objects until the first prompt.
399
403
  const client = await PiSessionClient.spawn([], spawnCwd, blank
400
404
  ? { kind: "create", ...(toolset === undefined ? {} : { tools: toolset }) }
401
- : { kind: "resume", sessionFile: record.sessionFile });
405
+ : { kind: "resume", sessionFile: record.sessionFile }, this.hubFor(id));
402
406
  let handle;
403
407
  let stored;
404
408
  let preparation;
@@ -23,14 +23,23 @@ import { projectSessionEvent } from "./pi-events.js";
23
23
  import { getPiModelRuntime } from "./models.js";
24
24
  import { trace } from "./knobs.js";
25
25
  /** Default factory: the real SDK, over the shared ModelRuntime. */
26
- let sessionFactory = async ({ cwd, mode }) => {
26
+ let sessionFactory = async ({ cwd, mode, hub }) => {
27
27
  const sdk = (await import("@earendil-works/pi-coding-agent"));
28
28
  const modelRuntime = await getPiModelRuntime();
29
29
  const sessionManager = mode.kind === "resume" ? sdk.SessionManager.open(mode.sessionFile) : sdk.SessionManager.create(cwd);
30
+ const extension = await hub?.extensionPath();
31
+ let resourceLoader;
32
+ if (extension !== undefined) {
33
+ const { DefaultResourceLoader, getAgentDir } = await import('@earendil-works/pi-coding-agent');
34
+ const loader = new DefaultResourceLoader({ cwd, agentDir: getAgentDir(), additionalExtensionPaths: [extension] });
35
+ await loader.reload();
36
+ resourceLoader = loader;
37
+ }
30
38
  const { session } = await sdk.createAgentSession({
31
39
  modelRuntime,
32
40
  sessionManager,
33
41
  cwd,
42
+ ...(resourceLoader === undefined ? {} : { resourceLoader }),
34
43
  ...(mode.kind === "create" && mode.tools !== undefined && mode.tools.length > 0 ? { tools: mode.tools } : {}),
35
44
  });
36
45
  return session;
@@ -57,11 +66,11 @@ export class PiSessionClient {
57
66
  * `ensureStarted()`. `args` is accepted for omp-seam parity and must be
58
67
  * empty — unknown values fail loud (the SDK line has no spawn flags).
59
68
  */
60
- static async spawn(args, cwd, mode) {
69
+ static async spawn(args, cwd, mode, hub) {
61
70
  if (args.length > 0) {
62
71
  throw new Error(`PiSessionClient: unsupported spawn arg "${String(args[0])}" (SDK line)`);
63
72
  }
64
- return new PiSessionClient({ cwd, mode });
73
+ return new PiSessionClient({ cwd, mode, ...(hub === undefined ? {} : { hub }) });
65
74
  }
66
75
  /** Register a wire event listener; returns the unsubscriber. */
67
76
  on(listener) {
@@ -183,6 +192,7 @@ export class PiSessionClient {
183
192
  if (this.#closed)
184
193
  return;
185
194
  this.#closed = true;
195
+ this.config.hub?.dispose();
186
196
  try {
187
197
  this.#session?.dispose();
188
198
  }
package/cordis.patch.yml CHANGED
@@ -12,15 +12,14 @@
12
12
  # zero-port sibling root, mounted under /<label>
13
13
  # - super-dsh/join the generic hub joiner for the standalone
14
14
  # claude adapter (R2)
15
- # - super-dsh/web-trust the page-trust component (ported from
16
- # better-dsh web-trust, authorities leg): the
17
- # browser-side twin of the server trustedHosts
18
- # fence — declares trusted page authorities so
19
- # the Web UI treats a non-loopback page (Caddy
20
- # domain / LAN IP) as loopback-equivalent.
21
- # On/off: row `disabled: true`, or config
22
- # `trustedPageAuthorities: []` (inert). Default
23
- # derives from the trusted-hosts env.
15
+ # - super-dsh/web-trust authentication-only admission (aligned with
16
+ # dashr): every serving Host/Origin is accepted
17
+ # after native cookie authentication, and the
18
+ # browser owns its Host on any authority. No
19
+ # trusted-host CLI flags or environment required.
20
+ # Set trustAllHosts: false to retain the native
21
+ # fence / declared-authorities mode, or disable
22
+ # this row explicitly.
24
23
  # - super-dsh/a2a the agent-facing A2A tool face (mesh P3): the
25
24
  # single `superd-hub` model tool on ctx0's tools
26
25
  # service (dormant without tools; world trees
@@ -36,6 +35,8 @@
36
35
  name: 'super-dsh'
37
36
  - id: superd-web-trust
38
37
  name: 'super-dsh/web-trust'
38
+ config:
39
+ trustAllHosts: true
39
40
  # Native UI fusion transport (plan Task 3): the ctx0 page-level state atom
40
41
  # + five call-time patches. Composing this row IS the switch — there is no
41
42
  # environment variable in the decision (2026-09-27 user ruling: a release
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "super-dsh",
3
- "version": "0.1.11",
3
+ "version": "0.1.12",
4
4
  "description": "Super D for the DeepSeek Harness (DSH) \u2014 one dsh plugin that mounts the superd hub (runtime selector, roster, per-runtime agents) and joins local foreign-agent runtimes (OMP, Codex, Claude, Pi, Hermes, Antigravity) into the DSH Web UI. Each runtime keeps its native home and session store; the hub only bridges.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -80,8 +80,6 @@
80
80
  },
81
81
  "dependencies": {
82
82
  "@earendil-works/pi-coding-agent": "0.84.2",
83
- "@openai/codex": "0.160.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": {
@@ -1,3 +1,5 @@
1
+ import { homedir } from 'node:os';
2
+ import { join } from 'node:path';
1
3
  import { resolveDshHome } from '@deepseek-ai/dsh-home-paths';
2
4
  import { createSuperdHubTool } from './hub-tool.js';
3
5
  import { createA2aIngest } from './a2a-ingest.js';
@@ -14,6 +16,10 @@ import { PtyPool } from './exec-pty-session.js';
14
16
  import { LocalScriptChannel, WsPtyChannel } from './exec-pty-channel.js';
15
17
  import { PTY_EXEC_PATH } from './exec-pty-rpc.js';
16
18
  import { agentLinks } from './agent-roster.js';
19
+ import { foreignAgentHandle, registerForeignHubHost, foreignHubEndpoint } from './foreign-hub.js';
20
+ import { refreshHermesNativeEndpoint } from './foreign-native.js';
21
+ import { refreshClaudeNativeEndpoint } from './foreign-claude.js';
22
+ import { refreshAgyNativeEndpoint } from './foreign-agy.js';
17
23
  /** Stable Cordis plugin (row) name. */
18
24
  export const name = 'superd-a2a';
19
25
  /** Zero static injects — the tools/typertGateway legs are dynamic (dormancy). */
@@ -69,7 +75,7 @@ export function wireHubToolRow(ctx, opts = {}) {
69
75
  let ingestRef;
70
76
  const service = createA2aService({ home, gatewayFor, ingest: () => ingestRef, log: alog });
71
77
  const delivery = {
72
- agents: { get: (sessionId) => liveAgentHandle(ctx, sessionId) },
78
+ agents: { get: (sessionId) => liveAgentHandle(ctx, sessionId) ?? foreignAgentHandle(home, sessionId) },
73
79
  buffer: (initiatorSessionId, message) => { service.buffer(initiatorSessionId, message); },
74
80
  };
75
81
  ingestRef = createA2aIngest({ home, gatewayFor, delivery, log: alog });
@@ -141,7 +147,7 @@ export function wireHubToolRow(ctx, opts = {}) {
141
147
  };
142
148
  const execFaceFull = { ...execFace, pty: execPty };
143
149
  // the a2a endpoints (POST /api/superd/a2a + GET /api/superd/a2a/outbox) ride the shared
144
- t.tools.register(createSuperdHubTool({
150
+ const toolDeps = {
145
151
  service,
146
152
  delivery,
147
153
  exec: execFaceFull,
@@ -152,7 +158,18 @@ export function wireHubToolRow(ctx, opts = {}) {
152
158
  // native DSH session, so the initiator's a6 is the 'native' row's.
153
159
  initiatorOf: (sessionId) => localA2aInitiator(home, 'native', sessionId),
154
160
  onWatch: (dispose) => { ctx.effect(() => dispose, 'superd-a2a: a2a completion watch'); },
155
- }));
161
+ };
162
+ t.tools.register(createSuperdHubTool(toolDeps));
163
+ t.effect(() => registerForeignHubHost(home, toolDeps), 'superd-a2a: foreign session facility');
164
+ t.effect(() => {
165
+ const publication = Promise.all([
166
+ refreshHermesNativeEndpoint(() => foreignHubEndpoint(home), join(homedir(), '.hermes')),
167
+ refreshClaudeNativeEndpoint(() => foreignHubEndpoint(home), process.env.CLAUDE_CONFIG_DIR ?? join(homedir(), '.claude')),
168
+ refreshAgyNativeEndpoint(() => foreignHubEndpoint(home), join(homedir(), '.gemini', 'antigravity-cli')),
169
+ ])
170
+ .catch(error => ctx.logger.warn(`superd-a2a: native plugin endpoint refresh failed: ${String(error)}`));
171
+ return () => publication;
172
+ }, 'superd-a2a: native post office discovery');
156
173
  });
157
174
  }
158
175
  /** MUST stay an arrow (RM0/better-dash note: function declarations get `new`-called, disposers lost). */
@@ -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;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Use the native index authenticator as a cookie-only admission delegate.
3
+ * A fixed, query-free index path prevents token exchange or redirects here;
4
+ * original Host and Cookie headers preserve the native authority binding and
5
+ * lifetime. No upstream private fields or cookie-format copies are needed.
6
+ */
7
+ export function installAuthenticationOnlyAdmission(connection) {
8
+ const original = connection.requestRejection;
9
+ const response = { writeHead() { }, end() { } };
10
+ connection.requestRejection = (request) => connection.authorizeIndex({
11
+ headers: request.headers,
12
+ method: 'GET',
13
+ url: '/index.html',
14
+ }, response) ? undefined : 401;
15
+ return () => { connection.requestRejection = original; };
16
+ }
@@ -0,0 +1,3 @@
1
+ import type { ForeignHubBinding } from './foreign-hub.js';
2
+ export declare function refreshAgyNativeEndpoint(endpoint: () => Promise<string | undefined>, nativeHome: string): Promise<void>;
3
+ export declare function prepareAgyNativePlugin(binding: Pick<ForeignHubBinding, 'transport'>, nativeHome: string, stagingDirectory: string, install: (directory: string) => Promise<unknown>): Promise<string | undefined>;