@adhdev/daemon-core 0.9.82-rc.460 → 0.9.82-rc.462

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.
@@ -102,6 +102,14 @@ export interface GitCommandServices {
102
102
  checkoutFiles?: (params: { workspace: string; paths: string[] }) => Promise<{ checkedOut: string[] }>;
103
103
  getRemoteUrl?: (params: { workspace: string; remote?: string }) => Promise<{ remoteUrl: string; remote: string }>;
104
104
  push?: (params: { workspace: string; remote?: string; branch?: string; setUpstream?: boolean }) => Promise<GitPushResult>;
105
+ /**
106
+ * Best-effort, non-blocking source of this daemon's detected provider CLI/ACP
107
+ * versions (keyed by provider id) + its build version, folded onto the git_status
108
+ * envelope so a mesh coordinator can self-heal each node's providerVersions the
109
+ * same way it does platform/arch. Wired at daemon boot from the cached CLI
110
+ * detection snapshot; omitted (⇒ versions not reported) when unavailable.
111
+ */
112
+ getReporterProviderVersions?: () => { providerVersions?: Record<string, string>; daemonBuildVersion?: string };
105
113
  }
106
114
 
107
115
  type GitCommandFailure = {
@@ -116,7 +124,7 @@ type GitCommandSuccess =
116
124
  // node's userOverrides.platform/arch (the fields capability-tag routing reads).
117
125
  // reporterMachineNickname carries the responding daemon's config.machineNickname
118
126
  // so the coordinator can populate node.machineNickname → the friendly display label.
119
- | { success: true; status: GitRepoStatus; reporterPlatform?: string; reporterArch?: string; reporterMachineNickname?: string }
127
+ | { success: true; status: GitRepoStatus; reporterPlatform?: string; reporterArch?: string; reporterMachineNickname?: string; reporterProviderVersions?: Record<string, string>; reporterDaemonBuildVersion?: string }
120
128
  | { success: true; diffSummary: GitDiffSummary }
121
129
  | { success: true; diff: GitFileDiff }
122
130
  | { success: true; snapshot: GitSnapshot }
@@ -179,7 +187,9 @@ const defaultSnapshotStore = createGitSnapshotStore({
179
187
  getDiffSummary: (workspace) => getGitDiffSummary(workspace),
180
188
  });
181
189
 
182
- export function createDefaultGitCommandServices(): GitCommandServices {
190
+ export function createDefaultGitCommandServices(
191
+ overrides?: Pick<GitCommandServices, 'getReporterProviderVersions'>,
192
+ ): GitCommandServices {
183
193
  return {
184
194
  getStatus: ({ workspace, refreshUpstream }) => getGitRepoStatus(workspace, { refreshUpstream }),
185
195
  getDiffSummary: ({ workspace, base }) => getGitDiffSummary(workspace, base ? { baseRef: base } : {}),
@@ -199,6 +209,9 @@ export function createDefaultGitCommandServices(): GitCommandServices {
199
209
  getRemoteUrl: async ({ workspace, remote = 'origin' }) => gitGetRemoteUrl(workspace, remote),
200
210
  push: async ({ workspace, remote = 'origin', branch, setUpstream = false }) =>
201
211
  gitPush(workspace, remote, branch, setUpstream),
212
+ ...(overrides?.getReporterProviderVersions
213
+ ? { getReporterProviderVersions: overrides.getReporterProviderVersions }
214
+ : {}),
202
215
  };
203
216
  }
204
217
 
@@ -328,12 +341,32 @@ export async function handleGitCommand(
328
341
  return undefined;
329
342
  }
330
343
  })();
344
+ // Provider versions + build version ride the same self-report channel (T7
345
+ // visibility). Best-effort and non-blocking: the service reads a cached
346
+ // snapshot, so a cold cache simply omits the fields on this probe.
347
+ const reporterVersions = (() => {
348
+ try {
349
+ return services.getReporterProviderVersions?.() ?? {};
350
+ } catch {
351
+ return {};
352
+ }
353
+ })();
354
+ const reporterProviderVersions =
355
+ reporterVersions.providerVersions && Object.keys(reporterVersions.providerVersions).length > 0
356
+ ? reporterVersions.providerVersions
357
+ : undefined;
358
+ const reporterDaemonBuildVersion =
359
+ typeof reporterVersions.daemonBuildVersion === 'string' && reporterVersions.daemonBuildVersion.trim()
360
+ ? reporterVersions.daemonBuildVersion.trim()
361
+ : undefined;
331
362
  return {
332
363
  success: true,
333
364
  status,
334
365
  reporterPlatform: process.platform,
335
366
  reporterArch: process.arch,
336
367
  ...(reporterMachineNickname ? { reporterMachineNickname } : {}),
368
+ ...(reporterProviderVersions ? { reporterProviderVersions } : {}),
369
+ ...(reporterDaemonBuildVersion ? { reporterDaemonBuildVersion } : {}),
337
370
  };
338
371
  }
