pi2dsh 0.18.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 (35) hide show
  1. package/README.md +46 -2
  2. package/README.zh.md +26 -0
  3. package/dist/{analyzer-BcuRfHLG.mjs → analyzer-BL7Nfvko.mjs} +2 -2
  4. package/dist/{analyzer-BcuRfHLG.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/client.js +118 -1
  8. package/dist/compat/pi-coding-agent.d.mts +2 -2
  9. package/dist/compat/pi-coding-agent.mjs +2 -2
  10. package/dist/{mcp-config-NvRpjToH.mjs → compatibility-D_PV1zgF.mjs} +10 -187
  11. package/dist/compatibility-D_PV1zgF.mjs.map +1 -0
  12. package/dist/credentials-oauth.mjs +1 -1
  13. package/dist/host.d.mts +9 -2
  14. package/dist/host.d.mts.map +1 -1
  15. package/dist/host.mjs +7 -2
  16. package/dist/host.mjs.map +1 -1
  17. package/dist/index.d.mts +8 -0
  18. package/dist/index.d.mts.map +1 -1
  19. package/dist/index.mjs +154 -8
  20. package/dist/index.mjs.map +1 -1
  21. package/dist/mcp-config-C1K-IzzB.mjs +180 -0
  22. package/dist/mcp-config-C1K-IzzB.mjs.map +1 -0
  23. package/dist/{pi-coding-agent-DXJ_Jzo0.mjs → pi-coding-agent-CPXFo0UO.mjs} +0 -0
  24. package/dist/pi-coding-agent-CPXFo0UO.mjs.map +1 -0
  25. package/dist/{pi-coding-agent-69yY0ke-.d.mts → pi-coding-agent-Pvy0m9tS.d.mts} +6 -2
  26. package/dist/{pi-coding-agent-69yY0ke-.d.mts.map → pi-coding-agent-Pvy0m9tS.d.mts.map} +1 -1
  27. package/dist/{runtime-C-cmLDY0.mjs → runtime-CBZcd1uU.mjs} +592 -21
  28. package/dist/runtime-CBZcd1uU.mjs.map +1 -0
  29. package/dist/runtime.d.mts +106 -68
  30. package/dist/runtime.d.mts.map +1 -1
  31. package/dist/runtime.mjs +2 -2
  32. package/package.json +7 -2
  33. package/dist/mcp-config-NvRpjToH.mjs.map +0 -1
  34. package/dist/pi-coding-agent-DXJ_Jzo0.mjs.map +0 -1
  35. package/dist/runtime-C-cmLDY0.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
@@ -1123,6 +1153,102 @@ var BrowserSurfaces = class {
1123
1153
  imageToolNames() {
1124
1154
  return [...this.#imageToolNames];
1125
1155
  }
1156
+ #scene;
1157
+ #sceneRevision = 0;
1158
+ #sceneWidth = 100;
1159
+ openScene(packageName, factory, theme, keybindings, options) {
1160
+ this.#finishScene(void 0);
1161
+ return new Promise((resolve, reject) => {
1162
+ const run = {
1163
+ package: packageName,
1164
+ component: void 0,
1165
+ options,
1166
+ resolve,
1167
+ reject
1168
+ };
1169
+ this.#scene = run;
1170
+ const driver = { requestRender: () => this.#invalidateScene() };
1171
+ const done = (value) => {
1172
+ if (this.#scene !== run) return;
1173
+ this.#finishScene(value);
1174
+ };
1175
+ Promise.resolve().then(() => factory(driver, theme, keybindings, done)).then((component) => {
1176
+ if (this.#scene !== run) {
1177
+ component.dispose?.();
1178
+ return;
1179
+ }
1180
+ if (typeof component?.render !== "function") {
1181
+ this.#scene = void 0;
1182
+ this.#invalidateScene();
1183
+ console.warn(`[pi2dsh] web scene for ${packageName}: component lacks render(width)`);
1184
+ reject(/* @__PURE__ */ new TypeError("Pi custom component must implement render(width)"));
1185
+ return;
1186
+ }
1187
+ run.component = component;
1188
+ this.#invalidateScene();
1189
+ }, (error) => {
1190
+ if (this.#scene !== run) return;
1191
+ this.#scene = void 0;
1192
+ this.#invalidateScene();
1193
+ console.warn(`[pi2dsh] web scene for ${packageName}: component factory failed: ${error instanceof Error ? error.stack ?? error.message : String(error)}`);
1194
+ reject(error);
1195
+ });
1196
+ });
1197
+ }
1198
+ /** What the browser paints: the live component's frame, or a closed marker. */
1199
+ sceneSnapshot() {
1200
+ const scene = this.#scene;
1201
+ if (scene?.component === void 0) return {
1202
+ open: false,
1203
+ revision: this.#sceneRevision
1204
+ };
1205
+ const requested = (typeof scene.options?.overlayOptions === "function" ? scene.options.overlayOptions() : scene.options?.overlayOptions)?.width;
1206
+ const width = typeof requested === "number" && Number.isFinite(requested) && requested > 0 ? Math.min(this.#sceneWidth, Math.floor(requested)) : this.#sceneWidth;
1207
+ let lines;
1208
+ try {
1209
+ lines = scene.component.render(width);
1210
+ } catch (error) {
1211
+ const failed = this.#scene;
1212
+ this.#scene = void 0;
1213
+ this.#invalidateScene();
1214
+ console.warn(`[pi2dsh] web scene for ${scene.package}: render(width) failed: ${error instanceof Error ? error.stack ?? error.message : String(error)}`);
1215
+ failed?.reject(error);
1216
+ return {
1217
+ open: false,
1218
+ revision: this.#sceneRevision
1219
+ };
1220
+ }
1221
+ return {
1222
+ open: true,
1223
+ package: scene.package,
1224
+ revision: this.#sceneRevision,
1225
+ lines
1226
+ };
1227
+ }
1228
+ /** Raw terminal input from the browser keyboard, verbatim into the component. */
1229
+ sceneInput(sequence, width) {
1230
+ if (typeof width === "number" && Number.isFinite(width)) this.#sceneWidth = Math.max(20, Math.min(400, Math.floor(width)));
1231
+ const component = this.#scene?.component;
1232
+ if (component === void 0 || sequence.length === 0) return;
1233
+ component.handleInput?.(sequence);
1234
+ component.invalidate?.();
1235
+ this.#invalidateScene();
1236
+ }
1237
+ /** The browser dismissed the overlay: resolve the Pi caller with undefined. */
1238
+ closeScene() {
1239
+ this.#finishScene(void 0);
1240
+ }
1241
+ #finishScene(value) {
1242
+ const scene = this.#scene;
1243
+ if (scene === void 0) return;
1244
+ this.#scene = void 0;
1245
+ scene.component?.dispose?.();
1246
+ this.#invalidateScene();
1247
+ scene.resolve(value);
1248
+ }
1249
+ #invalidateScene() {
1250
+ this.#sceneRevision += 1;
1251
+ }
1126
1252
  /**
1127
1253
  * Track one child session under its parent.
1128
1254
  * @param parentSessionId - the session the panel floats over.
@@ -1442,7 +1568,7 @@ function publishAuthorization(url) {
1442
1568
  function revokeAuthorization(path) {
1443
1569
  pendingAuthorizations.delete(path.slice(18));
1444
1570
  }
1445
- function registerBrowserSurfaceRoute(ctx, registry) {
1571
+ function registerBrowserSurfaceRoute(ctx, registry, hooks) {
1446
1572
  const web = ctx.get("webServer");
1447
1573
  if (web === void 0 || typeof web.register !== "function") return false;
1448
1574
  ctx.effect(() => web.register({
@@ -1467,6 +1593,63 @@ function registerBrowserSurfaceRoute(ctx, registry) {
1467
1593
  response.end();
1468
1594
  return;
1469
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
+ }
1637
+ if (method === "POST" && (url.pathname === "/pi2dsh/scene-input" || url.pathname === "/pi2dsh/scene-close")) {
1638
+ const chunks = [];
1639
+ const body = await new Promise((settle) => {
1640
+ const stream = req;
1641
+ stream.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
1642
+ stream.on("end", () => settle(Buffer.concat(chunks).toString("utf8")));
1643
+ });
1644
+ if (url.pathname === "/pi2dsh/scene-close") registry.closeScene();
1645
+ else try {
1646
+ const payload = JSON.parse(body || "{}");
1647
+ if (typeof payload.sequence === "string") registry.sceneInput(payload.sequence, typeof payload.width === "number" ? payload.width : void 0);
1648
+ } catch {}
1649
+ response.writeHead(204);
1650
+ response.end();
1651
+ return;
1652
+ }
1470
1653
  if (method !== "GET" && method !== "HEAD") {
1471
1654
  response.writeHead(405);
1472
1655
  response.end();
@@ -1512,11 +1695,13 @@ function registerBrowserSurfaceRoute(ctx, registry) {
1512
1695
  const body = JSON.stringify(session === "" ? {
1513
1696
  threads: [],
1514
1697
  surfaces: [],
1515
- entries: []
1698
+ entries: [],
1699
+ scene: registry.sceneSnapshot()
1516
1700
  } : {
1517
1701
  threads: registry.snapshot(session),
1518
1702
  surfaces: registry.surfaces(session),
1519
1703
  entries: registry.entries(session),
1704
+ scene: registry.sceneSnapshot(),
1520
1705
  ...registry.draftRequest(session) === void 0 ? {} : { draft: registry.draftRequest(session) }
1521
1706
  });
1522
1707
  response.writeHead(200, {
@@ -3001,7 +3186,7 @@ function registerPiProviderRoute(options) {
3001
3186
  }
3002
3187
  //#endregion
3003
3188
  //#region src/tui-surfaces.ts
3004
- function service(ctx, name) {
3189
+ function service$1(ctx, name) {
3005
3190
  try {
3006
3191
  return ctx.get(name, false);
3007
3192
  } catch {
@@ -3048,6 +3233,8 @@ function preferredWidth(options) {
3048
3233
  var TuiSurfaceAdapter = class {
3049
3234
  packageName;
3050
3235
  instanceKey;
3236
+ kind = "tuiScenes";
3237
+ nativeCommands = dshTuiLocalCommands();
3051
3238
  sceneId;
3052
3239
  scenes;
3053
3240
  status;
@@ -3060,10 +3247,10 @@ var TuiSurfaceAdapter = class {
3060
3247
  constructor(ctx, packageName, instanceKey) {
3061
3248
  this.packageName = packageName;
3062
3249
  this.instanceKey = instanceKey;
3063
- const scenes = service(ctx, "tuiScenes");
3250
+ const scenes = service$1(ctx, "tuiScenes");
3064
3251
  if (scenes === void 0) throw new Error("pi2dsh: dsh-TUI scene service is unavailable");
3065
3252
  this.scenes = scenes;
3066
- this.status = service(ctx, "tuiStatus");
3253
+ this.status = service$1(ctx, "tuiStatus");
3067
3254
  this.sceneId = sceneIdFor(instanceKey === void 0 ? packageName : `${packageName}-${instanceKey}`);
3068
3255
  this.unregisterScene = scenes.register({
3069
3256
  id: this.sceneId,
@@ -3196,7 +3383,7 @@ function mountTuiSurfaceAdapter(ctx, packageName, publish, instanceKey) {
3196
3383
  publish(void 0);
3197
3384
  });
3198
3385
  };
3199
- if (service(ctx, "tuiScenes") !== void 0) {
3386
+ if (service$1(ctx, "tuiScenes") !== void 0) {
3200
3387
  attach(ctx);
3201
3388
  return;
3202
3389
  }
@@ -3289,8 +3476,252 @@ function dshTuiLocalCommands() {
3289
3476
  cachedTuiLocalCommands = tuiLocalCommandsForVersion(version);
3290
3477
  return cachedTuiLocalCommands;
3291
3478
  }
3292
- function commandNameForDshTui(name, tuiAvailable) {
3293
- 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
+ }
3294
3725
  }
3295
3726
  //#endregion
3296
3727
  //#region src/runtime.ts
@@ -3808,9 +4239,11 @@ function contextFor(ctx, state, agent, signal, command = false, sessionOverride)
3808
4239
  setHeader: (factory) => putSurface(ctx, state, agent, "header", factory),
3809
4240
  setTitle: (title) => putSurface(ctx, state, agent, "title", title),
3810
4241
  custom: async (factory, options) => {
4242
+ if (typeof factory !== "function") return void 0;
3811
4243
  const surfaces = state.tuiSurfaces;
3812
- if (surfaces === void 0 || typeof factory !== "function") return void 0;
3813
- return surfaces.custom(factory, state.theme, getKeybindings(), options);
4244
+ if (surfaces !== void 0) return surfaces.custom(factory, state.theme, getKeybindings(), options);
4245
+ const browser = state.shared.browserSurfaces;
4246
+ if (browser !== void 0 && state.shared.browserSurfacesRouted === true) return browser.openScene(state.packageName, factory, state.theme, getKeybindings(), options);
3814
4247
  },
3815
4248
  pasteToEditor(text) {
3816
4249
  const surfaces = state.shared.browserSurfaces;
@@ -4007,7 +4440,8 @@ function contextFor(ctx, state, agent, signal, command = false, sessionOverride)
4007
4440
  },
4008
4441
  ui,
4009
4442
  get mode() {
4010
- 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";
4011
4445
  },
4012
4446
  get hasUI() {
4013
4447
  return state.tuiSurfaces?.available === true || humanAnswererAvailable(userQuestions, agent);
@@ -5121,6 +5555,59 @@ function oauthStoreOf(state) {
5121
5555
  }
5122
5556
  const COMPANION_ROUTE_PREFIX = "pi2dsh-companion/";
5123
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
+ }
5124
5611
  /**
5125
5612
  * Pi's provider ledger is layered, not flat (model-runtime.ts:742-786 at the
5126
5613
  * pinned upstream): a builtin base map, an extension overlay where defined
@@ -5381,6 +5868,10 @@ async function materializeAttachmentImage(ctx, attachment) {
5381
5868
  }
5382
5869
  }
5383
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
+ }
5384
5875
  const scoped = scopeOf(ctx);
5385
5876
  const owner = typeof scoped === "object" && scoped !== null ? scoped : ctx;
5386
5877
  const registered = state.shared.loginRegistered ??= /* @__PURE__ */ new WeakSet();
@@ -6166,9 +6657,9 @@ function normalizedPiCommandName(piName) {
6166
6657
  }
6167
6658
  function dshCommandName(ctx, state, piName) {
6168
6659
  const validName = normalizedPiCommandName(piName);
6169
- const name = commandNameForDshTui(validName, state.tuiSurfaces?.available === true || optionalService(ctx, "tuiScenes") !== void 0);
6660
+ const name = commandNameForTerminal(validName, state.tuiSurfaces?.nativeCommands);
6170
6661
  if (name !== piName) {
6171
- 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";
6172
6663
  logger(ctx).warn(`[pi2dsh] Pi command /${piName} registered as /${name} ${reason}`);
6173
6664
  }
6174
6665
  return name;
@@ -6654,6 +7145,12 @@ async function applyPiPackage(ctx, options) {
6654
7145
  const shared = sharedHostStateOf(ctx);
6655
7146
  const scopedOwner = options.ownerAgent ?? scopeOf(ctx);
6656
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
+ }
6657
7154
  const state = {
6658
7155
  shared,
6659
7156
  packageName: options.manifest.package.name,
@@ -6716,9 +7213,17 @@ async function applyPiPackage(ctx, options) {
6716
7213
  publishedOAuthKeys: shared.publishedOAuthKeys,
6717
7214
  tuiSurfaces: void 0
6718
7215
  };
6719
- 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) => {
6720
7225
  state.tuiSurfaces = adapter;
6721
- }, typeof ownerAgent?.id === "string" ? ownerAgent.id : void 0);
7226
+ }, surfaceInstanceKey);
6722
7227
  subscribeLifecycle(ctx, state);
6723
7228
  subscribeInterceptors(ctx, state);
6724
7229
  shared.providerBuiltins ??= /* @__PURE__ */ new Map();
@@ -6772,7 +7277,57 @@ async function applyPiPackage(ctx, options) {
6772
7277
  });
6773
7278
  }
6774
7279
  const browserSurfaces = state.shared.browserSurfaces ??= new BrowserSurfaces();
6775
- 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
+ }
6776
7331
  ctx.effect(() => browserSurfaces.trackCompletions(state.packageName ?? "pi", async (trigger, query) => {
6777
7332
  if (state.autocompleteProviders.length === 0) return [];
6778
7333
  let provider = { getSuggestions: async () => null };
@@ -6882,6 +7437,21 @@ async function applyPiPackage(ctx, options) {
6882
7437
  return state.bridge.readonlySessionManager(session, cwd);
6883
7438
  },
6884
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
+ },
6885
7455
  sessionGoneFromPersistence: async (sessionId) => {
6886
7456
  const persistence = optionalService(ctx, "sessionPersistence");
6887
7457
  if (typeof persistence?.list !== "function") return void 0;
@@ -6950,6 +7520,7 @@ const runtimeInternals = {
6950
7520
  currentPiModel,
6951
7521
  lastRequestRouteOf,
6952
7522
  resolveCallerRoute,
7523
+ resolveChildExtensionPackages,
6953
7524
  dshToPiContent,
6954
7525
  expandPrompt,
6955
7526
  isKnownImageTool,
@@ -6960,6 +7531,6 @@ const runtimeInternals = {
6960
7531
  textBlocks
6961
7532
  };
6962
7533
  //#endregion
6963
- 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 };
6964
7535
 
6965
- //# sourceMappingURL=runtime-C-cmLDY0.mjs.map
7536
+ //# sourceMappingURL=runtime-CBZcd1uU.mjs.map