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/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 existsSync4 } from "fs";
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 && existsSync4(exe))
1552
+ if (exe && existsSync5(exe))
1407
1553
  return exe;
1408
- const fallback = lines.find((line) => existsSync4(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 (existsSync4(gitExe))
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 (!existsSync4(candidate))
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
- const manifest = JSON.parse(readFileSync5(manifestPath, "utf-8"));
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"), `node_modules/
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
- return { status, pushed };
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.24.1";
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" && it.is_error === true)
9794
- digest.errorCount++;
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" && Array.isArray(message.content)) {
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
- if (it.name === "Skill" && it.input?.skill)
9817
- digest.skillInvocations.push(it.input.skill);
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
- return digest.userMessages.length > 0 ? digest : null;
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" && typeof p.cwd === "string") {
9927
- digest.project = p.cwd;
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 stableStringify(value) {
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(stableStringify(value));
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
- state = { entries: { ...state.entries, [key]: { ...entry, triage: { ...verdict, at: entry.lastActivityAt } } } };
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", "Failed to push before deployment", "Local commits were synced but could not be pushed to the remote.", ["Check your network connection", "Run runwork dev to retry syncing", "Then retry runwork 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("Push failed. Check your connection and retry `runwork deploy`.");
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 join42 } from "path";
19181
- import { homedir as homedir23 } from "os";
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(join42(homedir23(), ".runwork", "conversations.json"), snapshot);
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 join43 } from "path";
19324
- import { homedir as homedir24 } from "os";
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
- join43(process.cwd(), ".runwork", "setup.json"),
19805
- join43(homedir24(), ".runwork", "setup.json")
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 join47 } from "path";
21545
- import { homedir as homedir26 } from "os";
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 join46 } from "path";
21558
- import { homedir as homedir25 } from "os";
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 = join46(baseDir, skillName, "SKILL.md");
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 = join46(baseDir, `${filename}.md`);
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 = join46(homedir25(), ".runwork");
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 = join46(process.cwd(), ".runwork", "setup.json");
23088
- const userStatePath = join46(homedir25(), ".runwork", "setup.json");
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" ? join47(process.cwd(), ".runwork", "setup.json") : join47(homedir26(), ".runwork", "setup.json");
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" : join47(homedir26(), ".runwork");
23327
- writeJsonAtomic(join47(dir, "setup.json"), state);
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" ? join47(process.cwd(), ".runwork", "setup.json") : join47(homedir26(), ".runwork", "setup.json");
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 join48 } from "path";
23357
- import { homedir as homedir27 } from "os";
24386
+ import { join as join49 } from "path";
24387
+ import { homedir as homedir28 } from "os";
23358
24388
  function loadSetupState2() {
23359
- const projectPath = join48(process.cwd(), ".runwork", "setup.json");
23360
- const userPath = join48(homedir27(), ".runwork", "setup.json");
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 join49 } from "path";
23425
- import { homedir as homedir28 } from "os";
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 = join49(process.cwd(), ".runwork", "setup.json");
23447
- const userStatePath = join49(homedir28(), ".runwork", "setup.json");
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 join50 } from "path";
23541
- import { homedir as homedir29 } from "os";
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 = join50(process.cwd(), ".runwork", "setup.json");
23553
- const userStatePath = join50(homedir29(), ".runwork", "setup.json");
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" ? join50(process.cwd(), ".runwork") : join50(homedir29(), ".runwork");
24663
+ const stateDir = label === "project" ? join51(process.cwd(), ".runwork") : join51(homedir30(), ".runwork");
23634
24664
  if (opts.keepAuth && label === "user") {
23635
- const setupFile = join50(stateDir, "setup.json");
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 join51, sep as sep4 } from "path";
23887
- import { homedir as homedir30, platform as osPlatform2, arch as osArch } from "os";
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 = join51(process.cwd(), ".runwork.json");
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 = homedir30();
24020
- const canonicalDir = join51(home, ".runwork", "bin");
24021
- const canonicalBinary = isWindows2 ? join51(canonicalDir, "runwork.exe") : join51(canonicalDir, "runwork");
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 = join51(ctx.cwd, ".runwork.json");
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(join51(ctx.cwd, ".git"))) {
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 = join51(process.cwd(), ".runwork", "setup.json");
24271
- const userPath = join51(homedir30(), ".runwork", "setup.json");
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 = join51(homedir30(), ".codex", "config.toml");
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 = join51(homedir30(), ".codex", ".codex-global-state.json");
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 = join51(homedir30(), ".runwork");
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 = join51(skillsDir, name, "SKILL.md");
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 = homedir30();
25495
+ const home = homedir31();
24466
25496
  switch (slug) {
24467
25497
  case "claude-code":
24468
- return scope === "project" ? join51(process.cwd(), ".mcp.json") : join51(home, ".claude", "settings.json");
25498
+ return scope === "project" ? join52(process.cwd(), ".mcp.json") : join52(home, ".claude", "settings.json");
24469
25499
  case "cursor":
24470
- return scope === "project" ? join51(process.cwd(), ".cursor", "mcp.json") : join51(home, ".cursor", "mcp.json");
25500
+ return scope === "project" ? join52(process.cwd(), ".cursor", "mcp.json") : join52(home, ".cursor", "mcp.json");
24471
25501
  case "windsurf":
24472
- return scope === "project" ? join51(process.cwd(), ".windsurf", "mcp.json") : join51(home, ".windsurf", "mcp.json");
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" ? join51(home, ".codex", "config.toml") : null;
25505
+ return scope === "user" ? join52(home, ".codex", "config.toml") : null;
24476
25506
  case "gemini":
24477
- return scope === "user" ? join51(home, ".gemini", "settings.json") : null;
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 = join51(homedir30(), ".runwork", "setup.json");
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 = homedir30();
25546
+ const home = homedir31();
24517
25547
  switch (slug) {
24518
25548
  case "claude-code":
24519
- return scope === "project" ? join51(process.cwd(), ".claude", "skills") : join51(home, ".claude", "skills");
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" ? join51(process.cwd(), ".agents", "skills") : join51(home, ".agents", "skills");
25552
+ return scope === "project" ? join52(process.cwd(), ".agents", "skills") : join52(home, ".agents", "skills");
24523
25553
  case "gemini":
24524
- return scope === "project" ? join51(process.cwd(), ".gemini", "skills") : join51(home, ".gemini", "skills");
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 join52 } from "path";
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(join52(ctx.cwd, ".git"))) {
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 homedir31 } from "os";
24626
- import { join as join53 } from "path";
24627
- var RUNWORK_SESSIONS_DIR = join53(homedir31(), ".runwork", "sessions");
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 = join53(RUNWORK_SESSIONS_DIR, `${sessionId}.json`);
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 = join53(homedir31(), ".claude", "projects");
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 = join53(root, dir, `${sessionId}.jsonl`);
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 = join53(homedir31(), ".codex", "sessions");
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 = join53(dir, entry);
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 = join53(homedir31(), ".claude", "projects");
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 = join53(root, dir);
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 = join53(projectPath, file);
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 = join53(homedir31(), ".codex", "sessions");
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 = join53(dir, entry);
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 join54 } from "path";
26065
+ import { join as join55 } from "path";
25036
26066
  import { tmpdir as tmpdir4 } from "os";
25037
- import { createHash as createHash5 } from "crypto";
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(join54(tmpdir4(), "runwork-share-"));
25163
- const transcriptFile = join54(tempDir, "transcript.md");
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 createHash5("sha256").update(content, "utf8").digest("hex");
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 homedir32 } from "os";
25374
- import { join as join55 } from "path";
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 = join55(homedir32(), ".claude", "projects", encoded);
26443
+ const projectDir = join56(homedir33(), ".claude", "projects", encoded);
25414
26444
  mkdirSync28(projectDir, { recursive: true });
25415
- const placedAt = join55(projectDir, `${uuid}.jsonl`);
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 = join55(homedir32(), ".codex", "sessions", yyyy, mm, dd);
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 = join55(dir, `rollout-${ts}-${uuid}.jsonl`);
26457
+ const placedAt = join56(dir, `rollout-${ts}-${uuid}.jsonl`);
25428
26458
  writeFileSync32(placedAt, content);
25429
26459
  return { placedAt };
25430
26460
  }