@use-aistack/cli 0.13.0 → 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 +1074 -223
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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) {
|
|
@@ -2781,13 +2857,13 @@ function playbookHarnesses(reading) {
|
|
|
2781
2857
|
return reading.harnesses.filter((harness) => harness.phase !== void 0);
|
|
2782
2858
|
}
|
|
2783
2859
|
function startHoursUtc(reading) {
|
|
2784
|
-
const
|
|
2860
|
+
const counts2 = /* @__PURE__ */ new Map();
|
|
2785
2861
|
for (const harness of reading.harnesses) {
|
|
2786
2862
|
for (const cell of harness.startHours) {
|
|
2787
|
-
|
|
2863
|
+
counts2.set(cell.hourUtc, (counts2.get(cell.hourUtc) ?? 0) + cell.sessions);
|
|
2788
2864
|
}
|
|
2789
2865
|
}
|
|
2790
|
-
return
|
|
2866
|
+
return counts2;
|
|
2791
2867
|
}
|
|
2792
2868
|
function ownerLocalHour(hourUtc, offsetMinutes) {
|
|
2793
2869
|
return Math.floor(((hourUtc * 60 + offsetMinutes) / 60 % 24 + 24) % 24);
|
|
@@ -2795,13 +2871,13 @@ function ownerLocalHour(hourUtc, offsetMinutes) {
|
|
|
2795
2871
|
function modalStartHour(reading) {
|
|
2796
2872
|
const offsetMinutes = reading.utcOffsetMinutes;
|
|
2797
2873
|
if (offsetMinutes === void 0) return void 0;
|
|
2798
|
-
const
|
|
2874
|
+
const counts2 = /* @__PURE__ */ new Map();
|
|
2799
2875
|
for (const [hourUtc, sessions] of startHoursUtc(reading)) {
|
|
2800
2876
|
const hour = ownerLocalHour(hourUtc, offsetMinutes);
|
|
2801
|
-
|
|
2877
|
+
counts2.set(hour, (counts2.get(hour) ?? 0) + sessions);
|
|
2802
2878
|
}
|
|
2803
|
-
if (
|
|
2804
|
-
return [...
|
|
2879
|
+
if (counts2.size === 0) return void 0;
|
|
2880
|
+
return [...counts2.entries()].sort(
|
|
2805
2881
|
(a, b) => b[1] - a[1] || a[0] - b[0]
|
|
2806
2882
|
)[0]?.[0];
|
|
2807
2883
|
}
|
|
@@ -2809,10 +2885,10 @@ function modalStartHour(reading) {
|
|
|
2809
2885
|
// ../workflow-rules/src/componentRules.ts
|
|
2810
2886
|
var COMPONENT_RULES_V2 = "component-rules/v2";
|
|
2811
2887
|
var gitCoverage = () => 1;
|
|
2812
|
-
function harnessShare(input,
|
|
2888
|
+
function harnessShare(input, counts2) {
|
|
2813
2889
|
const synced = input.reading.harnesses.length;
|
|
2814
2890
|
if (synced === 0) return 0;
|
|
2815
|
-
return
|
|
2891
|
+
return counts2(input) / synced;
|
|
2816
2892
|
}
|
|
2817
2893
|
function topShare(entries) {
|
|
2818
2894
|
const total = entries.reduce((sum, entry) => sum + entry.value, 0);
|
|
@@ -3123,6 +3199,7 @@ var HANDOFF_MARKERS = {
|
|
|
3123
3199
|
"mcp__curia__request_review"
|
|
3124
3200
|
],
|
|
3125
3201
|
codex: ["request_user_input"],
|
|
3202
|
+
"grok-build": ["ask_user_question", "request_user_input"],
|
|
3126
3203
|
opencode: ["question"],
|
|
3127
3204
|
"pi-mono": []
|
|
3128
3205
|
};
|
|
@@ -3153,7 +3230,11 @@ var SCOUT_TOOLS = [
|
|
|
3153
3230
|
"web_search",
|
|
3154
3231
|
"tool_search",
|
|
3155
3232
|
"codebase_search",
|
|
3156
|
-
"find"
|
|
3233
|
+
"find",
|
|
3234
|
+
"search_tool",
|
|
3235
|
+
"search_files",
|
|
3236
|
+
"list_dir",
|
|
3237
|
+
"read_file"
|
|
3157
3238
|
];
|
|
3158
3239
|
var EDIT_TOOLS = [
|
|
3159
3240
|
"Edit",
|
|
@@ -3164,11 +3245,20 @@ var EDIT_TOOLS = [
|
|
|
3164
3245
|
"write",
|
|
3165
3246
|
"patch",
|
|
3166
3247
|
"multiedit",
|
|
3167
|
-
"apply_patch"
|
|
3248
|
+
"apply_patch",
|
|
3249
|
+
"write_file",
|
|
3250
|
+
"edit_file"
|
|
3168
3251
|
];
|
|
3169
3252
|
var REVIEW_SKILLS = ["code-review", "security-review", "review"];
|
|
3170
3253
|
var SCOUT_AGENTS = ["Explore", "Plan", "research"];
|
|
3171
|
-
var SHELL_TOOLS = [
|
|
3254
|
+
var SHELL_TOOLS = [
|
|
3255
|
+
"Bash",
|
|
3256
|
+
"bash",
|
|
3257
|
+
"shell",
|
|
3258
|
+
"local_shell",
|
|
3259
|
+
"exec_command",
|
|
3260
|
+
"run_terminal_command"
|
|
3261
|
+
];
|
|
3172
3262
|
var SKILL_TOOLS = ["Skill", "skill"];
|
|
3173
3263
|
var AGENT_TOOLS = ["Agent", "Task", "task", "agent"];
|
|
3174
3264
|
var BOOKKEEPING_TOOLS = [
|
|
@@ -4320,7 +4410,7 @@ function createHarnessWorkflowReducer(harness, localSources = createWorkflowLoca
|
|
|
4320
4410
|
0
|
|
4321
4411
|
);
|
|
4322
4412
|
const unknown = attributed === 0 ? 0 : windowPhaseSec.unknown / attributed;
|
|
4323
|
-
const routesModels = harness === "claude-code" || harness === "opencode";
|
|
4413
|
+
const routesModels = harness === "claude-code" || harness === "opencode" || harness === "grok-build";
|
|
4324
4414
|
const asRows = (map) => {
|
|
4325
4415
|
const safe = /* @__PURE__ */ new Map();
|
|
4326
4416
|
for (const [model, tokens] of map) {
|
|
@@ -4505,8 +4595,8 @@ function ingestClaudeCompaction(agg, rec, ctx, tsMs) {
|
|
|
4505
4595
|
...rec.isSidechain === true ? { sidechain: true } : {}
|
|
4506
4596
|
});
|
|
4507
4597
|
}
|
|
4508
|
-
function contextOf(
|
|
4509
|
-
return
|
|
4598
|
+
function contextOf(counts2) {
|
|
4599
|
+
return counts2.input + counts2.cacheWrite5m + counts2.cacheWrite1h + counts2.cacheWriteUnsplit + counts2.cacheRead;
|
|
4510
4600
|
}
|
|
4511
4601
|
function ingestClaudeTurnDuration(agg, rec, ctx, tsMs) {
|
|
4512
4602
|
if (tsMs === null || asStr(rec.subtype) !== "turn_duration" || asNum(rec.durationMs) <= 0) {
|
|
@@ -4534,12 +4624,12 @@ function ingestClaudeWorkflow(agg, rec, ctx, tsMs) {
|
|
|
4534
4624
|
const session = sidechain ? `${baseSession}:agent:${agentId ?? "unknown"}` : baseSession;
|
|
4535
4625
|
const parentSession = sidechain ? baseSession : void 0;
|
|
4536
4626
|
const usage = asObj(msg.usage);
|
|
4537
|
-
const
|
|
4627
|
+
const counts2 = usage ? readCounts(usage) : null;
|
|
4538
4628
|
const messageId = asStr(msg.id);
|
|
4539
4629
|
const newTurn = messageId ? !agg.workflowSeenTurns.has(messageId) : true;
|
|
4540
4630
|
if (messageId) agg.workflowSeenTurns.add(messageId);
|
|
4541
4631
|
let context = null;
|
|
4542
|
-
if (
|
|
4632
|
+
if (counts2 && isApiCall(rec)) {
|
|
4543
4633
|
const held = agg.contextFirstCall.get(session);
|
|
4544
4634
|
let first = false;
|
|
4545
4635
|
if (held === void 0) {
|
|
@@ -4547,11 +4637,11 @@ function ingestClaudeWorkflow(agg, rec, ctx, tsMs) {
|
|
|
4547
4637
|
first = true;
|
|
4548
4638
|
} else if (held !== null && held === messageId) first = true;
|
|
4549
4639
|
context = {
|
|
4550
|
-
contextTokens: contextOf(
|
|
4640
|
+
contextTokens: contextOf(counts2),
|
|
4551
4641
|
...first ? {
|
|
4552
4642
|
firstCall: {
|
|
4553
|
-
harnessTokens:
|
|
4554
|
-
instructionsTokens: contextOf(
|
|
4643
|
+
harnessTokens: counts2.cacheRead,
|
|
4644
|
+
instructionsTokens: contextOf(counts2) - counts2.cacheRead
|
|
4555
4645
|
}
|
|
4556
4646
|
} : {}
|
|
4557
4647
|
};
|
|
@@ -4565,8 +4655,8 @@ function ingestClaudeWorkflow(agg, rec, ctx, tsMs) {
|
|
|
4565
4655
|
...parentSession ? { parentSession } : {},
|
|
4566
4656
|
...messageId ? { responseId: messageId } : {},
|
|
4567
4657
|
...asName(msg.model) ? { model: asName(msg.model) } : {},
|
|
4568
|
-
...
|
|
4569
|
-
...
|
|
4658
|
+
...counts2 ? { responseTokens: counts2.output } : {},
|
|
4659
|
+
...counts2 ? { routingTokens: countsTotal(counts2) } : {},
|
|
4570
4660
|
...asStr(rec.effort) ?? asStr(msg.effort) ? { effort: asStr(rec.effort) ?? asStr(msg.effort) } : {},
|
|
4571
4661
|
...context ?? {}
|
|
4572
4662
|
});
|
|
@@ -4693,11 +4783,11 @@ function readCounts(usage) {
|
|
|
4693
4783
|
function modelKeyFor2(model, speed) {
|
|
4694
4784
|
return normalizeModel(speed === "fast" ? `${model}#fast` : model);
|
|
4695
4785
|
}
|
|
4696
|
-
function makeEntry(modelKey,
|
|
4786
|
+
function makeEntry(modelKey, counts2, tsMs) {
|
|
4697
4787
|
return {
|
|
4698
4788
|
modelKey,
|
|
4699
|
-
counts,
|
|
4700
|
-
costUSD: apiEquivalentCost(modelKey,
|
|
4789
|
+
counts: counts2,
|
|
4790
|
+
costUSD: apiEquivalentCost(modelKey, counts2, tsMs)
|
|
4701
4791
|
};
|
|
4702
4792
|
}
|
|
4703
4793
|
function buildContribution(usage, model, sidechain, tsMs) {
|
|
@@ -4742,24 +4832,24 @@ function buildContribution(usage, model, sidechain, tsMs) {
|
|
|
4742
4832
|
};
|
|
4743
4833
|
}
|
|
4744
4834
|
function applyContribution(agg, c, sign) {
|
|
4745
|
-
c.entries.forEach(({ modelKey, counts, costUSD }, i) => {
|
|
4835
|
+
c.entries.forEach(({ modelKey, counts: counts2, costUSD }, i) => {
|
|
4746
4836
|
let m = agg.byModel.get(modelKey);
|
|
4747
4837
|
if (!m) {
|
|
4748
4838
|
m = emptyUsage();
|
|
4749
4839
|
agg.byModel.set(modelKey, m);
|
|
4750
4840
|
}
|
|
4751
4841
|
if (i === 0) m.messages += sign;
|
|
4752
|
-
m.input += sign *
|
|
4753
|
-
m.output += sign *
|
|
4754
|
-
m.cacheWrite5m += sign *
|
|
4755
|
-
m.cacheWrite1h += sign *
|
|
4756
|
-
m.cacheWriteUnsplit += sign *
|
|
4757
|
-
m.cacheRead += sign *
|
|
4758
|
-
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);
|
|
4759
4849
|
else m.costUSD += sign * costUSD;
|
|
4760
4850
|
noteUsageResponse(
|
|
4761
4851
|
agg,
|
|
4762
|
-
{ tsMs: c.tsMs, modelKey, counts, costUSD, sidechain: c.sidechain },
|
|
4852
|
+
{ tsMs: c.tsMs, modelKey, counts: counts2, costUSD, sidechain: c.sidechain },
|
|
4763
4853
|
sign
|
|
4764
4854
|
);
|
|
4765
4855
|
});
|
|
@@ -5013,7 +5103,7 @@ function genuineDelta(payload, state, tsMs) {
|
|
|
5013
5103
|
if (!last) return null;
|
|
5014
5104
|
const inputTotal = asNum(last.input_tokens);
|
|
5015
5105
|
const cached = Math.min(asNum(last.cached_input_tokens), inputTotal);
|
|
5016
|
-
const
|
|
5106
|
+
const counts2 = {
|
|
5017
5107
|
input: inputTotal - cached,
|
|
5018
5108
|
output: asNum(last.output_tokens),
|
|
5019
5109
|
cacheWrite5m: 0,
|
|
@@ -5021,11 +5111,11 @@ function genuineDelta(payload, state, tsMs) {
|
|
|
5021
5111
|
cacheWriteUnsplit: 0,
|
|
5022
5112
|
cacheRead: cached
|
|
5023
5113
|
};
|
|
5024
|
-
if (countsTotal(
|
|
5114
|
+
if (countsTotal(counts2) === 0) return null;
|
|
5025
5115
|
if (tsMs !== null && state.metaTsMs !== null && tsMs - state.metaTsMs < FORK_REPLAY_WINDOW_MS)
|
|
5026
5116
|
return null;
|
|
5027
5117
|
return {
|
|
5028
|
-
counts,
|
|
5118
|
+
counts: counts2,
|
|
5029
5119
|
last,
|
|
5030
5120
|
contextWindow: info ? asNum(info.model_context_window) : 0
|
|
5031
5121
|
};
|
|
@@ -5092,8 +5182,8 @@ function noteActivity(agg, state, tsMs) {
|
|
|
5092
5182
|
function ingestEvent(agg, payload, state, tsMs, firstCall) {
|
|
5093
5183
|
const delta = genuineDelta(payload, state, tsMs);
|
|
5094
5184
|
if (!delta) return;
|
|
5095
|
-
const { counts, last, contextWindow } = delta;
|
|
5096
|
-
const total = countsTotal(
|
|
5185
|
+
const { counts: counts2, last, contextWindow } = delta;
|
|
5186
|
+
const total = countsTotal(counts2);
|
|
5097
5187
|
if (state.modelKey === null) return;
|
|
5098
5188
|
if (tsMs === null) agg.untimestampedResponses++;
|
|
5099
5189
|
agg.distinctResponses++;
|
|
@@ -5101,8 +5191,8 @@ function ingestEvent(agg, payload, state, tsMs, firstCall) {
|
|
|
5101
5191
|
addModelUsage(
|
|
5102
5192
|
agg,
|
|
5103
5193
|
modelKey,
|
|
5104
|
-
|
|
5105
|
-
apiEquivalentCost(modelKey,
|
|
5194
|
+
counts2,
|
|
5195
|
+
apiEquivalentCost(modelKey, counts2, tsMs),
|
|
5106
5196
|
1,
|
|
5107
5197
|
{ tsMs }
|
|
5108
5198
|
);
|
|
@@ -5116,7 +5206,7 @@ function ingestEvent(agg, payload, state, tsMs, firstCall) {
|
|
|
5116
5206
|
projectWorkspace: state.cwd ?? void 0,
|
|
5117
5207
|
tsMs,
|
|
5118
5208
|
model: modelKey,
|
|
5119
|
-
responseTokens:
|
|
5209
|
+
responseTokens: counts2.output,
|
|
5120
5210
|
routingTokens: total,
|
|
5121
5211
|
thinkingTokens: asNum(last.reasoning_output_tokens),
|
|
5122
5212
|
...state.effort ? { effort: state.effort } : {},
|
|
@@ -5125,12 +5215,12 @@ function ingestEvent(agg, payload, state, tsMs, firstCall) {
|
|
|
5125
5215
|
// instructions and tool specs, cached by an earlier session) and
|
|
5126
5216
|
// the fresh part is the instructions: AGENTS.md, environment,
|
|
5127
5217
|
// skills and the first prompt. Same method as Claude Code (#358).
|
|
5128
|
-
contextTokens:
|
|
5218
|
+
contextTokens: counts2.input + counts2.cacheRead,
|
|
5129
5219
|
...contextWindow > 0 ? { contextWindow } : {},
|
|
5130
5220
|
...firstCall && !state.forked ? {
|
|
5131
5221
|
firstCall: {
|
|
5132
|
-
harnessTokens:
|
|
5133
|
-
instructionsTokens:
|
|
5222
|
+
harnessTokens: counts2.cacheRead,
|
|
5223
|
+
instructionsTokens: counts2.input
|
|
5134
5224
|
}
|
|
5135
5225
|
} : {}
|
|
5136
5226
|
});
|
|
@@ -5350,11 +5440,11 @@ function ingestWithRetry(agg, file, opts) {
|
|
|
5350
5440
|
}
|
|
5351
5441
|
}
|
|
5352
5442
|
function ingestFile2(agg, file, opts) {
|
|
5353
|
-
const
|
|
5443
|
+
const readFile2 = opts.readFileImpl ?? readFileSync6;
|
|
5354
5444
|
let text;
|
|
5355
5445
|
if (file.endsWith(".zst")) {
|
|
5356
5446
|
if (zstdDecompress === null) throw readError("zstd-unsupported");
|
|
5357
|
-
const raw =
|
|
5447
|
+
const raw = readFile2(file);
|
|
5358
5448
|
try {
|
|
5359
5449
|
text = zstdDecompress(
|
|
5360
5450
|
Buffer.isBuffer(raw) ? raw : Buffer.from(raw)
|
|
@@ -5363,7 +5453,7 @@ function ingestFile2(agg, file, opts) {
|
|
|
5363
5453
|
throw readError("zstd-corrupt");
|
|
5364
5454
|
}
|
|
5365
5455
|
} else {
|
|
5366
|
-
text =
|
|
5456
|
+
text = readFile2(file).toString("utf8");
|
|
5367
5457
|
}
|
|
5368
5458
|
const records = [];
|
|
5369
5459
|
let nonEmptyLines = 0;
|
|
@@ -5466,6 +5556,510 @@ var codexAdapter = {
|
|
|
5466
5556
|
}
|
|
5467
5557
|
};
|
|
5468
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
|
+
|
|
5469
6063
|
// src/harness/opencode/analyzer.ts
|
|
5470
6064
|
var OPENCODE_BUILTIN_TOOLS = /* @__PURE__ */ new Set([
|
|
5471
6065
|
"apply_patch",
|
|
@@ -5485,7 +6079,7 @@ var OPENCODE_BUILTIN_TOOLS = /* @__PURE__ */ new Set([
|
|
|
5485
6079
|
"websearch",
|
|
5486
6080
|
"write"
|
|
5487
6081
|
]);
|
|
5488
|
-
function
|
|
6082
|
+
function createAggregate5() {
|
|
5489
6083
|
const workflowLocal = createWorkflowLocalSources();
|
|
5490
6084
|
return Object.assign(createAggregate(), {
|
|
5491
6085
|
workflow: createHarnessWorkflowReducer("opencode", workflowLocal),
|
|
@@ -5534,7 +6128,7 @@ function ingestMessageRow(agg, state, row) {
|
|
|
5534
6128
|
agg.assistantRecords++;
|
|
5535
6129
|
agg.distinctResponses++;
|
|
5536
6130
|
if (tsMs === null) agg.untimestampedResponses++;
|
|
5537
|
-
const
|
|
6131
|
+
const counts2 = {
|
|
5538
6132
|
input: asNum(row.input),
|
|
5539
6133
|
output: asNum(row.output),
|
|
5540
6134
|
cacheWrite5m: 0,
|
|
@@ -5542,7 +6136,7 @@ function ingestMessageRow(agg, state, row) {
|
|
|
5542
6136
|
cacheWriteUnsplit: asNum(row.cacheWrite),
|
|
5543
6137
|
cacheRead: asNum(row.cacheRead)
|
|
5544
6138
|
};
|
|
5545
|
-
const total =
|
|
6139
|
+
const total = counts2.input + counts2.output + counts2.cacheWriteUnsplit + counts2.cacheRead;
|
|
5546
6140
|
if (session?.parentId) agg.sidechainTokens += total;
|
|
5547
6141
|
else agg.mainTokens += total;
|
|
5548
6142
|
const provider = asStr(row.providerId);
|
|
@@ -5551,8 +6145,8 @@ function ingestMessageRow(agg, state, row) {
|
|
|
5551
6145
|
addModelUsage(
|
|
5552
6146
|
agg,
|
|
5553
6147
|
modelKey,
|
|
5554
|
-
|
|
5555
|
-
apiEquivalentCost(modelKey,
|
|
6148
|
+
counts2,
|
|
6149
|
+
apiEquivalentCost(modelKey, counts2, tsMs),
|
|
5556
6150
|
1,
|
|
5557
6151
|
{ tsMs, sidechain: Boolean(session?.parentId) }
|
|
5558
6152
|
);
|
|
@@ -5567,7 +6161,7 @@ function ingestMessageRow(agg, state, row) {
|
|
|
5567
6161
|
tsMs,
|
|
5568
6162
|
...provider && model ? { model: `${provider}:${model}` } : {},
|
|
5569
6163
|
thinkingTokens: asNum(row.reasoning),
|
|
5570
|
-
responseTokens:
|
|
6164
|
+
responseTokens: counts2.output,
|
|
5571
6165
|
routingTokens: total,
|
|
5572
6166
|
...completed > tsMs ? { durationSec: (completed - tsMs) / 1e3 } : {}
|
|
5573
6167
|
});
|
|
@@ -5650,14 +6244,14 @@ function noteConfiguredMcpServers2(agg, state, serverNames) {
|
|
|
5650
6244
|
|
|
5651
6245
|
// src/harness/opencode/scan.ts
|
|
5652
6246
|
import { readFileSync as readFileSync7 } from "node:fs";
|
|
5653
|
-
import { readdir as
|
|
5654
|
-
import { homedir as
|
|
5655
|
-
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";
|
|
5656
6250
|
var OPENCODE_MIGRATION_CEILING = 20260622202450;
|
|
5657
6251
|
function opencodeDataDirs() {
|
|
5658
6252
|
const xdg = process.env.XDG_DATA_HOME;
|
|
5659
|
-
const base = xdg ||
|
|
5660
|
-
return [
|
|
6253
|
+
const base = xdg || path5.join(homedir9(), ".local", "share");
|
|
6254
|
+
return [path5.join(base, "opencode")];
|
|
5661
6255
|
}
|
|
5662
6256
|
function isStoreFile(basename2) {
|
|
5663
6257
|
return basename2 === "opencode.db" || /^opencode-[^/]+\.db$/.test(basename2);
|
|
@@ -5666,8 +6260,8 @@ async function dbFilesIn(root) {
|
|
|
5666
6260
|
const override = process.env.OPENCODE_DB;
|
|
5667
6261
|
if (override) return [override];
|
|
5668
6262
|
try {
|
|
5669
|
-
const entries = await
|
|
5670
|
-
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();
|
|
5671
6265
|
} catch {
|
|
5672
6266
|
return [];
|
|
5673
6267
|
}
|
|
@@ -5687,7 +6281,7 @@ function errorClass2(e) {
|
|
|
5687
6281
|
return e instanceof Error ? e.constructor.name : "unknown";
|
|
5688
6282
|
}
|
|
5689
6283
|
var readError2 = (reason) => Object.assign(new Error(reason), { code: reason });
|
|
5690
|
-
async function
|
|
6284
|
+
async function scan4(agg, opts = {}) {
|
|
5691
6285
|
const stats = emptyScanStats();
|
|
5692
6286
|
const open2 = await loadSqlite();
|
|
5693
6287
|
const sinceMs = opts.sinceMs ?? 0;
|
|
@@ -5704,7 +6298,7 @@ async function scan3(agg, opts = {}) {
|
|
|
5704
6298
|
} catch (e) {
|
|
5705
6299
|
stats.filesUnreadable++;
|
|
5706
6300
|
stats.unreadableFiles.push({
|
|
5707
|
-
path:
|
|
6301
|
+
path: path5.basename(file),
|
|
5708
6302
|
reason: errorClass2(e)
|
|
5709
6303
|
});
|
|
5710
6304
|
}
|
|
@@ -5871,8 +6465,8 @@ function pickTs(jsonTs, columnTs) {
|
|
|
5871
6465
|
}
|
|
5872
6466
|
function opencodeConfigFile() {
|
|
5873
6467
|
const xdg = process.env.XDG_CONFIG_HOME;
|
|
5874
|
-
const base = xdg ||
|
|
5875
|
-
return
|
|
6468
|
+
const base = xdg || path5.join(homedir9(), ".config");
|
|
6469
|
+
return path5.join(base, "opencode", "opencode.json");
|
|
5876
6470
|
}
|
|
5877
6471
|
function readConfiguredMcpServers2(agg, state, configFile) {
|
|
5878
6472
|
const file = configFile ?? opencodeConfigFile();
|
|
@@ -5946,7 +6540,7 @@ async function detectOpencode(opts) {
|
|
|
5946
6540
|
}
|
|
5947
6541
|
async function exists3(p8) {
|
|
5948
6542
|
try {
|
|
5949
|
-
await
|
|
6543
|
+
await stat5(p8);
|
|
5950
6544
|
return true;
|
|
5951
6545
|
} catch {
|
|
5952
6546
|
return false;
|
|
@@ -5965,8 +6559,8 @@ var opencodeAdapter = {
|
|
|
5965
6559
|
});
|
|
5966
6560
|
},
|
|
5967
6561
|
async scan(opts) {
|
|
5968
|
-
const aggregate =
|
|
5969
|
-
const stats = await
|
|
6562
|
+
const aggregate = createAggregate5();
|
|
6563
|
+
const stats = await scan4(aggregate, {
|
|
5970
6564
|
sinceMs: opts.sinceMs,
|
|
5971
6565
|
...opts.onProgress ? { onProgress: opts.onProgress } : {}
|
|
5972
6566
|
});
|
|
@@ -5980,7 +6574,7 @@ var opencodeAdapter = {
|
|
|
5980
6574
|
};
|
|
5981
6575
|
|
|
5982
6576
|
// src/harness/pi/analyzer.ts
|
|
5983
|
-
function
|
|
6577
|
+
function createAggregate6() {
|
|
5984
6578
|
const workflowLocal = createWorkflowLocalSources();
|
|
5985
6579
|
return Object.assign(createAggregate(), {
|
|
5986
6580
|
workflow: createHarnessWorkflowReducer("pi-mono", workflowLocal),
|
|
@@ -6049,7 +6643,7 @@ function ingestEntry(agg, raw, state, fold, sinceMs) {
|
|
|
6049
6643
|
if (outcome !== "duplicate") {
|
|
6050
6644
|
if (state.sessionId && tsMs !== null) {
|
|
6051
6645
|
const usage = asObj(message.usage);
|
|
6052
|
-
const
|
|
6646
|
+
const counts2 = readCounts2(message.usage);
|
|
6053
6647
|
agg.workflow.ingest({
|
|
6054
6648
|
type: "response",
|
|
6055
6649
|
session: state.sessionId,
|
|
@@ -6058,8 +6652,8 @@ function ingestEntry(agg, raw, state, fold, sinceMs) {
|
|
|
6058
6652
|
tsMs,
|
|
6059
6653
|
...state.modelKey ? { model: state.modelKey } : {},
|
|
6060
6654
|
thinkingTokens: usage ? asNum(usage.reasoning) : 0,
|
|
6061
|
-
responseTokens:
|
|
6062
|
-
routingTokens:
|
|
6655
|
+
responseTokens: counts2?.output ?? 0,
|
|
6656
|
+
routingTokens: counts2 ? countsTotal(counts2) : 0
|
|
6063
6657
|
});
|
|
6064
6658
|
ingestContent(agg, message.content, state.sessionId, state.cwd, tsMs);
|
|
6065
6659
|
agg.workflow.ingest({
|
|
@@ -6086,9 +6680,9 @@ function noteActivity2(agg, state, tsMs) {
|
|
|
6086
6680
|
agg.projectDirs.add(state.cwd ?? "(unknown)");
|
|
6087
6681
|
}
|
|
6088
6682
|
function countUsage(agg, fold, rec, msgTsMs, usageRaw, modelKey, tsMs, priceable = true) {
|
|
6089
|
-
const
|
|
6090
|
-
if (!
|
|
6091
|
-
const total = countsTotal(
|
|
6683
|
+
const counts2 = readCounts2(usageRaw);
|
|
6684
|
+
if (!counts2) return "none";
|
|
6685
|
+
const total = countsTotal(counts2);
|
|
6092
6686
|
if (total === 0) return "none";
|
|
6093
6687
|
const id = asStr(rec.id);
|
|
6094
6688
|
if (id) {
|
|
@@ -6107,8 +6701,8 @@ function countUsage(agg, fold, rec, msgTsMs, usageRaw, modelKey, tsMs, priceable
|
|
|
6107
6701
|
addModelUsage(
|
|
6108
6702
|
agg,
|
|
6109
6703
|
key2,
|
|
6110
|
-
|
|
6111
|
-
priceable ? apiEquivalentCost(key2,
|
|
6704
|
+
counts2,
|
|
6705
|
+
priceable ? apiEquivalentCost(key2, counts2, tsMs) : null,
|
|
6112
6706
|
1,
|
|
6113
6707
|
{ tsMs }
|
|
6114
6708
|
);
|
|
@@ -6173,18 +6767,18 @@ function readCounts2(usageRaw) {
|
|
|
6173
6767
|
}
|
|
6174
6768
|
|
|
6175
6769
|
// src/harness/pi/scan.ts
|
|
6176
|
-
import { createReadStream as
|
|
6177
|
-
import { readdir as
|
|
6178
|
-
import { homedir as
|
|
6179
|
-
import
|
|
6180
|
-
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";
|
|
6181
6775
|
var MAX_SESSION_VERSION = 3;
|
|
6182
6776
|
function piAgentDir() {
|
|
6183
|
-
return process.env.PI_CODING_AGENT_DIR ||
|
|
6777
|
+
return process.env.PI_CODING_AGENT_DIR || path6.join(homedir10(), ".pi", "agent");
|
|
6184
6778
|
}
|
|
6185
|
-
function
|
|
6779
|
+
function sessionRoots2() {
|
|
6186
6780
|
const override = process.env.PI_CODING_AGENT_SESSION_DIR;
|
|
6187
|
-
return [override ||
|
|
6781
|
+
return [override || path6.join(piAgentDir(), "sessions")];
|
|
6188
6782
|
}
|
|
6189
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$/;
|
|
6190
6784
|
function isSessionFile(basename2) {
|
|
@@ -6193,21 +6787,21 @@ function isSessionFile(basename2) {
|
|
|
6193
6787
|
async function* walkSessions(dir) {
|
|
6194
6788
|
let entries;
|
|
6195
6789
|
try {
|
|
6196
|
-
entries = await
|
|
6790
|
+
entries = await readdir6(dir, { withFileTypes: true });
|
|
6197
6791
|
} catch {
|
|
6198
6792
|
return;
|
|
6199
6793
|
}
|
|
6200
6794
|
for (const e of entries) {
|
|
6201
|
-
const full =
|
|
6795
|
+
const full = path6.join(dir, e.name);
|
|
6202
6796
|
if (e.isDirectory()) yield* walkSessions(full);
|
|
6203
6797
|
else if (e.isFile() && isSessionFile(e.name)) yield full;
|
|
6204
6798
|
}
|
|
6205
6799
|
}
|
|
6206
|
-
async function
|
|
6800
|
+
async function scan5(agg, opts = {}) {
|
|
6207
6801
|
const stats = emptyScanStats();
|
|
6208
6802
|
const visited = /* @__PURE__ */ new Set();
|
|
6209
6803
|
const fold = createFoldState();
|
|
6210
|
-
for (const root of opts.roots ??
|
|
6804
|
+
for (const root of opts.roots ?? sessionRoots2()) {
|
|
6211
6805
|
if (!await exists4(root)) continue;
|
|
6212
6806
|
for await (const file of walkSessions(root)) {
|
|
6213
6807
|
stats.filesFound++;
|
|
@@ -6224,7 +6818,7 @@ async function scan4(agg, opts = {}) {
|
|
|
6224
6818
|
visited.add(resolved);
|
|
6225
6819
|
if (opts.sinceMs !== void 0) {
|
|
6226
6820
|
try {
|
|
6227
|
-
const st = await
|
|
6821
|
+
const st = await stat6(file);
|
|
6228
6822
|
if (st.mtimeMs < opts.sinceMs) {
|
|
6229
6823
|
stats.filesSkippedByMtime++;
|
|
6230
6824
|
continue;
|
|
@@ -6246,7 +6840,7 @@ async function scan4(agg, opts = {}) {
|
|
|
6246
6840
|
stats.filesUnreadable++;
|
|
6247
6841
|
stats.filesRead--;
|
|
6248
6842
|
stats.unreadableFiles.push({
|
|
6249
|
-
path:
|
|
6843
|
+
path: path6.relative(root, file),
|
|
6250
6844
|
reason: "version-too-new"
|
|
6251
6845
|
});
|
|
6252
6846
|
}
|
|
@@ -6254,7 +6848,7 @@ async function scan4(agg, opts = {}) {
|
|
|
6254
6848
|
stats.filesUnreadable++;
|
|
6255
6849
|
stats.filesRead--;
|
|
6256
6850
|
stats.unreadableFiles.push({
|
|
6257
|
-
path:
|
|
6851
|
+
path: path6.relative(root, file),
|
|
6258
6852
|
reason: "read-error"
|
|
6259
6853
|
});
|
|
6260
6854
|
}
|
|
@@ -6264,15 +6858,15 @@ async function scan4(agg, opts = {}) {
|
|
|
6264
6858
|
}
|
|
6265
6859
|
async function exists4(p8) {
|
|
6266
6860
|
try {
|
|
6267
|
-
await
|
|
6861
|
+
await stat6(p8);
|
|
6268
6862
|
return true;
|
|
6269
6863
|
} catch {
|
|
6270
6864
|
return false;
|
|
6271
6865
|
}
|
|
6272
6866
|
}
|
|
6273
6867
|
async function ingestFile3(agg, fold, file, sinceMs) {
|
|
6274
|
-
const rl =
|
|
6275
|
-
input:
|
|
6868
|
+
const rl = readline3.createInterface({
|
|
6869
|
+
input: createReadStream3(file, { encoding: "utf8" }),
|
|
6276
6870
|
crlfDelay: Number.POSITIVE_INFINITY
|
|
6277
6871
|
});
|
|
6278
6872
|
const state = createFileState2();
|
|
@@ -6325,14 +6919,14 @@ var piAdapter = {
|
|
|
6325
6919
|
builtinTools: PI_BUILTIN_TOOLS,
|
|
6326
6920
|
async detect(opts) {
|
|
6327
6921
|
return hasRecentFile(
|
|
6328
|
-
opts.roots ??
|
|
6922
|
+
opts.roots ?? sessionRoots2(),
|
|
6329
6923
|
isSessionFile,
|
|
6330
6924
|
opts.sinceMs
|
|
6331
6925
|
);
|
|
6332
6926
|
},
|
|
6333
6927
|
async scan(opts) {
|
|
6334
|
-
const aggregate =
|
|
6335
|
-
const stats = await
|
|
6928
|
+
const aggregate = createAggregate6();
|
|
6929
|
+
const stats = await scan5(aggregate, {
|
|
6336
6930
|
sinceMs: opts.sinceMs,
|
|
6337
6931
|
...opts.onProgress ? { onProgress: opts.onProgress } : {}
|
|
6338
6932
|
});
|
|
@@ -6349,12 +6943,14 @@ var piAdapter = {
|
|
|
6349
6943
|
var HARNESS_ADAPTERS = [
|
|
6350
6944
|
claudeAdapter,
|
|
6351
6945
|
codexAdapter,
|
|
6946
|
+
grokAdapter,
|
|
6352
6947
|
opencodeAdapter,
|
|
6353
6948
|
piAdapter
|
|
6354
6949
|
];
|
|
6355
6950
|
function harnessLabel2(name) {
|
|
6356
6951
|
if (name === CLAUDE_HARNESS_NAME) return "Claude Code";
|
|
6357
6952
|
if (name === CODEX_HARNESS_NAME) return "Codex";
|
|
6953
|
+
if (name === GROK_HARNESS_NAME) return "Grok Build";
|
|
6358
6954
|
if (name === OPENCODE_HARNESS_NAME) return "opencode";
|
|
6359
6955
|
if (name === PI_HARNESS_NAME) return "Pi";
|
|
6360
6956
|
return name;
|
|
@@ -6388,7 +6984,7 @@ var MCP_ADD_ARGS = [
|
|
|
6388
6984
|
"mcp"
|
|
6389
6985
|
];
|
|
6390
6986
|
var MCP_REMOVE_ARGS = ["mcp", "remove", "--scope", "user", "aistack"];
|
|
6391
|
-
var SKILL_DEST = join6(
|
|
6987
|
+
var SKILL_DEST = join6(homedir11(), ".claude", "skills", "aistack-sync");
|
|
6392
6988
|
function runClaude(args) {
|
|
6393
6989
|
const r = spawnSync("claude", args, { encoding: "utf-8" });
|
|
6394
6990
|
const notFound = r.error !== void 0 && r.error.code === "ENOENT";
|
|
@@ -6621,9 +7217,9 @@ async function createCommand() {
|
|
|
6621
7217
|
import { hostname } from "node:os";
|
|
6622
7218
|
import * as p5 from "@clack/prompts";
|
|
6623
7219
|
import open from "open";
|
|
6624
|
-
function proposedMachineName(
|
|
7220
|
+
function proposedMachineName(read2 = hostname) {
|
|
6625
7221
|
try {
|
|
6626
|
-
const name =
|
|
7222
|
+
const name = read2().trim().replace(/\.local$/i, "");
|
|
6627
7223
|
if (!name || name.length > 64) return void 0;
|
|
6628
7224
|
return name;
|
|
6629
7225
|
} catch {
|
|
@@ -6712,11 +7308,11 @@ import * as p7 from "@clack/prompts";
|
|
|
6712
7308
|
// src/autosync/codexHook.ts
|
|
6713
7309
|
import { createHash } from "node:crypto";
|
|
6714
7310
|
import { existsSync as existsSync8, mkdirSync as mkdirSync3, readFileSync as readFileSync9, writeFileSync as writeFileSync3 } from "node:fs";
|
|
6715
|
-
import { homedir as
|
|
7311
|
+
import { homedir as homedir12 } from "node:os";
|
|
6716
7312
|
import { dirname as dirname5, join as join8 } from "node:path";
|
|
6717
7313
|
import { parse } from "smol-toml";
|
|
6718
7314
|
function codexHome2() {
|
|
6719
|
-
return process.env.CODEX_HOME || join8(
|
|
7315
|
+
return process.env.CODEX_HOME || join8(homedir12(), ".codex");
|
|
6720
7316
|
}
|
|
6721
7317
|
function codexHooksFile() {
|
|
6722
7318
|
return join8(codexHome2(), "hooks.json");
|
|
@@ -6742,9 +7338,9 @@ function readHooksJson(file) {
|
|
|
6742
7338
|
}
|
|
6743
7339
|
var CODEX_TRUST_INSTRUCTION = "Codex hook written - open Codex and run /hooks once to trust it, or it will not run.";
|
|
6744
7340
|
function installCodexAutoSyncHook(file = codexHooksFile()) {
|
|
6745
|
-
const
|
|
6746
|
-
if ("error" in
|
|
6747
|
-
const settings =
|
|
7341
|
+
const read2 = readHooksJson(file);
|
|
7342
|
+
if ("error" in read2) return { ok: false, message: read2.error };
|
|
7343
|
+
const settings = read2.settings;
|
|
6748
7344
|
const hooks = settings.hooks ?? {};
|
|
6749
7345
|
const sessionStart = Array.isArray(hooks.SessionStart) ? hooks.SessionStart : [];
|
|
6750
7346
|
const kept = sessionStart.map((m) => ({
|
|
@@ -6764,9 +7360,9 @@ function installCodexAutoSyncHook(file = codexHooksFile()) {
|
|
|
6764
7360
|
function removeCodexAutoSyncHook(file = codexHooksFile()) {
|
|
6765
7361
|
if (!existsSync8(file))
|
|
6766
7362
|
return { ok: true, message: "no Codex hook to remove" };
|
|
6767
|
-
const
|
|
6768
|
-
if ("error" in
|
|
6769
|
-
const settings =
|
|
7363
|
+
const read2 = readHooksJson(file);
|
|
7364
|
+
if ("error" in read2) return { ok: false, message: read2.error };
|
|
7365
|
+
const settings = read2.settings;
|
|
6770
7366
|
const sessionStart = settings.hooks?.SessionStart;
|
|
6771
7367
|
if (!Array.isArray(sessionStart)) {
|
|
6772
7368
|
return { ok: true, message: "no Codex hook to remove" };
|
|
@@ -6791,18 +7387,18 @@ function removeCodexAutoSyncHook(file = codexHooksFile()) {
|
|
|
6791
7387
|
return { ok: true, message: `hook removed from ${file}` };
|
|
6792
7388
|
}
|
|
6793
7389
|
function codexAutoSyncHookInstalled(file = codexHooksFile()) {
|
|
6794
|
-
const
|
|
6795
|
-
if ("error" in
|
|
6796
|
-
const sessionStart =
|
|
7390
|
+
const read2 = readHooksJson(file);
|
|
7391
|
+
if ("error" in read2) return false;
|
|
7392
|
+
const sessionStart = read2.settings.hooks?.SessionStart;
|
|
6797
7393
|
if (!Array.isArray(sessionStart)) return false;
|
|
6798
7394
|
return sessionStart.some((m) => (m.hooks ?? []).some((h) => isOurs(h)));
|
|
6799
7395
|
}
|
|
6800
7396
|
function codexHookTrusted(configFile = codexConfigFile(), hooksFile = codexHooksFile()) {
|
|
6801
7397
|
try {
|
|
6802
7398
|
const config = parse(readFileSync9(configFile, "utf-8"));
|
|
6803
|
-
const
|
|
6804
|
-
if ("error" in
|
|
6805
|
-
const sessionStart =
|
|
7399
|
+
const read2 = readHooksJson(hooksFile);
|
|
7400
|
+
if ("error" in read2) return null;
|
|
7401
|
+
const sessionStart = read2.settings.hooks?.SessionStart;
|
|
6806
7402
|
if (!Array.isArray(sessionStart)) return false;
|
|
6807
7403
|
const matches = [];
|
|
6808
7404
|
for (const [groupIndex, group] of sessionStart.entries()) {
|
|
@@ -6855,19 +7451,131 @@ function canonicalJson2(value) {
|
|
|
6855
7451
|
// src/autosync/optin.ts
|
|
6856
7452
|
import * as p6 from "@clack/prompts";
|
|
6857
7453
|
|
|
6858
|
-
// src/autosync/
|
|
6859
|
-
import {
|
|
6860
|
-
|
|
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";
|
|
6861
7463
|
import { dirname as dirname6, join as join9 } from "node:path";
|
|
6862
|
-
var
|
|
6863
|
-
|
|
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
|
+
}
|
|
6864
7499
|
function isOurs2(entry) {
|
|
6865
7500
|
return typeof entry.command === "string" && entry.command.includes("@use-aistack/cli") && entry.command.includes("sync --auto");
|
|
6866
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
|
+
}
|
|
6867
7575
|
function readClaudeSettings(file) {
|
|
6868
|
-
if (!
|
|
7576
|
+
if (!existsSync10(file)) return { settings: {} };
|
|
6869
7577
|
try {
|
|
6870
|
-
const raw = JSON.parse(
|
|
7578
|
+
const raw = JSON.parse(readFileSync11(file, "utf-8"));
|
|
6871
7579
|
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
6872
7580
|
return { settings: raw };
|
|
6873
7581
|
}
|
|
@@ -6877,36 +7585,36 @@ function readClaudeSettings(file) {
|
|
|
6877
7585
|
}
|
|
6878
7586
|
}
|
|
6879
7587
|
function installAutoSyncHook(file = CLAUDE_SETTINGS_FILE) {
|
|
6880
|
-
const
|
|
6881
|
-
if ("error" in
|
|
6882
|
-
const settings =
|
|
7588
|
+
const read2 = readClaudeSettings(file);
|
|
7589
|
+
if ("error" in read2) return { ok: false, message: read2.error };
|
|
7590
|
+
const settings = read2.settings;
|
|
6883
7591
|
const hooks = settings.hooks ?? {};
|
|
6884
7592
|
const sessionStart = Array.isArray(hooks.SessionStart) ? hooks.SessionStart : [];
|
|
6885
7593
|
const kept = sessionStart.map((m) => ({
|
|
6886
7594
|
...m,
|
|
6887
|
-
hooks: (m.hooks ?? []).filter((h) => !
|
|
7595
|
+
hooks: (m.hooks ?? []).filter((h) => !isOurs3(h))
|
|
6888
7596
|
})).filter((m) => (m.hooks?.length ?? 0) > 0);
|
|
6889
7597
|
kept.push({
|
|
6890
7598
|
hooks: [{ type: "command", command: AUTO_SYNC_HOOK_COMMAND, async: true }]
|
|
6891
7599
|
});
|
|
6892
7600
|
settings.hooks = { ...hooks, SessionStart: kept };
|
|
6893
|
-
|
|
6894
|
-
|
|
7601
|
+
mkdirSync5(dirname7(file), { recursive: true });
|
|
7602
|
+
writeFileSync5(file, `${JSON.stringify(settings, null, 2)}
|
|
6895
7603
|
`);
|
|
6896
7604
|
return { ok: true, message: `SessionStart hook written to ${file}` };
|
|
6897
7605
|
}
|
|
6898
7606
|
function removeAutoSyncHook(file = CLAUDE_SETTINGS_FILE) {
|
|
6899
|
-
if (!
|
|
6900
|
-
const
|
|
6901
|
-
if ("error" in
|
|
6902
|
-
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;
|
|
6903
7611
|
const sessionStart = settings.hooks?.SessionStart;
|
|
6904
7612
|
if (!Array.isArray(sessionStart)) {
|
|
6905
7613
|
return { ok: true, message: "no hook to remove" };
|
|
6906
7614
|
}
|
|
6907
7615
|
const kept = sessionStart.map((m) => ({
|
|
6908
7616
|
...m,
|
|
6909
|
-
hooks: (m.hooks ?? []).filter((h) => !
|
|
7617
|
+
hooks: (m.hooks ?? []).filter((h) => !isOurs3(h))
|
|
6910
7618
|
})).filter((m) => (m.hooks?.length ?? 0) > 0);
|
|
6911
7619
|
const hooks = { ...settings.hooks };
|
|
6912
7620
|
if (kept.length > 0) {
|
|
@@ -6919,21 +7627,21 @@ function removeAutoSyncHook(file = CLAUDE_SETTINGS_FILE) {
|
|
|
6919
7627
|
} else {
|
|
6920
7628
|
delete settings.hooks;
|
|
6921
7629
|
}
|
|
6922
|
-
|
|
7630
|
+
writeFileSync5(file, `${JSON.stringify(settings, null, 2)}
|
|
6923
7631
|
`);
|
|
6924
7632
|
return { ok: true, message: `hook removed from ${file}` };
|
|
6925
7633
|
}
|
|
6926
7634
|
function autoSyncHookInstalled(file = CLAUDE_SETTINGS_FILE) {
|
|
6927
|
-
const
|
|
6928
|
-
if ("error" in
|
|
6929
|
-
const sessionStart =
|
|
7635
|
+
const read2 = readClaudeSettings(file);
|
|
7636
|
+
if ("error" in read2) return false;
|
|
7637
|
+
const sessionStart = read2.settings.hooks?.SessionStart;
|
|
6930
7638
|
if (!Array.isArray(sessionStart)) return false;
|
|
6931
|
-
return sessionStart.some((m) => (m.hooks ?? []).some((h) =>
|
|
7639
|
+
return sessionStart.some((m) => (m.hooks ?? []).some((h) => isOurs3(h)));
|
|
6932
7640
|
}
|
|
6933
7641
|
|
|
6934
7642
|
// src/autosync/optin.ts
|
|
6935
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.";
|
|
6936
|
-
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.`;
|
|
6937
7645
|
async function enableAutoSync(frequencyHours = DEFAULT_FREQUENCY_HOURS, deps = {}) {
|
|
6938
7646
|
frequencyHours = normalizeFrequencyHours(frequencyHours);
|
|
6939
7647
|
const detected = await (deps.detectedImpl ?? detectedAdapters)();
|
|
@@ -6966,6 +7674,10 @@ async function enableAutoSync(frequencyHours = DEFAULT_FREQUENCY_HOURS, deps = {
|
|
|
6966
7674
|
trustLine = codexResult.message;
|
|
6967
7675
|
}
|
|
6968
7676
|
}
|
|
7677
|
+
if (names.has(GROK_HARNESS_NAME)) {
|
|
7678
|
+
const grokResult = (deps.installGrokHook ?? installGrokAutoSyncHook)();
|
|
7679
|
+
if (!grokResult.ok) return grokResult;
|
|
7680
|
+
}
|
|
6969
7681
|
saveSettings(
|
|
6970
7682
|
{
|
|
6971
7683
|
autoSyncAnswered: true,
|
|
@@ -6997,7 +7709,8 @@ async function disableAutoSync(deps = {}) {
|
|
|
6997
7709
|
);
|
|
6998
7710
|
const result = (deps.removeHook ?? removeAutoSyncHook)();
|
|
6999
7711
|
const codexResult = (deps.removeCodexHook ?? removeCodexAutoSyncHook)();
|
|
7000
|
-
const
|
|
7712
|
+
const grokResult = (deps.removeGrokHook ?? removeGrokAutoSyncHook)();
|
|
7713
|
+
const failures = [result, codexResult, grokResult].filter((r) => !r.ok).map((r) => r.message);
|
|
7001
7714
|
const token = (deps.getTokenImpl ?? getToken)();
|
|
7002
7715
|
if (token !== null) {
|
|
7003
7716
|
try {
|
|
@@ -7020,7 +7733,40 @@ async function disableAutoSync(deps = {}) {
|
|
|
7020
7733
|
};
|
|
7021
7734
|
}
|
|
7022
7735
|
async function reconcileAutoSync(permission, deps = {}) {
|
|
7023
|
-
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
|
+
}
|
|
7024
7770
|
const detected = await (deps.detectedImpl ?? detectedAdapters)();
|
|
7025
7771
|
if (detected.length === 0) return null;
|
|
7026
7772
|
const names = new Set(detected.map((a) => a.name));
|
|
@@ -7042,6 +7788,11 @@ async function reconcileAutoSync(permission, deps = {}) {
|
|
|
7042
7788
|
deps.codexHookInstalledImpl ?? codexAutoSyncHookInstalled,
|
|
7043
7789
|
deps.installCodexHook ?? installCodexAutoSyncHook
|
|
7044
7790
|
);
|
|
7791
|
+
install(
|
|
7792
|
+
GROK_HARNESS_NAME,
|
|
7793
|
+
deps.grokHookInstalledImpl ?? grokAutoSyncHookInstalled,
|
|
7794
|
+
deps.installGrokHook ?? installGrokAutoSyncHook
|
|
7795
|
+
);
|
|
7045
7796
|
if (failures.length > 0) {
|
|
7046
7797
|
return {
|
|
7047
7798
|
ok: false,
|
|
@@ -7126,15 +7877,18 @@ async function offerAutoSyncOptIn(deps = {}) {
|
|
|
7126
7877
|
// src/autosync/run.ts
|
|
7127
7878
|
import {
|
|
7128
7879
|
appendFileSync,
|
|
7129
|
-
|
|
7130
|
-
|
|
7131
|
-
|
|
7880
|
+
closeSync,
|
|
7881
|
+
mkdirSync as mkdirSync7,
|
|
7882
|
+
openSync,
|
|
7883
|
+
readFileSync as readFileSync13,
|
|
7884
|
+
unlinkSync as unlinkSync2,
|
|
7885
|
+
writeFileSync as writeFileSync7
|
|
7132
7886
|
} from "node:fs";
|
|
7133
|
-
import { homedir as
|
|
7134
|
-
import { dirname as
|
|
7887
|
+
import { homedir as homedir16 } from "node:os";
|
|
7888
|
+
import { dirname as dirname8, join as join11 } from "node:path";
|
|
7135
7889
|
|
|
7136
7890
|
// src/sync/stage.ts
|
|
7137
|
-
import { createHash as
|
|
7891
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
7138
7892
|
|
|
7139
7893
|
// src/usage/days.ts
|
|
7140
7894
|
var round6 = (n) => Math.round(n * 1e6) / 1e6;
|
|
@@ -7227,7 +7981,13 @@ function mergeUsageDays(perHarness) {
|
|
|
7227
7981
|
function buildMeasuredDays(input) {
|
|
7228
7982
|
const workflowByDate = /* @__PURE__ */ new Map();
|
|
7229
7983
|
for (const day of input.workflow ?? []) workflowByDate.set(day.date, day);
|
|
7230
|
-
const dates = [
|
|
7984
|
+
const dates = [
|
|
7985
|
+
.../* @__PURE__ */ new Set([
|
|
7986
|
+
...input.usage.keys(),
|
|
7987
|
+
...workflowByDate.keys(),
|
|
7988
|
+
...input.includeDates ?? []
|
|
7989
|
+
])
|
|
7990
|
+
].filter(
|
|
7231
7991
|
(d) => /^\d{4}-\d{2}-\d{2}$/.test(d) && d >= input.from && d <= input.to
|
|
7232
7992
|
).sort();
|
|
7233
7993
|
return dates.map((date) => {
|
|
@@ -7284,7 +8044,7 @@ function selectDaysToPublish(input) {
|
|
|
7284
8044
|
|
|
7285
8045
|
// src/workflow/git.ts
|
|
7286
8046
|
import { execFile, execFileSync as execFileSync2 } from "node:child_process";
|
|
7287
|
-
import
|
|
8047
|
+
import path7 from "node:path";
|
|
7288
8048
|
var TEST_FILE_RULE_VERSION = "test-files/v2";
|
|
7289
8049
|
var FILE_TYPE_RULE_VERSION = "file-types/v2";
|
|
7290
8050
|
var COMMIT_SET_RULE_VERSION = "commit-set/v1";
|
|
@@ -7567,9 +8327,9 @@ function reduceGitHistories(histories, options) {
|
|
|
7567
8327
|
if (included) seenCommits.add(hash);
|
|
7568
8328
|
continue;
|
|
7569
8329
|
}
|
|
7570
|
-
const
|
|
7571
|
-
if (!
|
|
7572
|
-
let file =
|
|
8330
|
+
const stat7 = parseNumstat(field);
|
|
8331
|
+
if (!stat7) continue;
|
|
8332
|
+
let file = stat7.file;
|
|
7573
8333
|
if (file.length === 0) {
|
|
7574
8334
|
fieldIndex += 2;
|
|
7575
8335
|
file = fields[fieldIndex] ?? fields[fieldIndex - 1] ?? "";
|
|
@@ -7577,13 +8337,13 @@ function reduceGitHistories(histories, options) {
|
|
|
7577
8337
|
if (!current?.included) continue;
|
|
7578
8338
|
if (isUnauthoredPath(file)) continue;
|
|
7579
8339
|
current.authored = true;
|
|
7580
|
-
const fileChangedLines =
|
|
7581
|
-
current.additions +=
|
|
7582
|
-
current.removals +=
|
|
8340
|
+
const fileChangedLines = stat7.additions + stat7.removals;
|
|
8341
|
+
current.additions += stat7.additions;
|
|
8342
|
+
current.removals += stat7.removals;
|
|
7583
8343
|
current.changedLines += fileChangedLines;
|
|
7584
8344
|
if (isTestFile(file)) current.touchesTest = true;
|
|
7585
8345
|
if (fileChangedLines <= 0) continue;
|
|
7586
|
-
const extension =
|
|
8346
|
+
const extension = path7.extname(file).toLowerCase();
|
|
7587
8347
|
if (APPROVED_EXTENSIONS.has(extension)) {
|
|
7588
8348
|
current.extensionLines.set(
|
|
7589
8349
|
extension,
|
|
@@ -7686,6 +8446,47 @@ function buildWorkflowExtraction(harnessWorkflows, git, utcOffsetMinutes = machi
|
|
|
7686
8446
|
};
|
|
7687
8447
|
}
|
|
7688
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
|
+
|
|
7689
8490
|
// src/sync/summary.ts
|
|
7690
8491
|
function fmtTokens(n) {
|
|
7691
8492
|
const sig = (v) => {
|
|
@@ -8086,7 +8887,7 @@ function buildGateSummary(ctx) {
|
|
|
8086
8887
|
// src/sync/stage.ts
|
|
8087
8888
|
var utcDate2 = (ms) => new Date(ms).toISOString().slice(0, 10);
|
|
8088
8889
|
function stageId(bodyJson) {
|
|
8089
|
-
return
|
|
8890
|
+
return createHash3("sha256").update(bodyJson).digest("hex").slice(0, 12);
|
|
8090
8891
|
}
|
|
8091
8892
|
async function stageSync(deps) {
|
|
8092
8893
|
const now = (deps.now ?? Date.now)();
|
|
@@ -8138,6 +8939,9 @@ async function stageSync(deps) {
|
|
|
8138
8939
|
const sinceMs = windowStartMs(now, windowDays);
|
|
8139
8940
|
const daysSinceMs = windowStartMs(now, retentionDays);
|
|
8140
8941
|
const active2 = await adapters(sinceMs);
|
|
8942
|
+
const historical = await adapters(daysSinceMs);
|
|
8943
|
+
let dayScansComplete = true;
|
|
8944
|
+
let grokCurrentDates = null;
|
|
8141
8945
|
for (const adapter of active2) {
|
|
8142
8946
|
progress(`Scanning recent ${adapter.name} usage`);
|
|
8143
8947
|
const { aggregate, stats } = await adapter.scan({
|
|
@@ -8158,12 +8962,15 @@ async function stageSync(deps) {
|
|
|
8158
8962
|
})
|
|
8159
8963
|
);
|
|
8160
8964
|
}
|
|
8161
|
-
for (const adapter of
|
|
8965
|
+
for (const adapter of historical) {
|
|
8162
8966
|
progress(`Reading historical ${adapter.name} days`);
|
|
8163
|
-
const { aggregate, workflow: workflow2, workflowLocal } = await adapter.scan({
|
|
8967
|
+
const { aggregate, workflow: workflow2, workflowLocal, scanComplete, sessionDates } = await adapter.scan({
|
|
8164
8968
|
sinceMs: daysSinceMs,
|
|
8165
8969
|
onProgress: (files) => progress(`Reading historical ${adapter.name} days \xB7 ${files} files`)
|
|
8166
8970
|
});
|
|
8971
|
+
if (scanComplete === false) dayScansComplete = false;
|
|
8972
|
+
if (adapter.name === "grok-build")
|
|
8973
|
+
grokCurrentDates = sessionDates ?? /* @__PURE__ */ new Map();
|
|
8167
8974
|
workflowScans.push({ aggregate: workflow2, local: workflowLocal });
|
|
8168
8975
|
usageScans.push(
|
|
8169
8976
|
buildUsageDays({
|
|
@@ -8189,17 +8996,33 @@ async function stageSync(deps) {
|
|
|
8189
8996
|
toMs: now
|
|
8190
8997
|
});
|
|
8191
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
|
+
}
|
|
8192
9014
|
const localDays = applyDayConsent(
|
|
8193
9015
|
buildMeasuredDays({
|
|
8194
9016
|
usage: mergeUsageDays(usageScans),
|
|
8195
9017
|
...workflow ? { workflow: workflow.days } : {},
|
|
8196
9018
|
from: utcDate2(daysSinceMs),
|
|
8197
|
-
to: utcDate2(now)
|
|
9019
|
+
to: utcDate2(now),
|
|
9020
|
+
includeDates: correctionDates
|
|
8198
9021
|
}),
|
|
8199
9022
|
config
|
|
8200
9023
|
);
|
|
8201
9024
|
const days = selectDaysToPublish({
|
|
8202
|
-
local: localDays,
|
|
9025
|
+
local: dayScansComplete ? localDays : [],
|
|
8203
9026
|
manifest,
|
|
8204
9027
|
todayUtc: utcDate2(now)
|
|
8205
9028
|
});
|
|
@@ -8208,7 +9031,7 @@ async function stageSync(deps) {
|
|
|
8208
9031
|
config,
|
|
8209
9032
|
settings.autoSync,
|
|
8210
9033
|
deps.trigger,
|
|
8211
|
-
|
|
9034
|
+
historical.length > 0 && dayScansComplete ? {
|
|
8212
9035
|
aggregateVersion: MEASURED_DAYS_V1,
|
|
8213
9036
|
utcOffsetMinutes: workflow?.utcOffsetMinutes ?? machineUtcOffsetMinutes(),
|
|
8214
9037
|
days: days.send
|
|
@@ -8232,8 +9055,8 @@ async function stageSync(deps) {
|
|
|
8232
9055
|
width: process.stdout.columns
|
|
8233
9056
|
};
|
|
8234
9057
|
let blockedReason = null;
|
|
8235
|
-
if (
|
|
8236
|
-
blockedReason = `No
|
|
9058
|
+
if (historical.length === 0) {
|
|
9059
|
+
blockedReason = `No supported harness transcript from the last ${retentionDays} days to read.`;
|
|
8237
9060
|
} else if (token === null) {
|
|
8238
9061
|
blockedReason = "This machine is not linked. Run `npx @use-aistack/cli login` first.";
|
|
8239
9062
|
} else if (config.stack === null) {
|
|
@@ -8251,25 +9074,47 @@ async function stageSync(deps) {
|
|
|
8251
9074
|
stagedAt: now,
|
|
8252
9075
|
blockedReason,
|
|
8253
9076
|
days,
|
|
8254
|
-
prices
|
|
9077
|
+
prices,
|
|
9078
|
+
...acknowledgePublish ? { acknowledgePublish } : {}
|
|
8255
9079
|
};
|
|
8256
9080
|
}
|
|
8257
9081
|
|
|
8258
9082
|
// src/autosync/run.ts
|
|
8259
|
-
var SYNC_LOG_FILE =
|
|
9083
|
+
var SYNC_LOG_FILE = join11(homedir16(), ".config", "aistack", "sync.log");
|
|
8260
9084
|
var SYNC_LOG_MAX_LINES = 200;
|
|
8261
9085
|
var FIX_COMMAND = "npx @use-aistack/cli sync";
|
|
8262
9086
|
var REVOKED_RESULT = "off - auto-sync is switched off for this stack";
|
|
8263
9087
|
function appendLogLine(file, line) {
|
|
8264
|
-
|
|
9088
|
+
mkdirSync7(dirname8(file), { recursive: true });
|
|
8265
9089
|
appendFileSync(file, `${line}
|
|
8266
9090
|
`);
|
|
8267
|
-
const lines2 =
|
|
9091
|
+
const lines2 = readFileSync13(file, "utf-8").split("\n").filter(Boolean);
|
|
8268
9092
|
if (lines2.length > SYNC_LOG_MAX_LINES) {
|
|
8269
|
-
|
|
9093
|
+
writeFileSync7(file, `${lines2.slice(-SYNC_LOG_MAX_LINES).join("\n")}
|
|
8270
9094
|
`);
|
|
8271
9095
|
}
|
|
8272
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
|
+
}
|
|
8273
9118
|
async function runAutoSync(deps) {
|
|
8274
9119
|
const now = (deps.now ?? Date.now)();
|
|
8275
9120
|
const settingsFile = deps.settingsFile;
|
|
@@ -8284,9 +9129,13 @@ async function runAutoSync(deps) {
|
|
|
8284
9129
|
return;
|
|
8285
9130
|
}
|
|
8286
9131
|
const frequencyHours = normalizeFrequencyHours(config.frequencyHours);
|
|
9132
|
+
const windowMs = frequencyHours * 36e5;
|
|
8287
9133
|
const state = settings.autoSyncState ?? {};
|
|
8288
9134
|
const lastRunAt = state.lastRunAt ?? 0;
|
|
8289
|
-
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);
|
|
8290
9139
|
const stage = deps.stageImpl ?? stageSync;
|
|
8291
9140
|
const publish = deps.publishImpl ?? syncPublish;
|
|
8292
9141
|
const loadConfig = deps.loadConfigImpl ?? loadSyncConfig;
|
|
@@ -8377,7 +9226,7 @@ async function runAutoSync(deps) {
|
|
|
8377
9226
|
logFile,
|
|
8378
9227
|
`${stamp} fail (${consecutiveFailures} in a row) - ${failure2}`
|
|
8379
9228
|
);
|
|
8380
|
-
if (shouldWarn) {
|
|
9229
|
+
if (shouldWarn && deps.suppressOutput !== true && process.env.AISTACK_HOOK_SOURCE !== "grok") {
|
|
8381
9230
|
emit(
|
|
8382
9231
|
JSON.stringify({
|
|
8383
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.`
|
|
@@ -8522,6 +9371,7 @@ async function syncCommand(options = {}) {
|
|
|
8522
9371
|
s.start("Publishing");
|
|
8523
9372
|
try {
|
|
8524
9373
|
const res = await syncPublish(staged.token, staged.bodyJson);
|
|
9374
|
+
staged.acknowledgePublish?.();
|
|
8525
9375
|
s.stop("Published");
|
|
8526
9376
|
const lines2 = [
|
|
8527
9377
|
`Snapshot received ${fmtReceivedAt(res.receivedAt)}`,
|
|
@@ -8752,6 +9602,7 @@ function createSyncServer(deps, send) {
|
|
|
8752
9602
|
log7(`consent received, sending stage ${approvedStage.id}`);
|
|
8753
9603
|
publish(approvedStage.token, approvedStage.bodyJson).then(
|
|
8754
9604
|
(res) => {
|
|
9605
|
+
approvedStage.acknowledgePublish?.();
|
|
8755
9606
|
if (staged?.id === approvedStage.id) staged = null;
|
|
8756
9607
|
const lines2 = [
|
|
8757
9608
|
`Published. Snapshot received ${fmtReceivedAt(res.receivedAt)}.`,
|