@adhdev/daemon-core 0.9.82-rc.415 → 0.9.82-rc.416
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/dist/config/mesh-config.d.ts +8 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +166 -33
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +159 -33
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/mesh-ledger.d.ts +1 -1
- package/dist/mesh/mesh-magi-status.d.ts +63 -0
- package/package.json +2 -2
- package/src/config/mesh-config.ts +1 -1
- package/src/index.ts +4 -2
- package/src/mesh/mesh-ledger.ts +7 -0
- package/src/mesh/mesh-magi-status.ts +223 -0
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { EventEmitter } from 'events';
|
|
16
16
|
import { MeshRuntimeStore } from './mesh-runtime-store.js';
|
|
17
|
-
export type MeshLedgerKind = 'task_dispatched' | 'task_completed' | 'task_failed' | 'task_stalled' | 'task_approval_needed' | 'p2p_dispatch_failed' | 'session_launched' | 'session_auto_launch' | 'session_stopped' | 'checkpoint_created' | 'node_cloned' | 'node_joined' | 'node_removed' | 'coordinator_started' | 'recovery_attempted' | 'ledger_replicated' | 'ledger_reconciled' | 'direct_fast_forward' | 'delivery_unroutable' | 'direct_dispatch_pruned' | 'event_held' | 'task_reclaimed' | 'coordinator_operating_note' | 'mission_created' | 'mission_status_changed' | 'mission_goal_updated';
|
|
17
|
+
export type MeshLedgerKind = 'task_dispatched' | 'task_completed' | 'task_failed' | 'task_stalled' | 'task_approval_needed' | 'p2p_dispatch_failed' | 'session_launched' | 'session_auto_launch' | 'session_stopped' | 'checkpoint_created' | 'node_cloned' | 'node_joined' | 'node_removed' | 'coordinator_started' | 'recovery_attempted' | 'ledger_replicated' | 'ledger_reconciled' | 'direct_fast_forward' | 'delivery_unroutable' | 'direct_dispatch_pruned' | 'event_held' | 'task_reclaimed' | 'coordinator_operating_note' | 'mission_created' | 'mission_status_changed' | 'mission_goal_updated' | 'magi_dispatched' | 'magi_synthesis';
|
|
18
18
|
export interface MeshLedgerEntry {
|
|
19
19
|
id: string;
|
|
20
20
|
meshId: string;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { MeshLedgerEntry } from './mesh-ledger.js';
|
|
2
|
+
import type { MagiGitSkew } from '@adhdev/mesh-shared';
|
|
3
|
+
export type MeshMagiActivityStatus = 'running' | 'synthesized';
|
|
4
|
+
/** A bounded needs_verification preview item (claim text + category only). */
|
|
5
|
+
export interface MeshMagiNeedsVerificationItem {
|
|
6
|
+
claim: string;
|
|
7
|
+
category: string;
|
|
8
|
+
}
|
|
9
|
+
export interface MeshMagiActivitySummary {
|
|
10
|
+
consensusGroupId: string;
|
|
11
|
+
status: MeshMagiActivityStatus;
|
|
12
|
+
missionId?: string;
|
|
13
|
+
panel?: string;
|
|
14
|
+
question?: string;
|
|
15
|
+
replicaCount?: number;
|
|
16
|
+
answered?: number;
|
|
17
|
+
missing?: number;
|
|
18
|
+
staleReplicas?: number;
|
|
19
|
+
needsVerificationCount?: number;
|
|
20
|
+
agreedCount?: number;
|
|
21
|
+
independenceBanner?: string | null;
|
|
22
|
+
gitSkew?: MagiGitSkew;
|
|
23
|
+
/** Bounded sample of the needs_verification clusters (claim + category). */
|
|
24
|
+
needsVerification?: MeshMagiNeedsVerificationItem[];
|
|
25
|
+
openQuestions?: string[];
|
|
26
|
+
lastLedgerKind?: string;
|
|
27
|
+
lastUpdatedAt?: string;
|
|
28
|
+
}
|
|
29
|
+
/** Cap on inlined needs_verification preview items per group (keeps the payload bounded). */
|
|
30
|
+
export declare const MAGI_NEEDS_VERIFICATION_PREVIEW_CAP = 8;
|
|
31
|
+
/**
|
|
32
|
+
* Reconstruct per-consensusGroup MAGI activity from a ledger window. The newest
|
|
33
|
+
* `magi_synthesis` for a group supplies the synthesis fields; a `magi_dispatched`
|
|
34
|
+
* with no later synthesis stays `running`. Deduped by consensusGroupId, newest first.
|
|
35
|
+
*/
|
|
36
|
+
export declare function buildMeshMagiActivity(args: {
|
|
37
|
+
meshId?: string;
|
|
38
|
+
ledgerEntries?: MeshLedgerEntry[];
|
|
39
|
+
}): MeshMagiActivitySummary[];
|
|
40
|
+
/** Synthesized groups older than this (relative to the newest activity in the set) are
|
|
41
|
+
* folded out of the active list — already-resolved historical runs that should not keep
|
|
42
|
+
* inflating mesh_status. 6h covers a long working session. */
|
|
43
|
+
export declare const STALE_MAGI_WINDOW_MS: number;
|
|
44
|
+
/** Cap on recent synthesized groups kept in the active list even if all are fresh. */
|
|
45
|
+
export declare const RECENT_MAGI_CAP = 6;
|
|
46
|
+
export interface MeshMagiActivitySummaryFold {
|
|
47
|
+
total: number;
|
|
48
|
+
byStatus: Record<string, number>;
|
|
49
|
+
/** Synthesized groups dropped from the active list as stale historical residue. */
|
|
50
|
+
staleSynthesized: number;
|
|
51
|
+
/** Bounded set of recent/active groups (running first, then recent synthesized). */
|
|
52
|
+
groups: MeshMagiActivitySummary[];
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Bound the MAGI activity list for mesh_status: running groups are always kept; synthesized
|
|
56
|
+
* groups are kept only when recent (within STALE_MAGI_WINDOW_MS of the newest activity AND
|
|
57
|
+
* among the RECENT_MAGI_CAP most-recent). Freshness is measured relative to the newest group
|
|
58
|
+
* (not wall-clock) so the result is deterministic for a given input. Mirrors
|
|
59
|
+
* summarizeMeshAsyncRefineJobs.
|
|
60
|
+
*/
|
|
61
|
+
export declare function summarizeMeshMagiActivity(activity: MeshMagiActivitySummary[]): MeshMagiActivitySummaryFold;
|
|
62
|
+
/** Latest persisted synthesis activity for one consensusGroupId, or undefined. */
|
|
63
|
+
export declare function getMeshMagiActivityByGroup(ledgerEntries: MeshLedgerEntry[], consensusGroupId: string): MeshMagiActivitySummary | undefined;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adhdev/daemon-core",
|
|
3
|
-
"version": "0.9.82-rc.
|
|
3
|
+
"version": "0.9.82-rc.416",
|
|
4
4
|
"description": "ADHDev daemon core — CDP, IDE detection, providers, command execution",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"author": "vilmire",
|
|
47
47
|
"license": "AGPL-3.0-or-later",
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@adhdev/mesh-shared": "0.9.82-rc.
|
|
49
|
+
"@adhdev/mesh-shared": "0.9.82-rc.416",
|
|
50
50
|
"@adhdev/session-host-core": "*",
|
|
51
51
|
"@agentclientprotocol/sdk": "^0.16.1",
|
|
52
52
|
"ajv": "^8.20.0",
|
|
@@ -604,7 +604,7 @@ function normalizeReplicaCount(value: unknown): number | undefined {
|
|
|
604
604
|
* structurally invalid input so the calling tool returns a clear error rather than
|
|
605
605
|
* writing a malformed panel.
|
|
606
606
|
*/
|
|
607
|
-
function normalizeMagiPanel(config: unknown): MagiPanel {
|
|
607
|
+
export function normalizeMagiPanel(config: unknown): MagiPanel {
|
|
608
608
|
if (!config || typeof config !== 'object' || Array.isArray(config)) {
|
|
609
609
|
throw new Error('invalid_magi_panel: config must be an object');
|
|
610
610
|
}
|
package/src/index.ts
CHANGED
|
@@ -192,7 +192,7 @@ export type { SavedProviderSessionEntry } from './config/saved-sessions.js';
|
|
|
192
192
|
export {
|
|
193
193
|
listMeshes, getMesh, getMeshByRepo, createMesh, updateMesh, deleteMesh,
|
|
194
194
|
addNode, removeNode, updateNode, normalizeRepoIdentity,
|
|
195
|
-
listMagiPanels, getMagiPanel, upsertMagiPanel, removeMagiPanel,
|
|
195
|
+
listMagiPanels, getMagiPanel, upsertMagiPanel, removeMagiPanel, normalizeMagiPanel,
|
|
196
196
|
} from './config/mesh-config.js';
|
|
197
197
|
export type { CreateMeshOptions, UpdateMeshOptions, AddNodeOptions } from './config/mesh-config.js';
|
|
198
198
|
// MAGI panel / common-output / synthesis types (re-exported from the mesh-shared
|
|
@@ -201,7 +201,7 @@ export type { CreateMeshOptions, UpdateMeshOptions, AddNodeOptions } from './con
|
|
|
201
201
|
export type {
|
|
202
202
|
MagiPanel, MagiPanelMember, MagiPanelMap, MagiMode,
|
|
203
203
|
MagiClaim, MagiClaimStance, MagiAgentResponse,
|
|
204
|
-
MagiResponseSource, MagiSynthesizedResponse,
|
|
204
|
+
MagiResponseSource, MagiReplicaGitRef, MagiGitSkew, MagiSynthesizedResponse,
|
|
205
205
|
MagiClusterCategory, MagiClusterMember, MagiClaimCluster, MagiSynthesis,
|
|
206
206
|
} from '@adhdev/mesh-shared';
|
|
207
207
|
|
|
@@ -269,6 +269,8 @@ export type { StaleDirectPruneClassification, StaleDirectPruneResult, PruneStale
|
|
|
269
269
|
export type { MeshActiveWorkRecord, MeshActiveWorkStatus, MeshActiveWorkSummary, MeshActiveWorkSource, MeshStaleDirectWorkSummary } from './mesh/mesh-active-work.js';
|
|
270
270
|
export { buildMeshAsyncRefineJobs, summarizeMeshAsyncRefineJobs, STALE_TERMINAL_REFINE_WINDOW_MS, RECENT_TERMINAL_REFINE_CAP } from './mesh/mesh-refine-status.js';
|
|
271
271
|
export type { MeshAsyncRefineJobStatus, MeshAsyncRefineJobSummary, MeshAsyncRefineJobsSummary } from './mesh/mesh-refine-status.js';
|
|
272
|
+
export { buildMeshMagiActivity, summarizeMeshMagiActivity, getMeshMagiActivityByGroup, STALE_MAGI_WINDOW_MS, RECENT_MAGI_CAP, MAGI_NEEDS_VERIFICATION_PREVIEW_CAP } from './mesh/mesh-magi-status.js';
|
|
273
|
+
export type { MeshMagiActivityStatus, MeshMagiActivitySummary, MeshMagiActivitySummaryFold, MeshMagiNeedsVerificationItem } from './mesh/mesh-magi-status.js';
|
|
272
274
|
|
|
273
275
|
// ── Mesh Scheduling Runtime (observability projection) ──
|
|
274
276
|
export { buildMeshSchedulingRuntime } from './mesh/mesh-scheduling-runtime.js';
|
package/src/mesh/mesh-ledger.ts
CHANGED
|
@@ -59,6 +59,13 @@ export type MeshLedgerKind =
|
|
|
59
59
|
| 'mission_created'
|
|
60
60
|
| 'mission_status_changed'
|
|
61
61
|
| 'mission_goal_updated'
|
|
62
|
+
// MAGI (Multi-Agent Ground-truth Insight) cross-verification activity. Persisted
|
|
63
|
+
// so a wait=false fan-out and its later synthesis survive coordinator restarts and
|
|
64
|
+
// are foldable into mesh_status (keyed by consensusGroupId).
|
|
65
|
+
// magi_dispatched payload: { source:'magi', consensusGroupId, missionId?, panel?, question?, replicaCount }
|
|
66
|
+
// magi_synthesis payload: { source:'magi', consensusGroupId, missionId?, panel?, question?, synthesis }
|
|
67
|
+
| 'magi_dispatched'
|
|
68
|
+
| 'magi_synthesis'
|
|
62
69
|
;
|
|
63
70
|
|
|
64
71
|
export interface MeshLedgerEntry {
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
// MAGI (Multi-Agent Ground-truth Insight) activity reconstruction for mesh_status.
|
|
2
|
+
//
|
|
3
|
+
// MAGI cross-verification runs are persisted in the mesh ledger as `magi_dispatched`
|
|
4
|
+
// (fan-out enqueued) and `magi_synthesis` (collected + synthesized) entries, keyed by
|
|
5
|
+
// consensusGroupId. This module reconstructs the latest per-group activity from a
|
|
6
|
+
// ledger window and folds it into a BOUNDED mesh_status section so a coordinator (and
|
|
7
|
+
// the web dashboard's extractMagiActivity) can read the synthesis fields — the
|
|
8
|
+
// needs_verification counts, independence banner, and git skew — without re-running
|
|
9
|
+
// collection. Mirrors mesh-refine-status.ts (buildMeshAsyncRefineJobs / summarize…).
|
|
10
|
+
//
|
|
11
|
+
// Pure: operates on a ledger-entry array. No I/O, no Node/DOM APIs.
|
|
12
|
+
|
|
13
|
+
import type { MeshLedgerEntry } from './mesh-ledger.js';
|
|
14
|
+
import type { MagiGitSkew } from '@adhdev/mesh-shared';
|
|
15
|
+
|
|
16
|
+
export type MeshMagiActivityStatus = 'running' | 'synthesized';
|
|
17
|
+
|
|
18
|
+
/** A bounded needs_verification preview item (claim text + category only). */
|
|
19
|
+
export interface MeshMagiNeedsVerificationItem {
|
|
20
|
+
claim: string;
|
|
21
|
+
category: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface MeshMagiActivitySummary {
|
|
25
|
+
consensusGroupId: string;
|
|
26
|
+
status: MeshMagiActivityStatus;
|
|
27
|
+
missionId?: string;
|
|
28
|
+
panel?: string;
|
|
29
|
+
question?: string;
|
|
30
|
+
replicaCount?: number;
|
|
31
|
+
answered?: number;
|
|
32
|
+
missing?: number;
|
|
33
|
+
staleReplicas?: number;
|
|
34
|
+
needsVerificationCount?: number;
|
|
35
|
+
agreedCount?: number;
|
|
36
|
+
independenceBanner?: string | null;
|
|
37
|
+
gitSkew?: MagiGitSkew;
|
|
38
|
+
/** Bounded sample of the needs_verification clusters (claim + category). */
|
|
39
|
+
needsVerification?: MeshMagiNeedsVerificationItem[];
|
|
40
|
+
openQuestions?: string[];
|
|
41
|
+
lastLedgerKind?: string;
|
|
42
|
+
lastUpdatedAt?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function readString(value: unknown): string | undefined {
|
|
46
|
+
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function readRecord(value: unknown): Record<string, unknown> | undefined {
|
|
50
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
51
|
+
? value as Record<string, unknown>
|
|
52
|
+
: undefined;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function readNumber(value: unknown): number | undefined {
|
|
56
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Cap on inlined needs_verification preview items per group (keeps the payload bounded). */
|
|
60
|
+
export const MAGI_NEEDS_VERIFICATION_PREVIEW_CAP = 8;
|
|
61
|
+
|
|
62
|
+
function summarizeNeedsVerification(synthesis: Record<string, unknown> | undefined): MeshMagiNeedsVerificationItem[] | undefined {
|
|
63
|
+
const list = Array.isArray(synthesis?.needsVerification) ? synthesis!.needsVerification : undefined;
|
|
64
|
+
if (!list) return undefined;
|
|
65
|
+
const items: MeshMagiNeedsVerificationItem[] = [];
|
|
66
|
+
for (const raw of list.slice(0, MAGI_NEEDS_VERIFICATION_PREVIEW_CAP)) {
|
|
67
|
+
const r = readRecord(raw);
|
|
68
|
+
const claim = readString(r?.claim);
|
|
69
|
+
if (!claim) continue;
|
|
70
|
+
items.push({ claim, category: readString(r?.category) || 'needs_verification' });
|
|
71
|
+
}
|
|
72
|
+
return items;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function mergeGroup(
|
|
76
|
+
groups: Map<string, MeshMagiActivitySummary>,
|
|
77
|
+
patch: Partial<MeshMagiActivitySummary> & { consensusGroupId?: string },
|
|
78
|
+
): void {
|
|
79
|
+
const consensusGroupId = readString(patch.consensusGroupId);
|
|
80
|
+
if (!consensusGroupId) return;
|
|
81
|
+
const previous = groups.get(consensusGroupId);
|
|
82
|
+
// synthesized is terminal-ish and must not be downgraded back to running by an
|
|
83
|
+
// out-of-order dispatch entry.
|
|
84
|
+
const status: MeshMagiActivityStatus = patch.status === 'synthesized' || previous?.status === 'synthesized'
|
|
85
|
+
? 'synthesized'
|
|
86
|
+
: 'running';
|
|
87
|
+
const definedPatch = Object.fromEntries(
|
|
88
|
+
Object.entries(patch).filter(([, v]) => v !== undefined),
|
|
89
|
+
) as Partial<MeshMagiActivitySummary>;
|
|
90
|
+
groups.set(consensusGroupId, { ...previous, ...definedPatch, consensusGroupId, status });
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Reconstruct per-consensusGroup MAGI activity from a ledger window. The newest
|
|
95
|
+
* `magi_synthesis` for a group supplies the synthesis fields; a `magi_dispatched`
|
|
96
|
+
* with no later synthesis stays `running`. Deduped by consensusGroupId, newest first.
|
|
97
|
+
*/
|
|
98
|
+
export function buildMeshMagiActivity(args: {
|
|
99
|
+
meshId?: string;
|
|
100
|
+
ledgerEntries?: MeshLedgerEntry[];
|
|
101
|
+
}): MeshMagiActivitySummary[] {
|
|
102
|
+
const groups = new Map<string, MeshMagiActivitySummary>();
|
|
103
|
+
|
|
104
|
+
for (const entry of args.ledgerEntries || []) {
|
|
105
|
+
const payload = readRecord(entry.payload);
|
|
106
|
+
if (payload?.source !== 'magi') continue;
|
|
107
|
+
const consensusGroupId = readString(payload.consensusGroupId);
|
|
108
|
+
if (!consensusGroupId) continue;
|
|
109
|
+
|
|
110
|
+
if (entry.kind === 'magi_synthesis') {
|
|
111
|
+
const synthesis = readRecord(payload.synthesis);
|
|
112
|
+
mergeGroup(groups, {
|
|
113
|
+
consensusGroupId,
|
|
114
|
+
status: 'synthesized',
|
|
115
|
+
missionId: readString(payload.missionId),
|
|
116
|
+
panel: readString(payload.panel),
|
|
117
|
+
question: readString(payload.question),
|
|
118
|
+
replicaCount: readNumber(synthesis?.replicasExpected) ?? readNumber(payload.replicaCount),
|
|
119
|
+
answered: readNumber(synthesis?.replicasAnswered),
|
|
120
|
+
missing: readNumber(synthesis?.replicasMissing),
|
|
121
|
+
staleReplicas: readNumber(payload.staleReplicas) ?? readNumber(synthesis?.staleReplicas),
|
|
122
|
+
needsVerificationCount: Array.isArray(synthesis?.needsVerification) ? synthesis!.needsVerification.length : undefined,
|
|
123
|
+
agreedCount: Array.isArray(synthesis?.agreed) ? synthesis!.agreed.length : undefined,
|
|
124
|
+
independenceBanner: synthesis && 'independenceBanner' in synthesis ? (synthesis.independenceBanner as string | null) : undefined,
|
|
125
|
+
gitSkew: readRecord(synthesis?.gitSkew) as unknown as MagiGitSkew | undefined,
|
|
126
|
+
needsVerification: summarizeNeedsVerification(synthesis),
|
|
127
|
+
openQuestions: Array.isArray(synthesis?.openQuestions) ? (synthesis!.openQuestions as string[]).slice(0, 10) : undefined,
|
|
128
|
+
lastLedgerKind: entry.kind,
|
|
129
|
+
lastUpdatedAt: entry.timestamp,
|
|
130
|
+
});
|
|
131
|
+
} else if (entry.kind === 'magi_dispatched') {
|
|
132
|
+
mergeGroup(groups, {
|
|
133
|
+
consensusGroupId,
|
|
134
|
+
status: 'running',
|
|
135
|
+
missionId: readString(payload.missionId),
|
|
136
|
+
panel: readString(payload.panel),
|
|
137
|
+
question: readString(payload.question),
|
|
138
|
+
replicaCount: readNumber(payload.replicaCount),
|
|
139
|
+
lastLedgerKind: entry.kind,
|
|
140
|
+
lastUpdatedAt: entry.timestamp,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return Array.from(groups.values()).sort((a, b) => {
|
|
146
|
+
const at = new Date(a.lastUpdatedAt || '').getTime();
|
|
147
|
+
const bt = new Date(b.lastUpdatedAt || '').getTime();
|
|
148
|
+
return (Number.isFinite(bt) ? bt : 0) - (Number.isFinite(at) ? at : 0);
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Synthesized groups older than this (relative to the newest activity in the set) are
|
|
153
|
+
* folded out of the active list — already-resolved historical runs that should not keep
|
|
154
|
+
* inflating mesh_status. 6h covers a long working session. */
|
|
155
|
+
export const STALE_MAGI_WINDOW_MS = 6 * 60 * 60 * 1000;
|
|
156
|
+
|
|
157
|
+
/** Cap on recent synthesized groups kept in the active list even if all are fresh. */
|
|
158
|
+
export const RECENT_MAGI_CAP = 6;
|
|
159
|
+
|
|
160
|
+
export interface MeshMagiActivitySummaryFold {
|
|
161
|
+
total: number;
|
|
162
|
+
byStatus: Record<string, number>;
|
|
163
|
+
/** Synthesized groups dropped from the active list as stale historical residue. */
|
|
164
|
+
staleSynthesized: number;
|
|
165
|
+
/** Bounded set of recent/active groups (running first, then recent synthesized). */
|
|
166
|
+
groups: MeshMagiActivitySummary[];
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function activityTime(g: MeshMagiActivitySummary): number {
|
|
170
|
+
const t = new Date(g.lastUpdatedAt || '').getTime();
|
|
171
|
+
return Number.isFinite(t) ? t : 0;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Bound the MAGI activity list for mesh_status: running groups are always kept; synthesized
|
|
176
|
+
* groups are kept only when recent (within STALE_MAGI_WINDOW_MS of the newest activity AND
|
|
177
|
+
* among the RECENT_MAGI_CAP most-recent). Freshness is measured relative to the newest group
|
|
178
|
+
* (not wall-clock) so the result is deterministic for a given input. Mirrors
|
|
179
|
+
* summarizeMeshAsyncRefineJobs.
|
|
180
|
+
*/
|
|
181
|
+
export function summarizeMeshMagiActivity(
|
|
182
|
+
activity: MeshMagiActivitySummary[],
|
|
183
|
+
): MeshMagiActivitySummaryFold {
|
|
184
|
+
const running: MeshMagiActivitySummary[] = [];
|
|
185
|
+
const synthesized: MeshMagiActivitySummary[] = [];
|
|
186
|
+
for (const g of activity) {
|
|
187
|
+
if (g.status === 'synthesized') synthesized.push(g);
|
|
188
|
+
else running.push(g);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
let newest = 0;
|
|
192
|
+
for (const g of activity) newest = Math.max(newest, activityTime(g));
|
|
193
|
+
const cutoff = newest - STALE_MAGI_WINDOW_MS;
|
|
194
|
+
|
|
195
|
+
const synthesizedByRecency = [...synthesized].sort((a, b) => activityTime(b) - activityTime(a));
|
|
196
|
+
const freshSynthesized = synthesizedByRecency
|
|
197
|
+
.filter(g => activityTime(g) >= cutoff)
|
|
198
|
+
.slice(0, RECENT_MAGI_CAP);
|
|
199
|
+
|
|
200
|
+
const byStatus: Record<string, number> = {};
|
|
201
|
+
for (const g of [...running, ...freshSynthesized]) {
|
|
202
|
+
byStatus[g.status] = (byStatus[g.status] ?? 0) + 1;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Running first (most actionable), then recent synthesized — both newest-first.
|
|
206
|
+
const runningByRecency = [...running].sort((a, b) => activityTime(b) - activityTime(a));
|
|
207
|
+
return {
|
|
208
|
+
total: running.length + freshSynthesized.length,
|
|
209
|
+
byStatus,
|
|
210
|
+
staleSynthesized: synthesized.length - freshSynthesized.length,
|
|
211
|
+
groups: [...runningByRecency, ...freshSynthesized],
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/** Latest persisted synthesis activity for one consensusGroupId, or undefined. */
|
|
216
|
+
export function getMeshMagiActivityByGroup(
|
|
217
|
+
ledgerEntries: MeshLedgerEntry[],
|
|
218
|
+
consensusGroupId: string,
|
|
219
|
+
): MeshMagiActivitySummary | undefined {
|
|
220
|
+
const key = readString(consensusGroupId);
|
|
221
|
+
if (!key) return undefined;
|
|
222
|
+
return buildMeshMagiActivity({ ledgerEntries }).find(g => g.consensusGroupId === key);
|
|
223
|
+
}
|