pi2dsh 0.9.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 (33) hide show
  1. package/README.md +4 -1
  2. package/README.zh.md +4 -1
  3. package/dist/{analyzer-C86ba-27.mjs → analyzer-CsrBeS8r.mjs} +2 -2
  4. package/dist/{analyzer-C86ba-27.mjs.map → analyzer-CsrBeS8r.mjs.map} +1 -1
  5. package/dist/cli.mjs +3 -3
  6. package/dist/compat/pi-ai.d.mts +2 -1
  7. package/dist/compat/pi-ai.d.mts.map +1 -1
  8. package/dist/compat/pi-ai.mjs +2 -3
  9. package/dist/compat/pi-coding-agent.d.mts +259 -29
  10. package/dist/compat/pi-coding-agent.d.mts.map +1 -1
  11. package/dist/compat/pi-coding-agent.mjs +2 -2
  12. package/dist/credentials-oauth.mjs +2 -2
  13. package/dist/{generator-C0jNM-yu.mjs → generator-CSOv6wYT.mjs} +2 -2
  14. package/dist/{generator-C0jNM-yu.mjs.map → generator-CSOv6wYT.mjs.map} +1 -1
  15. package/dist/host.mjs +1 -1
  16. package/dist/index.mjs +4 -4
  17. package/dist/{mcp-config-Dlou-ZA-.mjs → mcp-config-Cn5gWemZ.mjs} +29 -29
  18. package/dist/mcp-config-Cn5gWemZ.mjs.map +1 -0
  19. package/dist/{pi-ai-BA3h0Dwv.mjs → pi-ai-Dyg4zyLZ.mjs} +196 -75
  20. package/dist/pi-ai-Dyg4zyLZ.mjs.map +1 -0
  21. package/dist/{pi-coding-agent-CznNsOnV.mjs → pi-coding-agent-Ctw-BhoY.mjs} +0 -0
  22. package/dist/pi-coding-agent-Ctw-BhoY.mjs.map +1 -0
  23. package/dist/{runtime-DMEJknhK.mjs → runtime-Ce3PTbWU.mjs} +417 -135
  24. package/dist/runtime-Ce3PTbWU.mjs.map +1 -0
  25. package/dist/runtime.d.mts.map +1 -1
  26. package/dist/runtime.mjs +1 -1
  27. package/package.json +5 -3
  28. package/dist/mcp-config-Dlou-ZA-.mjs.map +0 -1
  29. package/dist/pi-ai-BA3h0Dwv.mjs.map +0 -1
  30. package/dist/pi-coding-agent-CznNsOnV.mjs.map +0 -1
  31. package/dist/pi-uuid-Db8ShZsK.mjs +0 -47
  32. package/dist/pi-uuid-Db8ShZsK.mjs.map +0 -1
  33. package/dist/runtime-DMEJknhK.mjs.map +0 -1
@@ -1,7 +1,7 @@
1
1
 
2
- import { P as AssistantMessageEventStream, i as builtinProviders, r as __setPiAiLlmBridge } from "./pi-ai-BA3h0Dwv.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: {
@@ -1493,6 +1514,11 @@ function cwdOf(agent) {
1493
1514
  function unsupported(name) {
1494
1515
  throw new Error(`pi2dsh: Pi API ${name} requires a native DSH port; inspect the compatibility report`);
1495
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
+ }
1496
1522
  function optionalService(ctx, name) {
1497
1523
  return ctx.get(name);
1498
1524
  }
@@ -1551,7 +1577,45 @@ function thinkingLevelOf(state, agent) {
1551
1577
  }
1552
1578
  return state.globalThinkingLevel;
1553
1579
  }
