@ycodium-ai/agent-grok 0.2.3681 → 0.2.3766

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.
package/dist/index.js CHANGED
@@ -6,6 +6,11 @@ var __export = (target, all) => {
6
6
 
7
7
  // src/index.ts
8
8
  import { definePlugin } from "@ycodium-ai/plugin-api";
9
+ import {
10
+ EXECUTOR_EFFECTIVE_PAYLOAD_SCHEMA,
11
+ executorHomeEffectiveValue,
12
+ readExecutorEffectiveRequest
13
+ } from "@ycodium-ai/plugin-api/executor";
9
14
 
10
15
  // src/card.ts
11
16
  var GROK_DRIVER_KIND = "grok";
@@ -23,6 +28,8 @@ var GROK_CARD = {
23
28
  // `grokInstance.startSession` 里按用户选择拼。
24
29
  launchArgs: "agent stdio",
25
30
  homeEnvVar: "GROK_HOME",
31
+ // 设置表单里留空的格子此刻会用什么,见 index.ts 的同名命令。
32
+ effectiveCommand: "effectiveConfig",
26
33
  // 只有一个账户目录,它同时装着会话历史:换目录,已开的 thread 就接不上这个实例了。
27
34
  accountDirectory: {
28
35
  configKeys: ["homePath"],
@@ -161,6 +168,166 @@ var GROK_CARD = {
161
168
  }
162
169
  };
163
170
 
171
+ // src/grokHome.ts
172
+ import * as NodeCrypto from "node:crypto";
173
+ import * as NodeOS from "node:os";
174
+ import * as NodePath2 from "node:path";
175
+ import { mergeLaunchEnv } from "@ycodium-ai/plugin-api/executor";
176
+
177
+ // src/grokAccount.ts
178
+ import * as NodeFS from "node:fs";
179
+ import * as NodePath from "node:path";
180
+ var AUTH_FILE_NAME = "auth.json";
181
+ var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
182
+ var readString = (record4, key) => {
183
+ const value = record4[key];
184
+ if (typeof value !== "string") return void 0;
185
+ const trimmed3 = value.trim();
186
+ return trimmed3.length > 0 ? trimmed3 : void 0;
187
+ };
188
+ var parseJsonRecord = (raw) => {
189
+ let parsed;
190
+ try {
191
+ parsed = JSON.parse(raw);
192
+ } catch {
193
+ return void 0;
194
+ }
195
+ return isRecord(parsed) ? parsed : void 0;
196
+ };
197
+ var directoryExists = (homePath) => {
198
+ try {
199
+ return NodeFS.statSync(homePath).isDirectory();
200
+ } catch {
201
+ return false;
202
+ }
203
+ };
204
+ var readCredentialFile = (path) => {
205
+ try {
206
+ return NodeFS.readFileSync(path, "utf8");
207
+ } catch {
208
+ return void 0;
209
+ }
210
+ };
211
+ var signedOutProbe = (exists) => ({
212
+ state: "signed-out",
213
+ directoryExists: exists,
214
+ email: void 0,
215
+ name: void 0,
216
+ expiresAt: void 0
217
+ });
218
+ var unreadableProbe = (exists) => ({
219
+ state: "unreadable",
220
+ directoryExists: exists,
221
+ email: void 0,
222
+ name: void 0,
223
+ expiresAt: void 0
224
+ });
225
+ var joinName = (record4) => {
226
+ const first = readString(record4, "first_name");
227
+ const last = readString(record4, "last_name");
228
+ const joined = [first, last].filter((part) => part !== void 0).join(" ");
229
+ return joined.length > 0 ? joined : void 0;
230
+ };
231
+ var findIdentityEntry = (parsed) => {
232
+ for (const value of Object.values(parsed)) {
233
+ if (!isRecord(value)) continue;
234
+ if (readString(value, "email") !== void 0 || readString(value, "user_id") !== void 0) {
235
+ return value;
236
+ }
237
+ }
238
+ return void 0;
239
+ };
240
+ var isExpired = (expiresAt, nowMs) => {
241
+ if (expiresAt === void 0) return false;
242
+ const parsed = Date.parse(expiresAt);
243
+ return Number.isNaN(parsed) ? false : parsed <= nowMs;
244
+ };
245
+ var readGrokAccount = (homePath) => {
246
+ const exists = directoryExists(homePath);
247
+ const raw = readCredentialFile(NodePath.join(homePath, AUTH_FILE_NAME));
248
+ if (raw === void 0) return signedOutProbe(exists);
249
+ const parsed = parseJsonRecord(raw);
250
+ const identity = parsed === void 0 ? void 0 : findIdentityEntry(parsed);
251
+ if (identity === void 0) return unreadableProbe(exists);
252
+ const expiresAt = readString(identity, "expires_at");
253
+ return {
254
+ state: isExpired(expiresAt, Date.now()) ? "expired" : "signed-in",
255
+ directoryExists: exists,
256
+ email: readString(identity, "email"),
257
+ name: joinName(identity),
258
+ expiresAt
259
+ };
260
+ };
261
+
262
+ // src/grokHome.ts
263
+ function expandHomePath(value) {
264
+ if (value === "~") return NodeOS.homedir();
265
+ if (value.startsWith("~/") || value.startsWith("~\\")) {
266
+ return NodePath2.join(NodeOS.homedir(), value.slice(2));
267
+ }
268
+ return value;
269
+ }
270
+ function resolveGrokHomePath(value) {
271
+ const trimmed3 = value.trim();
272
+ if (trimmed3.length === 0) return NodePath2.join(NodeOS.homedir(), ".grok");
273
+ return NodePath2.resolve(expandHomePath(trimmed3));
274
+ }
275
+ function probeGrokAccount(accountPath) {
276
+ const resolved = resolveGrokHomePath(accountPath);
277
+ const account = readGrokAccount(resolved);
278
+ return {
279
+ accountPath: resolved,
280
+ state: account.state,
281
+ ...account.email === void 0 ? {} : { email: account.email },
282
+ ...account.name === void 0 ? {} : { name: account.name },
283
+ ...account.expiresAt === void 0 ? {} : { expiresAt: account.expiresAt }
284
+ };
285
+ }
286
+ function grokContinuationKey(recipe) {
287
+ return `grok:home:${resolveGrokHomePath(recipe.homePath)}`;
288
+ }
289
+ function resolveAccountHomePath(recipe, configuredHome) {
290
+ const fromEnvironment = recipe.environment["GROK_HOME"]?.trim();
291
+ if (fromEnvironment !== void 0 && fromEnvironment.length > 0) {
292
+ return NodePath2.resolve(expandHomePath(fromEnvironment));
293
+ }
294
+ return resolveGrokHomePath(configuredHome);
295
+ }
296
+ function absentPath() {
297
+ return NodePath2.join(
298
+ NodeOS.tmpdir(),
299
+ `ycodium-grok-no-auth-${NodeCrypto.randomUUID()}`,
300
+ "auth.json"
301
+ );
302
+ }
303
+ function ownEndpointAuthGuard(recipe) {
304
+ const connectivity = GROK_CARD.modelConnectivity;
305
+ const baseUrlKey = connectivity.baseUrlEnvKey;
306
+ if (baseUrlKey === void 0 || (recipe.environment[baseUrlKey]?.trim() ?? "") === "") return {};
307
+ const guard = {};
308
+ for (const entry of connectivity.extraEnv ?? []) {
309
+ if (entry.value === "$absentPath") guard[entry.name] = absentPath();
310
+ }
311
+ return guard;
312
+ }
313
+ function buildSignInEnv(recipe) {
314
+ const configuredHome = recipe.homePath;
315
+ const homeInjection = configuredHome.trim().length > 0 ? { [GROK_CARD.homeEnvVar]: resolveGrokHomePath(configuredHome) } : {};
316
+ return mergeLaunchEnv({
317
+ accountHome: homeInjection,
318
+ instanceTable: recipe.environment,
319
+ handOverrides: { GROK_OAUTH2_REFERRER: "ycodium" }
320
+ });
321
+ }
322
+ function buildSpawnEnv(recipe, resolvedHomePath, homeConfigured) {
323
+ const homeInjection = homeConfigured ? { [GROK_CARD.homeEnvVar]: resolvedHomePath } : {};
324
+ return mergeLaunchEnv({
325
+ accountHome: { ...homeInjection, ...ownEndpointAuthGuard(recipe) },
326
+ instanceTable: recipe.environment,
327
+ handOverrides: { GROK_OAUTH2_REFERRER: "ycodium" }
328
+ });
329
+ }
330
+
164
331
  // src/grokInstance.ts
165
332
  import * as NodeCrypto3 from "node:crypto";
166
333
  import {
@@ -169,7 +336,7 @@ import {
169
336
  import { nodeStreamsFromHandle as nodeStreamsFromHandle2 } from "@ycodium-ai/plugin-api/process-stdin";
170
337
 
171
338
  // ../../packages/ycodium-acp-executor/src/executor.ts
172
- import * as NodeCrypto from "node:crypto";
339
+ import * as NodeCrypto2 from "node:crypto";
173
340
 
174
341
  // ../../packages/ycodium-acp-executor/src/approvals.ts
175
342
  function createApprovalGate(input2) {
@@ -397,7 +564,7 @@ var UnboundedEventStream = class {
397
564
  };
398
565
 
399
566
  // ../../packages/ycodium-acp-executor/src/config-options.ts
400
- function isRecord(value) {
567
+ function isRecord2(value) {
401
568
  return typeof value === "object" && value !== null && !Array.isArray(value);
402
569
  }
403
570
  function asString(value) {
@@ -416,7 +583,7 @@ function flattenChoices(rawOptions, currentValue) {
416
583
  const out = [];
417
584
  const seen = /* @__PURE__ */ new Set();
418
585
  const push = (entry) => {
419
- if (!isRecord(entry)) return;
586
+ if (!isRecord2(entry)) return;
420
587
  const value = asString(entry["value"]);
421
588
  if (value === void 0 || seen.has(value)) return;
422
589
  seen.add(value);
@@ -431,7 +598,7 @@ function flattenChoices(rawOptions, currentValue) {
431
598
  });
432
599
  };
433
600
  for (const entry of rawOptions) {
434
- if (isRecord(entry) && Array.isArray(entry["options"])) {
601
+ if (isRecord2(entry) && Array.isArray(entry["options"])) {
435
602
  for (const nested of entry["options"]) push(nested);
436
603
  continue;
437
604
  }
@@ -440,7 +607,7 @@ function flattenChoices(rawOptions, currentValue) {
440
607
  return out;
441
608
  }
442
609
  function isConfigOption(entry, configId) {
443
- return isRecord(entry) && asString(entry["id"])?.trim() === configId.trim();
610
+ return isRecord2(entry) && asString(entry["id"])?.trim() === configId.trim();
444
611
  }
445
612
  function findConfigOptionEntry(configOptions, configId) {
446
613
  return Array.isArray(configOptions) && configId.trim() !== "" ? configOptions.find(
@@ -455,7 +622,7 @@ function withConfigOptionValue(configOptions, configId, value) {
455
622
  }
456
623
  function pickEffortOption(configOptions) {
457
624
  if (!Array.isArray(configOptions)) return void 0;
458
- const candidates = configOptions.filter(isRecord).filter((option) => normalizedField(option, "type") === "select").filter(looksLikeEffortOption);
625
+ const candidates = configOptions.filter(isRecord2).filter((option) => normalizedField(option, "type") === "select").filter(looksLikeEffortOption);
459
626
  return 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];
460
627
  }
461
628
  function findAcpEffortConfig(configOptions) {
@@ -475,7 +642,7 @@ function findAcpEffortConfig(configOptions) {
475
642
  }
476
643
 
477
644
  // ../../packages/ycodium-acp-executor/src/util.ts
478
- function isRecord2(value) {
645
+ function isRecord3(value) {
479
646
  return typeof value === "object" && value !== null && !Array.isArray(value);
480
647
  }
481
648
  function asTrimmedString(value) {
@@ -530,11 +697,11 @@ function extractCommandFromTitle(title) {
530
697
  return backtickMatch?.[1]?.trim() || void 0;
531
698
  }
532
699
  function extractToolCommand(data, title) {
533
- const item = isRecord2(data?.["item"]) ? data["item"] : void 0;
534
- const itemInput = item && isRecord2(item["input"]) ? item["input"] : void 0;
535
- const itemResult = item && isRecord2(item["result"]) ? item["result"] : void 0;
536
- const rawInput = isRecord2(data?.["rawInput"]) ? data["rawInput"] : void 0;
537
- const dataInput = isRecord2(data?.["input"]) ? data["input"] : void 0;
700
+ const item = isRecord3(data?.["item"]) ? data["item"] : void 0;
701
+ const itemInput = item && isRecord3(item["input"]) ? item["input"] : void 0;
702
+ const itemResult = item && isRecord3(item["result"]) ? item["result"] : void 0;
703
+ const rawInput = isRecord3(data?.["rawInput"]) ? data["rawInput"] : void 0;
704
+ const dataInput = isRecord3(data?.["input"]) ? data["input"] : void 0;
538
705
  const candidates = [
539
706
  normalizeCommandValue(item?.["command"]),
540
707
  normalizeCommandValue(itemInput?.["command"]),
@@ -579,7 +746,7 @@ function collectPaths(value, paths, seen, depth) {
579
746
  }
580
747
  return;
581
748
  }
582
- if (!isRecord2(value)) {
749
+ if (!isRecord3(value)) {
583
750
  return;
584
751
  }
585
752
  for (const key of ["path", "filePath", "relativePath", "filename", "newPath", "oldPath"]) {
@@ -657,7 +824,7 @@ function deriveToolActivityPresentation(input2) {
657
824
  const title = asTrimmedString(input2.title);
658
825
  const detail = stripTrailingExitCode(asTrimmedString(input2.detail));
659
826
  const fallbackSummary = asTrimmedString(input2.fallbackSummary) ?? "Tool";
660
- const data = isRecord2(input2.data) ? input2.data : void 0;
827
+ const data = isRecord3(input2.data) ? input2.data : void 0;
661
828
  const command = extractToolCommand(data, title);
662
829
  const primaryPath = extractPrimaryPath(data);
663
830
  const action = classifyToolAction({
@@ -689,7 +856,7 @@ function deriveToolActivityPresentation(input2) {
689
856
  };
690
857
  }
691
858
  if (action === "search") {
692
- const rawInput = isRecord2(data?.["rawInput"]) ? data["rawInput"] : void 0;
859
+ const rawInput = isRecord3(data?.["rawInput"]) ? data["rawInput"] : void 0;
693
860
  const query = asTrimmedString(rawInput?.["query"]) ?? asTrimmedString(rawInput?.["pattern"]) ?? asTrimmedString(rawInput?.["searchTerm"]);
694
861
  return {
695
862
  summary: "Searched files",
@@ -749,7 +916,7 @@ function extractCommandFromTitle2(title) {
749
916
  return match?.[1]?.trim() || void 0;
750
917
  }
751
918
  function extractToolCallCommand(rawInput, title) {
752
- if (isRecord2(rawInput)) {
919
+ if (isRecord3(rawInput)) {
753
920
  const directCommand = normalizeCommandValue2(rawInput["command"]);
754
921
  if (directCommand) {
755
922
  return directCommand;
@@ -777,7 +944,7 @@ function boundTailText(text4, limit = TOOL_CALL_CONTENT_MAX_CHARS) {
777
944
  var boundToolCallOutputText = (text4) => boundTailText(text4);
778
945
  var RAW_OUTPUT_TEXT_FIELDS = ["content", "stdout", "stderr", "output"];
779
946
  function boundToolCallRawOutput(rawOutput) {
780
- if (!isRecord2(rawOutput)) {
947
+ if (!isRecord3(rawOutput)) {
781
948
  return rawOutput;
782
949
  }
783
950
  let changed = false;
@@ -969,11 +1136,11 @@ function collectSessionConfigOptionValues(configOption) {
969
1136
  );
970
1137
  }
971
1138
  function collectSessionModelIds(models) {
972
- if (!isRecord2(models) || !Array.isArray(models["availableModels"])) {
1139
+ if (!isRecord3(models) || !Array.isArray(models["availableModels"])) {
973
1140
  return [];
974
1141
  }
975
1142
  return models["availableModels"].flatMap(
976
- (model) => isRecord2(model) && typeof model["modelId"] === "string" ? [model["modelId"]] : []
1143
+ (model) => isRecord3(model) && typeof model["modelId"] === "string" ? [model["modelId"]] : []
977
1144
  );
978
1145
  }
979
1146
  function catalogModelLastSegment(value) {
@@ -1045,7 +1212,7 @@ function parseAvailableCommandsUpdate(params) {
1045
1212
  }
1046
1213
  const commands = [];
1047
1214
  for (const command of availableCommands) {
1048
- if (!isRecord2(command)) {
1215
+ if (!isRecord3(command)) {
1049
1216
  continue;
1050
1217
  }
1051
1218
  const name = typeof command["name"] === "string" ? command["name"].trim() : "";
@@ -1053,7 +1220,7 @@ function parseAvailableCommandsUpdate(params) {
1053
1220
  continue;
1054
1221
  }
1055
1222
  const description = typeof command["description"] === "string" ? command["description"].trim() || void 0 : void 0;
1056
- const input2 = isRecord2(command["input"]) ? command["input"] : void 0;
1223
+ const input2 = isRecord3(command["input"]) ? command["input"] : void 0;
1057
1224
  const inputHint = typeof input2?.["hint"] === "string" ? input2["hint"].trim() || void 0 : void 0;
1058
1225
  commands.push({
1059
1226
  name,
@@ -1107,7 +1274,7 @@ function boundToolCallRawPayload(params, update, toolCall) {
1107
1274
  }
1108
1275
  function textFromContentBlocks(blocks) {
1109
1276
  const parts = blocks.flatMap(
1110
- (block) => isRecord2(block) && block["type"] === "text" && typeof block["text"] === "string" ? [block["text"]] : []
1277
+ (block) => isRecord3(block) && block["type"] === "text" && typeof block["text"] === "string" ? [block["text"]] : []
1111
1278
  );
1112
1279
  const joined = parts.join("");
1113
1280
  return joined.trim().length > 0 ? joined : void 0;
@@ -1132,7 +1299,7 @@ function parseSessionUpdateEvent(params) {
1132
1299
  const entries = Array.isArray(upd["entries"]) ? upd["entries"] : [];
1133
1300
  const plan = entries.map((entry, index) => ({
1134
1301
  step: readAcpText(entry, "content") ?? `Step ${index + 1}`,
1135
- status: normalizePlanStepStatus(isRecord2(entry) ? entry["status"] : void 0)
1302
+ status: normalizePlanStepStatus(isRecord3(entry) ? entry["status"] : void 0)
1136
1303
  }));
1137
1304
  if (plan.length > 0) {
1138
1305
  events.push({
@@ -1172,7 +1339,7 @@ function parseSessionUpdateEvent(params) {
1172
1339
  }
1173
1340
  case "agent_message_chunk":
1174
1341
  case "agent_thought_chunk": {
1175
- const content = isRecord2(upd["content"]) ? upd["content"] : void 0;
1342
+ const content = isRecord3(upd["content"]) ? upd["content"] : void 0;
1176
1343
  const contentType = content?.["type"];
1177
1344
  const streamKind = kind === "agent_thought_chunk" ? "reasoning_text" : "assistant_text";
1178
1345
  if (contentType === "text") {
@@ -1212,7 +1379,7 @@ function parseSessionUpdateEvent(params) {
1212
1379
  }
1213
1380
  case "compaction_summary_chunk": {
1214
1381
  const compactionId = readAcpText(upd, "compactionId");
1215
- const content = isRecord2(upd["content"]) ? upd["content"] : void 0;
1382
+ const content = isRecord3(upd["content"]) ? upd["content"] : void 0;
1216
1383
  const text4 = content?.["type"] === "text" ? content["text"] : void 0;
1217
1384
  if (compactionId === void 0 || typeof text4 !== "string" || text4.length === 0) break;
1218
1385
  events.push({ _tag: "CompactionSummaryChunk", compactionId, text: text4 });
@@ -22906,17 +23073,17 @@ function isResponseMessage(value) {
22906
23073
  function isNotificationMessage(value) {
22907
23074
  return isJsonRpcEnvelope(value) && !("id" in value) && typeof value["method"] === "string";
22908
23075
  }
22909
- function isRecord3(value) {
23076
+ function isRecord4(value) {
22910
23077
  return typeof value === "object" && value !== null;
22911
23078
  }
22912
23079
  function isJsonRpcEnvelope(value) {
22913
- return isRecord3(value) && value["jsonrpc"] === "2.0";
23080
+ return isRecord4(value) && value["jsonrpc"] === "2.0";
22914
23081
  }
22915
23082
  function isJsonRpcId(value) {
22916
23083
  return value === null || typeof value === "string" || typeof value === "number" && Number.isFinite(value);
22917
23084
  }
22918
23085
  function isResponseShapedMessage(value) {
22919
- return isRecord3(value) && !("method" in value) && ("id" in value || "result" in value || "error" in value);
23086
+ return isRecord4(value) && !("method" in value) && ("id" in value || "result" in value || "error" in value);
22920
23087
  }
22921
23088
  function isResponseBatch(batch) {
22922
23089
  let hasValidCall = false;
@@ -22926,7 +23093,7 @@ function isResponseBatch(batch) {
22926
23093
  for (const entry of batch) {
22927
23094
  hasValidCall ||= isRequestMessage(entry) || isNotificationMessage(entry);
22928
23095
  hasValidResponse ||= isResponseMessage(entry);
22929
- if (!isRecord3(entry)) {
23096
+ if (!isRecord4(entry)) {
22930
23097
  continue;
22931
23098
  }
22932
23099
  hasCallShape ||= "method" in entry;
@@ -22941,13 +23108,13 @@ function isResponseBatch(batch) {
22941
23108
  return hasResponseShape && !hasCallShape;
22942
23109
  }
22943
23110
  function cancelRequestId(params) {
22944
- if (!isRecord3(params) || !isJsonRpcId(params["requestId"])) {
23111
+ if (!isRecord4(params) || !isJsonRpcId(params["requestId"])) {
22945
23112
  return void 0;
22946
23113
  }
22947
23114
  return params["requestId"];
22948
23115
  }
22949
23116
  function isErrorResponse(value) {
22950
- return isRecord3(value) && typeof value["code"] === "number" && Number.isInteger(value["code"]) && typeof value["message"] === "string";
23117
+ return isRecord4(value) && typeof value["code"] === "number" && Number.isInteger(value["code"]) && typeof value["message"] === "string";
22951
23118
  }
22952
23119
  var Handled = {
22953
23120
  /**
@@ -23492,7 +23659,7 @@ var Connection = class {
23492
23659
  if (this.abortController.signal.aborted) {
23493
23660
  return Promise.resolve();
23494
23661
  }
23495
- if (!isRecord3(message)) {
23662
+ if (!isRecord4(message)) {
23496
23663
  console.error("Invalid message", { message });
23497
23664
  return Promise.resolve();
23498
23665
  }
@@ -23895,7 +24062,7 @@ function ndJsonStream(output2, input2) {
23895
24062
  await writeJson(protocolErrorResponse(RequestError.parseError()));
23896
24063
  return;
23897
24064
  }
23898
- if (isRecord3(message) || Array.isArray(message)) {
24065
+ if (isRecord4(message) || Array.isArray(message)) {
23899
24066
  controller.enqueue(message);
23900
24067
  } else {
23901
24068
  await writeJson(protocolErrorResponse(RequestError.invalidRequest(message)));
@@ -24882,14 +25049,14 @@ var legacyClientNotificationMethods = /* @__PURE__ */ new Set([
24882
25049
  import * as NodeStream from "node:stream";
24883
25050
 
24884
25051
  // ../../packages/ycodium-acp-executor/src/jsonrpc.ts
24885
- function isRecord4(value) {
25052
+ function isRecord5(value) {
24886
25053
  return typeof value === "object" && value !== null;
24887
25054
  }
24888
25055
  function isJsonRpcId2(value) {
24889
25056
  return typeof value === "string" || typeof value === "number" && Number.isFinite(value);
24890
25057
  }
24891
25058
  function isJsonRpcEnvelope2(value) {
24892
- return isRecord4(value) && value["jsonrpc"] === "2.0";
25059
+ return isRecord5(value) && value["jsonrpc"] === "2.0";
24893
25060
  }
24894
25061
  function isRequestMessage2(value) {
24895
25062
  return isJsonRpcEnvelope2(value) && "id" in value && typeof value["method"] === "string" && isJsonRpcId2(value["id"]);
@@ -24909,7 +25076,7 @@ function isNotificationMessage2(value) {
24909
25076
  return isJsonRpcEnvelope2(value) && !("id" in value) && typeof value["method"] === "string";
24910
25077
  }
24911
25078
  function readJsonRpcError(value) {
24912
- if (!isRecord4(value) || typeof value["code"] !== "number" || typeof value["message"] !== "string") {
25079
+ if (!isRecord5(value) || typeof value["code"] !== "number" || typeof value["message"] !== "string") {
24913
25080
  return void 0;
24914
25081
  }
24915
25082
  return {
@@ -24982,11 +25149,11 @@ var KNOWN_SESSION_UPDATES = /* @__PURE__ */ new Set([
24982
25149
  "compaction_summary_chunk"
24983
25150
  ]);
24984
25151
  function isSessionUpdateEnvelope(payload) {
24985
- if (!isRecord4(payload) || typeof payload["sessionId"] !== "string") {
25152
+ if (!isRecord5(payload) || typeof payload["sessionId"] !== "string") {
24986
25153
  return false;
24987
25154
  }
24988
25155
  const update = payload["update"];
24989
- return isRecord4(update) && typeof update["sessionUpdate"] === "string";
25156
+ return isRecord5(update) && typeof update["sessionUpdate"] === "string";
24990
25157
  }
24991
25158
  function hasKnownVariantShape(update) {
24992
25159
  const kind = update["sessionUpdate"];
@@ -24994,7 +25161,7 @@ function hasKnownVariantShape(update) {
24994
25161
  return false;
24995
25162
  }
24996
25163
  if (kind === "user_message_chunk" || kind === "agent_message_chunk" || kind === "agent_thought_chunk") {
24997
- return isRecord4(update["content"]);
25164
+ return isRecord5(update["content"]);
24998
25165
  }
24999
25166
  return true;
25000
25167
  }
@@ -25381,29 +25548,37 @@ function createAcpConnection(options) {
25381
25548
  function normalizeModelId(value) {
25382
25549
  return value?.trim() || void 0;
25383
25550
  }
25384
- function adoptReportedConfigOptions(state, configOptions) {
25551
+ function adoptReportedConfigOptions(state, configOptions, fromAgent = true) {
25385
25552
  const option = state.modelConfigId === void 0 ? void 0 : findConfigOptionEntry(configOptions, state.modelConfigId);
25386
25553
  const value = option?.["currentValue"];
25387
25554
  const reported = typeof value === "string" ? normalizeModelId(value) : void 0;
25388
25555
  if (reported === void 0) return { ...state, configOptions };
25389
25556
  const { modelUnconfirmed: _confirmed, ...known } = state;
25390
- return { ...known, configOptions, currentModelId: reported };
25557
+ const stillReported = fromAgent || state.currentModelReported === true && reported === state.currentModelId;
25558
+ const { currentModelReported: _stale, ...unreported } = known;
25559
+ return {
25560
+ ...stillReported ? known : unreported,
25561
+ configOptions,
25562
+ currentModelId: reported,
25563
+ ...stillReported ? { currentModelReported: true } : {}
25564
+ };
25391
25565
  }
25392
25566
  function forgetConfigOptionValue(state, configId) {
25393
25567
  const forgetsModel = configId === void 0 || configId === state.modelConfigId;
25568
+ const { currentModelReported: _forgottenReport, ...withoutReport } = state;
25394
25569
  const configOptions = configId !== void 0 && Array.isArray(state.configOptions) ? state.configOptions.map((option) => {
25395
- if (!isRecord2(option) || option["id"] !== configId) return option;
25570
+ if (!isRecord3(option) || option["id"] !== configId) return option;
25396
25571
  const { currentValue: _forgotten, ...rest } = option;
25397
25572
  return rest;
25398
25573
  }) : state.configOptions;
25399
25574
  return {
25400
- ...state,
25575
+ ...forgetsModel ? withoutReport : state,
25401
25576
  configOptions,
25402
25577
  ...forgetsModel ? { currentModelId: void 0, modelUnconfirmed: true } : {}
25403
25578
  };
25404
25579
  }
25405
25580
  function modelSelectionStateFromSetup(setup) {
25406
- const sessionModels = isRecord2(setup.models) ? setup.models : void 0;
25581
+ const sessionModels = isRecord3(setup.models) ? setup.models : void 0;
25407
25582
  const modelConfigId = extractModelConfigId(setup);
25408
25583
  const strategy = modelConfigId !== void 0 ? "config-option" : "session/set_model";
25409
25584
  const modelConfigOption = modelConfigId === void 0 ? void 0 : findSessionConfigOption(setup.configOptions, modelConfigId);
@@ -25414,6 +25589,7 @@ function modelSelectionStateFromSetup(setup) {
25414
25589
  sessionModelsAdvertised: sessionModels !== void 0,
25415
25590
  modelConfigId,
25416
25591
  currentModelId,
25592
+ ...currentModelId === void 0 ? {} : { currentModelReported: true },
25417
25593
  advertisedModelValues: strategy === "config-option" ? modelConfigOption === void 0 ? [] : collectSessionConfigOptionValues(modelConfigOption) : collectSessionModelIds(sessionModels),
25418
25594
  configOptions: setup.configOptions,
25419
25595
  catalogSetup: setup
@@ -26201,14 +26377,28 @@ function createExecutorPrompt({
26201
26377
  }
26202
26378
  };
26203
26379
  const alignTurnOptions = async (input2, sessionId, generation, isStale, signal) => {
26380
+ const before = shared.modelSelectionState;
26204
26381
  const applied = await applyRequestedModel({
26205
- state: shared.modelSelectionState,
26382
+ state: before,
26206
26383
  acpSessionId: sessionId,
26207
26384
  requestedModelId: input2.requestedModelId,
26208
26385
  expectedGeneration: generation,
26209
26386
  signal
26210
26387
  });
26211
26388
  shared.modelSelectionState = applied;
26389
+ if (applied.currentModelId !== void 0 && (applied.currentModelId !== before.currentModelId || applied.currentModelReported !== before.currentModelReported)) {
26390
+ emit({
26391
+ type: "thread.metadata.updated",
26392
+ ...stamp(),
26393
+ provider: options.provider,
26394
+ threadId: options.threadId,
26395
+ ...shared.activeTurnId === void 0 ? {} : { turnId: shared.activeTurnId },
26396
+ payload: {
26397
+ model: applied.currentModelId,
26398
+ modelSource: applied.currentModelReported === true ? "reported" : "echo"
26399
+ }
26400
+ });
26401
+ }
26212
26402
  if (isStale()) return;
26213
26403
  const switched = applied.configOptions;
26214
26404
  let aligned;
@@ -26233,7 +26423,8 @@ function createExecutorPrompt({
26233
26423
  if (aligned.configOptions !== switched) {
26234
26424
  shared.modelSelectionState = adoptReportedConfigOptions(
26235
26425
  shared.modelSelectionState,
26236
- aligned.configOptions
26426
+ aligned.configOptions,
26427
+ false
26237
26428
  );
26238
26429
  }
26239
26430
  if (aligned.unsupportedEffort !== void 0) {
@@ -26755,7 +26946,7 @@ function createExecutorRequests({
26755
26946
  pendingUserInputs.delete(requestId);
26756
26947
  pending.resolve({
26757
26948
  kind: "answered",
26758
- answers: isRecord2(answers) ? answers : {}
26949
+ answers: isRecord3(answers) ? answers : {}
26759
26950
  });
26760
26951
  };
26761
26952
  return {
@@ -26769,7 +26960,7 @@ function createExecutorRequests({
26769
26960
  }
26770
26961
 
26771
26962
  // ../../packages/ycodium-acp-executor/src/artifacts.ts
26772
- import * as NodePath from "node:path";
26963
+ import * as NodePath3 from "node:path";
26773
26964
  import * as NodeURL from "node:url";
26774
26965
  function resourceLinkArtifacts(content) {
26775
26966
  if (content["type"] !== "resource_link" || typeof content["uri"] !== "string") return [];
@@ -26780,7 +26971,7 @@ function resourceLinkArtifacts(content) {
26780
26971
  } catch {
26781
26972
  return [];
26782
26973
  }
26783
- if (!NodePath.isAbsolute(path)) return [];
26974
+ if (!NodePath3.isAbsolute(path)) return [];
26784
26975
  const name = typeof content["name"] === "string" ? content["name"].trim() : "";
26785
26976
  const mimeType = typeof content["mimeType"] === "string" ? content["mimeType"].trim() : "";
26786
26977
  return [
@@ -27054,18 +27245,18 @@ function modelRefreshFailedError(requestedModelId, error62) {
27054
27245
  }
27055
27246
 
27056
27247
  // ../../packages/ycodium-acp-executor/src/runtime-mode.ts
27057
- function isRecord5(value) {
27248
+ function isRecord6(value) {
27058
27249
  return typeof value === "object" && value !== null && !Array.isArray(value);
27059
27250
  }
27060
27251
  function asString2(value) {
27061
27252
  return typeof value === "string" ? value : void 0;
27062
27253
  }
27063
27254
  function acpModeConfigId(setup) {
27064
- if (!isRecord5(setup)) return void 0;
27255
+ if (!isRecord6(setup)) return void 0;
27065
27256
  const configOptions = setup["configOptions"];
27066
27257
  if (!Array.isArray(configOptions)) return void 0;
27067
27258
  for (const option of configOptions) {
27068
- if (!isRecord5(option)) continue;
27259
+ if (!isRecord6(option)) continue;
27069
27260
  if (asString2(option["category"])?.trim().toLowerCase() !== "mode") continue;
27070
27261
  const id = asString2(option["id"])?.trim();
27071
27262
  if (id) return id;
@@ -27154,18 +27345,20 @@ function createExecutorSession({
27154
27345
  const base = ours !== void 0 && ours.seq > issuedAt ? ours.configOptions : configOptions;
27155
27346
  return { configOptions: withConfigOptionValue(base, configId, value), reported: false };
27156
27347
  };
27157
- const writeConfigOption = async (...args) => (await writeConfigOptionReported(...args)).configOptions;
27158
27348
  const switchModel = async (input2) => {
27159
27349
  let configOptions = input2.state.configOptions;
27350
+ let fromAgent = true;
27160
27351
  const write = { option: "model", signal: input2.signal };
27161
27352
  if (input2.state.strategy === "config-option" && input2.state.modelConfigId !== void 0) {
27162
- configOptions = await writeConfigOption(
27353
+ const written = await writeConfigOptionReported(
27163
27354
  input2.sessionId,
27164
27355
  configOptions,
27165
27356
  input2.state.modelConfigId,
27166
27357
  input2.value,
27167
27358
  write
27168
27359
  );
27360
+ configOptions = written.configOptions;
27361
+ fromAgent = written.reported;
27169
27362
  } else {
27170
27363
  const before = shared.configOptionUpdates.get(input2.sessionId);
27171
27364
  const pending = connection.begin("session/set_model", {
@@ -27183,7 +27376,12 @@ function createExecutorSession({
27183
27376
  throw new AcpExecutorDisposedError();
27184
27377
  }
27185
27378
  const { modelUnconfirmed: _confirmed, ...known } = input2.state;
27186
- return adoptReportedConfigOptions({ ...known, currentModelId: input2.value }, configOptions);
27379
+ const { currentModelReported: _echoed, ...requested } = known;
27380
+ return adoptReportedConfigOptions(
27381
+ { ...requested, currentModelId: input2.value },
27382
+ configOptions,
27383
+ fromAgent
27384
+ );
27187
27385
  };
27188
27386
  const applyRequestedModel = async (input2) => {
27189
27387
  const requestedModelId = normalizeModelId(input2.requestedModelId) ?? "";
@@ -27327,7 +27525,8 @@ function createExecutorSession({
27327
27525
  });
27328
27526
  nextModelSelectionState = adoptReportedConfigOptions(
27329
27527
  nextModelSelectionState,
27330
- aligned.configOptions
27528
+ aligned.configOptions,
27529
+ false
27331
27530
  );
27332
27531
  if (aligned.unsupportedEffort !== void 0) {
27333
27532
  options.log?.info("requested effort is not offered by this model; using its default", {
@@ -27360,7 +27559,8 @@ function createExecutorSession({
27360
27559
  initializeResult: initialized,
27361
27560
  sessionSetupResult: setup,
27362
27561
  modelConfigId: extractModelConfigId(setup),
27363
- modelId: nextModelSelectionState.currentModelId
27562
+ modelId: nextModelSelectionState.currentModelId,
27563
+ modelReported: nextModelSelectionState.currentModelReported === true
27364
27564
  };
27365
27565
  shared.startResult = result;
27366
27566
  shared.status = "ready";
@@ -27503,14 +27703,14 @@ function createExecutorSettlement({
27503
27703
 
27504
27704
  // ../../packages/ycodium-acp-executor/src/executor/subagent-sessions.ts
27505
27705
  function parseState(raw) {
27506
- if (!isRecord2(raw)) return { status: void 0, error: void 0 };
27706
+ if (!isRecord3(raw)) return { status: void 0, error: void 0 };
27507
27707
  const state = raw["state"];
27508
27708
  if (state === "running") return { status: "running", error: void 0 };
27509
27709
  if (state === "requires_action") return { status: "waiting", error: void 0 };
27510
27710
  if (state !== "idle") return { status: void 0, error: void 0 };
27511
27711
  const stopReason = raw["stopReason"];
27512
- const error62 = isRecord2(raw["error"]) ? asTrimmedString(raw["error"]["message"]) : void 0;
27513
- if (stopReason === "error" || isRecord2(raw["error"])) return { status: "failed", error: error62 };
27712
+ const error62 = isRecord3(raw["error"]) ? asTrimmedString(raw["error"]["message"]) : void 0;
27713
+ if (stopReason === "error" || isRecord3(raw["error"])) return { status: "failed", error: error62 };
27514
27714
  if (stopReason === "cancelled") return { status: "cancelled", error: void 0 };
27515
27715
  return { status: "idle", error: void 0 };
27516
27716
  }
@@ -27526,7 +27726,7 @@ function childLinkage(childId, record4) {
27526
27726
  function createSubagentSessionTracker(registry2) {
27527
27727
  const observe = (notification, ctx) => {
27528
27728
  const update = notification.update;
27529
- if (!isRecord2(update) || update["sessionUpdate"] !== "subagent_update") return [];
27729
+ if (!isRecord3(update) || update["sessionUpdate"] !== "subagent_update") return [];
27530
27730
  const childId = asTrimmedString(update["sessionId"]);
27531
27731
  if (childId === void 0 || ctx.ownSessionIds.has(childId)) return [];
27532
27732
  const outerId = notification.sessionId;
@@ -27951,9 +28151,9 @@ function readOpenToolCalls(toolCalls, turnId) {
27951
28151
  }
27952
28152
 
27953
28153
  // ../../packages/ycodium-acp-executor/src/protocol-log.ts
27954
- import * as NodeFS from "node:fs";
27955
- import * as NodeOS from "node:os";
27956
- import * as NodePath2 from "node:path";
28154
+ import * as NodeFS2 from "node:fs";
28155
+ import * as NodeOS2 from "node:os";
28156
+ import * as NodePath4 from "node:path";
27957
28157
  var DEFAULT_MAX_BYTES = 8 * 1024 * 1024;
27958
28158
  var UNSAFE_FILE_CHARS = /[^A-Za-z0-9._-]+/gu;
27959
28159
  function safeFileName(value) {
@@ -27961,12 +28161,12 @@ function safeFileName(value) {
27961
28161
  }
27962
28162
  function createAcpProtocolLogFile(input2) {
27963
28163
  const maxBytes = input2.maxBytes ?? DEFAULT_MAX_BYTES;
27964
- NodeFS.mkdirSync(input2.directory, { recursive: true });
27965
- const filePath = NodePath2.join(
28164
+ NodeFS2.mkdirSync(input2.directory, { recursive: true });
28165
+ const filePath = NodePath4.join(
27966
28166
  input2.directory,
27967
28167
  `${safeFileName(input2.provider)}-${safeFileName(input2.threadId)}-${Date.now().toString(36)}.ndjson`
27968
28168
  );
27969
- const stream = (input2.openStream ?? ((path) => NodeFS.createWriteStream(path, { flags: "a" })))(
28169
+ const stream = (input2.openStream ?? ((path) => NodeFS2.createWriteStream(path, { flags: "a" })))(
27970
28170
  filePath
27971
28171
  );
27972
28172
  let bytes = 0;
@@ -28018,7 +28218,7 @@ function createAcpProtocolLogFromEnv(input2) {
28018
28218
  const flag = process.env.YCODIUM_ACP_PROTOCOL_LOG;
28019
28219
  if (flag !== "1" && flag?.toLowerCase() !== "true") return void 0;
28020
28220
  const configured = process.env.YCODIUM_ACP_PROTOCOL_LOG_DIR?.trim();
28021
- const directory = configured && configured.length > 0 ? configured : NodePath2.join(NodeOS.tmpdir(), "ycodium-acp-protocol");
28221
+ const directory = configured && configured.length > 0 ? configured : NodePath4.join(NodeOS2.tmpdir(), "ycodium-acp-protocol");
28022
28222
  try {
28023
28223
  const sink = createAcpProtocolLogFile({
28024
28224
  directory,
@@ -28042,7 +28242,7 @@ function defaultNowIso() {
28042
28242
  return (/* @__PURE__ */ new Date()).toISOString();
28043
28243
  }
28044
28244
  function defaultRandomId() {
28045
- return NodeCrypto.randomUUID();
28245
+ return NodeCrypto2.randomUUID();
28046
28246
  }
28047
28247
  function createAcpExecutor(options) {
28048
28248
  const hooks = { ...defaultVendorHooks, ...options.hooks };
@@ -28318,6 +28518,9 @@ function createAcpExecutor(options) {
28318
28518
  get modelId() {
28319
28519
  return shared.modelSelectionState.currentModelId;
28320
28520
  },
28521
+ get modelReported() {
28522
+ return shared.modelSelectionState.currentModelReported === true;
28523
+ },
28321
28524
  get status() {
28322
28525
  return shared.status;
28323
28526
  },
@@ -28635,28 +28838,28 @@ var MAX_VERSION_OUTPUT_BYTES = 64 * 1024;
28635
28838
 
28636
28839
  // src/config.ts
28637
28840
  var GROK_DEFAULT_LAUNCH_ARGS = "agent stdio";
28638
- function isRecord6(value) {
28841
+ function isRecord7(value) {
28639
28842
  return typeof value === "object" && value !== null && !Array.isArray(value);
28640
28843
  }
28641
- function readString(value) {
28844
+ function readString2(value) {
28642
28845
  return typeof value === "string" ? value : "";
28643
28846
  }
28644
28847
  function readTrimmed(value) {
28645
- return readString(value).trim();
28848
+ return readString2(value).trim();
28646
28849
  }
28647
28850
  function readEnvironment(value) {
28648
28851
  if (!Array.isArray(value)) return {};
28649
28852
  const env = {};
28650
28853
  for (const entry of value) {
28651
- if (!isRecord6(entry)) continue;
28854
+ if (!isRecord7(entry)) continue;
28652
28855
  const name = readTrimmed(entry["name"]);
28653
28856
  if (name.length === 0) continue;
28654
- env[name] = readString(entry["value"]);
28857
+ env[name] = readString2(entry["value"]);
28655
28858
  }
28656
28859
  return env;
28657
28860
  }
28658
28861
  function parseGrokInstanceConfig(config2) {
28659
- if (!isRecord6(config2)) {
28862
+ if (!isRecord7(config2)) {
28660
28863
  return {
28661
28864
  binaryPath: "",
28662
28865
  launchArgs: GROK_DEFAULT_LAUNCH_ARGS,
@@ -28666,11 +28869,11 @@ function parseGrokInstanceConfig(config2) {
28666
28869
  envelopeDriver: void 0
28667
28870
  };
28668
28871
  }
28669
- const nested = isRecord6(config2["config"]) ? config2["config"] : void 0;
28872
+ const nested = isRecord7(config2["config"]) ? config2["config"] : void 0;
28670
28873
  const settings = nested ?? config2;
28671
28874
  return {
28672
28875
  binaryPath: readTrimmed(settings["binaryPath"]),
28673
- launchArgs: readString(settings["launchArgs"]) || GROK_DEFAULT_LAUNCH_ARGS,
28876
+ launchArgs: readString2(settings["launchArgs"]) || GROK_DEFAULT_LAUNCH_ARGS,
28674
28877
  homePath: readTrimmed(settings["homePath"]) || readTrimmed(config2["homePath"]),
28675
28878
  homeEnvVar: readTrimmed(settings["homeEnvVar"]) || readTrimmed(config2["homeEnvVar"]),
28676
28879
  environment: readEnvironment(config2["environment"]),
@@ -28794,166 +28997,6 @@ function readDeviceCodeOutput(stdout, onText) {
28794
28997
  return { text: text4, code };
28795
28998
  }
28796
28999
 
28797
- // src/grokHome.ts
28798
- import * as NodeCrypto2 from "node:crypto";
28799
- import * as NodeOS2 from "node:os";
28800
- import * as NodePath4 from "node:path";
28801
- import { mergeLaunchEnv } from "@ycodium-ai/plugin-api/executor";
28802
-
28803
- // src/grokAccount.ts
28804
- import * as NodeFS2 from "node:fs";
28805
- import * as NodePath3 from "node:path";
28806
- var AUTH_FILE_NAME = "auth.json";
28807
- var isRecord7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
28808
- var readString2 = (record4, key) => {
28809
- const value = record4[key];
28810
- if (typeof value !== "string") return void 0;
28811
- const trimmed3 = value.trim();
28812
- return trimmed3.length > 0 ? trimmed3 : void 0;
28813
- };
28814
- var parseJsonRecord = (raw) => {
28815
- let parsed;
28816
- try {
28817
- parsed = JSON.parse(raw);
28818
- } catch {
28819
- return void 0;
28820
- }
28821
- return isRecord7(parsed) ? parsed : void 0;
28822
- };
28823
- var directoryExists = (homePath) => {
28824
- try {
28825
- return NodeFS2.statSync(homePath).isDirectory();
28826
- } catch {
28827
- return false;
28828
- }
28829
- };
28830
- var readCredentialFile = (path) => {
28831
- try {
28832
- return NodeFS2.readFileSync(path, "utf8");
28833
- } catch {
28834
- return void 0;
28835
- }
28836
- };
28837
- var signedOutProbe = (exists) => ({
28838
- state: "signed-out",
28839
- directoryExists: exists,
28840
- email: void 0,
28841
- name: void 0,
28842
- expiresAt: void 0
28843
- });
28844
- var unreadableProbe = (exists) => ({
28845
- state: "unreadable",
28846
- directoryExists: exists,
28847
- email: void 0,
28848
- name: void 0,
28849
- expiresAt: void 0
28850
- });
28851
- var joinName = (record4) => {
28852
- const first = readString2(record4, "first_name");
28853
- const last = readString2(record4, "last_name");
28854
- const joined = [first, last].filter((part) => part !== void 0).join(" ");
28855
- return joined.length > 0 ? joined : void 0;
28856
- };
28857
- var findIdentityEntry = (parsed) => {
28858
- for (const value of Object.values(parsed)) {
28859
- if (!isRecord7(value)) continue;
28860
- if (readString2(value, "email") !== void 0 || readString2(value, "user_id") !== void 0) {
28861
- return value;
28862
- }
28863
- }
28864
- return void 0;
28865
- };
28866
- var isExpired = (expiresAt, nowMs) => {
28867
- if (expiresAt === void 0) return false;
28868
- const parsed = Date.parse(expiresAt);
28869
- return Number.isNaN(parsed) ? false : parsed <= nowMs;
28870
- };
28871
- var readGrokAccount = (homePath) => {
28872
- const exists = directoryExists(homePath);
28873
- const raw = readCredentialFile(NodePath3.join(homePath, AUTH_FILE_NAME));
28874
- if (raw === void 0) return signedOutProbe(exists);
28875
- const parsed = parseJsonRecord(raw);
28876
- const identity = parsed === void 0 ? void 0 : findIdentityEntry(parsed);
28877
- if (identity === void 0) return unreadableProbe(exists);
28878
- const expiresAt = readString2(identity, "expires_at");
28879
- return {
28880
- state: isExpired(expiresAt, Date.now()) ? "expired" : "signed-in",
28881
- directoryExists: exists,
28882
- email: readString2(identity, "email"),
28883
- name: joinName(identity),
28884
- expiresAt
28885
- };
28886
- };
28887
-
28888
- // src/grokHome.ts
28889
- function expandHomePath(value) {
28890
- if (value === "~") return NodeOS2.homedir();
28891
- if (value.startsWith("~/") || value.startsWith("~\\")) {
28892
- return NodePath4.join(NodeOS2.homedir(), value.slice(2));
28893
- }
28894
- return value;
28895
- }
28896
- function resolveGrokHomePath(value) {
28897
- const trimmed3 = value.trim();
28898
- if (trimmed3.length === 0) return NodePath4.join(NodeOS2.homedir(), ".grok");
28899
- return NodePath4.resolve(expandHomePath(trimmed3));
28900
- }
28901
- function probeGrokAccount(accountPath) {
28902
- const resolved = resolveGrokHomePath(accountPath);
28903
- const account = readGrokAccount(resolved);
28904
- return {
28905
- accountPath: resolved,
28906
- state: account.state,
28907
- ...account.email === void 0 ? {} : { email: account.email },
28908
- ...account.name === void 0 ? {} : { name: account.name },
28909
- ...account.expiresAt === void 0 ? {} : { expiresAt: account.expiresAt }
28910
- };
28911
- }
28912
- function grokContinuationKey(recipe) {
28913
- return `grok:home:${resolveGrokHomePath(recipe.homePath)}`;
28914
- }
28915
- function resolveAccountHomePath(recipe, configuredHome) {
28916
- const fromEnvironment = recipe.environment["GROK_HOME"]?.trim();
28917
- if (fromEnvironment !== void 0 && fromEnvironment.length > 0) {
28918
- return NodePath4.resolve(expandHomePath(fromEnvironment));
28919
- }
28920
- return resolveGrokHomePath(configuredHome);
28921
- }
28922
- function absentPath() {
28923
- return NodePath4.join(
28924
- NodeOS2.tmpdir(),
28925
- `ycodium-grok-no-auth-${NodeCrypto2.randomUUID()}`,
28926
- "auth.json"
28927
- );
28928
- }
28929
- function ownEndpointAuthGuard(recipe) {
28930
- const connectivity = GROK_CARD.modelConnectivity;
28931
- const baseUrlKey = connectivity.baseUrlEnvKey;
28932
- if (baseUrlKey === void 0 || (recipe.environment[baseUrlKey]?.trim() ?? "") === "") return {};
28933
- const guard = {};
28934
- for (const entry of connectivity.extraEnv ?? []) {
28935
- if (entry.value === "$absentPath") guard[entry.name] = absentPath();
28936
- }
28937
- return guard;
28938
- }
28939
- function buildSignInEnv(recipe) {
28940
- const configuredHome = recipe.homePath;
28941
- const homeInjection = configuredHome.trim().length > 0 ? { [GROK_CARD.homeEnvVar]: resolveGrokHomePath(configuredHome) } : {};
28942
- return mergeLaunchEnv({
28943
- accountHome: homeInjection,
28944
- instanceTable: recipe.environment,
28945
- handOverrides: { GROK_OAUTH2_REFERRER: "ycodium" }
28946
- });
28947
- }
28948
- function buildSpawnEnv(recipe, resolvedHomePath, homeConfigured) {
28949
- const homeInjection = homeConfigured ? { [GROK_CARD.homeEnvVar]: resolvedHomePath } : {};
28950
- return mergeLaunchEnv({
28951
- accountHome: { ...homeInjection, ...ownEndpointAuthGuard(recipe) },
28952
- instanceTable: recipe.environment,
28953
- handOverrides: { GROK_OAUTH2_REFERRER: "ycodium" }
28954
- });
28955
- }
28956
-
28957
29000
  // src/grokSignIn.ts
28958
29001
  function makeGrokSignIn(input2) {
28959
29002
  const { host, recipe } = input2;
@@ -30780,7 +30823,11 @@ var createGrokInstance = (config2, host) => {
30780
30823
  createdAt,
30781
30824
  updatedAt: createdAt,
30782
30825
  resumeCursor,
30783
- ...started.modelId === void 0 ? {} : { model: started.modelId },
30826
+ ...started.modelId === void 0 ? {} : {
30827
+ model: started.modelId,
30828
+ // agent 在握手里报的才是读回的;切型号走 session/set_model 时回包不带型号,那只是请求值。
30829
+ modelSource: started.modelReported ? "reported" : "echo"
30830
+ },
30784
30831
  ...input2.bindingGeneration === void 0 ? {} : { bindingGeneration: input2.bindingGeneration }
30785
30832
  };
30786
30833
  registerSession(input2.threadId, {
@@ -30921,6 +30968,7 @@ var createGrokInstance = (config2, host) => {
30921
30968
  var executors = [
30922
30969
  {
30923
30970
  card: GROK_CARD,
30971
+ lifecycle: [{ kind: "detect", reversible: true }],
30924
30972
  createInstance: async (config2, host) => {
30925
30973
  host.log.info("createInstance");
30926
30974
  return createGrokInstance(config2, host);
@@ -30930,9 +30978,26 @@ var executors = [
30930
30978
  ];
30931
30979
  var index_default = definePlugin({
30932
30980
  id: "agent-grok",
30933
- apiVersion: 52,
30981
+ apiVersion: 56,
30934
30982
  executors,
30935
30983
  activate(ctx) {
30984
+ ctx.command({
30985
+ name: "effectiveConfig",
30986
+ description: "Settings form: the account directory an empty path resolves to on this machine.",
30987
+ scope: "read",
30988
+ payloadSchema: EXECUTOR_EFFECTIVE_PAYLOAD_SCHEMA,
30989
+ handle: async (payload) => {
30990
+ const request = readExecutorEffectiveRequest(payload);
30991
+ return {
30992
+ homePath: executorHomeEffectiveValue(
30993
+ "GROK_HOME",
30994
+ request,
30995
+ process.env,
30996
+ (value) => resolveGrokHomePath(value ?? "")
30997
+ )
30998
+ };
30999
+ }
31000
+ });
30936
31001
  ctx.log.info("grok executor ready");
30937
31002
  }
30938
31003
  });
package/locales/en.json CHANGED
@@ -1,4 +1,8 @@
1
1
  {
2
+ "presentation": {
3
+ "name": "Grok",
4
+ "description": "Use Grok in Ycodium and pick up earlier sessions where they left off."
5
+ },
2
6
  "form": {
3
7
  "binaryPath": {
4
8
  "title": "Program path",
@@ -9,6 +13,11 @@
9
13
  "title": "Account directory",
10
14
  "description": "Sessions and the `grok login` credentials live here, so providers sharing this directory can pick up each other's threads and providers with different directories cannot. Leave empty to use the CLI's default (~/.grok).",
11
15
  "placeholder": "e.g. ~/.grok-work"
16
+ },
17
+ "effective": {
18
+ "provider": "{{name}} in this provider",
19
+ "machine": "{{name}} on this machine",
20
+ "default": "default"
12
21
  }
13
22
  },
14
23
  "usage": {
@@ -1,4 +1,8 @@
1
1
  {
2
+ "presentation": {
3
+ "name": "Grok",
4
+ "description": "在 Ycodium 里使用 Grok,之前的会话可以接着做。"
5
+ },
2
6
  "form": {
3
7
  "binaryPath": {
4
8
  "title": "程序路径",
@@ -9,6 +13,11 @@
9
13
  "title": "账号目录",
10
14
  "description": "会话和 `grok login` 的凭据都保存在这里,因此共用同一目录的 provider 可以接手彼此的会话,目录不同的则不能。留空则使用该命令行工具的默认目录(~/.grok)。",
11
15
  "placeholder": "例如 ~/.grok-work"
16
+ },
17
+ "effective": {
18
+ "provider": "本 provider 的 {{name}}",
19
+ "machine": "本机的 {{name}}",
20
+ "default": "默认"
12
21
  }
13
22
  },
14
23
  "usage": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ycodium-ai/agent-grok",
3
- "version": "0.2.3681",
3
+ "version": "0.2.3766",
4
4
  "description": "Grok executor: grok CLI over Agent Client Protocol stdio.",
5
5
  "keywords": [
6
6
  "ycodium",
@@ -21,14 +21,16 @@
21
21
  "@types/node": "24.12.4",
22
22
  "esbuild": "0.28.1",
23
23
  "vite-plus": "0.2.2",
24
- "@ycodium-ai/plugin-api": "0.2.3681",
25
- "ycodium-acp-executor": "0.2.3681",
24
+ "@ycodium-ai/plugin-api": "0.2.3766",
25
+ "ycodium-acp-executor": "0.2.3766",
26
26
  "ycodium-executor-testing": "0.0.0"
27
27
  },
28
28
  "ycodium": {
29
29
  "plugin": "./dist/index.js",
30
30
  "displayName": "Grok",
31
- "description": "Run Grok (grok agent stdio) with its session/resume continuation, reasoning-effort spawn tiers, and device-code sign-in.",
31
+ "displayNameKey": "presentation.name",
32
+ "description": "Use Grok in Ycodium and pick up earlier sessions where they left off.",
33
+ "descriptionKey": "presentation.description",
32
34
  "icon": "./assets/icon.svg",
33
35
  "permissions": [
34
36
  {