@ycodium-ai/agent-claude 0.2.2106 → 0.2.2130

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +16 -2
  2. package/dist/index.js +651 -97
  3. package/package.json +3 -3
package/README.md CHANGED
@@ -1,8 +1,15 @@
1
1
  # @ycodium-ai/agent-claude
2
2
 
3
3
  Claude executor plugin. Talks to Claude Code through
4
- `@anthropic-ai/claude-agent-sdk` (`query()`), not ACP. The host Claude driver
5
- stays in this batch; both cards coexist in the catalog.
4
+ `@anthropic-ai/claude-agent-sdk` (`query()`), not ACP.
5
+
6
+ Since 2026-09-19 this package **is** the Claude hand: the catalog row carries
7
+ `executorPlugin: "@ycodium-ai/agent-claude"`, the host `ClaudeDriver` /
8
+ `ClaudeAdapter` / `ClaudeProvider` / `ClaudeTextGeneration` are deleted, and the
9
+ built-in `claudeAgent` card no longer joins the catalog. The host keeps one copy
10
+ of the connectivity literal (`executorRegistry.ts` → `officialCardForDriver`)
11
+ because `compileBinding` is synchronous and cannot read the plugin registry;
12
+ `executorRegistry.test.ts` pins the two copies to be byte-identical.
6
13
 
7
14
  Facts:
8
15
 
@@ -13,6 +20,13 @@ Facts:
13
20
  - Manifest permission is `{ kind: "executor" }`.
14
21
  - Card capabilities: `sessionModelSwitch: "in-session"`, `runtimeModeSwitch: "in-session"`,
15
22
  `contextCompaction: "native"`, `turnSteering: true`.
23
+ - `modelConnectivity` carries `baseUrlEnvKey: ANTHROPIC_BASE_URL`,
24
+ `credentialEnvKey: ANTHROPIC_AUTH_TOKEN` and seven `extraEnv` rows pinning every
25
+ model-role variable to `$model`. It deliberately does **not** claim
26
+ `supportsNativeSubscription`: Ycodium has no Claude native-subscription login.
27
+ - Recordings: `test/fixtures/*.sdk.ndjson`, ten scenes captured from Claude Code
28
+ 2.1.277 by `scripts/record-claude-sdk-session.ts`; `src/replay.test.ts` replays
29
+ them through the same `createQuery` seam the recorder tapped.
16
30
  - Quota-probe dissolution: `credentialSources` lists the json-file and
17
31
  macOS-keychain locations. `sessions.scan` / `sessions.read` take the Claude
18
32
  half of session-import.
package/dist/index.js CHANGED
@@ -5,7 +5,9 @@ import { definePlugin } from "@ycodium-ai/plugin-api";
5
5
  var CLAUDE_DRIVER_KIND = "claudeAgent";
