@ycodium-ai/agent-claude 0.2.2896 → 0.2.2898

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 (2) hide show
  1. package/dist/index.js +390 -128
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -58,6 +58,8 @@ var CLAUDE_CARD = {
58
58
  turnSteering: true,
59
59
  // 子代理、workflow 与其成员经 SDK 的 task_* 系统消息报成 task.*(claudeInstance.ts)。
60
60
  reportsSpawnedAgents: true,
61
+ // 用户点名的技能:最后一个原生 `/name` 派发,更早的读 SKILL.md 内联展开(claudeSkillDispatch.ts)。
62
+ handlesSkillInvocations: true,
61
63
  // 出处:Anthropic Messages API 的视觉文档——单张图片 5 MB、宽高各不超过 8000 像素,超了
62
64
  // API 直接拒掉整轮。5 MB 量的是 base64 之后的长度(SDK 把图片以 base64 块发出),所以
63
65
  // 原始字节上限是它的 3/4。宿主据此把更大的图退化成路径(Claude 自己的 Read 工具会读那个
@@ -180,7 +182,7 @@ var CLAUDE_CARD = {
180
182
 
181
183
  // src/claudeInstance.ts
182
184
  import * as NodeOS8 from "node:os";
183
- import * as NodePath13 from "node:path";
185
+ import * as NodePath14 from "node:path";
184
186
 
185
187
  // ../../node_modules/.pnpm/@anthropic-ai+claude-agent-sdk@0.3.170_@anthropic-ai+sdk@0.93.0_zod@4.6.5__@modelcontex_64204813d4dba2ebc464c3aa751485a4/node_modules/@anthropic-ai/claude-agent-sdk/sdk.mjs
186
188
  import { createRequire as xz } from "node:module";
@@ -19875,13 +19877,16 @@ function parseFrontMatterBoolean(value) {
19875
19877
  return void 0;
19876
19878
  }
19877
19879
  }
19880
+ function splitSkillFrontMatter(contents) {
19881
+ const lines = contents.split(/\r?\n/u);
19882
+ const close = lines[0]?.trim() === "---" ? lines.findIndex((line, index) => index > 0 && line.trim() === "---") : -1;
19883
+ return close === -1 ? { header: [], body: contents } : { header: lines.slice(1, close), body: lines.slice(close + 1).join("\n") };
19884
+ }
19878
19885
  function parseSkillFrontMatter(contents) {
19879
- const lines = contents.split(/\r?\n/);
19880
- if (lines[0]?.trim() !== "---") return {};
19886
+ const lines = splitSkillFrontMatter(contents).header;
19881
19887
  const values = {};
19882
- for (let index = 1; index < lines.length; index += 1) {
19888
+ for (let index = 0; index < lines.length; index += 1) {
19883
19889
  const line = lines[index] ?? "";
19884
- if (line.trim() === "---") break;
19885
19890
  const match = FRONT_MATTER_KEY.exec(line);
19886
19891
  if (match === null) continue;
19887
19892
  const key = match[1];
@@ -19890,7 +19895,7 @@ function parseSkillFrontMatter(contents) {
19890
19895
  const block = [];
19891
19896
  while (index + 1 < lines.length) {
19892
19897
  const next = lines[index + 1] ?? "";
19893
- if (next.trim() === "---" || next.trim().length > 0 && !/^[ \t]/.test(next)) break;
19898
+ if (next.trim().length > 0 && !/^[ \t]/.test(next)) break;
19894
19899
  index += 1;
19895
19900
  if (next.trim().length > 0) block.push(next.trim());
19896
19901
  }
@@ -20503,35 +20508,342 @@ function findSkillTokens(text) {
20503
20508
  });
20504
20509
  }
20505
20510
  var SKILL_TOKEN_AT = new RegExp(String.raw`([$/])(${SKILL_TOKEN_NAME_PATTERN})(?=\s|$)`, "y");
20511
+ function resolveSkillName(name, known) {
20512
+ if (known.has(name)) return name;
20513
+ if (!name.startsWith(SESSION_SKILL_PREFIX)) return void 0;
20514
+ const bare = name.slice(SESSION_SKILL_PREFIX.length);
20515
+ return known.has(bare) ? bare : void 0;
20516
+ }
20517
+ function lookupSkill(name, index) {
20518
+ const exact = index.get(name);
20519
+ if (exact !== void 0) return exact;
20520
+ if (!name.startsWith(SESSION_SKILL_PREFIX)) return void 0;
20521
+ const session = index.get(name.slice(SESSION_SKILL_PREFIX.length));
20522
+ return session?.scope === SESSION_SKILL_NAMESPACE ? session : void 0;
20523
+ }
20506
20524
  var SLASH_SKILL_TOKEN = new RegExp(String.raw`^/(${SKILL_TOKEN_NAME_PATTERN})$`);
20525
+ function matchSkillTokens(text, index) {
20526
+ if (index.size === 0) return [];
20527
+ return findSkillTokens(text).flatMap((token) => {
20528
+ const skill = lookupSkill(token.name, index);
20529
+ return skill === void 0 ? [] : [{ ...token, skill }];
20530
+ });
20531
+ }
20532
+ function skillInvocationNotice(names) {
20533
+ return `The user invoked these skills in this message; run them as requested: ${names.map((name) => `$${name}`).join(", ")}.`;
20534
+ }
20507
20535
 
20508
20536
  // src/claudeSkillDispatch.ts
20509
- function planClaudeSkillDispatch(prompt, skillNames) {
20510
- const mentions = findSkillTokens(prompt).filter((token) => skillNames.has(token.name));
20537
+ import { EXECUTOR_SEND_TURN_MAX_INPUT_CHARS as EXECUTOR_SEND_TURN_MAX_INPUT_CHARS2 } from "@ycodium-ai/plugin-api/executor";
20538
+
20539
+ // src/claudeInlineSkill.ts
20540
+ import * as NodeFSP2 from "node:fs/promises";
20541
+ import * as NodePath6 from "node:path";
20542
+ import { EXECUTOR_SEND_TURN_MAX_INPUT_CHARS } from "@ycodium-ai/plugin-api/executor";
20543
+
20544
+ // src/shellWords.ts
20545
+ function tokenizeShellWords(value) {
20546
+ const input = value.trim();
20547
+ if (!input) return [];
20548
+ const tokens = [];
20549
+ let current = "";
20550
+ let quote;
20551
+ let quoted = false;
20552
+ for (let index = 0; index < input.length; index++) {
20553
+ const char = input[index];
20554
+ if (char === void 0) continue;
20555
+ if (quote) {
20556
+ if (char === quote) {
20557
+ quote = void 0;
20558
+ quoted = true;
20559
+ } else if (char === "\\" && quote === '"') {
20560
+ const next = input[index + 1];
20561
+ if (next !== void 0 && ['"', "\\", "$", "`"].includes(next)) {
20562
+ current += next;
20563
+ index++;
20564
+ } else {
20565
+ current += char;
20566
+ }
20567
+ } else {
20568
+ current += char;
20569
+ }
20570
+ continue;
20571
+ }
20572
+ if (char === "'" || char === '"') {
20573
+ quote = char;
20574
+ quoted = true;
20575
+ } else if (/\s/.test(char)) {
20576
+ if (current || quoted) {
20577
+ tokens.push(current);
20578
+ current = "";
20579
+ quoted = false;
20580
+ }
20581
+ } else if (char === "\\") {
20582
+ const next = input[index + 1];
20583
+ if (next !== void 0 && /\s/.test(next)) {
20584
+ current += next;
20585
+ index++;
20586
+ } else {
20587
+ current += char;
20588
+ }
20589
+ } else {
20590
+ current += char;
20591
+ }
20592
+ }
20593
+ if (current || quoted) tokens.push(current);
20594
+ return tokens;
20595
+ }
20596
+
20597
+ // src/claudeSkillArguments.ts
20598
+ var escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
20599
+ function substituteSkillArguments(body, args, argumentNames) {
20600
+ const values = tokenizeShellWords(args);
20601
+ const named = argumentNames.map(escapeRegExp).toSorted((left, right) => right.length - left.length);
20602
+ const pattern = new RegExp(
20603
+ String.raw`(\\*)\$(?:ARGUMENTS\[(\d+)\]|(ARGUMENTS)(?![A-Za-z0-9_])|(\d+)` + (named.length > 0 ? String.raw`|(${named.join("|")})(?![A-Za-z0-9_-])` : "") + ")",
20604
+ "gu"
20605
+ );
20606
+ let received = false;
20607
+ const substituted = body.replace(
20608
+ pattern,
20609
+ (match, slashes, bracketIndex, all, shortIndex, name) => {
20610
+ if (slashes.length === 1) return match.slice(1);
20611
+ if (all !== void 0) {
20612
+ received = true;
20613
+ return slashes + args;
20614
+ }
20615
+ const index = bracketIndex ?? shortIndex;
20616
+ if (index !== void 0) {
20617
+ const value = values[Number(index)];
20618
+ if (value === void 0) return match;
20619
+ received = true;
20620
+ return slashes + value;
20621
+ }
20622
+ received = true;
20623
+ return slashes + (values[argumentNames.indexOf(name ?? "")] ?? "");
20624
+ }
20625
+ );
20626
+ return received || args.length === 0 ? substituted : `${substituted}
20627
+
20628
+ ARGUMENTS: ${args}`;
20629
+ }
20630
+
20631
+ // src/claudeInlineSkill.ts
20632
+ var MAX_INLINE_SKILLS = 4;
20633
+ var MAX_INLINE_SKILL_BYTES = 64 * 1024;
20634
+ var EXECUTION_FIELDS = [
20635
+ "model",
20636
+ "effort",
20637
+ "context",
20638
+ "agent",
20639
+ "hooks",
20640
+ "allowed-tools",
20641
+ "disallowed-tools"
20642
+ ];
20643
+ var TOP_LEVEL_KEY = /^([A-Za-z_][\w-]*):[ \t]*(.*)$/u;
20644
+ var DYNAMIC_COMMAND = /(?:^|\s)!`[^`\n]+`|^```!/mu;
20645
+ var CLAUDE_VARIABLE = /\$\{(CLAUDE_[A-Z0-9_]+)\}/gu;
20646
+ var unquote = (value) => value.trim().replace(/^(["'])(.*)\1$/u, "$2");
20647
+ var parseArgumentNames = (value, following) => {
20648
+ const inline = value.replace(/\s+#.*$/u, "").trim();
20649
+ if (inline.startsWith("[")) {
20650
+ return inline.replace(/^\[|\]$/gu, "").split(",").map(unquote).filter((name) => name.length > 0);
20651
+ }
20652
+ if (inline.length > 0) return unquote(inline).split(/\s+/u);
20653
+ const names = [];
20654
+ for (const line of following) {
20655
+ const item = /^[ \t]+-[ \t]*(.+)$/u.exec(line);
20656
+ if (item === null) break;
20657
+ names.push(unquote(item[1] ?? ""));
20658
+ }
20659
+ return names.filter((name) => name.length > 0);
20660
+ };
20661
+ function readSkillSource(contents) {
20662
+ const { header, body: rawBody } = splitSkillFrontMatter(contents);
20663
+ const body = rawBody.replace(/^\n+/u, "").trimEnd();
20664
+ let argumentNames = [];
20665
+ const fields = /* @__PURE__ */ new Set();
20666
+ header.forEach((line, index) => {
20667
+ const match = TOP_LEVEL_KEY.exec(line);
20668
+ if (match === null) return;
20669
+ const key = match[1] ?? "";
20670
+ fields.add(key);
20671
+ if (key === "arguments")
20672
+ argumentNames = parseArgumentNames(match[2] ?? "", header.slice(index + 1));
20673
+ });
20674
+ return {
20675
+ body,
20676
+ argumentNames,
20677
+ ignored: [
20678
+ ...EXECUTION_FIELDS.filter((field) => fields.has(field)),
20679
+ ...DYNAMIC_COMMAND.test(body) ? ["!`command` injection (not run)"] : []
20680
+ ]
20681
+ };
20682
+ }
20683
+ var loadSkill = async (path) => {
20684
+ try {
20685
+ const bytes = await NodeFSP2.readFile(path);
20686
+ if (bytes.byteLength > MAX_INLINE_SKILL_BYTES) {
20687
+ return { ok: false, reason: `${path} is larger than ${MAX_INLINE_SKILL_BYTES / 1024} KB` };
20688
+ }
20689
+ return { ok: true, source: readSkillSource(bytes.toString("utf8")) };
20690
+ } catch (error) {
20691
+ const code = error.code;
20692
+ return {
20693
+ ok: false,
20694
+ reason: `could not read ${path} (${typeof code === "string" ? code : String(error)})`
20695
+ };
20696
+ }
20697
+ };
20698
+ var skillBlock = (name, lines) => `<ycodium-skill name="${name}">
20699
+ ${lines.join("\n")}
20700
+ </ycodium-skill>`;
20701
+ var notLoadedBlock = (name, reason) => skillBlock(name, [
20702
+ `The user invoked the skill "${name}" in this message, but it could not be included: ${reason}. It is not applied; don't assume its instructions.`
20703
+ ]);
20704
+ var renderLoaded = (invocation, source, variables) => {
20705
+ const skillDir = NodePath6.dirname(invocation.path);
20706
+ const known = {
20707
+ CLAUDE_SKILL_DIR: skillDir,
20708
+ CLAUDE_PROJECT_DIR: variables.projectDir,
20709
+ CLAUDE_SESSION_ID: variables.sessionId
20710
+ };
20711
+ const unresolved = /* @__PURE__ */ new Set();
20712
+ const body = substituteSkillArguments(source.body, invocation.args, source.argumentNames).replace(
20713
+ CLAUDE_VARIABLE,
20714
+ (match, variable) => {
20715
+ const value = known[variable];
20716
+ if (value !== void 0) return value;
20717
+ unresolved.add(`\${${variable}} (left as is)`);
20718
+ return match;
20719
+ }
20720
+ );
20721
+ const ignored = [...source.ignored, ...unresolved];
20722
+ return {
20723
+ block: skillBlock(invocation.name, [
20724
+ `The user invoked the skill "${invocation.name}" in this message. Its SKILL.md is included below and already loaded: follow it for this request, and don't load "${invocation.name}" again with the Skill tool.`,
20725
+ `Base directory for this skill: ${skillDir}`,
20726
+ ...ignored.length > 0 ? [`Not applied when included this way: ${ignored.join(", ")}.`] : [],
20727
+ "",
20728
+ body
20729
+ ]),
20730
+ ignored
20731
+ };
20732
+ };
20733
+ async function renderInlineSkills(invocations, variables, maxChars) {
20734
+ const loads = /* @__PURE__ */ new Map();
20735
+ const blocks = [];
20736
+ const warnings = [];
20737
+ let used = 0;
20738
+ const append = (block) => {
20739
+ if (used + block.length + 2 > maxChars) return false;
20740
+ used += block.length + 2;
20741
+ blocks.push(block);
20742
+ return true;
20743
+ };
20744
+ const notLoaded = (name, reason) => {
20745
+ append(notLoadedBlock(name, reason));
20746
+ warnings.push(`Skill "${name}" was not applied: ${reason}.`);
20747
+ };
20748
+ for (const [index, invocation] of invocations.entries()) {
20749
+ if (index >= MAX_INLINE_SKILLS) {
20750
+ notLoaded(
20751
+ invocation.name,
20752
+ `a message can include at most ${MAX_INLINE_SKILLS} skills before the last one`
20753
+ );
20754
+ continue;
20755
+ }
20756
+ const load = loads.get(invocation.path) ?? loadSkill(invocation.path);
20757
+ loads.set(invocation.path, load);
20758
+ const loaded = await load;
20759
+ if (!loaded.ok) {
20760
+ notLoaded(invocation.name, loaded.reason);
20761
+ continue;
20762
+ }
20763
+ const rendered = renderLoaded(invocation, loaded.source, variables);
20764
+ if (!append(rendered.block)) {
20765
+ notLoaded(
20766
+ invocation.name,
20767
+ `including it would make the message longer than ${EXECUTOR_SEND_TURN_MAX_INPUT_CHARS} characters`
20768
+ );
20769
+ continue;
20770
+ }
20771
+ if (rendered.ignored.length > 0) {
20772
+ warnings.push(
20773
+ `Skill "${invocation.name}" was included in the message rather than run by Claude Code; not applied: ${rendered.ignored.join(", ")}.`
20774
+ );
20775
+ }
20776
+ }
20777
+ return { blocks, warnings };
20778
+ }
20779
+
20780
+ // src/claudeSkillDispatch.ts
20781
+ function planClaudeSkillDispatch(prompt, skills) {
20782
+ const mentions = matchSkillTokens(prompt, skills);
20783
+ const first = mentions[0];
20511
20784
  const last = mentions.at(-1);
20512
- if (last === void 0) return void 0;
20513
- const leading = prompt.slice(0, last.start);
20514
- const trailing = prompt.slice(last.end);
20515
- const leadingWithInlineSlashes = mentions.slice(0, -1).reduceRight(
20516
- (text, mention) => `${text.slice(0, mention.start)}/${text.slice(mention.start + 1)}`,
20517
- leading
20518
- ).trimEnd();
20785
+ if (first === void 0 || last === void 0) return void 0;
20519
20786
  return {
20520
- leadingText: leadingWithInlineSlashes.length > 0 ? leadingWithInlineSlashes : void 0,
20521
- commandText: `/${last.name}${trailing}`.trimEnd(),
20787
+ prefix: prompt.slice(0, first.start).trimEnd(),
20788
+ inline: mentions.slice(0, -1).map((mention, index) => ({
20789
+ name: mention.skill.name,
20790
+ path: mention.skill.path,
20791
+ args: prompt.slice(mention.end, mentions[index + 1]?.start ?? prompt.length).trim()
20792
+ })),
20793
+ commandText: `/${last.name}${prompt.slice(last.end)}`.trimEnd(),
20522
20794
  skillName: last.name
20523
20795
  };
20524
20796
  }
20525
- function dispatchableSkillNames(discovered, sessionSkills) {
20797
+ function dispatchableSkills(discovered, sessionSkills) {
20526
20798
  const hostExpanded = new Set(sessionSkills?.names ?? []);
20527
- return new Set(
20528
- discovered.filter((skill) => skill.enabled && skill.userInvocable !== false).map((skill) => skill.name).filter((name) => !hostExpanded.has(name))
20799
+ return new Map(
20800
+ discovered.filter((skill) => skill.enabled && skill.userInvocable !== false).filter((skill) => !hostExpanded.has(skill.name)).map((skill) => [skill.name, { name: skill.name, path: skill.path }])
20529
20801
  );
20530
20802
  }
20803
+ function unhandledSkillMentions(prompt, discovered, sessionSkills) {
20804
+ const discoveredNames = new Set(discovered.map((skill) => skill.name));
20805
+ const sessionNames = new Set(sessionSkills?.names ?? []);
20806
+ const names = findSkillTokens(prompt).filter((token) => token.sigil === "$" && !discoveredNames.has(token.name)).filter((token) => resolveSkillName(token.name, sessionNames) === void 0).map((token) => token.name);
20807
+ return [...new Set(names)];
20808
+ }
20809
+ var joinParagraphs = (parts) => parts.filter((part) => part.length > 0).join("\n\n");
20810
+ async function prepareClaudeTurnText(input) {
20811
+ const { session, sessionSkills, resumeSessionId } = input.context;
20812
+ const cwd = session.cwd;
20813
+ const environment = { ...process.env, ...input.recipe.environment };
20814
+ const discovered = await discoverClaudeSkills(
20815
+ resolveClaudeConfigDirPath(input.recipe.homePath, environment, cwd),
20816
+ { environment, ...cwd ? { cwd } : {} }
20817
+ );
20818
+ const preamble = input.preamble ?? "";
20819
+ const unhandled = unhandledSkillMentions(input.prompt, discovered, sessionSkills);
20820
+ const plan = planClaudeSkillDispatch(input.prompt, dispatchableSkills(discovered, sessionSkills));
20821
+ const tail = plan === void 0 ? [input.prompt] : [plan.prefix, plan.commandText];
20822
+ const room = (parts) => EXECUTOR_SEND_TURN_MAX_INPUT_CHARS2 - joinParagraphs([preamble, ...parts]).length;
20823
+ const candidate = unhandled.length > 0 ? skillInvocationNotice(unhandled) : "";
20824
+ const notice = room([candidate, ...tail]) >= 0 ? candidate : "";
20825
+ if (plan === void 0) {
20826
+ return {
20827
+ leadingText: joinParagraphs([preamble, notice, input.prompt]),
20828
+ commandText: void 0,
20829
+ warnings: []
20830
+ };
20831
+ }
20832
+ const rendered = await renderInlineSkills(
20833
+ plan.inline,
20834
+ { projectDir: cwd, sessionId: resumeSessionId },
20835
+ room([notice, ...tail])
20836
+ );
20837
+ return {
20838
+ leadingText: joinParagraphs([preamble, notice, plan.prefix, ...rendered.blocks]),
20839
+ commandText: plan.commandText,
20840
+ warnings: rendered.warnings
20841
+ };
20842
+ }
20531
20843
 
20532
20844
  // src/claudeWorkspaceInventory.ts
20533
- import * as NodeFSP2 from "node:fs/promises";
20534
- import * as NodePath6 from "node:path";
20845
+ import * as NodeFSP3 from "node:fs/promises";
20846
+ import * as NodePath7 from "node:path";
20535
20847
  var MAX_COMMAND_DEPTH = 5;
20536
20848
  var MAX_COMMAND_FILES = 500;
20537
20849
  var MAX_DESCRIPTION_LENGTH = 200;
@@ -20549,7 +20861,7 @@ var firstBodyLine = (contents) => {
20549
20861
  return void 0;
20550
20862
  };
20551
20863
  var scanCommands = async (claudeDir) => {
20552
- const root = NodePath6.join(claudeDir, "commands");
20864
+ const root = NodePath7.join(claudeDir, "commands");
20553
20865
  const commands = /* @__PURE__ */ new Map();
20554
20866
  const watched = [root];
20555
20867
  let level = [root];
@@ -20557,9 +20869,9 @@ var scanCommands = async (claudeDir) => {
20557
20869
  for (let depth = 0; depth < MAX_COMMAND_DEPTH && level.length > 0; depth += 1) {
20558
20870
  const next = [];
20559
20871
  for (const directory of level) {
20560
- const entries = await NodeFSP2.readdir(directory, { withFileTypes: true }).catch(() => []);
20872
+ const entries = await NodeFSP3.readdir(directory, { withFileTypes: true }).catch(() => []);
20561
20873
  for (const entry of entries.toSorted((left, right) => left.name.localeCompare(right.name))) {
20562
- const entryPath = NodePath6.join(directory, entry.name);
20874
+ const entryPath = NodePath7.join(directory, entry.name);
20563
20875
  if (entry.isDirectory()) {
20564
20876
  next.push(entryPath);
20565
20877
  watched.push(entryPath);
@@ -20571,7 +20883,7 @@ var scanCommands = async (claudeDir) => {
20571
20883
  files += 1;
20572
20884
  const name = entry.name.slice(0, -".md".length).trim();
20573
20885
  if (name.length === 0 || commands.has(name)) continue;
20574
- const contents = await NodeFSP2.readFile(entryPath, "utf8").catch(() => void 0);
20886
+ const contents = await NodeFSP3.readFile(entryPath, "utf8").catch(() => void 0);
20575
20887
  if (contents === void 0) continue;
20576
20888
  const frontMatter = parseSkillFrontMatter(contents);
20577
20889
  const description = frontMatter.description ?? firstBodyLine(contents);
@@ -20587,13 +20899,13 @@ var scanCommands = async (claudeDir) => {
20587
20899
  return { commands: [...commands.values()], watched };
20588
20900
  };
20589
20901
  var watchSkillsRoot = async (claudeDir) => {
20590
- const root = NodePath6.join(claudeDir, "skills");
20591
- const entries = await NodeFSP2.readdir(root).catch(() => []);
20902
+ const root = NodePath7.join(claudeDir, "skills");
20903
+ const entries = await NodeFSP3.readdir(root).catch(() => []);
20592
20904
  return [
20593
20905
  root,
20594
20906
  ...entries.flatMap((entry) => [
20595
- NodePath6.join(root, entry),
20596
- NodePath6.join(root, entry, "SKILL.md")
20907
+ NodePath7.join(root, entry),
20908
+ NodePath7.join(root, entry, "SKILL.md")
20597
20909
  ])
20598
20910
  ];
20599
20911
  };
@@ -20604,7 +20916,7 @@ var mergeByName = (base, preferred) => {
20604
20916
  return [...merged.values()].toSorted((left, right) => left.name.localeCompare(right.name));
20605
20917
  };
20606
20918
  var discoverClaudeWorkspaceInventory = async (input) => {
20607
- const projectClaudeDir = NodePath6.join(input.cwd, ".claude");
20919
+ const projectClaudeDir = NodePath7.join(input.cwd, ".claude");
20608
20920
  const [userCommands, projectCommands, skills, userSkillWatch, projectSkillWatch] = await Promise.all([
20609
20921
  scanCommands(input.configDirPath),
20610
20922
  scanCommands(projectClaudeDir),
@@ -20626,9 +20938,9 @@ var discoverClaudeWorkspaceInventory = async (input) => {
20626
20938
  ...userSkillWatch,
20627
20939
  ...projectSkillWatch,
20628
20940
  // `skillOverrides` 在这几个设置文件里:开关一个技能也要重查。
20629
- NodePath6.join(input.configDirPath, "settings.json"),
20630
- NodePath6.join(projectClaudeDir, "settings.json"),
20631
- NodePath6.join(projectClaudeDir, "settings.local.json")
20941
+ NodePath7.join(input.configDirPath, "settings.json"),
20942
+ NodePath7.join(projectClaudeDir, "settings.json"),
20943
+ NodePath7.join(projectClaudeDir, "settings.local.json")
20632
20944
  ]
20633
20945
  };
20634
20946
  };
@@ -20749,59 +21061,8 @@ function parseClaudeInstanceConfig(config) {
20749
21061
  envelopeDriver: readTrimmed(config["driver"]) || void 0
20750
21062
  };
20751
21063
  }
20752
- function tokenizeLaunchArgs(value) {
20753
- const input = value.trim();
20754
- if (!input) return [];
20755
- const tokens = [];
20756
- let current = "";
20757
- let quote;
20758
- let quoted = false;
20759
- for (let index = 0; index < input.length; index++) {
20760
- const char = input[index];
20761
- if (char === void 0) continue;
20762
- if (quote) {
20763
- if (char === quote) {
20764
- quote = void 0;
20765
- quoted = true;
20766
- } else if (char === "\\" && quote === '"') {
20767
- const next = input[index + 1];
20768
- if (next !== void 0 && ['"', "\\", "$", "`"].includes(next)) {
20769
- current += next;
20770
- index++;
20771
- } else {
20772
- current += char;
20773
- }
20774
- } else {
20775
- current += char;
20776
- }
20777
- continue;
20778
- }
20779
- if (char === "'" || char === '"') {
20780
- quote = char;
20781
- quoted = true;
20782
- } else if (/\s/.test(char)) {
20783
- if (current || quoted) {
20784
- tokens.push(current);
20785
- current = "";
20786
- quoted = false;
20787
- }
20788
- } else if (char === "\\") {
20789
- const next = input[index + 1];
20790
- if (next !== void 0 && /\s/.test(next)) {
20791
- current += next;
20792
- index++;
20793
- } else {
20794
- current += char;
20795
- }
20796
- } else {
20797
- current += char;
20798
- }
20799
- }
20800
- if (current || quoted) tokens.push(current);
20801
- return tokens;
20802
- }
20803
21064
  function parseCliArgs(args, options) {
20804
- const tokens = typeof args === "string" ? tokenizeLaunchArgs(args) : Array.from(args);
21065
+ const tokens = typeof args === "string" ? tokenizeShellWords(args) : Array.from(args);
20805
21066
  const booleanSet = options?.booleanFlags ? new Set(options.booleanFlags) : void 0;
20806
21067
  const flags = {};
20807
21068
  const positionals = [];
@@ -21687,7 +21948,7 @@ function claudeReadAllowRules(directories) {
21687
21948
  // src/sessions.ts
21688
21949
  import * as NodeFS3 from "node:fs";
21689
21950
  import * as NodeOS6 from "node:os";
21690
- import * as NodePath7 from "node:path";
21951
+ import * as NodePath8 from "node:path";
21691
21952
  import * as NodeProcess2 from "node:process";
21692
21953
 
21693
21954
  // ../../packages/ycodium-jsonl-stream/src/jsonlStream.ts
@@ -22316,7 +22577,7 @@ function listTranscriptFiles(transcriptDir, directory) {
22316
22577
  const scanned = matched.length > 0 ? matched : dirs;
22317
22578
  const found = [];
22318
22579
  for (const dir of scanned) {
22319
- const projectDir = NodePath7.join(transcriptDir, dir);
22580
+ const projectDir = NodePath8.join(transcriptDir, dir);
22320
22581
  let names;
22321
22582
  try {
22322
22583
  names = NodeFS3.readdirSync(projectDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(TRANSCRIPT_SUFFIX)).map((entry) => entry.name);
@@ -22326,12 +22587,12 @@ function listTranscriptFiles(transcriptDir, directory) {
22326
22587
  for (const name of names) {
22327
22588
  let stat2;
22328
22589
  try {
22329
- stat2 = NodeFS3.statSync(NodePath7.join(projectDir, name));
22590
+ stat2 = NodeFS3.statSync(NodePath8.join(projectDir, name));
22330
22591
  } catch {
22331
22592
  continue;
22332
22593
  }
22333
22594
  found.push({
22334
- path: NodePath7.join(projectDir, name),
22595
+ path: NodePath8.join(projectDir, name),
22335
22596
  sessionId: name.slice(0, -TRANSCRIPT_SUFFIX.length),
22336
22597
  size: stat2.size,
22337
22598
  mtimeMs: Number.isFinite(stat2.mtimeMs) ? stat2.mtimeMs : 0
@@ -22342,7 +22603,7 @@ function listTranscriptFiles(transcriptDir, directory) {
22342
22603
  }
22343
22604
  function readClaudeLiveSessionIds(configDir) {
22344
22605
  const live = /* @__PURE__ */ new Set();
22345
- const markerDir = NodePath7.join(configDir, "sessions");
22606
+ const markerDir = NodePath8.join(configDir, "sessions");
22346
22607
  let entries;
22347
22608
  try {
22348
22609
  entries = NodeFS3.readdirSync(markerDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => entry.name);
@@ -22352,7 +22613,7 @@ function readClaudeLiveSessionIds(configDir) {
22352
22613
  for (const entry of entries) {
22353
22614
  let marker;
22354
22615
  try {
22355
- marker = JSON.parse(NodeFS3.readFileSync(NodePath7.join(markerDir, entry), "utf8"));
22616
+ marker = JSON.parse(NodeFS3.readFileSync(NodePath8.join(markerDir, entry), "utf8"));
22356
22617
  } catch {
22357
22618
  continue;
22358
22619
  }
@@ -22406,7 +22667,7 @@ async function scanClaudeSessions(input) {
22406
22667
  function resolveTranscriptPath(input) {
22407
22668
  const fileName = `${input.sessionId}${TRANSCRIPT_SUFFIX}`;
22408
22669
  if (input.cwd !== void 0 && input.cwd.length > 0) {
22409
- const direct = NodePath7.join(
22670
+ const direct = NodePath8.join(
22410
22671
  input.transcriptDir,
22411
22672
  encodeClaudeProjectDirName(input.cwd),
22412
22673
  fileName
@@ -22423,7 +22684,7 @@ function resolveTranscriptPath(input) {
22423
22684
  return null;
22424
22685
  }
22425
22686
  for (const dir of dirs) {
22426
- const candidate = NodePath7.join(input.transcriptDir, dir, fileName);
22687
+ const candidate = NodePath8.join(input.transcriptDir, dir, fileName);
22427
22688
  try {
22428
22689
  if (NodeFS3.statSync(candidate).isFile()) return candidate;
22429
22690
  } catch {
@@ -22901,9 +23162,9 @@ function normalizeClaudeTaskProgressTokenUsage(value, memory) {
22901
23162
  }
22902
23163
 
22903
23164
  // src/textGeneration.ts
22904
- import * as NodeFSP3 from "node:fs/promises";
23165
+ import * as NodeFSP4 from "node:fs/promises";
22905
23166
  import * as NodeOS7 from "node:os";
22906
- import * as NodePath8 from "node:path";
23167
+ import * as NodePath9 from "node:path";
22907
23168
  import { mergeLaunchEnv } from "@ycodium-ai/plugin-api/executor";
22908
23169
 
22909
23170
  // src/textGenerationUtils.ts
@@ -23372,13 +23633,13 @@ function createClaudeTextGeneration(recipe, host, modelCatalog) {
23372
23633
  return decodeStructuredOutput(stdout, input.operation);
23373
23634
  };
23374
23635
  if (input.operation !== "generateThreadTitle") return runIn(input.cwd);
23375
- const scratchDirectory = await NodeFSP3.mkdtemp(
23376
- NodePath8.join(NodeOS7.tmpdir(), "ycodium-claude-title-")
23636
+ const scratchDirectory = await NodeFSP4.mkdtemp(
23637
+ NodePath9.join(NodeOS7.tmpdir(), "ycodium-claude-title-")
23377
23638
  );
23378
23639
  try {
23379
23640
  return await runIn(scratchDirectory);
23380
23641
  } finally {
23381
- await NodeFSP3.rm(scratchDirectory, { recursive: true, force: true }).catch(() => void 0);
23642
+ await NodeFSP4.rm(scratchDirectory, { recursive: true, force: true }).catch(() => void 0);
23382
23643
  }
23383
23644
  };
23384
23645
  const asRecord2 = (value, operation) => {
@@ -23528,11 +23789,11 @@ function createClaudeTextGeneration(recipe, host, modelCatalog) {
23528
23789
  }
23529
23790
 
23530
23791
  // src/usageRead/index.ts
23531
- import * as NodePath12 from "node:path";
23792
+ import * as NodePath13 from "node:path";
23532
23793
 
23533
23794
  // src/usageRead/quota.ts
23534
23795
  import * as NodeFS4 from "node:fs";
23535
- import * as NodePath9 from "node:path";
23796
+ import * as NodePath10 from "node:path";
23536
23797
  import { mergeLaunchEnv as mergeLaunchEnv2, setsOwnModelConnection } from "@ycodium-ai/plugin-api/executor";
23537
23798
  import {
23538
23799
  parseRetryAfterMs
@@ -23555,14 +23816,14 @@ var claudeCredentialFileCandidates = (recipe) => {
23555
23816
  const credentialsDir = resolveClaudeCredentialsDirPath(
23556
23817
  environment[CLAUDE_CREDENTIALS_DIR_ENVIRONMENT_VARIABLE] ?? ""
23557
23818
  );
23558
- if (credentialsDir !== null) return [NodePath9.join(credentialsDir, ".credentials.json")];
23819
+ if (credentialsDir !== null) return [NodePath10.join(credentialsDir, ".credentials.json")];
23559
23820
  const configDir = resolveClaudeConfigDirPath(recipe.homePath, environment);
23560
23821
  const home = resolveClaudeHomePath(recipe.homePath);
23561
23822
  return [
23562
23823
  .../* @__PURE__ */ new Set([
23563
- NodePath9.join(configDir, ".credentials.json"),
23564
- NodePath9.join(home, ".claude", ".credentials.json"),
23565
- NodePath9.join(home, ".credentials.json")
23824
+ NodePath10.join(configDir, ".credentials.json"),
23825
+ NodePath10.join(home, ".claude", ".credentials.json"),
23826
+ NodePath10.join(home, ".credentials.json")
23566
23827
  ])
23567
23828
  ];
23568
23829
  };
@@ -23723,11 +23984,11 @@ var claudeQuotaFromRateLimitEvent = (message) => {
23723
23984
  };
23724
23985
 
23725
23986
  // src/usageRead/scanCache.ts
23726
- import * as NodePath11 from "node:path";
23987
+ import * as NodePath12 from "node:path";
23727
23988
 
23728
23989
  // src/usageRead/transcriptScan.ts
23729
23990
  import * as NodeFS5 from "node:fs";
23730
- import * as NodePath10 from "node:path";
23991
+ import * as NodePath11 from "node:path";
23731
23992
 
23732
23993
  // src/usageRead/transcriptLine.ts
23733
23994
  var int = (value) => typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.trunc(value) : 0;
@@ -23789,7 +24050,7 @@ async function listTranscriptFiles2(root, sinceMs) {
23789
24050
  return;
23790
24051
  }
23791
24052
  for (const entry of entries) {
23792
- const child = NodePath10.join(dir, entry.name);
24053
+ const child = NodePath11.join(dir, entry.name);
23793
24054
  if (entry.isDirectory()) {
23794
24055
  await walk(child);
23795
24056
  continue;
@@ -24026,8 +24287,8 @@ function pruneScanCache(cache, options) {
24026
24287
  for (const [path, entry] of cache) {
24027
24288
  const agedOut = entry.mtimeMs < options.retentionCutoffMs;
24028
24289
  const underWalkedRoot = options.walkedRoots.some((root) => {
24029
- const relative2 = NodePath11.relative(root, path);
24030
- return relative2 === "" || relative2 !== ".." && !relative2.startsWith(`..${NodePath11.sep}`) && !NodePath11.isAbsolute(relative2);
24290
+ const relative2 = NodePath12.relative(root, path);
24291
+ return relative2 === "" || relative2 !== ".." && !relative2.startsWith(`..${NodePath12.sep}`) && !NodePath12.isAbsolute(relative2);
24031
24292
  });
24032
24293
  const deleted = underWalkedRoot && entry.mtimeMs >= options.windowStartMs && !options.livePaths.has(path);
24033
24294
  if (agedOut || deleted) {
@@ -24203,7 +24464,7 @@ var createClaudeUsage = (input) => {
24203
24464
  });
24204
24465
  const tokens = createClaudeTokenReader({
24205
24466
  configDir,
24206
- transcriptRoot: NodePath12.join(configDir, "projects"),
24467
+ transcriptRoot: NodePath13.join(configDir, "projects"),
24207
24468
  cache: input.cache
24208
24469
  });
24209
24470
  return {
@@ -24373,9 +24634,9 @@ function nativeProviderRefs(_context, options) {
24373
24634
  }
24374
24635
  async function buildUserMessageFromTurn(input) {
24375
24636
  const sdkContent = [];
24376
- const dispatch = input.skillNames === void 0 ? void 0 : planClaudeSkillDispatch(input.text, input.skillNames);
24377
- if (dispatch?.leadingText !== void 0) {
24378
- sdkContent.push({ type: "text", text: dispatch.leadingText });
24637
+ const { leadingText, commandText } = input.text;
24638
+ if (commandText !== void 0 && leadingText.length > 0) {
24639
+ sdkContent.push({ type: "text", text: leadingText });
24379
24640
  }
24380
24641
  for (const attachment of input.attachments ?? []) {
24381
24642
  if (!attachment || typeof attachment !== "object" || Array.isArray(attachment)) continue;
@@ -24395,10 +24656,10 @@ async function buildUserMessageFromTurn(input) {
24395
24656
  const bytes = await input.attachmentsApi.read(id2);
24396
24657
  sdkContent.push(buildClaudeImageContentBlock({ mimeType, bytes }));
24397
24658
  }
24398
- if (dispatch !== void 0) {
24399
- sdkContent.push({ type: "text", text: dispatch.commandText });
24400
- } else if (input.text.length > 0) {
24401
- sdkContent.push({ type: "text", text: input.text });
24659
+ if (commandText !== void 0) {
24660
+ sdkContent.push({ type: "text", text: commandText });
24661
+ } else if (leadingText.length > 0) {
24662
+ sdkContent.push({ type: "text", text: leadingText });
24402
24663
  }
24403
24664
  return buildUserMessage({ sdkContent });
24404
24665
  }
@@ -25520,7 +25781,7 @@ function createClaudeAdapter(recipe, host, options) {
25520
25781
  claudeFailureHint({
25521
25782
  authenticationFailed: context.turnState?.authenticationFailed === true,
25522
25783
  environment: buildLaunchEnvironment(),
25523
- cwd: NodePath13.resolve(context.session.cwd ?? "."),
25784
+ cwd: NodePath14.resolve(context.session.cwd ?? "."),
25524
25785
  ...context.turnState === void 0 ? {} : {
25525
25786
  usageLimit: {
25526
25787
  rejected: context.turnState.rejectedRateLimits,
@@ -27010,16 +27271,17 @@ function createClaudeAdapter(recipe, host, options) {
27010
27271
  providerRefs: {}
27011
27272
  });
27012
27273
  }
27013
- const cliEnvironment = { ...process.env, ...recipe.environment };
27014
- const skills = await discoverClaudeSkills(
27015
- resolveClaudeConfigDirPath(recipe.homePath, cliEnvironment, context.session.cwd),
27016
- { environment: cliEnvironment, ...context.session.cwd ? { cwd: context.session.cwd } : {} }
27017
- );
27274
+ const turnText = await prepareClaudeTurnText({
27275
+ prompt: buildPromptText(turnCapsForPrompt, input, boundInstanceId),
27276
+ preamble: input.inputPreamble,
27277
+ recipe,
27278
+ context
27279
+ });
27280
+ for (const warning of turnText.warnings) await emitRuntimeWarning(context, warning);
27018
27281
  const message = await buildUserMessageFromTurn({
27019
- text: buildPromptText(turnCapsForPrompt, input, boundInstanceId),
27282
+ text: turnText,
27020
27283
  attachments: input.attachments,
27021
- attachmentsApi: options?.attachments,
27022
- skillNames: dispatchableSkillNames(skills, context.sessionSkills)
27284
+ attachmentsApi: options?.attachments
27023
27285
  });
27024
27286
  validateSteeringTarget();
27025
27287
  context.promptQueue.push({
@@ -27195,7 +27457,7 @@ function createClaudeInstance(config, host, attachments, overrides, usageCache)
27195
27457
  );
27196
27458
  return {
27197
27459
  configDir,
27198
- transcriptDir: NodePath13.join(configDir, "projects")
27460
+ transcriptDir: NodePath14.join(configDir, "projects")
27199
27461
  };
27200
27462
  };
27201
27463
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ycodium-ai/agent-claude",
3
- "version": "0.2.2896",
3
+ "version": "0.2.2898",
4
4
  "description": "Claude executor: Claude Code CLI via @anthropic-ai/claude-agent-sdk.",
5
5
  "keywords": [
6
6
  "ycodium",
@@ -24,7 +24,7 @@
24
24
  "@types/node": "24.12.4",
25
25
  "esbuild": "0.28.1",
26
26
  "vite-plus": "0.2.2",
27
- "@ycodium-ai/plugin-api": "0.2.2896",
27
+ "@ycodium-ai/plugin-api": "0.2.2898",
28
28
  "ycodium-executor-testing": "0.0.0",
29
29
  "ycodium-jsonl-stream": "0.0.0",
30
30
  "ycodium-composer-tokens": "0.0.0"