pi2dsh 0.19.0 → 0.20.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.
Files changed (34) hide show
  1. package/README.md +46 -2
  2. package/README.zh.md +26 -0
  3. package/dist/{analyzer-DLwDZoYB.mjs → analyzer-BL7Nfvko.mjs} +2 -2
  4. package/dist/{analyzer-DLwDZoYB.mjs.map → analyzer-BL7Nfvko.mjs.map} +1 -1
  5. package/dist/cli.mjs +3 -2
  6. package/dist/cli.mjs.map +1 -1
  7. package/dist/compat/pi-coding-agent.d.mts +2 -2
  8. package/dist/compat/pi-coding-agent.mjs +2 -2
  9. package/dist/{mcp-config-CyCPWeUs.mjs → compatibility-D_PV1zgF.mjs} +10 -187
  10. package/dist/compatibility-D_PV1zgF.mjs.map +1 -0
  11. package/dist/credentials-oauth.mjs +1 -1
  12. package/dist/host.d.mts +9 -2
  13. package/dist/host.d.mts.map +1 -1
  14. package/dist/host.mjs +7 -2
  15. package/dist/host.mjs.map +1 -1
  16. package/dist/index.d.mts +8 -0
  17. package/dist/index.d.mts.map +1 -1
  18. package/dist/index.mjs +154 -8
  19. package/dist/index.mjs.map +1 -1
  20. package/dist/mcp-config-C1K-IzzB.mjs +180 -0
  21. package/dist/mcp-config-C1K-IzzB.mjs.map +1 -0
  22. package/dist/{pi-coding-agent-DXJ_Jzo0.mjs → pi-coding-agent-CPXFo0UO.mjs} +0 -0
  23. package/dist/pi-coding-agent-CPXFo0UO.mjs.map +1 -0
  24. package/dist/{pi-coding-agent-69yY0ke-.d.mts → pi-coding-agent-Pvy0m9tS.d.mts} +6 -2
  25. package/dist/{pi-coding-agent-69yY0ke-.d.mts.map → pi-coding-agent-Pvy0m9tS.d.mts.map} +1 -1
  26. package/dist/{runtime-CW52OWvW.mjs → runtime-CBZcd1uU.mjs} +476 -18
  27. package/dist/runtime-CBZcd1uU.mjs.map +1 -0
  28. package/dist/runtime.d.mts +66 -40
  29. package/dist/runtime.d.mts.map +1 -1
  30. package/dist/runtime.mjs +2 -2
  31. package/package.json +7 -2
  32. package/dist/mcp-config-CyCPWeUs.mjs.map +0 -1
  33. package/dist/pi-coding-agent-DXJ_Jzo0.mjs.map +0 -1
  34. package/dist/runtime-CW52OWvW.mjs.map +0 -1
@@ -1,15 +1,16 @@
1
1
 
2
- import { En as PiCapabilityError, F as generateBranchSummary, Tn as CapabilityLedger, b as Theme, d as ExtensionRunner, w as __runWithSubagentSessionFactory } from "./pi-coding-agent-DXJ_Jzo0.mjs";
2
+ import { Dn as PiCapabilityError, En as CapabilityLedger, F as generateBranchSummary, b as Theme, d as ExtensionRunner, w as __runWithSubagentSessionFactory } from "./pi-coding-agent-CPXFo0UO.mjs";
3
3
  import { t as getAgentDir } from "./pi-config-shim-CZ1wFzqM.mjs";
4
4
  import { $ as AssistantMessageEventStream, S as getSupportedThinkingLevels, c as builtinProviders, et as isContextOverflow, o as __runWithPiAiRuntime, r as __createPiAiRuntimeRegistry } from "./pi-ai-DQPHUR9S.mjs";
5
5
  import { W as getKeybindings, _t as stripTerminalSequences } from "./pi-tui-DruSeKmd.mjs";
6
+ import { r as piMcpConfigPaths, t as collectPiMcpServers } from "./mcp-config-C1K-IzzB.mjs";
6
7
  import { c as resolvePiProviderAuth, i as oauthCredentialRef, l as storedOAuthCredential, n as joinSignals, o as providerSupportsOAuth, r as loginPiProvider, t as FileCredentialStore } from "./oauth-bridge-C_MAPMGA.mjs";
7
8
  import { createRequire } from "node:module";
8
9
  import { access, mkdir, readFile, writeFile } from "node:fs/promises";
9
10
  import { appendFileSync, existsSync, mkdirSync, readFileSync, unlinkSync } from "node:fs";
10
11
  import { basename, dirname, join, resolve } from "node:path";
11
12
  import { fileURLToPath } from "node:url";
12
- import { tmpdir } from "node:os";
13
+ import { homedir, tmpdir } from "node:os";
13
14
  import { EventEmitter } from "node:events";
14
15
  import { AsyncLocalStorage } from "node:async_hooks";
15
16
  import { createJiti } from "jiti";
