@yanlinglabs/winter-agent-runtime 0.0.41 → 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.
Files changed (57) hide show
  1. package/dist/commands/resolver.d.ts +8 -13
  2. package/dist/context/agent-listing.d.ts +7 -11
  3. package/dist/context/attachments.d.ts +14 -23
  4. package/dist/context/dynamic-sections.d.ts +5 -5
  5. package/dist/context/git-branch-fixture.d.ts +22 -0
  6. package/dist/context/git-status.d.ts +2 -2
  7. package/dist/context/memory.d.ts +1 -1
  8. package/dist/context/output-styles.d.ts +2 -2
  9. package/dist/context/request-layout.d.ts +16 -17
  10. package/dist/context/seam.d.ts +7 -6
  11. package/dist/embedded-worker.js +3 -3
  12. package/dist/embedded.js +3 -3
  13. package/dist/engine.d.ts +3 -3
  14. package/dist/{index-ey5c52j3.js → index-14xkt4jp.js} +2 -2
  15. package/dist/{index-hdvvezf8.js → index-bh1ve8ws.js} +238 -154
  16. package/dist/{index-z9yngxj4.js → index-kqc3tdph.js} +964 -826
  17. package/dist/index.js +1 -1
  18. package/dist/mcp/lifecycle.d.ts +2 -1
  19. package/dist/permissions/edit-recognition.d.ts +1 -1
  20. package/dist/permissions/evaluator.d.ts +19 -30
  21. package/dist/permissions/file-rules.d.ts +177 -277
  22. package/dist/permissions/grammar.d.ts +58 -0
  23. package/dist/permissions/policy-state.d.ts +1 -1
  24. package/dist/permissions/ruleset.d.ts +1 -1
  25. package/dist/permissions/shell-structure.d.ts +1 -1
  26. package/dist/plugins/bundle.d.ts +4 -6
  27. package/dist/plugins/loader.corpus-fixture.d.ts +26 -0
  28. package/dist/plugins/loader.d.ts +3 -4
  29. package/dist/plugins/manifest.d.ts +23 -46
  30. package/dist/production-wiring.d.ts +1 -1
  31. package/dist/provider/lean-prompt.d.ts +1 -6
  32. package/dist/sandbox/profile.d.ts +39 -79
  33. package/dist/sandbox/spawn.d.ts +5 -6
  34. package/dist/skills/listing.d.ts +8 -26
  35. package/dist/skills/store.d.ts +2 -3
  36. package/dist/store/dialect.d.ts +1 -1
  37. package/dist/subagents/availability.d.ts +10 -8
  38. package/dist/subagents/child-handle.d.ts +5 -5
  39. package/dist/subagents/definitions.d.ts +7 -6
  40. package/dist/subagents/fork.d.ts +4 -8
  41. package/dist/subagents/notification-queue.d.ts +30 -38
  42. package/dist/testing.js +2 -2
  43. package/dist/tools/descriptors/web-fetch.d.ts +1 -1
  44. package/dist/tools/descriptors/web-search.d.ts +1 -1
  45. package/dist/tools/impl/_search-budget.d.ts +1 -1
  46. package/dist/tools/impl/_web-fetch-net.d.ts +6 -6
  47. package/dist/tools/impl/_web-search-assembler.d.ts +7 -20
  48. package/dist/tools/impl/background-task-runtime.d.ts +13 -13
  49. package/dist/tools/impl/bash.d.ts +3 -3
  50. package/dist/tools/impl/monitor.d.ts +2 -2
  51. package/dist/toolsearch/exposure.d.ts +3 -3
  52. package/dist/version.d.ts +1 -1
  53. package/dist/version.js +1 -1
  54. package/dist/web/fetchable-url.d.ts +1 -1
  55. package/dist/web/preapproved-hosts.d.ts +9 -31
  56. package/dist/workflows/store.d.ts +23 -31
  57. package/package.json +4 -4
