@yanlinglabs/winter-agent-sdk 0.0.40 → 0.0.43

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.js CHANGED
@@ -430,7 +430,8 @@ function toWireMcpServers(servers) {
430
430
  ...cfg.timeout !== undefined ? { timeout: cfg.timeout } : {},
431
431
  ...isWinterMcpServerInstance(cfg.instance) ? { tools: cfg.instance.listTools() } : {},
432
432
  ...cfg.toolNames !== undefined ? { toolNames: { ...cfg.toolNames } } : {},
433
- ...cfg.toolLanes !== undefined ? { toolLanes: { ...cfg.toolLanes } } : {}
433
+ ...cfg.toolLanes !== undefined ? { toolLanes: { ...cfg.toolLanes } } : {},
434
+ ...Array.isArray(cfg.concurrentTools) ? { concurrentTools: [...cfg.concurrentTools] } : {}
434
435
  } : cfg;
435
436
  }
436
437
  return out;
@@ -1270,32 +1271,38 @@ function withTestKeychainRedirect(env) {
1270
1271
  return { ...env, [TEST_KEYCHAIN_ENV]: redirect };
1271
1272
  }
1272
1273
  // src/version.ts
1273
- var SDK_VERSION = "0.0.40";
1274
+ var SDK_VERSION = "0.0.43";
1274
1275
  // src/paths/project-key.ts
1275
1276
  var TRANSCRIPT_PROJECT_KEY_MAX_LENGTH = 64;
1276
1277
  var VENDOR_PROJECT_KEY_PATTERN = /^[A-Za-z0-9_-]{1,64}$/;
1277
1278
  function isVendorCompliantProjectKey(key) {
1278
1279
  return VENDOR_PROJECT_KEY_PATTERN.test(key);
1279
1280
  }
