@ycodium-ai/agent-claude 0.2.2106 → 0.2.2126

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 +647 -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,6 +19729,10 @@ 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());
@@ -19714,18 +19753,47 @@ function resolveClaudeCredentialsDirPath(credentialsPath) {
19714
19753
  if (trimmed.length === 0) return null;
19715
19754
  return NodePath4.resolve(expandHomePath(trimmed));
19716
19755
  }
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);
19756
+ function resolveClaudeHomeLayout(input) {
19757
+ const userConfigDirPath = resolveClaudeConfigDirPath(input.homePath, input.environment ?? {});
19758
+ const instanceConfigDirPath = input.homePath.trim().length > 0 ? resolveClaudeHomePath(input.homePath) : void 0;
19759
+ const instanceCredentialsDirPath = resolveClaudeCredentialsDirPath(input.credentialsPath);
19760
+ const routedHomePath = input.routedHomePath?.trim() ?? "";
19761
+ if (routedHomePath.length === 0 || instanceConfigDirPath !== void 0 || instanceCredentialsDirPath !== null) {
19762
+ return {
19763
+ mode: "instance",
19764
+ userConfigDirPath,
19765
+ configDirPath: instanceConfigDirPath,
19766
+ credentialsDirPath: instanceCredentialsDirPath ?? void 0
19767
+ };
19768
+ }
19769
+ const resolvedRoutedHomePath = NodePath4.resolve(expandHomePath(routedHomePath));
19770
+ return {
19771
+ mode: "routed",
19772
+ userConfigDirPath,
19773
+ configDirPath: resolvedRoutedHomePath,
19774
+ // 登录目录显式钉成影子目录本身。不设这个变量时 CLI 本来就把凭据放配置目录,但
19775
+ // 「登录去哪」正是这次要修的东西,不靠默认值。
19776
+ credentialsDirPath: resolvedRoutedHomePath
19777
+ };
19778
+ }
19779
+ function claudeHomeVariables(layout) {
19723
19780
  return {
19724
- ...resolvedBaseEnv,
19725
- ...homePath.length > 0 ? { CLAUDE_CONFIG_DIR: resolvedHomePath } : {},
19726
- ...credentialsDirPath === null ? {} : { [CLAUDE_CREDENTIALS_DIR_ENVIRONMENT_VARIABLE]: credentialsDirPath }
19781
+ ...layout.configDirPath === void 0 ? {} : { CLAUDE_CONFIG_DIR: layout.configDirPath },
19782
+ ...layout.credentialsDirPath === void 0 ? {} : { [CLAUDE_CREDENTIALS_DIR_ENVIRONMENT_VARIABLE]: layout.credentialsDirPath }
19727
19783
  };
19728
19784
  }
19785
+ function makeClaudeEnvironment(config, baseEnv) {
19786
+ const resolvedBaseEnv = { ...baseEnv ?? process.env };
19787
+ const variables = claudeHomeVariables(
19788
+ resolveClaudeHomeLayout({
19789
+ homePath: config.homePath,
19790
+ credentialsPath: config.credentialsPath,
19791
+ environment: resolvedBaseEnv
19792
+ })
19793
+ );
19794
+ if (Object.keys(variables).length === 0) return resolvedBaseEnv;
19795
+ return { ...resolvedBaseEnv, ...variables };
19796
+ }
19729
19797
 
19730
19798
  // src/claudeSignIn.ts
19731
19799
  var POSIX_BARE_VALUE = /^[A-Za-z0-9_@%+=:,./-]+$/;
