@yanlinglabs/winter-agent-sdk 0.0.20 → 0.0.22

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.
@@ -118,7 +118,7 @@ function resolveWinterHome2(env, brand) {
118
118
  return override;
119
119
  const profile = e[envName2(b, "PROFILE")];
120
120
  const dirName = profile !== undefined && profile.trim() === "dev" ? `${b.homeDirName}-dev` : b.homeDirName;
121
- return join(homedir(), dirName);
121
+ return join(homedir(), dirName, "sdk");
122
122
  }
123
123
  function resolveKeychainServiceForProfile2(brand, env, hostSetKeychainService) {
124
124
  if (hostSetKeychainService)
package/dist/index.d.ts CHANGED
@@ -32,6 +32,8 @@ export { resolveWinterHome, resolveKeychainServiceForProfile, isUnset } from "./
32
32
  export { transcriptProjectKey, TRANSCRIPT_PROJECT_KEY_MAX_LENGTH, isVendorCompliantProjectKey } from "./paths/project-key.js";
33
33
  export { compatibilityKeys } from "./paths/keys.js";
34
34
  export type { CompatibilityKeys } from "./paths/keys.js";
35
+ export { globalConfigFileName } from "./paths/global-config.js";
36
+ export { storeHomeEnvName, pluginCacheDirEnvName, providerManagedByHostEnvName, disableCronEnvName } from "./paths/host-env.js";
35
37
  export { WinterCompatibilitySessionStore, WinterStoreError, WinterStoreLeaseError, DIALECT_RECORD_ENTRY_TYPE, PROVIDER_STATE_FILE_SUFFIX, } from "./store/session-store.js";
36
38
  export type { SessionKey, SessionStoreEntry, SessionSummaryEntry, SessionStore } from "./store/session-store.js";
37
39
  export { forkSessionByKey } from "./store/fork-session.js";
@@ -49,3 +51,7 @@ export type { PermissionUpdate, PermissionUpdateDestination, RuleSource } from "
49
51
  export type { CanUseTool, PermissionResult, PermissionDecisionClassification, PermissionRequestPayload } from "./permissions/types.js";
50
52
  export { HOOK_EVENTS } from "./permissions/types.js";
51
53
  export type { HookEvent, HookEventName, HookSource, HookPermissionDecision, BaseHookInput, PreToolUseHookInput, PostToolUseHookInput, PostToolUseFailureHookInput, UserPromptSubmitHookInput, StopHookInput, SessionStartHookInput, SessionEndHookInput, NotificationHookInput, PermissionRequestHookInput, PermissionDeniedHookInput, GenericHookInput, HookInput, AsyncHookJSONOutput, SyncHookJSONOutput, HookJSONOutput, PreToolUseHookSpecificOutput, PostToolUseHookSpecificOutput, PostToolUseFailureHookSpecificOutput, UserPromptSubmitHookSpecificOutput, StopHookSpecificOutput, SessionStartHookSpecificOutput, NotificationHookSpecificOutput, PermissionRequestHookSpecificOutput, PermissionDeniedHookSpecificOutput, GenericHookSpecificOutput, HookSpecificOutput, HookCallback, HookCallbackMatcher, HookInvocationPayload, } from "./permissions/types.js";
54
+ export { CATALOG_TAG_RENAMES } from "@yanlinglabs/winter-provider-catalog";
55
+ export { PluginManagerError, listMarketplaces, addMarketplace, removeMarketplace, updateMarketplace, installPlugin, uninstallPlugin, setPluginEnabled, updatePlugin, listPlugins } from "./plugins/manage.js";
56
+ export type { PluginScope, PluginManagerOptions, MarketplaceInfo, InstalledPlugin, PluginListing } from "./plugins/manage.js";
57
+ export { resolveMarketplacePluginPath } from "./plugins/marketplace-path.js";
package/dist/index.js CHANGED
@@ -14,7 +14,7 @@ import {
14
14
  DIALECT_RECORD_ENTRY_TYPE2,
15
15
  PROVIDER_STATE_FILE_SUFFIX2,
16
16
  WinterCompatibilitySessionStore2
17
- } from "./index-qst9rwbb.js";
17
+ } from "./index-0xks1t4r.js";
18
18
  import {
19
19
  deliveryUncertain2,
20
20
  refused2,
@@ -1021,7 +1021,7 @@ function query(args) {
1021
1021
  return gen;
1022
1022
  }
1023
1023
  // src/version.ts
1024
- var SDK_VERSION = "0.0.20";
1024
+ var SDK_VERSION = "0.0.22";
1025
1025
  // src/paths/project-key.ts
1026
1026
  var TRANSCRIPT_PROJECT_KEY_MAX_LENGTH = 64;
1027
1027
  var VENDOR_PROJECT_KEY_PATTERN = /^[A-Za-z0-9_-]{1,64}$/;
@@ -1110,6 +1110,23 @@ function compatibilityKeys(cwd) {
1110
1110
  const memoryKey = commonRoot === null ? cwdKey : transcriptProjectKey(commonRoot);
1111
1111
  return { transcriptProjectKey: cwdKey, memoryProjectKey: memoryKey, tempProjectKey: cwdKey };
1112
1112
  }
1113
+ // src/paths/global-config.ts
1114
+ function globalConfigFileName(brand) {
1115
+ return `${brand.homeDirName}.json`;
1116
+ }
1117
+ // src/paths/host-env.ts
1118
+ function storeHomeEnvName(brand) {
1119
+ return envName2(brand, "STORE_HOME");
1120
+ }
1121
+ function pluginCacheDirEnvName(brand) {
1122
+ return envName2(brand, "PLUGIN_CACHE_DIR");
1123
+ }
1124
+ function providerManagedByHostEnvName(brand) {
1125
+ return envName2(brand, "PROVIDER_MANAGED_BY_HOST");
1126
+ }
1127
+ function disableCronEnvName(brand) {
1128
+ return envName2(brand, "DISABLE_CRON");
1129
+ }
1113
1130
  // src/store/fork-session.ts
1114
1131
  import { randomUUID as randomUUID2 } from "node:crypto";
1115
1132
  var IDENTITY_FIELDS = ["providerId", "modelKey", "adapterId", "adapterVersion", "catalogVersion", "authRef", "classifierPin"];
@@ -1442,6 +1459,45 @@ function unionRuleArray(tiersLowestFirst, key) {
1442
1459
  }
1443
1460
  return out.length > 0 ? out : undefined;
1444
1461
  }