339
372
 
package/src/index.ts CHANGED
@@ -295,7 +295,7 @@ export { buildMeshHostRequiredFailure, createDefaultMeshHostMetadata, isMeshHost
295
295
  // export type { MeshGraph, MeshGraphNode, MeshGraphEdge, MeshGraphNodeType, MeshGraphEdgeType } from './mesh/mesh-visualization.js';
296
296
 
297
297
  // ── Mesh Events ──
298
- export { triggerMeshQueue, drainPendingMeshCoordinatorEvents, getPendingMeshCoordinatorEvents, clearPendingMeshCoordinatorEvents, queuePendingMeshCoordinatorEvent, reconcileDirectDispatchCompletionFromTranscript } from './mesh/mesh-events.js';
298
+ export { triggerMeshQueue, drainPendingMeshCoordinatorEvents, getPendingMeshCoordinatorEvents, clearPendingMeshCoordinatorEvents, queuePendingMeshCoordinatorEvent, serializeV2EnvelopeToWire, readV2EnvelopeFromWire, reconcileDirectDispatchCompletionFromTranscript } from './mesh/mesh-events.js';
299
299
  export type { PendingMeshCoordinatorEvent } from './mesh/mesh-events.js';
300
300
  // The coordinator-side preview surfaced from a worker's completion/status event
301
301
  // (finalSummary / workerResult.summary / lastMessagePreview). Same data the mobile
@@ -392,9 +392,27 @@ function buildNodeStatusSection(nodes: RepoMeshNodeStatus[]): string {
392
392
  ? `sessions: ${n.activeSessions.join(', ')}`
393
393
  : 'no active sessions';
394
394
  const branch = n.git?.branch ? `branch: \`${n.git.branch}\`` : '';
395
+ // Render each provider with its detected version when known (T7 visibility)
396
+ // so the coordinator can eyeball a per-node provider-version skew inline —
397
+ // e.g. `claude-cli@1.2.3`. Providers without a reported version render bare.
398
+ const providerVersions = n.providerVersions && typeof n.providerVersions === 'object'
399
+ ? n.providerVersions
400
+ : undefined;
401
+ const providersRendered = n.providers?.length
402
+ ? n.providers
403
+ .map((p) => {
404
+ const version = providerVersions?.[p];
405
+ return version ? `${p}@${version}` : p;
406
+ })
407
+ .join(', ')
408
+ : '';
409
+ const buildVersion = typeof n.daemonBuildVersion === 'string' && n.daemonBuildVersion
410
+ ? `build: ${n.daemonBuildVersion}`
411
+ : '';
395
412
  const context = [
396
413
  n.daemonId ? `daemon: \`${n.daemonId}\`` : '',
397
- n.providers?.length ? `providers: ${n.providers.join(', ')}` : '',
414
+ providersRendered ? `providers: ${providersRendered}` : '',
415
+ buildVersion,
398
416
  ].filter(Boolean).join(' | ');
399
417
  lines.push(`- ${healthIcon} **${n.machineLabel}** (nodeId: \`${n.nodeId}\`)`);
400
418
  lines.push(` workspace: \`${n.workspace}\`${context ? ` | ${context}` : ''} | ${branch} | ${sessions}`);
@@ -7,7 +7,7 @@ import type { SessionRecoveryContext } from './mesh-ledger.js';
7
7
  import { updateSessionTaskStatus, enqueueTask, updateDirectDispatchStatus, cleanupTerminalDirectDispatches, getActiveDirectDispatches, hasPendingDependents, getQueue } from './mesh-work-queue.js';
8
8
  import { markSessionDeliveriesTerminal, updateSessionDeliveryStatus, recordCompletionConflict } from './mesh-delivery-policy.js';
9
9
  import { MeshRuntimeStore } from './mesh-runtime-store.js';
10
- import { queuePendingMeshCoordinatorEvent, drainPendingMeshCoordinatorEvents, type PendingMeshCoordinatorEvent } from './mesh-events-pending.js';
10
+ import { queuePendingMeshCoordinatorEvent, drainPendingMeshCoordinatorEvents, readV2EnvelopeFromWire, type PendingMeshCoordinatorEvent } from './mesh-events-pending.js';
11
11
  import type { ProviderInstance } from '../providers/provider-instance.js';
12
12
  import { resolveWorkerDelegateRouting, recordUnroutableDelegateEvent, isUnroutableDelegateRejection } from './mesh-routing.js';
13
13
  import { resolveMeshHostStatus } from './mesh-host-ownership.js';
@@ -681,6 +681,13 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
681
681
  nodeLabel: string;
682
682
  event: string;
683
683
  metadataEvent: Record<string, unknown>;
684
+ // T4 (B3b): v2 envelope restored from a remote (P2P) relay's flat payload. Only the
685
+ // relay path (handleMeshForwardEvent) sets it; the in-process forward path leaves it
686
+ // undefined and the local emit stamp applies as usual. When present with a preserved
687
+ // eventId, it is spread onto the re-queued pending event so stampPendingEventV2's
688
+ // already-stamped short-circuit keeps the ORIGINAL eventId (cross-machine idempotency).
689
+ v2Envelope?: Partial<Pick<PendingMeshCoordinatorEvent,
690
+ 'protocolVersion' | 'eventId' | 'scope' | 'dispatchedBy' | 'intendedFor'>>;
684
691
  }) {
685
692
  const eventSessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId);
