@ycodium-ai/agent-acp 0.2.2092 → 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 +954 -113
  3. package/package.json +4 -4
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;
@@ -24261,7 +24273,7 @@ function makeAcpContentDeltaEvent(input2) {
24261
24273
  ...input2.turnId !== void 0 ? { turnId: input2.turnId } : {},
24262
24274
  ...input2.itemId ? { itemId: input2.itemId } : {},
24263
24275
  payload: {
24264
- streamKind: "assistant_text",
24276
+ streamKind: input2.streamKind ?? "assistant_text",
24265
24277
  delta: input2.text
24266
24278
  },
24267
24279
  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({
@@ -24945,21 +25224,24 @@ function parseSessionUpdateEvent(params) {
24945
25224
  }
24946
25225
  break;
24947
25226
  }
24948
- case "agent_message_chunk": {
24949
- const content = isRecord3(upd["content"]) ? upd["content"] : void 0;
25227
+ case "agent_message_chunk":
25228
+ case "agent_thought_chunk": {
25229
+ const content = isRecord5(upd["content"]) ? upd["content"] : void 0;
24950
25230
  const contentType = content?.["type"];
25231
+ const streamKind = kind === "agent_thought_chunk" ? "reasoning_text" : "assistant_text";
24951
25232
  if (contentType === "text") {
24952
25233
  const text = typeof content?.["text"] === "string" ? content["text"] : "";
24953
25234
  if (text.length > 0) {
24954
25235
  events.push({
24955
25236
  _tag: "ContentDelta",
24956
25237
  text,
25238
+ streamKind,
24957
25239
  rawPayload: params
24958
25240
  });
24959
25241
  }
24960
25242
  break;
24961
25243
  }
24962
- if (content && contentType !== "audio") {
25244
+ if (streamKind === "assistant_text" && content && contentType !== "audio") {
24963
25245
  events.push({
24964
25246
  _tag: "AssistantContentBlock",
24965
25247
  content,
@@ -25011,16 +25293,18 @@ function normalizeModelId(value) {
25011
25293
  return value?.trim() || void 0;
25012
25294
  }
25013
25295
  function modelSelectionStateFromSetup(setup) {
25014
- const sessionModels = isRecord3(setup.models) ? setup.models : void 0;
25296
+ const sessionModels = isRecord5(setup.models) ? setup.models : void 0;
25015
25297
  const modelConfigId = extractModelConfigId(setup);
25016
25298
  const strategy = sessionModels === void 0 && modelConfigId !== void 0 ? "config-option" : "session/set_model";
25017
- 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;
25018
25301
  const currentModelId = strategy === "config-option" ? typeof configModel === "string" ? normalizeModelId(configModel) : void 0 : typeof sessionModels?.["currentModelId"] === "string" ? normalizeModelId(sessionModels["currentModelId"]) : void 0;
25019
25302
  return {
25020
25303
  strategy,
25021
25304
  sessionModelsAdvertised: sessionModels !== void 0,
25022
25305
  modelConfigId,
25023
- currentModelId
25306
+ currentModelId,
25307
+ advertisedModelValues: strategy === "config-option" ? modelConfigOption === void 0 ? [] : collectSessionConfigOptionValues(modelConfigOption) : collectSessionModelIds(sessionModels)
25024
25308
  };
25025
25309
  }
25026
25310
  function createAcpExecutor(options) {
@@ -25039,8 +25323,10 @@ function createAcpExecutor(options) {
25039
25323
  strategy: "session/set_model",
25040
25324
  sessionModelsAdvertised: false,
25041
25325
  modelConfigId: void 0,
25042
- currentModelId: void 0
25326
+ currentModelId: void 0,
25327
+ advertisedModelValues: []
25043
25328
  };
25329
+ let lastUnmatchedModelId;
25044
25330
  let initializeResult;
25045
25331
  let startResult;
25046
25332
  let startInFlight;
@@ -25175,6 +25461,23 @@ function createAcpExecutor(options) {
25175
25461
  continue;
25176
25462
  }
25177
25463
  if (event._tag === "ContentDelta") {
25464
+ if (event.streamKind === "reasoning_text") {
25465
+ if (event.text.trim().length === 0) {
25466
+ continue;
25467
+ }
25468
+ emit(
25469
+ makeAcpContentDeltaEvent({
25470
+ stamp: stamp(),
25471
+ provider: options.provider,
25472
+ threadId: options.threadId,
25473
+ turnId: activeTurnId,
25474
+ text: event.text,
25475
+ streamKind: "reasoning_text",
25476
+ rawPayload: event.rawPayload
25477
+ })
25478
+ );
25479
+ continue;
25480
+ }
25178
25481
  if (event.text.trim().length === 0 && !assistantSegment.activeItemId) {
25179
25482
  continue;
25180
25483
  }
@@ -25187,6 +25490,7 @@ function createAcpExecutor(options) {
25187
25490
  turnId: activeTurnId,
25188
25491
  ...itemId ? { itemId } : {},
25189
25492
  text: event.text,
25493
+ streamKind: "assistant_text",
25190
25494
  rawPayload: event.rawPayload
25191
25495
  })
25192
25496
  );
@@ -25347,6 +25651,12 @@ function createAcpExecutor(options) {
25347
25651
  const userInputOutcome = await handlePermissionAsUserInput(params);
25348
25652
  if (userInputOutcome !== void 0) return userInputOutcome;
25349
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
+ }
25350
25660
  const requestId = randomId();
25351
25661
  const outcome = await new Promise((resolve) => {
25352
25662
  const pending = {
@@ -25434,36 +25744,70 @@ function createAcpExecutor(options) {
25434
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) {
25435
25745
  return input2.state;
25436
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
+ }
25437
25762
  if (input2.state.strategy === "config-option") {
25438
25763
  await connection.request("session/set_config_option", {
25439
25764
  sessionId: input2.acpSessionId,
25440
25765
  configId: input2.state.modelConfigId,
25441
- value: requestedModelId
25766
+ value
25442
25767
  });
25443
25768
  } else {
25444
25769
  await connection.request("session/set_model", {
25445
25770
  sessionId: input2.acpSessionId,
25446
- modelId: requestedModelId
25771
+ modelId: value
25447
25772
  });
25448
25773
  }
25449
25774
  if (!isLive(input2.expectedGeneration)) {
25450
25775
  throw new AcpExecutorDisposedError();
25451
25776
  }
25452
- 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
+ }
25453
25808
  };
25454
25809
  const startOnce = async (expectedGeneration) => {
25455
- const initializePayload = {
25456
- protocolVersion: PROTOCOL_VERSION,
25457
- clientCapabilities: {
25458
- fs: {
25459
- readTextFile: false,
25460
- writeTextFile: false,
25461
- ...options.clientCapabilities?.fs
25462
- },
25463
- terminal: options.clientCapabilities?.terminal ?? false
25464
- },
25465
- clientInfo: options.clientInfo
25466
- };
25810
+ const initializePayload = acpInitializePayload(options.clientInfo, options.clientCapabilities);
25467
25811
  const initialized = await connection.request(
25468
25812
  "initialize",
25469
25813
  initializePayload
@@ -25507,12 +25851,27 @@ function createAcpExecutor(options) {
25507
25851
  nextSessionId = created.sessionId;
25508
25852
  setup = created;
25509
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
+ }
25510
25863
  const nextModelSelectionState = await applyRequestedModel({
25511
25864
  state: modelSelectionStateFromSetup(setup),
25512
25865
  acpSessionId: nextSessionId,
25513
25866
  requestedModelId: options.requestedModelId,
25514
25867
  expectedGeneration
25515
25868
  });
25869
+ await applyRequestedEffort({
25870
+ setup,
25871
+ acpSessionId: nextSessionId,
25872
+ requestedEffort: options.requestedEffort,
25873
+ expectedGeneration
25874
+ });
25516
25875
  if (options.beforeApplyResult) {
25517
25876
  await options.beforeApplyResult();
25518
25877
  }
@@ -25877,6 +26236,26 @@ function createAcpExecutor(options) {
25877
26236
  };
25878
26237
  apply();
25879
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
+ };
25880
26259
  const respondToUserInput = async (requestId, answers) => {
25881
26260
  failIfDisposed();
25882
26261
  const pending = pendingUserInputs.get(requestId);
@@ -25893,7 +26272,7 @@ function createAcpExecutor(options) {
25893
26272
  pendingUserInputs.delete(requestId);
25894
26273
  pending.resolve({
25895
26274
  kind: "answered",
25896
- answers: isRecord3(answers) ? answers : {}
26275
+ answers: isRecord5(answers) ? answers : {}
25897
26276
  });
25898
26277
  };
25899
26278
  const dispose = async () => {
@@ -25943,10 +26322,53 @@ function createAcpExecutor(options) {
25943
26322
  cancel,
25944
26323
  respondToRequest,
25945
26324
  respondToUserInput,
26325
+ setNativeMode,
25946
26326
  dispose
25947
26327
  };
25948
26328
  }
25949
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
+
25950
26372
  // src/card.ts
25951
26373
  var ACP_DRIVER_KIND = "acp";
25952
26374
  var ACP_CARD = {
@@ -25978,7 +26400,21 @@ var ACP_CARD = {
25978
26400
  everydayHarness: false,
25979
26401
  capabilities: {
25980
26402
  sessionModelSwitch: "unsupported",
25981
- 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
25982
26418
  },
25983
26419
  credentialSources: [],
25984
26420
  diagnostics: [],
@@ -26001,12 +26437,20 @@ var ACP_CARD = {
26001
26437
  },
26002
26438
  modelConnectivity: {
26003
26439
  dialects: ["anthropic-messages", "openai-responses", "openai-chat-completions"],
26004
- 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
26005
26449
  }
26006
26450
  };
26007
26451
 
26008
26452
  // src/config.ts
26009
- function isRecord4(value) {
26453
+ function isRecord6(value) {
26010
26454
  return typeof value === "object" && value !== null && !Array.isArray(value);
26011
26455
  }
26012
26456
  function readString(value) {
@@ -26019,7 +26463,7 @@ function readEnvironment(value) {
26019
26463
  if (!Array.isArray(value)) return {};
26020
26464
  const env = {};
26021
26465
  for (const entry of value) {
26022
- if (!isRecord4(entry)) continue;
26466
+ if (!isRecord6(entry)) continue;
26023
26467
  const name = readTrimmed(entry["name"]);
26024
26468
  if (name.length === 0) continue;
26025
26469
  env[name] = readString(entry["value"]);
@@ -26027,17 +26471,18 @@ function readEnvironment(value) {
26027
26471
  return env;
26028
26472
  }
26029
26473
  function parseAcpInstanceConfig(config2) {
26030
- if (!isRecord4(config2)) {
26474
+ if (!isRecord6(config2)) {
26031
26475
  return {
26032
26476
  binaryPath: "",
26033
26477
  launchArgs: "",
26034
26478
  homePath: "",
26035
26479
  homeEnvVar: "",
26036
26480
  environment: {},
26037
- envelopeDriver: void 0
26481
+ envelopeDriver: void 0,
26482
+ providerId: ""
26038
26483
  };
26039
26484
  }
26040
- const nested = isRecord4(config2["config"]) ? config2["config"] : void 0;
26485
+ const nested = isRecord6(config2["config"]) ? config2["config"] : void 0;
26041
26486
  const settings = nested ?? config2;
26042
26487
  return {
26043
26488
  binaryPath: readTrimmed(settings["binaryPath"]),
@@ -26045,7 +26490,8 @@ function parseAcpInstanceConfig(config2) {
26045
26490
  homePath: readTrimmed(settings["homePath"]) || readTrimmed(config2["homePath"]),
26046
26491
  homeEnvVar: readTrimmed(settings["homeEnvVar"]) || readTrimmed(config2["homeEnvVar"]),
26047
26492
  environment: readEnvironment(config2["environment"]),
26048
- envelopeDriver: readTrimmed(config2["driver"]) || void 0
26493
+ envelopeDriver: readTrimmed(config2["driver"]) || void 0,
26494
+ providerId: readTrimmed(settings["providerId"]) || readTrimmed(config2["providerId"])
26049
26495
  };
26050
26496
  }
26051
26497
  function tokenizeLaunchArgs(value) {
@@ -26100,57 +26546,372 @@ function tokenizeLaunchArgs(value) {
26100
26546
  return tokens;
26101
26547
  }
26102
26548
 
26103
- // src/streams.ts
26104
- import * as NodeStream2 from "node:stream";
26105
- function nodeStreamsFromHandle(handle) {
26106
- const readable = new NodeStream2.PassThrough();
26107
- 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;
26108
26604
  try {
26109
- for await (const chunk of handle.stdout) {
26110
- if (!readable.writable) return;
26111
- if (!readable.write(chunk)) {
26112
- await new Promise((resolve) => {
26113
- const onDone = () => {
26114
- readable.off("drain", onDone);
26115
- readable.off("close", onDone);
26116
- resolve();
26117
- };
26118
- readable.once("drain", onDone);
26119
- readable.once("close", onDone);
26120
- });
26121
- }
26122
- }
26123
- } catch (error62) {
26124
- readable.destroy(error62 instanceof Error ? error62 : new Error(String(error62)));
26125
- return;
26605
+ record2 = JSON.parse(trimmed);
26606
+ } catch {
26607
+ continue;
26126
26608
  }
26127
- if (readable.writable) readable.end();
26128
- })();
26129
- void (async () => {
26130
- for await (const _chunk of handle.stderr) {
26131
- }
26132
- })().catch(() => void 0);
26133
- const writable = new NodeStream2.Writable({
26134
- write(chunk, _encoding, callback) {
26135
- const bytes = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
26136
- handle.writeStdin(bytes).then(
26137
- () => callback(),
26138
- (error62) => callback(error62 instanceof Error ? error62 : new Error(String(error62)))
26139
- );
26140
- },
26141
- final(callback) {
26142
- handle.closeStdin().then(
26143
- () => callback(),
26144
- (error62) => callback(error62 instanceof Error ? error62 : new Error(String(error62)))
26145
- );
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;
26146
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
+ );
26147
26724
  });
26148
- 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;
26149
26910
  }
26150
26911
 
26151
26912
  // src/acpInstance.ts
26152
26913
  var RESUME_SCHEMA_VERSION = 1;
26153
- var CLIENT_INFO = { name: "ycodium-agent-acp", version: "0.2.0" };
26914
+ var CLIENT_INFO2 = { name: "ycodium-agent-acp", version: "0.2.0" };
26154
26915
  var yieldTick = () => new Promise((resolve) => {
26155
26916
  setImmediate(resolve);
26156
26917
  });
@@ -26187,19 +26948,12 @@ var EventBus = class {
26187
26948
  }
26188
26949
  };
26189
26950
  function expandHomePath(value) {
26190
- if (value === "~") return NodeOS.homedir();
26951
+ if (value === "~") return NodeOS2.homedir();
26191
26952
  if (value.startsWith("~/") || value.startsWith("~\\")) {
26192
- return NodePath.join(NodeOS.homedir(), value.slice(2));
26953
+ return NodePath3.join(NodeOS2.homedir(), value.slice(2));
26193
26954
  }
26194
26955
  return value;
26195
26956
  }
26196
- function compactEnv(env) {
26197
- const out = {};
26198
- for (const [key, value] of Object.entries(env)) {
26199
- if (value !== void 0) out[key] = value;
26200
- }
26201
- return out;
26202
- }
26203
26957
  function modelIdFromSelection(selection) {
26204
26958
  if (typeof selection !== "object" || selection === null || Array.isArray(selection)) {
26205
26959
  return void 0;
@@ -26207,19 +26961,42 @@ function modelIdFromSelection(selection) {
26207
26961
  const model = selection["model"];
26208
26962
  return typeof model === "string" ? model.trim() || void 0 : void 0;
26209
26963
  }
26210
- function buildSpawnEnv(recipe, launch) {
26211
- const homePath = (launch.homePath?.trim() || recipe.homePath).trim();
26964
+ function buildSpawnEnv(recipe, configuredHomePath, launchEnv = {}) {
26965
+ const homePath = configuredHomePath.trim();
26212
26966
  const homeEnvVar = recipe.homeEnvVar.trim();
26213
26967
  const homeInjection = homePath.length > 0 && homeEnvVar.length > 0 ? { [homeEnvVar]: expandHomePath(homePath) } : {};
26214
- return {
26215
- ...compactEnv(launch.processEnv),
26216
- ...homeInjection,
26217
- ...recipe.environment
26218
- };
26968
+ return mergeLaunchEnv({
26969
+ brainSlots: brainSlotEnvKeys(ACP_CARD),
26970
+ accountHome: homeInjection,
26971
+ instanceTable: recipe.environment,
26972
+ launch: launchEnv
26973
+ });
26219
26974
  }
26220
26975
  function nowIso() {
26221
26976
  return (/* @__PURE__ */ new Date()).toISOString();
26222
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
+ }
26223
27000
  async function stopBooked(booked) {
26224
27001
  await booked.executor.dispose().catch(() => void 0);
26225
27002
  await booked.handle.kill("SIGTERM").catch(() => void 0);
@@ -26246,11 +27023,33 @@ var createAcpInstance = (config2, host) => {
26246
27023
  detail: "ACP program path is not configured"
26247
27024
  };
26248
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;
26249
27043
  return {
26250
27044
  installed: true,
26251
27045
  version: null,
26252
27046
  readiness: "unchecked",
26253
- 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
+ }
26254
27053
  };
26255
27054
  },
26256
27055
  startSession: async (input2, signal) => {
@@ -26266,7 +27065,11 @@ var createAcpInstance = (config2, host) => {
26266
27065
  await stopBooked(existing);
26267
27066
  threads.delete(input2.threadId);
26268
27067
  }
26269
- const env = buildSpawnEnv(recipe, input2.launch);
27068
+ const env = buildSpawnEnv(
27069
+ recipe,
27070
+ input2.launch.homePath?.trim() || recipe.homePath,
27071
+ input2.launch.processEnv
27072
+ );
26270
27073
  const handle = await host.process.spawn({
26271
27074
  program: recipe.binaryPath,
26272
27075
  args: tokenizeLaunchArgs(recipe.launchArgs),
@@ -26274,19 +27077,32 @@ var createAcpInstance = (config2, host) => {
26274
27077
  owner: { bindingId: host.bindingId },
26275
27078
  ...Object.keys(env).length > 0 ? { env } : {}
26276
27079
  });
26277
- const { readable, writable } = nodeStreamsFromHandle(handle);
27080
+ const { readable, writable } = nodeStreamsFromHandle2(handle);
26278
27081
  const resume = input2.discardResumeCursor === true ? void 0 : parseAcpResumeCursor(input2.resumeCursor, RESUME_SCHEMA_VERSION);
26279
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;
26280
27092
  const executor = createAcpExecutor({
26281
27093
  readable,
26282
27094
  writable,
27095
+ ...requestedEffort === void 0 ? {} : { requestedEffort },
26283
27096
  cwd,
26284
27097
  threadId: input2.threadId,
26285
27098
  provider: ACP_DRIVER_KIND,
26286
27099
  providerInstanceId: host.instanceId,
26287
- clientInfo: CLIENT_INFO,
27100
+ clientInfo: CLIENT_INFO2,
27101
+ log: host.log,
26288
27102
  ...requestedModelId === void 0 ? {} : { requestedModelId },
26289
27103
  ...input2.bindingGeneration === void 0 ? {} : { originBindingGeneration: input2.bindingGeneration },
27104
+ ...hooks === void 0 ? {} : { hooks },
27105
+ ...autoApprove === void 0 ? {} : { autoApprove },
26290
27106
  ...resume === void 0 ? {} : { resumeSessionId: resume.sessionId }
26291
27107
  });
26292
27108
  const forwarding = (async () => {
@@ -26296,6 +27112,13 @@ var createAcpInstance = (config2, host) => {
26296
27112
  })();
26297
27113
  try {
26298
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
+ }
26299
27122
  await yieldTick();
26300
27123
  const createdAt = nowIso();
26301
27124
  const resumeCursor = {
@@ -26307,6 +27130,7 @@ var createAcpInstance = (config2, host) => {
26307
27130
  providerInstanceId: host.instanceId,
26308
27131
  status: "idle",
26309
27132
  runtimeMode: input2.runtimeMode,
27133
+ ...input2.nativeModeId === void 0 ? {} : { nativeModeId: input2.nativeModeId },
26310
27134
  cwd,
26311
27135
  threadId: input2.threadId,
26312
27136
  createdAt,
@@ -26386,8 +27210,25 @@ var createAcpInstance = (config2, host) => {
26386
27210
  reason
26387
27211
  );
26388
27212
  },
26389
- respondToUserInput: async () => {
26390
- 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
+ };
26391
27232
  },
26392
27233
  setRuntimeMode: async (threadId, runtimeMode) => {
26393
27234
  const booked = requireThread(threadId);