pi2dsh 0.14.0 → 0.15.0

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.
@@ -4025,9 +4025,7 @@ function subscribeLifecycle(ctx, state) {
4025
4025
  }
4026
4026
  }
4027
4027
  });
4028
- cordis.on("session/event", (session, event) => {
4029
- const ownedSession = agentSession(state.ownerAgent);
4030
- if (ownedSession !== void 0 ? session !== ownedSession : isSubagentOrigin(session)) return;
4028
+ const projectSessionEvent = (session, event) => {
4031
4029
  const agent = [...state.activeAgents].find((candidate) => candidate.session === session);
4032
4030
  const eventContext = contextFor(ctx, state, agent, void 0);
4033
4031
  const type = event.type;
@@ -4174,6 +4172,14 @@ function subscribeLifecycle(ctx, state) {
4174
4172
  }, eventContext);
4175
4173
  await dispatch(state, "agent_settled", { type: "agent_settled" }, eventContext);
4176
4174
  }).catch((error) => warn("turn end lifecycle", error));
4175
+ };
4176
+ state.projectSessionEvent = projectSessionEvent;
4177
+ cordis.on("session/event", (session, event) => {
4178
+ if (state.hostAnchor) return;
4179
+ const ownedSession = agentSession(state.ownerAgent);
4180
+ if (ownedSession !== void 0 ? session !== ownedSession : isSubagentOrigin(session)) return;
4181
+ if (ownedSession !== void 0 && state.sessionEventsLive !== true) return;
4182
+ projectSessionEvent(session, event);
4177
4183
  });
4178
4184
  cordis.on("agent/request", async (payload, next) => {
4179
4185
  const config = await next();
@@ -4208,7 +4214,7 @@ function subscribeLifecycle(ctx, state) {
4208
4214
  for (const dispose of state.toolDisposers.values()) dispose();
4209
4215
  state.toolDisposers.clear();
4210
4216
  for (const provider of [...state.ownedProviderRoutes]) releaseSharedProviderRoute(state, provider);
4211
- state.eventBus.removeAllListeners();
4217
+ for (const off of state.eventBusOffs.splice(0)) off();
4212
4218
  }, "pi2dsh session shutdown");
4213
4219
  }
