pi2dsh 0.8.0 → 0.10.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 (41) hide show
  1. package/README.md +8 -2
  2. package/README.zh.md +7 -2
  3. package/dist/{analyzer-CKTsWVzn.mjs → analyzer-CsrBeS8r.mjs} +3 -5
  4. package/dist/{analyzer-CKTsWVzn.mjs.map → analyzer-CsrBeS8r.mjs.map} +1 -1
  5. package/dist/cli.mjs +3 -3
  6. package/dist/cli.mjs.map +1 -1
  7. package/dist/compat/pi-ai.d.mts +2 -1
  8. package/dist/compat/pi-ai.d.mts.map +1 -1
  9. package/dist/compat/pi-ai.mjs +2 -3
  10. package/dist/compat/pi-coding-agent.d.mts +259 -29
  11. package/dist/compat/pi-coding-agent.d.mts.map +1 -1
  12. package/dist/compat/pi-coding-agent.mjs +2 -2
  13. package/dist/credentials-oauth.mjs +2 -2
  14. package/dist/{generator-eyViezvs.mjs → generator-CSOv6wYT.mjs} +3 -5
  15. package/dist/{generator-eyViezvs.mjs.map → generator-CSOv6wYT.mjs.map} +1 -1
  16. package/dist/host.d.mts +2 -2
  17. package/dist/host.d.mts.map +1 -1
  18. package/dist/host.mjs +1 -1
  19. package/dist/host.mjs.map +1 -1
  20. package/dist/index.d.mts +6 -5
  21. package/dist/index.d.mts.map +1 -1
  22. package/dist/index.mjs +4 -4
  23. package/dist/index.mjs.map +1 -1
  24. package/dist/{mcp-config-Dlou-ZA-.mjs → mcp-config-Cn5gWemZ.mjs} +29 -29
  25. package/dist/mcp-config-Cn5gWemZ.mjs.map +1 -0
  26. package/dist/{pi-ai-C6Xag6xt.mjs → pi-ai-Dyg4zyLZ.mjs} +196 -85
  27. package/dist/pi-ai-Dyg4zyLZ.mjs.map +1 -0
  28. package/dist/{pi-coding-agent-CznNsOnV.mjs → pi-coding-agent-Ctw-BhoY.mjs} +0 -0
  29. package/dist/pi-coding-agent-Ctw-BhoY.mjs.map +1 -0
  30. package/dist/{runtime-B-Mun-jX.mjs → runtime-Ce3PTbWU.mjs} +511 -171
  31. package/dist/runtime-Ce3PTbWU.mjs.map +1 -0
  32. package/dist/runtime.d.mts +18 -10
  33. package/dist/runtime.d.mts.map +1 -1
  34. package/dist/runtime.mjs +1 -1
  35. package/package.json +5 -3
  36. package/dist/mcp-config-Dlou-ZA-.mjs.map +0 -1
  37. package/dist/pi-ai-C6Xag6xt.mjs.map +0 -1
  38. package/dist/pi-coding-agent-CznNsOnV.mjs.map +0 -1
  39. package/dist/pi-uuid-Db8ShZsK.mjs +0 -47
  40. package/dist/pi-uuid-Db8ShZsK.mjs.map +0 -1
  41. package/dist/runtime-B-Mun-jX.mjs.map +0 -1
@@ -1,7 +1,7 @@
1
1
 
2
- import { P as AssistantMessageEventStream, i as builtinProviders, r as __setPiAiLlmBridge } from "./pi-ai-C6Xag6xt.mjs";
2
+ import { F as AssistantMessageEventStream, a as builtinProviders, i as __setPiAiLlmBridge } from "./pi-ai-Dyg4zyLZ.mjs";
3
+ import { P as generateBranchSummary, Tn as PiCapabilityError, b as Theme, d as ExtensionRunner, w as __setSubagentSessionFactory, wn as CapabilityLedger } from "./pi-coding-agent-Ctw-BhoY.mjs";
3
4
  import { t as getAgentDir } from "./pi-config-shim-CZ1wFzqM.mjs";
4
- import { E as __setSubagentSessionFactory, S as Theme, f as ExtensionRunner } from "./pi-coding-agent-CznNsOnV.mjs";
5
5
  import { a as resolvePiProviderAuth, n as loginPiProvider, r as providerSupportsOAuth, t as FileCredentialStore } from "./oauth-bridge-CfyImWx4.mjs";
6
6
  import { createRequire } from "node:module";
7
7
  import { access, mkdir, readFile, writeFile } from "node:fs/promises";
@@ -94,6 +94,17 @@ var PiSessionBridge = class {
94
94
  });
95
95
  return id;
96
96
  }