1554
- 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) {
1555
1619
  const notices = [];
1556
1620
  const userQuestions = optionalService(ctx, "userQuestions");
1557
1621
  const ui = {
@@ -1626,7 +1690,7 @@ function contextFor(ctx, state, agent, signal, command = false) {
1626
1690
  state.toolsExpanded = expanded === true;
1627
1691
  }
1628
1692
  };
1629
- const session = agentSession(agent);
1693
+ const session = sessionOverride ?? agentSession(agent);
1630
1694
  const providerConfig = (name) => state.providers.get(name);
1631
1695
  const catalog = state.modelCatalog;
1632
1696
  const piNativeEntry = (provider, id) => {
@@ -1666,119 +1730,121 @@ function contextFor(ctx, state, agent, signal, command = false) {
1666
1730
  stream: dshRoutedStream,
1667
1731
  streamSimple: dshRoutedStream
1668
1732
  });
1669
- const base = {
1670
- ui,
1671
- mode: "rpc",
1672
- hasUI: userQuestions !== void 0,
1673
- cwd: cwdOf(agent),
1674
- sessionManager: session === void 0 ? state.bridge.readonlySessionManager({
1675
- id: "pi2dsh-detached",
1676
- events: []
1677
- }, cwdOf(agent)) : state.bridge.readonlySessionManager(session, cwdOf(agent)),
1678
- modelRegistry: {
1679
- getAll: () => allModels(),
1680
- getAvailable: () => allModels(),
1681
- find: (provider, modelId) => {
1682
- const entry = catalog?.find(provider, modelId);
1683
- return entry === void 0 ? void 0 : restorePiShape(entry);
1684
- },
1685
- getError: () => void 0,
1686
- hasConfiguredAuth: (model) => {
1687
- const provider = String(model?.provider ?? "");
1688
- if (provider.length === 0) return false;
1689
- return state.providers.has(provider) || (catalog?.all() ?? []).some((entry) => entry.provider === provider);
1690
- },
1691
- getApiKeyAndHeaders: async (model) => {
1692
- const provider = String(model?.provider ?? "");
1693
- const config = providerConfig(provider);
1694
- if (config !== void 0) {
1695
- const auth = (await resolvePiProviderAuth({
1696
- providerId: provider,
1697
- providerConfig: config,
1698
- store: oauthStoreOf(state)
1699
- }))?.auth;
1700
- if (auth?.apiKey === void 0) return { ok: false };
1701
- return {
1702
- ok: true,
1703
- apiKey: auth.apiKey,
1704
- ...auth.headers === void 0 ? {} : { headers: auth.headers },
1705
- ...auth.baseUrl === void 0 && config.baseUrl === void 0 ? {} : { baseUrl: auth.baseUrl ?? config.baseUrl }
1706
- };
1707
- }
1708
- try {
1709
- const entry = llmOf(ctx)?.listConfigurableProviders?.()?.find((candidate) => candidate.provider === provider);
1710
- if (entry === void 0) return { ok: false };
1711
- const section = ctx.get("settings")?.get(entry.settingsNs);
1712
- const ref = entry.settingsPath.reduce((node, key) => typeof node === "object" && node !== null ? node[key] : void 0, section)?.apiKeyEnv;
1713
- if (typeof ref !== "string" || ref.length === 0) return { ok: false };
1714
- const resolved = await ctx.get("credentials")?.resolve(ref);
1715
- if (typeof resolved?.value !== "string" || resolved.value.length === 0) return { ok: false };
1716
- return {
1717
- ok: true,
1718
- apiKey: resolved.value
1719
- };
1720
- } catch {
1721
- return { ok: false };
1722
- }
1723
- },
1724
- getProviderAuthStatus: (provider) => providerSupportsOAuth(providerConfig(provider)) ? "oauth" : "none",
1725
- getProvider: (provider) => {
1726
- const base = providerConfig(provider);
1727
- if (base !== void 0) return state.providerRouteDisposers.has(provider) ? dshRoutedPiProvider(base, provider) : base;
1728
- if ((catalog?.all() ?? []).some((entry) => entry.provider === provider)) return dshRoutedPiProvider({
1729
- id: provider,
1730
- name: provider
1731
- }, provider);
1732
- },
1733
- complete: async (model, context, options) => {
1734
- const result = await dshRoutedStream(model, context ?? {}, options).result();
1735
- if (result instanceof Error) throw result;
1736
- return result;
1737
- },
1738
- getRegisteredProviderConfig: (provider) => providerConfig(provider),
1739
- getRegisteredProviderIds: () => [...state.providers.keys()],
1740
- getProviderDisplayName: (provider) => {
1741
- const config = providerConfig(provider);
1742
- return typeof config?.name === "string" ? config.name : provider;
1743
- },
1744
- getProviderAuth: async (provider) => {
1745
- const config = providerConfig(provider);
1746
- if (config === void 0) return void 0;
1747
- const resolved = await resolvePiProviderAuth({
1748
- providerId: provider,
1749
- providerConfig: config,
1750
- store: oauthStoreOf(state)
1751
- });
1752
- if (resolved?.auth === void 0) return resolved;
1753
- return resolved.auth.baseUrl === void 0 && config.baseUrl !== void 0 ? {
1754
- ...resolved,
1755
- auth: {
1756
- ...resolved.auth,
1757
- baseUrl: config.baseUrl
1758
- }
1759
- } : resolved;
1760
- },
1761
- getApiKeyForProvider: async (provider) => {
1762
- const config = providerConfig(provider);
1763
- if (config === void 0) return void 0;
1764
- 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({
1765
1751
  providerId: provider,
1766
1752
  providerConfig: config,
1767
1753
  store: oauthStoreOf(state)
1768
- }))?.auth)?.apiKey;
1769
- },
1770
- isUsingOAuth: (model) => {
1771
- const provider = String(model?.provider ?? "");
1772
- return provider.length > 0 && providerSupportsOAuth(providerConfig(provider));
1773
- },
1774
- refresh: async () => {
1775
- await catalog?.refresh();
1754
+ }))?.auth;
1755
+ if (auth?.apiKey === void 0) return { ok: false };
1756
+ return {
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 }
1761
+ };
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 };
1776
1771
  return {
1777
- models: allModels(),
1778
- errors: []
1772
+ ok: true,
1773
+ apiKey: resolved.value
1779
1774
  };
