@wrongstack/core 0.310.1 → 0.313.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.
Files changed (187) hide show
  1. package/dist/chronicle/index.js +84 -6391
  2. package/dist/chronicle/project-server.js +84 -4868
  3. package/dist/chunk-2BUUIMQB.js +225 -0
  4. package/dist/chunk-2FK4WTFF.js +207 -0
  5. package/dist/chunk-2FQL4PDC.js +292 -0
  6. package/dist/chunk-2KKODIQU.js +2256 -0
  7. package/dist/chunk-2YU4WVBW.js +134 -0
  8. package/dist/chunk-3E244UDA.js +356 -0
  9. package/dist/chunk-3XPVB7BA.js +135 -0
  10. package/dist/chunk-4LJJYBAU.js +388 -0
  11. package/dist/chunk-4VWO3ZXL.js +342 -0
  12. package/dist/chunk-4XV6IR37.js +187 -0
  13. package/dist/chunk-4ZHTKJHM.js +183 -0
  14. package/dist/chunk-52RTVH4M.js +361 -0
  15. package/dist/chunk-57GEWK5X.js +26 -0
  16. package/dist/chunk-5DSMEVA4.js +931 -0
  17. package/dist/chunk-5RP3QTRL.js +1223 -0
  18. package/dist/chunk-5S437PRC.js +50 -0
  19. package/dist/chunk-5XDUGTKN.js +511 -0
  20. package/dist/chunk-62V2AFVG.js +422 -0
  21. package/dist/chunk-65RDIOR2.js +2470 -0
  22. package/dist/chunk-6AKOTGLM.js +597 -0
  23. package/dist/chunk-7ZHZE5EO.js +28 -0
  24. package/dist/chunk-A455ATZB.js +140 -0
  25. package/dist/chunk-AD7ZFZCS.js +46 -0
  26. package/dist/chunk-AI4LLQ2U.js +856 -0
  27. package/dist/chunk-ASX6AW36.js +789 -0
  28. package/dist/chunk-AT4V4XGI.js +3522 -0
  29. package/dist/chunk-AWNQ6ED6.js +311 -0
  30. package/dist/chunk-B4AMLZPB.js +9 -0
  31. package/dist/chunk-BAKVJLEV.js +417 -0
  32. package/dist/chunk-BJHVULHO.js +55 -0
  33. package/dist/chunk-CB2HFKTP.js +9 -0
  34. package/dist/chunk-CE4ZOCOI.js +1640 -0
  35. package/dist/chunk-CQQL4ZOQ.js +151 -0
  36. package/dist/chunk-DEIQMBD2.js +65 -0
  37. package/dist/chunk-E6I5RXVK.js +58 -0
  38. package/dist/chunk-EFEHDQGY.js +1589 -0
  39. package/dist/chunk-EODDVJBM.js +82 -0
  40. package/dist/chunk-EPC3GZD4.js +308 -0
  41. package/dist/chunk-FAYRX2AD.js +13 -0
  42. package/dist/chunk-FFSX2QDQ.js +362 -0
  43. package/dist/chunk-FHAMQS7Q.js +5856 -0
  44. package/dist/chunk-FIV4IJL7.js +2263 -0
  45. package/dist/chunk-FNXVT7HF.js +52 -0
  46. package/dist/chunk-FSWCWSTS.js +395 -0
  47. package/dist/chunk-FW5LDDMJ.js +16464 -0
  48. package/dist/chunk-FXQLIX3K.js +211 -0
  49. package/dist/chunk-G5AAKEEZ.js +228 -0
  50. package/dist/chunk-G5S3S5ZN.js +38 -0
  51. package/dist/chunk-GAZEWQEZ.js +231 -0
  52. package/dist/chunk-GFJOMSHG.js +125 -0
  53. package/dist/chunk-GFXZTHNI.js +158 -0
  54. package/dist/chunk-GM54GJBU.js +960 -0
  55. package/dist/chunk-GPQ3NJPR.js +900 -0
  56. package/dist/chunk-GTCU6DZ3.js +1 -0
  57. package/dist/chunk-H5E77KUJ.js +15 -0
  58. package/dist/chunk-H72H4XDU.js +1023 -0
  59. package/dist/chunk-HA4ZYA6T.js +178 -0
  60. package/dist/chunk-I45KAW2N.js +5020 -0
  61. package/dist/chunk-IQ35HMQL.js +1396 -0
  62. package/dist/chunk-IYFBFX34.js +86 -0
  63. package/dist/chunk-JJPMWPB6.js +109 -0
  64. package/dist/chunk-JTHKOYWU.js +663 -0
  65. package/dist/chunk-JVGPR3TS.js +365 -0
  66. package/dist/chunk-JVKB2TBL.js +48 -0
  67. package/dist/chunk-JX7VBZDK.js +31 -0
  68. package/dist/chunk-K3G6JBUC.js +5232 -0
  69. package/dist/chunk-K4YUNKU7.js +589 -0
  70. package/dist/chunk-KBZGE6LN.js +710 -0
  71. package/dist/chunk-KDQJCT2J.js +45 -0
  72. package/dist/chunk-KI4L2DUL.js +560 -0
  73. package/dist/chunk-KJ2ZTQSL.js +47 -0
  74. package/dist/chunk-KSJ5UTP7.js +154 -0
  75. package/dist/chunk-LHOA5DWA.js +28 -0
  76. package/dist/chunk-LX5QWLBL.js +27 -0
  77. package/dist/chunk-LZDDLNCZ.js +279 -0
  78. package/dist/chunk-M2WF6AXG.js +115 -0
  79. package/dist/chunk-M7BS2K6U.js +1 -0
  80. package/dist/chunk-NDBIPFNY.js +102 -0
  81. package/dist/chunk-NEMIWEXC.js +1199 -0
  82. package/dist/chunk-NFAYHPRH.js +323 -0
  83. package/dist/chunk-O37BQ65S.js +21 -0
  84. package/dist/chunk-O74MJHXU.js +187 -0
  85. package/dist/chunk-OJZEX73F.js +112 -0
  86. package/dist/chunk-PAAWLLZX.js +74 -0
  87. package/dist/chunk-PFXFQQRT.js +339 -0
  88. package/dist/chunk-PFZO3C6H.js +14 -0
  89. package/dist/chunk-PHH7LD45.js +113 -0
  90. package/dist/chunk-QRTG4XTI.js +188 -0
  91. package/dist/chunk-QVOVMXJR.js +1351 -0
  92. package/dist/chunk-RCQYT2I4.js +757 -0
  93. package/dist/chunk-RD6DEE36.js +1178 -0
  94. package/dist/chunk-RJIZEJ42.js +30 -0
  95. package/dist/chunk-RPOA7IAE.js +150 -0
  96. package/dist/chunk-SN2DQRL6.js +1172 -0
  97. package/dist/chunk-SQJVFEBV.js +41 -0
  98. package/dist/chunk-SQP64RCS.js +5391 -0
  99. package/dist/chunk-SVT4RNTR.js +49 -0
  100. package/dist/chunk-SWUFN7XM.js +43 -0
  101. package/dist/chunk-SXMVYC5H.js +174 -0
  102. package/dist/chunk-SZ6AYUIZ.js +23 -0
  103. package/dist/chunk-T3KGZBU5.js +3171 -0
  104. package/dist/chunk-TBRL2RCC.js +9 -0
  105. package/dist/chunk-TRT2HRWZ.js +18 -0
  106. package/dist/chunk-TYW2GI5I.js +1077 -0
  107. package/dist/chunk-UIWONWVG.js +4959 -0
  108. package/dist/chunk-VB3H5WWB.js +151 -0
  109. package/dist/chunk-W4A6QA3D.js +258 -0
  110. package/dist/chunk-WBPVKEEE.js +227 -0
  111. package/dist/chunk-WEBMSQP2.js +123 -0
  112. package/dist/chunk-WOEZV3Q4.js +333 -0
  113. package/dist/chunk-WQQON3SV.js +473 -0
  114. package/dist/chunk-WQTOVMGV.js +119 -0
  115. package/dist/chunk-WZO23AWO.js +2779 -0
  116. package/dist/chunk-XBDP4QPW.js +7 -0
  117. package/dist/chunk-XEBHSJLT.js +103 -0
  118. package/dist/chunk-XQ2ECNK3.js +1 -0
  119. package/dist/chunk-XQ2XGIBF.js +2086 -0
  120. package/dist/chunk-Y2GE5TSI.js +355 -0
  121. package/dist/chunk-YBUJCZ57.js +351 -0
  122. package/dist/chunk-YNCVSFGF.js +149 -0
  123. package/dist/chunk-YUPMXOGP.js +282 -0
  124. package/dist/chunk-YY6APYUM.js +49 -0
  125. package/dist/chunk-Z2IJUO45.js +691 -0
  126. package/dist/chunk-ZEHUCJHY.js +129 -0
  127. package/dist/chunk-ZYU2YOJ6.js +1496 -0
  128. package/dist/chunk-ZZ4V3WIZ.js +163 -0
  129. package/dist/coordination/agents/index.js +145 -6260
  130. package/dist/coordination/index.js +365 -34323
  131. package/dist/coordination/mailbox-project-server.js +48 -3220
  132. package/dist/coordination/mailbox-types.d.ts +13 -1
  133. package/dist/coordination/provider-status-tracker.d.ts +4 -0
  134. package/dist/coordination/sqlite-mailbox.d.ts +2 -1
  135. package/dist/core/agent.d.ts +14 -1
  136. package/dist/core/index.js +267 -12479
  137. package/dist/core/model-ref.js +5 -23
  138. package/dist/core/provider-runner.d.ts +36 -1
  139. package/dist/design/index.js +50 -1087
  140. package/dist/execution/index.d.ts +1 -1
  141. package/dist/execution/index.js +1599 -21887
  142. package/dist/execution/prompt-enhancer.js +16 -535
  143. package/dist/execution/retry-policy.d.ts +31 -1
  144. package/dist/extension/index.js +7 -311
  145. package/dist/goal/index.js +33 -3435
  146. package/dist/hooks/index.js +14 -1138
  147. package/dist/hq/index.js +112 -5139
  148. package/dist/hq/protocol.js +21 -668
  149. package/dist/index.js +1410 -92655
  150. package/dist/infrastructure/index.js +64 -4811
  151. package/dist/kernel/events/provider-events.d.ts +15 -0
  152. package/dist/kernel/events/wrongtrace-events.d.ts +26 -0
  153. package/dist/kernel/events.d.ts +16 -7
  154. package/dist/kernel/index.js +14 -992
  155. package/dist/models/index.js +40 -1117
  156. package/dist/notifications/index.js +3 -108
  157. package/dist/observability/index.js +17 -279
  158. package/dist/plugin/index.js +584 -14835
  159. package/dist/prompts/index.js +14 -948
  160. package/dist/registry/index.js +9 -822
  161. package/dist/registry-R23UBE7H.js +14 -0
  162. package/dist/replay/index.js +6 -84
  163. package/dist/review-finding-store-DKAV3AS6.js +21 -0
  164. package/dist/review-report-store-OWY3EC3N.js +19 -0
  165. package/dist/security/index.js +114 -2202
  166. package/dist/session-catalog/index.js +36 -2103
  167. package/dist/session-catalog/project-server.js +42 -1435
  168. package/dist/skills/index.js +35 -1578
  169. package/dist/storage/index.js +182 -15600
  170. package/dist/tasking/index.js +10 -676
  171. package/dist/tools/index.js +52 -9589
  172. package/dist/types/config/autonomy.d.ts +5 -0
  173. package/dist/types/config/tools.d.ts +2 -2
  174. package/dist/types/config/ui.d.ts +1 -1
  175. package/dist/types/errors.d.ts +18 -0
  176. package/dist/types/index.js +121 -1341
  177. package/dist/types/provider-runner.d.ts +8 -0
  178. package/dist/types/session-markers.js +8 -144
  179. package/dist/utils/error.js +3 -4
  180. package/dist/utils/expect-defined.js +3 -9
  181. package/dist/utils/heap-watchdog.js +6 -706
  182. package/dist/utils/index.js +269 -5685
  183. package/dist/utils/sage-output-block.js +5 -42
  184. package/dist/wiring/proxy-rewrite.d.ts +92 -4
  185. package/dist/wiring/proxy-rewrite.js +20 -61
  186. package/dist/worktree/index.js +6 -887
  187. package/package.json +4 -4
