@adhdev/daemon-core 0.9.82-rc.9 → 0.9.82-rc.90

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 (67) 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/index.d.ts +13 -6
  7. package/dist/index.js +5395 -1197
  8. package/dist/index.js.map +1 -1
  9. package/dist/index.mjs +5359 -1183
  10. package/dist/index.mjs.map +1 -1
  11. package/dist/installer.d.ts +1 -4
  12. package/dist/launch.d.ts +1 -1
  13. package/dist/logging/async-batch-writer.d.ts +10 -0
  14. package/dist/mesh/beads-db.d.ts +18 -0
  15. package/dist/mesh/mesh-active-work.d.ts +60 -0
  16. package/dist/mesh/mesh-events.d.ts +29 -5
  17. package/dist/mesh/mesh-fast-forward.d.ts +39 -0
  18. package/dist/mesh/mesh-host-ownership.d.ts +9 -0
  19. package/dist/mesh/mesh-ledger.d.ts +38 -1
  20. package/dist/mesh/mesh-work-queue.d.ts +27 -5
  21. package/dist/mesh/refine-config.d.ts +176 -0
  22. package/dist/providers/chat-message-normalization.d.ts +1 -0
  23. package/dist/providers/cli-provider-instance.d.ts +2 -1
  24. package/dist/repo-mesh-types.d.ts +46 -0
  25. package/dist/status/reporter.d.ts +2 -0
  26. package/package.json +3 -1
  27. package/src/boot/daemon-lifecycle.ts +1 -0
  28. package/src/cli-adapters/provider-cli-adapter.ts +91 -3
  29. package/src/cli-adapters/provider-cli-parse.d.ts +1 -0
  30. package/src/cli-adapters/provider-cli-parse.ts +4 -0
  31. package/src/cli-adapters/provider-cli-runtime.ts +3 -1
  32. package/src/cli-adapters/provider-cli-shared.d.ts +2 -0
  33. package/src/cli-adapters/provider-cli-shared.ts +20 -10
  34. package/src/commands/chat-commands.ts +454 -15
  35. package/src/commands/cli-manager.ts +126 -0
  36. package/src/commands/handler.ts +8 -1
  37. package/src/commands/mesh-coordinator.ts +13 -143
  38. package/src/commands/router.ts +2687 -435
  39. package/src/config/chat-history.ts +9 -7
  40. package/src/config/mesh-config.ts +245 -1
  41. package/src/daemon/dev-cli-debug.ts +10 -1
  42. package/src/detection/ide-detector.ts +26 -16
  43. package/src/index.ts +31 -5
  44. package/src/installer.d.ts +1 -1
  45. package/src/installer.ts +8 -6
  46. package/src/launch.d.ts +1 -1
  47. package/src/launch.ts +37 -28
  48. package/src/logging/async-batch-writer.ts +55 -0
  49. package/src/logging/logger.ts +2 -1
  50. package/src/mesh/beads-db.ts +176 -0
  51. package/src/mesh/coordinator-prompt.ts +30 -7
  52. package/src/mesh/mesh-active-work.ts +243 -0
  53. package/src/mesh/mesh-events.ts +400 -47
  54. package/src/mesh/mesh-fast-forward.ts +430 -0
  55. package/src/mesh/mesh-host-ownership.ts +73 -0
  56. package/src/mesh/mesh-ledger.ts +138 -1
  57. package/src/mesh/mesh-work-queue.ts +199 -137
  58. package/src/mesh/refine-config.ts +356 -0
  59. package/src/providers/chat-message-normalization.ts +3 -1
  60. package/src/providers/cli-provider-instance.ts +91 -13
  61. package/src/providers/ide-provider-instance.ts +17 -3
  62. package/src/providers/provider-loader.ts +10 -4
  63. package/src/providers/read-chat-contract.ts +1 -1
  64. package/src/providers/version-archive.ts +38 -20
  65. package/src/repo-mesh-types.ts +51 -0
  66. package/src/status/reporter.ts +15 -0
  67. 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
 
@@ -62,6 +94,13 @@ export interface RepoMeshPolicy {
62
94
  requirePreTaskCheckpoint: boolean;
63
95
  requirePostTaskCheckpoint: boolean;
64
96
  requireApprovalForPush: boolean;
97
+ /**
98
+ * Narrow Refinery opt-in: when validation and patch-equivalence have passed,
99
+ * allow Refinery to publish submodule gitlink commits to each submodule's
100
+ * configured remote main branch with a non-force push, then verify reachability.
101
+ * Defaults to false; root branch pushes/merges are not affected.
102
+ */
103
+ allowAutoPublishSubmoduleMainCommits?: boolean;
65
104
  requireApprovalForDestructiveGit: boolean;
66
105
  dirtyWorkspaceBehavior: 'block' | 'warn' | 'checkpoint_then_continue';
67
106
  maxParallelTasks: number;
@@ -126,6 +165,7 @@ export const DEFAULT_MESH_POLICY: RepoMeshPolicy = {
126
165
  requirePreTaskCheckpoint: false,
127
166
  requirePostTaskCheckpoint: true,
128
167
  requireApprovalForPush: true,
168
+ allowAutoPublishSubmoduleMainCommits: false,
129
169
  requireApprovalForDestructiveGit: true,
130
170
  dirtyWorkspaceBehavior: 'warn',
131
171
  maxParallelTasks: 2,
@@ -229,6 +269,7 @@ export interface LocalMeshEntry {
229
269
  defaultBranch?: string;
230
270
  policy: RepoMeshPolicy;
231
271
  coordinator: RepoMeshCoordinatorConfig;
272
+ meshHost?: RepoMeshHostMetadata;
232
273
  nodes: LocalMeshNodeEntry[];
233
274
  createdAt: string;
234
275
  updatedAt: string;
@@ -251,6 +292,7 @@ export interface LocalMeshNodeEntry {
251
292
  clonedFromNodeId?: string;
252
293
  /** Optional associated/external repos configured as node metadata. */
253
294
  relatedRepos?: RepoMeshRelatedRepo[];
295
+ role?: RepoMeshDaemonRole;
254
296
  }
255
297
 
256
298
  // ─── Mesh Status (runtime, not persisted) ───────
@@ -259,7 +301,9 @@ export interface RepoMeshStatus {
259
301
  meshId: string;
260
302
  meshName: string;
261
303
  repoIdentity: string;
304
+ defaultBranch?: string;
262
305
  refreshedAt: string;
306
+ meshHost?: RepoMeshHostStatus;
263
307
  nodes: RepoMeshNodeStatus[];
264
308
  queue?: RepoMeshQueueStatus;
265
309
  ledger?: RepoMeshLedgerStatus;
@@ -300,11 +344,18 @@ export interface RepoMeshNodeStatus {
300
344
  repoRoot?: string;
301
345
  daemonId?: string;
302
346
  machineId?: string;
347
+ role?: RepoMeshDaemonRole;
303
348
  machineStatus?: string;
304
349
  isLocalWorktree?: boolean;
305
350
  worktreeBranch?: string;
306
351
  health: RepoMeshNodeHealth;
307
352
  git?: GitRepoStatus;
353
+ /**
354
+ * True when the selected coordinator has evidence that a peer git probe is still
355
+ * in flight or just timed out during initial mesh handshake, so callers should
356
+ * treat missing git data as pending instead of authoritative absence.
357
+ */
358
+ gitProbePending?: boolean;
308
359
  providers: string[];
309
360
  activeSessions: string[];
310
361
  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
  }