@ycodium-ai/agent-claude 0.2.3254 → 0.2.3272

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +189 -47
  2. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -19620,7 +19620,9 @@ import { mergeLaunchEnv as mergeLaunchEnv3 } from "@ycodium-ai/plugin-api/execut
19620
19620
  // src/claudeProbe.ts
19621
19621
  import {
19622
19622
  describeExecutorSpawnFailure,
19623
- ExecutorSpawnRefusedError
19623
+ ExecutorSpawnAbortedError,
19624
+ ExecutorSpawnRefusedError,
19625
+ raceAbort
19624
19626
  } from "@ycodium-ai/plugin-api/executor";
19625
19627
 
19626
19628
  // src/claudeAccount.ts
@@ -20167,13 +20169,52 @@ function isClaudeUltracodeEffort(effort) {
20167
20169
  }
20168
20170
  function claudeCliModelId(input) {
20169
20171
  const requested = input.requestedModelId.trim();
20170
- if (requested.length > 0 && (input.catalog ?? []).length === 0) return requested;
20172
+ if (requested.length === 0) return input.placeholder;
20173
+ if ((input.catalog ?? []).length === 0) return requested;
20171
20174
  const known = [
20172
20175
  ...(input.catalog ?? []).map((model) => model.slug),
20173
20176
  ...input.declared ?? [],
20174
20177
  ...(input.customModels ?? []).map((model) => normalizeCustomModelSlug(model.slug))
20175
20178
  ];
20176
- return matchCatalogModel(input.requestedModelId, known) ?? input.placeholder;
20179
+ const match = matchCatalogModel(input.requestedModelId, known);
20180
+ if (match !== void 0) return match;
20181
+ throw Object.assign(
20182
+ new Error(
20183
+ `Requested model "${requested}" is not in this agent's catalog; it is on "${input.placeholder}". This turn was not sent. Refresh the model list in this agent's settings and try again.`
20184
+ ),
20185
+ { failureCategory: "model-unavailable" }
20186
+ );
20187
+ }
20188
+ var isModelUnavailable = (error) => typeof error === "object" && error !== null && error.failureCategory === "model-unavailable";
20189
+ async function resolveClaudeCliModelId(input) {
20190
+ const read = () => claudeCliModelId({
20191
+ requestedModelId: input.requestedModelId,
20192
+ placeholder: input.placeholder,
20193
+ catalog: input.catalog(),
20194
+ ...input.declared === void 0 ? {} : { declared: input.declared },
20195
+ ...input.customModels === void 0 ? {} : { customModels: input.customModels }
20196
+ });
20197
+ try {
20198
+ return read();
20199
+ } catch (error) {
20200
+ if (!isModelUnavailable(error)) throw error;
20201
+ let failure;
20202
+ try {
20203
+ await input.refreshCatalog();
20204
+ } catch (cause) {
20205
+ failure = cause;
20206
+ }
20207
+ if (failure !== void 0 || !input.refreshSucceeded()) {
20208
+ const detail = failure instanceof Error && failure.message.trim().length > 0 ? failure.message : "the model list could not be read";
20209
+ throw Object.assign(
20210
+ new Error(
20211
+ `Refreshing this agent's model list failed (${detail}), so this turn was not sent. Requested model "${input.requestedModelId.trim()}".`
20212
+ ),
20213
+ { failureCategory: "model-unavailable" }
20214
+ );
20215
+ }
20216
+ return read();
20217
+ }
20177
20218
  }
20178
20219
  function getEffectiveClaudeAgentEffort(effort) {
20179
20220
  const normalized = normalizeClaudeCliEffort(effort);
@@ -20378,17 +20419,20 @@ function createClaudeProbe(input) {
20378
20419
  };
20379
20420
  }
20380
20421
  if (signal?.aborted) {
20381
- throw new Error("aborted");
20422
+ throw new ExecutorSpawnAbortedError();
20382
20423
  }
20383
20424
  let handle;
