reflectt-node 0.1.4 → 0.1.6

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 (160) hide show
  1. package/README.md +63 -146
  2. package/defaults/TEAM-ROLES.yaml +221 -31
  3. package/dist/activationEvents.d.ts +13 -2
  4. package/dist/activationEvents.d.ts.map +1 -1
  5. package/dist/activationEvents.js +172 -38
  6. package/dist/activationEvents.js.map +1 -1
  7. package/dist/activity.d.ts +72 -0
  8. package/dist/activity.d.ts.map +1 -0
  9. package/dist/activity.js +510 -0
  10. package/dist/activity.js.map +1 -0
  11. package/dist/alert-preflight.d.ts +33 -0
  12. package/dist/alert-preflight.d.ts.map +1 -1
  13. package/dist/alert-preflight.js +218 -2
  14. package/dist/alert-preflight.js.map +1 -1
  15. package/dist/assignment.d.ts.map +1 -1
  16. package/dist/assignment.js +11 -6
  17. package/dist/assignment.js.map +1 -1
  18. package/dist/boardHealthWorker.d.ts.map +1 -1
  19. package/dist/boardHealthWorker.js +25 -12
  20. package/dist/boardHealthWorker.js.map +1 -1
  21. package/dist/canvas-slots.d.ts +1 -1
  22. package/dist/channels.d.ts +1 -1
  23. package/dist/chat-approval-detector.d.ts.map +1 -1
  24. package/dist/chat-approval-detector.js +29 -11
  25. package/dist/chat-approval-detector.js.map +1 -1
  26. package/dist/chat.d.ts +14 -0
  27. package/dist/chat.d.ts.map +1 -1
  28. package/dist/chat.js +68 -4
  29. package/dist/chat.js.map +1 -1
  30. package/dist/cli.js +349 -28
  31. package/dist/cli.js.map +1 -1
  32. package/dist/cloud.d.ts +28 -1
  33. package/dist/cloud.d.ts.map +1 -1
  34. package/dist/cloud.js +62 -25
  35. package/dist/cloud.js.map +1 -1
  36. package/dist/compliance-detector.d.ts +42 -0
  37. package/dist/compliance-detector.d.ts.map +1 -0
  38. package/dist/compliance-detector.js +286 -0
  39. package/dist/compliance-detector.js.map +1 -0
  40. package/dist/continuity-loop.d.ts.map +1 -1
  41. package/dist/continuity-loop.js +7 -3
  42. package/dist/continuity-loop.js.map +1 -1
  43. package/dist/dashboard.d.ts +6 -2
  44. package/dist/dashboard.d.ts.map +1 -1
  45. package/dist/dashboard.js +84 -28
  46. package/dist/dashboard.js.map +1 -1
  47. package/dist/db.d.ts.map +1 -1
  48. package/dist/db.js +24 -1
  49. package/dist/db.js.map +1 -1
  50. package/dist/doctor.d.ts.map +1 -1
  51. package/dist/doctor.js +17 -6
  52. package/dist/doctor.js.map +1 -1
  53. package/dist/executionSweeper.d.ts +2 -0
  54. package/dist/executionSweeper.d.ts.map +1 -1
  55. package/dist/executionSweeper.js +60 -4
  56. package/dist/executionSweeper.js.map +1 -1
  57. package/dist/focus.d.ts +20 -0
  58. package/dist/focus.d.ts.map +1 -0
  59. package/dist/focus.js +57 -0
  60. package/dist/focus.js.map +1 -0
  61. package/dist/health.d.ts +1 -0
  62. package/dist/health.d.ts.map +1 -1
  63. package/dist/health.js +47 -15
  64. package/dist/health.js.map +1 -1
  65. package/dist/hostConnectGuard.d.ts +25 -0
  66. package/dist/hostConnectGuard.d.ts.map +1 -0
  67. package/dist/hostConnectGuard.js +27 -0
  68. package/dist/hostConnectGuard.js.map +1 -0
  69. package/dist/index.js +257 -39
  70. package/dist/index.js.map +1 -1
  71. package/dist/insight-mutation.d.ts +26 -0
  72. package/dist/insight-mutation.d.ts.map +1 -1
  73. package/dist/insight-mutation.js +103 -12
  74. package/dist/insight-mutation.js.map +1 -1
  75. package/dist/insight-task-bridge.d.ts +1 -1
  76. package/dist/insight-task-bridge.d.ts.map +1 -1
  77. package/dist/insight-task-bridge.js +6 -3
  78. package/dist/insight-task-bridge.js.map +1 -1
  79. package/dist/insights.d.ts +20 -0
  80. package/dist/insights.d.ts.map +1 -1
  81. package/dist/insights.js +129 -4
  82. package/dist/insights.js.map +1 -1
  83. package/dist/mcp.d.ts.map +1 -1
  84. package/dist/mcp.js +9 -8
  85. package/dist/mcp.js.map +1 -1
  86. package/dist/notificationDedupeGuard.d.ts +33 -0
  87. package/dist/notificationDedupeGuard.d.ts.map +1 -0
  88. package/dist/notificationDedupeGuard.js +88 -0
  89. package/dist/notificationDedupeGuard.js.map +1 -0
  90. package/dist/openclaw.d.ts.map +1 -1
  91. package/dist/openclaw.js +3 -2
  92. package/dist/openclaw.js.map +1 -1
  93. package/dist/policy.d.ts +1 -1
  94. package/dist/policy.d.ts.map +1 -1
  95. package/dist/policy.js +3 -1
  96. package/dist/policy.js.map +1 -1
  97. package/dist/prAutoMerge.d.ts.map +1 -1
  98. package/dist/prAutoMerge.js +23 -0
  99. package/dist/prAutoMerge.js.map +1 -1
  100. package/dist/presence.d.ts +16 -1
  101. package/dist/presence.d.ts.map +1 -1
  102. package/dist/presence.js +97 -9
  103. package/dist/presence.js.map +1 -1
  104. package/dist/pulse.d.ts +60 -0
  105. package/dist/pulse.d.ts.map +1 -0
  106. package/dist/pulse.js +139 -0
  107. package/dist/pulse.js.map +1 -0
  108. package/dist/reflection-automation.d.ts.map +1 -1
  109. package/dist/reflection-automation.js +38 -0
  110. package/dist/reflection-automation.js.map +1 -1
  111. package/dist/release.d.ts +2 -0
  112. package/dist/release.d.ts.map +1 -1
  113. package/dist/release.js +14 -1
  114. package/dist/release.js.map +1 -1
  115. package/dist/request-tracker.d.ts +6 -0
  116. package/dist/request-tracker.d.ts.map +1 -1
  117. package/dist/request-tracker.js +31 -12
  118. package/dist/request-tracker.js.map +1 -1
  119. package/dist/scopeOverlap.d.ts +32 -0
  120. package/dist/scopeOverlap.d.ts.map +1 -0
  121. package/dist/scopeOverlap.js +219 -0
  122. package/dist/scopeOverlap.js.map +1 -0
  123. package/dist/server.d.ts.map +1 -1
  124. package/dist/server.js +736 -117
  125. package/dist/server.js.map +1 -1
  126. package/dist/service-probe.d.ts.map +1 -1
  127. package/dist/service-probe.js +39 -2
  128. package/dist/service-probe.js.map +1 -1
  129. package/dist/shipped-heartbeat.d.ts +1 -1
  130. package/dist/shipped-heartbeat.js +1 -1
  131. package/dist/taskPrecheck.js +6 -6
  132. package/dist/taskPrecheck.js.map +1 -1
  133. package/dist/tasks-next-diagnostics.d.ts +15 -0
  134. package/dist/tasks-next-diagnostics.d.ts.map +1 -0
  135. package/dist/tasks-next-diagnostics.js +33 -0
  136. package/dist/tasks-next-diagnostics.js.map +1 -0
  137. package/dist/tasks.d.ts +3 -2
  138. package/dist/tasks.d.ts.map +1 -1
  139. package/dist/tasks.js +41 -16
  140. package/dist/tasks.js.map +1 -1
  141. package/dist/team-config.d.ts.map +1 -1
  142. package/dist/team-config.js +20 -0
  143. package/dist/team-config.js.map +1 -1
  144. package/dist/todoHoardingGuard.d.ts +35 -0
  145. package/dist/todoHoardingGuard.d.ts.map +1 -0
  146. package/dist/todoHoardingGuard.js +150 -0
  147. package/dist/todoHoardingGuard.js.map +1 -0
  148. package/dist/types.d.ts +4 -2
  149. package/dist/types.d.ts.map +1 -1
  150. package/dist/version.d.ts +2 -0
  151. package/dist/version.d.ts.map +1 -0
  152. package/dist/version.js +16 -0
  153. package/dist/version.js.map +1 -0
  154. package/dist/working-contract.d.ts.map +1 -1
  155. package/dist/working-contract.js +59 -3
  156. package/dist/working-contract.js.map +1 -1
  157. package/package.json +5 -1
  158. package/public/dashboard.js +161 -20
  159. package/public/docs.md +68 -8
  160. package/public/polls-mock.html +1 -1