@@ -19953,8 +20021,8 @@ var EventBus = class {
19953
20021
  const queued = this.#queued.shift();
19954
20022
  if (queued !== void 0) return Promise.resolve({ done: false, value: queued });
19955
20023
  if (this.#closed) return Promise.resolve({ done: true, value: void 0 });
19956
- return new Promise((resolve2) => {
19957
- this.#waiters.push(resolve2);
20024
+ return new Promise((resolve3) => {
20025
+ this.#waiters.push(resolve3);
19958
20026
  });
19959
20027
  }
19960
20028
  };
@@ -19962,6 +20030,7 @@ var EventBus = class {
19962
20030
  };
19963
20031
 
19964
20032
  // src/model.ts
20033
+ import { matchCatalogModel } from "@ycodium-ai/plugin-api/executor";
19965
20034
  var DEFAULT_CLAUDE_MODEL_CAPABILITIES = {
19966
20035
  optionDescriptors: []
19967
20036
  };
@@ -19969,6 +20038,10 @@ var CLAUDE_CUSTOM_MODEL_THINKING_SELECT = {
19969
20038
  id: "effort",
19970
20039
  label: "Reasoning"
19971
20040
  };
20041
+ var MINIMUM_CLAUDE_OPUS_5_VERSION = "2.1.219";
20042
+ var MINIMUM_CLAUDE_FABLE_5_VERSION = "2.1.169";
20043
+ var MINIMUM_CLAUDE_OPUS_4_8_VERSION = "2.1.154";
20044
+ var MINIMUM_CLAUDE_OPUS_4_7_VERSION = "2.1.111";
19972
20045
  var CURRENT_CLAUDE_MODELS = /* @__PURE__ */ new Set(["claude-fable-5", "claude-opus-5", "claude-sonnet-5"]);
19973
20046
  function isLegacyClaudeModel(model) {
19974
20047
  return !CURRENT_CLAUDE_MODELS.has(model);
@@ -20209,6 +20282,35 @@ var CLAUDE_XHIGH_SLUGS = /* @__PURE__ */ new Set([
20209
20282
  "claude-opus-4-8",
20210
20283
  "claude-sonnet-5"
20211
20284
  ]);
20285
+ function compareSemverVersions(left, right) {
20286
+ const parse = (value) => {
20287
+ const match = value.trim().replace(/^v/, "").match(/^(\d+)\.(\d+)\.(\d+)/);
20288
+ if (!match) return null;
20289
+ return [Number(match[1]), Number(match[2]), Number(match[3])];
20290
+ };
20291
+ const a = parse(left);
20292
+ const b3 = parse(right);
20293
+ if (!a || !b3) return left.localeCompare(right);
20294
+ if (a[0] !== b3[0]) return a[0] - b3[0];
20295
+ if (a[1] !== b3[1]) return a[1] - b3[1];
20296
+ return a[2] - b3[2];
20297
+ }
20298
+ function supportsVersion(version, minimum) {
20299
+ return version ? compareSemverVersions(version, minimum) >= 0 : false;
20300
+ }
20301
+ function getBuiltInClaudeModelsForVersion(version) {
20302
+ return BUILT_IN_MODELS.filter((model) => {
20303
+ if (model.slug === "claude-opus-5")
20304
+ return supportsVersion(version, MINIMUM_CLAUDE_OPUS_5_VERSION);
20305
+ if (model.slug === "claude-fable-5")
20306
+ return supportsVersion(version, MINIMUM_CLAUDE_FABLE_5_VERSION);
20307
+ if (model.slug === "claude-opus-4-8")
20308
+ return supportsVersion(version, MINIMUM_CLAUDE_OPUS_4_8_VERSION);
20309
+ if (model.slug === "claude-opus-4-7")
20310
+ return supportsVersion(version, MINIMUM_CLAUDE_OPUS_4_7_VERSION);
20311
+ return true;
20312
+ });
20313
+ }
20212
20314
  function parseGenericCliVersion(output) {
20213
20315
  const match = output.match(/\b(\d+\.\d+\.\d+)\b/);
20214
20316
  return match?.[1] ?? null;
@@ -20316,6 +20418,13 @@ function resolveClaudeContextWindow(caps, modelSelection) {
20316
20418
  if (!descriptor || descriptor.type !== "select") return void 0;
20317
20419
  return currentSelectValue(descriptor, raw);
20318
20420
  }
20421
+ function claudeCliModelId(input) {
20422
+ const catalog = [
20423
+ ...BUILT_IN_MODELS.map((model) => model.slug),
20424
+ ...(input.customModels ?? []).map((model) => normalizeCustomModelSlug(model.slug))
20425
+ ];
20426
+ return matchCatalogModel(input.boundModelId, catalog) ?? input.placeholder;
20427
+ }
20319
20428
  function resolveClaudeApiModelId(caps, modelSelection) {
20320
20429
  switch (resolveClaudeContextWindow(caps, modelSelection)) {
20321
20430
  case "1m":
@@ -20386,6 +20495,15 @@ function toMessage(cause, fallback) {
20386
20495
  }
20387
20496
  return fallback;
20388
20497
  }
20498
+ function createOutputTail(maxChars) {
20499
+ let buffered = "";
20500
+ return {
20501
+ append: (chunk) => {
20502
+ buffered = (buffered + chunk).slice(-maxChars);
20503
+ },
20504
+ read: () => buffered.trim()
20505
+ };
20506
+ }
20389
20507
  function asRuntimeItemId(value) {
20390
20508
  return value;
20391
20509
  }
@@ -20533,10 +20651,10 @@ function inputDiffStat(input) {
20533
20651
  let deletions = 0;
20534
20652
  for (const edit of input.edits) {
20535
20653
  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;
20654
+ const stat2 = replacementDiffStat(edit, budget);
20655
+ if (!stat2) return void 0;
20656
+ additions += stat2.additions;
20657
+ deletions += stat2.deletions;
20540
20658
  }
20541
20659
  return { additions, deletions };
20542
20660
  }
@@ -21071,11 +21189,40 @@ function diffClaudeSessionSettings(previous, next) {
21071
21189
  }
21072
21190
  return changed ? patch : void 0;
21073
21191
  }
21074
- var CLAUDE_PERMISSION_MODE_BY_RUNTIME_MODE = {
21192
+ var CLAUDE_NATIVE_MODES = [
21193
+ { id: "default", name: "Manual", description: "Always ask before making changes" },
21194
+ { id: "acceptEdits", name: "Accept edits", description: "Automatically accept all file edits" },
21195
+ { id: "plan", name: "Plan", description: "Create a plan before making changes" },
21196
+ { id: "auto", name: "Auto", description: "Claude handles permission decisions" },
21197
+ {
21198
+ id: "bypassPermissions",
21199
+ name: "Bypass permissions",
21200
+ description: "Accepts all permissions"
21201
+ }
21202
+ ];
21203
+ var CLAUDE_DEFAULT_NATIVE_MODE_ID = "default";
21204
+ var CLAUDE_NATIVE_MODE_BY_RUNTIME_MODE = {
21205
+ "approval-required": "default",
21075
21206
  "auto-accept-edits": "acceptEdits",
21076
21207
  auto: "auto",
21077
21208
  "full-access": "bypassPermissions"
21078
21209
  };
21210
+ var CLAUDE_NATIVE_MODE_REPORT = {
21211
+ nativeModes: CLAUDE_NATIVE_MODES.map((mode) => ({
21212
+ id: mode.id,
21213
+ name: mode.name,
21214
+ description: mode.description
21215
+ })),
21216
+ currentNativeModeId: CLAUDE_DEFAULT_NATIVE_MODE_ID,
21217
+ nativeModeByRuntimeMode: CLAUDE_NATIVE_MODE_BY_RUNTIME_MODE
21218
+ };
21219
+ function isClaudeNativeModeId(value) {
21220
+ return CLAUDE_NATIVE_MODES.some((mode) => mode.id === value);
21221
+ }
21222
+ function claudePermissionModeFor(runtimeMode, nativeModeId) {
21223
+ if (nativeModeId !== void 0 && isClaudeNativeModeId(nativeModeId)) return nativeModeId;
21224
+ return CLAUDE_NATIVE_MODE_BY_RUNTIME_MODE[runtimeMode];
21225
+ }
21079
21226
  var CLAUDE_SETTING_SOURCES = [
21080
21227
  "user",
21081
21228
  "project",
@@ -21213,17 +21360,17 @@ function listTranscriptFiles(transcriptDir, directory) {
21213
21360
  continue;
21214
21361
  }
21215
21362
  for (const name of names) {
21216
- let stat;
21363
+ let stat2;
21217
21364
  try {
21218
- stat = NodeFS3.statSync(NodePath5.join(projectDir, name));
21365
+ stat2 = NodeFS3.statSync(NodePath5.join(projectDir, name));
21219
21366
  } catch {
21220
21367
  continue;
21221
21368
  }
21222
21369
  found.push({
21223
21370
  path: NodePath5.join(projectDir, name),
21224
21371
  sessionId: name.slice(0, -TRANSCRIPT_SUFFIX.length),
21225
- size: stat.size,
21226
- mtimeMs: Number.isFinite(stat.mtimeMs) ? stat.mtimeMs : 0
21372
+ size: stat2.size,
21373
+ mtimeMs: Number.isFinite(stat2.mtimeMs) ? stat2.mtimeMs : 0
21227
21374
  });
21228
21375
  }
21229
21376
  }
@@ -21787,6 +21934,305 @@ function normalizeClaudeTaskProgressTokenUsage(value, memory) {
21787
21934
  };
21788
21935
  }
21789
21936
 
21937
+ // src/probeModels.ts
21938
+ var CONTEXT_WINDOW_TOKENS = {
21939
+ "200k": 2e5,
21940
+ "1m": 1e6
21941
+ };
21942
+ var defaultContextWindowOf = (caps) => {
21943
+ const descriptor = caps.optionDescriptors.find(
21944
+ (entry) => entry.type === "select" && entry.id === "contextWindow"
21945
+ );
21946
+ if (descriptor === void 0) return void 0;
21947
+ const chosen = descriptor.options.find((option) => option.isDefault === true);
21948
+ return chosen === void 0 ? void 0 : CONTEXT_WINDOW_TOKENS[chosen.id];
21949
+ };
21950
+ var toProbeCapabilities = (caps) => ({
21951
+ optionDescriptors: caps.optionDescriptors.map(
21952
+ (descriptor) => descriptor.type === "select" ? {
21953
+ id: descriptor.id,
21954
+ label: descriptor.label,
21955
+ type: "select",
21956
+ options: descriptor.options.map((option) => ({
21957
+ id: option.id,
21958
+ label: option.label,
21959
+ ...option.description === void 0 ? {} : { description: option.description },
21960
+ ...option.isDefault === void 0 ? {} : { isDefault: option.isDefault }
21961
+ }))
21962
+ } : {
21963
+ id: descriptor.id,
21964
+ label: descriptor.label,
21965
+ type: "boolean"
21966
+ }
21967
+ )
21968
+ });
21969
+ var claudeProbeModels = (input) => {
21970
+ const seen = /* @__PURE__ */ new Set();
21971
+ const out = [];
21972
+ for (const model of input.builtIn) {
21973
+ if (seen.has(model.slug)) continue;
21974
+ seen.add(model.slug);
21975
+ const userLabel = input.customModels.find((entry) => entry.slug === model.slug)?.label;
21976
+ const contextWindow = defaultContextWindowOf(model.capabilities);
21977
+ out.push({
21978
+ slug: model.slug,
21979
+ name: userLabel?.trim() || model.name,
21980
+ ...model.isLegacy === void 0 ? {} : { isLegacy: model.isLegacy },
21981
+ capabilities: toProbeCapabilities(model.capabilities),
21982
+ ...contextWindow === void 0 ? {} : { contextWindow }
21983
+ });
21984
+ }
21985
+ for (const model of input.customModels) {
21986
+ if (model.slug.trim().length === 0 || seen.has(model.slug)) continue;
21987
+ seen.add(model.slug);
21988
+ out.push({ slug: model.slug, name: model.label?.trim() || model.slug });
21989
+ }
21990
+ for (const model of input.discovered ?? []) {
21991
+ if (seen.has(model.slug)) continue;
21992
+ seen.add(model.slug);
21993
+ out.push(model);
21994
+ }
21995
+ return out;
21996
+ };
21997
+
21998
+ // src/routedHome.ts
21999
+ import * as NodeFSP from "node:fs/promises";
22000
+ import * as NodePath6 from "node:path";
22001
+
22002
+ // src/routedSettings.ts
22003
+ import { brainSlotEnvKeys } from "@ycodium-ai/plugin-api";
22004
+ var PROXY_ENV_KEYS = [
22005
+ "HTTP_PROXY",
22006
+ "HTTPS_PROXY",
22007
+ "NO_PROXY",
22008
+ "ALL_PROXY",
22009
+ "http_proxy",
22010
+ "https_proxy",
22011
+ "no_proxy",
22012
+ "all_proxy"
22013
+ ];
22014
+ function isRoutedLaunch(launchProcessEnv) {
22015
+ const baseUrlKey = CLAUDE_CARD.modelConnectivity.baseUrlEnvKey;
22016
+ if (baseUrlKey === void 0) return false;
22017
+ return (launchProcessEnv?.[baseUrlKey] ?? "").trim().length > 0;
22018
+ }
22019
+ function routedEnvOverride(input) {
22020
+ if (!isRoutedLaunch(input.launchProcessEnv)) return void 0;
22021
+ const valueOf = (key) => input.mergedEnv[key] ?? "";
22022
+ const override = {};
22023
+ for (const key of brainSlotEnvKeys(CLAUDE_CARD)) override[key] = valueOf(key);
22024
+ for (const { name } of CLAUDE_CARD.modelConnectivity.extraEnv ?? []) {
22025
+ const bound = valueOf(name);
22026
+ override[name] = bound.length === 0 ? "" : claudeCliModelId({
22027
+ boundModelId: bound,
22028
+ placeholder: CLAUDE_CARD.defaultModel,
22029
+ ...input.customModels === void 0 ? {} : { customModels: input.customModels }
22030
+ });
22031
+ }
22032
+ for (const key of PROXY_ENV_KEYS) override[key] = valueOf(key);
22033
+ return override;
22034
+ }
22035
+
22036
+ // src/routedHome.ts
22037
+ var BORROWED_ENTRY_NAMES = [
22038
+ "projects",
22039
+ "plugins",
22040
+ "skills",
22041
+ "agents",
22042
+ "commands",
22043
+ "CLAUDE.md"
22044
+ ];
22045
+ var GLOBAL_CONFIG_FILE_NAME = ".claude.json";
22046
+ var USER_SETTINGS_FILE_NAME = "settings.json";
22047
+ var ROUTING_AND_IDENTITY_SETTING_KEYS = [
22048
+ "env",
22049
+ "apiKeyHelper",
22050
+ "awsAuthRefresh",
22051
+ "awsCredentialExport"
22052
+ ];
22053
+ var FIRST_RUN_FLAG_KEYS = ["hasCompletedOnboarding", "bypassPermissionsModeAccepted"];
22054
+ function isRecord5(value) {
22055
+ return typeof value === "object" && value !== null && !Array.isArray(value);
22056
+ }
22057
+ async function readTextFile(filePath) {
22058
+ try {
22059
+ return await NodeFSP.readFile(filePath, "utf8");
22060
+ } catch {
22061
+ return null;
22062
+ }
22063
+ }
22064
+ function parseJsonObject(raw) {
22065
+ try {
22066
+ const parsed = JSON.parse(raw);
22067
+ return isRecord5(parsed) ? parsed : null;
22068
+ } catch {
22069
+ return null;
22070
+ }
22071
+ }
22072
+ async function writeFileAtomic(filePath, contents) {
22073
+ const tempPath = `${filePath}.${crypto.randomUUID()}.tmp`;
22074
+ await NodeFSP.writeFile(tempPath, contents, { encoding: "utf8", mode: 384 });
22075
+ await NodeFSP.rename(tempPath, filePath);
22076
+ }
22077
+ async function readLinkState(linkPath) {
22078
+ try {
22079
+ return { _tag: "Symlink", target: await NodeFSP.readlink(linkPath) };
22080
+ } catch (error) {
22081
+ const code = typeof error === "object" && error !== null && "code" in error ? String(error.code) : "";
22082
+ if (code === "ENOENT") return { _tag: "Missing" };
22083
+ return { _tag: "NotSymlink" };
22084
+ }
22085
+ }
22086
+ async function isEmptyDirectory(path) {
22087
+ try {
22088
+ return (await NodeFSP.readdir(path)).length === 0;
22089
+ } catch {
22090
+ return false;
22091
+ }
22092
+ }
22093
+ async function borrowEntry(input) {
22094
+ const target = NodePath6.join(input.userConfigDirPath, input.entryName);
22095
+ const targetInfo = await NodeFSP.stat(target).then(
22096
+ (info) => info,
22097
+ () => null
22098
+ );
22099
+ if (targetInfo === null) return;
22100
+ const link = NodePath6.join(input.configDirPath, input.entryName);
22101
+ const state = await readLinkState(link);
22102
+ if (state._tag === "NotSymlink") {
22103
+ if (!await isEmptyDirectory(link)) {
22104
+ input.log?.warn("Claude routed home entry left unlinked: a real entry is already there.", {
22105
+ entryName: input.entryName,
22106
+ link
22107
+ });
22108
+ return;
22109
+ }
22110
+ await NodeFSP.rm(link, { recursive: true, force: true });
22111
+ await NodeFSP.symlink(target, link, targetInfo.isDirectory() ? "junction" : "file");
22112
+ return;
22113
+ }
22114
+ if (state._tag === "Symlink") {
22115
+ if (NodePath6.resolve(NodePath6.dirname(link), state.target) === target) return;
22116
+ await NodeFSP.rm(link, { force: true });
22117
+ }
22118
+ await NodeFSP.symlink(target, link, targetInfo.isDirectory() ? "junction" : "file");
22119
+ }
22120
+ async function mirrorUserSettings(input) {
22121
+ const sourcePath = NodePath6.join(input.userConfigDirPath, USER_SETTINGS_FILE_NAME);
22122
+ const raw = await readTextFile(sourcePath);
22123
+ if (raw === null) return;
22124
+ const parsed = parseJsonObject(raw);
22125
+ if (parsed === null) {
22126
+ input.log?.warn(
22127
+ "Claude routed home settings mirror skipped: the source is not a JSON object.",
22128
+ {
22129
+ sourcePath
22130
+ }
22131
+ );
22132
+ return;
22133
+ }
22134
+ const mirrored = { ...parsed };
22135
+ for (const key of ROUTING_AND_IDENTITY_SETTING_KEYS) delete mirrored[key];
22136
+ const targetPath = NodePath6.join(input.configDirPath, USER_SETTINGS_FILE_NAME);
22137
+ const desired = `${JSON.stringify(mirrored, null, 2)}
22138
+ `;
22139
+ if (await readTextFile(targetPath) === desired) return;
22140
+ await writeFileAtomic(targetPath, desired);
22141
+ }
22142
+ async function seedFirstRun(input) {
22143
+ const filePath = NodePath6.join(input.configDirPath, GLOBAL_CONFIG_FILE_NAME);
22144
+ const raw = await readTextFile(filePath);
22145
+ const parsed = raw === null ? {} : parseJsonObject(raw);
22146
+ if (parsed === null) {
22147
+ input.log?.warn("Claude routed home first-run seed skipped: the config file is not readable.", {
22148
+ filePath
22149
+ });
22150
+ return;
22151
+ }
22152
+ const next = { ...parsed };
22153
+ let changed = false;
22154
+ if (raw === null) {
22155
+ for (const key of FIRST_RUN_FLAG_KEYS) next[key] = true;
22156
+ changed = true;
22157
+ }
22158
+ const existingProjects = next["projects"];
22159
+ const projects = isRecord5(existingProjects) ? { ...existingProjects } : {};
22160
+ for (const trustedPath of input.trustedPaths) {
22161
+ const resolved = NodePath6.resolve(trustedPath);
22162
+ const entry = projects[resolved];
22163
+ const project = isRecord5(entry) ? { ...entry } : {};
22164
+ if (project["hasTrustDialogAccepted"] === true) continue;
22165
+ project["hasTrustDialogAccepted"] = true;
22166
+ projects[resolved] = project;
22167
+ changed = true;
22168
+ }
22169
+ if (Object.keys(projects).length > 0) next["projects"] = projects;
22170
+ if (!changed) return;
22171
+ await writeFileAtomic(filePath, `${JSON.stringify(next, null, 2)}
22172
+ `);
22173
+ }
22174
+ function resolveClaudeLaunchHome(input) {
22175
+ const routed = isRoutedLaunch(input.launchProcessEnv);
22176
+ const dataDirPath = input.pluginDataDirPath?.trim() ?? "";
22177
+ if (routed && dataDirPath.length === 0) {
22178
+ input.log?.warn(
22179
+ "Ycodium is routing this turn but has no directory of its own to hand the CLI; it will read the user's."
22180
+ );
22181
+ }
22182
+ return resolveClaudeHomeLayout({
22183
+ homePath: input.homePath,
22184
+ credentialsPath: input.credentialsPath,
22185
+ ...routed && dataDirPath.length > 0 ? { routedHomePath: claudeRoutedHomePath(dataDirPath) } : {},
22186
+ ...input.environment === void 0 ? {} : { environment: input.environment }
22187
+ });
22188
+ }
22189
+ async function materializeClaudeRoutedHome(layout, options) {
22190
+ if (layout.mode !== "routed") return;
22191
+ const configDirPath = layout.configDirPath;
22192
+ if (configDirPath === void 0) return;
22193
+ if (configDirPath === layout.userConfigDirPath) {
22194
+ throw new Error(
22195
+ `Ycodium's Claude configuration directory '${configDirPath}' must differ from the user's.`
22196
+ );
22197
+ }
22198
+ await NodeFSP.mkdir(configDirPath, { recursive: true });
22199
+ await NodeFSP.chmod(configDirPath, 448);
22200
+ const credentialsDirPath = layout.credentialsDirPath;
22201
+ if (credentialsDirPath !== void 0 && credentialsDirPath !== configDirPath) {
22202
+ await NodeFSP.mkdir(credentialsDirPath, { recursive: true });
22203
+ await NodeFSP.chmod(credentialsDirPath, 448);
22204
+ }
22205
+ const log = options?.log;
22206
+ for (const entryName of BORROWED_ENTRY_NAMES) {
22207
+ try {
22208
+ await borrowEntry({
22209
+ userConfigDirPath: layout.userConfigDirPath,
22210
+ configDirPath,
22211
+ entryName,
22212
+ log
22213
+ });
22214
+ } catch (error) {
22215
+ log?.warn("Claude routed home entry left unlinked.", {
22216
+ entryName,
22217
+ reason: error instanceof Error ? error.message : String(error)
22218
+ });
22219
+ }
22220
+ }
22221
+ await mirrorUserSettings({
22222
+ userConfigDirPath: layout.userConfigDirPath,
22223
+ configDirPath,
22224
+ log
22225
+ });
22226
+ await seedFirstRun({
22227
+ configDirPath,
22228
+ trustedPaths: options?.trustedPaths ?? [],
22229
+ log
22230
+ });
22231
+ }
22232
+
22233
+ // src/textGeneration.ts
22234
+ import { mergeLaunchEnv, brainSlotEnvKeys as brainSlotEnvKeys2 } from "@ycodium-ai/plugin-api/executor";
22235
+
21790
22236
  // src/textGenerationUtils.ts
21791
22237
  function limitSection(value, maxChars) {
21792
22238
  if (value.length <= maxChars) return value;
@@ -22134,7 +22580,7 @@ function buildHandoverSummaryPrompt(input) {
22134
22580
 
22135
22581
  // src/textGeneration.ts
22136
22582
  var CLAUDE_TIMEOUT_MS = 18e4;
22137
- function isRecord5(value) {
22583
+ function isRecord6(value) {
22138
22584
  return typeof value === "object" && value !== null && !Array.isArray(value);
22139
22585
  }
22140
22586
  function readString3(value) {
@@ -22156,18 +22602,31 @@ function decodeStructuredOutput(stdout, operation) {
22156
22602
  cause: error
22157
22603
  });
22158
22604
  }
22159
- if (!isRecord5(parsed) || !("structured_output" in parsed)) {
22605
+ if (!isRecord6(parsed) || !("structured_output" in parsed)) {
22160
22606
  throw new Error(`${operation}: Claude CLI returned unexpected output format.`);
22161
22607
  }
22162
22608
  return parsed.structured_output;
22163
22609
  }
22164
- function createClaudeTextGeneration(recipe, host) {
22610
+ function createClaudeTextGeneration(recipe, host, pluginDataDirPath) {
22165
22611
  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 : {};
22612
+ const homeLayout = resolveClaudeLaunchHome({
22613
+ homePath: recipe.homePath,
22614
+ credentialsPath: recipe.credentialsPath,
22615
+ launchProcessEnv: input.launch?.processEnv,
22616
+ pluginDataDirPath,
22617
+ log: host.log
22618
+ });
22619
+ await materializeClaudeRoutedHome(homeLayout, {
22620
+ trustedPaths: [input.cwd],
22621
+ log: host.log
22622
+ });
22623
+ const processEnv = mergeLaunchEnv({
22624
+ brainSlots: brainSlotEnvKeys2(CLAUDE_CARD),
22625
+ instanceTable: recipe.environment,
22626
+ handOverrides: claudeHomeVariables(homeLayout),
22627
+ launch: input.launch?.processEnv
22628
+ });
22629
+ const modelSelection = isRecord6(input.modelSelection) ? input.modelSelection : {};
22171
22630
  const requestedModel = input.launch?.requestedModelId ?? modelIdFromSelection(modelSelection) ?? "claude-haiku-4-5";
22172
22631
  const caps = claudeModelCapabilities(requestedModel, recipe.customModels);
22173
22632
  const rawEffort = getModelSelectionStringOptionValue(modelSelection, "effort");
@@ -22200,7 +22659,7 @@ function createClaudeTextGeneration(recipe, host) {
22200
22659
  program,
22201
22660
  args,
22202
22661
  cwd: input.cwd,
22203
- env: compactEnv(processEnv),
22662
+ env: processEnv,
22204
22663
  owner: { bindingId: host.bindingId }
22205
22664
  });
22206
22665
  const timeout = AbortSignal.timeout(CLAUDE_TIMEOUT_MS);
@@ -22229,7 +22688,7 @@ function createClaudeTextGeneration(recipe, host) {
22229
22688
  return decodeStructuredOutput(stdout, input.operation);
22230
22689
  };
22231
22690
  const asRecord = (value, operation) => {
22232
- if (!isRecord5(value))
22691
+ if (!isRecord6(value))
22233
22692
  throw new Error(`${operation}: Claude returned invalid structured output.`);
22234
22693
  return value;
22235
22694
  };
@@ -22243,7 +22702,7 @@ function createClaudeTextGeneration(recipe, host) {
22243
22702
  stagedSummary: readString3(input["stagedSummary"]),
22244
22703
  stagedPatch: readString3(input["stagedPatch"]),
22245
22704
  includeBranch,
22246
- ...isRecord5(input["policy"]) ? { policy: input["policy"] } : {}
22705
+ ...isRecord6(input["policy"]) ? { policy: input["policy"] } : {}
22247
22706
  });
22248
22707
  const generated = asRecord(
22249
22708
  await runClaudeJson({
@@ -22252,7 +22711,7 @@ function createClaudeTextGeneration(recipe, host) {
22252
22711
  prompt,
22253
22712
  outputSchema,
22254
22713
  modelSelection: input["modelSelection"],
22255
- ...isRecord5(input["launch"]) ? { launch: input["launch"] } : {},
22714
+ ...isRecord6(input["launch"]) ? { launch: input["launch"] } : {},
22256
22715
  ...signal === void 0 ? {} : { signal }
22257
22716
  }),
22258
22717
  "generateCommitMessage"
@@ -22276,7 +22735,7 @@ function createClaudeTextGeneration(recipe, host) {
22276
22735
  diffSummary: readString3(input["diffSummary"]),
22277
22736
  diffPatch: readString3(input["diffPatch"]),
22278
22737
  ...typeof input["changeRequestTemplate"] === "string" ? { changeRequestTemplate: input["changeRequestTemplate"] } : {},
22279
- ...isRecord5(input["policy"]) ? { policy: input["policy"] } : {}
22738
+ ...isRecord6(input["policy"]) ? { policy: input["policy"] } : {}
22280
22739
  });
22281
22740
  const generated = asRecord(
22282
22741
  await runClaudeJson({
@@ -22285,7 +22744,7 @@ function createClaudeTextGeneration(recipe, host) {
22285
22744
  prompt,
22286
22745
  outputSchema,
22287
22746
  modelSelection: input["modelSelection"],
22288
- ...isRecord5(input["launch"]) ? { launch: input["launch"] } : {},
22747
+ ...isRecord6(input["launch"]) ? { launch: input["launch"] } : {},
22289
22748
  ...signal === void 0 ? {} : { signal }
22290
22749
  }),
22291
22750
  "generatePrContent"
@@ -22304,7 +22763,7 @@ function createClaudeTextGeneration(recipe, host) {
22304
22763
  const { prompt, outputSchema } = buildBranchNamePrompt({
22305
22764
  message: readString3(input["message"]),
22306
22765
  ...Array.isArray(input["attachments"]) ? { attachments: input["attachments"] } : {},
22307
- ...isRecord5(input["policy"]) ? { policy: input["policy"] } : {}
22766
+ ...isRecord6(input["policy"]) ? { policy: input["policy"] } : {}
22308
22767
  });
22309
22768
  const generated = asRecord(
22310
22769
  await runClaudeJson({
@@ -22313,7 +22772,7 @@ function createClaudeTextGeneration(recipe, host) {
22313
22772
  prompt,
22314
22773
  outputSchema,
22315
22774
  modelSelection: input["modelSelection"],
22316
- ...isRecord5(input["launch"]) ? { launch: input["launch"] } : {},
22775
+ ...isRecord6(input["launch"]) ? { launch: input["launch"] } : {},
22317
22776
  ...signal === void 0 ? {} : { signal }
22318
22777
  }),
22319
22778
  "generateBranchName"
@@ -22330,7 +22789,7 @@ function createClaudeTextGeneration(recipe, host) {
22330
22789
  message: readString3(input["message"]),
22331
22790
  ...typeof input["previousTitle"] === "string" ? { previousTitle: input["previousTitle"] } : {},
22332
22791
  ...Array.isArray(input["attachments"]) ? { attachments: input["attachments"] } : {},
22333
- ...isRecord5(input["policy"]) ? { policy: input["policy"] } : {}
22792
+ ...isRecord6(input["policy"]) ? { policy: input["policy"] } : {}
22334
22793
  });
22335
22794
  const generated = asRecord(
22336
22795
  await runClaudeJson({
@@ -22339,7 +22798,7 @@ function createClaudeTextGeneration(recipe, host) {
22339
22798
  prompt,
22340
22799
  outputSchema,
22341
22800
  modelSelection: input["modelSelection"],
22342
- ...isRecord5(input["launch"]) ? { launch: input["launch"] } : {},
22801
+ ...isRecord6(input["launch"]) ? { launch: input["launch"] } : {},
22343
22802
  ...signal === void 0 ? {} : { signal }
22344
22803
  }),
22345
22804
  "generateThreadTitle"
@@ -22362,7 +22821,7 @@ function createClaudeTextGeneration(recipe, host) {
22362
22821
  prompt,
22363
22822
  outputSchema,
22364
22823
  modelSelection: input["modelSelection"],
22365
- ...isRecord5(input["launch"]) ? { launch: input["launch"] } : {},
22824
+ ...isRecord6(input["launch"]) ? { launch: input["launch"] } : {},
22366
22825
  ...signal === void 0 ? {} : { signal }
22367
22826
  }),
22368
22827
  "summarizeHandover"
@@ -22374,14 +22833,6 @@ function createClaudeTextGeneration(recipe, host) {
22374
22833
  }
22375
22834
  };
22376
22835
  }
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
22836
 
22386
22837
  // src/imageMime.ts
22387
22838
  var IMAGE_EXTENSION_BY_MIME_TYPE = {
@@ -22463,9 +22914,9 @@ var PromptQueue = class {
22463
22914
  const queued = this.#queued.shift();
22464
22915
  if (queued !== void 0) return Promise.resolve(queued);
22465
22916
  if (this.#closed) return Promise.resolve(void 0);
22466
- return new Promise((resolve2) => {
22917
+ return new Promise((resolve3) => {
22467
22918
  this.#waiters.push((result) => {
22468
- resolve2(result.done ? void 0 : result.value);
22919
+ resolve3(result.done ? void 0 : result.value);
22469
22920
  });
22470
22921
  });
22471
22922
  }
@@ -22496,6 +22947,7 @@ var SUPPORTED_CLAUDE_IMAGE_MIME_TYPES = /* @__PURE__ */ new Set([
22496
22947
  ]);
22497
22948
  var CLAUDE_INTERRUPT_GRACE_MS = 5e3;
22498
22949
  var CLAUDE_SESSION_CONTEXT_WAIT_MS = 1e4;
22950
+ var CLAUDE_STDERR_TAIL_CHARS = 4e3;
22499
22951
  function asClaudeModelSelection(value) {
22500
22952
  if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
22501
22953
  return value;
@@ -22583,7 +23035,7 @@ async function persistProducedImage(input) {
22583
23035
  }
22584
23036
 
22585
23037
  // src/claudeInstance.ts
22586
- function compactEnv2(env) {
23038
+ function compactEnv(env) {
22587
23039
  const out = {};
22588
23040
  if (!env) return out;
22589
23041
  for (const [key, value] of Object.entries(env)) {
@@ -22591,10 +23043,26 @@ function compactEnv2(env) {
22591
23043
  }
22592
23044
  return out;
22593
23045
  }
23046
+ function toClaudeMcpServers(entries) {
23047
+ if (entries === void 0 || entries.length === 0) return void 0;
23048
+ const servers = {};
23049
+ for (const entry of entries) {
23050
+ servers[entry.name] = entry.transport.type === "stdio" ? {
23051
+ type: "stdio",
23052
+ command: entry.transport.command,
23053
+ ...entry.transport.args === void 0 ? {} : { args: [...entry.transport.args] },
23054
+ ...entry.transport.env === void 0 ? {} : { env: { ...entry.transport.env } }
23055
+ } : {
23056
+ type: entry.transport.type,
23057
+ url: entry.transport.url,
23058
+ ...entry.transport.headers === void 0 ? {} : { headers: { ...entry.transport.headers } }
23059
+ };
23060
+ }
23061
+ return servers;
23062
+ }
22594
23063
  function createClaudeAdapter(recipe, host, options) {
22595
23064
  const boundInstanceId = options?.instanceId ?? host.instanceId ?? "claudeAgent";
22596
23065
  const claudeHomeEnvironment = makeClaudeEnvironment(recipe, options?.environment);
22597
- const sessionLaunchEnv = /* @__PURE__ */ new Map();
22598
23066
  const claudeSdkExecutablePath = resolveClaudeSdkExecutablePath(
22599
23067
  recipe.binaryPath,
22600
23068
  claudeHomeEnvironment
@@ -22886,8 +23354,8 @@ function createClaudeAdapter(recipe, host, options) {
22886
23354
  try {
22887
23355
  usage = await Promise.race([
22888
23356
  context.query.getContextUsage(),
22889
- new Promise((resolve2) => {
22890
- setTimeout(() => resolve2(void 0), 1e3);
23357
+ new Promise((resolve3) => {
23358
+ setTimeout(() => resolve3(void 0), 1e3);
22891
23359
  })
22892
23360
  ]);
22893
23361
  } catch {
@@ -24180,7 +24648,9 @@ function createClaudeAdapter(recipe, host, options) {
24180
24648
  await completeTurn(context, "interrupted", "Claude runtime interrupted.");
24181
24649
  }
24182
24650
  } else {
24183
- const message = cause instanceof Error ? cause.message : "Claude runtime stream failed.";
24651
+ const cliSaid = context.stderrTail.read();
24652
+ const reason = cause instanceof Error ? cause.message : "Claude runtime stream failed.";
24653
+ const message = cliSaid.length > 0 ? `${reason}: ${cliSaid}` : reason;
24184
24654
  await emitRuntimeError(context, message, { cause: String(cause) });
24185
24655
  await completeTurn(context, "failed", message);
24186
24656
  }
@@ -24256,7 +24726,7 @@ function createClaudeAdapter(recipe, host, options) {
24256
24726
  if (streamFiber) {
24257
24727
  await Promise.race([
24258
24728
  streamFiber.catch(() => void 0),
24259
- new Promise((resolve2) => setTimeout(resolve2, 1e3))
24729
+ new Promise((resolve3) => setTimeout(resolve3, 1e3))
24260
24730
  ]);
24261
24731
  }
24262
24732
  const updatedAt = nowIso();
@@ -24283,9 +24753,6 @@ function createClaudeAdapter(recipe, host, options) {
24283
24753
  }
24284
24754
  if (sessions.get(context.session.threadId) === context) {
24285
24755
  sessions.delete(context.session.threadId);
24286
- if (stopOptions?.keepLaunch !== true) {
24287
- sessionLaunchEnv.delete(context.session.threadId);
24288
- }
24289
24756
  }
24290
24757
  })();
24291
24758
  context.stopWork = work;
@@ -24365,7 +24832,13 @@ function createClaudeAdapter(recipe, host, options) {
24365
24832
  const modelSelection = boundModelSelection(input.modelSelection, boundInstanceId);
24366
24833
  const caps = claudeModelCapabilities(modelSelection?.model, recipe.customModels);
24367
24834
  const descriptors = caps.optionDescriptors;
24368
- const apiModelId = modelSelection?.model ? resolveClaudeApiModelId(caps, { model: modelSelection.model }) : void 0;
24835
+ const apiModelId = modelSelection?.model ? resolveClaudeApiModelId(caps, {
24836
+ model: claudeCliModelId({
24837
+ boundModelId: modelSelection.model,
24838
+ placeholder: CLAUDE_CARD.defaultModel,
24839
+ customModels: recipe.customModels
24840
+ })
24841
+ }) : void 0;
24369
24842
  const initialContextWindow = selectedClaudeContextWindow(caps, modelSelection);
24370
24843
  const rawEffort = getModelSelectionStringOptionValue(modelSelection, "effort");
24371
24844
  const effort = resolveClaudeEffort(caps, rawEffort) ?? null;
@@ -24379,16 +24852,13 @@ function createClaudeAdapter(recipe, host, options) {
24379
24852
  const thinking = thinkingSupported ? getModelSelectionBooleanOptionValue(modelSelection, "thinking") : void 0;
24380
24853
  const ultracode = isClaudeUltracodeEffort(effort);
24381
24854
  const effectiveEffort = getEffectiveClaudeAgentEffort(effort, modelSelection?.model, caps);
24382
- const permissionMode = CLAUDE_PERMISSION_MODE_BY_RUNTIME_MODE[input.runtimeMode];
24855
+ const permissionMode = claudePermissionModeFor(input.runtimeMode, input.nativeModeId);
24383
24856
  const settings = buildClaudeSessionSettings({
24384
24857
  thinking,
24385
24858
  fastMode,
24386
24859
  ultracode,
24387
24860
  autoCompactWindow: recipe.autoCompactWindow
24388
24861
  });
24389
- if (input.launch.processEnv) {
24390
- sessionLaunchEnv.set(input.threadId, input.launch.processEnv);
24391
- }
24392
24862
  const existingContext = sessions.get(input.threadId);
24393
24863
  if (existingContext) {
24394
24864
  const reconfigured = await reconfigureSession(existingContext, input, {
@@ -24407,10 +24877,7 @@ function createClaudeAdapter(recipe, host, options) {
24407
24877
  existingSessionStatus: existingContext.session.status,
24408
24878
  reason: "startSession called with existing active session"
24409
24879
  });
24410
- await stopSessionInternal(existingContext, {
24411
- emitExitEvent: false,
24412
- keepLaunch: true
24413
- });
24880
+ await stopSessionInternal(existingContext, { emitExitEvent: false });
24414
24881
  }
24415
24882
  const startedAt = nowIso();
24416
24883
  const resumeState = input.discardResumeCursor === true ? void 0 : readClaudeResumeState(input.resumeCursor);
@@ -24433,7 +24900,7 @@ function createClaudeAdapter(recipe, host, options) {
24433
24900
  return await Promise.race([
24434
24901
  contextDeferred.promise,
24435
24902
  new Promise(
24436
- (resolve2) => setTimeout(() => resolve2(void 0), CLAUDE_SESSION_CONTEXT_WAIT_MS)
24903
+ (resolve3) => setTimeout(() => resolve3(void 0), CLAUDE_SESSION_CONTEXT_WAIT_MS)
24437
24904
  )
24438
24905
  ]);
24439
24906
  } catch {
@@ -24575,7 +25042,7 @@ function createClaudeAdapter(recipe, host, options) {
24575
25042
  message: "The client captured your proposed plan. Stop here and wait for the user's feedback or implementation request in a later turn."
24576
25043
  };
24577
25044
  }
24578
- if (context2.session.runtimeMode === "full-access") {
25045
+ if (context2.currentPermissionMode === "bypassPermissions") {
24579
25046
  return {
24580
25047
  behavior: "allow",
24581
25048
  updatedInput: toolInput
@@ -24716,16 +25183,33 @@ function createClaudeAdapter(recipe, host, options) {
24716
25183
  };
24717
25184
  const onUserDialog = (request, callbackOptions) => handleResumeDialog(request, callbackOptions);
24718
25185
  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;
25186
+ const mcpServers = toClaudeMcpServers(input.mcpServers);
25187
+ const homeLayout = resolveClaudeLaunchHome({
25188
+ homePath: recipe.homePath,
25189
+ credentialsPath: recipe.credentialsPath,
25190
+ launchProcessEnv: input.launch.processEnv,
25191
+ pluginDataDirPath: options?.pluginDataDirPath,
25192
+ environment: claudeHomeEnvironment,
25193
+ ...options?.log === void 0 ? {} : { log: options.log }
25194
+ });
25195
+ await materializeClaudeRoutedHome(homeLayout, {
25196
+ ...input.cwd ? { trustedPaths: [input.cwd] } : {},
25197
+ ...options?.log === void 0 ? {} : { log: options.log }
25198
+ });
25199
+ const launchEnvironment = mergeLaunchEnv2({
25200
+ brainSlots: brainSlotEnvKeys3(CLAUDE_CARD),
25201
+ inherited: { ...process.env, ...options?.environment },
25202
+ instanceTable: recipe.environment,
25203
+ handOverrides: claudeHomeVariables(homeLayout),
25204
+ launch: input.launch.processEnv
25205
+ });
25206
+ const envOverride = routedEnvOverride({
25207
+ launchProcessEnv: input.launch.processEnv,
25208
+ mergedEnv: launchEnvironment,
25209
+ ...recipe.customModels === void 0 ? {} : { customModels: recipe.customModels }
25210
+ });
25211
+ const querySettings = envOverride === void 0 ? { ...settings } : { ...settings, env: envOverride };
25212
+ const stderrTail = createOutputTail(CLAUDE_STDERR_TAIL_CHARS);
24729
25213
  const queryOptions = {
24730
25214
  ...input.cwd ? { cwd: input.cwd } : {},
24731
25215
  ...apiModelId ? { model: apiModelId } : {},
@@ -24741,6 +25225,7 @@ function createClaudeAdapter(recipe, host, options) {
24741
25225
  ...existingResumeSessionId ? { resume: existingResumeSessionId } : {},
24742
25226
  ...newSessionId ? { sessionId: newSessionId } : {},
24743
25227
  includePartialMessages: true,
25228
+ stderr: stderrTail.append,
24744
25229
  canUseTool,
24745
25230
  onUserDialog,
24746
25231
  hooks: {
@@ -24758,9 +25243,10 @@ function createClaudeAdapter(recipe, host, options) {
24758
25243
  ]
24759
25244
  },
24760
25245
  supportedDialogKinds: ["resume_return"],
24761
- env: compactEnv2(launchEnvironment),
25246
+ env: compactEnv(launchEnvironment),
24762
25247
  additionalDirectories,
24763
- ...Object.keys(extraArgs).length > 0 ? { extraArgs } : {}
25248
+ ...Object.keys(extraArgs).length > 0 ? { extraArgs } : {},
25249
+ ...mcpServers === void 0 ? {} : { mcpServers }
24764
25250
  };
24765
25251
  let queryRuntime;
24766
25252
  try {
@@ -24777,6 +25263,7 @@ function createClaudeAdapter(recipe, host, options) {
24777
25263
  providerInstanceId: boundInstanceId,
24778
25264
  status: "ready",
24779
25265
  runtimeMode: input.runtimeMode,
25266
+ nativeModeId: permissionMode,
24780
25267
  ...input.cwd ? { cwd: input.cwd } : {},
24781
25268
  ...modelSelection?.model ? { model: modelSelection.model } : {},
24782
25269
  resumeCursor: {
@@ -24796,6 +25283,7 @@ function createClaudeAdapter(recipe, host, options) {
24796
25283
  },
24797
25284
  promptQueue,
24798
25285
  query: queryRuntime,
25286
+ stderrTail,
24799
25287
  streamFiber: void 0,
24800
25288
  startedAt,
24801
25289
  basePermissionMode: permissionMode,
@@ -24904,7 +25392,13 @@ function createClaudeAdapter(recipe, host, options) {
24904
25392
  }
24905
25393
  if (modelSelection?.model) {
24906
25394
  const turnCaps = claudeModelCapabilities(modelSelection.model, recipe.customModels);
24907
- const apiModelId = resolveClaudeApiModelId(turnCaps, { model: modelSelection.model });
25395
+ const apiModelId = resolveClaudeApiModelId(turnCaps, {
25396
+ model: claudeCliModelId({
25397
+ boundModelId: modelSelection.model,
25398
+ placeholder: CLAUDE_CARD.defaultModel,
25399
+ customModels: recipe.customModels
25400
+ })
25401
+ });
24908
25402
  if (context.currentApiModelId !== apiModelId) {
24909
25403
  try {
24910
25404
  await context.query.setModel(apiModelId);
@@ -25002,7 +25496,7 @@ function createClaudeAdapter(recipe, host, options) {
25002
25496
  context.query.interrupt().then(() => settled.promise).then(() => {
25003
25497
  acknowledged = true;
25004
25498
  }),
25005
- new Promise((resolve2) => setTimeout(resolve2, CLAUDE_INTERRUPT_GRACE_MS))
25499
+ new Promise((resolve3) => setTimeout(resolve3, CLAUDE_INTERRUPT_GRACE_MS))
25006
25500
  ]);
25007
25501
  } catch (cause) {
25008
25502
  throw toRequestError(threadId, "turn/interrupt", cause);
@@ -25037,22 +25531,49 @@ function createClaudeAdapter(recipe, host, options) {
25037
25531
  if (context.session.runtimeMode === runtimeMode) {
25038
25532
  return;
25039
25533
  }
25040
- const permissionMode = CLAUDE_PERMISSION_MODE_BY_RUNTIME_MODE[runtimeMode];
25534
+ const permissionMode = claudePermissionModeFor(runtimeMode);
25041
25535
  context.basePermissionMode = permissionMode;
25042
25536
  context.session = {
25043
25537
  ...context.session,
25044
25538
  runtimeMode,
25539
+ nativeModeId: permissionMode,
25045
25540
  updatedAt: nowIso()
25046
25541
  };
25047
25542
  if (context.currentPermissionMode !== "plan") {
25048
25543
  try {
25049
- await context.query.setPermissionMode(permissionMode ?? "default");
25544
+ await context.query.setPermissionMode(permissionMode);
25050
25545
  } catch (cause) {
25051
25546
  throw toRequestError(threadId, "session/setPermissionMode", cause);
25052
25547
  }
25053
25548
  context.currentPermissionMode = permissionMode;
25054
25549
  }
25055
- if (runtimeMode !== "full-access") {
25550
+ if (permissionMode !== "bypassPermissions") {
25551
+ return;
25552
+ }
25553
+ for (const [requestId, pending] of context.pendingApprovals) {
25554
+ context.pendingApprovals.delete(requestId);
25555
+ pending.decision.resolve({ decision: "accept" });
25556
+ }
25557
+ };
25558
+ const setNativeMode = async (threadId, nativeModeId) => {
25559
+ const context = requireSession(threadId);
25560
+ if (!isClaudeNativeModeId(nativeModeId)) return;
25561
+ if (context.currentPermissionMode === nativeModeId) return;
25562
+ context.basePermissionMode = nativeModeId;
25563
+ context.session = {
25564
+ ...context.session,
25565
+ nativeModeId,
25566
+ updatedAt: nowIso()
25567
+ };
25568
+ if (context.currentPermissionMode !== "plan" || nativeModeId === "plan") {
25569
+ try {
25570
+ await context.query.setPermissionMode(nativeModeId);
25571
+ } catch (cause) {
25572
+ throw toRequestError(threadId, "session/setPermissionMode", cause);
25573
+ }
25574
+ context.currentPermissionMode = nativeModeId;
25575
+ }
25576
+ if (nativeModeId !== "bypassPermissions") {
25056
25577
  return;
25057
25578
  }
25058
25579
  for (const [requestId, pending] of context.pendingApprovals) {
@@ -25085,6 +25606,7 @@ function createClaudeAdapter(recipe, host, options) {
25085
25606
  respondToRequest,
25086
25607
  respondToUserInput,
25087
25608
  setRuntimeMode,
25609
+ setNativeMode,
25088
25610
  stopSession,
25089
25611
  events: runtimeEventQueue,
25090
25612
  dispose: async () => {
@@ -25095,12 +25617,19 @@ function createClaudeAdapter(recipe, host, options) {
25095
25617
  }
25096
25618
  };
25097
25619
  }
25098
- function createClaudeInstance(config, host, attachments) {
25620
+ function claudeAdapterBaseEnvironment(recipe) {
25621
+ return {
25622
+ ...process.env,
25623
+ ...withoutBrainSlots(recipe.environment, brainSlotEnvKeys3(CLAUDE_CARD))
25624
+ };
25625
+ }
25626
+ function createClaudeInstance(config, host, attachments, pluginDataDirPath) {
25099
25627
  const recipe = parseClaudeInstanceConfig(config);
25100
25628
  const adapter = createClaudeAdapter(recipe, host, {
25101
25629
  instanceId: host.instanceId,
25102
25630
  ...attachments === void 0 ? {} : { attachments },
25103
- environment: { ...process.env, ...recipe.environment },
25631
+ ...pluginDataDirPath === void 0 ? {} : { pluginDataDirPath },
25632
+ environment: claudeAdapterBaseEnvironment(recipe),
25104
25633
  log: host.log
25105
25634
  });
25106
25635
  const collectProcessText = async (stream) => {
@@ -25111,6 +25640,15 @@ function createClaudeInstance(config, host, attachments) {
25111
25640
  return Buffer.concat(chunks).toString("utf8");
25112
25641
  };
25113
25642
  const probeAuth = () => {
25643
+ const hasEnvToken = (recipe.environment.ANTHROPIC_AUTH_TOKEN ?? "").trim().length > 0;
25644
+ const hasEnvApiKey = (recipe.environment.ANTHROPIC_API_KEY ?? "").trim().length > 0;
25645
+ if (hasEnvToken || hasEnvApiKey) {
25646
+ return {
25647
+ status: "authenticated",
25648
+ type: hasEnvToken ? "env-token" : "api-key",
25649
+ label: hasEnvToken ? "ANTHROPIC_AUTH_TOKEN" : "ANTHROPIC_API_KEY"
25650
+ };
25651
+ }
25114
25652
  const account = readClaudeAccount(resolveClaudeCredentialsDirPath(recipe.credentialsPath));
25115
25653
  switch (account.state) {
25116
25654
  case "signed-in":
@@ -25147,6 +25685,10 @@ function createClaudeInstance(config, host, attachments) {
25147
25685
  ]);
25148
25686
  const version = parseGenericCliVersion(`${stdout}
25149
25687
  ${stderr}`);
25688
+ const models = claudeProbeModels({
25689
+ builtIn: getBuiltInClaudeModelsForVersion(version),
25690
+ customModels: recipe.customModels
25691
+ });
25150
25692
  const auth = probeAuth();
25151
25693
  if (exitCode !== 0 && version === null) {
25152
25694
  const detail = (stderr || stdout || "claude --version failed").trim();
@@ -25164,14 +25706,19 @@ ${stderr}`);
25164
25706
  version,
25165
25707
  readiness: "error",
25166
25708
  auth,
25167
- detail: "Claude is signed out"
25709
+ models,
25710
+ detail: "Claude is signed out",
25711
+ // 退出登录了档位照样是那五档。不报就退回宿主四档,而那份映射少了 `plan`。
25712
+ ...CLAUDE_NATIVE_MODE_REPORT
25168
25713
  };
25169
25714
  }
25170
25715
  return {
25171
25716
  installed: true,
25172
25717
  version,
25173
25718
  readiness: "ready",
25174
- auth
25719
+ auth,
25720
+ models,
25721
+ ...CLAUDE_NATIVE_MODE_REPORT
25175
25722
  };
25176
25723
  };
25177
25724
  const resolveTranscriptRoots = (directory) => {
@@ -25182,7 +25729,7 @@ ${stderr}`);
25182
25729
  );
25183
25730
  return {
25184
25731
  configDir,
25185
- transcriptDir: NodePath6.join(configDir, "projects")
25732
+ transcriptDir: NodePath7.join(configDir, "projects")
25186
25733
  };
25187
25734
  };
25188
25735
  return {
@@ -25193,12 +25740,13 @@ ${stderr}`);
25193
25740
  respondToRequest: adapter.respondToRequest,
25194
25741
  respondToUserInput: adapter.respondToUserInput,
25195
25742
  setRuntimeMode: adapter.setRuntimeMode,
25743
+ setNativeMode: adapter.setNativeMode,
25196
25744
  compactContext: adapter.compactContext,
25197
25745
  stopSession: adapter.stopSession,
25198
25746
  readThread: adapter.readThread,
25199
25747
  events: adapter.events,
25200
25748
  dispose: adapter.dispose,
25201
- textGeneration: createClaudeTextGeneration(recipe, host),
25749
+ textGeneration: createClaudeTextGeneration(recipe, host, pluginDataDirPath),
25202
25750
  sessions: {
25203
25751
  scan: async (input) => {
25204
25752
  const roots = resolveTranscriptRoots(input.directory);
@@ -25239,12 +25787,13 @@ ${stderr}`);
25239
25787
 
25240
25788
  // src/index.ts
25241
25789
  var capturedAttachments;
25790
+ var capturedDataDirPath;
25242
25791
  var executors = [
25243
25792
  {
25244
25793
  card: CLAUDE_CARD,
25245
25794
  createInstance: async (config, host) => {
25246
25795
  host.log.info("createInstance");
25247
- return createClaudeInstance(config, host, capturedAttachments);
25796
+ return createClaudeInstance(config, host, capturedAttachments, capturedDataDirPath);
25248
25797
  }
25249
25798
  }
25250
25799
  ];
@@ -25254,6 +25803,7 @@ var index_default = definePlugin({
25254
25803
  executors,
25255
25804
  activate(ctx) {
25256
25805
  capturedAttachments = ctx.attachments;
25806
+ capturedDataDirPath = ctx.paths.dataDir;
25257
25807
  ctx.log.info("claude executor ready");
25258
25808
  }
25259
25809
  });
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.2126",
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.2126"
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": [