20384
20425
  try {
20385
20426
  handle = await host.process.spawn({
20386
20427
  program: recipe.binaryPath,
20387
20428
  args: ["--version"],
20388
- owner: { bindingId: host.bindingId }
20429
+ owner: { bindingId: host.bindingId },
20430
+ ...signal === void 0 ? {} : { signal }
20389
20431
  });
20390
20432
  } catch (error) {
20391
- if (error instanceof ExecutorSpawnRefusedError) throw error;
20433
+ if (error instanceof ExecutorSpawnRefusedError || error instanceof ExecutorSpawnAbortedError) {
20434
+ throw error;
20435
+ }
20392
20436
  const failure = describeExecutorSpawnFailure(error);
20393
20437
  return {
20394
20438
  installed: false,
@@ -20398,21 +20442,30 @@ function createClaudeProbe(input) {
20398
20442
  detail: failure.detail
20399
20443
  };
20400
20444
  }
20401
- const [stdout, stderr, exitCode] = await Promise.all([
20402
- collectProcessText(handle.stdout),
20403
- collectProcessText(handle.stderr),
20404
- handle.exited
20405
- ]);
20445
+ const [stdout, stderr, exitCode] = await raceAbort(
20446
+ Promise.all([
20447
+ collectProcessText(handle.stdout),
20448
+ collectProcessText(handle.stderr),
20449
+ handle.exited
20450
+ ]),
20451
+ signal
20452
+ );
20453
+ if (signal?.aborted) throw new ExecutorSpawnAbortedError();
20406
20454
  const version = parseGenericCliVersion(`${stdout}
20407
20455
  ${stderr}`);
20408
20456
  if (version !== null || exitCode === 0) {
20409
- await modelCatalog.refresh({ signal, force: version !== lastProbedVersion });
20457
+ await modelCatalog.refresh({
20458
+ signal,
20459
+ force: version !== lastProbedVersion,
20460
+ paired: true
20461
+ });
20410
20462
  lastProbedVersion = version;
20411
20463
  }
20412
20464
  const models = claudeProbeModels({
20413
20465
  catalog: modelCatalog.current(),
20414
20466
  customModels: recipe.customModels
20415
20467
  });
20468
+ const reportedModels = modelCatalog.lastRefreshOk() ? { models } : {};
20416
20469
  const auth = probeAuth();
20417
20470
  const skills = await discoverClaudeSkills(
20418
20471
  resolveClaudeConfigDirPath(recipe.homePath, { ...process.env, ...recipe.environment })
@@ -20433,7 +20486,7 @@ ${stderr}`);
20433
20486
  version,
20434
20487
  readiness: "error",
20435
20488
  auth,
20436
- models,
20489
+ ...reportedModels,
20437
20490
  skills,
20438
20491
  detail: "Claude is signed out",
20439
20492
  // 退出登录了档位照样是那五档。不报就退回宿主四档,而那份映射少了 `plan`。
@@ -20446,7 +20499,7 @@ ${stderr}`);
20446
20499
  version,
20447
20500
  readiness: "ready",
20448
20501
  auth,
20449
- models,
20502
+ ...reportedModels,
20450
20503
  skills,
20451
20504
  ...notice === void 0 ? {} : { notice },
20452
20505
  ...CLAUDE_NATIVE_MODE_REPORT
@@ -20454,6 +20507,46 @@ ${stderr}`);
20454
20507
  };
20455
20508
  return probe;
20456
20509
  }
20510
+ var CLAUDE_VERSION_TIMEOUT_MS = 15e3;
20511
+ async function readClaudeCliVersion(input) {
20512
+ if (input.recipe.binaryPath.length === 0) return null;
20513
+ let handle;
20514
+ try {
20515
+ handle = await input.host.process.spawn({
20516
+ program: input.recipe.binaryPath,
20517
+ args: ["--version"],
20518
+ owner: { bindingId: input.host.bindingId }
20519
+ });
20520
+ } catch {
20521
+ return null;
20522
+ }
20523
+ const collect = async (stream) => {
20524
+ const chunks = [];
20525
+ for await (const chunk of stream) chunks.push(Buffer.from(chunk));
20526
+ return Buffer.concat(chunks).toString("utf8");
20527
+ };
20528
+ const collected = Promise.all([collect(handle.stdout), collect(handle.stderr), handle.exited]);
20529
+ let timer;
20530
+ const timeout = new Promise((resolve4) => {
20531
+ timer = setTimeout(() => resolve4("timeout"), CLAUDE_VERSION_TIMEOUT_MS);
20532
+ timer.unref?.();
20533
+ });
20534
+ try {
20535
+ const settled = await Promise.race([collected, timeout]);
20536
+ if (settled === "timeout") {
20537
+ collected.catch(() => void 0);
20538
+ return null;
20539
+ }
20540
+ const [stdout, stderr] = settled;
20541
+ return parseGenericCliVersion(`${stdout}
20542
+ ${stderr}`);
20543
+ } catch {
20544
+ return null;
20545
+ } finally {
20546
+ if (timer !== void 0) clearTimeout(timer);
20547
+ await handle.kill("SIGTERM").catch(() => void 0);
20548
+ }
20549
+ }
20457
20550
 
20458
20551
  // src/claudeExecutable.ts
20459
20552
  import * as NodeFS2 from "node:fs";
@@ -22346,11 +22439,22 @@ var makeClaudeModelCatalog = (deps) => {
22346
22439
  let modelCatalog;
22347
22440
  let modelCatalogRead;
22348
22441
  let modelCatalogFailedAt;
22442
+ let catalogReadOk = false;
22443
+ let successfulReadAt;
22444
+ let skipPairedRefresh = false;
22349
22445
  const current = () => modelCatalog?.models ?? [];
22350
22446
  const refresh = (input) => {
22351
- const fresh = modelCatalog !== void 0 && deps.now() - modelCatalog.readAt < CLAUDE_SDK_MODELS_TTL_MS;
22352
- if (fresh && input?.force !== true) return Promise.resolve();
22447
+ if (input?.paired === true && skipPairedRefresh) {
22448
+ skipPairedRefresh = false;
22449
+ return Promise.resolve();
22450
+ }
22451
+ const fresh = successfulReadAt !== void 0 && deps.now() - successfulReadAt < CLAUDE_SDK_MODELS_TTL_MS;
22452
+ if (fresh && input?.force !== true) {
22453
+ catalogReadOk = true;
22454
+ return Promise.resolve();
22455
+ }
22353
22456
  if (input?.force !== true && modelCatalogFailedAt !== void 0 && deps.now() - modelCatalogFailedAt < CLAUDE_SDK_MODELS_RETRY_MS) {
22457
+ catalogReadOk = false;
22354
22458
  return Promise.resolve();
22355
22459
  }
22356
22460
  if (modelCatalogRead !== void 0) return modelCatalogRead;
@@ -22369,9 +22473,14 @@ var makeClaudeModelCatalog = (deps) => {
22369
22473
  });
22370
22474
  if (models === void 0) {
22371
22475
  modelCatalogFailedAt = deps.now();
22476
+ catalogReadOk = false;
22477
+ successfulReadAt = void 0;
22372
22478
  } else {
22479
+ const readAt = deps.now();
22373
22480
  modelCatalogFailedAt = void 0;
22374
- modelCatalog = { models: claudeCatalogFromSdk(models), readAt: deps.now() };
22481
+ catalogReadOk = true;
22482
+ successfulReadAt = readAt;
22483
+ modelCatalog = { models: claudeCatalogFromSdk(models), readAt };
22375
22484
  }
22376
22485
  })().finally(() => {
22377
22486
  modelCatalogRead = void 0;
@@ -22383,7 +22492,12 @@ var makeClaudeModelCatalog = (deps) => {
22383
22492
  current,
22384
22493
  refresh,
22385
22494
  /** 此刻正在进行的那次读取;没有就是 undefined。起会话等它读完,不为此另起一次。 */
22386
- pendingRead: () => modelCatalogRead
22495
+ pendingRead: () => modelCatalogRead,
22496
+ /** 最近一次读取拿到了清单。新鲜跳过也算;退避或读失败不算。 */
22497
+ lastRefreshOk: () => catalogReadOk,
22498
+ notePairedRefresh: () => {
22499
+ skipPairedRefresh = true;
22500
+ }
22387
22501
  };
22388
22502
  };
22389
22503
 
@@ -22830,20 +22944,14 @@ function claudeUsesOfficialChannel(env2) {
22830
22944
  return false;
22831
22945
  }
22832
22946
  }
22833
- var ALLOWLIST_MODE_TOOLS = [
22834
- "Read",
22835
- "Glob",
22836
- "Grep",
22837
- "Edit",
22838
- "MultiEdit",
22839
- "Write",
22840
- "NotebookEdit",
22841
- "Bash"
22842
- ];
22947
+ var ALLOWLIST_MODE_TOOLS = ["Read", "Glob", "Grep", "Bash"];
22843
22948
  function claudeSdkPermission(rules, mode) {
22844
22949
  if (mode !== "auto") return { mode, allow: rules.read };
22845
22950
  if (rules.officialChannel) return { mode, allow: [...rules.read, ...rules.hostMcp] };
22846
- return { mode: "dontAsk", allow: [...rules.read, ...rules.hostMcp, ...ALLOWLIST_MODE_TOOLS] };
22951
+ return {
22952
+ mode: "dontAsk",
22953
+ allow: [...rules.read, ...rules.hostMcp, ...ALLOWLIST_MODE_TOOLS, ...rules.workspaceWrite]
22954
+ };
22847
22955
  }
22848
22956
 
22849
22957
  // src/adapter/permissionMode.ts
@@ -22879,6 +22987,8 @@ var makeSendTurn = (deps) => {
22879
22987
  recipe,
22880
22988
  boundInstanceId,
22881
22989
  currentModelCatalog,
22990
+ refreshModelCatalogForSend,
22991
+ modelCatalogRefreshSucceeded,
22882
22992
  declaredModels,
22883
22993
  requireSession,
22884
22994
  completeTurn,
@@ -22895,18 +23005,20 @@ var makeSendTurn = (deps) => {
22895
23005
  await completeTurn(context, "completed");
22896
23006
  }
22897
23007
  if (modelSelection?.model) {
23008
+ const apiModelId = await resolveClaudeCliModelId({
23009
+ requestedModelId: modelSelection.model,
23010
+ placeholder: CLAUDE_CARD.defaultModel,
23011
+ catalog: currentModelCatalog,
23012
+ declared: declaredModels,
23013
+ customModels: recipe.customModels,
23014
+ refreshCatalog: refreshModelCatalogForSend,
23015
+ refreshSucceeded: modelCatalogRefreshSucceeded
23016
+ });
22898
23017
  const turnCaps = claudeModelCapabilities(
22899
23018
  modelSelection.model,
22900
23019
  currentModelCatalog(),
22901
23020
  recipe.customModels
22902
23021
  );
22903
- const apiModelId = claudeCliModelId({
22904
- requestedModelId: modelSelection.model,
22905
- placeholder: CLAUDE_CARD.defaultModel,
22906
- catalog: currentModelCatalog(),
22907
- declared: declaredModels,
22908
- customModels: recipe.customModels
22909
- });
22910
23022
  if (context.currentApiModelId !== apiModelId) {
22911
23023
  try {
22912
23024
  await context.query.setModel(apiModelId);
@@ -23907,14 +24019,20 @@ function toClaudeAbsolutePath(directory) {
23907
24019
  if (/^\/[^/]/.test(trimmed)) return `/${trimmed.replace(/\/+$/, "")}`;
23908
24020
  return void 0;
23909
24021
  }
23910
- function claudeReadAllowRules(directories) {
24022
+ function claudeDirectoryRules(tools, directories) {
23911
24023
  const rules = [];
23912
24024
  for (const directory of directories ?? []) {
23913
24025
  const path = toClaudeAbsolutePath(directory);
23914
- if (path !== void 0) rules.push(`Read(${path}/**)`);
24026
+ if (path !== void 0) for (const tool of tools) rules.push(`${tool}(${path}/**)`);
23915
24027
  }
23916
24028
  return rules;
23917
24029
  }
24030
+ function claudeReadAllowRules(directories) {
24031
+ return claudeDirectoryRules(["Read"], directories);
24032
+ }
24033
+ function claudeWorkspaceWriteAllowRules(directories) {
24034
+ return claudeDirectoryRules(["Edit", "MultiEdit", "Write", "NotebookEdit"], directories);
24035
+ }
23918
24036
 
23919
24037
  // src/adapter/startSession.ts
23920
24038
  function toClaudeMcpServers(entries) {
@@ -23967,19 +24085,21 @@ var makeStartSession = (deps) => {
23967
24085
  } = flags;
23968
24086
  const modelSelection = boundModelSelection(input.modelSelection, boundInstanceId);
23969
24087
  await modelCatalog.pendingRead();
24088
+ const apiModelId = modelSelection?.model ? await resolveClaudeCliModelId({
24089
+ requestedModelId: modelSelection.model,
24090
+ placeholder: CLAUDE_CARD.defaultModel,
24091
+ catalog: currentModelCatalog,
24092
+ declared: declaredModels,
24093
+ customModels: recipe.customModels,
24094
+ refreshCatalog: () => modelCatalog.refresh({ force: true }),
24095
+ refreshSucceeded: () => modelCatalog.lastRefreshOk()
24096
+ }) : void 0;
23970
24097
  const caps = claudeModelCapabilities(
23971
24098
  modelSelection?.model,
23972
24099
  currentModelCatalog(),
23973
24100
  recipe.customModels
23974
24101
  );
23975
24102
  const descriptors = caps.optionDescriptors;
23976
- const apiModelId = modelSelection?.model ? claudeCliModelId({
23977
- requestedModelId: modelSelection.model,
23978
- placeholder: CLAUDE_CARD.defaultModel,
23979
- catalog: currentModelCatalog(),
23980
- declared: declaredModels,
23981
- customModels: recipe.customModels
23982
- }) : void 0;
23983
24103
  const rawEffort = getModelSelectionStringOptionValue(modelSelection, "effort");
23984
24104
  const effort = resolveClaudeEffort(caps, rawEffort) ?? null;
23985
24105
  const fastModeSupported = descriptors.some(
@@ -24076,6 +24196,7 @@ var makeStartSession = (deps) => {
24076
24196
  const permissionRules = {
24077
24197
  read: claudeReadAllowRules(input.readOnlyDirectories),
24078
24198
  hostMcp: claudeHostMcpAllowRules(input.mcpServers),
24199
+ workspaceWrite: claudeWorkspaceWriteAllowRules(additionalDirectories),
24079
24200
  officialChannel: claudeUsesOfficialChannel(spawnEnvironment.processEnv)
24080
24201
  };
24081
24202
  const sdkPermission = claudeSdkPermission(permissionRules, permissionMode);
@@ -28097,6 +28218,8 @@ function createClaudeAdapter(recipe, host, options) {
28097
28218
  boundInstanceId,
28098
28219
  attachments: options?.attachments,
28099
28220
  currentModelCatalog,
28221
+ refreshModelCatalogForSend: () => modelCatalog.refresh({ force: true }),
28222
+ modelCatalogRefreshSucceeded: () => modelCatalog.lastRefreshOk(),
28100
28223
  declaredModels,
28101
28224
  requireSession,
28102
28225
  completeTurn,
@@ -28109,7 +28232,12 @@ function createClaudeAdapter(recipe, host, options) {
28109
28232
  };
28110
28233
  return {
28111
28234
  /** 型号目录:探测用它读并拿当前这份;会话内部自己走同一份。 */
28112
- modelCatalog: { refresh: modelCatalog.refresh, current: currentModelCatalog },
28235
+ modelCatalog: {
28236
+ refresh: modelCatalog.refresh,
28237
+ current: currentModelCatalog,
28238
+ lastRefreshOk: modelCatalog.lastRefreshOk,
28239
+ notePairedRefresh: modelCatalog.notePairedRefresh
28240
+ },
28113
28241
  /** 会话起进程用的那份环境;探测判工具搜索提示也看它。 */
28114
28242
  buildLaunchEnvironment,
28115
28243
  startSession,
@@ -28169,6 +28297,20 @@ function createClaudeInstance(config, host, attachments, overrides, usageCache)
28169
28297
  };
28170
28298
  return {
28171
28299
  probe,
28300
+ readCliVersion: () => readClaudeCliVersion({ recipe, host }),
28301
+ // `force: false` 立刻返回。每天那次探测自己按清单 TTL 决定要不要读;这里只服务「版本变了」和手动刷新。
28302
+ refreshModelCatalog: async ({ force }) => {
28303
+ if (!force) return;
28304
+ try {
28305
+ await adapter.modelCatalog.refresh({ force: true });
28306
+ } catch (error) {
28307
+ host.log.warn("claude model catalog refresh failed", {
28308
+ detail: error instanceof Error ? error.message : String(error)
28309
+ });
28310
+ } finally {
28311
+ adapter.modelCatalog.notePairedRefresh();
28312
+ }
28313
+ },
28172
28314
  startSession: adapter.startSession,
28173
28315
  sendTurn: adapter.sendTurn,
28174
28316
  interruptTurn: adapter.interruptTurn,
@@ -28246,7 +28388,7 @@ var executors = [
28246
28388
  ];
28247
28389
  var index_default = definePlugin({
28248
28390
  id: "agent-claude",
28249
- apiVersion: 30,
28391
+ apiVersion: 32,
28250
28392
  executors,
28251
28393
  activate(ctx) {
28252
28394
  capturedAttachments = ctx.attachments;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ycodium-ai/agent-claude",
3
- "version": "0.2.3254",
3
+ "version": "0.2.3272",
4
4
  "description": "Claude executor: Claude Code CLI via @anthropic-ai/claude-agent-sdk.",
5
5
  "keywords": [
6
6
  "ycodium",
@@ -24,10 +24,10 @@
24
24
  "@types/node": "24.12.4",
25
25
  "esbuild": "0.28.1",
26
26
  "vite-plus": "0.2.2",
27
- "@ycodium-ai/plugin-api": "0.2.3254",
28
27
  "ycodium-composer-tokens": "0.0.0",
29
- "ycodium-executor-testing": "0.0.0",
30
- "ycodium-jsonl-stream": "0.0.0"
28
+ "ycodium-jsonl-stream": "0.0.0",
29
+ "@ycodium-ai/plugin-api": "0.2.3272",
30
+ "ycodium-executor-testing": "0.0.0"
31
31
  },
32
32
  "ycodium": {
33
33
  "plugin": "./dist/index.js",