97
+ appendBranchSummary(sessionId, summary, fromId) {
98
+ const id = randomUUID();
99
+ this.persist(sessionId, {
100
+ kind: "branch_summary",
101
+ id,
102
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
103
+ summary,
104
+ fromId
105
+ });
106
+ return id;
107
+ }
97
108
  appendLabel(sessionId, targetId, label) {
98
109
  this.persist(sessionId, {
99
110
  kind: "label",
@@ -197,6 +208,16 @@ var PiSessionBridge = class {
197
208
  ...record.data === void 0 ? {} : { data: record.data }
198
209
  }
199
210
  });
211
+ else if (record.kind === "branch_summary") merged.push({
212
+ time,
213
+ entry: {
214
+ type: "branch_summary",
215
+ id: record.id,
216
+ timestamp: record.timestamp,
217
+ summary: record.summary,
218
+ fromId: record.fromId
219
+ }
220
+ });
200
221
  else if (record.kind === "label") merged.push({
201
222
  time,
202
223
  entry: {
@@ -1223,7 +1244,8 @@ async function textOnlyMessages(messages, materialize) {
1223
1244
  * extension handles the turn's entering messages.
1224
1245
  */
1225
1246
  function imageAdmissionCompanionAdapter(options) {
1226
- const { originalId, imageModels, llm } = options;
1247
+ const { originalId, llm } = options;
1248
+ const admits = (modelId) => options.imageModels === void 0 || options.imageModels.has(modelId);
1227
1249
  const materialize = options.materializeImage ?? (async () => void 0);
1228
1250
  const admitImage = (info, id) => {
1229
1251
  const modalities = Array.isArray(info.inputModalities) ? info.inputModalities.slice() : ["text"];
@@ -1242,10 +1264,10 @@ function imageAdmissionCompanionAdapter(options) {
1242
1264
  };
1243
1265
  },
1244
1266
  providerRetryPolicy: () => void 0,
1245
- listModels: async (id) => (await llm.listModels(originalId)).filter((model) => imageModels.has(String(model.id))).map((model) => admitImage(model, id)),
1267
+ listModels: async (id) => (await llm.listModels(originalId)).filter((model) => admits(String(model.id))).map((model) => admitImage(model, id)),
1246
1268
  resolveModel: async (id, modelId) => {
1247
1269
  const info = await llm.resolveModelInfo(originalId, modelId);
1248
- return imageModels.has(modelId) ? admitImage(info, id) : {
1270
+ return admits(modelId) ? admitImage(info, id) : {
1249
1271
  ...info,
1250
1272
  provider: id
1251
1273
  };
@@ -1492,6 +1514,11 @@ function cwdOf(agent) {
1492
1514
  function unsupported(name) {
1493
1515
  throw new Error(`pi2dsh: Pi API ${name} requires a native DSH port; inspect the compatibility report`);
1494
1516
  }
1517
+ function capabilityLedgerOf(ctx, state) {
1518
+ const shared = state.shared;
1519
+ if (shared.capabilityLedger === void 0) shared.capabilityLedger = new CapabilityLedger((message) => logger(ctx).warn(message));
1520
+ return shared.capabilityLedger;
1521
+ }
1495
1522
  function optionalService(ctx, name) {
1496
1523
  return ctx.get(name);
1497
1524
  }
@@ -1550,7 +1577,45 @@ function thinkingLevelOf(state, agent) {
1550
1577
  }
1551
1578
  return state.globalThinkingLevel;
1552
1579
  }
1553
- function contextFor(ctx, state, agent, signal, command = false) {
1580
+ function durableSeqOf(entryId) {
1581
+ const match = /^dsh-(\d+)$/.exec(entryId);
1582
+ return match === null ? void 0 : Number(match[1]);
1583
+ }
1584
+ function shrinkToTurnBoundary(events, boundary) {
1585
+ const slice = events.slice(0, boundary + 1);
1586
+ for (let i = slice.length - 1; i >= 0; i--) {
1587
+ const type = slice[i]?.type;
1588
+ if (type === "turn/end") return boundary;
1589
+ if (type === "turn/start") return Number(slice[i].seq) - 1;
1590
+ }
1591
+ return boundary;
1592
+ }
1593
+ async function summarizeAbandonedBranch(ctx, state, agent, events, boundary, options) {
1594
+ const abandoned = events.slice(boundary + 1);
1595
+ if (abandoned.length === 0) return void 0;
1596
+ const model = agent === void 0 ? void 0 : currentPiModel(state, agent);
1597
+ if (model === void 0) {
1598
+ logger(ctx).warn("[pi2dsh] ctx.navigateTree: no current model route, navigating without a branch summary");
1599
+ return;
1600
+ }
1601
+ const entries = state.bridge.readonlySessionManager({
1602
+ id: "pi2dsh-abandoned-branch",
1603
+ events: abandoned
1604
+ }, cwdOf(agent)).getEntries?.() ?? [];
1605
+ if (entries.length === 0) return void 0;
1606
+ const result = await generateBranchSummary(entries, {
1607
+ model,
1608
+ signal: new AbortController().signal,
1609
+ ...typeof options.customInstructions === "string" ? { customInstructions: options.customInstructions } : {},
1610
+ ...options.replaceInstructions === true ? { replaceInstructions: true } : {}
1611
+ });
1612
+ if (result.error !== void 0) {
1613
+ logger(ctx).warn(`[pi2dsh] ctx.navigateTree: branch summarization failed (${result.error}), navigating without a summary`);
1614
+ return;
1615
+ }
1616
+ return result.summary;
1617
+ }
1618
+ function contextFor(ctx, state, agent, signal, command = false, sessionOverride) {
1554
1619
  const notices = [];
1555
1620
  const userQuestions = optionalService(ctx, "userQuestions");
1556
1621
  const ui = {
@@ -1625,7 +1690,7 @@ function contextFor(ctx, state, agent, signal, command = false) {
1625
1690
  state.toolsExpanded = expanded === true;
1626
1691
  }
1627
1692
  };
1628
- const session = agentSession(agent);
1693
+ const session = sessionOverride ?? agentSession(agent);
1629
1694
  const providerConfig = (name) => state.providers.get(name);
1630
1695
  const catalog = state.modelCatalog;
1631
1696
  const piNativeEntry = (provider, id) => {
@@ -1665,119 +1730,121 @@ function contextFor(ctx, state, agent, signal, command = false) {
1665
1730
  stream: dshRoutedStream,
1666
1731
  streamSimple: dshRoutedStream
1667
1732
  });
1668
- const base = {
1669
- ui,
1670
- mode: "rpc",
1671
- hasUI: userQuestions !== void 0,
1672
- cwd: cwdOf(agent),
1673
- sessionManager: session === void 0 ? state.bridge.readonlySessionManager({
1674
- id: "pi2dsh-detached",
1675
- events: []
1676
- }, cwdOf(agent)) : state.bridge.readonlySessionManager(session, cwdOf(agent)),
1677
- modelRegistry: {
1678
- getAll: () => allModels(),
1679
- getAvailable: () => allModels(),
1680
- find: (provider, modelId) => {
1681
- const entry = catalog?.find(provider, modelId);
1682
- return entry === void 0 ? void 0 : restorePiShape(entry);
1683
- },
1684
- getError: () => void 0,
1685
- hasConfiguredAuth: (model) => {
1686
- const provider = String(model?.provider ?? "");
1687
- if (provider.length === 0) return false;
1688
- return state.providers.has(provider) || (catalog?.all() ?? []).some((entry) => entry.provider === provider);
1689
- },
1690
- getApiKeyAndHeaders: async (model) => {
1691
- const provider = String(model?.provider ?? "");
1692
- const config = providerConfig(provider);
1693
- if (config !== void 0) {
1694
- const auth = (await resolvePiProviderAuth({
1695
- providerId: provider,
1696
- providerConfig: config,
1697
- store: oauthStoreOf(state)
1698
- }))?.auth;
1699
- if (auth?.apiKey === void 0) return { ok: false };
1700
- return {
1701
- ok: true,
1702
- apiKey: auth.apiKey,
1703
- ...auth.headers === void 0 ? {} : { headers: auth.headers },
1704
- ...auth.baseUrl === void 0 && config.baseUrl === void 0 ? {} : { baseUrl: auth.baseUrl ?? config.baseUrl }
1705
- };
1706
- }
1707
- try {
1708
- const entry = llmOf(ctx)?.listConfigurableProviders?.()?.find((candidate) => candidate.provider === provider);
1709
- if (entry === void 0) return { ok: false };
1710
- const section = ctx.get("settings")?.get(entry.settingsNs);
1711
- const ref = entry.settingsPath.reduce((node, key) => typeof node === "object" && node !== null ? node[key] : void 0, section)?.apiKeyEnv;
1712
- if (typeof ref !== "string" || ref.length === 0) return { ok: false };
1713
- const resolved = await ctx.get("credentials")?.resolve(ref);
1714
- if (typeof resolved?.value !== "string" || resolved.value.length === 0) return { ok: false };
1715
- return {
1716
- ok: true,
1717
- apiKey: resolved.value
1718
- };
1719
- } catch {
1720
- return { ok: false };
1721
- }
1722
- },
1723
- getProviderAuthStatus: (provider) => providerSupportsOAuth(providerConfig(provider)) ? "oauth" : "none",
1724
- getProvider: (provider) => {
1725
- const base = providerConfig(provider);
1726
- if (base !== void 0) return state.providerRouteDisposers.has(provider) ? dshRoutedPiProvider(base, provider) : base;
1727
- if ((catalog?.all() ?? []).some((entry) => entry.provider === provider)) return dshRoutedPiProvider({
1728
- id: provider,
1729
- name: provider
1730
- }, provider);
1731
- },
1732
- complete: async (model, context, options) => {
1733
- const result = await dshRoutedStream(model, context ?? {}, options).result();
1734
- if (result instanceof Error) throw result;
1735
- return result;
1736
- },
1737
- getRegisteredProviderConfig: (provider) => providerConfig(provider),
1738
- getRegisteredProviderIds: () => [...state.providers.keys()],
1739
- getProviderDisplayName: (provider) => {
1740
- const config = providerConfig(provider);
1741
- return typeof config?.name === "string" ? config.name : provider;
1742
- },
1743
- getProviderAuth: async (provider) => {
1744
- const config = providerConfig(provider);
1745
- if (config === void 0) return void 0;
1746
- const resolved = await resolvePiProviderAuth({
1747
- providerId: provider,
1748
- providerConfig: config,
1749
- store: oauthStoreOf(state)
1750
- });
1751
- if (resolved?.auth === void 0) return resolved;
1752
- return resolved.auth.baseUrl === void 0 && config.baseUrl !== void 0 ? {
1753
- ...resolved,
1754
- auth: {
1755
- ...resolved.auth,
1756
- baseUrl: config.baseUrl
1757
- }
1758
- } : resolved;
1759
- },
1760
- getApiKeyForProvider: async (provider) => {
1761
- const config = providerConfig(provider);
1762
- if (config === void 0) return void 0;
1763
- return ((await resolvePiProviderAuth({
1733
+ const modelRegistry = {
1734
+ getAll: () => allModels(),
1735
+ getAvailable: () => allModels(),
1736
+ find: (provider, modelId) => {
1737
+ const entry = catalog?.find(provider, modelId);
1738
+ return entry === void 0 ? void 0 : restorePiShape(entry);
1739
+ },
1740
+ getError: () => void 0,
1741
+ hasConfiguredAuth: (model) => {
1742
+ const provider = String(model?.provider ?? "");
1743
+ if (provider.length === 0) return false;
1744
+ return state.providers.has(provider) || (catalog?.all() ?? []).some((entry) => entry.provider === provider);
1745
+ },
1746
+ getApiKeyAndHeaders: async (model) => {
1747
+ const provider = String(model?.provider ?? "");
1748
+ const config = providerConfig(provider);
1749
+ if (config !== void 0) {
1750
+ const auth = (await resolvePiProviderAuth({
1764
1751
  providerId: provider,
1765
1752
  providerConfig: config,
1766
1753
  store: oauthStoreOf(state)
1767
- }))?.auth)?.apiKey;
1768
- },
1769
- isUsingOAuth: (model) => {
1770
- const provider = String(model?.provider ?? "");
1771
- return provider.length > 0 && providerSupportsOAuth(providerConfig(provider));
1772
- },
1773
- refresh: async () => {
1774
- await catalog?.refresh();
1754
+ }))?.auth;
1755
+ if (auth?.apiKey === void 0) return { ok: false };
1775
1756
  return {
1776
- models: allModels(),
1777
- errors: []
1757
+ ok: true,
1758
+ apiKey: auth.apiKey,
1759
+ ...auth.headers === void 0 ? {} : { headers: auth.headers },
1760
+ ...auth.baseUrl === void 0 && config.baseUrl === void 0 ? {} : { baseUrl: auth.baseUrl ?? config.baseUrl }
1778
1761
  };
1779
1762
  }
1763
+ try {
1764
+ const entry = llmOf(ctx)?.listConfigurableProviders?.()?.find((candidate) => candidate.provider === provider);
1765
+ if (entry === void 0) return { ok: false };
1766
+ const section = ctx.get("settings")?.get(entry.settingsNs);
1767
+ const ref = entry.settingsPath.reduce((node, key) => typeof node === "object" && node !== null ? node[key] : void 0, section)?.apiKeyEnv;
1768
+ if (typeof ref !== "string" || ref.length === 0) return { ok: false };
1769
+ const resolved = await ctx.get("credentials")?.resolve(ref);
1770
+ if (typeof resolved?.value !== "string" || resolved.value.length === 0) return { ok: false };
1771
+ return {
1772
+ ok: true,
1773
+ apiKey: resolved.value
1774
+ };
1775
+ } catch {
1776
+ return { ok: false };
1777
+ }
1780
1778
  },
1779
+ getProviderAuthStatus: (provider) => providerSupportsOAuth(providerConfig(provider)) ? "oauth" : "none",
1780
+ getProvider: (provider) => {
1781
+ const base = providerConfig(provider);
1782
+ if (base !== void 0) return state.providerRouteDisposers.has(provider) ? dshRoutedPiProvider(base, provider) : base;
1783
+ if ((catalog?.all() ?? []).some((entry) => entry.provider === provider)) return dshRoutedPiProvider({
1784
+ id: provider,
1785
+ name: provider
1786
+ }, provider);
1787
+ },
1788
+ complete: async (model, context, options) => {
1789
+ const result = await dshRoutedStream(model, context ?? {}, options).result();
1790
+ if (result instanceof Error) throw result;
1791
+ return result;
1792
+ },
1793
+ getRegisteredProviderConfig: (provider) => providerConfig(provider),
1794
+ getRegisteredProviderIds: () => [...state.providers.keys()],
1795
+ getProviderDisplayName: (provider) => {
1796
+ const config = providerConfig(provider);
1797
+ return typeof config?.name === "string" ? config.name : provider;
1798
+ },
1799
+ getProviderAuth: async (provider) => {
1800
+ const config = providerConfig(provider);
1801
+ if (config === void 0) return void 0;
1802
+ const resolved = await resolvePiProviderAuth({
1803
+ providerId: provider,
1804
+ providerConfig: config,
1805
+ store: oauthStoreOf(state)
1806
+ });
1807
+ if (resolved?.auth === void 0) return resolved;
1808
+ return resolved.auth.baseUrl === void 0 && config.baseUrl !== void 0 ? {
1809
+ ...resolved,
1810
+ auth: {
1811
+ ...resolved.auth,
1812
+ baseUrl: config.baseUrl
1813
+ }
1814
+ } : resolved;
1815
+ },
1816
+ getApiKeyForProvider: async (provider) => {
1817
+ const config = providerConfig(provider);
1818
+ if (config === void 0) return void 0;
1819
+ return ((await resolvePiProviderAuth({
1820
+ providerId: provider,
1821
+ providerConfig: config,
1822
+ store: oauthStoreOf(state)
1823
+ }))?.auth)?.apiKey;
1824
+ },
1825
+ isUsingOAuth: (model) => {
1826
+ const provider = String(model?.provider ?? "");
1827
+ return provider.length > 0 && providerSupportsOAuth(providerConfig(provider));
1828
+ },
1829
+ refresh: async () => {
1830
+ await catalog?.refresh();
1831
+ return {
1832
+ models: allModels(),
1833
+ errors: []
1834
+ };
1835
+ }
1836
+ };
1837
+ const contextCwd = typeof (sessionOverride?.header)?.cwd === "string" ? sessionOverride.header.cwd : cwdOf(agent);
1838
+ const base = {
1839
+ ui,
1840
+ mode: "rpc",
1841
+ hasUI: userQuestions !== void 0,
1842
+ cwd: contextCwd,
1843
+ sessionManager: session === void 0 ? state.bridge.readonlySessionManager({
1844
+ id: "pi2dsh-detached",
1845
+ events: []
1846
+ }, contextCwd) : state.bridge.readonlySessionManager(session, contextCwd),
1847
+ modelRegistry,
1781
1848
  model: agent === void 0 ? void 0 : currentPiModel(state, agent),
1782
1849
  scopedModels: catalog?.all() ?? [],
1783
1850
  thinkingLevel: thinkingLevelOf(state, agent),
@@ -1793,9 +1860,55 @@ function contextFor(ctx, state, agent, signal, command = false) {
1793
1860
  });
1794
1861
  },
1795
1862
  hasPendingMessages: () => false,
1796
- shutdown: () => unsupported("ctx.shutdown"),
1863
+ shutdown: () => {
1864
+ capabilityLedgerOf(ctx, state).reportHostDecision({
1865
+ capability: "ctx.shutdown",
1866
+ reason: "Pi delegates shutdown behavior to the host, and on DSH the user owns process exit.",
1867
+ guidance: "The shutdown request was recorded and ignored.",
1868
+ packageName: state.packageName
1869
+ });
1870
+ },
1797
1871
  getContextUsage: () => void 0,
1798
- compact: () => unsupported("ctx.compact"),
1872
+ compact: (options) => {
1873
+ const ledger = capabilityLedgerOf(ctx, state);
1874
+ const compaction = optionalService(ctx, "compaction");
1875
+ const target = agent;
1876
+ const onError = options?.onError;
1877
+ if (compaction === void 0 || typeof target?.runMaintenance !== "function") {
1878
+ const gap = new PiCapabilityError({
1879
+ capability: "ctx.compact",
1880
+ reason: compaction === void 0 ? "this DSH composition mounts no compaction service." : "compaction needs a live DSH agent for this turn.",
1881
+ guidance: "Compaction runs through the host compaction plugin when one is composed.",
1882
+ packageName: state.packageName
1883
+ });
1884
+ ledger.reportDegraded({
1885
+ capability: "ctx.compact",
1886
+ reason: gap.message,
1887
+ guidance: "",
1888
+ packageName: state.packageName
1889
+ });
1890
+ onError?.(gap);
1891
+ return;
1892
+ }
1893
+ compaction.compactNow(target, new AbortController().signal).then((result) => {
1894
+ if (result === null || result === void 0) return;
1895
+ const dsh = result;
1896
+ const summaryText = (Array.isArray(dsh.summary) ? dsh.summary : []).filter((block) => typeof block === "object" && block !== null && block.type === "text" && typeof block.text === "string").map((block) => block.text).join("\n");
1897
+ const onComplete = options?.onComplete;
1898
+ onComplete?.({
1899
+ summary: summaryText,
1900
+ firstKeptEntryId: "",
1901
+ tokensBefore: dsh.shadowedTokenCount ?? 0
1902
+ });
1903
+ }).catch((error) => {
1904
+ const failure = error instanceof Error ? error : new Error(String(error));
1905
+ if (onError !== void 0) {
1906
+ onError(failure);
1907
+ return;
1908
+ }
1909
+ logger(ctx).warn(`[pi2dsh] plugin "${state.packageName}": ctx.compact failed: ${failure.message}`);
1910
+ });
1911
+ },
1799
1912
  getSystemPrompt: () => state.currentSystemPrompt,
1800
1913
  __agent: agent,
1801
1914
  __notices: notices
@@ -1806,11 +1919,139 @@ function contextFor(ctx, state, agent, signal, command = false) {
1806
1919
  const wait = agent?.whenIdle;
1807
1920
  if (typeof wait === "function") await wait.call(agent);
1808
1921
  },
1809
- newSession: () => unsupported("ctx.newSession"),
1810
- fork: () => unsupported("ctx.fork"),
1811
- navigateTree: () => unsupported("ctx.navigateTree"),
1812
- switchSession: () => unsupported("ctx.switchSession"),
1813
- reload: () => unsupported("ctx.reload")
1922
+ newSession: async (options) => {
1923
+ const sessions = optionalService(ctx, "sessions");
1924
+ if (sessions === void 0) {
1925
+ capabilityLedgerOf(ctx, state).reportDegraded({
1926
+ capability: "ctx.newSession",
1927
+ reason: "this DSH composition mounts no session service.",
1928
+ guidance: "",
1929
+ packageName: state.packageName
1930
+ });
1931
+ return { cancelled: true };
1932
+ }
1933
+ const parent = agentSession(agent);
1934
+ const created = sessions.create(void 0, { meta: {
1935
+ cwd: cwdOf(agent),
1936
+ ...parent?.id === void 0 ? {} : { parentSession: parent.id }
1937
+ } });
1938
+ const withSession = options?.withSession;
1939
+ await withSession?.(contextFor(ctx, state, agent, signal, true, created));
1940
+ capabilityLedgerOf(ctx, state).reportHostDecision({
1941
+ capability: "ctx.newSession",
1942
+ reason: `a new DSH session was created (${String(created.id)}).`,
1943
+ guidance: "Which session the surface shows stays a host choice — open it from the DSH session list.",
1944
+ packageName: state.packageName
1945
+ });
1946
+ return { cancelled: false };
1947
+ },
1948
+ fork: async (entryId, options) => {
1949
+ const sessions = optionalService(ctx, "sessions");
1950
+ const source = agentSession(agent);
1951
+ if (sessions === void 0 || source === void 0) {
1952
+ capabilityLedgerOf(ctx, state).reportDegraded({
1953
+ capability: "ctx.fork",
1954
+ reason: sessions === void 0 ? "this DSH composition mounts no session service." : "forking needs the live session of an active agent.",
1955
+ guidance: "",
1956
+ packageName: state.packageName
1957
+ });
1958
+ return { cancelled: true };
1959
+ }
1960
+ const seq = durableSeqOf(String(entryId));
1961
+ if (seq === void 0) throw new Error(`pi2dsh: ctx.fork(${JSON.stringify(String(entryId))}) — only durable-log entries (projected ids "dsh-<seq>") can anchor a fork; package-appended sidecar entries are not part of the DSH durable log`);
1962
+ const position = options?.position ?? "before";
1963
+ const events = source.events ?? [];
1964
+ const requested = Math.min(position === "at" ? seq : seq - 1, events.length - 1);
1965
+ const boundary = requested < 0 ? -1 : shrinkToTurnBoundary(events, requested);
1966
+ const child = boundary < 0 ? sessions.create(void 0, { meta: {
1967
+ cwd: cwdOf(agent),
1968
+ parentSession: source.id
1969
+ } }) : sessions.fork(source, boundary);
1970
+ const withSession = options?.withSession;
1971
+ await withSession?.(contextFor(ctx, state, agent, signal, true, child));
1972
+ capabilityLedgerOf(ctx, state).reportHostDecision({
1973
+ capability: "ctx.fork",
1974
+ reason: `the session was forked on DSH's official prefix-fork surface (child ${String(child.id)}; DSH forks land on completed-turn boundaries).`,
1975
+ guidance: "Open the forked session from the DSH session list.",
1976
+ packageName: state.packageName
1977
+ });
1978
+ return { cancelled: false };
1979
+ },
1980
+ navigateTree: async (targetId, options) => {
1981
+ const sessions = optionalService(ctx, "sessions");
1982
+ const source = agentSession(agent);
1983
+ if (sessions === void 0 || source === void 0) {
1984
+ capabilityLedgerOf(ctx, state).reportDegraded({
1985
+ capability: "ctx.navigateTree",
1986
+ reason: sessions === void 0 ? "this DSH composition mounts no session service." : "tree navigation needs the live session of an active agent.",
1987
+ guidance: "",
1988
+ packageName: state.packageName
1989
+ });
1990
+ return { cancelled: true };
1991
+ }
1992
+ const seq = durableSeqOf(String(targetId));
1993
+ if (seq === void 0) throw new Error(`pi2dsh: ctx.navigateTree(${JSON.stringify(String(targetId))}) — only durable-log entries (projected ids "dsh-<seq>") can be navigation targets; package-appended sidecar entries are not part of the DSH durable log`);
1994
+ const events = source.events ?? [];
1995
+ const capped = Math.min(seq, events.length - 1);
1996
+ const boundary = capped < 0 ? -1 : shrinkToTurnBoundary(events, capped);
1997
+ const child = boundary < 0 ? sessions.create(void 0, { meta: {
1998
+ cwd: cwdOf(agent),
1999
+ parentSession: source.id
2000
+ } }) : sessions.fork(source, boundary);
2001
+ if (options?.summarize === true) {
2002
+ const summary = await summarizeAbandonedBranch(ctx, state, agent, events, boundary, options);
2003
+ if (summary !== void 0) state.bridge.appendBranchSummary(String(child.id), summary, String(targetId));
2004
+ }
2005
+ if (typeof options?.label === "string") state.bridge.appendLabel(String(child.id), String(targetId), options.label);
2006
+ capabilityLedgerOf(ctx, state).reportHostDecision({
2007
+ capability: "ctx.navigateTree",
2008
+ reason: `navigation forked the session at the target on DSH's official surface (child ${String(child.id)}; the DSH tree lives BETWEEN sessions via fork lineage, not inside one log).`,
2009
+ guidance: "Open the navigated session from the DSH session list.",
2010
+ packageName: state.packageName
2011
+ });
2012
+ return { cancelled: false };
2013
+ },
2014
+ switchSession: async (sessionPath, options) => {
2015
+ const sessions = optionalService(ctx, "sessions");
2016
+ if (sessions === void 0) {
2017
+ capabilityLedgerOf(ctx, state).reportDegraded({
2018
+ capability: "ctx.switchSession",
2019
+ reason: "this DSH composition mounts no session service.",
2020
+ guidance: "",
2021
+ packageName: state.packageName
2022
+ });
2023
+ return { cancelled: true };
2024
+ }
2025
+ const raw = String(sessionPath);
2026
+ const base = raw.replace(/\\/g, "/").split("/").pop() ?? raw;
2027
+ const candidate = base.endsWith(".jsonl") ? base.slice(0, -6) : base;
2028
+ const target = sessions.get(candidate) ?? sessions.list().find((entry) => String(entry.id) === candidate);
2029
+ if (target === void 0) throw new Error(`pi2dsh: ctx.switchSession(${JSON.stringify(raw)}) — no live DSH session ${JSON.stringify(candidate)}; switching to persisted sessions is host-owned (resume them from the DSH surface first)`);
2030
+ const withSession = options?.withSession;
2031
+ await withSession?.(contextFor(ctx, state, agent, signal, true, target));
2032
+ capabilityLedgerOf(ctx, state).reportHostDecision({
2033
+ capability: "ctx.switchSession",
2034
+ reason: `the live DSH session ${JSON.stringify(candidate)} was targeted.`,
2035
+ guidance: "Which session the surface shows stays a host choice — open it from the DSH session list.",
2036
+ packageName: state.packageName
2037
+ });
2038
+ return { cancelled: false };
2039
+ },
2040
+ reload: async () => {
2041
+ const remounts = state.shared.packageRemounts;
2042
+ if (remounts === void 0 || remounts.size === 0) return;
2043
+ for (const [name, remount] of remounts) try {
2044
+ await remount();
2045
+ } catch (error) {
2046
+ logger(ctx).warn(`[pi2dsh] ctx.reload: remounting "${name}" failed: ${error instanceof Error ? error.message : String(error)}`);
2047
+ }
2048
+ capabilityLedgerOf(ctx, state).reportHostDecision({
2049
+ capability: "ctx.reload",
2050
+ reason: "extension entries were disposed and remounted through a fresh loader.",
2051
+ guidance: "Skills, prompts, and themes are host-managed and reload when dsh restarts.",
2052
+ packageName: state.packageName
2053
+ });
2054
+ }
1814
2055
  });
1815
2056
  return base;
1816
2057
  }
@@ -2327,45 +2568,102 @@ function sharedHostStateOf(ctx) {
2327
2568
  return shared;
2328
2569
  }
2329
2570
  /**
2330
- * Image-admission companion routes, configured on the DSH side (the
2331
- * engine's or a bundle's cordis config): `{ <existingRoute>: [modelIds] }`
2332
- * registers a `<route>-vision` route that admits images at the host's
2571
+ * Image-admission companion routes: for every text-only route in the DSH
2572
+ * llm directory, a `<route>-vision` route that admits images at the host's
2333
2573
  * admission checks, replaces image blocks with explicit path-carrying
2334
- * notices, and forwards text-only to the original route. The companion is
2335
- * an ordinary directory entry (single-directory contract); Pi's ctx.model
2336
- * reports the original route for it (companionRoutes). Idempotent per
2337
- * host: repeated calls (several packages, engine + bundle) keep one live
2338
- * adapter and one mapping.
2574
+ * notices, and forwards text-only to the original route. What happens to
2575
+ * an admitted image is decided at run time by whatever is mounted — a
2576
+ * vision extension analyzes it through the turn's entering messages, and
2577
+ * without one the notice's file path lets any image-capable tool read it —
2578
+ * so companions need no knowledge of any particular plugin and are
2579
+ * registered AUTOMATICALLY (zero configuration). `visionCompanions: false`
2580
+ * turns them off; an explicit `{ <route>: [modelIds] }` map narrows them.
2581
+ * The directory is live: adapters-updated re-sweeps, adding companions for
2582
+ * new text-only routes and disposing companions whose original vanished.
2583
+ * The companion is an ordinary directory entry (single-directory
2584
+ * contract); Pi's ctx.model reports the original route for it
2585
+ * (companionRoutes). Idempotent per host.
2339
2586
  */
2340
- function registerVisionCompanions(ctx, companions) {
2341
- if (companions === void 0) return;
2587
+ function registerVisionCompanions(ctx, config) {
2588
+ if (config === false) return;
2342
2589
  const shared = sharedHostStateOf(ctx);
2343
2590
  const llm = llmOf(ctx);
2344
- if (llm === void 0) {
2345
- logger(ctx).warn("[pi2dsh] visionCompanions configured, but this composition mounts no llm service; no companion route was registered");
2346
- return;
2347
- }
2348
- for (const [originalId, modelIds] of Object.entries(companions)) {
2349
- if (!Array.isArray(modelIds) || modelIds.length === 0) continue;
2350
- const companionId = `${originalId}-vision`;
2351
- shared.companionRoutes.set(companionId, originalId);
2352
- if (!llm.listProviders().some((provider) => provider.id === originalId)) {
2353
- logger(ctx).warn(`[pi2dsh] visionCompanions names route ${JSON.stringify(originalId)}, but no such llm route exists; no companion route was registered`);
2354
- continue;
2355
- }
2356
- if (shared.providerRouteDisposers.has(`${COMPANION_ROUTE_PREFIX}${companionId}`)) continue;
2357
- try {
2358
- const dispose = llm.registerAdapter([companionId], imageAdmissionCompanionAdapter({
2591
+ if (llm === void 0) return;
2592
+ const sweep = async () => {
2593
+ const explicit = config;
2594
+ const providers = llm.listProviders();
2595
+ const wanted = /* @__PURE__ */ new Map();
2596
+ if (explicit !== void 0) for (const [originalId, modelIds] of Object.entries(explicit)) {
2597
+ if (!Array.isArray(modelIds) || modelIds.length === 0) continue;
2598
+ if (!providers.some((provider) => provider.id === originalId)) {
2599
+ logger(ctx).warn(`[pi2dsh] visionCompanions names route ${JSON.stringify(originalId)}, but no such llm route exists; no companion route was registered`);
2600
+ continue;
2601
+ }
2602
+ wanted.set(`${originalId}-vision`, {
2359
2603
  originalId,
2360
- imageModels: new Set(modelIds.map(String)),
2361
- llm,
2362
- materializeImage: (attachment) => materializeAttachmentImage(ctx, attachment)
2363
- }));
2364
- shared.providerRouteDisposers.set(`${COMPANION_ROUTE_PREFIX}${companionId}`, dispose);
2365
- logger(ctx).info(`[pi2dsh] image-admission companion route ${JSON.stringify(companionId)} registered for ${JSON.stringify(originalId)} (${modelIds.length} models)`);
2366
- } catch (error) {
2367
- logger(ctx).warn(`[pi2dsh] companion route ${JSON.stringify(companionId)} already has a live adapter in this host (${error instanceof Error ? error.message : String(error)}); reusing it`);
2604
+ imageModels: new Set(modelIds.map(String))
2605
+ });
2606
+ }
2607
+ else for (const provider of providers) {
2608
+ if (shared.companionRoutes.has(provider.id)) continue;
2609
+ try {
2610
+ const models = await llm.listModels(provider.id);
2611
+ if (models.length === 0) continue;
2612
+ if (models.every((model) => !(Array.isArray(model.inputModalities) && model.inputModalities.includes("image")))) wanted.set(`${provider.id}-vision`, {
2613
+ originalId: provider.id,
2614
+ imageModels: void 0
2615
+ });
2616
+ } catch {}
2617
+ }
2618
+ for (const [key, dispose] of shared.providerRouteDisposers) {
2619
+ if (!key.startsWith(COMPANION_ROUTE_PREFIX)) continue;
2620
+ const companionId = key.slice(17);
2621
+ const originalId = shared.companionRoutes.get(companionId);
2622
+ if (originalId !== void 0 && !providers.some((provider) => provider.id === originalId)) {
2623
+ shared.providerRouteDisposers.delete(key);
2624
+ dispose();
2625
+ logger(ctx).info(`[pi2dsh] companion route ${JSON.stringify(companionId)} disposed (its original route ${JSON.stringify(originalId)} left the directory)`);
2626
+ }
2627
+ }
2628
+ for (const [companionId, spec] of wanted) {
2629
+ shared.companionRoutes.set(companionId, spec.originalId);
2630
+ if (shared.providerRouteDisposers.has(`${COMPANION_ROUTE_PREFIX}${companionId}`)) continue;
2631
+ try {
2632
+ const dispose = llm.registerAdapter([companionId], imageAdmissionCompanionAdapter({
2633
+ originalId: spec.originalId,
2634
+ ...spec.imageModels === void 0 ? {} : { imageModels: spec.imageModels },
2635
+ llm,
2636
+ materializeImage: (attachment) => materializeAttachmentImage(ctx, attachment)
2637
+ }));
2638
+ shared.providerRouteDisposers.set(`${COMPANION_ROUTE_PREFIX}${companionId}`, dispose);
2639
+ logger(ctx).info(`[pi2dsh] image-admission companion route ${JSON.stringify(companionId)} registered for ${JSON.stringify(spec.originalId)}`);
2640
+ } catch (error) {
2641
+ logger(ctx).warn(`[pi2dsh] companion route ${JSON.stringify(companionId)} already has a live adapter in this host (${error instanceof Error ? error.message : String(error)}); reusing it`);
2642
+ }
2643
+ }
2644
+ };
2645
+ let sweeping = false;
2646
+ let queued = false;
2647
+ const runSweep = () => {
2648
+ if (sweeping) {
2649
+ queued = true;
2650
+ return;
2368
2651
  }
2652
+ sweeping = true;
2653
+ sweep().finally(() => {
2654
+ sweeping = false;
2655
+ if (queued) {
2656
+ queued = false;
2657
+ runSweep();
2658
+ }
2659
+ });
2660
+ };
2661
+ runSweep();
2662
+ if (shared.companionSweepSubscribed !== true) {
2663
+ shared.companionSweepSubscribed = true;
2664
+ ctx.on("llm/adapters-updated", () => {
2665
+ runSweep();
2666
+ });
2369
2667
  }
2370
2668
  }
2371
2669
  const IMAGE_EXTENSIONS = {
@@ -2430,7 +2728,12 @@ function registerLoginCommand(ctx, state) {
2430
2728
  });
2431
2729
  }
2432
2730
  function registerTool(ctx, state, tool) {
2433
- if (state.tools.has(tool.name)) unregisterTool(state, tool.name);
2731
+ if (state.tools.has(tool.name)) {
2732
+ const previous = state.tools.get(tool.name);
2733
+ state.tools.set(tool.name, tool);
2734
+ if (JSON.stringify(previous.parameters ?? null) !== JSON.stringify(tool.parameters ?? null)) logger(ctx).warn(`[pi2dsh] tool ${tool.name} was re-registered with a different parameter schema; the new schema takes effect when dsh restarts (behavior is already live)`);
2735
+ return;
2736
+ }
2434
2737
  const normalized = normalizeToolSchema(tool.parameters);
2435
2738
  for (const warning of normalized.warnings) logger(ctx).warn(`[pi2dsh] tool ${tool.name}: ${warning}`);
2436
2739
  state.tools.set(tool.name, tool);
@@ -2445,12 +2748,13 @@ function registerTool(ctx, state, tool) {
2445
2748
  },
2446
2749
  isConcurrencySafe: () => tool.executionMode === "parallel",
2447
2750
  async execute(args, exec) {
2751
+ const live = state.tools.get(tool.name) ?? tool;
2448
2752
  const mutated = state.argMutations.get(exec);
2449
2753
  if (mutated !== void 0) state.argMutations.delete(exec);
2450
2754
  const effective = mutated ?? args;
2451
- const prepared = tool.prepareArguments?.(cloneJson(effective)) ?? effective;
2755
+ const prepared = live.prepareArguments?.(cloneJson(effective)) ?? effective;
2452
2756
  const agent = exec.agent;
2453
- const result = await normalizeToolResultForDsh(ctx, await state.agentScope.run(agent, () => tool.execute(String(exec.callId), prepared, exec.signal, (update) => {
2757
+ const result = await normalizeToolResultForDsh(ctx, await state.agentScope.run(agent, () => live.execute(String(exec.callId), prepared, exec.signal, (update) => {
2454
2758
  dispatch(state, "tool_execution_update", {
2455
2759
  type: "tool_execution_update",
2456
2760
  toolCallId: String(exec.callId),
@@ -2591,8 +2895,8 @@ function dshCommandName(ctx, piName) {
2591
2895
  }
2592
2896
  function registerCommand(ctx, state, command) {
2593
2897
  if (state.commands.has(command.name)) {
2594
- state.commandDisposers.get(command.name)?.();
2595
- state.commandDisposers.delete(command.name);
2898
+ state.commands.set(command.name, command);
2899
+ return;
2596
2900
  }
2597
2901
  state.commands.set(command.name, command);
2598
2902
  const commands = ctx.get("commands");
@@ -2608,7 +2912,8 @@ function registerCommand(ctx, state, command) {
2608
2912
  async handler(invocation) {
2609
2913
  const agent = invocation.agent;
2610
2914
  const commandContext = contextFor(ctx, state, agent, invocation.signal, true);
2611
- await state.agentScope.run(agent, () => command.handler(String(invocation.rawInput ?? "").trimStart(), commandContext));
2915
+ const live = state.commands.get(command.name) ?? command;
2916
+ await state.agentScope.run(agent, () => live.handler(String(invocation.rawInput ?? "").trimStart(), commandContext));
2612
2917
  const notices = commandContext.__notices;
2613
2918
  return {
2614
2919
  kind: "success",
@@ -2897,7 +3202,7 @@ async function registerPromptCommands(ctx, state, rootDir, manifest) {
2897
3202
  });
2898
3203
  }
2899
3204
  }
2900
- async function loadExtensions(rootDir, manifest, api, onExtensionError) {
3205
+ async function loadExtensions(rootDir, manifest, api, onExtensionError, onCapabilityGap, onHostInfraReference) {
2901
3206
  const resolveShim = async (name) => {
2902
3207
  const compiled = fileURLToPath(new URL(`./compat/${name}.mjs`, import.meta.url));
2903
3208
  try {
@@ -2937,14 +3242,22 @@ async function loadExtensions(rootDir, manifest, api, onExtensionError) {
2937
3242
  });
2938
3243
  const failures = [];
2939
3244
  let mounted = 0;
2940
- for (const extension of manifest.extensions) try {
2941
- const loaded = await jiti.import(join(rootDir, extension));
2942
- const candidate = typeof loaded === "object" && loaded !== null && "default" in loaded ? loaded.default : loaded;
2943
- if (typeof candidate !== "function") throw new TypeError(`Pi extension ${extension} has no default factory function`);
2944
- await candidate(api);
2945
- mounted += 1;
2946
- } catch (error) {
2947
- failures.push(`${extension}: ${error instanceof Error ? error.message : String(error)}`);
3245
+ for (const extension of manifest.extensions) {
3246
+ try {
3247
+ const source = await readFile(join(rootDir, extension), "utf8");
3248
+ const infraImport = /import[^;]*?\b(ModelRuntime|DefaultPackageManager)\b[^;]*?from\s*['"]@(?:earendil-works|mariozechner)\/pi-coding-agent['"]/su.exec(source);
3249
+ if (infraImport !== null) onHostInfraReference?.(infraImport[1], extension);
3250
+ } catch {}
3251
+ try {
3252
+ const loaded = await jiti.import(join(rootDir, extension));
3253
+ const candidate = typeof loaded === "object" && loaded !== null && "default" in loaded ? loaded.default : loaded;
3254
+ if (typeof candidate !== "function") throw new TypeError(`Pi extension ${extension} has no default factory function`);
3255
+ await candidate(api);
3256
+ mounted += 1;
3257
+ } catch (error) {
3258
+ if (error instanceof PiCapabilityError || error instanceof Error && error.name === "PiCapabilityError") onCapabilityGap?.(error, extension);
3259
+ failures.push(`${extension}: ${error instanceof Error ? error.message : String(error)}`);
3260
+ }
2948
3261
  }
2949
3262
  if (failures.length > 0 && mounted === 0 && manifest.extensions.length > 0) throw new Error(`every Pi extension entry failed to load:\n${failures.map((item) => `- ${item}`).join("\n")}`);
2950
3263
  for (const failure of failures) onExtensionError?.(failure);
@@ -2965,6 +3278,7 @@ async function applyPiPackage(ctx, options) {
2965
3278
  const shared = sharedHostStateOf(ctx);
2966
3279
  const state = {
2967
3280
  shared,
3281
+ packageName: options.manifest.package.name,
2968
3282
  handlers: /* @__PURE__ */ new Map(),
2969
3283
  tools: runtimeTools,
2970
3284
  runner: new ExtensionRunner(piToolRecords),
@@ -3058,8 +3372,34 @@ async function applyPiPackage(ctx, options) {
3058
3372
  }, subagentOptions));
3059
3373
  ctx.effect(() => () => __setSubagentSessionFactory(void 0));
3060
3374
  await registerPromptCommands(ctx, state, rootDir, options.manifest);
3061
- await loadExtensions(rootDir, options.manifest, createPiApi(ctx, state), (failure) => logger(ctx).warn(`[pi2dsh] extension entry failed and was skipped (matching Pi's per-extension error isolation): ${failure}`));
3062
- logger(ctx).info(`[pi2dsh] loaded ${options.manifest.package.name}: ${state.tools.size} tools, ${state.commands.size} commands, ${options.manifest.skillDirs.length} skill roots`);
3375
+ const onExtensionError = (failure) => logger(ctx).warn(`[pi2dsh] extension entry failed and was skipped (matching Pi's per-extension error isolation): ${failure}`);
3376
+ const onCapabilityGap = (gap) => capabilityLedgerOf(ctx, state).reportUnusable({
3377
+ capability: gap.capability,
3378
+ reason: "this package needs it during startup.",
3379
+ guidance: gap.message,
3380
+ packageName: state.packageName
3381
+ });
3382
+ const onHostInfraReference = (symbol) => capabilityLedgerOf(ctx, state).reportStartupReference({
3383
+ capability: symbol,
3384
+ reason: symbol === "DefaultPackageManager" ? "installing packages is owned by the DSH host and its security gates (dsh plugin add/remove)." : "standalone model stacks are owned by the DSH host llm configuration (packages read ctx.modelRegistry).",
3385
+ guidance: "",
3386
+ packageName: state.packageName
3387
+ });
3388
+ const mountExtensions = () => loadExtensions(rootDir, options.manifest, createPiApi(ctx, state), onExtensionError, onCapabilityGap, onHostInfraReference);
3389
+ await mountExtensions();
3390
+ const remounts = shared.packageRemounts ??= /* @__PURE__ */ new Map();
3391
+ remounts.set(state.packageName, async () => {
3392
+ state.handlers.clear();
3393
+ state.eventBus.removeAllListeners();
3394
+ await registerPromptCommands(ctx, state, rootDir, options.manifest);
3395
+ await mountExtensions();
3396
+ });
3397
+ ctx.effect(() => () => {
3398
+ remounts.delete(state.packageName);
3399
+ });
3400
+ const health = state.shared.capabilityLedger?.healthOf(state.packageName);
3401
+ const healthSuffix = health === void 0 || health.status === "ok" ? "" : ` — ${health.status.toUpperCase()}: missing Pi capabilities ${health.gaps.join(", ")}`;
3402
+ logger(ctx).info(`[pi2dsh] loaded ${options.manifest.package.name}: ${state.tools.size} tools, ${state.commands.size} commands, ${options.manifest.skillDirs.length} skill roots${healthSuffix}`);
3063
3403
  }
3064
3404
  const runtimeInternals = {
3065
3405
  expandPrompt,
@@ -3070,4 +3410,4 @@ const runtimeInternals = {
3070
3410
  //#endregion
3071
3411
  export { runtimeInternals as i, normalizeToolSchema as n, registerVisionCompanions as r, applyPiPackage as t };
3072
3412
 
3073
- //# sourceMappingURL=runtime-B-Mun-jX.mjs.map
3413
+ //# sourceMappingURL=runtime-Ce3PTbWU.mjs.map