@ycodium-ai/agent-dsh 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 (2) hide show
  1. package/dist/index.js +605 -123
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -9,6 +9,7 @@ import { definePlugin } from "@ycodium-ai/plugin-api";
9
9
 
10
10
  // src/card.ts
11
11
  var DSH_DRIVER_KIND = "dsh";
12
+ var DSH_CLIENT_INFO = { name: "ycodium-agent-dsh", version: "0.1.0" };
12
13
  var DSH_CARD = {
13
14
  driverKind: DSH_DRIVER_KIND,
14
15
  displayName: "DeepSeek Harness",
@@ -30,6 +31,12 @@ var DSH_CARD = {
30
31
  setupNote: "",
31
32
  // 模型 id 是 agent 自家目录的 `provider/model` 对:只 trim,不做别名展开,
32
33
  // 也没有默认值——空选择 = 保持 agent 起来时那个。
34
+ //
35
+ // **清单不靠这一栏兜底**:`probeDshModels` 每次探测都问 CLI 要那份目录
36
+ // (会话握手的 `configOptions`),报上来的是四个带名字的型号。这里留空是有意的:
37
+ // 编一个型号当兜底,等 CLI 换了目录就变成了发出去被拒的坏 id(grok 的名片上
38
+ // 有一条这样的兜底,代价写在它自己的注释里)。探测问不到时这双手就是没有清单,
39
+ // 界面按「机器没报模型」处理,比摆一个可能不对的型号诚实。
33
40
  defaultModel: "",
34
41
  defaultTextGenerationModel: "",
35
42
  customModelIdExample: "",
@@ -47,6 +54,17 @@ var DSH_CARD = {
47
54
  // DSH 换模型只有 `session/set_config_option` 一条路;库会按握手形状自动协商,
48
55
  // 但插件侧的执行路径还没接到模型管理 UI,名片必须说实话。
49
56
  sessionModelSwitch: "unsupported",
57
+ // **这一位今天是空头支票,别当它验过**(2026-09-19 拿真 `dsh --profile acp`
58
+ // 起一轮 ACP 会话实测)。两件事都不成立:
59
+ //
60
+ // 1. 本插件的 `setRuntimeMode` 只改自己的会话记录,一个字节都不发给 CLI——用户
61
+ // 中途切档,什么也没发生(与 Grok 那次 F8 同款缺陷)。
62
+ // 2. CLI 那头根本没有这条路:`session/new` 不报 `modes`,`session/set_mode` 回
63
+ // `-32601 "Method not found"`。所以照 kimi 那样接 `session/set_mode` 也没用,
64
+ // 得先查 dsh 自己有没有启动旗标(Grok 那条是 `--permission-mode`)。
65
+ //
66
+ // 改这一位要连着 `packages/contracts` 那张表一起改(`executorRegistry.test.ts`
67
+ // 的防漂移闸把两边钉成同值)。
50
68
  runtimeModeSwitch: "in-session"
51
69
  },
52
70
  // 凭据由 CLI 自己保管在它 home 里,宿主不读 token。
@@ -85,8 +103,7 @@ var DSH_CARD = {
85
103
  };
86
104
 
87
105
  // src/dshInstance.ts
88
- import * as NodeOS from "node:os";
89
- import * as NodePath from "node:path";
106
+ import { nodeStreamsFromHandle as nodeStreamsFromHandle2 } from "@ycodium-ai/plugin-api/process-stdin";
90
107
 
91
108
  // ../../packages/ycodium-acp-executor/src/executor.ts
92
109
  import * as NodeCrypto from "node:crypto";
@@ -23956,6 +23973,12 @@ function acpApprovalOptions(request) {
23956
23973
  }
23957
23974
  return options;
23958
23975
  }
23976
+ function selectAutoApprovedAcpPermissionOption(request) {
23977
+ if (new Set(request.options.map((option) => option.kind)).size !== request.options.length) {
23978
+ return void 0;
23979
+ }
23980
+ return selectAcpPermissionOptionId(request, "acceptForSession") ?? selectAcpPermissionOptionId(request, "accept");
23981
+ }
23959
23982
  function parseAcpResumeCursor(raw, schemaVersion) {
23960
23983
  if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return void 0;
23961
23984
  const record2 = raw;
@@ -24338,7 +24361,7 @@ function makeAcpContentDeltaEvent(input2) {
24338
24361
  ...input2.turnId !== void 0 ? { turnId: input2.turnId } : {},
24339
24362
  ...input2.itemId ? { itemId: input2.itemId } : {},
24340
24363
  payload: {
24341
- streamKind: "assistant_text",
24364
+ streamKind: input2.streamKind ?? "assistant_text",
24342
24365
  delta: input2.text
24343
24366
  },
24344
24367
  raw: {
@@ -24349,6 +24372,158 @@ function makeAcpContentDeltaEvent(input2) {
24349
24372
  };
24350
24373
  }
24351
24374
 
24375
+ // ../../packages/ycodium-acp-executor/src/config-options.ts
24376
+ function isRecord3(value) {
24377
+ return typeof value === "object" && value !== null && !Array.isArray(value);
24378
+ }
24379
+ function asString(value) {
24380
+ return typeof value === "string" ? value : void 0;
24381
+ }
24382
+ function normalizedField(option, key) {
24383
+ return asString(option[key])?.trim().toLowerCase() ?? "";
24384
+ }
24385
+ function looksLikeEffortOption(option) {
24386
+ const id = normalizedField(option, "id");
24387
+ const name = normalizedField(option, "name");
24388
+ return id === "effort" || id === "reasoning" || name === "effort" || name === "reasoning" || name.includes("effort") || name.includes("reasoning") || normalizedField(option, "category") === "thought_level";
24389
+ }
24390
+ function flattenChoices(rawOptions, currentValue) {
24391
+ if (!Array.isArray(rawOptions)) return [];
24392
+ const out = [];
24393
+ const seen = /* @__PURE__ */ new Set();
24394
+ const push = (entry) => {
24395
+ if (!isRecord3(entry)) return;
24396
+ const value = asString(entry["value"]);
24397
+ if (value === void 0 || seen.has(value)) return;
24398
+ seen.add(value);
24399
+ const description = asString(entry["description"])?.trim();
24400
+ const name = asString(entry["name"])?.trim();
24401
+ out.push({
24402
+ value,
24403
+ ...name ? { name } : {},
24404
+ label: name || value,
24405
+ ...description ? { description } : {},
24406
+ ...currentValue !== void 0 && currentValue === value ? { isDefault: true } : {}
24407
+ });
24408
+ };
24409
+ for (const entry of rawOptions) {
24410
+ if (isRecord3(entry) && Array.isArray(entry["options"])) {
24411
+ for (const nested of entry["options"]) push(nested);
24412
+ continue;
24413
+ }
24414
+ push(entry);
24415
+ }
24416
+ return out;
24417
+ }
24418
+ function findAcpEffortConfig(configOptions) {
24419
+ if (!Array.isArray(configOptions)) return void 0;
24420
+ const candidates = configOptions.filter(isRecord3).filter((option) => normalizedField(option, "type") === "select").filter(looksLikeEffortOption);
24421
+ if (candidates.length === 0) return void 0;
24422
+ 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];
24423
+ if (picked === void 0) return void 0;
24424
+ const configId = asString(picked["id"])?.trim();
24425
+ if (!configId) return void 0;
24426
+ const choices = flattenChoices(picked["options"], asString(picked["currentValue"])?.trim());
24427
+ if (choices.length === 0) return void 0;
24428
+ return {
24429
+ configId,
24430
+ label: asString(picked["name"])?.trim() ?? "",
24431
+ choices
24432
+ };
24433
+ }
24434
+ function acpModelChoicesFromConfigOptions(configOptions) {
24435
+ if (!Array.isArray(configOptions)) return void 0;
24436
+ const records = configOptions.filter(isRecord3);
24437
+ const modelOption = records.find((option) => normalizedField(option, "category") === "model");
24438
+ if (modelOption === void 0) return void 0;
24439
+ if (!asString(modelOption["id"])?.trim()) return void 0;
24440
+ return flattenChoices(modelOption["options"], asString(modelOption["currentValue"])?.trim());
24441
+ }
24442
+
24443
+ // ../../packages/ycodium-acp-executor/src/runtime-mode.ts
24444
+ function isRecord4(value) {
24445
+ return typeof value === "object" && value !== null && !Array.isArray(value);
24446
+ }
24447
+ function asString2(value) {
24448
+ return typeof value === "string" ? value : void 0;
24449
+ }
24450
+ function acpModeConfigId(setup) {
24451
+ if (!isRecord4(setup)) return void 0;
24452
+ const configOptions = setup["configOptions"];
24453
+ if (!Array.isArray(configOptions)) return void 0;
24454
+ for (const option of configOptions) {
24455
+ if (!isRecord4(option)) continue;
24456
+ if (asString2(option["category"])?.trim().toLowerCase() !== "mode") continue;
24457
+ const id = asString2(option["id"])?.trim();
24458
+ if (id) return id;
24459
+ }
24460
+ return void 0;
24461
+ }
24462
+
24463
+ // ../../packages/ycodium-acp-executor/src/handshake.ts
24464
+ function acpInitializePayload(clientInfo, overrides) {
24465
+ return {
24466
+ protocolVersion: PROTOCOL_VERSION,
24467
+ clientCapabilities: {
24468
+ fs: {
24469
+ readTextFile: false,
24470
+ writeTextFile: false,
24471
+ ...overrides?.fs
24472
+ },
24473
+ terminal: overrides?.terminal ?? false
24474
+ },
24475
+ clientInfo
24476
+ };
24477
+ }
24478
+
24479
+ // ../../packages/ycodium-acp-executor/src/config-option-wiring.ts
24480
+ var VENDOR_CATEGORY_PREFIX = "_";
24481
+ var ACP_CONFIG_CATEGORY_HANDLING = {
24482
+ mode: {
24483
+ consumed: true,
24484
+ via: "runtime-mode.ts\uFF1AacpAvailableModes / acpCurrentModeId / acpModeConfigId"
24485
+ },
24486
+ model: {
24487
+ consumed: true,
24488
+ via: "runtime-model.ts:extractModelConfigId \u2192 executor.ts:applyRequestedModel"
24489
+ },
24490
+ thought_level: {
24491
+ consumed: true,
24492
+ via: "config-options.ts:findAcpEffortConfig \u2192 executor.ts:applyRequestedEffort"
24493
+ },
24494
+ model_option: {
24495
+ consumed: true,
24496
+ // 精确说法:它不是**识别器**,是**优先级**。`looksLikeEffortOption` 先筛出候选,
24497
+ // 标了 `model_option` 的在候选里排第一(厂商自己标的最准),排在 id 叫 `effort`
24498
+ // 的和标 `thought_level` 的前面。光有这个 category 而 id/name 不像力度档的,
24499
+ // 压根进不了候选。
24500
+ via: "config-options.ts:findAcpEffortConfig\uFF08\u5019\u9009\u91CC\u7684\u6700\u9AD8\u4F18\u5148\u7EA7\uFF0C\u538B\u8FC7 thought_level\uFF09"
24501
+ },
24502
+ model_config: {
24503
+ consumed: false,
24504
+ 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"
24505
+ }
24506
+ };
24507
+ var KNOWN_CATEGORIES = new Set(Object.keys(ACP_CONFIG_CATEGORY_HANDLING));
24508
+ function unhandledConfigCategories(configOptions) {
24509
+ if (!configOptions) return [];
24510
+ const seen = /* @__PURE__ */ new Set();
24511
+ const out = [];
24512
+ for (const option of configOptions) {
24513
+ const category = option.category?.trim();
24514
+ if (!category) continue;
24515
+ if (KNOWN_CATEGORIES.has(category)) continue;
24516
+ if (seen.has(category)) continue;
24517
+ seen.add(category);
24518
+ out.push({
24519
+ category,
24520
+ optionId: option.id.trim(),
24521
+ vendorExtension: category.startsWith(VENDOR_CATEGORY_PREFIX)
24522
+ });
24523
+ }
24524
+ return out;
24525
+ }
24526
+
24352
24527
  // ../../packages/ycodium-acp-executor/src/mutex.ts
24353
24528
  var Mutex = class {
24354
24529
  locked = false;
@@ -24391,7 +24566,7 @@ var Mutex = class {
24391
24566
  };
24392
24567
 
24393
24568
  // ../../packages/ycodium-acp-executor/src/util.ts
24394
- function isRecord3(value) {
24569
+ function isRecord5(value) {
24395
24570
  return typeof value === "object" && value !== null && !Array.isArray(value);
24396
24571
  }
24397
24572
  function asTrimmedString(value) {
@@ -24437,11 +24612,11 @@ function extractCommandFromTitle(title) {
24437
24612
  return backtickMatch?.[1]?.trim() || void 0;
24438
24613
  }
24439
24614
  function extractToolCommand(data, title) {
24440
- const item = isRecord3(data?.["item"]) ? data["item"] : void 0;
24441
- const itemInput = item && isRecord3(item["input"]) ? item["input"] : void 0;
24442
- const itemResult = item && isRecord3(item["result"]) ? item["result"] : void 0;
24443
- const rawInput = isRecord3(data?.["rawInput"]) ? data["rawInput"] : void 0;
24444
- const dataInput = isRecord3(data?.["input"]) ? data["input"] : void 0;
24615
+ const item = isRecord5(data?.["item"]) ? data["item"] : void 0;
24616
+ const itemInput = item && isRecord5(item["input"]) ? item["input"] : void 0;
24617
+ const itemResult = item && isRecord5(item["result"]) ? item["result"] : void 0;
24618
+ const rawInput = isRecord5(data?.["rawInput"]) ? data["rawInput"] : void 0;
24619
+ const dataInput = isRecord5(data?.["input"]) ? data["input"] : void 0;
24445
24620
  const candidates = [
24446
24621
  normalizeCommandValue(item?.["command"]),
24447
24622
  normalizeCommandValue(itemInput?.["command"]),
@@ -24486,7 +24661,7 @@ function collectPaths(value, paths, seen, depth) {
24486
24661
  }
24487
24662
  return;
24488
24663
  }
24489
- if (!isRecord3(value)) {
24664
+ if (!isRecord5(value)) {
24490
24665
  return;
24491
24666
  }
24492
24667
  for (const key of ["path", "filePath", "relativePath", "filename", "newPath", "oldPath"]) {
@@ -24549,7 +24724,7 @@ function deriveToolActivityPresentation(input2) {
24549
24724
  const title = asTrimmedString(input2.title);
24550
24725
  const detail = stripTrailingExitCode(asTrimmedString(input2.detail));
24551
24726
  const fallbackSummary = asTrimmedString(input2.fallbackSummary) ?? "Tool";
24552
- const data = isRecord3(input2.data) ? input2.data : void 0;
24727
+ const data = isRecord5(input2.data) ? input2.data : void 0;
24553
24728
  const command = extractToolCommand(data, title);
24554
24729
  const primaryPath = extractPrimaryPath(data);
24555
24730
  const action = classifyToolAction({
@@ -24581,7 +24756,7 @@ function deriveToolActivityPresentation(input2) {
24581
24756
  };
24582
24757
  }
24583
24758
  if (action === "search") {
24584
- const rawInput = isRecord3(data?.["rawInput"]) ? data["rawInput"] : void 0;
24759
+ const rawInput = isRecord5(data?.["rawInput"]) ? data["rawInput"] : void 0;
24585
24760
  const query = asTrimmedString(rawInput?.["query"]) ?? asTrimmedString(rawInput?.["pattern"]) ?? asTrimmedString(rawInput?.["searchTerm"]);
24586
24761
  return {
24587
24762
  summary: "Searched files",
@@ -24620,6 +24795,46 @@ function findSessionConfigOption(configOptions, configId) {
24620
24795
  }
24621
24796
  return configOptions.find((option) => option.id.trim() === normalizedConfigId);
24622
24797
  }
24798
+ function collectSessionConfigOptionValues(configOption) {
24799
+ if (configOption.type !== "select" || !configOption.options) {
24800
+ return [];
24801
+ }
24802
+ return configOption.options.flatMap(
24803
+ (entry) => "value" in entry ? [entry.value] : entry.options.map((option) => option.value)
24804
+ );
24805
+ }
24806
+ function collectSessionModelIds(models) {
24807
+ if (!isRecord5(models) || !Array.isArray(models["availableModels"])) {
24808
+ return [];
24809
+ }
24810
+ return models["availableModels"].flatMap(
24811
+ (model) => isRecord5(model) && typeof model["modelId"] === "string" ? [model["modelId"]] : []
24812
+ );
24813
+ }
24814
+ function catalogModelLastSegment(value) {
24815
+ if (value.startsWith("[")) {
24816
+ let parsed;
24817
+ try {
24818
+ parsed = JSON.parse(value);
24819
+ } catch {
24820
+ return void 0;
24821
+ }
24822
+ if (!Array.isArray(parsed) || parsed.length < 2) return void 0;
24823
+ const last = parsed[parsed.length - 1];
24824
+ return typeof last === "string" ? last : void 0;
24825
+ }
24826
+ const separator = value.indexOf("/");
24827
+ return separator > 0 && separator < value.length - 1 ? value.slice(separator + 1) : void 0;
24828
+ }
24829
+ function matchAdvertisedModelValue(requestedModelId, advertisedValues) {
24830
+ const wanted = requestedModelId.trim();
24831
+ if (!wanted) return void 0;
24832
+ if (advertisedValues.includes(wanted)) return wanted;
24833
+ const byLastSegment = advertisedValues.filter(
24834
+ (value) => catalogModelLastSegment(value) === wanted
24835
+ );
24836
+ return byLastSegment.length === 1 ? byLastSegment[0] : void 0;
24837
+ }
24623
24838
  function parseSessionModeState(sessionResponse) {
24624
24839
  const modes = sessionResponse.modes;
24625
24840
  if (!modes) return void 0;
@@ -24658,7 +24873,7 @@ function parseAvailableCommandsUpdate(params) {
24658
24873
  }
24659
24874
  const commands = [];
24660
24875
  for (const command of availableCommands) {
24661
- if (!isRecord3(command)) {
24876
+ if (!isRecord5(command)) {
24662
24877
  continue;
24663
24878
  }
24664
24879
  const name = typeof command["name"] === "string" ? command["name"].trim() : "";
@@ -24666,7 +24881,7 @@ function parseAvailableCommandsUpdate(params) {
24666
24881
  continue;
24667
24882
  }
24668
24883
  const description = typeof command["description"] === "string" ? command["description"].trim() || void 0 : void 0;
24669
- const input2 = isRecord3(command["input"]) ? command["input"] : void 0;
24884
+ const input2 = isRecord5(command["input"]) ? command["input"] : void 0;
24670
24885
  const inputHint = typeof input2?.["hint"] === "string" ? input2["hint"].trim() || void 0 : void 0;
24671
24886
  commands.push({
24672
24887
  name,
@@ -24728,7 +24943,7 @@ function extractCommandFromTitle2(title) {
24728
24943
  return match?.[1]?.trim() || void 0;
24729
24944
  }
24730
24945
  function extractToolCallCommand(rawInput, title) {
24731
- if (isRecord3(rawInput)) {
24946
+ if (isRecord5(rawInput)) {
24732
24947
  const directCommand = normalizeCommandValue2(rawInput["command"]);
24733
24948
  if (directCommand) {
24734
24949
  return directCommand;
@@ -24755,7 +24970,7 @@ function boundToolCallOutputText(text) {
24755
24970
  }
24756
24971
  var RAW_OUTPUT_TEXT_FIELDS = ["content", "stdout", "stderr", "output"];
24757
24972
  function boundToolCallRawOutput(rawOutput) {
24758
- if (!isRecord3(rawOutput)) {
24973
+ if (!isRecord5(rawOutput)) {
24759
24974
  return rawOutput;
24760
24975
  }
24761
24976
  let changed = false;
@@ -24985,7 +25200,7 @@ function parseSessionUpdateEvent(params) {
24985
25200
  const entries = Array.isArray(upd["entries"]) ? upd["entries"] : [];
24986
25201
  const plan = entries.map((entry, index) => ({
24987
25202
  step: readAcpText(entry, "content") ?? `Step ${index + 1}`,
24988
- status: normalizePlanStepStatus(isRecord3(entry) ? entry["status"] : void 0)
25203
+ status: normalizePlanStepStatus(isRecord5(entry) ? entry["status"] : void 0)
24989
25204
  }));
24990
25205
  if (plan.length > 0) {
24991
25206
  events.push({
@@ -25022,21 +25237,24 @@ function parseSessionUpdateEvent(params) {
25022
25237
  }
25023
25238
  break;
25024
25239
  }
25025
- case "agent_message_chunk": {
25026
- const content = isRecord3(upd["content"]) ? upd["content"] : void 0;
25240
+ case "agent_message_chunk":
25241
+ case "agent_thought_chunk": {
25242
+ const content = isRecord5(upd["content"]) ? upd["content"] : void 0;
25027
25243
  const contentType = content?.["type"];
25244
+ const streamKind = kind === "agent_thought_chunk" ? "reasoning_text" : "assistant_text";
25028
25245
  if (contentType === "text") {
25029
25246
  const text = typeof content?.["text"] === "string" ? content["text"] : "";
25030
25247
  if (text.length > 0) {
25031
25248
  events.push({
25032
25249
  _tag: "ContentDelta",
25033
25250
  text,
25251
+ streamKind,
25034
25252
  rawPayload: params
25035
25253
  });
25036
25254
  }
25037
25255
  break;
25038
25256
  }
25039
- if (content && contentType !== "audio") {
25257
+ if (streamKind === "assistant_text" && content && contentType !== "audio") {
25040
25258
  events.push({
25041
25259
  _tag: "AssistantContentBlock",
25042
25260
  content,
@@ -25088,16 +25306,18 @@ function normalizeModelId(value) {
25088
25306
  return value?.trim() || void 0;
25089
25307
  }
25090
25308
  function modelSelectionStateFromSetup(setup) {
25091
- const sessionModels = isRecord3(setup.models) ? setup.models : void 0;
25309
+ const sessionModels = isRecord5(setup.models) ? setup.models : void 0;
25092
25310
  const modelConfigId = extractModelConfigId(setup);
25093
25311
  const strategy = sessionModels === void 0 && modelConfigId !== void 0 ? "config-option" : "session/set_model";
25094
- const configModel = modelConfigId === void 0 ? void 0 : findSessionConfigOption(setup.configOptions, modelConfigId)?.currentValue;
25312
+ const modelConfigOption = modelConfigId === void 0 ? void 0 : findSessionConfigOption(setup.configOptions, modelConfigId);
25313
+ const configModel = modelConfigOption?.currentValue;
25095
25314
  const currentModelId = strategy === "config-option" ? typeof configModel === "string" ? normalizeModelId(configModel) : void 0 : typeof sessionModels?.["currentModelId"] === "string" ? normalizeModelId(sessionModels["currentModelId"]) : void 0;
25096
25315
  return {
25097
25316
  strategy,
25098
25317
  sessionModelsAdvertised: sessionModels !== void 0,
25099
25318
  modelConfigId,
25100
- currentModelId
25319
+ currentModelId,
25320
+ advertisedModelValues: strategy === "config-option" ? modelConfigOption === void 0 ? [] : collectSessionConfigOptionValues(modelConfigOption) : collectSessionModelIds(sessionModels)
25101
25321
  };
25102
25322
  }
25103
25323
  function createAcpExecutor(options) {
@@ -25116,8 +25336,10 @@ function createAcpExecutor(options) {
25116
25336
  strategy: "session/set_model",
25117
25337
  sessionModelsAdvertised: false,
25118
25338
  modelConfigId: void 0,
25119
- currentModelId: void 0
25339
+ currentModelId: void 0,
25340
+ advertisedModelValues: []
25120
25341
  };
25342
+ let lastUnmatchedModelId;
25121
25343
  let initializeResult;
25122
25344
  let startResult;
25123
25345
  let startInFlight;
@@ -25252,6 +25474,23 @@ function createAcpExecutor(options) {
25252
25474
  continue;
25253
25475
  }
25254
25476
  if (event._tag === "ContentDelta") {
25477
+ if (event.streamKind === "reasoning_text") {
25478
+ if (event.text.trim().length === 0) {
25479
+ continue;
25480
+ }
25481
+ emit(
25482
+ makeAcpContentDeltaEvent({
25483
+ stamp: stamp(),
25484
+ provider: options.provider,
25485
+ threadId: options.threadId,
25486
+ turnId: activeTurnId,
25487
+ text: event.text,
25488
+ streamKind: "reasoning_text",
25489
+ rawPayload: event.rawPayload
25490
+ })
25491
+ );
25492
+ continue;
25493
+ }
25255
25494
  if (event.text.trim().length === 0 && !assistantSegment.activeItemId) {
25256
25495
  continue;
25257
25496
  }
@@ -25264,6 +25503,7 @@ function createAcpExecutor(options) {
25264
25503
  turnId: activeTurnId,
25265
25504
  ...itemId ? { itemId } : {},
25266
25505
  text: event.text,
25506
+ streamKind: "assistant_text",
25267
25507
  rawPayload: event.rawPayload
25268
25508
  })
25269
25509
  );
@@ -25424,6 +25664,12 @@ function createAcpExecutor(options) {
25424
25664
  const userInputOutcome = await handlePermissionAsUserInput(params);
25425
25665
  if (userInputOutcome !== void 0) return userInputOutcome;
25426
25666
  const permissionRequest = parsePermissionRequest(params);
25667
+ if (options.autoApprove?.(permissionRequest) === true) {
25668
+ const optionId = selectAutoApprovedAcpPermissionOption(params);
25669
+ if (optionId !== void 0) {
25670
+ return { outcome: { outcome: "selected", optionId } };
25671
+ }
25672
+ }
25427
25673
  const requestId = randomId();
25428
25674
  const outcome = await new Promise((resolve2) => {
25429
25675
  const pending = {
@@ -25511,36 +25757,70 @@ function createAcpExecutor(options) {
25511
25757
  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) {
25512
25758
  return input2.state;
25513
25759
  }
25760
+ const value = matchAdvertisedModelValue(requestedModelId, input2.state.advertisedModelValues);
25761
+ if (value === void 0) {
25762
+ if (requestedModelId !== lastUnmatchedModelId) {
25763
+ lastUnmatchedModelId = requestedModelId;
25764
+ options.log?.info("requested model is not in the agent catalog; keeping its own model", {
25765
+ requestedModelId,
25766
+ currentModelId: input2.state.currentModelId,
25767
+ advertisedModelValues: input2.state.advertisedModelValues
25768
+ });
25769
+ }
25770
+ return input2.state;
25771
+ }
25772
+ if (value === input2.state.currentModelId) {
25773
+ return input2.state;
25774
+ }
25514
25775
  if (input2.state.strategy === "config-option") {
25515
25776
  await connection.request("session/set_config_option", {
25516
25777
  sessionId: input2.acpSessionId,
25517
25778
  configId: input2.state.modelConfigId,
25518
- value: requestedModelId
25779
+ value
25519
25780
  });
25520
25781
  } else {
25521
25782
  await connection.request("session/set_model", {
25522
25783
  sessionId: input2.acpSessionId,
25523
- modelId: requestedModelId
25784
+ modelId: value
25524
25785
  });
25525
25786
  }
25526
25787
  if (!isLive(input2.expectedGeneration)) {
25527
25788
  throw new AcpExecutorDisposedError();
25528
25789
  }
25529
- return { ...input2.state, currentModelId: requestedModelId };
25790
+ return { ...input2.state, currentModelId: value };
25791
+ };
25792
+ const applyRequestedEffort = async (input2) => {
25793
+ const requested = input2.requestedEffort?.trim();
25794
+ if (!requested) return;
25795
+ const configOptions = typeof input2.setup === "object" && input2.setup !== null ? input2.setup.configOptions : void 0;
25796
+ const effort = findAcpEffortConfig(configOptions);
25797
+ if (effort === void 0) return;
25798
+ const matched = effort.choices.find((choice) => choice.value === requested);
25799
+ if (matched === void 0) {
25800
+ options.log?.info("requested effort is not in the agent catalog; keeping its own", {
25801
+ requestedEffort: requested,
25802
+ advertised: effort.choices.map((choice) => choice.value)
25803
+ });
25804
+ return;
25805
+ }
25806
+ if (matched.isDefault === true) return;
25807
+ if (!isLive(input2.expectedGeneration)) return;
25808
+ try {
25809
+ await connection.request("session/set_config_option", {
25810
+ sessionId: input2.acpSessionId,
25811
+ configId: effort.configId,
25812
+ value: requested
25813
+ });
25814
+ } catch (error62) {
25815
+ options.log?.info("setting the effort level failed; the session keeps its own", {
25816
+ requestedEffort: requested,
25817
+ configId: effort.configId,
25818
+ error: error62 instanceof Error ? error62.message : String(error62)
25819
+ });
25820
+ }
25530
25821
  };
25531
25822
  const startOnce = async (expectedGeneration) => {
25532
- const initializePayload = {
25533
- protocolVersion: PROTOCOL_VERSION,
25534
- clientCapabilities: {
25535
- fs: {
25536
- readTextFile: false,
25537
- writeTextFile: false,
25538
- ...options.clientCapabilities?.fs
25539
- },
25540
- terminal: options.clientCapabilities?.terminal ?? false
25541
- },
25542
- clientInfo: options.clientInfo
25543
- };
25823
+ const initializePayload = acpInitializePayload(options.clientInfo, options.clientCapabilities);
25544
25824
  const initialized = await connection.request(
25545
25825
  "initialize",
25546
25826
  initializePayload
@@ -25584,12 +25864,27 @@ function createAcpExecutor(options) {
25584
25864
  nextSessionId = created.sessionId;
25585
25865
  setup = created;
25586
25866
  }
25867
+ const unhandledCategories = unhandledConfigCategories(
25868
+ setup.configOptions
25869
+ );
25870
+ if (unhandledCategories.length > 0) {
25871
+ options.log?.info("acp session reported config option categories we do not handle", {
25872
+ sessionId: nextSessionId,
25873
+ categories: unhandledCategories
25874
+ });
25875
+ }
25587
25876
  const nextModelSelectionState = await applyRequestedModel({
25588
25877
  state: modelSelectionStateFromSetup(setup),
25589
25878
  acpSessionId: nextSessionId,
25590
25879
  requestedModelId: options.requestedModelId,
25591
25880
  expectedGeneration
25592
25881
  });
25882
+ await applyRequestedEffort({
25883
+ setup,
25884
+ acpSessionId: nextSessionId,
25885
+ requestedEffort: options.requestedEffort,
25886
+ expectedGeneration
25887
+ });
25593
25888
  if (options.beforeApplyResult) {
25594
25889
  await options.beforeApplyResult();
25595
25890
  }
@@ -25954,6 +26249,26 @@ function createAcpExecutor(options) {
25954
26249
  };
25955
26250
  apply();
25956
26251
  };
26252
+ const setNativeMode = async (modeId) => {
26253
+ failIfDisposed();
26254
+ const started = startResult;
26255
+ if (started === void 0) {
26256
+ throw new AcpExecutorRequestError("session/set_mode", "session has not started yet");
26257
+ }
26258
+ const configId = acpModeConfigId(started.sessionSetupResult);
26259
+ if (configId !== void 0) {
26260
+ await connection.request("session/set_config_option", {
26261
+ sessionId: started.sessionId,
26262
+ configId,
26263
+ value: modeId
26264
+ });
26265
+ return;
26266
+ }
26267
+ await connection.request("session/set_mode", {
26268
+ sessionId: started.sessionId,
26269
+ modeId
26270
+ });
26271
+ };
25957
26272
  const respondToUserInput = async (requestId, answers) => {
25958
26273
  failIfDisposed();
25959
26274
  const pending = pendingUserInputs.get(requestId);
@@ -25970,7 +26285,7 @@ function createAcpExecutor(options) {
25970
26285
  pendingUserInputs.delete(requestId);
25971
26286
  pending.resolve({
25972
26287
  kind: "answered",
25973
- answers: isRecord3(answers) ? answers : {}
26288
+ answers: isRecord5(answers) ? answers : {}
25974
26289
  });
25975
26290
  };
25976
26291
  const dispose = async () => {
@@ -26020,13 +26335,56 @@ function createAcpExecutor(options) {
26020
26335
  cancel,
26021
26336
  respondToRequest,
26022
26337
  respondToUserInput,
26338
+ setNativeMode,
26023
26339
  dispose
26024
26340
  };
26025
26341
  }
26026
26342
 
26343
+ // ../../packages/ycodium-acp-executor/src/probe.ts
26344
+ function withProbeTimeout(promise2, timeoutMs, method) {
26345
+ return new Promise((resolve2, reject) => {
26346
+ const timer = setTimeout(() => {
26347
+ reject(new Error(`ACP probe request '${method}' timed out after ${timeoutMs}ms`));
26348
+ }, timeoutMs);
26349
+ timer.unref?.();
26350
+ promise2.then(
26351
+ (value) => {
26352
+ clearTimeout(timer);
26353
+ resolve2(value);
26354
+ },
26355
+ (error62) => {
26356
+ clearTimeout(timer);
26357
+ reject(error62 instanceof Error ? error62 : new Error(String(error62)));
26358
+ }
26359
+ );
26360
+ });
26361
+ }
26362
+ async function openAcpProbeHandshake(input2) {
26363
+ const connection = createAcpConnection({
26364
+ readable: input2.readable,
26365
+ writable: input2.writable,
26366
+ onNotification: () => void 0
26367
+ });
26368
+ try {
26369
+ const initializeResult = await withProbeTimeout(
26370
+ connection.request("initialize", acpInitializePayload(input2.clientInfo)),
26371
+ input2.requestTimeoutMs,
26372
+ "initialize"
26373
+ );
26374
+ return {
26375
+ initializeResult,
26376
+ request: (method, params) => withProbeTimeout(connection.request(method, params), input2.requestTimeoutMs, method),
26377
+ close: () => connection.close()
26378
+ };
26379
+ } catch (error62) {
26380
+ await connection.close().catch(() => void 0);
26381
+ throw error62;
26382
+ }
26383
+ }
26384
+
26027
26385
  // src/config.ts
26028
26386
  var DSH_DEFAULT_LAUNCH_ARGS = "--profile acp";
26029
- function isRecord4(value) {
26387
+ function isRecord6(value) {
26030
26388
  return typeof value === "object" && value !== null && !Array.isArray(value);
26031
26389
  }
26032
26390
  function readString(value) {
@@ -26039,7 +26397,7 @@ function readEnvironment(value) {
26039
26397
  if (!Array.isArray(value)) return {};
26040
26398
  const env = {};
26041
26399
  for (const entry of value) {
26042
- if (!isRecord4(entry)) continue;
26400
+ if (!isRecord6(entry)) continue;
26043
26401
  const name = readTrimmed(entry["name"]);
26044
26402
  if (name.length === 0) continue;
26045
26403
  env[name] = readString(entry["value"]);
@@ -26047,7 +26405,7 @@ function readEnvironment(value) {
26047
26405
  return env;
26048
26406
  }
26049
26407
  function parseDshInstanceConfig(config2) {
26050
- if (!isRecord4(config2)) {
26408
+ if (!isRecord6(config2)) {
26051
26409
  return {
26052
26410
  binaryPath: "",
26053
26411
  launchArgs: DSH_DEFAULT_LAUNCH_ARGS,
@@ -26057,7 +26415,7 @@ function parseDshInstanceConfig(config2) {
26057
26415
  envelopeDriver: void 0
26058
26416
  };
26059
26417
  }
26060
- const nested = isRecord4(config2["config"]) ? config2["config"] : void 0;
26418
+ const nested = isRecord6(config2["config"]) ? config2["config"] : void 0;
26061
26419
  const settings = nested ?? config2;
26062
26420
  return {
26063
26421
  binaryPath: readTrimmed(settings["binaryPath"]),
@@ -26120,57 +26478,188 @@ function tokenizeLaunchArgs(value) {
26120
26478
  return tokens;
26121
26479
  }
26122
26480
 
26123
- // src/streams.ts
26124
- import * as NodeStream2 from "node:stream";
26125
- function nodeStreamsFromHandle(handle) {
26126
- const readable = new NodeStream2.PassThrough();
26127
- void (async () => {
26128
- try {
26129
- for await (const chunk of handle.stdout) {
26130
- if (!readable.writable) return;
26131
- if (!readable.write(chunk)) {
26132
- await new Promise((resolve2) => {
26133
- const onDone = () => {
26134
- readable.off("drain", onDone);
26135
- readable.off("close", onDone);
26136
- resolve2();
26137
- };
26138
- readable.once("drain", onDone);
26139
- readable.once("close", onDone);
26140
- });
26141
- }
26142
- }
26143
- } catch (error62) {
26144
- readable.destroy(error62 instanceof Error ? error62 : new Error(String(error62)));
26145
- return;
26146
- }
26147
- if (readable.writable) readable.end();
26148
- })();
26149
- void (async () => {
26150
- for await (const _chunk of handle.stderr) {
26151
- }
26152
- })().catch(() => void 0);
26153
- const writable = new NodeStream2.Writable({
26154
- write(chunk, _encoding, callback) {
26155
- const bytes = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
26156
- handle.writeStdin(bytes).then(
26157
- () => callback(),
26158
- (error62) => callback(error62 instanceof Error ? error62 : new Error(String(error62)))
26159
- );
26160
- },
26161
- final(callback) {
26162
- handle.closeStdin().then(
26163
- () => callback(),
26164
- (error62) => callback(error62 instanceof Error ? error62 : new Error(String(error62)))
26165
- );
26166
- }
26481
+ // src/probeModels.ts
26482
+ import * as NodeFS from "node:fs";
26483
+ import * as NodeOS2 from "node:os";
26484
+ import * as NodePath2 from "node:path";
26485
+ import { nodeStreamsFromHandle } from "@ycodium-ai/plugin-api/process-stdin";
26486
+
26487
+ // src/spawnEnv.ts
26488
+ import * as NodeOS from "node:os";
26489
+ import * as NodePath from "node:path";
26490
+ import { mergeLaunchEnv, brainSlotEnvKeys } from "@ycodium-ai/plugin-api/executor";
26491
+
26492
+ // src/permission.ts
26493
+ var DSH_NATIVE_MODES = [
26494
+ {
26495
+ id: "read-only",
26496
+ name: "Read only",
26497
+ description: "Sandboxed to reading. Asks before anything that writes or runs."
26498
+ },
26499
+ {
26500
+ id: "workspace-write",
26501
+ name: "Workspace write",
26502
+ description: "Writes inside the workspace. Asks before stepping outside it."
26503
+ },
26504
+ {
26505
+ id: "danger-full-access",
26506
+ name: "Full access",
26507
+ description: "No sandbox, never asks. Every command runs immediately."
26508
+ }
26509
+ ];
26510
+ var DSH_DEFAULT_NATIVE_MODE_ID = "workspace-write";
26511
+ function isDshNativeModeId(value) {
26512
+ return DSH_NATIVE_MODES.some((mode) => mode.id === value);
26513
+ }
26514
+ function dshNativeModeReport(instanceEnvironment) {
26515
+ const configured = instanceEnvironment["DSH_PERMISSION_MODE"]?.trim();
26516
+ return {
26517
+ nativeModes: DSH_NATIVE_MODES.map((mode) => ({
26518
+ id: mode.id,
26519
+ name: mode.name,
26520
+ description: mode.description
26521
+ })),
26522
+ currentNativeModeId: configured !== void 0 && isDshNativeModeId(configured) ? configured : DSH_DEFAULT_NATIVE_MODE_ID
26523
+ };
26524
+ }
26525
+ function dshPermissionEnv(nativeModeId) {
26526
+ if (nativeModeId === void 0 || !isDshNativeModeId(nativeModeId)) return {};
26527
+ return { DSH_PERMISSION_MODE: nativeModeId };
26528
+ }
26529
+
26530
+ // src/spawnEnv.ts
26531
+ function expandHomePath(value) {
26532
+ if (value === "~") return NodeOS.homedir();
26533
+ if (value.startsWith("~/") || value.startsWith("~\\")) {
26534
+ return NodePath.join(NodeOS.homedir(), value.slice(2));
26535
+ }
26536
+ return value;
26537
+ }
26538
+ function resolveHomePath(value) {
26539
+ const trimmed = value.trim();
26540
+ if (trimmed.length === 0) return "";
26541
+ return NodePath.resolve(expandHomePath(trimmed));
26542
+ }
26543
+ function buildSpawnEnv(recipe, resolvedHomePath, launchEnv = {}, nativeModeId) {
26544
+ const homeEnvVar = recipe.homeEnvVar.trim() || DSH_CARD.homeEnvVar;
26545
+ const homeInjection = resolvedHomePath.length > 0 && homeEnvVar.length > 0 ? { [homeEnvVar]: resolvedHomePath } : {};
26546
+ return mergeLaunchEnv({
26547
+ brainSlots: brainSlotEnvKeys(DSH_CARD),
26548
+ accountHome: homeInjection,
26549
+ instanceTable: recipe.environment,
26550
+ // 用户挑的档压过实例表:实例表里那一栏是这台机器的默认,输入区那一下是这一轮的
26551
+ // 意思。铺在 `handOverrides` 这一层正是为了这个先后(见 `mergeLaunchEnv` 的层序)。
26552
+ handOverrides: dshPermissionEnv(nativeModeId),
26553
+ launch: launchEnv
26167
26554
  });
26168
- return { readable, writable };
26555
+ }
26556
+
26557
+ // src/probeModels.ts
26558
+ var PROBE_REQUEST_TIMEOUT_MS = 15e3;
26559
+ function dshProbeSessionCwd(instanceId) {
26560
+ const safe = instanceId.trim().replace(/[^A-Za-z0-9._-]/g, "-") || "instance";
26561
+ return NodePath2.join(NodeOS2.tmpdir(), `ycodium-dsh-probe-${safe}`);
26562
+ }
26563
+ function isRecord7(value) {
26564
+ return typeof value === "object" && value !== null && !Array.isArray(value);
26565
+ }
26566
+ function asString3(value) {
26567
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
26568
+ }
26569
+ function dshProbeModels(setup) {
26570
+ if (!isRecord7(setup)) return void 0;
26571
+ const rawOptions = setup["configOptions"];
26572
+ const choices = acpModelChoicesFromConfigOptions(rawOptions);
26573
+ if (choices === void 0) return void 0;
26574
+ const models = choices.map((choice) => ({
26575
+ slug: choice.value,
26576
+ ...choice.name === void 0 ? {} : { name: choice.name },
26577
+ ...choice.isDefault === true ? { isDefault: true } : {}
26578
+ }));
26579
+ const effort = findAcpEffortConfig(rawOptions);
26580
+ if (effort === void 0) return models;
26581
+ const capabilities = {
26582
+ optionDescriptors: [
26583
+ {
26584
+ id: DSH_EFFORT_OPTION_ID,
26585
+ label: effort.label || "Reasoning",
26586
+ type: "select",
26587
+ options: effort.choices.map((choice) => ({
26588
+ id: choice.value,
26589
+ label: choice.label,
26590
+ ...choice.description === void 0 ? {} : { description: choice.description },
26591
+ ...choice.isDefault === true ? { isDefault: true } : {}
26592
+ }))
26593
+ }
26594
+ ]
26595
+ };
26596
+ return models.map((model) => ({ ...model, capabilities }));
26597
+ }
26598
+ var DSH_EFFORT_OPTION_ID = "reasoning";
26599
+ function findProbeSessionId(listed, probeCwd) {
26600
+ if (!isRecord7(listed) || !Array.isArray(listed["sessions"])) return void 0;
26601
+ for (const entry of listed["sessions"]) {
26602
+ if (!isRecord7(entry)) continue;
26603
+ if (asString3(entry["cwd"]) !== probeCwd) continue;
26604
+ const sessionId = asString3(entry["sessionId"]);
26605
+ if (sessionId !== void 0) return sessionId;
26606
+ }
26607
+ return void 0;
26608
+ }
26609
+ async function probeSessionSetup(handshake, probeCwd) {
26610
+ const listed = await handshake.request("session/list", {}).catch(() => void 0);
26611
+ const reusable = findProbeSessionId(listed, probeCwd);
26612
+ if (reusable !== void 0) {
26613
+ const resumed = await handshake.request("session/resume", { sessionId: reusable, cwd: probeCwd, mcpServers: [] }).catch(() => void 0);
26614
+ if (resumed !== void 0) return resumed;
26615
+ }
26616
+ return handshake.request("session/new", { cwd: probeCwd, mcpServers: [] });
26617
+ }
26618
+ var failureDetail = (error62) => (error62 instanceof Error ? error62.message : String(error62)).trim() || void 0;
26619
+ async function probeDshModels(input2) {
26620
+ const { recipe, host } = input2;
26621
+ const probeCwd = dshProbeSessionCwd(host.instanceId);
26622
+ try {
26623
+ NodeFS.mkdirSync(probeCwd, { recursive: true });
26624
+ } catch (error62) {
26625
+ return { kind: "failed", detail: failureDetail(error62) };
26626
+ }
26627
+ const env = buildSpawnEnv(recipe, resolveHomePath(recipe.homePath));
26628
+ let handle;
26629
+ try {
26630
+ handle = await host.process.spawn({
26631
+ program: recipe.binaryPath,
26632
+ args: tokenizeLaunchArgs(recipe.launchArgs),
26633
+ cwd: probeCwd,
26634
+ owner: { bindingId: host.bindingId },
26635
+ ...Object.keys(env).length > 0 ? { env } : {}
26636
+ });
26637
+ } catch (error62) {
26638
+ return { kind: "not-started", detail: failureDetail(error62) };
26639
+ }
26640
+ let handshake;
26641
+ try {
26642
+ const { readable, writable } = nodeStreamsFromHandle(handle);
26643
+ handshake = await openAcpProbeHandshake({
26644
+ readable,
26645
+ writable,
26646
+ clientInfo: DSH_CLIENT_INFO,
26647
+ requestTimeoutMs: PROBE_REQUEST_TIMEOUT_MS
26648
+ });
26649
+ return {
26650
+ kind: "answered",
26651
+ models: dshProbeModels(await probeSessionSetup(handshake, probeCwd))
26652
+ };
26653
+ } catch (error62) {
26654
+ return { kind: "failed", detail: failureDetail(error62) };
26655
+ } finally {
26656
+ await handshake?.close().catch(() => void 0);
26657
+ await handle.kill("SIGTERM").catch(() => void 0);
26658
+ }
26169
26659
  }
26170
26660
 
26171
26661
  // src/dshInstance.ts
26172
26662
  var RESUME_SCHEMA_VERSION = 1;
26173
- var CLIENT_INFO = { name: "ycodium-agent-dsh", version: "0.1.0" };
26174
26663
  var yieldTick = () => new Promise((resolve2) => {
26175
26664
  setImmediate(resolve2);
26176
26665
  });
@@ -26206,25 +26695,6 @@ var EventBus = class {
26206
26695
  };
26207
26696
  }
26208
26697
  };
26209
- function expandHomePath(value) {
26210
- if (value === "~") return NodeOS.homedir();
26211
- if (value.startsWith("~/") || value.startsWith("~\\")) {
26212
- return NodePath.join(NodeOS.homedir(), value.slice(2));
26213
- }
26214
- return value;
26215
- }
26216
- function resolveHomePath(value) {
26217
- const trimmed = value.trim();
26218
- if (trimmed.length === 0) return "";
26219
- return NodePath.resolve(expandHomePath(trimmed));
26220
- }
26221
- function compactEnv(env) {
26222
- const out = {};
26223
- for (const [key, value] of Object.entries(env)) {
26224
- if (value !== void 0) out[key] = value;
26225
- }
26226
- return out;
26227
- }
26228
26698
  function modelIdFromSelection(selection) {
26229
26699
  if (typeof selection !== "object" || selection === null || Array.isArray(selection)) {
26230
26700
  return void 0;
@@ -26232,15 +26702,6 @@ function modelIdFromSelection(selection) {
26232
26702
  const model = selection["model"];
26233
26703
  return typeof model === "string" ? model.trim() || void 0 : void 0;
26234
26704
  }
26235
- function buildSpawnEnv(recipe, launch, resolvedHomePath) {
26236
- const homeEnvVar = recipe.homeEnvVar.trim() || DSH_CARD.homeEnvVar;
26237
- const homeInjection = resolvedHomePath.length > 0 && homeEnvVar.length > 0 ? { [homeEnvVar]: resolvedHomePath } : {};
26238
- return {
26239
- ...compactEnv(launch.processEnv),
26240
- ...homeInjection,
26241
- ...recipe.environment
26242
- };
26243
- }
26244
26705
  function nowIso() {
26245
26706
  return (/* @__PURE__ */ new Date()).toISOString();
26246
26707
  }
@@ -26270,11 +26731,25 @@ var createDshInstance = (config2, host) => {
26270
26731
  detail: "DeepSeek Harness program path is not configured"
26271
26732
  };
26272
26733
  }
26734
+ const outcome = await probeDshModels({ recipe, host });
26735
+ if (outcome.kind !== "answered") {
26736
+ return {
26737
+ installed: outcome.kind === "failed",
26738
+ version: null,
26739
+ readiness: "error",
26740
+ auth: { status: "unknown" },
26741
+ ...outcome.detail === void 0 ? {} : { detail: outcome.detail },
26742
+ // 装着但握不上手时界面照样画权限格,不报档就退回宿主四档。
26743
+ ...outcome.kind === "failed" ? dshNativeModeReport(recipe.environment) : {}
26744
+ };
26745
+ }
26273
26746
  return {
26274
26747
  installed: true,
26275
26748
  version: null,
26276
- readiness: "unchecked",
26277
- auth: { status: "unknown" }
26749
+ readiness: "ready",
26750
+ auth: { status: "unknown" },
26751
+ ...outcome.models === void 0 ? {} : { models: outcome.models },
26752
+ ...dshNativeModeReport(recipe.environment)
26278
26753
  };
26279
26754
  },
26280
26755
  startSession: async (input2, signal) => {
@@ -26291,7 +26766,12 @@ var createDshInstance = (config2, host) => {
26291
26766
  threads.delete(input2.threadId);
26292
26767
  }
26293
26768
  const resolvedHomePath = resolveHomePath(input2.launch.homePath?.trim() || recipe.homePath);
26294
- const env = buildSpawnEnv(recipe, input2.launch, resolvedHomePath);
26769
+ const env = buildSpawnEnv(
26770
+ recipe,
26771
+ resolvedHomePath,
26772
+ input2.launch.processEnv,
26773
+ input2.nativeModeId
26774
+ );
26295
26775
  const handle = await host.process.spawn({
26296
26776
  program: recipe.binaryPath,
26297
26777
  args: tokenizeLaunchArgs(recipe.launchArgs),
@@ -26299,7 +26779,7 @@ var createDshInstance = (config2, host) => {
26299
26779
  owner: { bindingId: host.bindingId },
26300
26780
  ...Object.keys(env).length > 0 ? { env } : {}
26301
26781
  });
26302
- const { readable, writable } = nodeStreamsFromHandle(handle);
26782
+ const { readable, writable } = nodeStreamsFromHandle2(handle);
26303
26783
  const resume = input2.discardResumeCursor === true ? void 0 : parseAcpResumeCursor(input2.resumeCursor, RESUME_SCHEMA_VERSION);
26304
26784
  const requestedModelId = input2.launch.requestedModelId?.trim() || modelIdFromSelection(input2.modelSelection);
26305
26785
  const executor = createAcpExecutor({
@@ -26309,7 +26789,8 @@ var createDshInstance = (config2, host) => {
26309
26789
  threadId: input2.threadId,
26310
26790
  provider: DSH_DRIVER_KIND,
26311
26791
  providerInstanceId: host.instanceId,
26312
- clientInfo: CLIENT_INFO,
26792
+ clientInfo: DSH_CLIENT_INFO,
26793
+ log: host.log,
26313
26794
  // 该 agent 不实现 session/load,只实现 session/resume——续接重新激活
26314
26795
  // 持久会话而不重放记录,发 load 会卡在永远不结算的重放门上。
26315
26796
  resumeMethod: "session/resume",
@@ -26335,6 +26816,7 @@ var createDshInstance = (config2, host) => {
26335
26816
  providerInstanceId: host.instanceId,
26336
26817
  status: "idle",
26337
26818
  runtimeMode: input2.runtimeMode,
26819
+ ...input2.nativeModeId !== void 0 && isDshNativeModeId(input2.nativeModeId) ? { nativeModeId: input2.nativeModeId } : {},
26338
26820
  cwd,
26339
26821
  threadId: input2.threadId,
26340
26822
  createdAt,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ycodium-ai/agent-dsh",
3
- "version": "0.2.2092",
3
+ "version": "0.2.2126",
4
4
  "description": "DeepSeek Harness executor: dsh CLI over Agent Client Protocol stdio.",
5
5
  "keywords": [
6
6
  "ycodium",
@@ -21,7 +21,7 @@
21
21
  "esbuild": "0.28.1",
22
22
  "vite-plus": "0.2.2",
23
23
  "ycodium-acp-executor": "0.0.0",
24
- "@ycodium-ai/plugin-api": "0.2.2092",
24
+ "@ycodium-ai/plugin-api": "0.2.2126",
25
25
  "ycodium-acp-protocol": "0.0.0"
26
26
  },
27
27
  "ycodium": {