@adhdev/daemon-core 0.9.82-rc.424 → 0.9.82-rc.426

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/daemon-core",
3
- "version": "0.9.82-rc.424",
3
+ "version": "0.9.82-rc.426",
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.424",
49
+ "@adhdev/mesh-shared": "0.9.82-rc.426",
50
50
  "@adhdev/session-host-core": "*",
51
51
  "@agentclientprotocol/sdk": "^0.16.1",
52
52
  "ajv": "^8.20.0",
@@ -222,6 +222,64 @@ export const meshCrudHandlers: Record<string, MedFamilyHandler> = {
222
222
  }
223
223
  },
224
224
 
225
+ // ─── MAGI panels (machine-local config, sibling to meshes) ───────────────
226
+ // Panels live in ~/.adhdev/meshes.json `magiPanels` and are pure local config
227
+ // (no mesh ownership). These three handlers mirror list_meshes/create_mesh/
228
+ // update_mesh: dynamic-import the already-exported mesh-config accessors and
229
+ // surface normalizeMagiPanel's structured error codes (invalid_magi_panel,
230
+ // magi_panel_exists) verbatim so the dashboard can render them.
231
+ //
232
+ // Permission: magi_panel_set / magi_panel_remove are WRITE commands. They are
233
+ // intentionally NOT listed in canPeerUsePrivilegedShareCommand (daemon-cloud
234
+ // data-channel-router), so a peer holding ANY share permission hits its
235
+ // `default → false` branch — identical owner-only gating to create_mesh /
236
+ // update_mesh / list_meshes (none of which are listed there either). A trusted
237
+ // peer (no permission = the owner) passes the top `!permission → true` guard.
238
+ // Mirror, don't invent: do not add a new policy tier here.
239
+ //
240
+ // Resolvability (coupling / stale / available) is deliberately NOT computed
241
+ // here: buildMagiFanoutPlan lives in mcp-server, unreachable from daemon-core.
242
+ // magi_panel_list returns the raw definitions only; the dashboard derives
243
+ // member resolvability client-side (web-core MagiPanelManager, reusing the
244
+ // MagiGroupRow coupling logic) against live mesh_status.
245
+ magi_panel_list: async (_ctx: MedFamilyContext, _args: any) => {
246
+ try {
247
+ const { listMagiPanels } = await import('../../config/mesh-config.js');
248
+ return { success: true, panels: listMagiPanels() };
249
+ } catch (e: any) {
250
+ return { success: false, error: e.message };
251
+ }
252
+ },
253
+
254
+ magi_panel_set: async (_ctx: MedFamilyContext, args: any) => {
255
+ const name = typeof args?.name === 'string' ? args.name.trim() : '';
256
+ if (!name) return { success: false, error: 'invalid_magi_panel: panel name is required' };
257
+ try {
258
+ const { upsertMagiPanel } = await import('../../config/mesh-config.js');
259
+ // normalizeMagiPanel (invoked inside upsertMagiPanel) validates members,
260
+ // enforces MAX_MAGI_PANEL_MEMBERS, and clamps replica counts. Its
261
+ // invalid_magi_panel / magi_panel_exists messages flow back as `error`.
262
+ const panel = upsertMagiPanel(name, args?.panel, { overwrite: args?.overwrite === true });
263
+ return { success: true, name, panel };
264
+ } catch (e: any) {
265
+ // Surface the structured code (invalid_magi_panel: … / magi_panel_exists: …)
266
+ // verbatim so the editor can map it to a field-level message.
267
+ return { success: false, error: e.message };
268
+ }
269
+ },
270
+
271
+ magi_panel_remove: async (_ctx: MedFamilyContext, args: any) => {
272
+ const name = typeof args?.name === 'string' ? args.name.trim() : '';
273
+ if (!name) return { success: false, error: 'invalid_magi_panel: panel name is required' };
274
+ try {
275
+ const { removeMagiPanel } = await import('../../config/mesh-config.js');
276
+ const removed = removeMagiPanel(name);
277
+ return { success: true, removed };
278
+ } catch (e: any) {
279
+ return { success: false, error: e.message };
280
+ }
281
+ },
282
+
225
283
  add_mesh_node: async (ctx: MedFamilyContext, args: any) => {
226
284
  const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
227
285
  const workspace = typeof args?.workspace === 'string' ? args.workspace.trim() : '';
@@ -44,7 +44,7 @@ import type { DaemonComponents } from '../boot/daemon-lifecycle.js';
44
44
  import type { LocalMeshEntry } from '../repo-mesh-types.js';
45
45
  import { loadConfig } from '../config/config.js';
46
46
  import { listMeshes } from '../config/mesh-config.js';
47
- import { LOG } from '../logging/logger.js';
47
+ import { LOG, getLogLevel } from '../logging/logger.js';
48
48
  import { drainPendingMeshCoordinatorEvents, getPendingMeshCoordinatorEvents, buildPendingEventFingerprint, queuePendingMeshCoordinatorEvent } from './mesh-events-pending.js';
49
49
  import type { PendingMeshCoordinatorEvent } from './mesh-events-pending.js';
50
50
  import { appendLedgerEntry } from './mesh-ledger.js';
@@ -319,6 +319,38 @@ function findLiveCoordinators(components: DaemonComponents): LiveCoordinator[] {
319
319
  ? (inst as any).isModalParked() === true
320
320
  : (status === 'waiting_choice' || status === 'waiting_approval');
321
321
  const sessionId = readNonEmptyString(state.instanceId);
322
+ // ── NOTIF (B) desync diagnostic (read-only, no behavior change) ───────────
323
+ // The confirmed (B) defect: a coordinator whose FSM is idle (status above ===
324
+ // 'idle' for minutes) is nonetheless classified busy here, so the generating/
325
+ // modal-park hold never drains and a worker completion is stranded until the
326
+ // user's next turn edge. Static analysis found no code path where getState()
327
+ // returns generating while lastStatus and the adapter raw are both idle — so the
328
+ // divergence is a runtime desync between the three status sources. Capture all
329
+ // three (plus the auto-approve mask state that getState() overlays at :803) for
330
+ // EVERY mesh-coordinator candidate on this tick, so the source that diverges from
331
+ // the others can be read directly against the same-tick "skip → generating"/
332
+ // "skip → modal-parked" hold logs below (pair by sessionId + timestamp).
333
+ //
334
+ // CRITICAL: reuse the `state` already fetched above (line ~301) — do NOT call
335
+ // getState() again. getState() runs maybeAutoApproveStatus() as a side effect,
336
+ // which would mutate the very auto-approve mask we are trying to observe. The
337
+ // adapter raw read uses allowParse:false, which only reads engine.activeModal and
338
+ // is side-effect-free.
339
+ if (getLogLevel() === 'debug') {
340
+ let adapterRaw = '?';
341
+ try {
342
+ const a = (inst as any).adapter;
343
+ if (a && typeof a.getStatus === 'function') {
344
+ adapterRaw = readNonEmptyString(a.getStatus({ allowParse: false })?.status) || '?';
345
+ }
346
+ } catch (e: any) {
347
+ adapterRaw = `err:${e?.message || e}`;
348
+ }
349
+ const lastStatus = readNonEmptyString((inst as any).lastStatus) || '?';
350
+ const autoApproveBusy = (inst as any).autoApproveBusy;
351
+ const maskSince = (inst as any).autoApproveMaskSince;
352
+ LOG.debug('MeshReconcile', `coordDiag sess=${sessionId || '?'} mesh=${meshId} getState=${status || '?'} lastStatus=${lastStatus} adapterRaw=${adapterRaw} autoApproveBusy=${autoApproveBusy === true} maskSince=${maskSince || 0}`);
353
+ }
322
354
  // Modal-park transition observability: a coordinator entering modal-park is what
323
355
  // begins holding completion events under `modal_parked`; one leaving it is what
324
356
  // drains them. Both transitions were previously SILENT (the operator had no log
@@ -902,6 +934,12 @@ export async function runMeshReconcileTick(components: DaemonComponents): Promis
902
934
  }
903
935
  }