@@ -0,0 +1,789 @@
1
+ import {
2
+ CHRONICLE_MAX_APPEND_BATCH,
3
+ CHRONICLE_PROJECT_SERVER_MAX_FRAME_CHARS,
4
+ CHRONICLE_PROJECT_SERVER_PROTOCOL_VERSION,
5
+ ChronicleJournal,
6
+ ChronicleMetricsStore,
7
+ ChronicleQueryEngine,
8
+ ChronicleSqliteJournal,
9
+ chronicleProjectServerEndpoint,
10
+ chronicleProjectServerMetadataPath,
11
+ encodeChronicleProjectServerMessage,
12
+ importLegacyChronicleJournal,
13
+ resolveChronicleRuntimeLocation
14
+ } from "./chunk-AT4V4XGI.js";
15
+ import {
16
+ resolveWstackPaths
17
+ } from "./chunk-YNCVSFGF.js";
18
+
19
+ // src/chronicle/project-server-client.ts
20
+ import { spawn } from "node:child_process";
21
+ import * as fs from "node:fs";
22
+ import * as net from "node:net";
23
+ import * as path from "node:path";
24
+ import { fileURLToPath } from "node:url";
25
+ var CONNECT_ATTEMPT_TIMEOUT_MS = 750;
26
+ var SERVER_START_TIMEOUT_MS = 1e4;
27
+ var DEFAULT_CALL_TIMEOUT_MS = 3e4;
28
+ var APPEND_CALL_TIMEOUT_MS = 6e4;
29
+ var DEFAULT_BATCH_WINDOW_MS = 5;
30
+ var DEFAULT_MAX_PENDING = 1e5;
31
+ function resolveChronicleDaemonAvailability(moduleUrl = import.meta.url, exists = fs.existsSync) {
32
+ if (process.env["WRONGSTACK_CHRONICLE_INLINE"] || process.env["WRONGSTACK_CHRONICLE_SERVER"] === "0") {
33
+ return { kind: "inline-requested" };
34
+ }
35
+ const url = locateChronicleProjectServer(moduleUrl, exists);
36
+ return url ? { kind: "available", url } : { kind: "missing-build" };
37
+ }
38
+ function resolveChronicleProjectServerUrl(moduleUrl = import.meta.url, exists = fs.existsSync) {
39
+ const availability = resolveChronicleDaemonAvailability(moduleUrl, exists);
40
+ return availability.kind === "available" ? availability.url : null;
41
+ }
42
+ function locateChronicleProjectServer(moduleUrl, exists) {
43
+ const candidates = [
44
+ // Normal unbundled output: dist/chronicle/project-server-client.js.
45
+ "./project-server.js",
46
+ // Core plugin bundle: dist/plugin/index.js.
47
+ "../chronicle/project-server.js",
48
+ // Core root bundle: dist/index.js.
49
+ "./chronicle/project-server.js"
50
+ ];
51
+ for (const relativePath of candidates) {
52
+ try {
53
+ const url = new URL(relativePath, moduleUrl);
54
+ if (url.protocol === "file:" && exists(fileURLToPath(url))) return url;
55
+ } catch {
56
+ }
57
+ }
58
+ return null;
59
+ }
60
+ function isChronicleProjectServerAvailable() {
61
+ return resolveChronicleProjectServerUrl() !== null;
62
+ }
63
+ function delay(ms) {
64
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
65
+ }
66
+ var ChronicleProjectServerClient = class {
67
+ constructor(options) {
68
+ this.options = options;
69
+ this.endpoint = chronicleProjectServerEndpoint(options.projectDir);
70
+ }
71
+ options;
72
+ endpoint;
73
+ socket = null;
74
+ info = null;
75
+ buffer = "";
76
+ connecting = null;
77
+ connectResolve = null;
78
+ connectReject = null;
79
+ nextId = 1;
80
+ /** WS-027: read from the owner-only metadata file — see currentAuthToken. */
81
+ authToken;
82
+ pending = /* @__PURE__ */ new Map();
83
+ async call(op, args, options = {}) {
84
+ await this.ensureConnected(true);
85
+ return this.request(
86
+ { type: "request", op, args },
87
+ options.timeoutMs ?? DEFAULT_CALL_TIMEOUT_MS
88
+ );
89
+ }
90
+ ping() {
91
+ return this.call("ping", {}, { timeoutMs: 3e3 });
92
+ }
93
+ /**
94
+ * Request the Chronicle project server to shut down cleanly.
95
+ *
96
+ * Returns the server PID and acknowledgment. Does not throw if the server
97
+ * is already offline or unreachable — the caller is assumed to want the
98
+ * server stopped regardless of current state.
99
+ */
100
+ async shutdown(reason) {
101
+ try {
102
+ await this.ensureConnected(false);
103
+ } catch {
104
+ return { stopped: false, reason: "offline" };
105
+ }
106
+ const pid = this.info?.pid;
107
+ try {
108
+ const result = await this.request(
109
+ { type: "shutdown", reason },
110
+ 5e3
111
+ ) ?? { stopped: true };
112
+ return { ...result, ...pid !== void 0 && result.pid === void 0 ? { pid } : {} };
113
+ } catch {
114
+ return { stopped: false, ...pid !== void 0 ? { pid } : {}, reason: "write-failed" };
115
+ }
116
+ }
117
+ async close() {
118
+ const socket = this.socket;
119
+ this.socket = null;
120
+ this.info = null;
121
+ if (socket && !socket.destroyed) {
122
+ await new Promise((resolve3) => {
123
+ socket.once("close", resolve3);
124
+ socket.end();
125
+ });
126
+ }
127
+ }
128
+ async ensureConnected(spawnIfMissing) {
129
+ if (this.socket && !this.socket.destroyed && this.info) return;
130
+ if (this.connecting) return this.connecting;
131
+ if (!isChronicleProjectServerAvailable()) {
132
+ throw new Error(
133
+ "Built Chronicle project server is unavailable. Build @wrongstack/core or use the inline journal."
134
+ );
135
+ }
136
+ this.connecting = this.connectWithElection(spawnIfMissing).finally(() => {
137
+ this.connecting = null;
138
+ });
139
+ return this.connecting;
140
+ }
141
+ async connectWithElection(spawnIfMissing) {
142
+ const deadline = Date.now() + (spawnIfMissing ? SERVER_START_TIMEOUT_MS : CONNECT_ATTEMPT_TIMEOUT_MS);
143
+ let spawned = false;
144
+ let lastError = new Error("Chronicle project server unavailable");
145
+ while (Date.now() < deadline) {
146
+ try {
147
+ await this.connectOnce();
148
+ return;
149
+ } catch (error) {
150
+ lastError = error;
151
+ }
152
+ if (!spawnIfMissing) break;
153
+ if (!spawned) {
154
+ this.spawnDetachedServer();
155
+ spawned = true;
156
+ }
157
+ await delay(75);
158
+ }
159
+ throw lastError;
160
+ }
161
+ connectOnce() {
162
+ this.socket?.destroy();
163
+ this.socket = null;
164
+ this.authToken = void 0;
165
+ this.info = null;
166
+ this.buffer = "";
167
+ return new Promise((resolve3, reject) => {
168
+ const socket = net.createConnection(this.endpoint);
169
+ this.socket = socket;
170
+ socket.setEncoding("utf8");
171
+ const timer = setTimeout(() => {
172
+ reject(new Error("Chronicle project server handshake timed out"));
173
+ socket.destroy();
174
+ }, CONNECT_ATTEMPT_TIMEOUT_MS);
175
+ timer.unref?.();
176
+ this.connectResolve = () => {
177
+ clearTimeout(timer);
178
+ this.connectResolve = null;
179
+ this.connectReject = null;
180
+ resolve3();
181
+ };
182
+ this.connectReject = (error) => {
183
+ clearTimeout(timer);
184
+ this.connectResolve = null;
185
+ this.connectReject = null;
186
+ reject(error);
187
+ };
188
+ socket.on("data", (chunk) => this.onData(socket, chunk));
189
+ socket.on("error", (error) => {
190
+ if (!this.info) this.connectReject?.(error);
191
+ });
192
+ socket.on("close", () => this.onClose(socket));
193
+ });
194
+ }
195
+ /**
196
+ * WS-027: the per-process token, read from the daemon's owner-only metadata
197
+ * file — never from the `hello` frame, which the daemon sends to every
198
+ * socket that connects (the mistake WS-028 found in the SAGE daemon).
199
+ *
200
+ * Read lazily and re-read while unknown: the daemon starts listening before
201
+ * it writes metadata (endpoint ownership has to be won first), and a
202
+ * respawned daemon mints a new token, so a cached wrong one would be sticky.
203
+ */
204
+ currentAuthToken() {
205
+ if (this.authToken === void 0) {
206
+ try {
207
+ const raw = fs.readFileSync(
208
+ chronicleProjectServerMetadataPath(this.options.projectDir),
209
+ "utf8"
210
+ );
211
+ const parsed = JSON.parse(raw);
212
+ if (typeof parsed.authToken === "string" && parsed.authToken.length > 0) {
213
+ this.authToken = parsed.authToken;
214
+ }
215
+ } catch {
216
+ }
217
+ }
218
+ return this.authToken;
219
+ }
220
+ request(message, timeoutMs) {
221
+ const socket = this.socket;
222
+ if (!socket || socket.destroyed) {
223
+ return Promise.reject(new Error("Chronicle project server connection is unavailable"));
224
+ }
225
+ const id = this.nextId++;
226
+ const encoded = encodeChronicleProjectServerMessage({
227
+ ...message,
228
+ id,
229
+ authToken: this.currentAuthToken()
230
+ });
231
+ if (encoded.length > CHRONICLE_PROJECT_SERVER_MAX_FRAME_CHARS) {
232
+ return Promise.reject(new Error("Chronicle project server request exceeded frame limit"));
233
+ }
234
+ const label = message.type === "request" ? message.op : message.type;
235
+ return new Promise((resolve3, reject) => {
236
+ const timer = setTimeout(() => {
237
+ const pending = this.pending.get(id);
238
+ if (!pending) return;
239
+ this.pending.delete(id);
240
+ pending.reject(new Error(`Chronicle ${label} exceeded its ${timeoutMs}ms timeout`));
241
+ }, timeoutMs);
242
+ timer.unref?.();
243
+ this.pending.set(id, { resolve: resolve3, reject, timer });
244
+ socket.write(encoded);
245
+ });
246
+ }
247
+ onData(socket, chunk) {
248
+ if (socket !== this.socket) return;
249
+ this.buffer += chunk;
250
+ if (this.buffer.length > CHRONICLE_PROJECT_SERVER_MAX_FRAME_CHARS) {
251
+ socket.destroy(new Error("Chronicle project server response exceeded frame limit"));
252
+ return;
253
+ }
254
+ while (true) {
255
+ const newline = this.buffer.indexOf("\n");
256
+ if (newline < 0) return;
257
+ const line = this.buffer.slice(0, newline);
258
+ this.buffer = this.buffer.slice(newline + 1);
259
+ if (!line) continue;
260
+ let message;
261
+ try {
262
+ message = JSON.parse(line);
263
+ } catch {
264
+ socket.destroy(new Error("Invalid Chronicle project server response"));
265
+ return;
266
+ }
267
+ this.onMessage(message);
268
+ }
269
+ }
270
+ onMessage(message) {
271
+ if (message.type === "hello") {
272
+ if (message.protocolVersion !== CHRONICLE_PROJECT_SERVER_PROTOCOL_VERSION) {
273
+ this.connectReject?.(
274
+ new Error(
275
+ `Chronicle protocol mismatch: client=${CHRONICLE_PROJECT_SERVER_PROTOCOL_VERSION}, server=${message.protocolVersion}`
276
+ )
277
+ );
278
+ this.socket?.destroy();
279
+ return;
280
+ }
281
+ if (normalizePath(message.projectRoot) !== normalizePath(this.options.projectRoot) || normalizePath(message.projectDir) !== normalizePath(this.options.projectDir)) {
282
+ this.connectReject?.(new Error("Chronicle project server identity mismatch"));
283
+ this.socket?.destroy();
284
+ return;
285
+ }
286
+ this.info = message;
287
+ this.connectResolve?.();
288
+ return;
289
+ }
290
+ const pending = this.pending.get(message.id);
291
+ if (!pending) return;
292
+ this.pending.delete(message.id);
293
+ clearTimeout(pending.timer);
294
+ if (message.ok) pending.resolve(message.result);
295
+ else {
296
+ const error = new Error(message.error);
297
+ if (message.errorName) error.name = message.errorName;
298
+ pending.reject(error);
299
+ }
300
+ }
301
+ onClose(socket) {
302
+ if (socket !== this.socket) return;
303
+ this.socket = null;
304
+ this.info = null;
305
+ const error = new Error("Chronicle project server connection closed");
306
+ this.connectReject?.(error);
307
+ this.connectResolve = null;
308
+ this.connectReject = null;
309
+ const pending = [...this.pending.values()];
310
+ this.pending.clear();
311
+ for (const request of pending) {
312
+ clearTimeout(request.timer);
313
+ request.reject(error);
314
+ }
315
+ }
316
+ spawnDetachedServer() {
317
+ const url = resolveChronicleProjectServerUrl();
318
+ if (!url) throw new Error("Built Chronicle project server is unavailable");
319
+ const args = [
320
+ fileURLToPath(url),
321
+ "--project-root",
322
+ this.options.projectRoot,
323
+ "--global-root",
324
+ this.options.globalRoot,
325
+ "--project-id",
326
+ this.options.projectId,
327
+ "--project-dir",
328
+ this.options.projectDir,
329
+ "--workspace-id",
330
+ this.options.workspaceId
331
+ ];
332
+ if (this.options.retentionDays !== void 0) {
333
+ args.push("--retention-days", String(this.options.retentionDays));
334
+ }
335
+ const child = spawn(process.execPath, args, {
336
+ detached: true,
337
+ stdio: "ignore",
338
+ windowsHide: true,
339
+ env: process.env
340
+ });
341
+ child.unref();
342
+ }
343
+ };
344
+ var ChronicleRemoteJournal = class {
345
+ client;
346
+ maxPending;
347
+ batchWindowMs;
348
+ pending = [];
349
+ timer;
350
+ inflight = /* @__PURE__ */ new Set();
351
+ counters = {
352
+ acceptedEvents: 0,
353
+ persistedEvents: 0,
354
+ rejectedEvents: 0,
355
+ failedEvents: 0,
356
+ batches: 0,
357
+ maxObservedPending: 0,
358
+ largestBatch: 0,
359
+ partitionRolls: 0
360
+ };
361
+ lastBatchDurationMs;
362
+ constructor(options, client = new ChronicleProjectServerClient(options)) {
363
+ this.client = client;
364
+ this.maxPending = Math.max(1, options.maxPending ?? DEFAULT_MAX_PENDING);
365
+ this.batchWindowMs = Math.max(0, options.batchWindowMs ?? DEFAULT_BATCH_WINDOW_MS);
366
+ }
367
+ append(input) {
368
+ if (this.pending.length >= this.maxPending) {
369
+ this.counters.rejectedEvents++;
370
+ return Promise.reject(
371
+ new Error(`Chronicle backpressure limit reached (${this.maxPending} pending events)`)
372
+ );
373
+ }
374
+ const promise = new Promise((resolve3, reject) => {
375
+ this.pending.push({ input, resolve: resolve3, reject });
376
+ });
377
+ this.counters.acceptedEvents++;
378
+ this.counters.maxObservedPending = Math.max(
379
+ this.counters.maxObservedPending,
380
+ this.pending.length
381
+ );
382
+ this.schedule();
383
+ return promise;
384
+ }
385
+ stats() {
386
+ return {
387
+ ...this.counters,
388
+ pendingEvents: this.pending.length,
389
+ ...this.lastBatchDurationMs !== void 0 ? { lastBatchDurationMs: this.lastBatchDurationMs } : {}
390
+ };
391
+ }
392
+ async flush() {
393
+ if (this.timer) {
394
+ clearTimeout(this.timer);
395
+ this.timer = void 0;
396
+ }
397
+ while (this.pending.length > 0 || this.inflight.size > 0) {
398
+ if (this.pending.length > 0) this.startBatch();
399
+ await Promise.all([...this.inflight]);
400
+ }
401
+ if (this.counters.acceptedEvents === 0) return;
402
+ await this.client.call("flush", {}, { timeoutMs: APPEND_CALL_TIMEOUT_MS });
403
+ }
404
+ async dispose() {
405
+ await this.flush();
406
+ await this.client.close();
407
+ }
408
+ schedule() {
409
+ if (this.timer) return;
410
+ if (this.batchWindowMs === 0) {
411
+ queueMicrotask(() => this.startBatch());
412
+ return;
413
+ }
414
+ this.timer = setTimeout(() => {
415
+ this.timer = void 0;
416
+ this.startBatch();
417
+ }, this.batchWindowMs);
418
+ this.timer.unref?.();
419
+ }
420
+ startBatch() {
421
+ if (this.timer) {
422
+ clearTimeout(this.timer);
423
+ this.timer = void 0;
424
+ }
425
+ if (this.pending.length === 0) return;
426
+ const batch = this.pending.splice(0, CHRONICLE_MAX_APPEND_BATCH);
427
+ const started = performance.now();
428
+ this.counters.batches++;
429
+ this.counters.largestBatch = Math.max(this.counters.largestBatch, batch.length);
430
+ const run = this.client.call(
431
+ "append",
432
+ { inputs: batch.map((entry) => entry.input) },
433
+ { timeoutMs: APPEND_CALL_TIMEOUT_MS }
434
+ ).then((events) => {
435
+ if (events.length !== batch.length) {
436
+ throw new Error(
437
+ `Chronicle server returned ${events.length} events for ${batch.length} inputs`
438
+ );
439
+ }
440
+ batch.forEach((entry, index) => {
441
+ entry.resolve(events[index]);
442
+ });
443
+ this.counters.persistedEvents += batch.length;
444
+ }).catch((error) => {
445
+ this.counters.failedEvents += batch.length;
446
+ for (const entry of batch) entry.reject(error);
447
+ }).finally(() => {
448
+ this.lastBatchDurationMs = performance.now() - started;
449
+ this.inflight.delete(run);
450
+ if (this.pending.length > 0) this.schedule();
451
+ });
452
+ this.inflight.add(run);
453
+ }
454
+ };
455
+ function normalizePath(value) {
456
+ const resolved = path.resolve(value);
457
+ return process.platform === "win32" ? resolved.toLowerCase() : resolved;
458
+ }
459
+
460
+ // src/chronicle/project-access.ts
461
+ import * as fsPromises from "node:fs/promises";
462
+ import * as os from "node:os";
463
+ import * as path2 from "node:path";
464
+ function resolveChronicleProjectServerOptions(options) {
465
+ if (options.projectPaths) {
466
+ return {
467
+ projectRoot: path2.resolve(options.projectRoot),
468
+ ...options.projectPaths,
469
+ ...options.retentionDays !== void 0 ? { retentionDays: options.retentionDays } : {}
470
+ };
471
+ }
472
+ const paths = resolveWstackPaths({
473
+ projectRoot: options.projectRoot,
474
+ userHome: options.userHome ?? os.homedir()
475
+ });
476
+ return {
477
+ projectRoot: path2.resolve(options.projectRoot),
478
+ globalRoot: paths.globalRoot,
479
+ projectId: paths.projectHash,
480
+ projectDir: paths.projectDir,
481
+ workspaceId: paths.projectSlug,
482
+ ...options.retentionDays !== void 0 ? { retentionDays: options.retentionDays } : {}
483
+ };
484
+ }
485
+ function createChronicleProjectAccess(options) {
486
+ const resolved = resolveChronicleProjectServerOptions(options);
487
+ if (isChronicleProjectServerAvailable()) {
488
+ const client = new ChronicleProjectServerClient(resolved);
489
+ return {
490
+ mode: "server",
491
+ call: (op, args, callOptions) => client.call(op, args, callOptions),
492
+ close: () => client.close()
493
+ };
494
+ }
495
+ assertInlineWasRequested("createChronicleProjectAccess");
496
+ return new InlineChronicleProjectAccess(resolved);
497
+ }
498
+ function assertInlineWasRequested(caller) {
499
+ if (resolveChronicleDaemonAvailability().kind !== "missing-build") return;
500
+ throw new Error(
501
+ `${caller}: built Chronicle project server is unavailable. Build @wrongstack/core, or set WRONGSTACK_CHRONICLE_INLINE=1 to explicitly accept a process-local journal.`
502
+ );
503
+ }
504
+ function createChronicleEventJournal(options) {
505
+ const resolved = resolveChronicleProjectServerOptions(options);
506
+ const identity = resolveChronicleRuntimeLocation({
507
+ globalRoot: resolved.globalRoot,
508
+ projectId: resolved.projectId,
509
+ projectDir: resolved.projectDir
510
+ });
511
+ if (isChronicleProjectServerAvailable()) {
512
+ const journal2 = new ChronicleRemoteJournal(resolved);
513
+ return {
514
+ journal: journal2,
515
+ identity,
516
+ serverBacked: true,
517
+ dispose: () => journal2.dispose()
518
+ };
519
+ }
520
+ assertInlineWasRequested("createChronicleEventJournal");
521
+ if (useSqliteStore()) {
522
+ const journal2 = new ChronicleSqliteEventSink(path2.join(resolved.projectDir, "chronicle"));
523
+ return {
524
+ journal: journal2,
525
+ identity,
526
+ serverBacked: false,
527
+ dispose: () => journal2.close()
528
+ };
529
+ }
530
+ const journal = new ChronicleJournal({
531
+ filePath: legacyPartitionPath(identity),
532
+ retentionDays: options.retentionDays
533
+ });
534
+ return {
535
+ journal,
536
+ identity,
537
+ serverBacked: false,
538
+ dispose: () => journal.flush()
539
+ };
540
+ }
541
+ function legacyPartitionPath(location) {
542
+ return path2.join(location.chronicleDirectory, `${location.day}.events.jsonl`);
543
+ }
544
+ function useSqliteStore() {
545
+ return process.env["WRONGSTACK_CHRONICLE_STORE"] !== "jsonl";
546
+ }
547
+ var ChronicleSqliteEventSink = class {
548
+ ready;
549
+ last;
550
+ constructor(directory) {
551
+ this.ready = (async () => {
552
+ await fsPromises.mkdir(directory, { recursive: true });
553
+ const journal = new ChronicleSqliteJournal({ directory });
554
+ try {
555
+ await importLegacyChronicleJournal(journal, directory);
556
+ } catch (error) {
557
+ journal.close();
558
+ throw error;
559
+ }
560
+ return journal;
561
+ })();
562
+ }
563
+ async append(input) {
564
+ const journal = await this.ready;
565
+ const event = await journal.append(input);
566
+ this.last = journal.stats();
567
+ return event;
568
+ }
569
+ async flush() {
570
+ await this.ready;
571
+ }
572
+ stats() {
573
+ return this.last ?? EMPTY_JOURNAL_STATS;
574
+ }
575
+ async close() {
576
+ (await this.ready).close();
577
+ }
578
+ };
579
+ var EMPTY_JOURNAL_STATS = {
580
+ acceptedEvents: 0,
581
+ persistedEvents: 0,
582
+ rejectedEvents: 0,
583
+ failedEvents: 0,
584
+ batches: 0,
585
+ pendingEvents: 0,
586
+ maxObservedPending: 0,
587
+ largestBatch: 0,
588
+ partitionRolls: 0
589
+ };
590
+ var MAX_OPEN_JOURNAL_DAYS = 2;
591
+ var InlineChronicleProjectAccess = class {
592
+ constructor(options) {
593
+ this.options = options;
594
+ }
595
+ options;
596
+ mode = "inline";
597
+ journals = /* @__PURE__ */ new Map();
598
+ sqlite;
599
+ /**
600
+ * Open the SQLite journal, importing the legacy partitions the first time.
601
+ *
602
+ * The import is part of opening rather than a separate migration step so no
603
+ * caller can read a half-migrated store: until it finishes, this promise has
604
+ * not resolved and every operation is still queued behind it.
605
+ */
606
+ store() {
607
+ this.sqlite ??= (async () => {
608
+ await fsPromises.mkdir(this.chronicleDirectory, { recursive: true });
609
+ const journal = new ChronicleSqliteJournal({ directory: this.chronicleDirectory });
610
+ try {
611
+ await importLegacyChronicleJournal(journal, this.chronicleDirectory);
612
+ } catch (error) {
613
+ this.sqlite = void 0;
614
+ journal.close();
615
+ throw error;
616
+ }
617
+ return journal;
618
+ })();
619
+ return this.sqlite;
620
+ }
621
+ async call(op, rawArgs, _callOptions) {
622
+ switch (op) {
623
+ case "ping":
624
+ return this.health();
625
+ case "append": {
626
+ const args = rawArgs;
627
+ if (useSqliteStore()) {
628
+ const store = await this.store();
629
+ return await store.appendBatch(args.inputs);
630
+ }
631
+ return await Promise.all(
632
+ args.inputs.map((input) => this.journalForToday().append(input))
633
+ );
634
+ }
635
+ case "flush":
636
+ await this.flush();
637
+ return void 0;
638
+ case "purge": {
639
+ const args = rawArgs;
640
+ if (useSqliteStore()) {
641
+ const store = await this.store();
642
+ return await store.purge(args);
643
+ }
644
+ return await this.journalForToday().purge(args);
645
+ }
646
+ case "query": {
647
+ const args = rawArgs;
648
+ return await (await this.queryEngine()).query(args.query);
649
+ }
650
+ case "facet": {
651
+ const args = rawArgs;
652
+ const engine = await this.queryEngine();
653
+ return {
654
+ values: await engine.facet(args.field, args.query, args.limit),
655
+ diagnostics: engine.diagnostics
656
+ };
657
+ }
658
+ case "facets": {
659
+ const args = rawArgs;
660
+ const engine = await this.queryEngine();
661
+ return {
662
+ values: await engine.facets(args.fields, args.query, args.limit),
663
+ diagnostics: engine.diagnostics
664
+ };
665
+ }
666
+ case "graph": {
667
+ const args = rawArgs;
668
+ return await (await this.queryEngine()).graph(args.seed, args.hops, args.maxNodes);
669
+ }
670
+ case "metrics": {
671
+ const args = rawArgs;
672
+ const store = ChronicleMetricsStore.open(this.chronicleDirectory);
673
+ try {
674
+ const refreshed = args.refresh === false ? { ingestedEvents: 0, ingestedBytes: 0, sourceFiles: 0, invalidLines: 0 } : await store.refresh();
675
+ const data = args.view === "providers" ? store.providerDaily(args.providers) : args.view === "tasks" ? store.taskOutcomes(args.tasks) : args.view === "files" ? store.fileLineage(args.files) : store.summary();
676
+ return { refreshed, data };
677
+ } finally {
678
+ store.close();
679
+ }
680
+ }
681
+ }
682
+ }
683
+ async close() {
684
+ await this.flush();
685
+ if (this.sqlite) {
686
+ const store = await this.sqlite;
687
+ this.sqlite = void 0;
688
+ store.close();
689
+ }
690
+ for (const journal of this.journals.values()) {
691
+ await journal.flush().catch(() => {
692
+ });
693
+ }
694
+ this.journals.clear();
695
+ }
696
+ get chronicleDirectory() {
697
+ return path2.join(this.options.projectDir, "chronicle");
698
+ }
699
+ /**
700
+ * Drop journals for days we will not write to again.
701
+ *
702
+ * Mirrors the daemon's `pruneJournals` in `project-server.ts`: without a cap
703
+ * an inline access object that lives across midnight kept one open
704
+ * `ChronicleJournal` (with its write buffer and file handle) per day, forever.
705
+ */
706
+ pruneJournals(currentDay) {
707
+ if (this.journals.size <= MAX_OPEN_JOURNAL_DAYS) return;
708
+ const keep = new Set(
709
+ [...this.journals.keys()].sort().reverse().slice(0, MAX_OPEN_JOURNAL_DAYS)
710
+ );
711
+ keep.add(currentDay);
712
+ for (const [day, journal] of this.journals) {
713
+ if (keep.has(day)) continue;
714
+ this.journals.delete(day);
715
+ void journal.flush().catch(() => {
716
+ });
717
+ }
718
+ }
719
+ journalForToday() {
720
+ const now = /* @__PURE__ */ new Date();
721
+ const day = now.toISOString().slice(0, 10);
722
+ let journal = this.journals.get(day);
723
+ if (!journal) {
724
+ const identity = resolveChronicleRuntimeLocation({
725
+ globalRoot: this.options.globalRoot,
726
+ projectId: this.options.projectId,
727
+ projectDir: this.options.projectDir,
728
+ now
729
+ });
730
+ journal = new ChronicleJournal({
731
+ filePath: legacyPartitionPath(identity),
732
+ retentionDays: this.options.retentionDays
733
+ });
734
+ this.journals.set(day, journal);
735
+ this.pruneJournals(day);
736
+ }
737
+ return journal;
738
+ }
739
+ /**
740
+ * Both engines expose the same read surface, so every `call()` branch is
741
+ * backend-agnostic. The SQLite one shares the journal's connection, which is
742
+ * also what makes the legacy import a precondition of reading.
743
+ */
744
+ async queryEngine() {
745
+ if (useSqliteStore()) return (await this.store()).queryEngine();
746
+ return ChronicleQueryEngine.fromDirectory(this.chronicleDirectory);
747
+ }
748
+ async flush() {
749
+ if (useSqliteStore()) {
750
+ if (this.sqlite) await this.sqlite;
751
+ return;
752
+ }
753
+ await Promise.all([...this.journals.values()].map((journal) => journal.flush()));
754
+ }
755
+ health() {
756
+ const memory = process.memoryUsage();
757
+ return {
758
+ protocolVersion: CHRONICLE_PROJECT_SERVER_PROTOCOL_VERSION,
759
+ pid: process.pid,
760
+ projectRoot: this.options.projectRoot,
761
+ projectDir: this.options.projectDir,
762
+ chronicleDirectory: this.chronicleDirectory,
763
+ endpoint: "inline",
764
+ startedAt: new Date(Date.now() - process.uptime() * 1e3).toISOString(),
765
+ checkedAt: Date.now(),
766
+ uptimeMs: Math.round(process.uptime() * 1e3),
767
+ clients: 1,
768
+ activeRequests: 0,
769
+ journal: this.journalForToday().stats(),
770
+ watcher: { active: false, watchedFiles: 0 },
771
+ memory: {
772
+ rss: memory.rss,
773
+ heapUsed: memory.heapUsed,
774
+ heapTotal: memory.heapTotal,
775
+ external: memory.external
776
+ }
777
+ };
778
+ }
779
+ };
780
+
781
+ export {
782
+ isChronicleProjectServerAvailable,
783
+ ChronicleProjectServerClient,
784
+ ChronicleRemoteJournal,
785
+ resolveChronicleProjectServerOptions,
786
+ createChronicleProjectAccess,
787
+ createChronicleEventJournal
788
+ };
789
+ //# sourceMappingURL=chunk-ASX6AW36.js.map