codiedev 0.10.0-beta.3 → 0.10.0-beta.5

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.
Files changed (144) hide show
  1. package/README.md +7 -1
  2. package/assets/tui-brand-plugin.tsx +73 -0
  3. package/dist/backup/cache.js +1 -5
  4. package/dist/backup/match.js +0 -6
  5. package/dist/backup/run.js +3 -35
  6. package/dist/captureExclusions.js +0 -12
  7. package/dist/captureLog.js +0 -28
  8. package/dist/captureReadiness.js +0 -58
  9. package/dist/captureSignals.js +66 -3
  10. package/dist/cli.js +41 -18
  11. package/dist/codexHookTrust.js +61 -5
  12. package/dist/codexLimitScan.js +2 -109
  13. package/dist/codie.js +41 -56
  14. package/dist/codieBrandInstall.js +80 -0
  15. package/dist/codieLauncher.js +59 -59
  16. package/dist/codieRuntime.js +19 -23
  17. package/dist/codieRuntimeInstall.js +108 -23
  18. package/dist/codieStartup.js +0 -3
  19. package/dist/codieUsage.js +181 -0
  20. package/dist/commands/ask.js +0 -3
  21. package/dist/commands/capture.js +0 -2
  22. package/dist/commands/createTicket.js +0 -10
  23. package/dist/commands/doctor.js +41 -82
  24. package/dist/commands/inbox.js +0 -2
  25. package/dist/commands/library.js +0 -3
  26. package/dist/commands/pack.js +573 -0
  27. package/dist/commands/ping.js +0 -2
  28. package/dist/commands/pull.js +6 -10
  29. package/dist/commands/push.js +2 -12
  30. package/dist/commands/react.js +0 -2
  31. package/dist/commands/reverseTicket.js +0 -33
  32. package/dist/commands/search.js +0 -4
  33. package/dist/commands/send.js +0 -5
  34. package/dist/commands/share.js +0 -4
  35. package/dist/commands/shared.js +0 -35
  36. package/dist/commands/traces.js +280 -0
  37. package/dist/configDir.js +0 -10
  38. package/dist/connect.js +21 -77
  39. package/dist/connectFlow.js +0 -9
  40. package/dist/copilotHooks.js +395 -0
  41. package/dist/copilotInstallation.js +64 -0
  42. package/dist/cursorHooks.js +40 -17
  43. package/dist/cursorLifecycle.js +109 -0
  44. package/dist/detection.js +0 -30
  45. package/dist/harnessDirs.js +73 -0
  46. package/dist/hook.js +193 -316
  47. package/dist/inboxFormat.js +0 -24
  48. package/dist/launchContext.js +150 -0
  49. package/dist/launcher.js +140 -0
  50. package/dist/limitEvent.js +41 -35
  51. package/dist/limitEventReporter.js +1 -1
  52. package/dist/mcp.js +3 -77
  53. package/dist/nativeSteering.js +24 -0
  54. package/dist/packs/codie.js +182 -0
  55. package/dist/packs/harnesses.js +374 -0
  56. package/dist/packs/hookGate.js +256 -0
  57. package/dist/packs/install.js +285 -0
  58. package/dist/packs/manifest.js +126 -0
  59. package/dist/packs/paths.js +81 -0
  60. package/dist/packs/shim.js +214 -0
  61. package/dist/packs/store.js +155 -0
  62. package/dist/packs/sync.js +170 -0
  63. package/dist/prUrl.js +0 -5
  64. package/dist/repoResolver.js +0 -19
  65. package/dist/secret.js +0 -3
  66. package/dist/security.js +0 -20
  67. package/dist/securityDeepScan.js +1 -14
  68. package/dist/securityDoctor.js +0 -2
  69. package/dist/securityFeature.js +0 -1
  70. package/dist/securityInstall.js +6 -13
  71. package/dist/securityInventory.js +23 -13
  72. package/dist/securityPilot.js +0 -2
  73. package/dist/securityPolicy.js +0 -7
  74. package/dist/selfUpdate.js +331 -0
  75. package/dist/skillMarker.js +23 -0
  76. package/dist/subagents.js +135 -0
  77. package/dist/teamSkillContext.js +10 -0
  78. package/dist/transcriptModels.js +6 -8
  79. package/dist/upgradeNudge.js +0 -30
  80. package/dist/utils.js +286 -733
  81. package/dist/version.js +0 -10
  82. package/dist/versionCheck.js +0 -33
  83. package/package.json +8 -7
  84. package/dist/backup/cache.d.ts +0 -14
  85. package/dist/backup/match.d.ts +0 -2
  86. package/dist/backup/run.d.ts +0 -25
  87. package/dist/captureExclusions.d.ts +0 -30
  88. package/dist/captureLog.d.ts +0 -20
  89. package/dist/captureReadiness.d.ts +0 -65
  90. package/dist/captureSignals.d.ts +0 -19
  91. package/dist/cli.d.ts +0 -16
  92. package/dist/codexHookTrust.d.ts +0 -34
  93. package/dist/codexLimitScan.d.ts +0 -64
  94. package/dist/codie.d.ts +0 -2
  95. package/dist/codieLauncher.d.ts +0 -53
  96. package/dist/codieRuntime.d.ts +0 -29
  97. package/dist/codieRuntimeInstall.d.ts +0 -28
  98. package/dist/codieStartup.d.ts +0 -26
  99. package/dist/commands/ask.d.ts +0 -1
  100. package/dist/commands/capture.d.ts +0 -14
  101. package/dist/commands/createTicket.d.ts +0 -27
  102. package/dist/commands/delete.d.ts +0 -1
  103. package/dist/commands/doctor.d.ts +0 -1
  104. package/dist/commands/inbox.d.ts +0 -3
  105. package/dist/commands/library.d.ts +0 -2
  106. package/dist/commands/note.d.ts +0 -1
  107. package/dist/commands/ping.d.ts +0 -1
  108. package/dist/commands/promote.d.ts +0 -1
  109. package/dist/commands/pull.d.ts +0 -2
  110. package/dist/commands/push.d.ts +0 -33
  111. package/dist/commands/react.d.ts +0 -2
  112. package/dist/commands/reverseTicket.d.ts +0 -2
  113. package/dist/commands/search.d.ts +0 -2
  114. package/dist/commands/security.d.ts +0 -1
  115. package/dist/commands/send.d.ts +0 -2
  116. package/dist/commands/share.d.ts +0 -2
  117. package/dist/commands/shared.d.ts +0 -64
  118. package/dist/configDir.d.ts +0 -1
  119. package/dist/connect.d.ts +0 -1
  120. package/dist/connectFlow.d.ts +0 -13
  121. package/dist/cursorHooks.d.ts +0 -23
  122. package/dist/detection.d.ts +0 -30
  123. package/dist/hook.d.ts +0 -6
  124. package/dist/inboxFormat.d.ts +0 -38
  125. package/dist/limitEvent.d.ts +0 -16
  126. package/dist/limitEventReporter.d.ts +0 -21
  127. package/dist/mcp.d.ts +0 -12
  128. package/dist/prUrl.d.ts +0 -9
  129. package/dist/repoResolver.d.ts +0 -17
  130. package/dist/secret.d.ts +0 -1
  131. package/dist/security.d.ts +0 -102
  132. package/dist/securityDeepScan.d.ts +0 -41
  133. package/dist/securityDoctor.d.ts +0 -32
  134. package/dist/securityFeature.d.ts +0 -5
  135. package/dist/securityInstall.d.ts +0 -63
  136. package/dist/securityInventory.d.ts +0 -54
  137. package/dist/securityPilot.d.ts +0 -19
  138. package/dist/securityPolicy.d.ts +0 -33
  139. package/dist/securitySession.d.ts +0 -6
  140. package/dist/transcriptModels.d.ts +0 -18
  141. package/dist/upgradeNudge.d.ts +0 -25
  142. package/dist/utils.d.ts +0 -346
  143. package/dist/version.d.ts +0 -1
  144. package/dist/versionCheck.d.ts +0 -68
