@adhdev/daemon-core 0.9.82-rc.8 → 0.9.82-rc.81

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.
Files changed (74) hide show
  1. package/dist/cli-adapters/provider-cli-adapter.d.ts +2 -0
  2. package/dist/cli-adapters/provider-cli-parse.d.ts +1 -0
  3. package/dist/cli-adapters/provider-cli-shared.d.ts +2 -0
  4. package/dist/commands/router.d.ts +22 -0
  5. package/dist/config/mesh-config.d.ts +66 -1
  6. package/dist/git/git-commands.d.ts +1 -0
  7. package/dist/git/git-status.d.ts +5 -0
  8. package/dist/git/git-types.d.ts +10 -0
  9. package/dist/index.d.ts +13 -6
  10. package/dist/index.js +5074 -1177
  11. package/dist/index.js.map +1 -1
  12. package/dist/index.mjs +5038 -1163
  13. package/dist/index.mjs.map +1 -1
  14. package/dist/installer.d.ts +1 -4
  15. package/dist/launch.d.ts +1 -1
  16. package/dist/logging/async-batch-writer.d.ts +10 -0
  17. package/dist/mesh/beads-db.d.ts +18 -0
  18. package/dist/mesh/mesh-active-work.d.ts +60 -0
  19. package/dist/mesh/mesh-events.d.ts +29 -5
  20. package/dist/mesh/mesh-fast-forward.d.ts +39 -0
  21. package/dist/mesh/mesh-host-ownership.d.ts +9 -0
  22. package/dist/mesh/mesh-ledger.d.ts +38 -1
  23. package/dist/mesh/mesh-work-queue.d.ts +27 -5
  24. package/dist/mesh/refine-config.d.ts +119 -0
  25. package/dist/providers/chat-message-normalization.d.ts +1 -0
  26. package/dist/providers/cli-provider-instance.d.ts +2 -1
  27. package/dist/repo-mesh-types.d.ts +39 -0
  28. package/dist/status/reporter.d.ts +2 -0
  29. package/package.json +3 -1
  30. package/src/boot/daemon-lifecycle.ts +1 -0
  31. package/src/cli-adapters/provider-cli-adapter.ts +91 -3
  32. package/src/cli-adapters/provider-cli-parse.d.ts +1 -0
  33. package/src/cli-adapters/provider-cli-parse.ts +4 -0
  34. package/src/cli-adapters/provider-cli-runtime.ts +3 -1
  35. package/src/cli-adapters/provider-cli-shared.d.ts +2 -0
  36. package/src/cli-adapters/provider-cli-shared.ts +20 -10
  37. package/src/commands/chat-commands.ts +310 -12
  38. package/src/commands/cli-manager.ts +101 -0
  39. package/src/commands/handler.ts +8 -1
  40. package/src/commands/mesh-coordinator.ts +13 -143
  41. package/src/commands/router.ts +2435 -414
  42. package/src/config/chat-history.ts +9 -7
  43. package/src/config/mesh-config.ts +244 -1
  44. package/src/daemon/dev-cli-debug.ts +10 -1
  45. package/src/detection/ide-detector.ts +26 -16
  46. package/src/git/git-commands.ts +3 -3
  47. package/src/git/git-status.ts +97 -6
  48. package/src/git/git-summary.ts +3 -0
  49. package/src/git/git-types.ts +11 -0
  50. package/src/index.ts +31 -5
  51. package/src/installer.d.ts +1 -1
  52. package/src/installer.ts +8 -6
  53. package/src/launch.d.ts +1 -1
  54. package/src/launch.ts +37 -28
  55. package/src/logging/async-batch-writer.ts +55 -0
  56. package/src/logging/logger.ts +2 -1
  57. package/src/mesh/beads-db.ts +176 -0
  58. package/src/mesh/coordinator-prompt.ts +27 -7
  59. package/src/mesh/mesh-active-work.ts +243 -0
  60. package/src/mesh/mesh-events.ts +398 -46
  61. package/src/mesh/mesh-fast-forward.ts +430 -0
  62. package/src/mesh/mesh-host-ownership.ts +73 -0
  63. package/src/mesh/mesh-ledger.ts +138 -1
  64. package/src/mesh/mesh-work-queue.ts +199 -137
  65. package/src/mesh/refine-config.ts +306 -0
  66. package/src/providers/chat-message-normalization.ts +3 -1
  67. package/src/providers/cli-provider-instance.ts +91 -13
  68. package/src/providers/ide-provider-instance.ts +17 -3
  69. package/src/providers/provider-loader.ts +10 -4
  70. package/src/providers/read-chat-contract.ts +1 -1
  71. package/src/providers/version-archive.ts +38 -20
  72. package/src/repo-mesh-types.ts +43 -0
  73. package/src/status/reporter.ts +15 -0
  74. package/src/system/host-memory.ts +29 -12