1462
+ function unionSandboxFilesystemArray(tiersLowestFirst, key) {
1463
+ const seen = new Set;
1464
+ const out = [];
1465
+ for (const tier of tiersLowestFirst) {
1466
+ const sandbox = tier.values["sandbox"];
1467
+ if (!isPlainObject2(sandbox))
1468
+ continue;
1469
+ const filesystem = sandbox["filesystem"];
1470
+ if (!isPlainObject2(filesystem))
1471
+ continue;
1472
+ for (const rule of stringArrayOrUndefined(filesystem[key]) ?? []) {
1473
+ if (seen.has(rule))
1474
+ continue;
1475
+ seen.add(rule);
1476
+ out.push(rule);
1477
+ }
1478
+ }
1479
+ return out.length > 0 ? out : undefined;
1480
+ }
1481
+ function unionSandboxFilesystemDenyArrays(effective, tiersLowestFirst) {
1482
+ const mergedSandbox = effective["sandbox"];
1483
+ const mergedFilesystem = isPlainObject2(mergedSandbox) ? mergedSandbox["filesystem"] : undefined;
1484
+ const filesystem = isPlainObject2(mergedFilesystem) ? { ...mergedFilesystem } : {};
1485
+ let any = isPlainObject2(mergedFilesystem);
1486
+ for (const key of ["allowWrite", "denyWrite", "allowRead", "denyRead"]) {
1487
+ const unioned = unionSandboxFilesystemArray(tiersLowestFirst, key);
1488
+ if (unioned === undefined)
1489
+ delete filesystem[key];
1490
+ else {
1491
+ filesystem[key] = unioned;
1492
+ any = true;
1493
+ }
1494
+ }
1495
+ if (!any)
1496
+ return;
1497
+ const sandbox = isPlainObject2(mergedSandbox) ? { ...mergedSandbox } : {};
1498
+ sandbox["filesystem"] = filesystem;
1499
+ effective["sandbox"] = sandbox;
1500
+ }
1445
1501
  function restrictProviderEnables(effective, tiersLowestFirst) {
1446
1502
  const disabled = new Set;
1447
1503
  for (const tier of tiersLowestFirst) {
@@ -1540,6 +1596,7 @@ async function resolveSettingsDetailed(opts = {}) {
1540
1596
  const overlayFilteredTiers = lowestFirst.map((entry) => ({ values: tierContribution(entry.values, entry.source === "project", trustedWorkspace) }));
1541
1597
  unionPermissionRuleArrays(effective, overlayFilteredTiers);
1542
1598
  restrictProviderEnables(effective, overlayFilteredTiers);
1599
+ unionSandboxFilesystemDenyArrays(effective, overlayFilteredTiers);
1543
1600
  if (!trustedWorkspace) {
1544
1601
  const projectIndex = lowestFirst.findIndex((e) => e.source === "project");
1545
1602
  if (projectIndex !== -1) {
@@ -1740,9 +1797,394 @@ var HOOK_EVENTS = [
1740
1797
  "FileChanged",
1741
1798
  "DirectoryAdded"
1742
1799
  ];
1800
+
1801
+ // src/index.ts
1802
+ import { CATALOG_TAG_RENAMES } from "@yanlinglabs/winter-provider-catalog";
1803
+
1804
+ // src/plugins/manage.ts
1805
+ import { mkdir, open, readFile as readFile2, rename, rmdir, unlink, writeFile } from "node:fs/promises";
1806
+ import { dirname as dirname3, isAbsolute as isAbsolute2, join as join3, resolve as resolve2 } from "node:path";
1807
+ import { randomBytes } from "node:crypto";
1808
+
1809
+ // src/plugins/marketplace-path.ts
1810
+ import { resolve, sep } from "node:path";
1811
+ var BARE_SOURCE_NAME = /^[A-Za-z0-9][-A-Za-z0-9._]*$/;
1812
+ function isBareSourceName(source) {
1813
+ return BARE_SOURCE_NAME.test(source) && !source.includes("..");
1814
+ }
1815
+ function normalizePluginRoot(pluginRoot) {
1816
+ if (typeof pluginRoot !== "string" || pluginRoot === "" || pluginRoot.startsWith("/") || pluginRoot.includes("\\") || pluginRoot.includes(":"))
1817
+ return;
1818
+ const stripped = pluginRoot.replace(/^\.\//, "").replace(/\/+$/, "");
1819
+ if (stripped === "" || stripped === ".")
1820
+ return ".";
1821
+ if (stripped.split("/").some((segment) => segment === "" || segment === "." || segment === ".."))
1822
+ return;
1823
+ return stripped;
1824
+ }
1825
+ function withPluginRootPrefix(source, normalizedRoot) {
1826
+ if (normalizedRoot === undefined || !isBareSourceName(source))
1827
+ return source;
1828
+ return normalizedRoot === "." ? `./${source}` : `./${normalizedRoot}/${source}`;
1829
+ }
1830
+ function schemaSourcePath(source) {
1831
+ const normalized = source === "." ? "./" : source;
1832
+ return normalized.startsWith("./") ? normalized : undefined;
1833
+ }
1834
+ function resolveWithinBase(base, relativePath) {
1835
+ const normalizedBase = resolve(base);
1836
+ const resolved = resolve(base, relativePath);
1837
+ if (resolved !== normalizedBase && !resolved.startsWith(normalizedBase + sep)) {
1838
+ throw new Error(`Path traversal detected: "${relativePath}" would escape the base directory`);
1839
+ }
1840
+ return resolved;
1841
+ }
1842
+ function resolveMarketplacePluginPath(installLocation, pluginRoot, source) {
1843
+ if (typeof source !== "string" || source === "")
1844
+ return;
1845
+ const normalizedRoot = normalizePluginRoot(pluginRoot);
1846
+ const prefixed = withPluginRootPrefix(source, normalizedRoot);
1847
+ const sourcePath = schemaSourcePath(prefixed);
1848
+ if (sourcePath === undefined)
1849
+ return;
1850
+ try {
1851
+ return resolveWithinBase(installLocation, sourcePath);
1852
+ } catch {
1853
+ return;
1854
+ }
1855
+ }
1856
+
1857
+ // src/plugins/manage.ts
1858
+ class PluginManagerError extends Error {
1859
+ constructor(message) {
1860
+ super(message);
1861
+ this.name = "PluginManagerError";
1862
+ }
1863
+ }
1864
+ function installedPluginsPath(o) {
1865
+ return join3(o.pluginsRoot, "installed_plugins.json");
1866
+ }
1867
+ function knownMarketplacesPath(o) {
1868
+ return join3(o.pluginsRoot, "known_marketplaces.json");
1869
+ }
1870
+ function isEnoent(err) {
1871
+ return err?.code === "ENOENT";
1872
+ }
1873
+ var RETRYABLE_RENAME_CODES = new Set(["EXDEV", "EPERM", "EEXIST", "EBUSY"]);
1874
+ async function writeJsonAtomicNoLock(path, data) {
1875
+ const body = `${JSON.stringify(data, null, 2)}
1876
+ `;
1877
+ const tmp = `${path}.tmp.${randomBytes(4).toString("hex")}`;
1878
+ let created = false;
1879
+ try {
1880
+ const handle = await open(tmp, "wx", 384);
1881
+ created = true;
1882
+ try {
1883
+ await handle.writeFile(body, "utf8");
1884
+ } finally {
1885
+ await handle.close();
1886
+ }
1887
+ await rename(tmp, path);
1888
+ } catch (err) {
1889
+ const code = err?.code;
1890
+ if (code === undefined || !RETRYABLE_RENAME_CODES.has(code))
1891
+ throw err;
1892
+ await writeFile(path, body, { encoding: "utf8", mode: 384 });
1893
+ if (created) {
1894
+ try {
1895
+ await unlink(tmp);
1896
+ } catch (cleanupErr) {
1897
+ if (!isEnoent(cleanupErr))
1898
+ throw cleanupErr;
1899
+ }
1900
+ }
1901
+ }
1902
+ }
1903
+ async function readInstalledPluginsFile(o) {
1904
+ try {
1905
+ const raw = await readFile2(installedPluginsPath(o), "utf8");
1906
+ const parsed = JSON.parse(raw);
1907
+ const plugins = parsed !== null && typeof parsed === "object" && typeof parsed.plugins === "object" && parsed.plugins !== null ? parsed.plugins : {};
1908
+ return { version: 2, plugins };
1909
+ } catch (err) {
1910
+ if (isEnoent(err))
1911
+ return { version: 2, plugins: {} };
1912
+ throw err;
1913
+ }
1914
+ }
1915
+ var MARKETPLACES_LOCK_ATTEMPTS = 25;
1916
+ var MARKETPLACES_LOCK_RETRY_DELAY_MS = 20;
1917
+ async function withMarketplacesLock(path, fn) {
1918
+ const lockPath = `${path}.lock`;
1919
+ let acquired = false;
1920
+ for (let attempt = 0;attempt < MARKETPLACES_LOCK_ATTEMPTS; attempt++) {
1921
+ try {
1922
+ await mkdir(lockPath);
1923
+ acquired = true;
1924
+ break;
1925
+ } catch (err) {
1926
+ if (err?.code !== "EEXIST")
1927
+ throw err;
1928
+ await new Promise((r) => setTimeout(r, MARKETPLACES_LOCK_RETRY_DELAY_MS));
1929
+ }
1930
+ }
1931
+ try {
1932
+ return await fn();
1933
+ } finally {
1934
+ if (acquired) {
1935
+ try {
1936
+ await rmdir(lockPath);
1937
+ } catch (err) {
1938
+ if (!isEnoent(err))
1939
+ throw err;
1940
+ }
1941
+ }
1942
+ }
1943
+ }
1944
+ async function readKnownMarketplacesFile(o) {
1945
+ try {
1946
+ const raw = await readFile2(knownMarketplacesPath(o), "utf8");
1947
+ const parsed = JSON.parse(raw);
1948
+ return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
1949
+ } catch (err) {
1950
+ if (isEnoent(err))
1951
+ return {};
1952
+ throw err;
1953
+ }
1954
+ }
1955
+ async function writeKnownMarketplacesFile(path, file) {
1956
+ await writeJsonAtomicNoLock(path, file);
1957
+ }
1958
+ async function readDirectoryMarketplaceManifest(marketplaceDir) {
1959
+ const manifestPath = join3(marketplaceDir, ".claude-plugin", "marketplace.json");
1960
+ let raw;
1961
+ try {
1962
+ raw = await readFile2(manifestPath, "utf8");
1963
+ } catch (err) {
1964
+ if (isEnoent(err))
1965
+ throw new PluginManagerError(`${manifestPath}: no marketplace manifest there (expected .claude-plugin/marketplace.json under ${marketplaceDir})`);
1966
+ throw err;
1967
+ }
1968
+ let parsed;
1969
+ try {
1970
+ parsed = JSON.parse(raw);
1971
+ } catch (err) {
1972
+ throw new PluginManagerError(`${manifestPath}: malformed JSON (${err.message})`);
1973
+ }
1974
+ if (typeof parsed !== "object" || parsed === null)
1975
+ throw new PluginManagerError(`${manifestPath}: expected a JSON object at the top level`);
1976
+ const obj = parsed;
1977
+ if (typeof obj.name !== "string" || obj.name.length === 0)
1978
+ throw new PluginManagerError(`${manifestPath}: missing required "name"`);
1979
+ if (!Array.isArray(obj.plugins))
1980
+ throw new PluginManagerError(`${manifestPath}: missing required "plugins" array`);
1981
+ return {
1982
+ name: obj.name,
1983
+ plugins: obj.plugins,
1984
+ ...obj.version !== undefined ? { version: obj.version } : {},
1985
+ ...obj.description !== undefined ? { description: obj.description } : {},
1986
+ ...obj.metadata !== undefined ? { metadata: obj.metadata } : {}
1987
+ };
1988
+ }
1989
+ function classifySource(source) {
1990
+ if (/^https?:\/\//i.test(source))
1991
+ return { kind: "url", locator: source };
1992
+ if (/^git@[^:]+:/i.test(source) || /^git\+/i.test(source) || /\.git$/i.test(source))
1993
+ return { kind: "git", locator: source };
1994
+ if (/^[a-zA-Z0-9](?:[a-zA-Z0-9._-]*[a-zA-Z0-9])?\/[a-zA-Z0-9](?:[a-zA-Z0-9._-]*[a-zA-Z0-9])?$/.test(source) && !source.includes("..")) {
1995
+ return { kind: "github", locator: source };
1996
+ }
1997
+ return { kind: "directory", locator: isAbsolute2(source) ? source : resolve2(source) };
1998
+ }
1999
+ function marketplaceInfoOf(name, record) {
2000
+ const kind = record.source.source;
2001
+ const source = record.source.path ?? record.source.url ?? record.source.repo ?? record.installLocation;
2002
+ return { name, source, kind, path: record.installLocation };
2003
+ }
2004
+ function parseSpec(spec) {
2005
+ const at = spec.lastIndexOf("@");
2006
+ if (at <= 0 || at === spec.length - 1) {
2007
+ throw new PluginManagerError(`plugin spec must be "<name>@<marketplace>" (got ${JSON.stringify(spec)}) -- bare-name marketplace inference is not implemented in this build`);
2008
+ }
2009
+ return { name: spec.slice(0, at), marketplace: spec.slice(at + 1) };
2010
+ }
2011
+ function keyFor(name, marketplace) {
2012
+ return `${name}@${marketplace}`;
2013
+ }
2014
+ async function readEnabledFromSettings(o, scope, key) {
2015
+ const loaded = await loadSettingsFile(o.settingsPathFor(scope));
2016
+ return loaded.values.enabledPlugins?.[key] === true;
2017
+ }
2018
+ function assertSettingsWritable(loaded, path) {
2019
+ if (loaded.present && !loaded.loaded) {
2020
+ throw new PluginManagerError(`${path}: cannot update enabledPlugins -- the file exists but ${loaded.error ?? "could not be read"}; fix it by hand first`);
2021
+ }
2022
+ }
2023
+ async function setEnabledInSettings(o, scope, key, enabled) {
2024
+ const path = o.settingsPathFor(scope);
2025
+ const loaded = await loadSettingsFile(path);
2026
+ assertSettingsWritable(loaded, path);
2027
+ const settings = { ...loaded.values };
2028
+ const enabledPlugins = { ...settings.enabledPlugins ?? {} };
2029
+ if (enabled === undefined)
2030
+ delete enabledPlugins[key];
2031
+ else
2032
+ enabledPlugins[key] = enabled;
2033
+ settings.enabledPlugins = enabledPlugins;
2034
+ await mkdir(dirname3(path), { recursive: true });
2035
+ await writeJsonAtomicNoLock(path, settings);
2036
+ }
2037
+ async function listMarketplaces(o) {
2038
+ const file = await readKnownMarketplacesFile(o);
2039
+ return Object.entries(file).map(([name, rec]) => marketplaceInfoOf(name, rec));
2040
+ }
2041
+ async function addMarketplace(o, source) {
2042
+ const classification = classifySource(source);
2043
+ if (classification.kind !== "directory") {
2044
+ throw new PluginManagerError(`marketplace source kind "${classification.kind}" is not supported in this build (no network) -- only local directory marketplaces can be added; got ${JSON.stringify(source)}`);
2045
+ }
2046
+ const dir = classification.locator;
2047
+ const manifest = await readDirectoryMarketplaceManifest(dir);
2048
+ await mkdir(o.pluginsRoot, { recursive: true });
2049
+ const path = knownMarketplacesPath(o);
2050
+ const record = {
2051
+ source: { source: "directory", path: dir },
2052
+ installLocation: dir,
2053
+ lastUpdated: new Date().toISOString(),
2054
+ autoUpdate: false
2055
+ };
2056
+ await withMarketplacesLock(path, async () => {
2057
+ const file = await readKnownMarketplacesFile(o);
2058
+ file[manifest.name] = record;
2059
+ await writeKnownMarketplacesFile(path, file);
2060
+ });
2061
+ return marketplaceInfoOf(manifest.name, record);
2062
+ }
2063
+ async function removeMarketplace(o, name) {
2064
+ const path = knownMarketplacesPath(o);
2065
+ await withMarketplacesLock(path, async () => {
2066
+ const file = await readKnownMarketplacesFile(o);
2067
+ if (!(name in file))
2068
+ throw new PluginManagerError(`marketplace "${name}" is not known`);
2069
+ delete file[name];
2070
+ await writeKnownMarketplacesFile(path, file);
2071
+ });
2072
+ }
2073
+ async function updateMarketplace(o, name) {
2074
+ const path = knownMarketplacesPath(o);
2075
+ await withMarketplacesLock(path, async () => {
2076
+ const file = await readKnownMarketplacesFile(o);
2077
+ const names = name !== undefined ? [name] : Object.keys(file);
2078
+ for (const n of names) {
2079
+ const rec = file[n];
2080
+ if (rec === undefined)
2081
+ throw new PluginManagerError(`marketplace "${n}" is not known`);
2082
+ if (rec.source.source !== "directory") {
2083
+ throw new PluginManagerError(`marketplace "${n}" has source kind "${rec.source.source}", which this build cannot refresh (no network)`);
2084
+ }
2085
+ await readDirectoryMarketplaceManifest(rec.installLocation);
2086
+ rec.lastUpdated = new Date().toISOString();
2087
+ }
2088
+ await writeKnownMarketplacesFile(path, file);
2089
+ });
2090
+ }
2091
+ async function resolvePluginSourcePath(o, name, marketplace) {
2092
+ const marketplaces = await readKnownMarketplacesFile(o);
2093
+ const rec = marketplaces[marketplace];
2094
+ if (rec === undefined)
2095
+ throw new PluginManagerError(`marketplace "${marketplace}" is not known -- add it first`);
2096
+ if (rec.source.source !== "directory") {
2097
+ throw new PluginManagerError(`marketplace "${marketplace}" has source kind "${rec.source.source}", which this build cannot install from (no network)`);
2098
+ }
2099
+ const manifest = await readDirectoryMarketplaceManifest(rec.installLocation);
2100
+ const entry = manifest.plugins.find((p) => p.name === name);
2101
+ if (entry === undefined)
2102
+ throw new PluginManagerError(`plugin "${name}" is not listed by marketplace "${marketplace}"`);
2103
+ if (typeof entry.source !== "string") {
2104
+ throw new PluginManagerError(`plugin "${name}@${marketplace}" declares a non-local source, which this build cannot install (no network)`);
2105
+ }
2106
+ const installPath = resolveMarketplacePluginPath(rec.installLocation, manifest.metadata?.pluginRoot, entry.source);
2107
+ if (installPath === undefined) {
2108
+ throw new PluginManagerError(`plugin "${name}@${marketplace}" declares a source (${JSON.stringify(entry.source)}) this build refuses to resolve -- it must be a "./relative/path" under the marketplace root (or a bare name when the marketplace's own metadata.pluginRoot is set), and must not escape the marketplace directory`);
2109
+ }
2110
+ return { installPath, version: entry.version };
2111
+ }
2112
+ async function installPlugin(o, spec, scope) {
2113
+ const { name, marketplace } = parseSpec(spec);
2114
+ const settingsPath = o.settingsPathFor(scope);
2115
+ assertSettingsWritable(await loadSettingsFile(settingsPath), settingsPath);
2116
+ const { installPath, version } = await resolvePluginSourcePath(o, name, marketplace);
2117
+ const key = keyFor(name, marketplace);
2118
+ await mkdir(o.pluginsRoot, { recursive: true });
2119
+ const now = new Date().toISOString();
2120
+ const record = { scope, installPath, installedAt: now, ...version !== undefined ? { version } : {} };
2121
+ const file = await readInstalledPluginsFile(o);
2122
+ const existing = file.plugins[key] ?? [];
2123
+ file.plugins[key] = [...existing.filter((r) => r.scope !== scope), record];
2124
+ await writeJsonAtomicNoLock(installedPluginsPath(o), file);
2125
+ await setEnabledInSettings(o, scope, key, true);
2126
+ return { id: name, installPath, scope, ...version !== undefined ? { version } : {} };
2127
+ }
2128
+ async function uninstallPlugin(o, spec, scope) {
2129
+ const { name, marketplace } = parseSpec(spec);
2130
+ const key = keyFor(name, marketplace);
2131
+ const settingsPath = o.settingsPathFor(scope);
2132
+ assertSettingsWritable(await loadSettingsFile(settingsPath), settingsPath);
2133
+ const file = await readInstalledPluginsFile(o);
2134
+ const existing = file.plugins[key];
2135
+ const remaining = (existing ?? []).filter((r) => r.scope !== scope);
2136
+ if (existing === undefined || remaining.length === existing.length) {
2137
+ throw new PluginManagerError(`plugin "${key}" is not installed at scope "${scope}"`);
2138
+ }
2139
+ if (remaining.length > 0)
2140
+ file.plugins[key] = remaining;
2141
+ else
2142
+ delete file.plugins[key];
2143
+ await writeJsonAtomicNoLock(installedPluginsPath(o), file);
2144
+ await setEnabledInSettings(o, scope, key, undefined);
2145
+ }
2146
+ async function setPluginEnabled(o, spec, scope, enabled) {
2147
+ const { name, marketplace } = parseSpec(spec);
2148
+ const key = keyFor(name, marketplace);
2149
+ const file = await readInstalledPluginsFile(o);
2150
+ if (!(file.plugins[key]?.some((r) => r.scope === scope) ?? false)) {
2151
+ throw new PluginManagerError(`plugin "${key}" is not installed at scope "${scope}"`);
2152
+ }
2153
+ await setEnabledInSettings(o, scope, key, enabled);
2154
+ }
2155
+ async function updatePlugin(o, spec) {
2156
+ const { name, marketplace } = parseSpec(spec);
2157
+ const key = keyFor(name, marketplace);
2158
+ const file = await readInstalledPluginsFile(o);
2159
+ const records = file.plugins[key];
2160
+ if (records === undefined || records.length === 0)
2161
+ throw new PluginManagerError(`plugin "${key}" is not installed`);
2162
+ const { installPath, version } = await resolvePluginSourcePath(o, name, marketplace);
2163
+ const now = new Date().toISOString();
2164
+ const updated = records.map((r) => ({ ...r, installPath, lastUpdated: now, ...version !== undefined ? { version } : {} }));
2165
+ file.plugins[key] = updated;
2166
+ await writeJsonAtomicNoLock(installedPluginsPath(o), file);
2167
+ const last = updated[updated.length - 1];
2168
+ return { id: name, installPath, scope: last.scope, ...version !== undefined ? { version } : {} };
2169
+ }
2170
+ async function listPlugins(o) {
2171
+ const file = await readInstalledPluginsFile(o);
2172
+ const out = [];
2173
+ for (const [key, records] of Object.entries(file.plugins)) {
2174
+ const at = key.lastIndexOf("@");
2175
+ const name = at > 0 ? key.slice(0, at) : key;
2176
+ const marketplace = at > 0 ? key.slice(at + 1) : "";
2177
+ for (const rec of records) {
2178
+ const enabled = await readEnabledFromSettings(o, rec.scope, key);
2179
+ out.push({ id: name, installPath: rec.installPath, scope: rec.scope, enabled, marketplace, ...rec.version !== undefined ? { version: rec.version } : {} });
2180
+ }
2181
+ }
2182
+ return out;
2183
+ }
1743
2184
  export {
1744
2185
  AbortError,
1745
2186
  BRAND_TOKEN_RE2 as BRAND_TOKEN_RE,
2187
+ CATALOG_TAG_RENAMES,
1746
2188
  CLIConnectionError,
1747
2189
  DEFAULT_COMPACTION_THRESHOLD,
1748
2190
  DEFAULT_CONTEXT_WINDOW_TOKENS,
@@ -1763,6 +2205,7 @@ export {
1763
2205
  PROJECT_PERMISSIVE_KEYS,
1764
2206
  PROTOCOL_VERSION,
1765
2207
  PROVIDER_STATE_FILE_SUFFIX2 as PROVIDER_STATE_FILE_SUFFIX,
2208
+ PluginManagerError,
1766
2209
  ProcessError,
1767
2210
  ProtocolDecodeError,
1768
2211
  ProtocolError,
@@ -1780,11 +2223,13 @@ export {
1780
2223
  WinterSDKError,
1781
2224
  WinterStoreError2 as WinterStoreError,
1782
2225
  WinterStoreLeaseError2 as WinterStoreLeaseError,
2226
+ addMarketplace,
1783
2227
  applyWorkspaceTrust,
1784
2228
  compatibilityKeys,
1785
2229
  decodeFrame,
1786
2230
  defaultSpawn,
1787
2231
  deleteSession,
2232
+ disableCronEnvName,
1788
2233
  encodeFrame,
1789
2234
  envName2 as envName,
1790
2235
  filterEscalatingDefaultMode,
@@ -1793,6 +2238,8 @@ export {
1793
2238
  getSessionInfo,
1794
2239
  getSessionMessages,
1795
2240
  getSubagentMessages,
2241
+ globalConfigFileName,
2242
+ installPlugin,
1796
2243
  isDeliveryOutcome,
1797
2244
  isGlobalAgentMessage,
1798
2245
  isListedRuntimeObjectArray,
@@ -1808,25 +2255,36 @@ export {
1808
2255
  isUnset2 as isUnset,
1809
2256
  isVendorCompliantProjectKey,
1810
2257
  isWinterMcpServerInstance,
2258
+ listMarketplaces,
2259
+ listPlugins,
1811
2260
  listSessions,
1812
2261
  listSubagents,
1813
2262
  loadSettingsFile,
1814
2263
  mcpToolName2 as mcpToolName,
2264
+ pluginCacheDirEnvName,
2265
+ providerManagedByHostEnvName,
1815
2266
  providerSettingsFrom,
1816
2267
  query,
2268
+ removeMarketplace,
1817
2269
  renameSession,
1818
2270
  resolveBrand2 as resolveBrand,
1819
2271
  resolveFacetTarget,
1820
2272
  resolveKeychainServiceForProfile2 as resolveKeychainServiceForProfile,
2273
+ resolveMarketplacePluginPath,
1821
2274
  resolveRuntimeExecutable,
1822
2275
  resolveSettings,
1823
2276
  resolveSettingsDetailed,
1824
2277
  resolveWebToolsConfig,
1825
2278
  resolveWinterHome2 as resolveWinterHome,
2279
+ setPluginEnabled,
1826
2280
  settingsPathFor,
1827
2281
  splitFrames,
2282
+ storeHomeEnvName,
1828
2283
  tagSession,
1829
2284
  transcriptProjectKey,
2285
+ uninstallPlugin,
2286
+ updateMarketplace,
2287
+ updatePlugin,
1830
2288
  userAgent2 as userAgent,
1831
2289
  validateModelSlots
1832
2290
  };
@@ -0,0 +1,7 @@
1
+ import type { BrandProfile } from "../brand.js";
2
+ /**
3
+ * `<brand.homeDirName>.json` -- `.winter.json` for Winter's own brand. `brand.homeDirName` is
4
+ * documented (`brand.ts`) as never containing a slash, so this is always a single path segment,
5
+ * safe to `join` onto any directory without further validation.
6
+ */
7
+ export declare function globalConfigFileName(brand: Pick<BrandProfile, "homeDirName">): string;
@@ -19,9 +19,21 @@ export type HomeBrand = Pick<BrandProfile, "envPrefix" | "homeDirName">;
19
19
  * (packages/runtime/src/brand-gate.test.ts, rules 9 and 10) is what keeps it that way.
20
20
  *
21
21
  * PRECEDENCE (WS-01 §2.2, and the same rule the current daemon has always had): an explicit
22
- * `<PREFIX>HOME` wins over EVERYTHING, including the dev profile. `<PREFIX>PROFILE=dev` selects
23
- * `~/<homeDirName>-dev` — the dev/dist split, so a development build can never share a home (or a
24
- * transcript store, or a settings file) with the copy a user actually runs.
22
+ * `<PREFIX>HOME` wins over EVERYTHING, including the dev profile and the WS-21 `sdk` suffix below.
23
+ * `<PREFIX>PROFILE=dev` selects `~/<homeDirName>-dev/sdk` — the dev/dist split, so a development
24
+ * build can never share a home (or a transcript store, or a settings file) with the copy a user
25
+ * actually runs.
26
+ *
27
+ * WS-21 §6.3 item 8: the DEFAULT (no `<PREFIX>HOME`, no explicit `brand`-threaded value) is now
28
+ * `~/<homeDirName>/sdk`, not `~/<homeDirName>` -- Contract A's `sdkHomeOf(home) = join(home, "sdk")`,
29
+ * inlined here rather than imported (the router that exports it is a separate package this SDK
30
+ * never depends on). `brand.homeDirName` cannot contain a slash (`brand.ts`'s own validation), so
31
+ * this is always exactly one extra path segment. This changes ONLY the bare-default case: every
32
+ * router-routed session gets an EXPLICIT `WINTER_HOME` (the Global Constraints list: "only the
33
+ * router sets ... WINTER_HOME"), which still wins outright, and the daemon's own pre-router-link
34
+ * fallback threads its own value too (`session-driver.ts`'s `WINTER_HOME` default, unaffected by
35
+ * this function). A bare `winter` invocation with no env override is the only caller this moves --
36
+ * consistent with the shared runtime home being the thing the SDK itself now means by "home".
25
37
  */
26
38
  export declare function resolveWinterHome(env?: Record<string, string | undefined>, brand?: HomeBrand): string;
27
39
  /**
@@ -0,0 +1,11 @@
1
+ import { type BrandProfile } from "../brand.js";
2
+ type EnvPrefixBrand = Pick<BrandProfile, "envPrefix">;
3
+ /** `WINTER_STORE_HOME` -- the shared runtime home's durable-paths root (spec §3.7). No claude twin. */
4
+ export declare function storeHomeEnvName(brand: EnvPrefixBrand): string;
5
+ /** `WINTER_PLUGIN_CACHE_DIR` -- the twin of `CLAUDE_CODE_PLUGIN_CACHE_DIR` (F15). */
6
+ export declare function pluginCacheDirEnvName(brand: EnvPrefixBrand): string;
7
+ /** `WINTER_PROVIDER_MANAGED_BY_HOST` -- the twin of `CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST` (F20). */
8
+ export declare function providerManagedByHostEnvName(brand: EnvPrefixBrand): string;
9
+ /** `WINTER_DISABLE_CRON` -- the twin of `CLAUDE_CODE_DISABLE_CRON` (F19a). */
10
+ export declare function disableCronEnvName(brand: EnvPrefixBrand): string;
11
+ export {};
@@ -0,0 +1,37 @@
1
+ export declare class PluginManagerError extends Error {
2
+ constructor(message: string);
3
+ }
4
+ export type PluginScope = "user" | "project" | "local";
5
+ export interface PluginManagerOptions {
6
+ /** claude's plugins root (WS-21 §6.3 item 5: `storeHome/plugins`) -- `installed_plugins.json` and `known_marketplaces.json` live directly under it. */
7
+ pluginsRoot: string;
8
+ /** The `enabledPlugins` carrier for one scope -- the settings file at that tier (the shared runtime home's own `settings.json` for `user`; the project dir's `settings.json`/`settings.local.json` for `project`/`local`). */
9
+ settingsPathFor(scope: PluginScope): string;
10
+ }
11
+ export interface MarketplaceInfo {
12
+ name: string;
13
+ /** The human-facing locator: the directory path, the repo `owner/repo`, or the URL -- whichever the source kind carries. */
14
+ source: string;
15
+ kind: "directory" | "git" | "github" | "url";
16
+ /** Where the marketplace's own content lives on disk. For `directory`, this IS the source (read in place, F15). */
17
+ path: string;
18
+ }
19
+ export interface InstalledPlugin {
20
+ id: string;
21
+ version?: string;
22
+ installPath: string;
23
+ scope: PluginScope;
24
+ }
25
+ export interface PluginListing extends InstalledPlugin {
26
+ enabled: boolean;
27
+ marketplace: string;
28
+ }
29
+ export declare function listMarketplaces(o: PluginManagerOptions): Promise<MarketplaceInfo[]>;
30
+ export declare function addMarketplace(o: PluginManagerOptions, source: string): Promise<MarketplaceInfo>;
31
+ export declare function removeMarketplace(o: PluginManagerOptions, name: string): Promise<void>;
32
+ export declare function updateMarketplace(o: PluginManagerOptions, name?: string): Promise<void>;
33
+ export declare function installPlugin(o: PluginManagerOptions, spec: string, scope: PluginScope): Promise<InstalledPlugin>;
34
+ export declare function uninstallPlugin(o: PluginManagerOptions, spec: string, scope: PluginScope): Promise<void>;
35
+ export declare function setPluginEnabled(o: PluginManagerOptions, spec: string, scope: PluginScope, enabled: boolean): Promise<void>;
36
+ export declare function updatePlugin(o: PluginManagerOptions, spec: string): Promise<InstalledPlugin>;
37
+ export declare function listPlugins(o: PluginManagerOptions): Promise<PluginListing[]>;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Resolve one marketplace manifest plugin entry's install path -- the ONE function
3
+ * `packages/runtime/src/plugins/installed.ts` (the daemon's own directory-marketplace fallback,
4
+ * SV-4) and `packages/sdk/src/plugins/manage.ts` (`resolvePluginSourcePath`, the CLI-facing install
5
+ * command) both call, so the two can never resolve the identical manifest entry to two different
6
+ * paths. `undefined` for anything invalid or refused -- a bare name with no usable `pluginRoot`, an
7
+ * absolute/malformed source, or a path that would escape `installLocation` -- NEVER a throw a
8
+ * caller must remember to catch; each caller renders its own typed rejection from `undefined`.
9
+ */
10
+ export declare function resolveMarketplacePluginPath(installLocation: string, pluginRoot: unknown, source: unknown): string | undefined;
@@ -88,7 +88,9 @@ export interface SandboxSettingsConfig {
88
88
  filesystem?: {
89
89
  allowWrite?: string[];
90
90
  denyWrite?: string[];
91
+ allowRead?: string[];
91
92
  denyRead?: string[];
93
+ allowGitConfig?: boolean;
92
94
  };
93
95
  network?: {
94
96
  allowedDomains?: string[];
@@ -219,6 +221,17 @@ export interface RuntimeConfig {
219
221
  resumeDropsTurn?: boolean;
220
222
  persistSession?: boolean;
221
223
  winterHome?: string;
224
+ /**
225
+ * WS-21 §3.7/§6.3 item 11: the shared runtime home's durable-paths root (`storeHomeEnvName`'s
226
+ * value) and the claude-`CLAUDE_CODE_PLUGIN_CACHE_DIR` twin, next to `winterHome` for a host that
227
+ * assembles `--config-json` directly rather than only setting the child's env. The CANONICAL
228
+ * channel is still the child's own env (`production-wiring.ts` reads `config.storeHome ??
229
+ * env[storeHomeEnvName(brand)]`, the same precedence `winterHome` already has over
230
+ * `resolveWinterHome(env, brand)`) -- these two fields exist so a host with no separate env-setting
231
+ * step has an equivalent one.
232
+ */
233
+ storeHome?: string;
234
+ pluginCacheDir?: string;
222
235
  allowedTools?: string[];
223
236
  disallowedTools?: string[];
224
237
  permissions?: {
@@ -5,7 +5,7 @@ import type { BrandProfile } from "../brand.js";
5
5
  * caller can thread three fields rather than construct one.
6
6
  */
7
7
  export type SettingsBrand = Pick<BrandProfile, "envPrefix" | "homeDirName" | "projectDirName">;
8
- import type { ModelSlotSetting } from "../protocol/config.js";
8
+ import type { ModelSlotSetting, SandboxSettingsConfig } from "../protocol/config.js";
9
9
  /** The three settings FILE tiers a session may load. `sdk.d.ts:7917`, verbatim and in pinned order. */
10
10
  export type SettingSource = "user" | "project" | "local";
11
11
  /** Pinned order (`sdk.d.ts:7917`). Also the value `settingSources: undefined` means (all three). */
@@ -39,6 +39,35 @@ export interface SettingsPermissionsBlock {
39
39
  }
40
40
  export interface Settings {
41
41
  permissions?: SettingsPermissionsBlock;
42
+ /**
43
+ * SV-11 (WS-21 fix round 9, security): the CC-shaped sandbox configuration surface, same shape as
44
+ * `RuntimeConfig.sandbox` (`protocol/config.ts`'s own `SandboxSettingsConfig`, kept structurally
45
+ * identical to `packages/runtime/src/sandbox/profile.ts`'s `SandboxSettings` for the same
46
+ * WS-02 §3 reason that type's own header states). Before this field existed, Winter's runtime read
47
+ * ONLY the host-supplied `RuntimeConfig.sandbox`, ignoring `settings.json`'s own `sandbox` block
48
+ * entirely -- a denied write from a plain, un-escalated `sandbox.filesystem.denyWrite` path went
49
+ * through unsandboxed, silently, because nothing ever threaded that key into the running sandbox at
50
+ * all (not a merge-precedence bug; there was no merge). `production-wiring.ts` now feeds the host's
51
+ * own `RuntimeConfig.sandbox` into this resolution as the `flag` tier (claude's own architecture:
52
+ * its SDK `Options` occupies the identical tier position for this same setting), and reads the
53
+ * result back off `effective.sandbox` into the config it actually hands the engine.
54
+ *
55
+ * ALL FOUR `filesystem` arrays -- `allowWrite`, `denyWrite`, `allowRead`, `denyRead` -- UNION
56
+ * across every tier (deep-merge's ordinary replace-by-higher-tier would let a higher tier
57
+ * silently drop a lower tier's own restriction -- the identical fail-open shape
58
+ * `permissions.deny`'s own union exists to prevent; see `unionSandboxFilesystemDenyArrays` below).
59
+ * CORRECTED in fix round 10, item C: round 9 reasoned `allowWrite` should NOT union (a widening
60
+ * key should never accumulate) and used the ordinary replace-by-higher-tier merge for it instead
61
+ * -- the controller's round-10 ruling reverses this, dump-confirmed against claude's own live
62
+ * sandbox-reconciliation code unioning its OWN rule-derived allowWrite set with the native
63
+ * sandbox's currently-configured one (`xr=se([...yr,...$r.filter(...)])`, not a replacement).
64
+ * Claude itself unions the widening array too; ported here to match. `allowRead` (also new this
65
+ * round) is claude's own read carve-out (`getFsReadConfig`'s `allowWithinDeny`) -- carried and
66
+ * unioned identically for merge-parity, though `packages/runtime/src/sandbox/profile.ts`'s own
67
+ * `buildSeatbeltProfile` does not yet enforce it (a disclosed, fails-CLOSED gap: see that file's
68
+ * own `SandboxFilesystemSettings.allowRead` doc comment).
69
+ */
70
+ sandbox?: SandboxSettingsConfig;
42
71
  hooks?: SettingsHooksConfig;
43
72
  env?: Record<string, string>;
44
73
  apiKeyHelper?: string;
@@ -8,7 +8,7 @@ import {
8
8
  import {
9
9
  resolveWinterHome2,
10
10
  WinterCompatibilitySessionStore2
11
- } from "../index-qst9rwbb.js";
11
+ } from "../index-0xks1t4r.js";
12
12
 
13
13
  // src/tools/schemas.ts
14
14
  var SEND_MESSAGE_TO_MAX = 300;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yanlinglabs/winter-agent-sdk",
3
- "version": "0.0.20",
3
+ "version": "0.0.22",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "engines": {
@@ -45,15 +45,15 @@
45
45
  }
46
46
  },
47
47
  "dependencies": {
48
- "@yanlinglabs/winter-provider-catalog": "0.0.20"
48
+ "@yanlinglabs/winter-provider-catalog": "0.0.22"
49
49
  },
50
50
  "optionalDependencies": {
51
- "@yanlinglabs/winter-agent-sdk-darwin-arm64": "0.0.20"
51
+ "@yanlinglabs/winter-agent-sdk-darwin-arm64": "0.0.22"
52
52
  },
53
53
  "devDependencies": {
54
54
  "@types/node": "^26.4.0",
55
- "@yanlinglabs/winter-conformance": "0.0.20",
56
- "winter-agent-runtime": "0.0.20"
55
+ "@yanlinglabs/winter-conformance": "0.0.22",
56
+ "winter-agent-runtime": "0.0.22"
57
57
  },
58
58
  "scripts": {}
59
59
  }