@ycodium-ai/agent-acp 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 +17 -3
  2. package/dist/index.js +930 -110
  3. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -8,8 +8,14 @@ var __export = (target, all) => {
8
8
  import { definePlugin } from "@ycodium-ai/plugin-api";
9
9
 
10
10
  // src/acpInstance.ts
11
- import * as NodeOS from "node:os";
12
- import * as NodePath from "node:path";
11
+ import * as NodeOS2 from "node:os";
12
+ import * as NodePath3 from "node:path";
13
+ import {
14
+ mergeLaunchEnv,
15
+ modelSelectionStringOption,
16
+ brainSlotEnvKeys
17
+ } from "@ycodium-ai/plugin-api/executor";
18
+ import { nodeStreamsFromHandle as nodeStreamsFromHandle2 } from "@ycodium-ai/plugin-api/process-stdin";
13
19
 
14
20
  // ../../packages/ycodium-acp-executor/src/executor.ts
15
21
  import * as NodeCrypto from "node:crypto";
@@ -23879,6 +23885,12 @@ function acpApprovalOptions(request) {
23879
23885
  }
23880
23886
  return options;
23881
23887
  }
23888
+ function selectAutoApprovedAcpPermissionOption(request) {
23889
+ if (new Set(request.options.map((option) => option.kind)).size !== request.options.length) {
23890
+ return void 0;
23891
+ }
23892
+ return selectAcpPermissionOptionId(request, "acceptForSession") ?? selectAcpPermissionOptionId(request, "accept");
23893
+ }
23882
23894
  function parseAcpResumeCursor(raw, schemaVersion) {
23883
23895
  if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return void 0;
23884
23896
  const record2 = raw;
@@ -24272,6 +24284,233 @@ function makeAcpContentDeltaEvent(input2) {
24272
24284
  };
24273
24285
  }
24274
24286
 
24287
+ // ../../packages/ycodium-acp-executor/src/config-options.ts
24288
+ function isRecord3(value) {
24289
+ return typeof value === "object" && value !== null && !Array.isArray(value);
24290
+ }
24291
+ function asString(value) {
24292
+ return typeof value === "string" ? value : void 0;
24293
+ }
24294
+ function normalizedField(option, key) {
24295
+ return asString(option[key])?.trim().toLowerCase() ?? "";
24296
+ }
24297
+ function looksLikeEffortOption(option) {
24298
+ const id = normalizedField(option, "id");
24299
+ const name = normalizedField(option, "name");
24300
+ return id === "effort" || id === "reasoning" || name === "effort" || name === "reasoning" || name.includes("effort") || name.includes("reasoning") || normalizedField(option, "category") === "thought_level";
24301
+ }
24302
+ function flattenChoices(rawOptions, currentValue) {
24303
+ if (!Array.isArray(rawOptions)) return [];
24304
+ const out = [];
24305
+ const seen = /* @__PURE__ */ new Set();
24306
+ const push = (entry) => {
24307
+ if (!isRecord3(entry)) return;
24308
+ const value = asString(entry["value"]);
24309
+ if (value === void 0 || seen.has(value)) return;
24310
+ seen.add(value);
24311
+ const description = asString(entry["description"])?.trim();
24312
+ const name = asString(entry["name"])?.trim();
24313
+ out.push({
24314
+ value,
24315
+ ...name ? { name } : {},
24316
+ label: name || value,
24317
+ ...description ? { description } : {},
24318
+ ...currentValue !== void 0 && currentValue === value ? { isDefault: true } : {}
24319
+ });
24320
+ };
24321
+ for (const entry of rawOptions) {
24322
+ if (isRecord3(entry) && Array.isArray(entry["options"])) {
24323
+ for (const nested of entry["options"]) push(nested);
24324
+ continue;
24325
+ }
24326
+ push(entry);
24327
+ }
24328
+ return out;
24329
+ }
24330
+ function findAcpEffortConfig(configOptions) {
24331
+ if (!Array.isArray(configOptions)) return void 0;
24332
+ const candidates = configOptions.filter(isRecord3).filter((option) => normalizedField(option, "type") === "select").filter(looksLikeEffortOption);
24333
+ if (candidates.length === 0) return void 0;
24334
+ const picked = candidates.find((option) => normalizedField(option, "category") === "model_option") ?? candidates.find((option) => normalizedField(option, "id") === "effort") ?? candidates.find((option) => normalizedField(option, "category") === "thought_level") ?? candidates[0];
24335
+ if (picked === void 0) return void 0;
24336
+ const configId = asString(picked["id"])?.trim();
24337
+ if (!configId) return void 0;
24338
+ const choices = flattenChoices(picked["options"], asString(picked["currentValue"])?.trim());
24339
+ if (choices.length === 0) return void 0;
24340
+ return {
24341
+ configId,
24342
+ label: asString(picked["name"])?.trim() ?? "",
24343
+ choices
24344
+ };
24345
+ }
24346
+ function acpModelChoicesFromConfigOptions(configOptions) {
24347
+ if (!Array.isArray(configOptions)) return void 0;
24348
+ const records = configOptions.filter(isRecord3);
24349
+ const modelOption = records.find((option) => normalizedField(option, "category") === "model");
24350
+ if (modelOption === void 0) return void 0;
24351
+ if (!asString(modelOption["id"])?.trim()) return void 0;
24352
+ return flattenChoices(modelOption["options"], asString(modelOption["currentValue"])?.trim());
24353
+ }
24354
+ function acpModelChoicesFromSessionModels(models) {
24355
+ if (!isRecord3(models) || !Array.isArray(models["availableModels"])) return void 0;
24356
+ const currentModelId = asString(models["currentModelId"])?.trim();
24357
+ const out = [];
24358
+ const seen = /* @__PURE__ */ new Set();
24359
+ for (const entry of models["availableModels"]) {
24360
+ if (!isRecord3(entry)) continue;
24361
+ const value = asString(entry["modelId"])?.trim();
24362
+ if (!value || seen.has(value)) continue;
24363
+ seen.add(value);
24364
+ const name = asString(entry["name"])?.trim();
24365
+ const description = asString(entry["description"])?.trim();
24366
+ out.push({
24367
+ value,
24368
+ ...name ? { name } : {},
24369
+ label: name || value,
24370
+ ...description ? { description } : {},
24371
+ ...currentModelId !== void 0 && currentModelId === value ? { isDefault: true } : {}
24372
+ });
24373
+ }
24374
+ return out;
24375
+ }
24376
+
24377
+ // ../../packages/ycodium-acp-executor/src/runtime-mode.ts
24378
+ function isRecord4(value) {
24379
+ return typeof value === "object" && value !== null && !Array.isArray(value);
24380
+ }
24381
+ function asString2(value) {
24382
+ return typeof value === "string" ? value : void 0;
24383
+ }
24384
+ function acpAvailableModes(setup) {
24385
+ if (!isRecord4(setup)) return [];
24386
+ const out = [];
24387
+ const seen = /* @__PURE__ */ new Set();
24388
+ const push = (entry) => {
24389
+ if (!isRecord4(entry)) return;
24390
+ const id = (asString2(entry["id"]) ?? asString2(entry["value"]))?.trim();
24391
+ if (!id || seen.has(id)) return;
24392
+ seen.add(id);
24393
+ const meta3 = entry["_meta"];
24394
+ const kind = isRecord4(meta3) ? asString2(meta3["kind"])?.trim().toLowerCase() : void 0;
24395
+ const name = asString2(entry["name"])?.trim();
24396
+ const description = asString2(entry["description"])?.trim();
24397
+ out.push({
24398
+ id,
24399
+ name: name || id,
24400
+ ...description ? { description } : {},
24401
+ ...kind ? { kind } : {}
24402
+ });
24403
+ };
24404
+ const modes = setup["modes"];
24405
+ if (isRecord4(modes) && Array.isArray(modes["availableModes"])) {
24406
+ for (const entry of modes["availableModes"]) push(entry);
24407
+ }
24408
+ const configOptions = setup["configOptions"];
24409
+ if (Array.isArray(configOptions)) {
24410
+ for (const option of configOptions) {
24411
+ if (!isRecord4(option)) continue;
24412
+ if (asString2(option["category"])?.trim().toLowerCase() !== "mode") continue;
24413
+ if (!Array.isArray(option["options"])) continue;
24414
+ for (const entry of option["options"]) push(entry);
24415
+ }
24416
+ }
24417
+ return out;
24418
+ }
24419
+ function acpModeConfigId(setup) {
24420
+ if (!isRecord4(setup)) return void 0;
24421
+ const configOptions = setup["configOptions"];
24422
+ if (!Array.isArray(configOptions)) return void 0;
24423
+ for (const option of configOptions) {
24424
+ if (!isRecord4(option)) continue;
24425
+ if (asString2(option["category"])?.trim().toLowerCase() !== "mode") continue;
24426
+ const id = asString2(option["id"])?.trim();
24427
+ if (id) return id;
24428
+ }
24429
+ return void 0;
24430
+ }
24431
+ function acpCurrentModeId(setup) {
24432
+ if (!isRecord4(setup)) return void 0;
24433
+ const modes = setup["modes"];
24434
+ if (isRecord4(modes)) {
24435
+ const current = asString2(modes["currentModeId"])?.trim();
24436
+ if (current) return current;
24437
+ }
24438
+ const configOptions = setup["configOptions"];
24439
+ if (Array.isArray(configOptions)) {
24440
+ for (const option of configOptions) {
24441
+ if (!isRecord4(option)) continue;
24442
+ if (asString2(option["category"])?.trim().toLowerCase() !== "mode") continue;
24443
+ const current = asString2(option["currentValue"])?.trim();
24444
+ if (current) return current;
24445
+ }
24446
+ }
24447
+ return void 0;
24448
+ }
24449
+
24450
+ // ../../packages/ycodium-acp-executor/src/handshake.ts
24451
+ function acpInitializePayload(clientInfo, overrides) {
24452
+ return {
24453
+ protocolVersion: PROTOCOL_VERSION,
24454
+ clientCapabilities: {
24455
+ fs: {
24456
+ readTextFile: false,
24457
+ writeTextFile: false,
24458
+ ...overrides?.fs
24459
+ },
24460
+ terminal: overrides?.terminal ?? false
24461
+ },
24462
+ clientInfo
24463
+ };
24464
+ }
24465
+
24466
+ // ../../packages/ycodium-acp-executor/src/config-option-wiring.ts
24467
+ var VENDOR_CATEGORY_PREFIX = "_";
24468
+ var ACP_CONFIG_CATEGORY_HANDLING = {
24469
+ mode: {
24470
+ consumed: true,
24471
+ via: "runtime-mode.ts\uFF1AacpAvailableModes / acpCurrentModeId / acpModeConfigId"
24472
+ },
24473
+ model: {
24474
+ consumed: true,
24475
+ via: "runtime-model.ts:extractModelConfigId \u2192 executor.ts:applyRequestedModel"
24476
+ },
24477
+ thought_level: {
24478
+ consumed: true,
24479
+ via: "config-options.ts:findAcpEffortConfig \u2192 executor.ts:applyRequestedEffort"
24480
+ },
24481
+ model_option: {
24482
+ consumed: true,
24483
+ // 精确说法:它不是**识别器**,是**优先级**。`looksLikeEffortOption` 先筛出候选,
24484
+ // 标了 `model_option` 的在候选里排第一(厂商自己标的最准),排在 id 叫 `effort`
24485
+ // 的和标 `thought_level` 的前面。光有这个 category 而 id/name 不像力度档的,
24486
+ // 压根进不了候选。
24487
+ via: "config-options.ts:findAcpEffortConfig\uFF08\u5019\u9009\u91CC\u7684\u6700\u9AD8\u4F18\u5148\u7EA7\uFF0C\u538B\u8FC7 thought_level\uFF09"
24488
+ },
24489
+ model_config: {
24490
+ consumed: false,
24491
+ note: "\u53EA\u6709\u5BBF\u4E3B\u7684 CursorProvider \u6D88\u8D39\u8FC7\uFF08context / fast / thinking \u4E09\u4E2A\u5F00\u5173\uFF09\u3002Cursor 2026-09-20 \u5E9F\u5F03\u5220\u9664\uFF0C\u8FD9\u4E00\u7C7B\u73B0\u5728\u6CA1\u6709\u6D88\u8D39\u70B9\uFF1B\u6CA1\u89C1\u8FC7\u522B\u5BB6\u7684\u771F\u5B9E\u8F7D\u8377\uFF0C\u5148\u8BB0\u8D26\u4E0D\u731C\u5F62\u72B6\u3002"
24492
+ }
24493
+ };
24494
+ var KNOWN_CATEGORIES = new Set(Object.keys(ACP_CONFIG_CATEGORY_HANDLING));
24495
+ function unhandledConfigCategories(configOptions) {
24496
+ if (!configOptions) return [];
24497
+ const seen = /* @__PURE__ */ new Set();
24498
+ const out = [];
24499
+ for (const option of configOptions) {
24500
+ const category = option.category?.trim();
24501
+ if (!category) continue;
24502
+ if (KNOWN_CATEGORIES.has(category)) continue;
24503
+ if (seen.has(category)) continue;
24504
+ seen.add(category);
24505
+ out.push({
24506
+ category,
24507
+ optionId: option.id.trim(),
24508
+ vendorExtension: category.startsWith(VENDOR_CATEGORY_PREFIX)
24509
+ });
24510
+ }
24511
+ return out;
24512
+ }
24513
+
24275
24514
  // ../../packages/ycodium-acp-executor/src/mutex.ts