package/dist/cli.js CHANGED
@@ -1,19 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
- /**
4
- * Codiedev CLI dispatcher.
5
- *
6
- * Subcommands:
7
- * codiedev connect — link agent CLIs to your org
8
- * codiedev push <file> — author/update a spec/review/decision/etc.
9
- * codiedev pull <key> — fetch an artifact by filename key
10
- * codiedev ping <user> "<msg>" — send a message (optionally --with <key>)
11
- * codiedev inbox — list pings (use --unread to filter)
12
- * codiedev read <ping-id> — mark read + show full message
13
- * codiedev note "<text>" — capture a thought
14
- * codiedev promote <artifact-id> — promote an extracted artifact to authored
15
- * codiedev help — show this help
16
- */
17
3
  Object.defineProperty(exports, "__esModule", { value: true });
18
4
  const connect_1 = require("./connect");
19
5
  const push_1 = require("./commands/push");
@@ -34,6 +20,10 @@ const send_1 = require("./commands/send");
34
20
  const react_1 = require("./commands/react");
35
21
  const capture_1 = require("./commands/capture");
36
22
  const security_1 = require("./commands/security");
23
+ const pack_1 = require("./commands/pack");
24
+ const traces_1 = require("./commands/traces");
25
+ const hookGate_1 = require("./packs/hookGate");
26
+ const sync_1 = require("./packs/sync");
37
27
  const version_1 = require("./version");
