hzl-core 2.10.0 → 3.1.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 (78) hide show
  1. package/dist/__tests__/migrations/v5-upgrade.test.d.ts +2 -0
  2. package/dist/__tests__/migrations/v5-upgrade.test.d.ts.map +1 -0
  3. package/dist/__tests__/migrations/v5-upgrade.test.js +56 -0
  4. package/dist/__tests__/migrations/v5-upgrade.test.js.map +1 -0
  5. package/dist/db/migrations/index.d.ts.map +1 -1
  6. package/dist/db/migrations/index.js +5 -0
  7. package/dist/db/migrations/index.js.map +1 -1
  8. package/dist/db/migrations/v5.d.ts +2 -0
  9. package/dist/db/migrations/v5.d.ts.map +1 -0
  10. package/dist/db/migrations/v5.js +4 -0
  11. package/dist/db/migrations/v5.js.map +1 -0
  12. package/dist/db/schema.d.ts +1 -1
  13. package/dist/db/schema.d.ts.map +1 -1
  14. package/dist/db/schema.js +1 -0
  15. package/dist/db/schema.js.map +1 -1
  16. package/dist/db/test-seed.d.ts +22 -0
  17. package/dist/db/test-seed.d.ts.map +1 -0
  18. package/dist/db/test-seed.js +72 -0
  19. package/dist/db/test-seed.js.map +1 -0
  20. package/dist/events/store.d.ts +8 -0
  21. package/dist/events/store.d.ts.map +1 -1
  22. package/dist/events/store.js +19 -0
  23. package/dist/events/store.js.map +1 -1
  24. package/dist/events/store.test.js +54 -0
  25. package/dist/events/store.test.js.map +1 -1
  26. package/dist/events/types.d.ts +3 -1
  27. package/dist/events/types.d.ts.map +1 -1
  28. package/dist/events/types.js +4 -0
  29. package/dist/events/types.js.map +1 -1
  30. package/dist/events/types.test.js +31 -0
  31. package/dist/events/types.test.js.map +1 -1
  32. package/dist/index.d.ts +3 -1
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +2 -0
  35. package/dist/index.js.map +1 -1
  36. package/dist/projections/tasks-current.d.ts.map +1 -1
  37. package/dist/projections/tasks-current.js +3 -3
  38. package/dist/projections/tasks-current.js.map +1 -1
  39. package/dist/projections/tasks-current.test.js +18 -0
  40. package/dist/projections/tasks-current.test.js.map +1 -1
  41. package/dist/services/gateway-client.d.ts +56 -0
  42. package/dist/services/gateway-client.d.ts.map +1 -0
  43. package/dist/services/gateway-client.js +413 -0
  44. package/dist/services/gateway-client.js.map +1 -0
  45. package/dist/services/stats-service.d.ts +51 -0
  46. package/dist/services/stats-service.d.ts.map +1 -0
  47. package/dist/services/stats-service.js +183 -0
  48. package/dist/services/stats-service.js.map +1 -0
  49. package/dist/services/stats-service.test.d.ts +2 -0
  50. package/dist/services/stats-service.test.d.ts.map +1 -0
  51. package/dist/services/stats-service.test.js +196 -0
  52. package/dist/services/stats-service.test.js.map +1 -0
  53. package/dist/services/task-service.d.ts +3 -9
  54. package/dist/services/task-service.d.ts.map +1 -1
  55. package/dist/services/task-service.js +17 -34
  56. package/dist/services/task-service.js.map +1 -1
  57. package/dist/services/task-service.test.js +61 -32
  58. package/dist/services/task-service.test.js.map +1 -1
  59. package/dist/services/workflow-service.d.ts.map +1 -1
  60. package/dist/services/workflow-service.js +1 -14
  61. package/dist/services/workflow-service.js.map +1 -1
  62. package/dist/utils/duration.d.ts +3 -0
  63. package/dist/utils/duration.d.ts.map +1 -0
  64. package/dist/utils/duration.js +21 -0
  65. package/dist/utils/duration.js.map +1 -0
  66. package/dist/utils/duration.test.d.ts +2 -0
  67. package/dist/utils/duration.test.d.ts.map +1 -0
  68. package/dist/utils/duration.test.js +50 -0
  69. package/dist/utils/duration.test.js.map +1 -0
  70. package/dist/utils/json.d.ts +6 -0
  71. package/dist/utils/json.d.ts.map +1 -0
  72. package/dist/utils/json.js +27 -0
  73. package/dist/utils/json.js.map +1 -0
  74. package/dist/utils/json.test.d.ts +2 -0
  75. package/dist/utils/json.test.d.ts.map +1 -0
  76. package/dist/utils/json.test.js +30 -0
  77. package/dist/utils/json.test.js.map +1 -0
  78. package/package.json +2 -2
