sentinelayer-cli 0.4.5 → 0.8.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.
- package/README.md +16 -18
- package/package.json +7 -6
- package/src/agents/jules/config/definition.js +13 -62
- package/src/agents/jules/config/system-prompt.js +8 -1
- package/src/agents/jules/fix-cycle.js +12 -372
- package/src/agents/jules/loop.js +116 -26
- package/src/agents/jules/pulse.js +10 -327
- package/src/agents/jules/stream.js +13 -12
- package/src/agents/jules/swarm/orchestrator.js +3 -3
- package/src/agents/jules/swarm/sub-agent.js +6 -3
- package/src/agents/jules/tools/aidenid-email.js +189 -0
- package/src/agents/jules/tools/auth-audit.js +1187 -45
- package/src/agents/jules/tools/dispatch.js +25 -12
- package/src/agents/jules/tools/file-edit.js +2 -180
- package/src/agents/jules/tools/file-read.js +2 -100
- package/src/agents/jules/tools/glob.js +2 -168
- package/src/agents/jules/tools/grep.js +2 -228
- package/src/agents/jules/tools/path-guards.js +2 -161
- package/src/agents/jules/tools/runtime-audit.js +6 -2
- package/src/agents/jules/tools/shell.js +2 -383
- package/src/agents/persona-visuals.js +64 -0
- package/src/agents/shared-tools/dispatch-core.js +320 -0
- package/src/agents/shared-tools/file-edit.js +180 -0
- package/src/agents/shared-tools/file-read.js +100 -0
- package/src/agents/shared-tools/glob.js +168 -0
- package/src/agents/shared-tools/grep.js +228 -0
- package/src/agents/shared-tools/index.js +46 -0
- package/src/agents/shared-tools/path-guards.js +161 -0
- package/src/agents/shared-tools/shell.js +383 -0
- package/src/ai/aidenid.js +56 -7
- package/src/ai/client.js +45 -0
- package/src/ai/proxy.js +137 -0
- package/src/auth/gate.js +290 -16
- package/src/auth/http.js +450 -39
- package/src/auth/service.js +262 -47
- package/src/auth/session-store.js +475 -21
- package/src/cli.js +5 -0
- package/src/commands/audit.js +13 -8
- package/src/commands/auth.js +53 -9
- package/src/commands/omargate.js +10 -2
- package/src/commands/scan.js +10 -4
- package/src/commands/session.js +590 -0
- package/src/commands/spec.js +62 -0
- package/src/commands/watch.js +3 -2
- package/src/daemon/assignment-ledger.js +196 -0
- package/src/daemon/error-worker.js +599 -16
- package/src/daemon/fix-cycle.js +384 -0
- package/src/daemon/ingest-refresh.js +10 -9
- package/src/daemon/jira-lifecycle.js +135 -0
- package/src/daemon/pulse.js +327 -0
- package/src/daemon/scope-engine.js +1068 -0
- package/src/events/schema.js +190 -0
- package/src/interactive/index.js +18 -16
- package/src/legacy-cli.js +606 -37
- package/src/prompt/generator.js +19 -1
- package/src/review/ai-review.js +11 -1
- package/src/review/local-review.js +75 -19
- package/src/review/omargate-interactive.js +68 -0
- package/src/review/omargate-orchestrator.js +404 -0
- package/src/review/persona-prompts.js +296 -0
- package/src/review/scan-modes.js +48 -0
- package/src/scan/generator.js +1 -1
- package/src/session/agent-registry.js +352 -0
- package/src/session/daemon.js +801 -0
- package/src/session/paths.js +33 -0
- package/src/session/runtime-bridge.js +739 -0
- package/src/session/store.js +388 -0
- package/src/session/stream.js +325 -0
- package/src/spec/generator.js +100 -0
- package/src/telemetry/session-tracker.js +148 -32
- package/src/telemetry/sync.js +6 -2
- package/src/ui/command-hints.js +13 -0
|
@@ -3,13 +3,34 @@ import fsp from "node:fs/promises";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
|
|
5
5
|
import { resolveOutputRoot } from "../config/service.js";
|
|
6
|
+
import { createAgentEvent } from "../events/schema.js";
|
|
7
|
+
import { appendToStream } from "../session/stream.js";
|
|
6
8
|
|
|
7
9
|
const QUEUE_SCHEMA_VERSION = "1.0.0";
|
|
8
10
|
const STATE_SCHEMA_VERSION = "1.0.0";
|
|
9
11
|
const DEFAULT_MAX_EVENTS = 200;
|
|
12
|
+
const DEFAULT_DAEMON_POLL_MS = 5000;
|
|
13
|
+
const DEFAULT_SWEEP_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
14
|
+
const ERROR_DAEMON_AGENT_ID = "error-daemon";
|
|
15
|
+
const ACTIVE_DAEMONS = new Map();
|
|
10
16
|
|
|
11
17
|
const TERMINAL_WORK_ITEM_STATUSES = new Set(["DONE", "SQUASHED"]);
|
|
12
18
|
|
|
19
|
+
export const DEDUP_KEY_FIELDS = Object.freeze([
|
|
20
|
+
"service",
|
|
21
|
+
"endpoint",
|
|
22
|
+
"error_code",
|
|
23
|
+
"stack_fingerprint",
|
|
24
|
+
"commit_sha",
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
export const WAKE_MODES = Object.freeze({
|
|
28
|
+
REALTIME: "realtime",
|
|
29
|
+
SCHEDULED: "scheduled",
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const WAKE_MODE_SET = new Set(Object.values(WAKE_MODES));
|
|
33
|
+
|
|
13
34
|
export const WORK_ITEM_STATUSES = Object.freeze([
|
|
14
35
|
"QUEUED",
|
|
15
36
|
"ASSIGNED",
|
|
@@ -90,6 +111,51 @@ function normalizeStackTrace(value) {
|
|
|
90
111
|
return String(value || "").replace(/\r\n/g, "\n").trim();
|
|
91
112
|
}
|
|
92
113
|
|
|
114
|
+
function normalizeWakeMode(value, fallbackValue = WAKE_MODES.REALTIME) {
|
|
115
|
+
const normalized = normalizeString(value).toLowerCase();
|
|
116
|
+
if (!normalized) {
|
|
117
|
+
return fallbackValue;
|
|
118
|
+
}
|
|
119
|
+
if (!WAKE_MODE_SET.has(normalized)) {
|
|
120
|
+
throw new Error(`wakeMode must be one of: ${Object.values(WAKE_MODES).join(", ")}.`);
|
|
121
|
+
}
|
|
122
|
+
return normalized;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function normalizeRequestIds(values) {
|
|
126
|
+
const input = Array.isArray(values) ? values : [];
|
|
127
|
+
const deduped = [];
|
|
128
|
+
const seen = new Set();
|
|
129
|
+
for (const value of input) {
|
|
130
|
+
const normalized = normalizeString(value);
|
|
131
|
+
if (!normalized || seen.has(normalized)) {
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
seen.add(normalized);
|
|
135
|
+
deduped.push(normalized);
|
|
136
|
+
}
|
|
137
|
+
return deduped;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function mergeRequestIds(existing = [], next = []) {
|
|
141
|
+
return normalizeRequestIds([...normalizeRequestIds(existing), ...normalizeRequestIds(next)]);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function buildDedupKey(event = {}) {
|
|
145
|
+
const materialized = [
|
|
146
|
+
normalizeString(event.service).toLowerCase(),
|
|
147
|
+
normalizeString(event.endpoint).toLowerCase(),
|
|
148
|
+
normalizeString(event.errorCode || event.error_code).toLowerCase(),
|
|
149
|
+
normalizeString(event.stackFingerprint || event.stack_fingerprint).toLowerCase(),
|
|
150
|
+
normalizeString(event.commitSha || event.commit_sha).toLowerCase() || "none",
|
|
151
|
+
].join("|");
|
|
152
|
+
return materialized;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function buildDayKey(isoTimestamp = new Date().toISOString()) {
|
|
156
|
+
return normalizeIsoTimestamp(isoTimestamp, new Date().toISOString()).slice(0, 10);
|
|
157
|
+
}
|
|
158
|
+
|
|
93
159
|
function computeStackFingerprint(stackTrace = "") {
|
|
94
160
|
const normalized = normalizeStackTrace(stackTrace);
|
|
95
161
|
if (!normalized) {
|
|
@@ -108,14 +174,7 @@ function computeStackFingerprint(stackTrace = "") {
|
|
|
108
174
|
}
|
|
109
175
|
|
|
110
176
|
function computeFingerprint(event = {}) {
|
|
111
|
-
|
|
112
|
-
normalizeString(event.service).toLowerCase(),
|
|
113
|
-
normalizeString(event.endpoint).toLowerCase(),
|
|
114
|
-
normalizeString(event.errorCode).toLowerCase(),
|
|
115
|
-
normalizeString(event.stackFingerprint).toLowerCase(),
|
|
116
|
-
normalizeString(event.commitSha).toLowerCase(),
|
|
117
|
-
].join("|");
|
|
118
|
-
return createHash("sha256").update(materialized).digest("hex");
|
|
177
|
+
return createHash("sha256").update(buildDedupKey(event)).digest("hex");
|
|
119
178
|
}
|
|
120
179
|
|
|
121
180
|
function chooseHigherSeverity(left, right) {
|
|
@@ -148,22 +207,40 @@ function isTerminalStatus(status = "") {
|
|
|
148
207
|
|
|
149
208
|
function normalizeQueueItem(item = {}, fallbackNowIso = new Date().toISOString()) {
|
|
150
209
|
const createdAt = normalizeIsoTimestamp(item.createdAt, fallbackNowIso);
|
|
210
|
+
const service = normalizeString(item.service) || "unknown-service";
|
|
211
|
+
const endpoint = normalizeString(item.endpoint) || "unknown-endpoint";
|
|
212
|
+
const errorCode = normalizeString(item.errorCode || item.error_code) || "UNKNOWN_ERROR";
|
|
213
|
+
const stackFingerprint =
|
|
214
|
+
normalizeString(item.stackFingerprint || item.stack_fingerprint) || "none";
|
|
215
|
+
const commitSha = normalizeString(item.commitSha || item.commit_sha) || null;
|
|
216
|
+
const requestIds = normalizeRequestIds(item.requestIds || item.request_ids);
|
|
217
|
+
const dedupKey =
|
|
218
|
+
normalizeString(item.dedupKey || item.dedup_key) ||
|
|
219
|
+
buildDedupKey({
|
|
220
|
+
service,
|
|
221
|
+
endpoint,
|
|
222
|
+
errorCode,
|
|
223
|
+
stackFingerprint,
|
|
224
|
+
commitSha,
|
|
225
|
+
});
|
|
151
226
|
return {
|
|
152
227
|
workItemId: normalizeString(item.workItemId) || createWorkItemId(createdAt),
|
|
153
228
|
fingerprint: normalizeString(item.fingerprint),
|
|
154
229
|
source: normalizeString(item.source) || "admin_error_log",
|
|
155
|
-
service
|
|
156
|
-
endpoint
|
|
157
|
-
errorCode
|
|
230
|
+
service,
|
|
231
|
+
endpoint,
|
|
232
|
+
errorCode,
|
|
158
233
|
severity: normalizeSeverity(item.severity),
|
|
159
234
|
status: normalizeWorkItemStatus(item.status),
|
|
160
235
|
message: normalizeString(item.message),
|
|
161
|
-
stackFingerprint
|
|
162
|
-
commitSha
|
|
236
|
+
stackFingerprint,
|
|
237
|
+
commitSha,
|
|
238
|
+
dedupKey,
|
|
163
239
|
firstSeenAt: normalizeIsoTimestamp(item.firstSeenAt, createdAt),
|
|
164
240
|
lastSeenAt: normalizeIsoTimestamp(item.lastSeenAt, createdAt),
|
|
165
241
|
latestEventId: normalizeString(item.latestEventId) || null,
|
|
166
242
|
occurrenceCount: Math.max(1, normalizeNonNegativeInteger(item.occurrenceCount, 1)),
|
|
243
|
+
requestIds,
|
|
167
244
|
createdAt,
|
|
168
245
|
updatedAt: normalizeIsoTimestamp(item.updatedAt, createdAt),
|
|
169
246
|
metadata: normalizeMetadata(item.metadata),
|
|
@@ -286,6 +363,7 @@ function buildWorkItemFromEvent(event, nowIso) {
|
|
|
286
363
|
{
|
|
287
364
|
workItemId: createWorkItemId(nowIso),
|
|
288
365
|
fingerprint: event.fingerprint,
|
|
366
|
+
dedupKey: event.dedupKey,
|
|
289
367
|
source: event.source,
|
|
290
368
|
service: event.service,
|
|
291
369
|
endpoint: event.endpoint,
|
|
@@ -299,11 +377,13 @@ function buildWorkItemFromEvent(event, nowIso) {
|
|
|
299
377
|
lastSeenAt: event.occurredAt,
|
|
300
378
|
latestEventId: event.eventId,
|
|
301
379
|
occurrenceCount: 1,
|
|
380
|
+
requestIds: event.requestId ? [event.requestId] : [],
|
|
302
381
|
createdAt: nowIso,
|
|
303
382
|
updatedAt: nowIso,
|
|
304
383
|
metadata: {
|
|
305
384
|
...event.metadata,
|
|
306
385
|
sourceEventId: event.eventId,
|
|
386
|
+
requestId: event.requestId || null,
|
|
307
387
|
},
|
|
308
388
|
},
|
|
309
389
|
nowIso
|
|
@@ -313,6 +393,10 @@ function buildWorkItemFromEvent(event, nowIso) {
|
|
|
313
393
|
export function normalizeErrorEvent(event = {}, nowIso = new Date().toISOString()) {
|
|
314
394
|
const normalizedNow = normalizeIsoTimestamp(nowIso, new Date().toISOString());
|
|
315
395
|
const stackTrace = normalizeStackTrace(event.stackTrace || event.stack || "");
|
|
396
|
+
const requestId =
|
|
397
|
+
normalizeString(event.requestId || event.request_id) ||
|
|
398
|
+
normalizeString(event?.metadata?.requestId || event?.metadata?.request_id) ||
|
|
399
|
+
null;
|
|
316
400
|
const normalized = {
|
|
317
401
|
eventId: normalizeString(event.eventId) || randomUUID(),
|
|
318
402
|
occurredAt: normalizeIsoTimestamp(event.occurredAt, normalizedNow),
|
|
@@ -325,11 +409,14 @@ export function normalizeErrorEvent(event = {}, nowIso = new Date().toISOString(
|
|
|
325
409
|
message: normalizeString(event.message) || "Unhandled runtime error",
|
|
326
410
|
stackTrace,
|
|
327
411
|
stackFingerprint: computeStackFingerprint(stackTrace),
|
|
328
|
-
commitSha: normalizeString(event.commitSha) || null,
|
|
412
|
+
commitSha: normalizeString(event.commitSha || event.commit_sha) || null,
|
|
413
|
+
requestId,
|
|
329
414
|
metadata: normalizeMetadata(event.metadata),
|
|
330
415
|
};
|
|
416
|
+
const dedupKey = buildDedupKey(normalized);
|
|
331
417
|
return {
|
|
332
418
|
...normalized,
|
|
419
|
+
dedupKey,
|
|
333
420
|
fingerprint: computeFingerprint(normalized),
|
|
334
421
|
};
|
|
335
422
|
}
|
|
@@ -346,15 +433,18 @@ export async function resolveErrorDaemonStorage({
|
|
|
346
433
|
env,
|
|
347
434
|
homeDir,
|
|
348
435
|
});
|
|
349
|
-
const
|
|
436
|
+
const observabilityRoot = path.join(outputRoot, "observability");
|
|
437
|
+
const baseDir = path.join(observabilityRoot, "error-daemon");
|
|
350
438
|
return {
|
|
351
439
|
outputRoot,
|
|
440
|
+
observabilityRoot,
|
|
352
441
|
baseDir,
|
|
353
442
|
streamPath: path.join(baseDir, "admin-error-stream.ndjson"),
|
|
354
443
|
queuePath: path.join(baseDir, "queue.json"),
|
|
355
444
|
statePath: path.join(baseDir, "worker-state.json"),
|
|
356
445
|
intakeDir: path.join(baseDir, "intake"),
|
|
357
446
|
runsDir: path.join(baseDir, "runs"),
|
|
447
|
+
sweepsDir: path.join(baseDir, "sweeps"),
|
|
358
448
|
};
|
|
359
449
|
}
|
|
360
450
|
|
|
@@ -404,6 +494,86 @@ export async function appendAdminErrorEvent({
|
|
|
404
494
|
};
|
|
405
495
|
}
|
|
406
496
|
|
|
497
|
+
function resolveWorkItemIntakePath(storage, queueItem, nowIso = new Date().toISOString()) {
|
|
498
|
+
const dayKey = buildDayKey(queueItem.firstSeenAt || nowIso);
|
|
499
|
+
return path.join(storage.observabilityRoot, dayKey, queueItem.workItemId, "intake_event.json");
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
async function writeWorkItemIntakeArtifact(
|
|
503
|
+
storage,
|
|
504
|
+
queueItem,
|
|
505
|
+
{
|
|
506
|
+
nowIso = new Date().toISOString(),
|
|
507
|
+
} = {}
|
|
508
|
+
) {
|
|
509
|
+
const normalizedNow = normalizeIsoTimestamp(nowIso, new Date().toISOString());
|
|
510
|
+
const intakePath = resolveWorkItemIntakePath(storage, queueItem, normalizedNow);
|
|
511
|
+
await fsp.mkdir(path.dirname(intakePath), { recursive: true });
|
|
512
|
+
const normalizedRequestIds = normalizeRequestIds(queueItem.requestIds || []);
|
|
513
|
+
const payload = {
|
|
514
|
+
schemaVersion: "1.0.0",
|
|
515
|
+
generated_at: normalizedNow,
|
|
516
|
+
work_item_id: queueItem.workItemId,
|
|
517
|
+
service: queueItem.service,
|
|
518
|
+
endpoint: queueItem.endpoint,
|
|
519
|
+
error_code: queueItem.errorCode,
|
|
520
|
+
stack_fingerprint: queueItem.stackFingerprint,
|
|
521
|
+
commit_sha: queueItem.commitSha,
|
|
522
|
+
first_seen_at: queueItem.firstSeenAt,
|
|
523
|
+
occurrence_count: Math.max(1, Number(queueItem.occurrenceCount || 1)),
|
|
524
|
+
last_seen_at: queueItem.lastSeenAt,
|
|
525
|
+
dedup_key:
|
|
526
|
+
normalizeString(queueItem.dedupKey) ||
|
|
527
|
+
buildDedupKey({
|
|
528
|
+
service: queueItem.service,
|
|
529
|
+
endpoint: queueItem.endpoint,
|
|
530
|
+
errorCode: queueItem.errorCode,
|
|
531
|
+
stackFingerprint: queueItem.stackFingerprint,
|
|
532
|
+
commitSha: queueItem.commitSha,
|
|
533
|
+
}),
|
|
534
|
+
request_ids: normalizedRequestIds,
|
|
535
|
+
fingerprint: queueItem.fingerprint,
|
|
536
|
+
severity: queueItem.severity,
|
|
537
|
+
status: queueItem.status,
|
|
538
|
+
message: queueItem.message,
|
|
539
|
+
};
|
|
540
|
+
await fsp.writeFile(intakePath, `${JSON.stringify(payload, null, 2)}\n`, "utf-8");
|
|
541
|
+
return {
|
|
542
|
+
intakePath,
|
|
543
|
+
payload,
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
async function emitDaemonSessionEvent(
|
|
548
|
+
sessionId,
|
|
549
|
+
payload = {},
|
|
550
|
+
{
|
|
551
|
+
event = "agent_intake",
|
|
552
|
+
targetPath = ".",
|
|
553
|
+
nowIso = new Date().toISOString(),
|
|
554
|
+
} = {}
|
|
555
|
+
) {
|
|
556
|
+
const normalizedSessionId = normalizeString(sessionId);
|
|
557
|
+
if (!normalizedSessionId) {
|
|
558
|
+
return null;
|
|
559
|
+
}
|
|
560
|
+
const normalizedPayload =
|
|
561
|
+
payload && typeof payload === "object" && !Array.isArray(payload) ? { ...payload } : {};
|
|
562
|
+
const normalizedNow = normalizeIsoTimestamp(nowIso, new Date().toISOString());
|
|
563
|
+
const envelope = createAgentEvent({
|
|
564
|
+
event: normalizeString(event) || "agent_intake",
|
|
565
|
+
agentId: ERROR_DAEMON_AGENT_ID,
|
|
566
|
+
sessionId: normalizedSessionId,
|
|
567
|
+
ts: normalizedNow,
|
|
568
|
+
workItemId: normalizedPayload.workItemId || normalizedPayload.work_item_id || undefined,
|
|
569
|
+
payload: normalizedPayload,
|
|
570
|
+
});
|
|
571
|
+
await appendToStream(normalizedSessionId, envelope, {
|
|
572
|
+
targetPath: path.resolve(String(targetPath || ".")),
|
|
573
|
+
});
|
|
574
|
+
return envelope;
|
|
575
|
+
}
|
|
576
|
+
|
|
407
577
|
export async function getErrorDaemonState({
|
|
408
578
|
targetPath = ".",
|
|
409
579
|
outputDir = "",
|
|
@@ -427,14 +597,19 @@ export async function runErrorDaemonWorker({
|
|
|
427
597
|
targetPath = ".",
|
|
428
598
|
outputDir = "",
|
|
429
599
|
maxEvents = DEFAULT_MAX_EVENTS,
|
|
600
|
+
sessionId = "",
|
|
601
|
+
wakeMode = WAKE_MODES.REALTIME,
|
|
430
602
|
env,
|
|
431
603
|
homeDir,
|
|
432
604
|
nowIso = new Date().toISOString(),
|
|
433
605
|
} = {}) {
|
|
434
606
|
const normalizedNow = normalizeIsoTimestamp(nowIso, new Date().toISOString());
|
|
435
607
|
const normalizedMaxEvents = normalizePositiveInteger(maxEvents, DEFAULT_MAX_EVENTS);
|
|
608
|
+
const normalizedWakeMode = normalizeWakeMode(wakeMode, WAKE_MODES.REALTIME);
|
|
609
|
+
const normalizedSessionId = normalizeString(sessionId);
|
|
610
|
+
const resolvedTargetPath = path.resolve(String(targetPath || "."));
|
|
436
611
|
const storage = await resolveErrorDaemonStorage({
|
|
437
|
-
targetPath,
|
|
612
|
+
targetPath: resolvedTargetPath,
|
|
438
613
|
outputDir,
|
|
439
614
|
env,
|
|
440
615
|
homeDir,
|
|
@@ -463,6 +638,7 @@ export async function runErrorDaemonWorker({
|
|
|
463
638
|
let queuedCount = 0;
|
|
464
639
|
let dedupedCount = 0;
|
|
465
640
|
let parseErrorCount = 0;
|
|
641
|
+
const intakeMutations = [];
|
|
466
642
|
for (let index = startOffset; index < endOffset; index += 1) {
|
|
467
643
|
const rawLine = lines[index];
|
|
468
644
|
if (!rawLine) {
|
|
@@ -486,6 +662,11 @@ export async function runErrorDaemonWorker({
|
|
|
486
662
|
existing.updatedAt = normalizedNow;
|
|
487
663
|
existing.latestEventId = event.eventId;
|
|
488
664
|
existing.severity = chooseHigherSeverity(existing.severity, event.severity);
|
|
665
|
+
existing.dedupKey =
|
|
666
|
+
normalizeString(existing.dedupKey) ||
|
|
667
|
+
normalizeString(event.dedupKey) ||
|
|
668
|
+
buildDedupKey(event);
|
|
669
|
+
existing.requestIds = mergeRequestIds(existing.requestIds, event.requestId ? [event.requestId] : []);
|
|
489
670
|
if (event.message) {
|
|
490
671
|
existing.message = event.message;
|
|
491
672
|
}
|
|
@@ -493,7 +674,13 @@ export async function runErrorDaemonWorker({
|
|
|
493
674
|
...normalizeMetadata(existing.metadata),
|
|
494
675
|
...normalizeMetadata(event.metadata),
|
|
495
676
|
sourceEventId: event.eventId,
|
|
677
|
+
requestId: event.requestId || null,
|
|
496
678
|
};
|
|
679
|
+
intakeMutations.push({
|
|
680
|
+
action: "dedup",
|
|
681
|
+
queueItem: existing,
|
|
682
|
+
sourceEvent: event,
|
|
683
|
+
});
|
|
497
684
|
continue;
|
|
498
685
|
}
|
|
499
686
|
|
|
@@ -501,6 +688,11 @@ export async function runErrorDaemonWorker({
|
|
|
501
688
|
queue.items.push(queued);
|
|
502
689
|
openItemsByFingerprint.set(queued.fingerprint, queued);
|
|
503
690
|
queuedCount += 1;
|
|
691
|
+
intakeMutations.push({
|
|
692
|
+
action: "new",
|
|
693
|
+
queueItem: queued,
|
|
694
|
+
sourceEvent: event,
|
|
695
|
+
});
|
|
504
696
|
}
|
|
505
697
|
|
|
506
698
|
queue.generatedAt = normalizedNow;
|
|
@@ -521,6 +713,52 @@ export async function runErrorDaemonWorker({
|
|
|
521
713
|
normalizedNow
|
|
522
714
|
);
|
|
523
715
|
|
|
716
|
+
const intakeArtifacts = [];
|
|
717
|
+
const emittedEvents = [];
|
|
718
|
+
for (const mutation of intakeMutations) {
|
|
719
|
+
const written = await writeWorkItemIntakeArtifact(storage, mutation.queueItem, {
|
|
720
|
+
nowIso: normalizedNow,
|
|
721
|
+
});
|
|
722
|
+
intakeArtifacts.push({
|
|
723
|
+
action: mutation.action,
|
|
724
|
+
workItemId: mutation.queueItem.workItemId,
|
|
725
|
+
intakePath: written.intakePath,
|
|
726
|
+
dedupKey: written.payload.dedup_key,
|
|
727
|
+
occurrenceCount: written.payload.occurrence_count,
|
|
728
|
+
});
|
|
729
|
+
if (normalizedSessionId) {
|
|
730
|
+
const emitted = await emitDaemonSessionEvent(
|
|
731
|
+
normalizedSessionId,
|
|
732
|
+
{
|
|
733
|
+
action: mutation.action,
|
|
734
|
+
wakeMode: normalizedWakeMode,
|
|
735
|
+
source: mutation.sourceEvent.source,
|
|
736
|
+
eventId: mutation.sourceEvent.eventId,
|
|
737
|
+
workItemId: mutation.queueItem.workItemId,
|
|
738
|
+
service: mutation.queueItem.service,
|
|
739
|
+
endpoint: mutation.queueItem.endpoint,
|
|
740
|
+
errorCode: mutation.queueItem.errorCode,
|
|
741
|
+
severity: mutation.queueItem.severity,
|
|
742
|
+
stackFingerprint: mutation.queueItem.stackFingerprint,
|
|
743
|
+
commitSha: mutation.queueItem.commitSha,
|
|
744
|
+
dedupKey:
|
|
745
|
+
normalizeString(mutation.queueItem.dedupKey) ||
|
|
746
|
+
buildDedupKey(mutation.queueItem),
|
|
747
|
+
occurrenceCount: Math.max(1, Number(mutation.queueItem.occurrenceCount || 1)),
|
|
748
|
+
requestId: mutation.sourceEvent.requestId || null,
|
|
749
|
+
},
|
|
750
|
+
{
|
|
751
|
+
event: "agent_intake",
|
|
752
|
+
targetPath: resolvedTargetPath,
|
|
753
|
+
nowIso: normalizedNow,
|
|
754
|
+
}
|
|
755
|
+
);
|
|
756
|
+
if (emitted) {
|
|
757
|
+
emittedEvents.push(emitted);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
|
|
524
762
|
await fsp.mkdir(storage.runsDir, { recursive: true });
|
|
525
763
|
const runId = `error-daemon-run-${stableTimestampForFile(new Date(normalizedNow))}-${randomUUID().slice(0, 8)}`;
|
|
526
764
|
const runPath = path.join(storage.runsDir, `${runId}.json`);
|
|
@@ -531,6 +769,8 @@ export async function runErrorDaemonWorker({
|
|
|
531
769
|
schemaVersion: "1.0.0",
|
|
532
770
|
generatedAt: normalizedNow,
|
|
533
771
|
runId,
|
|
772
|
+
wakeMode: normalizedWakeMode,
|
|
773
|
+
sessionId: normalizedSessionId || null,
|
|
534
774
|
streamPath: storage.streamPath,
|
|
535
775
|
queuePath: storage.queuePath,
|
|
536
776
|
statePath: storage.statePath,
|
|
@@ -543,6 +783,8 @@ export async function runErrorDaemonWorker({
|
|
|
543
783
|
dedupedCount,
|
|
544
784
|
parseErrorCount,
|
|
545
785
|
queueDepth: savedQueue.items.length,
|
|
786
|
+
intakeArtifactCount: intakeArtifacts.length,
|
|
787
|
+
emittedEventCount: emittedEvents.length,
|
|
546
788
|
},
|
|
547
789
|
null,
|
|
548
790
|
2
|
|
@@ -554,6 +796,8 @@ export async function runErrorDaemonWorker({
|
|
|
554
796
|
...storage,
|
|
555
797
|
runId,
|
|
556
798
|
runPath,
|
|
799
|
+
wakeMode: normalizedWakeMode,
|
|
800
|
+
sessionId: normalizedSessionId || null,
|
|
557
801
|
maxEvents: normalizedMaxEvents,
|
|
558
802
|
startOffset,
|
|
559
803
|
endOffset,
|
|
@@ -563,11 +807,350 @@ export async function runErrorDaemonWorker({
|
|
|
563
807
|
dedupedCount,
|
|
564
808
|
parseErrorCount,
|
|
565
809
|
queueDepth: savedQueue.items.length,
|
|
810
|
+
intakeArtifacts,
|
|
811
|
+
emittedEvents,
|
|
566
812
|
queue: savedQueue,
|
|
567
813
|
state: savedState,
|
|
568
814
|
};
|
|
569
815
|
}
|
|
570
816
|
|
|
817
|
+
function buildDaemonInstanceKey({
|
|
818
|
+
targetPath = ".",
|
|
819
|
+
sessionId = "",
|
|
820
|
+
wakeMode = WAKE_MODES.REALTIME,
|
|
821
|
+
} = {}) {
|
|
822
|
+
const resolvedTargetPath = path.resolve(String(targetPath || "."));
|
|
823
|
+
const normalizedSessionId = normalizeString(sessionId) || "no-session";
|
|
824
|
+
const normalizedWakeMode = normalizeWakeMode(wakeMode, WAKE_MODES.REALTIME);
|
|
825
|
+
return `${resolvedTargetPath}::${normalizedSessionId}::${normalizedWakeMode}`;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
function buildSeverityCounts(items = []) {
|
|
829
|
+
const counts = {
|
|
830
|
+
UNKNOWN: 0,
|
|
831
|
+
P3: 0,
|
|
832
|
+
P2: 0,
|
|
833
|
+
P1: 0,
|
|
834
|
+
P0: 0,
|
|
835
|
+
};
|
|
836
|
+
for (const item of items) {
|
|
837
|
+
const normalizedSeverity = normalizeSeverity(item?.severity);
|
|
838
|
+
counts[normalizedSeverity] = (counts[normalizedSeverity] || 0) + 1;
|
|
839
|
+
}
|
|
840
|
+
return counts;
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
export async function scheduledErrorSweep({
|
|
844
|
+
targetPath = ".",
|
|
845
|
+
outputDir = "",
|
|
846
|
+
sessionId = "",
|
|
847
|
+
region = "global",
|
|
848
|
+
tz = "UTC",
|
|
849
|
+
env,
|
|
850
|
+
homeDir,
|
|
851
|
+
nowIso = new Date().toISOString(),
|
|
852
|
+
} = {}) {
|
|
853
|
+
const normalizedNow = normalizeIsoTimestamp(nowIso, new Date().toISOString());
|
|
854
|
+
const resolvedTargetPath = path.resolve(String(targetPath || "."));
|
|
855
|
+
const normalizedSessionId = normalizeString(sessionId);
|
|
856
|
+
const normalizedRegion = normalizeString(region) || "global";
|
|
857
|
+
const normalizedTz = normalizeString(tz) || "UTC";
|
|
858
|
+
const storage = await resolveErrorDaemonStorage({
|
|
859
|
+
targetPath: resolvedTargetPath,
|
|
860
|
+
outputDir,
|
|
861
|
+
env,
|
|
862
|
+
homeDir,
|
|
863
|
+
});
|
|
864
|
+
const queue = await loadQueueFile(storage.queuePath, normalizedNow);
|
|
865
|
+
const activeItems = queue.items.filter((item) => !isTerminalStatus(item.status));
|
|
866
|
+
const severityCounts = buildSeverityCounts(activeItems);
|
|
867
|
+
const totalOccurrences = activeItems.reduce(
|
|
868
|
+
(sum, item) => sum + Math.max(1, Number(item.occurrenceCount || 1)),
|
|
869
|
+
0
|
|
870
|
+
);
|
|
871
|
+
const runId = `error-daemon-sweep-${stableTimestampForFile(new Date(normalizedNow))}-${randomUUID().slice(0, 8)}`;
|
|
872
|
+
|
|
873
|
+
const digest = {
|
|
874
|
+
schemaVersion: "1.0.0",
|
|
875
|
+
generatedAt: normalizedNow,
|
|
876
|
+
runId,
|
|
877
|
+
wakeMode: WAKE_MODES.SCHEDULED,
|
|
878
|
+
region: normalizedRegion,
|
|
879
|
+
tz: normalizedTz,
|
|
880
|
+
queueDepth: activeItems.length,
|
|
881
|
+
totalOccurrences,
|
|
882
|
+
severityCounts,
|
|
883
|
+
workItems: activeItems.map((item) => ({
|
|
884
|
+
workItemId: item.workItemId,
|
|
885
|
+
service: item.service,
|
|
886
|
+
endpoint: item.endpoint,
|
|
887
|
+
errorCode: item.errorCode,
|
|
888
|
+
severity: item.severity,
|
|
889
|
+
status: item.status,
|
|
890
|
+
occurrenceCount: item.occurrenceCount,
|
|
891
|
+
dedupKey: item.dedupKey,
|
|
892
|
+
lastSeenAt: item.lastSeenAt,
|
|
893
|
+
})),
|
|
894
|
+
};
|
|
895
|
+
|
|
896
|
+
await fsp.mkdir(storage.sweepsDir, { recursive: true });
|
|
897
|
+
const sweepPath = path.join(storage.sweepsDir, `${runId}.json`);
|
|
898
|
+
await fsp.writeFile(sweepPath, `${JSON.stringify(digest, null, 2)}\n`, "utf-8");
|
|
899
|
+
|
|
900
|
+
let sweepEvent = null;
|
|
901
|
+
if (normalizedSessionId) {
|
|
902
|
+
sweepEvent = await emitDaemonSessionEvent(
|
|
903
|
+
normalizedSessionId,
|
|
904
|
+
{
|
|
905
|
+
action: "scheduled_rollup",
|
|
906
|
+
wakeMode: WAKE_MODES.SCHEDULED,
|
|
907
|
+
region: normalizedRegion,
|
|
908
|
+
tz: normalizedTz,
|
|
909
|
+
queueDepth: activeItems.length,
|
|
910
|
+
totalOccurrences,
|
|
911
|
+
severityCounts,
|
|
912
|
+
runId,
|
|
913
|
+
},
|
|
914
|
+
{
|
|
915
|
+
event: "agent_intake",
|
|
916
|
+
targetPath: resolvedTargetPath,
|
|
917
|
+
nowIso: normalizedNow,
|
|
918
|
+
}
|
|
919
|
+
);
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
return {
|
|
923
|
+
...storage,
|
|
924
|
+
runId,
|
|
925
|
+
sweepPath,
|
|
926
|
+
wakeMode: WAKE_MODES.SCHEDULED,
|
|
927
|
+
region: normalizedRegion,
|
|
928
|
+
tz: normalizedTz,
|
|
929
|
+
queueDepth: activeItems.length,
|
|
930
|
+
totalOccurrences,
|
|
931
|
+
severityCounts,
|
|
932
|
+
digest,
|
|
933
|
+
event: sweepEvent,
|
|
934
|
+
};
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
export async function startErrorEventDaemon({
|
|
938
|
+
sessionId = "",
|
|
939
|
+
wakeMode = WAKE_MODES.REALTIME,
|
|
940
|
+
targetPath = ".",
|
|
941
|
+
outputDir = "",
|
|
942
|
+
pollMs = DEFAULT_DAEMON_POLL_MS,
|
|
943
|
+
sweepIntervalMs = DEFAULT_SWEEP_INTERVAL_MS,
|
|
944
|
+
maxEvents = DEFAULT_MAX_EVENTS,
|
|
945
|
+
autoStart = true,
|
|
946
|
+
region = "global",
|
|
947
|
+
tz = "UTC",
|
|
948
|
+
env,
|
|
949
|
+
homeDir,
|
|
950
|
+
} = {}) {
|
|
951
|
+
const normalizedWakeMode = normalizeWakeMode(wakeMode, WAKE_MODES.REALTIME);
|
|
952
|
+
const normalizedSessionId = normalizeString(sessionId);
|
|
953
|
+
const resolvedTargetPath = path.resolve(String(targetPath || "."));
|
|
954
|
+
const daemonKey = buildDaemonInstanceKey({
|
|
955
|
+
targetPath: resolvedTargetPath,
|
|
956
|
+
sessionId: normalizedSessionId,
|
|
957
|
+
wakeMode: normalizedWakeMode,
|
|
958
|
+
});
|
|
959
|
+
const existing = ACTIVE_DAEMONS.get(daemonKey);
|
|
960
|
+
if (existing) {
|
|
961
|
+
return existing.handle;
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
const controller = new AbortController();
|
|
965
|
+
const startedAt = new Date().toISOString();
|
|
966
|
+
const normalizedPollMs = normalizePositiveInteger(pollMs, DEFAULT_DAEMON_POLL_MS);
|
|
967
|
+
const normalizedSweepIntervalMs = normalizePositiveInteger(
|
|
968
|
+
sweepIntervalMs,
|
|
969
|
+
DEFAULT_SWEEP_INTERVAL_MS
|
|
970
|
+
);
|
|
971
|
+
|
|
972
|
+
const daemonState = {
|
|
973
|
+
daemonKey,
|
|
974
|
+
sessionId: normalizedSessionId || null,
|
|
975
|
+
wakeMode: normalizedWakeMode,
|
|
976
|
+
targetPath: resolvedTargetPath,
|
|
977
|
+
startedAt,
|
|
978
|
+
running: true,
|
|
979
|
+
timer: null,
|
|
980
|
+
inFlight: false,
|
|
981
|
+
lastTickAt: null,
|
|
982
|
+
lastTickResult: null,
|
|
983
|
+
stopReason: null,
|
|
984
|
+
};
|
|
985
|
+
|
|
986
|
+
const runTick = async () => {
|
|
987
|
+
if (!daemonState.running || controller.signal.aborted || daemonState.inFlight) {
|
|
988
|
+
return daemonState.lastTickResult;
|
|
989
|
+
}
|
|
990
|
+
daemonState.inFlight = true;
|
|
991
|
+
try {
|
|
992
|
+
const result =
|
|
993
|
+
normalizedWakeMode === WAKE_MODES.SCHEDULED
|
|
994
|
+
? await scheduledErrorSweep({
|
|
995
|
+
targetPath: resolvedTargetPath,
|
|
996
|
+
outputDir,
|
|
997
|
+
sessionId: normalizedSessionId,
|
|
998
|
+
region,
|
|
999
|
+
tz,
|
|
1000
|
+
env,
|
|
1001
|
+
homeDir,
|
|
1002
|
+
})
|
|
1003
|
+
: await runErrorDaemonWorker({
|
|
1004
|
+
targetPath: resolvedTargetPath,
|
|
1005
|
+
outputDir,
|
|
1006
|
+
sessionId: normalizedSessionId,
|
|
1007
|
+
wakeMode: normalizedWakeMode,
|
|
1008
|
+
maxEvents,
|
|
1009
|
+
env,
|
|
1010
|
+
homeDir,
|
|
1011
|
+
});
|
|
1012
|
+
daemonState.lastTickAt = new Date().toISOString();
|
|
1013
|
+
daemonState.lastTickResult = result;
|
|
1014
|
+
return result;
|
|
1015
|
+
} finally {
|
|
1016
|
+
daemonState.inFlight = false;
|
|
1017
|
+
}
|
|
1018
|
+
};
|
|
1019
|
+
|
|
1020
|
+
const stop = async (reason = "manual") => {
|
|
1021
|
+
if (!daemonState.running) {
|
|
1022
|
+
return {
|
|
1023
|
+
stopped: false,
|
|
1024
|
+
daemonKey,
|
|
1025
|
+
reason: daemonState.stopReason || normalizeString(reason) || "manual",
|
|
1026
|
+
};
|
|
1027
|
+
}
|
|
1028
|
+
daemonState.running = false;
|
|
1029
|
+
daemonState.stopReason = normalizeString(reason) || "manual";
|
|
1030
|
+
controller.abort();
|
|
1031
|
+
if (daemonState.timer) {
|
|
1032
|
+
clearInterval(daemonState.timer);
|
|
1033
|
+
daemonState.timer = null;
|
|
1034
|
+
}
|
|
1035
|
+
ACTIVE_DAEMONS.delete(daemonKey);
|
|
1036
|
+
let event = null;
|
|
1037
|
+
if (normalizedSessionId) {
|
|
1038
|
+
event = await emitDaemonSessionEvent(
|
|
1039
|
+
normalizedSessionId,
|
|
1040
|
+
{
|
|
1041
|
+
target: ERROR_DAEMON_AGENT_ID,
|
|
1042
|
+
reason: daemonState.stopReason,
|
|
1043
|
+
wakeMode: normalizedWakeMode,
|
|
1044
|
+
},
|
|
1045
|
+
{
|
|
1046
|
+
event: "agent_killed",
|
|
1047
|
+
targetPath: resolvedTargetPath,
|
|
1048
|
+
nowIso: new Date().toISOString(),
|
|
1049
|
+
}
|
|
1050
|
+
);
|
|
1051
|
+
}
|
|
1052
|
+
return {
|
|
1053
|
+
stopped: true,
|
|
1054
|
+
daemonKey,
|
|
1055
|
+
sessionId: normalizedSessionId || null,
|
|
1056
|
+
wakeMode: normalizedWakeMode,
|
|
1057
|
+
stoppedAt: new Date().toISOString(),
|
|
1058
|
+
reason: daemonState.stopReason,
|
|
1059
|
+
event,
|
|
1060
|
+
};
|
|
1061
|
+
};
|
|
1062
|
+
|
|
1063
|
+
const handle = {
|
|
1064
|
+
daemonKey,
|
|
1065
|
+
sessionId: normalizedSessionId || null,
|
|
1066
|
+
wakeMode: normalizedWakeMode,
|
|
1067
|
+
targetPath: resolvedTargetPath,
|
|
1068
|
+
startedAt,
|
|
1069
|
+
signal: controller.signal,
|
|
1070
|
+
runTick,
|
|
1071
|
+
stop,
|
|
1072
|
+
isRunning: () => daemonState.running,
|
|
1073
|
+
getState: () => ({
|
|
1074
|
+
daemonKey,
|
|
1075
|
+
sessionId: normalizedSessionId || null,
|
|
1076
|
+
wakeMode: normalizedWakeMode,
|
|
1077
|
+
targetPath: resolvedTargetPath,
|
|
1078
|
+
startedAt,
|
|
1079
|
+
running: daemonState.running,
|
|
1080
|
+
lastTickAt: daemonState.lastTickAt,
|
|
1081
|
+
stopReason: daemonState.stopReason,
|
|
1082
|
+
}),
|
|
1083
|
+
};
|
|
1084
|
+
ACTIVE_DAEMONS.set(daemonKey, {
|
|
1085
|
+
...daemonState,
|
|
1086
|
+
handle,
|
|
1087
|
+
});
|
|
1088
|
+
|
|
1089
|
+
if (autoStart) {
|
|
1090
|
+
await runTick();
|
|
1091
|
+
const intervalMs =
|
|
1092
|
+
normalizedWakeMode === WAKE_MODES.SCHEDULED ? normalizedSweepIntervalMs : normalizedPollMs;
|
|
1093
|
+
daemonState.timer = setInterval(() => {
|
|
1094
|
+
void runTick().catch(() => {});
|
|
1095
|
+
}, intervalMs);
|
|
1096
|
+
if (typeof daemonState.timer.unref === "function") {
|
|
1097
|
+
daemonState.timer.unref();
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
return handle;
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
export async function stopErrorEventDaemon({
|
|
1105
|
+
targetPath = ".",
|
|
1106
|
+
sessionId = "",
|
|
1107
|
+
wakeMode = "",
|
|
1108
|
+
reason = "manual",
|
|
1109
|
+
} = {}) {
|
|
1110
|
+
const resolvedTargetPath = path.resolve(String(targetPath || "."));
|
|
1111
|
+
const normalizedSessionId = normalizeString(sessionId);
|
|
1112
|
+
const normalizedReason = normalizeString(reason) || "manual";
|
|
1113
|
+
const normalizedWakeMode = normalizeString(wakeMode)
|
|
1114
|
+
? normalizeWakeMode(wakeMode, WAKE_MODES.REALTIME)
|
|
1115
|
+
: "";
|
|
1116
|
+
|
|
1117
|
+
const matching = [];
|
|
1118
|
+
for (const [key, value] of ACTIVE_DAEMONS.entries()) {
|
|
1119
|
+
const sameTargetPath = value.targetPath === resolvedTargetPath;
|
|
1120
|
+
const sameSession =
|
|
1121
|
+
!normalizedSessionId || normalizeString(value.sessionId) === normalizedSessionId;
|
|
1122
|
+
const sameWakeMode = !normalizedWakeMode || value.wakeMode === normalizedWakeMode;
|
|
1123
|
+
if (sameTargetPath && sameSession && sameWakeMode) {
|
|
1124
|
+
matching.push({ key, value });
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
if (matching.length === 0) {
|
|
1129
|
+
return {
|
|
1130
|
+
stopped: false,
|
|
1131
|
+
count: 0,
|
|
1132
|
+
targetPath: resolvedTargetPath,
|
|
1133
|
+
sessionId: normalizedSessionId || null,
|
|
1134
|
+
wakeMode: normalizedWakeMode || null,
|
|
1135
|
+
};
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
const stopped = [];
|
|
1139
|
+
for (const match of matching) {
|
|
1140
|
+
const result = await match.value.handle.stop(normalizedReason);
|
|
1141
|
+
stopped.push(result);
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
return {
|
|
1145
|
+
stopped: true,
|
|
1146
|
+
count: stopped.length,
|
|
1147
|
+
targetPath: resolvedTargetPath,
|
|
1148
|
+
sessionId: normalizedSessionId || null,
|
|
1149
|
+
wakeMode: normalizedWakeMode || null,
|
|
1150
|
+
daemons: stopped,
|
|
1151
|
+
};
|
|
1152
|
+
}
|
|
1153
|
+
|
|
571
1154
|
function parseStatusFilter(statuses = []) {
|
|
572
1155
|
if (!Array.isArray(statuses)) {
|
|
573
1156
|
return null;
|