package/dist/cli.js CHANGED
@@ -5,8 +5,9 @@
5
5
  * reflectt CLI - Command line interface for reflectt-node
6
6
  */
7
7
  import { Command } from 'commander';
8
- import { existsSync, mkdirSync, writeFileSync, readFileSync, unlinkSync } from 'fs';
8
+ import { existsSync, mkdirSync, writeFileSync, readFileSync, unlinkSync, statSync, chmodSync } from 'fs';
9
9
  import { collectDoctorReport, formatDoctorHuman } from './doctor.js';
10
+ import { hostConnectGuard } from './hostConnectGuard.js';
10
11
  import { homedir, hostname } from 'os';
11
12
  import { join, dirname } from 'path';
12
13
  import { spawn, execSync } from 'child_process';
@@ -18,6 +19,7 @@ const PID_FILE = join(REFLECTT_HOME, 'server.pid');
18
19
  function loadConfig() {
19
20
  if (existsSync(CONFIG_PATH)) {
20
21
  try {
22
+ migrateConfigPermissions();
21
23
  return JSON.parse(readFileSync(CONFIG_PATH, 'utf-8'));
22
24
  }
23
25
  catch (err) {
@@ -27,7 +29,21 @@ function loadConfig() {
27
29
  return { port: 4445, host: '127.0.0.1' };
28
30
  }
29
31
  function saveConfig(config) {
30
- writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
32
+ writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), { mode: 0o600 });
33
+ }
34
+ /** Tighten config.json permissions if too open (contains cloud credentials). */
35
+ function migrateConfigPermissions() {
36
+ if (!existsSync(CONFIG_PATH))
37
+ return;
38
+ try {
39
+ const stat = statSync(CONFIG_PATH);
40
+ const mode = stat.mode & 0o777;
41
+ if (mode !== 0o600) {
42
+ chmodSync(CONFIG_PATH, 0o600);
43
+ console.log(`🔒 Tightened ${CONFIG_PATH} permissions: ${mode.toString(8)} → 600 (contains credentials)`);
44
+ }
45
+ }
46
+ catch { /* best-effort */ }
31
47
  }
32
48
  function ensureReflecttHome() {
33
49
  mkdirSync(REFLECTT_HOME, { recursive: true });
@@ -149,6 +165,44 @@ async function enrollHostWithApiKey(input) {
149
165
  }
150
166
  throw new Error(`API key enrollment failed: ${payload?.error || `${response.status} ${response.statusText}`}`);
151
167
  }
168
+ /**
169
+ * Try to reconnect using existing persisted credentials.
170
+ * Returns the existing registration if the host is still valid, null otherwise.
171
+ */
172
+ async function tryReconnectExistingHost(cloudUrl) {
173
+ try {
174
+ const config = loadConfig();
175
+ const cloud = config.cloud;
176
+ if (!cloud?.hostId || !cloud?.credential)
177
+ return null;
178
+ // Verify the host still exists by hitting the heartbeat endpoint
179
+ const cloudBase = cloudUrl.replace(/\/+$/, '');
180
+ const response = await fetch(`${cloudBase}/api/hosts/${cloud.hostId}/heartbeat`, {
181
+ method: 'POST',
182
+ headers: {
183
+ 'content-type': 'application/json',
184
+ authorization: `Bearer ${cloud.credential}`,
185
+ },
186
+ body: JSON.stringify({
187
+ contractVersion: 1,
188
+ status: 'online',
189
+ agents: [],
190
+ activeTasks: [],
191
+ timestamp: Date.now(),
192
+ source: {},
193
+ }),
194
+ });
195
+ if (response.ok) {
196
+ console.log(` ♻️ Reusing existing host (${cloud.hostId})`);
197
+ return { hostId: cloud.hostId, credential: cloud.credential };
198
+ }
199
+ console.log(` ⚠️ Existing host ${cloud.hostId} no longer valid (${response.status}), will register new`);
200
+ return null;
201
+ }
202
+ catch {
203
+ return null;
204
+ }
205
+ }
152
206
  async function registerHostWithCloud(input) {
153
207
  const cloudBase = input.cloudUrl.replace(/\/+$/, '');
154
208
  // New cloud API path (reflectt-cloud): /api/hosts/claim
@@ -267,10 +321,20 @@ function printStep(name, pass, detail) {
267
321
  console.log(`${icon} ${name} — ${detail}`);
268
322
  }
269
323
  const program = new Command();
324
+ // Read version from package.json at runtime
325
+ const PKG_VERSION = (() => {
326
+ try {
327
+ const pkgPath = join(import.meta.dirname ?? __dirname, '..', 'package.json');
328
+ return JSON.parse(readFileSync(pkgPath, 'utf-8')).version ?? '0.0.0';
329
+ }
330
+ catch {
331
+ return '0.0.0';
332
+ }
333
+ })();
270
334
  program
271
335
  .name('reflectt')
272
336
  .description('CLI for reflectt-node - local agent communication server')
273
- .version('0.1.0');
337
+ .version(PKG_VERSION);
274
338
  // ============ INIT COMMAND ============
275
339
  program
276
340
  .command('init')
@@ -403,8 +467,17 @@ program
403
467
  .option('-d, --detach', 'Run in background')
404
468
  .action(async (options) => {
405
469
  if (!existsSync(REFLECTT_HOME)) {
406
- console.error(' reflectt not initialized. Run: reflectt init');
407
- process.exit(1);
470
+ console.log('📦 First run initializing reflectt...');
471
+ // Auto-init: create directories and default config
472
+ mkdirSync(REFLECTT_HOME, { recursive: true });
473
+ mkdirSync(DATA_DIR, { recursive: true });
474
+ mkdirSync(join(DATA_DIR, 'inbox'), { recursive: true });
475
+ if (!existsSync(CONFIG_PATH)) {
476
+ saveConfig({ port: 4445, host: '127.0.0.1' });
477
+ console.log(' ✅ config.json');
478
+ }
479
+ console.log(' ✅ ~/.reflectt/ created');
480
+ console.log('');
408
481
  }
409
482
  // Check if already running
410
483
  if (existsSync(PID_FILE)) {
@@ -423,6 +496,27 @@ program
423
496
  }
424
497
  }
425
498
  const config = loadConfig();
499
+ // Port-level guard: refuse to start if something is already listening
500
+ // (catches LaunchAgent-managed instances that don't leave a PID file)
501
+ const clientHost = (config.host === '0.0.0.0' || config.host === '::') ? '127.0.0.1' : config.host;
502
+ try {
503
+ const controller = new AbortController();
504
+ const timeout = setTimeout(() => controller.abort(), 2000);
505
+ const res = await fetch(`http://${clientHost}:${config.port}/health/deploy`, { signal: controller.signal });
506
+ clearTimeout(timeout);
507
+ if (res.ok) {
508
+ const deploy = await res.json().catch(() => ({}));
509
+ const pid = deploy.pid ?? 'unknown';
510
+ const sha = deploy.gitSha ?? 'unknown';
511
+ console.error(`❌ Server already running on port ${config.port} (PID: ${pid}, sha: ${sha})`);
512
+ console.error(' If managed by LaunchAgent: launchctl kickstart -k gui/$(id -u)/com.reflectt.node');
513
+ console.error(' Otherwise: reflectt stop && reflectt start');
514
+ process.exit(1);
515
+ }
516
+ }
517
+ catch {
518
+ // Port not responding — safe to start
519
+ }
426
520
  const { projectRoot, serverPath, useNode } = getRuntimePaths();
427
521
  if (!existsSync(serverPath)) {
428
522
  console.error(`❌ Server file not found: ${serverPath}`);
@@ -432,11 +526,57 @@ program
432
526
  const cmd = useNode ? 'node' : 'npx';
433
527
  const args = useNode ? [serverPath] : ['tsx', serverPath];
434
528
  if (options.detach) {
529
+ // Ephemeral container warning: detach is a trap in docker run --rm
530
+ const isContainer = existsSync('/.dockerenv') || existsSync('/run/.containerenv');
531
+ if (isContainer) {
532
+ console.warn('⚠️ Detected container environment. Using --detach here is risky:');
533
+ console.warn(' The server will stop when the container exits.');
534
+ console.warn(' Consider: reflectt start (foreground) or use Docker CMD directly.');
535
+ console.warn('');
536
+ }
435
537
  const pid = startServerDetached(config);
436
- console.log(' Server started in background');
538
+ const clientHost = (config.host === '0.0.0.0' || config.host === '::') ? '127.0.0.1' : config.host;
539
+ console.log(`⏳ Starting reflectt server (PID: ${pid})...`);
540
+ // Health check: wait up to 10s for server to respond
541
+ let healthy = false;
542
+ for (let i = 0; i < 20; i++) {
543
+ await new Promise(r => setTimeout(r, 500));
544
+ try {
545
+ const controller = new AbortController();
546
+ const timeout = setTimeout(() => controller.abort(), 2000);
547
+ const res = await fetch(`http://${clientHost}:${config.port}/health`, { signal: controller.signal });
548
+ clearTimeout(timeout);
549
+ if (res.ok) {
550
+ healthy = true;
551
+ break;
552
+ }
553
+ }
554
+ catch { /* not ready yet */ }
555
+ }
556
+ if (healthy) {
557
+ console.log('✅ Server is running!');
558
+ // Show deploy info for verification
559
+ try {
560
+ const controller = new AbortController();
561
+ const timeout = setTimeout(() => controller.abort(), 2000);
562
+ const dRes = await fetch(`http://${clientHost}:${config.port}/health/deploy`, { signal: controller.signal });
563
+ clearTimeout(timeout);
564
+ if (dRes.ok) {
565
+ const deploy = await dRes.json();
566
+ if (deploy.gitSha)
567
+ console.log(` Commit: ${String(deploy.gitSha).slice(0, 12)}`);
568
+ if (deploy.startedAt)
569
+ console.log(` Started: ${deploy.startedAt}`);
570
+ }
571
+ }
572
+ catch { /* deploy endpoint not available */ }
573
+ }
574
+ else {
575
+ console.log('⚠️ Server started but not responding yet (may still be booting)');
576
+ }
437
577
  console.log(` PID: ${pid}`);
438
- console.log(` URL: http://${config.host}:${config.port}`);
439
- console.log(` Dashboard: http://${config.host}:${config.port}/dashboard`);
578
+ console.log(` URL: http://${clientHost}:${config.port}`);
579
+ console.log(` Dashboard: http://${clientHost}:${config.port}/dashboard`);
440
580
  if (config.cloud) {
441
581
  console.log(` Cloud: ${config.cloud.cloudUrl} (host: ${config.cloud.hostName})`);
442
582
  }
@@ -491,41 +631,211 @@ program
491
631
  }
492
632
  }
493
633
  });
634
+ // ============ UPGRADE COMMAND ============
635
+ program
636
+ .command('upgrade')
637
+ .description('Upgrade reflectt-node to the latest version and restart')
638
+ .action(async () => {
639
+ const { execSync } = await import('child_process');
640
+ // 1. Get current version
641
+ console.log('📦 Checking for updates...');
642
+ const currentVersion = PKG_VERSION;
643
+ // 2. Update via npm
644
+ try {
645
+ console.log('⬆️ Updating reflectt-node...');
646
+ execSync('npm update -g reflectt-node', { stdio: 'inherit' });
647
+ }
648
+ catch {
649
+ console.error('❌ npm update failed. Try: npm install -g reflectt-node@latest');
650
+ process.exit(1);
651
+ }
652
+ // 3. Check new version
653
+ try {
654
+ const newVersion = execSync('node -e "console.log(require(\'reflectt-node/package.json\').version)"', { encoding: 'utf-8' }).trim();
655
+ if (newVersion === currentVersion) {
656
+ console.log(`✅ Already on latest version (${currentVersion})`);
657
+ }
658
+ else {
659
+ console.log(`✅ Updated: ${currentVersion} → ${newVersion}`);
660
+ }
661
+ }
662
+ catch {
663
+ console.log('✅ Update complete');
664
+ }
665
+ // 4. Restart if running
666
+ if (existsSync(PID_FILE)) {
667
+ const pid = readFileSync(PID_FILE, 'utf-8').trim();
668
+ try {
669
+ process.kill(Number(pid), 'SIGTERM');
670
+ console.log('⏹️ Server stopped');
671
+ unlinkSync(PID_FILE);
672
+ await new Promise(resolve => setTimeout(resolve, 1500));
673
+ }
674
+ catch (err) {
675
+ if (err.code === 'ESRCH')
676
+ unlinkSync(PID_FILE);
677
+ }
678
+ const { spawn } = await import('child_process');
679
+ console.log('🚀 Starting server...');
680
+ const child = spawn(process.execPath, [process.argv[1], 'start'], {
681
+ stdio: 'inherit',
682
+ detached: false,
683
+ cwd: process.cwd(),
684
+ });
685
+ child.on('exit', (code) => process.exit(code ?? 0));
686
+ }
687
+ else {
688
+ console.log('ℹ️ Server not running. Start with: reflectt start');
689
+ }
690
+ });
691
+ // ============ RESTART COMMAND ============
692
+ program
693
+ .command('restart')
694
+ .description('Restart the reflectt server (stop + start)')
695
+ .action(async () => {
696
+ // Stop if running
697
+ if (existsSync(PID_FILE)) {
698
+ const pid = readFileSync(PID_FILE, 'utf-8').trim();
699
+ try {
700
+ process.kill(Number(pid), 'SIGTERM');
701
+ console.log('⏹️ Server stopped (PID ' + pid + ')');
702
+ unlinkSync(PID_FILE);
703
+ // Wait for port to free up
704
+ await new Promise(resolve => setTimeout(resolve, 1500));
705
+ }
706
+ catch (err) {
707
+ if (err.code === 'ESRCH') {
708
+ console.log('⚠️ Previous process not found, cleaning up');
709
+ unlinkSync(PID_FILE);
710
+ }
711
+ }
712
+ }
713
+ else {
714
+ console.log('ℹ️ No running server found, starting fresh');
715
+ }
716
+ // Start
717
+ const { spawn } = await import('child_process');
718
+ const child = spawn(process.execPath, [process.argv[1], 'start'], {
719
+ stdio: 'inherit',
720
+ detached: false,
721
+ cwd: process.cwd(),
722
+ });
723
+ child.on('exit', (code) => process.exit(code ?? 0));
724
+ });
494
725
  // ============ STATUS COMMAND ============
495
726
  program
496
727
  .command('status')
497
728
  .description('Check server health and status')
498
729
  .action(async () => {
499
730
  const config = loadConfig();
731
+ const DEFAULT_PORT = 4445;
732
+ const configHost = (config.host === '0.0.0.0' || config.host === '::') ? '127.0.0.1' : config.host;
500
733
  // Check PID file
501
734
  const isRunning = existsSync(PID_FILE);
502
735
  const pid = isRunning ? readFileSync(PID_FILE, 'utf-8').trim() : null;
503
736
  console.log('📊 reflectt Status');
504
737
  console.log(` Config: ${CONFIG_PATH}`);
505
- console.log(` URL: http://${config.host}:${config.port}`);
738
+ // Try config port first, then default port as fallback
739
+ let health = null;
740
+ let activePort = config.port;
741
+ let activeUrl = `http://${configHost}:${config.port}`;
742
+ async function tryPort(port) {
743
+ try {
744
+ const url = `http://${configHost}:${port}/health`;
745
+ const controller = new AbortController();
746
+ const timeout = setTimeout(() => controller.abort(), 3000);
747
+ const res = await fetch(url, { signal: controller.signal });
748
+ clearTimeout(timeout);
749
+ if (res.ok)
750
+ return await res.json();
751
+ }
752
+ catch { /* not responding on this port */ }
753
+ return null;
754
+ }
755
+ health = await tryPort(config.port);
756
+ if (!health && config.port !== DEFAULT_PORT) {
757
+ // Config port failed — try default port (common drift: config says 4446, server runs on 4445)
758
+ health = await tryPort(DEFAULT_PORT);
759
+ if (health) {
760
+ activePort = DEFAULT_PORT;
761
+ activeUrl = `http://${configHost}:${DEFAULT_PORT}`;
762
+ console.log(` ⚠️ Config port ${config.port} not responding, found server on default port ${DEFAULT_PORT}`);
763
+ // Auto-fix config port to match reality
764
+ try {
765
+ config.port = DEFAULT_PORT;
766
+ saveConfig(config);
767
+ console.log(` 🔧 Auto-fixed config.json → port ${DEFAULT_PORT}`);
768
+ }
769
+ catch {
770
+ console.log(` 💡 Fix: update ${CONFIG_PATH} → "port": ${DEFAULT_PORT}`);
771
+ }
772
+ }
773
+ }
774
+ console.log(` URL: ${activeUrl}`);
506
775
  if (pid) {
507
776
  try {
508
777
  process.kill(Number(pid), 0); // Check if process exists
509
- console.log(` Process: Running (PID: ${pid})`);
778
+ if (health) {
779
+ console.log(` Process: Running (PID: ${pid})`);
780
+ }
781
+ else {
782
+ console.log(` Process: PID ${pid} exists but /health not responding — server may be unhealthy`);
783
+ }
510
784
  }
511
785
  catch (err) {
512
- console.log(` Process: Not found (stale PID file)`);
513
- return;
786
+ if (health) {
787
+ console.log(` Process: PID file stale, but server is responding on port ${activePort}`);
788
+ }
789
+ else {
790
+ console.log(` Process: Not found (stale PID file)`);
791
+ return;
792
+ }
514
793
  }
515
794
  }
516
795
  else {
517
- console.log(` Process: Not running`);
518
- return;
796
+ if (health) {
797
+ console.log(` Process: No PID file, but server is responding on port ${activePort}`);
798
+ }
799
+ else {
800
+ console.log(` Process: Not running`);
801
+ return;
802
+ }
519
803
  }
520
- // Try to get health status
521
- try {
522
- const health = await apiRequest('/health');
804
+ // Show health status
805
+ if (health) {
523
806
  console.log('\n✅ Server Health');
524
807
  console.log(` Status: ${health.status}`);
808
+ console.log(` Version: ${health.version || 'unknown'}`);
809
+ // Fetch deploy info (commit SHA + startedAt) for done_criteria #3
810
+ try {
811
+ const deployUrl = `http://${configHost}:${activePort}/health/deploy`;
812
+ const controller = new AbortController();
813
+ const timeout = setTimeout(() => controller.abort(), 3000);
814
+ const dRes = await fetch(deployUrl, { signal: controller.signal });
815
+ clearTimeout(timeout);
816
+ if (dRes.ok) {
817
+ const deploy = await dRes.json();
818
+ if (deploy.gitSha)
819
+ console.log(` Commit: ${String(deploy.gitSha).slice(0, 12)}`);
820
+ if (deploy.startedAt)
821
+ console.log(` Started: ${deploy.startedAt}`);
822
+ if (deploy.pid)
823
+ console.log(` Server PID: ${deploy.pid}`);
824
+ }
825
+ }
826
+ catch { /* deploy endpoint not available */ }
525
827
  console.log(` Chat messages: ${health.chat?.messageCount || 0}`);
526
- console.log(` Tasks: ${health.tasks?.taskCount || 0}`);
828
+ const tasks = health.tasks;
829
+ console.log(` Tasks: ${tasks?.total || 0}`);
830
+ if (health.openclaw) {
831
+ const oc = health.openclaw;
832
+ console.log(` OpenClaw: ${oc.status}${oc.gateway ? ` (${oc.gateway})` : ''}`);
833
+ }
834
+ if (health.cloud) {
835
+ console.log(` Cloud: connected`);
836
+ }
527
837
  }
528
- catch (err) {
838
+ else {
529
839
  console.log('\n⚠️ Server process exists but not responding');
530
840
  }
531
841
  });
@@ -720,7 +1030,7 @@ dogfood
720
1030
  .description('Run end-to-end cloud enrollment chain verification')
721
1031
  .requiredOption('--team-id <id>', 'Cloud team id to target')
722
1032
  .requiredOption('--token <jwt>', 'Bearer token for cloud API auth (team admin/owner)')
723
- .option('--cloud-url <url>', 'Cloud API base URL', process.env.REFLECTT_CLOUD_URL || 'https://api.reflectt.ai')
1033
+ .option('--cloud-url <url>', 'Cloud API base URL', process.env.REFLECTT_CLOUD_URL || 'https://app.reflectt.ai')
724
1034
  .option('--dashboard-url <url>', 'Dashboard base URL', process.env.REFLECTT_APP_URL || 'https://app.reflectt.ai')
725
1035
  .option('--host-name <name>', 'Host name to register', `dogfood-${process.pid}`)
726
1036
  .option('--capability <value...>', 'Host capabilities', ['openclaw', 'dogfood-smoke'])
@@ -815,7 +1125,7 @@ program
815
1125
  .description('One-shot setup: init + connect to cloud + start server. Fastest way to get running.')
816
1126
  .option('--join-token <token>', 'Cloud host join token (get one at app.reflectt.ai)')
817
1127
  .option('--api-key <key>', 'Team API key')
818
- .option('--cloud-url <url>', 'Cloud API base URL', 'https://api.reflectt.ai')
1128
+ .option('--cloud-url <url>', 'Cloud API base URL', 'https://app.reflectt.ai')
819
1129
  .option('--name <hostName>', 'Host display name', hostname())
820
1130
  .option('--type <hostType>', 'Host type', 'openclaw')
821
1131
  .action(async (options) => {
@@ -860,8 +1170,10 @@ program
860
1170
  console.log(` ✅ Home: ${REFLECTT_HOME}`);
861
1171
  // Step 2: Connect
862
1172
  console.log('☁️ Step 2/3: Connecting to Reflectt Cloud...');
863
- const cloudUrl = String(options.cloudUrl || 'https://api.reflectt.ai').replace(/\/+$/, '');
864
- const registered = options.apiKey
1173
+ const cloudUrl = String(options.cloudUrl || 'https://app.reflectt.ai').replace(/\/+$/, '');
1174
+ // Try to reconnect existing host first (preserves hostId across re-enrollments)
1175
+ const existingHost = await tryReconnectExistingHost(cloudUrl);
1176
+ const registered = existingHost || (options.apiKey
865
1177
  ? await enrollHostWithApiKey({
866
1178
  cloudUrl,
867
1179
  apiKey: options.apiKey,
@@ -873,7 +1185,7 @@ program
873
1185
  joinToken: options.joinToken,
874
1186
  hostName: options.name,
875
1187
  hostType: options.type,
876
- });
1188
+ }));
877
1189
  const config = loadConfig();
878
1190
  const nextConfig = {
879
1191
  ...config,
@@ -941,10 +1253,11 @@ host
941
1253
  .description('Enroll this reflectt-node host with Reflectt Cloud')
942
1254
  .option('--join-token <token>', 'Cloud host join token (from dashboard)')
943
1255
  .option('--api-key <key>', 'Team API key for agent-friendly enrollment (no browser needed)')
944
- .option('--cloud-url <url>', 'Cloud API base URL', 'https://api.reflectt.ai')
1256
+ .option('--cloud-url <url>', 'Cloud API base URL', 'https://app.reflectt.ai')
945
1257
  .option('--name <hostName>', 'Host display name', hostname())
946
1258
  .option('--type <hostType>', 'Host type', 'openclaw')
947
1259
  .option('--auth-token <jwt>', 'Temporary user JWT for environments where claim endpoint is JWT-gated')
1260
+ .option('--force', 'Overwrite existing cloud enrollment (destructive)')
948
1261
  .option('--no-restart', 'Do not restart/start local reflectt server after enrollment')
949
1262
  .action(async (options) => {
950
1263
  try {
@@ -960,12 +1273,20 @@ host
960
1273
  }
961
1274
  ensureReflecttHome();
962
1275
  const config = loadConfig();
963
- const cloudUrl = String(options.cloudUrl || 'https://api.reflectt.ai').replace(/\/+$/, '');
1276
+ const cloudUrl = String(options.cloudUrl || 'https://app.reflectt.ai').replace(/\/+$/, '');
1277
+ // Guard against destructive overwrite.
1278
+ const decision = hostConnectGuard({ existingCloud: config.cloud, force: Boolean(options.force) });
1279
+ if (!decision.allow) {
1280
+ console.error(decision.warning);
1281
+ process.exit(1);
1282
+ }
964
1283
  console.log('☁️ Enrolling host with Reflectt Cloud...');
965
1284
  console.log(` Cloud: ${cloudUrl}`);
966
1285
  console.log(` Host: ${options.name} (${options.type})`);
967
1286
  console.log(` Method: ${options.apiKey ? 'API key' : 'join token'}`);
968
- const registered = options.apiKey
1287
+ // Try to reconnect existing host first (preserves hostId across re-enrollments)
1288
+ const existingHost = await tryReconnectExistingHost(cloudUrl);
1289
+ const registered = existingHost || (options.apiKey
969
1290
  ? await enrollHostWithApiKey({
970
1291
  cloudUrl,
971
1292
  apiKey: options.apiKey,
@@ -978,7 +1299,7 @@ host
978
1299
  hostName: options.name,
979
1300
  hostType: options.type,
980
1301
  authToken: options.authToken,
981
- });
1302
+ }));
982
1303
  const nextConfig = {
983
1304
  ...config,
984
1305
  cloud: {