@ironbee-ai/cli 0.5.2 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +1 -1
  3. package/dist/analysis/cross-session.d.ts +1 -1
  4. package/dist/analysis/cross-session.js +1 -1
  5. package/dist/analysis/cross-session.js.map +1 -1
  6. package/dist/analysis/time-analysis.js +3 -3
  7. package/dist/analysis/time-analysis.js.map +1 -1
  8. package/dist/analysis/verdict-details.d.ts +1 -1
  9. package/dist/clients/claude/hooks/clear-verdict.d.ts.map +1 -1
  10. package/dist/clients/claude/hooks/clear-verdict.js +17 -9
  11. package/dist/clients/claude/hooks/clear-verdict.js.map +1 -1
  12. package/dist/clients/claude/hooks/require-verification.d.ts.map +1 -1
  13. package/dist/clients/claude/hooks/require-verification.js +31 -4
  14. package/dist/clients/claude/hooks/require-verification.js.map +1 -1
  15. package/dist/clients/claude/hooks/session-end.d.ts.map +1 -1
  16. package/dist/clients/claude/hooks/session-end.js +6 -1
  17. package/dist/clients/claude/hooks/session-end.js.map +1 -1
  18. package/dist/clients/claude/hooks/session-start.d.ts.map +1 -1
  19. package/dist/clients/claude/hooks/session-start.js +9 -3
  20. package/dist/clients/claude/hooks/session-start.js.map +1 -1
  21. package/dist/clients/claude/hooks/track-action.d.ts +25 -4
  22. package/dist/clients/claude/hooks/track-action.d.ts.map +1 -1
  23. package/dist/clients/claude/hooks/track-action.js +160 -29
  24. package/dist/clients/claude/hooks/track-action.js.map +1 -1
  25. package/dist/clients/claude/hooks/verify-gate.d.ts.map +1 -1
  26. package/dist/clients/claude/hooks/verify-gate.js +5 -0
  27. package/dist/clients/claude/hooks/verify-gate.js.map +1 -1
  28. package/dist/clients/claude/index.d.ts.map +1 -1
  29. package/dist/clients/claude/index.js +18 -1
  30. package/dist/clients/claude/index.js.map +1 -1
  31. package/dist/clients/claude/util.d.ts +74 -0
  32. package/dist/clients/claude/util.d.ts.map +1 -0
  33. package/dist/clients/claude/util.js +352 -0
  34. package/dist/clients/claude/util.js.map +1 -0
  35. package/dist/clients/cursor/hooks/clear-verdict.d.ts.map +1 -1
  36. package/dist/clients/cursor/hooks/clear-verdict.js +19 -10
  37. package/dist/clients/cursor/hooks/clear-verdict.js.map +1 -1
  38. package/dist/clients/cursor/hooks/require-verification.d.ts.map +1 -1
  39. package/dist/clients/cursor/hooks/require-verification.js +28 -4
  40. package/dist/clients/cursor/hooks/require-verification.js.map +1 -1
  41. package/dist/clients/cursor/hooks/session-end.d.ts.map +1 -1
  42. package/dist/clients/cursor/hooks/session-end.js +6 -1
  43. package/dist/clients/cursor/hooks/session-end.js.map +1 -1
  44. package/dist/clients/cursor/hooks/session-start.d.ts.map +1 -1
  45. package/dist/clients/cursor/hooks/session-start.js +7 -2
  46. package/dist/clients/cursor/hooks/session-start.js.map +1 -1
  47. package/dist/clients/cursor/hooks/track-action.d.ts +30 -8
  48. package/dist/clients/cursor/hooks/track-action.d.ts.map +1 -1
  49. package/dist/clients/cursor/hooks/track-action.js +192 -54
  50. package/dist/clients/cursor/hooks/track-action.js.map +1 -1
  51. package/dist/clients/cursor/hooks/verify-gate.d.ts.map +1 -1
  52. package/dist/clients/cursor/hooks/verify-gate.js +4 -0
  53. package/dist/clients/cursor/hooks/verify-gate.js.map +1 -1
  54. package/dist/clients/cursor/index.d.ts.map +1 -1
  55. package/dist/clients/cursor/index.js +13 -2
  56. package/dist/clients/cursor/index.js.map +1 -1
  57. package/dist/clients/cursor/util.d.ts +52 -0
  58. package/dist/clients/cursor/util.d.ts.map +1 -0
  59. package/dist/clients/cursor/util.js +264 -0
  60. package/dist/clients/cursor/util.js.map +1 -0
  61. package/dist/commands/analyze.js +3 -3
  62. package/dist/commands/analyze.js.map +1 -1
  63. package/dist/commands/hook.js +1 -2
  64. package/dist/commands/hook.js.map +1 -1
  65. package/dist/commands/process-job-file.d.ts +10 -0
  66. package/dist/commands/process-job-file.d.ts.map +1 -0
  67. package/dist/commands/process-job-file.js +19 -0
  68. package/dist/commands/process-job-file.js.map +1 -0
  69. package/dist/commands/queue.d.ts +12 -0
  70. package/dist/commands/queue.d.ts.map +1 -0
  71. package/dist/commands/queue.js +495 -0
  72. package/dist/commands/queue.js.map +1 -0
  73. package/dist/hooks/core/actions.d.ts +168 -27
  74. package/dist/hooks/core/actions.d.ts.map +1 -1
  75. package/dist/hooks/core/actions.js +81 -4
  76. package/dist/hooks/core/actions.js.map +1 -1
  77. package/dist/hooks/core/activity.d.ts.map +1 -1
  78. package/dist/hooks/core/activity.js +13 -6
  79. package/dist/hooks/core/activity.js.map +1 -1
  80. package/dist/hooks/core/session-state.d.ts +19 -7
  81. package/dist/hooks/core/session-state.d.ts.map +1 -1
  82. package/dist/hooks/core/session-state.js +66 -12
  83. package/dist/hooks/core/session-state.js.map +1 -1
  84. package/dist/hooks/core/submit-verdict.d.ts.map +1 -1
  85. package/dist/hooks/core/submit-verdict.js +13 -4
  86. package/dist/hooks/core/submit-verdict.js.map +1 -1
  87. package/dist/hooks/core/verification-lifecycle.d.ts.map +1 -1
  88. package/dist/hooks/core/verification-lifecycle.js +14 -4
  89. package/dist/hooks/core/verification-lifecycle.js.map +1 -1
  90. package/dist/hooks/core/verify-gate.d.ts.map +1 -1
  91. package/dist/hooks/core/verify-gate.js +31 -19
  92. package/dist/hooks/core/verify-gate.js.map +1 -1
  93. package/dist/index.js +12 -3
  94. package/dist/index.js.map +1 -1
  95. package/dist/lib/collector.d.ts +64 -5
  96. package/dist/lib/collector.d.ts.map +1 -1
  97. package/dist/lib/collector.js +107 -44
  98. package/dist/lib/collector.js.map +1 -1
  99. package/dist/lib/config.d.ts +72 -8
  100. package/dist/lib/config.d.ts.map +1 -1
  101. package/dist/lib/config.js +40 -0
  102. package/dist/lib/config.js.map +1 -1
  103. package/dist/queue/dead-letter.d.ts +26 -0
  104. package/dist/queue/dead-letter.d.ts.map +1 -0
  105. package/dist/queue/dead-letter.js +233 -0
  106. package/dist/queue/dead-letter.js.map +1 -0
  107. package/dist/queue/drain.d.ts +36 -0
  108. package/dist/queue/drain.d.ts.map +1 -0
  109. package/dist/queue/drain.js +170 -0
  110. package/dist/queue/drain.js.map +1 -0
  111. package/dist/queue/flush.d.ts +64 -0
  112. package/dist/queue/flush.d.ts.map +1 -0
  113. package/dist/queue/flush.js +119 -0
  114. package/dist/queue/flush.js.map +1 -0
  115. package/dist/queue/handlers/send-event.d.ts +23 -0
  116. package/dist/queue/handlers/send-event.d.ts.map +1 -0
  117. package/dist/queue/handlers/send-event.js +131 -0
  118. package/dist/queue/handlers/send-event.js.map +1 -0
  119. package/dist/queue/index.d.ts +30 -0
  120. package/dist/queue/index.d.ts.map +1 -0
  121. package/dist/queue/index.js +71 -0
  122. package/dist/queue/index.js.map +1 -0
  123. package/dist/queue/paths.d.ts +40 -0
  124. package/dist/queue/paths.d.ts.map +1 -0
  125. package/dist/queue/paths.js +107 -0
  126. package/dist/queue/paths.js.map +1 -0
  127. package/dist/queue/process-file.d.ts +22 -0
  128. package/dist/queue/process-file.d.ts.map +1 -0
  129. package/dist/queue/process-file.js +257 -0
  130. package/dist/queue/process-file.js.map +1 -0
  131. package/dist/queue/register-handlers.d.ts +26 -0
  132. package/dist/queue/register-handlers.d.ts.map +1 -0
  133. package/dist/queue/register-handlers.js +36 -0
  134. package/dist/queue/register-handlers.js.map +1 -0
  135. package/dist/queue/registry.d.ts +42 -0
  136. package/dist/queue/registry.d.ts.map +1 -0
  137. package/dist/queue/registry.js +36 -0
  138. package/dist/queue/registry.js.map +1 -0
  139. package/dist/queue/snapshot.d.ts +16 -0
  140. package/dist/queue/snapshot.d.ts.map +1 -0
  141. package/dist/queue/snapshot.js +39 -0
  142. package/dist/queue/snapshot.js.map +1 -0
  143. package/dist/queue/spawn.d.ts +15 -0
  144. package/dist/queue/spawn.d.ts.map +1 -0
  145. package/dist/queue/spawn.js +45 -0
  146. package/dist/queue/spawn.js.map +1 -0
  147. package/dist/queue/submit.d.ts +19 -0
  148. package/dist/queue/submit.d.ts.map +1 -0
  149. package/dist/queue/submit.js +94 -0
  150. package/dist/queue/submit.js.map +1 -0
  151. package/dist/queue/types.d.ts +77 -0
  152. package/dist/queue/types.d.ts.map +1 -0
  153. package/dist/queue/types.js +83 -0
  154. package/dist/queue/types.js.map +1 -0
  155. package/dist/queue/worker-log.d.ts +21 -0
  156. package/dist/queue/worker-log.d.ts.map +1 -0
  157. package/dist/queue/worker-log.js +140 -0
  158. package/dist/queue/worker-log.js.map +1 -0
  159. package/package.json +1 -1