1280
- function sanitize(absPath) {
1281
- return absPath.replace(/[^a-zA-Z0-9]/g, "-");
1281
+ function isAsciiAlphanumeric(code) {
1282
+ return code >= 48 && code <= 57 || code >= 65 && code <= 90 || code >= 97 && code <= 122;
1282
1283
  }
1283
- function rollingHash32(raw) {
1284
- let hash = 0;
1285
- for (let i = 0;i < raw.length; i++) {
1286
- hash = (hash << 5) - hash + raw.charCodeAt(i) | 0;
1284
+ function sanitizeForKey(text) {
1285
+ let out = "";
1286
+ for (let i = 0;i < text.length; i++) {
1287
+ const code = text.charCodeAt(i);
1288
+ out += isAsciiAlphanumeric(code) ? text[i] : "-";
1287
1289
  }
1288
- return hash;
1290
+ return out;
1289
1291
  }
1290
- function overflowSuffix(rawAbsPath) {
1291
- return Math.abs(rollingHash32(rawAbsPath)).toString(36);
1292
+ function hashSuffix(text) {
1293
+ let hash = 0;
1294
+ for (let i = 0;i < text.length; i++) {
1295
+ hash = Math.imul(hash, 31) + text.charCodeAt(i) | 0;
1296
+ }
1297
+ return Math.abs(hash).toString(36);
1292
1298
  }
1293
1299
  function transcriptProjectKey(absPath) {
1294
- const sanitized = sanitize(absPath);
1300
+ const sanitized = sanitizeForKey(absPath);
1295
1301
  if (sanitized.length <= TRANSCRIPT_PROJECT_KEY_MAX_LENGTH)
1296
1302
  return sanitized;
1297
- const suffix = overflowSuffix(absPath);
1298
- return sanitized.slice(0, TRANSCRIPT_PROJECT_KEY_MAX_LENGTH - 1 - suffix.length) + "-" + suffix;
1303
+ const suffix = hashSuffix(absPath);
1304
+ const prefixLength = TRANSCRIPT_PROJECT_KEY_MAX_LENGTH - 1 - suffix.length;
1305
+ return `${sanitized.slice(0, prefixLength)}-${suffix}`;
1299
1306
  }
1300
1307
  // src/paths/keys.ts
1301
1308
  import { realpathSync } from "node:fs";
@@ -2052,52 +2059,52 @@ import { CATALOG_TAG_RENAMES } from "@yanlinglabs/winter-provider-catalog";
2052
2059
 
2053
2060
  // src/plugins/manage.ts
2054
2061
  import { mkdir, open, readFile as readFile2, rename, rmdir, unlink, writeFile } from "node:fs/promises";
2055
- import { dirname as dirname3, isAbsolute as isAbsolute2, join as join3, resolve as resolve2 } from "node:path";
2062
+ import { dirname as dirname3, isAbsolute as isAbsolute2, join as join3, resolve } from "node:path";
2056
2063
  import { randomBytes } from "node:crypto";
2057
2064
 
2058
2065
  // src/plugins/marketplace-path.ts
2059
- import { resolve, sep } from "node:path";
2060
- var BARE_SOURCE_NAME = /^[A-Za-z0-9][-A-Za-z0-9._]*$/;
2061
- function isBareSourceName(source) {
2062
- return BARE_SOURCE_NAME.test(source) && !source.includes("..");
2063
- }
2064
- function normalizePluginRoot(pluginRoot) {
2065
- if (typeof pluginRoot !== "string" || pluginRoot === "" || pluginRoot.startsWith("/") || pluginRoot.includes("\\") || pluginRoot.includes(":"))
2066
+ import path from "node:path";
2067
+ var MARKETPLACE_DIR = Symbol("marketplace-dir");
2068
+ function usableRoot(pluginRoot) {
2069
+ if (typeof pluginRoot !== "string")
2066
2070
  return;
2067
- const stripped = pluginRoot.replace(/^\.\//, "").replace(/\/+$/, "");
2068
- if (stripped === "" || stripped === ".")
2069
- return ".";
2070
- if (stripped.split("/").some((segment) => segment === "" || segment === "." || segment === ".."))
2071
+ if (pluginRoot === "" || pluginRoot.startsWith("/"))
2071
2072
  return;
2072
- return stripped;
2073
- }
2074
- function withPluginRootPrefix(source, normalizedRoot) {
2075
- if (normalizedRoot === undefined || !isBareSourceName(source))
2076
- return source;
2077
- return normalizedRoot === "." ? `./${source}` : `./${normalizedRoot}/${source}`;
2078
- }
2079
- function schemaSourcePath(source) {
2080
- const normalized = source === "." ? "./" : source;
2081
- return normalized.startsWith("./") ? normalized : undefined;
2082
- }
2083
- function resolveWithinBase(base, relativePath) {
2084
- const normalizedBase = resolve(base);
2085
- const resolved = resolve(base, relativePath);
2086
- if (resolved !== normalizedBase && !resolved.startsWith(normalizedBase + sep)) {
2087
- throw new Error(`Path traversal detected: "${relativePath}" would escape the base directory`);
2073
+ if (pluginRoot.includes("\\") || pluginRoot.includes(":"))
2074
+ return;
2075
+ let rest = pluginRoot.startsWith("./") ? pluginRoot.slice(2) : pluginRoot;
2076
+ rest = rest.replace(/\/+$/, "");
2077
+ if (rest === "" || rest === ".")
2078
+ return MARKETPLACE_DIR;
2079
+ for (const piece of rest.split("/")) {
2080
+ if (piece === "" || piece === "." || piece === "..")
2081
+ return;
2088
2082
  }
2089
- return resolved;
2083
+ return rest;
2084
+ }
2085
+ function isBareName(source) {
2086
+ return /^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(source) && !source.includes("..");
2090
2087
  }
2091
2088
  function resolveMarketplacePluginPath(installLocation, pluginRoot, source) {
2092
- if (typeof source !== "string" || source === "")
2093
- return;
2094
- const normalizedRoot = normalizePluginRoot(pluginRoot);
2095
- const prefixed = withPluginRootPrefix(source, normalizedRoot);
2096
- const sourcePath = schemaSourcePath(prefixed);
2097
- if (sourcePath === undefined)
2098
- return;
2099
2089
  try {
2100
- return resolveWithinBase(installLocation, sourcePath);
2090
+ if (typeof source !== "string" || source === "")
2091
+ return;
2092
+ let relativePath = source;
2093
+ if (isBareName(source)) {
2094
+ const root = usableRoot(pluginRoot);
2095
+ if (root !== undefined) {
2096
+ relativePath = root === MARKETPLACE_DIR ? `./${source}` : `./${root}/${source}`;
2097
+ }
2098
+ }
2099
+ if (relativePath === ".")
2100
+ relativePath = "./";
2101
+ if (!relativePath.startsWith("./"))
2102
+ return;
2103
+ const base = path.resolve(installLocation);
2104
+ const candidate = path.resolve(installLocation, relativePath);
2105
+ if (candidate === base || candidate.startsWith(base + path.sep))
2106
+ return candidate;
2107
+ return;
2101
2108
  } catch {
2102
2109
  return;
2103
2110
  }
@@ -2243,7 +2250,7 @@ function classifySource(source) {
2243
2250
  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("..")) {
2244
2251
  return { kind: "github", locator: source };
2245
2252
  }
2246
- return { kind: "directory", locator: isAbsolute2(source) ? source : resolve2(source) };
2253
+ return { kind: "directory", locator: isAbsolute2(source) ? source : resolve(source) };
2247
2254
  }
2248
2255
  function marketplaceInfoOf(name, record) {
2249
2256
  const kind = record.source.source;
@@ -1,3 +1,4 @@
1
1
  export declare const TRANSCRIPT_PROJECT_KEY_MAX_LENGTH = 64;
2
2
  export declare function isVendorCompliantProjectKey(key: string): boolean;
3
+ /** The transcript project key for an absolute path. */
3
4
  export declare function transcriptProjectKey(absPath: string): string;
@@ -1,10 +1 @@
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
1
  export declare function resolveMarketplacePluginPath(installLocation: string, pluginRoot: unknown, source: unknown): string | undefined;
@@ -406,6 +406,23 @@ export interface McpSdkServerConfig {
406
406
  * ignored. Only an in-process (`sdk`) server may declare lanes; the host vouches for its own tools.
407
407
  */
408
408
  toolLanes?: Record<string, string>;
409
+ /**
410
+ * SDK 0.0.41, a Winter extension: some of this server's tools, by the server's own tool name, that are
411
+ * SAFE TO RUN CONCURRENTLY without being read-only (`["session_spawn"]`). A round's calls normally run
412
+ * one at a time unless the tool is read-only (`readOnlyHint: true`); a tool listed here runs BESIDE the
413
+ * round's other concurrent calls (up to 10 at once), exactly like a read-only one -- several calls of it
414
+ * in one round run at the same time (claude runs several `Agent` calls of a round at once; this is the
415
+ * same thing for a host tool that starts work elsewhere).
416
+ *
417
+ * It is a SCHEDULING statement only. It never makes the tool read-only anywhere else: the tool's
418
+ * `annotations` are not touched, plan mode, permission evaluation, the hook input's `winter_mcp_server`
419
+ * and `canUseTool`'s `mcpServer` read the server's own `readOnlyHint` exactly as before. Use it only for a
420
+ * tool whose concurrent calls cannot interfere with each other (each call's work is its own); a tool
421
+ * that holds one exclusive resource belongs in a lane (`toolLanes`) instead. A tool named in BOTH is in
422
+ * its lane (the stricter answer). An entry that is not a string, or names no tool of this server, is
423
+ * ignored. Only an in-process (`sdk`) server may declare it; the host vouches for its own tools.
424
+ */
425
+ concurrentTools?: string[];
409
426
  }
410
427
  export type McpServerConfigForProcessTransport = McpStdioServerConfig | McpHttpServerConfig | McpSSEServerConfig | McpSdkServerConfig;
411
428
  export type AgentMcpServerSpec = string | Record<string, McpServerConfigForProcessTransport>;
@@ -69,12 +69,10 @@ export interface Settings {
69
69
  * `permissions.deny`'s own union exists to prevent; see `unionSandboxFilesystemDenyArrays` below).
70
70
  * CORRECTED in fix round 10, item C: round 9 reasoned `allowWrite` should NOT union (a widening
71
71
  * key should never accumulate) and used the ordinary replace-by-higher-tier merge for it instead
72
- * -- the controller's round-10 ruling reverses this, dump-confirmed against claude's own live
73
- * sandbox-reconciliation code unioning its OWN rule-derived allowWrite set with the native
74
- * sandbox's currently-configured one (`xr=se([...yr,...$r.filter(...)])`, not a replacement).
75
- * Claude itself unions the widening array too; ported here to match. `allowRead` (also new this
76
- * round) is claude's own read carve-out (`getFsReadConfig`'s `allowWithinDeny`) -- carried and
77
- * unioned identically for merge-parity, though `packages/runtime/src/sandbox/profile.ts`'s own
72
+ * -- the controller's round-10 ruling reverses this: Claude Code accumulates allowWrite entries
73
+ * from every source rather than letting one replace another, so Winter unions the widening array
74
+ * too. `allowRead` (also new this round) is the settings key for re-permitting reads inside a
75
+ * denied region -- carried and unioned identically for merge-parity, though `packages/runtime/src/sandbox/profile.ts`'s own
78
76
  * `buildSeatbeltProfile` does not yet enforce it (a disclosed, fails-CLOSED gap: see that file's
79
77
  * own `SandboxFilesystemSettings.allowRead` doc comment).
80
78
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yanlinglabs/winter-agent-sdk",
3
- "version": "0.0.40",
3
+ "version": "0.0.43",
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.40"
48
+ "@yanlinglabs/winter-provider-catalog": "0.0.43"
49
49
  },
50
50
  "optionalDependencies": {
51
- "@yanlinglabs/winter-agent-sdk-darwin-arm64": "0.0.40"
51
+ "@yanlinglabs/winter-agent-sdk-darwin-arm64": "0.0.43"
52
52
  },
53
53
  "devDependencies": {
54
54
  "@types/node": "^26.4.0",
55
- "@yanlinglabs/winter-agent-runtime": "0.0.40",
56
- "@yanlinglabs/winter-conformance": "0.0.40"
55
+ "@yanlinglabs/winter-agent-runtime": "0.0.43",
56
+ "@yanlinglabs/winter-conformance": "0.0.43"
57
57
  },
58
58
  "scripts": {}
59
59
  }