@@ -912,7 +913,21 @@ var PiBridgedAgentSession = class {
912
913
  this.#restrictionDispose = toolsService.restrict({ allow: this.#activeToolNames.map(nativeToolNameOf).filter((name) => known.has(name)) });
913
914
  } catch {}
914
915
  }
915
- async bindExtensions(_bindings = {}) {}
916
+ /** Per-extension mount failures recorded at creation, for bindExtensions' onError. */
917
+ #extensionFailures = [];
918
+ adoptExtensionFailures(failures) {
919
+ this.#extensionFailures = failures;
920
+ }
921
+ async bindExtensions(bindings = {}) {
922
+ const onError = bindings.onError;
923
+ if (typeof onError !== "function") return;
924
+ for (const failure of this.#extensionFailures) try {
925
+ onError({
926
+ extensionPath: failure.name,
927
+ error: failure.error
928
+ });
929
+ } catch {}
930
+ }
916
931
  async dispose() {
917
932
  for (const dispose of this.#disposers.splice(0)) try {
918
933
  dispose();
@@ -1068,8 +1083,23 @@ async function createBridgedAgentSession(host, options) {
1068
1083
  }
1069
1084
  const thinkingLevel = typeof options.thinkingLevel === "string" ? options.thinkingLevel : void 0;
1070
1085
  host.adoptChildAgent?.(handle.agent, thinkingLevel);
1086
+ let extensionFailures = [];
1087
+ try {
1088
+ extensionFailures = await host.mountChildExtensions?.(handle.agent, options.resourceLoader) ?? [];
1089
+ } catch (error) {
1090
+ extensionFailures = [{
1091
+ name: "pi2dsh:child-extensions",
1092
+ error: error instanceof Error ? error.message : String(error)
1093
+ }];
1094
+ }
1095
+ for (const failure of extensionFailures) {
1096
+ const message = `[pi2dsh] child extension ${JSON.stringify(failure.name)} did not mount: ${failure.error}`;
1097
+ host.cordis.logger?.warn?.(message);
1098
+ console.warn(message);
1099
+ }
1071
1100
  const session = new PiBridgedAgentSession(host, handle, tools, thinkingLevel);
1072
1101
  session.adoptRestriction(initialRestrictionDispose);
1102
+ session.adoptExtensionFailures(extensionFailures);
1073
1103
  return { session };
1074
1104
  }
1075
1105
  //#endregion
@@ -1150,6 +1180,7 @@ var BrowserSurfaces = class {
1150
1180
  if (typeof component?.render !== "function") {
1151
1181
  this.#scene = void 0;
1152
1182
  this.#invalidateScene();
1183
+ console.warn(`[pi2dsh] web scene for ${packageName}: component lacks render(width)`);
1153
1184
  reject(/* @__PURE__ */ new TypeError("Pi custom component must implement render(width)"));
1154
1185
  return;
1155
1186
  }
@@ -1159,6 +1190,7 @@ var BrowserSurfaces = class {
1159
1190
  if (this.#scene !== run) return;
1160
1191
  this.#scene = void 0;
1161
1192
  this.#invalidateScene();
1193
+ console.warn(`[pi2dsh] web scene for ${packageName}: component factory failed: ${error instanceof Error ? error.stack ?? error.message : String(error)}`);
1162
1194
  reject(error);
1163
1195
  });
1164
1196
  });