6
6
  var CLAUDE_CARD = {
7
7
  driverKind: CLAUDE_DRIVER_KIND,
8
- displayName: "Claude",
8
+ // 手叫 Claude Code,不叫 Claude——「Claude」会和脑(Anthropic · Claude Opus 5)混。
9
+ // 已有实例存的显示名不动,这里只影响新建实例与手脑拆分后的回填名。
10
+ displayName: "Claude Code",
9
11
  iconUrl: "./assets/icon.svg",
10
12
  commandName: "claude",
11
13
  launchArgs: "",
@@ -60,9 +62,16 @@ var CLAUDE_CARD = {
60
62
  verifiedSkills: true,
61
63
  everydayHarness: true,
62
64
  capabilities: {
65
+ // 会话中途换模型走 SDK 的 `setModel` 控制请求,不重开会话:`claudeInstance.ts` 的
66
+ // sendTurn 在 `currentApiModelId` 与本轮选择不一致时调它,`reconfigureSession` 复用
67
+ // 会话时也是同一条路。录像证据是 `test/fixtures/claudeModelSwitchTurn.sdk.ndjson`
68
+ // (同一会话两轮不同模型,中间一条 `setModel`)。宿主内置名片此前写 unsupported,
69
+ // 与 adapter 自己声明给编排的 `"in-session"` 相反——那边是真的,名片是错的。
63
70
  sessionModelSwitch: "in-session",
64
71
  sessionOptionSwitch: "unsupported",
65
72
  runtimeModeSwitch: "in-session",
73
+ // SDK 的 `setPermissionMode` 会话中途就能改,所以这一位也是真的。
74
+ nativeModeSwitch: true,
66
75
  contextCompaction: "native",
67
76
  turnSteering: true
68
77
  },
@@ -98,15 +107,34 @@ var CLAUDE_CARD = {
98
107
  modelConnectivity: {
99
108
  dialects: ["anthropic-messages"],
100
109
  acceptsExternalEndpoints: true,
110
+ // claude 认 origin:CLI 自己在注入的 base 后追加 `/v1/messages`,所以后缀整条
111
+ // 就是路由,注进去的 base 是纯 origin(compileBinding 按这一栏算出该注什么)。
101
112
  nativePathSuffix: "/v1/messages",
102
- credentialEnvKey: "ANTHROPIC_API_KEY",
103
- supportsNativeSubscription: true
113
+ // 与宿主内置名片逐字相同。credentialEnvKey 是宿主给绑定注入的那个变量;
114
+ // alternateCredentialEnvKeys 是 CLI 也认的备选凭据变量(手脑拆分线加的,用来识别捆绑)。
115
+ baseUrlEnvKey: "ANTHROPIC_BASE_URL",
116
+ credentialEnvKey: "ANTHROPIC_AUTH_TOKEN",
117
+ alternateCredentialEnvKeys: ["ANTHROPIC_API_KEY"],
118
+ supportsNativeSubscription: true,
119
+ // 七个档位变量一起钉成这次的模型:claude 的 opus / sonnet / haiku / fable 档与
120
+ // 子代理、小快模型各读一个变量,只钉 `ANTHROPIC_MODEL` 的话子代理会跑回默认档。
121
+ // 名单与旧 `CLAUDE_SETTINGS_MODEL_ENV_KEYS` 逐字相同(含 2026-09-09 从 2.1.265
122
+ // 二进制核实出来的 fable 那一条)。`$model` 由 compileBinding 替换。
123
+ extraEnv: [
124
+ { name: "ANTHROPIC_MODEL", value: "$model" },
125
+ { name: "ANTHROPIC_DEFAULT_OPUS_MODEL", value: "$model" },
126
+ { name: "ANTHROPIC_DEFAULT_SONNET_MODEL", value: "$model" },
127
+ { name: "ANTHROPIC_DEFAULT_HAIKU_MODEL", value: "$model" },
128
+ { name: "ANTHROPIC_DEFAULT_FABLE_MODEL", value: "$model" },
129
+ { name: "ANTHROPIC_SMALL_FAST_MODEL", value: "$model" },
130
+ { name: "CLAUDE_CODE_SUBAGENT_MODEL", value: "$model" }
131
+ ]
104
132
  }
105
133
  };
106
134
 
107
135
  // src/claudeInstance.ts
108
136
  import * as NodeOS6 from "node:os";
109
- import * as NodePath6 from "node:path";
137
+ import * as NodePath7 from "node:path";
110
138
 
111
139
  // ../../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
112
140
  import { createRequire as xz } from "node:module";
@@ -19538,6 +19566,13 @@ function HU(e, t) {
19538
19566
  return null;
19539
19567
  }
19540
19568
 
19569
+ // src/claudeInstance.ts
19570
+ import {
19571
+ mergeLaunchEnv as mergeLaunchEnv2,
19572
+ withoutBrainSlots,
19573
+ brainSlotEnvKeys as brainSlotEnvKeys3
19574
+ } from "@ycodium-ai/plugin-api/executor";
19575
+
19541
19576
  // src/claudeAccount.ts
19542
19577
  import * as NodeFS from "node:fs";
19543
19578
  import * as NodeOS from "node:os";
@@ -19694,10 +19729,17 @@ function expandHomePath(value) {
19694
19729
 
19695
19730
  // src/claudeHome.ts
19696
19731
  var CLAUDE_CREDENTIALS_DIR_ENVIRONMENT_VARIABLE = "CLAUDE_SECURESTORAGE_CONFIG_DIR";
19732
+ var CLAUDE_ROUTED_HOME_DIR_NAME = "claude-home";
19733
+ function claudeRoutedHomePath(pluginDataDirPath) {
19734
+ return NodePath4.join(pluginDataDirPath, CLAUDE_ROUTED_HOME_DIR_NAME);
19735
+ }
19697
19736
  function resolveClaudeHomePath(homePath) {
19698
19737
  const trimmed = homePath.trim();
19699
19738
  return NodePath4.resolve(trimmed.length > 0 ? expandHomePath(trimmed) : NodeOS4.homedir());
19700
19739
  }
19740
+ function claudeContinuationKey(homePath) {
19741
+ return `claude:home:${resolveClaudeHomePath(homePath)}`;
19742
+ }
19701
19743
  function resolveClaudeConfigDirPath(homePath, environment, cwd) {
19702
19744
  const trimmed = homePath.trim();
19703
19745
  if (trimmed.length > 0) {
@@ -19714,18 +19756,47 @@ function resolveClaudeCredentialsDirPath(credentialsPath) {
19714
19756
  if (trimmed.length === 0) return null;
19715
19757
  return NodePath4.resolve(expandHomePath(trimmed));
19716
19758
  }
19717
- function makeClaudeEnvironment(config, baseEnv) {
19718
- const resolvedBaseEnv = { ...baseEnv ?? process.env };
19719
- const homePath = config.homePath.trim();
19720
- const credentialsDirPath = resolveClaudeCredentialsDirPath(config.credentialsPath);
19721
- if (homePath.length === 0 && credentialsDirPath === null) return resolvedBaseEnv;
19722
- const resolvedHomePath = resolveClaudeHomePath(config.homePath);
19759
+ function resolveClaudeHomeLayout(input) {
19760
+ const userConfigDirPath = resolveClaudeConfigDirPath(input.homePath, input.environment ?? {});
19761
+ const instanceConfigDirPath = input.homePath.trim().length > 0 ? resolveClaudeHomePath(input.homePath) : void 0;
19762
+ const instanceCredentialsDirPath = resolveClaudeCredentialsDirPath(input.credentialsPath);
19763
+ const routedHomePath = input.routedHomePath?.trim() ?? "";
19764
+ if (routedHomePath.length === 0 || instanceConfigDirPath !== void 0 || instanceCredentialsDirPath !== null) {
19765
+ return {
19766
+ mode: "instance",
19767
+ userConfigDirPath,
19768
+ configDirPath: instanceConfigDirPath,
19769
+ credentialsDirPath: instanceCredentialsDirPath ?? void 0
19770
+ };
19771
+ }
19772
+ const resolvedRoutedHomePath = NodePath4.resolve(expandHomePath(routedHomePath));
19773
+ return {
19774
+ mode: "routed",
19775
+ userConfigDirPath,
19776
+ configDirPath: resolvedRoutedHomePath,
19777
+ // 登录目录显式钉成影子目录本身。不设这个变量时 CLI 本来就把凭据放配置目录,但
19778
+ // 「登录去哪」正是这次要修的东西,不靠默认值。
19779
+ credentialsDirPath: resolvedRoutedHomePath
19780
+ };
19781
+ }
19782
+ function claudeHomeVariables(layout) {
19723
19783
  return {
19724
- ...resolvedBaseEnv,
19725
- ...homePath.length > 0 ? { CLAUDE_CONFIG_DIR: resolvedHomePath } : {},
19726
- ...credentialsDirPath === null ? {} : { [CLAUDE_CREDENTIALS_DIR_ENVIRONMENT_VARIABLE]: credentialsDirPath }
19784
+ ...layout.configDirPath === void 0 ? {} : { CLAUDE_CONFIG_DIR: layout.configDirPath },
19785
+ ...layout.credentialsDirPath === void 0 ? {} : { [CLAUDE_CREDENTIALS_DIR_ENVIRONMENT_VARIABLE]: layout.credentialsDirPath }
19727
19786
  };
19728
19787
  }
19788
+ function makeClaudeEnvironment(config, baseEnv) {
19789
+ const resolvedBaseEnv = { ...baseEnv ?? process.env };
19790
+ const variables = claudeHomeVariables(
19791
+ resolveClaudeHomeLayout({
19792
+ homePath: config.homePath,
19793
+ credentialsPath: config.credentialsPath,
19794
+ environment: resolvedBaseEnv
19795
+ })
19796
+ );
19797
+ if (Object.keys(variables).length === 0) return resolvedBaseEnv;
19798
+ return { ...resolvedBaseEnv, ...variables };
19799
+ }
19729
19800
 
19730
19801
  // src/claudeSignIn.ts
19731
19802
  var POSIX_BARE_VALUE = /^[A-Za-z0-9_@%+=:,./-]+$/;
@@ -19953,8 +20024,8 @@ var EventBus = class {
19953
20024
  const queued = this.#queued.shift();
19954
20025
  if (queued !== void 0) return Promise.resolve({ done: false, value: queued });
19955
20026
  if (this.#closed) return Promise.resolve({ done: true, value: void 0 });
19956
- return new Promise((resolve2) => {
19957
- this.#waiters.push(resolve2);
20027
+ return new Promise((resolve3) => {
20028
+ this.#waiters.push(resolve3);
19958
20029
  });
19959
20030
  }
19960
20031
  };
@@ -19962,6 +20033,7 @@ var EventBus = class {
19962
20033
  };
19963
20034
 
19964
20035
  // src/model.ts
20036
+ import { matchCatalogModel } from "@ycodium-ai/plugin-api/executor";
19965
20037
  var DEFAULT_CLAUDE_MODEL_CAPABILITIES = {
19966
20038
  optionDescriptors: []
19967
20039
  };
@@ -19969,6 +20041,10 @@ var CLAUDE_CUSTOM_MODEL_THINKING_SELECT = {
19969
20041
  id: "effort",
19970
20042
  label: "Reasoning"
19971
20043
  };
20044
+ var MINIMUM_CLAUDE_OPUS_5_VERSION = "2.1.219";
20045
+ var MINIMUM_CLAUDE_FABLE_5_VERSION = "2.1.169";
20046
+ var MINIMUM_CLAUDE_OPUS_4_8_VERSION = "2.1.154";
20047
+ var MINIMUM_CLAUDE_OPUS_4_7_VERSION = "2.1.111";
19972
20048
  var CURRENT_CLAUDE_MODELS = /* @__PURE__ */ new Set(["claude-fable-5", "claude-opus-5", "claude-sonnet-5"]);
19973
20049
  function isLegacyClaudeModel(model) {
19974
20050
  return !CURRENT_CLAUDE_MODELS.has(model);
@@ -20209,6 +20285,35 @@ var CLAUDE_XHIGH_SLUGS = /* @__PURE__ */ new Set([
20209
20285
  "claude-opus-4-8",
20210
20286
  "claude-sonnet-5"
20211
20287
  ]);
20288
+ function compareSemverVersions(left, right) {
20289
+ const parse = (value) => {
20290
+ const match = value.trim().replace(/^v/, "").match(/^(\d+)\.(\d+)\.(\d+)/);
20291
+ if (!match) return null;
20292
+ return [Number(match[1]), Number(match[2]), Number(match[3])];
20293
+ };
20294
+ const a = parse(left);
20295
+ const b3 = parse(right);
20296
+ if (!a || !b3) return left.localeCompare(right);
20297
+ if (a[0] !== b3[0]) return a[0] - b3[0];
20298
+ if (a[1] !== b3[1]) return a[1] - b3[1];
20299
+ return a[2] - b3[2];
20300
+ }
20301
+ function supportsVersion(version, minimum) {
20302
+ return version ? compareSemverVersions(version, minimum) >= 0 : false;
20303
+ }
20304
+ function getBuiltInClaudeModelsForVersion(version) {
20305
+ return BUILT_IN_MODELS.filter((model) => {
20306
+ if (model.slug === "claude-opus-5")
20307
+ return supportsVersion(version, MINIMUM_CLAUDE_OPUS_5_VERSION);
20308
+ if (model.slug === "claude-fable-5")
20309
+ return supportsVersion(version, MINIMUM_CLAUDE_FABLE_5_VERSION);
20310
+ if (model.slug === "claude-opus-4-8")
20311
+ return supportsVersion(version, MINIMUM_CLAUDE_OPUS_4_8_VERSION);
20312
+ if (model.slug === "claude-opus-4-7")
20313
+ return supportsVersion(version, MINIMUM_CLAUDE_OPUS_4_7_VERSION);
20314
+ return true;
20315
+ });
20316
+ }
20212
20317
  function parseGenericCliVersion(output) {
20213
20318
  const match = output.match(/\b(\d+\.\d+\.\d+)\b/);
20214
20319
  return match?.[1] ?? null;
@@ -20316,6 +20421,13 @@ function resolveClaudeContextWindow(caps, modelSelection) {
20316
20421
  if (!descriptor || descriptor.type !== "select") return void 0;
20317
20422
  return currentSelectValue(descriptor, raw);
20318
20423
  }
20424
+ function claudeCliModelId(input) {
20425
+ const catalog = [
20426
+ ...BUILT_IN_MODELS.map((model) => model.slug),
20427
+ ...(input.customModels ?? []).map((model) => normalizeCustomModelSlug(model.slug))
20428
+ ];
20429
+ return matchCatalogModel(input.boundModelId, catalog) ?? input.placeholder;
20430
+ }
20319
20431
  function resolveClaudeApiModelId(caps, modelSelection) {
20320
20432
  switch (resolveClaudeContextWindow(caps, modelSelection)) {
20321
20433
  case "1m":
@@ -20386,6 +20498,15 @@ function toMessage(cause, fallback) {
20386
20498
  }
20387
20499
  return fallback;
20388
20500
  }
20501
+ function createOutputTail(maxChars) {
20502
+ let buffered = "";
20503
+ return {
20504
+ append: (chunk) => {
20505
+ buffered = (buffered + chunk).slice(-maxChars);
20506
+ },
20507
+ read: () => buffered.trim()
20508
+ };
20509
+ }
20389
20510
  function asRuntimeItemId(value) {
20390
20511
  return value;
20391
20512
  }
@@ -20533,10 +20654,10 @@ function inputDiffStat(input) {
20533
20654
  let deletions = 0;
20534
20655
  for (const edit of input.edits) {
20535
20656
  if (edit === null || typeof edit !== "object" || Array.isArray(edit)) return void 0;
20536
- const stat = replacementDiffStat(edit, budget);
20537
- if (!stat) return void 0;
20538
- additions += stat.additions;
20539
- deletions += stat.deletions;
20657
+ const stat2 = replacementDiffStat(edit, budget);
20658
+ if (!stat2) return void 0;
20659
+ additions += stat2.additions;
20660
+ deletions += stat2.deletions;
20540
20661
  }
20541
20662
  return { additions, deletions };
20542
20663
  }
@@ -21071,11 +21192,40 @@ function diffClaudeSessionSettings(previous, next) {
21071
21192
  }
21072
21193
  return changed ? patch : void 0;
21073
21194
  }
21074
- var CLAUDE_PERMISSION_MODE_BY_RUNTIME_MODE = {
21195
+ var CLAUDE_NATIVE_MODES = [
21196
+ { id: "default", name: "Manual", description: "Always ask before making changes" },
21197
+ { id: "acceptEdits", name: "Accept edits", description: "Automatically accept all file edits" },
21198
+ { id: "plan", name: "Plan", description: "Create a plan before making changes" },
21199
+ { id: "auto", name: "Auto", description: "Claude handles permission decisions" },
21200
+ {
21201
+ id: "bypassPermissions",
21202
+ name: "Bypass permissions",
21203
+ description: "Accepts all permissions"
21204
+ }
21205
+ ];
21206
+ var CLAUDE_DEFAULT_NATIVE_MODE_ID = "default";
21207
+ var CLAUDE_NATIVE_MODE_BY_RUNTIME_MODE = {
21208
+ "approval-required": "default",
21075
21209
  "auto-accept-edits": "acceptEdits",
21076
21210
  auto: "auto",
21077
21211
  "full-access": "bypassPermissions"
21078
21212
  };
21213
+ var CLAUDE_NATIVE_MODE_REPORT = {
21214
+ nativeModes: CLAUDE_NATIVE_MODES.map((mode) => ({
21215
+ id: mode.id,
21216
+ name: mode.name,
21217
+ description: mode.description
21218
+ })),
21219
+ currentNativeModeId: CLAUDE_DEFAULT_NATIVE_MODE_ID,
21220
+ nativeModeByRuntimeMode: CLAUDE_NATIVE_MODE_BY_RUNTIME_MODE
21221
+ };
21222
+ function isClaudeNativeModeId(value) {
21223
+ return CLAUDE_NATIVE_MODES.some((mode) => mode.id === value);
21224
+ }
21225
+ function claudePermissionModeFor(runtimeMode, nativeModeId) {
21226
+ if (nativeModeId !== void 0 && isClaudeNativeModeId(nativeModeId)) return nativeModeId;
21227
+ return CLAUDE_NATIVE_MODE_BY_RUNTIME_MODE[runtimeMode];
21228
+ }
21079
21229
  var CLAUDE_SETTING_SOURCES = [
21080
21230
  "user",
21081
21231
  "project",
@@ -21213,17 +21363,17 @@ function listTranscriptFiles(transcriptDir, directory) {
21213
21363
  continue;
21214
21364
  }
21215
21365
  for (const name of names) {
21216
- let stat;
21366
+ let stat2;
21217
21367
  try {
21218
- stat = NodeFS3.statSync(NodePath5.join(projectDir, name));
21368
+ stat2 = NodeFS3.statSync(NodePath5.join(projectDir, name));
21219
21369
  } catch {
21220
21370
  continue;
21221
21371
  }
21222
21372
  found.push({
21223
21373
  path: NodePath5.join(projectDir, name),
21224
21374
  sessionId: name.slice(0, -TRANSCRIPT_SUFFIX.length),
21225
- size: stat.size,
21226
- mtimeMs: Number.isFinite(stat.mtimeMs) ? stat.mtimeMs : 0
21375
+ size: stat2.size,
21376
+ mtimeMs: Number.isFinite(stat2.mtimeMs) ? stat2.mtimeMs : 0
21227
21377
  });
21228
21378
  }
21229
21379
  }
@@ -21787,6 +21937,305 @@ function normalizeClaudeTaskProgressTokenUsage(value, memory) {
21787
21937
  };
21788
21938
  }
21789
21939
 
21940
+ // src/probeModels.ts
21941
+ var CONTEXT_WINDOW_TOKENS = {
21942
+ "200k": 2e5,
21943
+ "1m": 1e6
21944
+ };
21945
+ var defaultContextWindowOf = (caps) => {
21946
+ const descriptor = caps.optionDescriptors.find(
21947
+ (entry) => entry.type === "select" && entry.id === "contextWindow"
21948
+ );
21949
+ if (descriptor === void 0) return void 0;
21950
+ const chosen = descriptor.options.find((option) => option.isDefault === true);
21951
+ return chosen === void 0 ? void 0 : CONTEXT_WINDOW_TOKENS[chosen.id];
21952
+ };
21953
+ var toProbeCapabilities = (caps) => ({
21954
+ optionDescriptors: caps.optionDescriptors.map(
21955
+ (descriptor) => descriptor.type === "select" ? {
21956
+ id: descriptor.id,
21957
+ label: descriptor.label,
21958
+ type: "select",
21959
+ options: descriptor.options.map((option) => ({
21960
+ id: option.id,
21961
+ label: option.label,
21962
+ ...option.description === void 0 ? {} : { description: option.description },
21963
+ ...option.isDefault === void 0 ? {} : { isDefault: option.isDefault }
21964
+ }))
21965
+ } : {
21966
+ id: descriptor.id,
21967
+ label: descriptor.label,
21968
+ type: "boolean"
21969
+ }
21970
+ )
21971
+ });
21972
+ var claudeProbeModels = (input) => {
21973
+ const seen = /* @__PURE__ */ new Set();
21974
+ const out = [];
21975
+ for (const model of input.builtIn) {
21976
+ if (seen.has(model.slug)) continue;
21977
+ seen.add(model.slug);
21978
+ const userLabel = input.customModels.find((entry) => entry.slug === model.slug)?.label;
21979
+ const contextWindow = defaultContextWindowOf(model.capabilities);
21980
+ out.push({
21981
+ slug: model.slug,
21982
+ name: userLabel?.trim() || model.name,
21983
+ ...model.isLegacy === void 0 ? {} : { isLegacy: model.isLegacy },
21984
+ capabilities: toProbeCapabilities(model.capabilities),
21985
+ ...contextWindow === void 0 ? {} : { contextWindow }
21986
+ });
21987
+ }
21988
+ for (const model of input.customModels) {
21989
+ if (model.slug.trim().length === 0 || seen.has(model.slug)) continue;
21990
+ seen.add(model.slug);
21991
+ out.push({ slug: model.slug, name: model.label?.trim() || model.slug });
21992
+ }
21993
+ for (const model of input.discovered ?? []) {
21994
+ if (seen.has(model.slug)) continue;
21995
+ seen.add(model.slug);
21996
+ out.push(model);
21997
+ }
21998
+ return out;
21999
+ };
22000
+
22001
+ // src/routedHome.ts
22002
+ import * as NodeFSP from "node:fs/promises";
22003
+ import * as NodePath6 from "node:path";
22004
+
22005
+ // src/routedSettings.ts
22006
+ import { brainSlotEnvKeys } from "@ycodium-ai/plugin-api";
22007
+ var PROXY_ENV_KEYS = [
22008
+ "HTTP_PROXY",
22009
+ "HTTPS_PROXY",
22010
+ "NO_PROXY",
22011
+ "ALL_PROXY",
22012
+ "http_proxy",
22013
+ "https_proxy",
22014
+ "no_proxy",
22015
+ "all_proxy"
22016
+ ];
22017
+ function isRoutedLaunch(launchProcessEnv) {
22018
+ const baseUrlKey = CLAUDE_CARD.modelConnectivity.baseUrlEnvKey;
22019
+ if (baseUrlKey === void 0) return false;
22020
+ return (launchProcessEnv?.[baseUrlKey] ?? "").trim().length > 0;
22021
+ }
22022
+ function routedEnvOverride(input) {
22023
+ if (!isRoutedLaunch(input.launchProcessEnv)) return void 0;
22024
+ const valueOf = (key) => input.mergedEnv[key] ?? "";
22025
+ const override = {};
22026
+ for (const key of brainSlotEnvKeys(CLAUDE_CARD)) override[key] = valueOf(key);
22027
+ for (const { name } of CLAUDE_CARD.modelConnectivity.extraEnv ?? []) {
22028
+ const bound = valueOf(name);
22029
+ override[name] = bound.length === 0 ? "" : claudeCliModelId({
22030
+ boundModelId: bound,
22031
+ placeholder: CLAUDE_CARD.defaultModel,
22032
+ ...input.customModels === void 0 ? {} : { customModels: input.customModels }
22033
+ });
22034
+ }
22035
+ for (const key of PROXY_ENV_KEYS) override[key] = valueOf(key);
22036
+ return override;
22037
+ }
22038
+
22039
+ // src/routedHome.ts
22040
+ var BORROWED_ENTRY_NAMES = [
22041
+ "projects",
22042
+ "plugins",
22043
+ "skills",
22044
+ "agents",
22045
+ "commands",
22046
+ "CLAUDE.md"
22047
+ ];
22048
+ var GLOBAL_CONFIG_FILE_NAME = ".claude.json";
22049
+ var USER_SETTINGS_FILE_NAME = "settings.json";
22050
+ var ROUTING_AND_IDENTITY_SETTING_KEYS = [
22051
+ "env",
22052
+ "apiKeyHelper",
22053
+ "awsAuthRefresh",
22054
+ "awsCredentialExport"
22055
+ ];
22056
+ var FIRST_RUN_FLAG_KEYS = ["hasCompletedOnboarding", "bypassPermissionsModeAccepted"];
22057
+ function isRecord5(value) {
22058
+ return typeof value === "object" && value !== null && !Array.isArray(value);
22059
+ }
22060
+ async function readTextFile(filePath) {
22061
+ try {
22062
+ return await NodeFSP.readFile(filePath, "utf8");
22063
+ } catch {
22064
+ return null;
22065
+ }
22066
+ }
22067
+ function parseJsonObject(raw) {
22068
+ try {
22069
+ const parsed = JSON.parse(raw);
22070
+ return isRecord5(parsed) ? parsed : null;
22071
+ } catch {
22072
+ return null;
22073
+ }
22074
+ }
22075
+ async function writeFileAtomic(filePath, contents) {
22076
+ const tempPath = `${filePath}.${crypto.randomUUID()}.tmp`;
22077
+ await NodeFSP.writeFile(tempPath, contents, { encoding: "utf8", mode: 384 });
22078
+ await NodeFSP.rename(tempPath, filePath);
22079
+ }
22080
+ async function readLinkState(linkPath) {
22081
+ try {
22082
+ return { _tag: "Symlink", target: await NodeFSP.readlink(linkPath) };
22083
+ } catch (error) {
22084
+ const code = typeof error === "object" && error !== null && "code" in error ? String(error.code) : "";
22085
+ if (code === "ENOENT") return { _tag: "Missing" };
22086
+ return { _tag: "NotSymlink" };
22087
+ }
22088
+ }
22089
+ async function isEmptyDirectory(path) {
22090
+ try {
22091
+ return (await NodeFSP.readdir(path)).length === 0;
22092
+ } catch {
22093
+ return false;
22094
+ }
22095
+ }
22096
+ async function borrowEntry(input) {
22097
+ const target = NodePath6.join(input.userConfigDirPath, input.entryName);
22098
+ const targetInfo = await NodeFSP.stat(target).then(
22099
+ (info) => info,
22100
+ () => null
22101
+ );
22102
+ if (targetInfo === null) return;
22103
+ const link = NodePath6.join(input.configDirPath, input.entryName);
22104
+ const state = await readLinkState(link);
22105
+ if (state._tag === "NotSymlink") {
22106
+ if (!await isEmptyDirectory(link)) {
22107
+ input.log?.warn("Claude routed home entry left unlinked: a real entry is already there.", {
22108
+ entryName: input.entryName,
22109
+ link
22110
+ });
22111
+ return;
22112
+ }
22113
+ await NodeFSP.rm(link, { recursive: true, force: true });
22114
+ await NodeFSP.symlink(target, link, targetInfo.isDirectory() ? "junction" : "file");
22115
+ return;
22116
+ }
22117
+ if (state._tag === "Symlink") {
22118
+ if (NodePath6.resolve(NodePath6.dirname(link), state.target) === target) return;
22119
+ await NodeFSP.rm(link, { force: true });
22120
+ }
22121
+ await NodeFSP.symlink(target, link, targetInfo.isDirectory() ? "junction" : "file");
22122
+ }
22123
+ async function mirrorUserSettings(input) {
22124
+ const sourcePath = NodePath6.join(input.userConfigDirPath, USER_SETTINGS_FILE_NAME);
22125
+ const raw = await readTextFile(sourcePath);
22126
+ if (raw === null) return;
22127
+ const parsed = parseJsonObject(raw);
22128
+ if (parsed === null) {
22129
+ input.log?.warn(
22130
+ "Claude routed home settings mirror skipped: the source is not a JSON object.",
22131
+ {
22132
+ sourcePath
22133
+ }
22134
+ );
22135
+ return;
22136
+ }
22137
+ const mirrored = { ...parsed };
22138
+ for (const key of ROUTING_AND_IDENTITY_SETTING_KEYS) delete mirrored[key];
22139
+ const targetPath = NodePath6.join(input.configDirPath, USER_SETTINGS_FILE_NAME);
22140
+ const desired = `${JSON.stringify(mirrored, null, 2)}
22141
+ `;
22142
+ if (await readTextFile(targetPath) === desired) return;
22143
+ await writeFileAtomic(targetPath, desired);
22144
+ }
22145
+ async function seedFirstRun(input) {
22146
+ const filePath = NodePath6.join(input.configDirPath, GLOBAL_CONFIG_FILE_NAME);
22147
+ const raw = await readTextFile(filePath);
22148
+ const parsed = raw === null ? {} : parseJsonObject(raw);
22149
+ if (parsed === null) {
22150
+ input.log?.warn("Claude routed home first-run seed skipped: the config file is not readable.", {
22151
+ filePath
22152
+ });
22153
+ return;
22154
+ }
22155
+ const next = { ...parsed };
22156
+ let changed = false;
22157
+ if (raw === null) {
22158
+ for (const key of FIRST_RUN_FLAG_KEYS) next[key] = true;
22159
+ changed = true;
22160
+ }
22161
+ const existingProjects = next["projects"];
22162
+ const projects = isRecord5(existingProjects) ? { ...existingProjects } : {};
22163
+ for (const trustedPath of input.trustedPaths) {
22164
+ const resolved = NodePath6.resolve(trustedPath);
22165
+ const entry = projects[resolved];
22166
+ const project = isRecord5(entry) ? { ...entry } : {};
22167
+ if (project["hasTrustDialogAccepted"] === true) continue;
22168
+ project["hasTrustDialogAccepted"] = true;
22169
+ projects[resolved] = project;
22170
+ changed = true;
22171
+ }
22172
+ if (Object.keys(projects).length > 0) next["projects"] = projects;
22173
+ if (!changed) return;
22174
+ await writeFileAtomic(filePath, `${JSON.stringify(next, null, 2)}
22175
+ `);
22176
+ }
22177
+ function resolveClaudeLaunchHome(input) {
22178
+ const routed = isRoutedLaunch(input.launchProcessEnv);
22179
+ const dataDirPath = input.pluginDataDirPath?.trim() ?? "";
22180
+ if (routed && dataDirPath.length === 0) {
22181
+ input.log?.warn(
22182
+ "Ycodium is routing this turn but has no directory of its own to hand the CLI; it will read the user's."
22183
+ );
22184
+ }
22185
+ return resolveClaudeHomeLayout({
22186
+ homePath: input.homePath,
22187
+ credentialsPath: input.credentialsPath,
22188
+ ...routed && dataDirPath.length > 0 ? { routedHomePath: claudeRoutedHomePath(dataDirPath) } : {},
22189
+ ...input.environment === void 0 ? {} : { environment: input.environment }
22190
+ });
22191
+ }
22192
+ async function materializeClaudeRoutedHome(layout, options) {
22193
+ if (layout.mode !== "routed") return;
22194
+ const configDirPath = layout.configDirPath;
22195
+ if (configDirPath === void 0) return;
22196
+ if (configDirPath === layout.userConfigDirPath) {
22197
+ throw new Error(
22198
+ `Ycodium's Claude configuration directory '${configDirPath}' must differ from the user's.`
22199
+ );
22200
+ }
22201
+ await NodeFSP.mkdir(configDirPath, { recursive: true });
22202
+ await NodeFSP.chmod(configDirPath, 448);
22203
+ const credentialsDirPath = layout.credentialsDirPath;
22204
+ if (credentialsDirPath !== void 0 && credentialsDirPath !== configDirPath) {
22205
+ await NodeFSP.mkdir(credentialsDirPath, { recursive: true });
22206
+ await NodeFSP.chmod(credentialsDirPath, 448);
22207
+ }
22208
+ const log = options?.log;
22209
+ for (const entryName of BORROWED_ENTRY_NAMES) {
22210
+ try {
22211
+ await borrowEntry({
22212
+ userConfigDirPath: layout.userConfigDirPath,
22213
+ configDirPath,
22214
+ entryName,
22215
+ log
22216
+ });
22217
+ } catch (error) {
22218
+ log?.warn("Claude routed home entry left unlinked.", {
22219
+ entryName,
22220
+ reason: error instanceof Error ? error.message : String(error)
22221
+ });
22222
+ }
22223
+ }
22224
+ await mirrorUserSettings({
22225
+ userConfigDirPath: layout.userConfigDirPath,
22226
+ configDirPath,
22227
+ log
22228
+ });
22229
+ await seedFirstRun({
22230
+ configDirPath,
22231
+ trustedPaths: options?.trustedPaths ?? [],
22232
+ log
22233
+ });
22234
+ }
22235
+
22236
+ // src/textGeneration.ts
22237
+ import { mergeLaunchEnv, brainSlotEnvKeys as brainSlotEnvKeys2 } from "@ycodium-ai/plugin-api/executor";
22238
+
21790
22239
  // src/textGenerationUtils.ts
21791
22240
  function limitSection(value, maxChars) {
21792
22241
  if (value.length <= maxChars) return value;
@@ -22134,7 +22583,7 @@ function buildHandoverSummaryPrompt(input) {
22134
22583
 
22135
22584
  // src/textGeneration.ts
22136
22585
  var CLAUDE_TIMEOUT_MS = 18e4;
22137
- function isRecord5(value) {
22586
+ function isRecord6(value) {
22138
22587
  return typeof value === "object" && value !== null && !Array.isArray(value);
22139
22588
  }
22140
22589
  function readString3(value) {
@@ -22156,18 +22605,31 @@ function decodeStructuredOutput(stdout, operation) {
22156
22605
  cause: error
22157
22606
  });
22158
22607
  }
22159
- if (!isRecord5(parsed) || !("structured_output" in parsed)) {
22608
+ if (!isRecord6(parsed) || !("structured_output" in parsed)) {
22160
22609
  throw new Error(`${operation}: Claude CLI returned unexpected output format.`);
22161
22610
  }
22162
22611
  return parsed.structured_output;
22163
22612
  }
22164
- function createClaudeTextGeneration(recipe, host) {
22613
+ function createClaudeTextGeneration(recipe, host, pluginDataDirPath) {
22165
22614
  const runClaudeJson = async (input) => {
22166
- const processEnv = {
22167
- ...makeClaudeEnvironment(recipe, input.launch?.processEnv ?? process.env),
22168
- ...compactEnv(input.launch?.processEnv)
22169
- };
22170
- const modelSelection = isRecord5(input.modelSelection) ? input.modelSelection : {};
22615
+ const homeLayout = resolveClaudeLaunchHome({
22616
+ homePath: recipe.homePath,
22617
+ credentialsPath: recipe.credentialsPath,
22618
+ launchProcessEnv: input.launch?.processEnv,
22619
+ pluginDataDirPath,
22620
+ log: host.log
22621
+ });
22622
+ await materializeClaudeRoutedHome(homeLayout, {
22623
+ trustedPaths: [input.cwd],
22624
+ log: host.log
22625
+ });
22626
+ const processEnv = mergeLaunchEnv({
22627
+ brainSlots: brainSlotEnvKeys2(CLAUDE_CARD),
22628
+ instanceTable: recipe.environment,
22629
+ handOverrides: claudeHomeVariables(homeLayout),
22630
+ launch: input.launch?.processEnv
22631
+ });
22632
+ const modelSelection = isRecord6(input.modelSelection) ? input.modelSelection : {};
22171
22633
  const requestedModel = input.launch?.requestedModelId ?? modelIdFromSelection(modelSelection) ?? "claude-haiku-4-5";
22172
22634
  const caps = claudeModelCapabilities(requestedModel, recipe.customModels);
22173
22635
  const rawEffort = getModelSelectionStringOptionValue(modelSelection, "effort");
@@ -22200,7 +22662,7 @@ function createClaudeTextGeneration(recipe, host) {
22200
22662
  program,
22201
22663
  args,
22202
22664
  cwd: input.cwd,
22203
- env: compactEnv(processEnv),
22665
+ env: processEnv,
22204
22666
  owner: { bindingId: host.bindingId }
22205
22667
  });
22206
22668
  const timeout = AbortSignal.timeout(CLAUDE_TIMEOUT_MS);
@@ -22229,7 +22691,7 @@ function createClaudeTextGeneration(recipe, host) {
22229
22691
  return decodeStructuredOutput(stdout, input.operation);
22230
22692
  };
22231
22693
  const asRecord = (value, operation) => {
22232
- if (!isRecord5(value))
22694
+ if (!isRecord6(value))
22233
22695
  throw new Error(`${operation}: Claude returned invalid structured output.`);
22234
22696
  return value;
22235
22697
  };
@@ -22243,7 +22705,7 @@ function createClaudeTextGeneration(recipe, host) {
22243
22705
  stagedSummary: readString3(input["stagedSummary"]),
22244
22706
  stagedPatch: readString3(input["stagedPatch"]),
22245
22707
  includeBranch,
22246
- ...isRecord5(input["policy"]) ? { policy: input["policy"] } : {}
22708
+ ...isRecord6(input["policy"]) ? { policy: input["policy"] } : {}
22247
22709
  });
22248
22710
  const generated = asRecord(
22249
22711
  await runClaudeJson({
@@ -22252,7 +22714,7 @@ function createClaudeTextGeneration(recipe, host) {
22252
22714
  prompt,
22253
22715
  outputSchema,
22254
22716
  modelSelection: input["modelSelection"],
22255
- ...isRecord5(input["launch"]) ? { launch: input["launch"] } : {},
22717
+ ...isRecord6(input["launch"]) ? { launch: input["launch"] } : {},
22256
22718
  ...signal === void 0 ? {} : { signal }
22257
22719
  }),
22258
22720
  "generateCommitMessage"
@@ -22276,7 +22738,7 @@ function createClaudeTextGeneration(recipe, host) {
22276
22738
  diffSummary: readString3(input["diffSummary"]),
22277
22739
  diffPatch: readString3(input["diffPatch"]),
22278
22740
  ...typeof input["changeRequestTemplate"] === "string" ? { changeRequestTemplate: input["changeRequestTemplate"] } : {},
22279
- ...isRecord5(input["policy"]) ? { policy: input["policy"] } : {}
22741
+ ...isRecord6(input["policy"]) ? { policy: input["policy"] } : {}
22280
22742
  });
22281
22743
  const generated = asRecord(
22282
22744
  await runClaudeJson({
@@ -22285,7 +22747,7 @@ function createClaudeTextGeneration(recipe, host) {
22285
22747
  prompt,
22286
22748
  outputSchema,
22287
22749
  modelSelection: input["modelSelection"],
22288
- ...isRecord5(input["launch"]) ? { launch: input["launch"] } : {},
22750
+ ...isRecord6(input["launch"]) ? { launch: input["launch"] } : {},
22289
22751
  ...signal === void 0 ? {} : { signal }
22290
22752
  }),
22291
22753
  "generatePrContent"
@@ -22304,7 +22766,7 @@ function createClaudeTextGeneration(recipe, host) {
22304
22766
  const { prompt, outputSchema } = buildBranchNamePrompt({
22305
22767
  message: readString3(input["message"]),
22306
22768
  ...Array.isArray(input["attachments"]) ? { attachments: input["attachments"] } : {},
22307
- ...isRecord5(input["policy"]) ? { policy: input["policy"] } : {}
22769
+ ...isRecord6(input["policy"]) ? { policy: input["policy"] } : {}
22308
22770
  });
22309
22771
  const generated = asRecord(
22310
22772
  await runClaudeJson({
@@ -22313,7 +22775,7 @@ function createClaudeTextGeneration(recipe, host) {
22313
22775
  prompt,
22314
22776
  outputSchema,
22315
22777
  modelSelection: input["modelSelection"],
22316
- ...isRecord5(input["launch"]) ? { launch: input["launch"] } : {},
22778
+ ...isRecord6(input["launch"]) ? { launch: input["launch"] } : {},
22317
22779
  ...signal === void 0 ? {} : { signal }
22318
22780
  }),
22319
22781
  "generateBranchName"
@@ -22330,7 +22792,7 @@ function createClaudeTextGeneration(recipe, host) {
22330
22792
  message: readString3(input["message"]),
22331
22793
  ...typeof input["previousTitle"] === "string" ? { previousTitle: input["previousTitle"] } : {},
22332
22794
  ...Array.isArray(input["attachments"]) ? { attachments: input["attachments"] } : {},
22333
- ...isRecord5(input["policy"]) ? { policy: input["policy"] } : {}
22795
+ ...isRecord6(input["policy"]) ? { policy: input["policy"] } : {}
22334
22796
  });
22335
22797
  const generated = asRecord(
22336
22798
  await runClaudeJson({
@@ -22339,7 +22801,7 @@ function createClaudeTextGeneration(recipe, host) {
22339
22801
  prompt,
22340
22802
  outputSchema,
22341
22803
  modelSelection: input["modelSelection"],
22342
- ...isRecord5(input["launch"]) ? { launch: input["launch"] } : {},
22804
+ ...isRecord6(input["launch"]) ? { launch: input["launch"] } : {},
22343
22805
  ...signal === void 0 ? {} : { signal }
22344
22806
  }),
22345
22807
  "generateThreadTitle"
@@ -22362,7 +22824,7 @@ function createClaudeTextGeneration(recipe, host) {
22362
22824
  prompt,
22363
22825
  outputSchema,
22364
22826
  modelSelection: input["modelSelection"],
22365
- ...isRecord5(input["launch"]) ? { launch: input["launch"] } : {},
22827
+ ...isRecord6(input["launch"]) ? { launch: input["launch"] } : {},
22366
22828
  ...signal === void 0 ? {} : { signal }
22367
22829
  }),
22368
22830
  "summarizeHandover"
@@ -22374,14 +22836,6 @@ function createClaudeTextGeneration(recipe, host) {
22374
22836
  }
22375
22837
  };
22376
22838
  }
22377
- function compactEnv(env) {
22378
- const out = {};
22379
- if (!env) return out;
22380
- for (const [key, value] of Object.entries(env)) {
22381
- if (value !== void 0) out[key] = value;
22382
- }
22383
- return out;
22384
- }
22385
22839
 
22386
22840
  // src/imageMime.ts
22387
22841
  var IMAGE_EXTENSION_BY_MIME_TYPE = {
@@ -22463,9 +22917,9 @@ var PromptQueue = class {
22463
22917
  const queued = this.#queued.shift();
22464
22918
  if (queued !== void 0) return Promise.resolve(queued);
22465
22919
  if (this.#closed) return Promise.resolve(void 0);
22466
- return new Promise((resolve2) => {
22920
+ return new Promise((resolve3) => {
22467
22921
  this.#waiters.push((result) => {
22468
- resolve2(result.done ? void 0 : result.value);
22922
+ resolve3(result.done ? void 0 : result.value);
22469
22923
  });
22470
22924
  });
22471
22925
  }
@@ -22496,6 +22950,7 @@ var SUPPORTED_CLAUDE_IMAGE_MIME_TYPES = /* @__PURE__ */ new Set([
22496
22950
  ]);
22497
22951
  var CLAUDE_INTERRUPT_GRACE_MS = 5e3;
22498
22952
  var CLAUDE_SESSION_CONTEXT_WAIT_MS = 1e4;
22953
+ var CLAUDE_STDERR_TAIL_CHARS = 4e3;
22499
22954
  function asClaudeModelSelection(value) {
22500
22955
  if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
22501
22956
  return value;
@@ -22583,7 +23038,7 @@ async function persistProducedImage(input) {
22583
23038
  }
22584
23039
 
22585
23040
  // src/claudeInstance.ts
22586
- function compactEnv2(env) {
23041
+ function compactEnv(env) {
22587
23042
  const out = {};
22588
23043
  if (!env) return out;
22589
23044
  for (const [key, value] of Object.entries(env)) {
@@ -22591,10 +23046,26 @@ function compactEnv2(env) {
22591
23046
  }
22592
23047
  return out;
22593
23048
  }
23049
+ function toClaudeMcpServers(entries) {
23050
+ if (entries === void 0 || entries.length === 0) return void 0;
23051
+ const servers = {};
23052
+ for (const entry of entries) {
23053
+ servers[entry.name] = entry.transport.type === "stdio" ? {
23054
+ type: "stdio",
23055
+ command: entry.transport.command,
23056
+ ...entry.transport.args === void 0 ? {} : { args: [...entry.transport.args] },
23057
+ ...entry.transport.env === void 0 ? {} : { env: { ...entry.transport.env } }
23058
+ } : {
23059
+ type: entry.transport.type,
23060
+ url: entry.transport.url,
23061
+ ...entry.transport.headers === void 0 ? {} : { headers: { ...entry.transport.headers } }
23062
+ };
23063
+ }
23064
+ return servers;
23065
+ }
22594
23066
  function createClaudeAdapter(recipe, host, options) {
22595
23067
  const boundInstanceId = options?.instanceId ?? host.instanceId ?? "claudeAgent";
22596
23068
  const claudeHomeEnvironment = makeClaudeEnvironment(recipe, options?.environment);
22597
- const sessionLaunchEnv = /* @__PURE__ */ new Map();
22598
23069
  const claudeSdkExecutablePath = resolveClaudeSdkExecutablePath(
22599
23070
  recipe.binaryPath,
22600
23071
  claudeHomeEnvironment
@@ -22886,8 +23357,8 @@ function createClaudeAdapter(recipe, host, options) {
22886
23357
  try {
22887
23358
  usage = await Promise.race([
22888
23359
  context.query.getContextUsage(),
22889
- new Promise((resolve2) => {
22890
- setTimeout(() => resolve2(void 0), 1e3);
23360
+ new Promise((resolve3) => {
23361
+ setTimeout(() => resolve3(void 0), 1e3);
22891
23362
  })
22892
23363
  ]);
22893
23364
  } catch {
@@ -24180,7 +24651,9 @@ function createClaudeAdapter(recipe, host, options) {
24180
24651
  await completeTurn(context, "interrupted", "Claude runtime interrupted.");
24181
24652
  }
24182
24653
  } else {
24183
- const message = cause instanceof Error ? cause.message : "Claude runtime stream failed.";
24654
+ const cliSaid = context.stderrTail.read();
24655
+ const reason = cause instanceof Error ? cause.message : "Claude runtime stream failed.";
24656
+ const message = cliSaid.length > 0 ? `${reason}: ${cliSaid}` : reason;
24184
24657
  await emitRuntimeError(context, message, { cause: String(cause) });
24185
24658
  await completeTurn(context, "failed", message);
24186
24659
  }
@@ -24256,7 +24729,7 @@ function createClaudeAdapter(recipe, host, options) {
24256
24729
  if (streamFiber) {
24257
24730
  await Promise.race([
24258
24731
  streamFiber.catch(() => void 0),
24259
- new Promise((resolve2) => setTimeout(resolve2, 1e3))
24732
+ new Promise((resolve3) => setTimeout(resolve3, 1e3))
24260
24733
  ]);
24261
24734
  }
24262
24735
  const updatedAt = nowIso();
@@ -24283,9 +24756,6 @@ function createClaudeAdapter(recipe, host, options) {
24283
24756
  }
24284
24757
  if (sessions.get(context.session.threadId) === context) {
24285
24758
  sessions.delete(context.session.threadId);
24286
- if (stopOptions?.keepLaunch !== true) {
24287
- sessionLaunchEnv.delete(context.session.threadId);
24288
- }
24289
24759
  }
24290
24760
  })();
24291
24761
  context.stopWork = work;
@@ -24365,7 +24835,13 @@ function createClaudeAdapter(recipe, host, options) {
24365
24835
  const modelSelection = boundModelSelection(input.modelSelection, boundInstanceId);
24366
24836
  const caps = claudeModelCapabilities(modelSelection?.model, recipe.customModels);
24367
24837
  const descriptors = caps.optionDescriptors;
24368
- const apiModelId = modelSelection?.model ? resolveClaudeApiModelId(caps, { model: modelSelection.model }) : void 0;
24838
+ const apiModelId = modelSelection?.model ? resolveClaudeApiModelId(caps, {
24839
+ model: claudeCliModelId({
24840
+ boundModelId: modelSelection.model,
24841
+ placeholder: CLAUDE_CARD.defaultModel,
24842
+ customModels: recipe.customModels
24843
+ })
24844
+ }) : void 0;
24369
24845
  const initialContextWindow = selectedClaudeContextWindow(caps, modelSelection);
24370
24846
  const rawEffort = getModelSelectionStringOptionValue(modelSelection, "effort");
24371
24847
  const effort = resolveClaudeEffort(caps, rawEffort) ?? null;
@@ -24379,16 +24855,13 @@ function createClaudeAdapter(recipe, host, options) {
24379
24855
  const thinking = thinkingSupported ? getModelSelectionBooleanOptionValue(modelSelection, "thinking") : void 0;
24380
24856
  const ultracode = isClaudeUltracodeEffort(effort);
24381
24857
  const effectiveEffort = getEffectiveClaudeAgentEffort(effort, modelSelection?.model, caps);
24382
- const permissionMode = CLAUDE_PERMISSION_MODE_BY_RUNTIME_MODE[input.runtimeMode];
24858
+ const permissionMode = claudePermissionModeFor(input.runtimeMode, input.nativeModeId);
24383
24859
  const settings = buildClaudeSessionSettings({
24384
24860
  thinking,
24385
24861
  fastMode,
24386
24862
  ultracode,
24387
24863
  autoCompactWindow: recipe.autoCompactWindow
24388
24864
  });
24389
- if (input.launch.processEnv) {
24390
- sessionLaunchEnv.set(input.threadId, input.launch.processEnv);
24391
- }
24392
24865
  const existingContext = sessions.get(input.threadId);
24393
24866
  if (existingContext) {
24394
24867
  const reconfigured = await reconfigureSession(existingContext, input, {
@@ -24407,10 +24880,7 @@ function createClaudeAdapter(recipe, host, options) {
24407
24880
  existingSessionStatus: existingContext.session.status,
24408
24881
  reason: "startSession called with existing active session"
24409
24882
  });
24410
- await stopSessionInternal(existingContext, {
24411
- emitExitEvent: false,
24412
- keepLaunch: true
24413
- });
24883
+ await stopSessionInternal(existingContext, { emitExitEvent: false });
24414
24884
  }
24415
24885
  const startedAt = nowIso();
24416
24886
  const resumeState = input.discardResumeCursor === true ? void 0 : readClaudeResumeState(input.resumeCursor);
@@ -24433,7 +24903,7 @@ function createClaudeAdapter(recipe, host, options) {
24433
24903
  return await Promise.race([
24434
24904
  contextDeferred.promise,
24435
24905
  new Promise(
24436
- (resolve2) => setTimeout(() => resolve2(void 0), CLAUDE_SESSION_CONTEXT_WAIT_MS)
24906
+ (resolve3) => setTimeout(() => resolve3(void 0), CLAUDE_SESSION_CONTEXT_WAIT_MS)
24437
24907
  )
24438
24908
  ]);
24439
24909
  } catch {
@@ -24575,7 +25045,7 @@ function createClaudeAdapter(recipe, host, options) {
24575
25045
  message: "The client captured your proposed plan. Stop here and wait for the user's feedback or implementation request in a later turn."
24576
25046
  };
24577
25047
  }
24578
- if (context2.session.runtimeMode === "full-access") {
25048
+ if (context2.currentPermissionMode === "bypassPermissions") {
24579
25049
  return {
24580
25050
  behavior: "allow",
24581
25051
  updatedInput: toolInput
@@ -24716,16 +25186,33 @@ function createClaudeAdapter(recipe, host, options) {
24716
25186
  };
24717
25187
  const onUserDialog = (request, callbackOptions) => handleResumeDialog(request, callbackOptions);
24718
25188
  const additionalDirectories = input.cwd ? [input.cwd] : [];
24719
- const launchEnvironment = {
24720
- ...makeClaudeEnvironment(recipe, {
24721
- ...process.env,
24722
- ...options?.environment,
24723
- ...recipe.environment,
24724
- ...sessionLaunchEnv.get(threadId)
24725
- }),
24726
- ...compactEnv2(input.launch.processEnv)
24727
- };
24728
- const querySettings = settings;
25189
+ const mcpServers = toClaudeMcpServers(input.mcpServers);
25190
+ const homeLayout = resolveClaudeLaunchHome({
25191
+ homePath: recipe.homePath,
25192
+ credentialsPath: recipe.credentialsPath,
25193
+ launchProcessEnv: input.launch.processEnv,
25194
+ pluginDataDirPath: options?.pluginDataDirPath,
25195
+ environment: claudeHomeEnvironment,
25196
+ ...options?.log === void 0 ? {} : { log: options.log }
25197
+ });
25198
+ await materializeClaudeRoutedHome(homeLayout, {
25199
+ ...input.cwd ? { trustedPaths: [input.cwd] } : {},
25200
+ ...options?.log === void 0 ? {} : { log: options.log }
25201
+ });
25202
+ const launchEnvironment = mergeLaunchEnv2({
25203
+ brainSlots: brainSlotEnvKeys3(CLAUDE_CARD),
25204
+ inherited: { ...process.env, ...options?.environment },
25205
+ instanceTable: recipe.environment,
25206
+ handOverrides: claudeHomeVariables(homeLayout),
25207
+ launch: input.launch.processEnv
25208
+ });
25209
+ const envOverride = routedEnvOverride({
25210
+ launchProcessEnv: input.launch.processEnv,
25211
+ mergedEnv: launchEnvironment,
25212
+ ...recipe.customModels === void 0 ? {} : { customModels: recipe.customModels }
25213
+ });
25214
+ const querySettings = envOverride === void 0 ? { ...settings } : { ...settings, env: envOverride };
25215
+ const stderrTail = createOutputTail(CLAUDE_STDERR_TAIL_CHARS);
24729
25216
  const queryOptions = {
24730
25217
  ...input.cwd ? { cwd: input.cwd } : {},
24731
25218
  ...apiModelId ? { model: apiModelId } : {},
@@ -24741,6 +25228,7 @@ function createClaudeAdapter(recipe, host, options) {
24741
25228
  ...existingResumeSessionId ? { resume: existingResumeSessionId } : {},
24742
25229
  ...newSessionId ? { sessionId: newSessionId } : {},
24743
25230
  includePartialMessages: true,
25231
+ stderr: stderrTail.append,
24744
25232
  canUseTool,
24745
25233
  onUserDialog,
24746
25234
  hooks: {
@@ -24758,9 +25246,10 @@ function createClaudeAdapter(recipe, host, options) {
24758
25246
  ]
24759
25247
  },
24760
25248
  supportedDialogKinds: ["resume_return"],
24761
- env: compactEnv2(launchEnvironment),
25249
+ env: compactEnv(launchEnvironment),
24762
25250
  additionalDirectories,
24763
- ...Object.keys(extraArgs).length > 0 ? { extraArgs } : {}
25251
+ ...Object.keys(extraArgs).length > 0 ? { extraArgs } : {},
25252
+ ...mcpServers === void 0 ? {} : { mcpServers }
24764
25253
  };
24765
25254
  let queryRuntime;
24766
25255
  try {
@@ -24777,6 +25266,7 @@ function createClaudeAdapter(recipe, host, options) {
24777
25266
  providerInstanceId: boundInstanceId,
24778
25267
  status: "ready",
24779
25268
  runtimeMode: input.runtimeMode,
25269
+ nativeModeId: permissionMode,
24780
25270
  ...input.cwd ? { cwd: input.cwd } : {},
24781
25271
  ...modelSelection?.model ? { model: modelSelection.model } : {},
24782
25272
  resumeCursor: {
@@ -24796,6 +25286,7 @@ function createClaudeAdapter(recipe, host, options) {
24796
25286
  },
24797
25287
  promptQueue,
24798
25288
  query: queryRuntime,
25289
+ stderrTail,
24799
25290
  streamFiber: void 0,
24800
25291
  startedAt,
24801
25292
  basePermissionMode: permissionMode,
@@ -24904,7 +25395,13 @@ function createClaudeAdapter(recipe, host, options) {
24904
25395
  }
24905
25396
  if (modelSelection?.model) {
24906
25397
  const turnCaps = claudeModelCapabilities(modelSelection.model, recipe.customModels);
24907
- const apiModelId = resolveClaudeApiModelId(turnCaps, { model: modelSelection.model });
25398
+ const apiModelId = resolveClaudeApiModelId(turnCaps, {
25399
+ model: claudeCliModelId({
25400
+ boundModelId: modelSelection.model,
25401
+ placeholder: CLAUDE_CARD.defaultModel,
25402
+ customModels: recipe.customModels
25403
+ })
25404
+ });
24908
25405
  if (context.currentApiModelId !== apiModelId) {
24909
25406
  try {
24910
25407
  await context.query.setModel(apiModelId);
@@ -25002,7 +25499,7 @@ function createClaudeAdapter(recipe, host, options) {
25002
25499
  context.query.interrupt().then(() => settled.promise).then(() => {
25003
25500
  acknowledged = true;
25004
25501
  }),
25005
- new Promise((resolve2) => setTimeout(resolve2, CLAUDE_INTERRUPT_GRACE_MS))
25502
+ new Promise((resolve3) => setTimeout(resolve3, CLAUDE_INTERRUPT_GRACE_MS))
25006
25503
  ]);
25007
25504
  } catch (cause) {
25008
25505
  throw toRequestError(threadId, "turn/interrupt", cause);
@@ -25037,22 +25534,49 @@ function createClaudeAdapter(recipe, host, options) {
25037
25534
  if (context.session.runtimeMode === runtimeMode) {
25038
25535
  return;
25039
25536
  }
25040
- const permissionMode = CLAUDE_PERMISSION_MODE_BY_RUNTIME_MODE[runtimeMode];
25537
+ const permissionMode = claudePermissionModeFor(runtimeMode);
25041
25538
  context.basePermissionMode = permissionMode;
25042
25539
  context.session = {
25043
25540
  ...context.session,
25044
25541
  runtimeMode,
25542
+ nativeModeId: permissionMode,
25045
25543
  updatedAt: nowIso()
25046
25544
  };
25047
25545
  if (context.currentPermissionMode !== "plan") {
25048
25546
  try {
25049
- await context.query.setPermissionMode(permissionMode ?? "default");
25547
+ await context.query.setPermissionMode(permissionMode);
25050
25548
  } catch (cause) {
25051
25549
  throw toRequestError(threadId, "session/setPermissionMode", cause);
25052
25550
  }
25053
25551
  context.currentPermissionMode = permissionMode;
25054
25552
  }
25055
- if (runtimeMode !== "full-access") {
25553
+ if (permissionMode !== "bypassPermissions") {
25554
+ return;
25555
+ }
25556
+ for (const [requestId, pending] of context.pendingApprovals) {
25557
+ context.pendingApprovals.delete(requestId);
25558
+ pending.decision.resolve({ decision: "accept" });
25559
+ }
25560
+ };
25561
+ const setNativeMode = async (threadId, nativeModeId) => {
25562
+ const context = requireSession(threadId);
25563
+ if (!isClaudeNativeModeId(nativeModeId)) return;
25564
+ if (context.currentPermissionMode === nativeModeId) return;
25565
+ context.basePermissionMode = nativeModeId;
25566
+ context.session = {
25567
+ ...context.session,
25568
+ nativeModeId,
25569
+ updatedAt: nowIso()
25570
+ };
25571
+ if (context.currentPermissionMode !== "plan" || nativeModeId === "plan") {
25572
+ try {
25573
+ await context.query.setPermissionMode(nativeModeId);
25574
+ } catch (cause) {
25575
+ throw toRequestError(threadId, "session/setPermissionMode", cause);
25576
+ }
25577
+ context.currentPermissionMode = nativeModeId;
25578
+ }
25579
+ if (nativeModeId !== "bypassPermissions") {
25056
25580
  return;
25057
25581
  }
25058
25582
  for (const [requestId, pending] of context.pendingApprovals) {
@@ -25085,6 +25609,7 @@ function createClaudeAdapter(recipe, host, options) {
25085
25609
  respondToRequest,
25086
25610
  respondToUserInput,
25087
25611
  setRuntimeMode,
25612
+ setNativeMode,
25088
25613
  stopSession,
25089
25614
  events: runtimeEventQueue,
25090
25615
  dispose: async () => {
@@ -25095,12 +25620,19 @@ function createClaudeAdapter(recipe, host, options) {
25095
25620
  }
25096
25621
  };
25097
25622
  }
25098
- function createClaudeInstance(config, host, attachments) {
25623
+ function claudeAdapterBaseEnvironment(recipe) {
25624
+ return {
25625
+ ...process.env,
25626
+ ...withoutBrainSlots(recipe.environment, brainSlotEnvKeys3(CLAUDE_CARD))
25627
+ };
25628
+ }
25629
+ function createClaudeInstance(config, host, attachments, pluginDataDirPath) {
25099
25630
  const recipe = parseClaudeInstanceConfig(config);
25100
25631
  const adapter = createClaudeAdapter(recipe, host, {
25101
25632
  instanceId: host.instanceId,
25102
25633
  ...attachments === void 0 ? {} : { attachments },
25103
- environment: { ...process.env, ...recipe.environment },
25634
+ ...pluginDataDirPath === void 0 ? {} : { pluginDataDirPath },
25635
+ environment: claudeAdapterBaseEnvironment(recipe),
25104
25636
  log: host.log
25105
25637
  });
25106
25638
  const collectProcessText = async (stream) => {
@@ -25111,6 +25643,15 @@ function createClaudeInstance(config, host, attachments) {
25111
25643
  return Buffer.concat(chunks).toString("utf8");
25112
25644
  };
25113
25645
  const probeAuth = () => {
25646
+ const hasEnvToken = (recipe.environment.ANTHROPIC_AUTH_TOKEN ?? "").trim().length > 0;
25647
+ const hasEnvApiKey = (recipe.environment.ANTHROPIC_API_KEY ?? "").trim().length > 0;
25648
+ if (hasEnvToken || hasEnvApiKey) {
25649
+ return {
25650
+ status: "authenticated",
25651
+ type: hasEnvToken ? "env-token" : "api-key",
25652
+ label: hasEnvToken ? "ANTHROPIC_AUTH_TOKEN" : "ANTHROPIC_API_KEY"
25653
+ };
25654
+ }
25114
25655
  const account = readClaudeAccount(resolveClaudeCredentialsDirPath(recipe.credentialsPath));
25115
25656
  switch (account.state) {
25116
25657
  case "signed-in":
@@ -25147,6 +25688,10 @@ function createClaudeInstance(config, host, attachments) {
25147
25688
  ]);
25148
25689
  const version = parseGenericCliVersion(`${stdout}
25149
25690
  ${stderr}`);
25691
+ const models = claudeProbeModels({
25692
+ builtIn: getBuiltInClaudeModelsForVersion(version),
25693
+ customModels: recipe.customModels
25694
+ });
25150
25695
  const auth = probeAuth();
25151
25696
  if (exitCode !== 0 && version === null) {
25152
25697
  const detail = (stderr || stdout || "claude --version failed").trim();
@@ -25164,14 +25709,19 @@ ${stderr}`);
25164
25709
  version,
25165
25710
  readiness: "error",
25166
25711
  auth,
25167
- detail: "Claude is signed out"
25712
+ models,
25713
+ detail: "Claude is signed out",
25714
+ // 退出登录了档位照样是那五档。不报就退回宿主四档,而那份映射少了 `plan`。
25715
+ ...CLAUDE_NATIVE_MODE_REPORT
25168
25716
  };
25169
25717
  }
25170
25718
  return {
25171
25719
  installed: true,
25172
25720
  version,
25173
25721
  readiness: "ready",
25174
- auth
25722
+ auth,
25723
+ models,
25724
+ ...CLAUDE_NATIVE_MODE_REPORT
25175
25725
  };
25176
25726
  };
25177
25727
  const resolveTranscriptRoots = (directory) => {
@@ -25182,7 +25732,7 @@ ${stderr}`);
25182
25732
  );
25183
25733
  return {
25184
25734
  configDir,
25185
- transcriptDir: NodePath6.join(configDir, "projects")
25735
+ transcriptDir: NodePath7.join(configDir, "projects")
25186
25736
  };
25187
25737
  };
25188
25738
  return {
@@ -25193,12 +25743,14 @@ ${stderr}`);
25193
25743
  respondToRequest: adapter.respondToRequest,
25194
25744
  respondToUserInput: adapter.respondToUserInput,
25195
25745
  setRuntimeMode: adapter.setRuntimeMode,
25746
+ setNativeMode: adapter.setNativeMode,
25196
25747
  compactContext: adapter.compactContext,
25197
25748
  stopSession: adapter.stopSession,
25198
25749
  readThread: adapter.readThread,
25199
25750
  events: adapter.events,
25200
25751
  dispose: adapter.dispose,
25201
- textGeneration: createClaudeTextGeneration(recipe, host),
25752
+ textGeneration: createClaudeTextGeneration(recipe, host, pluginDataDirPath),
25753
+ continuationKey: claudeContinuationKey(recipe.homePath),
25202
25754
  sessions: {
25203
25755
  scan: async (input) => {
25204
25756
  const roots = resolveTranscriptRoots(input.directory);
@@ -25239,12 +25791,13 @@ ${stderr}`);
25239
25791
 
25240
25792
  // src/index.ts
25241
25793
  var capturedAttachments;
25794
+ var capturedDataDirPath;
25242
25795
  var executors = [
25243
25796
  {
25244
25797
  card: CLAUDE_CARD,
25245
25798
  createInstance: async (config, host) => {
25246
25799
  host.log.info("createInstance");
25247
- return createClaudeInstance(config, host, capturedAttachments);
25800
+ return createClaudeInstance(config, host, capturedAttachments, capturedDataDirPath);
25248
25801
  }
25249
25802
  }
25250
25803
  ];
@@ -25254,6 +25807,7 @@ var index_default = definePlugin({
25254
25807
  executors,
25255
25808
  activate(ctx) {
25256
25809
  capturedAttachments = ctx.attachments;
25810
+ capturedDataDirPath = ctx.paths.dataDir;
25257
25811
  ctx.log.info("claude executor ready");
25258
25812
  }
25259
25813
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ycodium-ai/agent-claude",
3
- "version": "0.2.2106",
3
+ "version": "0.2.2130",
4
4
  "description": "Claude executor: Claude Code CLI via @anthropic-ai/claude-agent-sdk.",
5
5
  "keywords": [
6
6
  "ycodium",
@@ -23,11 +23,11 @@
23
23
  "@types/node": "24.12.4",
24
24
  "esbuild": "0.28.1",
25
25
  "vite-plus": "0.2.2",
26
- "@ycodium-ai/plugin-api": "0.2.2106"
26
+ "@ycodium-ai/plugin-api": "0.2.2130"
27
27
  },
28
28
  "ycodium": {
29
29
  "plugin": "./dist/index.js",
30
- "displayName": "Claude",
30
+ "displayName": "Claude Code",
31
31
  "description": "Run Claude Code with in-session model and runtime-mode switching, native compaction, and session import.",
32
32
  "icon": "./assets/icon.svg",
33
33
  "permissions": [