1775
+ } catch {
1776
+ return { ok: false };
1780
1777
  }
1781
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,
1782
1848
  model: agent === void 0 ? void 0 : currentPiModel(state, agent),
1783
1849
  scopedModels: catalog?.all() ?? [],
1784
1850
  thinkingLevel: thinkingLevelOf(state, agent),
@@ -1794,9 +1860,55 @@ function contextFor(ctx, state, agent, signal, command = false) {
1794
1860
  });
1795
1861
  },
1796
1862
  hasPendingMessages: () => false,
1797
- 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
+ },
1798
1871
  getContextUsage: () => void 0,
1799
- 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
+ },
1800
1912
  getSystemPrompt: () => state.currentSystemPrompt,
1801
1913
  __agent: agent,
1802
1914
  __notices: notices
@@ -1807,11 +1919,139 @@ function contextFor(ctx, state, agent, signal, command = false) {
1807
1919
  const wait = agent?.whenIdle;
1808
1920
  if (typeof wait === "function") await wait.call(agent);
1809
1921
  },
1810
- newSession: () => unsupported("ctx.newSession"),
1811
- fork: () => unsupported("ctx.fork"),
1812
- navigateTree: () => unsupported("ctx.navigateTree"),
1813
- switchSession: () => unsupported("ctx.switchSession"),
1814
- 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
+ }
1815
2055
  });
1816
2056
  return base;
1817
2057
  }
@@ -2488,7 +2728,12 @@ function registerLoginCommand(ctx, state) {
2488
2728
  });
2489
2729
  }
2490
2730
  function registerTool(ctx, state, tool) {
2491
- 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
+ }
2492
2737
  const normalized = normalizeToolSchema(tool.parameters);
2493
2738
  for (const warning of normalized.warnings) logger(ctx).warn(`[pi2dsh] tool ${tool.name}: ${warning}`);
2494
2739
  state.tools.set(tool.name, tool);
@@ -2503,12 +2748,13 @@ function registerTool(ctx, state, tool) {
2503
2748
  },
2504
2749
  isConcurrencySafe: () => tool.executionMode === "parallel",
