@ycodium-ai/agent-grok 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 +678 -190
  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;
@@ -24354,7 +24391,7 @@ function makeAcpContentDeltaEvent(input2) {
24354
24391
  ...input2.turnId !== void 0 ? { turnId: input2.turnId } : {},
24355
24392
  ...input2.itemId ? { itemId: input2.itemId } : {},
24356
24393
  payload: {
24357
- streamKind: "assistant_text",
24394
+ streamKind: input2.streamKind ?? "assistant_text",
24358
24395
  delta: input2.text
24359
24396
  },
24360
24397
  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({
@@ -25038,21 +25259,24 @@ function parseSessionUpdateEvent(params) {
25038
25259
  }
25039
25260
  break;
25040
25261
  }
25041
- case "agent_message_chunk": {
25042
- const content = isRecord3(upd["content"]) ? upd["content"] : void 0;
25262
+ case "agent_message_chunk":
25263
+ case "agent_thought_chunk": {
25264
+ const content = isRecord5(upd["content"]) ? upd["content"] : void 0;
25043
25265
  const contentType = content?.["type"];
25266
+ const streamKind = kind === "agent_thought_chunk" ? "reasoning_text" : "assistant_text";
25044
25267
  if (contentType === "text") {
25045
25268
  const text = typeof content?.["text"] === "string" ? content["text"] : "";
25046
25269
  if (text.length > 0) {
25047
25270
  events.push({
25048
25271
  _tag: "ContentDelta",
25049
25272
  text,
25273
+ streamKind,
25050
25274
  rawPayload: params
25051
25275
  });
25052
25276
  }
25053
25277
  break;
25054
25278
  }
25055
- if (content && contentType !== "audio") {
25279
+ if (streamKind === "assistant_text" && content && contentType !== "audio") {
25056
25280
  events.push({
25057
25281
  _tag: "AssistantContentBlock",
25058
25282
  content,
@@ -25104,16 +25328,18 @@ function normalizeModelId(value) {
25104
25328
  return value?.trim() || void 0;
25105
25329
  }
25106
25330
  function modelSelectionStateFromSetup(setup) {
25107
- const sessionModels = isRecord3(setup.models) ? setup.models : void 0;
25331
+ const sessionModels = isRecord5(setup.models) ? setup.models : void 0;
25108
25332
  const modelConfigId = extractModelConfigId(setup);
25109
25333
  const strategy = sessionModels === void 0 && modelConfigId !== void 0 ? "config-option" : "session/set_model";
25110
- 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;
25111
25336
  const currentModelId = strategy === "config-option" ? typeof configModel === "string" ? normalizeModelId(configModel) : void 0 : typeof sessionModels?.["currentModelId"] === "string" ? normalizeModelId(sessionModels["currentModelId"]) : void 0;
25112
25337
  return {
25113
25338
  strategy,
25114
25339
  sessionModelsAdvertised: sessionModels !== void 0,
25115
25340
  modelConfigId,
25116
- currentModelId
25341
+ currentModelId,
25342
+ advertisedModelValues: strategy === "config-option" ? modelConfigOption === void 0 ? [] : collectSessionConfigOptionValues(modelConfigOption) : collectSessionModelIds(sessionModels)
25117
25343
  };
25118
25344
  }
25119
25345
  function createAcpExecutor(options) {
@@ -25132,8 +25358,10 @@ function createAcpExecutor(options) {
25132
25358
  strategy: "session/set_model",
25133
25359
  sessionModelsAdvertised: false,
25134
25360
  modelConfigId: void 0,
25135
- currentModelId: void 0
25361
+ currentModelId: void 0,
25362
+ advertisedModelValues: []
25136
25363
  };
25364
+ let lastUnmatchedModelId;
25137
25365
  let initializeResult;
25138
25366
  let startResult;
25139
25367
  let startInFlight;
@@ -25268,6 +25496,23 @@ function createAcpExecutor(options) {
25268
25496
  continue;
25269
25497
  }
25270
25498
  if (event._tag === "ContentDelta") {
25499
+ if (event.streamKind === "reasoning_text") {
25500
+ if (event.text.trim().length === 0) {
25501
+ continue;
25502
+ }
25503
+ emit(
25504
+ makeAcpContentDeltaEvent({
25505
+ stamp: stamp(),
25506
+ provider: options.provider,
25507
+ threadId: options.threadId,
25508
+ turnId: activeTurnId,
25509
+ text: event.text,
25510
+ streamKind: "reasoning_text",
25511
+ rawPayload: event.rawPayload
25512
+ })
25513
+ );
25514
+ continue;
25515
+ }
25271
25516
  if (event.text.trim().length === 0 && !assistantSegment.activeItemId) {
25272
25517
  continue;
25273
25518
  }
@@ -25280,6 +25525,7 @@ function createAcpExecutor(options) {
25280
25525
  turnId: activeTurnId,
25281
25526
  ...itemId ? { itemId } : {},
25282
25527
  text: event.text,
25528
+ streamKind: "assistant_text",
25283
25529
  rawPayload: event.rawPayload
25284
25530
  })
25285
25531
  );
@@ -25440,6 +25686,12 @@ function createAcpExecutor(options) {
25440
25686
  const userInputOutcome = await handlePermissionAsUserInput(params);
25441
25687
  if (userInputOutcome !== void 0) return userInputOutcome;
25442
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
+ }
25443
25695
  const requestId = randomId();
25444
25696
  const outcome = await new Promise((resolve2) => {
25445
25697
  const pending = {
@@ -25527,36 +25779,70 @@ function createAcpExecutor(options) {
25527
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) {
25528
25780
  return input2.state;
25529
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
+ }
25530
25797
  if (input2.state.strategy === "config-option") {
25531
25798
  await connection.request("session/set_config_option", {
25532
25799
  sessionId: input2.acpSessionId,
25533
25800
  configId: input2.state.modelConfigId,
25534
- value: requestedModelId
25801
+ value
25535
25802
  });
25536
25803
  } else {
25537
25804
  await connection.request("session/set_model", {
25538
25805
  sessionId: input2.acpSessionId,
25539
- modelId: requestedModelId
25806
+ modelId: value
25540
25807
  });
25541
25808
  }
25542
25809
  if (!isLive(input2.expectedGeneration)) {
25543
25810
  throw new AcpExecutorDisposedError();
25544
25811
  }
25545
- 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
+ }
25546
25843
  };
25547
25844
  const startOnce = async (expectedGeneration) => {
25548
- const initializePayload = {
25549
- protocolVersion: PROTOCOL_VERSION,
25550
- clientCapabilities: {
25551
- fs: {
25552
- readTextFile: false,
25553
- writeTextFile: false,
25554
- ...options.clientCapabilities?.fs
25555
- },
25556
- terminal: options.clientCapabilities?.terminal ?? false
25557
- },
25558
- clientInfo: options.clientInfo
25559
- };
25845
+ const initializePayload = acpInitializePayload(options.clientInfo, options.clientCapabilities);
25560
25846
  const initialized = await connection.request(
25561
25847
  "initialize",
25562
25848
  initializePayload
@@ -25600,12 +25886,27 @@ function createAcpExecutor(options) {
25600
25886
  nextSessionId = created.sessionId;
25601
25887
  setup = created;
25602
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
+ }
25603
25898
  const nextModelSelectionState = await applyRequestedModel({
25604
25899
  state: modelSelectionStateFromSetup(setup),
25605
25900
  acpSessionId: nextSessionId,
25606
25901
  requestedModelId: options.requestedModelId,
25607
25902
  expectedGeneration
25608
25903
  });
25904
+ await applyRequestedEffort({
25905
+ setup,
25906
+ acpSessionId: nextSessionId,
25907
+ requestedEffort: options.requestedEffort,
25908
+ expectedGeneration
25909
+ });
25609
25910
  if (options.beforeApplyResult) {
25610
25911
  await options.beforeApplyResult();
25611
25912
  }
@@ -25970,6 +26271,26 @@ function createAcpExecutor(options) {
25970
26271
  };
25971
26272
  apply();
25972
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
+ };
25973
26294
  const respondToUserInput = async (requestId, answers) => {
25974
26295
  failIfDisposed();
25975
26296
  const pending = pendingUserInputs.get(requestId);
@@ -25986,7 +26307,7 @@ function createAcpExecutor(options) {
25986
26307
  pendingUserInputs.delete(requestId);
25987
26308
  pending.resolve({
25988
26309
  kind: "answered",
25989
- answers: isRecord3(answers) ? answers : {}
26310
+ answers: isRecord5(answers) ? answers : {}
25990
26311
  });
25991
26312
  };
25992
26313
  const dispose = async () => {
@@ -26036,13 +26357,56 @@ function createAcpExecutor(options) {
26036
26357
  cancel,
26037
26358
  respondToRequest,
26038
26359
  respondToUserInput,
26360
+ setNativeMode,
26039
26361
  dispose
26040
26362
  };
26041
26363
  }
26042
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
+
26043
26407
  // src/config.ts
26044
26408
  var GROK_DEFAULT_LAUNCH_ARGS = "agent stdio";
26045
- function isRecord4(value) {
26409
+ function isRecord6(value) {
26046
26410
  return typeof value === "object" && value !== null && !Array.isArray(value);
26047
26411
  }
26048
26412
  function readString(value) {
@@ -26055,7 +26419,7 @@ function readEnvironment(value) {
26055
26419
  if (!Array.isArray(value)) return {};
26056
26420
  const env = {};
26057
26421
  for (const entry of value) {
26058
- if (!isRecord4(entry)) continue;
26422
+ if (!isRecord6(entry)) continue;
26059
26423
  const name = readTrimmed(entry["name"]);
26060
26424
  if (name.length === 0) continue;
26061
26425
  env[name] = readString(entry["value"]);
@@ -26063,7 +26427,7 @@ function readEnvironment(value) {
26063
26427
  return env;
26064
26428
  }
26065
26429
  function parseGrokInstanceConfig(config2) {
26066
- if (!isRecord4(config2)) {
26430
+ if (!isRecord6(config2)) {
26067
26431
  return {
26068
26432
  binaryPath: "",
26069
26433
  launchArgs: GROK_DEFAULT_LAUNCH_ARGS,
@@ -26073,7 +26437,7 @@ function parseGrokInstanceConfig(config2) {
26073
26437
  envelopeDriver: void 0
26074
26438
  };
26075
26439
  }
26076
- const nested = isRecord4(config2["config"]) ? config2["config"] : void 0;
26440
+ const nested = isRecord6(config2["config"]) ? config2["config"] : void 0;
26077
26441
  const settings = nested ?? config2;
26078
26442
  return {
26079
26443
  binaryPath: readTrimmed(settings["binaryPath"]),
@@ -26140,7 +26504,7 @@ function tokenizeLaunchArgs(value) {
26140
26504
  import * as NodeFS from "node:fs";
26141
26505
  import * as NodePath from "node:path";
26142
26506
  var AUTH_FILE_NAME = "auth.json";
26143
- var isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
26507
+ var isRecord7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
26144
26508
  var readString2 = (record2, key) => {
26145
26509
  const value = record2[key];
26146
26510
  if (typeof value !== "string") return void 0;
@@ -26154,7 +26518,7 @@ var parseJsonRecord = (raw) => {
26154
26518
  } catch {
26155
26519
  return void 0;
26156
26520
  }
26157
- return isRecord5(parsed) ? parsed : void 0;
26521
+ return isRecord7(parsed) ? parsed : void 0;
26158
26522
  };
26159
26523
  var directoryExists = (homePath) => {
26160
26524
  try {
@@ -26192,7 +26556,7 @@ var joinName = (record2) => {
26192
26556
  };
26193
26557
  var findIdentityEntry = (parsed) => {
26194
26558
  for (const value of Object.values(parsed)) {
26195
- if (!isRecord5(value)) continue;
26559
+ if (!isRecord7(value)) continue;
26196
26560
  if (readString2(value, "email") !== void 0 || readString2(value, "user_id") !== void 0) {
26197
26561
  return value;
26198
26562
  }
@@ -26222,7 +26586,8 @@ var readGrokAccount = (homePath) => {
26222
26586
  };
26223
26587
 
26224
26588
  // src/grokSupport.ts
26225
- 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);
26226
26591
  var trimmed = (value) => {
26227
26592
  if (typeof value !== "string") return void 0;
26228
26593
  const text = value.trim();
@@ -26232,51 +26597,153 @@ function grokReasoningEffortArgs(effort) {
26232
26597
  const value = effort?.trim();
26233
26598
  return value === void 0 || value.length === 0 ? [] : ["--reasoning-effort", value];
26234
26599
  }
26235
- var GROK_REASONING_OPTION_ID = "effort";
26236
- function modelSelectionStringOption(selection, optionId) {
26237
- if (!isRecord6(selection)) return void 0;
26238
- const options = selection["options"];
26239
- if (!Array.isArray(options)) return void 0;
26240
- for (const entry of options) {
26241
- if (!isRecord6(entry)) continue;
26242
- if (entry["id"] !== optionId) continue;
26243
- 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."
26244
26621
  }
26245
- 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
+ };
26246
26702
  }
26703
+ var GROK_REASONING_OPTION_ID = "reasoningEffort";
26247
26704
  function resolveGrokAuthMethodId(initializeResult) {
26248
- const authMethods = isRecord6(initializeResult) ? initializeResult["authMethods"] : void 0;
26705
+ const authMethods = isRecord8(initializeResult) ? initializeResult["authMethods"] : void 0;
26249
26706
  if (!Array.isArray(authMethods)) return void 0;
26250
26707
  for (const method of authMethods) {
26251
- if (isRecord6(method) && method["id"] === "cached_token") return "cached_token";
26708
+ if (isRecord8(method) && method["id"] === "cached_token") return "cached_token";
26252
26709
  }
26253
26710
  return void 0;
26254
26711
  }
26255
26712
  var positive = (value) => typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.trunc(value) : void 0;
26256
26713
  function grokPromptUsage(promptResponse) {
26257
- if (!isRecord6(promptResponse)) return void 0;
26714
+ if (!isRecord8(promptResponse)) return void 0;
26258
26715
  const meta3 = promptResponse["_meta"];
26259
- if (!isRecord6(meta3)) return void 0;
26716
+ if (!isRecord8(meta3)) return void 0;
26260
26717
  const usedTokens = positive(meta3["totalTokens"]);
26261
26718
  if (usedTokens === void 0) return void 0;
26262
26719
  return { usedTokens, modelId: trimmed(meta3["modelId"]) };
26263
26720
  }
26264
- function grokAdvertisedModels(initializeResult) {
26265
- if (!isRecord6(initializeResult)) return [];
26721
+ function grokModelState(initializeResult) {
26722
+ if (!isRecord8(initializeResult)) return void 0;
26266
26723
  const meta3 = initializeResult["_meta"];
26267
- if (!isRecord6(meta3)) return [];
26724
+ if (!isRecord8(meta3)) return void 0;
26268
26725
  const modelState = meta3["modelState"];
26269
- if (!isRecord6(modelState)) return [];
26726
+ if (!isRecord8(modelState)) return void 0;
26270
26727
  const availableModels = modelState["availableModels"];
26271
- 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 [];
26272
26734
  const models = [];
26273
- for (const entry of availableModels) {
26274
- if (!isRecord6(entry)) continue;
26735
+ for (const entry of state.availableModels) {
26736
+ if (!isRecord8(entry)) continue;
26275
26737
  const modelId = trimmed(entry["modelId"]);
26276
26738
  if (modelId === void 0) continue;
26277
26739
  const modelMeta = entry["_meta"];
26278
- const contextWindow = isRecord6(modelMeta) ? positive(modelMeta["totalContextTokens"]) : void 0;
26279
- 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
+ });
26280
26747
  }
26281
26748
  return models;
26282
26749
  }
@@ -26285,7 +26752,7 @@ var XAI_ASK_USER_QUESTION_METHODS = [
26285
26752
  "x.ai/ask_user_question"
26286
26753
  ];
26287
26754
  var readOption = (value) => {
26288
- if (!isRecord6(value)) return void 0;
26755
+ if (!isRecord8(value)) return void 0;
26289
26756
  const label = typeof value["label"] === "string" ? value["label"] : void 0;
26290
26757
  if (label === void 0) return void 0;
26291
26758
  return {
@@ -26296,7 +26763,7 @@ var readOption = (value) => {
26296
26763
  };
26297
26764
  };
26298
26765
  var readQuestion = (value) => {
26299
- if (!isRecord6(value)) return void 0;
26766
+ if (!isRecord8(value)) return void 0;
26300
26767
  const question = typeof value["question"] === "string" ? value["question"] : void 0;
26301
26768
  if (question === void 0) return void 0;
26302
26769
  const optionsRaw = value["options"];
@@ -26312,9 +26779,9 @@ var readQuestion = (value) => {
26312
26779
  };
26313
26780
  };
26314
26781
  function parseXAiAskUserQuestionParams(params) {
26315
- const candidates = [params, ...isRecord6(params) ? [params["params"]] : []];
26782
+ const candidates = [params, ...isRecord8(params) ? [params["params"]] : []];
26316
26783
  for (const candidate of candidates) {
26317
- if (!isRecord6(candidate)) continue;
26784
+ if (!isRecord8(candidate)) continue;
26318
26785
  const questionsRaw = candidate["questions"];
26319
26786
  if (!Array.isArray(questionsRaw)) continue;
26320
26787
  const questions = questionsRaw.flatMap((entry) => {
@@ -26391,14 +26858,14 @@ var grokPromptSettlement = {
26391
26858
  // agent 认回哪个键是它的事,两个都给才不猜版本。
26392
26859
  requestFields: (promptKey) => ({ _meta: { promptId: promptKey, requestId: promptKey } }),
26393
26860
  responseKey: (response) => {
26394
- if (!isRecord6(response)) return void 0;
26861
+ if (!isRecord8(response)) return void 0;
26395
26862
  const meta3 = response["_meta"];
26396
- if (!isRecord6(meta3)) return void 0;
26863
+ if (!isRecord8(meta3)) return void 0;
26397
26864
  return trimmed(meta3["promptId"]) ?? trimmed(meta3["requestId"]);
26398
26865
  },
26399
26866
  fromNotification: (method, params) => {
26400
26867
  if (method !== XAI_PROMPT_COMPLETE_METHOD) return void 0;
26401
- if (!isRecord6(params)) return void 0;
26868
+ if (!isRecord8(params)) return void 0;
26402
26869
  const sessionId = params["sessionId"];
26403
26870
  if (typeof sessionId !== "string") return void 0;
26404
26871
  const stopReason = trimmed(params["stopReason"]);
@@ -26412,57 +26879,53 @@ var grokPromptSettlement = {
26412
26879
  }
26413
26880
  };
26414
26881
 
26415
- // src/streams.ts
26416
- import * as NodeStream2 from "node:stream";
26417
- function nodeStreamsFromHandle(handle) {
26418
- const readable = new NodeStream2.PassThrough();
26419
- void (async () => {
26420
- try {
26421
- for await (const chunk of handle.stdout) {
26422
- if (!readable.writable) return;
26423
- if (!readable.write(chunk)) {
26424
- await new Promise((resolve2) => {
26425
- const onDone = () => {
26426
- readable.off("drain", onDone);
26427
- readable.off("close", onDone);
26428
- resolve2();
26429
- };
26430
- readable.once("drain", onDone);
26431
- readable.once("close", onDone);
26432
- });
26433
- }
26434
- }
26435
- } catch (error62) {
26436
- readable.destroy(error62 instanceof Error ? error62 : new Error(String(error62)));
26437
- return;
26438
- }
26439
- if (readable.writable) readable.end();
26440
- })();
26441
- void (async () => {
26442
- for await (const _chunk of handle.stderr) {
26443
- }
26444
- })().catch(() => void 0);
26445
- const writable = new NodeStream2.Writable({
26446
- write(chunk, _encoding, callback) {
26447
- const bytes = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
26448
- handle.writeStdin(bytes).then(
26449
- () => callback(),
26450
- (error62) => callback(error62 instanceof Error ? error62 : new Error(String(error62)))
26451
- );
26452
- },
26453
- final(callback) {
26454
- handle.closeStdin().then(
26455
- () => callback(),
26456
- (error62) => callback(error62 instanceof Error ? error62 : new Error(String(error62)))
26457
- );
26458
- }
26459
- });
26460
- 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
+ }
26461
26925
  }
26462
26926
 
26463
26927
  // src/grokInstance.ts
26464
26928
  var RESUME_SCHEMA_VERSION = 1;
26465
- var CLIENT_INFO = { name: "ycodium-agent-grok", version: "0.1.0" };
26466
26929
  var yieldTick = () => new Promise((resolve2) => {
26467
26930
  setImmediate(resolve2);
26468
26931
  });
@@ -26522,21 +26985,15 @@ function resolveAccountHomePath(recipe, configuredHome) {
26522
26985
  }
26523
26986
  return resolveGrokHomePath(configuredHome);
26524
26987
  }
26525
- function compactEnv(env) {
26526
- const out = {};
26527
- for (const [key, value] of Object.entries(env)) {
26528
- if (value !== void 0) out[key] = value;
26529
- }
26530
- return out;
26531
- }
26532
- function buildSpawnEnv(recipe, launch, resolvedHomePath, homeConfigured) {
26988
+ function buildSpawnEnv(recipe, resolvedHomePath, homeConfigured, launchEnv = {}) {
26533
26989
  const homeInjection = homeConfigured ? { [GROK_CARD.homeEnvVar]: resolvedHomePath } : {};
26534
- return {
26535
- ...compactEnv(launch.processEnv),
26536
- ...homeInjection,
26537
- ...recipe.environment,
26538
- GROK_OAUTH2_REFERRER: "ycodium"
26539
- };
26990
+ return mergeLaunchEnv({
26991
+ brainSlots: brainSlotEnvKeys(GROK_CARD),
26992
+ accountHome: homeInjection,
26993
+ instanceTable: recipe.environment,
26994
+ handOverrides: { GROK_OAUTH2_REFERRER: "ycodium" },
26995
+ launch: launchEnv
26996
+ });
26540
26997
  }
26541
26998
  function nowIso() {
26542
26999
  return (/* @__PURE__ */ new Date()).toISOString();
@@ -26580,24 +27037,49 @@ var createGrokInstance = (config2, host) => {
26580
27037
  return { status: "unknown" };
26581
27038
  }
26582
27039
  };
26583
- const instance = {
26584
- probe: async () => {
26585
- if (recipe.binaryPath.length === 0) {
26586
- return {
26587
- installed: false,
26588
- version: null,
26589
- readiness: "error",
26590
- auth: { status: "unknown" },
26591
- detail: "Grok program path is not configured"
26592
- };
26593
- }
27040
+ const probe = async () => {
27041
+ if (recipe.binaryPath.length === 0) {
26594
27042
  return {
26595
- installed: true,
27043
+ installed: false,
26596
27044
  version: null,
26597
- readiness: "unchecked",
26598
- auth: probeAuth()
27045
+ readiness: "error",
27046
+ auth: { status: "unknown" },
27047
+ detail: "Grok program path is not configured"
26599
27048
  };
26600
- },
27049
+ }
27050
+ const configuredHome = recipe.homePath;
27051
+ const outcome = await probeGrokModels({
27052
+ recipe,
27053
+ host,
27054
+ env: buildSpawnEnv(
27055
+ recipe,
27056
+ resolveGrokHomePath(configuredHome),
27057
+ configuredHome.trim().length > 0
27058
+ )
27059
+ });
27060
+ if (outcome.kind !== "answered") {
27061
+ return {
27062
+ installed: outcome.kind === "failed",
27063
+ version: null,
27064
+ readiness: "error",
27065
+ auth: probeAuth(),
27066
+ ...outcome.detail === void 0 ? {} : { detail: outcome.detail },
27067
+ // 装着但握不上手时,界面照样画权限格。不报档就退回宿主四档,而那四档对这双手
27068
+ // 是撒谎的——这台开发机的 grok 就长期停在这个出口上。
27069
+ ...outcome.kind === "failed" ? GROK_NATIVE_MODE_REPORT : {}
27070
+ };
27071
+ }
27072
+ return {
27073
+ installed: true,
27074
+ version: null,
27075
+ readiness: "ready",
27076
+ auth: probeAuth(),
27077
+ ...outcome.models === void 0 ? {} : { models: outcome.models },
27078
+ ...GROK_NATIVE_MODE_REPORT
27079
+ };
27080
+ };
27081
+ const instance = {
27082
+ probe,
26601
27083
  startSession: async (input2, signal) => {
26602
27084
  if (disposed) throw new Error("instance disposed");
26603
27085
  if (signal?.aborted) throw new Error("aborted");
@@ -26613,11 +27095,18 @@ var createGrokInstance = (config2, host) => {
26613
27095
  }
26614
27096
  const configuredHome = input2.launch.homePath?.trim() || recipe.homePath;
26615
27097
  const resolvedHomePath = resolveGrokHomePath(configuredHome);
26616
- const env = buildSpawnEnv(recipe, input2.launch, resolvedHomePath, configuredHome.length > 0);
27098
+ const env = buildSpawnEnv(
27099
+ recipe,
27100
+ resolvedHomePath,
27101
+ configuredHome.length > 0,
27102
+ input2.launch.processEnv
27103
+ );
26617
27104
  const effort = modelSelectionStringOption(input2.modelSelection, GROK_REASONING_OPTION_ID);
26618
27105
  const baseArgs = tokenizeLaunchArgs(recipe.launchArgs);
26619
27106
  const insertAt = baseArgs.length <= 1 ? baseArgs.length : baseArgs.length - 1;
27107
+ const permissionMode = grokPermissionModeFor(input2.runtimeMode, input2.nativeModeId);
26620
27108
  const args = [
27109
+ ...grokPermissionModeArgs(input2.runtimeMode, permissionMode),
26621
27110
  ...baseArgs.slice(0, insertAt),
26622
27111
  ...grokReasoningEffortArgs(effort),
26623
27112
  ...baseArgs.slice(insertAt)
@@ -26629,7 +27118,7 @@ var createGrokInstance = (config2, host) => {
26629
27118
  owner: { bindingId: host.bindingId },
26630
27119
  ...Object.keys(env).length > 0 ? { env } : {}
26631
27120
  });
26632
- const { readable, writable } = nodeStreamsFromHandle(handle);
27121
+ const { readable, writable } = nodeStreamsFromHandle2(handle);
26633
27122
  const resume = input2.discardResumeCursor === true ? void 0 : parseAcpResumeCursor(input2.resumeCursor, RESUME_SCHEMA_VERSION);
26634
27123
  const requestedModelId = input2.launch.requestedModelId?.trim() || modelIdFromSelection(input2.modelSelection);
26635
27124
  let initializeResult;
@@ -26640,7 +27129,8 @@ var createGrokInstance = (config2, host) => {
26640
27129
  threadId: input2.threadId,
26641
27130
  provider: GROK_DRIVER_KIND,
26642
27131
  providerInstanceId: host.instanceId,
26643
- clientInfo: CLIENT_INFO,
27132
+ clientInfo: GROK_CLIENT_INFO,
27133
+ log: host.log,
26644
27134
  // 等 `initialize` 回来再挑认证方式:能不能用 `cached_token`,只有 agent
26645
27135
  // 播报的 `authMethods` 说了算(见 resolveGrokAuthMethodId)。
26646
27136
  authMethodId: resolveGrokAuthMethodId,
@@ -26694,6 +27184,7 @@ var createGrokInstance = (config2, host) => {
26694
27184
  providerInstanceId: host.instanceId,
26695
27185
  status: "idle",
26696
27186
  runtimeMode: input2.runtimeMode,
27187
+ nativeModeId: permissionMode,
26697
27188
  cwd,
26698
27189
  threadId: input2.threadId,
26699
27190
  createdAt,
@@ -26713,6 +27204,27 @@ var createGrokInstance = (config2, host) => {
26713
27204
  threadId: input2.threadId,
26714
27205
  sessionId: started.sessionId
26715
27206
  });
27207
+ const caveat = grokRuntimeModeCaveat(permissionMode);
27208
+ if (caveat !== void 0) {
27209
+ events.push({
27210
+ type: "runtime.warning",
27211
+ eventId: NodeCrypto2.randomUUID(),
27212
+ provider: GROK_DRIVER_KIND,
27213
+ providerInstanceId: host.instanceId,
27214
+ threadId: input2.threadId,
27215
+ createdAt: nowIso(),
27216
+ payload: {
27217
+ message: caveat.message,
27218
+ detail: {
27219
+ code: caveat.code,
27220
+ nativeModeId: caveat.nativeModeId,
27221
+ asked: caveat.asked,
27222
+ unasked: caveat.unasked,
27223
+ externalRulesPaths: caveat.externalRulesPaths
27224
+ }
27225
+ }
27226
+ });
27227
+ }
26716
27228
  return session;
26717
27229
  } catch (error62) {
26718
27230
  await executor.dispose().catch(() => void 0);
@@ -26782,14 +27294,6 @@ var createGrokInstance = (config2, host) => {
26782
27294
  const booked = requireThread(threadId);
26783
27295
  await booked.executor.respondToUserInput(requestId, answers);
26784
27296
  },
26785
- setRuntimeMode: async (threadId, runtimeMode) => {
26786
- const booked = requireThread(threadId);
26787
- booked.session = {
26788
- ...booked.session,
26789
- runtimeMode,
26790
- updatedAt: nowIso()
26791
- };
26792
- },
26793
27297
  stopSession: async (threadId) => {
26794
27298
  const booked = threads.get(threadId);
26795
27299
  if (booked === void 0) return;
@@ -26878,23 +27382,7 @@ var createGrokInstance = (config2, host) => {
26878
27382
  await operation.handle.kill("SIGTERM").catch(() => void 0);
26879
27383
  await operation.stdoutText.catch(() => void 0);
26880
27384
  },
26881
- verify: async () => {
26882
- if (recipe.binaryPath.length === 0) {
26883
- return {
26884
- installed: false,
26885
- version: null,
26886
- readiness: "error",
26887
- auth: { status: "unknown" },
26888
- detail: "Grok program path is not configured"
26889
- };
26890
- }
26891
- return {
26892
- installed: true,
26893
- version: null,
26894
- readiness: "unchecked",
26895
- auth: probeAuth()
26896
- };
26897
- }
27385
+ verify: () => probe()
26898
27386
  }
26899
27387
  };
26900
27388
  return instance;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ycodium-ai/agent-grok",
3
- "version": "0.2.2092",
3
+ "version": "0.2.2126",
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-acp-protocol": "0.0.0",
24
- "@ycodium-ai/plugin-api": "0.2.2092",
25
- "ycodium-acp-executor": "0.0.0"
23
+ "@ycodium-ai/plugin-api": "0.2.2126",
24
+ "ycodium-acp-executor": "0.0.0",
25
+ "ycodium-acp-protocol": "0.0.0"
26
26
  },
27
27
  "ycodium": {
28
28
  "plugin": "./dist/index.js",