@ycodium-ai/agent-grok 0.2.3710 → 0.2.3799

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"],
@@ -81,7 +88,10 @@ var GROK_CARD = {
81
88
  // 压缩生命周期事件报;CLI 没宣告这条命令时 `compactContext` 拒绝,不退回发文本。
82
89
  contextCompaction: "native",
83
90
  // 子 agent 经父会话上的 subagent_* 通知报成 task.*(grokSubagents.ts)。
84
- reportsSpawnedAgents: true
91
+ reportsSpawnedAgents: true,
92
+ // 插话:起会话前在配置里打开 `follow_up_behavior = "steer"`(grokFollowUpSteer.ts),运行中再发的 prompt
93
+ // 由 grok 在下一个工具调用结束时并进这一轮(1.0.70 ACP 模式实测);执行器不等前一条应答就写出去。
94
+ turnSteering: true
85
95
  },
86
96
  // 凭据由 CLI 自己保管在 `<GROK_HOME>/auth.json`,宿主读它只认 `/token`。
87
97
  credentialSources: [
@@ -161,6 +171,166 @@ var GROK_CARD = {
161
171
  }
162
172
  };
163
173
 
174
+ // src/grokHome.ts
175
+ import * as NodeCrypto from "node:crypto";
176
+ import * as NodeOS from "node:os";
177
+ import * as NodePath2 from "node:path";
178
+ import { mergeLaunchEnv } from "@ycodium-ai/plugin-api/executor";
179
+
180
+ // src/grokAccount.ts
181
+ import * as NodeFS from "node:fs";
182
+ import * as NodePath from "node:path";
183
+ var AUTH_FILE_NAME = "auth.json";
184
+ var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
185
+ var readString = (record4, key) => {
186
+ const value = record4[key];
187
+ if (typeof value !== "string") return void 0;
188
+ const trimmed3 = value.trim();
189
+ return trimmed3.length > 0 ? trimmed3 : void 0;
190
+ };
191
+ var parseJsonRecord = (raw) => {
192
+ let parsed;
193
+ try {
194
+ parsed = JSON.parse(raw);
195
+ } catch {
196
+ return void 0;
197
+ }
198
+ return isRecord(parsed) ? parsed : void 0;
199
+ };
200
+ var directoryExists = (homePath) => {
201
+ try {
202
+ return NodeFS.statSync(homePath).isDirectory();
203
+ } catch {
204
+ return false;
205
+ }
206
+ };
207
+ var readCredentialFile = (path) => {
208
+ try {
209
+ return NodeFS.readFileSync(path, "utf8");
210
+ } catch {
211
+ return void 0;
212
+ }
213
+ };
214
+ var signedOutProbe = (exists) => ({
215
+ state: "signed-out",
216
+ directoryExists: exists,
217
+ email: void 0,
218
+ name: void 0,
219
+ expiresAt: void 0
220
+ });
221
+ var unreadableProbe = (exists) => ({
222
+ state: "unreadable",
223
+ directoryExists: exists,
224
+ email: void 0,
225
+ name: void 0,
226
+ expiresAt: void 0
227
+ });
228
+ var joinName = (record4) => {
229
+ const first = readString(record4, "first_name");
230
+ const last = readString(record4, "last_name");
231
+ const joined = [first, last].filter((part) => part !== void 0).join(" ");
232
+ return joined.length > 0 ? joined : void 0;
233
+ };
234
+ var findIdentityEntry = (parsed) => {
235
+ for (const value of Object.values(parsed)) {
236
+ if (!isRecord(value)) continue;
237
+ if (readString(value, "email") !== void 0 || readString(value, "user_id") !== void 0) {
238
+ return value;
239
+ }
240
+ }
241
+ return void 0;
242
+ };
243
+ var isExpired = (expiresAt, nowMs) => {
244
+ if (expiresAt === void 0) return false;
245
+ const parsed = Date.parse(expiresAt);
246
+ return Number.isNaN(parsed) ? false : parsed <= nowMs;
247
+ };
248
+ var readGrokAccount = (homePath) => {
249
+ const exists = directoryExists(homePath);
250
+ const raw = readCredentialFile(NodePath.join(homePath, AUTH_FILE_NAME));
251
+ if (raw === void 0) return signedOutProbe(exists);
252
+ const parsed = parseJsonRecord(raw);
253
+ const identity = parsed === void 0 ? void 0 : findIdentityEntry(parsed);
254
+ if (identity === void 0) return unreadableProbe(exists);
255
+ const expiresAt = readString(identity, "expires_at");
256
+ return {
257
+ state: isExpired(expiresAt, Date.now()) ? "expired" : "signed-in",
258
+ directoryExists: exists,
259
+ email: readString(identity, "email"),
260
+ name: joinName(identity),
261
+ expiresAt
262
+ };
263
+ };
264
+
265
+ // src/grokHome.ts
266
+ function expandHomePath(value) {
267
+ if (value === "~") return NodeOS.homedir();
268
+ if (value.startsWith("~/") || value.startsWith("~\\")) {
269
+ return NodePath2.join(NodeOS.homedir(), value.slice(2));
270
+ }
271
+ return value;
272
+ }
273
+ function resolveGrokHomePath(value) {
274
+ const trimmed3 = value.trim();
275
+ if (trimmed3.length === 0) return NodePath2.join(NodeOS.homedir(), ".grok");
276
+ return NodePath2.resolve(expandHomePath(trimmed3));
277
+ }
278
+ function probeGrokAccount(accountPath) {
279
+ const resolved = resolveGrokHomePath(accountPath);
280
+ const account = readGrokAccount(resolved);
281
+ return {
282
+ accountPath: resolved,
283
+ state: account.state,
284
+ ...account.email === void 0 ? {} : { email: account.email },
285
+ ...account.name === void 0 ? {} : { name: account.name },
286
+ ...account.expiresAt === void 0 ? {} : { expiresAt: account.expiresAt }
287
+ };
288
+ }
289
+ function grokContinuationKey(recipe) {
290
+ return `grok:home:${resolveGrokHomePath(recipe.homePath)}`;
291
+ }
292
+ function resolveAccountHomePath(recipe, configuredHome) {
293
+ const fromEnvironment = recipe.environment["GROK_HOME"]?.trim();
294
+ if (fromEnvironment !== void 0 && fromEnvironment.length > 0) {
295
+ return NodePath2.resolve(expandHomePath(fromEnvironment));
296
+ }
297
+ return resolveGrokHomePath(configuredHome);
298
+ }
299
+ function absentPath() {
300
+ return NodePath2.join(
301
+ NodeOS.tmpdir(),
302
+ `ycodium-grok-no-auth-${NodeCrypto.randomUUID()}`,
303
+ "auth.json"
304
+ );
305
+ }
306
+ function ownEndpointAuthGuard(recipe) {
307
+ const connectivity = GROK_CARD.modelConnectivity;
308
+ const baseUrlKey = connectivity.baseUrlEnvKey;
309
+ if (baseUrlKey === void 0 || (recipe.environment[baseUrlKey]?.trim() ?? "") === "") return {};
310
+ const guard = {};
311
+ for (const entry of connectivity.extraEnv ?? []) {
312
+ if (entry.value === "$absentPath") guard[entry.name] = absentPath();
313
+ }
314
+ return guard;
315
+ }
316
+ function buildSignInEnv(recipe) {
317
+ const configuredHome = recipe.homePath;
318
+ const homeInjection = configuredHome.trim().length > 0 ? { [GROK_CARD.homeEnvVar]: resolveGrokHomePath(configuredHome) } : {};
319
+ return mergeLaunchEnv({
320
+ accountHome: homeInjection,
321
+ instanceTable: recipe.environment,
322
+ handOverrides: { GROK_OAUTH2_REFERRER: "ycodium" }
323
+ });
324
+ }
325
+ function buildSpawnEnv(recipe, resolvedHomePath, homeConfigured) {
326
+ const homeInjection = homeConfigured ? { [GROK_CARD.homeEnvVar]: resolvedHomePath } : {};
327
+ return mergeLaunchEnv({
328
+ accountHome: { ...homeInjection, ...ownEndpointAuthGuard(recipe) },
329
+ instanceTable: recipe.environment,
330
+ handOverrides: { GROK_OAUTH2_REFERRER: "ycodium" }
331
+ });
332
+ }
333
+
164
334
  // src/grokInstance.ts