2505
2750
  async execute(args, exec) {
2751
+ const live = state.tools.get(tool.name) ?? tool;
2506
2752
  const mutated = state.argMutations.get(exec);
2507
2753
  if (mutated !== void 0) state.argMutations.delete(exec);
2508
2754
  const effective = mutated ?? args;
2509
- const prepared = tool.prepareArguments?.(cloneJson(effective)) ?? effective;
2755
+ const prepared = live.prepareArguments?.(cloneJson(effective)) ?? effective;
2510
2756
  const agent = exec.agent;
2511
- 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) => {
2512
2758
  dispatch(state, "tool_execution_update", {
2513
2759
  type: "tool_execution_update",
2514
2760
  toolCallId: String(exec.callId),
@@ -2649,8 +2895,8 @@ function dshCommandName(ctx, piName) {
2649
2895
  }
2650
2896
  function registerCommand(ctx, state, command) {
2651
2897
  if (state.commands.has(command.name)) {
2652
- state.commandDisposers.get(command.name)?.();
2653
- state.commandDisposers.delete(command.name);
2898
+ state.commands.set(command.name, command);
2899
+ return;
2654
2900
  }
2655
2901
  state.commands.set(command.name, command);
2656
2902
  const commands = ctx.get("commands");
@@ -2666,7 +2912,8 @@ function registerCommand(ctx, state, command) {
2666
2912
  async handler(invocation) {
2667
2913
  const agent = invocation.agent;
2668
2914
  const commandContext = contextFor(ctx, state, agent, invocation.signal, true);
2669
- 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));
2670
2917
  const notices = commandContext.__notices;
2671
2918
  return {
2672
2919
  kind: "success",
@@ -2955,7 +3202,7 @@ async function registerPromptCommands(ctx, state, rootDir, manifest) {
2955
3202
  });
2956
3203
  }
2957
3204
  }
2958
- async function loadExtensions(rootDir, manifest, api, onExtensionError) {
3205
+ async function loadExtensions(rootDir, manifest, api, onExtensionError, onCapabilityGap, onHostInfraReference) {
2959
3206
  const resolveShim = async (name) => {
2960
3207
  const compiled = fileURLToPath(new URL(`./compat/${name}.mjs`, import.meta.url));
2961
3208
  try {
@@ -2995,14 +3242,22 @@ async function loadExtensions(rootDir, manifest, api, onExtensionError) {
2995
3242
  });
2996
3243
  const failures = [];
2997
3244
  let mounted = 0;
2998
- for (const extension of manifest.extensions) try {
2999
- const loaded = await jiti.import(join(rootDir, extension));
3000
- const candidate = typeof loaded === "object" && loaded !== null && "default" in loaded ? loaded.default : loaded;
3001
- if (typeof candidate !== "function") throw new TypeError(`Pi extension ${extension} has no default factory function`);
3002
- await candidate(api);
3003
- mounted += 1;
3004
- } catch (error) {
3005
- 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
+ }
3006
3261
  }
3007
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")}`);
3008
3263
  for (const failure of failures) onExtensionError?.(failure);
@@ -3023,6 +3278,7 @@ async function applyPiPackage(ctx, options) {
3023
3278
  const shared = sharedHostStateOf(ctx);
3024
3279
  const state = {
3025
3280
  shared,
3281
+ packageName: options.manifest.package.name,
3026
3282
  handlers: /* @__PURE__ */ new Map(),
3027
3283
  tools: runtimeTools,
3028
3284
  runner: new ExtensionRunner(piToolRecords),
@@ -3116,8 +3372,34 @@ async function applyPiPackage(ctx, options) {
3116
3372
  }, subagentOptions));
3117
3373
  ctx.effect(() => () => __setSubagentSessionFactory(void 0));
3118
3374
  await registerPromptCommands(ctx, state, rootDir, options.manifest);
3119
- 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}`));
3120
- 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}`);
3121
3403
  }
3122
3404
  const runtimeInternals = {
3123
3405
  expandPrompt,
@@ -3128,4 +3410,4 @@ const runtimeInternals = {
3128
3410
  //#endregion
3129
3411
  export { runtimeInternals as i, normalizeToolSchema as n, registerVisionCompanions as r, applyPiPackage as t };
3130
3412
 
3131
- //# sourceMappingURL=runtime-DMEJknhK.mjs.map
3413
+ //# sourceMappingURL=runtime-Ce3PTbWU.mjs.map