super-dsh 0.1.11-b → 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 CHANGED
@@ -14,7 +14,7 @@ the foreign store.
14
14
  ## Install
15
15
 
16
16
  ```bash
17
- dsh plugin --profile <name> add super-dsh@0.1.11
17
+ dsh plugin --profile <name> add super-dsh@0.1.12
18
18
  ```
19
19
 
20
20
  That single package composes the whole line (`@pgmi-builds/superd-hub` plus the
@@ -24,21 +24,16 @@ home under `<DSH_HOME>/agents/<label>/` on first boot.
24
24
 
25
25
  ## Remote web access
26
26
 
27
- To use the same authenticated UI through a reverse proxy or LAN relay, enable
28
- this profile patch:
27
+ The shipped bundle enables `superd-web-trust` with `trustAllHosts: true`.
28
+ Authenticated requests may use any serving Host or Origin, and the browser's
29
+ native trusted-host / loopback checks see the page as Host-owned. No `--host`,
30
+ `--trusted-host`, or trusted-host environment variables are needed for trust.
31
+ Native token-to-cookie login, cookie expiry, and authority binding remain in effect.
29
32
 
30
- ```yaml
31
- - id: superd-web-trust
32
- config:
33
- trustAllHosts: true
34
- ```
35
-
36
- This skips Host/Origin restrictions and enables browser ownership for every
37
- hostname. Native token-to-cookie login, cookie expiry, and authority binding
38
- remain in effect. The default is `false`, preserving declared-authority mode.
39
- It does not change the server's listening address: keep loopback behind Caddy
40
- and a LAN relay. The test launcher enables this mode without `--host` or
41
- `--trusted-host` and starts the relay on every test port, including 4999.
33
+ This does not change the listening address: use the native loopback listener
34
+ behind your reverse proxy or LAN relay. Remove older manual host/trusted-host
35
+ patches and any profile override disabling `superd-web-trust`. To restore the
36
+ native address fence, explicitly set this row's `trustAllHosts: false`.
42
37
 
43
38
  ## Runtimes
44
39
 
@@ -101,6 +101,8 @@ export declare class AgyAgent implements Agent {
101
101
  private deliver;
102
102
  private deliverPrompt;
103
103
  private sessionIdentityCommitted;
104
+ private seenToolCalls;
105
+ private stepOpen;
104
106
  /** Stamp the single-preset selection once per session (projection seed). */
105
107
  private bootstrapSessionIdentity;
106
108
  private startTurn;
@@ -44,7 +44,7 @@ const trace = (...parts) => {
44
44
  /** Classify a bridge turn failure into the stable {message, code} UI chip. */
45
45
  export function classifyTurnError(error) {
46
46
  const text = error instanceof Error ? error.message : String(error);
47
- if (/429|prepayment|quota|rate limit/i.test(text))
47
+ if (/429|prepayment|quota|rate limit|billing/i.test(text))
48
48
  return { message: text, code: "QUOTA" };
49
49
  if (/api key|authentication|unauthenticated|API_KEY/i.test(text))
50
50
  return { message: text, code: "AUTH" };
@@ -125,9 +125,15 @@ export class AgyAgent {
125
125
  if (!this.turnOpen)
126
126
  return;
127
127
  if (ev.event === "tool_call") {
128
- if (this.step > 0)
129
- this.closeStep();
130
- this.commitStepStart();
128
+ if (this.seenToolCalls.has(ev.id))
129
+ return;
130
+ this.seenToolCalls.add(ev.id);
131
+ // Concurrent tools share their originating step until every call resolves.
132
+ if (this.unpairedCalls.length === 0) {
133
+ if (this.step > 0)
134
+ this.closeStep();
135
+ this.commitStepStart();
136
+ }
131
137
  const callId = ToolCallId(ev.id || `agy-${this.dashTurn}-${this.step}`);
132
138
  const argumentsJson = ev.args || "{}";
133
139
  // Advertisement-before-execution (validator invariant, 2026-10-04 dev3
@@ -156,6 +162,8 @@ export class AgyAgent {
156
162
  else {
157
163
  // ToolResult chunks: the SDK yields none today; if a future version
158
164
  // does, pair them off immediately so no placeholder is written.
165
+ if (!this.unpairedCalls.some(c => c.callId === ev.id))
166
+ return;
159
167
  this.unpairedCalls = this.unpairedCalls.filter((c) => c.callId !== (ev.id || ""));
160
168
  this.session.append("tool/result", {
161
169
  turn: this.dashTurn, step: this.step,
@@ -254,6 +262,8 @@ export class AgyAgent {
254
262
  void this.startTurn(message);
255
263
  }
256
264
  sessionIdentityCommitted = false;
265
+ seenToolCalls = new Set();
266
+ stepOpen = false;
257
267
  /** Stamp the single-preset selection once per session (projection seed). */
258
268
  bootstrapSessionIdentity() {
259
269
  if (this.sessionIdentityCommitted)
@@ -264,6 +274,7 @@ export class AgyAgent {
264
274
  }
265
275
  }
266
276
  async startTurn(message) {
277
+ this.seenToolCalls.clear();
267
278
  this.bootstrapSessionIdentity();
268
279
  this.session.append("turn/start", { turn: this.dashTurn });
269
280
  this.session.append("user/message", message, { surfaceOp: "append" });
@@ -277,7 +288,13 @@ export class AgyAgent {
277
288
  trace("onboarding: key stored");
278
289
  }
279
290
  else {
280
- this.appendSystemNotice(ONBOARDING_PROMPT);
291
+ this.appendAssistantMessage(ONBOARDING_PROMPT);
292
+ this.closeStep();
293
+ this.closeTurn({ kind: "error", error: { code: "AUTH", message: ONBOARDING_PROMPT } });
294
+ this.markIdle();
295
+ this.endActivity();
296
+ this.flushFollowUps();
297
+ return;
281
298
  }
282
299
  this.closeTurn({ kind: "completed" });
283
300
  this.markIdle();
@@ -410,18 +427,21 @@ export class AgyAgent {
410
427
  closeTurn(reason) {
411
428
  if (!this.turnOpen)
412
429
  return;
430
+ this.closeStep();
413
431
  this.turnOpen = false;
414
432
  this.clearTurnDeadline();
415
433
  this.session.append("turn/end", { turn: this.dashTurn, reason });
416
434
  }
417
435
  closeStep() {
418
- if (!this.turnOpen || this.step <= 0)
436
+ if (!this.turnOpen || !this.stepOpen)
419
437
  return;
438
+ this.stepOpen = false;
420
439
  this.session.append("step/end", { turn: this.dashTurn, step: this.step });
421
440
  }
422
441
  commitStepStart() {
423
- if (!this.turnOpen)
442
+ if (!this.turnOpen || this.stepOpen)
424
443
  return;
444
+ this.stepOpen = true;
425
445
  this.step += 1;
426
446
  this.session.append("step/start", { turn: this.dashTurn, step: this.step });
427
447
  }
@@ -114,6 +114,9 @@ export declare class AgyCliClient {
114
114
  private dead;
115
115
  private conversationIdValue;
116
116
  private toolsSeen;
117
+ private toolCalls;
118
+ private toolResults;
119
+ private stepFailure?;
117
120
  private hubPreparation?;
118
121
  private hubSession?;
119
122
  private prepare;
@@ -66,6 +66,9 @@ export class AgyCliClient {
66
66
  dead = false;
67
67
  conversationIdValue = "";
68
68
  toolsSeen = 0;
69
+ toolCalls = new Set();
70
+ toolResults = new Set();
71
+ stepFailure;
69
72
  hubPreparation;
70
73
  hubSession;
71
74
  async prepare() {
@@ -248,8 +251,8 @@ export class AgyCliClient {
248
251
  const r = parsed.result;
249
252
  if (r?.conversation_id)
250
253
  this.conversationIdValue = r.conversation_id;
251
- if (r?.status !== "SUCCESS") {
252
- this.dispatch({ event: "error", error: this.withDiagnostics(r?.error ?? `agy result status=${r?.status}`), recoverable: false });
254
+ if (r?.status !== "SUCCESS" || this.stepFailure !== undefined) {
255
+ this.dispatch({ event: "error", error: this.withDiagnostics(r?.error ?? this.stepFailure ?? `agy result status=${r?.status}`), recoverable: false });
253
256
  }
254
257
  else {
255
258
  this.dispatch({ event: "done", conversation_id: this.conversationIdValue, turn_ms: 0 });
@@ -259,6 +262,12 @@ export class AgyCliClient {
259
262
  }
260
263
  consumeStep(su) {
261
264
  const st = su.step_type;
265
+ if (su.conversation_id)
266
+ this.conversationIdValue = su.conversation_id;
267
+ if (st === "error_message") {
268
+ this.stepFailure = su.text_delta || "Antigravity reported an error without details";
269
+ return;
270
+ }
262
271
  if (st === "system_message") {
263
272
  this.onSystemMessage?.(su.text_delta ?? "");
264
273
  return;
@@ -286,15 +295,22 @@ export class AgyCliClient {
286
295
  return;
287
296
  }
288
297
  if (st === "tool") {
289
- const name = su.tool_name ?? su.tool_info?.name ?? "unknown";
298
+ const wireName = String(su.tool_name ?? "");
299
+ const name = su.tool_info?.name || (/^\d+$/.test(wireName) ? "unknown" : wireName) || "unknown";
290
300
  const id = `${this.conversationIdValue}:${su.step_index ?? 0}`;
291
- if (su.state === "ACTIVE") {
301
+ if (su.state === "ACTIVE" && !this.toolCalls.has(id)) {
302
+ this.toolCalls.add(id);
292
303
  this.dispatch({
293
304
  event: "tool_call", id, name,
294
305
  args: JSON.stringify(su.tool_info?.parameters ?? {}),
295
306
  });
296
307
  }
297
- else if (su.state === "DONE" || su.state === "ERROR") {
308
+ else if ((su.state === "DONE" || su.state === "ERROR") && !this.toolResults.has(id)) {
309
+ if (!this.toolCalls.has(id)) {
310
+ this.toolCalls.add(id);
311
+ this.dispatch({ event: "tool_call", id, name, args: JSON.stringify(su.tool_info?.parameters ?? {}) });
312
+ }
313
+ this.toolResults.add(id);
298
314
  const output = su.tool_info?.output ?? "";
299
315
  const isError = su.state === "ERROR";
300
316
  this.dispatch({
@@ -327,6 +343,10 @@ export class AgyCliClient {
327
343
  await this.prepare();
328
344
  this.spawnCli();
329
345
  await this.expectReady();
346
+ this.toolCalls.clear();
347
+ this.toolResults.clear();
348
+ this.stepFailure = undefined;
349
+ this.diagnostics = [];
330
350
  this.send({ event: "user", message: { content: text } });
331
351
  let full = "";
332
352
  for (;;) {
@@ -503,7 +503,7 @@ export class CodexProvider extends Service {
503
503
  if (record === undefined) {
504
504
  throw new CodexResumeError("SESSION_MAP_MISS", `cannot resume session "${id}": no Codex thread is recorded for this Dash session id`);
505
505
  }
506
- if (record.threadId === null) {
506
+ if (record.threadId === null && (await readStoredEvents(persistence, id, options.signal)).some(event => event.type === "turn/start" || event.type === "user/message" || event.type === "assistant/message")) {
507
507
  // C2: never auto-start a fresh thread — the session's history is in the
508
508
  // (missing) native thread, not in a blank one.
509
509
  throw new CodexResumeError("NATIVE_THREAD_NEVER_STARTED", `cannot resume session "${id}": its Codex thread was never started (no thread id recorded)`);
@@ -518,7 +518,7 @@ export class CodexProvider extends Service {
518
518
  // `<worldHome>/.codex` home (never migrated) and pruned rollouts alike.
519
519
  // Without this gate the failure would surface only at the first turn
520
520
  // (resumeThread is zero-IO).
521
- if (readRolloutHead(resolveCodexHome(), record.threadId) === undefined) {
521
+ if (record.threadId !== null && readRolloutHead(resolveCodexHome(), record.threadId) === undefined) {
522
522
  throw new CodexResumeError("ROLLOUT_MISSING", `cannot resume session "${id}": no rollout for Codex thread ${record.threadId} exists in the current Codex home (${resolveCodexHome()})`);
523
523
  }
524
524
  // Spawn cwd comes from the recorded pairing — re-realpath and re-verify so
@@ -545,7 +545,7 @@ export class CodexProvider extends Service {
545
545
  // rollout). No process is spawned here — lazy-spawn gate, rules §14-i.
546
546
  const client = CodexAppServerClient.create({
547
547
  cwd: spawnCwd,
548
- resumeThreadId: record.threadId,
548
+ ...(record.threadId === null ? {} : { resumeThreadId: record.threadId }),
549
549
  approvalPolicy,
550
550
  sandboxMode,
551
551
  worldHome: this.home,
@@ -364,7 +364,9 @@ export class HermesProvider extends Service {
364
364
  if (record === undefined) {
365
365
  throw new Error(`cannot resume session "${id}": no Hermes gateway session is recorded for this Dash session id`);
366
366
  }
367
- if (record.gatewayStoredSessionId === null || record.gatewayStoredSessionId === undefined) {
367
+ const persistence = this.requirePersistence("resume");
368
+ if ((record.gatewayStoredSessionId === null || record.gatewayStoredSessionId === undefined)
369
+ && (await readStoredEvents(persistence, id, options.signal)).some(event => event.type === "turn/start" || event.type === "user/message" || event.type === "assistant/message")) {
368
370
  throw new Error(`cannot resume session "${id}": no durable gateway session id is recorded (this record predates durable-id capture; re-create the session)`);
369
371
  }
370
372
  // Spawn cwd comes from the recorded pairing — re-realpath and re-verify.
@@ -372,7 +374,6 @@ export class HermesProvider extends Service {
372
374
  if (spawnCwd === undefined) {
373
375
  throw new Error(`cannot resume session "${id}": its recorded working directory no longer exists`);
374
376
  }
375
- const persistence = this.requirePersistence("resume");
376
377
  // Preset (record-only): env override, else the recorded preset, else the
377
378
  // persisted session log, else the permission service's default.
378
379
  const envMode = envApprovalMode();
@@ -407,7 +408,7 @@ export class HermesProvider extends Service {
407
408
  this.followGatewaySessionId(id, client);
408
409
  handedOff = true;
409
410
  const agentOptions = await this.sessionAgentOptions(options.agentOptions);
410
- return await setupAndPublish(loopCtx, ownerCtx, id, agentOptions, options.setup, preparation.session, client, options.parentAgent, "resume", { enterSession: true, preparation }, stored, this.agentRuntime(client, spawnCwd, record.gatewayStoredSessionId));
411
+ return await setupAndPublish(loopCtx, ownerCtx, id, agentOptions, options.setup, preparation.session, client, options.parentAgent, "resume", { enterSession: true, preparation }, stored, this.agentRuntime(client, spawnCwd, record.gatewayStoredSessionId ?? undefined));
411
412
  }
412
413
  catch (error) {
413
414
  trace(`resume ${id} THREW: ${String(error)} | stack=${error instanceof Error ? error.stack?.slice(0, 400) : "n/a"}`);
@@ -24,7 +24,7 @@ import { realpathSync, statSync } from "node:fs";
24
24
  import { Service } from "@deepseek-ai/cordis";
25
25
  import { homedir } from "node:os";
26
26
  import { join } from "node:path";
27
- import { SessionPreparation, SessionLogOffset } from "@deepseek-ai/dsh-session";
27
+ import { interruptedTurnClosers, SessionPreparation, SessionLogOffset } from "@deepseek-ai/dsh-session";
28
28
  import { LazyOmpRpc } from "./lazy-rpc.js";
29
29
  import { OmpAgent } from "./agent.js";
30
30
  import { OmpLlmAdapter } from "./adapter.js";
@@ -438,10 +438,13 @@ export class OmpProvider extends Service {
438
438
  // Both branches are LAZY and both resolve their launch flag at spawn.
439
439
  let rpc;
440
440
  try {
441
+ const closers = interruptedTurnClosers(coldRead.events);
442
+ if (closers.length > 0)
443
+ await stored.append(closers);
441
444
  // Fresh attach: seed the dsh session from the dsh log (native replay),
442
445
  // header identity folded from the stored header verbatim.
443
446
  const preparation = SessionPreparation.create(loopCtx.sessions.prepare(id, {
444
- seed: [...coldRead.events],
447
+ seed: [...coldRead.events, ...closers],
445
448
  meta: structuredClone(stored.header),
446
449
  inheritedEventCount: stored.inheritedEventCount,
447
450
  eventState: coldRead.eventState,
@@ -465,7 +468,7 @@ export class OmpProvider extends Service {
465
468
  return ["--approval-mode", spawnPermission().approvalMode, "--resume", ompFile, ...(extension === undefined ? [] : ["-e", extension])];
466
469
  }, spawnCwd, () => hub.dispose());
467
470
  }
468
- 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 } });
469
472
  return this.registerHeld(id, ompFile ?? "", handle, rpc);
470
473
  }
471
474
  catch (error) {
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-b",
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",