@@ -12,7 +12,7 @@
12
12
  import * as fs from 'fs';
13
13
  import * as path from 'path';
14
14
  import * as os from 'os';
15
- import { execSync } from 'child_process';
15
+ // Removed execSync import
16
16
  import { platform } from 'os';
17
17
  import type { ProviderLoader } from './provider-loader.js';
18
18
  import type { ProviderModule } from './contracts.js';
@@ -117,22 +117,40 @@ export class VersionArchive {
117
117
 
118
118
  // ─── Version Detection ──────────────────────────────
119
119
 
120
- function runCommand(cmd: string, timeout = 10000): string | null {
121
- try {
122
- return execSync(cmd, {
120
+ import { exec } from 'child_process';
121
+
122
+ async function runCommand(cmd: string, timeout = 10000): Promise<string | null> {
123
+ return new Promise((resolve) => {
124
+ exec(cmd, {
123
125
  encoding: 'utf-8',
124
126
  timeout,
125
- stdio: ['pipe', 'pipe', 'pipe'],
126
- }).trim();
127
- } catch {
128
- return null;
129
- }
127
+ }, (error, stdout) => {
128
+ if (error) return resolve(null);
129
+ resolve(stdout.trim());
130
+ });
131
+ });
130
132
  }
131
133
 
132
134
  function findBinary(name: string): string | null {
133
- const cmd = platform() === 'win32' ? `where ${name}` : `which ${name}`;
134
- const result = runCommand(cmd, 5000);
135
- return result ? result.split('\n')[0] : null;
135
+ const isWin = platform() === 'win32';
136
+ const paths = (process.env.PATH || '').split(isWin ? ';' : ':');
137
+ const exes = isWin ? ['.exe', '.cmd', '.bat', ''] : [''];
138
+
139
+ for (const p of paths) {
140
+ if (!p) continue;
141
+ for (const ext of exes) {
142
+ const fullPath = path.join(p, name + ext);
143
+ try {
144
+ if (fs.existsSync(fullPath)) {
145
+ const stat = fs.statSync(fullPath);
146
+ if (stat.isFile() && (isWin || (stat.mode & 0o111))) {
147
+ return fullPath;
148
+ }
149
+ }
150
+ } catch { }
151
+ }
152
+ }
153
+ return null;
136
154
  }
137
155
 
138
156
  /** Extract version string from CLI output */
@@ -162,16 +180,16 @@ function getPlatformVersionCommand(
162
180
  return undefined;
163
181
  }
164
182
 
165
- function getVersion(binary: string, versionCommand?: string): string | null {
183
+ async function getVersion(binary: string, versionCommand?: string): Promise<string | null> {
166
184
  // Custom version command from provider.json
167
185
  if (versionCommand) {
168
- const raw = runCommand(versionCommand);
186
+ const raw = await runCommand(versionCommand);
169
187
  return raw ? parseVersion(raw) : null;
170
188
  }
171
189
 
172
190
  // Default: try --version, then -V, then -v
173
191
  for (const flag of ['--version', '-V', '-v']) {
174
- const raw = runCommand(`"${binary}" ${flag}`);
192
+ const raw = await runCommand(`"${binary}" ${flag}`);
175
193
  if (raw && raw.length < 500) return parseVersion(raw);
176
194
  }
177
195
  return null;
@@ -191,11 +209,11 @@ function checkPathExists(paths: string[]): string | null {
191
209
  }
192
210
 
193
211
  /** macOS: Get app version from Info.plist */
194
- function getMacAppVersion(appPath: string): string | null {
212
+ async function getMacAppVersion(appPath: string): Promise<string | null> {
195
213
  if (platform() !== 'darwin' || !appPath.endsWith('.app')) return null;
196
214
  const plistPath = path.join(appPath, 'Contents', 'Info.plist');
197
215
  if (!fs.existsSync(plistPath)) return null;
198
- const raw = runCommand(`/usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString" "${plistPath}"`);
216
+ const raw = await runCommand(`/usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString" "${plistPath}"`);
199
217
  return raw || null;
200
218
  }
201
219
 
@@ -242,10 +260,10 @@ export async function detectAllVersions(
242
260
 
243
261
  // Version: try CLI first, then plist
244
262
  if (resolvedBin) {
245
- info.version = getVersion(resolvedBin, versionCommand);
263
+ info.version = await getVersion(resolvedBin, versionCommand);
246
264
  }
247
265
  if (!info.version && appPath) {
248
- info.version = getMacAppVersion(appPath);
266
+ info.version = await getMacAppVersion(appPath);
249
267
  }
250
268
 
251
269
  } else if (provider.category === 'cli' || provider.category === 'acp') {
@@ -256,7 +274,7 @@ export async function detectAllVersions(
256
274
  info.binary = binPath || null;
257
275
 
258
276
  if (binPath) {
259
- info.version = getVersion(binPath, versionCommand);
277
+ info.version = await getVersion(binPath, versionCommand);
260
278
  }
261
279
 
262
280
  } else if (provider.category === 'extension') {
@@ -23,11 +23,42 @@ export interface RepoMesh {
23
23
  defaultBranch?: string;
24
24
  policy: RepoMeshPolicy;
25
25
  coordinator: RepoMeshCoordinatorConfig;
26
+ meshHost?: RepoMeshHostMetadata;
26
27
  projectContext: ProjectContextSnapshot;
27
28
  nodes: RepoMeshNode[];
28
29
  status: 'active' | 'archived' | 'deleted';
29
30
  }
30
31
 
32
+ export type RepoMeshDaemonRole = 'host' | 'member';
33
+
34
+ export interface RepoMeshHostPairingMetadata {
35
+ status: 'not_configured' | 'pairing' | 'paired' | 'rejected' | 'revoked';
36
+ tokenId?: string;
37
+ joinedAt?: string;
38
+ lastPairedAt?: string;
39
+ lastRejectedAt?: string;
40
+ expiresAt?: string;
41
+ }
42
+
43
+ export interface RepoMeshHostMetadata {
44
+ /** Local daemon role for this mesh. Missing metadata defaults to host for standalone compatibility. */
45
+ role: RepoMeshDaemonRole;
46
+ /** Daemon that owns mesh truth/status/git/queue/session/ledger/coordinator ownership. */
47
+ hostDaemonId?: string;
48
+ /** Mesh node that represents the host daemon, when known. */
49
+ hostNodeId?: string;
50
+ /** Future standalone manual pairing endpoint entered by member daemons. */
51
+ hostAddress?: string;
52
+ /** Redacted pairing state only; raw join tokens must not be persisted here. */
53
+ pairing?: RepoMeshHostPairingMetadata;
54
+ }
55
+
56
+ export interface RepoMeshHostStatus extends RepoMeshHostMetadata {
57
+ canOwnCoordinator: boolean;
58
+ canOwnQueue: boolean;
59
+ defaulted: boolean;
60
+ }
61
+
31
62
  export interface RepoMeshNode {
32
63
  id: string;
33
64
  daemonId: string;
@@ -42,6 +73,7 @@ export interface RepoMeshNode {
42
73
  effectiveCapabilities: RepoMeshNodeCapabilities;
43
74
  policy: RepoMeshNodePolicy;
44
75
  health: RepoMeshNodeHealth;
76
+ role?: RepoMeshDaemonRole;
45
77
  status: 'enabled' | 'disabled' | 'removed';
46
78
  }
47
79
 
@@ -229,6 +261,7 @@ export interface LocalMeshEntry {
229
261
  defaultBranch?: string;
230
262
  policy: RepoMeshPolicy;
231
263
  coordinator: RepoMeshCoordinatorConfig;
264
+ meshHost?: RepoMeshHostMetadata;
232
265
  nodes: LocalMeshNodeEntry[];
233
266
  createdAt: string;
234
267
  updatedAt: string;
@@ -251,6 +284,7 @@ export interface LocalMeshNodeEntry {
251
284
  clonedFromNodeId?: string;
252
285
  /** Optional associated/external repos configured as node metadata. */
253
286
  relatedRepos?: RepoMeshRelatedRepo[];
287
+ role?: RepoMeshDaemonRole;
254
288
  }
255
289
 
256
290
  // ─── Mesh Status (runtime, not persisted) ───────
@@ -259,7 +293,9 @@ export interface RepoMeshStatus {
259
293
  meshId: string;
260
294
  meshName: string;
261
295
  repoIdentity: string;
296
+ defaultBranch?: string;
262
297
  refreshedAt: string;
298
+ meshHost?: RepoMeshHostStatus;
263
299
  nodes: RepoMeshNodeStatus[];
264
300
  queue?: RepoMeshQueueStatus;
265
301
  ledger?: RepoMeshLedgerStatus;
@@ -300,11 +336,18 @@ export interface RepoMeshNodeStatus {
300
336
  repoRoot?: string;
301
337
  daemonId?: string;
302
338
  machineId?: string;
339
+ role?: RepoMeshDaemonRole;
303
340
  machineStatus?: string;
304
341
  isLocalWorktree?: boolean;
305
342
  worktreeBranch?: string;
306
343
  health: RepoMeshNodeHealth;
307
344
  git?: GitRepoStatus;
345
+ /**
346
+ * True when the selected coordinator has evidence that a peer git probe is still
347
+ * in flight or just timed out during initial mesh handshake, so callers should
348
+ * treat missing git data as pending instead of authoritative absence.
349
+ */
350
+ gitProbePending?: boolean;
308
351
  providers: string[];
309
352
  activeSessions: string[];
310
353
  activeSessionDetails?: RepoMeshSessionStatus[];
@@ -51,6 +51,8 @@ export class DaemonStatusReporter {
51
51
  private lastStatusSentAt = 0;
52
52
  private statusPendingThrottle = false;
53
53
  private lastP2PStatusHash = '';
54
+ private lastP2PStatusSentAt: number = 0;
55
+ private p2pDebounceTimer: ReturnType<typeof setTimeout> | null = null;
54
56
  private lastServerStatusHash = '';
55
57
  private lastStatusSummary = '';
56
58
 
@@ -355,7 +357,20 @@ export class DaemonStatusReporter {
355
357
  : { ...hashTarget, sessions };
356
358
  const h = this.simpleHash(JSON.stringify(hashPayload));
357
359
  if (h !== this.lastP2PStatusHash) {
360
+ const now = Date.now();
361
+ // Rate limit: max 1 per 500ms
362
+ if (this.lastP2PStatusSentAt && now - this.lastP2PStatusSentAt < 500) {
363
+ if (!this.p2pDebounceTimer) {
364
+ this.p2pDebounceTimer = setTimeout(() => {
365
+ this.p2pDebounceTimer = null;
366
+ this.sendUnifiedStatusReport({ reason: 'p2p_debounce' });
367
+ }, 500);
368
+ }
369
+ return false; // Dropped for now, but will trigger later
370
+ }
371
+
358
372
  this.lastP2PStatusHash = h;
373
+ this.lastP2PStatusSentAt = now;
359
374
  this.deps.p2p?.sendStatus(payload);
360
375
  return true;
361
376
  }
@@ -11,7 +11,10 @@
11
11
  */
12
12
 
13
13
  import * as os from 'os';
14
- import { execSync } from 'child_process';
14
+ import { exec } from 'child_process';
15
+ import { promisify } from 'util';
16
+
17
+ const execAsync = promisify(exec);
15
18
 
16
19
  export interface HostMemorySnapshot {
17
20
  totalMem: number;
@@ -21,19 +24,22 @@ export interface HostMemorySnapshot {
21
24
  availableMem: number;
22
25
  }
23
26
 
24
- function parseDarwinAvailableBytes(totalMem: number): number | null {
25
- if (os.platform() !== 'darwin') return null;
27
+ let cachedDarwinAvail: number | null = null;
28
+ let darwinMemoryInterval: NodeJS.Timeout | null = null;
29
+
30
+ async function updateDarwinMemoryCache() {
31
+ if (os.platform() !== 'darwin') return;
26
32
  try {
27
- const out = execSync('vm_stat', {
33
+ const { stdout } = await execAsync('vm_stat', {
28
34
  encoding: 'utf-8',
29
35
  timeout: 4000,
30
36
  maxBuffer: 256 * 1024,
31
37
  });
32
- const pageSizeMatch = out.match(/page size of (\d+)\s*bytes/i);
38
+ const pageSizeMatch = stdout.match(/page size of (\d+)\s*bytes/i);
33
39
  const pageSize = pageSizeMatch ? parseInt(pageSizeMatch[1], 10) : 4096;
34
40
 
35
41
  const counts: Record<string, number> = {};
36
- for (const line of out.split('\n')) {
42
+ for (const line of stdout.split('\n')) {
37
43
  const m = line.match(/^\s*Pages\s+([^:]+):\s+([\d,]+)\s*\.?/);
38
44
  if (!m) continue;
39
45
  const key = m[1].trim().toLowerCase().replace(/\s+/g, '_');
@@ -49,17 +55,28 @@ function parseDarwinAvailableBytes(totalMem: number): number | null {
49
55
 
50
56
  const availPages = free + inactive + speculative + purgeable + fileBacked;
51
57
  const bytes = availPages * pageSize;
52
- if (!Number.isFinite(bytes) || bytes < 0) return null;
53
- return Math.min(bytes, totalMem);
58
+ cachedDarwinAvail = Number.isFinite(bytes) && bytes >= 0 ? Math.min(bytes, os.totalmem()) : null;
54
59
  } catch {
55
- return null;
60
+ // silently fallback
56
61
  }
57
62
  }
58
63
 
59
64
  export function getHostMemorySnapshot(): HostMemorySnapshot {
65
+ if (os.platform() === 'darwin' && !darwinMemoryInterval) {
66
+ updateDarwinMemoryCache();
67
+ darwinMemoryInterval = setInterval(updateDarwinMemoryCache, 3000);
68
+ darwinMemoryInterval.unref();
69
+ }
70
+
60
71
  const totalMem = os.totalmem();
61
72
  const freeMem = os.freemem();
62
- const darwinAvail = parseDarwinAvailableBytes(totalMem);
63
- const availableMem = darwinAvail != null ? darwinAvail : freeMem;
64
- return { totalMem, freeMem, availableMem };
73
+ const availableMem = os.platform() === 'darwin'
74
+ ? (cachedDarwinAvail ?? freeMem)
75
+ : freeMem;
76
+
77
+ return {
78
+ totalMem,
79
+ freeMem,
80
+ availableMem,
81
+ };
65
82
  }