runwork 0.24.1 → 0.25.1
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/bundled-types/core-routes.d.ts +25 -0
- package/dist/index.js +1278 -248
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1380,8 +1380,154 @@ var init_identity = __esm(() => {
|
|
|
1380
1380
|
init_subprocess();
|
|
1381
1381
|
});
|
|
1382
1382
|
|
|
1383
|
+
// src/utils/ignore-matcher.ts
|
|
1384
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
|
|
1385
|
+
import { basename, join as join4 } from "path";
|
|
1386
|
+
function defaultIgnoreSets() {
|
|
1387
|
+
return {
|
|
1388
|
+
dirs: new Set(DEFAULT_DIR_NAMES),
|
|
1389
|
+
files: new Set(DEFAULT_FILE_NAMES),
|
|
1390
|
+
extensions: new Set(DEFAULT_EXTENSIONS)
|
|
1391
|
+
};
|
|
1392
|
+
}
|
|
1393
|
+
function parseGitignoreContent(content) {
|
|
1394
|
+
const dirs = new Set;
|
|
1395
|
+
const files = new Set;
|
|
1396
|
+
for (const rawLine of content.split(`
|
|
1397
|
+
`)) {
|
|
1398
|
+
const line = rawLine.trim();
|
|
1399
|
+
if (!line || line.startsWith("#"))
|
|
1400
|
+
continue;
|
|
1401
|
+
if (line.startsWith("!"))
|
|
1402
|
+
continue;
|
|
1403
|
+
if (GLOB_CHARS.test(line))
|
|
1404
|
+
continue;
|
|
1405
|
+
let pattern = line;
|
|
1406
|
+
const isDirOnly = pattern.endsWith("/");
|
|
1407
|
+
if (isDirOnly)
|
|
1408
|
+
pattern = pattern.slice(0, -1);
|
|
1409
|
+
if (pattern.startsWith("/"))
|
|
1410
|
+
pattern = pattern.slice(1);
|
|
1411
|
+
if (!pattern)
|
|
1412
|
+
continue;
|
|
1413
|
+
if (pattern.includes("/"))
|
|
1414
|
+
continue;
|
|
1415
|
+
if (isDirOnly) {
|
|
1416
|
+
dirs.add(pattern);
|
|
1417
|
+
} else {
|
|
1418
|
+
dirs.add(pattern);
|
|
1419
|
+
files.add(pattern);
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
return { dirs, files };
|
|
1423
|
+
}
|
|
1424
|
+
function loadGitignoreFromDir(dir) {
|
|
1425
|
+
const path = join4(dir, ".gitignore");
|
|
1426
|
+
if (!existsSync4(path))
|
|
1427
|
+
return { dirs: new Set, files: new Set };
|
|
1428
|
+
try {
|
|
1429
|
+
return parseGitignoreContent(readFileSync4(path, "utf-8"));
|
|
1430
|
+
} catch {
|
|
1431
|
+
return { dirs: new Set, files: new Set };
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
function buildIgnoreSets(dir) {
|
|
1435
|
+
const sets = defaultIgnoreSets();
|
|
1436
|
+
const parsed = loadGitignoreFromDir(dir);
|
|
1437
|
+
for (const name of parsed.dirs)
|
|
1438
|
+
sets.dirs.add(name);
|
|
1439
|
+
for (const name of parsed.files)
|
|
1440
|
+
sets.files.add(name);
|
|
1441
|
+
return sets;
|
|
1442
|
+
}
|
|
1443
|
+
function isPathIgnored(filePath, sets) {
|
|
1444
|
+
const name = basename(filePath);
|
|
1445
|
+
if (sets.dirs.has(name))
|
|
1446
|
+
return true;
|
|
1447
|
+
if (sets.files.has(name))
|
|
1448
|
+
return true;
|
|
1449
|
+
const dotIndex = name.lastIndexOf(".");
|
|
1450
|
+
if (dotIndex >= 0) {
|
|
1451
|
+
const ext = name.slice(dotIndex);
|
|
1452
|
+
if (sets.extensions.has(ext))
|
|
1453
|
+
return true;
|
|
1454
|
+
}
|
|
1455
|
+
return false;
|
|
1456
|
+
}
|
|
1457
|
+
function isRelPathIgnored(relPath, sets) {
|
|
1458
|
+
const segments = relPath.split("/");
|
|
1459
|
+
for (let i = 0;i < segments.length - 1; i++) {
|
|
1460
|
+
if (sets.dirs.has(segments[i]))
|
|
1461
|
+
return true;
|
|
1462
|
+
}
|
|
1463
|
+
return isPathIgnored(relPath, sets);
|
|
1464
|
+
}
|
|
1465
|
+
var ALWAYS_IGNORED_DIRS, DEFAULT_DIR_NAMES, DEFAULT_FILE_NAMES, DEFAULT_EXTENSIONS, GLOB_CHARS;
|
|
1466
|
+
var init_ignore_matcher = __esm(() => {
|
|
1467
|
+
ALWAYS_IGNORED_DIRS = [
|
|
1468
|
+
".git",
|
|
1469
|
+
"node_modules",
|
|
1470
|
+
".runwork",
|
|
1471
|
+
".bun-cache",
|
|
1472
|
+
".npm-cache",
|
|
1473
|
+
".pnpm-store",
|
|
1474
|
+
".turbo",
|
|
1475
|
+
".vite",
|
|
1476
|
+
".cache",
|
|
1477
|
+
"coverage",
|
|
1478
|
+
"dist"
|
|
1479
|
+
];
|
|
1480
|
+
DEFAULT_DIR_NAMES = ALWAYS_IGNORED_DIRS;
|
|
1481
|
+
DEFAULT_FILE_NAMES = [
|
|
1482
|
+
".dev.vars",
|
|
1483
|
+
".env"
|
|
1484
|
+
];
|
|
1485
|
+
DEFAULT_EXTENSIONS = [
|
|
1486
|
+
".log"
|
|
1487
|
+
];
|
|
1488
|
+
GLOB_CHARS = /[*?\[\]]/;
|
|
1489
|
+
});
|
|
1490
|
+
|
|
1491
|
+
// src/git/repo-config.ts
|
|
1492
|
+
function hardenRepoForRestrictedFs(cwd) {
|
|
1493
|
+
for (const [key, value] of HARDENING) {
|
|
1494
|
+
try {
|
|
1495
|
+
execFileSync("git", ["config", key, value], { cwd, stdio: "pipe" });
|
|
1496
|
+
} catch {}
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
function buildInitialGitignore() {
|
|
1500
|
+
const dirs = ALWAYS_IGNORED_DIRS.filter((dir) => dir !== ".git").map((dir) => `${dir}/`);
|
|
1501
|
+
return [
|
|
1502
|
+
"# Dependencies, caches and build output",
|
|
1503
|
+
...dirs,
|
|
1504
|
+
"",
|
|
1505
|
+
"# Local secrets. Never commit these.",
|
|
1506
|
+
".dev.vars",
|
|
1507
|
+
".dev.vars*",
|
|
1508
|
+
".env",
|
|
1509
|
+
".env.*",
|
|
1510
|
+
"",
|
|
1511
|
+
"# Logs and tool state",
|
|
1512
|
+
"*.log",
|
|
1513
|
+
"*.tsbuildinfo",
|
|
1514
|
+
".eslintcache",
|
|
1515
|
+
""
|
|
1516
|
+
].join(`
|
|
1517
|
+
`);
|
|
1518
|
+
}
|
|
1519
|
+
var HARDENING;
|
|
1520
|
+
var init_repo_config = __esm(() => {
|
|
1521
|
+
init_subprocess();
|
|
1522
|
+
init_ignore_matcher();
|
|
1523
|
+
HARDENING = [
|
|
1524
|
+
["gc.auto", "0"],
|
|
1525
|
+
["maintenance.auto", "false"]
|
|
1526
|
+
];
|
|
1527
|
+
});
|
|
1528
|
+
|
|
1383
1529
|
// src/git/preflight.ts
|
|
1384
|
-
import { existsSync as
|
|
1530
|
+
import { existsSync as existsSync5 } from "fs";
|
|
1385
1531
|
import { win32 as winPath } from "path";
|
|
1386
1532
|
import { homedir as homedir3 } from "os";
|
|
1387
1533
|
function tryRun(bin) {
|
|
@@ -1403,9 +1549,9 @@ function whereGit() {
|
|
|
1403
1549
|
const out = buf.toString("utf-8");
|
|
1404
1550
|
const lines = out.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
1405
1551
|
const exe = lines.find((line) => /\.exe$/i.test(line));
|
|
1406
|
-
if (exe &&
|
|
1552
|
+
if (exe && existsSync5(exe))
|
|
1407
1553
|
return exe;
|
|
1408
|
-
const fallback = lines.find((line) =>
|
|
1554
|
+
const fallback = lines.find((line) => existsSync5(line));
|
|
1409
1555
|
return fallback ?? null;
|
|
1410
1556
|
} catch {
|
|
1411
1557
|
return null;
|
|
@@ -1430,7 +1576,7 @@ function registryGit() {
|
|
|
1430
1576
|
continue;
|
|
1431
1577
|
const installRoot = match[1].trim();
|
|
1432
1578
|
const gitExe = winPath.join(installRoot, "cmd", "git.exe");
|
|
1433
|
-
if (
|
|
1579
|
+
if (existsSync5(gitExe))
|
|
1434
1580
|
return gitExe;
|
|
1435
1581
|
} catch {}
|
|
1436
1582
|
}
|
|
@@ -1498,7 +1644,7 @@ function probeGit() {
|
|
|
1498
1644
|
}
|
|
1499
1645
|
}
|
|
1500
1646
|
for (const candidate of canonicalGitCandidates()) {
|
|
1501
|
-
if (!
|
|
1647
|
+
if (!existsSync5(candidate))
|
|
1502
1648
|
continue;
|
|
1503
1649
|
const verify = tryRun(candidate);
|
|
1504
1650
|
if (verify.ok) {
|
|
@@ -1636,106 +1782,6 @@ async function resolveApp(client, nameOrId, workspaceId) {
|
|
|
1636
1782
|
process.exit(1);
|
|
1637
1783
|
}
|
|
1638
1784
|
|
|
1639
|
-
// src/utils/ignore-matcher.ts
|
|
1640
|
-
import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
|
|
1641
|
-
import { basename, join as join4 } from "path";
|
|
1642
|
-
function defaultIgnoreSets() {
|
|
1643
|
-
return {
|
|
1644
|
-
dirs: new Set(DEFAULT_DIR_NAMES),
|
|
1645
|
-
files: new Set(DEFAULT_FILE_NAMES),
|
|
1646
|
-
extensions: new Set(DEFAULT_EXTENSIONS)
|
|
1647
|
-
};
|
|
1648
|
-
}
|
|
1649
|
-
function parseGitignoreContent(content) {
|
|
1650
|
-
const dirs = new Set;
|
|
1651
|
-
const files = new Set;
|
|
1652
|
-
for (const rawLine of content.split(`
|
|
1653
|
-
`)) {
|
|
1654
|
-
const line = rawLine.trim();
|
|
1655
|
-
if (!line || line.startsWith("#"))
|
|
1656
|
-
continue;
|
|
1657
|
-
if (line.startsWith("!"))
|
|
1658
|
-
continue;
|
|
1659
|
-
if (GLOB_CHARS.test(line))
|
|
1660
|
-
continue;
|
|
1661
|
-
let pattern = line;
|
|
1662
|
-
const isDirOnly = pattern.endsWith("/");
|
|
1663
|
-
if (isDirOnly)
|
|
1664
|
-
pattern = pattern.slice(0, -1);
|
|
1665
|
-
if (pattern.startsWith("/"))
|
|
1666
|
-
pattern = pattern.slice(1);
|
|
1667
|
-
if (!pattern)
|
|
1668
|
-
continue;
|
|
1669
|
-
if (pattern.includes("/"))
|
|
1670
|
-
continue;
|
|
1671
|
-
if (isDirOnly) {
|
|
1672
|
-
dirs.add(pattern);
|
|
1673
|
-
} else {
|
|
1674
|
-
dirs.add(pattern);
|
|
1675
|
-
files.add(pattern);
|
|
1676
|
-
}
|
|
1677
|
-
}
|
|
1678
|
-
return { dirs, files };
|
|
1679
|
-
}
|
|
1680
|
-
function loadGitignoreFromDir(dir) {
|
|
1681
|
-
const path = join4(dir, ".gitignore");
|
|
1682
|
-
if (!existsSync5(path))
|
|
1683
|
-
return { dirs: new Set, files: new Set };
|
|
1684
|
-
try {
|
|
1685
|
-
return parseGitignoreContent(readFileSync4(path, "utf-8"));
|
|
1686
|
-
} catch {
|
|
1687
|
-
return { dirs: new Set, files: new Set };
|
|
1688
|
-
}
|
|
1689
|
-
}
|
|
1690
|
-
function buildIgnoreSets(dir) {
|
|
1691
|
-
const sets = defaultIgnoreSets();
|
|
1692
|
-
const parsed = loadGitignoreFromDir(dir);
|
|
1693
|
-
for (const name of parsed.dirs)
|
|
1694
|
-
sets.dirs.add(name);
|
|
1695
|
-
for (const name of parsed.files)
|
|
1696
|
-
sets.files.add(name);
|
|
1697
|
-
return sets;
|
|
1698
|
-
}
|
|
1699
|
-
function isPathIgnored(filePath, sets) {
|
|
1700
|
-
const name = basename(filePath);
|
|
1701
|
-
if (sets.dirs.has(name))
|
|
1702
|
-
return true;
|
|
1703
|
-
if (sets.files.has(name))
|
|
1704
|
-
return true;
|
|
1705
|
-
const dotIndex = name.lastIndexOf(".");
|
|
1706
|
-
if (dotIndex >= 0) {
|
|
1707
|
-
const ext = name.slice(dotIndex);
|
|
1708
|
-
if (sets.extensions.has(ext))
|
|
1709
|
-
return true;
|
|
1710
|
-
}
|
|
1711
|
-
return false;
|
|
1712
|
-
}
|
|
1713
|
-
function isRelPathIgnored(relPath, sets) {
|
|
1714
|
-
const segments = relPath.split("/");
|
|
1715
|
-
for (let i = 0;i < segments.length - 1; i++) {
|
|
1716
|
-
if (sets.dirs.has(segments[i]))
|
|
1717
|
-
return true;
|
|
1718
|
-
}
|
|
1719
|
-
return isPathIgnored(relPath, sets);
|
|
1720
|
-
}
|
|
1721
|
-
var DEFAULT_DIR_NAMES, DEFAULT_FILE_NAMES, DEFAULT_EXTENSIONS, GLOB_CHARS;
|
|
1722
|
-
var init_ignore_matcher = __esm(() => {
|
|
1723
|
-
DEFAULT_DIR_NAMES = [
|
|
1724
|
-
".git",
|
|
1725
|
-
"node_modules",
|
|
1726
|
-
".runwork",
|
|
1727
|
-
".bun-cache"
|
|
1728
|
-
];
|
|
1729
|
-
DEFAULT_FILE_NAMES = [
|
|
1730
|
-
".dev.vars",
|
|
1731
|
-
".env"
|
|
1732
|
-
];
|
|
1733
|
-
DEFAULT_EXTENSIONS = [
|
|
1734
|
-
".log"
|
|
1735
|
-
];
|
|
1736
|
-
GLOB_CHARS = /[*?\[\]]/;
|
|
1737
|
-
});
|
|
1738
|
-
|
|
1739
1785
|
// src/template/manifest.ts
|
|
1740
1786
|
import { createHash } from "crypto";
|
|
1741
1787
|
import { readFileSync as readFileSync5, writeFileSync as writeFileSync2, existsSync as existsSync6, readdirSync, mkdirSync as mkdirSync2 } from "fs";
|
|
@@ -1782,17 +1828,39 @@ async function saveManifest(dir, manifest) {
|
|
|
1782
1828
|
}
|
|
1783
1829
|
writeFileSync2(join5(manifestDir, "template-manifest.json"), JSON.stringify(manifest, null, 2));
|
|
1784
1830
|
}
|
|
1831
|
+
function loadManifestSync(dir) {
|
|
1832
|
+
const manifestPath = join5(dir, ".runwork", "template-manifest.json");
|
|
1833
|
+
if (!existsSync6(manifestPath))
|
|
1834
|
+
return null;
|
|
1835
|
+
try {
|
|
1836
|
+
return normalizeManifest(JSON.parse(readFileSync5(manifestPath, "utf-8")));
|
|
1837
|
+
} catch {
|
|
1838
|
+
return null;
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
function isPristineTemplateFile(dir, relPath, manifest) {
|
|
1842
|
+
const expectedHash = manifest?.files[relPath];
|
|
1843
|
+
if (!expectedHash)
|
|
1844
|
+
return false;
|
|
1845
|
+
try {
|
|
1846
|
+
return sha256(readFileSync5(join5(dir, relPath))) === expectedHash;
|
|
1847
|
+
} catch {
|
|
1848
|
+
return false;
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
function normalizeManifest(manifest) {
|
|
1852
|
+
const files = {};
|
|
1853
|
+
for (const [relPath, hash] of Object.entries(manifest.files)) {
|
|
1854
|
+
files[relPath.split("\\").join("/")] = hash;
|
|
1855
|
+
}
|
|
1856
|
+
return { ...manifest, files };
|
|
1857
|
+
}
|
|
1785
1858
|
async function loadManifest(dir) {
|
|
1786
1859
|
const manifestPath = join5(dir, ".runwork", "template-manifest.json");
|
|
1787
1860
|
if (!existsSync6(manifestPath))
|
|
1788
1861
|
return null;
|
|
1789
1862
|
try {
|
|
1790
|
-
|
|
1791
|
-
const files = {};
|
|
1792
|
-
for (const [relPath, hash] of Object.entries(manifest.files)) {
|
|
1793
|
-
files[relPath.split("\\").join("/")] = hash;
|
|
1794
|
-
}
|
|
1795
|
-
return { ...manifest, files };
|
|
1863
|
+
return normalizeManifest(JSON.parse(readFileSync5(manifestPath, "utf-8")));
|
|
1796
1864
|
} catch {
|
|
1797
1865
|
return null;
|
|
1798
1866
|
}
|
|
@@ -2169,7 +2237,7 @@ var init_agent_guidance = __esm(() => {
|
|
|
2169
2237
|
"worker/components.ts": "Reusable UI components other workspace apps can embed.",
|
|
2170
2238
|
"shared/types.ts": "TypeScript interfaces shared between frontend and backend.",
|
|
2171
2239
|
"src/pages/": "Frontend React pages. Each file becomes a route.",
|
|
2172
|
-
"blueprint.json": "App feature registry. Update after adding entities, workflows, agents, etc.",
|
|
2240
|
+
"blueprint.json": "App feature registry. Update after adding entities, workflows, agents, etc., including each one's valueProfile (what manual work it replaced).",
|
|
2173
2241
|
"CLAUDE.md": "Complete framework documentation. Read this before editing anything."
|
|
2174
2242
|
};
|
|
2175
2243
|
COMMON_TIPS = [
|
|
@@ -2180,6 +2248,7 @@ var init_agent_guidance = __esm(() => {
|
|
|
2180
2248
|
"Every workflow MUST have a trigger (API endpoint, scheduled job, or UI button). Workflows without triggers are dead code.",
|
|
2181
2249
|
"Every conversational agent MUST have a frontend page to access it.",
|
|
2182
2250
|
"After adding entities, workflows, or agents, update blueprint.json with the new metadata.",
|
|
2251
|
+
"When you add a feature that takes over manual work, ASK your human what they used to do by hand, how often THEY did it, and how long one round took, then record it as a valueProfile on that blueprint element. Set confirmedWithHuman only if they actually agreed to the numbers. If they cannot answer, omit valueProfile rather than guessing. See the valueProfile section in CLAUDE.md.",
|
|
2183
2252
|
"Never guess integration IDs. Run: runwork integrations search <query>"
|
|
2184
2253
|
];
|
|
2185
2254
|
});
|
|
@@ -2250,10 +2319,7 @@ async function execInit(client, appName, workspace, options = {}, creds) {
|
|
|
2250
2319
|
execFileSync("git", ["remote", "set-url", "runwork", remoteUrl], { cwd: dir, stdio: "pipe" });
|
|
2251
2320
|
}
|
|
2252
2321
|
if (!existsSync7(join8(dir, ".gitignore"))) {
|
|
2253
|
-
writeFileSync4(join8(dir, ".gitignore"),
|
|
2254
|
-
.runwork/
|
|
2255
|
-
.dev.vars
|
|
2256
|
-
`);
|
|
2322
|
+
writeFileSync4(join8(dir, ".gitignore"), buildInitialGitignore());
|
|
2257
2323
|
}
|
|
2258
2324
|
execFileSync("git", ["add", "--", ".runwork.json", ".gitignore"], { cwd: dir, stdio: "pipe" });
|
|
2259
2325
|
try {
|
|
@@ -2334,6 +2400,7 @@ var init_init = __esm(() => {
|
|
|
2334
2400
|
init_store();
|
|
2335
2401
|
init_client();
|
|
2336
2402
|
init_identity();
|
|
2403
|
+
init_repo_config();
|
|
2337
2404
|
init_preflight();
|
|
2338
2405
|
init_prompt();
|
|
2339
2406
|
init_manifest();
|
|
@@ -2395,23 +2462,6 @@ var init_remote = __esm(() => {
|
|
|
2395
2462
|
init_subprocess();
|
|
2396
2463
|
});
|
|
2397
2464
|
|
|
2398
|
-
// src/git/repo-config.ts
|
|
2399
|
-
function hardenRepoForRestrictedFs(cwd) {
|
|
2400
|
-
for (const [key, value] of HARDENING) {
|
|
2401
|
-
try {
|
|
2402
|
-
execFileSync("git", ["config", key, value], { cwd, stdio: "pipe" });
|
|
2403
|
-
} catch {}
|
|
2404
|
-
}
|
|
2405
|
-
}
|
|
2406
|
-
var HARDENING;
|
|
2407
|
-
var init_repo_config = __esm(() => {
|
|
2408
|
-
init_subprocess();
|
|
2409
|
-
HARDENING = [
|
|
2410
|
-
["gc.auto", "0"],
|
|
2411
|
-
["maintenance.auto", "false"]
|
|
2412
|
-
];
|
|
2413
|
-
});
|
|
2414
|
-
|
|
2415
2465
|
// src/git/classify-sync-error.ts
|
|
2416
2466
|
function classifySyncError(raw) {
|
|
2417
2467
|
if (!raw)
|
|
@@ -2419,6 +2469,9 @@ function classifySyncError(raw) {
|
|
|
2419
2469
|
const s = raw.toLowerCase();
|
|
2420
2470
|
if (raw === STASH_CONFLICT)
|
|
2421
2471
|
return "conflict";
|
|
2472
|
+
if (s.includes("[remote rejected]") || s.includes("[rejected]") || s.includes("pre-receive hook declined") || s.includes("push declined")) {
|
|
2473
|
+
return "rejected";
|
|
2474
|
+
}
|
|
2422
2475
|
if (s.includes("does not appear to be a git repository") || s.includes("'runwork' does not appear") || s.includes("no such remote") || s.includes("remote") && s.includes("not found")) {
|
|
2423
2476
|
return "no-remote";
|
|
2424
2477
|
}
|
|
@@ -2466,6 +2519,19 @@ function diagnoseSyncError(raw) {
|
|
|
2466
2519
|
"Confirm outbound access to runwork.ai, then run `runwork dev` again"
|
|
2467
2520
|
]
|
|
2468
2521
|
};
|
|
2522
|
+
case "rejected":
|
|
2523
|
+
return {
|
|
2524
|
+
reason,
|
|
2525
|
+
message: "the server refused the push",
|
|
2526
|
+
diagnosis: `The runwork remote rejected this push, so nothing was updated on the server. It said:
|
|
2527
|
+
${(raw ?? "").trim()}`,
|
|
2528
|
+
suggestions: [
|
|
2529
|
+
'Read the "remote:" lines above: they name the exact reason and the fix',
|
|
2530
|
+
"If an oversized file was rejected, remove it from the commit and add it to .gitignore",
|
|
2531
|
+
"If the history is reported as damaged, contact support to rebuild it",
|
|
2532
|
+
"Retrying without changing anything will fail the same way"
|
|
2533
|
+
]
|
|
2534
|
+
};
|
|
2469
2535
|
case "conflict":
|
|
2470
2536
|
return {
|
|
2471
2537
|
reason,
|
|
@@ -2941,26 +3007,61 @@ function hasTrackedChanges(cwd) {
|
|
|
2941
3007
|
}
|
|
2942
3008
|
}
|
|
2943
3009
|
function removeConflictingUntrackedFiles(cwd) {
|
|
3010
|
+
const kept = [];
|
|
2944
3011
|
try {
|
|
2945
3012
|
const remoteFiles = execFileSync("git", ["ls-tree", "-r", "--name-only", "runwork/main"], {
|
|
2946
3013
|
cwd,
|
|
2947
3014
|
encoding: "utf-8"
|
|
2948
3015
|
}).trim().split(`
|
|
2949
3016
|
`);
|
|
2950
|
-
const untrackedOutput = execFileSync("git", ["ls-files", "--others", "--exclude-standard"], {
|
|
3017
|
+
const untrackedOutput = execFileSync("git", ["-c", "core.quotePath=false", "ls-files", "--others", "--exclude-standard"], {
|
|
2951
3018
|
cwd,
|
|
2952
3019
|
encoding: "utf-8"
|
|
2953
3020
|
}).trim();
|
|
2954
3021
|
const untracked = new Set(untrackedOutput.split(`
|
|
2955
3022
|
`).filter(Boolean));
|
|
3023
|
+
const manifest = loadManifestSync(cwd);
|
|
2956
3024
|
for (const file of remoteFiles) {
|
|
2957
|
-
if (untracked.has(file))
|
|
3025
|
+
if (!untracked.has(file))
|
|
3026
|
+
continue;
|
|
3027
|
+
if (matchesRemote(cwd, file) || isPristineTemplateFile(cwd, file, manifest)) {
|
|
2958
3028
|
try {
|
|
2959
3029
|
unlinkSync2(join11(cwd, file));
|
|
2960
3030
|
} catch {}
|
|
3031
|
+
} else {
|
|
3032
|
+
kept.push(file);
|
|
2961
3033
|
}
|
|
2962
3034
|
}
|
|
2963
3035
|
} catch {}
|
|
3036
|
+
return { kept };
|
|
3037
|
+
}
|
|
3038
|
+
function matchesRemote(cwd, file) {
|
|
3039
|
+
try {
|
|
3040
|
+
const remoteOid = execFileSync("git", ["rev-parse", `runwork/main:${file}`], {
|
|
3041
|
+
cwd,
|
|
3042
|
+
encoding: "utf-8",
|
|
3043
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
3044
|
+
}).trim();
|
|
3045
|
+
const localOid = execFileSync("git", ["hash-object", "--", file], {
|
|
3046
|
+
cwd,
|
|
3047
|
+
encoding: "utf-8",
|
|
3048
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
3049
|
+
}).trim();
|
|
3050
|
+
return Boolean(remoteOid) && remoteOid === localOid;
|
|
3051
|
+
} catch {
|
|
3052
|
+
return false;
|
|
3053
|
+
}
|
|
3054
|
+
}
|
|
3055
|
+
function conflictedPaths(cwd) {
|
|
3056
|
+
try {
|
|
3057
|
+
return execFileSync("git", ["-c", "core.quotePath=false", "diff", "--name-only", "--diff-filter=U"], {
|
|
3058
|
+
cwd,
|
|
3059
|
+
encoding: "utf-8"
|
|
3060
|
+
}).trim().split(`
|
|
3061
|
+
`).filter(Boolean);
|
|
3062
|
+
} catch {
|
|
3063
|
+
return [];
|
|
3064
|
+
}
|
|
2964
3065
|
}
|
|
2965
3066
|
function extractGitError(err) {
|
|
2966
3067
|
if (err && typeof err === "object") {
|
|
@@ -2995,9 +3096,11 @@ function syncWithRemote(cwd) {
|
|
|
2995
3096
|
}
|
|
2996
3097
|
let status = "synced";
|
|
2997
3098
|
let syncError;
|
|
3099
|
+
let keptUntracked = [];
|
|
3100
|
+
let remoteOverwrote = [];
|
|
2998
3101
|
try {
|
|
2999
3102
|
execFileSync("git", ["fetch", "runwork", "main"], { cwd, stdio: "pipe" });
|
|
3000
|
-
removeConflictingUntrackedFiles(cwd);
|
|
3103
|
+
keptUntracked = removeConflictingUntrackedFiles(cwd).kept;
|
|
3001
3104
|
try {
|
|
3002
3105
|
execFileSync("git", ["rebase", "runwork/main"], { cwd, stdio: "pipe" });
|
|
3003
3106
|
} catch (rebaseErr) {
|
|
@@ -3008,6 +3111,7 @@ function syncWithRemote(cwd) {
|
|
|
3008
3111
|
execFileSync("git", ["merge", "runwork/main", "--allow-unrelated-histories", "--no-edit"], { cwd, stdio: "pipe" });
|
|
3009
3112
|
status = "merged";
|
|
3010
3113
|
} catch {
|
|
3114
|
+
remoteOverwrote = conflictedPaths(cwd);
|
|
3011
3115
|
try {
|
|
3012
3116
|
execFileSync("git", ["merge", "--abort"], { cwd, stdio: "pipe" });
|
|
3013
3117
|
} catch {}
|
|
@@ -3018,6 +3122,7 @@ function syncWithRemote(cwd) {
|
|
|
3018
3122
|
try {
|
|
3019
3123
|
execFileSync("git", ["merge", "--abort"], { cwd, stdio: "pipe" });
|
|
3020
3124
|
} catch {}
|
|
3125
|
+
remoteOverwrote = [];
|
|
3021
3126
|
status = "sync-failed";
|
|
3022
3127
|
syncError = extractGitError(mergeErr);
|
|
3023
3128
|
}
|
|
@@ -3031,21 +3136,25 @@ function syncWithRemote(cwd) {
|
|
|
3031
3136
|
try {
|
|
3032
3137
|
execFileSync("git", ["stash", "pop"], { cwd, stdio: "pipe" });
|
|
3033
3138
|
} catch {
|
|
3034
|
-
return { status, pushed: false, error: "stash-conflict" };
|
|
3139
|
+
return { status, pushed: false, error: "stash-conflict", keptUntracked, remoteOverwrote };
|
|
3035
3140
|
}
|
|
3036
3141
|
}
|
|
3037
3142
|
if (status === "sync-failed") {
|
|
3038
|
-
return { status, pushed: false, error: syncError };
|
|
3143
|
+
return { status, pushed: false, error: syncError, keptUntracked, remoteOverwrote };
|
|
3039
3144
|
}
|
|
3040
3145
|
let pushed = false;
|
|
3146
|
+
let pushError;
|
|
3041
3147
|
try {
|
|
3042
3148
|
execFileSync("git", ["push", "runwork", "HEAD:main"], { cwd, stdio: "pipe" });
|
|
3043
3149
|
pushed = true;
|
|
3044
|
-
} catch {
|
|
3045
|
-
|
|
3150
|
+
} catch (pushErr) {
|
|
3151
|
+
pushError = extractGitError(pushErr);
|
|
3152
|
+
}
|
|
3153
|
+
return { status, pushed, pushError, keptUntracked, remoteOverwrote };
|
|
3046
3154
|
}
|
|
3047
3155
|
var init_sync = __esm(() => {
|
|
3048
3156
|
init_subprocess();
|
|
3157
|
+
init_manifest();
|
|
3049
3158
|
});
|
|
3050
3159
|
|
|
3051
3160
|
// src/git/critical-files.ts
|
|
@@ -4981,6 +5090,31 @@ export interface ClientErrorReport {
|
|
|
4981
5090
|
level?: 'error' | 'warning' | 'info';
|
|
4982
5091
|
category?: string;
|
|
4983
5092
|
}
|
|
5093
|
+
/**
|
|
5094
|
+
* Decide whether to send the permissive \`Permissions-Policy\` header for an app
|
|
5095
|
+
* that is being embedded in an iframe.
|
|
5096
|
+
*
|
|
5097
|
+
* A document's own Permissions-Policy can only restrict, never grant: the grant
|
|
5098
|
+
* comes from the embedding page's policy plus the iframe \`allow\` attribute. This
|
|
5099
|
+
* header's job is therefore to stop the app self-restricting the features the
|
|
5100
|
+
* platform preview delegates to it, and this predicate decides who is trusted
|
|
5101
|
+
* enough to be handed that.
|
|
5102
|
+
*
|
|
5103
|
+
* Hostnames are parsed and compared exactly, never substring-matched against the
|
|
5104
|
+
* raw header. The difference is not cosmetic: \`origin.endsWith('runwork.ai')\`
|
|
5105
|
+
* also matches \`https://notrunwork.ai\`, and \`origin.includes(domain)\` matches
|
|
5106
|
+
* \`https://evil-acme.com.attacker.net\` against a tenant's \`acme.com\`, which would
|
|
5107
|
+
* hand an attacker's page camera, microphone, geolocation and screen capture over
|
|
5108
|
+
* an embedded app.
|
|
5109
|
+
*
|
|
5110
|
+
* @param origin The \`Origin\` header, or the \`Referer\` as a fallback. Origin is a
|
|
5111
|
+
* bare origin and Referer is a full URL; both parse the same way.
|
|
5112
|
+
* Empty means direct (non-embedded) access.
|
|
5113
|
+
* @param allowedOrigins Comma-separated exact hostnames from \`ALLOWED_ORIGINS\`,
|
|
5114
|
+
* injected by the platform as the workspace's active custom
|
|
5115
|
+
* domains. Matched exactly; subdomains are deliberately excluded.
|
|
5116
|
+
*/
|
|
5117
|
+
export declare function shouldGrantIframePermissions(origin: string, allowedOrigins?: string): boolean;
|
|
4984
5118
|
/**
|
|
4985
5119
|
* Mount all core platform routes on the Hono app
|
|
4986
5120
|
* Called from index.ts before user-defined routes
|
|
@@ -7839,7 +7973,7 @@ function createKeyboardListener() {
|
|
|
7839
7973
|
}
|
|
7840
7974
|
|
|
7841
7975
|
// src/generated/version.ts
|
|
7842
|
-
var VERSION = "0.
|
|
7976
|
+
var VERSION = "0.25.1";
|
|
7843
7977
|
|
|
7844
7978
|
// src/commands/dev.ts
|
|
7845
7979
|
var exports_dev = {};
|
|
@@ -8067,6 +8201,36 @@ async function execDev(options) {
|
|
|
8067
8201
|
}
|
|
8068
8202
|
}
|
|
8069
8203
|
}
|
|
8204
|
+
if (syncResult.remoteOverwrote && syncResult.remoteOverwrote.length > 0) {
|
|
8205
|
+
if (useJson) {
|
|
8206
|
+
jsonLine({ event: "sync_remote_overwrote", files: syncResult.remoteOverwrote, timestamp: ts() });
|
|
8207
|
+
} else {
|
|
8208
|
+
console.warn(yellow(` Sync resolved conflicts in the remote's favour; local changes were replaced in: ${syncResult.remoteOverwrote.join(", ")}`));
|
|
8209
|
+
console.warn(dim(" Recover them with `git reflog` / `git diff ORIG_HEAD` if that was wrong."));
|
|
8210
|
+
}
|
|
8211
|
+
}
|
|
8212
|
+
if (syncResult.keptUntracked && syncResult.keptUntracked.length > 0) {
|
|
8213
|
+
if (useJson) {
|
|
8214
|
+
jsonLine({ event: "sync_kept_untracked", files: syncResult.keptUntracked, timestamp: ts() });
|
|
8215
|
+
} else {
|
|
8216
|
+
console.warn(yellow(` Kept your untracked file(s) over the remote's copy: ${syncResult.keptUntracked.join(", ")}`));
|
|
8217
|
+
console.warn(dim(" Commit or remove them so the sync can reconcile that path."));
|
|
8218
|
+
}
|
|
8219
|
+
}
|
|
8220
|
+
if (!syncResult.pushed && syncResult.pushError) {
|
|
8221
|
+
const diag = diagnoseSyncError(syncResult.pushError);
|
|
8222
|
+
if (useJson) {
|
|
8223
|
+
jsonLine({
|
|
8224
|
+
event: "error",
|
|
8225
|
+
phase: "push",
|
|
8226
|
+
timestamp: ts(),
|
|
8227
|
+
error: { reason: diag.reason, message: diag.message, diagnosis: diag.diagnosis, suggestions: diag.suggestions }
|
|
8228
|
+
});
|
|
8229
|
+
} else {
|
|
8230
|
+
console.warn(yellow(` Push failed: ${diag.message}`));
|
|
8231
|
+
console.warn(dim(` ${diag.diagnosis}`));
|
|
8232
|
+
}
|
|
8233
|
+
}
|
|
8070
8234
|
if (useJson) {
|
|
8071
8235
|
jsonLine({ event: "startup", phase: "sync", status: syncResult.status, pushed: syncResult.pushed, timestamp: ts() });
|
|
8072
8236
|
if (syncResult.status === "sync-failed") {
|
|
@@ -9747,6 +9911,187 @@ var init_registry = __esm(() => {
|
|
|
9747
9911
|
});
|
|
9748
9912
|
|
|
9749
9913
|
// src/agents/utils/session-digest.ts
|
|
9914
|
+
function emptyGapBuckets() {
|
|
9915
|
+
return { lt10s: 0, s10to30: 0, s30to2m: 0, m2to5: 0, m5to15: 0, gte15m: 0 };
|
|
9916
|
+
}
|
|
9917
|
+
function gapBucketKey(seconds) {
|
|
9918
|
+
if (seconds < 10)
|
|
9919
|
+
return "lt10s";
|
|
9920
|
+
if (seconds < 30)
|
|
9921
|
+
return "s10to30";
|
|
9922
|
+
if (seconds < 120)
|
|
9923
|
+
return "s30to2m";
|
|
9924
|
+
if (seconds < 300)
|
|
9925
|
+
return "m2to5";
|
|
9926
|
+
if (seconds < 900)
|
|
9927
|
+
return "m5to15";
|
|
9928
|
+
return "gte15m";
|
|
9929
|
+
}
|
|
9930
|
+
function buildGapHistogram(epochMs) {
|
|
9931
|
+
const ts = epochMs.filter((n) => Number.isFinite(n)).sort((a, b) => a - b);
|
|
9932
|
+
if (ts.length < 2)
|
|
9933
|
+
return null;
|
|
9934
|
+
const gapCounts = emptyGapBuckets();
|
|
9935
|
+
const gapSeconds = emptyGapBuckets();
|
|
9936
|
+
let longest = 0;
|
|
9937
|
+
for (let i = 1;i < ts.length; i++) {
|
|
9938
|
+
const gap = (ts[i] - ts[i - 1]) / 1000;
|
|
9939
|
+
const key = gapBucketKey(gap);
|
|
9940
|
+
gapCounts[key]++;
|
|
9941
|
+
gapSeconds[key] += gap;
|
|
9942
|
+
if (gap > longest)
|
|
9943
|
+
longest = gap;
|
|
9944
|
+
}
|
|
9945
|
+
for (const key of Object.keys(gapSeconds)) {
|
|
9946
|
+
gapSeconds[key] = Math.round(gapSeconds[key]);
|
|
9947
|
+
}
|
|
9948
|
+
return {
|
|
9949
|
+
gapCounts,
|
|
9950
|
+
gapSeconds,
|
|
9951
|
+
spanMinutes: Math.round((ts[ts.length - 1] - ts[0]) / 6000) / 10,
|
|
9952
|
+
longestGapSeconds: Math.round(longest)
|
|
9953
|
+
};
|
|
9954
|
+
}
|
|
9955
|
+
function emptySessionSignals() {
|
|
9956
|
+
return {
|
|
9957
|
+
provenance: null,
|
|
9958
|
+
gapCounts: null,
|
|
9959
|
+
gapSeconds: null,
|
|
9960
|
+
spanMinutes: null,
|
|
9961
|
+
longestGapSeconds: null,
|
|
9962
|
+
permissionModes: null,
|
|
9963
|
+
modeEscalations: null,
|
|
9964
|
+
approvalPolicy: null,
|
|
9965
|
+
sandboxPolicy: null,
|
|
9966
|
+
subagentCount: null,
|
|
9967
|
+
planModeUsed: null,
|
|
9968
|
+
slashCommandCount: null,
|
|
9969
|
+
modelInitiatedSkillCount: null,
|
|
9970
|
+
promptLengthBuckets: null,
|
|
9971
|
+
toolFailureStreakMax: null,
|
|
9972
|
+
repeatedCallMax: null,
|
|
9973
|
+
models: null,
|
|
9974
|
+
tokens: null,
|
|
9975
|
+
tokensByModel: null
|
|
9976
|
+
};
|
|
9977
|
+
}
|
|
9978
|
+
function applyGapHistogram(signals, epochMs) {
|
|
9979
|
+
const h = buildGapHistogram(epochMs);
|
|
9980
|
+
if (!h)
|
|
9981
|
+
return;
|
|
9982
|
+
signals.gapCounts = h.gapCounts;
|
|
9983
|
+
signals.gapSeconds = h.gapSeconds;
|
|
9984
|
+
signals.spanMinutes = h.spanMinutes;
|
|
9985
|
+
signals.longestGapSeconds = h.longestGapSeconds;
|
|
9986
|
+
}
|
|
9987
|
+
function promptLengthBucket(len) {
|
|
9988
|
+
if (len < 120)
|
|
9989
|
+
return "short";
|
|
9990
|
+
if (len <= 600)
|
|
9991
|
+
return "medium";
|
|
9992
|
+
return "long";
|
|
9993
|
+
}
|
|
9994
|
+
function classifyClaudeProvenance(obj) {
|
|
9995
|
+
const entrypoint = typeof obj.entrypoint === "string" ? obj.entrypoint : null;
|
|
9996
|
+
const promptSource = typeof obj.promptSource === "string" ? obj.promptSource : null;
|
|
9997
|
+
const originKind = obj.origin?.kind ?? null;
|
|
9998
|
+
if (entrypoint === "sdk-cli" || entrypoint === "sdk-ts")
|
|
9999
|
+
return "headless";
|
|
10000
|
+
if (promptSource === "sdk")
|
|
10001
|
+
return "headless";
|
|
10002
|
+
if (originKind !== null && originKind !== "human")
|
|
10003
|
+
return "agent-spawned";
|
|
10004
|
+
if (entrypoint === "cli" || entrypoint === "claude-desktop" || entrypoint === "local-agent")
|
|
10005
|
+
return "user-driven";
|
|
10006
|
+
if (originKind === "human" || promptSource === "typed")
|
|
10007
|
+
return "user-driven";
|
|
10008
|
+
return null;
|
|
10009
|
+
}
|
|
10010
|
+
function sumTokenTotals(byModel) {
|
|
10011
|
+
let tokensIn = 0;
|
|
10012
|
+
let tokensOut = 0;
|
|
10013
|
+
let cacheReadTokens = null;
|
|
10014
|
+
let cacheCreationTokens = null;
|
|
10015
|
+
for (const t of byModel.values()) {
|
|
10016
|
+
tokensIn += t.tokensIn;
|
|
10017
|
+
tokensOut += t.tokensOut;
|
|
10018
|
+
if (t.cacheReadTokens !== null)
|
|
10019
|
+
cacheReadTokens = (cacheReadTokens ?? 0) + t.cacheReadTokens;
|
|
10020
|
+
if (t.cacheCreationTokens !== null)
|
|
10021
|
+
cacheCreationTokens = (cacheCreationTokens ?? 0) + t.cacheCreationTokens;
|
|
10022
|
+
}
|
|
10023
|
+
return { tokensIn, tokensOut, cacheReadTokens, cacheCreationTokens };
|
|
10024
|
+
}
|
|
10025
|
+
function applyTokenMap(signals, byModel) {
|
|
10026
|
+
if (byModel.size === 0)
|
|
10027
|
+
return;
|
|
10028
|
+
signals.models = [...byModel.keys()];
|
|
10029
|
+
signals.tokens = sumTokenTotals(byModel);
|
|
10030
|
+
signals.tokensByModel = Object.fromEntries(byModel);
|
|
10031
|
+
}
|
|
10032
|
+
function stableStringify(value) {
|
|
10033
|
+
if (Array.isArray(value))
|
|
10034
|
+
return "[" + value.map(stableStringify).join(",") + "]";
|
|
10035
|
+
if (value && typeof value === "object") {
|
|
10036
|
+
return "{" + Object.keys(value).sort().map((k) => JSON.stringify(k) + ":" + stableStringify(value[k])).join(",") + "}";
|
|
10037
|
+
}
|
|
10038
|
+
return JSON.stringify(value) ?? "undefined";
|
|
10039
|
+
}
|
|
10040
|
+
|
|
10041
|
+
class FrictionCounters {
|
|
10042
|
+
failStreak = 0;
|
|
10043
|
+
failStreakMax = 0;
|
|
10044
|
+
callCounts = new Map;
|
|
10045
|
+
repeatedCallMax = 0;
|
|
10046
|
+
result(failed) {
|
|
10047
|
+
if (failed) {
|
|
10048
|
+
this.failStreak++;
|
|
10049
|
+
if (this.failStreak > this.failStreakMax)
|
|
10050
|
+
this.failStreakMax = this.failStreak;
|
|
10051
|
+
} else {
|
|
10052
|
+
this.failStreak = 0;
|
|
10053
|
+
}
|
|
10054
|
+
}
|
|
10055
|
+
call(name, args) {
|
|
10056
|
+
const key = name + "\x00" + stableStringify(args ?? null);
|
|
10057
|
+
const n = (this.callCounts.get(key) ?? 0) + 1;
|
|
10058
|
+
this.callCounts.set(key, n);
|
|
10059
|
+
if (n > this.repeatedCallMax)
|
|
10060
|
+
this.repeatedCallMax = n;
|
|
10061
|
+
}
|
|
10062
|
+
}
|
|
10063
|
+
function recordAssetAttempt(acc, rawName, epochMs, turnsBefore, toolCallsBefore) {
|
|
10064
|
+
const existing = acc.get(rawName);
|
|
10065
|
+
if (existing) {
|
|
10066
|
+
existing.attempts++;
|
|
10067
|
+
return;
|
|
10068
|
+
}
|
|
10069
|
+
acc.set(rawName, { firstEpochMs: epochMs, attempts: 1, turnsBefore, toolCallsBefore });
|
|
10070
|
+
}
|
|
10071
|
+
function buildAssetMarkers(acc, epochMs) {
|
|
10072
|
+
if (acc.size === 0)
|
|
10073
|
+
return;
|
|
10074
|
+
const sorted = epochMs.filter((n) => Number.isFinite(n)).sort((a, b) => a - b);
|
|
10075
|
+
const start = sorted[0];
|
|
10076
|
+
const markers = [];
|
|
10077
|
+
for (const [rawName, m] of acc) {
|
|
10078
|
+
const first = m.firstEpochMs;
|
|
10079
|
+
const histogram = first !== null ? buildGapHistogram(sorted.filter((ms) => ms <= first)) : null;
|
|
10080
|
+
markers.push({
|
|
10081
|
+
kind: "skill",
|
|
10082
|
+
rawName,
|
|
10083
|
+
attempts: m.attempts,
|
|
10084
|
+
turnsBefore: m.turnsBefore,
|
|
10085
|
+
toolCallsBefore: m.toolCallsBefore,
|
|
10086
|
+
wallSecondsToAsset: first !== null && sorted.length > 0 ? Math.round((first - start) / 1000) : null,
|
|
10087
|
+
gapCountsToAsset: histogram?.gapCounts ?? null,
|
|
10088
|
+
gapSecondsToAsset: histogram?.gapSeconds ?? null
|
|
10089
|
+
});
|
|
10090
|
+
if (markers.length >= 8)
|
|
10091
|
+
break;
|
|
10092
|
+
}
|
|
10093
|
+
return markers;
|
|
10094
|
+
}
|
|
9750
10095
|
function extractClaudeJsonlSession(raw, agentSlug, project) {
|
|
9751
10096
|
const digest = {
|
|
9752
10097
|
agentSlug,
|
|
@@ -9760,6 +10105,21 @@ function extractClaudeJsonlSession(raw, agentSlug, project) {
|
|
|
9760
10105
|
errorCount: 0,
|
|
9761
10106
|
assistantTurns: 0
|
|
9762
10107
|
};
|
|
10108
|
+
const signals = emptySessionSignals();
|
|
10109
|
+
const epochMs = [];
|
|
10110
|
+
const modeStream = [];
|
|
10111
|
+
let subagentCount = 0;
|
|
10112
|
+
let planToolSeen = false;
|
|
10113
|
+
let slashCommandCount = 0;
|
|
10114
|
+
let modelInitiatedSkillCount = 0;
|
|
10115
|
+
const promptBuckets = { short: 0, medium: 0, long: 0 };
|
|
10116
|
+
const tokensByModel = new Map;
|
|
10117
|
+
const seenUsagePairs = new Set;
|
|
10118
|
+
let provenanceClassified = false;
|
|
10119
|
+
let sidechainUserSeen = false;
|
|
10120
|
+
let toolCallsSeen = 0;
|
|
10121
|
+
const assetAcc = new Map;
|
|
10122
|
+
const friction = new FrictionCounters;
|
|
9763
10123
|
for (const line of raw.split(`
|
|
9764
10124
|
`)) {
|
|
9765
10125
|
if (!line.trim())
|
|
@@ -9775,12 +10135,29 @@ function extractClaudeJsonlSession(raw, agentSlug, project) {
|
|
|
9775
10135
|
if (!digest.start)
|
|
9776
10136
|
digest.start = ts;
|
|
9777
10137
|
digest.end = ts;
|
|
10138
|
+
const ms = Date.parse(ts);
|
|
10139
|
+
if (Number.isFinite(ms))
|
|
10140
|
+
epochMs.push(ms);
|
|
9778
10141
|
}
|
|
9779
10142
|
const type = obj.type;
|
|
10143
|
+
if (type === "permission-mode" && typeof obj.permissionMode === "string") {
|
|
10144
|
+
modeStream.push(obj.permissionMode);
|
|
10145
|
+
continue;
|
|
10146
|
+
}
|
|
9780
10147
|
const message = obj.message;
|
|
9781
10148
|
if (!message || typeof message !== "object")
|
|
9782
10149
|
continue;
|
|
9783
10150
|
if (type === "user" && message.role === "user") {
|
|
10151
|
+
if (obj.isSidechain === true)
|
|
10152
|
+
sidechainUserSeen = true;
|
|
10153
|
+
else if (!provenanceClassified) {
|
|
10154
|
+
provenanceClassified = true;
|
|
10155
|
+
signals.provenance = classifyClaudeProvenance(obj);
|
|
10156
|
+
}
|
|
10157
|
+
if (typeof obj.permissionMode === "string")
|
|
10158
|
+
modeStream.push(obj.permissionMode);
|
|
10159
|
+
const originKind = obj.origin?.kind;
|
|
10160
|
+
const humanLine = obj.isSidechain !== true && (originKind === undefined || originKind === "human");
|
|
9784
10161
|
const texts = [];
|
|
9785
10162
|
if (typeof message.content === "string") {
|
|
9786
10163
|
texts.push(message.content);
|
|
@@ -9790,15 +10167,22 @@ function extractClaudeJsonlSession(raw, agentSlug, project) {
|
|
|
9790
10167
|
const it = item;
|
|
9791
10168
|
if (it.type === "text" && typeof it.text === "string")
|
|
9792
10169
|
texts.push(it.text);
|
|
9793
|
-
if (it.type === "tool_result"
|
|
9794
|
-
|
|
10170
|
+
if (it.type === "tool_result") {
|
|
10171
|
+
if (it.is_error === true)
|
|
10172
|
+
digest.errorCount++;
|
|
10173
|
+
friction.result(it.is_error === true);
|
|
10174
|
+
}
|
|
9795
10175
|
}
|
|
9796
10176
|
}
|
|
9797
10177
|
}
|
|
9798
10178
|
for (const text2 of texts) {
|
|
9799
10179
|
const trimmed = text2.trim();
|
|
10180
|
+
if (trimmed.startsWith("<command-name>"))
|
|
10181
|
+
slashCommandCount++;
|
|
9800
10182
|
if (!trimmed || SKIP_PREFIXES.some((p) => trimmed.startsWith(p)))
|
|
9801
10183
|
continue;
|
|
10184
|
+
if (humanLine)
|
|
10185
|
+
promptBuckets[promptLengthBucket(trimmed.length)]++;
|
|
9802
10186
|
if (digest.userMessages.length >= MAX_MSGS_PER_SESSION) {
|
|
9803
10187
|
digest.droppedUserMessages++;
|
|
9804
10188
|
continue;
|
|
@@ -9806,15 +10190,47 @@ function extractClaudeJsonlSession(raw, agentSlug, project) {
|
|
|
9806
10190
|
digest.userMessages.push(trimmed.length > MAX_MSG_CHARS ? trimmed.slice(0, MAX_MSG_CHARS) + " [...]" : trimmed);
|
|
9807
10191
|
}
|
|
9808
10192
|
}
|
|
9809
|
-
if (type === "assistant" && message.role === "assistant"
|
|
10193
|
+
if (type === "assistant" && message.role === "assistant") {
|
|
10194
|
+
const am = message;
|
|
10195
|
+
const model = typeof am.model === "string" ? am.model : null;
|
|
10196
|
+
const usage = am.usage;
|
|
10197
|
+
if (usage && typeof usage === "object" && model && model !== "<synthetic>") {
|
|
10198
|
+
const requestId = typeof obj.requestId === "string" ? obj.requestId : null;
|
|
10199
|
+
const pairKey = typeof am.id === "string" && requestId ? `${am.id}:${requestId}` : null;
|
|
10200
|
+
if (!pairKey || !seenUsagePairs.has(pairKey)) {
|
|
10201
|
+
if (pairKey)
|
|
10202
|
+
seenUsagePairs.add(pairKey);
|
|
10203
|
+
const t = tokensByModel.get(model) ?? { tokensIn: 0, tokensOut: 0, cacheReadTokens: 0, cacheCreationTokens: 0 };
|
|
10204
|
+
t.tokensIn += finiteNum(usage.input_tokens);
|
|
10205
|
+
t.tokensOut += finiteNum(usage.output_tokens);
|
|
10206
|
+
t.cacheReadTokens = (t.cacheReadTokens ?? 0) + finiteNum(usage.cache_read_input_tokens);
|
|
10207
|
+
t.cacheCreationTokens = (t.cacheCreationTokens ?? 0) + finiteNum(usage.cache_creation_input_tokens);
|
|
10208
|
+
tokensByModel.set(model, t);
|
|
10209
|
+
}
|
|
10210
|
+
}
|
|
10211
|
+
if (!Array.isArray(message.content))
|
|
10212
|
+
continue;
|
|
9810
10213
|
digest.assistantTurns++;
|
|
9811
10214
|
for (const item of message.content) {
|
|
9812
10215
|
if (item && typeof item === "object") {
|
|
9813
10216
|
const it = item;
|
|
9814
10217
|
if (it.type === "tool_use" && typeof it.name === "string") {
|
|
9815
10218
|
digest.toolCounts[it.name] = (digest.toolCounts[it.name] ?? 0) + 1;
|
|
9816
|
-
|
|
9817
|
-
|
|
10219
|
+
friction.call(it.name, it.input);
|
|
10220
|
+
if (it.name === "Agent" || it.name === "Task")
|
|
10221
|
+
subagentCount++;
|
|
10222
|
+
if (it.name === "EnterPlanMode" || it.name === "ExitPlanMode")
|
|
10223
|
+
planToolSeen = true;
|
|
10224
|
+
if (it.name === "Skill") {
|
|
10225
|
+
modelInitiatedSkillCount++;
|
|
10226
|
+
if (it.input?.skill)
|
|
10227
|
+
digest.skillInvocations.push(it.input.skill);
|
|
10228
|
+
}
|
|
10229
|
+
if (it.name.endsWith("__save_skill") && typeof it.input?.name === "string" && it.input.name.trim()) {
|
|
10230
|
+
const ms = ts ? Date.parse(ts) : NaN;
|
|
10231
|
+
recordAssetAttempt(assetAcc, it.input.name.trim(), Number.isFinite(ms) ? ms : null, Math.max(0, digest.assistantTurns - 1), toolCallsSeen);
|
|
10232
|
+
}
|
|
10233
|
+
toolCallsSeen++;
|
|
9818
10234
|
}
|
|
9819
10235
|
}
|
|
9820
10236
|
}
|
|
@@ -9822,11 +10238,40 @@ function extractClaudeJsonlSession(raw, agentSlug, project) {
|
|
|
9822
10238
|
}
|
|
9823
10239
|
if (digest.userMessages.length === 0)
|
|
9824
10240
|
return null;
|
|
10241
|
+
if (!provenanceClassified && sidechainUserSeen)
|
|
10242
|
+
signals.provenance = "agent-spawned";
|
|
10243
|
+
applyGapHistogram(signals, epochMs);
|
|
10244
|
+
const collapsed = modeStream.filter((m, i) => i === 0 || m !== modeStream[i - 1]);
|
|
10245
|
+
signals.permissionModes = collapsed.length > 0 ? [...new Set(collapsed)] : null;
|
|
10246
|
+
signals.modeEscalations = collapsed.length > 0 ? collapsed.length - 1 : null;
|
|
10247
|
+
signals.subagentCount = subagentCount;
|
|
10248
|
+
signals.planModeUsed = planToolSeen || collapsed.includes("plan");
|
|
10249
|
+
signals.slashCommandCount = slashCommandCount;
|
|
10250
|
+
signals.modelInitiatedSkillCount = modelInitiatedSkillCount;
|
|
10251
|
+
signals.promptLengthBuckets = promptBuckets;
|
|
10252
|
+
signals.toolFailureStreakMax = friction.failStreakMax;
|
|
10253
|
+
signals.repeatedCallMax = friction.repeatedCallMax;
|
|
10254
|
+
applyTokenMap(signals, tokensByModel);
|
|
10255
|
+
digest.signals = signals;
|
|
10256
|
+
digest.assetMarkers = buildAssetMarkers(assetAcc, epochMs);
|
|
9825
10257
|
return digest;
|
|
9826
10258
|
}
|
|
9827
10259
|
function decodeProjectDir(encoded) {
|
|
9828
10260
|
return encoded.replace(/^-/, "/").replace(/-/g, "/");
|
|
9829
10261
|
}
|
|
10262
|
+
function classifyCodexProvenance(meta) {
|
|
10263
|
+
const source = meta.source;
|
|
10264
|
+
const originator = typeof meta.originator === "string" ? meta.originator : null;
|
|
10265
|
+
if (source && typeof source === "object" && "subagent" in source)
|
|
10266
|
+
return "agent-spawned";
|
|
10267
|
+
if (originator === "Claude Code")
|
|
10268
|
+
return "agent-spawned";
|
|
10269
|
+
if (source === "exec")
|
|
10270
|
+
return "headless";
|
|
10271
|
+
if (source === "cli" || source === "vscode")
|
|
10272
|
+
return "user-driven";
|
|
10273
|
+
return null;
|
|
10274
|
+
}
|
|
9830
10275
|
function geminiMessageText(content) {
|
|
9831
10276
|
if (typeof content === "string")
|
|
9832
10277
|
return content;
|
|
@@ -9862,6 +10307,9 @@ function extractGeminiSession(raw, agentSlug, project) {
|
|
|
9862
10307
|
digest.start = session.startTime;
|
|
9863
10308
|
if (typeof session.lastUpdated === "string")
|
|
9864
10309
|
digest.end = session.lastUpdated;
|
|
10310
|
+
const signals = emptySessionSignals();
|
|
10311
|
+
const epochMs = [];
|
|
10312
|
+
const tokensByModel = new Map;
|
|
9865
10313
|
for (const raw2 of session.messages) {
|
|
9866
10314
|
const m = raw2;
|
|
9867
10315
|
if (typeof m.timestamp === "string") {
|
|
@@ -9869,9 +10317,21 @@ function extractGeminiSession(raw, agentSlug, project) {
|
|
|
9869
10317
|
digest.start = m.timestamp;
|
|
9870
10318
|
if (!digest.end || m.timestamp > digest.end)
|
|
9871
10319
|
digest.end = m.timestamp;
|
|
10320
|
+
const ms = Date.parse(m.timestamp);
|
|
10321
|
+
if (Number.isFinite(ms))
|
|
10322
|
+
epochMs.push(ms);
|
|
9872
10323
|
}
|
|
9873
10324
|
if (m.type === "gemini") {
|
|
9874
10325
|
digest.assistantTurns++;
|
|
10326
|
+
const tok = m.tokens;
|
|
10327
|
+
if (tok && typeof tok === "object") {
|
|
10328
|
+
const model = typeof m.model === "string" ? m.model : "unknown";
|
|
10329
|
+
const t = tokensByModel.get(model) ?? { tokensIn: 0, tokensOut: 0, cacheReadTokens: 0, cacheCreationTokens: null };
|
|
10330
|
+
t.tokensIn += finiteNum(tok.input);
|
|
10331
|
+
t.tokensOut += finiteNum(tok.output) + finiteNum(tok.thoughts);
|
|
10332
|
+
t.cacheReadTokens = (t.cacheReadTokens ?? 0) + finiteNum(tok.cached);
|
|
10333
|
+
tokensByModel.set(model, t);
|
|
10334
|
+
}
|
|
9875
10335
|
continue;
|
|
9876
10336
|
}
|
|
9877
10337
|
if (m.type === "error") {
|
|
@@ -9889,7 +10349,12 @@ function extractGeminiSession(raw, agentSlug, project) {
|
|
|
9889
10349
|
}
|
|
9890
10350
|
digest.userMessages.push(text2.length > MAX_MSG_CHARS ? `${text2.slice(0, MAX_MSG_CHARS)}...` : text2);
|
|
9891
10351
|
}
|
|
9892
|
-
|
|
10352
|
+
if (digest.userMessages.length === 0)
|
|
10353
|
+
return null;
|
|
10354
|
+
applyGapHistogram(signals, epochMs);
|
|
10355
|
+
applyTokenMap(signals, tokensByModel);
|
|
10356
|
+
digest.signals = signals;
|
|
10357
|
+
return digest;
|
|
9893
10358
|
}
|
|
9894
10359
|
function extractCodexRolloutSession(raw, agentSlug) {
|
|
9895
10360
|
const digest = {
|
|
@@ -9904,6 +10369,14 @@ function extractCodexRolloutSession(raw, agentSlug) {
|
|
|
9904
10369
|
errorCount: 0,
|
|
9905
10370
|
assistantTurns: 0
|
|
9906
10371
|
};
|
|
10372
|
+
const signals = emptySessionSignals();
|
|
10373
|
+
const epochMs = [];
|
|
10374
|
+
const models = [];
|
|
10375
|
+
let lastTokenTotals = null;
|
|
10376
|
+
let toolCallsSeen = 0;
|
|
10377
|
+
const assetAcc = new Map;
|
|
10378
|
+
const friction = new FrictionCounters;
|
|
10379
|
+
let failureMarkerSeen = false;
|
|
9907
10380
|
for (const line of raw.split(`
|
|
9908
10381
|
`)) {
|
|
9909
10382
|
if (!line.trim())
|
|
@@ -9919,12 +10392,49 @@ function extractCodexRolloutSession(raw, agentSlug) {
|
|
|
9919
10392
|
if (!digest.start)
|
|
9920
10393
|
digest.start = ts;
|
|
9921
10394
|
digest.end = ts;
|
|
10395
|
+
const ms = Date.parse(ts);
|
|
10396
|
+
if (Number.isFinite(ms))
|
|
10397
|
+
epochMs.push(ms);
|
|
9922
10398
|
}
|
|
9923
10399
|
const p = o.payload;
|
|
9924
10400
|
if (!p || typeof p !== "object")
|
|
9925
10401
|
continue;
|
|
9926
|
-
if (o.type === "session_meta"
|
|
9927
|
-
|
|
10402
|
+
if (o.type === "session_meta") {
|
|
10403
|
+
if (typeof p.cwd === "string")
|
|
10404
|
+
digest.project = p.cwd;
|
|
10405
|
+
signals.provenance = classifyCodexProvenance(p);
|
|
10406
|
+
continue;
|
|
10407
|
+
}
|
|
10408
|
+
if (o.type === "turn_context") {
|
|
10409
|
+
const ap = p.approval_policy;
|
|
10410
|
+
if (typeof ap === "string")
|
|
10411
|
+
signals.approvalPolicy = ap;
|
|
10412
|
+
else if (ap && typeof ap === "object")
|
|
10413
|
+
signals.approvalPolicy = Object.keys(ap)[0] ?? signals.approvalPolicy;
|
|
10414
|
+
const sp = p.sandbox_policy;
|
|
10415
|
+
if (sp && typeof sp === "object" && typeof sp.type === "string")
|
|
10416
|
+
signals.sandboxPolicy = sp.type;
|
|
10417
|
+
if (typeof p.model === "string" && !models.includes(p.model))
|
|
10418
|
+
models.push(p.model);
|
|
10419
|
+
continue;
|
|
10420
|
+
}
|
|
10421
|
+
if (o.type === "event_msg") {
|
|
10422
|
+
if (p.type === "token_count") {
|
|
10423
|
+
const info = p.info;
|
|
10424
|
+
if (info && typeof info === "object" && info.total_token_usage && typeof info.total_token_usage === "object") {
|
|
10425
|
+
lastTokenTotals = info.total_token_usage;
|
|
10426
|
+
}
|
|
10427
|
+
} else if (p.type === "exec_command_end") {
|
|
10428
|
+
failureMarkerSeen = true;
|
|
10429
|
+
friction.result(typeof p.exit_code === "number" && p.exit_code !== 0);
|
|
10430
|
+
} else if (p.type === "mcp_tool_call_end") {
|
|
10431
|
+
const result = p.result;
|
|
10432
|
+
failureMarkerSeen = true;
|
|
10433
|
+
friction.result(Boolean(result && typeof result === "object" && "Err" in result));
|
|
10434
|
+
} else if (p.type === "patch_apply_end") {
|
|
10435
|
+
failureMarkerSeen = true;
|
|
10436
|
+
friction.result(p.success === false);
|
|
10437
|
+
}
|
|
9928
10438
|
continue;
|
|
9929
10439
|
}
|
|
9930
10440
|
if (o.type !== "response_item")
|
|
@@ -9950,6 +10460,17 @@ function extractCodexRolloutSession(raw, agentSlug) {
|
|
|
9950
10460
|
}
|
|
9951
10461
|
} else if (pt === "function_call" && typeof p.name === "string") {
|
|
9952
10462
|
digest.toolCounts[p.name] = (digest.toolCounts[p.name] ?? 0) + 1;
|
|
10463
|
+
friction.call(p.name, typeof p.arguments === "string" ? p.arguments : null);
|
|
10464
|
+
if (p.name === "save_skill" && typeof p.arguments === "string") {
|
|
10465
|
+
try {
|
|
10466
|
+
const args = JSON.parse(p.arguments);
|
|
10467
|
+
if (typeof args.name === "string" && args.name.trim()) {
|
|
10468
|
+
const ms = ts ? Date.parse(ts) : NaN;
|
|
10469
|
+
recordAssetAttempt(assetAcc, args.name.trim(), Number.isFinite(ms) ? ms : null, digest.assistantTurns, toolCallsSeen);
|
|
10470
|
+
}
|
|
10471
|
+
} catch {}
|
|
10472
|
+
}
|
|
10473
|
+
toolCallsSeen++;
|
|
9953
10474
|
} else if (pt === "tool_search_call") {
|
|
9954
10475
|
digest.toolCounts["tool_search"] = (digest.toolCounts["tool_search"] ?? 0) + 1;
|
|
9955
10476
|
}
|
|
@@ -9958,6 +10479,21 @@ function extractCodexRolloutSession(raw, agentSlug) {
|
|
|
9958
10479
|
return null;
|
|
9959
10480
|
if (!digest.project)
|
|
9960
10481
|
digest.project = "codex";
|
|
10482
|
+
applyGapHistogram(signals, epochMs);
|
|
10483
|
+
if (models.length > 0)
|
|
10484
|
+
signals.models = models;
|
|
10485
|
+
if (lastTokenTotals) {
|
|
10486
|
+
signals.tokens = {
|
|
10487
|
+
tokensIn: finiteNum(lastTokenTotals.input_tokens),
|
|
10488
|
+
tokensOut: finiteNum(lastTokenTotals.output_tokens),
|
|
10489
|
+
cacheReadTokens: finiteNum(lastTokenTotals.cached_input_tokens),
|
|
10490
|
+
cacheCreationTokens: null
|
|
10491
|
+
};
|
|
10492
|
+
}
|
|
10493
|
+
signals.toolFailureStreakMax = failureMarkerSeen ? friction.failStreakMax : null;
|
|
10494
|
+
signals.repeatedCallMax = friction.repeatedCallMax;
|
|
10495
|
+
digest.signals = signals;
|
|
10496
|
+
digest.assetMarkers = buildAssetMarkers(assetAcc, epochMs);
|
|
9961
10497
|
return digest;
|
|
9962
10498
|
}
|
|
9963
10499
|
function extractCursorSessions(composerRows, bubbleRows, sinceISO) {
|
|
@@ -10006,6 +10542,7 @@ function extractCursorSessions(composerRows, bubbleRows, sinceISO) {
|
|
|
10006
10542
|
errorCount: 0,
|
|
10007
10543
|
assistantTurns: 0
|
|
10008
10544
|
};
|
|
10545
|
+
const friction = new FrictionCounters;
|
|
10009
10546
|
for (const b of bubbles) {
|
|
10010
10547
|
if (b.ts) {
|
|
10011
10548
|
if (!digest.start)
|
|
@@ -10016,6 +10553,7 @@ function extractCursorSessions(composerRows, bubbleRows, sinceISO) {
|
|
|
10016
10553
|
digest.toolCounts[b.tool] = (digest.toolCounts[b.tool] ?? 0) + 1;
|
|
10017
10554
|
if (b.status === "error")
|
|
10018
10555
|
digest.errorCount++;
|
|
10556
|
+
friction.result(b.status === "error");
|
|
10019
10557
|
} else if (b.type === 2) {
|
|
10020
10558
|
digest.assistantTurns++;
|
|
10021
10559
|
}
|
|
@@ -10042,6 +10580,9 @@ function extractCursorSessions(composerRows, bubbleRows, sinceISO) {
|
|
|
10042
10580
|
continue;
|
|
10043
10581
|
if (sinceISO && digest.end && digest.end < sinceISO)
|
|
10044
10582
|
continue;
|
|
10583
|
+
const signals = emptySessionSignals();
|
|
10584
|
+
signals.toolFailureStreakMax = friction.failStreakMax;
|
|
10585
|
+
digest.signals = signals;
|
|
10045
10586
|
digests.push(digest);
|
|
10046
10587
|
}
|
|
10047
10588
|
return digests;
|
|
@@ -10085,7 +10626,7 @@ function formatCombinedDigest(sessions, opts = { days: 7 }) {
|
|
|
10085
10626
|
return lines.join(`
|
|
10086
10627
|
`);
|
|
10087
10628
|
}
|
|
10088
|
-
var MAX_MSG_CHARS = 700, MAX_MSGS_PER_SESSION = 40, SKIP_PREFIXES, CODEX_SKIP_PREFIXES;
|
|
10629
|
+
var MAX_MSG_CHARS = 700, MAX_MSGS_PER_SESSION = 40, finiteNum = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0, SKIP_PREFIXES, CODEX_SKIP_PREFIXES;
|
|
10089
10630
|
var init_session_digest = __esm(() => {
|
|
10090
10631
|
SKIP_PREFIXES = [
|
|
10091
10632
|
"<system-reminder",
|
|
@@ -10424,7 +10965,7 @@ import { createHash as createHash2 } from "crypto";
|
|
|
10424
10965
|
function contentHash(content) {
|
|
10425
10966
|
return "sha256:" + createHash2("sha256").update(content).digest("hex");
|
|
10426
10967
|
}
|
|
10427
|
-
function
|
|
10968
|
+
function stableStringify2(value) {
|
|
10428
10969
|
return JSON.stringify(sortValue(value));
|
|
10429
10970
|
}
|
|
10430
10971
|
function sortValue(value) {
|
|
@@ -10441,7 +10982,7 @@ function sortValue(value) {
|
|
|
10441
10982
|
return value;
|
|
10442
10983
|
}
|
|
10443
10984
|
function configHash(value) {
|
|
10444
|
-
return contentHash(
|
|
10985
|
+
return contentHash(stableStringify2(value));
|
|
10445
10986
|
}
|
|
10446
10987
|
var init_hash = () => {};
|
|
10447
10988
|
|
|
@@ -15855,10 +16396,104 @@ var init_conversation_registry = __esm(async () => {
|
|
|
15855
16396
|
|
|
15856
16397
|
// src/reflect/session-summary.ts
|
|
15857
16398
|
import { createHash as createHash4 } from "crypto";
|
|
16399
|
+
import { execFileSync as execFileSync3 } from "child_process";
|
|
15858
16400
|
import { readFileSync as readFileSync35 } from "fs";
|
|
16401
|
+
import { isAbsolute as isAbsolute4 } from "path";
|
|
16402
|
+
function validateAssetMarkers(markers, knownSkills) {
|
|
16403
|
+
if (!markers || markers.length === 0)
|
|
16404
|
+
return null;
|
|
16405
|
+
let known = null;
|
|
16406
|
+
if (knownSkills) {
|
|
16407
|
+
known = new Map;
|
|
16408
|
+
for (const skill of knownSkills) {
|
|
16409
|
+
const name = canonicalSkillName(skill.name);
|
|
16410
|
+
if (name)
|
|
16411
|
+
known.set(name, skill.id);
|
|
16412
|
+
}
|
|
16413
|
+
}
|
|
16414
|
+
return markers.map((m) => {
|
|
16415
|
+
const canonical = canonicalSkillName(m.rawName);
|
|
16416
|
+
const validated = Boolean(known && canonical && known.has(canonical));
|
|
16417
|
+
return {
|
|
16418
|
+
assetKind: m.kind,
|
|
16419
|
+
assetKey: validated ? canonical : null,
|
|
16420
|
+
assetKeyId: validated ? known.get(canonical) ?? null : null,
|
|
16421
|
+
saveAttempts: m.attempts,
|
|
16422
|
+
turnsToAsset: m.turnsBefore,
|
|
16423
|
+
toolCallsToAsset: m.toolCallsBefore,
|
|
16424
|
+
wallSecondsToAsset: m.wallSecondsToAsset,
|
|
16425
|
+
gapCountsToAsset: m.gapCountsToAsset,
|
|
16426
|
+
gapSecondsToAsset: m.gapSecondsToAsset
|
|
16427
|
+
};
|
|
16428
|
+
});
|
|
16429
|
+
}
|
|
16430
|
+
function clampVendorString(value) {
|
|
16431
|
+
if (value === null)
|
|
16432
|
+
return null;
|
|
16433
|
+
return value.length > VENDOR_STRING_MAX_CHARS ? value.slice(0, VENDOR_STRING_MAX_CHARS) : value;
|
|
16434
|
+
}
|
|
16435
|
+
function clampVendorList(values) {
|
|
16436
|
+
if (values === null)
|
|
16437
|
+
return null;
|
|
16438
|
+
return values.slice(0, VENDOR_LIST_MAX_ITEMS).map((v) => clampVendorString(v));
|
|
16439
|
+
}
|
|
16440
|
+
function clampTokensByModel(map) {
|
|
16441
|
+
if (map === null)
|
|
16442
|
+
return null;
|
|
16443
|
+
const entries = Object.entries(map).slice(0, VENDOR_LIST_MAX_ITEMS).map(([model, totals]) => [clampVendorString(model), totals]);
|
|
16444
|
+
return Object.fromEntries(entries);
|
|
16445
|
+
}
|
|
15859
16446
|
function hashSessionKey(key) {
|
|
15860
16447
|
return createHash4("sha256").update(key).digest("hex").slice(0, 32);
|
|
15861
16448
|
}
|
|
16449
|
+
function normalizeGitRemote(url) {
|
|
16450
|
+
let out = url.trim().toLowerCase();
|
|
16451
|
+
if (!out)
|
|
16452
|
+
return null;
|
|
16453
|
+
const hadScheme = /^[a-z+]+:\/\//.test(out);
|
|
16454
|
+
out = out.replace(/^[a-z+]+:\/\//, "");
|
|
16455
|
+
out = out.replace(/^[^@/]+@/, "");
|
|
16456
|
+
if (hadScheme)
|
|
16457
|
+
out = out.replace(/^([^/:]+):(\d+)(?=\/)/, "$1");
|
|
16458
|
+
out = out.replace(":", "/");
|
|
16459
|
+
out = out.replace(/\/+$/, "");
|
|
16460
|
+
out = out.replace(/\.git$/, "");
|
|
16461
|
+
out = out.replace(/\/+$/, "");
|
|
16462
|
+
return out || null;
|
|
16463
|
+
}
|
|
16464
|
+
function normalizeProjectName(project) {
|
|
16465
|
+
if (!project)
|
|
16466
|
+
return null;
|
|
16467
|
+
const trimmed = project.trim().replace(/[/\\]+$/, "");
|
|
16468
|
+
if (!trimmed)
|
|
16469
|
+
return null;
|
|
16470
|
+
const idx = Math.max(trimmed.lastIndexOf("/"), trimmed.lastIndexOf("\\"));
|
|
16471
|
+
const base = (idx >= 0 ? trimmed.slice(idx + 1) : trimmed).toLowerCase();
|
|
16472
|
+
if (!base)
|
|
16473
|
+
return null;
|
|
16474
|
+
if (idx < 0 && PROJECT_PLACEHOLDERS.has(base))
|
|
16475
|
+
return null;
|
|
16476
|
+
return base;
|
|
16477
|
+
}
|
|
16478
|
+
function resolveProjectHash(project) {
|
|
16479
|
+
const normalized = normalizeProjectName(project);
|
|
16480
|
+
return normalized ? hashSessionKey(normalized) : null;
|
|
16481
|
+
}
|
|
16482
|
+
function resolveRepoHash(projectDir) {
|
|
16483
|
+
if (!projectDir || !isAbsolute4(projectDir))
|
|
16484
|
+
return null;
|
|
16485
|
+
let url;
|
|
16486
|
+
try {
|
|
16487
|
+
url = execFileSync3("git", ["-C", projectDir, "config", "--get", "remote.origin.url"], {
|
|
16488
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
16489
|
+
timeout: 3000
|
|
16490
|
+
}).toString().trim();
|
|
16491
|
+
} catch {
|
|
16492
|
+
return null;
|
|
16493
|
+
}
|
|
16494
|
+
const normalized = url ? normalizeGitRemote(url) : null;
|
|
16495
|
+
return normalized ? hashSessionKey(normalized) : null;
|
|
16496
|
+
}
|
|
15862
16497
|
function digestForEntry(entry) {
|
|
15863
16498
|
if (!entry.transcriptPath)
|
|
15864
16499
|
return null;
|
|
@@ -15892,9 +16527,10 @@ function deriveMcpUsage(toolCounts) {
|
|
|
15892
16527
|
}
|
|
15893
16528
|
return { mcpCallCount, mcpServers };
|
|
15894
16529
|
}
|
|
15895
|
-
function buildSessionSummary(key, entry, digest) {
|
|
16530
|
+
function buildSessionSummary(key, entry, digest, opts = {}) {
|
|
15896
16531
|
const toolCallCount = digest ? Object.values(digest.toolCounts).reduce((sum, n) => sum + n, 0) : null;
|
|
15897
16532
|
const userMessageCount = digest ? digest.userMessages.length + digest.droppedUserMessages : null;
|
|
16533
|
+
const sig = digest?.signals;
|
|
15898
16534
|
return {
|
|
15899
16535
|
agentSlug: entry.agentSlug,
|
|
15900
16536
|
sessionIdHash: hashSessionKey(key),
|
|
@@ -15907,7 +16543,32 @@ function buildSessionSummary(key, entry, digest) {
|
|
|
15907
16543
|
toolCounts: digest ? digest.toolCounts : null,
|
|
15908
16544
|
...deriveMcpUsage(digest ? digest.toolCounts : null),
|
|
15909
16545
|
errorCount: digest?.errorCount ?? null,
|
|
15910
|
-
skillsUsed: [...new Set((digest?.skillInvocations ?? []).map(canonicalSkillName).filter(Boolean))]
|
|
16546
|
+
skillsUsed: clampVendorList([...new Set((digest?.skillInvocations ?? []).map(canonicalSkillName).filter(Boolean))]) ?? [],
|
|
16547
|
+
provenance: sig?.provenance ?? null,
|
|
16548
|
+
gapCounts: sig?.gapCounts ?? null,
|
|
16549
|
+
gapSeconds: sig?.gapSeconds ?? null,
|
|
16550
|
+
spanMinutes: sig?.spanMinutes ?? null,
|
|
16551
|
+
longestGapSeconds: sig?.longestGapSeconds ?? null,
|
|
16552
|
+
permissionModes: clampVendorList(sig?.permissionModes ?? null),
|
|
16553
|
+
modeEscalations: sig?.modeEscalations ?? null,
|
|
16554
|
+
approvalPolicy: clampVendorString(sig?.approvalPolicy ?? null),
|
|
16555
|
+
sandboxPolicy: clampVendorString(sig?.sandboxPolicy ?? null),
|
|
16556
|
+
subagentCount: sig?.subagentCount ?? null,
|
|
16557
|
+
planModeUsed: sig?.planModeUsed ?? null,
|
|
16558
|
+
slashCommandCount: sig?.slashCommandCount ?? null,
|
|
16559
|
+
modelInitiatedSkillCount: sig?.modelInitiatedSkillCount ?? null,
|
|
16560
|
+
promptLengthBuckets: sig?.promptLengthBuckets ?? null,
|
|
16561
|
+
toolFailureStreakMax: sig?.toolFailureStreakMax ?? null,
|
|
16562
|
+
repeatedCallMax: sig?.repeatedCallMax ?? null,
|
|
16563
|
+
models: clampVendorList(sig?.models ?? null),
|
|
16564
|
+
tokensIn: sig?.tokens?.tokensIn ?? null,
|
|
16565
|
+
tokensOut: sig?.tokens?.tokensOut ?? null,
|
|
16566
|
+
cacheReadTokens: sig?.tokens?.cacheReadTokens ?? null,
|
|
16567
|
+
cacheCreationTokens: sig?.tokens?.cacheCreationTokens ?? null,
|
|
16568
|
+
tokensByModel: clampTokensByModel(sig?.tokensByModel ?? null),
|
|
16569
|
+
assetsCreated: validateAssetMarkers(digest?.assetMarkers, opts.knownSkills ?? null),
|
|
16570
|
+
repoHash: opts.repoHash ?? null,
|
|
16571
|
+
projectHash: resolveProjectHash(entry.project)
|
|
15911
16572
|
};
|
|
15912
16573
|
}
|
|
15913
16574
|
function buildSessionSummaryEvent(summary, nowISO) {
|
|
@@ -15928,14 +16589,41 @@ function buildSessionSummaryEvent(summary, nowISO) {
|
|
|
15928
16589
|
mcpCallCount: summary.mcpCallCount,
|
|
15929
16590
|
mcpServers: summary.mcpServers,
|
|
15930
16591
|
errorCount: summary.errorCount,
|
|
15931
|
-
skillsUsed: summary.skillsUsed
|
|
16592
|
+
skillsUsed: summary.skillsUsed,
|
|
16593
|
+
provenance: summary.provenance,
|
|
16594
|
+
gapCounts: summary.gapCounts,
|
|
16595
|
+
gapSeconds: summary.gapSeconds,
|
|
16596
|
+
spanMinutes: summary.spanMinutes,
|
|
16597
|
+
longestGapSeconds: summary.longestGapSeconds,
|
|
16598
|
+
permissionModes: summary.permissionModes,
|
|
16599
|
+
modeEscalations: summary.modeEscalations,
|
|
16600
|
+
approvalPolicy: summary.approvalPolicy,
|
|
16601
|
+
sandboxPolicy: summary.sandboxPolicy,
|
|
16602
|
+
subagentCount: summary.subagentCount,
|
|
16603
|
+
planModeUsed: summary.planModeUsed,
|
|
16604
|
+
slashCommandCount: summary.slashCommandCount,
|
|
16605
|
+
modelInitiatedSkillCount: summary.modelInitiatedSkillCount,
|
|
16606
|
+
promptLengthBuckets: summary.promptLengthBuckets,
|
|
16607
|
+
toolFailureStreakMax: summary.toolFailureStreakMax,
|
|
16608
|
+
repeatedCallMax: summary.repeatedCallMax,
|
|
16609
|
+
models: summary.models,
|
|
16610
|
+
tokensIn: summary.tokensIn,
|
|
16611
|
+
tokensOut: summary.tokensOut,
|
|
16612
|
+
cacheReadTokens: summary.cacheReadTokens,
|
|
16613
|
+
cacheCreationTokens: summary.cacheCreationTokens,
|
|
16614
|
+
tokensByModel: summary.tokensByModel,
|
|
16615
|
+
assetsCreated: summary.assetsCreated,
|
|
16616
|
+
repoHash: summary.repoHash,
|
|
16617
|
+
projectHash: summary.projectHash
|
|
15932
16618
|
},
|
|
15933
16619
|
timestamp: nowISO
|
|
15934
16620
|
}
|
|
15935
16621
|
};
|
|
15936
16622
|
}
|
|
16623
|
+
var VENDOR_STRING_MAX_CHARS = 64, VENDOR_LIST_MAX_ITEMS = 12, PROJECT_PLACEHOLDERS;
|
|
15937
16624
|
var init_session_summary = __esm(() => {
|
|
15938
16625
|
init_session_digest();
|
|
16626
|
+
PROJECT_PLACEHOLDERS = new Set(["codex", "cowork", "cursor"]);
|
|
15939
16627
|
});
|
|
15940
16628
|
|
|
15941
16629
|
// src/reflect/telemetry-outbox.ts
|
|
@@ -15977,6 +16665,222 @@ var init_telemetry_outbox = __esm(() => {
|
|
|
15977
16665
|
init_atomic_json();
|
|
15978
16666
|
});
|
|
15979
16667
|
|
|
16668
|
+
// src/reflect/active-time.ts
|
|
16669
|
+
function isValidActiveTimeCap(capSeconds) {
|
|
16670
|
+
return VALID_ACTIVE_TIME_CAPS_SECONDS.includes(capSeconds);
|
|
16671
|
+
}
|
|
16672
|
+
function activeSecondsFromBuckets(gapCounts, gapSeconds, capSeconds) {
|
|
16673
|
+
if (!gapCounts || !gapSeconds)
|
|
16674
|
+
return null;
|
|
16675
|
+
if (!isValidActiveTimeCap(capSeconds))
|
|
16676
|
+
return null;
|
|
16677
|
+
let total = 0;
|
|
16678
|
+
for (const { key, lower } of BUCKET_LOWER_EDGES) {
|
|
16679
|
+
if (lower >= capSeconds) {
|
|
16680
|
+
total += capSeconds * (gapCounts[key] ?? 0);
|
|
16681
|
+
} else {
|
|
16682
|
+
total += gapSeconds[key] ?? 0;
|
|
16683
|
+
}
|
|
16684
|
+
}
|
|
16685
|
+
return total;
|
|
16686
|
+
}
|
|
16687
|
+
var BUCKET_LOWER_EDGES, VALID_ACTIVE_TIME_CAPS_SECONDS;
|
|
16688
|
+
var init_active_time = __esm(() => {
|
|
16689
|
+
BUCKET_LOWER_EDGES = [
|
|
16690
|
+
{ key: "lt10s", lower: 0 },
|
|
16691
|
+
{ key: "s10to30", lower: 10 },
|
|
16692
|
+
{ key: "s30to2m", lower: 30 },
|
|
16693
|
+
{ key: "m2to5", lower: 120 },
|
|
16694
|
+
{ key: "m5to15", lower: 300 },
|
|
16695
|
+
{ key: "gte15m", lower: 900 }
|
|
16696
|
+
];
|
|
16697
|
+
VALID_ACTIVE_TIME_CAPS_SECONDS = [10, 30, 120, 300, 900];
|
|
16698
|
+
});
|
|
16699
|
+
|
|
16700
|
+
// src/reflect/pattern-store.ts
|
|
16701
|
+
import { createHash as createHash5 } from "crypto";
|
|
16702
|
+
import { join as join42 } from "path";
|
|
16703
|
+
import { homedir as homedir23 } from "os";
|
|
16704
|
+
function addBuckets(a, b) {
|
|
16705
|
+
if (!a)
|
|
16706
|
+
return b ? { ...b } : null;
|
|
16707
|
+
if (!b)
|
|
16708
|
+
return { ...a };
|
|
16709
|
+
return {
|
|
16710
|
+
lt10s: a.lt10s + b.lt10s,
|
|
16711
|
+
s10to30: a.s10to30 + b.s10to30,
|
|
16712
|
+
s30to2m: a.s30to2m + b.s30to2m,
|
|
16713
|
+
m2to5: a.m2to5 + b.m2to5,
|
|
16714
|
+
m5to15: a.m5to15 + b.m5to15,
|
|
16715
|
+
gte15m: a.gte15m + b.gte15m
|
|
16716
|
+
};
|
|
16717
|
+
}
|
|
16718
|
+
function canonicalizeEntities(entities) {
|
|
16719
|
+
const cleaned = entities.map((e) => e.trim().toLowerCase().replace(/\s+/g, " ")).filter((e) => e.length > 0);
|
|
16720
|
+
return [...new Set(cleaned)].sort().slice(0, MAX_KEY_ENTITIES);
|
|
16721
|
+
}
|
|
16722
|
+
function patternKeyHash(taskType, entities) {
|
|
16723
|
+
const task = taskType.trim().toLowerCase();
|
|
16724
|
+
const canonical = canonicalizeEntities(entities);
|
|
16725
|
+
if (!task || canonical.length === 0)
|
|
16726
|
+
return null;
|
|
16727
|
+
return createHash5("sha256").update(`${task}|${canonical.join(",")}`).digest("hex").slice(0, 32);
|
|
16728
|
+
}
|
|
16729
|
+
function median(sorted) {
|
|
16730
|
+
if (sorted.length === 0)
|
|
16731
|
+
return 0;
|
|
16732
|
+
const mid = Math.floor(sorted.length / 2);
|
|
16733
|
+
return sorted.length % 2 === 0 ? (sorted[mid - 1] + sorted[mid]) / 2 : sorted[mid];
|
|
16734
|
+
}
|
|
16735
|
+
function medianOf(values) {
|
|
16736
|
+
return median([...values].sort((a, b) => a - b));
|
|
16737
|
+
}
|
|
16738
|
+
function modal(values) {
|
|
16739
|
+
const counts = new Map;
|
|
16740
|
+
for (const v of values)
|
|
16741
|
+
counts.set(v, (counts.get(v) ?? 0) + 1);
|
|
16742
|
+
let best = null;
|
|
16743
|
+
let bestCount = 0;
|
|
16744
|
+
for (const [value, count] of counts) {
|
|
16745
|
+
if (count > bestCount) {
|
|
16746
|
+
best = value;
|
|
16747
|
+
bestCount = count;
|
|
16748
|
+
}
|
|
16749
|
+
}
|
|
16750
|
+
return best;
|
|
16751
|
+
}
|
|
16752
|
+
function emptyPatternStore() {
|
|
16753
|
+
return { version: PATTERN_STORE_VERSION, patterns: {} };
|
|
16754
|
+
}
|
|
16755
|
+
function recordObservation(state, observation) {
|
|
16756
|
+
const key = patternKeyHash(observation.taskType, observation.keyEntities);
|
|
16757
|
+
if (!key)
|
|
16758
|
+
return { state, key: null };
|
|
16759
|
+
const at = Date.parse(observation.at);
|
|
16760
|
+
if (!Number.isFinite(at))
|
|
16761
|
+
return { state, key: null };
|
|
16762
|
+
const activeSeconds = observation.gapCounts && observation.gapSeconds ? activeSecondsFromBuckets(observation.gapCounts, observation.gapSeconds, ACTIVE_MINUTES_CAP_SECONDS) : null;
|
|
16763
|
+
const existing = state.patterns[key];
|
|
16764
|
+
const occurrences = existing ? [...existing.occurrences] : [];
|
|
16765
|
+
const mergeIndex = occurrences.findIndex((o) => Math.abs(Date.parse(o.at) - at) <= SAME_OCCURRENCE_WINDOW_HOURS * 3600 * 1000);
|
|
16766
|
+
if (mergeIndex >= 0) {
|
|
16767
|
+
const prior = occurrences[mergeIndex];
|
|
16768
|
+
const priorAt = Date.parse(prior.at);
|
|
16769
|
+
occurrences[mergeIndex] = {
|
|
16770
|
+
at: priorAt <= at ? prior.at : observation.at,
|
|
16771
|
+
activeSeconds: prior.activeSeconds === null && activeSeconds === null ? null : (prior.activeSeconds ?? 0) + (activeSeconds ?? 0),
|
|
16772
|
+
gapCounts: addBuckets(prior.gapCounts, observation.gapCounts),
|
|
16773
|
+
gapSeconds: addBuckets(prior.gapSeconds, observation.gapSeconds),
|
|
16774
|
+
confidence: Math.min(prior.confidence, observation.confidence),
|
|
16775
|
+
sessionIdHashes: observation.sessionIdHash && !prior.sessionIdHashes.includes(observation.sessionIdHash) ? [...prior.sessionIdHashes, observation.sessionIdHash] : prior.sessionIdHashes,
|
|
16776
|
+
suggestedCapability: prior.suggestedCapability ?? observation.suggestedCapability ?? null,
|
|
16777
|
+
projectHash: prior.projectHash ?? observation.projectHash ?? null
|
|
16778
|
+
};
|
|
16779
|
+
} else {
|
|
16780
|
+
occurrences.push({
|
|
16781
|
+
at: observation.at,
|
|
16782
|
+
activeSeconds,
|
|
16783
|
+
gapCounts: observation.gapCounts ?? null,
|
|
16784
|
+
gapSeconds: observation.gapSeconds ?? null,
|
|
16785
|
+
confidence: observation.confidence,
|
|
16786
|
+
sessionIdHashes: observation.sessionIdHash ? [observation.sessionIdHash] : [],
|
|
16787
|
+
suggestedCapability: observation.suggestedCapability ?? null,
|
|
16788
|
+
projectHash: observation.projectHash ?? null
|
|
16789
|
+
});
|
|
16790
|
+
}
|
|
16791
|
+
occurrences.sort((a, b) => Date.parse(a.at) - Date.parse(b.at));
|
|
16792
|
+
const newest = Date.parse(occurrences[occurrences.length - 1].at);
|
|
16793
|
+
const kept = occurrences.filter((o) => newest - Date.parse(o.at) <= OCCURRENCE_RETENTION_DAYS * 24 * 3600 * 1000);
|
|
16794
|
+
return {
|
|
16795
|
+
state: {
|
|
16796
|
+
...state,
|
|
16797
|
+
patterns: {
|
|
16798
|
+
...state.patterns,
|
|
16799
|
+
[key]: {
|
|
16800
|
+
taskType: observation.taskType.trim().toLowerCase(),
|
|
16801
|
+
keyEntities: canonicalizeEntities(observation.keyEntities),
|
|
16802
|
+
occurrences: kept
|
|
16803
|
+
}
|
|
16804
|
+
}
|
|
16805
|
+
},
|
|
16806
|
+
key
|
|
16807
|
+
};
|
|
16808
|
+
}
|
|
16809
|
+
function evaluateMaturity(key, record) {
|
|
16810
|
+
const sorted = [...record.occurrences].sort((a, b) => Date.parse(a.at) - Date.parse(b.at));
|
|
16811
|
+
if (sorted.length < MATURITY_MIN_OCCURRENCES)
|
|
16812
|
+
return null;
|
|
16813
|
+
const newest = Date.parse(sorted[sorted.length - 1].at);
|
|
16814
|
+
const inWindow = sorted.filter((o) => newest - Date.parse(o.at) <= MATURITY_WINDOW_DAYS * 24 * 3600 * 1000);
|
|
16815
|
+
if (inWindow.length < MATURITY_MIN_OCCURRENCES)
|
|
16816
|
+
return null;
|
|
16817
|
+
const gapsDays = [];
|
|
16818
|
+
for (let i = 1;i < inWindow.length; i++) {
|
|
16819
|
+
gapsDays.push((Date.parse(inWindow[i].at) - Date.parse(inWindow[i - 1].at)) / (24 * 3600 * 1000));
|
|
16820
|
+
}
|
|
16821
|
+
const medianGap = medianOf(gapsDays);
|
|
16822
|
+
if (medianGap < MATURITY_MIN_PERIODICITY_DAYS)
|
|
16823
|
+
return null;
|
|
16824
|
+
const mad = medianOf(gapsDays.map((g) => Math.abs(g - medianGap)));
|
|
16825
|
+
if (mad > MATURITY_MAX_MAD_RATIO * medianGap)
|
|
16826
|
+
return null;
|
|
16827
|
+
const activeSecondsValues = inWindow.map((o) => o.activeSeconds).filter((s) => typeof s === "number");
|
|
16828
|
+
let activeGapCounts = null;
|
|
16829
|
+
let activeGapSeconds = null;
|
|
16830
|
+
for (const occurrence of inWindow) {
|
|
16831
|
+
activeGapCounts = addBuckets(activeGapCounts, occurrence.gapCounts);
|
|
16832
|
+
activeGapSeconds = addBuckets(activeGapSeconds, occurrence.gapSeconds);
|
|
16833
|
+
}
|
|
16834
|
+
const projectHashes = inWindow.map((o) => o.projectHash).filter((p) => typeof p === "string" && p.length > 0);
|
|
16835
|
+
const capabilities = inWindow.map((o) => o.suggestedCapability).filter((c) => typeof c === "string" && c.length > 0);
|
|
16836
|
+
return {
|
|
16837
|
+
patternKeyHash: key,
|
|
16838
|
+
taskType: record.taskType,
|
|
16839
|
+
occurrences: inWindow.length,
|
|
16840
|
+
periodicityDays: Math.round(medianGap * 10) / 10,
|
|
16841
|
+
medianActiveMinutes: activeSecondsValues.length > 0 ? Math.round(medianOf(activeSecondsValues) / 60 * 10) / 10 : null,
|
|
16842
|
+
activeMinutesCapSeconds: ACTIVE_MINUTES_CAP_SECONDS,
|
|
16843
|
+
activeGapCounts,
|
|
16844
|
+
activeGapSeconds,
|
|
16845
|
+
suggestedCapability: modal(capabilities),
|
|
16846
|
+
firstSeenAt: inWindow[0].at,
|
|
16847
|
+
lastSeenAt: inWindow[inWindow.length - 1].at,
|
|
16848
|
+
confidence: Math.min(...inWindow.map((o) => o.confidence)),
|
|
16849
|
+
projectHash: modal(projectHashes),
|
|
16850
|
+
distinctProjectHashes: new Set(projectHashes).size
|
|
16851
|
+
};
|
|
16852
|
+
}
|
|
16853
|
+
function buildPatternEvent(payload, nowISO) {
|
|
16854
|
+
return {
|
|
16855
|
+
dedupeKey: `pattern:${payload.patternKeyHash}`,
|
|
16856
|
+
event: {
|
|
16857
|
+
eventType: "local_agent.repeated_pattern",
|
|
16858
|
+
metadata: { ...payload },
|
|
16859
|
+
timestamp: nowISO
|
|
16860
|
+
}
|
|
16861
|
+
};
|
|
16862
|
+
}
|
|
16863
|
+
function storePath3() {
|
|
16864
|
+
return join42(homedir23(), ".runwork", "pattern-store.json");
|
|
16865
|
+
}
|
|
16866
|
+
function loadPatternStore() {
|
|
16867
|
+
const parsed = readJsonOrNull(storePath3());
|
|
16868
|
+
if (!parsed || typeof parsed !== "object" || !parsed.patterns || typeof parsed.patterns !== "object") {
|
|
16869
|
+
return emptyPatternStore();
|
|
16870
|
+
}
|
|
16871
|
+
if (parsed.version !== PATTERN_STORE_VERSION)
|
|
16872
|
+
return emptyPatternStore();
|
|
16873
|
+
return parsed;
|
|
16874
|
+
}
|
|
16875
|
+
function savePatternStore(state) {
|
|
16876
|
+
writeJsonAtomic(storePath3(), state);
|
|
16877
|
+
}
|
|
16878
|
+
var SAME_OCCURRENCE_WINDOW_HOURS = 4, MATURITY_MIN_OCCURRENCES = 3, MATURITY_WINDOW_DAYS = 45, MATURITY_MAX_MAD_RATIO = 0.5, MATURITY_MIN_PERIODICITY_DAYS = 1, OCCURRENCE_RETENTION_DAYS = 180, MAX_KEY_ENTITIES = 4, ACTIVE_MINUTES_CAP_SECONDS = 300, PATTERN_STORE_VERSION = 1;
|
|
16879
|
+
var init_pattern_store = __esm(() => {
|
|
16880
|
+
init_atomic_json();
|
|
16881
|
+
init_active_time();
|
|
16882
|
+
});
|
|
16883
|
+
|
|
15980
16884
|
// src/reflect/triage.ts
|
|
15981
16885
|
var exports_triage = {};
|
|
15982
16886
|
__export(exports_triage, {
|
|
@@ -15987,7 +16891,8 @@ __export(exports_triage, {
|
|
|
15987
16891
|
packTriageBatches: () => packTriageBatches,
|
|
15988
16892
|
buildTriagePrompt: () => buildTriagePrompt,
|
|
15989
16893
|
TRIAGE_ITEM_CHAR_CAP: () => TRIAGE_ITEM_CHAR_CAP,
|
|
15990
|
-
TRIAGE_BATCH_CHAR_BUDGET: () => TRIAGE_BATCH_CHAR_BUDGET
|
|
16894
|
+
TRIAGE_BATCH_CHAR_BUDGET: () => TRIAGE_BATCH_CHAR_BUDGET,
|
|
16895
|
+
TASK_TYPES: () => TASK_TYPES
|
|
15991
16896
|
});
|
|
15992
16897
|
function renderTriageItem(id, candidate) {
|
|
15993
16898
|
const d = candidate.digest;
|
|
@@ -16036,12 +16941,17 @@ You are the cheap triage tier of Runwork's reflection engine. At the end of this
|
|
|
16036
16941
|
|
|
16037
16942
|
Mark a conversation NOT worthy when it is trivial or one-shot, is routine work with no repeated pattern, or would only re-derive something in the already-suggested list. Most conversations are not worthy; be strict. Do not analyze content deeply — skim and judge.
|
|
16038
16943
|
|
|
16944
|
+
For EACH conversation also label the work itself, which is used to notice the same chore recurring across conversations:
|
|
16945
|
+
|
|
16946
|
+
- "taskType": exactly one of ${TASK_TYPES.join(" | ")}.
|
|
16947
|
+
- "keyEntities": 1 to 4 short CANONICAL noun phrases naming the OBJECT of the work ("invoice reformatting", "weekly sales report", "hubspot contact cleanup"). Canonical means: singular, lowercase, generic. NO dates, file names, person names, company names, counts, or version numbers. Two conversations doing the same chore in different words MUST produce the same phrases, so prefer the plainest wording of the underlying task over the user's phrasing.
|
|
16948
|
+
|
|
16039
16949
|
Reply with ONLY a single fenced \`json\` block:
|
|
16040
16950
|
|
|
16041
16951
|
\`\`\`json
|
|
16042
16952
|
{
|
|
16043
16953
|
"verdicts": [
|
|
16044
|
-
{ "id": "<the conversation id shown in its header>", "worthy": true|false, "confidence": <0..1> }
|
|
16954
|
+
{ "id": "<the conversation id shown in its header>", "worthy": true|false, "confidence": <0..1>, "taskType": "<one of the values above>", "keyEntities": ["<canonical phrase>"] }
|
|
16045
16955
|
]
|
|
16046
16956
|
}
|
|
16047
16957
|
\`\`\`
|
|
@@ -16071,9 +16981,13 @@ function parseTriageVerdicts(text2) {
|
|
|
16071
16981
|
const v = raw;
|
|
16072
16982
|
if (typeof v.id !== "string" || typeof v.worthy !== "boolean")
|
|
16073
16983
|
continue;
|
|
16984
|
+
const taskType = typeof v.taskType === "string" && TASK_TYPES.includes(v.taskType.trim().toLowerCase()) ? v.taskType.trim().toLowerCase() : undefined;
|
|
16985
|
+
const keyEntities = Array.isArray(v.keyEntities) ? v.keyEntities.filter((e) => typeof e === "string" && e.trim().length > 0).map((e) => e.trim()) : [];
|
|
16074
16986
|
out.set(v.id, {
|
|
16075
16987
|
worthy: v.worthy,
|
|
16076
|
-
confidence: typeof v.confidence === "number" ? Math.max(0, Math.min(1, v.confidence)) : 0.5
|
|
16988
|
+
confidence: typeof v.confidence === "number" ? Math.max(0, Math.min(1, v.confidence)) : 0.5,
|
|
16989
|
+
...taskType ? { taskType } : {},
|
|
16990
|
+
...keyEntities.length > 0 ? { keyEntities } : {}
|
|
16077
16991
|
});
|
|
16078
16992
|
}
|
|
16079
16993
|
return out.size > 0 ? out : null;
|
|
@@ -16144,7 +17058,7 @@ async function runTriagePass(candidates, opts) {
|
|
|
16144
17058
|
}
|
|
16145
17059
|
return { verdicts, summary };
|
|
16146
17060
|
}
|
|
16147
|
-
var TRIAGE_ITEM_CHAR_CAP = 12000, TRIAGE_BATCH_CHAR_BUDGET = 150000, TRIAGE_TIMEOUT_MS;
|
|
17061
|
+
var TRIAGE_ITEM_CHAR_CAP = 12000, TRIAGE_BATCH_CHAR_BUDGET = 150000, TRIAGE_TIMEOUT_MS, TASK_TYPES;
|
|
16148
17062
|
var init_triage = __esm(async () => {
|
|
16149
17063
|
init_which();
|
|
16150
17064
|
init_session_listing();
|
|
@@ -16153,6 +17067,7 @@ var init_triage = __esm(async () => {
|
|
|
16153
17067
|
init_conversation_analysis()
|
|
16154
17068
|
]);
|
|
16155
17069
|
TRIAGE_TIMEOUT_MS = 2 * 60 * 1000;
|
|
17070
|
+
TASK_TYPES = ["build", "automate", "analyze", "write", "research", "admin", "debug"];
|
|
16156
17071
|
});
|
|
16157
17072
|
|
|
16158
17073
|
// src/reflect/conversation-analysis.ts
|
|
@@ -16317,6 +17232,48 @@ async function analyzeConversations(opts = {}) {
|
|
|
16317
17232
|
releaseRunLock();
|
|
16318
17233
|
}
|
|
16319
17234
|
}
|
|
17235
|
+
function recordPatternObservations(verdicts, state, progress) {
|
|
17236
|
+
try {
|
|
17237
|
+
let store = loadPatternStore();
|
|
17238
|
+
const touchedKeys = new Set;
|
|
17239
|
+
for (const [key, verdict] of verdicts) {
|
|
17240
|
+
const entry = state.entries[key];
|
|
17241
|
+
if (!entry || !verdict.taskType || !verdict.keyEntities?.length)
|
|
17242
|
+
continue;
|
|
17243
|
+
const stats = entry.stats;
|
|
17244
|
+
const result = recordObservation(store, {
|
|
17245
|
+
taskType: verdict.taskType,
|
|
17246
|
+
keyEntities: verdict.keyEntities,
|
|
17247
|
+
at: entry.lastActivityAt,
|
|
17248
|
+
confidence: verdict.confidence,
|
|
17249
|
+
sessionIdHash: stats?.sessionIdHash,
|
|
17250
|
+
gapCounts: stats?.gapCounts ?? null,
|
|
17251
|
+
gapSeconds: stats?.gapSeconds ?? null,
|
|
17252
|
+
projectHash: stats?.projectHash ?? null
|
|
17253
|
+
});
|
|
17254
|
+
store = result.state;
|
|
17255
|
+
if (result.key)
|
|
17256
|
+
touchedKeys.add(result.key);
|
|
17257
|
+
}
|
|
17258
|
+
if (touchedKeys.size === 0)
|
|
17259
|
+
return;
|
|
17260
|
+
savePatternStore(store);
|
|
17261
|
+
const nowISO = new Date().toISOString();
|
|
17262
|
+
const events = [];
|
|
17263
|
+
for (const key of touchedKeys) {
|
|
17264
|
+
const record = store.patterns[key];
|
|
17265
|
+
if (!record)
|
|
17266
|
+
continue;
|
|
17267
|
+
const payload = evaluateMaturity(key, record);
|
|
17268
|
+
if (payload)
|
|
17269
|
+
events.push(buildPatternEvent(payload, nowISO));
|
|
17270
|
+
}
|
|
17271
|
+
if (events.length > 0) {
|
|
17272
|
+
appendToTelemetryOutbox(events);
|
|
17273
|
+
progress(`${events.length} recurring chore pattern(s) reached the recurrence threshold.`);
|
|
17274
|
+
}
|
|
17275
|
+
} catch {}
|
|
17276
|
+
}
|
|
16320
17277
|
async function analyzeConversationsLocked(opts, outcome, gates) {
|
|
16321
17278
|
const { cadence, explicitSession, manual } = gates;
|
|
16322
17279
|
if (!cadence.enabled && !manual && !explicitSession) {
|
|
@@ -16453,9 +17410,11 @@ async function analyzeConversationsLocked(opts, outcome, gates) {
|
|
|
16453
17410
|
for (const [key, verdict] of pass.verdicts) {
|
|
16454
17411
|
const entry = state.entries[key];
|
|
16455
17412
|
if (entry) {
|
|
16456
|
-
|
|
17413
|
+
const { keyEntities: _entities, ...cacheable } = verdict;
|
|
17414
|
+
state = { entries: { ...state.entries, [key]: { ...entry, triage: { ...cacheable, at: entry.lastActivityAt } } } };
|
|
16457
17415
|
}
|
|
16458
17416
|
}
|
|
17417
|
+
recordPatternObservations(pass.verdicts, state, progress);
|
|
16459
17418
|
}
|
|
16460
17419
|
}
|
|
16461
17420
|
const ranked = [];
|
|
@@ -16675,6 +17634,7 @@ var init_conversation_analysis = __esm(async () => {
|
|
|
16675
17634
|
init_session_listing();
|
|
16676
17635
|
init_insight_store();
|
|
16677
17636
|
init_telemetry_outbox();
|
|
17637
|
+
init_pattern_store();
|
|
16678
17638
|
init_run_log();
|
|
16679
17639
|
init_insight_store();
|
|
16680
17640
|
await __promiseAll([
|
|
@@ -17167,12 +18127,28 @@ var deployCommand = new Command5("deploy").description("Deploy the current app t
|
|
|
17167
18127
|
console.error(` - ${s}`);
|
|
17168
18128
|
process.exit(1);
|
|
17169
18129
|
}
|
|
18130
|
+
if (syncResult.remoteOverwrote && syncResult.remoteOverwrote.length > 0) {
|
|
18131
|
+
const files = syncResult.remoteOverwrote.join(", ");
|
|
18132
|
+
if (useJson) {
|
|
18133
|
+
jsonLine({ event: "sync_remote_overwrote", files: syncResult.remoteOverwrote, timestamp: new Date().toISOString() });
|
|
18134
|
+
} else {
|
|
18135
|
+
console.warn(`Sync resolved conflicts in the remote's favour; local changes were replaced in: ${files}`);
|
|
18136
|
+
console.warn(" Recover them with `git reflog` / `git diff ORIG_HEAD` before deploying again if that was wrong.");
|
|
18137
|
+
}
|
|
18138
|
+
}
|
|
18139
|
+
if (syncResult.keptUntracked && syncResult.keptUntracked.length > 0 && !useJson) {
|
|
18140
|
+
console.warn(`Kept your untracked file(s) over the remote's copy: ${syncResult.keptUntracked.join(", ")}`);
|
|
18141
|
+
}
|
|
17170
18142
|
if (!syncResult.pushed) {
|
|
18143
|
+
const diag = diagnoseSyncError(syncResult.pushError ?? syncResult.error);
|
|
17171
18144
|
if (useJson) {
|
|
17172
|
-
jsonOut(buildErrorResponse("deploy",
|
|
18145
|
+
jsonOut(buildErrorResponse("deploy", `Failed to push before deployment: ${diag.message}`, diag.diagnosis, [...diag.suggestions, "Then retry runwork deploy"]));
|
|
17173
18146
|
process.exit(1);
|
|
17174
18147
|
}
|
|
17175
|
-
console.error(
|
|
18148
|
+
console.error(`Push failed: ${diag.message}`);
|
|
18149
|
+
console.error(diag.diagnosis);
|
|
18150
|
+
for (const suggestion of diag.suggestions)
|
|
18151
|
+
console.error(` - ${suggestion}`);
|
|
17176
18152
|
process.exit(1);
|
|
17177
18153
|
}
|
|
17178
18154
|
if (!useJson)
|
|
@@ -19171,14 +20147,18 @@ init_session_summary();
|
|
|
19171
20147
|
init_telemetry_outbox();
|
|
19172
20148
|
init_insight_store();
|
|
19173
20149
|
init_atomic_json();
|
|
20150
|
+
init_store();
|
|
20151
|
+
init_client();
|
|
20152
|
+
init_resolve();
|
|
20153
|
+
init_workspace_state();
|
|
19174
20154
|
init_colors();
|
|
19175
20155
|
await __promiseAll([
|
|
19176
20156
|
init_conversation_registry(),
|
|
19177
20157
|
init_conversation_analysis()
|
|
19178
20158
|
]);
|
|
19179
20159
|
import { Command as Command15 } from "commander";
|
|
19180
|
-
import { join as
|
|
19181
|
-
import { homedir as
|
|
20160
|
+
import { join as join43 } from "path";
|
|
20161
|
+
import { homedir as homedir24 } from "os";
|
|
19182
20162
|
var DEFAULT_LOOKBACK_DAYS = 30;
|
|
19183
20163
|
function sinceFromDays(days) {
|
|
19184
20164
|
if (days <= 0)
|
|
@@ -19191,6 +20171,46 @@ function parseDays(raw) {
|
|
|
19191
20171
|
return DEFAULT_LOOKBACK_DAYS;
|
|
19192
20172
|
return Math.floor(n);
|
|
19193
20173
|
}
|
|
20174
|
+
function bareRegistrySkillId(skill) {
|
|
20175
|
+
if (skill.type !== "external" || typeof skill.id !== "string")
|
|
20176
|
+
return null;
|
|
20177
|
+
return skill.id.startsWith("ext-") ? skill.id.slice("ext-".length) || null : null;
|
|
20178
|
+
}
|
|
20179
|
+
async function fetchKnownSkills() {
|
|
20180
|
+
const creds = getCredentials();
|
|
20181
|
+
if (!creds)
|
|
20182
|
+
return null;
|
|
20183
|
+
let workspaceId;
|
|
20184
|
+
const client = new ApiClient(creds);
|
|
20185
|
+
try {
|
|
20186
|
+
workspaceId = (await resolveWorkspace2(client, {})).workspaceId;
|
|
20187
|
+
} catch {
|
|
20188
|
+
workspaceId = creds.defaultWorkspaceId;
|
|
20189
|
+
}
|
|
20190
|
+
if (!workspaceId)
|
|
20191
|
+
return null;
|
|
20192
|
+
try {
|
|
20193
|
+
const skills = await client.listWorkspaceSkills(workspaceId);
|
|
20194
|
+
const known = [];
|
|
20195
|
+
const seen = new Set;
|
|
20196
|
+
for (const sk of skills) {
|
|
20197
|
+
if (sk.type === "app")
|
|
20198
|
+
continue;
|
|
20199
|
+
const name = canonicalSkillName(sk.name);
|
|
20200
|
+
if (!name || seen.has(name))
|
|
20201
|
+
continue;
|
|
20202
|
+
seen.add(name);
|
|
20203
|
+
known.push({ name, id: bareRegistrySkillId(sk) });
|
|
20204
|
+
}
|
|
20205
|
+
updateWorkspaceRecord(workspaceId, { knownSkills: known });
|
|
20206
|
+
return known;
|
|
20207
|
+
} catch {
|
|
20208
|
+
const record = loadWorkspaceRecord(workspaceId);
|
|
20209
|
+
if (record.knownSkills)
|
|
20210
|
+
return record.knownSkills;
|
|
20211
|
+
return record.skillNames ? record.skillNames.map((name) => ({ name, id: null })) : null;
|
|
20212
|
+
}
|
|
20213
|
+
}
|
|
19194
20214
|
function projectLabel(project) {
|
|
19195
20215
|
const trimmed = project.replace(/[/\\]+$/, "");
|
|
19196
20216
|
const idx = Math.max(trimmed.lastIndexOf("/"), trimmed.lastIndexOf("\\"));
|
|
@@ -19247,11 +20267,21 @@ conversationsCommand.command("scan").description("Detect finished conversations
|
|
|
19247
20267
|
const nowISO = new Date().toISOString();
|
|
19248
20268
|
const summaryEvents = [];
|
|
19249
20269
|
const newlyPending = new Set(scan.newlyPendingKeys);
|
|
20270
|
+
const knownSkills = newlyPending.size > 0 ? await fetchKnownSkills() : null;
|
|
20271
|
+
const repoHashByProject = new Map;
|
|
20272
|
+
const repoHashFor = (project) => {
|
|
20273
|
+
if (!repoHashByProject.has(project))
|
|
20274
|
+
repoHashByProject.set(project, resolveRepoHash(project));
|
|
20275
|
+
return repoHashByProject.get(project) ?? null;
|
|
20276
|
+
};
|
|
19250
20277
|
let statsWritten = 0;
|
|
19251
20278
|
for (const [key, entry] of Object.entries(scan.state.entries)) {
|
|
19252
20279
|
if (!newlyPending.has(key) && entry.stats)
|
|
19253
20280
|
continue;
|
|
19254
|
-
const summary = buildSessionSummary(key, entry, digestForEntry(entry)
|
|
20281
|
+
const summary = buildSessionSummary(key, entry, digestForEntry(entry), {
|
|
20282
|
+
knownSkills,
|
|
20283
|
+
repoHash: repoHashFor(entry.project)
|
|
20284
|
+
});
|
|
19255
20285
|
scan.state.entries[key] = { ...entry, stats: summary };
|
|
19256
20286
|
statsWritten++;
|
|
19257
20287
|
if (newlyPending.has(key))
|
|
@@ -19290,7 +20320,7 @@ conversationsCommand.command("scan").description("Detect finished conversations
|
|
|
19290
20320
|
idleMinutes,
|
|
19291
20321
|
conversations: [...listed, ...rescued].sort((a, b) => b.lastActivityAt.localeCompare(a.lastActivityAt))
|
|
19292
20322
|
};
|
|
19293
|
-
writeJsonAtomic(
|
|
20323
|
+
writeJsonAtomic(join43(homedir24(), ".runwork", "conversations.json"), snapshot);
|
|
19294
20324
|
if (json) {
|
|
19295
20325
|
jsonOut({ queued: scan.queued, reopened: scan.reopened, pruned: scan.pruned, pending, finished: finished.length });
|
|
19296
20326
|
return;
|
|
@@ -19320,8 +20350,8 @@ init_client();
|
|
|
19320
20350
|
init_resolve();
|
|
19321
20351
|
import { Command as Command16 } from "commander";
|
|
19322
20352
|
import { readFileSync as readFileSync37, existsSync as existsSync48 } from "fs";
|
|
19323
|
-
import { join as
|
|
19324
|
-
import { homedir as
|
|
20353
|
+
import { join as join44 } from "path";
|
|
20354
|
+
import { homedir as homedir25 } from "os";
|
|
19325
20355
|
|
|
19326
20356
|
// ../../shared/agent-instructions/runwork-instructions.ts
|
|
19327
20357
|
function formatList(items, max = 8) {
|
|
@@ -19801,8 +20831,8 @@ async function buildInstructionContext(client, workspace) {
|
|
|
19801
20831
|
// src/commands/instructions.ts
|
|
19802
20832
|
function readSetupExtras(workspaceId) {
|
|
19803
20833
|
for (const path2 of [
|
|
19804
|
-
|
|
19805
|
-
|
|
20834
|
+
join44(process.cwd(), ".runwork", "setup.json"),
|
|
20835
|
+
join44(homedir25(), ".runwork", "setup.json")
|
|
19806
20836
|
]) {
|
|
19807
20837
|
if (!existsSync48(path2))
|
|
19808
20838
|
continue;
|
|
@@ -21541,8 +22571,8 @@ init_resolve();
|
|
|
21541
22571
|
init_prompt();
|
|
21542
22572
|
await init_detect();
|
|
21543
22573
|
import { Command as Command27 } from "commander";
|
|
21544
|
-
import { join as
|
|
21545
|
-
import { homedir as
|
|
22574
|
+
import { join as join48 } from "path";
|
|
22575
|
+
import { homedir as homedir27 } from "os";
|
|
21546
22576
|
|
|
21547
22577
|
// src/commands/sync.ts
|
|
21548
22578
|
init_store();
|
|
@@ -21554,8 +22584,8 @@ await __promiseAll([
|
|
|
21554
22584
|
]);
|
|
21555
22585
|
import { Command as Command26 } from "commander";
|
|
21556
22586
|
import { readFileSync as readFileSync40, existsSync as existsSync51 } from "fs";
|
|
21557
|
-
import { join as
|
|
21558
|
-
import { homedir as
|
|
22587
|
+
import { join as join47 } from "path";
|
|
22588
|
+
import { homedir as homedir26 } from "os";
|
|
21559
22589
|
|
|
21560
22590
|
// src/commands/mcp-entries.ts
|
|
21561
22591
|
init_types();
|
|
@@ -22454,13 +23484,13 @@ function readLocalSkills(state) {
|
|
|
22454
23484
|
if (!baseDir)
|
|
22455
23485
|
continue;
|
|
22456
23486
|
for (const skillName of state.skills) {
|
|
22457
|
-
const skillMdPath =
|
|
23487
|
+
const skillMdPath = join47(baseDir, skillName, "SKILL.md");
|
|
22458
23488
|
if (existsSync51(skillMdPath)) {
|
|
22459
23489
|
results.push({ name: skillName, content: readFileSync40(skillMdPath, "utf-8") });
|
|
22460
23490
|
continue;
|
|
22461
23491
|
}
|
|
22462
23492
|
const filename = skillName.toLowerCase().replace(/[^a-z0-9]+/g, "-");
|
|
22463
|
-
const flatPath =
|
|
23493
|
+
const flatPath = join47(baseDir, `${filename}.md`);
|
|
22464
23494
|
if (existsSync51(flatPath)) {
|
|
22465
23495
|
results.push({ name: skillName, content: readFileSync40(flatPath, "utf-8") });
|
|
22466
23496
|
}
|
|
@@ -22942,7 +23972,7 @@ This account is not a member of "${state.workspaceName || state.workspaceId}".`)
|
|
|
22942
23972
|
}
|
|
22943
23973
|
for (const adapter2 of adapters) {
|
|
22944
23974
|
if (adapter2 instanceof CodexAdapter) {
|
|
22945
|
-
const runworkDir =
|
|
23975
|
+
const runworkDir = join47(homedir26(), ".runwork");
|
|
22946
23976
|
const result = adapter2.registerDesktopWorkspace(runworkDir, "Runwork");
|
|
22947
23977
|
if (result === "written") {
|
|
22948
23978
|
vlog(` [${adapter2.name}] Registered workspace in Codex desktop app`);
|
|
@@ -23084,8 +24114,8 @@ var syncCommand = new Command26("sync").description("Sync skills bidirectionally
|
|
|
23084
24114
|
verbose: !!opts.verbose,
|
|
23085
24115
|
redetect: !!opts.redetect
|
|
23086
24116
|
};
|
|
23087
|
-
const projectStatePath =
|
|
23088
|
-
const userStatePath =
|
|
24117
|
+
const projectStatePath = join47(process.cwd(), ".runwork", "setup.json");
|
|
24118
|
+
const userStatePath = join47(homedir26(), ".runwork", "setup.json");
|
|
23089
24119
|
const projectState = loadSetupState(projectStatePath);
|
|
23090
24120
|
const userState = loadSetupState(userStatePath);
|
|
23091
24121
|
if (!projectState && !userState) {
|
|
@@ -23154,7 +24184,7 @@ function toSkillFilename(name) {
|
|
|
23154
24184
|
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-");
|
|
23155
24185
|
}
|
|
23156
24186
|
function loadSetupStateForScope(scope) {
|
|
23157
|
-
const path4 = scope === "project" ?
|
|
24187
|
+
const path4 = scope === "project" ? join48(process.cwd(), ".runwork", "setup.json") : join48(homedir27(), ".runwork", "setup.json");
|
|
23158
24188
|
return readJsonOrNull(path4);
|
|
23159
24189
|
}
|
|
23160
24190
|
async function parkAndTeardownWorkspace(previous, scopes) {
|
|
@@ -23323,8 +24353,8 @@ Re-run without --dry-run to sync workspace data.`);
|
|
|
23323
24353
|
}
|
|
23324
24354
|
persistDefaultWorkspace(workspaceId, workspaceName);
|
|
23325
24355
|
for (const s of scopes) {
|
|
23326
|
-
const dir = s === "project" ? ".runwork" :
|
|
23327
|
-
writeJsonAtomic(
|
|
24356
|
+
const dir = s === "project" ? ".runwork" : join48(homedir27(), ".runwork");
|
|
24357
|
+
writeJsonAtomic(join48(dir, "setup.json"), state);
|
|
23328
24358
|
}
|
|
23329
24359
|
if (restored)
|
|
23330
24360
|
clearParkedState(workspaceId);
|
|
@@ -23332,7 +24362,7 @@ Re-run without --dry-run to sync workspace data.`);
|
|
|
23332
24362
|
Syncing workspace data...
|
|
23333
24363
|
`);
|
|
23334
24364
|
for (const s of scopes) {
|
|
23335
|
-
const statePath2 = s === "project" ?
|
|
24365
|
+
const statePath2 = s === "project" ? join48(process.cwd(), ".runwork", "setup.json") : join48(homedir27(), ".runwork", "setup.json");
|
|
23336
24366
|
await syncFromState(state, statePath2, credentials, {
|
|
23337
24367
|
dryRun: false,
|
|
23338
24368
|
pullOnly: true,
|
|
@@ -23353,11 +24383,11 @@ import { Command as Command28 } from "commander";
|
|
|
23353
24383
|
|
|
23354
24384
|
// src/utils/setup-state.ts
|
|
23355
24385
|
import { existsSync as existsSync52, readFileSync as readFileSync41 } from "fs";
|
|
23356
|
-
import { join as
|
|
23357
|
-
import { homedir as
|
|
24386
|
+
import { join as join49 } from "path";
|
|
24387
|
+
import { homedir as homedir28 } from "os";
|
|
23358
24388
|
function loadSetupState2() {
|
|
23359
|
-
const projectPath =
|
|
23360
|
-
const userPath =
|
|
24389
|
+
const projectPath = join49(process.cwd(), ".runwork", "setup.json");
|
|
24390
|
+
const userPath = join49(homedir28(), ".runwork", "setup.json");
|
|
23361
24391
|
for (const p of [projectPath, userPath]) {
|
|
23362
24392
|
if (existsSync52(p)) {
|
|
23363
24393
|
try {
|
|
@@ -23421,8 +24451,8 @@ init_types();
|
|
|
23421
24451
|
await init_detect();
|
|
23422
24452
|
import { Command as Command29 } from "commander";
|
|
23423
24453
|
import { existsSync as existsSync53, readFileSync as readFileSync42 } from "fs";
|
|
23424
|
-
import { resolve as resolve3, join as
|
|
23425
|
-
import { homedir as
|
|
24454
|
+
import { resolve as resolve3, join as join50 } from "path";
|
|
24455
|
+
import { homedir as homedir29 } from "os";
|
|
23426
24456
|
function loadSetupState3(filePath) {
|
|
23427
24457
|
if (!existsSync53(filePath))
|
|
23428
24458
|
return null;
|
|
@@ -23443,8 +24473,8 @@ var buildPluginCommand = new Command29("build-plugin").description("Build an ins
|
|
|
23443
24473
|
process.exit(1);
|
|
23444
24474
|
}
|
|
23445
24475
|
const credentials = requireAuth();
|
|
23446
|
-
const projectStatePath =
|
|
23447
|
-
const userStatePath =
|
|
24476
|
+
const projectStatePath = join50(process.cwd(), ".runwork", "setup.json");
|
|
24477
|
+
const userStatePath = join50(homedir29(), ".runwork", "setup.json");
|
|
23448
24478
|
const state = loadSetupState3(projectStatePath) ?? loadSetupState3(userStatePath);
|
|
23449
24479
|
if (!state) {
|
|
23450
24480
|
console.error("No setup state found. Run `runwork setup` first.");
|
|
@@ -23537,8 +24567,8 @@ init_prompt();
|
|
|
23537
24567
|
await init_detect();
|
|
23538
24568
|
import { Command as Command30 } from "commander";
|
|
23539
24569
|
import { existsSync as existsSync54, readFileSync as readFileSync43, rmSync as rmSync13, unlinkSync as unlinkSync8 } from "fs";
|
|
23540
|
-
import { join as
|
|
23541
|
-
import { homedir as
|
|
24570
|
+
import { join as join51 } from "path";
|
|
24571
|
+
import { homedir as homedir30 } from "os";
|
|
23542
24572
|
function loadSetupState4(filePath) {
|
|
23543
24573
|
if (!existsSync54(filePath))
|
|
23544
24574
|
return null;
|
|
@@ -23549,8 +24579,8 @@ function loadSetupState4(filePath) {
|
|
|
23549
24579
|
}
|
|
23550
24580
|
}
|
|
23551
24581
|
var uninstallCommand = new Command30("uninstall").description("Remove all Runwork configuration from local agents (MCP servers, skills, instructions)").option("-y, --yes", "Skip confirmation prompt").option("--keep-auth", "Keep authentication credentials (only remove agent configs)").action(async (opts) => {
|
|
23552
|
-
const projectStatePath =
|
|
23553
|
-
const userStatePath =
|
|
24582
|
+
const projectStatePath = join51(process.cwd(), ".runwork", "setup.json");
|
|
24583
|
+
const userStatePath = join51(homedir30(), ".runwork", "setup.json");
|
|
23554
24584
|
const projectState = loadSetupState4(projectStatePath);
|
|
23555
24585
|
const userState = loadSetupState4(userStatePath);
|
|
23556
24586
|
if (!projectState && !userState) {
|
|
@@ -23630,9 +24660,9 @@ This will remove all Runwork configuration from your local agents:
|
|
|
23630
24660
|
}
|
|
23631
24661
|
}
|
|
23632
24662
|
}
|
|
23633
|
-
const stateDir = label === "project" ?
|
|
24663
|
+
const stateDir = label === "project" ? join51(process.cwd(), ".runwork") : join51(homedir30(), ".runwork");
|
|
23634
24664
|
if (opts.keepAuth && label === "user") {
|
|
23635
|
-
const setupFile =
|
|
24665
|
+
const setupFile = join51(stateDir, "setup.json");
|
|
23636
24666
|
if (existsSync54(setupFile)) {
|
|
23637
24667
|
try {
|
|
23638
24668
|
unlinkSync8(setupFile);
|
|
@@ -23883,8 +24913,8 @@ init_credentials();
|
|
|
23883
24913
|
await init_detect();
|
|
23884
24914
|
import { parse as parse2 } from "smol-toml";
|
|
23885
24915
|
import { existsSync as existsSync55, readFileSync as readFileSync45 } from "fs";
|
|
23886
|
-
import { join as
|
|
23887
|
-
import { homedir as
|
|
24916
|
+
import { join as join52, sep as sep4 } from "path";
|
|
24917
|
+
import { homedir as homedir31, platform as osPlatform2, arch as osArch } from "os";
|
|
23888
24918
|
var BASE_URL2 = process.env.RUNWORK_DOWNLOAD_BASE_URL || "https://runwork.ai";
|
|
23889
24919
|
var LATEST_JSON_URL2 = `${BASE_URL2}/cli/latest.json`;
|
|
23890
24920
|
function detectPlatform() {
|
|
@@ -23912,7 +24942,7 @@ function buildContext() {
|
|
|
23912
24942
|
const credentials = getCredentials();
|
|
23913
24943
|
const client = credentials ? new ApiClient(credentials) : null;
|
|
23914
24944
|
let config = null;
|
|
23915
|
-
const configPath =
|
|
24945
|
+
const configPath = join52(process.cwd(), ".runwork.json");
|
|
23916
24946
|
if (existsSync55(configPath)) {
|
|
23917
24947
|
try {
|
|
23918
24948
|
config = JSON.parse(readFileSync45(configPath, "utf-8"));
|
|
@@ -24016,9 +25046,9 @@ async function checkCliArtifactReachable() {
|
|
|
24016
25046
|
}
|
|
24017
25047
|
async function checkCliInstallLocation() {
|
|
24018
25048
|
const isWindows2 = osPlatform2() === "win32";
|
|
24019
|
-
const home =
|
|
24020
|
-
const canonicalDir =
|
|
24021
|
-
const canonicalBinary = isWindows2 ?
|
|
25049
|
+
const home = homedir31();
|
|
25050
|
+
const canonicalDir = join52(home, ".runwork", "bin");
|
|
25051
|
+
const canonicalBinary = isWindows2 ? join52(canonicalDir, "runwork.exe") : join52(canonicalDir, "runwork");
|
|
24022
25052
|
const candidates = [process.execPath, process.argv[1] || ""].filter(Boolean);
|
|
24023
25053
|
const runsFromCanonical = candidates.some((p) => normalizePath(p) === normalizePath(canonicalBinary));
|
|
24024
25054
|
if (runsFromCanonical) {
|
|
@@ -24150,7 +25180,7 @@ async function checkGitCredentialHelper(ctx) {
|
|
|
24150
25180
|
};
|
|
24151
25181
|
}
|
|
24152
25182
|
async function checkProjectConfig(ctx) {
|
|
24153
|
-
const configPath =
|
|
25183
|
+
const configPath = join52(ctx.cwd, ".runwork.json");
|
|
24154
25184
|
if (!existsSync55(configPath)) {
|
|
24155
25185
|
if (!ctx.credentials) {
|
|
24156
25186
|
return { name: "project-config", status: "skip", message: "no project (not logged in)" };
|
|
@@ -24213,7 +25243,7 @@ async function checkGitRemote(ctx) {
|
|
|
24213
25243
|
if (!ctx.config) {
|
|
24214
25244
|
return { name: "git-remote", status: "skip", message: "skipped (no project)" };
|
|
24215
25245
|
}
|
|
24216
|
-
if (!existsSync55(
|
|
25246
|
+
if (!existsSync55(join52(ctx.cwd, ".git"))) {
|
|
24217
25247
|
return {
|
|
24218
25248
|
name: "git-remote",
|
|
24219
25249
|
status: "fail",
|
|
@@ -24267,8 +25297,8 @@ async function checkDeployFreshness(ctx) {
|
|
|
24267
25297
|
return { name: "deploy-freshness", status: "skip", message: "local HEAD unknown" };
|
|
24268
25298
|
}
|
|
24269
25299
|
function loadSetupState5() {
|
|
24270
|
-
const projectPath =
|
|
24271
|
-
const userPath =
|
|
25300
|
+
const projectPath = join52(process.cwd(), ".runwork", "setup.json");
|
|
25301
|
+
const userPath = join52(homedir31(), ".runwork", "setup.json");
|
|
24272
25302
|
for (const p of [projectPath, userPath]) {
|
|
24273
25303
|
if (existsSync55(p)) {
|
|
24274
25304
|
try {
|
|
@@ -24287,7 +25317,7 @@ async function checkCodexNetwork() {
|
|
|
24287
25317
|
if (!state || !state.configuredAgents.includes("codex")) {
|
|
24288
25318
|
return { name, status: "skip", message: "Codex not configured for Runwork" };
|
|
24289
25319
|
}
|
|
24290
|
-
const configPath =
|
|
25320
|
+
const configPath = join52(homedir31(), ".codex", "config.toml");
|
|
24291
25321
|
if (!existsSync55(configPath)) {
|
|
24292
25322
|
return { name, status: "skip", message: "no Codex config found" };
|
|
24293
25323
|
}
|
|
@@ -24346,7 +25376,7 @@ async function checkCodexDesktopProject() {
|
|
|
24346
25376
|
if (!usesCodex) {
|
|
24347
25377
|
return { name, status: "skip", message: "Codex not configured for Runwork" };
|
|
24348
25378
|
}
|
|
24349
|
-
const statePath2 =
|
|
25379
|
+
const statePath2 = join52(homedir31(), ".codex", ".codex-global-state.json");
|
|
24350
25380
|
if (!existsSync55(statePath2)) {
|
|
24351
25381
|
return { name, status: "skip", message: "Codex desktop app not detected" };
|
|
24352
25382
|
}
|
|
@@ -24358,7 +25388,7 @@ async function checkCodexDesktopProject() {
|
|
|
24358
25388
|
} catch {
|
|
24359
25389
|
return { name, status: "warn", message: "could not read Codex desktop state" };
|
|
24360
25390
|
}
|
|
24361
|
-
const runworkDir =
|
|
25391
|
+
const runworkDir = join52(homedir31(), ".runwork");
|
|
24362
25392
|
if (savedRoots.includes(runworkDir)) {
|
|
24363
25393
|
return { name, status: "pass", message: "Runwork project added to Codex desktop sidebar" };
|
|
24364
25394
|
}
|
|
@@ -24436,7 +25466,7 @@ async function checkAgentSetup() {
|
|
|
24436
25466
|
if (!skillsDir)
|
|
24437
25467
|
continue;
|
|
24438
25468
|
const missingSkills = state.skills.filter((name) => {
|
|
24439
|
-
const skillPath =
|
|
25469
|
+
const skillPath = join52(skillsDir, name, "SKILL.md");
|
|
24440
25470
|
return !existsSync55(skillPath);
|
|
24441
25471
|
});
|
|
24442
25472
|
if (missingSkills.length > 0) {
|
|
@@ -24462,25 +25492,25 @@ async function checkAgentSetup() {
|
|
|
24462
25492
|
};
|
|
24463
25493
|
}
|
|
24464
25494
|
function getMcpConfigPath2(slug, scope) {
|
|
24465
|
-
const home =
|
|
25495
|
+
const home = homedir31();
|
|
24466
25496
|
switch (slug) {
|
|
24467
25497
|
case "claude-code":
|
|
24468
|
-
return scope === "project" ?
|
|
25498
|
+
return scope === "project" ? join52(process.cwd(), ".mcp.json") : join52(home, ".claude", "settings.json");
|
|
24469
25499
|
case "cursor":
|
|
24470
|
-
return scope === "project" ?
|
|
25500
|
+
return scope === "project" ? join52(process.cwd(), ".cursor", "mcp.json") : join52(home, ".cursor", "mcp.json");
|
|
24471
25501
|
case "windsurf":
|
|
24472
|
-
return scope === "project" ?
|
|
25502
|
+
return scope === "project" ? join52(process.cwd(), ".windsurf", "mcp.json") : join52(home, ".windsurf", "mcp.json");
|
|
24473
25503
|
case "codex":
|
|
24474
25504
|
case "codex-app":
|
|
24475
|
-
return scope === "user" ?
|
|
25505
|
+
return scope === "user" ? join52(home, ".codex", "config.toml") : null;
|
|
24476
25506
|
case "gemini":
|
|
24477
|
-
return scope === "user" ?
|
|
25507
|
+
return scope === "user" ? join52(home, ".gemini", "settings.json") : null;
|
|
24478
25508
|
default:
|
|
24479
25509
|
return null;
|
|
24480
25510
|
}
|
|
24481
25511
|
}
|
|
24482
25512
|
async function checkWorkspacePointers() {
|
|
24483
|
-
const userStatePath =
|
|
25513
|
+
const userStatePath = join52(homedir31(), ".runwork", "setup.json");
|
|
24484
25514
|
const state = existsSync55(userStatePath) ? (() => {
|
|
24485
25515
|
try {
|
|
24486
25516
|
return JSON.parse(readFileSync45(userStatePath, "utf-8"));
|
|
@@ -24513,15 +25543,15 @@ async function checkWorkspacePointers() {
|
|
|
24513
25543
|
};
|
|
24514
25544
|
}
|
|
24515
25545
|
function getSkillsDir(slug, scope) {
|
|
24516
|
-
const home =
|
|
25546
|
+
const home = homedir31();
|
|
24517
25547
|
switch (slug) {
|
|
24518
25548
|
case "claude-code":
|
|
24519
|
-
return scope === "project" ?
|
|
25549
|
+
return scope === "project" ? join52(process.cwd(), ".claude", "skills") : join52(home, ".claude", "skills");
|
|
24520
25550
|
case "codex":
|
|
24521
25551
|
case "codex-app":
|
|
24522
|
-
return scope === "project" ?
|
|
25552
|
+
return scope === "project" ? join52(process.cwd(), ".agents", "skills") : join52(home, ".agents", "skills");
|
|
24523
25553
|
case "gemini":
|
|
24524
|
-
return scope === "project" ?
|
|
25554
|
+
return scope === "project" ? join52(process.cwd(), ".gemini", "skills") : join52(home, ".gemini", "skills");
|
|
24525
25555
|
default:
|
|
24526
25556
|
return null;
|
|
24527
25557
|
}
|
|
@@ -24575,7 +25605,7 @@ async function runAllChecks(options) {
|
|
|
24575
25605
|
init_credentials();
|
|
24576
25606
|
init_remote();
|
|
24577
25607
|
import { existsSync as existsSync56 } from "fs";
|
|
24578
|
-
import { join as
|
|
25608
|
+
import { join as join53 } from "path";
|
|
24579
25609
|
async function applyDoctorFixes(ctx, failingNames) {
|
|
24580
25610
|
const failing = new Set(failingNames);
|
|
24581
25611
|
const outcomes = [];
|
|
@@ -24602,7 +25632,7 @@ async function applyDoctorFixes(ctx, failingNames) {
|
|
|
24602
25632
|
applied: false,
|
|
24603
25633
|
message: "no project config -- run inside an app directory"
|
|
24604
25634
|
});
|
|
24605
|
-
} else if (!existsSync56(
|
|
25635
|
+
} else if (!existsSync56(join53(ctx.cwd, ".git"))) {
|
|
24606
25636
|
outcomes.push({
|
|
24607
25637
|
name: "git-remote",
|
|
24608
25638
|
applied: false,
|
|
@@ -24622,9 +25652,9 @@ async function applyDoctorFixes(ctx, failingNames) {
|
|
|
24622
25652
|
|
|
24623
25653
|
// src/agents/runtime-detection.ts
|
|
24624
25654
|
import { existsSync as existsSync57, readFileSync as readFileSync46, statSync as statSync10, readdirSync as readdirSync16 } from "fs";
|
|
24625
|
-
import { homedir as
|
|
24626
|
-
import { join as
|
|
24627
|
-
var RUNWORK_SESSIONS_DIR =
|
|
25655
|
+
import { homedir as homedir32 } from "os";
|
|
25656
|
+
import { join as join54 } from "path";
|
|
25657
|
+
var RUNWORK_SESSIONS_DIR = join54(homedir32(), ".runwork", "sessions");
|
|
24628
25658
|
function detectCurrentAgent() {
|
|
24629
25659
|
const claudeCodeSessionId = process.env.CLAUDE_CODE_SESSION_ID;
|
|
24630
25660
|
if (claudeCodeSessionId) {
|
|
@@ -24687,7 +25717,7 @@ function detectCurrentAgent() {
|
|
|
24687
25717
|
return null;
|
|
24688
25718
|
}
|
|
24689
25719
|
function readHookSessionInfo(sessionId) {
|
|
24690
|
-
const path4 =
|
|
25720
|
+
const path4 = join54(RUNWORK_SESSIONS_DIR, `${sessionId}.json`);
|
|
24691
25721
|
if (!existsSync57(path4))
|
|
24692
25722
|
return null;
|
|
24693
25723
|
try {
|
|
@@ -24699,7 +25729,7 @@ function readHookSessionInfo(sessionId) {
|
|
|
24699
25729
|
}
|
|
24700
25730
|
}
|
|
24701
25731
|
function findClaudeCodeSessionFile(sessionId) {
|
|
24702
|
-
const root =
|
|
25732
|
+
const root = join54(homedir32(), ".claude", "projects");
|
|
24703
25733
|
if (!existsSync57(root))
|
|
24704
25734
|
return null;
|
|
24705
25735
|
let projectDirs;
|
|
@@ -24709,14 +25739,14 @@ function findClaudeCodeSessionFile(sessionId) {
|
|
|
24709
25739
|
return null;
|
|
24710
25740
|
}
|
|
24711
25741
|
for (const dir of projectDirs) {
|
|
24712
|
-
const candidate =
|
|
25742
|
+
const candidate = join54(root, dir, `${sessionId}.jsonl`);
|
|
24713
25743
|
if (existsSync57(candidate))
|
|
24714
25744
|
return candidate;
|
|
24715
25745
|
}
|
|
24716
25746
|
return null;
|
|
24717
25747
|
}
|
|
24718
25748
|
function findCodexRolloutFile(threadId) {
|
|
24719
|
-
const root =
|
|
25749
|
+
const root = join54(homedir32(), ".codex", "sessions");
|
|
24720
25750
|
if (!existsSync57(root))
|
|
24721
25751
|
return null;
|
|
24722
25752
|
const stack = [root];
|
|
@@ -24729,7 +25759,7 @@ function findCodexRolloutFile(threadId) {
|
|
|
24729
25759
|
continue;
|
|
24730
25760
|
}
|
|
24731
25761
|
for (const entry of entries) {
|
|
24732
|
-
const full =
|
|
25762
|
+
const full = join54(dir, entry);
|
|
24733
25763
|
let s;
|
|
24734
25764
|
try {
|
|
24735
25765
|
s = statSync10(full);
|
|
@@ -24746,7 +25776,7 @@ function findCodexRolloutFile(threadId) {
|
|
|
24746
25776
|
return null;
|
|
24747
25777
|
}
|
|
24748
25778
|
function findNewestClaudeCodeSession() {
|
|
24749
|
-
const root =
|
|
25779
|
+
const root = join54(homedir32(), ".claude", "projects");
|
|
24750
25780
|
if (!existsSync57(root))
|
|
24751
25781
|
return null;
|
|
24752
25782
|
let projectDirs;
|
|
@@ -24757,7 +25787,7 @@ function findNewestClaudeCodeSession() {
|
|
|
24757
25787
|
}
|
|
24758
25788
|
let best = null;
|
|
24759
25789
|
for (const dir of projectDirs) {
|
|
24760
|
-
const projectPath =
|
|
25790
|
+
const projectPath = join54(root, dir);
|
|
24761
25791
|
let files;
|
|
24762
25792
|
try {
|
|
24763
25793
|
files = readdirSync16(projectPath);
|
|
@@ -24767,7 +25797,7 @@ function findNewestClaudeCodeSession() {
|
|
|
24767
25797
|
for (const file of files) {
|
|
24768
25798
|
if (!file.endsWith(".jsonl"))
|
|
24769
25799
|
continue;
|
|
24770
|
-
const full =
|
|
25800
|
+
const full = join54(projectPath, file);
|
|
24771
25801
|
try {
|
|
24772
25802
|
const s = statSync10(full);
|
|
24773
25803
|
if (!best || s.mtimeMs > best.mtime) {
|
|
@@ -24785,7 +25815,7 @@ function findNewestClaudeCodeSession() {
|
|
|
24785
25815
|
return best ? { sessionId: best.sessionId, path: best.path } : null;
|
|
24786
25816
|
}
|
|
24787
25817
|
function findNewestCodexRollout() {
|
|
24788
|
-
const root =
|
|
25818
|
+
const root = join54(homedir32(), ".codex", "sessions");
|
|
24789
25819
|
if (!existsSync57(root))
|
|
24790
25820
|
return null;
|
|
24791
25821
|
const stack = [root];
|
|
@@ -24799,7 +25829,7 @@ function findNewestCodexRollout() {
|
|
|
24799
25829
|
continue;
|
|
24800
25830
|
}
|
|
24801
25831
|
for (const entry of entries) {
|
|
24802
|
-
const full =
|
|
25832
|
+
const full = join54(dir, entry);
|
|
24803
25833
|
let s;
|
|
24804
25834
|
try {
|
|
24805
25835
|
s = statSync10(full);
|
|
@@ -25032,9 +26062,9 @@ init_client();
|
|
|
25032
26062
|
init_resolve();
|
|
25033
26063
|
import { Command as Command35 } from "commander";
|
|
25034
26064
|
import { readFileSync as readFileSync47, writeFileSync as writeFileSync31, existsSync as existsSync58, mkdtempSync as mkdtempSync4 } from "fs";
|
|
25035
|
-
import { join as
|
|
26065
|
+
import { join as join55 } from "path";
|
|
25036
26066
|
import { tmpdir as tmpdir4 } from "os";
|
|
25037
|
-
import { createHash as
|
|
26067
|
+
import { createHash as createHash6 } from "crypto";
|
|
25038
26068
|
|
|
25039
26069
|
// src/agents/utils/transcript-render.ts
|
|
25040
26070
|
init_session_digest();
|
|
@@ -25159,8 +26189,8 @@ function resolveLocalSessionShare(opts, conversation) {
|
|
|
25159
26189
|
console.error("Error: this conversation has no shareable content.");
|
|
25160
26190
|
process.exit(1);
|
|
25161
26191
|
}
|
|
25162
|
-
const tempDir = mkdtempSync4(
|
|
25163
|
-
const transcriptFile =
|
|
26192
|
+
const tempDir = mkdtempSync4(join55(tmpdir4(), "runwork-share-"));
|
|
26193
|
+
const transcriptFile = join55(tempDir, "transcript.md");
|
|
25164
26194
|
writeFileSync31(transcriptFile, markdown);
|
|
25165
26195
|
opts.transcriptFile = transcriptFile;
|
|
25166
26196
|
opts.nativeFile = opts.nativeFile ?? conversation.transcriptPath;
|
|
@@ -25178,7 +26208,7 @@ function nativeBundleFormatForAgent(slug) {
|
|
|
25178
26208
|
return null;
|
|
25179
26209
|
}
|
|
25180
26210
|
function sha256Hex(content) {
|
|
25181
|
-
return
|
|
26211
|
+
return createHash6("sha256").update(content, "utf8").digest("hex");
|
|
25182
26212
|
}
|
|
25183
26213
|
function utf8ByteLength(content) {
|
|
25184
26214
|
return Buffer.byteLength(content, "utf8");
|
|
@@ -25370,8 +26400,8 @@ init_resolve();
|
|
|
25370
26400
|
init_registry_data();
|
|
25371
26401
|
import { Command as Command38 } from "commander";
|
|
25372
26402
|
import { writeFileSync as writeFileSync32, mkdirSync as mkdirSync28, realpathSync } from "fs";
|
|
25373
|
-
import { homedir as
|
|
25374
|
-
import { join as
|
|
26403
|
+
import { homedir as homedir33 } from "os";
|
|
26404
|
+
import { join as join56 } from "path";
|
|
25375
26405
|
import { spawn as spawn5 } from "child_process";
|
|
25376
26406
|
init_registry();
|
|
25377
26407
|
init_which();
|
|
@@ -25410,9 +26440,9 @@ function extractCodexUuid(rolloutContent) {
|
|
|
25410
26440
|
}
|
|
25411
26441
|
function placeClaudeJsonl(uuid, content, recipientCwd) {
|
|
25412
26442
|
const encoded = encodeClaudeCodeCwd(recipientCwd);
|
|
25413
|
-
const projectDir =
|
|
26443
|
+
const projectDir = join56(homedir33(), ".claude", "projects", encoded);
|
|
25414
26444
|
mkdirSync28(projectDir, { recursive: true });
|
|
25415
|
-
const placedAt =
|
|
26445
|
+
const placedAt = join56(projectDir, `${uuid}.jsonl`);
|
|
25416
26446
|
writeFileSync32(placedAt, content);
|
|
25417
26447
|
return { placedAt, runFromCwd: recipientCwd };
|
|
25418
26448
|
}
|
|
@@ -25421,10 +26451,10 @@ function placeCodexRollout(uuid, content) {
|
|
|
25421
26451
|
const yyyy = String(now.getUTCFullYear());
|
|
25422
26452
|
const mm = String(now.getUTCMonth() + 1).padStart(2, "0");
|
|
25423
26453
|
const dd = String(now.getUTCDate()).padStart(2, "0");
|
|
25424
|
-
const dir =
|
|
26454
|
+
const dir = join56(homedir33(), ".codex", "sessions", yyyy, mm, dd);
|
|
25425
26455
|
mkdirSync28(dir, { recursive: true });
|
|
25426
26456
|
const ts = now.toISOString().replace(/\.\d+Z$/, "").replace(/:/g, "-");
|
|
25427
|
-
const placedAt =
|
|
26457
|
+
const placedAt = join56(dir, `rollout-${ts}-${uuid}.jsonl`);
|
|
25428
26458
|
writeFileSync32(placedAt, content);
|
|
25429
26459
|
return { placedAt };
|
|
25430
26460
|
}
|