@quolu/lattice 0.79.2 → 0.79.4

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 (39) hide show
  1. package/LICENSE +1 -1
  2. package/README.ja.md +1 -1
  3. package/README.md +9 -3
  4. package/bin/lattice-bridge.mjs +31 -24
  5. package/bin/lattice-dashboard-supervisor.mjs +18 -0
  6. package/bin/lattice-dashboard.mjs +32 -10
  7. package/docs/bridge-setup.md +29 -0
  8. package/package.json +2 -2
  9. package/sensor/dist/bin/lattice-sensor.js +6 -26
  10. package/sensor/dist/bin/node-version-check.d.ts +10 -1
  11. package/sensor/dist/bin/node-version-check.d.ts.map +1 -1
  12. package/sensor/dist/bin/node-version-check.js +43 -1
  13. package/sensor/dist/bin/node-version-check.js.map +1 -1
  14. package/sensor/dist/extraction/parse-pool.d.ts +8 -1
  15. package/sensor/dist/extraction/parse-pool.d.ts.map +1 -1
  16. package/sensor/dist/extraction/parse-pool.js +83 -12
  17. package/sensor/dist/extraction/parse-pool.js.map +1 -1
  18. package/sensor/dist/extraction/parse-worker.js +2 -0
  19. package/sensor/dist/extraction/parse-worker.js.map +1 -1
  20. package/sensor/dist/mcp/daemon-paths.d.ts +4 -0
  21. package/sensor/dist/mcp/daemon-paths.d.ts.map +1 -1
  22. package/sensor/dist/mcp/daemon-paths.js +12 -0
  23. package/sensor/dist/mcp/daemon-paths.js.map +1 -1
  24. package/sensor/dist/mcp/index.d.ts.map +1 -1
  25. package/sensor/dist/mcp/index.js +52 -28
  26. package/sensor/dist/mcp/index.js.map +1 -1
  27. package/sensor/package.json +1 -1
  28. package/src/bridge-cli.mjs +6 -2
  29. package/src/bridge-config.mjs +8 -0
  30. package/src/bridge-daemon.mjs +30 -2
  31. package/src/dashboard-delivery.mjs +14 -2
  32. package/src/dashboard-lifecycle-ipc.mjs +5 -0
  33. package/src/dashboard-lifecycle-journal.mjs +235 -0
  34. package/src/dashboard-process-control.mjs +74 -0
  35. package/src/dashboard-supervisor.mjs +180 -0
  36. package/src/dashboard-windows-process-control.ps1 +40 -0
  37. package/src/factory-diagnostics.mjs +22 -9
  38. package/src/node-version-guard.mjs +3 -42
  39. package/src/todo-dashboard-registry.mjs +24 -3
@@ -1,4 +1,3 @@
1
- import { spawn } from 'node:child_process';
2
1
  import { randomBytes } from 'node:crypto';
3
2
  import { homedir } from 'node:os';
