@adhdev/daemon-core 0.9.82-rc.429 → 0.9.82-rc.430

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,12 +14,27 @@
14
14
  import { type MeshMissionStats } from './mesh-task-stats.js';
15
15
  export type MeshMissionStatus = 'active' | 'paused' | 'completed' | 'abandoned';
16
16
  export declare const MESH_MISSION_STATUSES: MeshMissionStatus[];
17
+ /**
18
+ * Provenance of a mission. `magi` marks an inline mission auto-created by a
19
+ * mesh_magi_review fan-out (one per cross-verification run). `coordinator` (the
20
+ * default semantic for an unstamped/legacy mission) marks a coordinator- or
21
+ * user-authored mission. Used to bound the accumulation of completed MAGI missions
22
+ * out of the default mesh_mission_list surface — see listMeshMissionSummaries.
23
+ */
24
+ export type MeshMissionSource = 'magi' | 'coordinator';
17
25
  export interface MeshMissionRecord {
18
26
  id: string;
19
27
  meshId: string;
20
28
  title: string;
21
29
  goal: string;
22
30
  status: MeshMissionStatus;
31
+ /**
32
+ * Optional provenance tag. Absent on missions created before this field existed
33
+ * and on coordinator/user missions that don't bother stamping it — both are
34
+ * treated as coordinator missions (never auto-hidden). Only explicit `magi`
35
+ * missions are bounded out of the default list once completed.
36
+ */
37
+ source?: MeshMissionSource;
23
38
  createdAt: string;
24
39
  updatedAt: string;
25
40
  }
@@ -74,6 +89,7 @@ export declare function upsertMeshMission(meshId: string, input: {
74
89
  title: string;
75
90
  goal?: string;
76
91
  status?: string;
92
+ source?: MeshMissionSource;
77
93
  }): MeshMissionRecord;
78
94
  export declare function getMeshMissions(meshId: string, statuses?: MeshMissionStatus[]): MeshMissionRecord[];
79
95
  export declare function getMeshMission(meshId: string, missionId: string): MeshMissionRecord | null;