24276
24515
  var Mutex = class {
24277
24516
  locked = false;
@@ -24314,7 +24553,7 @@ var Mutex = class {
24314
24553
  };
24315
24554
 
24316
24555
  // ../../packages/ycodium-acp-executor/src/util.ts
24317
- function isRecord3(value) {
24556
+ function isRecord5(value) {
24318
24557
  return typeof value === "object" && value !== null && !Array.isArray(value);
24319
24558
  }
24320
24559
  function asTrimmedString(value) {
@@ -24360,11 +24599,11 @@ function extractCommandFromTitle(title) {
24360
24599
  return backtickMatch?.[1]?.trim() || void 0;
24361
24600
  }
24362
24601
  function extractToolCommand(data, title) {
24363
- const item = isRecord3(data?.["item"]) ? data["item"] : void 0;
24364
- const itemInput = item && isRecord3(item["input"]) ? item["input"] : void 0;
24365
- const itemResult = item && isRecord3(item["result"]) ? item["result"] : void 0;
24366
- const rawInput = isRecord3(data?.["rawInput"]) ? data["rawInput"] : void 0;
24367
- const dataInput = isRecord3(data?.["input"]) ? data["input"] : void 0;
24602
+ const item = isRecord5(data?.["item"]) ? data["item"] : void 0;
24603
+ const itemInput = item && isRecord5(item["input"]) ? item["input"] : void 0;
24604
+ const itemResult = item && isRecord5(item["result"]) ? item["result"] : void 0;
24605
+ const rawInput = isRecord5(data?.["rawInput"]) ? data["rawInput"] : void 0;
24606
+ const dataInput = isRecord5(data?.["input"]) ? data["input"] : void 0;
24368
24607
  const candidates = [
24369
24608
  normalizeCommandValue(item?.["command"]),
24370
24609
  normalizeCommandValue(itemInput?.["command"]),
@@ -24409,7 +24648,7 @@ function collectPaths(value, paths, seen, depth) {
24409
24648
  }
24410
24649
  return;
24411
24650
  }
24412
- if (!isRecord3(value)) {
24651
+ if (!isRecord5(value)) {
24413
24652
  return;
24414
24653
  }
24415
24654
  for (const key of ["path", "filePath", "relativePath", "filename", "newPath", "oldPath"]) {
@@ -24472,7 +24711,7 @@ function deriveToolActivityPresentation(input2) {
24472
24711
  const title = asTrimmedString(input2.title);
24473
24712
  const detail = stripTrailingExitCode(asTrimmedString(input2.detail));
24474
24713
  const fallbackSummary = asTrimmedString(input2.fallbackSummary) ?? "Tool";
24475
- const data = isRecord3(input2.data) ? input2.data : void 0;
24714
+ const data = isRecord5(input2.data) ? input2.data : void 0;
24476
24715
  const command = extractToolCommand(data, title);
24477
24716
  const primaryPath = extractPrimaryPath(data);
24478
24717
  const action = classifyToolAction({
@@ -24504,7 +24743,7 @@ function deriveToolActivityPresentation(input2) {
24504
24743
  };
24505
24744
  }
24506
24745
  if (action === "search") {
24507
- const rawInput = isRecord3(data?.["rawInput"]) ? data["rawInput"] : void 0;
24746
+ const rawInput = isRecord5(data?.["rawInput"]) ? data["rawInput"] : void 0;
24508
24747
  const query = asTrimmedString(rawInput?.["query"]) ?? asTrimmedString(rawInput?.["pattern"]) ?? asTrimmedString(rawInput?.["searchTerm"]);
24509
24748
  return {
24510
24749
  summary: "Searched files",
@@ -24543,6 +24782,46 @@ function findSessionConfigOption(configOptions, configId) {
24543
24782
  }
24544
24783
  return configOptions.find((option) => option.id.trim() === normalizedConfigId);
24545
24784
  }
24785
+ function collectSessionConfigOptionValues(configOption) {
24786
+ if (configOption.type !== "select" || !configOption.options) {
24787
+ return [];
24788
+ }
24789
+ return configOption.options.flatMap(
24790
+ (entry) => "value" in entry ? [entry.value] : entry.options.map((option) => option.value)
24791
+ );
24792
+ }
24793
+ function collectSessionModelIds(models) {
24794
+ if (!isRecord5(models) || !Array.isArray(models["availableModels"])) {
24795
+ return [];
24796
+ }
24797
+ return models["availableModels"].flatMap(
24798
+ (model) => isRecord5(model) && typeof model["modelId"] === "string" ? [model["modelId"]] : []
24799
+ );
24800
+ }
24801
+ function catalogModelLastSegment(value) {
24802
+ if (value.startsWith("[")) {
24803
+ let parsed;
24804
+ try {
24805
+ parsed = JSON.parse(value);
24806
+ } catch {
24807
+ return void 0;
24808
+ }
24809
+ if (!Array.isArray(parsed) || parsed.length < 2) return void 0;
24810
+ const last = parsed[parsed.length - 1];
24811
+ return typeof last === "string" ? last : void 0;
24812
+ }
24813
+ const separator = value.indexOf("/");
24814
+ return separator > 0 && separator < value.length - 1 ? value.slice(separator + 1) : void 0;
24815
+ }
24816
+ function matchAdvertisedModelValue(requestedModelId, advertisedValues) {
24817
+ const wanted = requestedModelId.trim();
24818
+ if (!wanted) return void 0;
24819
+ if (advertisedValues.includes(wanted)) return wanted;
24820
+ const byLastSegment = advertisedValues.filter(
24821
+ (value) => catalogModelLastSegment(value) === wanted
24822
+ );
24823
+ return byLastSegment.length === 1 ? byLastSegment[0] : void 0;
24824
+ }
24546
24825
  function parseSessionModeState(sessionResponse) {
24547
24826
  const modes = sessionResponse.modes;
24548
24827
  if (!modes) return void 0;
@@ -24581,7 +24860,7 @@ function parseAvailableCommandsUpdate(params) {
24581
24860
  }
24582
24861
  const commands = [];
24583
24862
  for (const command of availableCommands) {
24584
- if (!isRecord3(command)) {
24863
+ if (!isRecord5(command)) {
24585
24864
  continue;
24586
24865
  }
24587
24866
  const name = typeof command["name"] === "string" ? command["name"].trim() : "";
@@ -24589,7 +24868,7 @@ function parseAvailableCommandsUpdate(params) {
24589
24868
  continue;
24590
24869
  }
24591
24870
  const description = typeof command["description"] === "string" ? command["description"].trim() || void 0 : void 0;
24592
- const input2 = isRecord3(command["input"]) ? command["input"] : void 0;
24871
+ const input2 = isRecord5(command["input"]) ? command["input"] : void 0;
24593
24872
  const inputHint = typeof input2?.["hint"] === "string" ? input2["hint"].trim() || void 0 : void 0;
24594
24873
  commands.push({
24595
24874
  name,
@@ -24651,7 +24930,7 @@ function extractCommandFromTitle2(title) {
24651
24930
  return match?.[1]?.trim() || void 0;
24652
24931
  }
24653
24932
  function extractToolCallCommand(rawInput, title) {
24654
- if (isRecord3(rawInput)) {
24933
+ if (isRecord5(rawInput)) {
24655
24934
  const directCommand = normalizeCommandValue2(rawInput["command"]);
24656
24935
  if (directCommand) {
24657
24936
  return directCommand;
@@ -24678,7 +24957,7 @@ function boundToolCallOutputText(text) {
24678
24957
  }
24679
24958
  var RAW_OUTPUT_TEXT_FIELDS = ["content", "stdout", "stderr", "output"];
24680
24959
  function boundToolCallRawOutput(rawOutput) {
24681
- if (!isRecord3(rawOutput)) {
24960
+ if (!isRecord5(rawOutput)) {
24682
24961
  return rawOutput;
24683
24962
  }
24684
24963
  let changed = false;
@@ -24908,7 +25187,7 @@ function parseSessionUpdateEvent(params) {
24908
25187
  const entries = Array.isArray(upd["entries"]) ? upd["entries"] : [];
24909
25188
  const plan = entries.map((entry, index) => ({
24910
25189
  step: readAcpText(entry, "content") ?? `Step ${index + 1}`,
24911
- status: normalizePlanStepStatus(isRecord3(entry) ? entry["status"] : void 0)
25190
+ status: normalizePlanStepStatus(isRecord5(entry) ? entry["status"] : void 0)
24912
25191
  }));
24913
25192
  if (plan.length > 0) {
24914
25193
  events.push({
@@ -24947,7 +25226,7 @@ function parseSessionUpdateEvent(params) {
24947
25226
  }
24948
25227
  case "agent_message_chunk":
24949
25228
  case "agent_thought_chunk": {
24950
- const content = isRecord3(upd["content"]) ? upd["content"] : void 0;
25229
+ const content = isRecord5(upd["content"]) ? upd["content"] : void 0;
24951
25230
  const contentType = content?.["type"];
24952
25231
  const streamKind = kind === "agent_thought_chunk" ? "reasoning_text" : "assistant_text";
24953
25232
  if (contentType === "text") {
@@ -25014,16 +25293,18 @@ function normalizeModelId(value) {
25014
25293
  return value?.trim() || void 0;
25015
25294
  }
25016
25295
  function modelSelectionStateFromSetup(setup) {
25017
- const sessionModels = isRecord3(setup.models) ? setup.models : void 0;
25296
+ const sessionModels = isRecord5(setup.models) ? setup.models : void 0;
25018
25297
  const modelConfigId = extractModelConfigId(setup);
25019
25298
  const strategy = sessionModels === void 0 && modelConfigId !== void 0 ? "config-option" : "session/set_model";
25020
- const configModel = modelConfigId === void 0 ? void 0 : findSessionConfigOption(setup.configOptions, modelConfigId)?.currentValue;
25299
+ const modelConfigOption = modelConfigId === void 0 ? void 0 : findSessionConfigOption(setup.configOptions, modelConfigId);
25300
+ const configModel = modelConfigOption?.currentValue;
25021
25301
  const currentModelId = strategy === "config-option" ? typeof configModel === "string" ? normalizeModelId(configModel) : void 0 : typeof sessionModels?.["currentModelId"] === "string" ? normalizeModelId(sessionModels["currentModelId"]) : void 0;
25022
25302
  return {
25023
25303
  strategy,
25024
25304
  sessionModelsAdvertised: sessionModels !== void 0,
25025
25305
  modelConfigId,
25026
- currentModelId
25306
+ currentModelId,
25307
+ advertisedModelValues: strategy === "config-option" ? modelConfigOption === void 0 ? [] : collectSessionConfigOptionValues(modelConfigOption) : collectSessionModelIds(sessionModels)
25027
25308
  };
25028
25309
  }
25029
25310
  function createAcpExecutor(options) {
@@ -25042,8 +25323,10 @@ function createAcpExecutor(options) {
25042
25323
  strategy: "session/set_model",
25043
25324
  sessionModelsAdvertised: false,
25044
25325
  modelConfigId: void 0,
25045
- currentModelId: void 0
25326
+ currentModelId: void 0,
25327
+ advertisedModelValues: []
25046
25328
  };
25329
+ let lastUnmatchedModelId;
25047
25330
  let initializeResult;
25048
25331
  let startResult;
25049
25332
  let startInFlight;
@@ -25368,6 +25651,12 @@ function createAcpExecutor(options) {
25368
25651
  const userInputOutcome = await handlePermissionAsUserInput(params);
25369
25652
  if (userInputOutcome !== void 0) return userInputOutcome;
25370
25653
  const permissionRequest = parsePermissionRequest(params);
25654
+ if (options.autoApprove?.(permissionRequest) === true) {
25655
+ const optionId = selectAutoApprovedAcpPermissionOption(params);
25656
+ if (optionId !== void 0) {
25657
+ return { outcome: { outcome: "selected", optionId } };
25658
+ }
25659
+ }
25371
25660
  const requestId = randomId();
25372
25661
  const outcome = await new Promise((resolve) => {
25373
25662
  const pending = {
@@ -25455,36 +25744,70 @@ function createAcpExecutor(options) {
25455
25744
  if (requestedModelId === void 0 || requestedModelId === input2.state.currentModelId || input2.state.strategy === "session/set_model" && !input2.state.sessionModelsAdvertised || input2.state.strategy === "config-option" && input2.state.modelConfigId === void 0) {
25456
25745
  return input2.state;
25457
25746
  }
25747
+ const value = matchAdvertisedModelValue(requestedModelId, input2.state.advertisedModelValues);
25748
+ if (value === void 0) {
25749
+ if (requestedModelId !== lastUnmatchedModelId) {
25750
+ lastUnmatchedModelId = requestedModelId;
25751
+ options.log?.info("requested model is not in the agent catalog; keeping its own model", {
25752
+ requestedModelId,
25753
+ currentModelId: input2.state.currentModelId,
25754
+ advertisedModelValues: input2.state.advertisedModelValues
25755
+ });
25756
+ }
25757
+ return input2.state;
25758
+ }
25759
+ if (value === input2.state.currentModelId) {
25760
+ return input2.state;
25761
+ }
25458
25762
  if (input2.state.strategy === "config-option") {
25459
25763
  await connection.request("session/set_config_option", {
25460
25764
  sessionId: input2.acpSessionId,
25461
25765
  configId: input2.state.modelConfigId,
25462
- value: requestedModelId
25766
+ value
25463
25767
  });
25464
25768
  } else {
25465
25769
  await connection.request("session/set_model", {
25466
25770
  sessionId: input2.acpSessionId,
25467
- modelId: requestedModelId
25771
+ modelId: value
25468
25772
  });
25469
25773
  }
25470
25774
  if (!isLive(input2.expectedGeneration)) {
25471
25775
  throw new AcpExecutorDisposedError();
25472
25776
  }
25473
- return { ...input2.state, currentModelId: requestedModelId };
25777
+ return { ...input2.state, currentModelId: value };
25778
+ };
25779
+ const applyRequestedEffort = async (input2) => {
25780
+ const requested = input2.requestedEffort?.trim();
25781
+ if (!requested) return;
25782
+ const configOptions = typeof input2.setup === "object" && input2.setup !== null ? input2.setup.configOptions : void 0;
25783
+ const effort = findAcpEffortConfig(configOptions);
25784
+ if (effort === void 0) return;
25785
+ const matched = effort.choices.find((choice) => choice.value === requested);
25786
+ if (matched === void 0) {
25787
+ options.log?.info("requested effort is not in the agent catalog; keeping its own", {
25788
+ requestedEffort: requested,
25789
+ advertised: effort.choices.map((choice) => choice.value)
25790
+ });
25791
+ return;
25792
+ }
25793
+ if (matched.isDefault === true) return;
25794
+ if (!isLive(input2.expectedGeneration)) return;
25795
+ try {
25796
+ await connection.request("session/set_config_option", {
25797
+ sessionId: input2.acpSessionId,
25798
+ configId: effort.configId,
25799
+ value: requested
25800
+ });
25801
+ } catch (error62) {
25802
+ options.log?.info("setting the effort level failed; the session keeps its own", {
25803
+ requestedEffort: requested,
25804
+ configId: effort.configId,
25805
+ error: error62 instanceof Error ? error62.message : String(error62)
25806
+ });
25807
+ }
25474
25808
  };
25475
25809
  const startOnce = async (expectedGeneration) => {
25476
- const initializePayload = {
25477
- protocolVersion: PROTOCOL_VERSION,
25478
- clientCapabilities: {
25479
- fs: {
25480
- readTextFile: false,
25481
- writeTextFile: false,
25482
- ...options.clientCapabilities?.fs
25483
- },
25484
- terminal: options.clientCapabilities?.terminal ?? false
25485
- },
25486
- clientInfo: options.clientInfo
25487
- };
25810
+ const initializePayload = acpInitializePayload(options.clientInfo, options.clientCapabilities);
25488
25811
  const initialized = await connection.request(
25489
25812
  "initialize",
25490
25813
  initializePayload
@@ -25528,12 +25851,27 @@ function createAcpExecutor(options) {
25528
25851
  nextSessionId = created.sessionId;
25529
25852
  setup = created;
25530
25853
  }
25854
+ const unhandledCategories = unhandledConfigCategories(
25855
+ setup.configOptions
25856
+ );
25857
+ if (unhandledCategories.length > 0) {
25858
+ options.log?.info("acp session reported config option categories we do not handle", {
25859
+ sessionId: nextSessionId,
25860
+ categories: unhandledCategories
25861
+ });
25862
+ }
25531
25863
  const nextModelSelectionState = await applyRequestedModel({
25532
25864
  state: modelSelectionStateFromSetup(setup),
25533
25865
  acpSessionId: nextSessionId,
25534
25866
  requestedModelId: options.requestedModelId,
25535
25867
  expectedGeneration
25536
25868
  });
25869
+ await applyRequestedEffort({
25870
+ setup,
25871
+ acpSessionId: nextSessionId,
25872
+ requestedEffort: options.requestedEffort,
25873
+ expectedGeneration
25874
+ });
25537
25875
  if (options.beforeApplyResult) {
25538
25876
  await options.beforeApplyResult();
25539
25877
  }
@@ -25898,6 +26236,26 @@ function createAcpExecutor(options) {
25898
26236
  };
25899
26237
  apply();
25900
26238
  };
26239
+ const setNativeMode = async (modeId) => {
26240
+ failIfDisposed();
26241
+ const started = startResult;
26242
+ if (started === void 0) {
26243
+ throw new AcpExecutorRequestError("session/set_mode", "session has not started yet");
26244
+ }
26245
+ const configId = acpModeConfigId(started.sessionSetupResult);
26246
+ if (configId !== void 0) {
26247
+ await connection.request("session/set_config_option", {
26248
+ sessionId: started.sessionId,
26249
+ configId,
26250
+ value: modeId
26251
+ });
26252
+ return;
26253
+ }
26254
+ await connection.request("session/set_mode", {
26255
+ sessionId: started.sessionId,
26256
+ modeId
26257
+ });
26258
+ };
25901
26259
  const respondToUserInput = async (requestId, answers) => {
25902
26260
  failIfDisposed();
25903
26261
  const pending = pendingUserInputs.get(requestId);
@@ -25914,7 +26272,7 @@ function createAcpExecutor(options) {
25914
26272
  pendingUserInputs.delete(requestId);
25915
26273
  pending.resolve({
25916
26274
  kind: "answered",
25917
- answers: isRecord3(answers) ? answers : {}
26275
+ answers: isRecord5(answers) ? answers : {}
25918
26276
  });
25919
26277
  };
25920
26278
  const dispose = async () => {
@@ -25964,10 +26322,53 @@ function createAcpExecutor(options) {
25964
26322
  cancel,
25965
26323
  respondToRequest,
25966
26324
  respondToUserInput,
26325
+ setNativeMode,
25967
26326
  dispose
25968
26327
  };
25969
26328
  }
25970
26329
 
26330
+ // ../../packages/ycodium-acp-executor/src/probe.ts
26331
+ function withProbeTimeout(promise2, timeoutMs, method) {
26332
+ return new Promise((resolve, reject) => {
26333
+ const timer = setTimeout(() => {
26334
+ reject(new Error(`ACP probe request '${method}' timed out after ${timeoutMs}ms`));
26335
+ }, timeoutMs);
26336
+ timer.unref?.();
26337
+ promise2.then(
26338
+ (value) => {
26339
+ clearTimeout(timer);
26340
+ resolve(value);
26341
+ },
26342
+ (error62) => {
26343
+ clearTimeout(timer);
26344
+ reject(error62 instanceof Error ? error62 : new Error(String(error62)));
26345
+ }
26346
+ );
26347
+ });
26348
+ }
26349
+ async function openAcpProbeHandshake(input2) {
26350
+ const connection = createAcpConnection({
26351
+ readable: input2.readable,
26352
+ writable: input2.writable,
26353
+ onNotification: () => void 0
26354
+ });
26355
+ try {
26356
+ const initializeResult = await withProbeTimeout(
26357
+ connection.request("initialize", acpInitializePayload(input2.clientInfo)),
26358
+ input2.requestTimeoutMs,
26359
+ "initialize"
26360
+ );
26361
+ return {
26362
+ initializeResult,
26363
+ request: (method, params) => withProbeTimeout(connection.request(method, params), input2.requestTimeoutMs, method),
26364
+ close: () => connection.close()
26365
+ };
26366
+ } catch (error62) {
26367
+ await connection.close().catch(() => void 0);
26368
+ throw error62;
26369
+ }
26370
+ }
26371
+
25971
26372
  // src/card.ts
25972
26373
  var ACP_DRIVER_KIND = "acp";
25973
26374
  var ACP_CARD = {
@@ -25999,7 +26400,21 @@ var ACP_CARD = {
25999
26400
  everydayHarness: false,
26000
26401
  capabilities: {
26001
26402
  sessionModelSwitch: "unsupported",
26002
- runtimeModeSwitch: "in-session"
26403
+ // **这一位仍是空头支票,切档走的是下面那位。** `setRuntimeMode` 在这里只改自己的
26404
+ // 会话记录,从不发 RPC——宿主四档发不到 CLI 上。它没删是因为编排还在读它(哪些手
26405
+ // 要重开会话),但用户真正切得动的是 `nativeModeSwitch` 那条:这只手自报几档、
26406
+ // 界面画几档、选哪档发哪档,不经四档翻译(各家语义本来就对不齐:grok 的
26407
+ // `acceptEdits` 实测等同 `default`,照样问)。
26408
+ runtimeModeSwitch: "in-session",
26409
+ // 能中途切自报的那几档。`setNativeMode` 按 agent 报的形状挑通道:报了
26410
+ // `configOptions` 里 `category: "mode"` 那一项就走 `session/set_config_option`
26411
+ // (claude-agent-acp),否则走 `session/set_mode`。
26412
+ //
26413
+ // 两条路都实测过:kimi `acp` 的 `session/set_mode plan` 回 `{}` 并推
26414
+ // `current_mode_update`,不存在的 modeId 被 `-32602 Unknown modeId` 拒掉——不是
26415
+ // Grok 那种「收下任何 modeId 都回 {}」的静默无效。dsh 两条都没有(`-32601`),
26416
+ // 所以它不声明这一位。
26417
+ nativeModeSwitch: true
26003
26418
  },
26004
26419
  credentialSources: [],
26005
26420
  diagnostics: [],
@@ -26022,12 +26437,20 @@ var ACP_CARD = {
26022
26437
  },
26023
26438
  modelConnectivity: {
26024
26439
  dialects: ["anthropic-messages", "openai-responses", "openai-chat-completions"],
26025
- acceptsExternalEndpoints: true
26440
+ // **通用 ACP 这张名片接不了外部端点,而且接不了是对的。** ACP 协议本身没有端点
26441
+ // 概念,地址与钥匙全靠各家 CLI 自己的环境变量,五家各不相同(kimi 的
26442
+ // `KIMI_MODEL_*`、qwen 的 `OPENAI_*`、gemini 的 `GOOGLE_GEMINI_*`……)。这张名片
26443
+ // 是认不出是哪一家时的兜底,它不知道该注什么。
26444
+ //
26445
+ // 原来这里写着 `true` 却一栏开关都不给——名片对外宣称能接,实际绑定时报一个含糊
26446
+ // 的 `driver-unknown`,而提案 0025 的进度表据此把 kimi 记成「通」。哪家要接,就为
26447
+ // 那一家单出一张名片(`resolveByLocator` 按启动命令认家),别回来改这一张。
26448
+ acceptsExternalEndpoints: false
26026
26449
  }
26027
26450
  };
26028
26451
 
26029
26452
  // src/config.ts
26030
- function isRecord4(value) {
26453
+ function isRecord6(value) {
26031
26454
  return typeof value === "object" && value !== null && !Array.isArray(value);
26032
26455
  }
26033
26456
  function readString(value) {
@@ -26040,7 +26463,7 @@ function readEnvironment(value) {
26040
26463
  if (!Array.isArray(value)) return {};
26041
26464
  const env = {};
26042
26465
  for (const entry of value) {
26043
- if (!isRecord4(entry)) continue;
26466
+ if (!isRecord6(entry)) continue;
26044
26467
  const name = readTrimmed(entry["name"]);
26045
26468
  if (name.length === 0) continue;
26046
26469
  env[name] = readString(entry["value"]);
@@ -26048,17 +26471,18 @@ function readEnvironment(value) {
26048
26471
  return env;
26049
26472
  }
26050
26473
  function parseAcpInstanceConfig(config2) {
26051
- if (!isRecord4(config2)) {
26474
+ if (!isRecord6(config2)) {
26052
26475
  return {
26053
26476
  binaryPath: "",
26054
26477
  launchArgs: "",
26055
26478
  homePath: "",
26056
26479
  homeEnvVar: "",
26057
26480
  environment: {},
26058
- envelopeDriver: void 0
26481
+ envelopeDriver: void 0,
26482
+ providerId: ""
26059
26483
  };
26060
26484
  }
26061
- const nested = isRecord4(config2["config"]) ? config2["config"] : void 0;
26485
+ const nested = isRecord6(config2["config"]) ? config2["config"] : void 0;
26062
26486
  const settings = nested ?? config2;
26063
26487
  return {
26064
26488
  binaryPath: readTrimmed(settings["binaryPath"]),
@@ -26066,7 +26490,8 @@ function parseAcpInstanceConfig(config2) {
26066
26490
  homePath: readTrimmed(settings["homePath"]) || readTrimmed(config2["homePath"]),
26067
26491
  homeEnvVar: readTrimmed(settings["homeEnvVar"]) || readTrimmed(config2["homeEnvVar"]),
26068
26492
  environment: readEnvironment(config2["environment"]),
26069
- envelopeDriver: readTrimmed(config2["driver"]) || void 0
26493
+ envelopeDriver: readTrimmed(config2["driver"]) || void 0,
26494
+ providerId: readTrimmed(settings["providerId"]) || readTrimmed(config2["providerId"])
26070
26495
  };
26071
26496
  }
26072
26497
  function tokenizeLaunchArgs(value) {
@@ -26121,57 +26546,372 @@ function tokenizeLaunchArgs(value) {
26121
26546
  return tokens;
26122
26547
  }
26123
26548
 
26124
- // src/streams.ts
26125
- import * as NodeStream2 from "node:stream";
26126
- function nodeStreamsFromHandle(handle) {
26127
- const readable = new NodeStream2.PassThrough();
26128
- void (async () => {
26549
+ // src/kimi.ts
26550
+ import * as NodeFS from "node:fs";
26551
+ import * as NodePath from "node:path";
26552
+ var KIMI_PROVIDER_ID = "kimiCode";
26553
+ var KIMI_HOME_ENV_VAR = "KIMI_CODE_HOME";
26554
+ var KIMI_USER_INPUT_TITLES = /* @__PURE__ */ new Set(["AskUserQuestion", "ExitPlanMode"]);
26555
+ var isRecord7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
26556
+ function questionTextFromContent(content) {
26557
+ if (!Array.isArray(content)) return "";
26558
+ const parts = [];
26559
+ for (const entry of content) {
26560
+ if (!isRecord7(entry) || entry["type"] !== "content") continue;
26561
+ const inner = entry["content"];
26562
+ if (!isRecord7(inner) || inner["type"] !== "text") continue;
26563
+ const text = inner["text"];
26564
+ if (typeof text === "string") parts.push(text);
26565
+ }
26566
+ return parts.join("\n\n").trim();
26567
+ }
26568
+ function kimiPermissionUserInput(request) {
26569
+ const title = request.toolCall.title;
26570
+ if (typeof title !== "string" || !KIMI_USER_INPUT_TITLES.has(title)) return void 0;
26571
+ const ids = new Set(request.options.map((option) => option.optionId));
26572
+ if (request.options.length === 0 || ids.size !== request.options.length) {
26573
+ throw new Error("Kimi interaction must provide distinct response options.");
26574
+ }
26575
+ const question = questionTextFromContent(request.toolCall.content);
26576
+ if (question.length === 0) {
26577
+ throw new Error("Kimi interaction is missing a question or plan.");
26578
+ }
26579
+ return {
26580
+ id: request.toolCall.toolCallId,
26581
+ header: title,
26582
+ question,
26583
+ kind: title === "ExitPlanMode" ? "plan" : "question",
26584
+ allowFreeform: false,
26585
+ multiSelect: false,
26586
+ options: request.options.map((option) => ({
26587
+ id: option.optionId,
26588
+ label: option.name,
26589
+ description: ""
26590
+ }))
26591
+ };
26592
+ }
26593
+ var WIRE_LOG_SEGMENTS = ["agents", "main", "wire.jsonl"];
26594
+ var SESSIONS_DIR = "sessions";
26595
+ var TAIL_SCAN_BYTES = 256 * 1024;
26596
+ var positiveInt = (value) => typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.trunc(value) : void 0;
26597
+ function parseKimiContextWindow(contents) {
26598
+ let usedTokens;
26599
+ let maxTokens;
26600
+ for (const line of contents.split("\n")) {
26601
+ const trimmed = line.trim();
26602
+ if (trimmed.length === 0) continue;
26603
+ let record2;
26129
26604
  try {
26130
- for await (const chunk of handle.stdout) {
26131
- if (!readable.writable) return;
26132
- if (!readable.write(chunk)) {
26133
- await new Promise((resolve) => {
26134
- const onDone = () => {
26135
- readable.off("drain", onDone);
26136
- readable.off("close", onDone);
26137
- resolve();
26138
- };
26139
- readable.once("drain", onDone);
26140
- readable.once("close", onDone);
26141
- });
26142
- }
26143
- }
26144
- } catch (error62) {
26145
- readable.destroy(error62 instanceof Error ? error62 : new Error(String(error62)));
26146
- return;
26605
+ record2 = JSON.parse(trimmed);
26606
+ } catch {
26607
+ continue;
26147
26608
  }
26148
- if (readable.writable) readable.end();
26149
- })();
26150
- void (async () => {
26151
- for await (const _chunk of handle.stderr) {
26152
- }
26153
- })().catch(() => void 0);
26154
- const writable = new NodeStream2.Writable({
26155
- write(chunk, _encoding, callback) {
26156
- const bytes = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
26157
- handle.writeStdin(bytes).then(
26158
- () => callback(),
26159
- (error62) => callback(error62 instanceof Error ? error62 : new Error(String(error62)))
26160
- );
26161
- },
26162
- final(callback) {
26163
- handle.closeStdin().then(
26164
- () => callback(),
26165
- (error62) => callback(error62 instanceof Error ? error62 : new Error(String(error62)))
26166
- );
26609
+ if (!isRecord7(record2)) continue;
26610
+ const type = record2["type"];
26611
+ if (type === "token_counting.turn_recorded" || type === "token_counting.measured") {
26612
+ usedTokens = positiveInt(record2["tokens"]) ?? usedTokens;
26613
+ continue;
26167
26614
  }
26615
+ if (type === "llm.request") {
26616
+ maxTokens = positiveInt(record2["maxTokens"]) ?? maxTokens;
26617
+ }
26618
+ }
26619
+ if (usedTokens === void 0) return void 0;
26620
+ return maxTokens === void 0 ? { usedTokens } : { usedTokens, maxTokens };
26621
+ }
26622
+ function readTail(filePath) {
26623
+ let handle;
26624
+ try {
26625
+ const size = NodeFS.statSync(filePath).size;
26626
+ if (!Number.isFinite(size) || size <= 0) return void 0;
26627
+ const length = size > TAIL_SCAN_BYTES ? TAIL_SCAN_BYTES : size;
26628
+ const buffer = Buffer.alloc(length);
26629
+ handle = NodeFS.openSync(filePath, "r");
26630
+ NodeFS.readSync(handle, buffer, 0, length, size - length);
26631
+ return buffer.toString("utf8");
26632
+ } catch {
26633
+ return void 0;
26634
+ } finally {
26635
+ if (handle !== void 0) {
26636
+ try {
26637
+ NodeFS.closeSync(handle);
26638
+ } catch {
26639
+ }
26640
+ }
26641
+ }
26642
+ }
26643
+ function findKimiSessionWireLog(input2) {
26644
+ const sessionsRoot = NodePath.join(input2.homePath, SESSIONS_DIR);
26645
+ let workspaces;
26646
+ try {
26647
+ workspaces = NodeFS.readdirSync(sessionsRoot);
26648
+ } catch {
26649
+ return void 0;
26650
+ }
26651
+ for (const workspace of workspaces) {
26652
+ const candidate = NodePath.join(
26653
+ sessionsRoot,
26654
+ workspace,
26655
+ input2.acpSessionId,
26656
+ ...WIRE_LOG_SEGMENTS
26657
+ );
26658
+ if (NodeFS.existsSync(candidate)) return candidate;
26659
+ }
26660
+ return void 0;
26661
+ }
26662
+ function readKimiContextWindow(input2) {
26663
+ if (input2.homePath.trim().length === 0) return void 0;
26664
+ const wireLog = findKimiSessionWireLog(input2);
26665
+ if (wireLog === void 0) return void 0;
26666
+ const contents = readTail(wireLog);
26667
+ return contents === void 0 ? void 0 : parseKimiContextWindow(contents);
26668
+ }
26669
+
26670
+ // src/kimiModels.ts
26671
+ var PROBE_TIMEOUT_MS = 15e3;
26672
+ var MAX_OUTPUT_BYTES = 1024 * 1024;
26673
+ var KIMI_MODEL_LIST_ARGS = ["provider", "list", "--json"];
26674
+ function isRecord8(value) {
26675
+ return typeof value === "object" && value !== null && !Array.isArray(value);
26676
+ }
26677
+ function asString3(value) {
26678
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
26679
+ }
26680
+ function positiveInt2(value) {
26681
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.trunc(value) : void 0;
26682
+ }
26683
+ function kimiProbeModels(stdout) {
26684
+ let parsed;
26685
+ try {
26686
+ parsed = JSON.parse(stdout);
26687
+ } catch {
26688
+ return void 0;
26689
+ }
26690
+ if (!isRecord8(parsed)) return void 0;
26691
+ const models = parsed["models"];
26692
+ if (!isRecord8(models)) return void 0;
26693
+ const out = [];
26694
+ for (const [slug, entry] of Object.entries(models)) {
26695
+ const trimmed = slug.trim();
26696
+ if (trimmed.length === 0 || !isRecord8(entry)) continue;
26697
+ const name = asString3(entry["displayName"]);
26698
+ const contextWindow = positiveInt2(entry["maxContextSize"]);
26699
+ out.push({
26700
+ slug: trimmed,
26701
+ ...name === void 0 ? {} : { name },
26702
+ ...contextWindow === void 0 ? {} : { contextWindow }
26703
+ });
26704
+ }
26705
+ return out;
26706
+ }
26707
+ function withProbeTimeout2(promise2, what) {
26708
+ return new Promise((resolve, reject) => {
26709
+ const timer = setTimeout(
26710
+ () => reject(new Error(`${what} timed out after ${PROBE_TIMEOUT_MS}ms`)),
26711
+ PROBE_TIMEOUT_MS
26712
+ );
26713
+ timer.unref?.();
26714
+ promise2.then(
26715
+ (value) => {
26716
+ clearTimeout(timer);
26717
+ resolve(value);
26718
+ },
26719
+ (error62) => {
26720
+ clearTimeout(timer);
26721
+ reject(error62 instanceof Error ? error62 : new Error(String(error62)));
26722
+ }
26723
+ );
26168
26724
  });
26169
- return { readable, writable };
26725
+ }
26726
+ function concat(chunks) {
26727
+ const total = chunks.reduce((sum, chunk) => sum + chunk.byteLength, 0);
26728
+ const out = new Uint8Array(total);
26729
+ let offset = 0;
26730
+ for (const chunk of chunks) {
26731
+ out.set(chunk, offset);
26732
+ offset += chunk.byteLength;
26733
+ }
26734
+ return out;
26735
+ }
26736
+ async function probeKimiModels(input2) {
26737
+ const { recipe, host, env } = input2;
26738
+ if (recipe.binaryPath.length === 0) return void 0;
26739
+ let handle;
26740
+ try {
26741
+ handle = await host.process.spawn({
26742
+ program: recipe.binaryPath,
26743
+ // 实例信封里的启动参数(Kimi 那行预填 `acp`)在这一栏用不上:读配置要换成
26744
+ // `provider list --json`,留着 `acp` 就等于让它起 ACP 服务然后什么都不问。
26745
+ args: KIMI_MODEL_LIST_ARGS,
26746
+ cwd: process.cwd(),
26747
+ owner: { bindingId: host.bindingId },
26748
+ ...Object.keys(env).length > 0 ? { env } : {}
26749
+ });
26750
+ const spawned = handle;
26751
+ const collect = withProbeTimeout2(
26752
+ (async () => {
26753
+ const chunks = [];
26754
+ let total = 0;
26755
+ for await (const chunk of spawned.stdout) {
26756
+ total += chunk.byteLength;
26757
+ if (total > MAX_OUTPUT_BYTES) return void 0;
26758
+ chunks.push(chunk);
26759
+ }
26760
+ return new TextDecoder().decode(concat(chunks));
26761
+ })(),
26762
+ "kimi provider list"
26763
+ );
26764
+ const drain = (async () => {
26765
+ for await (const _chunk of spawned.stderr) {
26766
+ }
26767
+ })().catch(() => void 0);
26768
+ const [stdout, , exitCode] = await Promise.all([collect, drain, spawned.exited]);
26769
+ if (stdout === void 0) return void 0;
26770
+ if (exitCode !== 0) return void 0;
26771
+ return kimiProbeModels(stdout);
26772
+ } catch {
26773
+ return void 0;
26774
+ } finally {
26775
+ await handle?.kill("SIGTERM").catch(() => void 0);
26776
+ }
26777
+ }
26778
+
26779
+ // src/probeModels.ts
26780
+ import * as NodeFS2 from "node:fs";
26781
+ import * as NodeOS from "node:os";
26782
+ import * as NodePath2 from "node:path";
26783
+ import { nodeStreamsFromHandle } from "@ycodium-ai/plugin-api/process-stdin";
26784
+ var PROBE_REQUEST_TIMEOUT_MS = 15e3;
26785
+ var CLIENT_INFO = { name: "ycodium-agent-acp-probe", version: "0.2.0" };
26786
+ var ACP_EFFORT_OPTION_ID = "reasoning";
26787
+ function acpProbeSessionCwd(instanceId) {
26788
+ const safe = instanceId.replace(/[^a-zA-Z0-9_-]/g, "_");
26789
+ return NodePath2.join(NodeOS.tmpdir(), `ycodium-acp-probe-${safe}`);
26790
+ }
26791
+ function isRecord9(value) {
26792
+ return typeof value === "object" && value !== null && !Array.isArray(value);
26793
+ }
26794
+ function asString4(value) {
26795
+ return typeof value === "string" ? value : void 0;
26796
+ }
26797
+ function findAcpProbeSessionId(listed, probeCwd) {
26798
+ if (!isRecord9(listed) || !Array.isArray(listed["sessions"])) return void 0;
26799
+ for (const entry of listed["sessions"]) {
26800
+ if (!isRecord9(entry)) continue;
26801
+ if (asString4(entry["cwd"]) !== probeCwd) continue;
26802
+ const sessionId = asString4(entry["sessionId"]);
26803
+ if (sessionId !== void 0) return sessionId;
26804
+ }
26805
+ return void 0;
26806
+ }
26807
+ async function probeSessionSetup(handshake, probeCwd) {
26808
+ const listed = await handshake.request("session/list", {}).catch(() => void 0);
26809
+ const reusable = findAcpProbeSessionId(listed, probeCwd);
26810
+ if (reusable !== void 0) {
26811
+ const resumed = await handshake.request("session/resume", { sessionId: reusable, cwd: probeCwd, mcpServers: [] }).catch(() => void 0);
26812
+ if (resumed !== void 0) return resumed;
26813
+ }
26814
+ return handshake.request("session/new", { cwd: probeCwd, mcpServers: [] });
26815
+ }
26816
+ function acpProbeModels(setup) {
26817
+ if (!isRecord9(setup)) return void 0;
26818
+ const rawOptions = setup["configOptions"];
26819
+ const choices = acpModelChoicesFromConfigOptions(rawOptions) ?? acpModelChoicesFromSessionModels(setup["models"]);
26820
+ if (choices === void 0 || choices.length === 0) return void 0;
26821
+ const models = choices.map((choice) => ({
26822
+ slug: choice.value,
26823
+ ...choice.name === void 0 ? {} : { name: choice.name },
26824
+ ...choice.isDefault === true ? { isDefault: true } : {}
26825
+ }));
26826
+ const effort = findAcpEffortConfig(rawOptions);
26827
+ if (effort === void 0) return models;
26828
+ const capabilities = {
26829
+ optionDescriptors: [
26830
+ {
26831
+ id: ACP_EFFORT_OPTION_ID,
26832
+ label: effort.label || "Reasoning",
26833
+ type: "select",
26834
+ options: effort.choices.map((choice) => ({
26835
+ id: choice.value,
26836
+ label: choice.label,
26837
+ ...choice.description === void 0 ? {} : { description: choice.description },
26838
+ ...choice.isDefault === true ? { isDefault: true } : {}
26839
+ }))
26840
+ }
26841
+ ]
26842
+ };
26843
+ return models.map((model) => ({ ...model, capabilities }));
26844
+ }
26845
+ var failureDetail = (error62) => (error62 instanceof Error ? error62.message : String(error62)).trim() || void 0;
26846
+ async function probeAcpModelsViaSession(input2) {
26847
+ const { recipe, host, env } = input2;
26848
+ const probeCwd = acpProbeSessionCwd(host.instanceId);
26849
+ try {
26850
+ NodeFS2.mkdirSync(probeCwd, { recursive: true });
26851
+ } catch (error62) {
26852
+ return { kind: "failed", detail: failureDetail(error62) };
26853
+ }
26854
+ let handle;
26855
+ try {
26856
+ handle = await host.process.spawn({
26857
+ program: recipe.binaryPath,
26858
+ args: tokenizeLaunchArgs(recipe.launchArgs),
26859
+ cwd: probeCwd,
26860
+ owner: { bindingId: host.bindingId },
26861
+ ...Object.keys(env).length > 0 ? { env } : {}
26862
+ });
26863
+ } catch (error62) {
26864
+ return { kind: "not-started", detail: failureDetail(error62) };
26865
+ }
26866
+ let handshake;
26867
+ try {
26868
+ const { readable, writable } = nodeStreamsFromHandle(handle);
26869
+ handshake = await openAcpProbeHandshake({
26870
+ readable,
26871
+ writable,
26872
+ clientInfo: CLIENT_INFO,
26873
+ requestTimeoutMs: PROBE_REQUEST_TIMEOUT_MS
26874
+ });
26875
+ const setup = await probeSessionSetup(handshake, probeCwd);
26876
+ return {
26877
+ kind: "answered",
26878
+ models: acpProbeModels(setup),
26879
+ // 审批档原样上报:宿主落进 provider 快照的 `nativeModes`,composer 直接画这只手
26880
+ // 自己的那几档,不再退回宿主四档开关。
26881
+ nativeModes: acpAvailableModes(setup).map((mode) => ({
26882
+ id: mode.id,
26883
+ name: mode.name,
26884
+ ...mode.description === void 0 ? {} : { description: mode.description }
26885
+ })),
26886
+ currentNativeModeId: acpCurrentModeId(setup)
26887
+ };
26888
+ } catch (error62) {
26889
+ return { kind: "failed", detail: failureDetail(error62) };
26890
+ } finally {
26891
+ await handshake?.close().catch(() => void 0);
26892
+ await handle.kill("SIGTERM").catch(() => void 0);
26893
+ }
26894
+ }
26895
+
26896
+ // src/runtimeModeApproval.ts
26897
+ function acpRuntimeModeAutoApproves(runtimeMode, toolKind) {
26898
+ switch (runtimeMode) {
26899
+ case "full-access":
26900
+ return true;
26901
+ case "auto-accept-edits":
26902
+ return toolKind === "edit";
26903
+ case "approval-required":
26904
+ case "auto":
26905
+ return false;
26906
+ }
26907
+ }
26908
+ function acpSessionModeToApply(requestedNativeModeId, startedModeId) {
26909
+ return requestedNativeModeId !== void 0 && requestedNativeModeId !== startedModeId ? requestedNativeModeId : void 0;
26170
26910
  }
26171
26911
 
26172
26912
  // src/acpInstance.ts
26173
26913
  var RESUME_SCHEMA_VERSION = 1;
26174
- var CLIENT_INFO = { name: "ycodium-agent-acp", version: "0.2.0" };
26914
+ var CLIENT_INFO2 = { name: "ycodium-agent-acp", version: "0.2.0" };
26175
26915
  var yieldTick = () => new Promise((resolve) => {
26176
26916
  setImmediate(resolve);
26177
26917
  });
@@ -26208,19 +26948,12 @@ var EventBus = class {
26208
26948
  }
26209
26949
  };
26210
26950
  function expandHomePath(value) {
26211
- if (value === "~") return NodeOS.homedir();
26951
+ if (value === "~") return NodeOS2.homedir();
26212
26952
  if (value.startsWith("~/") || value.startsWith("~\\")) {
26213
- return NodePath.join(NodeOS.homedir(), value.slice(2));
26953
+ return NodePath3.join(NodeOS2.homedir(), value.slice(2));
26214
26954
  }
26215
26955
  return value;
26216
26956
  }
26217
- function compactEnv(env) {
26218
- const out = {};
26219
- for (const [key, value] of Object.entries(env)) {
26220
- if (value !== void 0) out[key] = value;
26221
- }
26222
- return out;
26223
- }
26224
26957
  function modelIdFromSelection(selection) {
26225
26958
  if (typeof selection !== "object" || selection === null || Array.isArray(selection)) {
26226
26959
  return void 0;
@@ -26228,19 +26961,42 @@ function modelIdFromSelection(selection) {
26228
26961
  const model = selection["model"];
26229
26962
  return typeof model === "string" ? model.trim() || void 0 : void 0;
26230
26963
  }
26231
- function buildSpawnEnv(recipe, launch) {
26232
- const homePath = (launch.homePath?.trim() || recipe.homePath).trim();
26964
+ function buildSpawnEnv(recipe, configuredHomePath, launchEnv = {}) {
26965
+ const homePath = configuredHomePath.trim();
26233
26966
  const homeEnvVar = recipe.homeEnvVar.trim();
26234
26967
  const homeInjection = homePath.length > 0 && homeEnvVar.length > 0 ? { [homeEnvVar]: expandHomePath(homePath) } : {};
26235
- return {
26236
- ...compactEnv(launch.processEnv),
26237
- ...homeInjection,
26238
- ...recipe.environment
26239
- };
26968
+ return mergeLaunchEnv({
26969
+ brainSlots: brainSlotEnvKeys(ACP_CARD),
26970
+ accountHome: homeInjection,
26971
+ instanceTable: recipe.environment,
26972
+ launch: launchEnv
26973
+ });
26240
26974
  }
26241
26975
  function nowIso() {
26242
26976
  return (/* @__PURE__ */ new Date()).toISOString();
26243
26977
  }
26978
+ function resolveHomePath(recipe, launchHomePath) {
26979
+ const envVar = recipe.homeEnvVar.trim();
26980
+ const fromEnvironment = envVar.length > 0 ? recipe.environment[envVar]?.trim() : void 0;
26981
+ if (fromEnvironment !== void 0 && fromEnvironment.length > 0) {
26982
+ return expandHomePath(fromEnvironment);
26983
+ }
26984
+ const configured = (launchHomePath?.trim() || recipe.homePath).trim();
26985
+ return configured.length > 0 ? expandHomePath(configured) : "";
26986
+ }
26987
+ function isKimiInstance(recipe) {
26988
+ return recipe.providerId === KIMI_PROVIDER_ID || recipe.homeEnvVar.trim() === KIMI_HOME_ENV_VAR;
26989
+ }
26990
+ function vendorHooksFor(recipe, launchHomePath) {
26991
+ if (!isKimiInstance(recipe)) return void 0;
26992
+ const homePath = resolveHomePath(recipe, launchHomePath);
26993
+ return {
26994
+ permissionUserInput: kimiPermissionUserInput,
26995
+ // ACP 的 `usage_update` 我们这边不消费,进度圈只能从各家自己写在磁盘上的
26996
+ // 记录里读。目前只有 Kimi 的格式核实过。
26997
+ readContextWindow: ({ sessionId }) => readKimiContextWindow({ homePath, acpSessionId: sessionId })
26998
+ };
26999
+ }
26244
27000
  async function stopBooked(booked) {
26245
27001
  await booked.executor.dispose().catch(() => void 0);
26246
27002
  await booked.handle.kill("SIGTERM").catch(() => void 0);
@@ -26267,11 +27023,33 @@ var createAcpInstance = (config2, host) => {
26267
27023
  detail: "ACP program path is not configured"
26268
27024
  };
26269
27025
  }
27026
+ const outcome = isKimiInstance(recipe) ? {
27027
+ kind: "answered",
27028
+ models: await probeKimiModels({
27029
+ recipe,
27030
+ host,
27031
+ env: buildSpawnEnv(recipe, recipe.homePath)
27032
+ }),
27033
+ nativeModes: void 0,
27034
+ currentNativeModeId: void 0
27035
+ } : await probeAcpModelsViaSession({
27036
+ recipe,
27037
+ host,
27038
+ env: buildSpawnEnv(recipe, recipe.homePath)
27039
+ });
27040
+ const models = outcome.kind === "answered" ? outcome.models : void 0;
27041
+ const nativeModes = outcome.kind === "answered" && outcome.nativeModes !== void 0 ? outcome.nativeModes : void 0;
27042
+ const currentNativeModeId = outcome.kind === "answered" ? outcome.currentNativeModeId : void 0;
26270
27043
  return {
26271
27044
  installed: true,
26272
27045
  version: null,
26273
27046
  readiness: "unchecked",
26274
- auth: { status: "unknown" }
27047
+ auth: { status: "unknown" },
27048
+ ...models === void 0 ? {} : { models },
27049
+ ...nativeModes === void 0 || nativeModes.length === 0 ? {} : {
27050
+ nativeModes,
27051
+ ...currentNativeModeId === void 0 ? {} : { currentNativeModeId }
27052
+ }
26275
27053
  };
26276
27054
  },
26277
27055
  startSession: async (input2, signal) => {
@@ -26287,7 +27065,11 @@ var createAcpInstance = (config2, host) => {
26287
27065
  await stopBooked(existing);
26288
27066
  threads.delete(input2.threadId);
26289
27067
  }
26290
- const env = buildSpawnEnv(recipe, input2.launch);
27068
+ const env = buildSpawnEnv(
27069
+ recipe,
27070
+ input2.launch.homePath?.trim() || recipe.homePath,
27071
+ input2.launch.processEnv
27072
+ );
26291
27073
  const handle = await host.process.spawn({
26292
27074
  program: recipe.binaryPath,
26293
27075
  args: tokenizeLaunchArgs(recipe.launchArgs),
@@ -26295,19 +27077,32 @@ var createAcpInstance = (config2, host) => {
26295
27077
  owner: { bindingId: host.bindingId },
26296
27078
  ...Object.keys(env).length > 0 ? { env } : {}
26297
27079
  });
26298
- const { readable, writable } = nodeStreamsFromHandle(handle);
27080
+ const { readable, writable } = nodeStreamsFromHandle2(handle);
26299
27081
  const resume = input2.discardResumeCursor === true ? void 0 : parseAcpResumeCursor(input2.resumeCursor, RESUME_SCHEMA_VERSION);
26300
27082
  const requestedModelId = input2.launch.requestedModelId?.trim() || modelIdFromSelection(input2.modelSelection);
27083
+ const requestedEffort = modelSelectionStringOption(
27084
+ input2.modelSelection,
27085
+ ACP_EFFORT_OPTION_ID
27086
+ );
27087
+ const hooks = vendorHooksFor(recipe, input2.launch.homePath);
27088
+ const autoApprove = input2.nativeModeId === void 0 ? (request) => acpRuntimeModeAutoApproves(
27089
+ threads.get(input2.threadId)?.session.runtimeMode ?? input2.runtimeMode,
27090
+ request.kind
27091
+ ) : void 0;
26301
27092
  const executor = createAcpExecutor({
26302
27093
  readable,
26303
27094
  writable,
27095
+ ...requestedEffort === void 0 ? {} : { requestedEffort },
26304
27096
  cwd,
26305
27097
  threadId: input2.threadId,
26306
27098
  provider: ACP_DRIVER_KIND,
26307
27099
  providerInstanceId: host.instanceId,
26308
- clientInfo: CLIENT_INFO,
27100
+ clientInfo: CLIENT_INFO2,
27101
+ log: host.log,
26309
27102
  ...requestedModelId === void 0 ? {} : { requestedModelId },
26310
27103
  ...input2.bindingGeneration === void 0 ? {} : { originBindingGeneration: input2.bindingGeneration },
27104
+ ...hooks === void 0 ? {} : { hooks },
27105
+ ...autoApprove === void 0 ? {} : { autoApprove },
26311
27106
  ...resume === void 0 ? {} : { resumeSessionId: resume.sessionId }
26312
27107
  });
26313
27108
  const forwarding = (async () => {
@@ -26317,6 +27112,13 @@ var createAcpInstance = (config2, host) => {
26317
27112
  })();
26318
27113
  try {
26319
27114
  const started = await executor.start();
27115
+ const modeToApply = acpSessionModeToApply(
27116
+ input2.nativeModeId,
27117
+ acpCurrentModeId(started.sessionSetupResult)
27118
+ );
27119
+ if (modeToApply !== void 0) {
27120
+ await executor.setNativeMode(modeToApply);
27121
+ }
26320
27122
  await yieldTick();
26321
27123
  const createdAt = nowIso();
26322
27124
  const resumeCursor = {
@@ -26328,6 +27130,7 @@ var createAcpInstance = (config2, host) => {
26328
27130
  providerInstanceId: host.instanceId,
26329
27131
  status: "idle",
26330
27132
  runtimeMode: input2.runtimeMode,
27133
+ ...input2.nativeModeId === void 0 ? {} : { nativeModeId: input2.nativeModeId },
26331
27134
  cwd,
26332
27135
  threadId: input2.threadId,
26333
27136
  createdAt,
@@ -26407,8 +27210,25 @@ var createAcpInstance = (config2, host) => {
26407
27210
  reason
26408
27211
  );
26409
27212
  },
26410
- respondToUserInput: async () => {
26411
- host.log.info("respondToUserInput ignored");
27213
+ respondToUserInput: async (threadId, requestId, answers) => {
27214
+ const booked = requireThread(threadId);
27215
+ await booked.executor.respondToUserInput(requestId, answers);
27216
+ },
27217
+ /**
27218
+ * 切这只手自报的审批档。`nativeModeId` 是它自己报的 id(探测时原样上报的那些),
27219
+ * 一路不翻译成宿主四档。
27220
+ *
27221
+ * 这是 `setRuntimeMode` 做不到的那一半:那个只改插件自己的会话记录,CLI 收不到
27222
+ * (`runtime-mode-never-reaches-cli` 记的那族缺陷)。
27223
+ */
27224
+ setNativeMode: async (threadId, nativeModeId) => {
27225
+ const booked = requireThread(threadId);
27226
+ await booked.executor.setNativeMode(nativeModeId);
27227
+ booked.session = {
27228
+ ...booked.session,
27229
+ nativeModeId,
27230
+ updatedAt: nowIso()
27231
+ };
26412
27232
  },
26413
27233
  setRuntimeMode: async (threadId, runtimeMode) => {
26414
27234
  const booked = requireThread(threadId);