@@ -7,7 +7,6 @@ import {
7
7
  computeActiveSlotSet,
8
8
  resolveSlotToProvider,
9
9
  shellWords,
10
- escapeRegExpLiteral,
11
10
  validatePermissionRuleString,
12
11
  getRegisteredTool,
13
12
  listRegisteredTools,
@@ -86,7 +85,7 @@ import {
86
85
  providerCredentialRef2,
87
86
  createModelClassifier2,
88
87
  selectClassifierRoute2
89
- } from "./index-z9yngxj4.js";
88
+ } from "./index-kqc3tdph.js";
90
89
  import {
91
90
  createKeychainCredentialStore,
92
91
  createKeychainSecretReader,
@@ -2243,7 +2242,7 @@ var ALL_TIER_REFUSED_ENV = [
2243
2242
  disableCronEnvName(WINTER_BRAND5),
2244
2243
  mcpOAuthTestStoreEnvName(WINTER_BRAND5)
2245
2244
  ];
2246
- function escapeRegExpLiteral2(literal) {
2245
+ function escapeRegExpLiteral(literal) {
2247
2246
  return literal.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2248
2247
  }
2249
2248
  var HOST_MANAGED_REFUSED_ENV_PATTERNS = [
@@ -2258,7 +2257,7 @@ var HOST_MANAGED_REFUSED_ENV_PATTERNS = [
2258
2257
  /_BASE_URL$/,
2259
2258
  /_API_BASE$/,
2260
2259
  /^CLAUDE_CODE_USE_/,
2261
- new RegExp(`^${escapeRegExpLiteral2(envName3(WINTER_BRAND5, "USE_"))}`)
2260
+ new RegExp(`^${escapeRegExpLiteral(envName3(WINTER_BRAND5, "USE_"))}`)
2262
2261
  ];
2263
2262
  var HOST_MANAGED_DISABLED_SETTINGS_KEYS = ["apiKeyHelper"];
2264
2263
  function filterSettingsEnv(env, tier, opts) {
@@ -2333,29 +2332,30 @@ var BUILTIN_OUTPUT_STYLES = [
2333
2332
  ];
2334
2333
  var BUILTIN_OUTPUT_STYLE_NAMES = BUILTIN_OUTPUT_STYLES.map((s) => s.name);
2335
2334
  var STYLE_NAME = /^[A-Za-z0-9_-]+$/;
2335
+ var TRUE_WORDS = new Set(["1", "true", "yes", "on"]);
2336
+ var FALSE_WORDS = new Set(["0", "false", "no", "off"]);
2336
2337
  function claudeBoolean(value) {
2337
2338
  if (typeof value === "boolean")
2338
2339
  return value;
2339
2340
  if (typeof value !== "string" && typeof value !== "number")
2340
2341
  return;
2341
- const normalized = String(value).toLowerCase().trim();
2342
- if (["1", "true", "yes", "on"].includes(normalized))
2342
+ const word = String(value).toLowerCase().trim();
2343
+ if (TRUE_WORDS.has(word))
2343
2344
  return true;
2344
- if (["0", "false", "no", "off"].includes(normalized))
2345
+ if (FALSE_WORDS.has(word))
2345
2346
  return false;
2346
2347
  return;
2347
2348
  }
2349
+ var EXCERPT_MAX = 100;
2350
+ var HEADING_LINE = /^#+\s+(.+)$/;
2348
2351
  function bodyExcerpt(body, def) {
2349
- for (const rawLine of body.split(`
2350
- `)) {
2351
- const line = rawLine.trim();
2352
- if (line === "")
2353
- continue;
2354
- const heading = /^#+\s+(.+)$/.exec(line);
2355
- const text = heading?.[1] ?? line;
2356
- return text.length > 100 ? `${text.slice(0, 97)}...` : text;
2357
- }
2358
- return def;
2352
+ const line = body.split(`
2353
+ `).map((l) => l.trim()).find((l) => l.length > 0);
2354
+ if (line === undefined)
2355
+ return def;
2356
+ const heading = HEADING_LINE.exec(line);
2357
+ const text = heading !== null ? heading[1] : line;
2358
+ return text.length > EXCERPT_MAX ? `${text.slice(0, EXCERPT_MAX - 3)}...` : text;
2359
2359
  }
2360
2360
  function parseStyleFile(path, fallbackName, source) {
2361
2361
  let raw;
@@ -2398,21 +2398,20 @@ function parsePluginStyleFile(path, pluginName, fallbackBaseName) {
2398
2398
  } catch {
2399
2399
  return null;
2400
2400
  }
2401
- const { attrs, body: parsedBody } = parseFrontmatter(raw);
2402
- const declaredNameRaw = attrs["name"];
2403
- const declaredName = declaredNameRaw !== null && declaredNameRaw !== undefined ? String(declaredNameRaw) : undefined;
2404
- const baseName = declaredName !== undefined && declaredName.length > 0 ? declaredName : fallbackBaseName;
2401
+ const { attrs, body } = parseFrontmatter(raw);
2402
+ const nameAttr = attrs["name"];
2403
+ const declaredName = nameAttr === null || nameAttr === undefined ? "" : String(nameAttr);
2404
+ const baseName = declaredName.length > 0 ? declaredName : fallbackBaseName;
2405
2405
  if (!STYLE_NAME.test(baseName))
2406
2406
  return null;
2407
- const descriptionRaw = attrs["description"];
2408
- const descriptionCoerced = typeof descriptionRaw === "string" || typeof descriptionRaw === "number" || typeof descriptionRaw === "boolean" ? String(descriptionRaw).trim() : "";
2409
- const description = descriptionCoerced.length > 0 ? descriptionCoerced : bodyExcerpt(parsedBody, `Output style from ${pluginName} plugin`);
2410
- const keepCodingInstructions = claudeBoolean(attrs["keep-coding-instructions"]) === true;
2407
+ const descriptionAttr = attrs["description"];
2408
+ const declaredDescription = typeof descriptionAttr === "string" || typeof descriptionAttr === "number" || typeof descriptionAttr === "boolean" ? String(descriptionAttr).trim() : "";
2409
+ const description = declaredDescription.length > 0 ? declaredDescription : bodyExcerpt(body, `Output style from ${pluginName} plugin`);
2411
2410
  return {
2412
2411
  name: `${pluginName}:${baseName}`,
2413
2412
  description,
2414
- body: capBytes(neutralizeReminderTags(parsedBody.trim()), OUTPUT_STYLE_MAX_BYTES).text,
2415
- keepCodingInstructions,
2413
+ body: capBytes(neutralizeReminderTags(body.trim()), OUTPUT_STYLE_MAX_BYTES).text,
2414
+ keepCodingInstructions: claudeBoolean(attrs["keep-coding-instructions"]) === true,
2416
2415
  source: "plugin",
2417
2416
  replacementDowngraded: false
2418
2417
  };
@@ -2488,15 +2487,24 @@ var ENVIRONMENT_HEADING = "# Environment";
2488
2487
  var ENVIRONMENT_LEAD_IN = "You have been invoked in the following environment: ";
2489
2488
  var WINTER_PRODUCT_LINE = "Winter runs this session as an agent runtime on behalf of a host application; the host decides how your output is shown to the user.";
2490
2489
  function shellName(raw) {
2491
- const shell = raw.trim().length > 0 ? raw : "unknown";
2492
- if (shell.includes("zsh"))
2490
+ if (raw.trim().length === 0)
2491
+ return "unknown";
2492
+ if (raw.includes("zsh"))
2493
2493
  return "zsh";
2494
- if (shell.includes("bash"))
2494
+ if (raw.includes("bash"))
2495
2495
  return "bash";
2496
- return shell;
2496
+ return raw;
2497
2497
  }
2498
2498
  function bullets(items) {
2499
- return items.flatMap((item) => typeof item === "string" ? [` - ${item}`] : item.map((sub) => ` - ${sub}`));
2499
+ const lines = [];
2500
+ for (const item of items) {
2501
+ if (typeof item === "string")
2502
+ lines.push(` - ${item}`);
2503
+ else
2504
+ for (const member of item)
2505
+ lines.push(` - ${member}`);
2506
+ }
2507
+ return lines;
2500
2508
  }
2501
2509
  function modelLines(input) {
2502
2510
  const lines = [];
@@ -2690,13 +2698,21 @@ import { existsSync as existsSync2, readFileSync as readFileSync2, realpathSync,
2690
2698
  import { homedir as homedir2 } from "node:os";
2691
2699
  import { dirname, resolve as resolve2 } from "node:path";
2692
2700
  var DEFAULT_MAX_DEPTH2 = 5;
2693
- var IMPORT_TOKEN_RE = /(^|\s)@((?:[^\s\\]|\\ )+)/g;
2694
- function isValidImportPath(path) {
2701
+ function importTokenPattern() {
2702
+ return /(^|\s)@((?:[^\s\\]|\\ )+)/g;
2703
+ }
2704
+ var BARE_IMPORT_START = /^[A-Za-z0-9._-]/;
2705
+ function importTokenPath(rawToken) {
2706
+ const hash = rawToken.indexOf("#");
2707
+ const withoutFragment = hash === -1 ? rawToken : rawToken.slice(0, hash);
2708
+ if (withoutFragment.length === 0)
2709
+ return;
2710
+ const path = withoutFragment.replaceAll("\\ ", " ");
2695
2711
  if (path.startsWith("./") || path.startsWith("~/"))
2696
- return true;
2697
- if (path.startsWith("/") && path !== "/")
2698
- return true;
2699
- return !path.startsWith("@") && !/^[#%^&*()]/.test(path) && /^[a-zA-Z0-9._-]/.test(path);
2712
+ return path;
2713
+ if (path.startsWith("/"))
2714
+ return path === "/" ? undefined : path;
2715
+ return BARE_IMPORT_START.test(path) ? path : undefined;
2700
2716
  }
2701
2717
  function resolveImportPath(token, containingFileDir) {
2702
2718
  if (token.startsWith("~/"))
@@ -2765,15 +2781,9 @@ function expandTextRun(text, containingFileDir, depth, ctx) {
2765
2781
  return parts.map((part, i) => i % 2 === 1 ? part : replaceImportTokens(part, containingFileDir, depth, ctx)).join("");
2766
2782
  }
2767
2783
  function replaceImportTokens(text, containingFileDir, depth, ctx) {
2768
- return text.replace(IMPORT_TOKEN_RE, (full, leading, rawToken) => {
2769
- let token = rawToken;
2770
- const hashIndex = token.indexOf("#");
2771
- if (hashIndex !== -1)
2772
- token = token.slice(0, hashIndex);
2773
- if (token.length === 0)
2774
- return full;
2775
- token = token.replace(/\\ /g, " ");
2776
- if (!isValidImportPath(token))
2784
+ return text.replace(importTokenPattern(), (full, leading, rawToken) => {
2785
+ const token = importTokenPath(rawToken);
2786
+ if (token === undefined)
2777
2787
  return full;
2778
2788
  const resolvedPath = resolveImportPath(token, containingFileDir);
2779
2789
  if (ctx.tier !== "user") {
@@ -2905,9 +2915,9 @@ var INSTRUCTIONS_CONTEXT_LABELS = {
2905
2915
  user: " (user's private global instructions for all projects)"
2906
2916
  };
2907
2917
  function renderInstructionsContext(files) {
2908
- const entries = files.filter((f) => f.content.length > 0).map((f) => `Contents of ${f.path}${INSTRUCTIONS_CONTEXT_LABELS[f.kind]}:
2918
+ const entries = files.filter((file) => file.content !== "").map((file) => `Contents of ${file.path}${INSTRUCTIONS_CONTEXT_LABELS[file.kind]}:
2909
2919
 
2910
- ${f.content.trim()}`);
2920
+ ${file.content.trim()}`);
2911
2921
  if (entries.length === 0)
2912
2922
  return;
2913
2923
  return `${INSTRUCTIONS_CONTEXT_HEADER}
@@ -3444,17 +3454,37 @@ function scanPluginSkillsAt(skillsParentDir, pluginName) {
3444
3454
  }
3445
3455
  return out;
3446
3456
  }
3447
- function resolvePluginSkills(root, pluginName, declared, warnings) {
3448
- const defaultSkills = scanPluginSkillsAt(join9(root, "skills"), pluginName);
3449
- const overridePaths = resolveManifestComponentOverride(root, pluginName, "skills", declared, true, warnings);
3450
- if (overridePaths === undefined)
3451
- return defaultSkills;
3452
- const overrideSkills = overridePaths.flatMap((path) => scanPluginSkillsAt(path, pluginName));
3457
+ function dedupeByName(entries) {
3453
3458
  const byName = new Map;
3454
- for (const entry of [...defaultSkills, ...overrideSkills])
3459
+ for (const entry of entries)
3455
3460
  byName.set(entry.name, entry);
3456
3461
  return [...byName.values()];
3457
3462
  }
3463
+ function realPathOr(path) {
3464
+ try {
3465
+ return realpathSync3(path);
3466
+ } catch {
3467
+ return path;
3468
+ }
3469
+ }
3470
+ function isInlineCommandsMap(declared) {
3471
+ if (typeof declared !== "object" || declared === null || Array.isArray(declared))
3472
+ return false;
3473
+ const first = Object.values(declared)[0];
3474
+ if (typeof first !== "object" || first === null)
3475
+ return false;
3476
+ return "source" in first || "content" in first;
3477
+ }
3478
+ function resolvePluginSkills(root, pluginName, declared, warnings) {
3479
+ const defaults = scanPluginSkillsAt(join9(root, "skills"), pluginName);
3480
+ const override = resolveManifestComponentOverride(root, pluginName, "skills", declared, true, warnings);
3481
+ if (override === undefined)
3482
+ return defaults;
3483
+ const all = [...defaults];
3484
+ for (const dir of override)
3485
+ all.push(...scanPluginSkillsAt(dir, pluginName));
3486
+ return dedupeByName(all);
3487
+ }
3458
3488
  function scanPluginCommandFile(path, pluginName) {
3459
3489
  const name = basename2(path, ".md");
3460
3490
  let raw;
@@ -3491,7 +3521,7 @@ function scanPluginCommands(root, pluginName) {
3491
3521
  return scanPluginCommandsDir(join9(root, "commands"), pluginName);
3492
3522
  }
3493
3523
  function scanPluginCommandsOverride(paths, pluginName) {
3494
- const byName = new Map;
3524
+ const all = [];
3495
3525
  for (const path of paths) {
3496
3526
  let isDir;
3497
3527
  try {
@@ -3500,25 +3530,21 @@ function scanPluginCommandsOverride(paths, pluginName) {
3500
3530
  continue;
3501
3531
  }
3502
3532
  if (isDir) {
3503
- for (const entry of scanPluginCommandsDir(path, pluginName))
3504
- byName.set(entry.name, entry);
3533
+ all.push(...scanPluginCommandsDir(path, pluginName));
3505
3534
  } else {
3506
3535
  const entry = scanPluginCommandFile(path, pluginName);
3507
3536
  if (entry !== undefined)
3508
- byName.set(entry.name, entry);
3537
+ all.push(entry);
3509
3538
  }
3510
3539
  }
3511
- return [...byName.values()];
3540
+ return dedupeByName(all);
3512
3541
  }
3513
3542
  function resolveCommandsManifestOverride(root, pluginName, declared, warnings) {
3514
3543
  if (declared === undefined)
3515
3544
  return;
3516
- if (!Array.isArray(declared) && typeof declared === "object" && declared !== null) {
3517
- const firstValue = Object.values(declared)[0];
3518
- if (firstValue !== null && typeof firstValue === "object" && (("source" in firstValue) || ("content" in firstValue))) {
3519
- warnings.push(`plugin "${pluginName}"'s manifest "commands" uses the inline {name: {source|content}} form, which Winter does not support yet -- no commands were loaded from it (the default commands/ directory is still shadowed, matching claude's own behaviour whenever the key is present)`);
3520
- return [];
3521
- }
3545
+ if (isInlineCommandsMap(declared)) {
3546
+ warnings.push(`plugin "${pluginName}"'s manifest "commands" uses the inline {name: {source|content}} form, which Winter does not support yet -- no commands were loaded from it (the default commands/ directory is still shadowed, matching claude's own behaviour whenever the key is present)`);
3547
+ return [];
3522
3548
  }
3523
3549
  return resolveManifestComponentOverride(root, pluginName, "commands", declared, false, warnings);
3524
3550
  }
@@ -3573,7 +3599,7 @@ function scanPluginAgents(root, pluginName) {
3573
3599
  return scanPluginAgentsDir(join9(root, "agents"), pluginName);
3574
3600
  }
3575
3601
  function scanPluginAgentsOverride(paths, pluginName) {
3576
- const out = {};
3602
+ const agents = {};
3577
3603
  const rejected = [];
3578
3604
  for (const path of paths) {
3579
3605
  let isDir;
@@ -3584,13 +3610,14 @@ function scanPluginAgentsOverride(paths, pluginName) {
3584
3610
  }
3585
3611
  if (isDir) {
3586
3612
  const scanned = scanPluginAgentsDir(path, pluginName);
3587
- Object.assign(out, scanned.agents);
3613
+ for (const [name, def] of Object.entries(scanned.agents))
3614
+ agents[name] = def;
3588
3615
  rejected.push(...scanned.rejected);
3589
3616
  } else {
3590
- scanPluginAgentFile(path, pluginName, out, rejected);
3617
+ scanPluginAgentFile(path, pluginName, agents, rejected);
3591
3618
  }
3592
3619
  }
3593
- return { agents: out, rejected };
3620
+ return { agents, rejected };
3594
3621
  }
3595
3622
  function collectMcpServers(root, manifest) {
3596
3623
  const servers = {};
@@ -3641,26 +3668,20 @@ function readPluginHooksJson(root, pluginName, warnings) {
3641
3668
  return readHooksFile(join9(root, "hooks", "hooks.json"), pluginName, "hooks/hooks.json", warnings);
3642
3669
  }
3643
3670
  function resolveManifestHooksStringEntries(root, pluginName, declared, warnings) {
3644
- const entries = Array.isArray(declared) ? declared : declared !== undefined ? [declared] : [];
3645
- const defaultHooksJsonPath = resolve6(join9(root, "hooks", "hooks.json"));
3646
- const defaultHooksJsonReal = (() => {
3647
- try {
3648
- return realpathSync3(defaultHooksJsonPath);
3649
- } catch {
3650
- return defaultHooksJsonPath;
3651
- }
3652
- })();
3653
- const seenReal = new Set;
3654
- const results = [];
3671
+ const entries = declared === undefined ? [] : Array.isArray(declared) ? declared : [declared];
3672
+ const standardPath = resolve6(join9(root, "hooks", "hooks.json"));
3673
+ const standardIdentity = realPathOr(standardPath);
3674
+ const accepted = new Set;
3675
+ const out = [];
3655
3676
  for (const entry of entries) {
3656
- if (typeof entry !== "string" || entry.length === 0)
3677
+ if (typeof entry !== "string" || entry === "")
3657
3678
  continue;
3658
3679
  const full = resolve6(root, entry);
3659
3680
  if (!resolvesWithinPluginRoot(full, root)) {
3660
3681
  warnings.paths.push(`plugin "${pluginName}"'s manifest "hooks" entry "${entry}" escapes the plugin directory -- ignoring it`);
3661
3682
  continue;
3662
3683
  }
3663
- let isFile;
3684
+ let isFile = false;
3664
3685
  try {
3665
3686
  isFile = statSync5(full).isFile();
3666
3687
  } catch {
@@ -3670,25 +3691,19 @@ function resolveManifestHooksStringEntries(root, pluginName, declared, warnings)
3670
3691
  warnings.paths.push(`plugin "${pluginName}"'s manifest "hooks" entry "${entry}" was not found at ${full} -- ignoring it`);
3671
3692
  continue;
3672
3693
  }
3673
- const real = (() => {
3674
- try {
3675
- return realpathSync3(full);
3676
- } catch {
3677
- return full;
3678
- }
3679
- })();
3680
- if (real === defaultHooksJsonReal)
3694
+ const real = realPathOr(full);
3695
+ if (real === standardIdentity)
3681
3696
  continue;
3682
- if (seenReal.has(real)) {
3697
+ if (accepted.has(real)) {
3683
3698
  warnings.paths.push(`plugin "${pluginName}"'s manifest "hooks" entry "${entry}" duplicates another entry (both resolve to ${real}) -- loaded once`);
3684
3699
  continue;
3685
3700
  }
3686
- seenReal.add(real);
3687
- const hooksObject = readHooksFile(full, pluginName, `manifest "hooks" entry "${entry}"`, warnings.content);
3688
- if (hooksObject !== undefined)
3689
- results.push(hooksObject);
3701
+ accepted.add(real);
3702
+ const hooks = readHooksFile(full, pluginName, `manifest "hooks" entry "${entry}"`, warnings.content);
3703
+ if (hooks !== undefined)
3704
+ out.push(hooks);
3690
3705
  }
3691
- return results;
3706
+ return out;
3692
3707
  }
3693
3708
  function mergeHookSources(root, pluginName, fromHooksJson, manifestHooks, pathWarnings, contentWarnings) {
3694
3709
  const merged = {};
@@ -3727,39 +3742,39 @@ function resolveManifestComponentOverride(root, pluginName, componentKey, declar
3727
3742
  if (declared === undefined)
3728
3743
  return;
3729
3744
  const entries = Array.isArray(declared) ? declared : [declared];
3730
- const resolved = [];
3745
+ const label = `plugin "${pluginName}"'s manifest "${componentKey}"`;
3746
+ const kept = [];
3731
3747
  for (const entry of entries) {
3732
- if (typeof entry !== "string" || entry.length === 0) {
3733
- warnings.push(`plugin "${pluginName}"'s manifest "${componentKey}" entry ${JSON.stringify(entry)} is not a non-empty string -- ignoring it`);
3748
+ if (typeof entry !== "string" || entry === "") {
3749
+ warnings.push(`${label} entry ${JSON.stringify(entry)} is not a non-empty string -- ignoring it`);
3734
3750
  continue;
3735
3751
  }
3736
3752
  const full = resolve6(root, entry);
3737
3753
  if (!resolvesWithinPluginRoot(full, root)) {
3738
- warnings.push(`plugin "${pluginName}"'s manifest "${componentKey}" path "${entry}" escapes the plugin directory -- ignoring it`);
3754
+ warnings.push(`${label} path "${entry}" escapes the plugin directory -- ignoring it`);
3739
3755
  continue;
3740
3756
  }
3741
- let stat;
3757
+ let isDir;
3742
3758
  try {
3743
- stat = statSync5(full);
3759
+ isDir = statSync5(full).isDirectory();
3744
3760
  } catch {
3745
- stat = undefined;
3746
- }
3747
- if (stat === undefined) {
3748
- warnings.push(`plugin "${pluginName}"'s manifest "${componentKey}" path "${entry}" was not found at ${full} -- ignoring it`);
3761
+ warnings.push(`${label} path "${entry}" was not found at ${full} -- ignoring it`);
3749
3762
  continue;
3750
3763
  }
3751
- if (requireDirectory && !stat.isDirectory()) {
3752
- const skillHint = componentKey === "skills" && basename2(entry).toLowerCase() === "skill.md" ? ` -- point to its parent directory instead` : "";
3753
- warnings.push(`plugin "${pluginName}"'s manifest "${componentKey}" path "${entry}" is a file, not a directory${componentKey === "skills" ? " (skills entries must be directories containing SKILL.md)" : ""}${skillHint} -- ignoring it`);
3764
+ if (requireDirectory && !isDir) {
3765
+ const isSkills = componentKey === "skills";
3766
+ const why = isSkills ? " (skills entries must be directories containing SKILL.md)" : "";
3767
+ const hint = isSkills && basename2(entry).toLowerCase() === "skill.md" ? " -- point to its parent directory instead" : "";
3768
+ warnings.push(`${label} path "${entry}" is a file, not a directory${why}${hint} -- ignoring it`);
3754
3769
  continue;
3755
3770
  }
3756
- resolved.push(full);
3771
+ kept.push(full);
3757
3772
  }
3758
- return resolved;
3773
+ return kept;
3759
3774
  }
3760
3775
  function manifestOverrideIncludesDefaultDir(resolvedEntries, defaultDirPath) {
3761
- const normalizedDefault = defaultDirPath + sep3;
3762
- return resolvedEntries.some((entry) => (entry + sep3).startsWith(normalizedDefault));
3776
+ const beneath = defaultDirPath + sep3;
3777
+ return resolvedEntries.some((entry) => entry === defaultDirPath || entry.startsWith(beneath));
3763
3778
  }
3764
3779
  function shadowedFolderWarning(pluginName, componentDirName, manifestFieldName) {
3765
3780
  return `plugin "${pluginName}": the "${componentDirName}/" folder exists but is not auto-loaded because the manifest sets "${manifestFieldName}"`;
@@ -4373,47 +4388,116 @@ function splitCommand(prompt) {
4373
4388
  const args = firstSpace === -1 ? "" : trimmed.slice(firstSpace).trim();
4374
4389
  return { name, args };
4375
4390
  }
4376
- var ARG_DOLLAR_SENTINEL = "￿";
4377
- var ARG_BOUNDARY_SENTINEL = "￾";
4378
- function protectSubstitutedValue(value) {
4379
- const cleaned = (value ?? "").replaceAll(ARG_DOLLAR_SENTINEL, "�").replaceAll(ARG_BOUNDARY_SENTINEL, "�");
4380
- return ARG_BOUNDARY_SENTINEL + cleaned.replaceAll("$", ARG_DOLLAR_SENTINEL) + ARG_BOUNDARY_SENTINEL;
4381
- }
4382
4391
  function substituteArguments(body, args, namedArgs = []) {
4383
- let out = body.replaceAll(ARG_DOLLAR_SENTINEL, "�").replaceAll(ARG_BOUNDARY_SENTINEL, "�");
4384
- const words = shellWords(args).map((w) => w.word);
4385
- const named = namedArgs.map((name, i) => ({ name, i })).filter((n) => n.name.length > 0).sort((a, b) => b.name.length - a.name.length);
4386
- const escapeGroup = ["\\d", "ARGUMENTS", ...named.map(({ name }) => `${escapeRegExpLiteral(name)}(?![\\[\\w])`)].join("|");
4387
- out = out.replace(new RegExp(`(?<!\\\\)\\\\\\$(?=${escapeGroup})`, "g"), ARG_DOLLAR_SENTINEL);
4388
- let substituted = false;
4389
- for (const { name, i } of named) {
4390
- out = out.replace(new RegExp(`\\$${escapeRegExpLiteral(name)}(?![\\[\\w])`, "g"), () => {
4391
- substituted = true;
4392
- return protectSubstitutedValue(words[i]);
4393
- });
4392
+ const text = cleanNoncharacters(body);
4393
+ const wholeArgs = cleanNoncharacters(args);
4394
+ const words = shellWords(args).map((w) => cleanNoncharacters(w.word));
4395
+ const named = namedArgs.map((name, position) => ({ name, position })).filter((n) => n.name.length > 0).sort((a, b) => b.name.length - a.name.length);
4396
+ const claimed = new Uint8Array(text.length);
4397
+ const replacements = [];
4398
+ let substitutions = 0;
4399
+ const isFree = (start, end) => {
4400
+ for (let k = start;k < end; k++)
4401
+ if (claimed[k])
4402
+ return false;
4403
+ return true;
4404
+ };
4405
+ const claim = (start, end, value) => {
4406
+ claimed.fill(1, start, end);
4407
+ replacements.push({ start, end, value });
4408
+ };
4409
+ const endsCleanly = (at) => at >= text.length || text[at] !== "[" && !isWordChar(text.charCodeAt(at));
4410
+ for (let i = text.indexOf("\\$");i !== -1; i = text.indexOf("\\$", i + 1)) {
4411
+ if (i > 0 && text[i - 1] === "\\")
4412
+ continue;
4413
+ const after = i + 2;
4414
+ const escapes = isDigit(text.charCodeAt(after)) || text.startsWith("ARGUMENTS", after) || named.some((n) => text.startsWith(n.name, after) && endsCleanly(after + n.name.length));
4415
+ if (escapes) {
4416
+ claim(i, after, "$");
4417
+ i = after - 1;
4418
+ }
4394
4419
  }
4395
- out = out.replace(/\$ARGUMENTS\[(\d+)\]/g, (whole, digits) => {
4396
- const idx = Number.parseInt(digits, 10);
4397
- if (words[idx] === undefined)
4398
- return ARG_DOLLAR_SENTINEL + whole.slice(1);
4399
- substituted = true;
4400
- return protectSubstitutedValue(words[idx]);
4401
- });
4402
- out = out.replace(/\$(\d+)(?!\w)/g, (whole, digits) => {
4403
- const idx = Number.parseInt(digits, 10);
4404
- if (words[idx] === undefined)
4405
- return whole;
4406
- substituted = true;
4407
- return protectSubstitutedValue(words[idx]);
4408
- });
4409
- out = out.replaceAll("$ARGUMENTS", () => {
4410
- substituted = true;
4411
- return protectSubstitutedValue(args);
4412
- });
4413
- if (!substituted && args)
4414
- out = `${out}
4415
- ARGUMENTS: ${protectSubstitutedValue(args)}`;
4416
- return out.replaceAll(ARG_DOLLAR_SENTINEL, "$").replaceAll(ARG_BOUNDARY_SENTINEL, "");
4420
+ for (const { name, position } of named) {
4421
+ const needle = "$" + name;
4422
+ for (let i = text.indexOf(needle);i !== -1; ) {
4423
+ const end = i + needle.length;
4424
+ if (isFree(i, end) && endsCleanly(end)) {
4425
+ claim(i, end, words[position] ?? "");
4426
+ substitutions++;
4427
+ i = text.indexOf(needle, end);
4428
+ } else {
4429
+ i = text.indexOf(needle, i + 1);
4430
+ }
4431
+ }
4432
+ }
4433
+ const INDEXED_PREFIX = "$ARGUMENTS[";
4434
+ for (let i = text.indexOf(INDEXED_PREFIX);i !== -1; ) {
4435
+ let j = i + INDEXED_PREFIX.length;
4436
+ while (isDigit(text.charCodeAt(j)))
4437
+ j++;
4438
+ const digitsStart = i + INDEXED_PREFIX.length;
4439
+ if (j > digitsStart && text[j] === "]" && isFree(i, j + 1)) {
4440
+ const index = Number(text.slice(digitsStart, j));
4441
+ const word = index < words.length ? words[index] : undefined;
4442
+ if (word !== undefined) {
4443
+ claim(i, j + 1, word);
4444
+ substitutions++;
4445
+ } else {
4446
+ claim(i, j + 1, text.slice(i, j + 1));
4447
+ }
4448
+ i = text.indexOf(INDEXED_PREFIX, j + 1);
4449
+ } else {
4450
+ i = text.indexOf(INDEXED_PREFIX, i + 1);
4451
+ }
4452
+ }
4453
+ for (let i = text.indexOf("$");i !== -1; ) {
4454
+ let j = i + 1;
4455
+ while (isDigit(text.charCodeAt(j)))
4456
+ j++;
4457
+ if (j > i + 1 && !isWordChar(text.charCodeAt(j)) && isFree(i, j)) {
4458
+ const index = Number(text.slice(i + 1, j));
4459
+ const word = index < words.length ? words[index] : undefined;
4460
+ if (word !== undefined) {
4461
+ claim(i, j, word);
4462
+ substitutions++;
4463
+ }
4464
+ i = text.indexOf("$", j);
4465
+ } else {
4466
+ i = text.indexOf("$", i + 1);
4467
+ }
4468
+ }
4469
+ const PLAIN = "$ARGUMENTS";
4470
+ for (let i = text.indexOf(PLAIN);i !== -1; ) {
4471
+ const end = i + PLAIN.length;
4472
+ if (isFree(i, end)) {
4473
+ claim(i, end, wholeArgs);
4474
+ substitutions++;
4475
+ i = text.indexOf(PLAIN, end);
4476
+ } else {
4477
+ i = text.indexOf(PLAIN, i + 1);
4478
+ }
4479
+ }
4480
+ replacements.sort((a, b) => a.start - b.start);
4481
+ let out = "";
4482
+ let cursor = 0;
4483
+ for (const r of replacements) {
4484
+ out += text.slice(cursor, r.start) + r.value;
4485
+ cursor = r.end;
4486
+ }
4487
+ out += text.slice(cursor);
4488
+ if (substitutions === 0 && args.length > 0)
4489
+ out += `
4490
+ ARGUMENTS: ` + wholeArgs;
4491
+ return out;
4492
+ }
4493
+ function cleanNoncharacters(s) {
4494
+ return s.replace(/[￾￿]/g, "�");
4495
+ }
4496
+ function isDigit(code) {
4497
+ return code >= 48 && code <= 57;
4498
+ }
4499
+ function isWordChar(code) {
4500
+ return isDigit(code) || code >= 65 && code <= 90 || code >= 97 && code <= 122 || code === 95;
4417
4501
  }
4418
4502
  function substituteWorkflowArguments(body, args) {
4419
4503
  const JSON_TOKEN = "$ARGUMENTS_JSON";