@use-aistack/cli 0.12.4 → 0.14.0

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
@@ -9,7 +9,11 @@ var LOCAL_PRICING_TABLE_VERSION = "local-no-charge";
9
9
  var PROVIDER_VENDOR = {
10
10
  anthropic: "anthropic",
11
11
  openai: "openai",
12
- google: "google"
12
+ google: "google",
13
+ xai: "xai"
14
+ };
15
+ var PRICING_ALIASES = {
16
+ "grok-4.6-build": "grok-4.6"
13
17
  };
14
18
  var LOCAL_PROVIDERS = /* @__PURE__ */ new Set([
15
19
  "ollama",
@@ -105,6 +109,11 @@ var Pricer = class {
105
109
  firstLayerWith(slug, provider) {
106
110
  return this.layers.find((l) => l.has(slug, provider)) ?? null;
107
111
  }
112
+ lookupSlug(slug, provider) {
113
+ if (this.firstLayerWith(slug, provider)) return slug;
114
+ const alias = PRICING_ALIASES[slug];
115
+ return alias && this.firstLayerWith(alias, provider) ? alias : null;
116
+ }
108
117
  /**
109
118
  * Every period that applies to a pricing key, or an empty list when none
110
119
  * can be cited.
@@ -117,14 +126,19 @@ var Pricer = class {
117
126
  periodsFor(modelKey) {
118
127
  const { provider, model } = splitModelKey(modelKey);
119
128
  if (provider === null) {
120
- return this.firstLayerWith(model, null)?.rowsFor(model, null) ?? [];
129
+ const slug = this.lookupSlug(model, null);
130
+ return slug ? this.firstLayerWith(slug, null)?.rowsFor(slug, null) ?? [] : [];
121
131
  }
122
132
  if (LOCAL_PROVIDERS.has(provider)) return [FREE_PERIOD];
123
- const own = this.firstLayerWith(model, provider);
124
- if (own) return own.rowsFor(model, provider);
133
+ const ownSlug = this.lookupSlug(model, provider);
134
+ if (ownSlug) {
135
+ return this.firstLayerWith(ownSlug, provider)?.rowsFor(ownSlug, provider) ?? [];
136
+ }
125
137
  const vendor = PROVIDER_VENDOR[provider];
126
- if (!vendor || this.vendorOf(model) !== vendor) return [];
127
- return this.firstLayerWith(model, null)?.rowsFor(model, null) ?? [];
138
+ const vendorSlug = this.lookupSlug(model, null);
139
+ if (!vendor || !vendorSlug || this.vendorOf(vendorSlug) !== vendor)
140
+ return [];
141
+ return this.firstLayerWith(vendorSlug, null)?.rowsFor(vendorSlug, null) ?? [];
128
142
  }
129
143
  isLocal(modelKey) {
130
144
  const { provider } = splitModelKey(modelKey);
@@ -176,7 +190,7 @@ function parsePriceTable(body) {
176
190
  if (typeof r?.modelSlug !== "string" || r.modelSlug.length === 0 || !num(r.from) || !num(r.input) || !num(r.output) || typeof r.source !== "string") {
177
191
  continue;
178
192
  }
179
- const vendor = r.vendor === "anthropic" || r.vendor === "openai" || r.vendor === "google" || r.vendor === "local" ? r.vendor : void 0;
193
+ const vendor = r.vendor === "anthropic" || r.vendor === "openai" || r.vendor === "google" || r.vendor === "xai" || r.vendor === "local" ? r.vendor : void 0;
180
194
  rows.push({
181
195
  modelSlug: r.modelSlug,
182
196
  ...typeof r.provider === "string" && r.provider.length > 0 ? { provider: r.provider } : {},
@@ -198,7 +212,8 @@ function parsePriceTable(body) {
198
212
  var PRICING_TABLE_VERSION = "anthropic-list-2026-07-25";
199
213
  var OPENAI_PRICING_TABLE_VERSION = "openai-list-2026-08-02";
200
214
  var GOOGLE_PRICING_TABLE_VERSION = "google-list-2026-08-09";
201
- var BUNDLED_PRICE_TABLE_ID = "bundled-2026-08-29";
215
+ var XAI_PRICING_TABLE_VERSION = "models.dev@2026-08-29";
216
+ var BUNDLED_PRICE_TABLE_ID = "bundled-2026-09-10";
202
217
  var CACHE_WRITE_5M_MULTIPLIER = 1.25;
203
218
  var CACHE_WRITE_1H_MULTIPLIER = 2;
204
219
  var CACHE_READ_MULTIPLIER = 0.1;
@@ -213,6 +228,11 @@ var GOOGLE_CACHE_MULTIPLIERS = {
213
228
  write1h: 1,
214
229
  read: 0.1
215
230
  };
231
+ var XAI_CACHE_MULTIPLIERS = {
232
+ write5m: 0,
233
+ write1h: 0,
234
+ read: 0.25
235
+ };
216
236
  var anthropic = (periods) => ({
217
237
  vendor: "anthropic",
218
238
  table: PRICING_TABLE_VERSION,
@@ -231,6 +251,12 @@ var google = (periods) => ({
231
251
  periods,
232
252
  cache: GOOGLE_CACHE_MULTIPLIERS
233
253
  });
254
+ var xai = (periods) => ({
255
+ vendor: "xai",
256
+ table: XAI_PRICING_TABLE_VERSION,
257
+ periods,
258
+ cache: XAI_CACHE_MULTIPLIERS
259
+ });
234
260
  var flat = (input, output) => [
235
261
  { from: null, input, output }
236
262
  ];
@@ -288,7 +314,10 @@ var PRICES = {
288
314
  "gemini-3-pro-preview": google(flat(2, 12)),
289
315
  // A real Anthropic model with no row until #123. Measured in #122 as
290
316
  // `claude-opus-4-5-20251101`, which the dated-suffix rule strips to this key.
291
- "claude-opus-4-5": anthropic(flat(5, 25))
317
+ "claude-opus-4-5": anthropic(flat(5, 25)),
318
+ // models.dev's xAI row mirrored into the live table on 2026-08-29. Grok
319
+ // Build's explicit `grok-4.6-build` pricing alias reaches this base rate.
320
+ "grok-4.6": xai(flat(2, 6))
292
321
  };
293
322
  function bundledPriceTable() {
294
323
  const rows = [];
@@ -348,12 +377,12 @@ function apiEquivalentCost(modelKey, t, atMs) {
348
377
  }
349
378
 
350
379
  // src/version.ts
351
- var CLI_VERSION = true ? "0.12.4" : "0.0.0-dev";
380
+ var CLI_VERSION = true ? "0.14.0" : "0.0.0-dev";
352
381
 
353
382
  // src/api.ts
354
383
  var BASE_URL = process.env.AISTACK_URL || "https://aistack.to";
355
- async function request(path7, options = {}) {
356
- return fetch(`${BASE_URL}${path7}`, {
384
+ async function request(path9, options = {}) {
385
+ return fetch(`${BASE_URL}${path9}`, {
357
386
  ...options,
358
387
  headers: {
359
388
  "Content-Type": "application/json",
@@ -771,9 +800,9 @@ function canonicalizeRepoUrl(input) {
771
800
  function repoNameFromCanonical(canonical) {
772
801
  return parseRepo(canonical)?.repo ?? "";
773
802
  }
774
- function normalizeUpstreamPath(path7) {
775
- if (!path7) return "";
776
- return path7.split("/").filter(Boolean).join("/");
803
+ function normalizeUpstreamPath(path9) {
804
+ if (!path9) return "";
805
+ return path9.split("/").filter(Boolean).join("/");
777
806
  }
778
807
 
779
808
  // src/git.ts
@@ -819,16 +848,16 @@ function buildRepoLinkResource(canonical) {
819
848
  import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
820
849
  import { homedir as homedir2 } from "node:os";
821
850
  import { join as join2 } from "node:path";
822
- function readJson(path7) {
851
+ function readJson(path9) {
823
852
  try {
824
- if (!existsSync2(path7)) return null;
825
- return JSON.parse(readFileSync2(path7, "utf-8"));
853
+ if (!existsSync2(path9)) return null;
854
+ return JSON.parse(readFileSync2(path9, "utf-8"));
826
855
  } catch {
827
856
  return null;
828
857
  }
829
858
  }
830
- function hooksFrom(path7, source, out, seen) {
831
- const hooks = readJson(path7)?.hooks;
859
+ function hooksFrom(path9, source, out, seen) {
860
+ const hooks = readJson(path9)?.hooks;
832
861
  if (!hooks || typeof hooks !== "object") return;
833
862
  for (const [event, config] of Object.entries(hooks)) {
834
863
  const stableKey = `hooks:${source}:${event}`;
@@ -917,10 +946,19 @@ function splitImageTag(image) {
917
946
  }
918
947
  function parseMcpPackage(server) {
919
948
  if (server.url) {
949
+ let safeUrl;
950
+ try {
951
+ const parsed = new URL(server.url);
952
+ parsed.username = "";
953
+ parsed.password = "";
954
+ safeUrl = parsed.toString();
955
+ } catch {
956
+ return null;
957
+ }
920
958
  const t = (server.type ?? server.transport ?? "").toLowerCase();
921
959
  return {
922
960
  registry: "url",
923
- id: server.url,
961
+ id: safeUrl,
924
962
  transport: t === "sse" ? "sse" : "http"
925
963
  };
926
964
  }
@@ -968,16 +1006,16 @@ function buildMcpResource(name, group, pkg) {
968
1006
  pkg
969
1007
  };
970
1008
  }
971
- function readText(path7) {
1009
+ function readText(path9) {
972
1010
  try {
973
- if (!existsSync3(path7)) return null;
974
- return readFileSync3(path7, "utf-8");
1011
+ if (!existsSync3(path9)) return null;
1012
+ return readFileSync3(path9, "utf-8");
975
1013
  } catch {
976
1014
  return null;
977
1015
  }
978
1016
  }
979
- function readParsed(path7, parse2) {
980
- const raw = readText(path7);
1017
+ function readParsed(path9, parse2) {
1018
+ const raw = readText(path9);
981
1019
  if (raw === null) return null;
982
1020
  try {
983
1021
  return parse2(raw);
@@ -985,14 +1023,14 @@ function readParsed(path7, parse2) {
985
1023
  return null;
986
1024
  }
987
1025
  }
988
- function readJson2(path7) {
989
- return readParsed(path7, JSON.parse);
1026
+ function readJson2(path9) {
1027
+ return readParsed(path9, JSON.parse);
990
1028
  }
991
- function readYaml(path7) {
992
- return readParsed(path7, parseYaml);
1029
+ function readYaml(path9) {
1030
+ return readParsed(path9, parseYaml);
993
1031
  }
994
- function readToml(path7) {
995
- return readParsed(path7, parseToml);
1032
+ function readToml(path9) {
1033
+ return readParsed(path9, parseToml);
996
1034
  }
997
1035
  function continueListToMap(file) {
998
1036
  if (!file?.mcpServers?.length) return void 0;
@@ -1027,6 +1065,25 @@ function detectMcpServers(cwd, home = homedir3()) {
1027
1065
  out.push(resource);
1028
1066
  }
1029
1067
  };
1068
+ const grokHome = process.env.GROK_HOME ?? join3(home, ".grok");
1069
+ const grokGlobal = readToml(join3(grokHome, "config.toml"));
1070
+ const grokProject = readToml(join3(cwd, ".grok", "config.toml"));
1071
+ const disabled = /* @__PURE__ */ new Set([
1072
+ ...grokGlobal?.disabled_mcp_servers ?? [],
1073
+ ...grokProject?.disabled_mcp_servers ?? []
1074
+ ]);
1075
+ const effectiveGrok = {
1076
+ ...grokGlobal?.mcp_servers ?? {},
1077
+ ...grokProject?.mcp_servers ?? {}
1078
+ };
1079
+ add(
1080
+ Object.fromEntries(
1081
+ Object.entries(effectiveGrok).filter(
1082
+ ([name, config]) => config.enabled !== false && !disabled.has(name)
1083
+ )
1084
+ ),
1085
+ "grok-build"
1086
+ );
1030
1087
  add(readJson2(join3(cwd, ".mcp.json"))?.mcpServers, "claude-code");
1031
1088
  add(readJson2(join3(cwd, "mcp.json"))?.mcpServers, "generic");
1032
1089
  add(
@@ -1112,8 +1169,8 @@ function resolveSource(entry, mpRepoUrl) {
1112
1169
  const src = entry.source;
1113
1170
  if (typeof src === "string") {
1114
1171
  if (!mpRepoUrl) return null;
1115
- const path7 = src.replace(/^\.\//, "").replace(/\/+$/, "");
1116
- return { url: mpRepoUrl, path: path7 || void 0 };
1172
+ const path9 = src.replace(/^\.\//, "").replace(/\/+$/, "");
1173
+ return { url: mpRepoUrl, path: path9 || void 0 };
1117
1174
  }
1118
1175
  if (src && typeof src === "object" && src.url) {
1119
1176
  return { url: src.url, path: src.path, sha: src.sha };
@@ -1150,10 +1207,10 @@ function resolvePluginLinks(installed, marketplaces, manifests) {
1150
1207
  }
1151
1208
  return out;
1152
1209
  }
1153
- function readJson3(path7) {
1210
+ function readJson3(path9) {
1154
1211
  try {
1155
- if (!existsSync4(path7)) return null;
1156
- return JSON.parse(readFileSync4(path7, "utf-8"));
1212
+ if (!existsSync4(path9)) return null;
1213
+ return JSON.parse(readFileSync4(path9, "utf-8"));
1157
1214
  } catch {
1158
1215
  return null;
1159
1216
  }
@@ -1187,6 +1244,7 @@ var LOCAL_PATTERNS = [
1187
1244
  // Rules
1188
1245
  { path: "CLAUDE.md", type: "rule", group: "claude-code" },
1189
1246
  { path: "AGENTS.md", type: "rule", group: "claude-code" },
1247
+ { path: "GROK.md", type: "rule", group: "grok-build" },
1190
1248
  { path: "GEMINI.md", type: "rule", group: "gemini" },
1191
1249
  { path: ".cursorrules", type: "rule", group: "cursor" },
1192
1250
  { path: ".windsurfrules", type: "rule", group: "windsurf" },
@@ -1217,8 +1275,16 @@ var LOCAL_DIR_PATTERNS = [
1217
1275
  { dir: ".github/instructions", type: "rule", group: "copilot" },
1218
1276
  { dir: ".github/prompts", type: "prompt", group: "copilot" },
1219
1277
  { dir: ".claude/commands", type: "command", group: "claude-code" },
1278
+ { dir: ".claude/skills", type: "skill", group: "claude-code" },
1220
1279
  { dir: ".claude/agents", type: "subagent", group: "claude-code" },
1221
1280
  { dir: ".claude/hooks", type: "hook", group: "claude-code" },
1281
+ { dir: ".grok/skills", type: "skill", group: "grok-build" },
1282
+ { dir: ".grok/commands", type: "command", group: "grok-build" },
1283
+ { dir: ".grok/agents", type: "subagent", group: "grok-build" },
1284
+ { dir: ".grok/hooks", type: "hook", group: "grok-build" },
1285
+ { dir: ".cursor/skills", type: "skill", group: "cursor" },
1286
+ { dir: ".agents/skills", type: "skill", group: "generic" },
1287
+ { dir: ".agents/commands", type: "command", group: "generic" },
1222
1288
  { dir: "prompts", type: "prompt", group: "generic" },
1223
1289
  { dir: ".ai", type: "custom", group: "generic" }
1224
1290
  ];
@@ -1233,8 +1299,8 @@ function loadGitignore(cwd) {
1233
1299
  }
1234
1300
  function readFileSafe(filePath) {
1235
1301
  try {
1236
- const stat6 = statSync(filePath);
1237
- if (stat6.size > MAX_FILE_SIZE) return null;
1302
+ const stat7 = statSync(filePath);
1303
+ if (stat7.size > MAX_FILE_SIZE) return null;
1238
1304
  return readFileSync5(filePath, "utf-8");
1239
1305
  } catch {
1240
1306
  return null;
@@ -1299,7 +1365,9 @@ function scanLocal(cwd) {
1299
1365
  for (const entry of readdirSync2(cwd, { withFileTypes: true }).filter(
1300
1366
  (e) => e.isDirectory()
1301
1367
  )) {
1302
- if (ig.ignores(entry.name + "/")) continue;
1368
+ if ([".grok", ".claude", ".cursor", ".agents"].includes(entry.name))
1369
+ continue;
1370
+ if (ig.ignores(`${entry.name}/`)) continue;
1303
1371
  scanSkillDirs(join5(cwd, entry.name), cwd, ig, results, 1);
1304
1372
  }
1305
1373
  } catch {
@@ -1332,7 +1400,7 @@ function scanSkillDirs(dir, cwd, ig, results, depth) {
1332
1400
  for (const entry of readdirSync2(dir, { withFileTypes: true })) {
1333
1401
  if (entry.isDirectory()) {
1334
1402
  const rel = relative(cwd, join5(dir, entry.name));
1335
- if (!ig.ignores(rel + "/")) {
1403
+ if (!ig.ignores(`${rel}/`)) {
1336
1404
  scanSkillDirs(join5(dir, entry.name), cwd, ig, results, depth + 1);
1337
1405
  }
1338
1406
  }
@@ -1345,6 +1413,7 @@ function scanGlobal() {
1345
1413
  const results = [];
1346
1414
  const globalPatterns = [
1347
1415
  { path: ".claude/CLAUDE.md", type: "rule", group: "claude-code" },
1416
+ { path: ".grok/GROK.md", type: "rule", group: "grok-build" },
1348
1417
  { path: ".claude/settings.json", type: "config", group: "claude-code" },
1349
1418
  { path: ".continue/config.json", type: "config", group: "continue" },
1350
1419
  { path: ".continue/config.yaml", type: "config", group: "continue" },
@@ -1376,7 +1445,14 @@ function scanGlobal() {
1376
1445
  { dir: ".claude/commands", type: "command", group: "claude-code" },
1377
1446
  { dir: ".claude/agents", type: "subagent", group: "claude-code" },
1378
1447
  { dir: ".claude/hooks", type: "hook", group: "claude-code" },
1379
- { dir: ".cursor/rules", type: "rule", group: "cursor" }
1448
+ { dir: ".grok/skills", type: "skill", group: "grok-build" },
1449
+ { dir: ".grok/commands", type: "command", group: "grok-build" },
1450
+ { dir: ".grok/agents", type: "subagent", group: "grok-build" },
1451
+ { dir: ".grok/hooks", type: "hook", group: "grok-build" },
1452
+ { dir: ".cursor/rules", type: "rule", group: "cursor" },
1453
+ { dir: ".cursor/skills", type: "skill", group: "cursor" },
1454
+ { dir: ".agents/skills", type: "skill", group: "generic" },
1455
+ { dir: ".agents/commands", type: "command", group: "generic" }
1380
1456
  ];
1381
1457
  for (const { dir, type, group } of globalDirs) {
1382
1458
  const dirPath = join5(home, dir);
@@ -1768,7 +1844,7 @@ function diffResources(current, existing) {
1768
1844
  // src/commands/connect.ts
1769
1845
  import { spawnSync } from "node:child_process";
1770
1846
  import { cpSync, existsSync as existsSync6 } from "node:fs";
1771
- import { homedir as homedir10 } from "node:os";
1847
+ import { homedir as homedir11 } from "node:os";
1772
1848
  import { dirname as dirname3, join as join6 } from "node:path";
1773
1849
  import { fileURLToPath } from "node:url";
1774
1850
  import * as p3 from "@clack/prompts";
@@ -1914,12 +1990,12 @@ function emptyUsage() {
1914
1990
  };
1915
1991
  }
1916
1992
  var countsTotal = (t) => t.input + t.output + t.cacheWrite5m + t.cacheWrite1h + t.cacheWriteUnsplit + t.cacheRead;
1917
- function addModelUsage(agg, modelKey, counts, costUSD, messages = 1, at) {
1993
+ function addModelUsage(agg, modelKey, counts2, costUSD, messages = 1, at) {
1918
1994
  if (at) {
1919
1995
  noteUsageResponse(agg, {
1920
1996
  tsMs: at.tsMs,
1921
1997
  modelKey,
1922
- counts,
1998
+ counts: counts2,
1923
1999
  costUSD,
1924
2000
  ...at.sidechain ? { sidechain: true } : {}
1925
2001
  });
@@ -1930,13 +2006,13 @@ function addModelUsage(agg, modelKey, counts, costUSD, messages = 1, at) {
1930
2006
  agg.byModel.set(modelKey, m);
1931
2007
  }
1932
2008
  m.messages += messages;
1933
- m.input += counts.input;
1934
- m.output += counts.output;
1935
- m.cacheWrite5m += counts.cacheWrite5m;
1936
- m.cacheWrite1h += counts.cacheWrite1h;
1937
- m.cacheWriteUnsplit += counts.cacheWriteUnsplit;
1938
- m.cacheRead += counts.cacheRead;
1939
- if (costUSD === null) m.unpricedTokens += countsTotal(counts);
2009
+ m.input += counts2.input;
2010
+ m.output += counts2.output;
2011
+ m.cacheWrite5m += counts2.cacheWrite5m;
2012
+ m.cacheWrite1h += counts2.cacheWrite1h;
2013
+ m.cacheWriteUnsplit += counts2.cacheWriteUnsplit;
2014
+ m.cacheRead += counts2.cacheRead;
2015
+ if (costUSD === null) m.unpricedTokens += countsTotal(counts2);
1940
2016
  else m.costUSD += costUSD;
1941
2017
  }
1942
2018
  function buildModelRows(agg) {
@@ -2408,8 +2484,9 @@ async function hasRecentFile(roots, matches, sinceMs, opts = {}) {
2408
2484
  }
2409
2485
 
2410
2486
  // ../workflow-rules/src/daily.ts
2411
- var WORKFLOW_AGGREGATES_V2 = "workflow-aggregates/v2";
2487
+ var WORKFLOW_AGGREGATES_V3 = "workflow-aggregates/v3";
2412
2488
  var LOG_BUCKETS_V1 = "log-buckets/v1";
2489
+ var LOG_BUCKETS_V2 = "log-buckets/v2";
2413
2490
  var EMPTY_PHASE_TOTALS = Object.freeze({
2414
2491
  scout: 0,
2415
2492
  build: 0,
@@ -2463,6 +2540,10 @@ function medianBucket(buckets) {
2463
2540
  }
2464
2541
  return sorted[sorted.length - 1]?.bucket;
2465
2542
  }
2543
+ function logBucketV2(value) {
2544
+ if (!(value >= 1)) return 0;
2545
+ return Math.floor(2 * Math.log2(value)) + 1;
2546
+ }
2466
2547
  function median(values) {
2467
2548
  if (values.length === 0) return void 0;
2468
2549
  const sorted = [...values].sort((a, b) => a - b);
@@ -2505,6 +2586,52 @@ function foldModels(rows) {
2505
2586
  (row) => ({ ...row })
2506
2587
  ).sort((a, b) => b.tokens - a.tokens || a.model.localeCompare(b.model));
2507
2588
  }
2589
+ function foldCountBuckets(rows, field) {
2590
+ return sumBy(
2591
+ rows,
2592
+ (row) => String(row.bucket),
2593
+ (into, from) => {
2594
+ into[field] += from[field];
2595
+ },
2596
+ (row) => ({ ...row })
2597
+ ).sort((a, b) => a.bucket - b.bucket);
2598
+ }
2599
+ function foldContextDays(days) {
2600
+ const versions = [...new Set(days.map((d) => d.bucketRuleVersion))].sort().join(" \xB7 ");
2601
+ const windows = days.map((d) => d.window).filter((w) => w !== void 0);
2602
+ const window = windows[windows.length - 1];
2603
+ return {
2604
+ bucketRuleVersion: versions,
2605
+ calls: {
2606
+ main: foldCountBuckets(
2607
+ days.flatMap((d) => d.calls.main),
2608
+ "calls"
2609
+ ),
2610
+ subagents: foldCountBuckets(
2611
+ days.flatMap((d) => d.calls.subagents),
2612
+ "calls"
2613
+ )
2614
+ },
2615
+ firstCalls: {
2616
+ main: foldCountBuckets(
2617
+ days.flatMap((d) => d.firstCalls.main),
2618
+ "sessions"
2619
+ )
2620
+ },
2621
+ firstCallHarnessTokens: days.reduce(
2622
+ (sum, d) => sum + d.firstCallHarnessTokens,
2623
+ 0
2624
+ ),
2625
+ firstCallInstructionsTokens: days.reduce(
2626
+ (sum, d) => sum + d.firstCallInstructionsTokens,
2627
+ 0
2628
+ ),
2629
+ firstCallCount: days.reduce((sum, d) => sum + d.firstCallCount, 0),
2630
+ maxContext: Math.max(0, ...days.map((d) => d.maxContext)),
2631
+ compactions: days.reduce((sum, d) => sum + d.compactions, 0),
2632
+ ...window === void 0 ? {} : { window }
2633
+ };
2634
+ }
2508
2635
  function foldLengths(rows) {
2509
2636
  return sumBy(
2510
2637
  rows,
@@ -2641,6 +2768,8 @@ function foldHarnessDays(days) {
2641
2768
  0
2642
2769
  );
2643
2770
  }
2771
+ const contexts = days.flatMap((day) => day.context ? [day.context] : []);
2772
+ if (contexts.length > 0) out.context = foldContextDays(contexts);
2644
2773
  return out;
2645
2774
  }
2646
2775
  var MAX_CHANGED_LINES_PER_COMMIT = 4096;
@@ -2690,8 +2819,9 @@ function foldGitDays(days) {
2690
2819
  }
2691
2820
  function foldWorkflowDays(days, options) {
2692
2821
  if (days.length === 0) return void 0;
2822
+ const dated = [...days].sort((a, b) => a.date.localeCompare(b.date));
2693
2823
  const byHarness = /* @__PURE__ */ new Map();
2694
- for (const day of days) {
2824
+ for (const day of dated) {
2695
2825
  for (const harness of day.harnesses) {
2696
2826
  const held = byHarness.get(harness.harness) ?? [];
2697
2827
  held.push(harness);
@@ -2727,13 +2857,13 @@ function playbookHarnesses(reading) {
2727
2857
  return reading.harnesses.filter((harness) => harness.phase !== void 0);
2728
2858
  }
2729
2859
  function startHoursUtc(reading) {
2730
- const counts = /* @__PURE__ */ new Map();
2860
+ const counts2 = /* @__PURE__ */ new Map();
2731
2861
  for (const harness of reading.harnesses) {
2732
2862
  for (const cell of harness.startHours) {
2733
- counts.set(cell.hourUtc, (counts.get(cell.hourUtc) ?? 0) + cell.sessions);
2863
+ counts2.set(cell.hourUtc, (counts2.get(cell.hourUtc) ?? 0) + cell.sessions);
2734
2864
  }
2735
2865
  }
2736
- return counts;
2866
+ return counts2;
2737
2867
  }
2738
2868
  function ownerLocalHour(hourUtc, offsetMinutes) {
2739
2869
  return Math.floor(((hourUtc * 60 + offsetMinutes) / 60 % 24 + 24) % 24);
@@ -2741,13 +2871,13 @@ function ownerLocalHour(hourUtc, offsetMinutes) {
2741
2871
  function modalStartHour(reading) {
2742
2872
  const offsetMinutes = reading.utcOffsetMinutes;
2743
2873
  if (offsetMinutes === void 0) return void 0;
2744
- const counts = /* @__PURE__ */ new Map();
2874
+ const counts2 = /* @__PURE__ */ new Map();
2745
2875
  for (const [hourUtc, sessions] of startHoursUtc(reading)) {
2746
2876
  const hour = ownerLocalHour(hourUtc, offsetMinutes);
2747
- counts.set(hour, (counts.get(hour) ?? 0) + sessions);
2877
+ counts2.set(hour, (counts2.get(hour) ?? 0) + sessions);
2748
2878
  }
2749
- if (counts.size === 0) return void 0;
2750
- return [...counts.entries()].sort(
2879
+ if (counts2.size === 0) return void 0;
2880
+ return [...counts2.entries()].sort(
2751
2881
  (a, b) => b[1] - a[1] || a[0] - b[0]
2752
2882
  )[0]?.[0];
2753
2883
  }
@@ -2755,10 +2885,10 @@ function modalStartHour(reading) {
2755
2885
  // ../workflow-rules/src/componentRules.ts
2756
2886
  var COMPONENT_RULES_V2 = "component-rules/v2";
2757
2887
  var gitCoverage = () => 1;
2758
- function harnessShare(input, counts) {
2888
+ function harnessShare(input, counts2) {
2759
2889
  const synced = input.reading.harnesses.length;
2760
2890
  if (synced === 0) return 0;
2761
- return counts(input) / synced;
2891
+ return counts2(input) / synced;
2762
2892
  }
2763
2893
  function topShare(entries) {
2764
2894
  const total = entries.reduce((sum, entry) => sum + entry.value, 0);
@@ -3069,6 +3199,7 @@ var HANDOFF_MARKERS = {
3069
3199
  "mcp__curia__request_review"
3070
3200
  ],
3071
3201
  codex: ["request_user_input"],
3202
+ "grok-build": ["ask_user_question", "request_user_input"],
3072
3203
  opencode: ["question"],
3073
3204
  "pi-mono": []
3074
3205
  };
@@ -3099,7 +3230,11 @@ var SCOUT_TOOLS = [
3099
3230
  "web_search",
3100
3231
  "tool_search",
3101
3232
  "codebase_search",
3102
- "find"
3233
+ "find",
3234
+ "search_tool",
3235
+ "search_files",
3236
+ "list_dir",
3237
+ "read_file"
3103
3238
  ];
3104
3239
  var EDIT_TOOLS = [
3105
3240
  "Edit",
@@ -3110,11 +3245,20 @@ var EDIT_TOOLS = [
3110
3245
  "write",
3111
3246
  "patch",
3112
3247
  "multiedit",
3113
- "apply_patch"
3248
+ "apply_patch",
3249
+ "write_file",
3250
+ "edit_file"
3114
3251
  ];
3115
3252
  var REVIEW_SKILLS = ["code-review", "security-review", "review"];
3116
3253
  var SCOUT_AGENTS = ["Explore", "Plan", "research"];
3117
- var SHELL_TOOLS = ["Bash", "bash", "shell", "local_shell", "exec_command"];
3254
+ var SHELL_TOOLS = [
3255
+ "Bash",
3256
+ "bash",
3257
+ "shell",
3258
+ "local_shell",
3259
+ "exec_command",
3260
+ "run_terminal_command"
3261
+ ];
3118
3262
  var SKILL_TOOLS = ["Skill", "skill"];
3119
3263
  var AGENT_TOOLS = ["Agent", "Task", "task", "agent"];
3120
3264
  var BOOKKEEPING_TOOLS = [
@@ -3863,7 +4007,7 @@ function buildSyncBody(built, syncConfig, autoSync, trigger = "manual", measured
3863
4007
  }
3864
4008
 
3865
4009
  // src/workflow/reducer.ts
3866
- var WORKFLOW_AGGREGATE_VERSION = WORKFLOW_AGGREGATES_V2;
4010
+ var WORKFLOW_AGGREGATE_VERSION = WORKFLOW_AGGREGATES_V3;
3867
4011
  function createWorkflowLocalSources() {
3868
4012
  return { projectWorkspaces: /* @__PURE__ */ new Set(), activeProjectDays: /* @__PURE__ */ new Map() };
3869
4013
  }
@@ -3879,6 +4023,11 @@ var bump2 = (map, key2, amount = 1) => {
3879
4023
  map.set(key2, (map.get(key2) ?? 0) + amount);
3880
4024
  };
3881
4025
  var utcDateOf2 = (ms) => new Date(ms).toISOString().slice(0, 10);
4026
+ function asBuckets(map, field) {
4027
+ return [...map].map(
4028
+ ([bucket, count]) => ({ bucket, [field]: count })
4029
+ ).sort((a, b) => a.bucket - b.bucket);
4030
+ }
3882
4031
  var PHASE_RANK = {
3883
4032
  verify: 4,
3884
4033
  handoff: 3,
@@ -3976,13 +4125,25 @@ function dayState() {
3976
4125
  questions: { asked: 0, turns: 0 },
3977
4126
  hasQuestions: false,
3978
4127
  webSearches: 0,
3979
- hasWebSearches: false
4128
+ hasWebSearches: false,
4129
+ context: {
4130
+ calls: { main: /* @__PURE__ */ new Map(), subagents: /* @__PURE__ */ new Map() },
4131
+ firstCalls: /* @__PURE__ */ new Map(),
4132
+ firstCallHarnessTokens: 0,
4133
+ firstCallInstructionsTokens: 0,
4134
+ firstCallCount: 0,
4135
+ maxContext: 0,
4136
+ compactions: 0,
4137
+ window: void 0
4138
+ },
4139
+ hasContext: false
3980
4140
  };
3981
4141
  }
3982
4142
  function createHarnessWorkflowReducer(harness, localSources = createWorkflowLocalSources()) {
3983
4143
  const sessions = /* @__PURE__ */ new Map();
3984
4144
  const eventCells = /* @__PURE__ */ new Map();
3985
4145
  const webSearchesByDate = /* @__PURE__ */ new Map();
4146
+ const compactionsByDate = /* @__PURE__ */ new Map();
3986
4147
  const eventDates = /* @__PURE__ */ new Set();
3987
4148
  let finished;
3988
4149
  const getSession = (key2) => {
@@ -4023,22 +4184,39 @@ function createHarnessWorkflowReducer(harness, localSources = createWorkflowLoca
4023
4184
  } else if (observation.type === "response") {
4024
4185
  const responseId = observation.responseId ?? `anonymous:${state.nextAnonymousResponse++}`;
4025
4186
  const duration = finiteNonnegative(observation.durationSec);
4187
+ const contextTokens = finiteNonnegative(observation.contextTokens);
4188
+ const contextWindow = finiteNonnegative(observation.contextWindow);
4026
4189
  const response = {
4190
+ tsMs: observation.tsMs,
4027
4191
  ...observation.model ? { model: observation.model } : {},
4028
4192
  ...observation.thinkingTokens !== void 0 ? { thinkingTokens: finiteNonnegative(observation.thinkingTokens) } : {},
4029
4193
  ...observation.responseTokens !== void 0 ? { responseTokens: finiteNonnegative(observation.responseTokens) } : {},
4030
4194
  ...observation.routingTokens !== void 0 ? { routingTokens: finiteNonnegative(observation.routingTokens) } : {},
4031
4195
  ...observation.effort ? { effort: observation.effort } : {},
4032
- ...duration > 0 ? { durationSec: duration } : {}
4196
+ ...duration > 0 ? { durationSec: duration } : {},
4197
+ ...observation.contextTokens !== void 0 ? { contextTokens } : {},
4198
+ ...contextWindow > 0 ? { contextWindow } : {},
4199
+ ...observation.firstCall ? {
4200
+ firstCall: {
4201
+ harnessTokens: finiteNonnegative(
4202
+ observation.firstCall.harnessTokens
4203
+ ),
4204
+ instructionsTokens: finiteNonnegative(
4205
+ observation.firstCall.instructionsTokens
4206
+ )
4207
+ }
4208
+ } : {}
4033
4209
  };
4034
4210
  const existing = state.responses.get(responseId);
4035
4211
  const magnitude = (value) => value.routingTokens ?? (value.thinkingTokens ?? 0) + (value.responseTokens ?? 0);
4036
4212
  if (!existing || magnitude(response) > magnitude(existing)) {
4037
4213
  state.responses.set(responseId, response);
4038
4214
  }
4039
- } else {
4215
+ } else if (observation.type === "turn") {
4040
4216
  const turnId = observation.turnId ?? `anonymous:${state.nextAnonymousTurn++}`;
4041
4217
  state.turns.set(turnId, observation.questionBack);
4218
+ } else {
4219
+ bump2(compactionsByDate, date);
4042
4220
  }
4043
4221
  },
4044
4222
  finish() {
@@ -4144,6 +4322,33 @@ function createHarnessWorkflowReducer(harness, localSources = createWorkflowLoca
4144
4322
  Boolean
4145
4323
  ).length;
4146
4324
  }
4325
+ for (const response of responses) {
4326
+ if (response.contextTokens === void 0) continue;
4327
+ day.hasContext = true;
4328
+ const context = day.context;
4329
+ bump2(context.calls[routing], logBucketV2(response.contextTokens));
4330
+ context.maxContext = Math.max(
4331
+ context.maxContext,
4332
+ response.contextTokens
4333
+ );
4334
+ if (response.contextWindow !== void 0 && (context.window === void 0 || response.tsMs >= context.window.tsMs)) {
4335
+ context.window = {
4336
+ tsMs: response.tsMs,
4337
+ window: response.contextWindow
4338
+ };
4339
+ }
4340
+ if (response.firstCall && routing === "main") {
4341
+ bump2(context.firstCalls, logBucketV2(response.contextTokens));
4342
+ context.firstCallHarnessTokens += response.firstCall.harnessTokens;
4343
+ context.firstCallInstructionsTokens += response.firstCall.instructionsTokens;
4344
+ context.firstCallCount++;
4345
+ }
4346
+ }
4347
+ }
4348
+ for (const [date, compactions] of compactionsByDate) {
4349
+ const day = dayOf(date);
4350
+ day.hasContext = true;
4351
+ day.context.compactions += compactions;
4147
4352
  }
4148
4353
  for (const date of eventDates) {
4149
4354
  const day = dayOf(date);
@@ -4205,7 +4410,7 @@ function createHarnessWorkflowReducer(harness, localSources = createWorkflowLoca
4205
4410
  0
4206
4411
  );
4207
4412
  const unknown = attributed === 0 ? 0 : windowPhaseSec.unknown / attributed;
4208
- const routesModels = harness === "claude-code" || harness === "opencode";
4413
+ const routesModels = harness === "claude-code" || harness === "opencode" || harness === "grok-build";
4209
4414
  const asRows = (map) => {
4210
4415
  const safe = /* @__PURE__ */ new Map();
4211
4416
  for (const [model, tokens] of map) {
@@ -4276,12 +4481,34 @@ function createHarnessWorkflowReducer(harness, localSources = createWorkflowLoca
4276
4481
  }
4277
4482
  } : {},
4278
4483
  ...day.hasQuestions ? { questions: day.questions } : {},
4279
- ...day.hasWebSearches ? { webSearches: day.webSearches } : {}
4484
+ ...day.hasWebSearches ? { webSearches: day.webSearches } : {},
4485
+ ...day.hasContext ? {
4486
+ context: {
4487
+ bucketRuleVersion: LOG_BUCKETS_V2,
4488
+ calls: {
4489
+ main: asBuckets(day.context.calls.main, "calls"),
4490
+ subagents: asBuckets(
4491
+ day.context.calls.subagents,
4492
+ "calls"
4493
+ )
4494
+ },
4495
+ firstCalls: {
4496
+ main: asBuckets(day.context.firstCalls, "sessions")
4497
+ },
4498
+ firstCallHarnessTokens: day.context.firstCallHarnessTokens,
4499
+ firstCallInstructionsTokens: day.context.firstCallInstructionsTokens,
4500
+ firstCallCount: day.context.firstCallCount,
4501
+ maxContext: day.context.maxContext,
4502
+ compactions: day.context.compactions,
4503
+ ...day.context.window ? { window: day.context.window.window } : {}
4504
+ }
4505
+ } : {}
4280
4506
  }))
4281
4507
  };
4282
4508
  sessions.clear();
4283
4509
  eventCells.clear();
4284
4510
  webSearchesByDate.clear();
4511
+ compactionsByDate.clear();
4285
4512
  eventDates.clear();
4286
4513
  return finished;
4287
4514
  }
@@ -4295,9 +4522,30 @@ function createAggregate2() {
4295
4522
  workflow: createHarnessWorkflowReducer("claude-code", workflowLocal),
4296
4523
  workflowLocal,
4297
4524
  workflowSeenCalls: /* @__PURE__ */ new Set(),
4298
- workflowSeenTurns: /* @__PURE__ */ new Set()
4525
+ workflowSeenTurns: /* @__PURE__ */ new Set(),
4526
+ contextFirstCall: /* @__PURE__ */ new Map()
4299
4527
  });
4300
4528
  }
4529
+ function workflowSessionKey(rec) {
4530
+ const baseSession = asStr(rec.sessionId);
4531
+ if (!baseSession) return null;
4532
+ if (rec.isSidechain !== true) return baseSession;
4533
+ return `${baseSession}:agent:${asStr(rec.agentId) ?? "unknown"}`;
4534
+ }
4535
+ function isApiCall(rec) {
4536
+ if (asStr(rec.type) !== "assistant") return false;
4537
+ const msg = asObj(rec.message);
4538
+ if (!msg || !asObj(msg.usage)) return false;
4539
+ return !(asName(msg.model) ?? "").startsWith("<");
4540
+ }
4541
+ function noteRecordBeforeWindow(agg, raw) {
4542
+ const rec = asObj(raw);
4543
+ if (!rec || !isApiCall(rec)) return;
4544
+ const session = workflowSessionKey(rec);
4545
+ if (session && !agg.contextFirstCall.has(session)) {
4546
+ agg.contextFirstCall.set(session, null);
4547
+ }
4548
+ }
4301
4549
  function projectWorkspaceDirectory(raw) {
4302
4550
  const rec = asObj(raw);
4303
4551
  return rec ? asStr(rec.cwd) : null;
@@ -4330,7 +4578,25 @@ function ingestRecord(agg, raw, ctx) {
4330
4578
  ingestClaudeWorkflow(agg, rec, ctx, tsMs);
4331
4579
  ingestAssistant(agg, rec, tsMs);
4332
4580
  } else if (type === "user") ingestUser(agg, rec);
4333
- else if (type === "system") ingestClaudeTurnDuration(agg, rec, ctx, tsMs);
4581
+ else if (type === "system") {
4582
+ ingestClaudeTurnDuration(agg, rec, ctx, tsMs);
4583
+ ingestClaudeCompaction(agg, rec, ctx, tsMs);
4584
+ }
4585
+ }
4586
+ function ingestClaudeCompaction(agg, rec, ctx, tsMs) {
4587
+ if (tsMs === null || asStr(rec.subtype) !== "compact_boundary") return;
4588
+ const session = workflowSessionKey(rec);
4589
+ if (!session) return;
4590
+ agg.workflow.ingest({
4591
+ type: "compaction",
4592
+ session,
4593
+ projectWorkspace: projectWorkspaceDirectory(rec) ?? ctx.projectDir,
4594
+ tsMs,
4595
+ ...rec.isSidechain === true ? { sidechain: true } : {}
4596
+ });
4597
+ }
4598
+ function contextOf(counts2) {
4599
+ return counts2.input + counts2.cacheWrite5m + counts2.cacheWrite1h + counts2.cacheWriteUnsplit + counts2.cacheRead;
4334
4600
  }
4335
4601
  function ingestClaudeTurnDuration(agg, rec, ctx, tsMs) {
4336
4602
  if (tsMs === null || asStr(rec.subtype) !== "turn_duration" || asNum(rec.durationMs) <= 0) {
@@ -4358,10 +4624,28 @@ function ingestClaudeWorkflow(agg, rec, ctx, tsMs) {
4358
4624
  const session = sidechain ? `${baseSession}:agent:${agentId ?? "unknown"}` : baseSession;
4359
4625
  const parentSession = sidechain ? baseSession : void 0;
4360
4626
  const usage = asObj(msg.usage);
4361
- const counts = usage ? readCounts(usage) : null;
4627
+ const counts2 = usage ? readCounts(usage) : null;
4362
4628
  const messageId = asStr(msg.id);
4363
4629
  const newTurn = messageId ? !agg.workflowSeenTurns.has(messageId) : true;
4364
4630
  if (messageId) agg.workflowSeenTurns.add(messageId);
4631
+ let context = null;
4632
+ if (counts2 && isApiCall(rec)) {
4633
+ const held = agg.contextFirstCall.get(session);
4634
+ let first = false;
4635
+ if (held === void 0) {
4636
+ agg.contextFirstCall.set(session, messageId);
4637
+ first = true;
4638
+ } else if (held !== null && held === messageId) first = true;
4639
+ context = {
4640
+ contextTokens: contextOf(counts2),
4641
+ ...first ? {
4642
+ firstCall: {
4643
+ harnessTokens: counts2.cacheRead,
4644
+ instructionsTokens: contextOf(counts2) - counts2.cacheRead
4645
+ }
4646
+ } : {}
4647
+ };
4648
+ }
4365
4649
  agg.workflow.ingest({
4366
4650
  type: "response",
4367
4651
  session,
@@ -4371,9 +4655,10 @@ function ingestClaudeWorkflow(agg, rec, ctx, tsMs) {
4371
4655
  ...parentSession ? { parentSession } : {},
4372
4656
  ...messageId ? { responseId: messageId } : {},
4373
4657
  ...asName(msg.model) ? { model: asName(msg.model) } : {},
4374
- ...counts ? { responseTokens: counts.output } : {},
4375
- ...counts ? { routingTokens: countsTotal(counts) } : {},
4376
- ...asStr(rec.effort) ?? asStr(msg.effort) ? { effort: asStr(rec.effort) ?? asStr(msg.effort) } : {}
4658
+ ...counts2 ? { responseTokens: counts2.output } : {},
4659
+ ...counts2 ? { routingTokens: countsTotal(counts2) } : {},
4660
+ ...asStr(rec.effort) ?? asStr(msg.effort) ? { effort: asStr(rec.effort) ?? asStr(msg.effort) } : {},
4661
+ ...context ?? {}
4377
4662
  });
4378
4663
  const tools = [];
4379
4664
  for (const rawBlock of asArr(msg.content)) {
@@ -4498,11 +4783,11 @@ function readCounts(usage) {
4498
4783
  function modelKeyFor2(model, speed) {
4499
4784
  return normalizeModel(speed === "fast" ? `${model}#fast` : model);
4500
4785
  }
4501
- function makeEntry(modelKey, counts, tsMs) {
4786
+ function makeEntry(modelKey, counts2, tsMs) {
4502
4787
  return {
4503
4788
  modelKey,
4504
- counts,
4505
- costUSD: apiEquivalentCost(modelKey, counts, tsMs)
4789
+ counts: counts2,
4790
+ costUSD: apiEquivalentCost(modelKey, counts2, tsMs)
4506
4791
  };
4507
4792
  }
4508
4793
  function buildContribution(usage, model, sidechain, tsMs) {
@@ -4547,24 +4832,24 @@ function buildContribution(usage, model, sidechain, tsMs) {
4547
4832
  };
4548
4833
  }
4549
4834
  function applyContribution(agg, c, sign) {
4550
- c.entries.forEach(({ modelKey, counts, costUSD }, i) => {
4835
+ c.entries.forEach(({ modelKey, counts: counts2, costUSD }, i) => {
4551
4836
  let m = agg.byModel.get(modelKey);
4552
4837
  if (!m) {
4553
4838
  m = emptyUsage();
4554
4839
  agg.byModel.set(modelKey, m);
4555
4840
  }
4556
4841
  if (i === 0) m.messages += sign;
4557
- m.input += sign * counts.input;
4558
- m.output += sign * counts.output;
4559
- m.cacheWrite5m += sign * counts.cacheWrite5m;
4560
- m.cacheWrite1h += sign * counts.cacheWrite1h;
4561
- m.cacheWriteUnsplit += sign * counts.cacheWriteUnsplit;
4562
- m.cacheRead += sign * counts.cacheRead;
4563
- if (costUSD === null) m.unpricedTokens += sign * countsTotal(counts);
4842
+ m.input += sign * counts2.input;
4843
+ m.output += sign * counts2.output;
4844
+ m.cacheWrite5m += sign * counts2.cacheWrite5m;
4845
+ m.cacheWrite1h += sign * counts2.cacheWrite1h;
4846
+ m.cacheWriteUnsplit += sign * counts2.cacheWriteUnsplit;
4847
+ m.cacheRead += sign * counts2.cacheRead;
4848
+ if (costUSD === null) m.unpricedTokens += sign * countsTotal(counts2);
4564
4849
  else m.costUSD += sign * costUSD;
4565
4850
  noteUsageResponse(
4566
4851
  agg,
4567
- { tsMs: c.tsMs, modelKey, counts, costUSD, sidechain: c.sidechain },
4852
+ { tsMs: c.tsMs, modelKey, counts: counts2, costUSD, sidechain: c.sidechain },
4568
4853
  sign
4569
4854
  );
4570
4855
  });
@@ -4751,7 +5036,10 @@ async function ingestFile(agg, file, projectDir, projectWorkspaces, sinceMs) {
4751
5036
  }
4752
5037
  if (sinceMs !== void 0) {
4753
5038
  const ts = rec && typeof rec === "object" && "timestamp" in rec && typeof rec.timestamp === "string" ? Date.parse(rec.timestamp) : Number.NaN;
4754
- if (Number.isNaN(ts) || ts < sinceMs) continue;
5039
+ if (Number.isNaN(ts) || ts < sinceMs) {
5040
+ noteRecordBeforeWindow(agg, rec);
5041
+ continue;
5042
+ }
4755
5043
  }
4756
5044
  ingestRecord(agg, rec, { projectDir: projectWorkspace });
4757
5045
  }
@@ -4803,7 +5091,33 @@ function createFileState() {
4803
5091
  responseIndex: 0,
4804
5092
  currentQuestionBack: false,
4805
5093
  counted: false,
4806
- metaTsMs: null
5094
+ metaTsMs: null,
5095
+ forked: false,
5096
+ sawResponse: false
5097
+ };
5098
+ }
5099
+ function genuineDelta(payload, state, tsMs) {
5100
+ if (asStr(payload.type) !== "token_count") return null;
5101
+ const info = asObj(payload.info);
5102
+ const last = info ? asObj(info.last_token_usage) : null;
5103
+ if (!last) return null;
5104
+ const inputTotal = asNum(last.input_tokens);
5105
+ const cached = Math.min(asNum(last.cached_input_tokens), inputTotal);
5106
+ const counts2 = {
5107
+ input: inputTotal - cached,
5108
+ output: asNum(last.output_tokens),
5109
+ cacheWrite5m: 0,
5110
+ cacheWrite1h: 0,
5111
+ cacheWriteUnsplit: 0,
5112
+ cacheRead: cached
5113
+ };
5114
+ if (countsTotal(counts2) === 0) return null;
5115
+ if (tsMs !== null && state.metaTsMs !== null && tsMs - state.metaTsMs < FORK_REPLAY_WINDOW_MS)
5116
+ return null;
5117
+ return {
5118
+ counts: counts2,
5119
+ last,
5120
+ contextWindow: info ? asNum(info.model_context_window) : 0
4807
5121
  };
4808
5122
  }
4809
5123
  var FORK_REPLAY_WINDOW_MS = 500;
@@ -4825,11 +5139,17 @@ function ingestLine(agg, raw, state, sinceMs) {
4825
5139
  state.cliVersion = asStr(payload.cli_version) ?? state.cliVersion;
4826
5140
  state.cwd = asStr(payload.cwd) ?? state.cwd;
4827
5141
  state.metaTsMs = tsMs ?? state.metaTsMs;
5142
+ state.forked = payload.forked_from_id !== void 0 && payload.forked_from_id !== null;
4828
5143
  } else if (type === "turn_context" && payload) {
4829
5144
  const model = asName(payload.model);
4830
5145
  if (model) state.modelKey = normalizeModel(model);
4831
5146
  state.effort = asStr(payload.effort) ?? state.effort;
4832
5147
  }
5148
+ let firstCall = false;
5149
+ if (type === "event_msg" && payload && genuineDelta(payload, state, tsMs)) {
5150
+ firstCall = !state.sawResponse;
5151
+ state.sawResponse = true;
5152
+ }
4833
5153
  if (!inWindow) return;
4834
5154
  if (tsMs !== null && timestamp) {
4835
5155
  agg.activeDays.add(timestamp.slice(0, 10));
@@ -4838,9 +5158,18 @@ function ingestLine(agg, raw, state, sinceMs) {
4838
5158
  }
4839
5159
  noteActivity(agg, state, tsMs);
4840
5160
  noteProjectDay(agg, state.cwd ?? "(unknown)", tsMs);
4841
- if (type === "event_msg" && payload) ingestEvent(agg, payload, state, tsMs);
5161
+ if (type === "event_msg" && payload)
5162
+ ingestEvent(agg, payload, state, tsMs, firstCall);
4842
5163
  else if (type === "response_item" && payload)
4843
5164
  ingestItem(agg, payload, state, tsMs);
5165
+ else if (type === "compacted" && tsMs !== null && state.sessionId) {
5166
+ agg.workflow.ingest({
5167
+ type: "compaction",
5168
+ session: state.sessionId,
5169
+ projectWorkspace: state.cwd ?? void 0,
5170
+ tsMs
5171
+ });
5172
+ }
4844
5173
  }
4845
5174
  function noteActivity(agg, state, tsMs) {
4846
5175
  if (state.sessionId) noteSessionStart(agg, state.sessionId, tsMs);
@@ -4850,25 +5179,11 @@ function noteActivity(agg, state, tsMs) {
4850
5179
  if (state.cliVersion) agg.ccVersions.add(cleanName(state.cliVersion));
4851
5180
  agg.projectDirs.add(state.cwd ?? "(unknown)");
4852
5181
  }
4853
- function ingestEvent(agg, payload, state, tsMs) {
4854
- if (asStr(payload.type) !== "token_count") return;
4855
- const info = asObj(payload.info);
4856
- const last = info ? asObj(info.last_token_usage) : null;
4857
- if (!last) return;
4858
- const inputTotal = asNum(last.input_tokens);
4859
- const cached = Math.min(asNum(last.cached_input_tokens), inputTotal);
4860
- const counts = {
4861
- input: inputTotal - cached,
4862
- output: asNum(last.output_tokens),
4863
- cacheWrite5m: 0,
4864
- cacheWrite1h: 0,
4865
- cacheWriteUnsplit: 0,
4866
- cacheRead: cached
4867
- };
4868
- const total = countsTotal(counts);
4869
- if (total === 0) return;
4870
- if (tsMs !== null && state.metaTsMs !== null && tsMs - state.metaTsMs < FORK_REPLAY_WINDOW_MS)
4871
- return;
5182
+ function ingestEvent(agg, payload, state, tsMs, firstCall) {
5183
+ const delta = genuineDelta(payload, state, tsMs);
5184
+ if (!delta) return;
5185
+ const { counts: counts2, last, contextWindow } = delta;
5186
+ const total = countsTotal(counts2);
4872
5187
  if (state.modelKey === null) return;
4873
5188
  if (tsMs === null) agg.untimestampedResponses++;
4874
5189
  agg.distinctResponses++;
@@ -4876,8 +5191,8 @@ function ingestEvent(agg, payload, state, tsMs) {
4876
5191
  addModelUsage(
4877
5192
  agg,
4878
5193
  modelKey,
4879
- counts,
4880
- apiEquivalentCost(modelKey, counts, tsMs),
5194
+ counts2,
5195
+ apiEquivalentCost(modelKey, counts2, tsMs),
4881
5196
  1,
4882
5197
  { tsMs }
4883
5198
  );
@@ -4891,10 +5206,23 @@ function ingestEvent(agg, payload, state, tsMs) {
4891
5206
  projectWorkspace: state.cwd ?? void 0,
4892
5207
  tsMs,
4893
5208
  model: modelKey,
4894
- responseTokens: counts.output,
5209
+ responseTokens: counts2.output,
4895
5210
  routingTokens: total,
4896
5211
  thinkingTokens: asNum(last.reasoning_output_tokens),
4897
- ...state.effort ? { effort: state.effort } : {}
5212
+ ...state.effort ? { effort: state.effort } : {},
5213
+ // The context at this call is `input_tokens` (cached is a subset).
5214
+ // On the first call the cached part is the harness (base
5215
+ // instructions and tool specs, cached by an earlier session) and
5216
+ // the fresh part is the instructions: AGENTS.md, environment,
5217
+ // skills and the first prompt. Same method as Claude Code (#358).
5218
+ contextTokens: counts2.input + counts2.cacheRead,
5219
+ ...contextWindow > 0 ? { contextWindow } : {},
5220
+ ...firstCall && !state.forked ? {
5221
+ firstCall: {
5222
+ harnessTokens: counts2.cacheRead,
5223
+ instructionsTokens: counts2.input
5224
+ }
5225
+ } : {}
4898
5226
  });
4899
5227
  agg.workflow.ingest({
4900
5228
  type: "turn",
@@ -5112,11 +5440,11 @@ function ingestWithRetry(agg, file, opts) {
5112
5440
  }
5113
5441
  }
5114
5442
  function ingestFile2(agg, file, opts) {
5115
- const readFile = opts.readFileImpl ?? readFileSync6;
5443
+ const readFile2 = opts.readFileImpl ?? readFileSync6;
5116
5444
  let text;
5117
5445
  if (file.endsWith(".zst")) {
5118
5446
  if (zstdDecompress === null) throw readError("zstd-unsupported");
5119
- const raw = readFile(file);
5447
+ const raw = readFile2(file);
5120
5448
  try {
5121
5449
  text = zstdDecompress(
5122
5450
  Buffer.isBuffer(raw) ? raw : Buffer.from(raw)
@@ -5125,7 +5453,7 @@ function ingestFile2(agg, file, opts) {
5125
5453
  throw readError("zstd-corrupt");
5126
5454
  }
5127
5455
  } else {
5128
- text = readFile(file).toString("utf8");
5456
+ text = readFile2(file).toString("utf8");
5129
5457
  }
5130
5458
  const records = [];
5131
5459
  let nonEmptyLines = 0;
@@ -5228,6 +5556,510 @@ var codexAdapter = {
5228
5556
  }
5229
5557
  };
5230
5558
 
5559
+ // src/harness/grok/analyzer.ts
5560
+ function createAggregate4() {
5561
+ const workflowLocal = createWorkflowLocalSources();
5562
+ return Object.assign(createAggregate(), {
5563
+ workflow: createHarnessWorkflowReducer("grok-build", workflowLocal),
5564
+ workflowLocal
5565
+ });
5566
+ }
5567
+ var createGrokEventState = (parentSession) => ({
5568
+ tools: /* @__PURE__ */ new Map(),
5569
+ completedTools: /* @__PURE__ */ new Set(),
5570
+ ...parentSession ? { parentSession } : {}
5571
+ });
5572
+ var bump3 = (map, key2) => {
5573
+ map.set(key2, (map.get(key2) ?? 0) + 1);
5574
+ };
5575
+ var toolMetadata = (update) => {
5576
+ const meta = asObj(update._meta);
5577
+ return meta && asObj(meta["x.ai/tool"]);
5578
+ };
5579
+ function ingestUpdate(agg, state, value, projectDir, sinceMs) {
5580
+ const root = asObj(value);
5581
+ const params = root && asObj(root.params);
5582
+ const update = params && asObj(params.update);
5583
+ const session = params && asStr(params.sessionId);
5584
+ const tsMs = timestampMs(
5585
+ asObj(params?._meta)?.agentTimestampMs ?? root?.timestamp
5586
+ );
5587
+ if (!update || !session || tsMs === null) return;
5588
+ const kind = asStr(update.sessionUpdate);
5589
+ if (kind === "tool_call") {
5590
+ const id = asStr(update.toolCallId);
5591
+ const metadata = toolMetadata(update);
5592
+ const name = asName(metadata?.name ?? update.toolName);
5593
+ if (!id || !name || state.tools.has(id)) return;
5594
+ const raw = asObj(update.input);
5595
+ const arg = asStr(raw?.command ?? raw?.query ?? raw?.skill ?? raw?.name);
5596
+ state.tools.set(id, { name, ...arg ? { arg } : {}, tsMs });
5597
+ return;
5598
+ }
5599
+ if (sinceMs !== void 0 && tsMs < sinceMs) return;
5600
+ if (kind === "tool_call_update") {
5601
+ const id = asStr(update.toolCallId);
5602
+ if (!id || asStr(update.status) !== "completed") return;
5603
+ completeTool(agg, state, id, session, projectDir, tsMs);
5604
+ return;
5605
+ }
5606
+ if (kind !== "turn_completed") return;
5607
+ const usage = asObj(update.usage);
5608
+ if (!usage) return;
5609
+ const prompt = asStr(update.prompt_id) ?? `turn:${tsMs}`;
5610
+ for (const [model, raw] of Object.entries(asObj(usage.modelUsage) ?? {})) {
5611
+ const row = asObj(raw);
5612
+ agg.workflow.ingest({
5613
+ type: "response",
5614
+ session,
5615
+ projectWorkspace: projectDir,
5616
+ parentSession: state.parentSession,
5617
+ tsMs,
5618
+ responseId: `${prompt}:${model}`,
5619
+ model,
5620
+ thinkingTokens: asNum(row?.reasoningTokens),
5621
+ responseTokens: asNum(row?.outputTokens),
5622
+ routingTokens: asNum(row?.outputTokens),
5623
+ ...asNum(row?.apiDurationMs) > 0 ? { durationSec: asNum(row?.apiDurationMs) / 1e3 } : asNum(update.elapsed_ms) > 0 ? { durationSec: asNum(update.elapsed_ms) / 1e3 } : {}
5624
+ });
5625
+ }
5626
+ agg.workflow.ingest({
5627
+ type: "turn",
5628
+ session,
5629
+ projectWorkspace: projectDir,
5630
+ parentSession: state.parentSession,
5631
+ tsMs,
5632
+ turnId: prompt,
5633
+ questionBack: asStr(update.stop_reason) === "question"
5634
+ });
5635
+ }
5636
+ function completeTool(agg, state, id, session, projectDir, tsMs) {
5637
+ if (state.completedTools.has(id)) return;
5638
+ const tool = state.tools.get(id);
5639
+ if (!tool) return;
5640
+ state.completedTools.add(id);
5641
+ bump3(agg.toolCalls, tool.name);
5642
+ if (["web_search", "websearch", "search_web"].includes(tool.name))
5643
+ agg.webSearchRequests++;
5644
+ if (["skill", "use_skill"].includes(tool.name) && tool.arg)
5645
+ bump3(agg.skillCalls, tool.arg);
5646
+ const mcp = /^(?:mcp__|mcp:)([^_:]+)[_:](.+)$/.exec(tool.name);
5647
+ if (mcp) {
5648
+ bump3(agg.mcpServerCalls, mcp[1]);
5649
+ bump3(agg.mcpToolCalls, tool.name);
5650
+ } else if (tool.name === "use_tool" && tool.arg?.includes("__")) {
5651
+ const [server] = tool.arg.split("__", 1);
5652
+ if (server) {
5653
+ bump3(agg.mcpServerCalls, server);
5654
+ bump3(agg.mcpToolCalls, tool.arg);
5655
+ }
5656
+ }
5657
+ agg.workflow.ingest({
5658
+ type: "event",
5659
+ session,
5660
+ projectWorkspace: projectDir,
5661
+ parentSession: state.parentSession,
5662
+ tsMs: tool.tsMs || tsMs,
5663
+ tool: tool.name,
5664
+ ...tool.arg ? { arg: tool.arg } : {},
5665
+ batchId: id
5666
+ });
5667
+ }
5668
+ function ingestEvent2(agg, state, value, sessionFallback, projectDir, sinceMs) {
5669
+ const row = asObj(value);
5670
+ if (!row) return;
5671
+ const session = asStr(row.session_id) ?? sessionFallback;
5672
+ const tsMs = timestampMs(row.ts);
5673
+ if (!session || tsMs === null) return;
5674
+ if (asStr(row.type) === "tool_started") {
5675
+ const id = asStr(row.tool_call_id);
5676
+ const name = asName(row.tool_name);
5677
+ if (id && name && !state.tools.has(id)) state.tools.set(id, { name, tsMs });
5678
+ } else if (sinceMs !== void 0 && tsMs < sinceMs) {
5679
+ return;
5680
+ } else if (asStr(row.type) === "tool_completed") {
5681
+ const id = asStr(row.tool_call_id);
5682
+ if (id) completeTool(agg, state, id, session, projectDir, tsMs);
5683
+ } else if (asStr(row.type) === "compaction") {
5684
+ agg.workflow.ingest({
5685
+ type: "compaction",
5686
+ session,
5687
+ projectWorkspace: projectDir,
5688
+ parentSession: state.parentSession,
5689
+ tsMs
5690
+ });
5691
+ }
5692
+ }
5693
+ var timestampMs = (value) => {
5694
+ if (typeof value === "string") {
5695
+ const parsed = Date.parse(value);
5696
+ return Number.isFinite(parsed) ? parsed : null;
5697
+ }
5698
+ if (typeof value !== "number" || !Number.isFinite(value)) return null;
5699
+ return value < 1e10 ? value * 1e3 : value;
5700
+ };
5701
+ function counts(value) {
5702
+ const row = asObj(value);
5703
+ if (!row) return null;
5704
+ const totalInput = asNum(row.inputTokens);
5705
+ const cacheRead = asNum(row.cachedReadTokens);
5706
+ const cacheWrite = asNum(row.cacheCreationTokens);
5707
+ if (totalInput < cacheRead + cacheWrite) return null;
5708
+ const result = {
5709
+ input: totalInput - cacheRead - cacheWrite,
5710
+ output: asNum(row.outputTokens),
5711
+ cacheWrite5m: 0,
5712
+ cacheWrite1h: 0,
5713
+ cacheWriteUnsplit: cacheWrite,
5714
+ cacheRead
5715
+ };
5716
+ return countsTotal(result) > 0 ? result : null;
5717
+ }
5718
+ function sidecarContributions(value, projectDir) {
5719
+ const root = asObj(value);
5720
+ const sessionId = root && asStr(root.sessionId);
5721
+ if (!root || !sessionId) return [];
5722
+ const turns = Array.isArray(root.turns) ? root.turns : [];
5723
+ const out = [];
5724
+ for (const raw of turns) {
5725
+ const turn = asObj(raw);
5726
+ const tsMs = turn && timestampMs(turn.endedAt);
5727
+ if (!turn || tsMs === null) continue;
5728
+ const models = [];
5729
+ const perModel = asObj(turn.modelUsage);
5730
+ for (const [model, usage] of Object.entries(perModel ?? {})) {
5731
+ const c = counts(usage);
5732
+ if (c) models.push({ model, counts: c });
5733
+ }
5734
+ if (models.length === 0) {
5735
+ const c = counts(turn);
5736
+ const model = asStr(turn.primaryModelId);
5737
+ if (c && model) models.push({ model, counts: c });
5738
+ }
5739
+ if (models.length > 0)
5740
+ out.push({
5741
+ sessionId,
5742
+ projectDir,
5743
+ tsMs,
5744
+ ...asNum(turn.apiDurationMs) > 0 ? { durationMs: asNum(turn.apiDurationMs) } : {},
5745
+ models
5746
+ });
5747
+ }
5748
+ return out;
5749
+ }
5750
+ function terminalContribution(value, projectDir) {
5751
+ const root = asObj(value);
5752
+ const params = root && asObj(root.params);
5753
+ const update = params && asObj(params.update);
5754
+ const meta = params && asObj(params._meta);
5755
+ if (!update || asStr(update.sessionUpdate) !== "turn_completed") return null;
5756
+ const usage = asObj(update.usage);
5757
+ const sessionId = params && asStr(params.sessionId);
5758
+ const tsMs = timestampMs(meta?.agentTimestampMs ?? root?.timestamp);
5759
+ if (!usage || !sessionId || tsMs === null) return null;
5760
+ const models = [];
5761
+ for (const [model, row] of Object.entries(asObj(usage.modelUsage) ?? {})) {
5762
+ const c = counts(row);
5763
+ if (c) models.push({ model, counts: c });
5764
+ }
5765
+ return models.length === 0 ? null : {
5766
+ sessionId,
5767
+ projectDir,
5768
+ tsMs,
5769
+ ...asNum(update.elapsed_ms) > 0 ? { durationMs: asNum(update.elapsed_ms) } : {},
5770
+ models
5771
+ };
5772
+ }
5773
+ function ingestContribution(agg, row) {
5774
+ agg.records++;
5775
+ agg.assistantRecords++;
5776
+ agg.distinctResponses++;
5777
+ agg.sessions.add(row.sessionId);
5778
+ agg.activeDays.add(new Date(row.tsMs).toISOString().slice(0, 10));
5779
+ agg.projectDirs.add(row.projectDir);
5780
+ agg.firstTs = agg.firstTs === null ? row.tsMs : Math.min(agg.firstTs, row.tsMs);
5781
+ agg.lastTs = agg.lastTs === null ? row.tsMs : Math.max(agg.lastTs, row.tsMs);
5782
+ noteSessionStart(agg, row.sessionId, row.tsMs);
5783
+ noteProjectDay(agg, row.projectDir, row.tsMs);
5784
+ for (const { model, counts: tokenCounts } of row.models) {
5785
+ const key2 = normalizeModel(model);
5786
+ addModelUsage(
5787
+ agg,
5788
+ key2,
5789
+ tokenCounts,
5790
+ apiEquivalentCost(key2, tokenCounts, row.tsMs),
5791
+ 1,
5792
+ {
5793
+ tsMs: row.tsMs
5794
+ }
5795
+ );
5796
+ }
5797
+ }
5798
+
5799
+ // src/harness/grok/scan.ts
5800
+ import { createReadStream as createReadStream2 } from "node:fs";
5801
+ import { readdir as readdir4, readFile, stat as stat4 } from "node:fs/promises";
5802
+ import { homedir as homedir8 } from "node:os";
5803
+ import path4 from "node:path";
5804
+ import readline2 from "node:readline";
5805
+ import { parse as parseToml3 } from "smol-toml";
5806
+ function sessionRoots() {
5807
+ return [
5808
+ path4.join(
5809
+ process.env.GROK_HOME || path4.join(homedir8(), ".grok"),
5810
+ "sessions"
5811
+ )
5812
+ ];
5813
+ }
5814
+ var isGrokEvidenceFile = (name) => name === "usage.json" || name === "updates.jsonl" || name === "events.jsonl";
5815
+ var delays = [0, 100, 300];
5816
+ var pause = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
5817
+ async function stableRead(file, jsonl) {
5818
+ for (const delay of delays) {
5819
+ if (delay) await pause(delay);
5820
+ try {
5821
+ const before = await stat4(file);
5822
+ if (!before.isFile()) return { complete: false };
5823
+ if (jsonl) {
5824
+ const lines2 = [];
5825
+ const input = readline2.createInterface({
5826
+ input: createReadStream2(file),
5827
+ crlfDelay: Infinity
5828
+ });
5829
+ for await (const line of input) {
5830
+ if (!line.trim()) continue;
5831
+ try {
5832
+ lines2.push(JSON.parse(line));
5833
+ } catch {
5834
+ lines2.push(null);
5835
+ }
5836
+ }
5837
+ const after = await stat4(file);
5838
+ if (before.size === after.size && before.mtimeMs === after.mtimeMs)
5839
+ return { lines: lines2, complete: true };
5840
+ } else {
5841
+ const raw = await readFile(file, "utf8");
5842
+ const after = await stat4(file);
5843
+ if (before.size === after.size && before.mtimeMs === after.mtimeMs)
5844
+ return { json: JSON.parse(raw), complete: true };
5845
+ }
5846
+ } catch {
5847
+ }
5848
+ }
5849
+ return { complete: false };
5850
+ }
5851
+ async function directories(root) {
5852
+ try {
5853
+ const workspaces = await readdir4(root, { withFileTypes: true });
5854
+ const out = [];
5855
+ for (const workspace of workspaces) {
5856
+ if (!workspace.isDirectory() || workspace.isSymbolicLink()) continue;
5857
+ const workspacePath = path4.join(root, workspace.name);
5858
+ for (const session of await readdir4(workspacePath, {
5859
+ withFileTypes: true
5860
+ })) {
5861
+ if (session.isDirectory() && !session.isSymbolicLink())
5862
+ out.push(path4.join(workspacePath, session.name));
5863
+ }
5864
+ }
5865
+ return out.sort();
5866
+ } catch (error) {
5867
+ return error.code === "ENOENT" ? [] : null;
5868
+ }
5869
+ }
5870
+ async function modelAliasesForRoot(root) {
5871
+ if (process.env.GROK_MODELS_BASE_URL) return /* @__PURE__ */ new Map();
5872
+ try {
5873
+ const parsed = asObj(
5874
+ parseToml3(await readFile(path4.join(root, "..", "config.toml"), "utf8"))
5875
+ );
5876
+ if (!parsed || asObj(parsed.endpoints)?.models_base_url) return /* @__PURE__ */ new Map();
5877
+ const models = asObj(parsed.model);
5878
+ const aliases = /* @__PURE__ */ new Map();
5879
+ for (const [alias, raw] of Object.entries(models ?? {})) {
5880
+ const entry = asObj(raw);
5881
+ const model = entry && asStr(entry.model);
5882
+ if (!model || entry?.base_url || entry?.model_provider) continue;
5883
+ aliases.set(alias, model);
5884
+ }
5885
+ return aliases;
5886
+ } catch {
5887
+ return /* @__PURE__ */ new Map();
5888
+ }
5889
+ }
5890
+ async function scan3(agg, opts = {}) {
5891
+ const stats = emptyScanStats();
5892
+ const sessionDates = /* @__PURE__ */ new Map();
5893
+ const candidates = /* @__PURE__ */ new Map();
5894
+ let complete = true;
5895
+ for (const root of opts.roots ?? sessionRoots()) {
5896
+ const aliases = await modelAliasesForRoot(root);
5897
+ const dirs = await directories(root);
5898
+ if (dirs === null) {
5899
+ complete = false;
5900
+ continue;
5901
+ }
5902
+ for (const dir of dirs) {
5903
+ let entries;
5904
+ try {
5905
+ entries = await readdir4(dir, { withFileTypes: true });
5906
+ } catch {
5907
+ complete = false;
5908
+ continue;
5909
+ }
5910
+ const files = new Map(
5911
+ entries.filter(
5912
+ (e) => e.isFile() && (isGrokEvidenceFile(e.name) || e.name === "summary.json")
5913
+ ).map((e) => [e.name, path4.join(dir, e.name)])
5914
+ );
5915
+ let projectDir = dir;
5916
+ let sessionFallback = path4.basename(dir);
5917
+ let child = false;
5918
+ let parentSession;
5919
+ const summary = files.get("summary.json");
5920
+ if (summary) {
5921
+ const read2 = await stableRead(summary, false);
5922
+ if (!read2.complete) {
5923
+ complete = false;
5924
+ stats.filesUnreadable++;
5925
+ continue;
5926
+ }
5927
+ const value = asObj(read2.json);
5928
+ const info = value && asObj(value.info);
5929
+ projectDir = asStr(info?.cwd) ?? asStr(value?.cwd) ?? dir;
5930
+ sessionFallback = asStr(value?.sessionId) ?? sessionFallback;
5931
+ parentSession = asStr(value?.parentSessionId) ?? asStr(value?.parent_session_id) ?? asStr(info?.parentSessionId) ?? asStr(info?.parent_session_id) ?? void 0;
5932
+ child = parentSession !== void 0;
5933
+ }
5934
+ let rows = [];
5935
+ let precedence = 0;
5936
+ const usage = files.get("usage.json");
5937
+ if (usage) {
5938
+ stats.filesFound++;
5939
+ const read2 = await stableRead(usage, false);
5940
+ if (!read2.complete) {
5941
+ complete = false;
5942
+ stats.filesUnreadable++;
5943
+ continue;
5944
+ }
5945
+ stats.filesRead++;
5946
+ rows = sidecarContributions(read2.json, projectDir);
5947
+ if (rows.length > 0) precedence = 2;
5948
+ }
5949
+ if (child) {
5950
+ rows = [];
5951
+ precedence = 0;
5952
+ }
5953
+ const eventState = createGrokEventState(parentSession);
5954
+ const updates = files.get("updates.jsonl");
5955
+ if (updates) {
5956
+ const useTerminalUsage = rows.length === 0 && !child;
5957
+ stats.filesFound++;
5958
+ const read2 = await stableRead(updates, true);
5959
+ if (!read2.complete) {
5960
+ complete = false;
5961
+ stats.filesUnreadable++;
5962
+ continue;
5963
+ }
5964
+ stats.filesRead++;
5965
+ for (const value of read2.lines ?? []) {
5966
+ if (value === null) {
5967
+ agg.parseErrors++;
5968
+ continue;
5969
+ }
5970
+ ingestUpdate(agg, eventState, value, projectDir, opts.sinceMs);
5971
+ if (useTerminalUsage) {
5972
+ const row = terminalContribution(value, projectDir);
5973
+ if (row) rows.push(row);
5974
+ }
5975
+ }
5976
+ if (rows.length > 0) precedence = 1;
5977
+ }
5978
+ const events = files.get("events.jsonl");
5979
+ if (events) {
5980
+ stats.filesFound++;
5981
+ const read2 = await stableRead(events, true);
5982
+ if (!read2.complete) {
5983
+ complete = false;
5984
+ stats.filesUnreadable++;
5985
+ continue;
5986
+ }
5987
+ stats.filesRead++;
5988
+ for (const value of read2.lines ?? []) {
5989
+ if (value === null) agg.parseErrors++;
5990
+ else
5991
+ ingestEvent2(
5992
+ agg,
5993
+ eventState,
5994
+ value,
5995
+ sessionFallback,
5996
+ projectDir,
5997
+ opts.sinceMs
5998
+ );
5999
+ }
6000
+ }
6001
+ rows = rows.filter(
6002
+ (row) => opts.sinceMs === void 0 || row.tsMs >= opts.sinceMs
6003
+ );
6004
+ rows = rows.map((row) => ({
6005
+ ...row,
6006
+ models: row.models.map(({ model, counts: counts2 }) => ({
6007
+ model: aliases.get(model) ?? model,
6008
+ counts: counts2
6009
+ }))
6010
+ }));
6011
+ if (rows.length > 0) {
6012
+ const sessionId = rows[0]?.sessionId;
6013
+ const held = candidates.get(sessionId);
6014
+ const total = (values) => values.flatMap((value) => value.models).reduce((sum, model) => sum + countsTotal(model.counts), 0);
6015
+ if (!held || precedence > held.precedence || precedence === held.precedence && total(rows) > total(held.rows))
6016
+ candidates.set(sessionId, { precedence, rows });
6017
+ }
6018
+ opts.onProgress?.(stats.filesFound);
6019
+ }
6020
+ }
6021
+ for (const { rows } of candidates.values()) {
6022
+ for (const row of rows) {
6023
+ ingestContribution(agg, row);
6024
+ const dates = sessionDates.get(row.sessionId) ?? /* @__PURE__ */ new Set();
6025
+ dates.add(new Date(row.tsMs).toISOString().slice(0, 10));
6026
+ sessionDates.set(row.sessionId, dates);
6027
+ }
6028
+ }
6029
+ return { stats, complete, sessionDates };
6030
+ }
6031
+
6032
+ // src/harness/grok/adapter.ts
6033
+ var GROK_HARNESS_NAME = "grok-build";
6034
+ var GROK_BUILTIN_TOOLS = /* @__PURE__ */ new Set([
6035
+ "run_terminal_command",
6036
+ "read_file",
6037
+ "write_file",
6038
+ "search",
6039
+ "web_search"
6040
+ ]);
6041
+ var grokAdapter = {
6042
+ name: GROK_HARNESS_NAME,
6043
+ builtinTools: GROK_BUILTIN_TOOLS,
6044
+ detect: (opts) => hasRecentFile(
6045
+ opts.roots ?? sessionRoots(),
6046
+ isGrokEvidenceFile,
6047
+ opts.sinceMs
6048
+ ),
6049
+ async scan(opts) {
6050
+ const aggregate = createAggregate4();
6051
+ const result = await scan3(aggregate, opts);
6052
+ return {
6053
+ aggregate,
6054
+ stats: result.stats,
6055
+ workflow: aggregate.workflow.finish(),
6056
+ workflowLocal: aggregate.workflowLocal,
6057
+ scanComplete: result.complete,
6058
+ sessionDates: result.sessionDates
6059
+ };
6060
+ }
6061
+ };
6062
+
5231
6063
  // src/harness/opencode/analyzer.ts
5232
6064
  var OPENCODE_BUILTIN_TOOLS = /* @__PURE__ */ new Set([
5233
6065
  "apply_patch",
@@ -5247,7 +6079,7 @@ var OPENCODE_BUILTIN_TOOLS = /* @__PURE__ */ new Set([
5247
6079
  "websearch",
5248
6080
  "write"
5249
6081
  ]);
5250
- function createAggregate4() {
6082
+ function createAggregate5() {
5251
6083
  const workflowLocal = createWorkflowLocalSources();
5252
6084
  return Object.assign(createAggregate(), {
5253
6085
  workflow: createHarnessWorkflowReducer("opencode", workflowLocal),
@@ -5296,7 +6128,7 @@ function ingestMessageRow(agg, state, row) {
5296
6128
  agg.assistantRecords++;
5297
6129
  agg.distinctResponses++;
5298
6130
  if (tsMs === null) agg.untimestampedResponses++;
5299
- const counts = {
6131
+ const counts2 = {
5300
6132
  input: asNum(row.input),
5301
6133
  output: asNum(row.output),
5302
6134
  cacheWrite5m: 0,
@@ -5304,7 +6136,7 @@ function ingestMessageRow(agg, state, row) {
5304
6136
  cacheWriteUnsplit: asNum(row.cacheWrite),
5305
6137
  cacheRead: asNum(row.cacheRead)
5306
6138
  };
5307
- const total = counts.input + counts.output + counts.cacheWriteUnsplit + counts.cacheRead;
6139
+ const total = counts2.input + counts2.output + counts2.cacheWriteUnsplit + counts2.cacheRead;
5308
6140
  if (session?.parentId) agg.sidechainTokens += total;
5309
6141
  else agg.mainTokens += total;
5310
6142
  const provider = asStr(row.providerId);
@@ -5313,8 +6145,8 @@ function ingestMessageRow(agg, state, row) {
5313
6145
  addModelUsage(
5314
6146
  agg,
5315
6147
  modelKey,
5316
- counts,
5317
- apiEquivalentCost(modelKey, counts, tsMs),
6148
+ counts2,
6149
+ apiEquivalentCost(modelKey, counts2, tsMs),
5318
6150
  1,
5319
6151
  { tsMs, sidechain: Boolean(session?.parentId) }
5320
6152
  );
@@ -5329,7 +6161,7 @@ function ingestMessageRow(agg, state, row) {
5329
6161
  tsMs,
5330
6162
  ...provider && model ? { model: `${provider}:${model}` } : {},
5331
6163
  thinkingTokens: asNum(row.reasoning),
5332
- responseTokens: counts.output,
6164
+ responseTokens: counts2.output,
5333
6165
  routingTokens: total,
5334
6166
  ...completed > tsMs ? { durationSec: (completed - tsMs) / 1e3 } : {}
5335
6167
  });
@@ -5412,14 +6244,14 @@ function noteConfiguredMcpServers2(agg, state, serverNames) {
5412
6244
 
5413
6245
  // src/harness/opencode/scan.ts
5414
6246
  import { readFileSync as readFileSync7 } from "node:fs";
5415
- import { readdir as readdir4, stat as stat4 } from "node:fs/promises";
5416
- import { homedir as homedir8 } from "node:os";
5417
- import path4 from "node:path";
6247
+ import { readdir as readdir5, stat as stat5 } from "node:fs/promises";
6248
+ import { homedir as homedir9 } from "node:os";
6249
+ import path5 from "node:path";
5418
6250
  var OPENCODE_MIGRATION_CEILING = 20260622202450;
5419
6251
  function opencodeDataDirs() {
5420
6252
  const xdg = process.env.XDG_DATA_HOME;
5421
- const base = xdg || path4.join(homedir8(), ".local", "share");
5422
- return [path4.join(base, "opencode")];
6253
+ const base = xdg || path5.join(homedir9(), ".local", "share");
6254
+ return [path5.join(base, "opencode")];
5423
6255
  }
5424
6256
  function isStoreFile(basename2) {
5425
6257
  return basename2 === "opencode.db" || /^opencode-[^/]+\.db$/.test(basename2);
@@ -5428,8 +6260,8 @@ async function dbFilesIn(root) {
5428
6260
  const override = process.env.OPENCODE_DB;
5429
6261
  if (override) return [override];
5430
6262
  try {
5431
- const entries = await readdir4(root, { withFileTypes: true });
5432
- return entries.filter((e) => e.isFile() && isStoreFile(e.name)).map((e) => path4.join(root, e.name)).sort();
6263
+ const entries = await readdir5(root, { withFileTypes: true });
6264
+ return entries.filter((e) => e.isFile() && isStoreFile(e.name)).map((e) => path5.join(root, e.name)).sort();
5433
6265
  } catch {
5434
6266
  return [];
5435
6267
  }
@@ -5449,7 +6281,7 @@ function errorClass2(e) {
5449
6281
  return e instanceof Error ? e.constructor.name : "unknown";
5450
6282
  }
5451
6283
  var readError2 = (reason) => Object.assign(new Error(reason), { code: reason });
5452
- async function scan3(agg, opts = {}) {
6284
+ async function scan4(agg, opts = {}) {
5453
6285
  const stats = emptyScanStats();
5454
6286
  const open2 = await loadSqlite();
5455
6287
  const sinceMs = opts.sinceMs ?? 0;
@@ -5466,7 +6298,7 @@ async function scan3(agg, opts = {}) {
5466
6298
  } catch (e) {
5467
6299
  stats.filesUnreadable++;
5468
6300
  stats.unreadableFiles.push({
5469
- path: path4.basename(file),
6301
+ path: path5.basename(file),
5470
6302
  reason: errorClass2(e)
5471
6303
  });
5472
6304
  }
@@ -5633,8 +6465,8 @@ function pickTs(jsonTs, columnTs) {
5633
6465
  }
5634
6466
  function opencodeConfigFile() {
5635
6467
  const xdg = process.env.XDG_CONFIG_HOME;
5636
- const base = xdg || path4.join(homedir8(), ".config");
5637
- return path4.join(base, "opencode", "opencode.json");
6468
+ const base = xdg || path5.join(homedir9(), ".config");
6469
+ return path5.join(base, "opencode", "opencode.json");
5638
6470
  }
5639
6471
  function readConfiguredMcpServers2(agg, state, configFile) {
5640
6472
  const file = configFile ?? opencodeConfigFile();
@@ -5708,7 +6540,7 @@ async function detectOpencode(opts) {
5708
6540
  }
5709
6541
  async function exists3(p8) {
5710
6542
  try {
5711
- await stat4(p8);
6543
+ await stat5(p8);
5712
6544
  return true;
5713
6545
  } catch {
5714
6546
  return false;
@@ -5727,8 +6559,8 @@ var opencodeAdapter = {
5727
6559
  });
5728
6560
  },
5729
6561
  async scan(opts) {
5730
- const aggregate = createAggregate4();
5731
- const stats = await scan3(aggregate, {
6562
+ const aggregate = createAggregate5();
6563
+ const stats = await scan4(aggregate, {
5732
6564
  sinceMs: opts.sinceMs,
5733
6565
  ...opts.onProgress ? { onProgress: opts.onProgress } : {}
5734
6566
  });
@@ -5742,7 +6574,7 @@ var opencodeAdapter = {
5742
6574
  };
5743
6575
 
5744
6576
  // src/harness/pi/analyzer.ts
5745
- function createAggregate5() {
6577
+ function createAggregate6() {
5746
6578
  const workflowLocal = createWorkflowLocalSources();
5747
6579
  return Object.assign(createAggregate(), {
5748
6580
  workflow: createHarnessWorkflowReducer("pi-mono", workflowLocal),
@@ -5811,7 +6643,7 @@ function ingestEntry(agg, raw, state, fold, sinceMs) {
5811
6643
  if (outcome !== "duplicate") {
5812
6644
  if (state.sessionId && tsMs !== null) {
5813
6645
  const usage = asObj(message.usage);
5814
- const counts = readCounts2(message.usage);
6646
+ const counts2 = readCounts2(message.usage);
5815
6647
  agg.workflow.ingest({
5816
6648
  type: "response",
5817
6649
  session: state.sessionId,
@@ -5820,8 +6652,8 @@ function ingestEntry(agg, raw, state, fold, sinceMs) {
5820
6652
  tsMs,
5821
6653
  ...state.modelKey ? { model: state.modelKey } : {},
5822
6654
  thinkingTokens: usage ? asNum(usage.reasoning) : 0,
5823
- responseTokens: counts?.output ?? 0,
5824
- routingTokens: counts ? countsTotal(counts) : 0
6655
+ responseTokens: counts2?.output ?? 0,
6656
+ routingTokens: counts2 ? countsTotal(counts2) : 0
5825
6657
  });
5826
6658
  ingestContent(agg, message.content, state.sessionId, state.cwd, tsMs);
5827
6659
  agg.workflow.ingest({
@@ -5848,9 +6680,9 @@ function noteActivity2(agg, state, tsMs) {
5848
6680
  agg.projectDirs.add(state.cwd ?? "(unknown)");
5849
6681
  }
5850
6682
  function countUsage(agg, fold, rec, msgTsMs, usageRaw, modelKey, tsMs, priceable = true) {
5851
- const counts = readCounts2(usageRaw);
5852
- if (!counts) return "none";
5853
- const total = countsTotal(counts);
6683
+ const counts2 = readCounts2(usageRaw);
6684
+ if (!counts2) return "none";
6685
+ const total = countsTotal(counts2);
5854
6686
  if (total === 0) return "none";
5855
6687
  const id = asStr(rec.id);
5856
6688
  if (id) {
@@ -5869,8 +6701,8 @@ function countUsage(agg, fold, rec, msgTsMs, usageRaw, modelKey, tsMs, priceable
5869
6701
  addModelUsage(
5870
6702
  agg,
5871
6703
  key2,
5872
- counts,
5873
- priceable ? apiEquivalentCost(key2, counts, tsMs) : null,
6704
+ counts2,
6705
+ priceable ? apiEquivalentCost(key2, counts2, tsMs) : null,
5874
6706
  1,
5875
6707
  { tsMs }
5876
6708
  );
@@ -5935,18 +6767,18 @@ function readCounts2(usageRaw) {
5935
6767
  }
5936
6768
 
5937
6769
  // src/harness/pi/scan.ts
5938
- import { createReadStream as createReadStream2 } from "node:fs";
5939
- import { readdir as readdir5, realpath as realpath3, stat as stat5 } from "node:fs/promises";
5940
- import { homedir as homedir9 } from "node:os";
5941
- import path5 from "node:path";
5942
- import readline2 from "node:readline";
6770
+ import { createReadStream as createReadStream3 } from "node:fs";
6771
+ import { readdir as readdir6, realpath as realpath3, stat as stat6 } from "node:fs/promises";
6772
+ import { homedir as homedir10 } from "node:os";
6773
+ import path6 from "node:path";
6774
+ import readline3 from "node:readline";
5943
6775
  var MAX_SESSION_VERSION = 3;
5944
6776
  function piAgentDir() {
5945
- return process.env.PI_CODING_AGENT_DIR || path5.join(homedir9(), ".pi", "agent");
6777
+ return process.env.PI_CODING_AGENT_DIR || path6.join(homedir10(), ".pi", "agent");
5946
6778
  }
5947
- function sessionRoots() {
6779
+ function sessionRoots2() {
5948
6780
  const override = process.env.PI_CODING_AGENT_SESSION_DIR;
5949
- return [override || path5.join(piAgentDir(), "sessions")];
6781
+ return [override || path6.join(piAgentDir(), "sessions")];
5950
6782
  }
5951
6783
  var SESSION_FILE_RE = /^\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}-\d{3}Z_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.jsonl$/;
5952
6784
  function isSessionFile(basename2) {
@@ -5955,21 +6787,21 @@ function isSessionFile(basename2) {
5955
6787
  async function* walkSessions(dir) {
5956
6788
  let entries;
5957
6789
  try {
5958
- entries = await readdir5(dir, { withFileTypes: true });
6790
+ entries = await readdir6(dir, { withFileTypes: true });
5959
6791
  } catch {
5960
6792
  return;
5961
6793
  }
5962
6794
  for (const e of entries) {
5963
- const full = path5.join(dir, e.name);
6795
+ const full = path6.join(dir, e.name);
5964
6796
  if (e.isDirectory()) yield* walkSessions(full);
5965
6797
  else if (e.isFile() && isSessionFile(e.name)) yield full;
5966
6798
  }
5967
6799
  }
5968
- async function scan4(agg, opts = {}) {
6800
+ async function scan5(agg, opts = {}) {
5969
6801
  const stats = emptyScanStats();
5970
6802
  const visited = /* @__PURE__ */ new Set();
5971
6803
  const fold = createFoldState();
5972
- for (const root of opts.roots ?? sessionRoots()) {
6804
+ for (const root of opts.roots ?? sessionRoots2()) {
5973
6805
  if (!await exists4(root)) continue;
5974
6806
  for await (const file of walkSessions(root)) {
5975
6807
  stats.filesFound++;
@@ -5986,7 +6818,7 @@ async function scan4(agg, opts = {}) {
5986
6818
  visited.add(resolved);
5987
6819
  if (opts.sinceMs !== void 0) {
5988
6820
  try {
5989
- const st = await stat5(file);
6821
+ const st = await stat6(file);
5990
6822
  if (st.mtimeMs < opts.sinceMs) {
5991
6823
  stats.filesSkippedByMtime++;
5992
6824
  continue;
@@ -6008,7 +6840,7 @@ async function scan4(agg, opts = {}) {
6008
6840
  stats.filesUnreadable++;
6009
6841
  stats.filesRead--;
6010
6842
  stats.unreadableFiles.push({
6011
- path: path5.relative(root, file),
6843
+ path: path6.relative(root, file),
6012
6844
  reason: "version-too-new"
6013
6845
  });
6014
6846
  }
@@ -6016,7 +6848,7 @@ async function scan4(agg, opts = {}) {
6016
6848
  stats.filesUnreadable++;
6017
6849
  stats.filesRead--;
6018
6850
  stats.unreadableFiles.push({
6019
- path: path5.relative(root, file),
6851
+ path: path6.relative(root, file),
6020
6852
  reason: "read-error"
6021
6853
  });
6022
6854
  }
@@ -6026,15 +6858,15 @@ async function scan4(agg, opts = {}) {
6026
6858
  }
6027
6859
  async function exists4(p8) {
6028
6860
  try {
6029
- await stat5(p8);
6861
+ await stat6(p8);
6030
6862
  return true;
6031
6863
  } catch {
6032
6864
  return false;
6033
6865
  }
6034
6866
  }
6035
6867
  async function ingestFile3(agg, fold, file, sinceMs) {
6036
- const rl = readline2.createInterface({
6037
- input: createReadStream2(file, { encoding: "utf8" }),
6868
+ const rl = readline3.createInterface({
6869
+ input: createReadStream3(file, { encoding: "utf8" }),
6038
6870
  crlfDelay: Number.POSITIVE_INFINITY
6039
6871
  });
6040
6872
  const state = createFileState2();
@@ -6087,14 +6919,14 @@ var piAdapter = {
6087
6919
  builtinTools: PI_BUILTIN_TOOLS,
6088
6920
  async detect(opts) {
6089
6921
  return hasRecentFile(
6090
- opts.roots ?? sessionRoots(),
6922
+ opts.roots ?? sessionRoots2(),
6091
6923
  isSessionFile,
6092
6924
  opts.sinceMs
6093
6925
  );
6094
6926
  },
6095
6927
  async scan(opts) {
6096
- const aggregate = createAggregate5();
6097
- const stats = await scan4(aggregate, {
6928
+ const aggregate = createAggregate6();
6929
+ const stats = await scan5(aggregate, {
6098
6930
  sinceMs: opts.sinceMs,
6099
6931
  ...opts.onProgress ? { onProgress: opts.onProgress } : {}
6100
6932
  });
@@ -6111,12 +6943,14 @@ var piAdapter = {
6111
6943
  var HARNESS_ADAPTERS = [
6112
6944
  claudeAdapter,
6113
6945
  codexAdapter,
6946
+ grokAdapter,
6114
6947
  opencodeAdapter,
6115
6948
  piAdapter
6116
6949
  ];
6117
6950
  function harnessLabel2(name) {
6118
6951
  if (name === CLAUDE_HARNESS_NAME) return "Claude Code";
6119
6952
  if (name === CODEX_HARNESS_NAME) return "Codex";
6953
+ if (name === GROK_HARNESS_NAME) return "Grok Build";
6120
6954
  if (name === OPENCODE_HARNESS_NAME) return "opencode";
6121
6955
  if (name === PI_HARNESS_NAME) return "Pi";
6122
6956
  return name;
@@ -6150,7 +6984,7 @@ var MCP_ADD_ARGS = [
6150
6984
  "mcp"
6151
6985
  ];
6152
6986
  var MCP_REMOVE_ARGS = ["mcp", "remove", "--scope", "user", "aistack"];
6153
- var SKILL_DEST = join6(homedir10(), ".claude", "skills", "aistack-sync");
6987
+ var SKILL_DEST = join6(homedir11(), ".claude", "skills", "aistack-sync");
6154
6988
  function runClaude(args) {
6155
6989
  const r = spawnSync("claude", args, { encoding: "utf-8" });
6156
6990
  const notFound = r.error !== void 0 && r.error.code === "ENOENT";
@@ -6383,9 +7217,9 @@ async function createCommand() {
6383
7217
  import { hostname } from "node:os";
6384
7218
  import * as p5 from "@clack/prompts";
6385
7219
  import open from "open";
6386
- function proposedMachineName(read = hostname) {
7220
+ function proposedMachineName(read2 = hostname) {
6387
7221
  try {
6388
- const name = read().trim().replace(/\.local$/i, "");
7222
+ const name = read2().trim().replace(/\.local$/i, "");
6389
7223
  if (!name || name.length > 64) return void 0;
6390
7224
  return name;
6391
7225
  } catch {
@@ -6474,11 +7308,11 @@ import * as p7 from "@clack/prompts";
6474
7308
  // src/autosync/codexHook.ts
6475
7309
  import { createHash } from "node:crypto";
6476
7310
  import { existsSync as existsSync8, mkdirSync as mkdirSync3, readFileSync as readFileSync9, writeFileSync as writeFileSync3 } from "node:fs";
6477
- import { homedir as homedir11 } from "node:os";
7311
+ import { homedir as homedir12 } from "node:os";
6478
7312
  import { dirname as dirname5, join as join8 } from "node:path";
6479
7313
  import { parse } from "smol-toml";
6480
7314
  function codexHome2() {
6481
- return process.env.CODEX_HOME || join8(homedir11(), ".codex");
7315
+ return process.env.CODEX_HOME || join8(homedir12(), ".codex");
6482
7316
  }
6483
7317
  function codexHooksFile() {
6484
7318
  return join8(codexHome2(), "hooks.json");
@@ -6504,9 +7338,9 @@ function readHooksJson(file) {
6504
7338
  }
6505
7339
  var CODEX_TRUST_INSTRUCTION = "Codex hook written - open Codex and run /hooks once to trust it, or it will not run.";
6506
7340
  function installCodexAutoSyncHook(file = codexHooksFile()) {
6507
- const read = readHooksJson(file);
6508
- if ("error" in read) return { ok: false, message: read.error };
6509
- const settings = read.settings;
7341
+ const read2 = readHooksJson(file);
7342
+ if ("error" in read2) return { ok: false, message: read2.error };
7343
+ const settings = read2.settings;
6510
7344
  const hooks = settings.hooks ?? {};
6511
7345
  const sessionStart = Array.isArray(hooks.SessionStart) ? hooks.SessionStart : [];
6512
7346
  const kept = sessionStart.map((m) => ({
@@ -6526,9 +7360,9 @@ function installCodexAutoSyncHook(file = codexHooksFile()) {
6526
7360
  function removeCodexAutoSyncHook(file = codexHooksFile()) {
6527
7361
  if (!existsSync8(file))
6528
7362
  return { ok: true, message: "no Codex hook to remove" };
6529
- const read = readHooksJson(file);
6530
- if ("error" in read) return { ok: false, message: read.error };
6531
- const settings = read.settings;
7363
+ const read2 = readHooksJson(file);
7364
+ if ("error" in read2) return { ok: false, message: read2.error };
7365
+ const settings = read2.settings;
6532
7366
  const sessionStart = settings.hooks?.SessionStart;
6533
7367
  if (!Array.isArray(sessionStart)) {
6534
7368
  return { ok: true, message: "no Codex hook to remove" };
@@ -6553,18 +7387,18 @@ function removeCodexAutoSyncHook(file = codexHooksFile()) {
6553
7387
  return { ok: true, message: `hook removed from ${file}` };
6554
7388
  }
6555
7389
  function codexAutoSyncHookInstalled(file = codexHooksFile()) {
6556
- const read = readHooksJson(file);
6557
- if ("error" in read) return false;
6558
- const sessionStart = read.settings.hooks?.SessionStart;
7390
+ const read2 = readHooksJson(file);
7391
+ if ("error" in read2) return false;
7392
+ const sessionStart = read2.settings.hooks?.SessionStart;
6559
7393
  if (!Array.isArray(sessionStart)) return false;
6560
7394
  return sessionStart.some((m) => (m.hooks ?? []).some((h) => isOurs(h)));
6561
7395
  }
6562
7396
  function codexHookTrusted(configFile = codexConfigFile(), hooksFile = codexHooksFile()) {
6563
7397
  try {
6564
7398
  const config = parse(readFileSync9(configFile, "utf-8"));
6565
- const read = readHooksJson(hooksFile);
6566
- if ("error" in read) return null;
6567
- const sessionStart = read.settings.hooks?.SessionStart;
7399
+ const read2 = readHooksJson(hooksFile);
7400
+ if ("error" in read2) return null;
7401
+ const sessionStart = read2.settings.hooks?.SessionStart;
6568
7402
  if (!Array.isArray(sessionStart)) return false;
6569
7403
  const matches = [];
6570
7404
  for (const [groupIndex, group] of sessionStart.entries()) {
@@ -6617,19 +7451,131 @@ function canonicalJson2(value) {
6617
7451
  // src/autosync/optin.ts
6618
7452
  import * as p6 from "@clack/prompts";
6619
7453
 
6620
- // src/autosync/hook.ts
6621
- import { existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync10, writeFileSync as writeFileSync4 } from "node:fs";
6622
- import { homedir as homedir12 } from "node:os";
7454
+ // src/autosync/grokHook.ts
7455
+ import {
7456
+ existsSync as existsSync9,
7457
+ mkdirSync as mkdirSync4,
7458
+ readFileSync as readFileSync10,
7459
+ unlinkSync,
7460
+ writeFileSync as writeFileSync4
7461
+ } from "node:fs";
7462
+ import { homedir as homedir13, platform as platform2 } from "node:os";
6623
7463
  import { dirname as dirname6, join as join9 } from "node:path";
6624
- var CLAUDE_SETTINGS_FILE = join9(homedir12(), ".claude", "settings.json");
6625
- var AUTO_SYNC_HOOK_COMMAND = "npx -y @use-aistack/cli@latest sync --auto || npx -y --prefer-offline @use-aistack/cli sync --auto";
7464
+ var GROK_HOOK_FILE = join9(
7465
+ process.env.GROK_HOME || join9(homedir13(), ".grok"),
7466
+ "hooks",
7467
+ "aistack.json"
7468
+ );
7469
+ var SYNC_COMMAND = "npx -y @use-aistack/cli@latest sync --auto || npx -y --prefer-offline @use-aistack/cli sync --auto";
7470
+ function grokHookCommand(os = platform2()) {
7471
+ if (os === "win32") {
7472
+ return `Start-Process -WindowStyle Hidden -FilePath "cmd.exe" -ArgumentList '/d /s /c "set AISTACK_HOOK_SOURCE=grok&& (${SYNC_COMMAND}) >NUL 2>&1"'`;
7473
+ }
7474
+ if (os === "darwin") {
7475
+ return `nohup sh -c 'export AISTACK_HOOK_SOURCE=grok; ${SYNC_COMMAND}' </dev/null >/dev/null 2>&1 &`;
7476
+ }
7477
+ return `setsid nohup sh -c 'export AISTACK_HOOK_SOURCE=grok; ${SYNC_COMMAND}' >/dev/null 2>&1 &`;
7478
+ }
7479
+ function readHookFile(file) {
7480
+ if (!existsSync9(file)) return { value: {} };
7481
+ try {
7482
+ const value = JSON.parse(readFileSync10(file, "utf-8"));
7483
+ if (value && typeof value === "object" && !Array.isArray(value)) {
7484
+ const candidate = value;
7485
+ if (candidate.hooks !== void 0 && (!candidate.hooks || typeof candidate.hooks !== "object" || Array.isArray(candidate.hooks) || Object.values(candidate.hooks).some(
7486
+ (groups) => !Array.isArray(groups)
7487
+ ))) {
7488
+ return {
7489
+ error: `${file} has a malformed hooks object. Fix it, then retry.`
7490
+ };
7491
+ }
7492
+ return { value: candidate };
7493
+ }
7494
+ } catch {
7495
+ return { error: `${file} is not valid JSON. Fix it, then retry.` };
7496
+ }
7497
+ return { error: `${file} does not hold a JSON object` };
7498
+ }
6626
7499
  function isOurs2(entry) {
6627
7500
  return typeof entry.command === "string" && entry.command.includes("@use-aistack/cli") && entry.command.includes("sync --auto");
6628
7501
  }
7502
+ function installGrokAutoSyncHook(file = GROK_HOOK_FILE, os = platform2()) {
7503
+ const read2 = readHookFile(file);
7504
+ if ("error" in read2) return { ok: false, message: read2.error };
7505
+ const foreignKeys = Object.keys(read2.value).filter((key2) => key2 !== "hooks");
7506
+ const foreignEvents = Object.keys(read2.value.hooks ?? {}).filter(
7507
+ (key2) => key2 !== "SessionStart"
7508
+ );
7509
+ const sessionStart = read2.value.hooks?.SessionStart ?? [];
7510
+ const foreignHandlers = sessionStart.flatMap(
7511
+ (group) => (group.hooks ?? []).filter((entry) => !isOurs2(entry))
7512
+ );
7513
+ if (foreignKeys.length > 0 || foreignEvents.length > 0 || foreignHandlers.length > 0) {
7514
+ return {
7515
+ ok: false,
7516
+ message: `${file} contains hooks not owned by AI Stack. Move them to another Grok hook file, then retry.`
7517
+ };
7518
+ }
7519
+ const value = {
7520
+ hooks: {
7521
+ SessionStart: [
7522
+ {
7523
+ hooks: [
7524
+ { type: "command", command: grokHookCommand(os), timeout: 5 }
7525
+ ]
7526
+ }
7527
+ ]
7528
+ }
7529
+ };
7530
+ mkdirSync4(dirname6(file), { recursive: true });
7531
+ writeFileSync4(file, `${JSON.stringify(value, null, 2)}
7532
+ `);
7533
+ return {
7534
+ ok: true,
7535
+ message: `Grok Build SessionStart hook written to ${file}. Start a new Grok session or reload hooks before expecting it to run.`
7536
+ };
7537
+ }
7538
+ function removeGrokAutoSyncHook(file = GROK_HOOK_FILE) {
7539
+ if (!existsSync9(file))
7540
+ return { ok: true, message: "no Grok Build hook to remove" };
7541
+ const read2 = readHookFile(file);
7542
+ if ("error" in read2) return { ok: false, message: read2.error };
7543
+ const entries = read2.value.hooks?.SessionStart ?? [];
7544
+ const onlyOurs = Object.keys(read2.value).every((key2) => key2 === "hooks") && Object.keys(read2.value.hooks ?? {}).every(
7545
+ (key2) => key2 === "SessionStart"
7546
+ ) && entries.every(
7547
+ (group) => (group.hooks ?? []).every((entry) => isOurs2(entry))
7548
+ );
7549
+ if (!onlyOurs) {
7550
+ return {
7551
+ ok: false,
7552
+ message: `${file} contains hooks not owned by AI Stack and was not removed.`
7553
+ };
7554
+ }
7555
+ unlinkSync(file);
7556
+ return { ok: true, message: `Grok Build hook removed from ${file}` };
7557
+ }
7558
+ function grokAutoSyncHookInstalled(file = GROK_HOOK_FILE) {
7559
+ const read2 = readHookFile(file);
7560
+ if ("error" in read2) return false;
7561
+ return (read2.value.hooks?.SessionStart ?? []).some(
7562
+ (group) => (group.hooks ?? []).some((entry) => isOurs2(entry))
7563
+ );
7564
+ }
7565
+
7566
+ // src/autosync/hook.ts
7567
+ import { existsSync as existsSync10, mkdirSync as mkdirSync5, readFileSync as readFileSync11, writeFileSync as writeFileSync5 } from "node:fs";
7568
+ import { homedir as homedir14 } from "node:os";
7569
+ import { dirname as dirname7, join as join10 } from "node:path";
7570
+ var CLAUDE_SETTINGS_FILE = join10(homedir14(), ".claude", "settings.json");
7571
+ var AUTO_SYNC_HOOK_COMMAND = "npx -y @use-aistack/cli@latest sync --auto || npx -y --prefer-offline @use-aistack/cli sync --auto";
7572
+ function isOurs3(entry) {
7573
+ return typeof entry.command === "string" && entry.command.includes("@use-aistack/cli") && entry.command.includes("sync --auto");
7574
+ }
6629
7575
  function readClaudeSettings(file) {
6630
- if (!existsSync9(file)) return { settings: {} };
7576
+ if (!existsSync10(file)) return { settings: {} };
6631
7577
  try {
6632
- const raw = JSON.parse(readFileSync10(file, "utf-8"));
7578
+ const raw = JSON.parse(readFileSync11(file, "utf-8"));
6633
7579
  if (raw && typeof raw === "object" && !Array.isArray(raw)) {
6634
7580
  return { settings: raw };
6635
7581
  }
@@ -6639,36 +7585,36 @@ function readClaudeSettings(file) {
6639
7585
  }
6640
7586
  }
6641
7587
  function installAutoSyncHook(file = CLAUDE_SETTINGS_FILE) {
6642
- const read = readClaudeSettings(file);
6643
- if ("error" in read) return { ok: false, message: read.error };
6644
- const settings = read.settings;
7588
+ const read2 = readClaudeSettings(file);
7589
+ if ("error" in read2) return { ok: false, message: read2.error };
7590
+ const settings = read2.settings;
6645
7591
  const hooks = settings.hooks ?? {};
6646
7592
  const sessionStart = Array.isArray(hooks.SessionStart) ? hooks.SessionStart : [];
6647
7593
  const kept = sessionStart.map((m) => ({
6648
7594
  ...m,
6649
- hooks: (m.hooks ?? []).filter((h) => !isOurs2(h))
7595
+ hooks: (m.hooks ?? []).filter((h) => !isOurs3(h))
6650
7596
  })).filter((m) => (m.hooks?.length ?? 0) > 0);
6651
7597
  kept.push({
6652
7598
  hooks: [{ type: "command", command: AUTO_SYNC_HOOK_COMMAND, async: true }]
6653
7599
  });
6654
7600
  settings.hooks = { ...hooks, SessionStart: kept };
6655
- mkdirSync4(dirname6(file), { recursive: true });
6656
- writeFileSync4(file, `${JSON.stringify(settings, null, 2)}
7601
+ mkdirSync5(dirname7(file), { recursive: true });
7602
+ writeFileSync5(file, `${JSON.stringify(settings, null, 2)}
6657
7603
  `);
6658
7604
  return { ok: true, message: `SessionStart hook written to ${file}` };
6659
7605
  }
6660
7606
  function removeAutoSyncHook(file = CLAUDE_SETTINGS_FILE) {
6661
- if (!existsSync9(file)) return { ok: true, message: "no hook to remove" };
6662
- const read = readClaudeSettings(file);
6663
- if ("error" in read) return { ok: false, message: read.error };
6664
- const settings = read.settings;
7607
+ if (!existsSync10(file)) return { ok: true, message: "no hook to remove" };
7608
+ const read2 = readClaudeSettings(file);
7609
+ if ("error" in read2) return { ok: false, message: read2.error };
7610
+ const settings = read2.settings;
6665
7611
  const sessionStart = settings.hooks?.SessionStart;
6666
7612
  if (!Array.isArray(sessionStart)) {
6667
7613
  return { ok: true, message: "no hook to remove" };
6668
7614
  }
6669
7615
  const kept = sessionStart.map((m) => ({
6670
7616
  ...m,
6671
- hooks: (m.hooks ?? []).filter((h) => !isOurs2(h))
7617
+ hooks: (m.hooks ?? []).filter((h) => !isOurs3(h))
6672
7618
  })).filter((m) => (m.hooks?.length ?? 0) > 0);
6673
7619
  const hooks = { ...settings.hooks };
6674
7620
  if (kept.length > 0) {
@@ -6681,21 +7627,21 @@ function removeAutoSyncHook(file = CLAUDE_SETTINGS_FILE) {
6681
7627
  } else {
6682
7628
  delete settings.hooks;
6683
7629
  }
6684
- writeFileSync4(file, `${JSON.stringify(settings, null, 2)}
7630
+ writeFileSync5(file, `${JSON.stringify(settings, null, 2)}
6685
7631
  `);
6686
7632
  return { ok: true, message: `hook removed from ${file}` };
6687
7633
  }
6688
7634
  function autoSyncHookInstalled(file = CLAUDE_SETTINGS_FILE) {
6689
- const read = readClaudeSettings(file);
6690
- if ("error" in read) return false;
6691
- const sessionStart = read.settings.hooks?.SessionStart;
7635
+ const read2 = readClaudeSettings(file);
7636
+ if ("error" in read2) return false;
7637
+ const sessionStart = read2.settings.hooks?.SessionStart;
6692
7638
  if (!Array.isArray(sessionStart)) return false;
6693
- return sessionStart.some((m) => (m.hooks ?? []).some((h) => isOurs2(h)));
7639
+ return sessionStart.some((m) => (m.hooks ?? []).some((h) => isOurs3(h)));
6694
7640
  }
6695
7641
 
6696
7642
  // src/autosync/optin.ts
6697
7643
  var NOT_LINKED = "This machine is not linked to an aistack account, and the auto-sync permission lives on your stack. Run `npx @use-aistack/cli sync` first.";
6698
- var NOTHING_TO_TRIGGER = `No Claude Code or Codex session on this machine in the last ${DEFAULT_WINDOW_DAYS} days, so nothing would trigger an auto-sync. Nothing was changed.`;
7644
+ var NOTHING_TO_TRIGGER = `No supported session on this machine in the last ${DEFAULT_WINDOW_DAYS} days, so nothing would trigger an auto-sync. Nothing was changed.`;
6699
7645
  async function enableAutoSync(frequencyHours = DEFAULT_FREQUENCY_HOURS, deps = {}) {
6700
7646
  frequencyHours = normalizeFrequencyHours(frequencyHours);
6701
7647
  const detected = await (deps.detectedImpl ?? detectedAdapters)();
@@ -6728,6 +7674,10 @@ async function enableAutoSync(frequencyHours = DEFAULT_FREQUENCY_HOURS, deps = {
6728
7674
  trustLine = codexResult.message;
6729
7675
  }
6730
7676
  }
7677
+ if (names.has(GROK_HARNESS_NAME)) {
7678
+ const grokResult = (deps.installGrokHook ?? installGrokAutoSyncHook)();
7679
+ if (!grokResult.ok) return grokResult;
7680
+ }
6731
7681
  saveSettings(
6732
7682
  {
6733
7683
  autoSyncAnswered: true,
@@ -6759,7 +7709,8 @@ async function disableAutoSync(deps = {}) {
6759
7709
  );
6760
7710
  const result = (deps.removeHook ?? removeAutoSyncHook)();
6761
7711
  const codexResult = (deps.removeCodexHook ?? removeCodexAutoSyncHook)();
6762
- const failures = [result, codexResult].filter((r) => !r.ok).map((r) => r.message);
7712
+ const grokResult = (deps.removeGrokHook ?? removeGrokAutoSyncHook)();
7713
+ const failures = [result, codexResult, grokResult].filter((r) => !r.ok).map((r) => r.message);
6763
7714
  const token = (deps.getTokenImpl ?? getToken)();
6764
7715
  if (token !== null) {
6765
7716
  try {
@@ -6782,7 +7733,40 @@ async function disableAutoSync(deps = {}) {
6782
7733
  };
6783
7734
  }
6784
7735
  async function reconcileAutoSync(permission, deps = {}) {
6785
- if (permission?.enabled !== true) return null;
7736
+ if (permission === null) return null;
7737
+ if (permission.enabled !== true) {
7738
+ const settings = getSettings(deps.settingsFile);
7739
+ saveSettings(
7740
+ {
7741
+ autoSyncAnswered: true,
7742
+ autoSync: {
7743
+ enabled: false,
7744
+ frequencyHours: normalizeFrequencyHours(
7745
+ permission.frequencyHours ?? settings.autoSync?.frequencyHours
7746
+ )
7747
+ }
7748
+ },
7749
+ deps.settingsFile
7750
+ );
7751
+ const results = [
7752
+ (deps.removeHook ?? removeAutoSyncHook)(),
7753
+ (deps.removeCodexHook ?? removeCodexAutoSyncHook)(),
7754
+ (deps.removeGrokHook ?? removeGrokAutoSyncHook)()
7755
+ ];
7756
+ const failures2 = results.filter((result) => !result.ok);
7757
+ if (failures2.length > 0) {
7758
+ return {
7759
+ ok: false,
7760
+ message: `Auto-sync is off on this machine, but a trigger could not be removed: ${failures2.map((result) => result.message).join("; ")}. The next interactive sync will retry.`
7761
+ };
7762
+ }
7763
+ const changed = settings.autoSync?.enabled !== false || results.some((result) => !result.message.toLowerCase().startsWith("no "));
7764
+ if (!changed) return null;
7765
+ return {
7766
+ ok: true,
7767
+ message: "Auto-sync is off on this machine. Removed its local triggers."
7768
+ };
7769
+ }
6786
7770
  const detected = await (deps.detectedImpl ?? detectedAdapters)();
6787
7771
  if (detected.length === 0) return null;
6788
7772
  const names = new Set(detected.map((a) => a.name));
@@ -6804,6 +7788,11 @@ async function reconcileAutoSync(permission, deps = {}) {
6804
7788
  deps.codexHookInstalledImpl ?? codexAutoSyncHookInstalled,
6805
7789
  deps.installCodexHook ?? installCodexAutoSyncHook
6806
7790
  );
7791
+ install(
7792
+ GROK_HARNESS_NAME,
7793
+ deps.grokHookInstalledImpl ?? grokAutoSyncHookInstalled,
7794
+ deps.installGrokHook ?? installGrokAutoSyncHook
7795
+ );
6807
7796
  if (failures.length > 0) {
6808
7797
  return {
6809
7798
  ok: false,
@@ -6888,15 +7877,18 @@ async function offerAutoSyncOptIn(deps = {}) {
6888
7877
  // src/autosync/run.ts
6889
7878
  import {
6890
7879
  appendFileSync,
6891
- mkdirSync as mkdirSync5,
6892
- readFileSync as readFileSync11,
6893
- writeFileSync as writeFileSync5
7880
+ closeSync,
7881
+ mkdirSync as mkdirSync7,
7882
+ openSync,
7883
+ readFileSync as readFileSync13,
7884
+ unlinkSync as unlinkSync2,
7885
+ writeFileSync as writeFileSync7
6894
7886
  } from "node:fs";
6895
- import { homedir as homedir13 } from "node:os";
6896
- import { dirname as dirname7, join as join10 } from "node:path";
7887
+ import { homedir as homedir16 } from "node:os";
7888
+ import { dirname as dirname8, join as join11 } from "node:path";
6897
7889
 
6898
7890
  // src/sync/stage.ts
6899
- import { createHash as createHash2 } from "node:crypto";
7891
+ import { createHash as createHash3 } from "node:crypto";
6900
7892
 
6901
7893
  // src/usage/days.ts
6902
7894
  var round6 = (n) => Math.round(n * 1e6) / 1e6;
@@ -6989,7 +7981,13 @@ function mergeUsageDays(perHarness) {
6989
7981
  function buildMeasuredDays(input) {
6990
7982
  const workflowByDate = /* @__PURE__ */ new Map();
6991
7983
  for (const day of input.workflow ?? []) workflowByDate.set(day.date, day);
6992
- const dates = [.../* @__PURE__ */ new Set([...input.usage.keys(), ...workflowByDate.keys()])].filter(
7984
+ const dates = [
7985
+ .../* @__PURE__ */ new Set([
7986
+ ...input.usage.keys(),
7987
+ ...workflowByDate.keys(),
7988
+ ...input.includeDates ?? []
7989
+ ])
7990
+ ].filter(
6993
7991
  (d) => /^\d{4}-\d{2}-\d{2}$/.test(d) && d >= input.from && d <= input.to
6994
7992
  ).sort();
6995
7993
  return dates.map((date) => {
@@ -7046,7 +8044,7 @@ function selectDaysToPublish(input) {
7046
8044
 
7047
8045
  // src/workflow/git.ts
7048
8046
  import { execFile, execFileSync as execFileSync2 } from "node:child_process";
7049
- import path6 from "node:path";
8047
+ import path7 from "node:path";
7050
8048
  var TEST_FILE_RULE_VERSION = "test-files/v2";
7051
8049
  var FILE_TYPE_RULE_VERSION = "file-types/v2";
7052
8050
  var COMMIT_SET_RULE_VERSION = "commit-set/v1";
@@ -7329,9 +8327,9 @@ function reduceGitHistories(histories, options) {
7329
8327
  if (included) seenCommits.add(hash);
7330
8328
  continue;
7331
8329
  }
7332
- const stat6 = parseNumstat(field);
7333
- if (!stat6) continue;
7334
- let file = stat6.file;
8330
+ const stat7 = parseNumstat(field);
8331
+ if (!stat7) continue;
8332
+ let file = stat7.file;
7335
8333
  if (file.length === 0) {
7336
8334
  fieldIndex += 2;
7337
8335
  file = fields[fieldIndex] ?? fields[fieldIndex - 1] ?? "";
@@ -7339,13 +8337,13 @@ function reduceGitHistories(histories, options) {
7339
8337
  if (!current?.included) continue;
7340
8338
  if (isUnauthoredPath(file)) continue;
7341
8339
  current.authored = true;
7342
- const fileChangedLines = stat6.additions + stat6.removals;
7343
- current.additions += stat6.additions;
7344
- current.removals += stat6.removals;
8340
+ const fileChangedLines = stat7.additions + stat7.removals;
8341
+ current.additions += stat7.additions;
8342
+ current.removals += stat7.removals;
7345
8343
  current.changedLines += fileChangedLines;
7346
8344
  if (isTestFile(file)) current.touchesTest = true;
7347
8345
  if (fileChangedLines <= 0) continue;
7348
- const extension = path6.extname(file).toLowerCase();
8346
+ const extension = path7.extname(file).toLowerCase();
7349
8347
  if (APPROVED_EXTENSIONS.has(extension)) {
7350
8348
  current.extensionLines.set(
7351
8349
  extension,
@@ -7434,7 +8432,7 @@ function buildWorkflowExtraction(harnessWorkflows, git, utcOffsetMinutes = machi
7434
8432
  ])
7435
8433
  ].sort();
7436
8434
  return {
7437
- aggregateVersion: WORKFLOW_AGGREGATES_V2,
8435
+ aggregateVersion: WORKFLOW_AGGREGATES_V3,
7438
8436
  utcOffsetMinutes,
7439
8437
  days: dates.map((date) => {
7440
8438
  const projects = projectDays.get(date)?.size;
@@ -7448,6 +8446,47 @@ function buildWorkflowExtraction(harnessWorkflows, git, utcOffsetMinutes = machi
7448
8446
  };
7449
8447
  }
7450
8448
 
8449
+ // src/sync/grokDateCache.ts
8450
+ import { createHash as createHash2 } from "node:crypto";
8451
+ import { existsSync as existsSync11, mkdirSync as mkdirSync6, readFileSync as readFileSync12, writeFileSync as writeFileSync6 } from "node:fs";
8452
+ import { homedir as homedir15 } from "node:os";
8453
+ import path8 from "node:path";
8454
+ var defaultFile = path8.join(
8455
+ homedir15(),
8456
+ ".config",
8457
+ "aistack",
8458
+ "grok-session-dates.json"
8459
+ );
8460
+ function grokCacheScope(baseUrl, stack, token) {
8461
+ return createHash2("sha256").update(`${baseUrl}\0${stack}\0${token}`).digest("hex");
8462
+ }
8463
+ function read(file) {
8464
+ if (!existsSync11(file)) return {};
8465
+ try {
8466
+ const value = JSON.parse(readFileSync12(file, "utf8"));
8467
+ return value && typeof value === "object" ? value : {};
8468
+ } catch {
8469
+ return {};
8470
+ }
8471
+ }
8472
+ function loadGrokDateHints(scope, file = defaultFile) {
8473
+ return read(file)[scope] ?? {};
8474
+ }
8475
+ function saveGrokDateHints(scope, hints, file = defaultFile) {
8476
+ const cache = read(file);
8477
+ cache[scope] = hints;
8478
+ mkdirSync6(path8.dirname(file), { recursive: true });
8479
+ writeFileSync6(file, JSON.stringify(cache, null, 2));
8480
+ }
8481
+ function mapToHints(value, floor) {
8482
+ return Object.fromEntries(
8483
+ [...value].map(([id, dates]) => [
8484
+ id,
8485
+ [...dates].filter((d) => d >= floor).sort()
8486
+ ])
8487
+ );
8488
+ }
8489
+
7451
8490
  // src/sync/summary.ts
7452
8491
  function fmtTokens(n) {
7453
8492
  const sig = (v) => {
@@ -7690,7 +8729,7 @@ var PHASE_ORDER = ["scout", "build", "verify", "handoff", "unknown"];
7690
8729
  function workflowBlock(workflowDays, utcOffsetMinutes, host) {
7691
8730
  const out = [];
7692
8731
  const folded = foldWorkflowDays(workflowDays, {
7693
- aggregateVersion: WORKFLOW_AGGREGATES_V2,
8732
+ aggregateVersion: WORKFLOW_AGGREGATES_V3,
7694
8733
  utcOffsetMinutes
7695
8734
  });
7696
8735
  const harnesses = folded?.harnesses ?? [];
@@ -7700,7 +8739,7 @@ function workflowBlock(workflowDays, utcOffsetMinutes, host) {
7700
8739
  ...new Set(withPlaybook.map((h) => h.phase?.ruleVersion ?? ""))
7701
8740
  ].filter(Boolean);
7702
8741
  out.push(
7703
- `workflow ${harnesses.length} harness${harnesses.length === 1 ? "" : "es"} \xB7 ${sessions} sessions \xB7 ${WORKFLOW_AGGREGATES_V2}`
8742
+ `workflow ${harnesses.length} harness${harnesses.length === 1 ? "" : "es"} \xB7 ${sessions} sessions \xB7 ${WORKFLOW_AGGREGATES_V3}`
7704
8743
  );
7705
8744
  const first = folded?.dates[0];
7706
8745
  const last = folded?.dates.at(-1);
@@ -7848,7 +8887,7 @@ function buildGateSummary(ctx) {
7848
8887
  // src/sync/stage.ts
7849
8888
  var utcDate2 = (ms) => new Date(ms).toISOString().slice(0, 10);
7850
8889
  function stageId(bodyJson) {
7851
- return createHash2("sha256").update(bodyJson).digest("hex").slice(0, 12);
8890
+ return createHash3("sha256").update(bodyJson).digest("hex").slice(0, 12);
7852
8891
  }
7853
8892
  async function stageSync(deps) {
7854
8893
  const now = (deps.now ?? Date.now)();
@@ -7900,6 +8939,9 @@ async function stageSync(deps) {
7900
8939
  const sinceMs = windowStartMs(now, windowDays);
7901
8940
  const daysSinceMs = windowStartMs(now, retentionDays);
7902
8941
  const active2 = await adapters(sinceMs);
8942
+ const historical = await adapters(daysSinceMs);
8943
+ let dayScansComplete = true;
8944
+ let grokCurrentDates = null;
7903
8945
  for (const adapter of active2) {
7904
8946
  progress(`Scanning recent ${adapter.name} usage`);
7905
8947
  const { aggregate, stats } = await adapter.scan({
@@ -7920,12 +8962,15 @@ async function stageSync(deps) {
7920
8962
  })
7921
8963
  );
7922
8964
  }
7923
- for (const adapter of active2) {
8965
+ for (const adapter of historical) {
7924
8966
  progress(`Reading historical ${adapter.name} days`);
7925
- const { aggregate, workflow: workflow2, workflowLocal } = await adapter.scan({
8967
+ const { aggregate, workflow: workflow2, workflowLocal, scanComplete, sessionDates } = await adapter.scan({
7926
8968
  sinceMs: daysSinceMs,
7927
8969
  onProgress: (files) => progress(`Reading historical ${adapter.name} days \xB7 ${files} files`)
7928
8970
  });
8971
+ if (scanComplete === false) dayScansComplete = false;
8972
+ if (adapter.name === "grok-build")
8973
+ grokCurrentDates = sessionDates ?? /* @__PURE__ */ new Map();
7929
8974
  workflowScans.push({ aggregate: workflow2, local: workflowLocal });
7930
8975
  usageScans.push(
7931
8976
  buildUsageDays({
@@ -7951,17 +8996,33 @@ async function stageSync(deps) {
7951
8996
  toMs: now
7952
8997
  });
7953
8998
  }
8999
+ const correctionDates = /* @__PURE__ */ new Set();
9000
+ let acknowledgePublish;
9001
+ if (grokCurrentDates && token && config.stack) {
9002
+ const scope = grokCacheScope(deps.baseUrl, config.stack.slug, token);
9003
+ const floor = utcDate2(daysSinceMs);
9004
+ const previous = loadGrokDateHints(scope);
9005
+ const current = mapToHints(grokCurrentDates, floor);
9006
+ for (const dates of Object.values(previous))
9007
+ for (const date of dates) correctionDates.add(date);
9008
+ for (const dates of Object.values(current))
9009
+ for (const date of dates) correctionDates.add(date);
9010
+ if (Object.keys(previous).some((id) => !(id in current)))
9011
+ dayScansComplete = false;
9012
+ acknowledgePublish = () => saveGrokDateHints(scope, current);
9013
+ }
7954
9014
  const localDays = applyDayConsent(
7955
9015
  buildMeasuredDays({
7956
9016
  usage: mergeUsageDays(usageScans),
7957
9017
  ...workflow ? { workflow: workflow.days } : {},
7958
9018
  from: utcDate2(daysSinceMs),
7959
- to: utcDate2(now)
9019
+ to: utcDate2(now),
9020
+ includeDates: correctionDates
7960
9021
  }),
7961
9022
  config
7962
9023
  );
7963
9024
  const days = selectDaysToPublish({
7964
- local: localDays,
9025
+ local: dayScansComplete ? localDays : [],
7965
9026
  manifest,
7966
9027
  todayUtc: utcDate2(now)
7967
9028
  });
@@ -7970,7 +9031,7 @@ async function stageSync(deps) {
7970
9031
  config,
7971
9032
  settings.autoSync,
7972
9033
  deps.trigger,
7973
- active2.length > 0 ? {
9034
+ historical.length > 0 && dayScansComplete ? {
7974
9035
  aggregateVersion: MEASURED_DAYS_V1,
7975
9036
  utcOffsetMinutes: workflow?.utcOffsetMinutes ?? machineUtcOffsetMinutes(),
7976
9037
  days: days.send
@@ -7994,8 +9055,8 @@ async function stageSync(deps) {
7994
9055
  width: process.stdout.columns
7995
9056
  };
7996
9057
  let blockedReason = null;
7997
- if (built.length === 0) {
7998
- blockedReason = `No active harness on this machine - no Claude Code and no Codex transcript from the last ${windowDays} days to read.`;
9058
+ if (historical.length === 0) {
9059
+ blockedReason = `No supported harness transcript from the last ${retentionDays} days to read.`;
7999
9060
  } else if (token === null) {
8000
9061
  blockedReason = "This machine is not linked. Run `npx @use-aistack/cli login` first.";
8001
9062
  } else if (config.stack === null) {
@@ -8013,25 +9074,47 @@ async function stageSync(deps) {
8013
9074
  stagedAt: now,
8014
9075
  blockedReason,
8015
9076
  days,
8016
- prices
9077
+ prices,
9078
+ ...acknowledgePublish ? { acknowledgePublish } : {}
8017
9079
  };
8018
9080
  }
8019
9081
 
8020
9082
  // src/autosync/run.ts
8021
- var SYNC_LOG_FILE = join10(homedir13(), ".config", "aistack", "sync.log");
9083
+ var SYNC_LOG_FILE = join11(homedir16(), ".config", "aistack", "sync.log");
8022
9084
  var SYNC_LOG_MAX_LINES = 200;
8023
9085
  var FIX_COMMAND = "npx @use-aistack/cli sync";
8024
9086
  var REVOKED_RESULT = "off - auto-sync is switched off for this stack";
8025
9087
  function appendLogLine(file, line) {
8026
- mkdirSync5(dirname7(file), { recursive: true });
9088
+ mkdirSync7(dirname8(file), { recursive: true });
8027
9089
  appendFileSync(file, `${line}
8028
9090
  `);
8029
- const lines2 = readFileSync11(file, "utf-8").split("\n").filter(Boolean);
9091
+ const lines2 = readFileSync13(file, "utf-8").split("\n").filter(Boolean);
8030
9092
  if (lines2.length > SYNC_LOG_MAX_LINES) {
8031
- writeFileSync5(file, `${lines2.slice(-SYNC_LOG_MAX_LINES).join("\n")}
9093
+ writeFileSync7(file, `${lines2.slice(-SYNC_LOG_MAX_LINES).join("\n")}
8032
9094
  `);
8033
9095
  }
8034
9096
  }
9097
+ function reserveAttempt(file, now, windowMs) {
9098
+ mkdirSync7(dirname8(file), { recursive: true });
9099
+ for (; ; ) {
9100
+ try {
9101
+ const fd = openSync(file, "wx");
9102
+ writeFileSync7(fd, String(now));
9103
+ closeSync(fd);
9104
+ return true;
9105
+ } catch (error) {
9106
+ if (error.code !== "EEXIST") throw error;
9107
+ const held = Number(readFileSync13(file, "utf-8"));
9108
+ if (Number.isFinite(held) && now - held < windowMs) return false;
9109
+ try {
9110
+ unlinkSync2(file);
9111
+ } catch (unlinkError) {
9112
+ if (unlinkError.code !== "ENOENT")
9113
+ throw unlinkError;
9114
+ }
9115
+ }
9116
+ }
9117
+ }
8035
9118
  async function runAutoSync(deps) {
8036
9119
  const now = (deps.now ?? Date.now)();
8037
9120
  const settingsFile = deps.settingsFile;
@@ -8046,9 +9129,13 @@ async function runAutoSync(deps) {
8046
9129
  return;
8047
9130
  }
8048
9131
  const frequencyHours = normalizeFrequencyHours(config.frequencyHours);
9132
+ const windowMs = frequencyHours * 36e5;
8049
9133
  const state = settings.autoSyncState ?? {};
8050
9134
  const lastRunAt = state.lastRunAt ?? 0;
8051
- if (now - lastRunAt < frequencyHours * 36e5) return;
9135
+ if (now - lastRunAt < windowMs) return;
9136
+ const reservationFile = deps.reservationFile ?? `${settingsFile ?? join11(homedir16(), ".config", "aistack", "settings.json")}.auto-sync-attempt`;
9137
+ if (!reserveAttempt(reservationFile, now, windowMs)) return;
9138
+ saveSettings({ autoSyncState: { ...state, lastRunAt: now } }, settingsFile);
8052
9139
  const stage = deps.stageImpl ?? stageSync;
8053
9140
  const publish = deps.publishImpl ?? syncPublish;
8054
9141
  const loadConfig = deps.loadConfigImpl ?? loadSyncConfig;
@@ -8139,7 +9226,7 @@ async function runAutoSync(deps) {
8139
9226
  logFile,
8140
9227
  `${stamp} fail (${consecutiveFailures} in a row) - ${failure2}`
8141
9228
  );
8142
- if (shouldWarn) {
9229
+ if (shouldWarn && deps.suppressOutput !== true && process.env.AISTACK_HOOK_SOURCE !== "grok") {
8143
9230
  emit(
8144
9231
  JSON.stringify({
8145
9232
  systemMessage: `aistack auto-sync failed ${consecutiveFailures} times in a row (${failure2}). Run \`${FIX_COMMAND}\` in a terminal to fix it, or \`${FIX_COMMAND} --auto off\` to stop these runs.`
@@ -8284,6 +9371,7 @@ async function syncCommand(options = {}) {
8284
9371
  s.start("Publishing");
8285
9372
  try {
8286
9373
  const res = await syncPublish(staged.token, staged.bodyJson);
9374
+ staged.acknowledgePublish?.();
8287
9375
  s.stop("Published");
8288
9376
  const lines2 = [
8289
9377
  `Snapshot received ${fmtReceivedAt(res.receivedAt)}`,
@@ -8514,6 +9602,7 @@ function createSyncServer(deps, send) {
8514
9602
  log7(`consent received, sending stage ${approvedStage.id}`);
8515
9603
  publish(approvedStage.token, approvedStage.bodyJson).then(
8516
9604
  (res) => {
9605
+ approvedStage.acknowledgePublish?.();
8517
9606
  if (staged?.id === approvedStage.id) staged = null;
8518
9607
  const lines2 = [
8519
9608
  `Published. Snapshot received ${fmtReceivedAt(res.receivedAt)}.`,