4214
4220
  async function flushPendingSessionStarts(ctx, state) {
@@ -4226,6 +4232,22 @@ async function flushPendingSessionStarts(ctx, state) {
4226
4232
  const pending = [...state.pendingSessionStarts];
4227
4233
  state.pendingSessionStarts.clear();
4228
4234
  for (const [agent, reason] of pending) await startPiSession(ctx, state, agent, reason, void 0);
4235
+ if (state.ownerAgent !== void 0 && state.projectSessionEvent !== void 0) {
4236
+ const session = agentSession(state.ownerAgent);
4237
+ if (session !== void 0) {
4238
+ const events = session.events ?? [];
4239
+ let from = -1;
4240
+ for (let index = events.length - 1; index >= 0; index -= 1) {
4241
+ if (events[index].type === "turn/end") break;
4242
+ if (events[index].type === "turn/start") {
4243
+ from = index;
4244
+ break;
4245
+ }
4246
+ }
4247
+ if (from >= 0) for (let index = from; index < events.length; index += 1) state.projectSessionEvent(session, events[index]);
4248
+ }
4249
+ state.sessionEventsLive = true;
4250
+ }
4229
4251
  }
4230
4252
  /**
4231
4253
  * Start freshly reloaded extension handlers in every live Pi host session.
@@ -4589,15 +4611,113 @@ function oauthStoreOf(state) {
4589
4611
  const COMPANION_ROUTE_PREFIX = "pi2dsh-companion/";
4590
4612
  const SHARED_HOST_STATE = /* @__PURE__ */ new WeakMap();
4591
4613
  /**
4592
- * The engine's own built-in OAuth directory entries, by object identity.
4593
- * They are login placeholders (no models, no transport), preloaded so
4594
- * `/login <provider>` works before any package is installed — and a package
4595
- * registering the SAME provider id owns the richer definition (transport,
4596
- * catalog, its own flow) and must supersede the placeholder in the shared
4597
- * ledger. Two PACKAGES colliding on one id still keep first-wins, matching
4598
- * the route-ownership rule.
4614
+ * Pi's provider ledger is layered, not flat (model-runtime.ts:742-786 at the
4615
+ * pinned upstream): a builtin base map, an extension overlay where defined
4616
+ * fields merge over the base (undefined fields expose the base — a package
4617
+ * overriding only baseUrl keeps the builtin OAuth flow), later registrations
4618
+ * overriding earlier ones, and unregistration restoring the builtin. These
4619
+ * helpers vendor that contract for the shared host ledger. One deliberate
4620
+ * adaptation: the overlay is slotted per PACKAGE and folded in package
4621
+ * first-registration order, because in Pi "last wins" is load order (each
4622
+ * session rebuilds the runtime so registration order is always load order),
4623
+ * while our host ledger accumulates across sessions — folding raw arrival
4624
+ * order would let a later agent's re-registration of package A overturn
4625
+ * package B for no Pi-semantic reason.
4599
4626
  */
4600
- const BUILTIN_PROVIDER_SEEDS = /* @__PURE__ */ new WeakSet();
4627
+ function mergeProviderRegistration(previous, config) {
4628
+ const effective = { ...previous };
4629
+ for (const [key, value] of Object.entries(config)) if (value !== void 0) effective[key] = value;
4630
+ return effective;
4631
+ }
4632
+ function overlayProviderConfig(base, overlay) {
4633
+ const merged = { ...base };
4634
+ for (const [key, value] of Object.entries(overlay)) if (value !== void 0) merged[key] = value;
4635
+ const baseModels = base?.models;
4636
+ if (overlay.models === void 0 && typeof overlay.baseUrl === "string" && Array.isArray(baseModels)) merged.models = baseModels.map((entry) => typeof entry === "object" && entry !== null ? {
4637
+ ...entry,
4638
+ baseUrl: overlay.baseUrl
4639
+ } : entry);
4640
+ return merged;
4641
+ }
4642
+ /** Structural fingerprint used to decide whether a recomposition actually
4643
+ * changed the canonical shape. Function identities differ on every factory
4644
+ * run (each agent instance registers fresh closures), so functions compare
4645
+ * by presence — behaviourally equivalent re-registrations keep the existing
4646
+ * canonical reference and the route built on it, exactly like today. */
4647
+ function providerFingerprint(config) {
4648
+ if (config === void 0) return "absent";
4649
+ return JSON.stringify(config, (_key, value) => typeof value === "function" ? "[function]" : value);
4650
+ }
4651
+ function recordPackageProviderRegistration(shared, packageName, name, value) {
4652
+ shared.providerPackages ??= /* @__PURE__ */ new Map();
4653
+ shared.providerPackageOrder ??= [];
4654
+ if (!shared.providerPackageOrder.includes(packageName)) shared.providerPackageOrder.push(packageName);
4655
+ let slots = shared.providerPackages.get(name);
4656
+ if (slots === void 0) {
4657
+ slots = /* @__PURE__ */ new Map();
4658
+ shared.providerPackages.set(name, slots);
4659
+ }
4660
+ slots.set(packageName, mergeProviderRegistration(slots.get(packageName), value));
4661
+ }
4662
+ /** Rebuild the canonical entry for one provider id from builtin + package
4663
+ * slots. Returns whether the composed shape changed (callers retire the
4664
+ * route on change so it is rebuilt from the new canonical). */
4665
+ function recomposeSharedProvider(shared, name) {
4666
+ const builtin = shared.providerBuiltins?.get(name);
4667
+ const slots = shared.providerPackages?.get(name);
4668
+ let canonical;
4669
+ if (slots === void 0 || slots.size === 0) canonical = builtin;
4670
+ else {
4671
+ canonical = { ...builtin ?? {} };
4672
+ for (const packageName of shared.providerPackageOrder ?? []) {
4673
+ const registration = slots.get(packageName);
4674
+ if (registration !== void 0) canonical = overlayProviderConfig(canonical, registration);
4675
+ }
4676
+ }
4677
+ if (!(providerFingerprint(shared.providers.get(name)) !== providerFingerprint(canonical))) return false;
4678
+ if (canonical === void 0) shared.providers.delete(name);
4679
+ else shared.providers.set(name, canonical);
4680
+ return true;
4681
+ }
4682
+ /** Force-retire a provider's route regardless of refcounts: the canonical
4683
+ * definition changed, so a route built on the old shape must not serve it. */
4684
+ function retireSharedProviderRoute(shared, name) {
4685
+ const route = shared.providerRouteDisposers.get(name);
4686
+ if (route === void 0) return;
4687
+ shared.providerRouteDisposers.delete(name);
4688
+ shared.providerRouteOwners.delete(name);
4689
+ route();
4690
+ }
4691
+ function sharedEventBusFor(shared, ownerAgent) {
4692
+ if (ownerAgent === void 0) {
4693
+ if (shared.hostEventBus === void 0) {
4694
+ shared.hostEventBus = new EventEmitter();
4695
+ shared.hostEventBus.setMaxListeners(0);
4696
+ }
4697
+ return shared.hostEventBus;
4698
+ }
4699
+ shared.agentEventBuses ??= /* @__PURE__ */ new WeakMap();
4700
+ let bus = shared.agentEventBuses.get(ownerAgent);
4701
+ if (bus === void 0) {
4702
+ bus = new EventEmitter();
4703
+ bus.setMaxListeners(0);
4704
+ shared.agentEventBuses.set(ownerAgent, bus);
4705
+ }
4706
+ return bus;
4707
+ }
4708
+ function sharedThemeFor(shared, ownerAgent) {
4709
+ if (ownerAgent === void 0) {
4710
+ shared.hostTheme ??= new Theme();
4711
+ return shared.hostTheme;
4712
+ }
4713
+ shared.agentThemes ??= /* @__PURE__ */ new WeakMap();
4714
+ let theme = shared.agentThemes.get(ownerAgent);
4715
+ if (theme === void 0) {
4716
+ theme = new Theme();
4717
+ shared.agentThemes.set(ownerAgent, theme);
4718
+ }
4719
+ return theme;
4720
+ }
4601
4721
  function sharedHostStateOf(ctx) {
4602
4722
  const key = ctx.root ?? ctx;
4603
4723
  let shared = SHARED_HOST_STATE.get(key);
@@ -5473,7 +5593,7 @@ function registerSharedProviderRoute(ctx, state, name, value) {
5473
5593
  return existing;
5474
5594
  }
5475
5595
  const canonical = shared.providers.get(name) ?? value;
5476
- shared.providers.set(name, canonical);
5596
+ if (!shared.providers.has(name)) shared.providers.set(name, canonical);
5477
5597
  const route = registerPiProviderRoute({
5478
5598
  llm: llmOf(ctx),
5479
5599
  ctx,
@@ -5528,31 +5648,27 @@ function createPiApi(ctx, state) {
5528
5648
  const name = typeof providerOrName === "string" ? providerOrName : String(providerOrName?.id ?? providerOrName?.name ?? "unnamed");
5529
5649
  const value = typeof providerOrName === "string" ? config ?? {} : providerOrName;
5530
5650
  state.providers.set(name, value);
5531
- const sharedExisting = state.shared.providers.get(name);
5532
- if (sharedExisting === void 0 || BUILTIN_PROVIDER_SEEDS.has(sharedExisting)) {
5533
- state.shared.providers.set(name, value);
5534
- if (sharedExisting !== void 0) {
5535
- const stale = state.shared.providerRouteDisposers.get(name);
5536
- if (stale !== void 0) {
5537
- state.shared.providerRouteDisposers.delete(name);
5538
- state.shared.providerRouteOwners.delete(name);
5539
- stale();
5540
- }
5541
- }
5542
- }
5543
- if (providerSupportsOAuth(value)) {
5651
+ recordPackageProviderRegistration(state.shared, state.packageName, name, value);
5652
+ if (recomposeSharedProvider(state.shared, name)) retireSharedProviderRoute(state.shared, name);
5653
+ const canonical = state.shared.providers.get(name) ?? value;
5654
+ if (providerSupportsOAuth(canonical)) {
5544
5655
  ensureLoginCommand(ctx, state);
5545
5656
  logger(ctx).info(`[pi2dsh] Pi provider ${JSON.stringify(name)} supports OAuth — log in with /login ${name}`);
5546
5657
  }
5547
- if (registerSharedProviderRoute(ctx, state, name, value) !== void 0) {
5658
+ if (registerSharedProviderRoute(ctx, state, name, canonical) !== void 0) {
5548
5659
  state.modelCatalog?.refresh();
5549
- if (providerCarriesTransport(value)) logger(ctx).info(`[pi2dsh] Pi provider ${JSON.stringify(name)} registered as a native DSH llm route`);
5550
- } else if (!providerSupportsOAuth(value)) logger(ctx).info(`[pi2dsh] recorded Pi provider ${JSON.stringify(name)}; model calls stay on DSH llm adapters`);
5551
- discoverProviderModels(ctx, state, name, value);
5660
+ if (providerCarriesTransport(canonical)) logger(ctx).info(`[pi2dsh] Pi provider ${JSON.stringify(name)} registered as a native DSH llm route`);
5661
+ } else if (!providerSupportsOAuth(canonical)) logger(ctx).info(`[pi2dsh] recorded Pi provider ${JSON.stringify(name)}; model calls stay on DSH llm adapters`);
5662
+ discoverProviderModels(ctx, state, name, canonical);
5552
5663
  },
5553
5664
  unregisterProvider(name) {
5554
5665
  state.providers.delete(name);
5555
5666
  releaseSharedProviderRoute(state, name);
5667
+ if ((state.shared.providerPackages?.get(name))?.delete(state.packageName) === true && recomposeSharedProvider(state.shared, name)) {
5668
+ retireSharedProviderRoute(state.shared, name);
5669
+ const restored = state.shared.providers.get(name);
5670
+ if (restored !== void 0) registerSharedProviderRoute(ctx, state, name, restored);
5671
+ }
5556
5672
  },
5557
5673
  registerMessageRenderer(customType, renderer) {
5558
5674
  state.messageRenderers.set(customType, renderer);
@@ -5665,7 +5781,11 @@ function createPiApi(ctx, state) {
5665
5781
  Promise.resolve(handler(data)).catch((error) => logger(ctx).warn(`[pi2dsh] package event ${channel} handler failed: ${String(error)}`));
5666
5782
  };
5667
5783
  state.eventBus.on(channel, safeHandler);
5668
- return () => state.eventBus.off(channel, safeHandler);
5784
+ const off = () => {
5785
+ state.eventBus.off(channel, safeHandler);
5786
+ };
5787
+ state.eventBusOffs.push(off);
5788
+ return off;
5669
5789
  }
5670
5790
  }
5671
5791
  };
@@ -5855,13 +5975,14 @@ async function applyPiPackage(ctx, options) {
5855
5975
  pendingSessionStarts: /* @__PURE__ */ new Map(),
5856
5976
  currentSystemPrompt: "",
5857
5977
  messageSource: `pi2dsh:${options.manifest.package.name}`,
5858
- eventBus: new EventEmitter(),
5978
+ eventBus: sharedEventBusFor(shared, ownerAgent),
5979
+ eventBusOffs: [],
5859
5980
  agentScope: new AsyncLocalStorage(),
5860
5981
  llmBridge: void 0,
5861
5982
  piAiRegistry: __createPiAiRuntimeRegistry(),
5862
5983
  subagentSessionFactory: void 0,
5863
5984
  bridge: new PiSessionBridge(),
5864
- theme: new Theme(),
5985
+ theme: sharedThemeFor(shared, ownerAgent),
5865
5986
  shortcuts: /* @__PURE__ */ new Map(),
5866
5987
  messageRenderers: /* @__PURE__ */ new Map(),
5867
5988
  entryRenderers: /* @__PURE__ */ new Map(),
@@ -5892,15 +6013,18 @@ async function applyPiPackage(ctx, options) {
5892
6013
  }, typeof ownerAgent?.id === "string" ? ownerAgent.id : void 0);
5893
6014
  subscribeLifecycle(ctx, state);
5894
6015
  subscribeInterceptors(ctx, state);
6016
+ shared.providerBuiltins ??= /* @__PURE__ */ new Map();
5895
6017
  for (const provider of builtinProviders()) {
5896
6018
  const config = {
5897
6019
  name: provider.name,
5898
6020
  baseUrl: provider.baseUrl,
5899
6021
  oauth: provider.auth.oauth
5900
6022
  };
5901
- BUILTIN_PROVIDER_SEEDS.add(config);
5902
6023
  state.providers.set(provider.id, config);
5903
- if (!shared.providers.has(provider.id)) shared.providers.set(provider.id, config);
6024
+ if (!shared.providerBuiltins.has(provider.id)) {
6025
+ shared.providerBuiltins.set(provider.id, config);
6026
+ recomposeSharedProvider(shared, provider.id);
6027
+ }
5904
6028
  }
5905
6029
  ensureLoginCommand(ctx, state);
5906
6030
  for (const [name, config] of [...state.providers]) try {
@@ -6051,7 +6175,7 @@ async function applyPiPackage(ctx, options) {
6051
6175
  remounts.set(state.packageName, async () => {
6052
6176
  state.extensionsReady = false;
6053
6177
  state.handlers.clear();
6054
- state.eventBus.removeAllListeners();
6178
+ for (const off of state.eventBusOffs.splice(0)) off();
6055
6179
  try {
6056
6180
  await registerPromptCommands(ctx, state, rootDir, options.manifest);
6057
6181
  await mountExtensions();
@@ -6080,6 +6204,6 @@ const runtimeInternals = {
6080
6204
  textBlocks
6081
6205
  };
6082
6206
  //#endregion
6083
- export { runtimeInternals as i, normalizeToolSchema as n, registerVisionCompanions as r, applyPiPackage as t };
6207
+ export { registerVisionCompanions as a, overlayProviderConfig as i, mergeProviderRegistration as n, runtimeInternals as o, normalizeToolSchema as r, applyPiPackage as t };
6084
6208
 
6085
- //# sourceMappingURL=runtime-Bb9Bfk4M.mjs.map
6209
+ //# sourceMappingURL=runtime-BN4E4OtI.mjs.map