@ycodium-ai/agent-grok 0.2.2690 → 0.2.2694

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/README.md CHANGED
@@ -47,6 +47,17 @@ Facts:
47
47
  exist (the card's `extraEnv` `$absentPath` entry), unless the instance sets
48
48
  `GROK_AUTH_PATH` itself. The login file in the account directory is left untouched.
49
49
  Settings also gives a new instance that sets these variables its own account directory.
50
+ - Usage (ADR-0034), per instance, read only when the user asks:
51
+ - Quota: `ExecutorInstance.usage.readQuota` reads the weekly credit window from
52
+ `cli-chat-proxy.grok.com/v1/billing?format=credits` with the x.ai token in the instance's
53
+ account directory (only entries whose issuer starts with `https://auth.x.ai::`), through
54
+ `host.fetch`. An instance that sets `GROK_MODELS_BASE_URL` reports `customEndpoint` without
55
+ reading credentials; a signed-out instance with only `XAI_API_KEY` reports `unsupportedLogin`
56
+ (xAI's balance API needs a separate management key). Credentials are never refreshed or written.
57
+ - Tokens: no `readTokenUsage`. The turn's usage comes from the `session/prompt` reply's
58
+ `_meta.usage` and is booked on `turn.completed.payload.tokenUsage` (the host writes
59
+ `turn.usage`). The reply arrives a few milliseconds after `prompt_complete`, so
60
+ `turn.completed` waits up to 300 ms for it and carries no usage when it never comes.
50
61
 
51
62
  Develop with `vp run --filter @ycodium-ai/agent-grok typecheck`, `build`, and `test`.
52
63
 
package/dist/index.js CHANGED
@@ -156,7 +156,7 @@ var GROK_CARD = {
156
156
  // src/grokInstance.ts
157
157
  import * as NodeCrypto2 from "node:crypto";
158
158
  import * as NodeOS2 from "node:os";
159
- import * as NodePath2 from "node:path";
159
+ import * as NodePath4 from "node:path";
160
160
  import { mergeLaunchEnv } from "@ycodium-ai/plugin-api/executor";
161
161
  import { nodeStreamsFromHandle as nodeStreamsFromHandle2 } from "@ycodium-ai/plugin-api/process-stdin";
162
162
 
@@ -1429,14 +1429,14 @@ function codePointLength(str) {
1429
1429
  const units = str.length;
1430
1430
  if (!highSurrogate.test(str))
1431
1431
  return units;
1432
- let count = units;
1432
+ let count2 = units;
1433
1433
  for (let i = 0; i < units - 1; i++) {
1434
1434
  if ((str.charCodeAt(i) & 64512) === 55296 && (str.charCodeAt(i + 1) & 64512) === 56320) {
1435
- count--;
1435
+ count2--;
1436
1436
  i++;
1437
1437
  }
1438
1438
  }
1439
- return count;
1439
+ return count2;
1440
1440
  }
1441
1441
  function getLengthableOrigin(input2) {
1442
1442
  if (Array.isArray(input2))
@@ -5791,8 +5791,8 @@ function az_default() {
5791
5791
  }
5792
5792
 
5793
5793
  // ../../node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/locales/be.js
5794
- function getBelarusianPlural(count, one, few, many) {
5795
- const absCount = Math.abs(count);
5794
+ function getBelarusianPlural(count2, one, few, many) {
5795
+ const absCount = Math.abs(count2);
5796
5796
  const lastDigit = absCount % 10;
5797
5797
  const lastTwoDigits = absCount % 100;
5798
5798
  if (lastTwoDigits >= 11 && lastTwoDigits <= 19) {
@@ -8439,8 +8439,8 @@ function hu_default() {
8439
8439
  }
8440
8440
 
8441
8441
  // ../../node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/locales/hy.js
8442
- function getArmenianPlural(count, one, many) {
8443
- return Math.abs(count) === 1 ? one : many;
8442
+ function getArmenianPlural(count2, one, many) {
8443
+ return Math.abs(count2) === 1 ? one : many;
8444
8444
  }
8445
8445
  function withDefiniteArticle(word) {
8446
8446
  if (!word)
@@ -9524,8 +9524,8 @@ function ko_default() {
9524
9524
  }
9525
9525
 
9526
9526
  // ../../node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/locales/lt.js
9527
- var capitalizeFirstCharacter = (text2) => {
9528
- return text2.charAt(0).toUpperCase() + text2.slice(1);
9527
+ var capitalizeFirstCharacter = (text3) => {
9528
+ return text3.charAt(0).toUpperCase() + text3.slice(1);
9529
9529
  };
9530
9530
  function getUnitTypeFromNumber(number4) {
9531
9531
  const abs = Math.abs(number4);
@@ -11178,8 +11178,8 @@ function ro_default() {
11178
11178
  }
11179
11179
 
11180
11180
  // ../../node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/locales/ru.js
11181
- function getRussianPlural(count, one, few, many) {
11182
- const absCount = Math.abs(count);
11181
+ function getRussianPlural(count2, one, few, many) {
11182
+ const absCount = Math.abs(count2);
11183
11183
  const lastDigit = absCount % 10;
11184
11184
  const lastTwoDigits = absCount % 100;
11185
11185
  if (lastTwoDigits >= 11 && lastTwoDigits <= 19) {
@@ -22120,9 +22120,9 @@ var Connection = class {
22120
22120
  return;
22121
22121
  }
22122
22122
  const responseBatch = isResponseBatch(batch);
22123
- const responseCount = responseBatch ? 0 : batch.reduce((count, message) => count + (isNotificationMessage(message) ? 0 : 1), 0);
22123
+ const responseCount = responseBatch ? 0 : batch.reduce((count2, message) => count2 + (isNotificationMessage(message) ? 0 : 1), 0);
22124
22124
  let remaining = responseCount;
22125
- let remainingNotifications = batch.reduce((count, message) => count + (isNotificationMessage(message) ? 1 : 0), 0);
22125
+ let remainingNotifications = batch.reduce((count2, message) => count2 + (isNotificationMessage(message) ? 1 : 0), 0);
22126
22126
  let responseSent = false;
22127
22127
  const responses = [];
22128
22128
  const sendResponsesIfReady = async () => {
@@ -23934,8 +23934,8 @@ function createAcpConnection(options) {
23934
23934
  }
23935
23935
  await dispatchMessage(value);
23936
23936
  };
23937
- const pullEnqueued = async (count) => {
23938
- for (let i = 0; i < count; i += 1) {
23937
+ const pullEnqueued = async (count2) => {
23938
+ for (let i = 0; i < count2; i += 1) {
23939
23939
  const { value, done } = await incoming.read();
23940
23940
  if (done || value === void 0) {
23941
23941
  return;
@@ -24937,6 +24937,13 @@ function parseSessionModeState(sessionResponse) {
24937
24937
  availableModes
24938
24938
  };
24939
24939
  }
24940
+ function acpCommandsToSlashCommands(commands) {
24941
+ return commands.map((command) => ({
24942
+ name: command.name,
24943
+ ...command.description === void 0 ? {} : { description: command.description },
24944
+ ...command.inputHint === void 0 ? {} : { input: { hint: command.inputHint } }
24945
+ }));
24946
+ }
24940
24947
  function parseAvailableCommandsUpdate(params) {
24941
24948
  const update = params.update;
24942
24949
  if (update["sessionUpdate"] !== "available_commands_update") {
@@ -25036,11 +25043,11 @@ function extractToolCallCommand(rawInput, title) {
25036
25043
  }
25037
25044
  var TOOL_CALL_CONTENT_MAX_CHARS = 8e3;
25038
25045
  var TOOL_CALL_CONTENT_TRUNCATION_MARKER = "[Earlier output truncated]\n\n";
25039
- function boundToolCallOutputText(text2) {
25040
- if (text2.length <= TOOL_CALL_CONTENT_MAX_CHARS) {
25041
- return text2;
25046
+ function boundToolCallOutputText(text3) {
25047
+ if (text3.length <= TOOL_CALL_CONTENT_MAX_CHARS) {
25048
+ return text3;
25042
25049
  }
25043
- const tail = text2.slice(text2.length - TOOL_CALL_CONTENT_MAX_CHARS);
25050
+ const tail = text3.slice(text3.length - TOOL_CALL_CONTENT_MAX_CHARS);
25044
25051
  return `${TOOL_CALL_CONTENT_TRUNCATION_MARKER}${tail}`;
25045
25052
  }
25046
25053
  var RAW_OUTPUT_TEXT_FIELDS = ["content", "stdout", "stderr", "output"];
@@ -25072,9 +25079,9 @@ function extractTextContentFromToolCallContent(content) {
25072
25079
  const typed = content;
25073
25080
  const chunks = [];
25074
25081
  for (const entry of typed) {
25075
- const text2 = toolCallContentText(entry)?.trim();
25076
- if (text2) {
25077
- chunks.push(text2);
25082
+ const text3 = toolCallContentText(entry)?.trim();
25083
+ if (text3) {
25084
+ chunks.push(text3);
25078
25085
  }
25079
25086
  }
25080
25087
  if (chunks.length === 0) {
@@ -25325,11 +25332,11 @@ function parseSessionUpdateEvent(params) {
25325
25332
  const contentType = content?.["type"];
25326
25333
  const streamKind = kind === "agent_thought_chunk" ? "reasoning_text" : "assistant_text";
25327
25334
  if (contentType === "text") {
25328
- const text2 = typeof content?.["text"] === "string" ? content["text"] : "";
25329
- if (text2.length > 0) {
25335
+ const text3 = typeof content?.["text"] === "string" ? content["text"] : "";
25336
+ if (text3.length > 0) {
25330
25337
  events.push({
25331
25338
  _tag: "ContentDelta",
25332
- text: text2,
25339
+ text: text3,
25333
25340
  streamKind,
25334
25341
  rawPayload: params
25335
25342
  });
@@ -25362,9 +25369,9 @@ function parseSessionUpdateEvent(params) {
25362
25369
  case "compaction_summary_chunk": {
25363
25370
  const compactionId = readAcpText(upd, "compactionId");
25364
25371
  const content = isRecord5(upd["content"]) ? upd["content"] : void 0;
25365
- const text2 = content?.["type"] === "text" ? content["text"] : void 0;
25366
- if (compactionId === void 0 || typeof text2 !== "string" || text2.length === 0) break;
25367
- events.push({ _tag: "CompactionSummaryChunk", compactionId, text: text2 });
25372
+ const text3 = content?.["type"] === "text" ? content["text"] : void 0;
25373
+ if (compactionId === void 0 || typeof text3 !== "string" || text3.length === 0) break;
25374
+ events.push({ _tag: "CompactionSummaryChunk", compactionId, text: text3 });
25368
25375
  break;
25369
25376
  }
25370
25377
  default:
@@ -25388,24 +25395,7 @@ function filterMcpServersByAgentCapabilities(mcpServers, agentCapabilities) {
25388
25395
  });
25389
25396
  }
25390
25397
 
25391
- // ../../packages/ycodium-acp-executor/src/vendor-hooks.ts
25392
- var defaultVendorHooks = {
25393
- completedStopReason: (response) => response.stopReason
25394
- };
25395
-
25396
- // ../../packages/ycodium-acp-executor/src/executor.ts
25397
- var DEFAULT_CANCEL_GRACE_MS = 5e3;
25398
- var userInputAnswerOptionId = (question, answers) => {
25399
- const answer = answers[question.id];
25400
- const selected = typeof answer === "string" ? answer : Array.isArray(answer) && answer.length === 1 && typeof answer[0] === "string" ? answer[0] : void 0;
25401
- return selected !== void 0 && question.options.some((option) => option.id === selected) ? selected : void 0;
25402
- };
25403
- function defaultNowIso() {
25404
- return (/* @__PURE__ */ new Date()).toISOString();
25405
- }
25406
- function defaultRandomId() {
25407
- return NodeCrypto.randomUUID();
25408
- }
25398
+ // ../../packages/ycodium-acp-executor/src/model-selection.ts
25409
25399
  function normalizeModelId(value) {
25410
25400
  return value?.trim() || void 0;
25411
25401
  }
@@ -25424,6 +25414,25 @@ function modelSelectionStateFromSetup(setup) {
25424
25414
  advertisedModelValues: strategy === "config-option" ? modelConfigOption === void 0 ? [] : collectSessionConfigOptionValues(modelConfigOption) : collectSessionModelIds(sessionModels)
25425
25415
  };
25426
25416
  }
25417
+
25418
+ // ../../packages/ycodium-acp-executor/src/vendor-hooks.ts
25419
+ var defaultVendorHooks = {
25420
+ completedStopReason: (response) => response.stopReason
25421
+ };
25422
+
25423
+ // ../../packages/ycodium-acp-executor/src/executor.ts
25424
+ var DEFAULT_CANCEL_GRACE_MS = 5e3;
25425
+ var userInputAnswerOptionId = (question, answers) => {
25426
+ const answer = answers[question.id];
25427
+ const selected = typeof answer === "string" ? answer : Array.isArray(answer) && answer.length === 1 && typeof answer[0] === "string" ? answer[0] : void 0;
25428
+ return selected !== void 0 && question.options.some((option) => option.id === selected) ? selected : void 0;
25429
+ };
25430
+ function defaultNowIso() {
25431
+ return (/* @__PURE__ */ new Date()).toISOString();
25432
+ }
25433
+ function defaultRandomId() {
25434
+ return NodeCrypto.randomUUID();
25435
+ }
25427
25436
  function createAcpExecutor(options) {
25428
25437
  const hooks = { ...defaultVendorHooks, ...options.hooks };
25429
25438
  const cancelGraceMs = options.cancelGraceMs ?? DEFAULT_CANCEL_GRACE_MS;
@@ -25633,6 +25642,7 @@ function createAcpExecutor(options) {
25633
25642
  const commands = parseAvailableCommandsUpdate(params);
25634
25643
  if (commands !== void 0) {
25635
25644
  availableCommands = commands;
25645
+ options.onAvailableCommands?.(commands);
25636
25646
  }
25637
25647
  if (sessionLoadInFlight) {
25638
25648
  droppedReplayUpdates += parsed.events.filter((event) => event._tag !== "ModeChanged").length;
@@ -26748,13 +26758,13 @@ function parseCliVersion(output2) {
26748
26758
  }
26749
26759
  async function collectCappedText(stream, maxBytes) {
26750
26760
  const decoder = new TextDecoder();
26751
- let text2 = "";
26761
+ let text3 = "";
26752
26762
  let total = 0;
26753
26763
  for await (const chunk of stream) {
26754
26764
  total += chunk.byteLength;
26755
- if (total <= maxBytes) text2 += decoder.decode(chunk, { stream: true });
26765
+ if (total <= maxBytes) text3 += decoder.decode(chunk, { stream: true });
26756
26766
  }
26757
- return { text: text2 + decoder.decode(), overflowed: total > maxBytes };
26767
+ return { text: text3 + decoder.decode(), overflowed: total > maxBytes };
26758
26768
  }
26759
26769
  async function collectProbeProcess(child, input2) {
26760
26770
  try {
@@ -26975,6 +26985,233 @@ var readGrokAccount = (homePath) => {
26975
26985
  };
26976
26986
  };
26977
26987
 
26988
+ // src/grokQuota.ts
26989
+ import * as NodeFS2 from "node:fs";
26990
+ import * as NodePath2 from "node:path";
26991
+ import {
26992
+ parseRetryAfterMs
26993
+ } from "@ycodium-ai/plugin-api/usage";
26994
+ var GROK_USAGE_URL = "https://cli-chat-proxy.grok.com/v1/billing?format=credits";
26995
+ var GROK_ISSUER_PREFIX = "https://auth.x.ai::";
26996
+ var AUTH_FILE_NAME2 = "auth.json";
26997
+ var QUOTA_TIMEOUT_MS = 1e4;
26998
+ var asObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
26999
+ var asString3 = (value) => typeof value === "string" && value.length > 0 ? value : null;
27000
+ var asNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
27001
+ var parseJson = (raw) => {
27002
+ try {
27003
+ return JSON.parse(raw);
27004
+ } catch {
27005
+ return null;
27006
+ }
27007
+ };
27008
+ var extractGrokToken = (auth) => {
27009
+ const record3 = asObject(auth);
27010
+ if (record3 === null) return null;
27011
+ for (const [key, value] of Object.entries(record3)) {
27012
+ if (!key.startsWith(GROK_ISSUER_PREFIX)) continue;
27013
+ const nestedKey = asString3(asObject(value)?.["key"]);
27014
+ if (nestedKey !== null) return nestedKey;
27015
+ }
27016
+ return null;
27017
+ };
27018
+ var clampPercent = (value) => Math.min(100, Math.max(0, value));
27019
+ var grokQuotaWindows = (body) => {
27020
+ const config2 = asObject(asObject(body)?.["config"]);
27021
+ const productUsage = config2?.["productUsage"];
27022
+ const fromProduct = Array.isArray(productUsage) ? asNumber(
27023
+ productUsage.map(asObject).find((row) => row !== null && asString3(row["product"]) === "GrokBuild")?.["usagePercent"]
27024
+ ) : null;
27025
+ const usedPercent = asNumber(config2?.["creditUsagePercent"]) ?? fromProduct;
27026
+ return usedPercent === null ? [] : [
27027
+ {
27028
+ id: "weekly",
27029
+ label: { key: "usage.window.weekly" },
27030
+ usedPercent: clampPercent(usedPercent),
27031
+ resetsAt: asString3(asObject(config2?.["currentPeriod"])?.["end"]) ?? asString3(config2?.["billingPeriodEnd"])
27032
+ }
27033
+ ];
27034
+ };
27035
+ var createGrokUsage = (input2) => {
27036
+ const now = input2.now ?? Date.now;
27037
+ const authPath = NodePath2.join(input2.homePath, AUTH_FILE_NAME2);
27038
+ const readQuota = async (request) => {
27039
+ const baseUrlKey = GROK_CARD.modelConnectivity.baseUrlEnvKey;
27040
+ if (baseUrlKey !== void 0 && (input2.environment[baseUrlKey]?.trim() ?? "") !== "") {
27041
+ return { status: "unavailable", reason: "customEndpoint" };
27042
+ }
27043
+ let token = null;
27044
+ try {
27045
+ token = extractGrokToken(parseJson(NodeFS2.readFileSync(authPath, "utf8")));
27046
+ } catch {
27047
+ token = null;
27048
+ }
27049
+ if (token === null) {
27050
+ const apiKey = input2.environment["XAI_API_KEY"]?.trim() ?? "";
27051
+ return {
27052
+ status: "unavailable",
27053
+ reason: apiKey.length > 0 ? "unsupportedLogin" : "missingCredentials"
27054
+ };
27055
+ }
27056
+ let response;
27057
+ try {
27058
+ response = await input2.fetch(GROK_USAGE_URL, {
27059
+ headers: {
27060
+ Authorization: `Bearer ${token}`,
27061
+ Accept: "application/json",
27062
+ "X-XAI-Token-Auth": "xai-grok-cli"
27063
+ },
27064
+ signal: AbortSignal.any([
27065
+ ...request.signal === void 0 ? [] : [request.signal],
27066
+ AbortSignal.timeout(QUOTA_TIMEOUT_MS)
27067
+ ])
27068
+ });
27069
+ } catch {
27070
+ return { status: "unavailable", reason: "unreachable" };
27071
+ }
27072
+ if (response.status === 401 || response.status === 403) {
27073
+ return { status: "unavailable", reason: "authRejected" };
27074
+ }
27075
+ if (response.status === 429) {
27076
+ const retryAfterMs = parseRetryAfterMs(response.headers.get("retry-after"), now());
27077
+ return {
27078
+ status: "unavailable",
27079
+ reason: "rateLimited",
27080
+ ...retryAfterMs === null ? {} : { retryAfterMs }
27081
+ };
27082
+ }
27083
+ if (!response.ok) return { status: "unavailable", reason: "unreachable" };
27084
+ let body;
27085
+ try {
27086
+ body = parseJson(await response.text());
27087
+ } catch {
27088
+ return { status: "unavailable", reason: "unreachable" };
27089
+ }
27090
+ if (asObject(body) === null) return { status: "unavailable", reason: "unreachable" };
27091
+ return {
27092
+ status: "available",
27093
+ windows: grokQuotaWindows(body),
27094
+ balances: [],
27095
+ accountKey: authPath
27096
+ };
27097
+ };
27098
+ return { readQuota };
27099
+ };
27100
+
27101
+ // src/grokTurnUsage.ts
27102
+ var USAGE_GRACE_MS = 300;
27103
+ var isRecord8 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
27104
+ var count = (value) => typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.trunc(value) : 0;
27105
+ var text = (value) => typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
27106
+ var readModelTotals = (entry) => {
27107
+ if (!isRecord8(entry)) return void 0;
27108
+ const input2 = count(entry["inputTokens"]);
27109
+ const cachedInputTokens = count(entry["cachedReadTokens"]);
27110
+ const cacheCreationTokens = count(entry["cacheCreationTokens"]);
27111
+ const outputTokens = count(entry["outputTokens"]);
27112
+ return {
27113
+ uncachedInputTokens: Math.max(0, input2 - cachedInputTokens - cacheCreationTokens),
27114
+ cachedInputTokens,
27115
+ cacheCreationTokens,
27116
+ outputTokens,
27117
+ reasoningTokens: Math.min(count(entry["reasoningTokens"]), outputTokens)
27118
+ };
27119
+ };
27120
+ var sumOf = (totals) => totals.uncachedInputTokens + totals.cachedInputTokens + totals.cacheCreationTokens + totals.outputTokens;
27121
+ var grokTurnUsageReport = (promptResponse) => {
27122
+ const meta3 = isRecord8(promptResponse) ? promptResponse["_meta"] : void 0;
27123
+ const usage = isRecord8(meta3) ? meta3["usage"] : void 0;
27124
+ if (!isRecord8(meta3) || !isRecord8(usage)) return void 0;
27125
+ const perModel = /* @__PURE__ */ new Map();
27126
+ const modelUsage = usage["modelUsage"];
27127
+ if (isRecord8(modelUsage)) {
27128
+ for (const [model, entry] of Object.entries(modelUsage)) {
27129
+ const totals = readModelTotals(entry);
27130
+ if (totals !== void 0 && model.trim().length > 0) perModel.set(model.trim(), totals);
27131
+ }
27132
+ }
27133
+ if (perModel.size === 0) {
27134
+ const fallbackModel = text(meta3["modelId"]);
27135
+ const totals = readModelTotals(usage);
27136
+ if (fallbackModel === void 0 || totals === void 0) return void 0;
27137
+ perModel.set(fallbackModel, totals);
27138
+ }
27139
+ let dominant;
27140
+ const sum = {
27141
+ uncachedInputTokens: 0,
27142
+ cachedInputTokens: 0,
27143
+ cacheCreationTokens: 0,
27144
+ outputTokens: 0,
27145
+ reasoningTokens: 0
27146
+ };
27147
+ for (const [model, totals] of perModel) {
27148
+ sum.uncachedInputTokens += totals.uncachedInputTokens;
27149
+ sum.cachedInputTokens += totals.cachedInputTokens;
27150
+ sum.cacheCreationTokens += totals.cacheCreationTokens;
27151
+ sum.outputTokens += totals.outputTokens;
27152
+ sum.reasoningTokens += totals.reasoningTokens;
27153
+ const size = sumOf(totals);
27154
+ if (dominant === void 0 || size > dominant.size) dominant = { model, size };
27155
+ }
27156
+ if (dominant === void 0 || sumOf(sum) === 0) return void 0;
27157
+ const sessionId = text(meta3["sessionId"]);
27158
+ return {
27159
+ provider: "grok",
27160
+ model: dominant.model,
27161
+ totals: sum,
27162
+ ...sessionId === void 0 ? {} : { sessionId }
27163
+ };
27164
+ };
27165
+ var createGrokTurnUsageCollector = (base) => {
27166
+ const pastKeys = [];
27167
+ let promptKey;
27168
+ let report;
27169
+ let seen = false;
27170
+ let wake;
27171
+ const mark = () => {
27172
+ seen = true;
27173
+ wake?.();
27174
+ };
27175
+ const observe = (response) => {
27176
+ const key = base.responseKey?.(response);
27177
+ if (key !== void 0 && key !== promptKey && pastKeys.includes(key)) return;
27178
+ report = grokTurnUsageReport(response) ?? report;
27179
+ mark();
27180
+ };
27181
+ const settlement = {
27182
+ ...base,
27183
+ requestFields: (key) => {
27184
+ if (promptKey !== void 0) pastKeys.push(promptKey);
27185
+ if (pastKeys.length > 16) pastKeys.shift();
27186
+ promptKey = key;
27187
+ report = void 0;
27188
+ seen = false;
27189
+ return base.requestFields(key);
27190
+ },
27191
+ responseKey: (response) => {
27192
+ observe(response);
27193
+ return base.responseKey?.(response);
27194
+ }
27195
+ };
27196
+ const attach = async (event) => {
27197
+ if (event.type !== "turn.completed" || !isRecord8(event.payload)) return event;
27198
+ if (event.payload["state"] === "completed" && !seen) {
27199
+ await new Promise((resolve2) => {
27200
+ const timer = setTimeout(resolve2, USAGE_GRACE_MS);
27201
+ wake = () => {
27202
+ clearTimeout(timer);
27203
+ resolve2();
27204
+ };
27205
+ });
27206
+ wake = void 0;
27207
+ }
27208
+ const taken = report;
27209
+ report = void 0;
27210
+ return taken === void 0 ? event : { ...event, payload: { ...event.payload, tokenUsage: taken } };
27211
+ };
27212
+ return { settlement, attach };
27213
+ };
27214
+
26978
27215
  // src/grokPlanGate.ts
26979
27216
  var XAI_EXIT_PLAN_MODE_METHODS = [
26980
27217
  "_x.ai/exit_plan_mode",
@@ -26983,16 +27220,16 @@ var XAI_EXIT_PLAN_MODE_METHODS = [
26983
27220
  var GROK_PLAN_APPROVE_OPTION_ID = "approved";
26984
27221
  var GROK_PLAN_ABANDON_OPTION_ID = "abandoned";
26985
27222
  var GROK_EMPTY_PLAN_TEXT = "The agent exited plan mode without writing a plan.";
26986
- var isRecord8 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
27223
+ var isRecord9 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
26987
27224
  var trimmed = (value) => {
26988
27225
  if (typeof value !== "string") return void 0;
26989
- const text2 = value.trim();
26990
- return text2.length > 0 ? text2 : void 0;
27226
+ const text3 = value.trim();
27227
+ return text3.length > 0 ? text3 : void 0;
26991
27228
  };
26992
27229
  function parseXAiExitPlanModeParams(params) {
26993
- const candidates = [params, ...isRecord8(params) ? [params["params"]] : []];
27230
+ const candidates = [params, ...isRecord9(params) ? [params["params"]] : []];
26994
27231
  for (const candidate of candidates) {
26995
- if (!isRecord8(candidate)) continue;
27232
+ if (!isRecord9(candidate)) continue;
26996
27233
  const toolCallId = trimmed(candidate["toolCallId"]);
26997
27234
  if (toolCallId === void 0) continue;
26998
27235
  return { toolCallId, planContent: trimmed(candidate["planContent"]) };
@@ -27033,18 +27270,18 @@ function makeXAiExitPlanModeResponse(params, resolution) {
27033
27270
  if (resolution.kind === "cancelled") return abandoned();
27034
27271
  const answer = resolution.answers[params.toolCallId];
27035
27272
  const selected = typeof answer === "string" ? answer : Array.isArray(answer) && answer.length === 1 && typeof answer[0] === "string" ? answer[0] : void 0;
27036
- const text2 = trimmed(selected);
27037
- if (text2 === void 0) return abandoned();
27038
- if (text2 === GROK_PLAN_APPROVE_OPTION_ID) return { outcome: "approved" };
27039
- if (text2 === GROK_PLAN_ABANDON_OPTION_ID) return abandoned();
27040
- return { outcome: "request_changes", feedback: text2 };
27273
+ const text3 = trimmed(selected);
27274
+ if (text3 === void 0) return abandoned();
27275
+ if (text3 === GROK_PLAN_APPROVE_OPTION_ID) return { outcome: "approved" };
27276
+ if (text3 === GROK_PLAN_ABANDON_OPTION_ID) return abandoned();
27277
+ return { outcome: "request_changes", feedback: text3 };
27041
27278
  }
27042
27279
 
27043
27280
  // src/grokBackgroundTasks.ts
27044
27281
  var record2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
27045
- var text = (value) => typeof value === "string" ? value.trim() || void 0 : void 0;
27282
+ var text2 = (value) => typeof value === "string" ? value.trim() || void 0 : void 0;
27046
27283
  var lifecycle = (status, exitCode) => {
27047
- switch (text(status)?.toLowerCase()) {
27284
+ switch (text2(status)?.toLowerCase()) {
27048
27285
  case "pending":
27049
27286
  case "running":
27050
27287
  return "running";
@@ -27098,19 +27335,19 @@ function buildGrokBackgroundTaskEvents(input2) {
27098
27335
  });
27099
27336
  };
27100
27337
  if (output2["type"] === "Monitor" && toolCallStatus === "completed") {
27101
- const id = text(output2["taskId"]);
27338
+ const id = text2(output2["taskId"]);
27102
27339
  if (id) {
27103
- start(id, "monitor", text(record2(input2.rawInput)["description"]) ?? "Monitor", toolCallId);
27340
+ start(id, "monitor", text2(record2(input2.rawInput)["description"]) ?? "Monitor", toolCallId);
27104
27341
  }
27105
27342
  } else if (output2["type"] === "BackgroundTaskStarted") {
27106
- const id = text(output2["task_id"]) ?? text(output2["taskId"]);
27107
- const command = text(output2["command"]);
27343
+ const id = text2(output2["task_id"]) ?? text2(output2["taskId"]);
27344
+ const command = text2(output2["command"]);
27108
27345
  if (id && command) start(id, "shell", command.split("\n")[0].slice(0, 200), toolCallId);
27109
27346
  } else if (output2["type"] === "TaskOutput" || output2["type"] === "KillTask") {
27110
27347
  const results = record2(output2["MultiResult"])["results"];
27111
27348
  for (const value of Array.isArray(results) ? results : [output2["Result"]]) {
27112
27349
  const result = record2(value);
27113
- const id = text(result["task_id"]);
27350
+ const id = text2(result["task_id"]);
27114
27351
  if (!id) continue;
27115
27352
  if (output2["type"] === "KillTask") {
27116
27353
  const task2 = tasks.get(id);
@@ -27119,11 +27356,11 @@ function buildGrokBackgroundTaskEvents(input2) {
27119
27356
  }
27120
27357
  continue;
27121
27358
  }
27122
- const command = text(result["command"]);
27359
+ const command = text2(result["command"]);
27123
27360
  const status = lifecycle(result["status"], result["exit_code"]);
27124
27361
  if (!command || !status || command.startsWith("[subagent:")) continue;
27125
27362
  const task = start(id, /^\[monitor[:\]]/.test(command) ? "monitor" : "shell", command);
27126
- const summary = text(result["output"])?.split("\n").find((line) => line.trim())?.trim();
27363
+ const summary = text2(result["output"])?.split("\n").find((line) => line.trim())?.trim();
27127
27364
  if (status === "running") {
27128
27365
  events.push({
27129
27366
  type: "task.progress",
@@ -27140,11 +27377,11 @@ function buildGrokBackgroundTaskEvents(input2) {
27140
27377
 
27141
27378
  // src/grokSupport.ts
27142
27379
  import { modelSelectionStringOption } from "@ycodium-ai/plugin-api/executor";
27143
- var isRecord9 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
27380
+ var isRecord10 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
27144
27381
  var trimmed2 = (value) => {
27145
27382
  if (typeof value !== "string") return void 0;
27146
- const text2 = value.trim();
27147
- return text2.length > 0 ? text2 : void 0;
27383
+ const text3 = value.trim();
27384
+ return text3.length > 0 ? text3 : void 0;
27148
27385
  };
27149
27386
  function grokReasoningEffortArgs(effort) {
27150
27387
  const value = effort?.trim();
@@ -27255,28 +27492,28 @@ function grokRuntimeModeCaveat(nativeModeId) {
27255
27492
  }
27256
27493
  var GROK_REASONING_OPTION_ID = "reasoningEffort";
27257
27494
  function resolveGrokAuthMethodId(initializeResult) {
27258
- const authMethods = isRecord9(initializeResult) ? initializeResult["authMethods"] : void 0;
27495
+ const authMethods = isRecord10(initializeResult) ? initializeResult["authMethods"] : void 0;
27259
27496
  if (!Array.isArray(authMethods)) return void 0;
27260
27497
  for (const method of authMethods) {
27261
- if (isRecord9(method) && method["id"] === "cached_token") return "cached_token";
27498
+ if (isRecord10(method) && method["id"] === "cached_token") return "cached_token";
27262
27499
  }
27263
27500
  return void 0;
27264
27501
  }
27265
27502
  var positive = (value) => typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.trunc(value) : void 0;
27266
27503
  function grokPromptUsage(promptResponse) {
27267
- if (!isRecord9(promptResponse)) return void 0;
27504
+ if (!isRecord10(promptResponse)) return void 0;
27268
27505
  const meta3 = promptResponse["_meta"];
27269
- if (!isRecord9(meta3)) return void 0;
27506
+ if (!isRecord10(meta3)) return void 0;
27270
27507
  const usedTokens = positive(meta3["totalTokens"]);
27271
27508
  if (usedTokens === void 0) return void 0;
27272
27509
  return { usedTokens, modelId: trimmed2(meta3["modelId"]) };
27273
27510
  }
27274
27511
  function grokModelState(initializeResult) {
27275
- if (!isRecord9(initializeResult)) return void 0;
27512
+ if (!isRecord10(initializeResult)) return void 0;
27276
27513
  const meta3 = initializeResult["_meta"];
27277
- if (!isRecord9(meta3)) return void 0;
27514
+ if (!isRecord10(meta3)) return void 0;
27278
27515
  const modelState = meta3["modelState"];
27279
- if (!isRecord9(modelState)) return void 0;
27516
+ if (!isRecord10(modelState)) return void 0;
27280
27517
  const availableModels = modelState["availableModels"];
27281
27518
  if (!Array.isArray(availableModels)) return void 0;
27282
27519
  return { currentModelId: trimmed2(modelState["currentModelId"]), availableModels };
@@ -27286,11 +27523,11 @@ function grokAdvertisedModels(initializeResult) {
27286
27523
  if (state === void 0) return [];
27287
27524
  const models = [];
27288
27525
  for (const entry of state.availableModels) {
27289
- if (!isRecord9(entry)) continue;
27526
+ if (!isRecord10(entry)) continue;
27290
27527
  const modelId = trimmed2(entry["modelId"]);
27291
27528
  if (modelId === void 0) continue;
27292
27529
  const modelMeta = entry["_meta"];
27293
- const contextWindow = isRecord9(modelMeta) ? positive(modelMeta["totalContextTokens"]) : void 0;
27530
+ const contextWindow = isRecord10(modelMeta) ? positive(modelMeta["totalContextTokens"]) : void 0;
27294
27531
  models.push({
27295
27532
  modelId,
27296
27533
  name: trimmed2(entry["name"]),
@@ -27305,7 +27542,7 @@ var XAI_ASK_USER_QUESTION_METHODS = [
27305
27542
  "x.ai/ask_user_question"
27306
27543
  ];
27307
27544
  var readOption = (value) => {
27308
- if (!isRecord9(value)) return void 0;
27545
+ if (!isRecord10(value)) return void 0;
27309
27546
  const label = typeof value["label"] === "string" ? value["label"] : void 0;
27310
27547
  if (label === void 0) return void 0;
27311
27548
  return {
@@ -27316,7 +27553,7 @@ var readOption = (value) => {
27316
27553
  };
27317
27554
  };
27318
27555
  var readQuestion = (value) => {
27319
- if (!isRecord9(value)) return void 0;
27556
+ if (!isRecord10(value)) return void 0;
27320
27557
  const question = typeof value["question"] === "string" ? value["question"] : void 0;
27321
27558
  if (question === void 0) return void 0;
27322
27559
  const optionsRaw = value["options"];
@@ -27332,9 +27569,9 @@ var readQuestion = (value) => {
27332
27569
  };
27333
27570
  };
27334
27571
  function parseXAiAskUserQuestionParams(params) {
27335
- const candidates = [params, ...isRecord9(params) ? [params["params"]] : []];
27572
+ const candidates = [params, ...isRecord10(params) ? [params["params"]] : []];
27336
27573
  for (const candidate of candidates) {
27337
- if (!isRecord9(candidate)) continue;
27574
+ if (!isRecord10(candidate)) continue;
27338
27575
  const questionsRaw = candidate["questions"];
27339
27576
  if (!Array.isArray(questionsRaw)) continue;
27340
27577
  const questions = questionsRaw.flatMap((entry) => {
@@ -27360,12 +27597,12 @@ function extractXAiAskUserQuestions(params) {
27360
27597
  var answerValues = (answer) => {
27361
27598
  if (Array.isArray(answer)) {
27362
27599
  return answer.flatMap((entry) => {
27363
- const text3 = trimmed2(entry);
27364
- return text3 === void 0 ? [] : [text3];
27600
+ const text4 = trimmed2(entry);
27601
+ return text4 === void 0 ? [] : [text4];
27365
27602
  });
27366
27603
  }
27367
- const text2 = trimmed2(answer);
27368
- return text2 === void 0 ? [] : [text2];
27604
+ const text3 = trimmed2(answer);
27605
+ return text3 === void 0 ? [] : [text3];
27369
27606
  };
27370
27607
  function makeXAiAskUserQuestionResponse(params, answers) {
27371
27608
  const normalized = params.questions.flatMap((question) => {
@@ -27409,21 +27646,21 @@ var XAI_PROMPT_COMPLETE_METHOD = "_x.ai/session/prompt_complete";
27409
27646
  var GROK_RATE_LIMIT_MESSAGE = "Grok usage limit reached or rate limited. Try again later.";
27410
27647
  var grokAgentResultMessage = (value) => {
27411
27648
  if (typeof value === "string") return trimmed2(value);
27412
- return isRecord9(value) ? trimmed2(value["message"]) : void 0;
27649
+ return isRecord10(value) ? trimmed2(value["message"]) : void 0;
27413
27650
  };
27414
27651
  var grokPromptSettlement = {
27415
27652
  // 关联 id 双写 promptId 与 requestId(宿主 XAiAcpExtension 同款):
27416
27653
  // agent 认回哪个键是它的事,两个都给才不猜版本。
27417
27654
  requestFields: (promptKey) => ({ _meta: { promptId: promptKey, requestId: promptKey } }),
27418
27655
  responseKey: (response) => {
27419
- if (!isRecord9(response)) return void 0;
27656
+ if (!isRecord10(response)) return void 0;
27420
27657
  const meta3 = response["_meta"];
27421
- if (!isRecord9(meta3)) return void 0;
27658
+ if (!isRecord10(meta3)) return void 0;
27422
27659
  return trimmed2(meta3["promptId"]) ?? trimmed2(meta3["requestId"]);
27423
27660
  },
27424
27661
  fromNotification: (method, params) => {
27425
27662
  if (method !== XAI_PROMPT_COMPLETE_METHOD) return void 0;
27426
- if (!isRecord9(params)) return void 0;
27663
+ if (!isRecord10(params)) return void 0;
27427
27664
  const sessionId = params["sessionId"];
27428
27665
  if (typeof sessionId !== "string") return void 0;
27429
27666
  const stopReason = trimmed2(params["stopReason"]);
@@ -27441,10 +27678,11 @@ var grokPromptSettlement = {
27441
27678
 
27442
27679
  // src/grokSkills.ts
27443
27680
  import * as NodeOS from "node:os";
27681
+ import * as NodePath3 from "node:path";
27444
27682
  var INSPECT_TIMEOUT_MS = 15e3;
27445
27683
  var MAX_INSPECT_OUTPUT_BYTES = 4 * 1024 * 1024;
27446
27684
  var GROK_INSPECT_ARGS = ["inspect", "--json"];
27447
- var isRecord10 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
27685
+ var isRecord11 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
27448
27686
  var trimmedString = (value) => typeof value === "string" ? value.trim() : "";
27449
27687
  function parseGrokInspectSkills(stdout) {
27450
27688
  let parsed;
@@ -27453,13 +27691,13 @@ function parseGrokInspectSkills(stdout) {
27453
27691
  } catch {
27454
27692
  return [];
27455
27693
  }
27456
- const entries = isRecord10(parsed) ? parsed["skills"] : void 0;
27694
+ const entries = isRecord11(parsed) ? parsed["skills"] : void 0;
27457
27695
  if (!Array.isArray(entries)) return [];
27458
27696
  const byName = /* @__PURE__ */ new Map();
27459
27697
  for (const entry of entries) {
27460
- if (!isRecord10(entry)) continue;
27698
+ if (!isRecord11(entry)) continue;
27461
27699
  const name = trimmedString(entry["name"]);
27462
- const source = isRecord10(entry["source"]) ? entry["source"] : void 0;
27700
+ const source = isRecord11(entry["source"]) ? entry["source"] : void 0;
27463
27701
  const path = trimmedString(source?.["path"]);
27464
27702
  if (name.length === 0 || path.length === 0) continue;
27465
27703
  const scope = trimmedString(source?.["type"]);
@@ -27474,27 +27712,51 @@ function parseGrokInspectSkills(stdout) {
27474
27712
  }
27475
27713
  return [...byName.values()].toSorted((left, right) => left.name.localeCompare(right.name));
27476
27714
  }
27715
+ var runGrokInspect = async (input2) => {
27716
+ const { recipe, host, env, cwd } = input2;
27717
+ const handle = await host.process.spawn({
27718
+ program: recipe.binaryPath,
27719
+ args: GROK_INSPECT_ARGS,
27720
+ cwd,
27721
+ owner: { bindingId: host.bindingId },
27722
+ ...Object.keys(env).length > 0 ? { env } : {}
27723
+ });
27724
+ const run = await collectProbeProcess(handle, {
27725
+ timeoutMs: INSPECT_TIMEOUT_MS,
27726
+ maxOutputBytes: MAX_INSPECT_OUTPUT_BYTES,
27727
+ what: "grok inspect"
27728
+ });
27729
+ if (run.exitCode !== 0 || run.stdout === void 0) {
27730
+ throw new Error(`grok inspect exited with ${String(run.exitCode)}`);
27731
+ }
27732
+ const parsed = JSON.parse(run.stdout);
27733
+ if (!isRecord11(parsed) || !Array.isArray(parsed["skills"])) {
27734
+ throw new Error("grok inspect output has no skills list");
27735
+ }
27736
+ return parseGrokInspectSkills(run.stdout);
27737
+ };
27477
27738
  async function probeGrokSkills(input2) {
27478
- const { recipe, host, env } = input2;
27479
27739
  try {
27480
- const handle = await host.process.spawn({
27481
- program: recipe.binaryPath,
27482
- args: GROK_INSPECT_ARGS,
27483
- cwd: NodeOS.homedir(),
27484
- owner: { bindingId: host.bindingId },
27485
- ...Object.keys(env).length > 0 ? { env } : {}
27486
- });
27487
- const run = await collectProbeProcess(handle, {
27488
- timeoutMs: INSPECT_TIMEOUT_MS,
27489
- maxOutputBytes: MAX_INSPECT_OUTPUT_BYTES,
27490
- what: "grok inspect"
27491
- });
27492
- if (run.exitCode !== 0 || run.stdout === void 0) return [];
27493
- return parseGrokInspectSkills(run.stdout);
27740
+ return await runGrokInspect({ ...input2, cwd: NodeOS.homedir() });
27494
27741
  } catch {
27495
27742
  return [];
27496
27743
  }
27497
27744
  }
27745
+ async function inspectGrokWorkspace(input2) {
27746
+ const skills = await runGrokInspect(input2);
27747
+ const watched = /* @__PURE__ */ new Set([
27748
+ // 项目技能目录的位置没有实测,这两处只是触发重查的提示,不影响答案本身。
27749
+ NodePath3.join(input2.cwd, ".grok", "skills"),
27750
+ NodePath3.join(input2.cwd, ".agents", "skills")
27751
+ ]);
27752
+ for (const skill of skills) {
27753
+ watched.add(skill.path);
27754
+ const skillDirectory = NodePath3.dirname(skill.path);
27755
+ watched.add(skillDirectory);
27756
+ watched.add(NodePath3.dirname(skillDirectory));
27757
+ }
27758
+ return { skills, watchPaths: [...watched] };
27759
+ }
27498
27760
 
27499
27761
  // src/probeModels.ts
27500
27762
  import {
@@ -27603,14 +27865,14 @@ var hasDeviceCode = (output2) => deviceCodeUrl(output2) !== void 0 && deviceCode
27603
27865
  function expandHomePath(value) {
27604
27866
  if (value === "~") return NodeOS2.homedir();
27605
27867
  if (value.startsWith("~/") || value.startsWith("~\\")) {
27606
- return NodePath2.join(NodeOS2.homedir(), value.slice(2));
27868
+ return NodePath4.join(NodeOS2.homedir(), value.slice(2));
27607
27869
  }
27608
27870
  return value;
27609
27871
  }
27610
27872
  function resolveGrokHomePath(value) {
27611
27873
  const trimmed3 = value.trim();
27612
- if (trimmed3.length === 0) return NodePath2.join(NodeOS2.homedir(), ".grok");
27613
- return NodePath2.resolve(expandHomePath(trimmed3));
27874
+ if (trimmed3.length === 0) return NodePath4.join(NodeOS2.homedir(), ".grok");
27875
+ return NodePath4.resolve(expandHomePath(trimmed3));
27614
27876
  }
27615
27877
  function probeGrokAccount(accountPath) {
27616
27878
  const resolved = resolveGrokHomePath(accountPath);
@@ -27629,12 +27891,12 @@ function grokContinuationKey(recipe) {
27629
27891
  function resolveAccountHomePath(recipe, configuredHome) {
27630
27892
  const fromEnvironment = recipe.environment["GROK_HOME"]?.trim();
27631
27893
  if (fromEnvironment !== void 0 && fromEnvironment.length > 0) {
27632
- return NodePath2.resolve(expandHomePath(fromEnvironment));
27894
+ return NodePath4.resolve(expandHomePath(fromEnvironment));
27633
27895
  }
27634
27896
  return resolveGrokHomePath(configuredHome);
27635
27897
  }
27636
27898
  function absentPath() {
27637
- return NodePath2.join(
27899
+ return NodePath4.join(
27638
27900
  NodeOS2.tmpdir(),
27639
27901
  `ycodium-grok-no-auth-${NodeCrypto2.randomUUID()}`,
27640
27902
  "auth.json"
@@ -27758,6 +28020,23 @@ var createGrokInstance = (config2, host) => {
27758
28020
  };
27759
28021
  const instance = {
27760
28022
  probe,
28023
+ // 项目里的技能:探测在 home 下跑 inspect 只看得到整机的;这里在项目目录里再跑一次。
28024
+ workspaceInventory: ({ cwd }) => {
28025
+ if (recipe.binaryPath.length === 0) {
28026
+ return Promise.reject(new Error("Grok program path is not configured"));
28027
+ }
28028
+ const configuredHome = recipe.homePath;
28029
+ return inspectGrokWorkspace({
28030
+ recipe,
28031
+ host,
28032
+ cwd,
28033
+ env: buildSpawnEnv(
28034
+ recipe,
28035
+ resolveGrokHomePath(configuredHome),
28036
+ configuredHome.trim().length > 0
28037
+ )
28038
+ });
28039
+ },
27761
28040
  // 有意不处理 input.readOnlyDirectories:grok 的 read_file 在每一档都不弹审批、
27762
28041
  // 绝对路径读到哪儿都一样(实测表见 grokSupport.ts),附件目录无需放行。
27763
28042
  startSession: async (input2, signal) => {
@@ -27801,6 +28080,7 @@ var createGrokInstance = (config2, host) => {
27801
28080
  const requestedModelId = modelIdFromSelection(input2.modelSelection);
27802
28081
  let initializeResult;
27803
28082
  const backgroundTasks = /* @__PURE__ */ new Map();
28083
+ const turnUsage = createGrokTurnUsageCollector(grokPromptSettlement);
27804
28084
  const executor = createAcpExecutor({
27805
28085
  readable,
27806
28086
  writable,
@@ -27811,11 +28091,16 @@ var createGrokInstance = (config2, host) => {
27811
28091
  providerInstanceId: host.instanceId,
27812
28092
  clientInfo: GROK_CLIENT_INFO,
27813
28093
  log: host.log,
28094
+ // agent 自己宣告的斜杠命令按会话报给宿主,输入区的 / 菜单才有它们。
28095
+ onAvailableCommands: (commands) => host.reportSessionInventory?.({
28096
+ threadId: input2.threadId,
28097
+ slashCommands: acpCommandsToSlashCommands(commands)
28098
+ }),
27814
28099
  // 等 `initialize` 回来再挑认证方式:能不能用 `cached_token`,只有 agent
27815
28100
  // 播报的 `authMethods` 说了算(见 resolveGrokAuthMethodId)。
27816
28101
  authMethodId: resolveGrokAuthMethodId,
27817
28102
  hooks: {
27818
- promptSettlement: grokPromptSettlement,
28103
+ promptSettlement: turnUsage.settlement,
27819
28104
  onToolCallUpdated: (update) => buildGrokBackgroundTaskEvents({
27820
28105
  tasks: backgroundTasks,
27821
28106
  toolCallId: update.toolCallId,
@@ -27867,7 +28152,7 @@ var createGrokInstance = (config2, host) => {
27867
28152
  });
27868
28153
  const forwarding = (async () => {
27869
28154
  for await (const event of executor.events) {
27870
- events.push(event);
28155
+ events.push(await turnUsage.attach(event));
27871
28156
  }
27872
28157
  })();
27873
28158
  try {
@@ -28014,6 +28299,12 @@ var createGrokInstance = (config2, host) => {
28014
28299
  return { threadId, turns: booked.turns };
28015
28300
  },
28016
28301
  events,
28302
+ // 额度读实例自己的账号目录与连接(与它的 CLI 同源);token 走编排账本,见 grokTurnUsage.ts。
28303
+ usage: createGrokUsage({
28304
+ homePath: resolveAccountHomePath(recipe, recipe.homePath),
28305
+ environment: recipe.environment,
28306
+ fetch: host.fetch
28307
+ }),
28017
28308
  continuationKey: grokContinuationKey(recipe),
28018
28309
  dispose: async () => {
28019
28310
  if (disposed) return;
@@ -28054,9 +28345,9 @@ var createGrokInstance = (config2, host) => {
28054
28345
  const stdoutText = (async () => {
28055
28346
  const decoder = new TextDecoder();
28056
28347
  for await (const chunk of handle.stdout) {
28057
- const text2 = decoder.decode(chunk, { stream: true });
28058
- collected.push(text2);
28059
- host.log.info("sign-in output", { text: text2 });
28348
+ const text3 = decoder.decode(chunk, { stream: true });
28349
+ collected.push(text3);
28350
+ host.log.info("sign-in output", { text: text3 });
28060
28351
  if (onCodeFound !== void 0 && hasDeviceCode(collected.join(""))) {
28061
28352
  onCodeFound();
28062
28353
  onCodeFound = void 0;
package/locales/en.json CHANGED
@@ -10,5 +10,10 @@
10
10
  "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
11
  "placeholder": "e.g. ~/.grok-work"
12
12
  }
13
+ },
14
+ "usage": {
15
+ "window": {
16
+ "weekly": "Weekly"
17
+ }
13
18
  }
14
19
  }
@@ -10,5 +10,10 @@
10
10
  "description": "会话和 `grok login` 的凭据都保存在这里,因此共用同一目录的 provider 可以接手彼此的会话,目录不同的则不能。留空则使用该命令行工具的默认目录(~/.grok)。",
11
11
  "placeholder": "例如 ~/.grok-work"
12
12
  }
13
+ },
14
+ "usage": {
15
+ "window": {
16
+ "weekly": "每周"
17
+ }
13
18
  }
14
19
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ycodium-ai/agent-grok",
3
- "version": "0.2.2690",
3
+ "version": "0.2.2694",
4
4
  "description": "Grok executor: grok CLI over Agent Client Protocol stdio.",
5
5
  "keywords": [
6
6
  "ycodium",
@@ -21,9 +21,9 @@
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.2690",
24
+ "ycodium-acp-executor": "0.2.2694",
25
25
  "ycodium-executor-testing": "0.0.0",
26
- "ycodium-acp-executor": "0.2.2690"
26
+ "@ycodium-ai/plugin-api": "0.2.2694"
27
27
  },
28
28
  "ycodium": {
29
29
  "plugin": "./dist/index.js",