4
3
  import {
@@ -9,6 +8,10 @@ import path from 'node:path';
9
8
  import packageJson from '../package.json' with { type: 'json' };
10
9
  import { isAuditPendingPhaseStatus } from './todo-audit-pending.mjs';
11
10
  import { observeStartIdentityEpochMs } from './runtime-os-observation.mjs';
11
+ import { spawnSupervisedDashboard } from './dashboard-supervisor.mjs';
12
+ import { reconcileLifecycleRuns, readDashboardLifecycleStatus,
13
+ recordDashboardEnsureDecision } from './dashboard-lifecycle-journal.mjs';
14
+ import { observeDashboardProcess } from './dashboard-process-control.mjs';
12
15
 
13
16
  /**
14
17
  * The version of the code in THIS process. A daemon loads its modules once at
@@ -49,6 +52,10 @@ function paths(env) {
49
52
  };
50
53
  }
51
54
 
55
+ export async function readTodoDashboardLifecycleStatus({ env = process.env } = {}) {
56
+ return readDashboardLifecycleStatus(runtimeDir(env));
57
+ }
58
+
52
59
  function validEntry(entry) {
53
60
  return entry !== null && typeof entry === 'object' && !Array.isArray(entry)
54
61
  && Object.keys(entry).sort().join(',') === 'display_name,last_seen_at,project_id,repo_root,session_id'
@@ -523,7 +530,7 @@ export async function forgetTodoDashboardDaemonRecord({ env = process.env } = {}
523
530
  await rm(daemonRecordRef(paths(env), process.pid), { force: true });
524
531
  }
525
532
 
526
- export async function ensureTodoDashboardDaemon({ env = process.env, spawnDaemon = spawn,
533
+ export async function ensureTodoDashboardDaemon({ env = process.env, spawnDaemon = spawnSupervisedDashboard,
527
534
  signalProcess = process.kill, isProcessAlive = processIsAlive, startupTimeoutMs = 120_000,
528
535
  legacyStopTimeoutMs = 3_000, replacementIsProcessAlive = processIsAlive,
529
536
  attestationTimeoutMs = 2_000, strayStopTimeoutMs = 3_000,
@@ -535,15 +542,18 @@ export async function ensureTodoDashboardDaemon({ env = process.env, spawnDaemon
535
542
  const reap = (keepPid) => reapStrayDaemons(refs, keepPid,
536
543
  { signalProcess, isProcessAlive, timeoutMs: strayStopTimeoutMs });
537
544
  return withLock(refs.startupLock, async () => {
545
+ await reconcileLifecycleRuns(refs.root, observeDashboardProcess);
538
546
  const existing = await readJson(refs.descriptor, null);
539
547
  const existingAttestation = await daemonAttestation(existing, { timeoutMs: attestationTimeoutMs });
540
548
  if (existingAttestation === 'current') {
549
+ await recordDashboardEnsureDecision(refs.root, 'reuse_current', existing.pid);
541
550
  await reap(existing.pid);
542
551
  return existing;
543
552
  }
544
553
  if (validDaemonDescriptor(existing) && existingAttestation === null
545
554
  && await isProcessAlive(existing.pid)
546
555
  && !await descriptorPidWasReused(existing, { observeProcessStartEpochMs })) {
556
+ await recordDashboardEnsureDecision(refs.root, 'alive_unresponsive', existing.pid);
547
557
  throw dashboardError('DASHBOARD_DAEMON_UNRESPONSIVE', 'dashboard daemon is alive but temporarily unresponsive');
548
558
  }
549
559
  let legacy = existingAttestation === 'legacy' ? existing : null;
@@ -558,6 +568,7 @@ export async function ensureTodoDashboardDaemon({ env = process.env, spawnDaemon
558
568
  // 引き取った相手は生きて応答しているので、descriptorの不変条件を満たす。
559
569
  await atomicJson(refs.descriptor, adopted.record);
560
570
  if (adopted.attestation === 'current') {
571
+ await recordDashboardEnsureDecision(refs.root, 'adopt_current', adopted.record.pid);
561
572
  await reap(adopted.record.pid);
562
573
  return adopted.record;
563
574
  }
@@ -569,9 +580,14 @@ export async function ensureTodoDashboardDaemon({ env = process.env, spawnDaemon
569
580
  && Number(portText) <= 65_535 ? Number(portText) : DEFAULT_PORT;
570
581
  const port = legacy === null ? configuredPort : 0;
571
582
  if (legacy === null) await rm(refs.descriptor, { force: true });
572
- const child = spawnDaemon(process.execPath, [path.resolve(import.meta.dirname, '../bin/lattice-dashboard.mjs')], {
583
+ const startupDecision = legacy !== null ? 'replace_legacy'
584
+ : validDaemonDescriptor(existing) && await isProcessAlive(existing.pid) ? 'pid_reused'
585
+ : 'start_missing';
586
+ await recordDashboardEnsureDecision(refs.root, startupDecision, legacy?.pid ?? null);
587
+ const child = await spawnDaemon(process.execPath, [path.resolve(import.meta.dirname, '../bin/lattice-dashboard.mjs')], {
573
588
  detached: true,
574
589
  stdio: 'ignore',
590
+ version: TODO_DASHBOARD_CODE_VERSION,
575
591
  env: { ...env, LATTICE_DASHBOARD_RUNTIME_DIR: refs.root, LATTICE_DASHBOARD_PORT: String(port) },
576
592
  });
577
593
  child.unref();
@@ -584,6 +600,10 @@ export async function ensureTodoDashboardDaemon({ env = process.env, spawnDaemon
584
600
  const deadline = Date.now() + startupTimeoutMs;
585
601
  while (Date.now() < deadline) {
586
602
  await new Promise((resolve) => setTimeout(resolve, 50));
603
+ if (child.observationError) {
604
+ child.kill?.('SIGTERM');
605
+ throw child.observationError;
606
+ }
587
607
  const descriptor = typeof child.pid === 'number'
588
608
  ? await readJson(daemonRecordRef(refs, child.pid), null) : null;
589
609
  const healthy = await daemonHealthy(descriptor);
@@ -601,6 +621,7 @@ export async function ensureTodoDashboardDaemon({ env = process.env, spawnDaemon
601
621
  }
602
622
  }
603
623
  await atomicJson(refs.descriptor, descriptor);
624
+ await recordDashboardEnsureDecision(refs.root, startupDecision, descriptor.pid);
604
625
  await reap(descriptor.pid);
605
626
  return descriptor;
606
627
  }