@@ -149,12 +165,21 @@ export declare function getMeshStatusMissionsCompact(meshId: string, options?: {
149
165
  * coordinator can deliberately surface paused/abandoned/completed missions that
150
166
  * the live status view would hide or truncate.
151
167
  *
168
+ * MAGI bounding: by default this EXCLUDES completed MAGI missions (source==='magi'
169
+ * && status==='completed') — a mesh_magi_review fan-out auto-creates one inline
170
+ * mission per run and auto-closes it on collection, so without this they accumulate
171
+ * unbounded and drown out coordinator missions in the list. In-progress MAGI missions
172
+ * (active/paused) are still shown so a running cross-verification stays visible. Pass
173
+ * `includeMagi: true` to return every mission including completed MAGI ones. A mission
174
+ * with no `source` (legacy / coordinator) is never affected.
175
+ *
152
176
  * Compact (the default) elides each goal to a capped preview + goalTruncated
153
177
  * flag; verbose returns the full goal text. The stored goal is never mutated.
154
178
  */
155
179
  export declare function listMeshMissionSummaries(meshId: string, options?: {
156
180
  statuses?: MeshMissionStatus[];
157
181
  verbose?: boolean;
182
+ includeMagi?: boolean;
158
183
  }): MeshMissionSummary[] | MeshMissionSlimSummary[];
159
184
  /**
160
185
  * M3-3: render active missions as a prompt section for {{mission}}.
@@ -405,6 +405,7 @@ export declare class MeshRuntimeStore {
405
405
  title: string;
406
406
  goal?: string;
407
407
  status?: string;
408
+ source?: string;
408
409
  }): void;
409
410
  getMission(meshId: string, missionId: string): {
410
411
  id: string;
@@ -412,6 +413,7 @@ export declare class MeshRuntimeStore {
412
413
  title: string;
413
414
  goal: string;
414
415
  status: string;
416
+ source?: string;
415
417
  createdAt: string;
416
418
  updatedAt: string;
417
419
  } | null;
@@ -421,6 +423,7 @@ export declare class MeshRuntimeStore {
421
423
  title: string;
422
424
  goal: string;
423
425
  status: string;
426
+ source?: string;
424
427
  createdAt: string;
425
428
  updatedAt: string;
426
429
  }>;
@@ -12,6 +12,7 @@
12
12
  */
13
13
  import type { GitRepoStatus, GitCompactSummary } from './git/git-types.js';
14
14
  import type { MeshMissionSummary, MeshMissionSlimSummary } from './mesh/mesh-missions.js';
15
+ import type { MeshMagiActivitySummary } from './mesh/mesh-magi-status.js';
15
16
  import type { MagiPanelMap } from '@adhdev/mesh-shared';
16
17
  export interface RepoMesh {
17
18
  id: string;
@@ -667,6 +668,17 @@ export interface RepoMeshStatus {
667
668
  * an optional `stats` operational rollup (durations / retries).
668
669
  */
669
670
  missions?: (MeshMissionSummary | MeshMissionSlimSummary)[];
671
+ /**
672
+ * MAGI cross-verification activity, reconstructed from the mesh ledger
673
+ * (magi_dispatched / magi_synthesis entries) and folded in so the dashboard's
674
+ * MAGI surface can read synthesis output — needs_verification counts, the
675
+ * independence banner, git skew, and a bounded needs_verification preview —
676
+ * without re-running collection. Running groups are always included; synthesized
677
+ * groups are bounded to recent ones (see summarizeMeshMagiActivity). Omitted by
678
+ * daemons predating the exposure and when no MAGI run is present; treat as
679
+ * optional. Mirrors the MCP `mesh_status` tool's `magiActivity` field.
680
+ */
681
+ magiActivity?: MeshMagiActivitySummary[];
670
682
  }
671
683
  export type { RepoMeshSessionStatus } from '@adhdev/mesh-shared';
672
684
  import type { RepoMeshSessionStatus } from '@adhdev/mesh-shared';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/daemon-core",
3
- "version": "0.9.82-rc.429",
3
+ "version": "0.9.82-rc.430",
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.429",
49
+ "@adhdev/mesh-shared": "0.9.82-rc.430",
50
50
  "@adhdev/session-host-core": "*",
51
51
  "@agentclientprotocol/sdk": "^0.16.1",
52
52
  "ajv": "^8.20.0",
@@ -494,6 +494,24 @@ export const meshStatusHandlers: Record<string, HighFamilyHandler> = {
494
494
  // returned here (live + capped history), so the cost stays linear
495
495
  // in visible missions rather than the whole mesh history.
496
496
  const missions = getMeshStatusMissionSummaries(meshId, { verbose: verboseMissions, withStats: true });
497
+
498
+ // FIX C-backend: fold persisted MAGI cross-verification activity into the
499
+ // dashboard mesh_status payload so the web MAGI surface (extractMagiActivity)
500
+ // can read synthesis output — needs_verification counts, the independence
501
+ // banner, git skew, and a bounded needs_verification preview — without
502
+ // re-running collection. Mirrors the MCP mesh_status tool
503
+ // (mesh-tools-status.ts buildMeshMagiActivity / summarizeMeshMagiActivity).
504
+ // Read magi_dispatched/magi_synthesis entries over a wider tail than the 20
505
+ // rendered above (MAGI events are sparse — a 20-entry tail routinely misses
506
+ // the dispatch/synthesis pair). Compact (the default) folds synthesized groups
507
+ // to a bounded recent set + a count summary; verbose carries the full list.
508
+ const { buildMeshMagiActivity, summarizeMeshMagiActivity } = await import('../../mesh/mesh-magi-status.js');
509
+ const magiLedgerEntries = readLedgerEntries(meshId, { kind: ['magi_dispatched', 'magi_synthesis'], tail: 200 });
510
+ const magiActivity = buildMeshMagiActivity({ meshId, ledgerEntries: magiLedgerEntries });
511
+ let magiActivityFold: ReturnType<typeof summarizeMeshMagiActivity> | undefined;
512
+ if (magiActivity.length > 0 && !verboseMissions) {
513
+ magiActivityFold = summarizeMeshMagiActivity(magiActivity);
514
+ }
497
515
  const statusResult = {
498
516
  success: true,
499
517
  meshId: mesh.id,
@@ -550,6 +568,20 @@ export const meshStatusHandlers: Record<string, HighFamilyHandler> = {
550
568
  queue: { tasks: queue, summary: queueSummary },
551
569
  ledger: { entries: ledgerEntries, summary: ledgerSummary },
552
570
  ...(missions.length > 0 ? { missions } : {}),
571
+ // Compact (default): bounded folded groups + a status-count summary.
572
+ // Verbose: the full reconstructed activity list. Mirrors the MCP tool.
573
+ ...(magiActivity.length > 0
574
+ ? (magiActivityFold
575
+ ? {
576
+ ...(magiActivityFold.groups.length > 0 ? { magiActivity: magiActivityFold.groups } : {}),
577
+ magiActivitySummary: {
578
+ total: magiActivityFold.total,
579
+ byStatus: magiActivityFold.byStatus,
580
+ ...(magiActivityFold.staleSynthesized > 0 ? { staleSynthesized: magiActivityFold.staleSynthesized } : {}),
581
+ },
582
+ }
583
+ : { magiActivity })
584
+ : {}),
553
585
  ...(asyncRefineJobs.length > 0 ? { asyncRefineJobs } : {}),
554
586
  ...(historicalSessions ? { historicalSessions } : {}),
555
587
  ...(pendingCoordinatorEvents.length > 0 ? { pendingCoordinatorEvents } : {}),
package/src/index.ts CHANGED
@@ -207,6 +207,9 @@ export type {
207
207
  MagiResponseSource, MagiReplicaGitRef, MagiGitSkew, MagiSynthesizedResponse,
208
208
  MagiClusterCategory, MagiClusterMember, MagiClaimCluster, MagiSynthesis,
209
209
  } from '@adhdev/mesh-shared';
210
+ // Value re-export: per-replica raw-answer truncation cap (used by the mcp-server
211
+ // collection path, which depends only on @adhdev/daemon-core).
212
+ export { MAGI_RAW_ANSWER_CAP } from '@adhdev/mesh-shared';
210
213
 
211
214
  // ── Mesh shared daemon-id / node-id helpers (re-export so external tooling —
212
215
  // e.g. the mcp-server, which depends only on @adhdev/daemon-core — can
@@ -34,12 +34,28 @@ export type MeshMissionStatus = 'active' | 'paused' | 'completed' | 'abandoned';
34
34
 
35
35
  export const MESH_MISSION_STATUSES: MeshMissionStatus[] = ['active', 'paused', 'completed', 'abandoned'];
36
36
 
37
+ /**
38
+ * Provenance of a mission. `magi` marks an inline mission auto-created by a
39
+ * mesh_magi_review fan-out (one per cross-verification run). `coordinator` (the
40
+ * default semantic for an unstamped/legacy mission) marks a coordinator- or
41
+ * user-authored mission. Used to bound the accumulation of completed MAGI missions
42
+ * out of the default mesh_mission_list surface — see listMeshMissionSummaries.
43
+ */
44
+ export type MeshMissionSource = 'magi' | 'coordinator';
45
+
37
46
  export interface MeshMissionRecord {
38
47
  id: string;
39
48
  meshId: string;
40
49
  title: string;
41
50
  goal: string;
42
51
  status: MeshMissionStatus;
52
+ /**
53
+ * Optional provenance tag. Absent on missions created before this field existed
54
+ * and on coordinator/user missions that don't bother stamping it — both are
55
+ * treated as coordinator missions (never auto-hidden). Only explicit `magi`
56
+ * missions are bounded out of the default list once completed.
57
+ */
58
+ source?: MeshMissionSource;
43
59
  createdAt: string;
44
60
  updatedAt: string;
45
61
  }
@@ -101,11 +117,16 @@ function normalizeMissionStatus(value: unknown): MeshMissionStatus {
101
117
  : 'active';
102
118
  }
103
119
 
120
+ function normalizeMissionSource(value: unknown): MeshMissionSource | undefined {
121
+ return value === 'magi' || value === 'coordinator' ? value : undefined;
122
+ }
123
+
104
124
  export function upsertMeshMission(meshId: string, input: {
105
125
  id?: string;
106
126
  title: string;
107
127
  goal?: string;
108
128
  status?: string;
129
+ source?: MeshMissionSource;
109
130
  }): MeshMissionRecord {
110
131
  const title = typeof input.title === 'string' ? input.title.trim() : '';
111
132
  if (!title) throw new Error('mission_title_required: a mission needs a non-empty title');
@@ -123,10 +144,18 @@ export function upsertMeshMission(meshId: string, input: {
123
144
  title,
124
145
  goal: typeof input.goal === 'string' ? input.goal : existing?.goal ?? '',
125
146
  status: normalizeMissionStatus(input.status ?? existing?.status),
147
+ // source is write-once: only forwarded when the caller supplies one. The
148
+ // store COALESCEs it against the existing value, so a later status/goal
149
+ // upsert that omits source never clears a previously-stamped tag.
150
+ ...(input.source ? { source: input.source } : {}),
126
151
  };
127
152
  store.upsertMission(record);
128
153
  const saved = store.getMission(meshId, id)!;
129
- const result: MeshMissionRecord = { ...saved, status: normalizeMissionStatus(saved.status) };
154
+ const result: MeshMissionRecord = {
155
+ ...saved,
156
+ status: normalizeMissionStatus(saved.status),
157
+ source: normalizeMissionSource(saved.source),
158
+ };
130
159
 
131
160
  // Mission audit trail: record this mutation in the mesh ledger so mission
132
161
  // lifecycle (create, goal rewrite, status transition) is auditable alongside
@@ -201,12 +230,12 @@ function appendMissionLedgerEntries(
201
230
 
202
231
  export function getMeshMissions(meshId: string, statuses?: MeshMissionStatus[]): MeshMissionRecord[] {
203
232
  return MeshRuntimeStore.getInstance().getMissions(meshId, statuses)
204
- .map(m => ({ ...m, status: normalizeMissionStatus(m.status) }));
233
+ .map(m => ({ ...m, status: normalizeMissionStatus(m.status), source: normalizeMissionSource(m.source) }));
205
234
  }
206
235
 
207
236
  export function getMeshMission(meshId: string, missionId: string): MeshMissionRecord | null {
208
237
  const record = MeshRuntimeStore.getInstance().getMission(meshId, missionId);
209
- return record ? { ...record, status: normalizeMissionStatus(record.status) } : null;
238
+ return record ? { ...record, status: normalizeMissionStatus(record.status), source: normalizeMissionSource(record.source) } : null;
210
239
  }
211
240
 
212
241
  /** Aggregate task statuses for a mission at query time (no stored progress). */
@@ -367,15 +396,28 @@ export function getMeshStatusMissionsCompact(
367
396
  * coordinator can deliberately surface paused/abandoned/completed missions that
368
397
  * the live status view would hide or truncate.
369
398
  *
399
+ * MAGI bounding: by default this EXCLUDES completed MAGI missions (source==='magi'
400
+ * && status==='completed') — a mesh_magi_review fan-out auto-creates one inline
401
+ * mission per run and auto-closes it on collection, so without this they accumulate
402
+ * unbounded and drown out coordinator missions in the list. In-progress MAGI missions
403
+ * (active/paused) are still shown so a running cross-verification stays visible. Pass
404
+ * `includeMagi: true` to return every mission including completed MAGI ones. A mission
405
+ * with no `source` (legacy / coordinator) is never affected.
406
+ *
370
407
  * Compact (the default) elides each goal to a capped preview + goalTruncated
371
408
  * flag; verbose returns the full goal text. The stored goal is never mutated.
372
409
  */
373
410
  export function listMeshMissionSummaries(
374
411
  meshId: string,
375
- options?: { statuses?: MeshMissionStatus[]; verbose?: boolean },
412
+ options?: { statuses?: MeshMissionStatus[]; verbose?: boolean; includeMagi?: boolean },
376
413
  ): MeshMissionSummary[] | MeshMissionSlimSummary[] {
377
414
  const statuses = options?.statuses && options.statuses.length > 0 ? options.statuses : undefined;
415
+ const includeMagi = options?.includeMagi === true;
378
416
  const missions = getMeshMissions(meshId, statuses)
417
+ // Bound completed-MAGI accumulation by default. Only a mission EXPLICITLY
418
+ // tagged source==='magi' AND completed is hidden — coordinator/legacy
419
+ // (source undefined) missions and in-progress MAGI missions always pass.
420
+ .filter(m => includeMagi || !(m.source === 'magi' && m.status === 'completed'))
379
421
  .sort((a, b) => (b.updatedAt || '').localeCompare(a.updatedAt || ''));
380
422
  const full = missions.map(mission => summarizeMeshMission(meshId, mission));
381
423
  return options?.verbose ? full : full.map(summary => slimMissionSummary(summary));
@@ -298,6 +298,7 @@ export class MeshRuntimeStore {
298
298
  title TEXT NOT NULL,
299
299
  goal TEXT NOT NULL DEFAULT '',
300
300
  status TEXT NOT NULL DEFAULT 'active',
301
+ source TEXT,
301
302
  created_at TEXT NOT NULL,
302
303
  updated_at TEXT NOT NULL
303
304
  );
@@ -373,6 +374,15 @@ export class MeshRuntimeStore {
373
374
  );
374
375
  `);
375
376
  }
377
+
378
+ // 3. mesh_missions.source: nullable provenance tag ('magi' | 'coordinator').
379
+ // Pre-existing rows keep source NULL — listMeshMissionSummaries treats a
380
+ // NULL/absent source as a coordinator mission (never auto-hidden), so the
381
+ // completed-MAGI bounding only ever affects rows explicitly stamped 'magi'.
382
+ const missionCols = this.tableColumns('mesh_missions');
383
+ if (!missionCols.has('source')) {
384
+ this.db.exec(`ALTER TABLE mesh_missions ADD COLUMN source TEXT`);
385
+ }
376
386
  } catch (err: any) {
377
387
  // Best-effort: a failed isolation migration must not brick the store. The
378
388
  // CREATE-TABLE definitions above already carry the new schema for fresh DBs;
@@ -1742,15 +1752,21 @@ export class MeshRuntimeStore {
1742
1752
  title: string;
1743
1753
  goal?: string;
1744
1754
  status?: string;
1755
+ source?: string;
1745
1756
  }): void {
1746
1757
  const now = new Date().toISOString();
1758
+ // `source` is a write-once provenance tag: on conflict we only overwrite it
1759
+ // with a non-null incoming value (COALESCE(excluded, existing)), so a later
1760
+ // status/goal upsert that omits source never clears a previously-stamped
1761
+ // 'magi'/'coordinator' tag.
1747
1762
  this.db.prepare(
1748
- `INSERT INTO mesh_missions (id, mesh_id, title, goal, status, created_at, updated_at)
1749
- VALUES (?, ?, ?, ?, ?, ?, ?)
1763
+ `INSERT INTO mesh_missions (id, mesh_id, title, goal, status, source, created_at, updated_at)
1764
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
1750
1765
  ON CONFLICT(id) DO UPDATE SET
1751
1766
  title = excluded.title,
1752
1767
  goal = excluded.goal,
1753
1768
  status = excluded.status,
1769
+ source = COALESCE(excluded.source, mesh_missions.source),
1754
1770
  updated_at = excluded.updated_at`
1755
1771
  ).run(
1756
1772
  mission.id,
@@ -1758,21 +1774,22 @@ export class MeshRuntimeStore {
1758
1774
  mission.title,
1759
1775
  mission.goal ?? '',
1760
1776
  mission.status ?? 'active',
1777
+ mission.source ?? null,
1761
1778
  now,
1762
1779
  now,
1763
1780
  );
1764
1781
  this.maybeCheckpointWal();
1765
1782
  }
1766
1783
 
1767
- getMission(meshId: string, missionId: string): { id: string; meshId: string; title: string; goal: string; status: string; createdAt: string; updatedAt: string } | null {
1784
+ getMission(meshId: string, missionId: string): { id: string; meshId: string; title: string; goal: string; status: string; source?: string; createdAt: string; updatedAt: string } | null {
1768
1785
  const row = this.db.prepare(
1769
1786
  'SELECT * FROM mesh_missions WHERE mesh_id = ? AND id = ?'
1770
1787
  ).get(meshId, missionId) as Record<string, string> | undefined;
1771
1788
  if (!row) return null;
1772
- return { id: row.id, meshId: row.mesh_id, title: row.title, goal: row.goal, status: row.status, createdAt: row.created_at, updatedAt: row.updated_at };
1789
+ return { id: row.id, meshId: row.mesh_id, title: row.title, goal: row.goal, status: row.status, source: row.source ?? undefined, createdAt: row.created_at, updatedAt: row.updated_at };
1773
1790
  }
1774
1791
 
1775
- getMissions(meshId: string, statuses?: string[]): Array<{ id: string; meshId: string; title: string; goal: string; status: string; createdAt: string; updatedAt: string }> {
1792
+ getMissions(meshId: string, statuses?: string[]): Array<{ id: string; meshId: string; title: string; goal: string; status: string; source?: string; createdAt: string; updatedAt: string }> {
1776
1793
  let rows: Array<Record<string, string>>;
1777
1794
  if (statuses?.length) {
1778
1795
  const placeholders = statuses.map(() => '?').join(', ');
@@ -1784,7 +1801,7 @@ export class MeshRuntimeStore {
1784
1801
  'SELECT * FROM mesh_missions WHERE mesh_id = ? ORDER BY updated_at DESC'
1785
1802
  ).all(meshId) as Array<Record<string, string>>;
1786
1803
  }
1787
- return rows.map(row => ({ id: row.id, meshId: row.mesh_id, title: row.title, goal: row.goal, status: row.status, createdAt: row.created_at, updatedAt: row.updated_at }));
1804
+ return rows.map(row => ({ id: row.id, meshId: row.mesh_id, title: row.title, goal: row.goal, status: row.status, source: row.source ?? undefined, createdAt: row.created_at, updatedAt: row.updated_at }));
1788
1805
  }
1789
1806
 
1790
1807
  /** Remove all missions for a mesh — mesh deletion / test cleanup. */
@@ -13,6 +13,7 @@
13
13
 
14
14
  import type { GitRepoStatus, GitCompactSummary } from './git/git-types.js';
15
15
  import type { MeshMissionSummary, MeshMissionSlimSummary } from './mesh/mesh-missions.js';
16
+ import type { MeshMagiActivitySummary } from './mesh/mesh-magi-status.js';
16
17
  import type { MagiPanelMap } from '@adhdev/mesh-shared';
17
18
 
18
19
  // ─── Core Mesh Types ────────────────────────────
@@ -944,6 +945,17 @@ export interface RepoMeshStatus {
944
945
  * an optional `stats` operational rollup (durations / retries).
945
946
  */
946
947
  missions?: (MeshMissionSummary | MeshMissionSlimSummary)[];
948
+ /**
949
+ * MAGI cross-verification activity, reconstructed from the mesh ledger
950
+ * (magi_dispatched / magi_synthesis entries) and folded in so the dashboard's
951
+ * MAGI surface can read synthesis output — needs_verification counts, the
952
+ * independence banner, git skew, and a bounded needs_verification preview —
953
+ * without re-running collection. Running groups are always included; synthesized
954
+ * groups are bounded to recent ones (see summarizeMeshMagiActivity). Omitted by
955
+ * daemons predating the exposure and when no MAGI run is present; treat as
956
+ * optional. Mirrors the MCP `mesh_status` tool's `magiActivity` field.
957
+ */
958
+ magiActivity?: MeshMagiActivitySummary[];
947
959
  }
948
960
 
949
961
  // RepoMeshSessionStatus shape now lives in @adhdev/mesh-shared (shared with