686
693
  const eventNodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId);
@@ -1363,6 +1370,12 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
1363
1370
  // Top-level session anchor for the local PHASE 2 strict-match on the coordinator
1364
1371
  // daemon. Absent → daemon-level broadcast (legacy / single-coordinator path).
1365
1372
  ...(workerCoordinatorSessionId ? { targetCoordinatorSessionId: workerCoordinatorSessionId } : {}),
1373
+ // T4 (B3b): restore the v2 envelope from the remote relay so the re-queue keeps the
1374
+ // ORIGINAL eventId. Spread LAST so its authoritative eventId/scope/identity win over
1375
+ // any default. queuePendingMeshCoordinatorEvent → stampPendingEventV2 then no-ops
1376
+ // (already-stamped short-circuit) instead of minting a fresh eventId. Empty object
1377
+ // for a v1 relay → unchanged v1 emit-stamp path (version-skew safe).
1378
+ ...(args.v2Envelope ?? {}),
1366
1379
  };
1367
1380
  if (queuePendingMeshCoordinatorEvent(pendingEvent)) {
1368
1381
  LOG.info('MeshEvents', `Queued ${args.event} for coordinator (mesh ${args.meshId}${workerCoordinatorDaemonId ? `, coordinator daemon ${workerCoordinatorDaemonId}` : ''}${workerCoordinatorSessionId ? `, coordinator session ${workerCoordinatorSessionId}` : ''})`);
@@ -1518,6 +1531,11 @@ export function handleMeshForwardEvent(components: DaemonComponents, payload: Re
1518
1531
  nodeLabel,
1519
1532
  event: eventName,
1520
1533
  metadataEvent: buildRelayMetadataEvent(payload),
1534
+ // T4 (B3b): restore the v2 envelope carried at the top level of the relayed flat
1535
+ // payload (buildForwardPayloadFromPending → serializeV2EnvelopeToWire) so the
1536
+ // re-queue preserves the original eventId (idempotency) and unicast routing rather
1537
+ // than re-stamping a fresh v1/broadcast event. Empty for a v1 relay (version-skew safe).
1538
+ v2Envelope: readV2EnvelopeFromWire(payload),
1521
1539
  });
1522
1540
  }
1523
1541