@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.
- package/dist/{index-qst9rwbb.js → index-0xks1t4r.js} +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.js +460 -2
- package/dist/paths/global-config.d.ts +7 -0
- package/dist/paths/home.d.ts +15 -3
- package/dist/paths/host-env.d.ts +11 -0
- package/dist/plugins/manage.d.ts +37 -0
- package/dist/plugins/marketplace-path.d.ts +10 -0
- package/dist/protocol/config.d.ts +13 -0
- package/dist/settings/types.d.ts +30 -1
- package/dist/tools/index.js +1 -1
- package/package.json +5 -5
|
@@ -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-
|
|
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.
|
|
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;
|
package/dist/paths/home.d.ts
CHANGED
|
@@ -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
|
|
23
|
-
* `~/<homeDirName>-dev` — the dev/dist split, so a development
|
|
24
|
-
* transcript store, or a settings file) with the copy a user
|
|
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?: {
|
package/dist/settings/types.d.ts
CHANGED
|
@@ -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;
|
package/dist/tools/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yanlinglabs/winter-agent-sdk",
|
|
3
|
-
"version": "0.0.
|
|
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.
|
|
48
|
+
"@yanlinglabs/winter-provider-catalog": "0.0.22"
|
|
49
49
|
},
|
|
50
50
|
"optionalDependencies": {
|
|
51
|
-
"@yanlinglabs/winter-agent-sdk-darwin-arm64": "0.0.
|
|
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.
|
|
56
|
-
"winter-agent-runtime": "0.0.
|
|
55
|
+
"@yanlinglabs/winter-conformance": "0.0.22",
|
|
56
|
+
"winter-agent-runtime": "0.0.22"
|
|
57
57
|
},
|
|
58
58
|
"scripts": {}
|
|
59
59
|
}
|