@yugabytedb/meko-mcp 2.2.0 → 2.5.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 +168 -61
- package/assets/hooks-handlers/lib/capture-cleanup-cli.js +2134 -0
- package/assets/hooks-handlers/lib/capture-notices.js +224 -14
- package/assets/hooks-handlers/lib/capture-state.js +206 -1
- package/assets/hooks-handlers/lib/capture-status-cli.js +6 -2
- package/assets/hooks-handlers/lib/capture.js +1364 -139
- package/assets/hooks-handlers/lib/checkpoint-timer.js +445 -18
- package/assets/hooks-handlers/lib/journal-bridge.js +218 -0
- package/assets/hooks-handlers/lib/kiro-handler.js +26 -4
- package/assets/hooks-handlers/lib/opencode-handler.js +643 -0
- package/assets/hooks-handlers/lib/opencode-normalizer.js +145 -0
- package/assets/hooks-handlers/lib/terminal-handoff.js +10 -1
- package/assets/opencode-plugin/meko.js +213 -0
- package/assets/scripts/meko-statusline.sh +102 -19
- package/assets/skills/meko-mcp-tools/SKILL.md +59 -16
- package/assets/skills/meko-mcp-tools/references/_sections.md +2 -2
- package/assets/skills/meko-mcp-tools/references/tools-agent-id-conventions.md +15 -7
- package/assets/skills/meko-mcp-tools/references/tools-cookbook.md +275 -19
- package/assets/skills/meko-mcp-tools/references/tools-datapack-workflow.md +7 -4
- package/assets/skills/meko-mcp-tools/references/tools-known-limitations.md +39 -11
- package/assets/skills/meko-mcp-tools/references/tools-memory-vs-conversation.md +10 -6
- package/assets/skills/meko-mcp-tools/references/tools-overview.md +44 -29
- package/assets/skills/meko-mcp-tools/references/tools-rag-workflow.md +79 -14
- package/assets/skills/meko-mcp-tools/references/tools-troubleshooting.md +47 -1
- package/assets/skills/meko-mcp-tools/references/tools-user-id-conventions.md +6 -39
- package/assets/skills/meko-mcp-tools-desktop/SKILL.md +57 -21
- package/assets/skills/meko-mcp-tools-desktop/desktop/README.md +4 -4
- package/assets/skills/meko-mcp-tools-desktop/references/_sections.md +1 -1
- package/assets/skills/meko-mcp-tools-desktop/references/tools-agent-id-conventions.md +8 -6
- package/assets/skills/meko-mcp-tools-desktop/references/tools-cookbook.md +284 -38
- package/assets/skills/meko-mcp-tools-desktop/references/tools-known-limitations.md +32 -10
- package/assets/skills/meko-mcp-tools-desktop/references/tools-memory-vs-conversation.md +16 -12
- package/assets/skills/meko-mcp-tools-desktop/references/tools-overview.md +46 -31
- package/assets/skills/meko-mcp-tools-desktop/references/tools-rag-workflow.md +79 -14
- package/assets/skills/meko-mcp-tools-desktop/references/tools-troubleshooting.md +69 -1
- package/assets/skills/meko-select-datapack-desktop/SKILL.md +93 -14
- package/assets/skills/meko-select-datapack-desktop/references/cookbook.md +44 -12
- package/assets/skills/meko-select-datapack-desktop/references/role-display-future.md +14 -10
- package/assets/skills/meko-select-datapack-desktop/references/troubleshooting.md +17 -5
- package/bin/create-meko-setup.mjs +186 -30
- package/lib/autofix.mjs +100 -0
- package/lib/browser-login.mjs +495 -0
- package/lib/capture-cleanup.mjs +63 -0
- package/lib/clients/claude-code.mjs +1 -0
- package/lib/clients/codex.mjs +80 -35
- package/lib/clients/common.mjs +7 -0
- package/lib/clients/index.mjs +2 -0
- package/lib/clients/kiro.mjs +5 -5
- package/lib/clients/opencode.mjs +537 -0
- package/lib/config.mjs +68 -1
- package/lib/installer.mjs +18 -2
- package/lib/migrate/adapters/claude-code.mjs +11 -2
- package/lib/migrate/adapters/codex.mjs +9 -2
- package/lib/migrate/adapters/memory-md.mjs +27 -8
- package/lib/migrate/adapters/opencode.mjs +148 -0
- package/lib/migrate/claude-parser.mjs +153 -9
- package/lib/migrate/datapack.mjs +254 -16
- package/lib/migrate/id.mjs +195 -13
- package/lib/migrate/mcp-client.mjs +59 -1
- package/lib/migrate/opencode-normalizer.mjs +132 -0
- package/lib/migrate/orchestrator.mjs +294 -78
- package/lib/migrate/progress.mjs +239 -5
- package/lib/migrate/registry.mjs +72 -0
- package/lib/migrate.mjs +97 -9
- package/lib/validate.mjs +111 -22
- package/package.json +5 -5
- package/scripts/bundle-assets.mjs +4 -0
|
@@ -131,6 +131,53 @@ function healthCacheFilename() {
|
|
|
131
131
|
return envString("MEKO_HEALTH_CACHE", DEFAULTS.HEALTH_CACHE);
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
+
/**
|
|
135
|
+
* The retry date `--capture-cleanup` parks a session until. Hooks skip the
|
|
136
|
+
* network while failure_class is set and next_retry_at is in the future, so
|
|
137
|
+
* a session with this date is parked, not retrying.
|
|
138
|
+
*/
|
|
139
|
+
const CLEANUP_PARK_UNTIL = "9999-12-31T00:00:00.000Z";
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Automatic repair from the checkpoint timer. MEKO_AUTOFIX (on/off) wins when
|
|
143
|
+
* set; otherwise autofix-config.json in the capture directory, which the
|
|
144
|
+
* installer writes for --autofix / --no-autofix. On by default.
|
|
145
|
+
*/
|
|
146
|
+
function autofixEnabled() {
|
|
147
|
+
return autofixSetting().enabled;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Whether automatic repair is on, and what decided it: "env" (MEKO_AUTOFIX),
|
|
152
|
+
* "config" (autofix-config.json, written by --autofix / --no-autofix), or
|
|
153
|
+
* "default" (on).
|
|
154
|
+
*/
|
|
155
|
+
function autofixSetting() {
|
|
156
|
+
const raw = process.env.MEKO_AUTOFIX;
|
|
157
|
+
if (raw != null && raw !== "") {
|
|
158
|
+
return { enabled: !/^(0|off|false|no)$/i.test(String(raw)), source: "env", path: null };
|
|
159
|
+
}
|
|
160
|
+
const file = autofixConfigPath();
|
|
161
|
+
try {
|
|
162
|
+
const cfg = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
163
|
+
if (cfg && typeof cfg.enabled === "boolean") {
|
|
164
|
+
return { enabled: cfg.enabled, source: "config", path: file };
|
|
165
|
+
}
|
|
166
|
+
} catch {
|
|
167
|
+
/* no config: default on */
|
|
168
|
+
}
|
|
169
|
+
return { enabled: true, source: "default", path: null };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function autofixConfigPath() {
|
|
173
|
+
return path.join(watermarkDir(), "autofix-config.json");
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Seconds between automatic `--capture-cleanup fix` runs on one machine. */
|
|
177
|
+
function autofixIntervalSeconds() {
|
|
178
|
+
return Math.max(60, envInt("MEKO_AUTOFIX_INTERVAL", 1800));
|
|
179
|
+
}
|
|
180
|
+
|
|
134
181
|
// ---------------------------------------------------------------------------
|
|
135
182
|
// Path helpers
|
|
136
183
|
// ---------------------------------------------------------------------------
|
|
@@ -162,6 +209,20 @@ function healthCachePath() {
|
|
|
162
209
|
return path.join(watermarkDir(), healthCacheFilename());
|
|
163
210
|
}
|
|
164
211
|
|
|
212
|
+
/** Summary of the last `--capture-cleanup fix` run, manual or automatic. */
|
|
213
|
+
function autofixLastPath() {
|
|
214
|
+
return path.join(watermarkDir(), "autofix-last.json");
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function readAutofixLast() {
|
|
218
|
+
try {
|
|
219
|
+
const value = JSON.parse(fs.readFileSync(autofixLastPath(), "utf8"));
|
|
220
|
+
return value && typeof value === "object" ? value : null;
|
|
221
|
+
} catch {
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
165
226
|
/**
|
|
166
227
|
* Convert an agent_id into the filesystem slug used by legacy error files and
|
|
167
228
|
* datapack pins (matches capture.js datapackPinSlug).
|
|
@@ -293,6 +354,21 @@ function blockedOutcome(failureClass, error, extras) {
|
|
|
293
354
|
};
|
|
294
355
|
}
|
|
295
356
|
|
|
357
|
+
/**
|
|
358
|
+
* MEKO-635: resumable sessions (OpenCode) must not be finalized as
|
|
359
|
+
* confirmed_dead by timer idle or cross-session recovery. Only OpenCode sets
|
|
360
|
+
* this policy today; other clients continue to finalize as before. Existing
|
|
361
|
+
* pre-MEKO-635 `client: "opencode"` watermarks without an explicit
|
|
362
|
+
* `pending_turn_policy` field are also treated as resumable so upgrades don't
|
|
363
|
+
* finalize a turn that a later snapshot could still complete.
|
|
364
|
+
*/
|
|
365
|
+
function isResumableState(state) {
|
|
366
|
+
if (!state || typeof state !== "object") return false;
|
|
367
|
+
if (state.pending_turn_policy === "resumable") return true;
|
|
368
|
+
if (state.pending_turn_policy == null && state.client === "opencode") return true;
|
|
369
|
+
return false;
|
|
370
|
+
}
|
|
371
|
+
|
|
296
372
|
/**
|
|
297
373
|
* Validate a candidate v2 state object.
|
|
298
374
|
* @returns {{ ok:true, state:Object } | { ok:false, delivery:"blocked", failure_class:"state_corrupt", error:string }}
|
|
@@ -342,6 +418,16 @@ function validateV2(state) {
|
|
|
342
418
|
`invalid failure_class: ${state.failure_class}`,
|
|
343
419
|
);
|
|
344
420
|
}
|
|
421
|
+
if (
|
|
422
|
+
state.consecutive_exchange_drops != null &&
|
|
423
|
+
(!Number.isInteger(state.consecutive_exchange_drops) ||
|
|
424
|
+
state.consecutive_exchange_drops < 0)
|
|
425
|
+
) {
|
|
426
|
+
return blockedOutcome(
|
|
427
|
+
"state_corrupt",
|
|
428
|
+
"consecutive_exchange_drops must be a non-negative integer",
|
|
429
|
+
);
|
|
430
|
+
}
|
|
345
431
|
return { ok: true, state };
|
|
346
432
|
}
|
|
347
433
|
|
|
@@ -461,6 +547,7 @@ function normalizeState(raw, context) {
|
|
|
461
547
|
last_success_at:
|
|
462
548
|
raw.last_success_at == null ? null : String(raw.last_success_at),
|
|
463
549
|
blocked_reason: null,
|
|
550
|
+
consecutive_exchange_drops: 0,
|
|
464
551
|
};
|
|
465
552
|
|
|
466
553
|
const validated = validateV2(state);
|
|
@@ -938,6 +1025,8 @@ function writeSessionIntent(sessionId, fields) {
|
|
|
938
1025
|
last_error: null,
|
|
939
1026
|
last_success_at: null,
|
|
940
1027
|
blocked_reason: null,
|
|
1028
|
+
// null = finalize unless client is opencode (isResumableState).
|
|
1029
|
+
pending_turn_policy: null,
|
|
941
1030
|
};
|
|
942
1031
|
|
|
943
1032
|
const conversationId =
|
|
@@ -974,6 +1063,12 @@ function writeSessionIntent(sessionId, fields) {
|
|
|
974
1063
|
: "needs_conversation",
|
|
975
1064
|
last_activity_at: nowIso(),
|
|
976
1065
|
updated_at: nowIso(),
|
|
1066
|
+
// MEKO-635: preserve or set pending_turn_policy. Only "resumable" is a
|
|
1067
|
+
// meaningful non-default value today; anything else stays null.
|
|
1068
|
+
pending_turn_policy:
|
|
1069
|
+
f.pending_turn_policy !== undefined
|
|
1070
|
+
? (f.pending_turn_policy === "resumable" ? "resumable" : null)
|
|
1071
|
+
: (base.pending_turn_policy === "resumable" ? "resumable" : null),
|
|
977
1072
|
};
|
|
978
1073
|
|
|
979
1074
|
if (f.last_line_number != null && existing.missing) {
|
|
@@ -1065,6 +1160,10 @@ function rebucket(state, newConversationId) {
|
|
|
1065
1160
|
blocked_reason: null,
|
|
1066
1161
|
next_retry_at: null,
|
|
1067
1162
|
attempt_count: 0,
|
|
1163
|
+
consecutive_exchange_drops: 0,
|
|
1164
|
+
// A replacement guard belongs only to the conversation that set it.
|
|
1165
|
+
recovered_from_conversation_id: null,
|
|
1166
|
+
missing_conversation_streak: null,
|
|
1068
1167
|
updated_at: nowIso(),
|
|
1069
1168
|
last_activity_at: nowIso(),
|
|
1070
1169
|
};
|
|
@@ -1257,6 +1356,8 @@ const PERSISTENT_PATTERNS = [
|
|
|
1257
1356
|
["payment_required", /payment[_\s-]?required/i],
|
|
1258
1357
|
["subscription_required", /subscription[_\s-]?(required|inactive|expired)/i],
|
|
1259
1358
|
["entitlement_denied", /entitlement.*(denied|required|missing)|(?:denied|required|missing).*entitlement/i],
|
|
1359
|
+
// A viewer of a shared datapack can't record conversations (MEKO-876).
|
|
1360
|
+
["read_only_access", /read_only_access/i],
|
|
1260
1361
|
];
|
|
1261
1362
|
|
|
1262
1363
|
/**
|
|
@@ -1384,6 +1485,26 @@ function timerLooksLive(sessionId) {
|
|
|
1384
1485
|
}
|
|
1385
1486
|
}
|
|
1386
1487
|
|
|
1488
|
+
/**
|
|
1489
|
+
* A session whose client may still be writing turns: its lifecycle is
|
|
1490
|
+
* active and its transcript changed within recoveryMinIdle() seconds, or its
|
|
1491
|
+
* checkpoint timer is alive. --capture-cleanup uses it to decide drain or
|
|
1492
|
+
* recover, and capture.js move re-checks it under the lease.
|
|
1493
|
+
*/
|
|
1494
|
+
function sessionLooksLive(state, nowMs) {
|
|
1495
|
+
if (!state || state.lifecycle !== "active") return false;
|
|
1496
|
+
const now = nowMs != null ? nowMs : Date.now();
|
|
1497
|
+
if (state.transcript_path) {
|
|
1498
|
+
try {
|
|
1499
|
+
const st = fs.statSync(state.transcript_path);
|
|
1500
|
+
if (now - st.mtimeMs < recoveryMinIdle() * 1000) return true;
|
|
1501
|
+
} catch {
|
|
1502
|
+
// A missing transcript says nothing about liveness; check the timer.
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
return timerLooksLive(state.session_id);
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1387
1508
|
function buildEligibility(state, nowMs, options) {
|
|
1388
1509
|
const opts = options && typeof options === "object" ? options : {};
|
|
1389
1510
|
const now = nowMs != null ? nowMs : Date.now();
|
|
@@ -1467,6 +1588,18 @@ function sessionHealthBucket(entry) {
|
|
|
1467
1588
|
) {
|
|
1468
1589
|
return "blocked_action_required";
|
|
1469
1590
|
}
|
|
1591
|
+
// A session that has delivered everything is healthy even if an earlier
|
|
1592
|
+
// attempt failed: failure_class records the last attempt, not outstanding
|
|
1593
|
+
// work. Without this, one transient blip pins a closed, fully drained
|
|
1594
|
+
// session to degraded_retrying forever and the rollup reports degraded with
|
|
1595
|
+
// an empty queue. Blocked classes are checked above and still win, since
|
|
1596
|
+
// those need a human.
|
|
1597
|
+
if (
|
|
1598
|
+
(state.delivery === "complete" || state.delivery === "idle") &&
|
|
1599
|
+
coerceNonNegInt(state.queued_exchanges, 0) === 0
|
|
1600
|
+
) {
|
|
1601
|
+
return "healthy";
|
|
1602
|
+
}
|
|
1470
1603
|
if (
|
|
1471
1604
|
state.delivery === "retry_wait" ||
|
|
1472
1605
|
fc === "transient" ||
|
|
@@ -1554,6 +1687,15 @@ function scanStates(options) {
|
|
|
1554
1687
|
return { ok: true, entries, dir };
|
|
1555
1688
|
}
|
|
1556
1689
|
|
|
1690
|
+
function scanCompleteForDropClamp(scan) {
|
|
1691
|
+
return Boolean(
|
|
1692
|
+
scan &&
|
|
1693
|
+
scan.ok &&
|
|
1694
|
+
Array.isArray(scan.entries) &&
|
|
1695
|
+
scan.entries.every((entry) => entry && entry.ok !== false),
|
|
1696
|
+
);
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1557
1699
|
function sortOldestFirst(entries) {
|
|
1558
1700
|
return [...(entries || [])].sort((a, b) => {
|
|
1559
1701
|
const ak =
|
|
@@ -1588,9 +1730,11 @@ function computeAggregateHealth(states, options) {
|
|
|
1588
1730
|
blocked_action_required: 0,
|
|
1589
1731
|
};
|
|
1590
1732
|
let queued = 0;
|
|
1733
|
+
let dropped = 0;
|
|
1591
1734
|
let oldestPendingAge = null;
|
|
1592
1735
|
let lastSuccessAt = null;
|
|
1593
1736
|
const blockedReasons = [];
|
|
1737
|
+
const dropReasons = [];
|
|
1594
1738
|
const sessions = [];
|
|
1595
1739
|
let status = "healthy";
|
|
1596
1740
|
|
|
@@ -1612,6 +1756,20 @@ function computeAggregateHealth(states, options) {
|
|
|
1612
1756
|
: 0;
|
|
1613
1757
|
queued += q;
|
|
1614
1758
|
|
|
1759
|
+
// Exchanges the server will never accept (see capture.js
|
|
1760
|
+
// classifyPersistentCaptureFailure, scope "exchange"). They are not
|
|
1761
|
+
// backlog — capture moved past them — but the user is still owed the
|
|
1762
|
+
// fact that some turns are missing, so they aggregate separately.
|
|
1763
|
+
const d =
|
|
1764
|
+
state && typeof state === "object"
|
|
1765
|
+
? coerceNonNegInt(state.dropped_exchanges, 0)
|
|
1766
|
+
: 0;
|
|
1767
|
+
dropped += d;
|
|
1768
|
+
if (d > 0 && state.last_drop_reason) {
|
|
1769
|
+
const reason = String(state.last_drop_reason);
|
|
1770
|
+
if (!dropReasons.includes(reason)) dropReasons.push(reason);
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1615
1773
|
if (state && state.last_success_at) {
|
|
1616
1774
|
if (
|
|
1617
1775
|
!lastSuccessAt ||
|
|
@@ -1659,7 +1817,18 @@ function computeAggregateHealth(states, options) {
|
|
|
1659
1817
|
(entry && entry.failure_class) ||
|
|
1660
1818
|
null,
|
|
1661
1819
|
queued_exchanges: q,
|
|
1820
|
+
dropped_exchanges: d,
|
|
1662
1821
|
lifecycle: state ? state.lifecycle : null,
|
|
1822
|
+
// Why --capture-cleanup parked this session ("stop", "drain failed: ...")
|
|
1823
|
+
// while the park is in effect; null otherwise.
|
|
1824
|
+
parked_reason:
|
|
1825
|
+
state &&
|
|
1826
|
+
state.parked &&
|
|
1827
|
+
typeof state.parked === "object" &&
|
|
1828
|
+
state.failure_class &&
|
|
1829
|
+
state.next_retry_at === CLEANUP_PARK_UNTIL
|
|
1830
|
+
? String(state.parked.reason || "parked")
|
|
1831
|
+
: null,
|
|
1663
1832
|
});
|
|
1664
1833
|
}
|
|
1665
1834
|
|
|
@@ -1669,14 +1838,17 @@ function computeAggregateHealth(states, options) {
|
|
|
1669
1838
|
updated_at: nowIso(nowMs),
|
|
1670
1839
|
counts,
|
|
1671
1840
|
queued_exchanges: queued,
|
|
1841
|
+
dropped_exchanges: dropped,
|
|
1672
1842
|
oldest_pending_age_seconds: oldestPendingAge,
|
|
1673
1843
|
last_success_at: lastSuccessAt,
|
|
1674
1844
|
blocked_reasons: blockedReasons,
|
|
1845
|
+
drop_reasons: dropReasons,
|
|
1675
1846
|
sessions,
|
|
1676
1847
|
};
|
|
1677
1848
|
}
|
|
1678
1849
|
|
|
1679
|
-
function writeHealthCache(health) {
|
|
1850
|
+
function writeHealthCache(health, options) {
|
|
1851
|
+
const opts = options && typeof options === "object" ? options : {};
|
|
1680
1852
|
const existing = readHealthCache();
|
|
1681
1853
|
const merged = {
|
|
1682
1854
|
...(health && typeof health === "object" ? health : {}),
|
|
@@ -1690,6 +1862,29 @@ function writeHealthCache(health) {
|
|
|
1690
1862
|
) {
|
|
1691
1863
|
merged.last_notified_status = existing.last_notified_status;
|
|
1692
1864
|
}
|
|
1865
|
+
// Drop notices are one-shot per new count. Clamp only after a complete scan
|
|
1866
|
+
// proves the aggregate shrank because watermarks disappeared. A failed scan
|
|
1867
|
+
// or unreadable watermark may recover and must not re-announce old drops.
|
|
1868
|
+
if (
|
|
1869
|
+
merged.last_notified_dropped === undefined &&
|
|
1870
|
+
existing &&
|
|
1871
|
+
existing.last_notified_dropped !== undefined
|
|
1872
|
+
) {
|
|
1873
|
+
const previous = coerceNonNegInt(existing.last_notified_dropped, 0);
|
|
1874
|
+
merged.last_notified_dropped = opts.clampDropped
|
|
1875
|
+
? Math.min(previous, coerceNonNegInt(merged.dropped_exchanges, 0))
|
|
1876
|
+
: previous;
|
|
1877
|
+
}
|
|
1878
|
+
// A ready-made status bar text, so meko-statusline.sh needs no JSON parsing
|
|
1879
|
+
// beyond one field. capture-notices requires this module, so load it here.
|
|
1880
|
+
try {
|
|
1881
|
+
const fields = require("./capture-notices").statuslineFields(merged, readAutofixLast());
|
|
1882
|
+
merged.statusline = fields.statusline;
|
|
1883
|
+
merged.statusline_until = fields.statusline_until != null ? fields.statusline_until : null;
|
|
1884
|
+
merged.statusline_after = fields.statusline_after != null ? fields.statusline_after : null;
|
|
1885
|
+
} catch {
|
|
1886
|
+
/* keep the cache write even if the text can't be built */
|
|
1887
|
+
}
|
|
1693
1888
|
try {
|
|
1694
1889
|
atomicWriteJson(healthCachePath(), merged);
|
|
1695
1890
|
return { ok: true, path: healthCachePath(), health: merged };
|
|
@@ -2025,6 +2220,13 @@ module.exports = {
|
|
|
2025
2220
|
workerMaxAgeSeconds,
|
|
2026
2221
|
timerMaxAge,
|
|
2027
2222
|
healthCacheFilename,
|
|
2223
|
+
CLEANUP_PARK_UNTIL,
|
|
2224
|
+
autofixEnabled,
|
|
2225
|
+
autofixSetting,
|
|
2226
|
+
autofixConfigPath,
|
|
2227
|
+
autofixIntervalSeconds,
|
|
2228
|
+
autofixLastPath,
|
|
2229
|
+
readAutofixLast,
|
|
2028
2230
|
|
|
2029
2231
|
// paths
|
|
2030
2232
|
watermarkDir,
|
|
@@ -2032,6 +2234,7 @@ module.exports = {
|
|
|
2032
2234
|
leasePath,
|
|
2033
2235
|
mutationLockPath,
|
|
2034
2236
|
timerPidPath,
|
|
2237
|
+
sessionLooksLive,
|
|
2035
2238
|
healthCachePath,
|
|
2036
2239
|
legacyErrorPath,
|
|
2037
2240
|
agentSlug,
|
|
@@ -2039,6 +2242,7 @@ module.exports = {
|
|
|
2039
2242
|
// normalize / validate
|
|
2040
2243
|
normalizeState,
|
|
2041
2244
|
validateV2,
|
|
2245
|
+
isResumableState,
|
|
2042
2246
|
|
|
2043
2247
|
// persistence
|
|
2044
2248
|
readState,
|
|
@@ -2063,6 +2267,7 @@ module.exports = {
|
|
|
2063
2267
|
|
|
2064
2268
|
// scan
|
|
2065
2269
|
scanStates,
|
|
2270
|
+
scanCompleteForDropClamp,
|
|
2066
2271
|
sortOldestFirst,
|
|
2067
2272
|
sessionHealthBucket,
|
|
2068
2273
|
buildEligibility,
|
|
@@ -22,6 +22,7 @@ const {
|
|
|
22
22
|
importLegacyCaptureErrors,
|
|
23
23
|
legacyErrorsSafeToRemove,
|
|
24
24
|
readHealthCache,
|
|
25
|
+
scanCompleteForDropClamp,
|
|
25
26
|
scanStates,
|
|
26
27
|
writeHealthCache,
|
|
27
28
|
} = require("./capture-state");
|
|
@@ -63,12 +64,15 @@ function main() {
|
|
|
63
64
|
);
|
|
64
65
|
}
|
|
65
66
|
|
|
66
|
-
|
|
67
|
+
let health = computeAggregateHealth(scan.entries || []);
|
|
67
68
|
const existing = readHealthCache();
|
|
68
69
|
if (existing && existing.last_notified_status !== undefined) {
|
|
69
70
|
health.last_notified_status = existing.last_notified_status;
|
|
70
71
|
}
|
|
71
|
-
writeHealthCache(health
|
|
72
|
+
const written = writeHealthCache(health, {
|
|
73
|
+
clampDropped: scanCompleteForDropClamp(scan),
|
|
74
|
+
});
|
|
75
|
+
if (written.ok) health = written.health;
|
|
72
76
|
|
|
73
77
|
const payload = captureStatusJson(health);
|
|
74
78
|
if (json) {
|