904
936
  LOG.info('MeshReconcile', `Reconcile skip → modal-parked: holding pending event(s) for mesh ${meshId} (${modalParkedCoordinators.length} coordinator(s) awaiting a modal answer; events left queued${orphanEscaped > 0 ? `; ${orphanEscaped} orphan-targeted event(s) routed to strict-route TTL` : ''})`);
937
+ // NOTIF (B) diagnostic: name the session(s) classified modal-parked so the
938
+ // same-tick coordDiag line (paired by sessionId) shows whether the modal-park
939
+ // overlay is a real human-await or an unreleased mask (the getState_overlay origin).
940
+ if (getLogLevel() === 'debug') {
941
+ LOG.debug('MeshReconcile', `coordHoldModalParked mesh=${meshId} heldFor=[${modalParkedCoordinators.map(c => c.sessionId || '?').join(',')}] (these were classified modal-parked; cross-ref same-tick coordDiag by sessionId)`);
942
+ }
905
943
  // C1: mirror held terminal events into the ledger so a held completion's
906
944
  // worker summary is auditable/recoverable even if the modal is never
907
945
  // resolved, the coordinator restarts, or the pending file is later trimmed.
@@ -937,6 +975,14 @@ export async function runMeshReconcileTick(components: DaemonComponents): Promis
937
975
  }
938
976
  if (hasPending) {
939
977
  LOG.info('MeshReconcile', `Reconcile skip → generating: holding pending event(s) for mesh ${meshId} (${generatingCoordinators.length} coordinator(s) busy; events left queued for the next idle tick)`);
978
+ // NOTIF (B) diagnostic: this is the hold that strands the completion. Name
979
+ // the sessionId(s) the loop just classified non-idle/non-modal so the
980
+ // same-tick coordDiag line above (paired by sessionId) reveals which status
981
+ // source diverged. If a coordDiag for one of these sessions shows getState
982
+ // (or lastStatus/adapterRaw) === idle, that is the runtime desync origin.
983
+ if (getLogLevel() === 'debug') {
984
+ LOG.debug('MeshReconcile', `coordHoldGenerating mesh=${meshId} heldFor=[${generatingCoordinators.map(c => c.sessionId || '?').join(',')}] (these were classified busy; cross-ref same-tick coordDiag by sessionId)`);
985
+ }
940
986
  recordHeldTerminalEventsToLedger(
941
987
  meshId,
942
988
  drainDaemonIds.length > 0 ? drainDaemonIds : (localDaemonId ? [localDaemonId] : []),