165
335
  import * as NodeCrypto3 from "node:crypto";
166
336
  import {
@@ -169,7 +339,7 @@ import {
169
339
  import { nodeStreamsFromHandle as nodeStreamsFromHandle2 } from "@ycodium-ai/plugin-api/process-stdin";
170
340
 
171
341
  // ../../packages/ycodium-acp-executor/src/executor.ts
172
- import * as NodeCrypto from "node:crypto";
342
+ import * as NodeCrypto2 from "node:crypto";
173
343
 
174
344
  // ../../packages/ycodium-acp-executor/src/approvals.ts
175
345
  function createApprovalGate(input2) {
@@ -397,7 +567,7 @@ var UnboundedEventStream = class {
397
567
  };
398
568
 
399
569
  // ../../packages/ycodium-acp-executor/src/config-options.ts
400
- function isRecord(value) {
570
+ function isRecord2(value) {
401
571
  return typeof value === "object" && value !== null && !Array.isArray(value);
402
572
  }
403
573
  function asString(value) {
@@ -416,7 +586,7 @@ function flattenChoices(rawOptions, currentValue) {
416
586
  const out = [];
417
587
  const seen = /* @__PURE__ */ new Set();
418
588
  const push = (entry) => {
419
- if (!isRecord(entry)) return;
589
+ if (!isRecord2(entry)) return;
420
590
  const value = asString(entry["value"]);
421
591
  if (value === void 0 || seen.has(value)) return;
422
592
  seen.add(value);
@@ -431,7 +601,7 @@ function flattenChoices(rawOptions, currentValue) {
431
601
  });
432
602
  };
433
603
  for (const entry of rawOptions) {
434
- if (isRecord(entry) && Array.isArray(entry["options"])) {
604
+ if (isRecord2(entry) && Array.isArray(entry["options"])) {
435
605
  for (const nested of entry["options"]) push(nested);
436
606
  continue;
437
607
  }
@@ -440,7 +610,7 @@ function flattenChoices(rawOptions, currentValue) {
440
610
  return out;
441
611
  }
442
612
  function isConfigOption(entry, configId) {
443
- return isRecord(entry) && asString(entry["id"])?.trim() === configId.trim();
613
+ return isRecord2(entry) && asString(entry["id"])?.trim() === configId.trim();
444
614
  }
445
615
  function findConfigOptionEntry(configOptions, configId) {
446
616
  return Array.isArray(configOptions) && configId.trim() !== "" ? configOptions.find(
@@ -455,7 +625,7 @@ function withConfigOptionValue(configOptions, configId, value) {
455
625
  }
456
626
  function pickEffortOption(configOptions) {
457
627
  if (!Array.isArray(configOptions)) return void 0;
458
- const candidates = configOptions.filter(isRecord).filter((option) => normalizedField(option, "type") === "select").filter(looksLikeEffortOption);
628
+ const candidates = configOptions.filter(isRecord2).filter((option) => normalizedField(option, "type") === "select").filter(looksLikeEffortOption);
459
629
  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
630
  }
461
631
  function findAcpEffortConfig(configOptions) {
@@ -475,7 +645,7 @@ function findAcpEffortConfig(configOptions) {
475
645
  }
476
646
 
477
647
  // ../../packages/ycodium-acp-executor/src/util.ts
478
- function isRecord2(value) {
648
+ function isRecord3(value) {
479
649
  return typeof value === "object" && value !== null && !Array.isArray(value);
480
650
  }
481
651
  function asTrimmedString(value) {
@@ -530,11 +700,11 @@ function extractCommandFromTitle(title) {
530
700
  return backtickMatch?.[1]?.trim() || void 0;
531
701
  }
532
702
  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;
703
+ const item = isRecord3(data?.["item"]) ? data["item"] : void 0;
704
+ const itemInput = item && isRecord3(item["input"]) ? item["input"] : void 0;
705
+ const itemResult = item && isRecord3(item["result"]) ? item["result"] : void 0;
706
+ const rawInput = isRecord3(data?.["rawInput"]) ? data["rawInput"] : void 0;
707
+ const dataInput = isRecord3(data?.["input"]) ? data["input"] : void 0;
538
708
  const candidates = [
539
709
  normalizeCommandValue(item?.["command"]),
540
710
  normalizeCommandValue(itemInput?.["command"]),
@@ -579,7 +749,7 @@ function collectPaths(value, paths, seen, depth) {
579
749
  }
580
750
  return;
581
751
  }
582
- if (!isRecord2(value)) {
752
+ if (!isRecord3(value)) {
583
753
  return;
584
754
  }
585
755
  for (const key of ["path", "filePath", "relativePath", "filename", "newPath", "oldPath"]) {
@@ -657,7 +827,7 @@ function deriveToolActivityPresentation(input2) {
657
827
  const title = asTrimmedString(input2.title);
658
828
  const detail = stripTrailingExitCode(asTrimmedString(input2.detail));
659
829
  const fallbackSummary = asTrimmedString(input2.fallbackSummary) ?? "Tool";
660
- const data = isRecord2(input2.data) ? input2.data : void 0;
830
+ const data = isRecord3(input2.data) ? input2.data : void 0;
661
831
  const command = extractToolCommand(data, title);
662
832
  const primaryPath = extractPrimaryPath(data);
663
833
  const action = classifyToolAction({
@@ -689,7 +859,7 @@ function deriveToolActivityPresentation(input2) {
689
859
  };
690
860
  }
691
861
  if (action === "search") {
692
- const rawInput = isRecord2(data?.["rawInput"]) ? data["rawInput"] : void 0;
862
+ const rawInput = isRecord3(data?.["rawInput"]) ? data["rawInput"] : void 0;
693
863
  const query = asTrimmedString(rawInput?.["query"]) ?? asTrimmedString(rawInput?.["pattern"]) ?? asTrimmedString(rawInput?.["searchTerm"]);
694
864
  return {
695
865
  summary: "Searched files",
@@ -749,7 +919,7 @@ function extractCommandFromTitle2(title) {
749
919
  return match?.[1]?.trim() || void 0;
750
920
  }
751
921
  function extractToolCallCommand(rawInput, title) {
752
- if (isRecord2(rawInput)) {
922
+ if (isRecord3(rawInput)) {
753
923
  const directCommand = normalizeCommandValue2(rawInput["command"]);
754
924
  if (directCommand) {
755
925
  return directCommand;
@@ -777,7 +947,7 @@ function boundTailText(text4, limit = TOOL_CALL_CONTENT_MAX_CHARS) {
777
947
  var boundToolCallOutputText = (text4) => boundTailText(text4);
778
948
  var RAW_OUTPUT_TEXT_FIELDS = ["content", "stdout", "stderr", "output"];
779
949
  function boundToolCallRawOutput(rawOutput) {
780
- if (!isRecord2(rawOutput)) {
950
+ if (!isRecord3(rawOutput)) {
781
951
  return rawOutput;
782
952
  }
783
953
  let changed = false;
@@ -969,11 +1139,11 @@ function collectSessionConfigOptionValues(configOption) {
969
1139
  );
970
1140
  }
971
1141
  function collectSessionModelIds(models) {
972
- if (!isRecord2(models) || !Array.isArray(models["availableModels"])) {
1142
+ if (!isRecord3(models) || !Array.isArray(models["availableModels"])) {
973
1143
  return [];
974
1144
  }
975
1145
  return models["availableModels"].flatMap(
976
- (model) => isRecord2(model) && typeof model["modelId"] === "string" ? [model["modelId"]] : []
1146
+ (model) => isRecord3(model) && typeof model["modelId"] === "string" ? [model["modelId"]] : []
977
1147
  );
978
1148
  }
979
1149
  function catalogModelLastSegment(value) {
@@ -1045,7 +1215,7 @@ function parseAvailableCommandsUpdate(params) {
1045
1215
  }
1046
1216
  const commands = [];
1047
1217
  for (const command of availableCommands) {
1048
- if (!isRecord2(command)) {
1218
+ if (!isRecord3(command)) {
1049
1219
  continue;
1050
1220
  }
1051
1221
  const name = typeof command["name"] === "string" ? command["name"].trim() : "";
@@ -1053,7 +1223,7 @@ function parseAvailableCommandsUpdate(params) {
1053
1223
  continue;
1054
1224
  }
1055
1225
  const description = typeof command["description"] === "string" ? command["description"].trim() || void 0 : void 0;
1056
- const input2 = isRecord2(command["input"]) ? command["input"] : void 0;
1226
+ const input2 = isRecord3(command["input"]) ? command["input"] : void 0;
1057
1227
  const inputHint = typeof input2?.["hint"] === "string" ? input2["hint"].trim() || void 0 : void 0;
1058
1228
  commands.push({
1059
1229
  name,
@@ -1107,7 +1277,7 @@ function boundToolCallRawPayload(params, update, toolCall) {
1107
1277
  }
1108
1278
  function textFromContentBlocks(blocks) {
1109
1279
  const parts = blocks.flatMap(
1110
- (block) => isRecord2(block) && block["type"] === "text" && typeof block["text"] === "string" ? [block["text"]] : []
1280
+ (block) => isRecord3(block) && block["type"] === "text" && typeof block["text"] === "string" ? [block["text"]] : []
1111
1281
  );
1112
1282
  const joined = parts.join("");
1113
1283
  return joined.trim().length > 0 ? joined : void 0;
@@ -1132,7 +1302,7 @@ function parseSessionUpdateEvent(params) {
1132
1302
  const entries = Array.isArray(upd["entries"]) ? upd["entries"] : [];
1133
1303
  const plan = entries.map((entry, index) => ({
1134
1304
  step: readAcpText(entry, "content") ?? `Step ${index + 1}`,
1135
- status: normalizePlanStepStatus(isRecord2(entry) ? entry["status"] : void 0)
1305
+ status: normalizePlanStepStatus(isRecord3(entry) ? entry["status"] : void 0)
1136
1306
  }));
1137
1307
  if (plan.length > 0) {
1138
1308
  events.push({
@@ -1172,7 +1342,7 @@ function parseSessionUpdateEvent(params) {
1172
1342
  }
1173
1343
  case "agent_message_chunk":
1174
1344
  case "agent_thought_chunk": {
1175
- const content = isRecord2(upd["content"]) ? upd["content"] : void 0;
1345
+ const content = isRecord3(upd["content"]) ? upd["content"] : void 0;
1176
1346
  const contentType = content?.["type"];
1177
1347
  const streamKind = kind === "agent_thought_chunk" ? "reasoning_text" : "assistant_text";
1178
1348
  if (contentType === "text") {
@@ -1212,7 +1382,7 @@ function parseSessionUpdateEvent(params) {
1212
1382
  }
1213
1383
  case "compaction_summary_chunk": {
1214
1384
  const compactionId = readAcpText(upd, "compactionId");
1215
- const content = isRecord2(upd["content"]) ? upd["content"] : void 0;
1385
+ const content = isRecord3(upd["content"]) ? upd["content"] : void 0;
1216
1386
  const text4 = content?.["type"] === "text" ? content["text"] : void 0;
1217
1387
  if (compactionId === void 0 || typeof text4 !== "string" || text4.length === 0) break;
1218
1388
  events.push({ _tag: "CompactionSummaryChunk", compactionId, text: text4 });
@@ -22906,17 +23076,17 @@ function isResponseMessage(value) {
22906
23076
  function isNotificationMessage(value) {
22907
23077
  return isJsonRpcEnvelope(value) && !("id" in value) && typeof value["method"] === "string";
22908
23078
  }
22909
- function isRecord3(value) {
23079
+ function isRecord4(value) {
22910
23080
  return typeof value === "object" && value !== null;
22911
23081
  }
22912
23082
  function isJsonRpcEnvelope(value) {
22913
- return isRecord3(value) && value["jsonrpc"] === "2.0";
23083
+ return isRecord4(value) && value["jsonrpc"] === "2.0";
22914
23084
  }
22915
23085
  function isJsonRpcId(value) {
22916
23086
  return value === null || typeof value === "string" || typeof value === "number" && Number.isFinite(value);
22917
23087
  }
22918
23088
  function isResponseShapedMessage(value) {
22919
- return isRecord3(value) && !("method" in value) && ("id" in value || "result" in value || "error" in value);
23089
+ return isRecord4(value) && !("method" in value) && ("id" in value || "result" in value || "error" in value);
22920
23090
  }
22921
23091
  function isResponseBatch(batch) {
22922
23092
  let hasValidCall = false;
@@ -22926,7 +23096,7 @@ function isResponseBatch(batch) {
22926
23096
  for (const entry of batch) {
22927
23097
  hasValidCall ||= isRequestMessage(entry) || isNotificationMessage(entry);
22928
23098
  hasValidResponse ||= isResponseMessage(entry);
22929
- if (!isRecord3(entry)) {
23099
+ if (!isRecord4(entry)) {
22930
23100
  continue;
22931
23101
  }
22932
23102
  hasCallShape ||= "method" in entry;
@@ -22941,13 +23111,13 @@ function isResponseBatch(batch) {
22941
23111
  return hasResponseShape && !hasCallShape;
22942
23112
  }
22943
23113
  function cancelRequestId(params) {
22944
- if (!isRecord3(params) || !isJsonRpcId(params["requestId"])) {
23114
+ if (!isRecord4(params) || !isJsonRpcId(params["requestId"])) {
22945
23115
  return void 0;
22946
23116
  }
22947
23117
  return params["requestId"];
22948
23118
  }
22949
23119
  function isErrorResponse(value) {
22950
- return isRecord3(value) && typeof value["code"] === "number" && Number.isInteger(value["code"]) && typeof value["message"] === "string";
23120
+ return isRecord4(value) && typeof value["code"] === "number" && Number.isInteger(value["code"]) && typeof value["message"] === "string";
22951
23121
  }
22952
23122
  var Handled = {
22953
23123
  /**
@@ -23492,7 +23662,7 @@ var Connection = class {
23492
23662
  if (this.abortController.signal.aborted) {
23493
23663
  return Promise.resolve();
23494
23664
  }
23495
- if (!isRecord3(message)) {
23665
+ if (!isRecord4(message)) {
23496
23666
  console.error("Invalid message", { message });
23497
23667
  return Promise.resolve();
23498
23668
  }
@@ -23895,7 +24065,7 @@ function ndJsonStream(output2, input2) {
23895
24065
  await writeJson(protocolErrorResponse(RequestError.parseError()));
23896
24066
  return;
23897
24067
  }
23898
- if (isRecord3(message) || Array.isArray(message)) {
24068
+ if (isRecord4(message) || Array.isArray(message)) {
23899
24069
  controller.enqueue(message);
23900
24070
  } else {
23901
24071
  await writeJson(protocolErrorResponse(RequestError.invalidRequest(message)));
@@ -24882,14 +25052,14 @@ var legacyClientNotificationMethods = /* @__PURE__ */ new Set([
24882
25052
  import * as NodeStream from "node:stream";
24883
25053
 
24884
25054
  // ../../packages/ycodium-acp-executor/src/jsonrpc.ts
24885
- function isRecord4(value) {
25055
+ function isRecord5(value) {
24886
25056
  return typeof value === "object" && value !== null;
24887
25057
  }
24888
25058
  function isJsonRpcId2(value) {
24889
25059
  return typeof value === "string" || typeof value === "number" && Number.isFinite(value);
24890
25060
  }
24891
25061
  function isJsonRpcEnvelope2(value) {
24892
- return isRecord4(value) && value["jsonrpc"] === "2.0";
25062
+ return isRecord5(value) && value["jsonrpc"] === "2.0";
24893
25063
  }
24894
25064
  function isRequestMessage2(value) {
24895
25065
  return isJsonRpcEnvelope2(value) && "id" in value && typeof value["method"] === "string" && isJsonRpcId2(value["id"]);
@@ -24909,7 +25079,7 @@ function isNotificationMessage2(value) {
24909
25079
  return isJsonRpcEnvelope2(value) && !("id" in value) && typeof value["method"] === "string";
24910
25080
  }
24911
25081
  function readJsonRpcError(value) {
24912
- if (!isRecord4(value) || typeof value["code"] !== "number" || typeof value["message"] !== "string") {
25082
+ if (!isRecord5(value) || typeof value["code"] !== "number" || typeof value["message"] !== "string") {
24913
25083
  return void 0;
24914
25084
  }
24915
25085
  return {
@@ -24982,11 +25152,11 @@ var KNOWN_SESSION_UPDATES = /* @__PURE__ */ new Set([
24982
25152
  "compaction_summary_chunk"
24983
25153
  ]);
24984
25154
  function isSessionUpdateEnvelope(payload) {
24985
- if (!isRecord4(payload) || typeof payload["sessionId"] !== "string") {
25155
+ if (!isRecord5(payload) || typeof payload["sessionId"] !== "string") {
24986
25156
  return false;
24987
25157
  }
24988
25158
  const update = payload["update"];
24989
- return isRecord4(update) && typeof update["sessionUpdate"] === "string";
25159
+ return isRecord5(update) && typeof update["sessionUpdate"] === "string";
24990
25160
  }
24991
25161
  function hasKnownVariantShape(update) {
24992
25162
  const kind = update["sessionUpdate"];
@@ -24994,7 +25164,7 @@ function hasKnownVariantShape(update) {
24994
25164
  return false;
24995
25165
  }
24996
25166
  if (kind === "user_message_chunk" || kind === "agent_message_chunk" || kind === "agent_thought_chunk") {
24997
- return isRecord4(update["content"]);
25167
+ return isRecord5(update["content"]);
24998
25168
  }
24999
25169
  return true;
25000
25170
  }
@@ -25400,7 +25570,7 @@ function forgetConfigOptionValue(state, configId) {
25400
25570
  const forgetsModel = configId === void 0 || configId === state.modelConfigId;
25401
25571
  const { currentModelReported: _forgottenReport, ...withoutReport } = state;
25402
25572
  const configOptions = configId !== void 0 && Array.isArray(state.configOptions) ? state.configOptions.map((option) => {
25403
- if (!isRecord2(option) || option["id"] !== configId) return option;
25573
+ if (!isRecord3(option) || option["id"] !== configId) return option;
25404
25574
  const { currentValue: _forgotten, ...rest } = option;
25405
25575
  return rest;
25406
25576
  }) : state.configOptions;
@@ -25411,7 +25581,7 @@ function forgetConfigOptionValue(state, configId) {
25411
25581
  };
25412
25582
  }
25413
25583
  function modelSelectionStateFromSetup(setup) {
25414
- const sessionModels = isRecord2(setup.models) ? setup.models : void 0;
25584
+ const sessionModels = isRecord3(setup.models) ? setup.models : void 0;
25415
25585
  const modelConfigId = extractModelConfigId(setup);
25416
25586
  const strategy = modelConfigId !== void 0 ? "config-option" : "session/set_model";
25417
25587
  const modelConfigOption = modelConfigId === void 0 ? void 0 : findSessionConfigOption(setup.configOptions, modelConfigId);
@@ -26121,6 +26291,7 @@ function createExecutorPrompt({
26121
26291
  promptResults,
26122
26292
  settleToolSubagents
26123
26293
  }) {
26294
+ const ownOutcomes = /* @__PURE__ */ new Map();
26124
26295
  const settleTurn = (input2) => {
26125
26296
  if (!isLive(input2.expectedGeneration) || input2.record.finished) {
26126
26297
  return false;
@@ -26137,14 +26308,22 @@ function createExecutorPrompt({
26137
26308
  }
26138
26309
  closeActiveAssistantSegment(input2.turnId);
26139
26310
  shared.promptsInFlight = Math.max(0, shared.promptsInFlight - 1);
26140
- if (shared.promptsInFlight !== 0 || shared.activeTurnId !== input2.turnId) return false;
26311
+ if (shared.promptsInFlight !== 0 || shared.activeTurnId !== input2.turnId) {
26312
+ if (input2.foldedIn !== true) ownOutcomes.set(input2.turnId, input2);
26313
+ return false;
26314
+ }
26315
+ const outcome = (input2.foldedIn === true ? ownOutcomes.get(input2.turnId) : void 0) ?? input2;
26316
+ ownOutcomes.delete(input2.turnId);
26141
26317
  shared.activeTurnId = void 0;
26142
26318
  if (shared.status !== "disposed") {
26143
26319
  shared.status = "ready";
26144
26320
  }
26145
26321
  const interrupted = promptResults.hasInterrupted(input2.turnId);
26146
26322
  settleToolSubagents(
26147
- toolSubagentSettlementOfTurn(interrupted ? "cancelled" : input2.state, input2.failureCategory),
26323
+ toolSubagentSettlementOfTurn(
26324
+ interrupted ? "cancelled" : outcome.state,
26325
+ outcome.failureCategory
26326
+ ),
26148
26327
  input2.turnId
26149
26328
  );
26150
26329
  emit({
@@ -26154,10 +26333,10 @@ function createExecutorPrompt({
26154
26333
  threadId: options.threadId,
26155
26334
  turnId: input2.turnId,
26156
26335
  payload: {
26157
- state: interrupted ? "cancelled" : input2.state,
26158
- stopReason: interrupted ? "cancelled" : input2.stopReason,
26159
- ...input2.errorMessage === void 0 ? {} : { errorMessage: input2.errorMessage },
26160
- ...input2.state === "failed" ? failureCategoryField(input2.failureCategory) : {}
26336
+ state: interrupted ? "cancelled" : outcome.state,
26337
+ stopReason: interrupted ? "cancelled" : outcome.stopReason,
26338
+ ...outcome.errorMessage === void 0 ? {} : { errorMessage: outcome.errorMessage },
26339
+ ...outcome.state === "failed" ? failureCategoryField(outcome.failureCategory) : {}
26161
26340
  }
26162
26341
  });
26163
26342
  interruptedTurnIds.delete(input2.turnId);
@@ -26182,7 +26361,7 @@ function createExecutorPrompt({
26182
26361
  return await promptRpc;
26183
26362
  } else {
26184
26363
  const { promise: early, resolve: settleEarly } = Promise.withResolvers();
26185
- shared.pendingSettlement = {
26364
+ shared.pendingSettlements.set(promptKey, {
26186
26365
  promptKey,
26187
26366
  sessionId,
26188
26367
  settle: settleEarly,
@@ -26191,7 +26370,7 @@ function createExecutorPrompt({
26191
26370
  onError: () => promptResults.reject(record4.promptId),
26192
26371
  hasResult: () => record4.response !== void 0
26193
26372
  }
26194
- };
26373
+ });
26195
26374
  try {
26196
26375
  const outcome = await Promise.race([
26197
26376
  promptRpc.then((value) => {
@@ -26204,7 +26383,7 @@ function createExecutorPrompt({
26204
26383
  if (outcome.error !== void 0) throw outcome.error;
26205
26384
  return outcome.response;
26206
26385
  } finally {
26207
- shared.pendingSettlement = void 0;
26386
+ shared.pendingSettlements.delete(promptKey);
26208
26387
  void promptRpc.catch(() => void 0);
26209
26388
  }
26210
26389
  }
@@ -26354,7 +26533,9 @@ function createExecutorPrompt({
26354
26533
  settle2({
26355
26534
  turnId,
26356
26535
  stopReason,
26357
- state: response2.stopReason === "cancelled" ? "cancelled" : "completed"
26536
+ state: response2.stopReason === "cancelled" ? "cancelled" : "completed",
26537
+ // 立刻写出的插话没被打断却应答 cancelled:agent 把它并进了这一轮(grok 的 removedFromQueue)。
26538
+ foldedIn: response2.stopReason === "cancelled" && input2.expectedActiveTurnId !== void 0 && hooks.queuesPromptsWhileRunning === true
26358
26539
  });
26359
26540
  return {
26360
26541
  threadId: options.threadId,
@@ -26363,7 +26544,8 @@ function createExecutorPrompt({
26363
26544
  ...response2.usage !== void 0 && response2.usage !== null ? { usage: response2.usage } : {}
26364
26545
  };
26365
26546
  };
26366
- await promptMutex.acquire();
26547
+ const writesWhileRunning = input2.expectedActiveTurnId !== void 0 && hooks.queuesPromptsWhileRunning === true;
26548
+ if (!writesWhileRunning) await promptMutex.acquire();
26367
26549
  let response;
26368
26550
  try {
26369
26551
  failIfDisposed();
@@ -26445,7 +26627,7 @@ function createExecutorPrompt({
26445
26627
  await options.beforeApplyResult();
26446
26628
  }
26447
26629
  } finally {
26448
- promptMutex.release();
26630
+ if (!writesWhileRunning) promptMutex.release();
26449
26631
  notifyPromptSettled();
26450
26632
  }
26451
26633
  if (!isLive(started.generation)) {
@@ -26779,7 +26961,7 @@ function createExecutorRequests({
26779
26961
  pendingUserInputs.delete(requestId);
26780
26962
  pending.resolve({
26781
26963
  kind: "answered",
26782
- answers: isRecord2(answers) ? answers : {}
26964
+ answers: isRecord3(answers) ? answers : {}
26783
26965
  });
26784
26966
  };
26785
26967
  return {
@@ -26793,7 +26975,7 @@ function createExecutorRequests({
26793
26975
  }
26794
26976
 
26795
26977
  // ../../packages/ycodium-acp-executor/src/artifacts.ts
26796
- import * as NodePath from "node:path";
26978
+ import * as NodePath3 from "node:path";
26797
26979
  import * as NodeURL from "node:url";
26798
26980
  function resourceLinkArtifacts(content) {
26799
26981
  if (content["type"] !== "resource_link" || typeof content["uri"] !== "string") return [];
@@ -26804,7 +26986,7 @@ function resourceLinkArtifacts(content) {
26804
26986
  } catch {
26805
26987
  return [];
26806
26988
  }
26807
- if (!NodePath.isAbsolute(path)) return [];
26989
+ if (!NodePath3.isAbsolute(path)) return [];
26808
26990
  const name = typeof content["name"] === "string" ? content["name"].trim() : "";
26809
26991
  const mimeType = typeof content["mimeType"] === "string" ? content["mimeType"].trim() : "";
26810
26992
  return [
@@ -27078,18 +27260,18 @@ function modelRefreshFailedError(requestedModelId, error62) {
27078
27260
  }
27079
27261
 
27080
27262
  // ../../packages/ycodium-acp-executor/src/runtime-mode.ts
27081
- function isRecord5(value) {
27263
+ function isRecord6(value) {
27082
27264
  return typeof value === "object" && value !== null && !Array.isArray(value);
27083
27265
  }
27084
27266
  function asString2(value) {
27085
27267
  return typeof value === "string" ? value : void 0;
27086
27268
  }
27087
27269
  function acpModeConfigId(setup) {
27088
- if (!isRecord5(setup)) return void 0;
27270
+ if (!isRecord6(setup)) return void 0;
27089
27271
  const configOptions = setup["configOptions"];
27090
27272
  if (!Array.isArray(configOptions)) return void 0;
27091
27273
  for (const option of configOptions) {
27092
- if (!isRecord5(option)) continue;
27274
+ if (!isRecord6(option)) continue;
27093
27275
  if (asString2(option["category"])?.trim().toLowerCase() !== "mode") continue;
27094
27276
  const id = asString2(option["id"])?.trim();
27095
27277
  if (id) return id;
@@ -27488,28 +27670,29 @@ function createExecutorSettlement({
27488
27670
  if (settledPromptKeys.length > SETTLED_PROMPT_KEY_LIMIT) settledPromptKeys.shift();
27489
27671
  };
27490
27672
  const abortPendingSettlement = (reason, failureCategory) => {
27491
- const waiting = shared.pendingSettlement;
27492
- if (waiting === void 0) return;
27493
- if (waiting.hasResult?.()) return;
27494
- shared.pendingSettlement = void 0;
27495
- waiting.settle({
27496
- error: withFailureCategory(
27497
- new AcpExecutorRequestError("session/prompt", reason),
27498
- failureCategory
27499
- )
27500
- });
27673
+ for (const [key, waiting] of shared.pendingSettlements) {
27674
+ if (waiting.hasResult?.()) continue;
27675
+ shared.pendingSettlements.delete(key);
27676
+ waiting.settle({
27677
+ error: withFailureCategory(
27678
+ new AcpExecutorRequestError("session/prompt", reason),
27679
+ failureCategory
27680
+ )
27681
+ });
27682
+ }
27501
27683
  };
27502
27684
  const settleFromNotification = (method, params) => {
27503
27685
  const hook = hooks.promptSettlement;
27504
- if (hook === void 0 || shared.pendingSettlement === void 0) return false;
27686
+ if (hook === void 0 || shared.pendingSettlements.size === 0) return false;
27505
27687
  const signal = hook.fromNotification(method, params);
27506
27688
  if (signal === void 0) return false;
27507
- const waiting = shared.pendingSettlement;
27508
- if (signal.sessionId !== waiting.sessionId) return false;
27509
- if (signal.promptKey !== void 0 && signal.promptKey !== waiting.promptKey) {
27510
- return settledPromptKeys.includes(signal.promptKey);
27689
+ const waiting = signal.promptKey === void 0 ? [...shared.pendingSettlements.values()].find(
27690
+ (candidate) => candidate.sessionId === signal.sessionId
27691
+ ) : shared.pendingSettlements.get(signal.promptKey);
27692
+ if (waiting === void 0 || waiting.sessionId !== signal.sessionId) {
27693
+ return signal.promptKey !== void 0 && settledPromptKeys.includes(signal.promptKey);
27511
27694
  }
27512
- shared.pendingSettlement = void 0;
27695
+ shared.pendingSettlements.delete(waiting.promptKey);
27513
27696
  rememberSettledPromptKey(waiting.promptKey);
27514
27697
  if (signal.outcome === "error") {
27515
27698
  waiting.onError?.();
@@ -27536,14 +27719,14 @@ function createExecutorSettlement({
27536
27719
 
27537
27720
  // ../../packages/ycodium-acp-executor/src/executor/subagent-sessions.ts
27538
27721
  function parseState(raw) {
27539
- if (!isRecord2(raw)) return { status: void 0, error: void 0 };
27722
+ if (!isRecord3(raw)) return { status: void 0, error: void 0 };
27540
27723
  const state = raw["state"];
27541
27724
  if (state === "running") return { status: "running", error: void 0 };
27542
27725
  if (state === "requires_action") return { status: "waiting", error: void 0 };
27543
27726
  if (state !== "idle") return { status: void 0, error: void 0 };
27544
27727
  const stopReason = raw["stopReason"];
27545
- const error62 = isRecord2(raw["error"]) ? asTrimmedString(raw["error"]["message"]) : void 0;
27546
- if (stopReason === "error" || isRecord2(raw["error"])) return { status: "failed", error: error62 };
27728
+ const error62 = isRecord3(raw["error"]) ? asTrimmedString(raw["error"]["message"]) : void 0;
27729
+ if (stopReason === "error" || isRecord3(raw["error"])) return { status: "failed", error: error62 };
27547
27730
  if (stopReason === "cancelled") return { status: "cancelled", error: void 0 };
27548
27731
  return { status: "idle", error: void 0 };
27549
27732
  }
@@ -27559,7 +27742,7 @@ function childLinkage(childId, record4) {
27559
27742
  function createSubagentSessionTracker(registry2) {
27560
27743
  const observe = (notification, ctx) => {
27561
27744
  const update = notification.update;
27562
- if (!isRecord2(update) || update["sessionUpdate"] !== "subagent_update") return [];
27745
+ if (!isRecord3(update) || update["sessionUpdate"] !== "subagent_update") return [];
27563
27746
  const childId = asTrimmedString(update["sessionId"]);
27564
27747
  if (childId === void 0 || ctx.ownSessionIds.has(childId)) return [];
27565
27748
  const outerId = notification.sessionId;
@@ -27984,9 +28167,9 @@ function readOpenToolCalls(toolCalls, turnId) {
27984
28167
  }
27985
28168
 
27986
28169
  // ../../packages/ycodium-acp-executor/src/protocol-log.ts
27987
- import * as NodeFS from "node:fs";
27988
- import * as NodeOS from "node:os";
27989
- import * as NodePath2 from "node:path";
28170
+ import * as NodeFS2 from "node:fs";
28171
+ import * as NodeOS2 from "node:os";
28172
+ import * as NodePath4 from "node:path";
27990
28173
  var DEFAULT_MAX_BYTES = 8 * 1024 * 1024;
27991
28174
  var UNSAFE_FILE_CHARS = /[^A-Za-z0-9._-]+/gu;
27992
28175
  function safeFileName(value) {
@@ -27994,12 +28177,12 @@ function safeFileName(value) {
27994
28177
  }
27995
28178
  function createAcpProtocolLogFile(input2) {
27996
28179
  const maxBytes = input2.maxBytes ?? DEFAULT_MAX_BYTES;
27997
- NodeFS.mkdirSync(input2.directory, { recursive: true });
27998
- const filePath = NodePath2.join(
28180
+ NodeFS2.mkdirSync(input2.directory, { recursive: true });
28181
+ const filePath = NodePath4.join(
27999
28182
  input2.directory,
28000
28183
  `${safeFileName(input2.provider)}-${safeFileName(input2.threadId)}-${Date.now().toString(36)}.ndjson`
28001
28184
  );
28002
- const stream = (input2.openStream ?? ((path) => NodeFS.createWriteStream(path, { flags: "a" })))(
28185
+ const stream = (input2.openStream ?? ((path) => NodeFS2.createWriteStream(path, { flags: "a" })))(
28003
28186
  filePath
28004
28187
  );
28005
28188
  let bytes = 0;
@@ -28051,7 +28234,7 @@ function createAcpProtocolLogFromEnv(input2) {
28051
28234
  const flag = process.env.YCODIUM_ACP_PROTOCOL_LOG;
28052
28235
  if (flag !== "1" && flag?.toLowerCase() !== "true") return void 0;
28053
28236
  const configured = process.env.YCODIUM_ACP_PROTOCOL_LOG_DIR?.trim();
28054
- const directory = configured && configured.length > 0 ? configured : NodePath2.join(NodeOS.tmpdir(), "ycodium-acp-protocol");
28237
+ const directory = configured && configured.length > 0 ? configured : NodePath4.join(NodeOS2.tmpdir(), "ycodium-acp-protocol");
28055
28238
  try {
28056
28239
  const sink = createAcpProtocolLogFile({
28057
28240
  directory,
@@ -28075,7 +28258,7 @@ function defaultNowIso() {
28075
28258
  return (/* @__PURE__ */ new Date()).toISOString();
28076
28259
  }
28077
28260
  function defaultRandomId() {
28078
- return NodeCrypto.randomUUID();
28261
+ return NodeCrypto2.randomUUID();
28079
28262
  }
28080
28263
  function createAcpExecutor(options) {
28081
28264
  const hooks = { ...defaultVendorHooks, ...options.hooks };
@@ -28103,7 +28286,7 @@ function createAcpExecutor(options) {
28103
28286
  startInFlight: void 0,
28104
28287
  activeTurnId: void 0,
28105
28288
  promptsInFlight: 0,
28106
- pendingSettlement: void 0,
28289
+ pendingSettlements: /* @__PURE__ */ new Map(),
28107
28290
  manualCompaction: void 0,
28108
28291
  assistantSegment: { nextSegmentIndex: 0 },
28109
28292
  modeState: void 0,
@@ -28671,28 +28854,28 @@ var MAX_VERSION_OUTPUT_BYTES = 64 * 1024;
28671
28854
 
28672
28855
  // src/config.ts
28673
28856
  var GROK_DEFAULT_LAUNCH_ARGS = "agent stdio";
28674
- function isRecord6(value) {
28857
+ function isRecord7(value) {
28675
28858
  return typeof value === "object" && value !== null && !Array.isArray(value);
28676
28859
  }
28677
- function readString(value) {
28860
+ function readString2(value) {
28678
28861
  return typeof value === "string" ? value : "";
28679
28862
  }
28680
28863
  function readTrimmed(value) {
28681
- return readString(value).trim();
28864
+ return readString2(value).trim();
28682
28865
  }
28683
28866
  function readEnvironment(value) {
28684
28867
  if (!Array.isArray(value)) return {};
28685
28868
  const env = {};
28686
28869
  for (const entry of value) {
28687
- if (!isRecord6(entry)) continue;
28870
+ if (!isRecord7(entry)) continue;
28688
28871
  const name = readTrimmed(entry["name"]);
28689
28872
  if (name.length === 0) continue;
28690
- env[name] = readString(entry["value"]);
28873
+ env[name] = readString2(entry["value"]);
28691
28874
  }
28692
28875
  return env;
28693
28876
  }
28694
28877
  function parseGrokInstanceConfig(config2) {
28695
- if (!isRecord6(config2)) {
28878
+ if (!isRecord7(config2)) {
28696
28879
  return {
28697
28880
  binaryPath: "",
28698
28881
  launchArgs: GROK_DEFAULT_LAUNCH_ARGS,
@@ -28702,11 +28885,11 @@ function parseGrokInstanceConfig(config2) {
28702
28885
  envelopeDriver: void 0
28703
28886
  };
28704
28887
  }
28705
- const nested = isRecord6(config2["config"]) ? config2["config"] : void 0;
28888
+ const nested = isRecord7(config2["config"]) ? config2["config"] : void 0;
28706
28889
  const settings = nested ?? config2;
28707
28890
  return {
28708
28891
  binaryPath: readTrimmed(settings["binaryPath"]),
28709
- launchArgs: readString(settings["launchArgs"]) || GROK_DEFAULT_LAUNCH_ARGS,
28892
+ launchArgs: readString2(settings["launchArgs"]) || GROK_DEFAULT_LAUNCH_ARGS,
28710
28893
  homePath: readTrimmed(settings["homePath"]) || readTrimmed(config2["homePath"]),
28711
28894
  homeEnvVar: readTrimmed(settings["homeEnvVar"]) || readTrimmed(config2["homeEnvVar"]),
28712
28895
  environment: readEnvironment(config2["environment"]),
@@ -28830,166 +29013,6 @@ function readDeviceCodeOutput(stdout, onText) {
28830
29013
  return { text: text4, code };
28831
29014
  }
28832
29015
 
28833
- // src/grokHome.ts
28834
- import * as NodeCrypto2 from "node:crypto";
28835
- import * as NodeOS2 from "node:os";
28836
- import * as NodePath4 from "node:path";
28837
- import { mergeLaunchEnv } from "@ycodium-ai/plugin-api/executor";
28838
-
28839
- // src/grokAccount.ts
28840
- import * as NodeFS2 from "node:fs";
28841
- import * as NodePath3 from "node:path";
28842
- var AUTH_FILE_NAME = "auth.json";
28843
- var isRecord7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
28844
- var readString2 = (record4, key) => {
28845
- const value = record4[key];
28846
- if (typeof value !== "string") return void 0;
28847
- const trimmed3 = value.trim();
28848
- return trimmed3.length > 0 ? trimmed3 : void 0;
28849
- };
28850
- var parseJsonRecord = (raw) => {
28851
- let parsed;
28852
- try {
28853
- parsed = JSON.parse(raw);
28854
- } catch {
28855
- return void 0;
28856
- }
28857
- return isRecord7(parsed) ? parsed : void 0;
28858
- };
28859
- var directoryExists = (homePath) => {
28860
- try {
28861
- return NodeFS2.statSync(homePath).isDirectory();
28862
- } catch {
28863
- return false;
28864
- }
28865
- };
28866
- var readCredentialFile = (path) => {
28867
- try {
28868
- return NodeFS2.readFileSync(path, "utf8");
28869
- } catch {
28870
- return void 0;
28871
- }
28872
- };
28873
- var signedOutProbe = (exists) => ({
28874
- state: "signed-out",
28875
- directoryExists: exists,
28876
- email: void 0,
28877
- name: void 0,
28878
- expiresAt: void 0
28879
- });
28880
- var unreadableProbe = (exists) => ({
28881
- state: "unreadable",
28882
- directoryExists: exists,
28883
- email: void 0,
28884
- name: void 0,
28885
- expiresAt: void 0
28886
- });
28887
- var joinName = (record4) => {
28888
- const first = readString2(record4, "first_name");
28889
- const last = readString2(record4, "last_name");
28890
- const joined = [first, last].filter((part) => part !== void 0).join(" ");
28891
- return joined.length > 0 ? joined : void 0;
28892
- };
28893
- var findIdentityEntry = (parsed) => {
28894
- for (const value of Object.values(parsed)) {
28895
- if (!isRecord7(value)) continue;
28896
- if (readString2(value, "email") !== void 0 || readString2(value, "user_id") !== void 0) {
28897
- return value;
28898
- }
28899
- }
28900
- return void 0;
28901
- };
28902
- var isExpired = (expiresAt, nowMs) => {
28903
- if (expiresAt === void 0) return false;
28904
- const parsed = Date.parse(expiresAt);
28905
- return Number.isNaN(parsed) ? false : parsed <= nowMs;
28906
- };
28907
- var readGrokAccount = (homePath) => {
28908
- const exists = directoryExists(homePath);
28909
- const raw = readCredentialFile(NodePath3.join(homePath, AUTH_FILE_NAME));
28910
- if (raw === void 0) return signedOutProbe(exists);
28911
- const parsed = parseJsonRecord(raw);
28912
- const identity = parsed === void 0 ? void 0 : findIdentityEntry(parsed);
28913
- if (identity === void 0) return unreadableProbe(exists);
28914
- const expiresAt = readString2(identity, "expires_at");
28915
- return {
28916
- state: isExpired(expiresAt, Date.now()) ? "expired" : "signed-in",
28917
- directoryExists: exists,
28918
- email: readString2(identity, "email"),
28919
- name: joinName(identity),
28920
- expiresAt
28921
- };
28922
- };
28923
-
28924
- // src/grokHome.ts
28925
- function expandHomePath(value) {
28926
- if (value === "~") return NodeOS2.homedir();
28927
- if (value.startsWith("~/") || value.startsWith("~\\")) {
28928
- return NodePath4.join(NodeOS2.homedir(), value.slice(2));
28929
- }
28930
- return value;
28931
- }
28932
- function resolveGrokHomePath(value) {
28933
- const trimmed3 = value.trim();
28934
- if (trimmed3.length === 0) return NodePath4.join(NodeOS2.homedir(), ".grok");
28935
- return NodePath4.resolve(expandHomePath(trimmed3));
28936
- }
28937
- function probeGrokAccount(accountPath) {
28938
- const resolved = resolveGrokHomePath(accountPath);
28939
- const account = readGrokAccount(resolved);
28940
- return {
28941
- accountPath: resolved,
28942
- state: account.state,
28943
- ...account.email === void 0 ? {} : { email: account.email },
28944
- ...account.name === void 0 ? {} : { name: account.name },
28945
- ...account.expiresAt === void 0 ? {} : { expiresAt: account.expiresAt }
28946
- };
28947
- }
28948
- function grokContinuationKey(recipe) {
28949
- return `grok:home:${resolveGrokHomePath(recipe.homePath)}`;
28950
- }
28951
- function resolveAccountHomePath(recipe, configuredHome) {
28952
- const fromEnvironment = recipe.environment["GROK_HOME"]?.trim();
28953
- if (fromEnvironment !== void 0 && fromEnvironment.length > 0) {
28954
- return NodePath4.resolve(expandHomePath(fromEnvironment));
28955
- }
28956
- return resolveGrokHomePath(configuredHome);
28957
- }
28958
- function absentPath() {
28959
- return NodePath4.join(
28960
- NodeOS2.tmpdir(),
28961
- `ycodium-grok-no-auth-${NodeCrypto2.randomUUID()}`,
28962
- "auth.json"
28963
- );
28964
- }
28965
- function ownEndpointAuthGuard(recipe) {
28966
- const connectivity = GROK_CARD.modelConnectivity;
28967
- const baseUrlKey = connectivity.baseUrlEnvKey;
28968
- if (baseUrlKey === void 0 || (recipe.environment[baseUrlKey]?.trim() ?? "") === "") return {};
28969
- const guard = {};
28970
- for (const entry of connectivity.extraEnv ?? []) {
28971
- if (entry.value === "$absentPath") guard[entry.name] = absentPath();
28972
- }
28973
- return guard;
28974
- }
28975
- function buildSignInEnv(recipe) {
28976
- const configuredHome = recipe.homePath;
28977
- const homeInjection = configuredHome.trim().length > 0 ? { [GROK_CARD.homeEnvVar]: resolveGrokHomePath(configuredHome) } : {};
28978
- return mergeLaunchEnv({
28979
- accountHome: homeInjection,
28980
- instanceTable: recipe.environment,
28981
- handOverrides: { GROK_OAUTH2_REFERRER: "ycodium" }
28982
- });
28983
- }
28984
- function buildSpawnEnv(recipe, resolvedHomePath, homeConfigured) {
28985
- const homeInjection = homeConfigured ? { [GROK_CARD.homeEnvVar]: resolvedHomePath } : {};
28986
- return mergeLaunchEnv({
28987
- accountHome: { ...homeInjection, ...ownEndpointAuthGuard(recipe) },
28988
- instanceTable: recipe.environment,
28989
- handOverrides: { GROK_OAUTH2_REFERRER: "ycodium" }
28990
- });
28991
- }
28992
-
28993
29016
  // src/grokSignIn.ts
28994
29017
  function makeGrokSignIn(input2) {
28995
29018
  const { host, recipe } = input2;
@@ -29231,30 +29254,38 @@ var grokTurnUsageReport = (promptResponse) => {
29231
29254
  ...sessionId === void 0 ? {} : { sessionId }
29232
29255
  };
29233
29256
  };
29257
+ var mergeReports = (left, right) => {
29258
+ if (left === void 0 || right === void 0) return left ?? right;
29259
+ const totals = {
29260
+ uncachedInputTokens: left.totals.uncachedInputTokens + right.totals.uncachedInputTokens,
29261
+ cachedInputTokens: left.totals.cachedInputTokens + right.totals.cachedInputTokens,
29262
+ cacheCreationTokens: left.totals.cacheCreationTokens + right.totals.cacheCreationTokens,
29263
+ outputTokens: left.totals.outputTokens + right.totals.outputTokens,
29264
+ reasoningTokens: left.totals.reasoningTokens + right.totals.reasoningTokens
29265
+ };
29266
+ const larger = sumOf(left.totals) >= sumOf(right.totals) ? left : right;
29267
+ return { ...larger, totals };
29268
+ };
29234
29269
  var createGrokTurnUsageCollector = (base) => {
29235
29270
  const pastKeys = [];
29236
- let promptKey;
29237
- let report;
29238
- let seen = false;
29271
+ const turn = /* @__PURE__ */ new Map();
29272
+ const answered = /* @__PURE__ */ new Set();
29273
+ let latestKey;
29239
29274
  let wake;
29240
- const mark = () => {
29241
- seen = true;
29242
- wake?.();
29243
- };
29275
+ const allAnswered = () => [...turn.keys()].every((key) => answered.has(key));
29244
29276
  const observe = (response) => {
29245
- const key = base.responseKey?.(response);
29246
- if (key !== void 0 && key !== promptKey && pastKeys.includes(key)) return;
29247
- report = grokTurnUsageReport(response) ?? report;
29248
- mark();
29277
+ const key = base.responseKey?.(response) ?? latestKey;
29278
+ if (key === void 0) return;
29279
+ if (!turn.has(key) && pastKeys.includes(key)) return;
29280
+ turn.set(key, grokTurnUsageReport(response) ?? turn.get(key));
29281
+ answered.add(key);
29282
+ if (allAnswered()) wake?.();
29249
29283
  };
29250
29284
  const settlement = {
29251
29285
  ...base,
29252
29286
  requestFields: (key) => {
29253
- if (promptKey !== void 0) pastKeys.push(promptKey);
29254
- if (pastKeys.length > 16) pastKeys.shift();
29255
- promptKey = key;
29256
- report = void 0;
29257
- seen = false;
29287
+ turn.set(key, void 0);
29288
+ latestKey = key;
29258
29289
  return base.requestFields(key);
29259
29290
  },
29260
29291
  responseKey: (response) => {
@@ -29264,7 +29295,7 @@ var createGrokTurnUsageCollector = (base) => {
29264
29295
  };
29265
29296
  const attach = async (event) => {
29266
29297
  if (event.type !== "turn.completed" || !isRecord8(event.payload)) return event;
29267
- if (event.payload["state"] === "completed" && !seen) {
29298
+ if (event.payload["state"] === "completed" && !allAnswered()) {
29268
29299
  await new Promise((resolve2) => {
29269
29300
  const timer = setTimeout(resolve2, USAGE_GRACE_MS);
29270
29301
  wake = () => {
@@ -29274,8 +29305,12 @@ var createGrokTurnUsageCollector = (base) => {
29274
29305
  });
29275
29306
  wake = void 0;
29276
29307
  }
29277
- const taken = report;
29278
- report = void 0;
29308
+ let taken;
29309
+ for (const report of turn.values()) taken = mergeReports(taken, report);
29310
+ for (const key of turn.keys()) pastKeys.push(key);
29311
+ while (pastKeys.length > 16) pastKeys.shift();
29312
+ turn.clear();
29313
+ answered.clear();
29279
29314
  return taken === void 0 ? event : { ...event, payload: { ...event.payload, tokenUsage: taken } };
29280
29315
  };
29281
29316
  return { settlement, attach };
@@ -30482,6 +30517,55 @@ function createGrokModelCatalog(input2) {
30482
30517
  return { readCliVersion, refreshModelCatalog, refreshForSend };
30483
30518
  }
30484
30519
 
30520
+ // src/grokFollowUpSteer.ts
30521
+ import * as NodeFS5 from "node:fs";
30522
+ import * as NodePath8 from "node:path";
30523
+ var SETTING = 'follow_up_behavior = "steer"';
30524
+ function grokFollowUpSteerIsOn(text4) {
30525
+ const lines = text4.split(/\r?\n/).map((line) => line.trim());
30526
+ const headers = lines.flatMap((line, index) => line === "[ui]" ? [index] : []);
30527
+ const header = headers[0];
30528
+ if (headers.length !== 1 || header === void 0) return false;
30529
+ for (const line of lines.slice(header + 1)) {
30530
+ if (line.startsWith("[")) return false;
30531
+ if (line === SETTING) return true;
30532
+ }
30533
+ return false;
30534
+ }
30535
+ function ensureGrokFollowUpSteer(homePath) {
30536
+ const path = NodePath8.join(homePath, "config.toml");
30537
+ try {
30538
+ return grokFollowUpSteerIsOn(NodeFS5.readFileSync(path, "utf8")) ? "on" : "off";
30539
+ } catch (error62) {
30540
+ if (error62.code !== "ENOENT") throw error62;
30541
+ }
30542
+ NodeFS5.mkdirSync(homePath, { recursive: true });
30543
+ try {
30544
+ NodeFS5.writeFileSync(path, `[ui]
30545
+ ${SETTING}
30546
+ `, { encoding: "utf8", flag: "wx" });
30547
+ return "created";
30548
+ } catch (error62) {
30549
+ if (error62.code !== "EEXIST") throw error62;
30550
+ return grokFollowUpSteerIsOn(NodeFS5.readFileSync(path, "utf8")) ? "on" : "off";
30551
+ }
30552
+ }
30553
+ function prepareGrokFollowUpSteer(homePath, log, threadId) {
30554
+ try {
30555
+ if (ensureGrokFollowUpSteer(homePath) === "off" && threadId !== void 0) {
30556
+ log.info(
30557
+ "grok follow-up steering is off in config.toml; mid-turn messages run after the current request",
30558
+ { threadId }
30559
+ );
30560
+ }
30561
+ } catch (error62) {
30562
+ log.warn("could not read or create the grok config", {
30563
+ ...threadId === void 0 ? {} : { threadId },
30564
+ cause: error62 instanceof Error ? error62.message : String(error62)
30565
+ });
30566
+ }
30567
+ }
30568
+
30485
30569
  // src/grokInstance.ts
30486
30570
  var RESUME_SCHEMA_VERSION = 1;
30487
30571
  var yieldTick = () => new Promise((resolve2) => {
@@ -30596,6 +30680,7 @@ var createGrokInstance = (config2, host) => {
30596
30680
  detail: "Grok program path is not configured"
30597
30681
  };
30598
30682
  }
30683
+ prepareGrokFollowUpSteer(resolveAccountHomePath(recipe, recipe.homePath), host.log);
30599
30684
  const env = spawnEnv();
30600
30685
  const version2 = await raceAbort3(modelCatalog.readCliVersion(signal), signal);
30601
30686
  if (signal?.aborted) throw new Error("aborted");
@@ -30684,6 +30769,11 @@ var createGrokInstance = (config2, host) => {
30684
30769
  if (disposed) throw new Error("instance disposed");
30685
30770
  throwIfStartAborted(signal);
30686
30771
  }
30772
+ prepareGrokFollowUpSteer(
30773
+ resolveAccountHomePath(recipe, recipe.homePath),
30774
+ host.log,
30775
+ input2.threadId
30776
+ );
30687
30777
  const env = spawnEnv();
30688
30778
  const effort = modelSelectionStringOption(input2.modelSelection, GROK_REASONING_OPTION_ID);
30689
30779
  const baseArgs = tokenizeLaunchArgs(recipe.launchArgs);
@@ -30730,6 +30820,8 @@ var createGrokInstance = (config2, host) => {
30730
30820
  authMethodId: resolveGrokAuthMethodId,
30731
30821
  hooks: {
30732
30822
  promptSettlement: turnUsage.settlement,
30823
+ // 运行中再收到的 prompt 由 grok 自己排队、在下一个工具调用结束时并进这一轮(配合上面写的配置)。
30824
+ queuesPromptsWhileRunning: true,
30733
30825
  modelDefaultEffort: grokModelDefaultEffort,
30734
30826
  // 派发子 agent 的调用由子 agent 名单接管,不再画成普通工具。
30735
30827
  onToolCallUpdated: (update) => ({
@@ -30961,6 +31053,7 @@ var createGrokInstance = (config2, host) => {
30961
31053
  var executors = [
30962
31054
  {
30963
31055
  card: GROK_CARD,
31056
+ lifecycle: [{ kind: "detect", reversible: true }],
30964
31057
  createInstance: async (config2, host) => {
30965
31058
  host.log.info("createInstance");
30966
31059
  return createGrokInstance(config2, host);
@@ -30970,9 +31063,26 @@ var executors = [
30970
31063
  ];
30971
31064
  var index_default = definePlugin({
30972
31065
  id: "agent-grok",
30973
- apiVersion: 53,
31066
+ apiVersion: 60,
30974
31067
  executors,
30975
31068
  activate(ctx) {
31069
+ ctx.command({
31070
+ name: "effectiveConfig",
31071
+ description: "Settings form: the account directory an empty path resolves to on this machine.",
31072
+ scope: "read",
31073
+ payloadSchema: EXECUTOR_EFFECTIVE_PAYLOAD_SCHEMA,
31074
+ handle: async (payload) => {
31075
+ const request = readExecutorEffectiveRequest(payload);
31076
+ return {
31077
+ homePath: executorHomeEffectiveValue(
31078
+ "GROK_HOME",
31079
+ request,
31080
+ process.env,
31081
+ (value) => resolveGrokHomePath(value ?? "")
31082
+ )
31083
+ };
31084
+ }
31085
+ });
30976
31086
  ctx.log.info("grok executor ready");
30977
31087
  }
30978
31088
  });
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.3710",
3
+ "version": "0.2.3799",
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.3710",
25
- "ycodium-acp-executor": "0.2.3710",
24
+ "@ycodium-ai/plugin-api": "0.2.3799",
25
+ "ycodium-acp-executor": "0.2.3799",
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
  {