38
28
  const HELP = `
39
29
  Codiedev CLI
@@ -43,7 +33,8 @@ Codiedev CLI
43
33
  someone left off across sessions and providers.
44
34
 
45
35
  Connect:
46
- codiedev connect Link Claude Code / Codex / Cursor to your org
36
+ codiedev connect Link Claude Code / Codex / Cursor /
37
+ GitHub Copilot to your org
47
38
  codiedev doctor Verify setup, capture, and Agent Security
48
39
  codiedev security pilot Run safe synthetic policy checks
49
40
  codiedev security inventory Inventory MCPs, skills, hooks, plugins,
@@ -96,6 +87,22 @@ Messaging:
96
87
  codiedev inbox [--unread] [--limit N] Show messages
97
88
  codiedev read <ping-id> Mark read + show full message
98
89
 
90
+ Starter packs:
91
+ codiedev pack list Packs assigned to your team
92
+ codiedev pack install [slug] [--harness claude,codex,copilot,cursor,codie]
93
+ [--dry-run] [--json]
94
+ Install a pack (or all) into every
95
+ detected tool
96
+ codiedev pack status Installed packs, up to date or not
97
+
98
+ Traces (agents on the gateway):
99
+ codiedev traces agents Agents you maintain, with saved test cases
100
+ codiedev traces cases <agent> [--suite name] [--model id]
101
+ Pull an agent's test cases (or one suite)
102
+ into this repo for your coding agent to
103
+ run, optionally on another model
104
+ codiedev traces report <results.json> Send the results back to Traces
105
+
99
106
  Discovery:
100
107
  codiedev search "<query>" Semantic search across the team's
101
108
  captured sessions and artifacts
@@ -159,6 +166,8 @@ locally before any capture leaves your machine, so an offline or failed portal
159
166
  status sync never weakens it.
160
167
  `.trim();