@@ -1179,6 +1211,7 @@ var BrowserSurfaces = class {
1179
1211
  const failed = this.#scene;
1180
1212
  this.#scene = void 0;
1181
1213
  this.#invalidateScene();
1214
+ console.warn(`[pi2dsh] web scene for ${scene.package}: render(width) failed: ${error instanceof Error ? error.stack ?? error.message : String(error)}`);
1182
1215
  failed?.reject(error);
1183
1216
  return {
1184
1217
  open: false,
@@ -1535,7 +1568,7 @@ function publishAuthorization(url) {
1535
1568
  function revokeAuthorization(path) {
1536
1569
  pendingAuthorizations.delete(path.slice(18));
1537
1570
  }
1538
- function registerBrowserSurfaceRoute(ctx, registry) {
1571
+ function registerBrowserSurfaceRoute(ctx, registry, hooks) {
1539
1572
  const web = ctx.get("webServer");
1540
1573
  if (web === void 0 || typeof web.register !== "function") return false;
1541
1574
  ctx.effect(() => web.register({
@@ -1560,6 +1593,47 @@ function registerBrowserSurfaceRoute(ctx, registry) {
1560
1593
  response.end();
1561
1594
  return;
1562
1595
  }
1596
+ if (url.pathname === "/pi2dsh/mcp-state" && (method === "GET" || method === "HEAD")) {
1597
+ if (hooks?.mcpState === void 0) {
1598
+ response.writeHead(404);
1599
+ response.end();
1600
+ return;
1601
+ }
1602
+ const body = JSON.stringify(await hooks.mcpState(url.searchParams.get("session") ?? ""));
1603
+ response.writeHead(200, {
1604
+ "content-type": "application/json; charset=utf-8",
1605
+ "cache-control": "no-store"
1606
+ });
1607
+ response.end(method === "HEAD" ? void 0 : body);
1608
+ return;
1609
+ }
1610
+ if (url.pathname === "/pi2dsh/mcp-action" && method === "POST") {
1611
+ if (hooks?.mcpAction === void 0) {
1612
+ response.writeHead(404);
1613
+ response.end();
1614
+ return;
1615
+ }
1616
+ const chunks = [];
1617
+ const raw = await new Promise((settle) => {
1618
+ const stream = req;
1619
+ stream.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
1620
+ stream.on("end", () => settle(Buffer.concat(chunks).toString("utf8")));
1621
+ });
1622
+ let outcome;
1623
+ try {
1624
+ const payload = JSON.parse(raw || "{}");
1625
+ if (typeof payload.session !== "string" || typeof payload.server !== "string" || typeof payload.disabled !== "boolean") throw new TypeError("mcp-action needs { session, server, disabled }");
1626
+ const scope = payload.scope === "global" ? "global" : "project";
1627
+ outcome = await hooks.mcpAction(payload.session, payload.server, payload.disabled, scope);
1628
+ } catch (error) {
1629
+ response.writeHead(400, { "content-type": "application/json; charset=utf-8" });
1630
+ response.end(JSON.stringify({ error: error instanceof Error ? error.message : String(error) }));
1631
+ return;
1632
+ }
1633
+ response.writeHead(200, { "content-type": "application/json; charset=utf-8" });
1634
+ response.end(JSON.stringify(outcome ?? { ok: true }));
1635
+ return;
1636
+ }
1563
1637
  if (method === "POST" && (url.pathname === "/pi2dsh/scene-input" || url.pathname === "/pi2dsh/scene-close")) {
1564
1638
  const chunks = [];
1565
1639
  const body = await new Promise((settle) => {
@@ -3112,7 +3186,7 @@ function registerPiProviderRoute(options) {
3112
3186
  }
3113
3187
  //#endregion
3114
3188
  //#region src/tui-surfaces.ts
3115
- function service(ctx, name) {
3189
+ function service$1(ctx, name) {
3116
3190
  try {
3117
3191
  return ctx.get(name, false);
3118
3192
  } catch {
@@ -3159,6 +3233,8 @@ function preferredWidth(options) {
3159
3233
  var TuiSurfaceAdapter = class {
3160
3234
  packageName;
3161
3235
  instanceKey;
3236
+ kind = "tuiScenes";
3237
+ nativeCommands = dshTuiLocalCommands();
3162
3238
  sceneId;
3163
3239
  scenes;
3164
3240
  status;
@@ -3171,10 +3247,10 @@ var TuiSurfaceAdapter = class {
3171
3247
  constructor(ctx, packageName, instanceKey) {
3172
3248
  this.packageName = packageName;
3173
3249
  this.instanceKey = instanceKey;
3174
- const scenes = service(ctx, "tuiScenes");
3250
+ const scenes = service$1(ctx, "tuiScenes");
3175
3251
  if (scenes === void 0) throw new Error("pi2dsh: dsh-TUI scene service is unavailable");
3176
3252
  this.scenes = scenes;
3177
- this.status = service(ctx, "tuiStatus");
3253
+ this.status = service$1(ctx, "tuiStatus");
3178
3254
  this.sceneId = sceneIdFor(instanceKey === void 0 ? packageName : `${packageName}-${instanceKey}`);
3179
3255
  this.unregisterScene = scenes.register({
3180
3256
  id: this.sceneId,
@@ -3307,7 +3383,7 @@ function mountTuiSurfaceAdapter(ctx, packageName, publish, instanceKey) {
3307
3383
  publish(void 0);
3308
3384
  });
3309
3385
  };
3310
- if (service(ctx, "tuiScenes") !== void 0) {
3386
+ if (service$1(ctx, "tuiScenes") !== void 0) {
3311
3387
  attach(ctx);
3312
3388
  return;
3313
3389
  }
@@ -3400,8 +3476,252 @@ function dshTuiLocalCommands() {
3400
3476
  cachedTuiLocalCommands = tuiLocalCommandsForVersion(version);
3401
3477
  return cachedTuiLocalCommands;
3402
3478
  }
3403
- function commandNameForDshTui(name, tuiAvailable) {
3404
- return tuiAvailable && dshTuiLocalCommands().has(name) ? `pi-${name}` : name;
3479
+ /** Host-native command ownership, independent of the concrete TUI package. */
3480
+ function commandNameForTerminal(name, nativeCommands) {
3481
+ return nativeCommands?.has(name) === true ? `pi-${name}` : name;
3482
+ }
3483
+ /** Bridge fallback commands disappear when the active Host already owns one. */
3484
+ function shouldRegisterCompatibilityCommand(name, nativeCommands) {
3485
+ return nativeCommands?.has(name) !== true;
3486
+ }
3487
+ //#endregion
3488
+ //#region src/pi-component-relay.ts
3489
+ let relaySerial = 0;
3490
+ function relayId() {
3491
+ relaySerial += 1;
3492
+ return `pi2dsh-custom-${relaySerial}`;
3493
+ }
3494
+ /**
3495
+ * One Host-owned Pi component session expressed as data messages.
3496
+ *
3497
+ * The result value never crosses the presentation boundary: Pi's own `done`
3498
+ * callback completes it on the Host. Frames and input are deliberately plain
3499
+ * data so the presentation transport can change independently.
3500
+ */
3501
+ var PiComponentRelay = class {
3502
+ sessionId = relayId();
3503
+ options;
3504
+ ready;
3505
+ result;
3506
+ component;
3507
+ invalidations = /* @__PURE__ */ new Set();
3508
+ resolveResult;
3509
+ rejectResult;
3510
+ revision = 0;
3511
+ closed = false;
3512
+ constructor(factory, theme, keybindings, options) {
3513
+ this.options = options;
3514
+ this.result = new Promise((resolve, reject) => {
3515
+ this.resolveResult = resolve;
3516
+ this.rejectResult = reject;
3517
+ });
3518
+ const driver = { requestRender: () => this.invalidate() };
3519
+ const done = (value) => this.close(value);
3520
+ this.ready = Promise.resolve().then(() => factory(driver, theme, keybindings, done)).then((component) => {
3521
+ if (this.closed) {
3522
+ component.dispose?.();
3523
+ return false;
3524
+ }
3525
+ if (typeof component?.render !== "function") throw new TypeError("Pi custom component must implement render(width)");
3526
+ this.component = component;
3527
+ this.invalidate();
3528
+ return true;
3529
+ }).catch((error) => {
3530
+ this.fail(error);
3531
+ return false;
3532
+ });
3533
+ }
3534
+ get active() {
3535
+ return !this.closed;
3536
+ }
3537
+ onInvalidate(listener) {
3538
+ this.invalidations.add(listener);
3539
+ return () => this.invalidations.delete(listener);
3540
+ }
3541
+ frame(request) {
3542
+ if (request.sessionId !== this.sessionId) throw new Error("stale Pi component render session");
3543
+ const width = Math.max(1, Math.floor(request.width));
3544
+ return {
3545
+ sessionId: this.sessionId,
3546
+ revision: this.revision,
3547
+ width,
3548
+ lines: this.component?.render(width) ?? []
3549
+ };
3550
+ }
3551
+ input(message) {
3552
+ if (message.sessionId !== this.sessionId || this.closed) return;
3553
+ this.component?.handleInput?.(message.data);
3554
+ }
3555
+ invalidate() {
3556
+ if (this.closed) return;
3557
+ this.revision += 1;
3558
+ for (const listener of this.invalidations) listener(this.revision);
3559
+ }
3560
+ close(value) {
3561
+ if (this.closed) return;
3562
+ this.closed = true;
3563
+ const component = this.component;
3564
+ this.component = void 0;
3565
+ component?.dispose?.();
3566
+ this.invalidations.clear();
3567
+ this.resolveResult(value);
3568
+ }
3569
+ fail(error) {
3570
+ if (this.closed) return;
3571
+ this.closed = true;
3572
+ const component = this.component;
3573
+ this.component = void 0;
3574
+ component?.dispose?.();
3575
+ this.invalidations.clear();
3576
+ this.rejectResult(error);
3577
+ }
3578
+ };
3579
+ //#endregion
3580
+ //#region src/pi-tui-extension-surface.ts
3581
+ function service(ctx) {
3582
+ try {
3583
+ return ctx.get("piTuiExtensions", false);
3584
+ } catch {
3585
+ return;
3586
+ }
3587
+ }
3588
+ function statusId(packageName, instanceKey, key) {
3589
+ return `pi2dsh-status-${`${packageName}${instanceKey === void 0 ? "" : `-${instanceKey}`}-${key}`.toLowerCase().replace(/[^a-z0-9_-]+/gu, "-").replace(/^-+|-+$/gu, "") || "entry"}`;
3590
+ }
3591
+ function overlayOptions(options) {
3592
+ const raw = typeof options?.overlayOptions === "function" ? options.overlayOptions() : options?.overlayOptions;
3593
+ return raw === void 0 ? void 0 : { ...raw };
3594
+ }
3595
+ /** dsh-pi-tui backend over the public `piTuiExtensions` service. */
3596
+ var PiTuiExtensionSurfaceAdapter = class {
3597
+ extensionService;
3598
+ unstableFacade;
3599
+ packageName;
3600
+ instanceKey;
3601
+ kind = "piTuiExtensions";
3602
+ nativeCommands;
3603
+ statusHandles = /* @__PURE__ */ new Map();
3604
+ mounts = /* @__PURE__ */ new Set();
3605
+ disposed = false;
3606
+ constructor(extensionService, unstableFacade, nativeCommands, packageName, instanceKey) {
3607
+ this.extensionService = extensionService;
3608
+ this.unstableFacade = unstableFacade;
3609
+ this.packageName = packageName;
3610
+ this.instanceKey = instanceKey;
3611
+ this.nativeCommands = new Set(nativeCommands);
3612
+ }
3613
+ get available() {
3614
+ if (this.disposed) return false;
3615
+ try {
3616
+ return this.extensionService.api().capabilities.has("unstable.surface.handle");
3617
+ } catch {
3618
+ return false;
3619
+ }
3620
+ }
3621
+ setStatus(key, text) {
3622
+ if (this.disposed) return;
3623
+ const id = statusId(this.packageName, this.instanceKey, key);
3624
+ const previous = this.statusHandles.get(id);
3625
+ if (text === void 0) {
3626
+ previous?.dispose();
3627
+ this.statusHandles.delete(id);
3628
+ return;
3629
+ }
3630
+ const value = {
3631
+ spans: [{
3632
+ text: String(text),
3633
+ tone: "textDim"
3634
+ }],
3635
+ minWidth: 4,
3636
+ importance: 50
3637
+ };
3638
+ if (previous !== void 0) {
3639
+ previous.replace(value);
3640
+ return;
3641
+ }
3642
+ if (!this.extensionService.api().capabilities.has("slot.chrome.footer.status")) return;
3643
+ this.statusHandles.set(id, this.extensionService.register("chrome.footer.status", {
3644
+ id,
3645
+ order: 500,
3646
+ description: `Pi status ${key} from ${this.packageName}`
3647
+ }, value));
3648
+ }
3649
+ async custom(factory, theme, keybindings, options) {
3650
+ if (!this.available) return void 0;
3651
+ for (const active of [...this.mounts]) active.relay.close(void 0);
3652
+ const relay = new PiComponentRelay(factory, theme, keybindings, options);
3653
+ const record = { relay };
3654
+ this.mounts.add(record);
3655
+ try {
3656
+ if (!await relay.ready || !relay.active) return await relay.result;
3657
+ if (this.disposed) {
3658
+ relay.close(void 0);
3659
+ return await relay.result;
3660
+ }
3661
+ const lease = this.unstableFacade.surface.handle.mountComponent({
3662
+ render: (width) => relay.frame({
3663
+ sessionId: relay.sessionId,
3664
+ width
3665
+ }).lines,
3666
+ handleInput: (data) => relay.input({
3667
+ sessionId: relay.sessionId,
3668
+ data
3669
+ }),
3670
+ dispose: () => relay.close(void 0)
3671
+ }, overlayOptions(options));
3672
+ record.lease = lease;
3673
+ const offInvalidate = relay.onInvalidate(() => lease.invalidate());
3674
+ const release = () => {
3675
+ offInvalidate();
3676
+ lease.close();
3677
+ };
3678
+ relay.result.then(release, release);
3679
+ if (!lease.active) relay.close(void 0);
3680
+ lease.focus();
3681
+ return await relay.result;
3682
+ } catch (error) {
3683
+ relay.fail(error);
3684
+ throw error;
3685
+ } finally {
3686
+ this.mounts.delete(record);
3687
+ }
3688
+ }
3689
+ dispose() {
3690
+ if (this.disposed) return;
3691
+ this.disposed = true;
3692
+ for (const handle of this.statusHandles.values()) handle.dispose();
3693
+ this.statusHandles.clear();
3694
+ for (const active of this.mounts) {
3695
+ active.relay.close(void 0);
3696
+ active.lease?.close();
3697
+ }
3698
+ this.mounts.clear();
3699
+ }
3700
+ };
3701
+ /**
3702
+ * Build the optional backend from published public entry points.
3703
+ * Returns undefined when the service is absent or cannot provide the exact
3704
+ * raw-component capability; callers then keep the existing dsh-TUI/Web path.
3705
+ */
3706
+ async function createPiTuiExtensionSurfaceAdapter(ctx, packageName, instanceKey) {
3707
+ const extensionService = service(ctx);
3708
+ if (extensionService === void 0) return void 0;
3709
+ const api = extensionService.api();
3710
+ if (api.apiVersion !== 1 || !api.capabilities.has("unstable.surface.handle")) {
3711
+ ctx.logger?.warn(`pi2dsh: piTuiExtensions cannot host Pi components (api=${api.apiVersion}, raw surface=${api.capabilities.has("unstable.surface.handle")})`);
3712
+ return;
3713
+ }
3714
+ try {
3715
+ const [hostModule, unstableModule] = await Promise.all([import("@xmoon76/dsh-pi-tui"), import("@xmoon76/dsh-pi-tui/extensions/unstable")]);
3716
+ if (unstableModule.UNSTABLE_API_LEVEL !== 1) {
3717
+ ctx.logger?.warn(`pi2dsh: unsupported dsh-pi-tui unstable API level ${unstableModule.UNSTABLE_API_LEVEL}`);
3718
+ return;
3719
+ }
3720
+ return new PiTuiExtensionSurfaceAdapter(extensionService, unstableModule.unstable(extensionService), hostModule.LOCAL_COMMANDS, packageName, instanceKey);
3721
+ } catch (error) {
3722
+ ctx.logger?.warn(`pi2dsh: could not attach the public piTuiExtensions backend: ${error instanceof Error ? error.message : String(error)}`);
3723
+ return;
3724
+ }
3405
3725
  }
3406
3726
  //#endregion
3407
3727
  //#region src/runtime.ts
@@ -4120,7 +4440,8 @@ function contextFor(ctx, state, agent, signal, command = false, sessionOverride)
4120
4440
  },
4121
4441
  ui,
4122
4442
  get mode() {
4123
- return state.tuiSurfaces?.available === true ? "tui" : "rpc";
4443
+ if (state.tuiSurfaces?.available === true) return "tui";
4444
+ return state.shared.browserSurfaces !== void 0 && state.shared.browserSurfacesRouted === true ? "tui" : "rpc";
4124
4445
  },
4125
4446
  get hasUI() {
4126
4447
  return state.tuiSurfaces?.available === true || humanAnswererAvailable(userQuestions, agent);
@@ -5234,6 +5555,59 @@ function oauthStoreOf(state) {
5234
5555
  }
5235
5556
  const COMPANION_ROUTE_PREFIX = "pi2dsh-companion/";
5236
5557
  const SHARED_HOST_STATE = /* @__PURE__ */ new WeakMap();
5558
+ function registerChildExtensionCatalog(ctx, catalog) {
5559
+ sharedHostStateOf(ctx).childExtensions = catalog;
5560
+ }
5561
+ /** Read the installed-package catalog registered on shared host state, or undefined. */
5562
+ function getSharedChildExtensionCatalog(ctx) {
5563
+ return sharedHostStateOf(ctx).childExtensions;
5564
+ }
5565
+ /**
5566
+ * Which installed packages a creator's resource loader selects for its child
5567
+ * — the creator's OWN filter code decides (the loader's getExtensions applies
5568
+ * noExtensions and extensionsOverride), this only maps the surviving entry
5569
+ * paths back to package names. No loader at all means Pi's default: the full
5570
+ * discovered set (sdk.ts builds a default loader and loads everything).
5571
+ */
5572
+ function resolveChildExtensionPackages(loader, catalog) {
5573
+ if (loader === void 0 || loader === null) return {
5574
+ names: [...new Set(catalog.packageByEntryPath.values())],
5575
+ failures: []
5576
+ };
5577
+ const getExtensions = loader.getExtensions;
5578
+ if (typeof getExtensions !== "function") return {
5579
+ names: [],
5580
+ failures: []
5581
+ };
5582
+ const names = /* @__PURE__ */ new Set();
5583
+ const failures = [];
5584
+ let entries;
5585
+ try {
5586
+ entries = getExtensions.call(loader)?.extensions ?? [];
5587
+ } catch (error) {
5588
+ return {
5589
+ names: [],
5590
+ failures: [{
5591
+ name: "resourceLoader.getExtensions",
5592
+ error: error instanceof Error ? error.message : String(error)
5593
+ }]
5594
+ };
5595
+ }
5596
+ for (const entry of entries) {
5597
+ const path = entry?.path;
5598
+ if (typeof path !== "string" || path.length === 0) continue;
5599
+ const name = catalog.packageByEntryPath.get(resolve(path));
5600
+ if (name !== void 0) names.add(name);
5601
+ else failures.push({
5602
+ name: path,
5603
+ error: "not an installed DSH plugin — path-loaded extensions are not supported on this host; install it with `dsh plugin add <pkg>`"
5604
+ });
5605
+ }
5606
+ return {
5607
+ names: [...names],
5608
+ failures
5609
+ };
5610
+ }
5237
5611
  /**
5238
5612
  * Pi's provider ledger is layered, not flat (model-runtime.ts:742-786 at the
5239
5613
  * pinned upstream): a builtin base map, an extension overlay where defined
@@ -5494,6 +5868,10 @@ async function materializeAttachmentImage(ctx, attachment) {
5494
5868
  }
5495
5869
  }
5496
5870
  function ensureLoginCommand(ctx, state) {
5871
+ if (!shouldRegisterCompatibilityCommand("login", state.tuiSurfaces?.nativeCommands) && optionalService(ctx, "authorization") !== void 0) {
5872
+ logger(ctx).debug("[pi2dsh] native terminal owns /login and the authorization service is composed; Pi OAuth flows are available through it");
5873
+ return;
5874
+ }
5497
5875
  const scoped = scopeOf(ctx);
5498
5876
  const owner = typeof scoped === "object" && scoped !== null ? scoped : ctx;
5499
5877
  const registered = state.shared.loginRegistered ??= /* @__PURE__ */ new WeakSet();
@@ -6279,9 +6657,9 @@ function normalizedPiCommandName(piName) {
6279
6657
  }
6280
6658
  function dshCommandName(ctx, state, piName) {
6281
6659
  const validName = normalizedPiCommandName(piName);
6282
- const name = commandNameForDshTui(validName, state.tuiSurfaces?.available === true || optionalService(ctx, "tuiScenes") !== void 0);
6660
+ const name = commandNameForTerminal(validName, state.tuiSurfaces?.nativeCommands);
6283
6661
  if (name !== piName) {
6284
- const reason = name === `pi-${validName}` ? `because dsh-TUI reserves /${validName} for its own local command (locals win on name collisions)` : "to satisfy DSH command naming";
6662
+ const reason = name === `pi-${validName}` ? `because the active terminal reserves /${validName} for its native command (host commands win on collisions)` : "to satisfy DSH command naming";
6285
6663
  logger(ctx).warn(`[pi2dsh] Pi command /${piName} registered as /${name} ${reason}`);
6286
6664
  }
6287
6665
  return name;
@@ -6767,6 +7145,12 @@ async function applyPiPackage(ctx, options) {
6767
7145
  const shared = sharedHostStateOf(ctx);
6768
7146
  const scopedOwner = options.ownerAgent ?? scopeOf(ctx);
6769
7147
  const ownerAgent = typeof scopedOwner === "object" && scopedOwner !== null ? scopedOwner : void 0;
7148
+ if (ownerAgent !== void 0 && options.hostAnchor !== true) {
7149
+ shared.childPackageTools ??= /* @__PURE__ */ new WeakMap();
7150
+ const byPackage = shared.childPackageTools.get(ownerAgent) ?? /* @__PURE__ */ new Map();
7151
+ byPackage.set(options.manifest.package.name, runtimeTools);
7152
+ shared.childPackageTools.set(ownerAgent, byPackage);
7153
+ }
6770
7154
  const state = {
6771
7155
  shared,
6772
7156
  packageName: options.manifest.package.name,
@@ -6829,9 +7213,17 @@ async function applyPiPackage(ctx, options) {
6829
7213
  publishedOAuthKeys: shared.publishedOAuthKeys,
6830
7214
  tuiSurfaces: void 0
6831
7215
  };
6832
- mountTuiSurfaceAdapter(ctx, state.packageName, (adapter) => {
7216
+ const surfaceInstanceKey = typeof ownerAgent?.id === "string" ? ownerAgent.id : void 0;
7217
+ const piTuiSurface = await createPiTuiExtensionSurfaceAdapter(ctx, state.packageName, surfaceInstanceKey);
7218
+ if (piTuiSurface !== void 0) {
7219
+ state.tuiSurfaces = piTuiSurface;
7220
+ ctx.effect(() => () => {
7221
+ piTuiSurface.dispose();
7222
+ if (state.tuiSurfaces === piTuiSurface) state.tuiSurfaces = void 0;
7223
+ });
7224
+ } else mountTuiSurfaceAdapter(ctx, state.packageName, (adapter) => {
6833
7225
  state.tuiSurfaces = adapter;
6834
- }, typeof ownerAgent?.id === "string" ? ownerAgent.id : void 0);
7226
+ }, surfaceInstanceKey);
6835
7227
  subscribeLifecycle(ctx, state);
6836
7228
  subscribeInterceptors(ctx, state);
6837
7229
  shared.providerBuiltins ??= /* @__PURE__ */ new Map();
@@ -6885,7 +7277,57 @@ async function applyPiPackage(ctx, options) {
6885
7277
  });
6886
7278
  }
6887
7279
  const browserSurfaces = state.shared.browserSurfaces ??= new BrowserSurfaces();
6888
- if (state.shared.browserSurfacesRouted !== true) state.shared.browserSurfacesRouted = registerBrowserSurfaceRoute(ctx, browserSurfaces);
7280
+ if (state.shared.browserSurfacesRouted !== true) {
7281
+ const sessionCwd = (session) => {
7282
+ const cwd = ctx.get("sessions")?.get?.(session)?.header?.cwd;
7283
+ if (typeof cwd === "string" && cwd.length > 0) return cwd;
7284
+ return session === "" ? homedir() : process.cwd();
7285
+ };
7286
+ const globalLayerPaths = (cwd) => new Set(piMcpConfigPaths(cwd).slice(0, 4));
7287
+ state.shared.browserSurfacesRouted = registerBrowserSurfaceRoute(ctx, browserSurfaces, {
7288
+ async mcpState(session) {
7289
+ const cwd = sessionCwd(session);
7290
+ const globals = globalLayerPaths(cwd);
7291
+ const { servers, sources } = collectPiMcpServers(cwd);
7292
+ return {
7293
+ cwd,
7294
+ sources,
7295
+ servers: [...servers.values()].map((server) => ({
7296
+ name: server.name,
7297
+ transport: server.transport,
7298
+ target: server.transport === "stdio" ? [server.command, ...server.args ?? []].filter(Boolean).join(" ") : server.url ?? "",
7299
+ disabled: server.disabled,
7300
+ sourcePath: server.sourcePath,
7301
+ layer: globals.has(server.sourcePath) ? "global" : "project",
7302
+ envKeys: Object.keys(server.env ?? {}),
7303
+ headerKeys: Object.keys(server.headers ?? {})
7304
+ }))
7305
+ };
7306
+ },
7307
+ async mcpAction(session, server, disabled, scope) {
7308
+ const cwd = sessionCwd(session);
7309
+ const { servers } = collectPiMcpServers(cwd);
7310
+ if (!servers.has(server)) throw new TypeError(`unknown MCP server ${JSON.stringify(server)}`);
7311
+ const overridePath = scope === "global" ? [...globalLayerPaths(cwd)][3] : join(cwd, ".pi", "mcp.json");
7312
+ let current = {};
7313
+ try {
7314
+ current = JSON.parse(await readFile(overridePath, "utf8"));
7315
+ } catch {}
7316
+ const mcpServers = typeof current.mcpServers === "object" && current.mcpServers !== null ? current.mcpServers : {};
7317
+ mcpServers[server] = {
7318
+ ...typeof mcpServers[server] === "object" && mcpServers[server] !== null ? mcpServers[server] : {},
7319
+ disabled
7320
+ };
7321
+ current.mcpServers = mcpServers;
7322
+ await mkdir(dirname(overridePath), { recursive: true });
7323
+ await writeFile(overridePath, `${JSON.stringify(current, null, 2)}\n`);
7324
+ return {
7325
+ ok: true,
7326
+ note: "run /reload (or restart the session) to apply"
7327
+ };
7328
+ }
7329
+ });
7330
+ }
6889
7331
  ctx.effect(() => browserSurfaces.trackCompletions(state.packageName ?? "pi", async (trigger, query) => {
6890
7332
  if (state.autocompleteProviders.length === 0) return [];
6891
7333
  let provider = { getSuggestions: async () => null };
@@ -6995,6 +7437,21 @@ async function applyPiPackage(ctx, options) {
6995
7437
  return state.bridge.readonlySessionManager(session, cwd);
6996
7438
  },
6997
7439
  resumeSessionIdFor: (file) => state.bridge.sessionIdOfArchiveFile(file),
7440
+ mountChildExtensions: async (childAgent, loader) => {
7441
+ const shared = sharedHostStateOf(ctx);
7442
+ const catalog = shared.childExtensions;
7443
+ if (catalog === void 0) return [];
7444
+ const { names, failures } = resolveChildExtensionPackages(loader, catalog);
7445
+ if (names.length === 0) return failures;
7446
+ const mountFailures = await catalog.mount(childAgent, names);
7447
+ const attachable = loader;
7448
+ if (typeof attachable?.attachChildToolResolver === "function") attachable.attachChildToolResolver((path) => {
7449
+ const packageName = catalog.packageByEntryPath.get(resolve(path));
7450
+ if (packageName === void 0) return void 0;
7451
+ return shared.childPackageTools?.get(childAgent)?.get(packageName);
7452
+ });
7453
+ return [...failures, ...mountFailures];
7454
+ },
6998
7455
  sessionGoneFromPersistence: async (sessionId) => {
6999
7456
  const persistence = optionalService(ctx, "sessionPersistence");
7000
7457
  if (typeof persistence?.list !== "function") return void 0;
@@ -7063,6 +7520,7 @@ const runtimeInternals = {
7063
7520
  currentPiModel,
7064
7521
  lastRequestRouteOf,
7065
7522
  resolveCallerRoute,
7523
+ resolveChildExtensionPackages,
7066
7524
  dshToPiContent,
7067
7525
  expandPrompt,
7068
7526
  isKnownImageTool,
@@ -7073,6 +7531,6 @@ const runtimeInternals = {
7073
7531
  textBlocks
7074
7532
  };
7075
7533
  //#endregion
7076
- export { registerVisionCompanions as a, overlayProviderConfig as i, mergeProviderRegistration as n, runtimeInternals as o, normalizeToolSchema as r, applyPiPackage as t };
7534
+ export { overlayProviderConfig as a, runtimeInternals as c, normalizeToolSchema as i, getSharedChildExtensionCatalog as n, registerChildExtensionCatalog as o, mergeProviderRegistration as r, registerVisionCompanions as s, applyPiPackage as t };
7077
7535
 
7078
- //# sourceMappingURL=runtime-CW52OWvW.mjs.map
7536
+ //# sourceMappingURL=runtime-CBZcd1uU.mjs.map