@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.
@@ -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.415",
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.415",
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';
@@ -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
+ }