161
168
  const COMMAND_HELP = {
169
+ pack: pack_1.PACK_HELP,
170
+ traces: traces_1.TRACES_HELP,
162
171
  connect: `
163
172
  codiedev connect — link your agent CLI to Codiedev
164
173
 
@@ -170,6 +179,7 @@ codiedev connect — link your agent CLI to Codiedev
170
179
  - ~/.codex/AGENTS.md instructions (same, for Codex)
171
180
  - ~/.cursor/hooks.json hooks (session, prompt, security, capture)
172
181
  - ~/.cursor/rules/codiedev.mdc (teaches Cursor the commands)
182
+ - ~/.copilot/hooks/codiedev.json (GitHub Copilot: VS Code + CLI capture)
173
183
 
174
184
  Re-run anytime to swap accounts or refresh the instruction block.
175
185
 
@@ -416,6 +426,15 @@ Tokens are per-user. Rotate any time by revoking + creating a new one.
416
426
  `.trim();
417
427
  async function main() {
418
428
  const [, , command, ...rest] = process.argv;
429
+ if (command === "hook-gate") {
430
+ (0, hookGate_1.runHookGateCli)(rest);
431
+ return;
432
+ }
433
+ if (command && !["pack", "packs", "help", "docs", "version", "--version", "-v", "--help", "-h"].includes(command) && !rest.includes("--json")) {
434
+ const nag = (0, sync_1.packNagLine)();
435
+ if (nag)
436
+ console.error(`\n${nag}\n`);
437
+ }
419
438
  if (!command || command === "--help" || command === "-h") {
420
439
  console.log(HELP);
421
440
  return;
@@ -438,7 +457,6 @@ async function main() {
438
457
  console.log(DOCS);
439
458
  return;
440
459
  }
441
- // Support `codiedev <command> --help` too.
442
460
  if (rest.includes("--help") || rest.includes("-h")) {
443
461
  if (COMMAND_HELP[command]) {
444
462
  console.log(COMMAND_HELP[command]);
@@ -479,7 +497,6 @@ async function main() {
479
497
  return;
480
498
  case "inbox":
481
499
  case "check":
482
- // Support `codiedev check inbox` as an alias.
483
500
  if (command === "check" && rest[0] === "inbox") {
484
501
  await (0, inbox_1.runInbox)(rest.slice(1));
485
502
  }
@@ -511,6 +528,9 @@ async function main() {
511
528
  case "ls":
512
529
  await (0, library_1.runLibrary)(rest);
513
530
  return;
531
+ case "traces":
532
+ await (0, traces_1.runTraces)(rest);
533
+ return;
514
534
  case "share":
515
535
  await (0, share_1.runShare)(rest);
516
536
  return;
@@ -525,8 +545,11 @@ async function main() {
525
545
  case "ignored":
526
546
  await (0, capture_1.runCaptureCommand)([command, ...rest]);
527
547
  return;
548
+ case "pack":
549
+ case "packs":
550
+ await (0, pack_1.runPack)(rest);
551
+ return;
528
552
  case "capture":
529
- // Back-compat alias for the earlier `codiedev capture <sub>` form.
530
553
  await (0, capture_1.runCaptureCommand)(rest);
531
554
  return;
532
555
  default:
@@ -37,6 +37,8 @@ exports.codexCommandHookHash = codexCommandHookHash;
37
37
  exports.codiedevCodexHookTrustEntries = codiedevCodexHookTrustEntries;
38
38
  exports.writeCodexHookTrust = writeCodexHookTrust;
39
39
  exports.inspectCodexHookTrust = inspectCodexHookTrust;
40
+ exports.packCodexHookTrustEntries = packCodexHookTrustEntries;
41
+ exports.writeCodexPackHookTrust = writeCodexPackHookTrust;
40
42
  const crypto = __importStar(require("crypto"));
41
43
  const fs = __importStar(require("fs"));
42
44
  const path = __importStar(require("path"));
@@ -85,8 +87,6 @@ function normalizedTimeout(eventName, value) {
85
87
  return Math.min(3, Math.max(1, configured ?? 1));
86
88
  return Math.max(1, configured ?? 600);
87
89
  }
88
- /** Codex hashes a canonical, normalized hook identity before it will execute a
89
- * user hook. Keep this compatible with codex_config::version_for_toml. */
90
90
  function codexCommandHookHash(eventName, group, handler) {
91
91
  const normalizedHandler = {
92
92
  type: "command",
@@ -173,9 +173,6 @@ function removeTable(lines, header) {
173
173
  }
174
174
  return output;
175
175
  }
176
- /** `codiedev connect` is an explicit install action, so trust only the exact
177
- * Codiedev commands it just wrote. Any later command change produces a new
178
- * hash and Codex disables that hook until connect is run again. */
179
176
  function writeCodexHookTrust(configPath, hooksPath, hooksFile) {
180
177
  const entries = codiedevCodexHookTrustEntries(hooksPath, hooksFile);
181
178
  let existing = fs.existsSync(configPath) ? fs.readFileSync(configPath, "utf8") : "";
@@ -226,3 +223,62 @@ function inspectCodexHookTrust(configPath, hooksPath, hooksFile, requiredSubcomm
226
223
  }).length;
227
224
  return { ok: entries.length > 0 && trusted === entries.length, total: entries.length, trusted };
228
225
  }
226
+ const PACK_TRUST_BEGIN = "# codiedev-pack-trust:begin";
227
+ const PACK_TRUST_END = "# codiedev-pack-trust:end";
228
+ function packCodexHookTrustEntries(hooksPath, hooksFile) {
229
+ const events = hooksFile?.hooks;
230
+ if (!events || typeof events !== "object")
231
+ return [];
232
+ const source = normalizedHooksPath(hooksPath);
233
+ const entries = [];
234
+ for (const [eventName, groupsValue] of Object.entries(events)) {
235
+ const eventLabel = EVENT_LABELS[eventName];
236
+ if (!eventLabel || !Array.isArray(groupsValue))
237
+ continue;
238
+ groupsValue.forEach((groupValue, groupIndex) => {
239
+ const group = groupValue;
240
+ if (!group || !Array.isArray(group.hooks))
241
+ return;
242
+ group.hooks.forEach((handlerValue, handlerIndex) => {
243
+ const handler = handlerValue;
244
+ if (!handler || handler.type !== "command" || typeof handler.command !== "string")
245
+ return;
246
+ entries.push({
247
+ key: `${source}:${eventLabel}:${groupIndex}:${handlerIndex}`,
248
+ eventName,
249
+ command: handler.command,
250
+ currentHash: codexCommandHookHash(eventName, group, handler),
251
+ });
252
+ });
253
+ });
254
+ }
255
+ return entries;
256
+ }
257
+ function writeCodexPackHookTrust(configPath, entries) {
258
+ let existing = fs.existsSync(configPath) ? fs.readFileSync(configPath, "utf8") : "";
259
+ existing = existing.replace(new RegExp(`\\n?${PACK_TRUST_BEGIN}\\n[\\s\\S]*?${PACK_TRUST_END}\\n?`, "g"), "\n");
260
+ let lines = existing.split("\n");
261
+ for (const entry of entries)
262
+ lines = removeTable(lines, tableHeader(entry.key));
263
+ const base = lines.join("\n").trimEnd();
264
+ if (entries.length === 0) {
265
+ const next = base ? `${base}\n` : "";
266
+ if (!fs.existsSync(configPath) && !next)
267
+ return entries;
268
+ fs.mkdirSync(path.dirname(configPath), { recursive: true, mode: 0o700 });
269
+ fs.writeFileSync(configPath, next, { encoding: "utf8", mode: 0o600 });
270
+ fs.chmodSync(configPath, 0o600);
271
+ return entries;
272
+ }
273
+ const trustBlock = entries.flatMap((entry) => [
274
+ tableHeader(entry.key),
275
+ "enabled = true",
276
+ `trusted_hash = ${JSON.stringify(entry.currentHash)}`,
277
+ "",
278
+ ]).join("\n");
279
+ const next = `${base}${base ? "\n\n" : ""}${PACK_TRUST_BEGIN}\n${trustBlock}${PACK_TRUST_END}\n`;
280
+ fs.mkdirSync(path.dirname(configPath), { recursive: true, mode: 0o700 });
281
+ fs.writeFileSync(configPath, next, { encoding: "utf8", mode: 0o600 });
282
+ fs.chmodSync(configPath, 0o600);
283
+ return entries;
284
+ }
@@ -41,49 +41,17 @@ exports.sessionIdFromRolloutPath = sessionIdFromRolloutPath;
41
41
  exports.codexSessionsDir = codexSessionsDir;
42
42
  exports.recordCodexLimitReported = recordCodexLimitReported;
43
43
  exports.sweepCodexLimits = sweepCodexLimits;
44
- // Reporting a Codex credit/rate-limit block.
45
- //
46
- // WHY THIS EXISTS: the `limit-codex` reporter is a Codex `Stop` hook, and
47
- // Codex does NOT run Stop hooks on the turn a block kills. Measured on real
48
- // rollouts: every normal `task_complete` pairs with a Stop-hook run within
49
- // ~200ms, and the blocked one never does (21 turns → 20 runs; 11 → 10, short
50
- // by exactly the blocked turn each time). Claude Code has a dedicated
51
- // StopFailure event with a `rate_limit` matcher, which is why it reports
52
- // correctly; Codex's HookEventName enum has no equivalent — there is no event
53
- // at all that fires at the block.
54
- //
55
- // So the block can only be reported LATER, from a hook that does run. This
56
- // module scans the rollout files Codex has already written and reports any
57
- // block that has not been reported yet. It is driven from UserPromptSubmit
58
- // (`discover-codex`), which fires on the developer's next prompt — in the
59
- // blocked session or in a new one — and from Stop, which still catches the
60
- // (rarer) case of a block followed by more turns.
61
- //
62
- // The scan reads BACKWARDS from end of file. The previous reader took a fixed
63
- // 512 KB tail, which silently lost the event whenever the session kept writing
64
- // after the block: in one real 44 MB rollout the marker sat 1.13 MB from the
65
- // end, outside the window, and the parse returned null.
66
44
  const fs = __importStar(require("fs"));
67
- const os = __importStar(require("os"));
68
45
  const path = __importStar(require("path"));
69
46
  const limitEvent_1 = require("./limitEvent");
70
47
  const configDir_1 = require("./configDir");
48
+ const harnessDirs_1 = require("./harnessDirs");
71
49
  const MARKER = Buffer.from("rate_limit_reached_type");
72
50
  const SPEND_MARKER = Buffer.from("spend_control_reached");
73
51
  const NEWLINE = 0x0a;
74
- // Read backwards in 256 KB chunks, and give up after 16 MB. Codex rollouts run
75
- // to tens of megabytes, but a block is always in the session's LAST turn — the
76
- // bytes after it are whatever the session did once the window reset. 16 MB
77
- // covers that comfortably without ever loading a whole rollout.
78
52
  const DEFAULT_CHUNK_SIZE = 256 * 1024;
79
53
  const DEFAULT_MAX_SCAN_BYTES = 16 * 1024 * 1024;
80
- // Tail shipped to the server so it can summarize what the developer was doing
81
- // when they got blocked. Matches the server-side cap in http.ts.
82
54
  const DEFAULT_TAIL_BYTES = 64 * 1024;
83
- /**
84
- * Find the LAST rate-limit/credit block recorded in a Codex rollout file.
85
- * Returns null when the session was never blocked.
86
- */
87
55
  function scanCodexRolloutForLimit(filePath, options = {}) {
88
56
  const chunkSize = options.chunkSize ?? DEFAULT_CHUNK_SIZE;
89
57
  const maxScanBytes = options.maxScanBytes ?? DEFAULT_MAX_SCAN_BYTES;
@@ -105,8 +73,6 @@ function scanCodexRolloutForLimit(filePath, options = {}) {
105
73
  }
106
74
  try {
107
75
  let pos = size;
108
- // Bytes of the (earlier-read) chunk that belong to a line whose start we
109
- // have not reached yet. Carried into the next, earlier chunk.
110
76
  let carry = Buffer.alloc(0);
111
77
  let scanned = 0;
112
78
  while (pos > 0 && scanned < maxScanBytes) {
@@ -118,15 +84,10 @@ function scanCodexRolloutForLimit(filePath, options = {}) {
118
84
  const read = fs.readSync(fd, chunk, 0, length, pos);
119
85
  scanned += read;
120
86
  const window = Buffer.concat([chunk.subarray(0, read), carry]);
121
- // At pos > 0 the first line in the window is partial — its start lies in
122
- // an earlier chunk — so hold it back rather than parse a torn line.
123
87
  let completeStart = 0;
124
88
  if (pos > 0) {
125
89
  const firstNewline = window.indexOf(NEWLINE);
126
90
  if (firstNewline < 0) {
127
- // The whole window is one unterminated line. A token_count record is
128
- // ~1 KB, so a line this long is never the one we want: drop it rather
129
- // than grow the carry without bound.
130
91
  carry = window.length > maxScanBytes ? Buffer.alloc(0) : window;
131
92
  continue;
132
93
  }
@@ -139,8 +100,6 @@ function scanCodexRolloutForLimit(filePath, options = {}) {
139
100
  const region = window.subarray(completeStart);
140
101
  if (region.indexOf(MARKER) < 0 && region.indexOf(SPEND_MARKER) < 0)
141
102
  continue;
142
- // Walk this window's complete lines from the end backwards; the first
143
- // match going backwards is the latest one in the file.
144
103
  const bounds = [];
145
104
  let lineStart = 0;
146
105
  for (let index = 0; index < region.length; index += 1) {
@@ -168,18 +127,10 @@ function scanCodexRolloutForLimit(filePath, options = {}) {
168
127
  fs.closeSync(fd);
169
128
  }
170
129
  catch {
171
- /* best-effort */
172
130
  }
173
131
  }
174
132
  }
175
- /**
176
- * Read up to `maxBytes` of the rollout ENDING at `lineEnd` — the work the
177
- * developer was doing when the block hit, not whatever the session did after
178
- * the window reset. Resumes at a line boundary so every JSONL line parses.
179
- */
180
133
  function readTailEndingAt(filePath, lineEnd, maxBytes = DEFAULT_TAIL_BYTES) {
181
- // Clamp to the file as it is NOW: the offset came from an earlier scan, and
182
- // reading past EOF would return a short, garbled tail.
183
134
  let end = lineEnd;
184
135
  try {
185
136
  end = Math.min(lineEnd, fs.statSync(filePath).size);
@@ -215,13 +166,11 @@ function readTailEndingAt(filePath, lineEnd, maxBytes = DEFAULT_TAIL_BYTES) {
215
166
  fs.closeSync(fd);
216
167
  }
217
168
  catch {
218
- /* best-effort */
219
169
  }
220
170
  }
221
171
  }
222
172
  const DEFAULT_WITHIN_MS = 24 * 3600_000;
223
173
  const DEFAULT_MAX_FILES = 12;
224
- /** Codex rollout files, newest first, limited by age and count. */
225
174
  function findRecentCodexRollouts(sessionsDir, options = {}) {
226
175
  const withinMs = options.withinMs ?? DEFAULT_WITHIN_MS;
227
176
  const maxFiles = options.maxFiles ?? DEFAULT_MAX_FILES;
@@ -252,7 +201,6 @@ function findRecentCodexRollouts(sessionsDir, options = {}) {
252
201
  found.push({ file: full, mtime });
253
202
  }
254
203
  catch {
255
- /* unreadable — skip */
256
204
  }
257
205
  }
258
206
  };
@@ -262,17 +210,14 @@ function findRecentCodexRollouts(sessionsDir, options = {}) {
262
210
  .slice(0, maxFiles)
263
211
  .map((entry) => entry.file);
264
212
  }
265
- /** Codex names rollouts `rollout-<ISO local time>-<session id>.jsonl`. */
266
213
  function sessionIdFromRolloutPath(filePath) {
267
214
  const match = path.basename(filePath).match(/^rollout-.*?T[\d-]+-(.+)\.jsonl$/);
268
215
  return match ? match[1] : undefined;
269
216
  }
270
- /** Where Codex writes its rollout JSONL files. */
271
217
  function codexSessionsDir() {
272
- return path.join(os.homedir(), ".codex", "sessions");
218
+ return path.join((0, harnessDirs_1.codexHome)(), "sessions");
273
219
  }
274
220
  exports.CODEX_LIMIT_STATE_PATH = path.join((0, configDir_1.codiedevStateDir)(), "codex-limit-state.json");
275
- // Bounds the state file on a machine with a long Codex history.
276
221
  const MAX_STATE_ENTRIES = 500;
277
222
  function readSweepState(statePath) {
278
223
  let parsed;
@@ -297,10 +242,6 @@ function readSweepState(statePath) {
297
242
  };
298
243
  }
299
244
  }
300
- // Migrate the 0.10.0-beta shape ({ reportedThrough, clean }). The old global
301
- // watermark is DELIBERATELY dropped rather than carried over: it is exactly
302
- // what could suppress an older block forever. Re-reporting is safe — the
303
- // server deduplicates on (companyId, userId, at).
304
245
  const legacyClean = parsed.clean;
305
246
  if (legacyClean && typeof legacyClean === "object") {
306
247
  for (const [file, size] of Object.entries(legacyClean)) {
@@ -310,9 +251,6 @@ function readSweepState(statePath) {
310
251
  }
311
252
  return { seen };
312
253
  }
313
- // A record is dropped only once its file has been gone for a while. Pruning
314
- // the moment a path is missing would re-report an already-sent block after any
315
- // transient rename or move.
316
254
  const PRUNE_AFTER_MS = 7 * 24 * 3600_000;
317
255
  function pruneSweepState(state, now) {
318
256
  for (const [file, record] of Object.entries(state.seen)) {
@@ -329,11 +267,6 @@ function pruneSweepState(state, now) {
329
267
  .slice(MAX_STATE_ENTRIES)
330
268
  .forEach((file) => delete state.seen[file]);
331
269
  }
332
- /**
333
- * Merge our records into whatever is on disk NOW and write the result. Two
334
- * Codex sessions can submit a prompt at the same time; a plain
335
- * read-modify-write would silently drop the other process's records.
336
- */
337
270
  function commitSweepState(statePath, ours, now) {
338
271
  const current = readSweepState(statePath);
339
272
  for (const [file, record] of Object.entries(ours))
@@ -342,9 +275,6 @@ function commitSweepState(statePath, ours, now) {
342
275
  writeSweepState(statePath, current);
343
276
  }
344
277
  function writeSweepState(statePath, state) {
345
- // Written through a temp file and renamed: Codex kills a hook that overruns
346
- // its timeout, and a half-written state file would read as "nothing seen"
347
- // and re-report every block in the window.
348
278
  const temp = `${statePath}.${process.pid}.tmp`;
349
279
  try {
350
280
  fs.mkdirSync(path.dirname(statePath), { recursive: true });
@@ -356,7 +286,6 @@ function writeSweepState(statePath, state) {
356
286
  fs.unlinkSync(temp);
357
287
  }
358
288
  catch {
359
- /* best-effort */
360
289
  }
361
290
  }
362
291
  }
@@ -369,46 +298,15 @@ function fingerprint(filePath) {
369
298
  return null;
370
299
  }
371
300
  }
372
- /**
373
- * Record a block reported outside the sweep (the Stop path), so the next
374
- * prompt sweep neither re-scans the rollout nor re-posts the event.
375
- */
376
301
  function recordCodexLimitReported(filePath, at, statePath = exports.CODEX_LIMIT_STATE_PATH) {
377
302
  const print = fingerprint(filePath);
378
303
  if (!print)
379
304
  return;
380
305
  commitSweepState(statePath, { [filePath]: { ...print, at, t: Date.now() } }, Date.now());
381
306
  }
382
- // Candidates are listed generously and settled by one statSync each; only
383
- // files that actually changed cost a read. The budget therefore bounds work
384
- // without letting idle sessions bury an unreported block.
385
- //
386
- // The budget binds only on a COLD START, where nothing has been seen before,
387
- // so it must be big enough to finish that first pass. A budget of 8 silently
388
- // starved the older blocked rollouts on a real machine: 49 candidates in the
389
- // window, the newest 8 all unblocked, nothing reported. A full pass over
390
- // those 49 costs ~205ms — the backward scan stops at the marker rather than
391
- // reading each file to its cap — so the budget can afford to cover them.
392
307
  const DEFAULT_CANDIDATES = 200;
393
308
  const DEFAULT_MAX_SCANS = 64;
394
- // The budget bounds how MANY files are read; this bounds how LONG. A count is
395
- // not enough on a slow or network-mounted disk, and this runs on the user's
396
- // prompt clock. Whatever is not reached stays unrecorded, so the next prompt
397
- // picks it up rather than skipping it.
398
309
  const DEFAULT_DEADLINE_MS = 1_500;
399
- /**
400
- * Report every Codex block this machine has not reported yet. Returns how many
401
- * events were posted. Never throws — it runs inside a hook.
402
- *
403
- * State is kept PER FILE, not as one newest-wins timestamp. A single global
404
- * watermark loses data: rollouts are capped per sweep, so a block in a file
405
- * outside the cap can be older than one already reported and would then be
406
- * suppressed forever. A per-file record can only ever cause a re-report, which
407
- * the server deduplicates on (companyId, userId, at).
408
- *
409
- * A file is recorded only after its POST succeeds, so a failed send is retried
410
- * on the next prompt.
411
- */
412
310
  async function sweepCodexLimits(args) {
413
311
  const statePath = args.statePath ?? exports.CODEX_LIMIT_STATE_PATH;
414
312
  const state = readSweepState(statePath);
@@ -426,8 +324,6 @@ async function sweepCodexLimits(args) {
426
324
  if (!print)
427
325
  continue;
428
326
  const record = state.seen[file];
429
- // Settled by the stat alone — it has not changed since the last scan, so
430
- // it holds nothing new and does not spend the read budget.
431
327
  if (record && record.size === print.size && record.mtime === print.mtime)
432
328
  continue;
433
329
  if (budget <= 0 || Date.now() >= deadline)
@@ -439,14 +335,11 @@ async function sweepCodexLimits(args) {
439
335
  continue;
440
336
  }
441
337
  if (record && record.at === hit.event.at) {
442
- // Same block, already reported; the file just grew.
443
338
  touched[file] = { ...print, at: record.at, t: now };
444
339
  continue;
445
340
  }
446
341
  pending.push({ file, print, hit });
447
342
  }
448
- // Posted concurrently: each file is independent, and this runs on the
449
- // prompt path where a serial chain of network timeouts would be felt.
450
343
  const results = await Promise.allSettled(pending.map(({ file, hit }) => args.post({
451
344
  source: "codex",
452
345
  ...hit.event,