@adhdev/daemon-core 0.9.82-rc.548 → 0.9.82-rc.549

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.548",
3
+ "version": "0.9.82-rc.549",
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",
@@ -47,8 +47,8 @@
47
47
  "author": "vilmire",
48
48
  "license": "AGPL-3.0-or-later",
49
49
  "dependencies": {
50
- "@adhdev/mesh-shared": "0.9.82-rc.548",
51
- "@adhdev/session-host-core": "0.9.82-rc.548",
50
+ "@adhdev/mesh-shared": "0.9.82-rc.549",
51
+ "@adhdev/session-host-core": "0.9.82-rc.549",
52
52
  "@agentclientprotocol/sdk": "^0.16.1",
53
53
  "ajv": "^8.20.0",
54
54
  "ajv-formats": "^3.0.1",
@@ -19,6 +19,9 @@ import { LOG } from '../../logging/logger.js';
19
19
  import { readStringValue } from '../router.js';
20
20
  import type { MedFamilyContext, MedFamilyHandler } from './types.js';
21
21
 
22
+ /** Outcome of the coordinator-mirror refresh: not a mesh worker (no-op), refreshed, or failed. */
23
+ type MirrorForwardOutcome = 'skipped' | 'refreshed' | 'failed';
24
+
22
25
  /**
23
26
  * MESH-WORKER-PREFS Fix B: when a mesh WORKER session's per-conversation Hide/Mute is toggled
24
27
  * (set_conversation_prefs, forwarded to the worker daemon by the coordinator's
@@ -27,19 +30,25 @@ import type { MedFamilyContext, MedFamilyHandler } from './types.js';
27
30
  * meshOwnedSessions) is only refreshed by a `mesh_forward_event` carrying `sessionSettings`.
28
31
  * Without one the mirror stays stale, so when the coordinator re-emits the session metadata to
29
32
  * the dashboard the old surfaceHidden/muted comes back and the toggle reverts after the 8s
30
- * optimistic overlay expires. Push a best-effort `mesh_forward_event` to the coordinator daemon
31
- * with the fresh prefs so its mirror updates immediately. Fire-and-forget: the coordinator's
32
- * mesh_forward_event command consumer calls updateMeshOwnedSession(args) BEFORE routing, so the
33
- * mirror is refreshed even though the (non coordinator-event) event name is otherwise ignored;
34
- * a self-addressed or failed dispatch is harmless (the local status snapshot already fired).
33
+ * optimistic overlay expires. Push a `mesh_forward_event` to the coordinator daemon with the
34
+ * fresh prefs so its mirror updates immediately. The coordinator's mesh_forward_event command
35
+ * consumer calls updateMeshOwnedSession(args) BEFORE routing, so the mirror is refreshed even
36
+ * though the (non coordinator-event) event name is otherwise ignored.
37
+ *
38
+ * Fix (4) — atomicity: the worker's local updateSettings and the coordinator mirror refresh were
39
+ * fire-and-forget, so a dropped/rejected forward left the mirror stale and the dashboard toggle
40
+ * silently reverted (the exact "restore does nothing" defect). This now AWAITS the dispatch, retries
41
+ * once on failure, and reports the outcome so the handler can flag a stale mirror to the caller
42
+ * instead of returning an unqualified success. It never rejects — a failure is reported, not thrown,
43
+ * so the local write (already applied) still returns success with a mirrorStale marker.
35
44
  */
36
- function forwardConversationPrefsToCoordinator(
45
+ async function forwardConversationPrefsToCoordinator(
37
46
  ctx: MedFamilyContext,
38
47
  sessionId: string,
39
48
  patch: Record<string, unknown>,
40
- ): void {
49
+ ): Promise<MirrorForwardOutcome> {
41
50
  const dispatch = ctx.deps.dispatchMeshCommand;
42
- if (!dispatch) return;
51
+ if (!dispatch) return 'skipped';
43
52
  let settings: Record<string, unknown> = {};
44
53
  try {
45
54
  const state = ctx.deps.instanceManager.getInstance(sessionId)?.getState?.();
@@ -51,7 +60,7 @@ function forwardConversationPrefsToCoordinator(
51
60
  const coordinatorDaemonId = readNonEmptyString(settings.meshCoordinatorDaemonId);
52
61
  const meshId = readNonEmptyString(settings.meshNodeFor);
53
62
  // A non-delegated (non-mesh) session has no coordinator anchor — nothing to mirror.
54
- if (!coordinatorDaemonId || !meshId) return;
63
+ if (!coordinatorDaemonId || !meshId) return 'skipped';
55
64
  const nodeId = readNonEmptyString(settings.meshNodeId) || readNonEmptyString(settings.meshLastNodeId);
56
65
 
57
66
  // sessionSettings mirrors the exact keys updateMeshOwnedSession merges onto the mirror's
@@ -64,8 +73,20 @@ function forwardConversationPrefsToCoordinator(
64
73
  ...(nodeId ? { nodeId } : {}),
65
74
  sessionSettings: { ...patch },
66
75
  };
67
- Promise.resolve(dispatch(coordinatorDaemonId, 'mesh_forward_event', payload))
68
- .catch((e: any) => LOG.warn('Mesh', `[Mesh] set_conversation_prefs mirror forward to coordinator ${coordinatorDaemonId.slice(0, 12)} failed: ${e?.message || e}`));
76
+
77
+ // Await the mirror refresh (Fix 4). One retry: a transient P2P blip on the coordinator
78
+ // channel shouldn't strand the mirror stale when the worker's own state already moved.
79
+ for (let attempt = 0; attempt < 2; attempt++) {
80
+ try {
81
+ await dispatch(coordinatorDaemonId, 'mesh_forward_event', payload);
82
+ return 'refreshed';
83
+ } catch (e: any) {
84
+ if (attempt === 0) continue;
85
+ LOG.warn('Mesh', `[Mesh] set_conversation_prefs mirror forward to coordinator ${coordinatorDaemonId.slice(0, 12)} failed after retry: ${e?.message || e}`);
86
+ return 'failed';
87
+ }
88
+ }
89
+ return 'failed';
69
90
  }
70
91
 
71
92
  export const cliAgentHandlers: Record<string, MedFamilyHandler> = {
@@ -139,12 +160,25 @@ export const cliAgentHandlers: Record<string, MedFamilyHandler> = {
139
160
  // Push a fresh status snapshot so all clients see the updated
140
161
  // surfaceHidden/muted immediately (cloud path). The standalone server has a
141
162
  // parallel broadcast gate keyed on the command name (see daemon-standalone).
163
+ // Fired BEFORE the awaited mirror refresh so the local write is visible even if
164
+ // the coordinator mirror dispatch is slow or fails.
142
165
  ctx.deps.onStatusChange?.();
143
- // MESH-WORKER-PREFS Fix B: if this is a mesh worker session, mirror the fresh prefs onto
144
- // the coordinator so its stale meshOwnedSessions copy can't revert the toggle when it
145
- // re-emits the dashboard metadata. Best-effort / no-op for non-mesh sessions.
146
- forwardConversationPrefsToCoordinator(ctx, sessionId, patch);
147
- return { success: true, sessionId, ...patch };
166
+ // MESH-WORKER-PREFS Fix B + Fix (4): if this is a mesh worker session, mirror the fresh
167
+ // prefs onto the coordinator so its stale meshOwnedSessions copy can't revert the toggle
168
+ // when it re-emits the dashboard metadata. AWAITED (with one retry) so a dropped mirror
169
+ // refresh is reported to the caller (mirrorStale) instead of silently leaving the
170
+ // coordinator to re-stamp the old value — the "restore does nothing" revert. No-op for
171
+ // non-mesh sessions. Never throws: the local write already succeeded.
172
+ const mirror = await forwardConversationPrefsToCoordinator(ctx, sessionId, patch);
173
+ return {
174
+ success: true,
175
+ sessionId,
176
+ ...patch,
177
+ // Only surface the mirror status for a genuine mesh worker session (refreshed/failed);
178
+ // a non-mesh session ('skipped') carries no mirror field, so nothing changes for it.
179
+ ...(mirror === 'failed' ? { mirrorStale: true } : {}),
180
+ ...(mirror === 'refreshed' ? { mirrorRefreshed: true } : {}),
181
+ };
148
182
  },
149
183
 
150
184
  agent_command: async (ctx: MedFamilyContext, args: any) => {