@@ -0,0 +1,233 @@
1
+ "use strict";
2
+ /**
3
+ * IronBee CLI — Dead Letter Writer + Rotation
4
+ *
5
+ * See docs/job-queue.md §6.1 (entry shape), §6.2 (rotation + retention).
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.deadLetterParsed = deadLetterParsed;
9
+ exports.deadLetterParseError = deadLetterParseError;
10
+ exports.readDeadLetterEntries = readDeadLetterEntries;
11
+ const fs_1 = require("fs");
12
+ const path_1 = require("path");
13
+ const crypto_1 = require("crypto");
14
+ const logger_1 = require("../lib/logger");
15
+ const types_1 = require("./types");
16
+ const paths_1 = require("./paths");
17
+ /**
18
+ * Append a parsed-job dead-letter entry. Enforces the 4 KB line ceiling via a
19
+ * cascade of three truncation fallbacks. The last fallback always fits — it
20
+ * replaces the entire entry with a minimal overflow marker — so the atomic
21
+ * append invariant (§6.1) always holds.
22
+ */
23
+ function deadLetterParsed(input, job, category) {
24
+ const filename = (0, path_1.basename)(input.sourceFile);
25
+ const receivedAt = new Date().toISOString();
26
+ const entryId = (0, crypto_1.randomUUID)();
27
+ // Fallback 0: the happy case — full entry.
28
+ let entry = {
29
+ id: entryId,
30
+ received_at: receivedAt,
31
+ source_file: filename,
32
+ category,
33
+ original: job,
34
+ };
35
+ let line = JSON.stringify(entry) + "\n";
36
+ if (Buffer.byteLength(line, "utf-8") > types_1.MAX_LINE_BYTES) {
37
+ // Fallback 1: drop `original.data`.
38
+ entry = {
39
+ ...entry,
40
+ original: {
41
+ id: job.id,
42
+ type: job.type,
43
+ created_at: job.created_at,
44
+ data: { __truncated__: true },
45
+ },
46
+ };
47
+ line = JSON.stringify(entry) + "\n";
48
+ }
49
+ if (Buffer.byteLength(line, "utf-8") > types_1.MAX_LINE_BYTES) {
50
+ // Fallback 2: also truncate the category string.
51
+ entry = {
52
+ ...entry,
53
+ category: truncateString(category, 256),
54
+ };
55
+ line = JSON.stringify(entry) + "\n";
56
+ }
57
+ if (Buffer.byteLength(line, "utf-8") > types_1.MAX_LINE_BYTES) {
58
+ // Fallback 3: minimal overflow marker. Preserves identity + triage
59
+ // pointer (job id, source_file) but drops everything else.
60
+ const minimal = {
61
+ id: entryId,
62
+ received_at: receivedAt,
63
+ source_file: truncateString(filename, 128),
64
+ category: "overflow",
65
+ original: { id: job.id, type: truncateString(job.type, 64) },
66
+ __truncated__: true,
67
+ };
68
+ line = JSON.stringify(minimal) + "\n";
69
+ // Absolute last resort — if even this exceeds 4 KB (which would
70
+ // require a pathologically long source_file name the OS likely
71
+ // rejected anyway), hard-truncate the byte array and append a "\n".
72
+ if (Buffer.byteLength(line, "utf-8") > types_1.MAX_LINE_BYTES) {
73
+ const raw = Buffer.from(line, "utf-8").subarray(0, types_1.MAX_LINE_BYTES - 1);
74
+ line = raw.toString("utf-8") + "\n";
75
+ }
76
+ }
77
+ writeDeadLetterLine(input.projectDir, input.sessionId, line);
78
+ }
79
+ /**
80
+ * Append a parse-error dead-letter entry. The raw line is truncated to 512
81
+ * chars and ASCII control chars are escaped.
82
+ */
83
+ function deadLetterParseError(input, raw, category) {
84
+ const filename = (0, path_1.basename)(input.sourceFile);
85
+ const sanitized = escapeControls(raw).slice(0, types_1.DEAD_LETTER_RAW_MAX);
86
+ const entry = {
87
+ id: (0, crypto_1.randomUUID)(),
88
+ received_at: new Date().toISOString(),
89
+ source_file: filename,
90
+ category,
91
+ raw: sanitized,
92
+ };
93
+ const line = JSON.stringify(entry) + "\n";
94
+ writeDeadLetterLine(input.projectDir, input.sessionId, line);
95
+ }
96
+ function writeDeadLetterLine(projectDir, sessionId, line) {
97
+ const dir = (0, paths_1.queueDir)(projectDir, sessionId);
98
+ (0, fs_1.mkdirSync)(dir, { recursive: true, mode: 0o700 });
99
+ const path = (0, paths_1.deadLetterFile)(projectDir, sessionId);
100
+ const flags = fs_1.constants.O_CREAT
101
+ | fs_1.constants.O_WRONLY
102
+ | fs_1.constants.O_APPEND
103
+ | (typeof fs_1.constants.O_NOFOLLOW === "number" ? fs_1.constants.O_NOFOLLOW : 0);
104
+ const fd = (0, fs_1.openSync)(path, flags, 0o600);
105
+ try {
106
+ const bytes = Buffer.from(line, "utf-8");
107
+ (0, fs_1.writeSync)(fd, bytes, 0, bytes.length);
108
+ }
109
+ finally {
110
+ try {
111
+ (0, fs_1.closeSync)(fd);
112
+ }
113
+ catch {
114
+ // ignore
115
+ }
116
+ }
117
+ rotateIfNeeded(projectDir, sessionId);
118
+ }
119
+ function rotateIfNeeded(projectDir, sessionId) {
120
+ // Size-only rotation — `stat()` is O(1), appends stay sub-ms even with a
121
+ // near-threshold file. A line-count check would require reading the whole
122
+ // file on every append, which defeats the purpose of append-only writes.
123
+ // Practical dead-letter entries are 400–1500 bytes, so 10 MB naturally
124
+ // bounds line count to ~7k–25k.
125
+ const path = (0, paths_1.deadLetterFile)(projectDir, sessionId);
126
+ let size;
127
+ try {
128
+ size = (0, fs_1.statSync)(path).size;
129
+ }
130
+ catch {
131
+ return;
132
+ }
133
+ if (size <= types_1.DEAD_LETTER_ROTATE_BYTES) {
134
+ return;
135
+ }
136
+ const dir = (0, paths_1.queueDir)(projectDir, sessionId);
137
+ const rotated = (0, path_1.join)(dir, `dead-letter-${(0, crypto_1.randomUUID)()}.jsonl`);
138
+ try {
139
+ (0, fs_1.renameSync)(path, rotated);
140
+ }
141
+ catch (e) {
142
+ const err = e;
143
+ if (err.code === "ENOENT") {
144
+ // another worker already rotated; nothing to do
145
+ return;
146
+ }
147
+ // Keep the writer robust — non-ENOENT failure (EACCES, EIO, EXDEV, …)
148
+ // means the dead-letter file keeps growing past the 10 MB target
149
+ // until the cause is fixed. Surface via debug so the operator has a
150
+ // trace to follow instead of a silent slow-grow.
151
+ logger_1.logger.debug(`dead-letter rotation failed on ${path}: ${err.code ?? err.message}`);
152
+ return;
153
+ }
154
+ enforceRetention(projectDir, sessionId);
155
+ }
156
+ function enforceRetention(projectDir, sessionId) {
157
+ const dir = (0, paths_1.queueDir)(projectDir, sessionId);
158
+ let entries;
159
+ try {
160
+ entries = (0, fs_1.readdirSync)(dir);
161
+ }
162
+ catch {
163
+ return;
164
+ }
165
+ const rotated = [];
166
+ for (const name of entries) {
167
+ if (!paths_1.DEAD_LETTER_ROTATED_REGEX.test(name)) {
168
+ continue;
169
+ }
170
+ const full = (0, path_1.join)(dir, name);
171
+ try {
172
+ const st = (0, fs_1.statSync)(full);
173
+ rotated.push({ path: full, mtimeMs: st.mtimeMs });
174
+ }
175
+ catch {
176
+ // ignore
177
+ }
178
+ }
179
+ if (rotated.length <= types_1.DEAD_LETTER_RETAIN_COUNT) {
180
+ return;
181
+ }
182
+ // sort newest first; unlink anything past the keep count
183
+ rotated.sort((a, b) => b.mtimeMs - a.mtimeMs);
184
+ for (let i = types_1.DEAD_LETTER_RETAIN_COUNT; i < rotated.length; i++) {
185
+ try {
186
+ (0, fs_1.unlinkSync)(rotated[i].path);
187
+ }
188
+ catch {
189
+ // ignore — best-effort
190
+ }
191
+ }
192
+ }
193
+ function escapeControls(s) {
194
+ let out = "";
195
+ for (let i = 0; i < s.length; i++) {
196
+ const code = s.charCodeAt(i);
197
+ if (code < 0x20 || code === 0x7f) {
198
+ out += `\\x${code.toString(16).padStart(2, "0")}`;
199
+ }
200
+ else {
201
+ out += s[i];
202
+ }
203
+ }
204
+ return out;
205
+ }
206
+ function truncateString(s, max) {
207
+ return s.length <= max ? s : s.slice(0, max) + "…";
208
+ }
209
+ /** Exposed for tests and CLI tooling. */
210
+ function readDeadLetterEntries(projectDir, sessionId) {
211
+ const path = (0, paths_1.deadLetterFile)(projectDir, sessionId);
212
+ let content;
213
+ try {
214
+ content = (0, fs_1.readFileSync)(path, "utf-8");
215
+ }
216
+ catch {
217
+ return [];
218
+ }
219
+ const out = [];
220
+ for (const line of content.split("\n")) {
221
+ if (line.length === 0) {
222
+ continue;
223
+ }
224
+ try {
225
+ out.push(JSON.parse(line));
226
+ }
227
+ catch {
228
+ // skip unparseable dead-letter lines — shouldn't happen in practice
229
+ }
230
+ }
231
+ return out;
232
+ }
233
+ //# sourceMappingURL=dead-letter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dead-letter.js","sourceRoot":"","sources":["../../src/queue/dead-letter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * IronBee CLI — Queue drain()
3
+ *
4
+ * See docs/job-queue.md §5.4. Synchronous per-session final-flush.
5
+ * Cleanup touches ONLY the queue/ subdir — never the parent session dir,
6
+ * which is owned by the consumer (actions.jsonl, verdict.json, …).
7
+ */
8
+ import { HandlerRegistry } from "./registry";
9
+ export interface DrainOptions {
10
+ registry?: HandlerRegistry;
11
+ /**
12
+ * Skip snapshot files whose mtime is within the last `skipRecentMs`
13
+ * milliseconds. Used by `flushSynchronously` at SessionEnd to avoid
14
+ * racing with a detached worker that was just spawned by a Stop-class
15
+ * hook (§7.3). Operator-facing `ironbee queue drain` leaves this at 0
16
+ * so it catches every snapshot, including those from a just-crashed
17
+ * worker. Default 0.
18
+ */
19
+ skipRecentMs?: number;
20
+ }
21
+ export interface DrainResult {
22
+ snapshotsProcessed: number;
23
+ snapshotsRemaining: number;
24
+ cleanedQueueDir: boolean;
25
+ /** Snapshots that were skipped because they are too recent (see DrainOptions.skipRecentMs). */
26
+ snapshotsSkippedRecent: number;
27
+ }
28
+ /**
29
+ * Drain one session synchronously:
30
+ * 1. Roll the live file (if any) into a snapshot.
31
+ * 2. Process every snapshot in sorted order.
32
+ * 3. Best-effort cleanup of the queue/ dir if fully empty of undelivered
33
+ * work and triage records.
34
+ */
35
+ export declare function drain(projectDir: string, sessionId: string, opts?: DrainOptions): Promise<DrainResult>;
36
+ //# sourceMappingURL=drain.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"drain.d.ts","sourceRoot":"","sources":["../../src/queue/drain.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAG7C,MAAM,WAAW,YAAY;IACzB,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,WAAW;IACxB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,eAAe,EAAE,OAAO,CAAC;IACzB,+FAA+F;IAC/F,sBAAsB,EAAE,MAAM,CAAC;CAClC;AAED;;;;;;GAMG;AACH,wBAAsB,KAAK,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,CA2C5G"}
@@ -0,0 +1,170 @@
1
+ "use strict";
2
+ /**
3
+ * IronBee CLI — Queue drain()
4
+ *
5
+ * See docs/job-queue.md §5.4. Synchronous per-session final-flush.
6
+ * Cleanup touches ONLY the queue/ subdir — never the parent session dir,
7
+ * which is owned by the consumer (actions.jsonl, verdict.json, …).
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.drain = drain;
11
+ const fs_1 = require("fs");
12
+ const path_1 = require("path");
13
+ const snapshot_1 = require("./snapshot");
14
+ const process_file_1 = require("./process-file");
15
+ const paths_1 = require("./paths");
16
+ /**
17
+ * Drain one session synchronously:
18
+ * 1. Roll the live file (if any) into a snapshot.
19
+ * 2. Process every snapshot in sorted order.
20
+ * 3. Best-effort cleanup of the queue/ dir if fully empty of undelivered
21
+ * work and triage records.
22
+ */
23
+ async function drain(projectDir, sessionId, opts) {
24
+ (0, paths_1.validateSessionId)(sessionId);
25
+ const dir = (0, paths_1.queueDir)(projectDir, sessionId);
26
+ const result = {
27
+ snapshotsProcessed: 0,
28
+ snapshotsRemaining: 0,
29
+ cleanedQueueDir: false,
30
+ snapshotsSkippedRecent: 0,
31
+ };
32
+ if (!(0, fs_1.existsSync)(dir)) {
33
+ return result;
34
+ }
35
+ const skipRecentMs = opts?.skipRecentMs ?? 0;
36
+ // Step 3 of §5.4: roll live file into a snapshot if it exists. This
37
+ // freshly-renamed snapshot has a brand-new mtime, so skipRecentMs would
38
+ // exclude it. Remember its path (if any) and force-process it below.
39
+ let justRolled = null;
40
+ try {
41
+ justRolled = (0, snapshot_1.snapshot)(projectDir, sessionId);
42
+ }
43
+ catch {
44
+ // snapshot() already returns null on ENOENT; any other failure bubbles
45
+ // into the glob below which will skip it. Keep drain() fault-tolerant.
46
+ }
47
+ // Step 4: process every snapshot.
48
+ const { eligible, skipped } = listSnapshots(dir, skipRecentMs, justRolled);
49
+ result.snapshotsSkippedRecent = skipped;
50
+ for (const snap of eligible) {
51
+ try {
52
+ await (0, process_file_1.processFile)(snap, { registry: opts?.registry });
53
+ result.snapshotsProcessed++;
54
+ }
55
+ catch {
56
+ // processFile never throws in normal paths, but be defensive
57
+ }
58
+ }
59
+ // Step 5: cleanup — strictly queue/ only; never the session dir.
60
+ result.snapshotsRemaining = listSnapshots(dir, 0, null).eligible.length;
61
+ result.cleanedQueueDir = tryCleanupQueueDir(projectDir, sessionId);
62
+ return result;
63
+ }
64
+ /**
65
+ * Enumerate snapshot files under `dir`, filtering out those touched within
66
+ * `skipRecentMs` unless they match `forceInclude` (the path we just rolled
67
+ * this drain cycle — it always needs processing even if its mtime is fresh).
68
+ */
69
+ function listSnapshots(dir, skipRecentMs, forceInclude) {
70
+ let entries;
71
+ try {
72
+ entries = (0, fs_1.readdirSync)(dir);
73
+ }
74
+ catch {
75
+ return { eligible: [], skipped: 0 };
76
+ }
77
+ const now = Date.now();
78
+ const eligible = [];
79
+ let skipped = 0;
80
+ for (const name of entries) {
81
+ if (!paths_1.SNAPSHOT_FILENAME_REGEX.test(name)) {
82
+ continue;
83
+ }
84
+ const full = (0, path_1.join)(dir, name);
85
+ if (forceInclude !== null && full === forceInclude) {
86
+ eligible.push(full);
87
+ continue;
88
+ }
89
+ if (skipRecentMs > 0) {
90
+ try {
91
+ const mtime = (0, fs_1.statSync)(full).mtimeMs;
92
+ if (now - mtime < skipRecentMs) {
93
+ skipped++;
94
+ continue;
95
+ }
96
+ }
97
+ catch {
98
+ // stat failed — include and let processFile handle it
99
+ }
100
+ }
101
+ eligible.push(full);
102
+ }
103
+ eligible.sort();
104
+ return { eligible, skipped };
105
+ }
106
+ /**
107
+ * Remove worker.log and empty dead-letter.jsonl, then rmdir queue/ — but
108
+ * only if no undelivered work or non-empty triage record remains.
109
+ * The session directory itself is owned by the consumer; never touched.
110
+ */
111
+ function tryCleanupQueueDir(projectDir, sessionId) {
112
+ const dir = (0, paths_1.queueDir)(projectDir, sessionId);
113
+ let entries;
114
+ try {
115
+ entries = (0, fs_1.readdirSync)(dir);
116
+ }
117
+ catch {
118
+ return false;
119
+ }
120
+ // Any undelivered snapshot, any live file, or any non-empty dead-letter
121
+ // content blocks cleanup.
122
+ for (const name of entries) {
123
+ if (name === "jobs.jsonl") {
124
+ if (fileSize((0, path_1.join)(dir, name)) > 0) {
125
+ return false;
126
+ }
127
+ }
128
+ if (paths_1.SNAPSHOT_FILENAME_REGEX.test(name)) {
129
+ return false;
130
+ }
131
+ if (name === "dead-letter.jsonl" || paths_1.DEAD_LETTER_ROTATED_REGEX.test(name)) {
132
+ if (fileSize((0, path_1.join)(dir, name)) > 0) {
133
+ return false;
134
+ }
135
+ }
136
+ }
137
+ // Safe to cleanup. Remove discardable files first.
138
+ for (const name of entries) {
139
+ if (name === "jobs.jsonl"
140
+ || name === "dead-letter.jsonl"
141
+ || paths_1.DEAD_LETTER_ROTATED_REGEX.test(name)
142
+ || name === "worker.log"
143
+ || paths_1.WORKER_LOG_ROTATED_REGEX.test(name)) {
144
+ try {
145
+ (0, fs_1.unlinkSync)((0, path_1.join)(dir, name));
146
+ }
147
+ catch {
148
+ // best-effort
149
+ }
150
+ }
151
+ }
152
+ // Attempt rmdir queue/ — fails silently if other (unknown) files remain,
153
+ // which is the safe thing to do.
154
+ try {
155
+ (0, fs_1.rmdirSync)(dir);
156
+ return true;
157
+ }
158
+ catch {
159
+ return false;
160
+ }
161
+ }
162
+ function fileSize(path) {
163
+ try {
164
+ return (0, fs_1.statSync)(path).size;
165
+ }
166
+ catch {
167
+ return 0;
168
+ }
169
+ }
170
+ //# sourceMappingURL=drain.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"drain.js","sourceRoot":"","sources":["../../src/queue/drain.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * IronBee CLI — Queue flush helpers
3
+ *
4
+ * Per-session flush triggers for consumer hooks. Two modes, to be chosen per
5
+ * §5.6 of docs/job-queue.md:
6
+ *
7
+ * - flushInBackground() — used by Stop-class hooks (active session).
8
+ * snapshot() the live file and fire-and-forget a
9
+ * detached worker on it. Returns in <10 ms.
10
+ *
11
+ * - flushSynchronously() — used by SessionEnd-class hooks (consumer is
12
+ * about to exit). Synchronously drain() every
13
+ * snapshot. May block for milliseconds to low
14
+ * seconds.
15
+ *
16
+ * Both helpers are fail-safe: any thrown error is swallowed and debug-logged
17
+ * so a queue fault cannot break the hook.
18
+ */
19
+ import { DrainOptions, DrainResult } from "./drain";
20
+ /** Default threshold for `maybeAutoFlush` when unset in config (32 KB). */
21
+ export declare const DEFAULT_AUTO_FLUSH_SIZE_BYTES: number;
22
+ /**
23
+ * Stop-class hooks use this: snapshot the live queue and launch a detached
24
+ * worker. The caller's process may exit immediately after — the worker's
25
+ * `child.unref()` detaches it from the parent's event loop and process group
26
+ * to the extent the platform allows.
27
+ *
28
+ * Behaviour when queue has no work: snapshot() returns null (no live file),
29
+ * no worker spawned. No-op when the queue module has no producer yet.
30
+ */
31
+ export declare function flushInBackground(projectDir: string, sessionId: string): void;
32
+ /**
33
+ * Producer-side auto-flush trigger. Called from `submit()` after a successful
34
+ * append. If the live `jobs.jsonl` has grown to at least the configured
35
+ * threshold (`jobQueue.autoFlushSizeBytes`, default 32 KB), snapshot it and
36
+ * spawn a detached worker in the background — same mechanism as a Stop hook,
37
+ * just triggered mid-session by queue growth instead of by a lifecycle event.
38
+ *
39
+ * Complements Stop / SessionEnd flushes: provides checkpointing for turns
40
+ * that emit many tool_call events (so collector sees them sooner) and caps
41
+ * worst-case live file size on abandoned sessions.
42
+ *
43
+ * Fail-safe: config read errors, stat failures, and flushInBackground
44
+ * exceptions are all swallowed so a queue fault can never break submit().
45
+ *
46
+ * Threshold <= 0 disables the feature (only lifecycle flushes remain).
47
+ */
48
+ export declare function maybeAutoFlush(projectDir: string, sessionId: string): void;
49
+ /** Default recent-skip window used by `flushSynchronously` (see §7.3). */
50
+ export declare const SESSION_END_SKIP_RECENT_MS: number;
51
+ /**
52
+ * SessionEnd-class hooks use this: synchronously drain every snapshot for the
53
+ * session, then (if possible) clean up the queue/ subdir. Consumer owns the
54
+ * session dir itself — drain never touches anything outside queue/.
55
+ *
56
+ * By default skips snapshot files modified in the last 2 seconds — this is
57
+ * the "in-flight detached worker" window opened by a Stop hook firing right
58
+ * before SessionEnd (§7.3). Override with `opts.skipRecentMs = 0` if you
59
+ * want to treat recent snapshots as orphans (e.g. from tests). Skipped
60
+ * snapshots remain on disk and are drained by the next `ironbee queue drain`
61
+ * invocation.
62
+ */
63
+ export declare function flushSynchronously(projectDir: string, sessionId: string, opts?: DrainOptions): Promise<DrainResult>;
64
+ //# sourceMappingURL=flush.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flush.d.ts","sourceRoot":"","sources":["../../src/queue/flush.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAKH,OAAO,EAAE,YAAY,EAAE,WAAW,EAAS,MAAM,SAAS,CAAC;AAK3D,2EAA2E;AAC3E,eAAO,MAAM,6BAA6B,EAAE,MAAkB,CAAC;AAE/D;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAU7E;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAkB1E;AAED,0EAA0E;AAC1E,eAAO,MAAM,0BAA0B,EAAE,MAAa,CAAC;AAEvD;;;;;;;;;;;GAWG;AACH,wBAAsB,kBAAkB,CACpC,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,IAAI,CAAC,EAAE,YAAY,GACpB,OAAO,CAAC,WAAW,CAAC,CAYtB"}
@@ -0,0 +1,119 @@
1
+ "use strict";
2
+ /**
3
+ * IronBee CLI — Queue flush helpers
4
+ *
5
+ * Per-session flush triggers for consumer hooks. Two modes, to be chosen per
6
+ * §5.6 of docs/job-queue.md:
7
+ *
8
+ * - flushInBackground() — used by Stop-class hooks (active session).
9
+ * snapshot() the live file and fire-and-forget a
10
+ * detached worker on it. Returns in <10 ms.
11
+ *
12
+ * - flushSynchronously() — used by SessionEnd-class hooks (consumer is
13
+ * about to exit). Synchronously drain() every
14
+ * snapshot. May block for milliseconds to low
15
+ * seconds.
16
+ *
17
+ * Both helpers are fail-safe: any thrown error is swallowed and debug-logged
18
+ * so a queue fault cannot break the hook.
19
+ */
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.SESSION_END_SKIP_RECENT_MS = exports.DEFAULT_AUTO_FLUSH_SIZE_BYTES = void 0;
22
+ exports.flushInBackground = flushInBackground;
23
+ exports.maybeAutoFlush = maybeAutoFlush;
24
+ exports.flushSynchronously = flushSynchronously;
25
+ const fs_1 = require("fs");
26
+ const config_1 = require("../lib/config");
27
+ const logger_1 = require("../lib/logger");
28
+ const drain_1 = require("./drain");
29
+ const paths_1 = require("./paths");
30
+ const snapshot_1 = require("./snapshot");
31
+ const spawn_1 = require("./spawn");
32
+ /** Default threshold for `maybeAutoFlush` when unset in config (32 KB). */
33
+ exports.DEFAULT_AUTO_FLUSH_SIZE_BYTES = 32 * 1024;
34
+ /**
35
+ * Stop-class hooks use this: snapshot the live queue and launch a detached
36
+ * worker. The caller's process may exit immediately after — the worker's
37
+ * `child.unref()` detaches it from the parent's event loop and process group
38
+ * to the extent the platform allows.
39
+ *
40
+ * Behaviour when queue has no work: snapshot() returns null (no live file),
41
+ * no worker spawned. No-op when the queue module has no producer yet.
42
+ */
43
+ function flushInBackground(projectDir, sessionId) {
44
+ try {
45
+ (0, paths_1.validateSessionId)(sessionId);
46
+ const snap = (0, snapshot_1.snapshot)(projectDir, sessionId);
47
+ if (snap !== null) {
48
+ (0, spawn_1.spawnDetachedWorker)(snap);
49
+ }
50
+ }
51
+ catch (e) {
52
+ logger_1.logger.debug(`queue flushInBackground failed: ${e instanceof Error ? e.message : e}`);
53
+ }
54
+ }
55
+ /**
56
+ * Producer-side auto-flush trigger. Called from `submit()` after a successful
57
+ * append. If the live `jobs.jsonl` has grown to at least the configured
58
+ * threshold (`jobQueue.autoFlushSizeBytes`, default 32 KB), snapshot it and
59
+ * spawn a detached worker in the background — same mechanism as a Stop hook,
60
+ * just triggered mid-session by queue growth instead of by a lifecycle event.
61
+ *
62
+ * Complements Stop / SessionEnd flushes: provides checkpointing for turns
63
+ * that emit many tool_call events (so collector sees them sooner) and caps
64
+ * worst-case live file size on abandoned sessions.
65
+ *
66
+ * Fail-safe: config read errors, stat failures, and flushInBackground
67
+ * exceptions are all swallowed so a queue fault can never break submit().
68
+ *
69
+ * Threshold <= 0 disables the feature (only lifecycle flushes remain).
70
+ */
71
+ function maybeAutoFlush(projectDir, sessionId) {
72
+ try {
73
+ const config = (0, config_1.loadConfig)(projectDir);
74
+ const raw = config.jobQueue?.autoFlushSizeBytes;
75
+ const threshold = typeof raw === "number" ? raw : exports.DEFAULT_AUTO_FLUSH_SIZE_BYTES;
76
+ if (threshold <= 0) {
77
+ return;
78
+ }
79
+ const livePath = (0, paths_1.liveQueueFile)(projectDir, sessionId);
80
+ const size = (0, fs_1.statSync)(livePath).size;
81
+ if (size >= threshold) {
82
+ flushInBackground(projectDir, sessionId);
83
+ }
84
+ }
85
+ catch (e) {
86
+ // ENOENT on stat (file rolled between submit and stat) or any other
87
+ // fault must not propagate — submit() already persisted the line.
88
+ logger_1.logger.debug(`queue maybeAutoFlush skipped: ${e instanceof Error ? e.message : e}`);
89
+ }
90
+ }
91
+ /** Default recent-skip window used by `flushSynchronously` (see §7.3). */
92
+ exports.SESSION_END_SKIP_RECENT_MS = 2000;
93
+ /**
94
+ * SessionEnd-class hooks use this: synchronously drain every snapshot for the
95
+ * session, then (if possible) clean up the queue/ subdir. Consumer owns the
96
+ * session dir itself — drain never touches anything outside queue/.
97
+ *
98
+ * By default skips snapshot files modified in the last 2 seconds — this is
99
+ * the "in-flight detached worker" window opened by a Stop hook firing right
100
+ * before SessionEnd (§7.3). Override with `opts.skipRecentMs = 0` if you
101
+ * want to treat recent snapshots as orphans (e.g. from tests). Skipped
102
+ * snapshots remain on disk and are drained by the next `ironbee queue drain`
103
+ * invocation.
104
+ */
105
+ async function flushSynchronously(projectDir, sessionId, opts) {
106
+ try {
107
+ (0, paths_1.validateSessionId)(sessionId);
108
+ const merged = {
109
+ ...opts,
110
+ skipRecentMs: opts?.skipRecentMs ?? exports.SESSION_END_SKIP_RECENT_MS,
111
+ };
112
+ return await (0, drain_1.drain)(projectDir, sessionId, merged);
113
+ }
114
+ catch (e) {
115
+ logger_1.logger.debug(`queue flushSynchronously failed: ${e instanceof Error ? e.message : e}`);
116
+ return { snapshotsProcessed: 0, snapshotsRemaining: 0, cleanedQueueDir: false, snapshotsSkippedRecent: 0 };
117
+ }
118
+ }
119
+ //# sourceMappingURL=flush.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flush.js","sourceRoot":"","sources":["../../src/queue/flush.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;GAiBG;;;AAsBH,8CAUC;AAkBD,wCAkBC;AAiBD,gDAgBC;AAnGD,2BAA8B;AAC9B,0CAA0D;AAC1D,0CAAuC;AACvC,mCAA2D;AAC3D,mCAA2D;AAC3D,yCAAsC;AACtC,mCAA8C;AAE9C,2EAA2E;AAC9D,QAAA,6BAA6B,GAAW,EAAE,GAAG,IAAI,CAAC;AAE/D;;;;;;;;GAQG;AACH,SAAgB,iBAAiB,CAAC,UAAkB,EAAE,SAAiB;IACnE,IAAI,CAAC;QACD,IAAA,yBAAiB,EAAC,SAAS,CAAC,CAAC;QAC7B,MAAM,IAAI,GAAkB,IAAA,mBAAQ,EAAC,UAAU,EAAE,SAAS,CAAC,CAAC;QAC5D,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;YAChB,IAAA,2BAAmB,EAAC,IAAI,CAAC,CAAC;QAC9B,CAAC;IACL,CAAC;IAAC,OAAO,CAAU,EAAE,CAAC;QAClB,eAAM,CAAC,KAAK,CAAC,mCAAmC,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAC1F,CAAC;AACL,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,SAAgB,cAAc,CAAC,UAAkB,EAAE,SAAiB;IAChE,IAAI,CAAC;QACD,MAAM,MAAM,GAAkB,IAAA,mBAAU,EAAC,UAAU,CAAC,CAAC;QACrD,MAAM,GAAG,GAAuB,MAAM,CAAC,QAAQ,EAAE,kBAAkB,CAAC;QACpE,MAAM,SAAS,GAAW,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,qCAA6B,CAAC;QACxF,IAAI,SAAS,IAAI,CAAC,EAAE,CAAC;YACjB,OAAO;QACX,CAAC;QACD,MAAM,QAAQ,GAAW,IAAA,qBAAa,EAAC,UAAU,EAAE,SAAS,CAAC,CAAC;QAC9D,MAAM,IAAI,GAAW,IAAA,aAAQ,EAAC,QAAQ,CAAC,CAAC,IAAI,CAAC;QAC7C,IAAI,IAAI,IAAI,SAAS,EAAE,CAAC;YACpB,iBAAiB,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;QAC7C,CAAC;IACL,CAAC;IAAC,OAAO,CAAU,EAAE,CAAC;QAClB,oEAAoE;QACpE,kEAAkE;QAClE,eAAM,CAAC,KAAK,CAAC,iCAAiC,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACxF,CAAC;AACL,CAAC;AAED,0EAA0E;AAC7D,QAAA,0BAA0B,GAAW,IAAI,CAAC;AAEvD;;;;;;;;;;;GAWG;AACI,KAAK,UAAU,kBAAkB,CACpC,UAAkB,EAClB,SAAiB,EACjB,IAAmB;IAEnB,IAAI,CAAC;QACD,IAAA,yBAAiB,EAAC,SAAS,CAAC,CAAC;QAC7B,MAAM,MAAM,GAAiB;YACzB,GAAG,IAAI;YACP,YAAY,EAAE,IAAI,EAAE,YAAY,IAAI,kCAA0B;SACjE,CAAC;QACF,OAAO,MAAM,IAAA,aAAK,EAAC,UAAU,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC;IACtD,CAAC;IAAC,OAAO,CAAU,EAAE,CAAC;QAClB,eAAM,CAAC,KAAK,CAAC,oCAAoC,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACvF,OAAO,EAAE,kBAAkB,EAAE,CAAC,EAAE,kBAAkB,EAAE,CAAC,EAAE,eAAe,EAAE,KAAK,EAAE,sBAAsB,EAAE,CAAC,EAAE,CAAC;IAC/G,CAAC;AACL,CAAC"}