@ycodium-ai/agent-claude 0.2.2690 → 0.2.2696

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
@@ -217,7 +217,7 @@ var CLAUDE_CARD = {
217
217
 
218
218
  // src/claudeInstance.ts
219
219
  import * as NodeOS8 from "node:os";
220
- import * as NodePath8 from "node:path";
220
+ import * as NodePath13 from "node:path";
221
221
 
222
222
  // ../../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
223
223
  import { createRequire as xz } from "node:module";
@@ -19650,10 +19650,12 @@ function HU(e, t) {
19650
19650
  }
19651
19651
 
19652
19652
  // src/claudeInstance.ts
19653
+ import { mergeLaunchEnv as mergeLaunchEnv3 } from "@ycodium-ai/plugin-api/executor";
19654
+
19655
+ // src/claudeProbe.ts
19653
19656
  import {
19654
19657
  describeExecutorSpawnFailure,
19655
- ExecutorSpawnRefusedError,
19656
- mergeLaunchEnv as mergeLaunchEnv2
19658
+ ExecutorSpawnRefusedError
19657
19659
  } from "@ycodium-ai/plugin-api/executor";
19658
19660
 
19659
19661
  // src/claudeAccount.ts
@@ -19743,78 +19745,73 @@ function readClaudeAccount(credentialsDirPath) {
19743
19745
  };
19744
19746
  }
19745
19747
 
19746
- // src/claudeExecutable.ts
19747
- import * as NodeFS2 from "node:fs";
19748
- import * as NodeOS2 from "node:os";
19749
- import * as NodePath2 from "node:path";
19750
- var WINDOWS_SHIM_EXTENSIONS = /* @__PURE__ */ new Set([".cmd", ".bat", ".ps1"]);
19751
- var NPM_PACKAGE_ENTRY_CANDIDATES = [
19752
- ["node_modules", "@anthropic-ai", "claude-code", "bin", "claude.exe"],
19753
- ["node_modules", "@anthropic-ai", "claude-code", "cli.js"]
19754
- ];
19755
- function isExistingFile(filePath) {
19756
- try {
19757
- return NodeFS2.statSync(filePath).isFile();
19758
- } catch {
19759
- return false;
19760
- }
19761
- }
19762
- function resolveClaudeSdkExecutablePath(binaryPath, environment = process.env, isFile = isExistingFile, isWindows = NodeOS2.type() === "Windows_NT") {
19763
- if (!isWindows) {
19764
- return binaryPath;
19765
- }
19766
- const resolved = resolveWindowsExecutable(binaryPath, environment) ?? binaryPath;
19767
- const extension = NodePath2.win32.extname(resolved).toLowerCase();
19768
- if (!WINDOWS_SHIM_EXTENSIONS.has(extension)) {
19769
- return resolved;
19770
- }
19771
- const shimDirectory = NodePath2.win32.dirname(resolved);
19772
- for (const entrySegments of NPM_PACKAGE_ENTRY_CANDIDATES) {
19773
- const candidate = NodePath2.win32.join(shimDirectory, ...entrySegments);
19774
- if (isFile(candidate)) {
19775
- return candidate;
19776
- }
19777
- }
19778
- return binaryPath;
19779
- }
19780
- function resolveWindowsExecutable(binaryPath, environment) {
19781
- const trimmed = binaryPath.trim();
19782
- if (trimmed.length === 0) return void 0;
19783
- if (NodePath2.win32.isAbsolute(trimmed) && isExistingFile(trimmed)) return trimmed;
19784
- const pathValue = environment.PATH ?? environment.Path ?? "";
19785
- const pathext = environment.PATHEXT ?? ".EXE;.CMD;.BAT;.COM";
19786
- const extensions = pathext.split(";").filter((ext) => ext.length > 0);
19787
- const hasExt = NodePath2.win32.extname(trimmed).length > 0;
19788
- for (const dir of pathValue.split(";")) {
19789
- if (dir.trim().length === 0) continue;
19790
- const candidates = hasExt ? [NodePath2.win32.join(dir, trimmed)] : extensions.map((ext) => NodePath2.win32.join(dir, `${trimmed}${ext}`));
19791
- for (const candidate of candidates) {
19792
- if (isExistingFile(candidate)) return candidate;
19793
- }
19794
- }
19795
- return void 0;
19796
- }
19797
-
19798
19748
  // src/claudeHome.ts
19799
- import * as NodeOS4 from "node:os";
19800
- import * as NodePath4 from "node:path";
19801
-
19802
- // src/pathExpansion.ts
19803
19749
  import * as NodeOS3 from "node:os";
19804
19750
  import * as NodePath3 from "node:path";
19751
+
19752
+ // src/pathExpansion.ts
19753
+ import * as NodeOS2 from "node:os";
19754
+ import * as NodePath2 from "node:path";
19805
19755
  function expandHomePath(value) {
19806
- if (value === "~") return NodeOS3.homedir();
19756
+ if (value === "~") return NodeOS2.homedir();
19807
19757
  if (value.startsWith("~/") || value.startsWith("~\\")) {
19808
- return NodePath3.join(NodeOS3.homedir(), value.slice(2));
19758
+ return NodePath2.join(NodeOS2.homedir(), value.slice(2));
19809
19759
  }
19810
19760
  return value;
19811
19761
  }
19812
19762
 
19763
+ // src/usageLimit.ts
19764
+ var WINDOW_NAMES = {
19765
+ five_hour: "5-hour",
19766
+ seven_day: "7-day",
19767
+ seven_day_opus: "7-day Opus",
19768
+ seven_day_sonnet: "7-day Sonnet",
19769
+ overage: "overage"
19770
+ };
19771
+ var MAX_CREDIBLE_WAIT_MS = 30 * 24 * 60 * 60 * 1e3;
19772
+ var isBlockingRateLimit = (info) => info.status === "rejected" && info.overageStatus !== "allowed" && info.overageStatus !== "allowed_warning" && info.isUsingOverage !== true && info.overageInUse !== true;
19773
+ var isClearingRateLimit = (info) => info.status === "allowed" || info.status === "allowed_warning" || info.overageStatus === "allowed" || info.overageStatus === "allowed_warning" || info.isUsingOverage === true || info.overageInUse === true;
19774
+ var rateLimitKey = (info) => `${info.rateLimitType ?? "unknown"}:${String(info.resetsAt ?? "unknown")}`;
19775
+ var formatLimitWait = (waitMs) => {
19776
+ const totalMinutes = Math.max(1, Math.ceil(waitMs / 6e4));
19777
+ const days = Math.floor(totalMinutes / (24 * 60));
19778
+ const hours = Math.floor(totalMinutes % (24 * 60) / 60);
19779
+ const minutes = totalMinutes % 60;
19780
+ if (days > 0) return hours === 0 ? `${String(days)}d` : `${String(days)}d ${String(hours)}h`;
19781
+ if (hours === 0) return `${String(totalMinutes)}m`;
19782
+ return minutes === 0 ? `${String(hours)}h` : `${String(hours)}h ${String(minutes)}m`;
19783
+ };
19784
+ var waitUntil = (resetsAtSeconds, nowMs) => {
19785
+ if (resetsAtSeconds === void 0 || !Number.isFinite(nowMs)) return void 0;
19786
+ const waitMs = resetsAtSeconds * 1e3 - nowMs;
19787
+ return waitMs > 0 && waitMs <= MAX_CREDIBLE_WAIT_MS ? formatLimitWait(waitMs) : void 0;
19788
+ };
19789
+ var describeUsageLimitPause = (info, nowMs) => {
19790
+ const name = info.rateLimitType === void 0 ? void 0 : WINDOW_NAMES[info.rateLimitType];
19791
+ const wait = waitUntil(info.resetsAt, nowMs);
19792
+ return `Claude usage limit reached. This turn is paused until the ${name === void 0 ? "" : `${name} `}limit resets${wait === void 0 ? "" : ` in ${wait}`}.`;
19793
+ };
19794
+ var describeUsageLimitFailure = (rejected, nowMs) => {
19795
+ let name;
19796
+ let wait;
19797
+ let latestResetSeconds = Number.NEGATIVE_INFINITY;
19798
+ for (const [type4, resetsAt] of rejected) {
19799
+ const label = WINDOW_NAMES[type4];
19800
+ const reachable = waitUntil(resetsAt, nowMs);
19801
+ if (label === void 0 || reachable === void 0 || resetsAt === void 0) continue;
19802
+ if (resetsAt <= latestResetSeconds) continue;
19803
+ latestResetSeconds = resetsAt;
19804
+ name = label;
19805
+ wait = reachable;
19806
+ }
19807
+ return name === void 0 ? "Claude usage limit reached. Send the message again once the limit resets." : `Claude usage limit reached. The ${name} limit resets in ${wait}. Send the message again once it resets.`;
19808
+ };
19809
+
19813
19810
  // src/claudeHome.ts
19814
19811
  var CLAUDE_CREDENTIALS_DIR_ENVIRONMENT_VARIABLE = "CLAUDE_SECURESTORAGE_CONFIG_DIR";
19815
19812
  function resolveClaudeHomePath(homePath) {
19816
19813
  const trimmed = homePath.trim();
19817
- return NodePath4.resolve(trimmed.length > 0 ? expandHomePath(trimmed) : NodeOS4.homedir());
19814
+ return NodePath3.resolve(trimmed.length > 0 ? expandHomePath(trimmed) : NodeOS3.homedir());
19818
19815
  }
