dsh-loop-engine 1.0.0-rc14 → 1.0.0-rc15

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/lib/index.js CHANGED
@@ -2980,7 +2980,7 @@ function specsEqual(a, b) {
2980
2980
  return a.cwd === b.cwd && a.env === b.env && a.argv.length === b.argv.length && a.argv.every((value, index) => value === b.argv[index]);
2981
2981
  }
2982
2982
  var PiAgent = class {
2983
- constructor(loopCtx, id, options, session, config, spawn4, bin) {
2983
+ constructor(loopCtx, id, options, session, config, spawn4, bin, catalog) {
2984
2984
  this.loopCtx = loopCtx;
2985
2985
  this.id = id;
2986
2986
  this.options = options;
@@ -2988,6 +2988,7 @@ var PiAgent = class {
2988
2988
  this.config = config;
2989
2989
  this.spawn = spawn4;
2990
2990
  this.bin = bin;
2991
+ this.catalog = catalog;
2991
2992
  this.dispatch = agentEvents3(loopCtx, this);
2992
2993
  this.inbox = new Inbox3(session, {
2993
2994
  inserted: (message) => {
@@ -3016,6 +3017,7 @@ var PiAgent = class {
3016
3017
  config;
3017
3018
  spawn;
3018
3019
  bin;
3020
+ catalog;
3019
3021
  inbox;
3020
3022
  phase;
3021
3023
  activityDone = Promise.resolve();
@@ -3337,9 +3339,28 @@ var PiAgent = class {
3337
3339
  return void 0;
3338
3340
  }
3339
3341
  /** Build the `pi --mode rpc` argv/cwd/env for one step's child process. */
3342
+ /**
3343
+ * Resolve the `--model` for the RPC child. The session-selected model (last
3344
+ * `model/selection` event) is honored only when it is one of pi's discovered
3345
+ * models; an unknown harness model (e.g. another provider's model such as
3346
+ * `anyai-v1`, which pi cannot serve) is dropped so the child falls back to pi's
3347
+ * own default instead of exiting with "Model ... not found". An empty catalog
3348
+ * (probe not concluded) keeps the candidate, matching prior behavior.
3349
+ */
3350
+ pickModel() {
3351
+ const candidate = this.dynamicModel() ?? this.config.model;
3352
+ if (candidate === void 0) return void 0;
3353
+ if (this.catalog.entries.length > 0) {
3354
+ const known = this.catalog.entries.some(
3355
+ (entry) => entry.model === candidate || `${entry.provider}/${entry.model}` === candidate
3356
+ );
3357
+ if (!known) return void 0;
3358
+ }
3359
+ return candidate;
3360
+ }
3340
3361
  spawnSpec(cwd) {
3341
3362
  const argv = [];
3342
- const model = this.dynamicModel() ?? this.config.model;
3363
+ const model = this.pickModel();
3343
3364
  if (this.config.provider !== void 0) argv.push("--provider", this.config.provider);
3344
3365
  if (model !== void 0 && this.config.thinkingLevel !== void 0) {
3345
3366
  argv.push("--model", `${model}:${this.config.thinkingLevel}`);
@@ -3582,6 +3603,8 @@ var PiAgent = class {
3582
3603
  } finally {
3583
3604
  signal.removeEventListener("abort", cancel);
3584
3605
  controller.abort();
3606
+ this.rpc?.dispose();
3607
+ this.rpc = void 0;
3585
3608
  }
3586
3609
  }
3587
3610
  /** Append one Pi tool result to the durable log as a `tool/result` message. */
@@ -3608,24 +3631,30 @@ function waitForExit(child) {
3608
3631
  );
3609
3632
  });
3610
3633
  }
3611
- function collectStdout(child) {
3634
+ function collectOutput(child) {
3612
3635
  return new Promise((resolve5) => {
3613
- let text = "";
3614
- if (child.stdout.on === void 0) {
3636
+ if (child.stdout.on === void 0 || child.stderr.on === void 0) {
3615
3637
  resolve5("");
3616
3638
  return;
3617
3639
  }
3640
+ let text = "";
3641
+ let pending = 2;
3642
+ const finish = () => {
3643
+ pending -= 1;
3644
+ if (pending === 0) resolve5(text);
3645
+ };
3618
3646
  const out = child.stdout;
3647
+ const err = child.stderr;
3619
3648
  out.setEncoding("utf8");
3620
3649
  out.on("data", (data) => {
3621
3650
  text += String(data);
3622
3651
  });
3623
- out.on("end", () => {
3624
- resolve5(text);
3625
- });
3626
- out.on("close", () => {
3627
- resolve5(text);
3652
+ out.on("close", finish);
3653
+ err.setEncoding("utf8");
3654
+ err.on("data", (data) => {
3655
+ text += String(data);
3628
3656
  });
3657
+ err.on("close", finish);
3629
3658
  });
3630
3659
  }
3631
3660
  function parsePiModelList(output) {
@@ -3653,11 +3682,11 @@ async function probePiModels(bin, spawn4) {
3653
3682
  } catch {
3654
3683
  return [];
3655
3684
  }
3656
- const stdoutPromise = collectStdout(child);
3685
+ const outputPromise = collectOutput(child);
3657
3686
  const exitCode = await waitForExit(child);
3658
3687
  if (exitCode !== 0) return [];
3659
- const stdout = await stdoutPromise;
3660
- return parsePiModelList(stdout);
3688
+ const output = await outputPromise;
3689
+ return parsePiModelList(output);
3661
3690
  }
3662
3691
 
3663
3692
  // src/engine-pi/loop.ts
@@ -3730,12 +3759,15 @@ var PiLoop = class extends Service3 {
3730
3759
  spawn;
3731
3760
  /** Resolved Pi CLI entrypoint; `argv[0]` of every Pi RPC child. */
3732
3761
  bin;
3762
+ /** Discovered Pi model catalog, forwarded to each agent so it can validate the session-selected model against what pi can actually serve. */
3763
+ catalog;
3733
3764
  constructor(ctx, config) {
3734
3765
  super(ctx, "agentLoopPi");
3735
3766
  this.config = resolveConfig3(config);
3736
3767
  this.ownership = new FactoryOwnership(ctx.fiber);
3737
3768
  this.runtime = { ctx };
3738
3769
  this.bin = piCliEntrypoint();
3770
+ this.catalog = config.piCatalogHolder ?? { entries: [] };
3739
3771
  this.spawn = (spec) => fromSubprocess(this.runtime.ctx.subprocess.spawn(piSubprocessSpec(spec, PI_DISPOSE_GRACE_MS)));
3740
3772
  const holder = config.piCatalogHolder;
3741
3773
  if (holder !== void 0) {
@@ -3823,7 +3855,16 @@ var PiLoop = class extends Service3 {
3823
3855
  throw abort.signal.reason instanceof Error ? abort.signal.reason : new Error(String(abort.signal.reason));
3824
3856
  };
3825
3857
  try {
3826
- const agent = machine = new PiAgent(loopCtx, id, options, session, this.config, this.spawn, this.bin);
3858
+ const agent = machine = new PiAgent(
3859
+ loopCtx,
3860
+ id,
3861
+ options,
3862
+ session,
3863
+ this.config,
3864
+ this.spawn,
3865
+ this.bin,
3866
+ this.catalog
3867
+ );
3827
3868
  machineReady.resolve();
3828
3869
  assertLive();
3829
3870
  return {
@@ -16,6 +16,7 @@ import type { Scope } from '@deepseek-ai/dsh-scope';
16
16
  import type { Session, SessionId, UserMessage } from '@deepseek-ai/dsh-session';
17
17
  import type { Context } from '@deepseek-ai/cordis';
18
18
  import type { ResolvedConfig } from './types.ts';
19
+ import type { PiModelEntry } from './probe.ts';
19
20
  import { type PiSpawnCapability } from './rpc/client.ts';
20
21
  /** Provider route label used for logged header snapshots and message provenance. */
21
22
  export declare const PROVIDER = "pi";
@@ -28,6 +29,7 @@ export declare class PiAgent implements Agent {
28
29
  private readonly config;
29
30
  private readonly spawn;
30
31
  private readonly bin;
32
+ private readonly catalog;
31
33
  readonly inbox: Inbox;
32
34
  private phase;
33
35
  private activityDone;
@@ -42,7 +44,9 @@ export declare class PiAgent implements Agent {
42
44
  private rpc;
43
45
  /** The spawn spec the cached client was built from; a change forces a respawn. */
44
46
  private lastSpec;
45
- constructor(loopCtx: Context, id: SessionId, options: AgentOptions, session: Session, config: ResolvedConfig, spawn: PiSpawnCapability, bin: string);
47
+ constructor(loopCtx: Context, id: SessionId, options: AgentOptions, session: Session, config: ResolvedConfig, spawn: PiSpawnCapability, bin: string, catalog: {
48
+ readonly entries: readonly PiModelEntry[];
49
+ });
46
50
  /** Return the cached RPC client, respawning when the spec or process changed. */
47
51
  private rpcClient;
48
52
  get status(): AgentStatus;
@@ -118,6 +122,15 @@ export declare class PiAgent implements Agent {
118
122
  */
119
123
  private dynamicModel;
120
124
  /** Build the `pi --mode rpc` argv/cwd/env for one step's child process. */
125
+ /**
126
+ * Resolve the `--model` for the RPC child. The session-selected model (last
127
+ * `model/selection` event) is honored only when it is one of pi's discovered
128
+ * models; an unknown harness model (e.g. another provider's model such as
129
+ * `anyai-v1`, which pi cannot serve) is dropped so the child falls back to pi's
130
+ * own default instead of exiting with "Model ... not found". An empty catalog
131
+ * (probe not concluded) keeps the candidate, matching prior behavior.
132
+ */
133
+ private pickModel;
121
134
  private spawnSpec;
122
135
  /**
123
136
  * Run one Pi RPC query for the current step and map its event stream into the
@@ -70,6 +70,8 @@ export declare class PiLoop extends Service implements AgentFactory {
70
70
  readonly spawn: (spec: PiSpawnSpec) => PiProcess;
71
71
  /** Resolved Pi CLI entrypoint; `argv[0]` of every Pi RPC child. */
72
72
  readonly bin: string;
73
+ /** Discovered Pi model catalog, forwarded to each agent so it can validate the session-selected model against what pi can actually serve. */
74
+ private readonly catalog;
73
75
  constructor(ctx: Context, config: Config);
74
76
  /**
75
77
  * Construct the driver, scope, and one memoized reverse teardown for a new
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-loop-engine",
3
3
  "description": "Web-switchable agent loop engine selection for the DeepSeek Harness - out-of-tree plugin (Claude Code / Codex / Pi / Kimi Code drivers) maintained by @kuun993, zero main-repo changes",
4
- "version": "1.0.0-rc14",
4
+ "version": "1.0.0-rc15",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },