@ycodium-ai/agent-grok 0.2.2106 → 0.2.2130

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 +658 -187
  2. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -9,15 +9,18 @@ import { definePlugin } from "@ycodium-ai/plugin-api";
9
9
 
10
10
  // src/card.ts
11
11
  var GROK_DRIVER_KIND = "grok";
12
+ var GROK_CLIENT_INFO = { name: "ycodium-agent-grok", version: "0.1.0" };
12
13
  var GROK_CARD = {
13
14
  driverKind: GROK_DRIVER_KIND,
14
15
  displayName: "Grok",
15
16
  iconUrl: "./assets/icon.svg",
16
17
  commandName: "grok",
17
18
  // 启动命令是 `grok agent stdio`(GrokSettings 磁盘形状没有 launchArgs 字段,
18
- // 宿主旧实现把它写死在 buildGrokAcpSpawnInput,这里落在名片上)。思考档位
19
- // spawn 参数 `--reasoning-effort`,由实例按用户选择动态插进 agent 与 stdio
20
- // 之间,不走名片这一栏。
19
+ // 宿主旧实现把它写死在 buildGrokAcpSpawnInput,这里落在名片上)。两个动态旗标
20
+ // 都不走名片这一栏,而且**位置不同**:思考档位 `--reasoning-effort` agent 级、
21
+ // 插在 agent 与 stdio 之间;运行模式 `--permission-mode` 是顶层、排在 `agent`
22
+ // 前面(放进 agent 那一段 grok 当场报 unexpected argument)。两者都在
23
+ // `grokInstance.startSession` 里按用户选择拼。
21
24
  launchArgs: "agent stdio",
22
25
  homeEnvVar: "GROK_HOME",
23
26
  // 只有这一条是实话:实测这个 CLI 从不播报 api key 认证方式,点名 XAI_API_KEY
@@ -33,7 +36,7 @@ var GROK_CARD = {
33
36
  signIn: [{ kind: "command", command: "grok login" }],
34
37
  deviceCodeSignInArgs: ["login", "--device-auth"],
35
38
  // npm 上的 `@xai-official/grok` 只发 darwin-arm64,写出去等于给 Linux/Windows
36
- // 用户一条装不上的路(宿主 GrokDriver 同一条注释)。
39
+ // 用户一条装不上的路。
37
40
  npmPackage: "",
38
41
  homebrewFormula: "",
39
42
  selfUpdateArgs: ["update"],
@@ -57,7 +60,16 @@ var GROK_CARD = {
57
60
  // Grok 换模型只有 `session/set_config_option` 一条路;库会按握手形状自动协商,
58
61
  // 但插件侧的执行路径还没接到模型管理 UI,名片必须说实话。
59
62
  sessionModelSwitch: "unsupported",
60
- runtimeModeSwitch: "in-session"
63
+ // 运行模式只在 spawn 时定得了(`--permission-mode` 是启动旗标,ACP 那一侧
64
+ // `modes: null`,`session/set_mode` 对它无效),所以不许声称能会话中途换。
65
+ // 报 next-turn 之后宿主 `ProviderCommandReactor` 不再下发,而是把新档记在
66
+ // thread 上;下一轮 `ensureSessionForThread` 判到 runtimeModeChanged,带着
67
+ // resume cursor 真重开会话——历史续上,档位换掉。
68
+ runtimeModeSwitch: "next-turn",
69
+ // 档位走 spawn 旗标,切档要重开会话(与 runtimeModeSwitch 同为 next-turn 的原因)。
70
+ // 声明这一位让界面画这双手自报的三档;不声明就退回宿主四档,而四档在这只手上
71
+ // 有两档撞车(都落到 `auto`)。
72
+ nativeModeSwitch: true
61
73
  },
62
74
  // 凭据由 CLI 自己保管在 `<GROK_HOME>/auth.json`,宿主读它只认 `/token`。
63
75
  credentialSources: [
@@ -84,11 +96,17 @@ var GROK_CARD = {
84
96
  modelConnectivity: {
85
97
  dialects: ["anthropic-messages", "openai-responses", "openai-chat-completions"],
86
98
  acceptsExternalEndpoints: true,
87
- // 必须设 `GROK_MODELS_BASE_URL`,不能只设 `XAI_API_KEY`。它的凭据顺序是
99
+ // 地址那一栏必须投:不设 `GROK_MODELS_BASE_URL` 的话整条请求根本不来我们这儿
100
+ // (`grokModelProxyEgress.test.ts` 拿掉这一栏就红)。
101
+ //
102
+ // **但设了 base 并不会把客户端切成 API-key 认证。** 它的凭据顺序是
88
103
  // `api_key` → `env_key` → **已登录的 session token** → `XAI_API_KEY`
89
- // (`~/.grok/docs/user-guide/11-custom-models.md:105-114`)——用户 `grok login` 过的话
90
- // 只设 key 不起作用,它会把**用户的真 OIDC token** 发到我们的代理。设了 base 才会
91
- // 把整个客户端切成 API-key 认证。
104
+ // (`~/.grok/docs/user-guide/11-custom-models.md` "Credential Resolution")。
105
+ // 1.0.34 实测:账号目录里有 `auth.json` 时,启动预取 `/v1/models` 用的是
106
+ // `XAI_API_KEY`,而**推理请求带的是用户自己的 x.ai OIDC token**——本地端点只认
107
+ // 这笔绑定的凭据,别的一律 401。所以绑定时还要把 `GROK_AUTH_PATH` 指到一个不存在
108
+ // 的路径(见下面那栏),让这次进程读不到那份登录。同批的实测落在
109
+ // `grokModelProxyEgress.test.ts`。
92
110
  //
93
111
  // 它启动时先打 `{base_url}/models` 拉模型列表(README:1953「on startup」),不给就
94
112
  // 当端点不可用——代理为此支持了 /models。
@@ -96,13 +114,26 @@ var GROK_CARD = {
96
114
  // base 含 /v1(它的例子是 `https://api.acme.com/v1`),后面自己加 /chat/completions。
97
115
  nativePathSuffix: "/chat/completions",
98
116
  baseUrlEnvKey: "GROK_MODELS_BASE_URL",
99
- credentialEnvKey: "XAI_API_KEY"
117
+ credentialEnvKey: "XAI_API_KEY",
118
+ // 绑定这一轮不准用它自己的登录:凭据顺序里 session token 排在 `XAI_API_KEY`
119
+ // 前面,账号目录里有 `auth.json` 就一定发那一份,本机端点一律 401。
120
+ // `GROK_AUTH_PATH` 是 CLI 读登录文件的路径(1.0.34 的 argv 里与 `GROK_AUTH`、
121
+ // `GROK_DEPLOYMENT_KEY` 并列在同一张敏感变量表上)。`$absentPath` 是宿主按这次
122
+ // 绑定算的、必定不存在的路径——不用空设备是因为那是平台语义(POSIX `/dev/null`
123
+ // 对 Windows `NUL`),而「读不到」两边只有一种行为。1.0.34 实测:`authMethods`
124
+ // 少掉 `cached_token`、两笔请求(推理与标题)都改带绑定凭据,账号目录里那份
125
+ // `auth.json` 一个字节没动,也没在别处新建文件。
126
+ // 只在绑定时注入,非绑定会话照旧用用户自己的登录。
127
+ extraEnv: [{ name: "GROK_AUTH_PATH", value: "$absentPath" }]
100
128
  }
101
129
  };
102
130
 
103
131
  // src/grokInstance.ts
132
+ import * as NodeCrypto2 from "node:crypto";
104
133
  import * as NodeOS from "node:os";
105
134
  import * as NodePath2 from "node:path";
135
+ import { mergeLaunchEnv, brainSlotEnvKeys } from "@ycodium-ai/plugin-api/executor";
136
+ import { nodeStreamsFromHandle as nodeStreamsFromHandle2 } from "@ycodium-ai/plugin-api/process-stdin";
106
137
 
107
138
  // ../../packages/ycodium-acp-executor/src/executor.ts
108
139
  import * as NodeCrypto from "node:crypto";
@@ -23972,6 +24003,12 @@ function acpApprovalOptions(request) {
23972
24003
  }
23973
24004
  return options;
23974
24005
  }
24006
+ function selectAutoApprovedAcpPermissionOption(request) {
24007
+ if (new Set(request.options.map((option) => option.kind)).size !== request.options.length) {
24008
+ return void 0;
24009
+ }
24010
+ return selectAcpPermissionOptionId(request, "acceptForSession") ?? selectAcpPermissionOptionId(request, "accept");
24011
+ }
23975
24012
  function parseAcpResumeCursor(raw, schemaVersion) {
23976
24013
  if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return void 0;
23977
24014
  const record2 = raw;
@@ -24365,6 +24402,150 @@ function makeAcpContentDeltaEvent(input2) {
24365
24402
  };
24366
24403
  }
24367
24404
 
24405
+ // ../../packages/ycodium-acp-executor/src/config-options.ts
24406
+ function isRecord3(value) {
24407
+ return typeof value === "object" && value !== null && !Array.isArray(value);
24408
+ }
24409
+ function asString(value) {
24410
+ return typeof value === "string" ? value : void 0;
24411
+ }
24412
+ function normalizedField(option, key) {
24413
+ return asString(option[key])?.trim().toLowerCase() ?? "";
24414
+ }
24415
+ function looksLikeEffortOption(option) {
24416
+ const id = normalizedField(option, "id");
24417
+ const name = normalizedField(option, "name");
24418
+ return id === "effort" || id === "reasoning" || name === "effort" || name === "reasoning" || name.includes("effort") || name.includes("reasoning") || normalizedField(option, "category") === "thought_level";
24419
+ }
24420
+ function flattenChoices(rawOptions, currentValue) {
24421
+ if (!Array.isArray(rawOptions)) return [];
24422
+ const out = [];
24423
+ const seen = /* @__PURE__ */ new Set();
24424
+ const push = (entry) => {
24425
+ if (!isRecord3(entry)) return;
24426
+ const value = asString(entry["value"]);
24427
+ if (value === void 0 || seen.has(value)) return;
24428
+ seen.add(value);
24429
+ const description = asString(entry["description"])?.trim();
24430
+ const name = asString(entry["name"])?.trim();
24431
+ out.push({
24432
+ value,
24433
+ ...name ? { name } : {},
24434
+ label: name || value,
24435
+ ...description ? { description } : {},
24436
+ ...currentValue !== void 0 && currentValue === value ? { isDefault: true } : {}
24437
+ });
24438
+ };
24439
+ for (const entry of rawOptions) {
24440
+ if (isRecord3(entry) && Array.isArray(entry["options"])) {
24441
+ for (const nested of entry["options"]) push(nested);
24442
+ continue;
24443
+ }
24444
+ push(entry);
24445
+ }
24446
+ return out;
24447
+ }
24448
+ function findAcpEffortConfig(configOptions) {
24449
+ if (!Array.isArray(configOptions)) return void 0;
24450
+ const candidates = configOptions.filter(isRecord3).filter((option) => normalizedField(option, "type") === "select").filter(looksLikeEffortOption);
24451
+ if (candidates.length === 0) return void 0;
24452
+ 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];
24453
+ if (picked === void 0) return void 0;
24454
+ const configId = asString(picked["id"])?.trim();
24455
+ if (!configId) return void 0;
24456
+ const choices = flattenChoices(picked["options"], asString(picked["currentValue"])?.trim());
24457
+ if (choices.length === 0) return void 0;
24458
+ return {
24459
+ configId,
24460
+ label: asString(picked["name"])?.trim() ?? "",
24461
+ choices
24462
+ };
24463
+ }
24464
+
24465
+ // ../../packages/ycodium-acp-executor/src/runtime-mode.ts
24466
+ function isRecord4(value) {
24467
+ return typeof value === "object" && value !== null && !Array.isArray(value);
24468
+ }
24469
+ function asString2(value) {
24470
+ return typeof value === "string" ? value : void 0;
24471
+ }
24472
+ function acpModeConfigId(setup) {
24473
+ if (!isRecord4(setup)) return void 0;
24474
+ const configOptions = setup["configOptions"];
24475
+ if (!Array.isArray(configOptions)) return void 0;
24476
+ for (const option of configOptions) {
24477
+ if (!isRecord4(option)) continue;
24478
+ if (asString2(option["category"])?.trim().toLowerCase() !== "mode") continue;
24479
+ const id = asString2(option["id"])?.trim();
24480
+ if (id) return id;
24481
+ }
24482
+ return void 0;
24483
+ }
24484
+
24485
+ // ../../packages/ycodium-acp-executor/src/handshake.ts
24486
+ function acpInitializePayload(clientInfo, overrides) {
24487
+ return {
24488
+ protocolVersion: PROTOCOL_VERSION,
24489
+ clientCapabilities: {
24490
+ fs: {
24491
+ readTextFile: false,
24492
+ writeTextFile: false,
24493
+ ...overrides?.fs
24494
+ },
24495
+ terminal: overrides?.terminal ?? false
24496
+ },
24497
+ clientInfo
24498
+ };
24499
+ }
24500
+
24501
+ // ../../packages/ycodium-acp-executor/src/config-option-wiring.ts
24502
+ var VENDOR_CATEGORY_PREFIX = "_";
24503
+ var ACP_CONFIG_CATEGORY_HANDLING = {
24504
+ mode: {
24505
+ consumed: true,
24506
+ via: "runtime-mode.ts\uFF1AacpAvailableModes / acpCurrentModeId / acpModeConfigId"
24507
+ },
24508
+ model: {
24509
+ consumed: true,
24510
+ via: "runtime-model.ts:extractModelConfigId \u2192 executor.ts:applyRequestedModel"
24511
+ },
24512
+ thought_level: {
24513
+ consumed: true,
24514
+ via: "config-options.ts:findAcpEffortConfig \u2192 executor.ts:applyRequestedEffort"
24515
+ },
24516
+ model_option: {
24517
+ consumed: true,
24518
+ // 精确说法:它不是**识别器**,是**优先级**。`looksLikeEffortOption` 先筛出候选,
24519
+ // 标了 `model_option` 的在候选里排第一(厂商自己标的最准),排在 id 叫 `effort`
24520
+ // 的和标 `thought_level` 的前面。光有这个 category 而 id/name 不像力度档的,
24521
+ // 压根进不了候选。
24522
+ via: "config-options.ts:findAcpEffortConfig\uFF08\u5019\u9009\u91CC\u7684\u6700\u9AD8\u4F18\u5148\u7EA7\uFF0C\u538B\u8FC7 thought_level\uFF09"
24523
+ },
24524
+ model_config: {
24525
+ consumed: false,
24526
+ 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"
24527
+ }
24528
+ };
24529
+ var KNOWN_CATEGORIES = new Set(Object.keys(ACP_CONFIG_CATEGORY_HANDLING));
24530
+ function unhandledConfigCategories(configOptions) {
24531
+ if (!configOptions) return [];
24532
+ const seen = /* @__PURE__ */ new Set();
24533
+ const out = [];
24534
+ for (const option of configOptions) {
24535
+ const category = option.category?.trim();
24536
+ if (!category) continue;
24537
+ if (KNOWN_CATEGORIES.has(category)) continue;
24538
+ if (seen.has(category)) continue;
24539
+ seen.add(category);
24540
+ out.push({
24541
+ category,
24542
+ optionId: option.id.trim(),
24543
+ vendorExtension: category.startsWith(VENDOR_CATEGORY_PREFIX)
24544
+ });
24545
+ }
24546
+ return out;
24547
+ }
24548
+
24368
24549
  // ../../packages/ycodium-acp-executor/src/mutex.ts
24369
24550
  var Mutex = class {
24370
24551
  locked = false;
@@ -24407,7 +24588,7 @@ var Mutex = class {
24407
24588
  };
24408
24589
 
24409
24590
  // ../../packages/ycodium-acp-executor/src/util.ts
24410
- function isRecord3(value) {
24591
+ function isRecord5(value) {
24411
24592
  return typeof value === "object" && value !== null && !Array.isArray(value);
24412
24593
  }
24413
24594
  function asTrimmedString(value) {
@@ -24453,11 +24634,11 @@ function extractCommandFromTitle(title) {
24453
24634
  return backtickMatch?.[1]?.trim() || void 0;
24454
24635
  }
24455
24636
  function extractToolCommand(data, title) {
24456
- const item = isRecord3(data?.["item"]) ? data["item"] : void 0;
24457
- const itemInput = item && isRecord3(item["input"]) ? item["input"] : void 0;
24458
- const itemResult = item && isRecord3(item["result"]) ? item["result"] : void 0;
24459
- const rawInput = isRecord3(data?.["rawInput"]) ? data["rawInput"] : void 0;
24460
- const dataInput = isRecord3(data?.["input"]) ? data["input"] : void 0;
24637
+ const item = isRecord5(data?.["item"]) ? data["item"] : void 0;
24638
+ const itemInput = item && isRecord5(item["input"]) ? item["input"] : void 0;
24639
+ const itemResult = item && isRecord5(item["result"]) ? item["result"] : void 0;
24640
+ const rawInput = isRecord5(data?.["rawInput"]) ? data["rawInput"] : void 0;
24641
+ const dataInput = isRecord5(data?.["input"]) ? data["input"] : void 0;
24461
24642
  const candidates = [
24462
24643
  normalizeCommandValue(item?.["command"]),
24463
24644
  normalizeCommandValue(itemInput?.["command"]),
@@ -24502,7 +24683,7 @@ function collectPaths(value, paths, seen, depth) {
24502
24683
  }
24503
24684
  return;
24504
24685
  }
24505
- if (!isRecord3(value)) {
24686
+ if (!isRecord5(value)) {
24506
24687
  return;
24507
24688
  }
24508
24689
  for (const key of ["path", "filePath", "relativePath", "filename", "newPath", "oldPath"]) {
@@ -24565,7 +24746,7 @@ function deriveToolActivityPresentation(input2) {
24565
24746
  const title = asTrimmedString(input2.title);
24566
24747
  const detail = stripTrailingExitCode(asTrimmedString(input2.detail));
24567
24748
  const fallbackSummary = asTrimmedString(input2.fallbackSummary) ?? "Tool";
24568
- const data = isRecord3(input2.data) ? input2.data : void 0;
24749
+ const data = isRecord5(input2.data) ? input2.data : void 0;
24569
24750
  const command = extractToolCommand(data, title);
24570
24751
  const primaryPath = extractPrimaryPath(data);
24571
24752
  const action = classifyToolAction({
@@ -24597,7 +24778,7 @@ function deriveToolActivityPresentation(input2) {
24597
24778
  };
24598
24779
  }
24599
24780
  if (action === "search") {
24600
- const rawInput = isRecord3(data?.["rawInput"]) ? data["rawInput"] : void 0;
24781
+ const rawInput = isRecord5(data?.["rawInput"]) ? data["rawInput"] : void 0;
24601
24782
  const query = asTrimmedString(rawInput?.["query"]) ?? asTrimmedString(rawInput?.["pattern"]) ?? asTrimmedString(rawInput?.["searchTerm"]);
24602
24783
  return {
24603
24784
  summary: "Searched files",
@@ -24636,6 +24817,46 @@ function findSessionConfigOption(configOptions, configId) {
24636
24817
  }
24637
24818
  return configOptions.find((option) => option.id.trim() === normalizedConfigId);
24638
24819
  }
24820
+ function collectSessionConfigOptionValues(configOption) {
24821
+ if (configOption.type !== "select" || !configOption.options) {
24822
+ return [];
24823
+ }
24824
+ return configOption.options.flatMap(
24825
+ (entry) => "value" in entry ? [entry.value] : entry.options.map((option) => option.value)
24826
+ );
24827
+ }
24828
+ function collectSessionModelIds(models) {
24829
+ if (!isRecord5(models) || !Array.isArray(models["availableModels"])) {
24830
+ return [];
24831
+ }
24832
+ return models["availableModels"].flatMap(
24833
+ (model) => isRecord5(model) && typeof model["modelId"] === "string" ? [model["modelId"]] : []
24834
+ );
24835
+ }
24836
+ function catalogModelLastSegment(value) {
24837
+ if (value.startsWith("[")) {
24838
+ let parsed;
24839
+ try {
24840
+ parsed = JSON.parse(value);
24841
+ } catch {
24842
+ return void 0;
24843
+ }
24844
+ if (!Array.isArray(parsed) || parsed.length < 2) return void 0;
24845
+ const last = parsed[parsed.length - 1];
24846
+ return typeof last === "string" ? last : void 0;
24847
+ }
24848
+ const separator = value.indexOf("/");
24849
+ return separator > 0 && separator < value.length - 1 ? value.slice(separator + 1) : void 0;
24850
+ }
24851
+ function matchAdvertisedModelValue(requestedModelId, advertisedValues) {
24852
+ const wanted = requestedModelId.trim();
24853
+ if (!wanted) return void 0;
24854
+ if (advertisedValues.includes(wanted)) return wanted;
24855
+ const byLastSegment = advertisedValues.filter(
24856
+ (value) => catalogModelLastSegment(value) === wanted
24857
+ );
24858
+ return byLastSegment.length === 1 ? byLastSegment[0] : void 0;
24859
+ }
24639
24860
  function parseSessionModeState(sessionResponse) {
24640
24861
  const modes = sessionResponse.modes;
24641
24862
  if (!modes) return void 0;
@@ -24674,7 +24895,7 @@ function parseAvailableCommandsUpdate(params) {
24674
24895
  }
24675
24896
  const commands = [];
24676
24897
  for (const command of availableCommands) {
24677
- if (!isRecord3(command)) {
24898
+ if (!isRecord5(command)) {
24678
24899
  continue;
24679
24900
  }
24680
24901
  const name = typeof command["name"] === "string" ? command["name"].trim() : "";
@@ -24682,7 +24903,7 @@ function parseAvailableCommandsUpdate(params) {
24682
24903
  continue;
24683
24904
  }
24684
24905
  const description = typeof command["description"] === "string" ? command["description"].trim() || void 0 : void 0;
24685
- const input2 = isRecord3(command["input"]) ? command["input"] : void 0;
24906
+ const input2 = isRecord5(command["input"]) ? command["input"] : void 0;
24686
24907
  const inputHint = typeof input2?.["hint"] === "string" ? input2["hint"].trim() || void 0 : void 0;
24687
24908
  commands.push({
24688
24909
  name,
@@ -24744,7 +24965,7 @@ function extractCommandFromTitle2(title) {
24744
24965
  return match?.[1]?.trim() || void 0;
24745
24966
  }
24746
24967
  function extractToolCallCommand(rawInput, title) {
24747
- if (isRecord3(rawInput)) {
24968
+ if (isRecord5(rawInput)) {
24748
24969
  const directCommand = normalizeCommandValue2(rawInput["command"]);
24749
24970
  if (directCommand) {
24750
24971
  return directCommand;
@@ -24771,7 +24992,7 @@ function boundToolCallOutputText(text) {
24771
24992
  }
24772
24993
  var RAW_OUTPUT_TEXT_FIELDS = ["content", "stdout", "stderr", "output"];
24773
24994
  function boundToolCallRawOutput(rawOutput) {
24774
- if (!isRecord3(rawOutput)) {
24995
+ if (!isRecord5(rawOutput)) {
24775
24996
  return rawOutput;
24776
24997
  }
24777
24998
  let changed = false;
@@ -25001,7 +25222,7 @@ function parseSessionUpdateEvent(params) {
25001
25222
  const entries = Array.isArray(upd["entries"]) ? upd["entries"] : [];
25002
25223
  const plan = entries.map((entry, index) => ({
25003
25224
  step: readAcpText(entry, "content") ?? `Step ${index + 1}`,
25004
- status: normalizePlanStepStatus(isRecord3(entry) ? entry["status"] : void 0)
25225
+ status: normalizePlanStepStatus(isRecord5(entry) ? entry["status"] : void 0)
25005
25226
  }));
25006
25227
  if (plan.length > 0) {
25007
25228
  events.push({
@@ -25040,7 +25261,7 @@ function parseSessionUpdateEvent(params) {
25040
25261
  }
25041
25262
  case "agent_message_chunk":
25042
25263
  case "agent_thought_chunk": {
25043
- const content = isRecord3(upd["content"]) ? upd["content"] : void 0;
25264
+ const content = isRecord5(upd["content"]) ? upd["content"] : void 0;
25044
25265
  const contentType = content?.["type"];
25045
25266
  const streamKind = kind === "agent_thought_chunk" ? "reasoning_text" : "assistant_text";
25046
25267
  if (contentType === "text") {
@@ -25107,16 +25328,18 @@ function normalizeModelId(value) {
25107
25328
  return value?.trim() || void 0;
25108
25329
  }
25109
25330
  function modelSelectionStateFromSetup(setup) {
25110
- const sessionModels = isRecord3(setup.models) ? setup.models : void 0;
25331
+ const sessionModels = isRecord5(setup.models) ? setup.models : void 0;
25111
25332
  const modelConfigId = extractModelConfigId(setup);
25112
25333
  const strategy = sessionModels === void 0 && modelConfigId !== void 0 ? "config-option" : "session/set_model";
25113
- const configModel = modelConfigId === void 0 ? void 0 : findSessionConfigOption(setup.configOptions, modelConfigId)?.currentValue;
25334
+ const modelConfigOption = modelConfigId === void 0 ? void 0 : findSessionConfigOption(setup.configOptions, modelConfigId);
25335
+ const configModel = modelConfigOption?.currentValue;
25114
25336
  const currentModelId = strategy === "config-option" ? typeof configModel === "string" ? normalizeModelId(configModel) : void 0 : typeof sessionModels?.["currentModelId"] === "string" ? normalizeModelId(sessionModels["currentModelId"]) : void 0;
25115
25337
  return {
25116
25338
  strategy,
25117
25339
  sessionModelsAdvertised: sessionModels !== void 0,
25118
25340
  modelConfigId,
25119
- currentModelId
25341
+ currentModelId,
25342
+ advertisedModelValues: strategy === "config-option" ? modelConfigOption === void 0 ? [] : collectSessionConfigOptionValues(modelConfigOption) : collectSessionModelIds(sessionModels)
25120
25343
  };
25121
25344
  }
25122
25345
  function createAcpExecutor(options) {
@@ -25135,8 +25358,10 @@ function createAcpExecutor(options) {
25135
25358
  strategy: "session/set_model",
25136
25359
  sessionModelsAdvertised: false,
25137
25360
  modelConfigId: void 0,
25138
- currentModelId: void 0
25361
+ currentModelId: void 0,
25362
+ advertisedModelValues: []
25139
25363
  };
25364
+ let lastUnmatchedModelId;
25140
25365
  let initializeResult;
25141
25366
  let startResult;
25142
25367
  let startInFlight;
@@ -25461,6 +25686,12 @@ function createAcpExecutor(options) {
25461
25686
  const userInputOutcome = await handlePermissionAsUserInput(params);
25462
25687
  if (userInputOutcome !== void 0) return userInputOutcome;
25463
25688
  const permissionRequest = parsePermissionRequest(params);
25689
+ if (options.autoApprove?.(permissionRequest) === true) {
25690
+ const optionId = selectAutoApprovedAcpPermissionOption(params);
25691
+ if (optionId !== void 0) {
25692
+ return { outcome: { outcome: "selected", optionId } };
25693
+ }
25694
+ }
25464
25695
  const requestId = randomId();
25465
25696
  const outcome = await new Promise((resolve2) => {
25466
25697
  const pending = {
@@ -25548,36 +25779,70 @@ function createAcpExecutor(options) {
25548
25779
  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) {
25549
25780
  return input2.state;
25550
25781
  }
25782
+ const value = matchAdvertisedModelValue(requestedModelId, input2.state.advertisedModelValues);
25783
+ if (value === void 0) {
25784
+ if (requestedModelId !== lastUnmatchedModelId) {
25785
+ lastUnmatchedModelId = requestedModelId;
25786
+ options.log?.info("requested model is not in the agent catalog; keeping its own model", {
25787
+ requestedModelId,
25788
+ currentModelId: input2.state.currentModelId,
25789
+ advertisedModelValues: input2.state.advertisedModelValues
25790
+ });
25791
+ }
25792
+ return input2.state;
25793
+ }
25794
+ if (value === input2.state.currentModelId) {
25795
+ return input2.state;
25796
+ }
25551
25797
  if (input2.state.strategy === "config-option") {
25552
25798
  await connection.request("session/set_config_option", {
25553
25799
  sessionId: input2.acpSessionId,
25554
25800
  configId: input2.state.modelConfigId,
25555
- value: requestedModelId
25801
+ value
25556
25802
  });
25557
25803
  } else {
25558
25804
  await connection.request("session/set_model", {
25559
25805
  sessionId: input2.acpSessionId,
25560
- modelId: requestedModelId
25806
+ modelId: value
25561
25807
  });
25562
25808
  }
25563
25809
  if (!isLive(input2.expectedGeneration)) {
25564
25810
  throw new AcpExecutorDisposedError();
25565
25811
  }
25566
- return { ...input2.state, currentModelId: requestedModelId };
25812
+ return { ...input2.state, currentModelId: value };
25813
+ };
25814
+ const applyRequestedEffort = async (input2) => {
25815
+ const requested = input2.requestedEffort?.trim();
25816
+ if (!requested) return;
25817
+ const configOptions = typeof input2.setup === "object" && input2.setup !== null ? input2.setup.configOptions : void 0;
25818
+ const effort = findAcpEffortConfig(configOptions);
25819
+ if (effort === void 0) return;
25820
+ const matched = effort.choices.find((choice) => choice.value === requested);
25821
+ if (matched === void 0) {
25822
+ options.log?.info("requested effort is not in the agent catalog; keeping its own", {
25823
+ requestedEffort: requested,
25824
+ advertised: effort.choices.map((choice) => choice.value)
25825
+ });
25826
+ return;
25827
+ }
25828
+ if (matched.isDefault === true) return;
25829
+ if (!isLive(input2.expectedGeneration)) return;
25830
+ try {
25831
+ await connection.request("session/set_config_option", {
25832
+ sessionId: input2.acpSessionId,
25833
+ configId: effort.configId,
25834
+ value: requested
25835
+ });
25836
+ } catch (error62) {
25837
+ options.log?.info("setting the effort level failed; the session keeps its own", {
25838
+ requestedEffort: requested,
25839
+ configId: effort.configId,
25840
+ error: error62 instanceof Error ? error62.message : String(error62)
25841
+ });
25842
+ }
25567
25843
  };
25568
25844
  const startOnce = async (expectedGeneration) => {
25569
- const initializePayload = {
25570
- protocolVersion: PROTOCOL_VERSION,
25571
- clientCapabilities: {
25572
- fs: {
25573
- readTextFile: false,
25574
- writeTextFile: false,
25575
- ...options.clientCapabilities?.fs
25576
- },
25577
- terminal: options.clientCapabilities?.terminal ?? false
25578
- },
25579
- clientInfo: options.clientInfo
25580
- };
25845
+ const initializePayload = acpInitializePayload(options.clientInfo, options.clientCapabilities);
25581
25846
  const initialized = await connection.request(
25582
25847
  "initialize",
25583
25848
  initializePayload
@@ -25621,12 +25886,27 @@ function createAcpExecutor(options) {
25621
25886
  nextSessionId = created.sessionId;
25622
25887
  setup = created;
25623
25888
  }
25889
+ const unhandledCategories = unhandledConfigCategories(
25890
+ setup.configOptions
25891
+ );
25892
+ if (unhandledCategories.length > 0) {
25893
+ options.log?.info("acp session reported config option categories we do not handle", {
25894
+ sessionId: nextSessionId,
25895
+ categories: unhandledCategories
25896
+ });
25897
+ }
25624
25898
  const nextModelSelectionState = await applyRequestedModel({
25625
25899
  state: modelSelectionStateFromSetup(setup),
25626
25900
  acpSessionId: nextSessionId,
25627
25901
  requestedModelId: options.requestedModelId,
25628
25902
  expectedGeneration
25629
25903
  });
25904
+ await applyRequestedEffort({
25905
+ setup,
25906
+ acpSessionId: nextSessionId,
25907
+ requestedEffort: options.requestedEffort,
25908
+ expectedGeneration
25909
+ });
25630
25910
  if (options.beforeApplyResult) {
25631
25911
  await options.beforeApplyResult();
25632
25912
  }
@@ -25991,6 +26271,26 @@ function createAcpExecutor(options) {
25991
26271
  };
25992
26272
  apply();
25993
26273
  };
26274
+ const setNativeMode = async (modeId) => {
26275
+ failIfDisposed();
26276
+ const started = startResult;
26277
+ if (started === void 0) {
26278
+ throw new AcpExecutorRequestError("session/set_mode", "session has not started yet");
26279
+ }
26280
+ const configId = acpModeConfigId(started.sessionSetupResult);
26281
+ if (configId !== void 0) {
26282
+ await connection.request("session/set_config_option", {
26283
+ sessionId: started.sessionId,
26284
+ configId,
26285
+ value: modeId
26286
+ });
26287
+ return;
26288
+ }
26289
+ await connection.request("session/set_mode", {
26290
+ sessionId: started.sessionId,
26291
+ modeId
26292
+ });
26293
+ };
25994
26294
  const respondToUserInput = async (requestId, answers) => {
25995
26295
  failIfDisposed();
25996
26296
  const pending = pendingUserInputs.get(requestId);
@@ -26007,7 +26307,7 @@ function createAcpExecutor(options) {
26007
26307
  pendingUserInputs.delete(requestId);
26008
26308
  pending.resolve({
26009
26309
  kind: "answered",
26010
- answers: isRecord3(answers) ? answers : {}
26310
+ answers: isRecord5(answers) ? answers : {}
26011
26311
  });
26012
26312
  };
26013
26313
  const dispose = async () => {
@@ -26057,13 +26357,56 @@ function createAcpExecutor(options) {
26057
26357
  cancel,
26058
26358
  respondToRequest,
26059
26359
  respondToUserInput,
26360
+ setNativeMode,
26060
26361
  dispose
26061
26362
  };
26062
26363
  }
26063
26364
 
26365
+ // ../../packages/ycodium-acp-executor/src/probe.ts
26366
+ function withProbeTimeout(promise2, timeoutMs, method) {
26367
+ return new Promise((resolve2, reject) => {
26368
+ const timer = setTimeout(() => {
26369
+ reject(new Error(`ACP probe request '${method}' timed out after ${timeoutMs}ms`));
26370
+ }, timeoutMs);
26371
+ timer.unref?.();
26372
+ promise2.then(
26373
+ (value) => {
26374
+ clearTimeout(timer);
26375
+ resolve2(value);
26376
+ },
26377
+ (error62) => {
26378
+ clearTimeout(timer);
26379
+ reject(error62 instanceof Error ? error62 : new Error(String(error62)));
26380
+ }
26381
+ );
26382
+ });
26383
+ }
26384
+ async function openAcpProbeHandshake(input2) {
26385
+ const connection = createAcpConnection({
26386
+ readable: input2.readable,
26387
+ writable: input2.writable,
26388
+ onNotification: () => void 0
26389
+ });
26390
+ try {
26391
+ const initializeResult = await withProbeTimeout(
26392
+ connection.request("initialize", acpInitializePayload(input2.clientInfo)),
26393
+ input2.requestTimeoutMs,
26394
+ "initialize"
26395
+ );
26396
+ return {
26397
+ initializeResult,
26398
+ request: (method, params) => withProbeTimeout(connection.request(method, params), input2.requestTimeoutMs, method),
26399
+ close: () => connection.close()
26400
+ };
26401
+ } catch (error62) {
26402
+ await connection.close().catch(() => void 0);
26403
+ throw error62;
26404
+ }
26405
+ }
26406
+
26064
26407
  // src/config.ts
26065
26408
  var GROK_DEFAULT_LAUNCH_ARGS = "agent stdio";
26066
- function isRecord4(value) {
26409
+ function isRecord6(value) {
26067
26410
  return typeof value === "object" && value !== null && !Array.isArray(value);
26068
26411
  }
26069
26412
  function readString(value) {
@@ -26076,7 +26419,7 @@ function readEnvironment(value) {
26076
26419
  if (!Array.isArray(value)) return {};
26077
26420
  const env = {};
26078
26421
  for (const entry of value) {
26079
- if (!isRecord4(entry)) continue;
26422
+ if (!isRecord6(entry)) continue;
26080
26423
  const name = readTrimmed(entry["name"]);
26081
26424
  if (name.length === 0) continue;
26082
26425
  env[name] = readString(entry["value"]);
@@ -26084,7 +26427,7 @@ function readEnvironment(value) {
26084
26427
  return env;
26085
26428
  }
26086
26429
  function parseGrokInstanceConfig(config2) {
26087
- if (!isRecord4(config2)) {
26430
+ if (!isRecord6(config2)) {
26088
26431
  return {
26089
26432
  binaryPath: "",
26090
26433
  launchArgs: GROK_DEFAULT_LAUNCH_ARGS,
@@ -26094,7 +26437,7 @@ function parseGrokInstanceConfig(config2) {
26094
26437
  envelopeDriver: void 0
26095
26438
  };
26096
26439
  }
26097
- const nested = isRecord4(config2["config"]) ? config2["config"] : void 0;
26440
+ const nested = isRecord6(config2["config"]) ? config2["config"] : void 0;
26098
26441
  const settings = nested ?? config2;
26099
26442
  return {
26100
26443
  binaryPath: readTrimmed(settings["binaryPath"]),
@@ -26161,7 +26504,7 @@ function tokenizeLaunchArgs(value) {
26161
26504
  import * as NodeFS from "node:fs";
26162
26505
  import * as NodePath from "node:path";
26163
26506
  var AUTH_FILE_NAME = "auth.json";
26164
- var isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
26507
+ var isRecord7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
26165
26508
  var readString2 = (record2, key) => {
26166
26509
  const value = record2[key];
26167
26510
  if (typeof value !== "string") return void 0;
@@ -26175,7 +26518,7 @@ var parseJsonRecord = (raw) => {
26175
26518
  } catch {
26176
26519
  return void 0;
26177
26520
  }
26178
- return isRecord5(parsed) ? parsed : void 0;
26521
+ return isRecord7(parsed) ? parsed : void 0;
26179
26522
  };
26180
26523
  var directoryExists = (homePath) => {
26181
26524
  try {
@@ -26213,7 +26556,7 @@ var joinName = (record2) => {
26213
26556
  };
26214
26557
  var findIdentityEntry = (parsed) => {
26215
26558
  for (const value of Object.values(parsed)) {
26216
- if (!isRecord5(value)) continue;
26559
+ if (!isRecord7(value)) continue;
26217
26560
  if (readString2(value, "email") !== void 0 || readString2(value, "user_id") !== void 0) {
26218
26561
  return value;
26219
26562
  }
@@ -26243,7 +26586,8 @@ var readGrokAccount = (homePath) => {
26243
26586
  };
26244
26587
 
26245
26588
  // src/grokSupport.ts
26246
- var isRecord6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
26589
+ import { modelSelectionStringOption } from "@ycodium-ai/plugin-api/executor";
26590
+ var isRecord8 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
26247
26591
  var trimmed = (value) => {
26248
26592
  if (typeof value !== "string") return void 0;
26249
26593
  const text = value.trim();
@@ -26253,51 +26597,153 @@ function grokReasoningEffortArgs(effort) {
26253
26597
  const value = effort?.trim();
26254
26598
  return value === void 0 || value.length === 0 ? [] : ["--reasoning-effort", value];
26255
26599
  }
26256
- var GROK_REASONING_OPTION_ID = "effort";
26257
- function modelSelectionStringOption(selection, optionId) {
26258
- if (!isRecord6(selection)) return void 0;
26259
- const options = selection["options"];
26260
- if (!Array.isArray(options)) return void 0;
26261
- for (const entry of options) {
26262
- if (!isRecord6(entry)) continue;
26263
- if (entry["id"] !== optionId) continue;
26264
- return trimmed(entry["value"]);
26600
+ var GROK_NATIVE_MODE_BY_RUNTIME_MODE = {
26601
+ "approval-required": "default",
26602
+ "auto-accept-edits": "auto",
26603
+ auto: "auto",
26604
+ "full-access": "bypassPermissions"
26605
+ };
26606
+ var GROK_NATIVE_MODES = [
26607
+ {
26608
+ id: "default",
26609
+ name: "Ask every time",
26610
+ description: "Asks before writing files, running commands, or spawning subagents."
26611
+ },
26612
+ {
26613
+ id: "auto",
26614
+ name: "Auto",
26615
+ description: "Runs edits and subagents without asking; still asks for shell writes and monitor."
26616
+ },
26617
+ {
26618
+ id: "bypassPermissions",
26619
+ name: "Bypass permissions",
26620
+ description: "Never asks. Every tool runs immediately."
26265
26621
  }
26266
- return void 0;
26622
+ ];
26623
+ var GROK_DEFAULT_NATIVE_MODE_ID = "default";
26624
+ var GROK_NATIVE_MODE_REPORT = {
26625
+ nativeModes: GROK_NATIVE_MODES.map((mode) => ({
26626
+ id: mode.id,
26627
+ name: mode.name,
26628
+ description: mode.description
26629
+ })),
26630
+ currentNativeModeId: GROK_DEFAULT_NATIVE_MODE_ID,
26631
+ nativeModeByRuntimeMode: GROK_NATIVE_MODE_BY_RUNTIME_MODE
26632
+ };
26633
+ function isGrokNativeModeId(value) {
26634
+ return GROK_NATIVE_MODES.some((mode) => mode.id === value);
26635
+ }
26636
+ function grokPermissionModeFor(runtimeMode, nativeModeId) {
26637
+ return nativeModeId !== void 0 && isGrokNativeModeId(nativeModeId) ? nativeModeId : GROK_NATIVE_MODE_BY_RUNTIME_MODE[runtimeMode];
26638
+ }
26639
+ function grokPermissionModeArgs(runtimeMode, nativeModeId) {
26640
+ return ["--permission-mode", grokPermissionModeFor(runtimeMode, nativeModeId)];
26641
+ }
26642
+ var GROK_AUTO_APPROVAL_COVERAGE = {
26643
+ asked: ["run_terminal_command", "monitor"],
26644
+ unasked: [
26645
+ "write",
26646
+ "search_replace",
26647
+ "read_file",
26648
+ "todo_write",
26649
+ "web_search",
26650
+ "scheduler_create",
26651
+ "spawn_subagent",
26652
+ "workflow",
26653
+ "use_tool",
26654
+ "send_feedback",
26655
+ "image_gen"
26656
+ ]
26657
+ };
26658
+ var GROK_APPROVAL_COVERAGE = {
26659
+ default: {
26660
+ asked: [
26661
+ "write",
26662
+ "search_replace",
26663
+ "run_terminal_command",
26664
+ "monitor",
26665
+ "spawn_subagent",
26666
+ "workflow",
26667
+ "scheduler_create",
26668
+ "use_tool",
26669
+ "send_feedback",
26670
+ "image_gen"
26671
+ ],
26672
+ unasked: ["read_file", "todo_write", "web_search"]
26673
+ },
26674
+ // 「编辑不问、命令仍问」的那一档,实测只剩 shell 写删与 monitor 会问——落差就是
26675
+ // 这条要说的。
26676
+ auto: GROK_AUTO_APPROVAL_COVERAGE,
26677
+ // 全放行:档位自己就说「不问」,没有落差可报。
26678
+ bypassPermissions: null
26679
+ };
26680
+ var GROK_EXTERNAL_PERMISSION_RULES_PATHS = [
26681
+ // 顺序无语义:**这不是优先级表**,只是文案里列出来的次序(碰巧按用户级 → 用户本地
26682
+ // → 机器级排,别读出别的意思)。三份同时给冲突规则时谁赢**没量过**,别照这个顺序
26683
+ // 推断;要用就先量。唯一量过的相邻关系是「`settings.local.json` 的 `deny` 赢过
26684
+ // `settings.json` 的 `allow`」(F13 `loc-local-deny`)。
26685
+ "~/.claude/settings.json",
26686
+ "~/.claude/settings.local.json",
26687
+ "/etc/claude-code/managed-settings.json"
26688
+ ];
26689
+ var RUNTIME_MODE_APPROVAL_COVERAGE_CODE = "runtime-mode-approval-coverage";
26690
+ function grokRuntimeModeCaveat(nativeModeId) {
26691
+ const coverage = GROK_APPROVAL_COVERAGE[nativeModeId];
26692
+ if (coverage === null) return void 0;
26693
+ const message = `Approval prompts do not cover every Grok tool in this mode. No prompt, inside or outside the workspace: ${coverage.unasked.join(", ")}, plus shell commands Grok rates safe such as \`touch\`. Prompts: ${coverage.asked.join(", ")}. Permission rules in ${GROK_EXTERNAL_PERMISSION_RULES_PATHS.join(", ")} override this mode \u2014 a matching allow entry drops the prompt, a matching deny entry blocks the call outright.`;
26694
+ return {
26695
+ code: RUNTIME_MODE_APPROVAL_COVERAGE_CODE,
26696
+ nativeModeId,
26697
+ asked: coverage.asked,
26698
+ unasked: coverage.unasked,
26699
+ externalRulesPaths: GROK_EXTERNAL_PERMISSION_RULES_PATHS,
26700
+ message
26701
+ };
26267
26702
  }
26703
+ var GROK_REASONING_OPTION_ID = "reasoningEffort";
26268
26704
  function resolveGrokAuthMethodId(initializeResult) {
26269
- const authMethods = isRecord6(initializeResult) ? initializeResult["authMethods"] : void 0;
26705
+ const authMethods = isRecord8(initializeResult) ? initializeResult["authMethods"] : void 0;
26270
26706
  if (!Array.isArray(authMethods)) return void 0;
26271
26707
  for (const method of authMethods) {
26272
- if (isRecord6(method) && method["id"] === "cached_token") return "cached_token";
26708
+ if (isRecord8(method) && method["id"] === "cached_token") return "cached_token";
26273
26709
  }
26274
26710
  return void 0;
26275
26711
  }
26276
26712
  var positive = (value) => typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.trunc(value) : void 0;
26277
26713
  function grokPromptUsage(promptResponse) {
26278
- if (!isRecord6(promptResponse)) return void 0;
26714
+ if (!isRecord8(promptResponse)) return void 0;
26279
26715
  const meta3 = promptResponse["_meta"];
26280
- if (!isRecord6(meta3)) return void 0;
26716
+ if (!isRecord8(meta3)) return void 0;
26281
26717
  const usedTokens = positive(meta3["totalTokens"]);
26282
26718
  if (usedTokens === void 0) return void 0;
26283
26719
  return { usedTokens, modelId: trimmed(meta3["modelId"]) };
26284
26720
  }
26285
- function grokAdvertisedModels(initializeResult) {
26286
- if (!isRecord6(initializeResult)) return [];
26721
+ function grokModelState(initializeResult) {
26722
+ if (!isRecord8(initializeResult)) return void 0;
26287
26723
  const meta3 = initializeResult["_meta"];
26288
- if (!isRecord6(meta3)) return [];
26724
+ if (!isRecord8(meta3)) return void 0;
26289
26725
  const modelState = meta3["modelState"];
26290
- if (!isRecord6(modelState)) return [];
26726
+ if (!isRecord8(modelState)) return void 0;
26291
26727
  const availableModels = modelState["availableModels"];
26292
- if (!Array.isArray(availableModels)) return [];
26728
+ if (!Array.isArray(availableModels)) return void 0;
26729
+ return { currentModelId: trimmed(modelState["currentModelId"]), availableModels };
26730
+ }
26731
+ function grokAdvertisedModels(initializeResult) {
26732
+ const state = grokModelState(initializeResult);
26733
+ if (state === void 0) return [];
26293
26734
  const models = [];
26294
- for (const entry of availableModels) {
26295
- if (!isRecord6(entry)) continue;
26735
+ for (const entry of state.availableModels) {
26736
+ if (!isRecord8(entry)) continue;
26296
26737
  const modelId = trimmed(entry["modelId"]);
26297
26738
  if (modelId === void 0) continue;
26298
26739
  const modelMeta = entry["_meta"];
26299
- const contextWindow = isRecord6(modelMeta) ? positive(modelMeta["totalContextTokens"]) : void 0;
26300
- models.push({ modelId, contextWindow });
26740
+ const contextWindow = isRecord8(modelMeta) ? positive(modelMeta["totalContextTokens"]) : void 0;
26741
+ models.push({
26742
+ modelId,
26743
+ name: trimmed(entry["name"]),
26744
+ isDefault: state.currentModelId !== void 0 && modelId === state.currentModelId,
26745
+ contextWindow
26746
+ });
26301
26747
  }
26302
26748
  return models;
26303
26749
  }
@@ -26306,7 +26752,7 @@ var XAI_ASK_USER_QUESTION_METHODS = [
26306
26752
  "x.ai/ask_user_question"
26307
26753
  ];
26308
26754
  var readOption = (value) => {
26309
- if (!isRecord6(value)) return void 0;
26755
+ if (!isRecord8(value)) return void 0;
26310
26756
  const label = typeof value["label"] === "string" ? value["label"] : void 0;
26311
26757
  if (label === void 0) return void 0;
26312
26758
  return {
@@ -26317,7 +26763,7 @@ var readOption = (value) => {
26317
26763
  };
26318
26764
  };
26319
26765
  var readQuestion = (value) => {
26320
- if (!isRecord6(value)) return void 0;
26766
+ if (!isRecord8(value)) return void 0;
26321
26767
  const question = typeof value["question"] === "string" ? value["question"] : void 0;
26322
26768
  if (question === void 0) return void 0;
26323
26769
  const optionsRaw = value["options"];
@@ -26333,9 +26779,9 @@ var readQuestion = (value) => {
26333
26779
  };
26334
26780
  };
26335
26781
  function parseXAiAskUserQuestionParams(params) {
26336
- const candidates = [params, ...isRecord6(params) ? [params["params"]] : []];
26782
+ const candidates = [params, ...isRecord8(params) ? [params["params"]] : []];
26337
26783
  for (const candidate of candidates) {
26338
- if (!isRecord6(candidate)) continue;
26784
+ if (!isRecord8(candidate)) continue;
26339
26785
  const questionsRaw = candidate["questions"];
26340
26786
  if (!Array.isArray(questionsRaw)) continue;
26341
26787
  const questions = questionsRaw.flatMap((entry) => {
@@ -26412,14 +26858,14 @@ var grokPromptSettlement = {
26412
26858
  // agent 认回哪个键是它的事,两个都给才不猜版本。
26413
26859
  requestFields: (promptKey) => ({ _meta: { promptId: promptKey, requestId: promptKey } }),
26414
26860
  responseKey: (response) => {
26415
- if (!isRecord6(response)) return void 0;
26861
+ if (!isRecord8(response)) return void 0;
26416
26862
  const meta3 = response["_meta"];
26417
- if (!isRecord6(meta3)) return void 0;
26863
+ if (!isRecord8(meta3)) return void 0;
26418
26864
  return trimmed(meta3["promptId"]) ?? trimmed(meta3["requestId"]);
26419
26865
  },
26420
26866
  fromNotification: (method, params) => {
26421
26867
  if (method !== XAI_PROMPT_COMPLETE_METHOD) return void 0;
26422
- if (!isRecord6(params)) return void 0;
26868
+ if (!isRecord8(params)) return void 0;
26423
26869
  const sessionId = params["sessionId"];
26424
26870
  if (typeof sessionId !== "string") return void 0;
26425
26871
  const stopReason = trimmed(params["stopReason"]);
@@ -26433,57 +26879,53 @@ var grokPromptSettlement = {
26433
26879
  }
26434
26880
  };
26435
26881
 
26436
- // src/streams.ts
26437
- import * as NodeStream2 from "node:stream";
26438
- function nodeStreamsFromHandle(handle) {
26439
- const readable = new NodeStream2.PassThrough();
26440
- void (async () => {
26441
- try {
26442
- for await (const chunk of handle.stdout) {
26443
- if (!readable.writable) return;
26444
- if (!readable.write(chunk)) {
26445
- await new Promise((resolve2) => {
26446
- const onDone = () => {
26447
- readable.off("drain", onDone);
26448
- readable.off("close", onDone);
26449
- resolve2();
26450
- };
26451
- readable.once("drain", onDone);
26452
- readable.once("close", onDone);
26453
- });
26454
- }
26455
- }
26456
- } catch (error62) {
26457
- readable.destroy(error62 instanceof Error ? error62 : new Error(String(error62)));
26458
- return;
26459
- }
26460
- if (readable.writable) readable.end();
26461
- })();
26462
- void (async () => {
26463
- for await (const _chunk of handle.stderr) {
26464
- }
26465
- })().catch(() => void 0);
26466
- const writable = new NodeStream2.Writable({
26467
- write(chunk, _encoding, callback) {
26468
- const bytes = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
26469
- handle.writeStdin(bytes).then(
26470
- () => callback(),
26471
- (error62) => callback(error62 instanceof Error ? error62 : new Error(String(error62)))
26472
- );
26473
- },
26474
- final(callback) {
26475
- handle.closeStdin().then(
26476
- () => callback(),
26477
- (error62) => callback(error62 instanceof Error ? error62 : new Error(String(error62)))
26478
- );
26479
- }
26480
- });
26481
- return { readable, writable };
26882
+ // src/probeModels.ts
26883
+ import { nodeStreamsFromHandle } from "@ycodium-ai/plugin-api/process-stdin";
26884
+ var PROBE_REQUEST_TIMEOUT_MS = 2e4;
26885
+ function grokProbeModels(initializeResult) {
26886
+ if (grokModelState(initializeResult) === void 0) return void 0;
26887
+ return grokAdvertisedModels(initializeResult).map((model) => ({
26888
+ slug: model.modelId,
26889
+ ...model.name === void 0 ? {} : { name: model.name },
26890
+ ...model.isDefault ? { isDefault: true } : {},
26891
+ ...model.contextWindow === void 0 ? {} : { contextWindow: model.contextWindow }
26892
+ }));
26893
+ }
26894
+ var failureDetail = (error62) => (error62 instanceof Error ? error62.message : String(error62)).trim() || void 0;
26895
+ async function probeGrokModels(input2) {
26896
+ const { recipe, host, env } = input2;
26897
+ let handle;
26898
+ try {
26899
+ handle = await host.process.spawn({
26900
+ program: recipe.binaryPath,
26901
+ args: tokenizeLaunchArgs(recipe.launchArgs),
26902
+ cwd: process.cwd(),
26903
+ owner: { bindingId: host.bindingId },
26904
+ ...Object.keys(env).length > 0 ? { env } : {}
26905
+ });
26906
+ } catch (error62) {
26907
+ return { kind: "not-started", detail: failureDetail(error62) };
26908
+ }
26909
+ let handshake;
26910
+ try {
26911
+ const { readable, writable } = nodeStreamsFromHandle(handle);
26912
+ handshake = await openAcpProbeHandshake({
26913
+ readable,
26914
+ writable,
26915
+ clientInfo: GROK_CLIENT_INFO,
26916
+ requestTimeoutMs: PROBE_REQUEST_TIMEOUT_MS
26917
+ });
26918
+ return { kind: "answered", models: grokProbeModels(handshake.initializeResult) };
26919
+ } catch (error62) {
26920
+ return { kind: "failed", detail: failureDetail(error62) };
26921
+ } finally {
26922
+ await handshake?.close().catch(() => void 0);
26923
+ await handle.kill("SIGTERM").catch(() => void 0);
26924
+ }
26482
26925
  }
26483
26926
 
26484
26927
  // src/grokInstance.ts
26485
26928
  var RESUME_SCHEMA_VERSION = 1;
26486
- var CLIENT_INFO = { name: "ycodium-agent-grok", version: "0.1.0" };
26487
26929
  var yieldTick = () => new Promise((resolve2) => {
26488
26930
  setImmediate(resolve2);
26489
26931
  });
@@ -26536,6 +26978,9 @@ function resolveGrokHomePath(value) {
26536
26978
  if (trimmed2.length === 0) return NodePath2.join(NodeOS.homedir(), ".grok");
26537
26979
  return NodePath2.resolve(expandHomePath(trimmed2));
26538
26980
  }
26981
+ function grokContinuationKey(recipe) {
26982
+ return `grok:home:${resolveGrokHomePath(recipe.homePath)}`;
26983
+ }
26539
26984
  function resolveAccountHomePath(recipe, configuredHome) {
26540
26985
  const fromEnvironment = recipe.environment["GROK_HOME"]?.trim();
26541
26986
  if (fromEnvironment !== void 0 && fromEnvironment.length > 0) {
@@ -26543,21 +26988,15 @@ function resolveAccountHomePath(recipe, configuredHome) {
26543
26988
  }
26544
26989
  return resolveGrokHomePath(configuredHome);
26545
26990
  }
26546
- function compactEnv(env) {
26547
- const out = {};
26548
- for (const [key, value] of Object.entries(env)) {
26549
- if (value !== void 0) out[key] = value;
26550
- }
26551
- return out;
26552
- }
26553
- function buildSpawnEnv(recipe, launch, resolvedHomePath, homeConfigured) {
26991
+ function buildSpawnEnv(recipe, resolvedHomePath, homeConfigured, launchEnv = {}) {
26554
26992
  const homeInjection = homeConfigured ? { [GROK_CARD.homeEnvVar]: resolvedHomePath } : {};
26555
- return {
26556
- ...compactEnv(launch.processEnv),
26557
- ...homeInjection,
26558
- ...recipe.environment,
26559
- GROK_OAUTH2_REFERRER: "ycodium"
26560
- };
26993
+ return mergeLaunchEnv({
26994
+ brainSlots: brainSlotEnvKeys(GROK_CARD),
26995
+ accountHome: homeInjection,
26996
+ instanceTable: recipe.environment,
26997
+ handOverrides: { GROK_OAUTH2_REFERRER: "ycodium" },
26998
+ launch: launchEnv
26999
+ });
26561
27000
  }
26562
27001
  function nowIso() {
26563
27002
  return (/* @__PURE__ */ new Date()).toISOString();
@@ -26601,24 +27040,49 @@ var createGrokInstance = (config2, host) => {
26601
27040
  return { status: "unknown" };
26602
27041
  }
26603
27042
  };
26604
- const instance = {
26605
- probe: async () => {
26606
- if (recipe.binaryPath.length === 0) {
26607
- return {
26608
- installed: false,
26609
- version: null,
26610
- readiness: "error",
26611
- auth: { status: "unknown" },
26612
- detail: "Grok program path is not configured"
26613
- };
26614
- }
27043
+ const probe = async () => {
27044
+ if (recipe.binaryPath.length === 0) {
26615
27045
  return {
26616
- installed: true,
27046
+ installed: false,
26617
27047
  version: null,
26618
- readiness: "unchecked",
26619
- auth: probeAuth()
27048
+ readiness: "error",
27049
+ auth: { status: "unknown" },
27050
+ detail: "Grok program path is not configured"
26620
27051
  };
26621
- },
27052
+ }
27053
+ const configuredHome = recipe.homePath;
27054
+ const outcome = await probeGrokModels({
27055
+ recipe,
27056
+ host,
27057
+ env: buildSpawnEnv(
27058
+ recipe,
27059
+ resolveGrokHomePath(configuredHome),
27060
+ configuredHome.trim().length > 0
27061
+ )
27062
+ });
27063
+ if (outcome.kind !== "answered") {
27064
+ return {
27065
+ installed: outcome.kind === "failed",
27066
+ version: null,
27067
+ readiness: "error",
27068
+ auth: probeAuth(),
27069
+ ...outcome.detail === void 0 ? {} : { detail: outcome.detail },
27070
+ // 装着但握不上手时,界面照样画权限格。不报档就退回宿主四档,而那四档对这双手
27071
+ // 是撒谎的——这台开发机的 grok 就长期停在这个出口上。
27072
+ ...outcome.kind === "failed" ? GROK_NATIVE_MODE_REPORT : {}
27073
+ };
27074
+ }
27075
+ return {
27076
+ installed: true,
27077
+ version: null,
27078
+ readiness: "ready",
27079
+ auth: probeAuth(),
27080
+ ...outcome.models === void 0 ? {} : { models: outcome.models },
27081
+ ...GROK_NATIVE_MODE_REPORT
27082
+ };
27083
+ };
27084
+ const instance = {
27085
+ probe,
26622
27086
  startSession: async (input2, signal) => {
26623
27087
  if (disposed) throw new Error("instance disposed");
26624
27088
  if (signal?.aborted) throw new Error("aborted");
@@ -26634,11 +27098,18 @@ var createGrokInstance = (config2, host) => {
26634
27098
  }
26635
27099
  const configuredHome = input2.launch.homePath?.trim() || recipe.homePath;
26636
27100
  const resolvedHomePath = resolveGrokHomePath(configuredHome);
26637
- const env = buildSpawnEnv(recipe, input2.launch, resolvedHomePath, configuredHome.length > 0);
27101
+ const env = buildSpawnEnv(
27102
+ recipe,
27103
+ resolvedHomePath,
27104
+ configuredHome.length > 0,
27105
+ input2.launch.processEnv
27106
+ );
26638
27107
  const effort = modelSelectionStringOption(input2.modelSelection, GROK_REASONING_OPTION_ID);
26639
27108
  const baseArgs = tokenizeLaunchArgs(recipe.launchArgs);
26640
27109
  const insertAt = baseArgs.length <= 1 ? baseArgs.length : baseArgs.length - 1;
27110
+ const permissionMode = grokPermissionModeFor(input2.runtimeMode, input2.nativeModeId);
26641
27111
  const args = [
27112
+ ...grokPermissionModeArgs(input2.runtimeMode, permissionMode),
26642
27113
  ...baseArgs.slice(0, insertAt),
26643
27114
  ...grokReasoningEffortArgs(effort),
26644
27115
  ...baseArgs.slice(insertAt)
@@ -26650,7 +27121,7 @@ var createGrokInstance = (config2, host) => {
26650
27121
  owner: { bindingId: host.bindingId },
26651
27122
  ...Object.keys(env).length > 0 ? { env } : {}
26652
27123
  });
26653
- const { readable, writable } = nodeStreamsFromHandle(handle);
27124
+ const { readable, writable } = nodeStreamsFromHandle2(handle);
26654
27125
  const resume = input2.discardResumeCursor === true ? void 0 : parseAcpResumeCursor(input2.resumeCursor, RESUME_SCHEMA_VERSION);
26655
27126
  const requestedModelId = input2.launch.requestedModelId?.trim() || modelIdFromSelection(input2.modelSelection);
26656
27127
  let initializeResult;
@@ -26661,7 +27132,8 @@ var createGrokInstance = (config2, host) => {
26661
27132
  threadId: input2.threadId,
26662
27133
  provider: GROK_DRIVER_KIND,
26663
27134
  providerInstanceId: host.instanceId,
26664
- clientInfo: CLIENT_INFO,
27135
+ clientInfo: GROK_CLIENT_INFO,
27136
+ log: host.log,
26665
27137
  // 等 `initialize` 回来再挑认证方式:能不能用 `cached_token`,只有 agent
26666
27138
  // 播报的 `authMethods` 说了算(见 resolveGrokAuthMethodId)。
26667
27139
  authMethodId: resolveGrokAuthMethodId,
@@ -26715,6 +27187,7 @@ var createGrokInstance = (config2, host) => {
26715
27187
  providerInstanceId: host.instanceId,
26716
27188
  status: "idle",
26717
27189
  runtimeMode: input2.runtimeMode,
27190
+ nativeModeId: permissionMode,
26718
27191
  cwd,
26719
27192
  threadId: input2.threadId,
26720
27193
  createdAt,
@@ -26734,6 +27207,27 @@ var createGrokInstance = (config2, host) => {
26734
27207
  threadId: input2.threadId,
26735
27208
  sessionId: started.sessionId
26736
27209
  });
27210
+ const caveat = grokRuntimeModeCaveat(permissionMode);
27211
+ if (caveat !== void 0) {
27212
+ events.push({
27213
+ type: "runtime.warning",
27214
+ eventId: NodeCrypto2.randomUUID(),
27215
+ provider: GROK_DRIVER_KIND,
27216
+ providerInstanceId: host.instanceId,
27217
+ threadId: input2.threadId,
27218
+ createdAt: nowIso(),
27219
+ payload: {
27220
+ message: caveat.message,
27221
+ detail: {
27222
+ code: caveat.code,
27223
+ nativeModeId: caveat.nativeModeId,
27224
+ asked: caveat.asked,
27225
+ unasked: caveat.unasked,
27226
+ externalRulesPaths: caveat.externalRulesPaths
27227
+ }
27228
+ }
27229
+ });
27230
+ }
26737
27231
  return session;
26738
27232
  } catch (error62) {
26739
27233
  await executor.dispose().catch(() => void 0);
@@ -26803,14 +27297,6 @@ var createGrokInstance = (config2, host) => {
26803
27297
  const booked = requireThread(threadId);
26804
27298
  await booked.executor.respondToUserInput(requestId, answers);
26805
27299
  },
26806
- setRuntimeMode: async (threadId, runtimeMode) => {
26807
- const booked = requireThread(threadId);
26808
- booked.session = {
26809
- ...booked.session,
26810
- runtimeMode,
26811
- updatedAt: nowIso()
26812
- };
26813
- },
26814
27300
  stopSession: async (threadId) => {
26815
27301
  const booked = threads.get(threadId);
26816
27302
  if (booked === void 0) return;
@@ -26822,6 +27308,7 @@ var createGrokInstance = (config2, host) => {
26822
27308
  return { threadId, turns: booked.turns };
26823
27309
  },
26824
27310
  events,
27311
+ continuationKey: grokContinuationKey(recipe),
26825
27312
  dispose: async () => {
26826
27313
  if (disposed) return;
26827
27314
  disposed = true;
@@ -26899,23 +27386,7 @@ var createGrokInstance = (config2, host) => {
26899
27386
  await operation.handle.kill("SIGTERM").catch(() => void 0);
26900
27387
  await operation.stdoutText.catch(() => void 0);
26901
27388
  },
26902
- verify: async () => {
26903
- if (recipe.binaryPath.length === 0) {
26904
- return {
26905
- installed: false,
26906
- version: null,
26907
- readiness: "error",
26908
- auth: { status: "unknown" },
26909
- detail: "Grok program path is not configured"
26910
- };
26911
- }
26912
- return {
26913
- installed: true,
26914
- version: null,
26915
- readiness: "unchecked",
26916
- auth: probeAuth()
26917
- };
26918
- }
27389
+ verify: () => probe()
26919
27390
  }
26920
27391
  };
26921
27392
  return instance;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ycodium-ai/agent-grok",
3
- "version": "0.2.2106",
3
+ "version": "0.2.2130",
4
4
  "description": "Grok executor: grok CLI over Agent Client Protocol stdio.",
5
5
  "keywords": [
6
6
  "ycodium",
@@ -20,9 +20,9 @@
20
20
  "@types/node": "24.12.4",
21
21
  "esbuild": "0.28.1",
22
22
  "vite-plus": "0.2.2",
23
- "@ycodium-ai/plugin-api": "0.2.2106",
24
- "ycodium-acp-executor": "0.0.0",
25
- "ycodium-acp-protocol": "0.0.0"
23
+ "@ycodium-ai/plugin-api": "0.2.2130",
24
+ "ycodium-acp-protocol": "0.0.0",
25
+ "ycodium-acp-executor": "0.0.0"
26
26
  },
27
27
  "ycodium": {
28
28
  "plugin": "./dist/index.js",