@@ -0,0 +1,413 @@
1
+ // packages/hzl-core/src/services/gateway-client.ts
2
+ import { generateKeyPairSync, sign, createPrivateKey, createHash } from 'node:crypto';
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ const RPC_TIMEOUT_MS = 30_000;
6
+ const MAX_RECONNECT_DELAY_MS = 30_000;
7
+ const INITIAL_RECONNECT_DELAY_MS = 1_000;
8
+ const PAIRING_RETRY_DELAY_MS = 5_000;
9
+ const DEVICE_IDENTITY_FILE = 'gateway-device.json';
10
+ const PROTOCOL_VERSION = 3;
11
+ const ED25519_SPKI_PREFIX_LEN = 12; // ASN.1 header length for Ed25519 SPKI DER
12
+ function base64UrlEncode(buf) {
13
+ return buf.toString('base64').replaceAll('+', '-').replaceAll('/', '_').replace(/=+$/g, '');
14
+ }
15
+ export class GatewayClient {
16
+ url;
17
+ token;
18
+ configDir;
19
+ ws = null;
20
+ _status = 'disconnected';
21
+ deviceIdentity = null;
22
+ pendingCalls = new Map();
23
+ nextId = 1;
24
+ reconnectTimer;
25
+ reconnectDelay = INITIAL_RECONNECT_DELAY_MS;
26
+ connectPromise = null;
27
+ disposed = false;
28
+ pairingMessageShown = false;
29
+ constructor(options) {
30
+ this.url = options.url;
31
+ this.token = options.token;
32
+ this.configDir = options.configDir;
33
+ this.deviceIdentity = this.loadDeviceIdentity();
34
+ }
35
+ getStatus() {
36
+ return this._status;
37
+ }
38
+ /**
39
+ * Update gateway configuration. Disconnects and reconnects if URL or token changed.
40
+ */
41
+ configure(url, token) {
42
+ const changed = url !== this.url || token !== this.token;
43
+ this.url = url;
44
+ this.token = token;
45
+ if (changed && this._status === 'connected') {
46
+ this.disconnect();
47
+ }
48
+ }
49
+ /**
50
+ * Send an RPC call to the gateway. Lazily connects on first call.
51
+ */
52
+ async call(method, params) {
53
+ await this.ensureConnected();
54
+ return new Promise((resolve, reject) => {
55
+ const id = String(this.nextId++);
56
+ const timer = setTimeout(() => {
57
+ this.pendingCalls.delete(id);
58
+ reject(new Error(`Gateway RPC timeout: ${method}`));
59
+ }, RPC_TIMEOUT_MS);
60
+ this.pendingCalls.set(id, { resolve, reject, timer });
61
+ const frame = { type: 'req', id, method };
62
+ if (params !== undefined) {
63
+ frame.params = params;
64
+ }
65
+ this.ws.send(JSON.stringify(frame));
66
+ });
67
+ }
68
+ /**
69
+ * Disconnect from the gateway and clean up resources.
70
+ */
71
+ dispose() {
72
+ this.disposed = true;
73
+ this.disconnect();
74
+ }
75
+ disconnect() {
76
+ if (this.reconnectTimer) {
77
+ clearTimeout(this.reconnectTimer);
78
+ this.reconnectTimer = undefined;
79
+ }
80
+ if (this.ws) {
81
+ this.ws.onopen = null;
82
+ this.ws.onclose = null;
83
+ this.ws.onerror = null;
84
+ this.ws.onmessage = null;
85
+ if (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING) {
86
+ this.ws.close();
87
+ }
88
+ this.ws = null;
89
+ }
90
+ // Reject all pending calls
91
+ for (const call of this.pendingCalls.values()) {
92
+ clearTimeout(call.timer);
93
+ call.reject(new Error('Gateway disconnected'));
94
+ }
95
+ this.pendingCalls.clear();
96
+ this._status = 'disconnected';
97
+ this.connectPromise = null;
98
+ }
99
+ async ensureConnected() {
100
+ if (this._status === 'connected' && this.ws?.readyState === WebSocket.OPEN) {
101
+ return;
102
+ }
103
+ if (this.connectPromise) {
104
+ return this.connectPromise;
105
+ }
106
+ this.connectPromise = this.connect();
107
+ try {
108
+ await this.connectPromise;
109
+ }
110
+ finally {
111
+ this.connectPromise = null;
112
+ }
113
+ }
114
+ connect() {
115
+ return new Promise((resolve, reject) => {
116
+ this._status = 'connecting';
117
+ const ws = new WebSocket(this.url);
118
+ this.ws = ws;
119
+ let handshakeComplete = false;
120
+ let challengeNonce = null;
121
+ ws.onopen = () => {
122
+ this.reconnectDelay = INITIAL_RECONNECT_DELAY_MS;
123
+ };
124
+ ws.onmessage = (event) => {
125
+ const raw = String(event.data);
126
+ let msg;
127
+ try {
128
+ msg = JSON.parse(raw);
129
+ }
130
+ catch {
131
+ return;
132
+ }
133
+ // Handshake phase
134
+ if (!handshakeComplete) {
135
+ // Step 1: Receive connect.challenge
136
+ // Gateway sends: { type: "event", event: "connect.challenge", payload: { nonce, ts } }
137
+ const isChallenge = (msg.event === 'connect.challenge' && msg.payload) ||
138
+ (msg.method === 'connect.challenge' && (msg.params ?? msg.payload));
139
+ const challengeData = msg.payload ?? msg.params;
140
+ if (isChallenge && challengeData) {
141
+ challengeNonce = typeof challengeData.nonce === 'string' ? challengeData.nonce : '';
142
+ this.sendConnectFrame(ws, challengeNonce);
143
+ return;
144
+ }
145
+ // Step 2: Receive hello-ok response
146
+ // Gateway sends: { type: "res", id: "…", ok: true, payload: { type: "hello-ok", ... } }
147
+ const isHelloOk = (msg.type === 'res' && msg.ok === true && msg.payload) ||
148
+ (msg.type === 'res' && !msg.error && challengeNonce);
149
+ if (isHelloOk) {
150
+ handshakeComplete = true;
151
+ this._status = 'connected';
152
+ if (this.pairingMessageShown) {
153
+ console.log(' Device approved — gateway connected.');
154
+ console.log('');
155
+ this.pairingMessageShown = false;
156
+ }
157
+ else {
158
+ console.log(`Gateway connected (${this.url})`);
159
+ }
160
+ // Persist device token if provided
161
+ // Token is at payload.auth.deviceToken
162
+ const payload = msg.payload;
163
+ const auth = payload?.auth;
164
+ const deviceToken = auth?.deviceToken;
165
+ if (typeof deviceToken === 'string' && this.deviceIdentity) {
166
+ this.deviceIdentity.deviceToken = deviceToken;
167
+ this.saveDeviceIdentity(this.deviceIdentity);
168
+ }
169
+ resolve();
170
+ return;
171
+ }
172
+ // Handshake error
173
+ if (msg.type === 'res' && (msg.error || msg.ok === false)) {
174
+ const errorMsg = msg.error?.message ?? 'unknown error';
175
+ const details = msg.error?.details;
176
+ const rawCode = details?.code ?? msg.error?.code;
177
+ const code = typeof rawCode === 'string' ? rawCode : '';
178
+ if (code === 'PAIRING_REQUIRED') {
179
+ if (!this.pairingMessageShown) {
180
+ const rawReqId = details?.requestId;
181
+ const requestId = typeof rawReqId === 'string' ? rawReqId : '';
182
+ console.log('');
183
+ console.log(' Device pairing required');
184
+ console.log('');
185
+ console.log(' This device must be approved on your OpenClaw gateway.');
186
+ console.log(' Run on your gateway host:');
187
+ console.log('');
188
+ console.log(` openclaw devices approve ${requestId}`);
189
+ console.log('');
190
+ console.log(' Waiting for approval...');
191
+ this.pairingMessageShown = true;
192
+ }
193
+ // Retry every 5s — user may approve at any time
194
+ this._status = 'disconnected';
195
+ this.reconnectDelay = PAIRING_RETRY_DELAY_MS;
196
+ reject(new Error(`Gateway handshake failed: ${errorMsg}`));
197
+ if (!this.disposed) {
198
+ this.scheduleReconnect();
199
+ }
200
+ return;
201
+ }
202
+ const err = new Error(`Gateway handshake failed: ${errorMsg}`);
203
+ this._status = 'disconnected';
204
+ reject(err);
205
+ return;
206
+ }
207
+ return;
208
+ }
209
+ // Normal RPC response
210
+ if (msg.type === 'res' && msg.id) {
211
+ const call = this.pendingCalls.get(msg.id);
212
+ if (call) {
213
+ this.pendingCalls.delete(msg.id);
214
+ clearTimeout(call.timer);
215
+ if (msg.error) {
216
+ call.reject(new Error(msg.error.message));
217
+ }
218
+ else {
219
+ // Result may be in msg.result or msg.payload
220
+ call.resolve(msg.result ?? msg.payload);
221
+ }
222
+ }
223
+ }
224
+ };
225
+ ws.onclose = () => {
226
+ const wasConnected = handshakeComplete;
227
+ this._status = 'disconnected';
228
+ if (!handshakeComplete) {
229
+ reject(new Error('Gateway connection closed during handshake'));
230
+ }
231
+ // Reject pending calls
232
+ for (const call of this.pendingCalls.values()) {
233
+ clearTimeout(call.timer);
234
+ call.reject(new Error('Gateway connection closed'));
235
+ }
236
+ this.pendingCalls.clear();
237
+ // Auto-reconnect if we were connected and not disposed
238
+ if (wasConnected && !this.disposed) {
239
+ this.scheduleReconnect();
240
+ }
241
+ };
242
+ ws.onerror = () => {
243
+ if (!handshakeComplete) {
244
+ this._status = 'disconnected';
245
+ reject(new Error(`Gateway connection failed: ${this.url}`));
246
+ }
247
+ };
248
+ });
249
+ }
250
+ sendConnectFrame(ws, nonce) {
251
+ const identity = this.getOrCreateDeviceIdentity();
252
+ const connectId = String(this.nextId++);
253
+ const signedAt = Date.now();
254
+ const clientId = 'gateway-client';
255
+ const clientMode = 'backend';
256
+ const role = 'operator';
257
+ const scopes = ['operator.read', 'operator.write', 'operator.admin'];
258
+ const token = identity.deviceToken ?? this.token ?? '';
259
+ const platform = process.platform;
260
+ // Build v3 auth payload: v3|deviceId|clientId|clientMode|role|scopes|signedAtMs|token|nonce|platform|deviceFamily
261
+ const authPayload = [
262
+ 'v3',
263
+ identity.id,
264
+ clientId,
265
+ clientMode,
266
+ role,
267
+ scopes.join(','),
268
+ String(signedAt),
269
+ token,
270
+ nonce,
271
+ platform.toLowerCase(),
272
+ '', // deviceFamily (empty for server)
273
+ ].join('|');
274
+ const signature = this.signPayload(authPayload, identity.privateKeyPkcs8);
275
+ const frame = {
276
+ type: 'req',
277
+ id: connectId,
278
+ method: 'connect',
279
+ params: {
280
+ minProtocol: PROTOCOL_VERSION,
281
+ maxProtocol: PROTOCOL_VERSION,
282
+ client: {
283
+ id: clientId,
284
+ version: '1.0.0',
285
+ platform,
286
+ mode: clientMode,
287
+ },
288
+ role,
289
+ scopes,
290
+ caps: [],
291
+ commands: [],
292
+ permissions: {},
293
+ auth: { token: identity.deviceToken ?? this.token },
294
+ locale: 'en-US',
295
+ userAgent: 'hzl-dashboard/1.0.0',
296
+ device: {
297
+ id: identity.id,
298
+ publicKey: identity.publicKey,
299
+ signature,
300
+ signedAt,
301
+ nonce,
302
+ },
303
+ },
304
+ };
305
+ ws.send(JSON.stringify(frame));
306
+ }
307
+ signPayload(payload, privateKeyPkcs8Base64) {
308
+ const privateKeyDer = Buffer.from(privateKeyPkcs8Base64, 'base64');
309
+ const key = createPrivateKey({
310
+ key: privateKeyDer,
311
+ format: 'der',
312
+ type: 'pkcs8',
313
+ });
314
+ const sig = sign(null, Buffer.from(payload, 'utf8'), key);
315
+ return base64UrlEncode(sig);
316
+ }
317
+ /**
318
+ * Derive device ID as SHA256 hex of the raw 32-byte Ed25519 public key.
319
+ * publicKeyBase64Url is the raw 32-byte key in base64url encoding.
320
+ */
321
+ deriveDeviceId(publicKeyBase64Url) {
322
+ // Decode base64url → raw 32 bytes → SHA256 hex
323
+ const normalized = publicKeyBase64Url.replaceAll('-', '+').replaceAll('_', '/');
324
+ const padded = normalized + '='.repeat((4 - (normalized.length % 4)) % 4);
325
+ const raw = Buffer.from(padded, 'base64');
326
+ return createHash('sha256').update(raw).digest('hex');
327
+ }
328
+ getOrCreateDeviceIdentity() {
329
+ if (this.deviceIdentity) {
330
+ let changed = false;
331
+ // Migrate old SPKI base64 public keys to raw base64url
332
+ // SPKI Ed25519 keys start with 'MCowBQ' (the ASN.1 header)
333
+ if (this.deviceIdentity.publicKey.startsWith('MCowBQ')) {
334
+ const spkiDer = Buffer.from(this.deviceIdentity.publicKey, 'base64');
335
+ const rawKey = spkiDer.subarray(ED25519_SPKI_PREFIX_LEN);
336
+ this.deviceIdentity.publicKey = base64UrlEncode(rawKey);
337
+ changed = true;
338
+ }
339
+ // Validate stored ID matches derived fingerprint
340
+ const expectedId = this.deriveDeviceId(this.deviceIdentity.publicKey);
341
+ if (this.deviceIdentity.id !== expectedId) {
342
+ this.deviceIdentity.id = expectedId;
343
+ changed = true;
344
+ }
345
+ if (changed) {
346
+ this.saveDeviceIdentity(this.deviceIdentity);
347
+ }
348
+ return this.deviceIdentity;
349
+ }
350
+ const { publicKey, privateKey } = generateKeyPairSync('ed25519');
351
+ // Extract raw 32-byte public key from SPKI DER (strip 12-byte ASN.1 header)
352
+ const spkiDer = publicKey.export({ type: 'spki', format: 'der' });
353
+ const rawPub = spkiDer.subarray(ED25519_SPKI_PREFIX_LEN);
354
+ const pubBase64Url = base64UrlEncode(rawPub);
355
+ const identity = {
356
+ id: this.deriveDeviceId(pubBase64Url),
357
+ publicKey: pubBase64Url,
358
+ privateKeyPkcs8: privateKey.export({ type: 'pkcs8', format: 'der' }).toString('base64'),
359
+ };
360
+ this.deviceIdentity = identity;
361
+ this.saveDeviceIdentity(identity);
362
+ return identity;
363
+ }
364
+ loadDeviceIdentity() {
365
+ const filePath = path.join(this.configDir, DEVICE_IDENTITY_FILE);
366
+ try {
367
+ if (!fs.existsSync(filePath))
368
+ return null;
369
+ const data = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
370
+ const id = data.id;
371
+ const publicKey = data.publicKey;
372
+ // Support both old ('privateKey') and new ('privateKeyPkcs8') field names
373
+ const privateKeyPkcs8 = (data.privateKeyPkcs8 ?? data.privateKey);
374
+ if (id && publicKey && privateKeyPkcs8) {
375
+ return { id, publicKey, privateKeyPkcs8, deviceToken: data.deviceToken };
376
+ }
377
+ return null;
378
+ }
379
+ catch {
380
+ return null;
381
+ }
382
+ }
383
+ saveDeviceIdentity(identity) {
384
+ try {
385
+ if (!fs.existsSync(this.configDir)) {
386
+ fs.mkdirSync(this.configDir, { recursive: true });
387
+ }
388
+ const filePath = path.join(this.configDir, DEVICE_IDENTITY_FILE);
389
+ const tempPath = `${filePath}.tmp.${process.pid}`;
390
+ fs.writeFileSync(tempPath, JSON.stringify(identity, null, 2) + '\n');
391
+ fs.renameSync(tempPath, filePath);
392
+ }
393
+ catch {
394
+ // Non-fatal: device identity will be regenerated next time
395
+ }
396
+ }
397
+ scheduleReconnect() {
398
+ if (this.disposed || this.reconnectTimer)
399
+ return;
400
+ this.reconnectTimer = setTimeout(() => {
401
+ this.reconnectTimer = undefined;
402
+ if (!this.disposed) {
403
+ this.ensureConnected().catch(() => {
404
+ // Reconnect failed, schedule another attempt
405
+ this.reconnectDelay = Math.min(this.reconnectDelay * 2, MAX_RECONNECT_DELAY_MS);
406
+ this.scheduleReconnect();
407
+ });
408
+ }
409
+ }, this.reconnectDelay);
410
+ this.reconnectDelay = Math.min(this.reconnectDelay * 2, MAX_RECONNECT_DELAY_MS);
411
+ }
412
+ }
413
+ //# sourceMappingURL=gateway-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gateway-client.js","sourceRoot":"","sources":["../../src/services/gateway-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ import type Database from 'libsql';
2
+ import type { TaskService } from './task-service.js';
3
+ export interface StatsQueryOptions {
4
+ project?: string;
5
+ windowMinutes?: number;
6
+ windowLabel?: string;
7
+ asOf?: string;
8
+ }
9
+ export interface QueueStats {
10
+ backlog: number;
11
+ ready: number;
12
+ in_progress: number;
13
+ blocked: number;
14
+ done: number;
15
+ archived: number;
16
+ available: number;
17
+ stale: number;
18
+ expired_leases: number;
19
+ }
20
+ export interface CompletionStats {
21
+ total: number;
22
+ by_agent: Record<string, number>;
23
+ }
24
+ export interface ExecutionTimeStats {
25
+ count: number;
26
+ mean: number | null;
27
+ min: number | null;
28
+ max: number | null;
29
+ excluded_without_start: number;
30
+ }
31
+ export interface StatsSnapshot {
32
+ window: string;
33
+ generated_at: string;
34
+ projects: string[];
35
+ queue: QueueStats;
36
+ completions: CompletionStats;
37
+ execution_time_ms: ExecutionTimeStats;
38
+ }
39
+ export declare class StatsService {
40
+ private cacheDb;
41
+ private eventsDb;
42
+ private taskService;
43
+ constructor(cacheDb: Database.Database, eventsDb: Database.Database, taskService: Pick<TaskService, 'getAvailableTasks' | 'getStaleTasks'>);
44
+ getStats(options?: StatsQueryOptions): StatsSnapshot;
45
+ private getProjects;
46
+ private getTrackedTasks;
47
+ private getQueueStats;
48
+ private getExpiredLeaseCount;
49
+ private getHistoricalStats;
50
+ }
51
+ //# sourceMappingURL=stats-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stats-service.d.ts","sourceRoot":"","sources":["../../src/services/stats-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,QAAQ,CAAC;AACnC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAuBrD,MAAM,WAAW,iBAAiB;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,sBAAsB,EAAE,MAAM,CAAC;CAChC;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,KAAK,EAAE,UAAU,CAAC;IAClB,WAAW,EAAE,eAAe,CAAC;IAC7B,iBAAiB,EAAE,kBAAkB,CAAC;CACvC;AAED,qBAAa,YAAY;IAErB,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,WAAW;gBAFX,OAAO,EAAE,QAAQ,CAAC,QAAQ,EAC1B,QAAQ,EAAE,QAAQ,CAAC,QAAQ,EAC3B,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,mBAAmB,GAAG,eAAe,CAAC;IAG/E,QAAQ,CAAC,OAAO,GAAE,iBAAsB,GAAG,aAAa;IAyBxD,OAAO,CAAC,WAAW;IAOnB,OAAO,CAAC,eAAe;IAUvB,OAAO,CAAC,aAAa;IAoCrB,OAAO,CAAC,oBAAoB;IAoB5B,OAAO,CAAC,kBAAkB;CAuG3B"}
@@ -0,0 +1,183 @@
1
+ const DEFAULT_STALE_THRESHOLD_MINUTES = 10;
2
+ const DEFAULT_WINDOW_MINUTES = 24 * 60;
3
+ const TASK_CHUNK_SIZE = 500;
4
+ export class StatsService {
5
+ cacheDb;
6
+ eventsDb;
7
+ taskService;
8
+ constructor(cacheDb, eventsDb, taskService) {
9
+ this.cacheDb = cacheDb;
10
+ this.eventsDb = eventsDb;
11
+ this.taskService = taskService;
12
+ }
13
+ getStats(options = {}) {
14
+ const generatedAt = options.asOf ?? new Date().toISOString();
15
+ const windowMinutes = options.windowMinutes ?? DEFAULT_WINDOW_MINUTES;
16
+ const windowLabel = options.windowLabel ?? (windowMinutes === 24 * 60 ? '24h' : `${windowMinutes}m`);
17
+ const windowStart = new Date(new Date(generatedAt).getTime() - windowMinutes * 60_000).toISOString();
18
+ const trackedTasks = this.getTrackedTasks(options.project);
19
+ const historical = this.getHistoricalStats({
20
+ trackedTasks,
21
+ windowStart,
22
+ windowEnd: generatedAt,
23
+ });
24
+ return {
25
+ window: windowLabel,
26
+ generated_at: generatedAt,
27
+ projects: this.getProjects(),
28
+ queue: this.getQueueStats({ project: options.project, asOf: generatedAt }),
29
+ completions: historical.completions,
30
+ execution_time_ms: historical.execution_time_ms,
31
+ };
32
+ }
33
+ getProjects() {
34
+ const rows = this.cacheDb
35
+ .prepare('SELECT name FROM projects ORDER BY name')
36
+ .all();
37
+ return rows.map((row) => row.name);
38
+ }
39
+ getTrackedTasks(project) {
40
+ const rows = (project
41
+ ? this.cacheDb
42
+ .prepare('SELECT task_id, project, agent FROM tasks_current WHERE project = ?')
43
+ .all(project)
44
+ : this.cacheDb.prepare('SELECT task_id, project, agent FROM tasks_current').all());
45
+ return new Map(rows.map((row) => [row.task_id, row]));
46
+ }
47
+ getQueueStats(options) {
48
+ const params = [];
49
+ const projectWhere = options.project ? 'WHERE project = ?' : '';
50
+ if (options.project) {
51
+ params.push(options.project);
52
+ }
53
+ const rows = this.cacheDb.prepare(`
54
+ SELECT status, COUNT(*) as count
55
+ FROM tasks_current
56
+ ${projectWhere}
57
+ GROUP BY status
58
+ `).all(...params);
59
+ const queue = {
60
+ backlog: 0,
61
+ ready: 0,
62
+ in_progress: 0,
63
+ blocked: 0,
64
+ done: 0,
65
+ archived: 0,
66
+ available: this.taskService.getAvailableTasks({ project: options.project }).length,
67
+ stale: this.taskService.getStaleTasks({
68
+ project: options.project,
69
+ thresholdMinutes: DEFAULT_STALE_THRESHOLD_MINUTES,
70
+ }).size,
71
+ expired_leases: this.getExpiredLeaseCount(options),
72
+ };
73
+ for (const row of rows) {
74
+ queue[row.status] = row.count;
75
+ }
76
+ return queue;
77
+ }
78
+ getExpiredLeaseCount(options) {
79
+ const params = [options.asOf];
80
+ let projectClause = '';
81
+ if (options.project) {
82
+ projectClause = 'AND project = ?';
83
+ params.push(options.project);
84
+ }
85
+ const row = this.cacheDb.prepare(`
86
+ SELECT COUNT(*) as count
87
+ FROM tasks_current
88
+ WHERE status = 'in_progress'
89
+ AND lease_until IS NOT NULL
90
+ AND lease_until < ?
91
+ ${projectClause}
92
+ `).get(...params);
93
+ return row.count;
94
+ }
95
+ getHistoricalStats(options) {
96
+ const completions = {
97
+ total: 0,
98
+ by_agent: {},
99
+ };
100
+ const durations = [];
101
+ let excludedWithoutStart = 0;
102
+ if (options.trackedTasks.size === 0) {
103
+ return {
104
+ completions,
105
+ execution_time_ms: {
106
+ count: 0,
107
+ mean: null,
108
+ min: null,
109
+ max: null,
110
+ excluded_without_start: 0,
111
+ },
112
+ };
113
+ }
114
+ const taskIds = [...options.trackedTasks.keys()];
115
+ for (let index = 0; index < taskIds.length; index += TASK_CHUNK_SIZE) {
116
+ const chunk = taskIds.slice(index, index + TASK_CHUNK_SIZE);
117
+ const placeholders = chunk.map(() => '?').join(', ');
118
+ const rows = this.eventsDb.prepare(`
119
+ SELECT
120
+ e.task_id,
121
+ e.timestamp AS done_at,
122
+ (
123
+ SELECT start.timestamp
124
+ FROM events start
125
+ WHERE start.task_id = e.task_id
126
+ AND start.type = 'status_changed'
127
+ AND json_extract(start.data, '$.to') = 'in_progress'
128
+ AND start.id < e.id
129
+ ORDER BY start.id DESC
130
+ LIMIT 1
131
+ ) AS started_at
132
+ FROM events e
133
+ WHERE e.type = 'status_changed'
134
+ AND json_extract(e.data, '$.to') = 'done'
135
+ AND e.timestamp >= ?
136
+ AND e.timestamp <= ?
137
+ AND e.task_id IN (${placeholders})
138
+ ORDER BY e.id ASC
139
+ `).all(options.windowStart, options.windowEnd, ...chunk);
140
+ for (const row of rows) {
141
+ const task = options.trackedTasks.get(row.task_id);
142
+ if (!task) {
143
+ continue;
144
+ }
145
+ completions.total += 1;
146
+ if (task.agent) {
147
+ completions.by_agent[task.agent] = (completions.by_agent[task.agent] ?? 0) + 1;
148
+ }
149
+ if (!row.started_at) {
150
+ excludedWithoutStart += 1;
151
+ continue;
152
+ }
153
+ const duration = new Date(row.done_at).getTime() - new Date(row.started_at).getTime();
154
+ if (Number.isFinite(duration) && duration >= 0) {
155
+ durations.push(duration);
156
+ }
157
+ else {
158
+ excludedWithoutStart += 1;
159
+ }
160
+ }
161
+ }
162
+ const executionTimeMs = durations.length === 0
163
+ ? {
164
+ count: 0,
165
+ mean: null,
166
+ min: null,
167
+ max: null,
168
+ excluded_without_start: excludedWithoutStart,
169
+ }
170
+ : {
171
+ count: durations.length,
172
+ mean: durations.reduce((sum, duration) => sum + duration, 0) / durations.length,
173
+ min: durations.reduce((m, d) => (d < m ? d : m), Infinity),
174
+ max: durations.reduce((m, d) => (d > m ? d : m), -Infinity),
175
+ excluded_without_start: excludedWithoutStart,
176
+ };
177
+ return {
178
+ completions,
179
+ execution_time_ms: executionTimeMs,
180
+ };
181
+ }
182
+ }
183
+ //# sourceMappingURL=stats-service.js.map