19819
19816
  function claudeContinuationKey(homePath) {
19820
19817
  return `claude:home:${resolveClaudeHomePath(homePath)}`;
@@ -19822,18 +19819,18 @@ function claudeContinuationKey(homePath) {
19822
19819
  function resolveClaudeConfigDirPath(homePath, environment, cwd) {
19823
19820
  const trimmed = homePath.trim();
19824
19821
  if (trimmed.length > 0) {
19825
- return NodePath4.resolve(expandHomePath(trimmed));
19822
+ return NodePath3.resolve(expandHomePath(trimmed));
19826
19823
  }
19827
19824
  const environmentConfigDir = environment.CLAUDE_CONFIG_DIR?.trim() ?? "";
19828
19825
  if (environmentConfigDir.length > 0) {
19829
- return cwd ? NodePath4.resolve(cwd, environmentConfigDir) : NodePath4.resolve(environmentConfigDir);
19826
+ return cwd ? NodePath3.resolve(cwd, environmentConfigDir) : NodePath3.resolve(environmentConfigDir);
19830
19827
  }
19831
- return NodePath4.join(NodeOS4.homedir(), ".claude");
19828
+ return NodePath3.join(NodeOS3.homedir(), ".claude");
19832
19829
  }
19833
19830
  function resolveClaudeCredentialsDirPath(credentialsPath) {
19834
19831
  const trimmed = credentialsPath.trim();
19835
19832
  if (trimmed.length === 0) return null;
19836
- return NodePath4.resolve(expandHomePath(trimmed));
19833
+ return NodePath3.resolve(expandHomePath(trimmed));
19837
19834
  }
19838
19835
  function resolveClaudeHomeLayout(input) {
19839
19836
  return {
@@ -19864,13 +19861,13 @@ function claudeSignedOutMessage(input) {
19864
19861
  const configDir = input.configDir?.trim() ?? "";
19865
19862
  if (configDir.length > 0) {
19866
19863
  variables.push(
19867
- `CLAUDE_CONFIG_DIR set to ${quoteLiteral(NodePath4.resolve(input.cwd, configDir))}`
19864
+ `CLAUDE_CONFIG_DIR set to ${quoteLiteral(NodePath3.resolve(input.cwd, configDir))}`
19868
19865
  );
19869
19866
  }
19870
19867
  const credentialsDir = input.credentialsDir?.trim() ?? "";
19871
19868
  if (credentialsDir.length > 0) {
19872
19869
  variables.push(
19873
- `${CLAUDE_CREDENTIALS_DIR_ENVIRONMENT_VARIABLE} set to ${quoteLiteral(NodePath4.resolve(input.cwd, credentialsDir))}`
19870
+ `${CLAUDE_CREDENTIALS_DIR_ENVIRONMENT_VARIABLE} set to ${quoteLiteral(NodePath3.resolve(input.cwd, credentialsDir))}`
19874
19871
  );
19875
19872
  }
19876
19873
  const withVariables = variables.length > 0 ? `, with ${variables.join(" and ")}` : "";
@@ -19884,34 +19881,18 @@ function claudeFailureHint(input) {
19884
19881
  cwd: input.cwd
19885
19882
  });
19886
19883
  }
19884
+ const limit = input.usageLimit;
19885
+ if (limit !== void 0 && (limit.rejected.size > 0 || limit.latestAssistantRateLimited)) {
19886
+ return describeUsageLimitFailure(limit.rejected, limit.nowMs);
19887
+ }
19887
19888
  return void 0;
19888
19889
  }
19889
19890
 
19890
- // src/claudeSignIn.ts
19891
- var POSIX_BARE_VALUE = /^[A-Za-z0-9_@%+=:,./-]+$/;
19892
- function quotePosixShellValue(value) {
19893
- if (POSIX_BARE_VALUE.test(value)) return value;
19894
- return `'${value.replaceAll("'", `'\\''`)}'`;
19895
- }
19896
- function quotePowerShellValue(value) {
19897
- return `'${value.replaceAll("'", "''")}'`;
19898
- }
19899
- function withClaudeCredentialsDir(input) {
19900
- const variable = CLAUDE_CREDENTIALS_DIR_ENVIRONMENT_VARIABLE;
19901
- return input.platform === "win32" ? `$env:${variable}=${quotePowerShellValue(input.credentialsDirPath)}; ${input.command}` : `${variable}=${quotePosixShellValue(input.credentialsDirPath)} ${input.command}`;
19902
- }
19903
- function claudeSignInCommand(credentialsPath, platform2) {
19904
- const command = "claude auth login";
19905
- const credentialsDirPath = resolveClaudeCredentialsDirPath(credentialsPath);
19906
- if (credentialsDirPath === null) return command;
19907
- return withClaudeCredentialsDir({ command, credentialsDirPath, platform: platform2 });
19908
- }
19909
-
19910
19891
  // src/claudeSkills.ts
19911
19892
  import * as NodeFSP from "node:fs/promises";
19912
- import * as NodePath5 from "node:path";
19893
+ import * as NodePath4 from "node:path";
19913
19894
  import * as NodeProcess from "node:process";
19914
- var FRONT_MATTER_KEY = /^(description|disable-model-invocation|user-invocable):[ \t]*(.*)$/;
19895
+ var FRONT_MATTER_KEY = /^(description|disable-model-invocation|user-invocable|argument-hint):[ \t]*(.*)$/;
19915
19896
  var BLOCK_SCALAR = /^[|>][+-]?$/;
19916
19897
  function parseFrontMatterBoolean(value) {
19917
19898
  switch (value?.trim().toLowerCase()) {
@@ -19952,14 +19933,16 @@ function parseSkillFrontMatter(contents) {
19952
19933
  }
19953
19934
  value = block.join(value.startsWith("|") ? "\n" : " ");
19954
19935
  } else {
19955
- if (key !== "description") value = value.replace(/\s+#.*$/, "");
19936
+ if (key !== "description" && key !== "argument-hint") value = value.replace(/\s+#.*$/, "");
19956
19937
  value = value.replace(/^(["'])(.*)\1$/, "$2").trim();
19957
19938
  }
19958
19939
  if (value.length > 0 && values[key] === void 0) values[key] = value;
19959
19940
  }
19960
19941
  const description = values["description"];
19942
+ const argumentHint = values["argument-hint"];
19961
19943
  return {
19962
19944
  ...description === void 0 ? {} : { description },
19945
+ ...argumentHint === void 0 ? {} : { argumentHint },
19963
19946
  ...parseFrontMatterBoolean(values["disable-model-invocation"]) === true ? { userInvocationOnly: true } : {},
19964
19947
  ...parseFrontMatterBoolean(values["user-invocable"]) === false ? { userInvocable: false } : {}
19965
19948
  };
@@ -19970,7 +19953,7 @@ function claudeManagedSettingsPath(platform2, environment) {
19970
19953
  }
19971
19954
  if (platform2 === "win32") {
19972
19955
  const programData = environment["PROGRAMDATA"]?.trim();
19973
- return programData ? NodePath5.join(programData, "ClaudeCode", "managed-settings.json") : void 0;
19956
+ return programData ? NodePath4.join(programData, "ClaudeCode", "managed-settings.json") : void 0;
19974
19957
  }
19975
19958
  return "/etc/claude-code/managed-settings.json";
19976
19959
  }
@@ -19978,24 +19961,24 @@ function skillOverrideSettingsPaths(configDirPath, cwd, platform2, environment,
19978
19961
  const managedPath = claudeManagedSettingsPath(platform2, environment);
19979
19962
  const root = repositoryRoot !== void 0 && repositoryRoot !== cwd ? repositoryRoot : void 0;
19980
19963
  return [
19981
- NodePath5.join(configDirPath, "settings.json"),
19964
+ NodePath4.join(configDirPath, "settings.json"),
19982
19965
  ...cwd ? [
19983
- NodePath5.join(cwd, ".claude", "settings.json"),
19984
- NodePath5.join(cwd, ".claude", "settings.local.json")
19966
+ NodePath4.join(cwd, ".claude", "settings.json"),
19967
+ NodePath4.join(cwd, ".claude", "settings.local.json")
19985
19968
  ] : [],
19986
- ...root ? [NodePath5.join(root, ".claude", "settings.local.json")] : [],
19969
+ ...root ? [NodePath4.join(root, ".claude", "settings.local.json")] : [],
19987
19970
  ...managedPath ? [managedPath] : []
19988
19971
  ];
19989
19972
  }
19990
19973
  async function findRepositoryRoot(cwd) {
19991
- let current = NodePath5.resolve(cwd);
19974
+ let current = NodePath4.resolve(cwd);
19992
19975
  for (; ; ) {
19993
- const found = await NodeFSP.stat(NodePath5.join(current, ".git")).then(
19976
+ const found = await NodeFSP.stat(NodePath4.join(current, ".git")).then(
19994
19977
  () => true,
19995
19978
  () => false
19996
19979
  );
19997
19980
  if (found) return current;
19998
- const parent = NodePath5.dirname(current);
19981
+ const parent = NodePath4.dirname(current);
19999
19982
  if (parent === current) return void 0;
20000
19983
  current = parent;
20001
19984
  }
@@ -20077,8 +20060,8 @@ async function discoverClaudeSkills(configDirPath, options = {}) {
20077
20060
  options.environment ?? NodeProcess.env
20078
20061
  );
20079
20062
  const roots = [
20080
- { directory: NodePath5.join(configDirPath, "skills"), scope: "user" },
20081
- ...cwd ? [{ directory: NodePath5.join(cwd, ".claude", "skills"), scope: "project" }] : []
20063
+ { directory: NodePath4.join(configDirPath, "skills"), scope: "user" },
20064
+ ...cwd ? [{ directory: NodePath4.join(cwd, ".claude", "skills"), scope: "project" }] : []
20082
20065
  ];
20083
20066
  const skills = /* @__PURE__ */ new Map();
20084
20067
  for (const root of roots) {
@@ -20086,7 +20069,7 @@ async function discoverClaudeSkills(configDirPath, options = {}) {
20086
20069
  for (const entry of entries.toSorted()) {
20087
20070
  const name = entry.trim();
20088
20071
  if (name.length === 0 || skills.has(name)) continue;
20089
- const skillPath = NodePath5.join(root.directory, entry, "SKILL.md");
20072
+ const skillPath = NodePath4.join(root.directory, entry, "SKILL.md");
20090
20073
  const contents = await NodeFSP.readFile(skillPath, "utf8").catch(() => void 0);
20091
20074
  if (contents === void 0) continue;
20092
20075
  const frontMatter = parseSkillFrontMatter(contents);
@@ -20106,115 +20089,690 @@ async function discoverClaudeSkills(configDirPath, options = {}) {
20106
20089
  return [...skills.values()].toSorted((left, right) => left.name.localeCompare(right.name));
20107
20090
  }
20108
20091
 
20109
- // src/config.ts
20110
- var CLAUDE_AUTO_COMPACT_WINDOW_PATTERN = /^(?:|[1-9]\d{5}|1000000)$/;
20111
- function isRecord2(value) {
20112
- return typeof value === "object" && value !== null && !Array.isArray(value);
20092
+ // src/model.ts
20093
+ import { matchCatalogModel } from "@ycodium-ai/plugin-api/executor";
20094
+ var DEFAULT_CLAUDE_MODEL_CAPABILITIES = {
20095
+ optionDescriptors: []
20096
+ };
20097
+ var CLAUDE_CUSTOM_MODEL_THINKING_SELECT = {
20098
+ id: "effort",
20099
+ label: "Reasoning"
20100
+ };
20101
+ function parseGenericCliVersion(output) {
20102
+ const match = output.match(/\bv?(\d+\.\d+\.\d+)\b/);
20103
+ return match?.[1] ?? null;
20113
20104
  }
20114
- function readString(value) {
20115
- return typeof value === "string" ? value : "";
20105
+ function normalizeCustomModelSlug(slug) {
20106
+ return slug.trim();
20116
20107
  }
20117
- function readTrimmed(value) {
20118
- return readString(value).trim();
20108
+ function knownClaudeModelCapabilities(model, catalog, customModels) {
20109
+ const slug = model?.trim();
20110
+ const fromCatalog = catalog.find((candidate) => candidate.slug === slug)?.capabilities;
20111
+ if (fromCatalog) return fromCatalog;
20112
+ const custom = customModels?.find(
20113
+ (candidate) => normalizeCustomModelSlug(candidate.slug) === slug
20114
+ );
20115
+ if (custom?.thinkingOptions !== void 0 && custom.thinkingOptions.length > 0) {
20116
+ const currentValue = custom.thinkingOptions.find((option) => option.isDefault)?.id;
20117
+ return {
20118
+ optionDescriptors: [
20119
+ {
20120
+ type: "select",
20121
+ id: CLAUDE_CUSTOM_MODEL_THINKING_SELECT.id,
20122
+ label: CLAUDE_CUSTOM_MODEL_THINKING_SELECT.label,
20123
+ options: custom.thinkingOptions,
20124
+ ...currentValue !== void 0 ? {} : {}
20125
+ }
20126
+ ]
20127
+ };
20128
+ }
20129
+ return void 0;
20119
20130
  }
20120
- function readEnvironment(value) {
20121
- if (!Array.isArray(value)) return {};
20122
- const env2 = {};
20123
- for (const entry of value) {
20131
+ function claudeModelCapabilities(model, catalog, customModels) {
20132
+ return knownClaudeModelCapabilities(model, catalog, customModels) ?? DEFAULT_CLAUDE_MODEL_CAPABILITIES;
20133
+ }
20134
+ function isRecord2(value) {
20135
+ return typeof value === "object" && value !== null && !Array.isArray(value);
20136
+ }
20137
+ function getModelSelectionStringOptionValue(modelSelection, optionId) {
20138
+ if (!isRecord2(modelSelection)) return void 0;
20139
+ const options = modelSelection["options"];
20140
+ if (!Array.isArray(options)) return void 0;
20141
+ for (const entry of options) {
20124
20142
  if (!isRecord2(entry)) continue;
20125
- const name = readTrimmed(entry["name"]);
20126
- if (name.length === 0) continue;
20127
- env2[name] = readString(entry["value"]);
20143
+ if (entry["id"] === optionId && typeof entry["value"] === "string") {
20144
+ const trimmed = entry["value"].trim();
20145
+ if (trimmed.length > 0) return trimmed;
20146
+ }
20128
20147
  }
20129
- return env2;
20148
+ return void 0;
20130
20149
  }
20131
- function readCustomModels(value) {
20132
- if (!Array.isArray(value)) return [];
20133
- const models = [];
20134
- for (const entry of value) {
20135
- if (typeof entry === "string") {
20136
- const slug2 = entry.trim();
20137
- if (slug2.length > 0) models.push({ slug: slug2 });
20138
- continue;
20139
- }
20150
+ function getModelSelectionBooleanOptionValue(modelSelection, optionId) {
20151
+ if (!isRecord2(modelSelection)) return void 0;
20152
+ const options = modelSelection["options"];
20153
+ if (!Array.isArray(options)) return void 0;
20154
+ for (const entry of options) {
20140
20155
  if (!isRecord2(entry)) continue;
20141
- const slug = readTrimmed(entry["slug"]);
20142
- if (slug.length === 0) continue;
20143
- const thinkingRaw = entry["thinkingOptions"];
20144
- const thinkingOptions = Array.isArray(thinkingRaw) ? thinkingRaw.flatMap((option) => {
20145
- if (!isRecord2(option)) return [];
20146
- const id2 = readTrimmed(option["id"]) || readTrimmed(option["value"]);
20147
- const label = readTrimmed(option["label"]) || id2;
20148
- if (id2.length === 0) return [];
20149
- return [
20150
- {
20151
- id: id2,
20152
- label,
20153
- ...typeof option["description"] === "string" ? { description: option["description"] } : {},
20154
- ...option["isDefault"] === true ? { isDefault: true } : {}
20155
- }
20156
- ];
20157
- }) : void 0;
20158
- const aliasesRaw = entry["aliases"];
20159
- const aliases = Array.isArray(aliasesRaw) ? aliasesRaw.filter(
20160
- (alias) => typeof alias === "string" && alias.trim().length > 0
20161
- ) : void 0;
20162
- const contextWindow = typeof entry["contextWindow"] === "number" && Number.isFinite(entry["contextWindow"]) && entry["contextWindow"] > 0 ? Math.round(entry["contextWindow"]) : void 0;
20163
- models.push({
20164
- slug,
20165
- ...readTrimmed(entry["label"]) ? { label: readTrimmed(entry["label"]) } : {},
20166
- ...thinkingOptions && thinkingOptions.length > 0 ? { thinkingOptions } : {},
20167
- ...contextWindow !== void 0 ? { contextWindow } : {},
20168
- ...aliases && aliases.length > 0 ? { aliases } : {}
20169
- });
20156
+ if (entry["id"] === optionId && typeof entry["value"] === "boolean") {
20157
+ return entry["value"];
20158
+ }
20170
20159
  }
20171
- return models;
20160
+ return void 0;
20172
20161
  }
20173
- function readAutoCompactWindow(value) {
20174
- const trimmed = readTrimmed(value);
20175
- return CLAUDE_AUTO_COMPACT_WINDOW_PATTERN.test(trimmed) ? trimmed : "";
20162
+ function modelIdFromSelection(selection) {
20163
+ if (!isRecord2(selection)) return void 0;
20164
+ const model = selection["model"];
20165
+ return typeof model === "string" ? model.trim() || void 0 : void 0;
20176
20166
  }
20177
- function parseClaudeInstanceConfig(config) {
20178
- if (!isRecord2(config)) {
20179
- return {
20180
- binaryPath: "",
20181
- homePath: "",
20182
- credentialsPath: "",
20183
- launchArgs: "",
20184
- autoCompactWindow: "",
20185
- customModels: [],
20186
- enabled: true,
20187
- environment: {},
20188
- envelopeDriver: void 0
20189
- };
20167
+ function currentSelectValue(descriptor, raw) {
20168
+ if (raw && descriptor.options.some((option) => option.id === raw)) return raw;
20169
+ return descriptor.options.find((option) => option.isDefault)?.id;
20170
+ }
20171
+ function resolveClaudeEffort(caps, raw) {
20172
+ const descriptor = caps.optionDescriptors.find((entry) => entry.id === "effort");
20173
+ if (!descriptor || descriptor.type !== "select") return void 0;
20174
+ return currentSelectValue(descriptor, raw);
20175
+ }
20176
+ function normalizeClaudeCliEffort(effort) {
20177
+ if (!effort || effort === "ultrathink") {
20178
+ return void 0;
20190
20179
  }
20191
- const nested = isRecord2(config["config"]) ? config["config"] : void 0;
20192
- const settings = nested ?? config;
20193
- return {
20194
- binaryPath: readTrimmed(settings["binaryPath"]),
20195
- homePath: readTrimmed(settings["homePath"]) || readTrimmed(config["homePath"]),
20196
- credentialsPath: readTrimmed(settings["credentialsPath"]) || readTrimmed(config["credentialsPath"]),
20197
- launchArgs: readString(settings["launchArgs"]),
20198
- autoCompactWindow: readAutoCompactWindow(settings["autoCompactWindow"]),
20199
- customModels: readCustomModels(settings["customModels"]),
20200
- enabled: settings["enabled"] !== false,
20201
- environment: readEnvironment(config["environment"]),
20202
- envelopeDriver: readTrimmed(config["driver"]) || void 0
20203
- };
20180
+ if (effort === "ultracode") {
20181
+ return "xhigh";
20182
+ }
20183
+ return effort;
20204
20184
  }
20205
- function tokenizeLaunchArgs(value) {
20206
- const input = value.trim();
20207
- if (!input) return [];
20208
- const tokens = [];
20209
- let current = "";
20210
- let quote;
20211
- let quoted = false;
20212
- for (let index = 0; index < input.length; index++) {
20213
- const char = input[index];
20214
- if (char === void 0) continue;
20215
- if (quote) {
20216
- if (char === quote) {
20217
- quote = void 0;
20185
+ function isClaudeUltracodeEffort(effort) {
20186
+ return effort === "ultracode";
20187
+ }
20188
+ function claudeCliModelId(input) {
20189
+ const requested = input.requestedModelId.trim();
20190
+ if (requested.length > 0 && (input.catalog ?? []).length === 0) return requested;
20191
+ const known = [
20192
+ ...(input.catalog ?? []).map((model) => model.slug),
20193
+ ...input.declared ?? [],
20194
+ ...(input.customModels ?? []).map((model) => normalizeCustomModelSlug(model.slug))
20195
+ ];
20196
+ return matchCatalogModel(input.requestedModelId, known) ?? input.placeholder;
20197
+ }
20198
+ function resolvePromptInjectedEffort(caps, rawEffort) {
20199
+ const trimmed = rawEffort?.trim();
20200
+ if (!trimmed) return void 0;
20201
+ const descriptor = caps.optionDescriptors.find((entry) => entry.id === "effort");
20202
+ if (!descriptor || descriptor.type !== "select") return void 0;
20203
+ if (!descriptor.promptInjectedValues?.includes(trimmed)) return void 0;
20204
+ return trimmed;
20205
+ }
20206
+ function applyClaudePromptEffortPrefix(text, effort) {
20207
+ const trimmed = text.trim();
20208
+ if (!trimmed) {
20209
+ return trimmed;
20210
+ }
20211
+ if (effort !== "ultrathink" || /^\/[^\s/]+(?:\s|$)/u.test(trimmed)) {
20212
+ return trimmed;
20213
+ }
20214
+ if (trimmed.startsWith("Ultrathink:")) {
20215
+ return trimmed;
20216
+ }
20217
+ return `Ultrathink:
20218
+ ${trimmed}`;
20219
+ }
20220
+ function getEffectiveClaudeAgentEffort(effort) {
20221
+ const normalized = normalizeClaudeCliEffort(effort);
20222
+ return normalized ? normalized : null;
20223
+ }
20224
+
20225
+ // src/probeModels.ts
20226
+ var toProbeCapabilities = (caps) => ({
20227
+ optionDescriptors: caps.optionDescriptors.map(
20228
+ (descriptor) => descriptor.type === "select" ? {
20229
+ id: descriptor.id,
20230
+ label: descriptor.label,
20231
+ type: "select",
20232
+ options: descriptor.options.map((option) => ({
20233
+ id: option.id,
20234
+ label: option.label,
20235
+ ...option.description === void 0 ? {} : { description: option.description },
20236
+ ...option.isDefault === void 0 ? {} : { isDefault: option.isDefault }
20237
+ }))
20238
+ } : {
20239
+ id: descriptor.id,
20240
+ label: descriptor.label,
20241
+ type: "boolean"
20242
+ }
20243
+ )
20244
+ });
20245
+ var claudeProbeModels = (input) => {
20246
+ const seen = /* @__PURE__ */ new Set();
20247
+ const out = [];
20248
+ for (const model of input.catalog) {
20249
+ if (seen.has(model.slug)) continue;
20250
+ seen.add(model.slug);
20251
+ const userLabel = input.customModels.find((entry) => entry.slug === model.slug)?.label;
20252
+ out.push({
20253
+ slug: model.slug,
20254
+ name: userLabel?.trim() || model.name,
20255
+ capabilities: toProbeCapabilities(model.capabilities)
20256
+ });
20257
+ }
20258
+ for (const model of input.customModels) {
20259
+ if (model.slug.trim().length === 0 || seen.has(model.slug)) continue;
20260
+ seen.add(model.slug);
20261
+ out.push({ slug: model.slug, name: model.label?.trim() || model.slug });
20262
+ }
20263
+ return out;
20264
+ };
20265
+
20266
+ // src/sessionSettings.ts
20267
+ function toSessionPermissionUpdates(toolName, suggestions) {
20268
+ const sessionScoped = (suggestions ?? []).map(
20269
+ (suggestion) => ({ ...suggestion, destination: "session" })
20270
+ );
20271
+ if (sessionScoped.length > 0) {
20272
+ return sessionScoped;
20273
+ }
20274
+ return [
20275
+ {
20276
+ type: "addRules",
20277
+ rules: [{ toolName }],
20278
+ behavior: "allow",
20279
+ destination: "session"
20280
+ }
20281
+ ];
20282
+ }
20283
+ var CLAUDE_SESSION_SETTING_KEYS = [
20284
+ "alwaysThinkingEnabled",
20285
+ "fastMode",
20286
+ "ultracode",
20287
+ "autoCompactWindow"
20288
+ ];
20289
+ function buildClaudeSessionSettings(input) {
20290
+ return {
20291
+ ...typeof input.thinking === "boolean" ? { alwaysThinkingEnabled: input.thinking } : {},
20292
+ ...input.fastMode ? { fastMode: true } : {},
20293
+ ...input.ultracode ? { ultracode: true } : {},
20294
+ ...input.autoCompactWindow ? { autoCompactWindow: Number(input.autoCompactWindow) } : {}
20295
+ };
20296
+ }
20297
+ function diffClaudeSessionSettings(previous, next) {
20298
+ const patch = {};
20299
+ let changed = false;
20300
+ const track = (key) => {
20301
+ if (previous[key] === next[key]) {
20302
+ return;
20303
+ }
20304
+ changed = true;
20305
+ patch[key] = next[key] ?? null;
20306
+ };
20307
+ for (const key of CLAUDE_SESSION_SETTING_KEYS) {
20308
+ track(key);
20309
+ }
20310
+ return changed ? patch : void 0;
20311
+ }
20312
+ var CLAUDE_NATIVE_MODES = [
20313
+ { id: "default", name: "Manual", description: "Always ask before making changes" },
20314
+ { id: "acceptEdits", name: "Accept edits", description: "Automatically accept all file edits" },
20315
+ { id: "plan", name: "Plan", description: "Create a plan before making changes" },
20316
+ { id: "auto", name: "Auto", description: "Claude handles permission decisions" },
20317
+ {
20318
+ id: "bypassPermissions",
20319
+ name: "Bypass permissions",
20320
+ description: "Accepts all permissions"
20321
+ }
20322
+ ];
20323
+ var CLAUDE_DEFAULT_NATIVE_MODE_ID = "default";
20324
+ var CLAUDE_NATIVE_MODE_BY_RUNTIME_MODE = {
20325
+ "approval-required": "default",
20326
+ "auto-accept-edits": "acceptEdits",
20327
+ auto: "auto",
20328
+ "full-access": "bypassPermissions"
20329
+ };
20330
+ var CLAUDE_NATIVE_MODE_REPORT = {
20331
+ nativeModes: CLAUDE_NATIVE_MODES.map((mode) => ({
20332
+ id: mode.id,
20333
+ name: mode.name,
20334
+ description: mode.description
20335
+ })),
20336
+ currentNativeModeId: CLAUDE_DEFAULT_NATIVE_MODE_ID,
20337
+ nativeModeByRuntimeMode: CLAUDE_NATIVE_MODE_BY_RUNTIME_MODE
20338
+ };
20339
+ function isClaudeNativeModeId(value) {
20340
+ return CLAUDE_NATIVE_MODES.some((mode) => mode.id === value);
20341
+ }
20342
+ function claudePermissionModeFor(runtimeMode, nativeModeId) {
20343
+ if (nativeModeId !== void 0 && isClaudeNativeModeId(nativeModeId)) return nativeModeId;
20344
+ return CLAUDE_NATIVE_MODE_BY_RUNTIME_MODE[runtimeMode];
20345
+ }
20346
+ var CLAUDE_SETTING_SOURCES = [
20347
+ "user",
20348
+ "project",
20349
+ "local"
20350
+ ];
20351
+
20352
+ // src/claudeProbe.ts
20353
+ var CLAUDE_PROGRAM_NOT_CONFIGURED = "Claude program path is not configured";
20354
+ function createClaudeProbe(input) {
20355
+ const { recipe, host, modelCatalog } = input;
20356
+ const collectProcessText = async (stream) => {
20357
+ const chunks = [];
20358
+ for await (const chunk of stream) {
20359
+ chunks.push(Buffer.from(chunk));
20360
+ }
20361
+ return Buffer.concat(chunks).toString("utf8");
20362
+ };
20363
+ const probeAuth = () => {
20364
+ const hasEnvToken = (recipe.environment.ANTHROPIC_AUTH_TOKEN ?? "").trim().length > 0;
20365
+ const hasEnvApiKey = (recipe.environment.ANTHROPIC_API_KEY ?? "").trim().length > 0;
20366
+ if (hasEnvToken || hasEnvApiKey) {
20367
+ return {
20368
+ status: "authenticated",
20369
+ type: hasEnvToken ? "env-token" : "api-key",
20370
+ label: hasEnvToken ? "ANTHROPIC_AUTH_TOKEN" : "ANTHROPIC_API_KEY"
20371
+ };
20372
+ }
20373
+ const account = readClaudeAccount(resolveClaudeCredentialsDirPath(recipe.credentialsPath));
20374
+ switch (account.state) {
20375
+ case "signed-in":
20376
+ case "expired":
20377
+ return { status: "authenticated" };
20378
+ case "signed-out":
20379
+ return { status: "unauthenticated" };
20380
+ case "unreadable":
20381
+ return { status: "unknown" };
20382
+ }
20383
+ };
20384
+ let lastProbedVersion;
20385
+ const probe = async (signal) => {
20386
+ if (recipe.binaryPath.length === 0) {
20387
+ return {
20388
+ installed: false,
20389
+ version: null,
20390
+ readiness: "not-installed",
20391
+ auth: { status: "unknown" },
20392
+ detail: CLAUDE_PROGRAM_NOT_CONFIGURED
20393
+ };
20394
+ }
20395
+ if (signal?.aborted) {
20396
+ throw new Error("aborted");
20397
+ }
20398
+ let handle;
20399
+ try {
20400
+ handle = await host.process.spawn({
20401
+ program: recipe.binaryPath,
20402
+ args: ["--version"],
20403
+ owner: { bindingId: host.bindingId }
20404
+ });
20405
+ } catch (error) {
20406
+ if (error instanceof ExecutorSpawnRefusedError) throw error;
20407
+ const failure = describeExecutorSpawnFailure(error);
20408
+ return {
20409
+ installed: false,
20410
+ version: null,
20411
+ readiness: failure.readiness,
20412
+ auth: { status: "unknown" },
20413
+ detail: failure.detail
20414
+ };
20415
+ }
20416
+ const [stdout, stderr, exitCode] = await Promise.all([
20417
+ collectProcessText(handle.stdout),
20418
+ collectProcessText(handle.stderr),
20419
+ handle.exited
20420
+ ]);
20421
+ const version = parseGenericCliVersion(`${stdout}
20422
+ ${stderr}`);
20423
+ if (version !== null || exitCode === 0) {
20424
+ await modelCatalog.refresh({ signal, force: version !== lastProbedVersion });
20425
+ lastProbedVersion = version;
20426
+ }
20427
+ const models = claudeProbeModels({
20428
+ catalog: modelCatalog.current(),
20429
+ customModels: recipe.customModels
20430
+ });
20431
+ const auth = probeAuth();
20432
+ const skills = await discoverClaudeSkills(
20433
+ resolveClaudeConfigDirPath(recipe.homePath, { ...process.env, ...recipe.environment })
20434
+ );
20435
+ if (exitCode !== 0 && version === null) {
20436
+ const detail = (stderr || stdout || "claude --version failed").trim();
20437
+ return {
20438
+ installed: false,
20439
+ version: null,
20440
+ readiness: "error",
20441
+ auth,
20442
+ ...detail.length > 0 ? { detail } : {}
20443
+ };
20444
+ }
20445
+ if (auth.status === "unauthenticated") {
20446
+ return {
20447
+ installed: true,
20448
+ version,
20449
+ readiness: "error",
20450
+ auth,
20451
+ models,
20452
+ skills,
20453
+ detail: "Claude is signed out",
20454
+ // 退出登录了档位照样是那五档。不报就退回宿主四档,而那份映射少了 `plan`。
20455
+ ...CLAUDE_NATIVE_MODE_REPORT
20456
+ };
20457
+ }
20458
+ return {
20459
+ installed: true,
20460
+ version,
20461
+ readiness: "ready",
20462
+ auth,
20463
+ models,
20464
+ skills,
20465
+ ...CLAUDE_NATIVE_MODE_REPORT
20466
+ };
20467
+ };
20468
+ return probe;
20469
+ }
20470
+
20471
+ // src/claudeExecutable.ts
20472
+ import * as NodeFS2 from "node:fs";
20473
+ import * as NodeOS4 from "node:os";
20474
+ import * as NodePath5 from "node:path";
20475
+ var WINDOWS_SHIM_EXTENSIONS = /* @__PURE__ */ new Set([".cmd", ".bat", ".ps1"]);
20476
+ var NPM_PACKAGE_ENTRY_CANDIDATES = [
20477
+ ["node_modules", "@anthropic-ai", "claude-code", "bin", "claude.exe"],
20478
+ ["node_modules", "@anthropic-ai", "claude-code", "cli.js"]
20479
+ ];
20480
+ function isExistingFile(filePath) {
20481
+ try {
20482
+ return NodeFS2.statSync(filePath).isFile();
20483
+ } catch {
20484
+ return false;
20485
+ }
20486
+ }
20487
+ function resolveClaudeSdkExecutablePath(binaryPath, environment = process.env, isFile = isExistingFile, isWindows = NodeOS4.type() === "Windows_NT") {
20488
+ if (!isWindows) {
20489
+ return binaryPath;
20490
+ }
20491
+ const resolved = resolveWindowsExecutable(binaryPath, environment) ?? binaryPath;
20492
+ const extension = NodePath5.win32.extname(resolved).toLowerCase();
20493
+ if (!WINDOWS_SHIM_EXTENSIONS.has(extension)) {
20494
+ return resolved;
20495
+ }
20496
+ const shimDirectory = NodePath5.win32.dirname(resolved);
20497
+ for (const entrySegments of NPM_PACKAGE_ENTRY_CANDIDATES) {
20498
+ const candidate = NodePath5.win32.join(shimDirectory, ...entrySegments);
20499
+ if (isFile(candidate)) {
20500
+ return candidate;
20501
+ }
20502
+ }
20503
+ return binaryPath;
20504
+ }
20505
+ function resolveWindowsExecutable(binaryPath, environment) {
20506
+ const trimmed = binaryPath.trim();
20507
+ if (trimmed.length === 0) return void 0;
20508
+ if (NodePath5.win32.isAbsolute(trimmed) && isExistingFile(trimmed)) return trimmed;
20509
+ const pathValue = environment.PATH ?? environment.Path ?? "";
20510
+ const pathext = environment.PATHEXT ?? ".EXE;.CMD;.BAT;.COM";
20511
+ const extensions = pathext.split(";").filter((ext) => ext.length > 0);
20512
+ const hasExt = NodePath5.win32.extname(trimmed).length > 0;
20513
+ for (const dir of pathValue.split(";")) {
20514
+ if (dir.trim().length === 0) continue;
20515
+ const candidates = hasExt ? [NodePath5.win32.join(dir, trimmed)] : extensions.map((ext) => NodePath5.win32.join(dir, `${trimmed}${ext}`));
20516
+ for (const candidate of candidates) {
20517
+ if (isExistingFile(candidate)) return candidate;
20518
+ }
20519
+ }
20520
+ return void 0;
20521
+ }
20522
+
20523
+ // src/claudeSignIn.ts
20524
+ var POSIX_BARE_VALUE = /^[A-Za-z0-9_@%+=:,./-]+$/;
20525
+ function quotePosixShellValue(value) {
20526
+ if (POSIX_BARE_VALUE.test(value)) return value;
20527
+ return `'${value.replaceAll("'", `'\\''`)}'`;
20528
+ }
20529
+ function quotePowerShellValue(value) {
20530
+ return `'${value.replaceAll("'", "''")}'`;
20531
+ }
20532
+ function withClaudeCredentialsDir(input) {
20533
+ const variable = CLAUDE_CREDENTIALS_DIR_ENVIRONMENT_VARIABLE;
20534
+ return input.platform === "win32" ? `$env:${variable}=${quotePowerShellValue(input.credentialsDirPath)}; ${input.command}` : `${variable}=${quotePosixShellValue(input.credentialsDirPath)} ${input.command}`;
20535
+ }
20536
+ function claudeSignInCommand(credentialsPath, platform2) {
20537
+ const command = "claude auth login";
20538
+ const credentialsDirPath = resolveClaudeCredentialsDirPath(credentialsPath);
20539
+ if (credentialsDirPath === null) return command;
20540
+ return withClaudeCredentialsDir({ command, credentialsDirPath, platform: platform2 });
20541
+ }
20542
+
20543
+ // src/claudeWorkspaceInventory.ts
20544
+ import * as NodeFSP2 from "node:fs/promises";
20545
+ import * as NodePath6 from "node:path";
20546
+ var MAX_COMMAND_DEPTH = 5;
20547
+ var MAX_COMMAND_FILES = 500;
20548
+ var MAX_DESCRIPTION_LENGTH = 200;
20549
+ var firstBodyLine = (contents) => {
20550
+ const lines = contents.split(/\r?\n/);
20551
+ let index = 0;
20552
+ if (lines[0]?.trim() === "---") {
20553
+ const close = lines.findIndex((line, position) => position > 0 && line.trim() === "---");
20554
+ index = close === -1 ? lines.length : close + 1;
20555
+ }
20556
+ for (; index < lines.length; index += 1) {
20557
+ const line = (lines[index] ?? "").trim().replace(/^#+\s*/, "");
20558
+ if (line.length > 0) return line.slice(0, MAX_DESCRIPTION_LENGTH);
20559
+ }
20560
+ return void 0;
20561
+ };
20562
+ var scanCommands = async (claudeDir) => {
20563
+ const root = NodePath6.join(claudeDir, "commands");
20564
+ const commands = /* @__PURE__ */ new Map();
20565
+ const watched = [root];
20566
+ let level = [root];
20567
+ let files = 0;
20568
+ for (let depth = 0; depth < MAX_COMMAND_DEPTH && level.length > 0; depth += 1) {
20569
+ const next = [];
20570
+ for (const directory of level) {
20571
+ const entries = await NodeFSP2.readdir(directory, { withFileTypes: true }).catch(() => []);
20572
+ for (const entry of entries.toSorted((left, right) => left.name.localeCompare(right.name))) {
20573
+ const entryPath = NodePath6.join(directory, entry.name);
20574
+ if (entry.isDirectory()) {
20575
+ next.push(entryPath);
20576
+ watched.push(entryPath);
20577
+ continue;
20578
+ }
20579
+ if (!entry.isFile() || !entry.name.toLowerCase().endsWith(".md")) continue;
20580
+ watched.push(entryPath);
20581
+ if (files >= MAX_COMMAND_FILES) continue;
20582
+ files += 1;
20583
+ const name = entry.name.slice(0, -".md".length).trim();
20584
+ if (name.length === 0 || commands.has(name)) continue;
20585
+ const contents = await NodeFSP2.readFile(entryPath, "utf8").catch(() => void 0);
20586
+ if (contents === void 0) continue;
20587
+ const frontMatter = parseSkillFrontMatter(contents);
20588
+ const description = frontMatter.description ?? firstBodyLine(contents);
20589
+ commands.set(name, {
20590
+ name,
20591
+ ...description === void 0 ? {} : { description },
20592
+ ...frontMatter.argumentHint === void 0 ? {} : { input: { hint: frontMatter.argumentHint } }
20593
+ });
20594
+ }
20595
+ }
20596
+ level = next;
20597
+ }
20598
+ return { commands: [...commands.values()], watched };
20599
+ };
20600
+ var watchSkillsRoot = async (claudeDir) => {
20601
+ const root = NodePath6.join(claudeDir, "skills");
20602
+ const entries = await NodeFSP2.readdir(root).catch(() => []);
20603
+ return [
20604
+ root,
20605
+ ...entries.flatMap((entry) => [
20606
+ NodePath6.join(root, entry),
20607
+ NodePath6.join(root, entry, "SKILL.md")
20608
+ ])
20609
+ ];
20610
+ };
20611
+ var mergeByName = (base, preferred) => {
20612
+ const merged = /* @__PURE__ */ new Map();
20613
+ for (const entry of base) merged.set(entry.name, entry);
20614
+ for (const entry of preferred) merged.set(entry.name, entry);
20615
+ return [...merged.values()].toSorted((left, right) => left.name.localeCompare(right.name));
20616
+ };
20617
+ var discoverClaudeWorkspaceInventory = async (input) => {
20618
+ const projectClaudeDir = NodePath6.join(input.cwd, ".claude");
20619
+ const [userCommands, projectCommands, skills, userSkillWatch, projectSkillWatch] = await Promise.all([
20620
+ scanCommands(input.configDirPath),
20621
+ scanCommands(projectClaudeDir),
20622
+ discoverClaudeSkills(input.configDirPath, {
20623
+ cwd: input.cwd,
20624
+ ...input.environment === void 0 ? {} : { environment: input.environment }
20625
+ }),
20626
+ watchSkillsRoot(input.configDirPath),
20627
+ watchSkillsRoot(projectClaudeDir)
20628
+ ]);
20629
+ return {
20630
+ slashCommands: mergeByName(userCommands.commands, projectCommands.commands),
20631
+ skills,
20632
+ // 项目目录本身也看:`.claude` 整个是后来才建的,它出现就该重查。
20633
+ watchPaths: [
20634
+ projectClaudeDir,
20635
+ ...userCommands.watched,
20636
+ ...projectCommands.watched,
20637
+ ...userSkillWatch,
20638
+ ...projectSkillWatch,
20639
+ // `skillOverrides` 在这几个设置文件里:开关一个技能也要重查。
20640
+ NodePath6.join(input.configDirPath, "settings.json"),
20641
+ NodePath6.join(projectClaudeDir, "settings.json"),
20642
+ NodePath6.join(projectClaudeDir, "settings.local.json")
20643
+ ]
20644
+ };
20645
+ };
20646
+ var asRecord = (value) => typeof value === "object" && value !== null ? value : void 0;
20647
+ var nonEmptyString = (value) => typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
20648
+ var claudeSlashCommandsFromNames = (names) => Array.isArray(names) ? names.flatMap((entry) => {
20649
+ const name = nonEmptyString(entry);
20650
+ return name === void 0 ? [] : [{ name }];
20651
+ }) : [];
20652
+ var claudeSlashCommandsFromSdk = (commands) => Array.isArray(commands) ? commands.flatMap((entry) => {
20653
+ const record = asRecord(entry);
20654
+ const name = nonEmptyString(record?.["name"]);
20655
+ if (name === void 0) return [];
20656
+ const description = nonEmptyString(record?.["description"]);
20657
+ const hint = nonEmptyString(record?.["argumentHint"]);
20658
+ return [
20659
+ {
20660
+ name,
20661
+ ...description === void 0 ? {} : { description },
20662
+ ...hint === void 0 ? {} : { input: { hint } }
20663
+ }
20664
+ ];
20665
+ }) : [];
20666
+
20667
+ // src/config.ts
20668
+ var CLAUDE_AUTO_COMPACT_WINDOW_PATTERN = /^(?:|[1-9]\d{5}|1000000)$/;
20669
+ function isRecord3(value) {
20670
+ return typeof value === "object" && value !== null && !Array.isArray(value);
20671
+ }
20672
+ function readString(value) {
20673
+ return typeof value === "string" ? value : "";
20674
+ }
20675
+ function readTrimmed(value) {
20676
+ return readString(value).trim();
20677
+ }
20678
+ function readEnvironment(value) {
20679
+ if (!Array.isArray(value)) return {};
20680
+ const env2 = {};
20681
+ for (const entry of value) {
20682
+ if (!isRecord3(entry)) continue;
20683
+ const name = readTrimmed(entry["name"]);
20684
+ if (name.length === 0) continue;
20685
+ env2[name] = readString(entry["value"]);
20686
+ }
20687
+ return env2;
20688
+ }
20689
+ function readCustomModels(value) {
20690
+ if (!Array.isArray(value)) return [];
20691
+ const models = [];
20692
+ for (const entry of value) {
20693
+ if (typeof entry === "string") {
20694
+ const slug2 = entry.trim();
20695
+ if (slug2.length > 0) models.push({ slug: slug2 });
20696
+ continue;
20697
+ }
20698
+ if (!isRecord3(entry)) continue;
20699
+ const slug = readTrimmed(entry["slug"]);
20700
+ if (slug.length === 0) continue;
20701
+ const thinkingRaw = entry["thinkingOptions"];
20702
+ const thinkingOptions = Array.isArray(thinkingRaw) ? thinkingRaw.flatMap((option) => {
20703
+ if (!isRecord3(option)) return [];
20704
+ const id2 = readTrimmed(option["id"]) || readTrimmed(option["value"]);
20705
+ const label = readTrimmed(option["label"]) || id2;
20706
+ if (id2.length === 0) return [];
20707
+ return [
20708
+ {
20709
+ id: id2,
20710
+ label,
20711
+ ...typeof option["description"] === "string" ? { description: option["description"] } : {},
20712
+ ...option["isDefault"] === true ? { isDefault: true } : {}
20713
+ }
20714
+ ];
20715
+ }) : void 0;
20716
+ const aliasesRaw = entry["aliases"];
20717
+ const aliases = Array.isArray(aliasesRaw) ? aliasesRaw.filter(
20718
+ (alias) => typeof alias === "string" && alias.trim().length > 0
20719
+ ) : void 0;
20720
+ const contextWindow = typeof entry["contextWindow"] === "number" && Number.isFinite(entry["contextWindow"]) && entry["contextWindow"] > 0 ? Math.round(entry["contextWindow"]) : void 0;
20721
+ models.push({
20722
+ slug,
20723
+ ...readTrimmed(entry["label"]) ? { label: readTrimmed(entry["label"]) } : {},
20724
+ ...thinkingOptions && thinkingOptions.length > 0 ? { thinkingOptions } : {},
20725
+ ...contextWindow !== void 0 ? { contextWindow } : {},
20726
+ ...aliases && aliases.length > 0 ? { aliases } : {}
20727
+ });
20728
+ }
20729
+ return models;
20730
+ }
20731
+ function readAutoCompactWindow(value) {
20732
+ const trimmed = readTrimmed(value);
20733
+ return CLAUDE_AUTO_COMPACT_WINDOW_PATTERN.test(trimmed) ? trimmed : "";
20734
+ }
20735
+ function parseClaudeInstanceConfig(config) {
20736
+ if (!isRecord3(config)) {
20737
+ return {
20738
+ binaryPath: "",
20739
+ homePath: "",
20740
+ credentialsPath: "",
20741
+ launchArgs: "",
20742
+ autoCompactWindow: "",
20743
+ customModels: [],
20744
+ enabled: true,
20745
+ environment: {},
20746
+ envelopeDriver: void 0
20747
+ };
20748
+ }
20749
+ const nested = isRecord3(config["config"]) ? config["config"] : void 0;
20750
+ const settings = nested ?? config;
20751
+ return {
20752
+ binaryPath: readTrimmed(settings["binaryPath"]),
20753
+ homePath: readTrimmed(settings["homePath"]) || readTrimmed(config["homePath"]),
20754
+ credentialsPath: readTrimmed(settings["credentialsPath"]) || readTrimmed(config["credentialsPath"]),
20755
+ launchArgs: readString(settings["launchArgs"]),
20756
+ autoCompactWindow: readAutoCompactWindow(settings["autoCompactWindow"]),
20757
+ customModels: readCustomModels(settings["customModels"]),
20758
+ enabled: settings["enabled"] !== false,
20759
+ environment: readEnvironment(config["environment"]),
20760
+ envelopeDriver: readTrimmed(config["driver"]) || void 0
20761
+ };
20762
+ }
20763
+ function tokenizeLaunchArgs(value) {
20764
+ const input = value.trim();
20765
+ if (!input) return [];
20766
+ const tokens = [];
20767
+ let current = "";
20768
+ let quote;
20769
+ let quoted = false;
20770
+ for (let index = 0; index < input.length; index++) {
20771
+ const char = input[index];
20772
+ if (char === void 0) continue;
20773
+ if (quote) {
20774
+ if (char === quote) {
20775
+ quote = void 0;
20218
20776
  quoted = true;
20219
20777
  } else if (char === "\\" && quote === '"') {
20220
20778
  const next = input[index + 1];
@@ -20275,183 +20833,50 @@ function parseCliArgs(args, options) {
20275
20833
  const next = tokens[i + 1];
20276
20834
  if (next !== void 0 && !next.startsWith("--")) {
20277
20835
  flags[rest] = next;
20278
- i++;
20279
- } else {
20280
- flags[rest] = null;
20281
- }
20282
- } else {
20283
- positionals.push(token);
20284
- }
20285
- }
20286
- return { flags, positionals };
20287
- }
20288
-
20289
- // src/events.ts
20290
- var EventBus = class {
20291
- #queued = [];
20292
- #waiters = [];
20293
- #closed = false;
20294
- push(event) {
20295
- const waiter = this.#waiters.shift();
20296
- if (waiter) {
20297
- waiter({ done: false, value: event });
20298
- return;
20299
- }
20300
- this.#queued.push(event);
20301
- }
20302
- close() {
20303
- if (this.#closed) return;
20304
- this.#closed = true;
20305
- while (this.#waiters.length > 0) {
20306
- this.#waiters.shift()?.({ done: true, value: void 0 });
20307
- }
20308
- }
20309
- [Symbol.asyncIterator]() {
20310
- return {
20311
- next: () => {
20312
- const queued = this.#queued.shift();
20313
- if (queued !== void 0) return Promise.resolve({ done: false, value: queued });
20314
- if (this.#closed) return Promise.resolve({ done: true, value: void 0 });
20315
- return new Promise((resolve4) => {
20316
- this.#waiters.push(resolve4);
20317
- });
20318
- }
20319
- };
20320
- }
20321
- };
20322
-
20323
- // src/model.ts
20324
- import { matchCatalogModel } from "@ycodium-ai/plugin-api/executor";
20325
- var DEFAULT_CLAUDE_MODEL_CAPABILITIES = {
20326
- optionDescriptors: []
20327
- };
20328
- var CLAUDE_CUSTOM_MODEL_THINKING_SELECT = {
20329
- id: "effort",
20330
- label: "Reasoning"
20331
- };
20332
- function parseGenericCliVersion(output) {
20333
- const match = output.match(/\bv?(\d+\.\d+\.\d+)\b/);
20334
- return match?.[1] ?? null;
20335
- }
20336
- function normalizeCustomModelSlug(slug) {
20337
- return slug.trim();
20338
- }
20339
- function knownClaudeModelCapabilities(model, catalog, customModels) {
20340
- const slug = model?.trim();
20341
- const fromCatalog = catalog.find((candidate) => candidate.slug === slug)?.capabilities;
20342
- if (fromCatalog) return fromCatalog;
20343
- const custom = customModels?.find(
20344
- (candidate) => normalizeCustomModelSlug(candidate.slug) === slug
20345
- );
20346
- if (custom?.thinkingOptions !== void 0 && custom.thinkingOptions.length > 0) {
20347
- const currentValue = custom.thinkingOptions.find((option) => option.isDefault)?.id;
20348
- return {
20349
- optionDescriptors: [
20350
- {
20351
- type: "select",
20352
- id: CLAUDE_CUSTOM_MODEL_THINKING_SELECT.id,
20353
- label: CLAUDE_CUSTOM_MODEL_THINKING_SELECT.label,
20354
- options: custom.thinkingOptions,
20355
- ...currentValue !== void 0 ? {} : {}
20356
- }
20357
- ]
20358
- };
20359
- }
20360
- return void 0;
20361
- }
20362
- function claudeModelCapabilities(model, catalog, customModels) {
20363
- return knownClaudeModelCapabilities(model, catalog, customModels) ?? DEFAULT_CLAUDE_MODEL_CAPABILITIES;
20364
- }
20365
- function isRecord3(value) {
20366
- return typeof value === "object" && value !== null && !Array.isArray(value);
20367
- }
20368
- function getModelSelectionStringOptionValue(modelSelection, optionId) {
20369
- if (!isRecord3(modelSelection)) return void 0;
20370
- const options = modelSelection["options"];
20371
- if (!Array.isArray(options)) return void 0;
20372
- for (const entry of options) {
20373
- if (!isRecord3(entry)) continue;
20374
- if (entry["id"] === optionId && typeof entry["value"] === "string") {
20375
- const trimmed = entry["value"].trim();
20376
- if (trimmed.length > 0) return trimmed;
20836
+ i++;
20837
+ } else {
20838
+ flags[rest] = null;
20839
+ }
20840
+ } else {
20841
+ positionals.push(token);
20377
20842
  }
20378
20843
  }
20379
- return void 0;
20844
+ return { flags, positionals };
20380
20845
  }
20381
- function getModelSelectionBooleanOptionValue(modelSelection, optionId) {
20382
- if (!isRecord3(modelSelection)) return void 0;
20383
- const options = modelSelection["options"];
20384
- if (!Array.isArray(options)) return void 0;
20385
- for (const entry of options) {
20386
- if (!isRecord3(entry)) continue;
20387
- if (entry["id"] === optionId && typeof entry["value"] === "boolean") {
20388
- return entry["value"];
20846
+
20847
+ // src/events.ts
20848
+ var EventBus = class {
20849
+ #queued = [];
20850
+ #waiters = [];
20851
+ #closed = false;
20852
+ push(event) {
20853
+ const waiter = this.#waiters.shift();
20854
+ if (waiter) {
20855
+ waiter({ done: false, value: event });
20856
+ return;
20389
20857
  }
20858
+ this.#queued.push(event);
20390
20859
  }
20391
- return void 0;
20392
- }
20393
- function modelIdFromSelection(selection) {
20394
- if (!isRecord3(selection)) return void 0;
20395
- const model = selection["model"];
20396
- return typeof model === "string" ? model.trim() || void 0 : void 0;
20397
- }
20398
- function currentSelectValue(descriptor, raw) {
20399
- if (raw && descriptor.options.some((option) => option.id === raw)) return raw;
20400
- return descriptor.options.find((option) => option.isDefault)?.id;
20401
- }
20402
- function resolveClaudeEffort(caps, raw) {
20403
- const descriptor = caps.optionDescriptors.find((entry) => entry.id === "effort");
20404
- if (!descriptor || descriptor.type !== "select") return void 0;
20405
- return currentSelectValue(descriptor, raw);
20406
- }
20407
- function normalizeClaudeCliEffort(effort) {
20408
- if (!effort || effort === "ultrathink") {
20409
- return void 0;
20410
- }
20411
- if (effort === "ultracode") {
20412
- return "xhigh";
20413
- }
20414
- return effort;
20415
- }
20416
- function isClaudeUltracodeEffort(effort) {
20417
- return effort === "ultracode";
20418
- }
20419
- function claudeCliModelId(input) {
20420
- const requested = input.requestedModelId.trim();
20421
- if (requested.length > 0 && (input.catalog ?? []).length === 0) return requested;
20422
- const known = [
20423
- ...(input.catalog ?? []).map((model) => model.slug),
20424
- ...input.declared ?? [],
20425
- ...(input.customModels ?? []).map((model) => normalizeCustomModelSlug(model.slug))
20426
- ];
20427
- return matchCatalogModel(input.requestedModelId, known) ?? input.placeholder;
20428
- }
20429
- function resolvePromptInjectedEffort(caps, rawEffort) {
20430
- const trimmed = rawEffort?.trim();
20431
- if (!trimmed) return void 0;
20432
- const descriptor = caps.optionDescriptors.find((entry) => entry.id === "effort");
20433
- if (!descriptor || descriptor.type !== "select") return void 0;
20434
- if (!descriptor.promptInjectedValues?.includes(trimmed)) return void 0;
20435
- return trimmed;
20436
- }
20437
- function applyClaudePromptEffortPrefix(text, effort) {
20438
- const trimmed = text.trim();
20439
- if (!trimmed) {
20440
- return trimmed;
20441
- }
20442
- if (effort !== "ultrathink" || /^\/[^\s/]+(?:\s|$)/u.test(trimmed)) {
20443
- return trimmed;
20860
+ close() {
20861
+ if (this.#closed) return;
20862
+ this.#closed = true;
20863
+ while (this.#waiters.length > 0) {
20864
+ this.#waiters.shift()?.({ done: true, value: void 0 });
20865
+ }
20444
20866
  }
20445
- if (trimmed.startsWith("Ultrathink:")) {
20446
- return trimmed;
20867
+ [Symbol.asyncIterator]() {
20868
+ return {
20869
+ next: () => {
20870
+ const queued = this.#queued.shift();
20871
+ if (queued !== void 0) return Promise.resolve({ done: false, value: queued });
20872
+ if (this.#closed) return Promise.resolve({ done: true, value: void 0 });
20873
+ return new Promise((resolve4) => {
20874
+ this.#waiters.push(resolve4);
20875
+ });
20876
+ }
20877
+ };
20447
20878
  }
20448
- return `Ultrathink:
20449
- ${trimmed}`;
20450
- }
20451
- function getEffectiveClaudeAgentEffort(effort) {
20452
- const normalized = normalizeClaudeCliEffort(effort);
20453
- return normalized ? normalized : null;
20454
- }
20879
+ };
20455
20880
 
20456
20881
  // src/sdkModels.ts
20457
20882
  import * as NodeOS5 from "node:os";
@@ -21121,7 +21546,7 @@ function terminalResultError(reason, failureHint) {
21121
21546
  case "turn_setup_failed":
21122
21547
  return "Claude could not start the turn.";
21123
21548
  case "blocking_limit":
21124
- return "Claude stopped: a usage limit blocked the request.";
21549
+ return failureHint ?? "Claude stopped: a usage limit blocked the request.";
21125
21550
  case "rapid_refill_breaker":
21126
21551
  return "Claude stopped: the context refilled too quickly after compaction.";
21127
21552
  case "prompt_too_long":
@@ -21156,10 +21581,8 @@ function resultOutcome(result, failureHint) {
21156
21581
  return { status: "completed", errorMessage };
21157
21582
  }
21158
21583
  if (isInterruptedResult(result)) return { status: "interrupted", errorMessage };
21159
- return {
21160
- status: resultErrorsText(result).includes("cancel") ? "cancelled" : "failed",
21161
- errorMessage
21162
- };
21584
+ const cancelled = resultErrorsText(result).includes("cancel");
21585
+ return cancelled ? { status: "cancelled", errorMessage } : { status: "failed", errorMessage: errorMessage ?? failureHint };
21163
21586
  }
21164
21587
  function streamKindFromDeltaType(deltaType) {
21165
21588
  return deltaType.includes("thinking") ? "reasoning_text" : "assistant_text";
@@ -21256,92 +21679,6 @@ function describeUnknownSdkMessage(kind, message) {
21256
21679
  return preview ? `${kind} \u2014 ${preview}` : `${kind} (no displayable text content)`;
21257
21680
  }
21258
21681
 
21259
- // src/sessionSettings.ts
21260
- function toSessionPermissionUpdates(toolName, suggestions) {
21261
- const sessionScoped = (suggestions ?? []).map(
21262
- (suggestion) => ({ ...suggestion, destination: "session" })
21263
- );
21264
- if (sessionScoped.length > 0) {
21265
- return sessionScoped;
21266
- }
21267
- return [
21268
- {
21269
- type: "addRules",
21270
- rules: [{ toolName }],
21271
- behavior: "allow",
21272
- destination: "session"
21273
- }
21274
- ];
21275
- }
21276
- var CLAUDE_SESSION_SETTING_KEYS = [
21277
- "alwaysThinkingEnabled",
21278
- "fastMode",
21279
- "ultracode",
21280
- "autoCompactWindow"
21281
- ];
21282
- function buildClaudeSessionSettings(input) {
21283
- return {
21284
- ...typeof input.thinking === "boolean" ? { alwaysThinkingEnabled: input.thinking } : {},
21285
- ...input.fastMode ? { fastMode: true } : {},
21286
- ...input.ultracode ? { ultracode: true } : {},
21287
- ...input.autoCompactWindow ? { autoCompactWindow: Number(input.autoCompactWindow) } : {}
21288
- };
21289
- }
21290
- function diffClaudeSessionSettings(previous, next) {
21291
- const patch = {};
21292
- let changed = false;
21293
- const track = (key) => {
21294
- if (previous[key] === next[key]) {
21295
- return;
21296
- }
21297
- changed = true;
21298
- patch[key] = next[key] ?? null;
21299
- };
21300
- for (const key of CLAUDE_SESSION_SETTING_KEYS) {
21301
- track(key);
21302
- }
21303
- return changed ? patch : void 0;
21304
- }
21305
- var CLAUDE_NATIVE_MODES = [
21306
- { id: "default", name: "Manual", description: "Always ask before making changes" },
21307
- { id: "acceptEdits", name: "Accept edits", description: "Automatically accept all file edits" },
21308
- { id: "plan", name: "Plan", description: "Create a plan before making changes" },
21309
- { id: "auto", name: "Auto", description: "Claude handles permission decisions" },
21310
- {
21311
- id: "bypassPermissions",
21312
- name: "Bypass permissions",
21313
- description: "Accepts all permissions"
21314
- }
21315
- ];
21316
- var CLAUDE_DEFAULT_NATIVE_MODE_ID = "default";
21317
- var CLAUDE_NATIVE_MODE_BY_RUNTIME_MODE = {
21318
- "approval-required": "default",
21319
- "auto-accept-edits": "acceptEdits",
21320
- auto: "auto",
21321
- "full-access": "bypassPermissions"
21322
- };
21323
- var CLAUDE_NATIVE_MODE_REPORT = {
21324
- nativeModes: CLAUDE_NATIVE_MODES.map((mode) => ({
21325
- id: mode.id,
21326
- name: mode.name,
21327
- description: mode.description
21328
- })),
21329
- currentNativeModeId: CLAUDE_DEFAULT_NATIVE_MODE_ID,
21330
- nativeModeByRuntimeMode: CLAUDE_NATIVE_MODE_BY_RUNTIME_MODE
21331
- };
21332
- function isClaudeNativeModeId(value) {
21333
- return CLAUDE_NATIVE_MODES.some((mode) => mode.id === value);
21334
- }
21335
- function claudePermissionModeFor(runtimeMode, nativeModeId) {
21336
- if (nativeModeId !== void 0 && isClaudeNativeModeId(nativeModeId)) return nativeModeId;
21337
- return CLAUDE_NATIVE_MODE_BY_RUNTIME_MODE[runtimeMode];
21338
- }
21339
- var CLAUDE_SETTING_SOURCES = [
21340
- "user",
21341
- "project",
21342
- "local"
21343
- ];
21344
-
21345
21682
  // src/readOnlyDirectories.ts
21346
21683
  var GLOB_METACHARACTERS = /[*?[\]{}]/;
21347
21684
  function toClaudeAbsolutePath(directory) {
@@ -21367,7 +21704,7 @@ function claudeReadAllowRules(directories) {
21367
21704
  // src/sessions.ts
21368
21705
  import * as NodeFS3 from "node:fs";
21369
21706
  import * as NodeOS6 from "node:os";
21370
- import * as NodePath6 from "node:path";
21707
+ import * as NodePath7 from "node:path";
21371
21708
  import * as NodeProcess2 from "node:process";
21372
21709
 
21373
21710
  // ../../packages/ycodium-jsonl-stream/src/jsonlStream.ts
@@ -21996,7 +22333,7 @@ function listTranscriptFiles(transcriptDir, directory) {
21996
22333
  const scanned = matched.length > 0 ? matched : dirs;
21997
22334
  const found = [];
21998
22335
  for (const dir of scanned) {
21999
- const projectDir = NodePath6.join(transcriptDir, dir);
22336
+ const projectDir = NodePath7.join(transcriptDir, dir);
22000
22337
  let names;
22001
22338
  try {
22002
22339
  names = NodeFS3.readdirSync(projectDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(TRANSCRIPT_SUFFIX)).map((entry) => entry.name);
@@ -22006,12 +22343,12 @@ function listTranscriptFiles(transcriptDir, directory) {
22006
22343
  for (const name of names) {
22007
22344
  let stat2;
22008
22345
  try {
22009
- stat2 = NodeFS3.statSync(NodePath6.join(projectDir, name));
22346
+ stat2 = NodeFS3.statSync(NodePath7.join(projectDir, name));
22010
22347
  } catch {
22011
22348
  continue;
22012
22349
  }
22013
22350
  found.push({
22014
- path: NodePath6.join(projectDir, name),
22351
+ path: NodePath7.join(projectDir, name),
22015
22352
  sessionId: name.slice(0, -TRANSCRIPT_SUFFIX.length),
22016
22353
  size: stat2.size,
22017
22354
  mtimeMs: Number.isFinite(stat2.mtimeMs) ? stat2.mtimeMs : 0
@@ -22022,7 +22359,7 @@ function listTranscriptFiles(transcriptDir, directory) {
22022
22359
  }
22023
22360
  function readClaudeLiveSessionIds(configDir) {
22024
22361
  const live = /* @__PURE__ */ new Set();
22025
- const markerDir = NodePath6.join(configDir, "sessions");
22362
+ const markerDir = NodePath7.join(configDir, "sessions");
22026
22363
  let entries;
22027
22364
  try {
22028
22365
  entries = NodeFS3.readdirSync(markerDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => entry.name);
@@ -22032,7 +22369,7 @@ function readClaudeLiveSessionIds(configDir) {
22032
22369
  for (const entry of entries) {
22033
22370
  let marker;
22034
22371
  try {
22035
- marker = JSON.parse(NodeFS3.readFileSync(NodePath6.join(markerDir, entry), "utf8"));
22372
+ marker = JSON.parse(NodeFS3.readFileSync(NodePath7.join(markerDir, entry), "utf8"));
22036
22373
  } catch {
22037
22374
  continue;
22038
22375
  }
@@ -22086,7 +22423,7 @@ async function scanClaudeSessions(input) {
22086
22423
  function resolveTranscriptPath(input) {
22087
22424
  const fileName = `${input.sessionId}${TRANSCRIPT_SUFFIX}`;
22088
22425
  if (input.cwd !== void 0 && input.cwd.length > 0) {
22089
- const direct = NodePath6.join(
22426
+ const direct = NodePath7.join(
22090
22427
  input.transcriptDir,
22091
22428
  encodeClaudeProjectDirName(input.cwd),
22092
22429
  fileName
@@ -22103,7 +22440,7 @@ function resolveTranscriptPath(input) {
22103
22440
  return null;
22104
22441
  }
22105
22442
  for (const dir of dirs) {
22106
- const candidate = NodePath6.join(input.transcriptDir, dir, fileName);
22443
+ const candidate = NodePath7.join(input.transcriptDir, dir, fileName);
22107
22444
  try {
22108
22445
  if (NodeFS3.statSync(candidate).isFile()) return candidate;
22109
22446
  } catch {
@@ -22557,74 +22894,33 @@ function normalizeClaudeTaskProgressTokenUsage(value, memory) {
22557
22894
  const lastUsedTokens = memory.lastKnownTokenUsage?.usedTokens;
22558
22895
  const activeTokens = lastUsedTokens !== void 0 ? Math.max(totalTokens, lastUsedTokens) : totalTokens;
22559
22896
  if (lastUsedTokens !== void 0 && activeTokens === lastUsedTokens) {
22560
- return void 0;
22561
- }
22562
- const usage = value;
22563
- const snapshot = makeClaudeTokenUsageSnapshot({
22564
- activeTokens,
22565
- ...memory.lastKnownContextWindow !== void 0 ? { contextWindow: memory.lastKnownContextWindow } : {},
22566
- totalProcessedTokens: Math.max(
22567
- totalTokens,
22568
- memory.lastKnownTotalProcessedTokens ?? totalTokens
22569
- )
22570
- });
22571
- if (!snapshot) {
22572
- return void 0;
22573
- }
22574
- const toolUses = finiteNonNegativeInteger(usage.tool_uses);
22575
- const durationMs = finiteNonNegativeInteger(usage.duration_ms);
22576
- return {
22577
- ...snapshot,
22578
- ...toolUses !== void 0 ? { toolUses } : {},
22579
- ...durationMs !== void 0 ? { durationMs } : {}
22580
- };
22581
- }
22582
-
22583
- // src/probeModels.ts
22584
- var toProbeCapabilities = (caps) => ({
22585
- optionDescriptors: caps.optionDescriptors.map(
22586
- (descriptor) => descriptor.type === "select" ? {
22587
- id: descriptor.id,
22588
- label: descriptor.label,
22589
- type: "select",
22590
- options: descriptor.options.map((option) => ({
22591
- id: option.id,
22592
- label: option.label,
22593
- ...option.description === void 0 ? {} : { description: option.description },
22594
- ...option.isDefault === void 0 ? {} : { isDefault: option.isDefault }
22595
- }))
22596
- } : {
22597
- id: descriptor.id,
22598
- label: descriptor.label,
22599
- type: "boolean"
22600
- }
22601
- )
22602
- });
22603
- var claudeProbeModels = (input) => {
22604
- const seen = /* @__PURE__ */ new Set();
22605
- const out = [];
22606
- for (const model of input.catalog) {
22607
- if (seen.has(model.slug)) continue;
22608
- seen.add(model.slug);
22609
- const userLabel = input.customModels.find((entry) => entry.slug === model.slug)?.label;
22610
- out.push({
22611
- slug: model.slug,
22612
- name: userLabel?.trim() || model.name,
22613
- capabilities: toProbeCapabilities(model.capabilities)
22614
- });
22615
- }
22616
- for (const model of input.customModels) {
22617
- if (model.slug.trim().length === 0 || seen.has(model.slug)) continue;
22618
- seen.add(model.slug);
22619
- out.push({ slug: model.slug, name: model.label?.trim() || model.slug });
22897
+ return void 0;
22620
22898
  }
22621
- return out;
22622
- };
22899
+ const usage = value;
22900
+ const snapshot = makeClaudeTokenUsageSnapshot({
22901
+ activeTokens,
22902
+ ...memory.lastKnownContextWindow !== void 0 ? { contextWindow: memory.lastKnownContextWindow } : {},
22903
+ totalProcessedTokens: Math.max(
22904
+ totalTokens,
22905
+ memory.lastKnownTotalProcessedTokens ?? totalTokens
22906
+ )
22907
+ });
22908
+ if (!snapshot) {
22909
+ return void 0;
22910
+ }
22911
+ const toolUses = finiteNonNegativeInteger(usage.tool_uses);
22912
+ const durationMs = finiteNonNegativeInteger(usage.duration_ms);
22913
+ return {
22914
+ ...snapshot,
22915
+ ...toolUses !== void 0 ? { toolUses } : {},
22916
+ ...durationMs !== void 0 ? { durationMs } : {}
22917
+ };
22918
+ }
22623
22919
 
22624
22920
  // src/textGeneration.ts
22625
- import * as NodeFSP2 from "node:fs/promises";
22921
+ import * as NodeFSP3 from "node:fs/promises";
22626
22922
  import * as NodeOS7 from "node:os";
22627
- import * as NodePath7 from "node:path";
22923
+ import * as NodePath8 from "node:path";
22628
22924
  import { mergeLaunchEnv } from "@ycodium-ai/plugin-api/executor";
22629
22925
 
22630
22926
  // src/textGenerationUtils.ts
@@ -23093,16 +23389,16 @@ function createClaudeTextGeneration(recipe, host, modelCatalog) {
23093
23389
  return decodeStructuredOutput(stdout, input.operation);
23094
23390
  };
23095
23391
  if (input.operation !== "generateThreadTitle") return runIn(input.cwd);
23096
- const scratchDirectory = await NodeFSP2.mkdtemp(
23097
- NodePath7.join(NodeOS7.tmpdir(), "ycodium-claude-title-")
23392
+ const scratchDirectory = await NodeFSP3.mkdtemp(
23393
+ NodePath8.join(NodeOS7.tmpdir(), "ycodium-claude-title-")
23098
23394
  );
23099
23395
  try {
23100
23396
  return await runIn(scratchDirectory);
23101
23397
  } finally {
23102
- await NodeFSP2.rm(scratchDirectory, { recursive: true, force: true }).catch(() => void 0);
23398
+ await NodeFSP3.rm(scratchDirectory, { recursive: true, force: true }).catch(() => void 0);
23103
23399
  }
23104
23400
  };
23105
- const asRecord = (value, operation) => {
23401
+ const asRecord2 = (value, operation) => {
23106
23402
  if (!isRecord5(value))
23107
23403
  throw new Error(`${operation}: Claude returned invalid structured output.`);
23108
23404
  return value;
@@ -23110,7 +23406,7 @@ function createClaudeTextGeneration(recipe, host, modelCatalog) {
23110
23406
  return {
23111
23407
  generateCommitMessage: async (raw, signal) => {
23112
23408
  try {
23113
- const input = asRecord(raw, "generateCommitMessage");
23409
+ const input = asRecord2(raw, "generateCommitMessage");
23114
23410
  const includeBranch = input["includeBranch"] === true;
23115
23411
  const { prompt, outputSchema } = buildCommitMessagePrompt({
23116
23412
  branch: typeof input["branch"] === "string" ? input["branch"] : null,
@@ -23119,7 +23415,7 @@ function createClaudeTextGeneration(recipe, host, modelCatalog) {
23119
23415
  includeBranch,
23120
23416
  ...isRecord5(input["policy"]) ? { policy: input["policy"] } : {}
23121
23417
  });
23122
- const generated = asRecord(
23418
+ const generated = asRecord2(
23123
23419
  await runClaudeJson({
23124
23420
  operation: "generateCommitMessage",
23125
23421
  cwd: readString3(input["cwd"]) || ".",
@@ -23138,115 +23434,776 @@ function createClaudeTextGeneration(recipe, host, modelCatalog) {
23138
23434
  } catch (error) {
23139
23435
  throw normalizeCliError("claude", error, "Failed to generate commit message");
23140
23436
  }
23141
- },
23142
- generatePrContent: async (raw, signal) => {
23143
- try {
23144
- const input = asRecord(raw, "generatePrContent");
23145
- const { prompt, outputSchema } = buildPrContentPrompt({
23146
- baseBranch: readString3(input["baseBranch"]),
23147
- headBranch: readString3(input["headBranch"]),
23148
- commitSummary: readString3(input["commitSummary"]),
23149
- diffSummary: readString3(input["diffSummary"]),
23150
- diffPatch: readString3(input["diffPatch"]),
23151
- ...typeof input["changeRequestTemplate"] === "string" ? { changeRequestTemplate: input["changeRequestTemplate"] } : {},
23152
- ...isRecord5(input["policy"]) ? { policy: input["policy"] } : {}
23153
- });
23154
- const generated = asRecord(
23155
- await runClaudeJson({
23156
- operation: "generatePrContent",
23157
- cwd: readString3(input["cwd"]) || ".",
23158
- prompt,
23159
- outputSchema,
23160
- modelSelection: input["modelSelection"],
23161
- ...signal === void 0 ? {} : { signal }
23162
- }),
23163
- "generatePrContent"
23164
- );
23165
- return {
23166
- title: sanitizePrTitle(readString3(generated["title"])),
23167
- body: readString3(generated["body"]).trim()
23168
- };
23169
- } catch (error) {
23170
- throw normalizeCliError("claude", error, "Failed to generate change request content");
23437
+ },
23438
+ generatePrContent: async (raw, signal) => {
23439
+ try {
23440
+ const input = asRecord2(raw, "generatePrContent");
23441
+ const { prompt, outputSchema } = buildPrContentPrompt({
23442
+ baseBranch: readString3(input["baseBranch"]),
23443
+ headBranch: readString3(input["headBranch"]),
23444
+ commitSummary: readString3(input["commitSummary"]),
23445
+ diffSummary: readString3(input["diffSummary"]),
23446
+ diffPatch: readString3(input["diffPatch"]),
23447
+ ...typeof input["changeRequestTemplate"] === "string" ? { changeRequestTemplate: input["changeRequestTemplate"] } : {},
23448
+ ...isRecord5(input["policy"]) ? { policy: input["policy"] } : {}
23449
+ });
23450
+ const generated = asRecord2(
23451
+ await runClaudeJson({
23452
+ operation: "generatePrContent",
23453
+ cwd: readString3(input["cwd"]) || ".",
23454
+ prompt,
23455
+ outputSchema,
23456
+ modelSelection: input["modelSelection"],
23457
+ ...signal === void 0 ? {} : { signal }
23458
+ }),
23459
+ "generatePrContent"
23460
+ );
23461
+ return {
23462
+ title: sanitizePrTitle(readString3(generated["title"])),
23463
+ body: readString3(generated["body"]).trim()
23464
+ };
23465
+ } catch (error) {
23466
+ throw normalizeCliError("claude", error, "Failed to generate change request content");
23467
+ }
23468
+ },
23469
+ generateBranchName: async (raw, signal) => {
23470
+ try {
23471
+ const input = asRecord2(raw, "generateBranchName");
23472
+ const { prompt, outputSchema } = buildBranchNamePrompt({
23473
+ message: readString3(input["message"]),
23474
+ ...Array.isArray(input["attachments"]) ? { attachments: input["attachments"] } : {},
23475
+ ...isRecord5(input["policy"]) ? { policy: input["policy"] } : {}
23476
+ });
23477
+ const generated = asRecord2(
23478
+ await runClaudeJson({
23479
+ operation: "generateBranchName",
23480
+ cwd: readString3(input["cwd"]) || ".",
23481
+ prompt,
23482
+ outputSchema,
23483
+ modelSelection: input["modelSelection"],
23484
+ ...signal === void 0 ? {} : { signal }
23485
+ }),
23486
+ "generateBranchName"
23487
+ );
23488
+ return { branch: sanitizeBranchFragment(readString3(generated["branch"])) };
23489
+ } catch (error) {
23490
+ throw normalizeCliError("claude", error, "Failed to generate branch name");
23491
+ }
23492
+ },
23493
+ generateThreadTitle: async (raw, signal) => {
23494
+ try {
23495
+ const input = asRecord2(raw, "generateThreadTitle");
23496
+ const { prompt, outputSchema } = buildThreadTitlePrompt({
23497
+ message: readString3(input["message"]),
23498
+ ...typeof input["linkedContext"] === "string" && input["linkedContext"].length > 0 ? { linkedContext: input["linkedContext"] } : {},
23499
+ ...typeof input["previousTitle"] === "string" ? { previousTitle: input["previousTitle"] } : {},
23500
+ ...Array.isArray(input["attachments"]) ? { attachments: input["attachments"] } : {},
23501
+ ...isRecord5(input["policy"]) ? { policy: input["policy"] } : {}
23502
+ });
23503
+ const generated = asRecord2(
23504
+ await runClaudeJson({
23505
+ operation: "generateThreadTitle",
23506
+ cwd: readString3(input["cwd"]) || ".",
23507
+ prompt,
23508
+ outputSchema,
23509
+ modelSelection: input["modelSelection"],
23510
+ ...signal === void 0 ? {} : { signal }
23511
+ }),
23512
+ "generateThreadTitle"
23513
+ );
23514
+ return {
23515
+ title: sanitizeThreadTitle(readString3(generated["title"])),
23516
+ ...generated["needsRefinement"] === true ? { needsRefinement: true } : {}
23517
+ };
23518
+ } catch (error) {
23519
+ throw normalizeCliError("claude", error, "Failed to generate thread title");
23520
+ }
23521
+ },
23522
+ summarizeHandover: async (raw, signal) => {
23523
+ try {
23524
+ const input = asRecord2(raw, "summarizeHandover");
23525
+ const { prompt, outputSchema } = buildHandoverSummaryPrompt({
23526
+ transcript: readString3(input["transcript"])
23527
+ });
23528
+ const generated = asRecord2(
23529
+ await runClaudeJson({
23530
+ operation: "summarizeHandover",
23531
+ cwd: readString3(input["cwd"]) || ".",
23532
+ prompt,
23533
+ outputSchema,
23534
+ modelSelection: input["modelSelection"],
23535
+ ...signal === void 0 ? {} : { signal }
23536
+ }),
23537
+ "summarizeHandover"
23538
+ );
23539
+ return { summary: readString3(generated["summary"]).trim() };
23540
+ } catch (error) {
23541
+ throw normalizeCliError("claude", error, "Failed to summarize handover");
23542
+ }
23543
+ }
23544
+ };
23545
+ }
23546
+
23547
+ // src/usageRead/index.ts
23548
+ import * as NodePath12 from "node:path";
23549
+
23550
+ // src/usageRead/quota.ts
23551
+ import * as NodeFS4 from "node:fs";
23552
+ import * as NodePath9 from "node:path";
23553
+ import { mergeLaunchEnv as mergeLaunchEnv2, setsOwnModelConnection } from "@ycodium-ai/plugin-api/executor";
23554
+ import {
23555
+ parseRetryAfterMs
23556
+ } from "@ycodium-ai/plugin-api/usage";
23557
+ var CLAUDE_QUOTA_URL = "https://api.anthropic.com/api/oauth/usage";
23558
+ var REQUEST_TIMEOUT_MS = 1e4;
23559
+ var HOUR_MS = 60 * 60 * 1e3;
23560
+ var WINDOWS = [
23561
+ { field: "five_hour", id: "session", labelKey: "usage.window.session", windowMs: 5 * HOUR_MS },
23562
+ { field: "seven_day", id: "weekly", labelKey: "usage.window.weekly", windowMs: 7 * 24 * HOUR_MS }
23563
+ ];
23564
+ var isRecord6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
23565
+ var launchEnvironment = (recipe) => mergeLaunchEnv2({
23566
+ inherited: process.env,
23567
+ instanceTable: recipe.environment,
23568
+ handOverrides: claudeHomeVariables(resolveClaudeHomeLayout(recipe))
23569
+ });
23570
+ var claudeCredentialFileCandidates = (recipe) => {
23571
+ const environment = launchEnvironment(recipe);
23572
+ const credentialsDir = resolveClaudeCredentialsDirPath(
23573
+ environment[CLAUDE_CREDENTIALS_DIR_ENVIRONMENT_VARIABLE] ?? ""
23574
+ );
23575
+ if (credentialsDir !== null) return [NodePath9.join(credentialsDir, ".credentials.json")];
23576
+ const configDir = resolveClaudeConfigDirPath(recipe.homePath, environment);
23577
+ const home = resolveClaudeHomePath(recipe.homePath);
23578
+ return [
23579
+ .../* @__PURE__ */ new Set([
23580
+ NodePath9.join(configDir, ".credentials.json"),
23581
+ NodePath9.join(home, ".claude", ".credentials.json"),
23582
+ NodePath9.join(home, ".credentials.json")
23583
+ ])
23584
+ ];
23585
+ };
23586
+ var readString4 = (value) => typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
23587
+ var readLoginCredential = (candidates) => {
23588
+ for (const path of candidates) {
23589
+ let raw;
23590
+ try {
23591
+ raw = NodeFS4.readFileSync(path, "utf8");
23592
+ } catch {
23593
+ continue;
23594
+ }
23595
+ if (raw.trim().length === 0) continue;
23596
+ let parsed;
23597
+ try {
23598
+ parsed = JSON.parse(raw);
23599
+ } catch {
23600
+ continue;
23601
+ }
23602
+ const oauth = isRecord6(parsed) ? parsed["claudeAiOauth"] : void 0;
23603
+ if (!isRecord6(oauth)) continue;
23604
+ const accessToken = readString4(oauth["accessToken"]);
23605
+ if (accessToken === void 0) continue;
23606
+ return {
23607
+ accessToken,
23608
+ subscriptionType: readString4(oauth["subscriptionType"]),
23609
+ rateLimitTier: readString4(oauth["rateLimitTier"]),
23610
+ path
23611
+ };
23612
+ }
23613
+ return null;
23614
+ };
23615
+ var claudePlanName = (credential) => {
23616
+ const type4 = credential.subscriptionType;
23617
+ if (type4 === void 0) return void 0;
23618
+ const name = `${type4.charAt(0).toUpperCase()}${type4.slice(1)}`;
23619
+ const multiplier = /(?:^|_)(\d+x)$/.exec(credential.rateLimitTier ?? "")?.[1];
23620
+ return multiplier === void 0 ? name : `${name} ${multiplier}`;
23621
+ };
23622
+ var toIso = (value) => {
23623
+ if (typeof value !== "string") return null;
23624
+ const parsed = Date.parse(value);
23625
+ return Number.isNaN(parsed) ? null : new Date(parsed).toISOString();
23626
+ };
23627
+ var readWindows = (body) => WINDOWS.flatMap((spec) => {
23628
+ const row = body[spec.field];
23629
+ if (!isRecord6(row)) return [];
23630
+ const utilization = row["utilization"];
23631
+ if (typeof utilization !== "number" || !Number.isFinite(utilization)) return [];
23632
+ return [
23633
+ {
23634
+ id: spec.id,
23635
+ label: { key: spec.labelKey },
23636
+ usedPercent: Math.min(100, Math.max(0, utilization)),
23637
+ resetsAt: toIso(row["resets_at"]),
23638
+ windowMs: spec.windowMs
23639
+ }
23640
+ ];
23641
+ });
23642
+ var readClaudeQuota = async (request) => {
23643
+ const { recipe } = request;
23644
+ const connectivity = CLAUDE_CARD.modelConnectivity;
23645
+ const environmentRows = Object.entries(recipe.environment).map(([name, value]) => ({
23646
+ name,
23647
+ value
23648
+ }));
23649
+ if (connectivity !== void 0 && setsOwnModelConnection(connectivity, environmentRows)) {
23650
+ const changesEndpoint = setsOwnModelConnection(
23651
+ {
23652
+ baseUrlEnvKey: connectivity.baseUrlEnvKey,
23653
+ backendSwitchEnvKeys: connectivity.backendSwitchEnvKeys
23654
+ },
23655
+ environmentRows
23656
+ );
23657
+ return {
23658
+ status: "unavailable",
23659
+ reason: changesEndpoint ? "customEndpoint" : "unsupportedLogin"
23660
+ };
23661
+ }
23662
+ const credential = readLoginCredential(claudeCredentialFileCandidates(recipe));
23663
+ if (credential === null) return { status: "unavailable", reason: "missingCredentials" };
23664
+ const now = request.now ?? Date.now;
23665
+ let response;
23666
+ try {
23667
+ response = await request.fetch(CLAUDE_QUOTA_URL, {
23668
+ method: "GET",
23669
+ headers: {
23670
+ Accept: "application/json",
23671
+ "anthropic-beta": "oauth-2025-04-20",
23672
+ Authorization: `Bearer ${credential.accessToken}`
23673
+ },
23674
+ redirect: "error",
23675
+ signal: request.signal === void 0 ? AbortSignal.timeout(REQUEST_TIMEOUT_MS) : AbortSignal.any([request.signal, AbortSignal.timeout(REQUEST_TIMEOUT_MS)])
23676
+ });
23677
+ } catch (cause) {
23678
+ if (request.signal?.aborted === true) throw cause;
23679
+ return { status: "unavailable", reason: "unreachable" };
23680
+ }
23681
+ const release = () => {
23682
+ void response.body?.cancel().catch(() => void 0);
23683
+ };
23684
+ if (response.status === 429) {
23685
+ const retryAfterMs = parseRetryAfterMs(response.headers.get("Retry-After"), now());
23686
+ release();
23687
+ return {
23688
+ status: "unavailable",
23689
+ reason: "rateLimited",
23690
+ ...retryAfterMs === null ? {} : { retryAfterMs }
23691
+ };
23692
+ }
23693
+ if (response.status === 401 || response.status === 403) {
23694
+ release();
23695
+ return { status: "unavailable", reason: "authRejected" };
23696
+ }
23697
+ if (response.status < 200 || response.status >= 300) {
23698
+ release();
23699
+ return { status: "unavailable", reason: "unreachable" };
23700
+ }
23701
+ let body;
23702
+ try {
23703
+ body = JSON.parse(await response.text());
23704
+ } catch {
23705
+ return { status: "unavailable", reason: "unreachable" };
23706
+ }
23707
+ if (!isRecord6(body)) return { status: "unavailable", reason: "unreachable" };
23708
+ const plan = claudePlanName(credential);
23709
+ return {
23710
+ status: "available",
23711
+ ...plan === void 0 ? {} : { plan },
23712
+ windows: readWindows(body),
23713
+ balances: [],
23714
+ accountKey: credential.path
23715
+ };
23716
+ };
23717
+
23718
+ // src/usageRead/scanCache.ts
23719
+ import * as NodePath11 from "node:path";
23720
+
23721
+ // src/usageRead/transcriptScan.ts
23722
+ import * as NodeFS5 from "node:fs";
23723
+ import * as NodePath10 from "node:path";
23724
+
23725
+ // src/usageRead/transcriptLine.ts
23726
+ var int = (value) => typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.trunc(value) : 0;
23727
+ var parseTimestampMs = (value) => {
23728
+ if (typeof value !== "string") return null;
23729
+ const parsed = Date.parse(value);
23730
+ return Number.isNaN(parsed) ? null : parsed;
23731
+ };
23732
+ var mightCarryUsage = (line) => line.includes('"usage"');
23733
+ var parseClaudeLine = (line) => {
23734
+ let parsed;
23735
+ try {
23736
+ parsed = JSON.parse(line);
23737
+ } catch {
23738
+ return null;
23739
+ }
23740
+ if (typeof parsed !== "object" || parsed === null) return null;
23741
+ const record = parsed;
23742
+ if (record["type"] !== "assistant") return null;
23743
+ const message = record["message"];
23744
+ if (typeof message !== "object" || message === null) return null;
23745
+ const messageRecord = message;
23746
+ const usage = messageRecord["usage"];
23747
+ if (typeof usage !== "object" || usage === null) return null;
23748
+ const usageRecord = usage;
23749
+ const timestampMs = parseTimestampMs(record["timestamp"]);
23750
+ if (timestampMs === null) return null;
23751
+ const model = typeof messageRecord["model"] === "string" ? messageRecord["model"] : "";
23752
+ if (model.length === 0) return null;
23753
+ const messageId = typeof messageRecord["id"] === "string" ? messageRecord["id"] : null;
23754
+ const requestId = typeof record["requestId"] === "string" ? record["requestId"] : null;
23755
+ const dedupeKey = messageId === null && requestId === null ? null : `${messageId ?? ""}:${requestId ?? ""}`;
23756
+ const cost = record["costUSD"];
23757
+ return {
23758
+ timestampMs,
23759
+ model,
23760
+ sessionId: typeof record["sessionId"] === "string" ? record["sessionId"] : "",
23761
+ totals: {
23762
+ uncachedInputTokens: int(usageRecord["input_tokens"]),
23763
+ cachedInputTokens: int(usageRecord["cache_read_input_tokens"]),
23764
+ cacheCreationTokens: int(usageRecord["cache_creation_input_tokens"]),
23765
+ outputTokens: int(usageRecord["output_tokens"]),
23766
+ reasoningTokens: 0
23767
+ },
23768
+ reportedCostUsd: typeof cost === "number" && Number.isFinite(cost) ? cost : null,
23769
+ dedupeKey
23770
+ };
23771
+ };
23772
+
23773
+ // src/usageRead/transcriptScan.ts
23774
+ var TRANSCRIPT_SUFFIX2 = ".jsonl";
23775
+ async function listTranscriptFiles2(root, sinceMs) {
23776
+ const found = [];
23777
+ const walk = async (dir) => {
23778
+ let entries;
23779
+ try {
23780
+ entries = await NodeFS5.promises.readdir(dir, { withFileTypes: true });
23781
+ } catch {
23782
+ return;
23783
+ }
23784
+ for (const entry of entries) {
23785
+ const child = NodePath10.join(dir, entry.name);
23786
+ if (entry.isDirectory()) {
23787
+ await walk(child);
23788
+ continue;
23789
+ }
23790
+ if (!entry.isFile() || !entry.name.endsWith(TRANSCRIPT_SUFFIX2)) continue;
23791
+ let stat2;
23792
+ try {
23793
+ stat2 = await NodeFS5.promises.stat(child);
23794
+ } catch {
23795
+ continue;
23796
+ }
23797
+ if (stat2.mtimeMs < sinceMs) continue;
23798
+ found.push({ path: child, size: stat2.size, mtimeMs: stat2.mtimeMs });
23799
+ }
23800
+ };
23801
+ await walk(root);
23802
+ return found;
23803
+ }
23804
+ async function statTranscriptRoot(root) {
23805
+ try {
23806
+ const stat2 = await NodeFS5.promises.stat(root);
23807
+ if (!stat2.isDirectory()) return null;
23808
+ return { volumeId: stat2.ino === 0 ? "" : `${String(stat2.dev)}:${String(stat2.ino)}` };
23809
+ } catch {
23810
+ return null;
23811
+ }
23812
+ }
23813
+ var GUARD_LENGTH = 64;
23814
+ var NEWLINE = 10;
23815
+ var CARRIAGE_RETURN = 13;
23816
+ var fnv1a = (bytes) => {
23817
+ let hash = 2166136261;
23818
+ for (let index = 0; index < bytes.length; index += 1) {
23819
+ hash ^= bytes[index];
23820
+ hash = Math.imul(hash, 16777619);
23821
+ }
23822
+ return hash >>> 0;
23823
+ };
23824
+ var asBuffer = (chunk) => typeof chunk === "string" ? Buffer.from(chunk) : Buffer.from(chunk.buffer, chunk.byteOffset, chunk.byteLength);
23825
+ async function readMatchingGuard(filePath, position) {
23826
+ const { guardLength, resumeOffset } = position;
23827
+ if (guardLength <= 0 || guardLength > GUARD_LENGTH || guardLength > resumeOffset) return null;
23828
+ try {
23829
+ const chunks = [];
23830
+ for await (const chunk of NodeFS5.createReadStream(filePath, {
23831
+ start: resumeOffset - guardLength,
23832
+ end: resumeOffset - 1
23833
+ })) {
23834
+ chunks.push(asBuffer(chunk));
23835
+ }
23836
+ const window2 = Buffer.concat(chunks);
23837
+ return window2.length === guardLength && fnv1a(window2) === position.guardHash ? window2 : null;
23838
+ } catch {
23839
+ return null;
23840
+ }
23841
+ }
23842
+ async function readTranscriptRecords(filePath, resumeFrom) {
23843
+ try {
23844
+ let resumed = false;
23845
+ let start = 0;
23846
+ let guardWindow = Buffer.alloc(0);
23847
+ if (resumeFrom !== void 0 && resumeFrom.resumeOffset > 0) {
23848
+ const window2 = await readMatchingGuard(filePath, resumeFrom);
23849
+ if (window2 !== null) {
23850
+ start = resumeFrom.resumeOffset;
23851
+ guardWindow = window2;
23852
+ resumed = true;
23853
+ }
23854
+ }
23855
+ const parseLine = (line, out) => {
23856
+ if (!mightCarryUsage(line)) return;
23857
+ const record = parseClaudeLine(line);
23858
+ if (record !== null) out.push(record);
23859
+ };
23860
+ const toLineString = (lineBuffer) => {
23861
+ const content = lineBuffer.length > 0 && lineBuffer[lineBuffer.length - 1] === CARRIAGE_RETURN ? lineBuffer.subarray(0, -1) : lineBuffer;
23862
+ return content.toString("utf8");
23863
+ };
23864
+ const records = [];
23865
+ let resumeOffset = start;
23866
+ let pendingChunks = [];
23867
+ for await (const raw of NodeFS5.createReadStream(filePath, start > 0 ? { start } : void 0)) {
23868
+ const chunk = asBuffer(raw);
23869
+ if (!chunk.includes(NEWLINE)) {
23870
+ pendingChunks.push(chunk);
23871
+ continue;
23872
+ }
23873
+ const buffer = pendingChunks.length === 0 ? chunk : Buffer.concat([...pendingChunks, chunk]);
23874
+ pendingChunks = [];
23875
+ let lineStart = 0;
23876
+ for (; ; ) {
23877
+ const newlineIndex = buffer.indexOf(NEWLINE, lineStart);
23878
+ if (newlineIndex === -1) break;
23879
+ parseLine(toLineString(buffer.subarray(lineStart, newlineIndex)), records);
23880
+ lineStart = newlineIndex + 1;
23881
+ }
23882
+ resumeOffset += lineStart;
23883
+ const consumed = buffer.subarray(0, lineStart);
23884
+ guardWindow = consumed.length >= GUARD_LENGTH ? Buffer.from(consumed.subarray(-GUARD_LENGTH)) : Buffer.concat([guardWindow, consumed]).subarray(-GUARD_LENGTH);
23885
+ if (lineStart < buffer.length) pendingChunks.push(buffer.subarray(lineStart));
23886
+ }
23887
+ const tailRecords = [];
23888
+ if (pendingChunks.length > 0) {
23889
+ const pending = pendingChunks.length === 1 ? pendingChunks[0] : Buffer.concat(pendingChunks);
23890
+ if (pending.length > 0) parseLine(toLineString(pending), tailRecords);
23891
+ }
23892
+ return {
23893
+ records,
23894
+ tailRecords,
23895
+ position: {
23896
+ resumeOffset,
23897
+ guardLength: guardWindow.length,
23898
+ guardHash: guardWindow.length > 0 ? fnv1a(guardWindow) : 0
23899
+ },
23900
+ resumed
23901
+ };
23902
+ } catch {
23903
+ return null;
23904
+ }
23905
+ }
23906
+
23907
+ // src/usageRead/scanCache.ts
23908
+ var USAGE_SCAN_CACHE_VERSION = 1;
23909
+ function encodeScanCache(cache) {
23910
+ const models = [];
23911
+ const sessions = [];
23912
+ const modelIndex = /* @__PURE__ */ new Map();
23913
+ const sessionIndex = /* @__PURE__ */ new Map();
23914
+ const intern = (table, index, value) => {
23915
+ const existing = index.get(value);
23916
+ if (existing !== void 0) return existing;
23917
+ const next = table.length;
23918
+ table.push(value);
23919
+ index.set(value, next);
23920
+ return next;
23921
+ };
23922
+ const serializeRecord = (record) => [
23923
+ record.timestampMs,
23924
+ intern(models, modelIndex, record.model),
23925
+ intern(sessions, sessionIndex, record.sessionId),
23926
+ record.totals.uncachedInputTokens,
23927
+ record.totals.cachedInputTokens,
23928
+ record.totals.cacheCreationTokens,
23929
+ record.totals.outputTokens,
23930
+ record.totals.reasoningTokens,
23931
+ record.dedupeKey,
23932
+ record.reportedCostUsd
23933
+ ];
23934
+ const files = {};
23935
+ for (const [path, entry] of cache) {
23936
+ files[path] = {
23937
+ s: entry.size,
23938
+ m: entry.mtimeMs,
23939
+ r: entry.records.map(serializeRecord),
23940
+ t: entry.tailRecords.map(serializeRecord),
23941
+ o: entry.position.resumeOffset,
23942
+ gl: entry.position.guardLength,
23943
+ gh: entry.position.guardHash
23944
+ };
23945
+ }
23946
+ return { version: USAGE_SCAN_CACHE_VERSION, models, sessions, files };
23947
+ }
23948
+ function decodeScanCache(document) {
23949
+ const cache = /* @__PURE__ */ new Map();
23950
+ if (typeof document !== "object" || document === null) return cache;
23951
+ const root = document;
23952
+ if (root.version !== USAGE_SCAN_CACHE_VERSION) return cache;
23953
+ if (!Array.isArray(root.models) || !Array.isArray(root.sessions)) return cache;
23954
+ if (typeof root.files !== "object" || root.files === null) return cache;
23955
+ if (!root.models.every((value) => typeof value === "string")) return cache;
23956
+ if (!root.sessions.every((value) => typeof value === "string")) return cache;
23957
+ const models = root.models;
23958
+ const sessions = root.sessions;
23959
+ const decodeRecords = (rows) => {
23960
+ const records = [];
23961
+ for (const row of rows) {
23962
+ if (!Array.isArray(row) || row.length < 10) return null;
23963
+ const [
23964
+ timestampMs,
23965
+ modelIndex,
23966
+ sessionIndex,
23967
+ uncached,
23968
+ cached,
23969
+ cacheCreation,
23970
+ output,
23971
+ reasoning,
23972
+ dedupeKey,
23973
+ reportedCostUsd
23974
+ ] = row;
23975
+ const model = typeof modelIndex === "number" ? models[modelIndex] : void 0;
23976
+ if (typeof timestampMs !== "number" || !Number.isFinite(timestampMs) || model === void 0 || !Number.isFinite(uncached) || !Number.isFinite(cached) || !Number.isFinite(cacheCreation) || !Number.isFinite(output) || !Number.isFinite(reasoning)) {
23977
+ return null;
23978
+ }
23979
+ records.push({
23980
+ timestampMs,
23981
+ model,
23982
+ sessionId: (typeof sessionIndex === "number" ? sessions[sessionIndex] : void 0) ?? "",
23983
+ totals: {
23984
+ uncachedInputTokens: uncached,
23985
+ cachedInputTokens: cached,
23986
+ cacheCreationTokens: cacheCreation,
23987
+ outputTokens: output,
23988
+ reasoningTokens: reasoning
23989
+ },
23990
+ reportedCostUsd: typeof reportedCostUsd === "number" ? reportedCostUsd : null,
23991
+ dedupeKey: typeof dedupeKey === "string" ? dedupeKey : null
23992
+ });
23993
+ }
23994
+ return records;
23995
+ };
23996
+ for (const [path, raw] of Object.entries(root.files)) {
23997
+ if (typeof raw !== "object" || raw === null) continue;
23998
+ const entry = raw;
23999
+ if (typeof entry.s !== "number" || typeof entry.m !== "number") continue;
24000
+ if (!Array.isArray(entry.r) || !Array.isArray(entry.t)) continue;
24001
+ if (typeof entry.o !== "number" || !Number.isSafeInteger(entry.o) || entry.o < 0 || typeof entry.gl !== "number" || !Number.isSafeInteger(entry.gl) || entry.gl < 0 || entry.gl > GUARD_LENGTH || entry.gl > entry.o || typeof entry.gh !== "number" || !Number.isFinite(entry.gh)) {
24002
+ continue;
24003
+ }
24004
+ const records = decodeRecords(entry.r);
24005
+ const tailRecords = decodeRecords(entry.t);
24006
+ if (records === null || tailRecords === null) continue;
24007
+ cache.set(path, {
24008
+ size: entry.s,
24009
+ mtimeMs: entry.m,
24010
+ records,
24011
+ tailRecords,
24012
+ position: { resumeOffset: entry.o, guardLength: entry.gl, guardHash: entry.gh }
24013
+ });
24014
+ }
24015
+ return cache;
24016
+ }
24017
+ function pruneScanCache(cache, options) {
24018
+ let removed = 0;
24019
+ for (const [path, entry] of cache) {
24020
+ const agedOut = entry.mtimeMs < options.retentionCutoffMs;
24021
+ const underWalkedRoot = options.walkedRoots.some((root) => {
24022
+ const relative2 = NodePath11.relative(root, path);
24023
+ return relative2 === "" || relative2 !== ".." && !relative2.startsWith(`..${NodePath11.sep}`) && !NodePath11.isAbsolute(relative2);
24024
+ });
24025
+ const deleted = underWalkedRoot && entry.mtimeMs >= options.windowStartMs && !options.livePaths.has(path);
24026
+ if (agedOut || deleted) {
24027
+ cache.delete(path);
24028
+ removed += 1;
24029
+ }
24030
+ }
24031
+ return removed;
24032
+ }
24033
+ function dedupeWithinFile(records, seen = /* @__PURE__ */ new Set()) {
24034
+ const kept = [];
24035
+ for (const record of records) {
24036
+ if (record.dedupeKey !== null) {
24037
+ if (seen.has(record.dedupeKey)) continue;
24038
+ seen.add(record.dedupeKey);
24039
+ }
24040
+ kept.push(record);
24041
+ }
24042
+ return kept;
24043
+ }
24044
+
24045
+ // src/usageRead/tokenUsage.ts
24046
+ var MTIME_SLACK_MS = 36 * 60 * 60 * 1e3;
24047
+ var CACHE_RETENTION_MS = 90 * 24 * 60 * 60 * 1e3;
24048
+ var SCAN_CACHE_KEY_PREFIX = "claude-usage-scan:";
24049
+ var runtimes = /* @__PURE__ */ new Map();
24050
+ var runtimeFor = (root) => {
24051
+ let runtime = runtimes.get(root);
24052
+ if (runtime === void 0) {
24053
+ runtime = { fileCache: /* @__PURE__ */ new Map(), loaded: void 0, dirty: false, queue: Promise.resolve() };
24054
+ runtimes.set(root, runtime);
24055
+ }
24056
+ return runtime;
24057
+ };
24058
+ var ensureLoaded = (runtime, root, cache) => {
24059
+ runtime.loaded ??= (async () => {
24060
+ if (cache === void 0) return;
24061
+ const cached = await cache.getJson(`${SCAN_CACHE_KEY_PREFIX}${root}`).catch(() => null);
24062
+ if (cached === null) return;
24063
+ for (const [path, entry] of decodeScanCache(cached.value)) runtime.fileCache.set(path, entry);
24064
+ })();
24065
+ return runtime.loaded;
24066
+ };
24067
+ var readFileRecords = async (runtime, filePath, size, mtimeMs) => {
24068
+ const cached = runtime.fileCache.get(filePath);
24069
+ if (cached && cached.size === size && cached.mtimeMs === mtimeMs) {
24070
+ return cached.tailRecords.length === 0 ? cached.records : [...cached.records, ...cached.tailRecords];
24071
+ }
24072
+ const resumeFrom = cached !== void 0 && size > cached.size ? cached.position : void 0;
24073
+ const parsed = await readTranscriptRecords(filePath, resumeFrom);
24074
+ if (parsed === null) return null;
24075
+ const base = parsed.resumed && cached !== void 0 ? cached.records : [];
24076
+ const seen = /* @__PURE__ */ new Set();
24077
+ const records = dedupeWithinFile([...base, ...parsed.records], seen);
24078
+ const tailRecords = dedupeWithinFile(parsed.tailRecords, seen);
24079
+ runtime.fileCache.set(filePath, {
24080
+ size,
24081
+ mtimeMs,
24082
+ records,
24083
+ tailRecords,
24084
+ position: parsed.position
24085
+ });
24086
+ runtime.dirty = true;
24087
+ return tailRecords.length === 0 ? records : [...records, ...tailRecords];
24088
+ };
24089
+ var createClaudeTokenReader = (options) => {
24090
+ const { configDir, transcriptRoot, cache } = options;
24091
+ const scan = async (input) => {
24092
+ const sinceMs = Date.parse(input.since);
24093
+ const untilMs = Date.parse(input.until);
24094
+ if (Number.isNaN(sinceMs) || Number.isNaN(untilMs)) {
24095
+ throw new Error(`readTokenUsage: invalid window '${input.since}'..'${input.until}'`);
24096
+ }
24097
+ const root = await statTranscriptRoot(transcriptRoot);
24098
+ if (root === null) {
24099
+ return {
24100
+ records: [],
24101
+ source: {
24102
+ homePath: configDir,
24103
+ status: "missing",
24104
+ scannedFiles: 0,
24105
+ skippedFiles: 0,
24106
+ message: "No Claude Code session records found on this machine."
24107
+ }
24108
+ };
24109
+ }
24110
+ const runtime = runtimeFor(transcriptRoot);
24111
+ await ensureLoaded(runtime, transcriptRoot, cache);
24112
+ const startedAtMs = Date.now();
24113
+ const windowStartMs = sinceMs - MTIME_SLACK_MS;
24114
+ const files = (await listTranscriptFiles2(transcriptRoot, windowStartMs)).toSorted(
24115
+ (a, b3) => a.path < b3.path ? -1 : a.path > b3.path ? 1 : 0
24116
+ );
24117
+ let scannedFiles = 0;
24118
+ let skippedFiles = 0;
24119
+ let failedFiles = 0;
24120
+ const livePaths = /* @__PURE__ */ new Set();
24121
+ const seen = /* @__PURE__ */ new Set();
24122
+ const records = [];
24123
+ for (const file of files) {
24124
+ input.signal?.throwIfAborted();
24125
+ livePaths.add(file.path);
24126
+ const fileRecords = await readFileRecords(runtime, file.path, file.size, file.mtimeMs);
24127
+ if (fileRecords === null) {
24128
+ failedFiles += 1;
24129
+ continue;
24130
+ }
24131
+ if (fileRecords.length === 0) {
24132
+ skippedFiles += 1;
24133
+ continue;
23171
24134
  }
23172
- },
23173
- generateBranchName: async (raw, signal) => {
23174
- try {
23175
- const input = asRecord(raw, "generateBranchName");
23176
- const { prompt, outputSchema } = buildBranchNamePrompt({
23177
- message: readString3(input["message"]),
23178
- ...Array.isArray(input["attachments"]) ? { attachments: input["attachments"] } : {},
23179
- ...isRecord5(input["policy"]) ? { policy: input["policy"] } : {}
24135
+ scannedFiles += 1;
24136
+ for (const record of fileRecords) {
24137
+ if (record.dedupeKey !== null) {
24138
+ if (seen.has(record.dedupeKey)) continue;
24139
+ seen.add(record.dedupeKey);
24140
+ }
24141
+ if (record.timestampMs < sinceMs || record.timestampMs >= untilMs) continue;
24142
+ records.push({
24143
+ at: new Date(record.timestampMs).toISOString(),
24144
+ model: record.model,
24145
+ ...record.sessionId.length > 0 ? { sessionId: record.sessionId } : {},
24146
+ totals: record.totals,
24147
+ ...record.reportedCostUsd === null ? {} : { reportedCostUsd: record.reportedCostUsd }
23180
24148
  });
23181
- const generated = asRecord(
23182
- await runClaudeJson({
23183
- operation: "generateBranchName",
23184
- cwd: readString3(input["cwd"]) || ".",
23185
- prompt,
23186
- outputSchema,
23187
- modelSelection: input["modelSelection"],
23188
- ...signal === void 0 ? {} : { signal }
23189
- }),
23190
- "generateBranchName"
23191
- );
23192
- return { branch: sanitizeBranchFragment(readString3(generated["branch"])) };
23193
- } catch (error) {
23194
- throw normalizeCliError("claude", error, "Failed to generate branch name");
23195
24149
  }
23196
- },
23197
- generateThreadTitle: async (raw, signal) => {
24150
+ }
24151
+ const pruned = pruneScanCache(runtime.fileCache, {
24152
+ livePaths,
24153
+ walkedRoots: [transcriptRoot],
24154
+ windowStartMs,
24155
+ retentionCutoffMs: startedAtMs - CACHE_RETENTION_MS
24156
+ });
24157
+ if (pruned > 0) runtime.dirty = true;
24158
+ if (runtime.dirty && cache !== void 0) {
23198
24159
  try {
23199
- const input = asRecord(raw, "generateThreadTitle");
23200
- const { prompt, outputSchema } = buildThreadTitlePrompt({
23201
- message: readString3(input["message"]),
23202
- ...typeof input["linkedContext"] === "string" && input["linkedContext"].length > 0 ? { linkedContext: input["linkedContext"] } : {},
23203
- ...typeof input["previousTitle"] === "string" ? { previousTitle: input["previousTitle"] } : {},
23204
- ...Array.isArray(input["attachments"]) ? { attachments: input["attachments"] } : {},
23205
- ...isRecord5(input["policy"]) ? { policy: input["policy"] } : {}
23206
- });
23207
- const generated = asRecord(
23208
- await runClaudeJson({
23209
- operation: "generateThreadTitle",
23210
- cwd: readString3(input["cwd"]) || ".",
23211
- prompt,
23212
- outputSchema,
23213
- modelSelection: input["modelSelection"],
23214
- ...signal === void 0 ? {} : { signal }
23215
- }),
23216
- "generateThreadTitle"
24160
+ await cache.setJson(
24161
+ `${SCAN_CACHE_KEY_PREFIX}${transcriptRoot}`,
24162
+ encodeScanCache(runtime.fileCache)
23217
24163
  );
23218
- return {
23219
- title: sanitizeThreadTitle(readString3(generated["title"])),
23220
- ...generated["needsRefinement"] === true ? { needsRefinement: true } : {}
23221
- };
23222
- } catch (error) {
23223
- throw normalizeCliError("claude", error, "Failed to generate thread title");
24164
+ runtime.dirty = false;
24165
+ } catch {
23224
24166
  }
23225
- },
23226
- summarizeHandover: async (raw, signal) => {
23227
- try {
23228
- const input = asRecord(raw, "summarizeHandover");
23229
- const { prompt, outputSchema } = buildHandoverSummaryPrompt({
23230
- transcript: readString3(input["transcript"])
23231
- });
23232
- const generated = asRecord(
23233
- await runClaudeJson({
23234
- operation: "summarizeHandover",
23235
- cwd: readString3(input["cwd"]) || ".",
23236
- prompt,
23237
- outputSchema,
23238
- modelSelection: input["modelSelection"],
23239
- ...signal === void 0 ? {} : { signal }
23240
- }),
23241
- "summarizeHandover"
23242
- );
23243
- return { summary: readString3(generated["summary"]).trim() };
23244
- } catch (error) {
23245
- throw normalizeCliError("claude", error, "Failed to summarize handover");
24167
+ }
24168
+ return {
24169
+ records,
24170
+ source: {
24171
+ homePath: configDir,
24172
+ volumeId: root.volumeId,
24173
+ status: failedFiles > 0 ? "partial" : "ok",
24174
+ scannedFiles,
24175
+ skippedFiles,
24176
+ ...failedFiles > 0 ? { message: `${String(failedFiles)} session record file(s) could not be read.` } : {}
23246
24177
  }
24178
+ };
24179
+ };
24180
+ return {
24181
+ readTokenUsage: (input) => {
24182
+ const runtime = runtimeFor(transcriptRoot);
24183
+ const run = runtime.queue.then(() => scan(input));
24184
+ runtime.queue = run.catch(() => void 0);
24185
+ return run;
23247
24186
  }
23248
24187
  };
23249
- }
24188
+ };
24189
+
24190
+ // src/usageRead/index.ts
24191
+ var createClaudeUsage = (input) => {
24192
+ const { recipe, host } = input;
24193
+ const configDir = resolveClaudeConfigDirPath(recipe.homePath, {
24194
+ ...process.env,
24195
+ ...recipe.environment
24196
+ });
24197
+ const tokens = createClaudeTokenReader({
24198
+ configDir,
24199
+ transcriptRoot: NodePath12.join(configDir, "projects"),
24200
+ cache: input.cache
24201
+ });
24202
+ return {
24203
+ readQuota: ({ signal }) => readClaudeQuota({ recipe, fetch: host.fetch, signal }),
24204
+ readTokenUsage: tokens.readTokenUsage
24205
+ };
24206
+ };
23250
24207
 
23251
24208
  // src/claudeSkillDispatch.ts
23252
24209
  var SKILL_MENTION_PATTERN = /(^|\s)\$([a-zA-Z][a-zA-Z0-9:_-]*)(?=\s|$)/g;
@@ -23504,7 +24461,6 @@ function toClaudeMcpServers(entries) {
23504
24461
  }
23505
24462
  return servers;
23506
24463
  }
23507
- var CLAUDE_PROGRAM_NOT_CONFIGURED = "Claude program path is not configured";
23508
24464
  function createClaudeAdapter(recipe, host, options) {
23509
24465
  const boundInstanceId = options?.instanceId ?? host.instanceId ?? "claudeAgent";
23510
24466
  const claudeHomeEnvironment = makeClaudeEnvironment(recipe, options?.environment);
@@ -23520,7 +24476,7 @@ function createClaudeAdapter(recipe, host, options) {
23520
24476
  if (recipe.binaryPath.length === 0) throw new Error(CLAUDE_PROGRAM_NOT_CONFIGURED);
23521
24477
  return sdkCreateQuery(input);
23522
24478
  };
23523
- const buildLaunchEnvironment = () => mergeLaunchEnv2({
24479
+ const buildLaunchEnvironment = () => mergeLaunchEnv3({
23524
24480
  inherited: { ...process.env, ...options?.environment },
23525
24481
  instanceTable: recipe.environment,
23526
24482
  handOverrides: claudeHomeVariables(resolveClaudeHomeLayout(recipe))
@@ -23913,7 +24869,10 @@ function createClaudeAdapter(recipe, host, options) {
23913
24869
  compactedSinceLatestAssistantUsage: false,
23914
24870
  retrying: false,
23915
24871
  authenticationFailed: false,
23916
- nextSyntheticAssistantBlockIndex: -1
24872
+ nextSyntheticAssistantBlockIndex: -1,
24873
+ rejectedRateLimits: /* @__PURE__ */ new Map(),
24874
+ announcedUsageLimits: /* @__PURE__ */ new Set(),
24875
+ latestAssistantRateLimited: false
23917
24876
  };
23918
24877
  context.session = {
23919
24878
  ...context.session,
@@ -24500,6 +25459,10 @@ function createClaudeAdapter(recipe, host, options) {
24500
25459
  if (message.type !== "assistant") {
24501
25460
  return;
24502
25461
  }
25462
+ const rateLimitParent = message.parent_tool_use_id;
25463
+ if (context.turnState && (rateLimitParent === null || rateLimitParent === void 0)) {
25464
+ context.turnState.latestAssistantRateLimited = message.error === "rate_limit";
25465
+ }
24503
25466
  if (isClaudeSyntheticApiErrorMessage(message)) {
24504
25467
  if (context.turnState && message.error === "authentication_failed") {
24505
25468
  context.turnState.authenticationFailed = true;
@@ -24571,14 +25534,22 @@ function createClaudeAdapter(recipe, host, options) {
24571
25534
  return;
24572
25535
  }
24573
25536
  context.compactionInProgress = false;
24574
- const { status, errorMessage } = resultOutcome(
25537
+ const outcome = resultOutcome(
24575
25538
  message,
24576
25539
  claudeFailureHint({
24577
25540
  authenticationFailed: context.turnState?.authenticationFailed === true,
24578
25541
  environment: buildLaunchEnvironment(),
24579
- cwd: NodePath8.resolve(context.session.cwd ?? ".")
25542
+ cwd: NodePath13.resolve(context.session.cwd ?? "."),
25543
+ ...context.turnState === void 0 ? {} : {
25544
+ usageLimit: {
25545
+ rejected: context.turnState.rejectedRateLimits,
25546
+ latestAssistantRateLimited: context.turnState.latestAssistantRateLimited,
25547
+ nowMs: Date.now()
25548
+ }
25549
+ }
24580
25550
  })
24581
25551
  );
25552
+ const { status, errorMessage } = outcome;
24582
25553
  if (status === "failed") {
24583
25554
  await emitRuntimeError(context, errorMessage ?? "Claude turn failed.");
24584
25555
  }
@@ -24685,7 +25656,7 @@ function createClaudeAdapter(recipe, host, options) {
24685
25656
  }
24686
25657
  }
24687
25658
  switch (message.subtype) {
24688
- case "init":
25659
+ case "init": {
24689
25660
  offerRuntimeEvent(context, {
24690
25661
  ...base,
24691
25662
  type: "session.configured",
@@ -24693,6 +25664,27 @@ function createClaudeAdapter(recipe, host, options) {
24693
25664
  config: message
24694
25665
  }
24695
25666
  });
25667
+ const threadId = context.session.threadId;
25668
+ const names = claudeSlashCommandsFromNames(message.slash_commands);
25669
+ if (names.length > 0) host.reportSessionInventory?.({ threadId, slashCommands: names });
25670
+ void context.query.supportedCommands?.().then(
25671
+ (commands) => {
25672
+ const detailed = claudeSlashCommandsFromSdk(commands);
25673
+ if (detailed.length > 0) {
25674
+ host.reportSessionInventory?.({ threadId, slashCommands: detailed });
25675
+ }
25676
+ },
25677
+ () => void 0
25678
+ );
25679
+ return;
25680
+ }
25681
+ case "commands_changed":
25682
+ if (Array.isArray(message.commands)) {
25683
+ host.reportSessionInventory?.({
25684
+ threadId: context.session.threadId,
25685
+ slashCommands: claudeSlashCommandsFromSdk(message.commands)
25686
+ });
25687
+ }
24696
25688
  return;
24697
25689
  case "status": {
24698
25690
  const compacting = message.status === "compacting";
@@ -25000,7 +25992,6 @@ function createClaudeAdapter(recipe, host, options) {
25000
25992
  // deliberately so they don't masquerade as unknown-subtype warnings.
25001
25993
  case "local_command_output":
25002
25994
  case "plugin_install":
25003
- case "commands_changed":
25004
25995
  case "memory_recall":
25005
25996
  case "elicitation_complete":
25006
25997
  return;
@@ -25100,6 +26091,25 @@ function createClaudeAdapter(recipe, host, options) {
25100
26091
  rateLimits: message
25101
26092
  }
25102
26093
  });
26094
+ const info = message.rate_limit_info;
26095
+ const turn = context.turnState;
26096
+ if (info && turn) {
26097
+ const type4 = info.rateLimitType ?? "unknown";
26098
+ if (isBlockingRateLimit(info)) {
26099
+ turn.rejectedRateLimits.set(type4, info.resetsAt);
26100
+ const key = rateLimitKey(info);
26101
+ if (!turn.announcedUsageLimits.has(key)) {
26102
+ turn.announcedUsageLimits.add(key);
26103
+ await emitRuntimeWarning(
26104
+ context,
26105
+ describeUsageLimitPause(info, Date.parse(stamp.createdAt)),
26106
+ info
26107
+ );
26108
+ }
26109
+ } else if (isClearingRateLimit(info)) {
26110
+ turn.rejectedRateLimits.delete(type4);
26111
+ }
26112
+ }
25103
26113
  return;
25104
26114
  }
25105
26115
  };
@@ -25992,7 +27002,10 @@ function createClaudeAdapter(recipe, host, options) {
25992
27002
  compactedSinceLatestAssistantUsage: false,
25993
27003
  retrying: false,
25994
27004
  authenticationFailed: false,
25995
- nextSyntheticAssistantBlockIndex: -1
27005
+ nextSyntheticAssistantBlockIndex: -1,
27006
+ rejectedRateLimits: /* @__PURE__ */ new Map(),
27007
+ announcedUsageLimits: /* @__PURE__ */ new Set(),
27008
+ latestAssistantRateLimited: false
25996
27009
  };
25997
27010
  const updatedAt = nowIso();
25998
27011
  context.turnState = turnState;
@@ -26024,9 +27037,7 @@ function createClaudeAdapter(recipe, host, options) {
26024
27037
  attachments: input.attachments,
26025
27038
  attachmentsApi: options?.attachments,
26026
27039
  skillNames: new Set(
26027
- skills.filter(
26028
- (skill) => skill.scope === "user" && skill.enabled && skill.userInvocable !== false
26029
- ).map((skill) => skill.name)
27040
+ skills.filter((skill) => skill.enabled && skill.userInvocable !== false).map((skill) => skill.name)
26030
27041
  )
26031
27042
  });
26032
27043
  validateSteeringTarget();
@@ -26185,7 +27196,7 @@ function createClaudeAdapter(recipe, host, options) {
26185
27196
  function claudeAdapterBaseEnvironment(recipe) {
26186
27197
  return { ...process.env, ...recipe.environment };
26187
27198
  }
26188
- function createClaudeInstance(config, host, attachments, overrides) {
27199
+ function createClaudeInstance(config, host, attachments, overrides, usageCache) {
26189
27200
  const recipe = parseClaudeInstanceConfig(config);
26190
27201
  const adapter = createClaudeAdapter(recipe, host, {
26191
27202
  instanceId: host.instanceId,
@@ -26194,118 +27205,7 @@ function createClaudeInstance(config, host, attachments, overrides) {
26194
27205
  environment: claudeAdapterBaseEnvironment(recipe),
26195
27206
  log: host.log
26196
27207
  });
26197
- const collectProcessText = async (stream) => {
26198
- const chunks = [];
26199
- for await (const chunk of stream) {
26200
- chunks.push(Buffer.from(chunk));
26201
- }
26202
- return Buffer.concat(chunks).toString("utf8");
26203
- };
26204
- const probeAuth = () => {
26205
- const hasEnvToken = (recipe.environment.ANTHROPIC_AUTH_TOKEN ?? "").trim().length > 0;
26206
- const hasEnvApiKey = (recipe.environment.ANTHROPIC_API_KEY ?? "").trim().length > 0;
26207
- if (hasEnvToken || hasEnvApiKey) {
26208
- return {
26209
- status: "authenticated",
26210
- type: hasEnvToken ? "env-token" : "api-key",
26211
- label: hasEnvToken ? "ANTHROPIC_AUTH_TOKEN" : "ANTHROPIC_API_KEY"
26212
- };
26213
- }
26214
- const account = readClaudeAccount(resolveClaudeCredentialsDirPath(recipe.credentialsPath));
26215
- switch (account.state) {
26216
- case "signed-in":
26217
- case "expired":
26218
- return { status: "authenticated" };
26219
- case "signed-out":
26220
- return { status: "unauthenticated" };
26221
- case "unreadable":
26222
- return { status: "unknown" };
26223
- }
26224
- };
26225
- let lastProbedVersion;
26226
- const probe = async (signal) => {
26227
- if (recipe.binaryPath.length === 0) {
26228
- return {
26229
- installed: false,
26230
- version: null,
26231
- readiness: "not-installed",
26232
- auth: { status: "unknown" },
26233
- detail: CLAUDE_PROGRAM_NOT_CONFIGURED
26234
- };
26235
- }
26236
- if (signal?.aborted) {
26237
- throw new Error("aborted");
26238
- }
26239
- let handle;
26240
- try {
26241
- handle = await host.process.spawn({
26242
- program: recipe.binaryPath,
26243
- args: ["--version"],
26244
- owner: { bindingId: host.bindingId }
26245
- });
26246
- } catch (error) {
26247
- if (error instanceof ExecutorSpawnRefusedError) throw error;
26248
- const failure = describeExecutorSpawnFailure(error);
26249
- return {
26250
- installed: false,
26251
- version: null,
26252
- readiness: failure.readiness,
26253
- auth: { status: "unknown" },
26254
- detail: failure.detail
26255
- };
26256
- }
26257
- const [stdout, stderr, exitCode] = await Promise.all([
26258
- collectProcessText(handle.stdout),
26259
- collectProcessText(handle.stderr),
26260
- handle.exited
26261
- ]);
26262
- const version = parseGenericCliVersion(`${stdout}
26263
- ${stderr}`);
26264
- if (version !== null || exitCode === 0) {
26265
- await adapter.modelCatalog.refresh({ signal, force: version !== lastProbedVersion });
26266
- lastProbedVersion = version;
26267
- }
26268
- const models = claudeProbeModels({
26269
- catalog: adapter.modelCatalog.current(),
26270
- customModels: recipe.customModels
26271
- });
26272
- const auth = probeAuth();
26273
- const skills = await discoverClaudeSkills(
26274
- resolveClaudeConfigDirPath(recipe.homePath, { ...process.env, ...recipe.environment })
26275
- );
26276
- if (exitCode !== 0 && version === null) {
26277
- const detail = (stderr || stdout || "claude --version failed").trim();
26278
- return {
26279
- installed: false,
26280
- version: null,
26281
- readiness: "error",
26282
- auth,
26283
- ...detail.length > 0 ? { detail } : {}
26284
- };
26285
- }
26286
- if (auth.status === "unauthenticated") {
26287
- return {
26288
- installed: true,
26289
- version,
26290
- readiness: "error",
26291
- auth,
26292
- models,
26293
- skills,
26294
- detail: "Claude is signed out",
26295
- // 退出登录了档位照样是那五档。不报就退回宿主四档,而那份映射少了 `plan`。
26296
- ...CLAUDE_NATIVE_MODE_REPORT
26297
- };
26298
- }
26299
- return {
26300
- installed: true,
26301
- version,
26302
- readiness: "ready",
26303
- auth,
26304
- models,
26305
- skills,
26306
- ...CLAUDE_NATIVE_MODE_REPORT
26307
- };
26308
- };
27208
+ const probe = createClaudeProbe({ recipe, host, modelCatalog: adapter.modelCatalog });
26309
27209
  const resolveTranscriptRoots = (directory) => {
26310
27210
  const configDir = resolveClaudeConfigDirPath(
26311
27211
  recipe.homePath,
@@ -26314,7 +27214,7 @@ ${stderr}`);
26314
27214
  );
26315
27215
  return {
26316
27216
  configDir,
26317
- transcriptDir: NodePath8.join(configDir, "projects")
27217
+ transcriptDir: NodePath13.join(configDir, "projects")
26318
27218
  };
26319
27219
  };
26320
27220
  return {
@@ -26331,8 +27231,18 @@ ${stderr}`);
26331
27231
  readThread: adapter.readThread,
26332
27232
  events: adapter.events,
26333
27233
  dispose: adapter.dispose,
27234
+ // 项目里的命令与技能:探测没有工作区,只能报用户配置目录那份;这里按目录扫。
27235
+ workspaceInventory: ({ cwd }) => {
27236
+ const cliEnvironment = { ...process.env, ...recipe.environment };
27237
+ return discoverClaudeWorkspaceInventory({
27238
+ configDirPath: resolveClaudeConfigDirPath(recipe.homePath, cliEnvironment, cwd),
27239
+ cwd,
27240
+ environment: cliEnvironment
27241
+ });
27242
+ },
26334
27243
  textGeneration: createClaudeTextGeneration(recipe, host, adapter.modelCatalog.current),
26335
27244
  continuationKey: claudeContinuationKey(recipe.homePath),
27245
+ usage: createClaudeUsage({ recipe, host, cache: usageCache }),
26336
27246
  sessions: {
26337
27247
  scan: async (input) => {
26338
27248
  const roots = resolveTranscriptRoots(input.directory);
@@ -26373,12 +27283,13 @@ ${stderr}`);
26373
27283
 
26374
27284
  // src/index.ts
26375
27285
  var capturedAttachments;
27286
+ var capturedCache;
26376
27287
  var executors = [
26377
27288
  {
26378
27289
  card: CLAUDE_CARD,
26379
27290
  createInstance: async (config, host) => {
26380
27291
  host.log.info("createInstance");
26381
- return createClaudeInstance(config, host, capturedAttachments);
27292
+ return createClaudeInstance(config, host, capturedAttachments, void 0, capturedCache);
26382
27293
  }
26383
27294
  }
26384
27295
  ];
@@ -26388,6 +27299,7 @@ var index_default = definePlugin({
26388
27299
  executors,
26389
27300
  activate(ctx) {
26390
27301
  capturedAttachments = ctx.attachments;
27302
+ capturedCache = ctx.cache;
26391
27303
  ctx.log.info("claude executor ready");
26392
27304
  }
26393
27305
  });