@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/README.md +2 -2
- package/dist/index.js +1334 -245
- package/dist/index.js.map +1 -1
- package/package.json +11 -10
- package/LICENSE +0 -21
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
|
-
|
|
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
|
|
124
|
-
if (
|
|
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
|
-
|
|
127
|
-
|
|
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
|
|
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.
|
|
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(
|
|
356
|
-
return fetch(`${BASE_URL}${
|
|
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(
|
|
775
|
-
if (!
|
|
776
|
-
return
|
|
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(
|
|
851
|
+
function readJson(path9) {
|
|
823
852
|
try {
|
|
824
|
-
if (!existsSync2(
|
|
825
|
-
return JSON.parse(readFileSync2(
|
|
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(
|
|
831
|
-
const hooks = readJson(
|
|
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:
|
|
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(
|
|
1009
|
+
function readText(path9) {
|
|
972
1010
|
try {
|
|
973
|
-
if (!existsSync3(
|
|
974
|
-
return readFileSync3(
|
|
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(
|
|
980
|
-
const raw = readText(
|
|
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(
|
|
989
|
-
return readParsed(
|
|
1026
|
+
function readJson2(path9) {
|
|
1027
|
+
return readParsed(path9, JSON.parse);
|
|
990
1028
|
}
|
|
991
|
-
function readYaml(
|
|
992
|
-
return readParsed(
|
|
1029
|
+
function readYaml(path9) {
|
|
1030
|
+
return readParsed(path9, parseYaml);
|
|
993
1031
|
}
|
|
994
|
-
function readToml(
|
|
995
|
-
return readParsed(
|
|
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
|
|
1116
|
-
return { url: mpRepoUrl, path:
|
|
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(
|
|
1210
|
+
function readJson3(path9) {
|
|
1154
1211
|
try {
|
|
1155
|
-
if (!existsSync4(
|
|
1156
|
-
return JSON.parse(readFileSync4(
|
|
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
|
|
1237
|
-
if (
|
|
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 (
|
|
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: ".
|
|
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
|
|
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,
|
|
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 +=
|
|
1934
|
-
m.output +=
|
|
1935
|
-
m.cacheWrite5m +=
|
|
1936
|
-
m.cacheWrite1h +=
|
|
1937
|
-
m.cacheWriteUnsplit +=
|
|
1938
|
-
m.cacheRead +=
|
|
1939
|
-
if (costUSD === null) m.unpricedTokens += countsTotal(
|
|
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
|
|
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
|
|
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
|
|
2860
|
+
const counts2 = /* @__PURE__ */ new Map();
|
|
2731
2861
|
for (const harness of reading.harnesses) {
|
|
2732
2862
|
for (const cell of harness.startHours) {
|
|
2733
|
-
|
|
2863
|
+
counts2.set(cell.hourUtc, (counts2.get(cell.hourUtc) ?? 0) + cell.sessions);
|
|
2734
2864
|
}
|
|
2735
2865
|
}
|
|
2736
|
-
return
|
|
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
|
|
2874
|
+
const counts2 = /* @__PURE__ */ new Map();
|
|
2745
2875
|
for (const [hourUtc, sessions] of startHoursUtc(reading)) {
|
|
2746
2876
|
const hour = ownerLocalHour(hourUtc, offsetMinutes);
|
|
2747
|
-
|
|
2877
|
+
counts2.set(hour, (counts2.get(hour) ?? 0) + sessions);
|
|
2748
2878
|
}
|
|
2749
|
-
if (
|
|
2750
|
-
return [...
|
|
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,
|
|
2888
|
+
function harnessShare(input, counts2) {
|
|
2759
2889
|
const synced = input.reading.harnesses.length;
|
|
2760
2890
|
if (synced === 0) return 0;
|
|
2761
|
-
return
|
|
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 = [
|
|
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 =
|
|
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")
|
|
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
|
|
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
|
-
...
|
|
4375
|
-
...
|
|
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,
|
|
4786
|
+
function makeEntry(modelKey, counts2, tsMs) {
|
|
4502
4787
|
return {
|
|
4503
4788
|
modelKey,
|
|
4504
|
-
counts,
|
|
4505
|
-
costUSD: apiEquivalentCost(modelKey,
|
|
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 *
|
|
4558
|
-
m.output += sign *
|
|
4559
|
-
m.cacheWrite5m += sign *
|
|
4560
|
-
m.cacheWrite1h += sign *
|
|
4561
|
-
m.cacheWriteUnsplit += sign *
|
|
4562
|
-
m.cacheRead += sign *
|
|
4563
|
-
if (costUSD === null) m.unpricedTokens += sign * countsTotal(
|
|
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)
|
|
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)
|
|
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
|
-
|
|
4855
|
-
|
|
4856
|
-
const
|
|
4857
|
-
|
|
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
|
-
|
|
4880
|
-
apiEquivalentCost(modelKey,
|
|
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:
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
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
|
-
|
|
5317
|
-
apiEquivalentCost(modelKey,
|
|
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:
|
|
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
|
|
5416
|
-
import { homedir as
|
|
5417
|
-
import
|
|
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 ||
|
|
5422
|
-
return [
|
|
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
|
|
5432
|
-
return entries.filter((e) => e.isFile() && isStoreFile(e.name)).map((e) =>
|
|
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
|
|
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:
|
|
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 ||
|
|
5637
|
-
return
|
|
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
|
|
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 =
|
|
5731
|
-
const stats = await
|
|
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
|
|
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
|
|
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:
|
|
5824
|
-
routingTokens:
|
|
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
|
|
5852
|
-
if (!
|
|
5853
|
-
const total = countsTotal(
|
|
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
|
-
|
|
5873
|
-
priceable ? apiEquivalentCost(key2,
|
|
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
|
|
5939
|
-
import { readdir as
|
|
5940
|
-
import { homedir as
|
|
5941
|
-
import
|
|
5942
|
-
import
|
|
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 ||
|
|
6777
|
+
return process.env.PI_CODING_AGENT_DIR || path6.join(homedir10(), ".pi", "agent");
|
|
5946
6778
|
}
|
|
5947
|
-
function
|
|
6779
|
+
function sessionRoots2() {
|
|
5948
6780
|
const override = process.env.PI_CODING_AGENT_SESSION_DIR;
|
|
5949
|
-
return [override ||
|
|
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
|
|
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 =
|
|
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
|
|
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 ??
|
|
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
|
|
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:
|
|
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:
|
|
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
|
|
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 =
|
|
6037
|
-
input:
|
|
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 ??
|
|
6922
|
+
opts.roots ?? sessionRoots2(),
|
|
6091
6923
|
isSessionFile,
|
|
6092
6924
|
opts.sinceMs
|
|
6093
6925
|
);
|
|
6094
6926
|
},
|
|
6095
6927
|
async scan(opts) {
|
|
6096
|
-
const aggregate =
|
|
6097
|
-
const stats = await
|
|
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(
|
|
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(
|
|
7220
|
+
function proposedMachineName(read2 = hostname) {
|
|
6387
7221
|
try {
|
|
6388
|
-
const name =
|
|
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
|
|
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(
|
|
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
|
|
6508
|
-
if ("error" in
|
|
6509
|
-
const 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
|
|
6530
|
-
if ("error" in
|
|
6531
|
-
const 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
|
|
6557
|
-
if ("error" in
|
|
6558
|
-
const 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
|
|
6566
|
-
if ("error" in
|
|
6567
|
-
const 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/
|
|
6621
|
-
import {
|
|
6622
|
-
|
|
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
|
|
6625
|
-
|
|
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 (!
|
|
7576
|
+
if (!existsSync10(file)) return { settings: {} };
|
|
6631
7577
|
try {
|
|
6632
|
-
const raw = JSON.parse(
|
|
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
|
|
6643
|
-
if ("error" in
|
|
6644
|
-
const 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) => !
|
|
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
|
-
|
|
6656
|
-
|
|
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 (!
|
|
6662
|
-
const
|
|
6663
|
-
if ("error" in
|
|
6664
|
-
const 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) => !
|
|
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
|
-
|
|
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
|
|
6690
|
-
if ("error" in
|
|
6691
|
-
const 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) =>
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
6892
|
-
|
|
6893
|
-
|
|
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
|
|
6896
|
-
import { dirname as
|
|
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
|
|
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 = [
|
|
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
|
|
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
|
|
7333
|
-
if (!
|
|
7334
|
-
let 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 =
|
|
7343
|
-
current.additions +=
|
|
7344
|
-
current.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 =
|
|
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:
|
|
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:
|
|
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 ${
|
|
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
|
|
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
|
|
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
|
-
|
|
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 (
|
|
7998
|
-
blockedReason = `No
|
|
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 =
|
|
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
|
-
|
|
9088
|
+
mkdirSync7(dirname8(file), { recursive: true });
|
|
8027
9089
|
appendFileSync(file, `${line}
|
|
8028
9090
|
`);
|
|
8029
|
-
const lines2 =
|
|
9091
|
+
const lines2 = readFileSync13(file, "utf-8").split("\n").filter(Boolean);
|
|
8030
9092
|
if (lines2.length > SYNC_LOG_MAX_LINES) {
|
|
8031
|
-
|
|
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 <
|
|
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)}.`,
|