Route routine core, engine, and dashboard diagnostics through debug-gated shared loggers. - Demote steady-state diagnostic sites while preserving warnings and errors for actionable failures. - Add cross-package severity contracts and manifest coverage for demoted log sites. - Document logging severity guidance and add a patch changeset. Files changed: .changeset/fn-8603-log-severity.md | 7 ++ docs/diagnostics.md | 20 ++++-- .../__tests__/log-severity-spam-contract.test.ts | 71 ++++++++++++++++++ packages/core/src/activity-analytics.ts | 5 +- packages/core/src/ai-summarize.ts | 61 +++++++--------- packages/core/src/async-mission-store.ts | 5 +- packages/core/src/async-secrets-store.ts | 7 +- packages/core/src/central-core.ts | 17 ++--- packages/core/src/docker-provisioning.ts | 13 ++-- packages/core/src/index.ts | 1 + packages/core/src/master-key.ts | 9 ++- packages/core/src/memory-compaction.ts | 29 ++++---- packages/core/src/memory-insights.ts | 7 +- packages/core/src/migration-orchestrator.ts | 7 +- packages/core/src/mission-store.ts | 5 +- packages/core/src/node-discovery.ts | 7 +- packages/core/src/notification/dispatcher.ts | 9 ++- .../core/src/plugins/bundled-plugin-install.ts | 11 +-- packages/core/src/reflection-store.ts | 5 +- packages/core/src/secrets-store.ts | 7 +- packages/core/src/task-store/agent-logs.ts | 21 +++--- packages/core/src/task-store/async-events.ts | 5 +- packages/core/src/task-store/async-maintenance.ts | 7 +- packages/core/src/task-store/comments-ops.ts | 7 +- packages/core/src/task-store/task-mutation-ops.ts | 11 +-- packages/core/src/task-store/workflow-integrity.ts | 9 ++- packages/core/src/types/merge-policy.ts | 5 +- packages/core/src/usage-events.ts | 5 +- .../__tests__/log-severity-spam-contract.test.ts | 48 +++++++++++++ packages/dashboard/src/ai-refine.ts | 5 +- packages/dashboard/src/ai-session-diagnostics.ts | 10 +-- packages/dashboard/src/chat.ts | 8 ++- packages/dashboard/src/devserver-manager.ts | 9 ++- packages/dashboard/src/file-service.ts | 5 +- packages/dashboard/src/github-tracking-comments.ts | 7 +- .../dashboard/src/github-tracking-reconciler.ts | 5 +- packages/dashboard/src/github-tracking-state.ts | 5 +- packages/dashboard/src/gitlab-lifecycle.ts | 5 +- packages/dashboard/src/insights-routes.ts | 9 ++- packages/dashboard/src/issue-image-attachments.ts | 5 +- packages/dashboard/src/knowledge-index.ts | 5 +- packages/dashboard/src/plugin-routes.ts | 7 +- packages/dashboard/src/routes/board-workflows.ts | 5 +- packages/dashboard/src/routes/context.ts | 5 +- .../dashboard/src/routes/register-auth-routes.ts | 13 ++-- .../routes/register-docker-provisioning-routes.ts | 7 +- .../dashboard/src/routes/register-git-github.ts | 21 +++--- packages/dashboard/src/routes/register-gitlab.ts | 7 +- .../src/routes/register-session-diff-routes.ts | 9 ++- .../src/routes/register-settings-memory-routes.ts | 7 +- .../src/routes/register-setup-activity-routes.ts | 7 +- .../dashboard/src/routes/register-signal-routes.ts | 5 +- .../src/routes/register-task-workflow-routes.ts | 11 +-- packages/dashboard/src/runtime-logger.ts | 11 +-- packages/dashboard/src/server.ts | 7 +- packages/dashboard/src/sse.ts | 8 ++- packages/dashboard/src/terminal-service.ts | 34 ++++----- packages/dashboard/src/view-chunk-manifest.ts | 5 +- .../engine/src/__tests__/log-severity-manifest.ts | 83 ++++++++++++++++++++++ .../__tests__/log-severity-spam-contract.test.ts | 40 ++++++++++- .../src/__tests__/logger-debug-gating.test.ts | 7 +- packages/engine/src/goal-anchoring-audit.ts | 5 +- packages/engine/src/plugin-runner.ts | 44 ++++++------ packages/engine/src/pty-native.ts | 9 ++- .../engine/src/runtimes/child-process-worker.ts | 4 +- packages/engine/src/self-healing.ts | 12 ++-- packages/engine/src/worktree-hooks.ts | 10 ++- 67 files changed, 632 insertions(+), 250 deletions(-) Fusion-Task-Id: FN-8603 Fusion-Task-Lineage: 53901db6-1af2-4bd7-b5ea-49507e048ef2 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
215 lines
6.8 KiB
TypeScript
215 lines
6.8 KiB
TypeScript
/**
|
|
* Shared node-pty native-asset loader.
|
|
*
|
|
* Centralizes the lazy-load, prebuild path resolution, dlopen fallback, and
|
|
* native-permission repair machinery so PTY owners (the dashboard terminal
|
|
* service and the CLI agent executor) share one implementation. The runtime
|
|
* package is `@homebridge/node-pty-prebuilt-multiarch`, aliased as `node-pty`
|
|
* in package.json.
|
|
*/
|
|
|
|
import * as fs from "node:fs";
|
|
import { createRequire } from "node:module";
|
|
import { join, dirname } from "node:path";
|
|
import { createLogger } from "./logger.js";
|
|
|
|
const log = createLogger("terminal");
|
|
|
|
// Detect if we're running as a Bun-compiled binary
|
|
// @ts-expect-error - Bun global is only available in Bun runtime
|
|
const isBunBinary = typeof Bun !== "undefined" && !!Bun.embeddedFiles;
|
|
|
|
// Lazy-loaded node-pty module (only loaded when a PTY is actually used)
|
|
let ptyModule: typeof import("node-pty") | null = null;
|
|
let ptyLoadError: Error | null = null;
|
|
|
|
const require = createRequire(import.meta.url);
|
|
|
|
/**
|
|
* Resolve the `<platform>-<arch>` directory name used for staged native
|
|
* prebuilds next to a Bun-compiled binary.
|
|
*/
|
|
export function getNativePrebuildName(): string {
|
|
const platform =
|
|
process.platform === "darwin"
|
|
? "darwin"
|
|
: process.platform === "linux"
|
|
? "linux"
|
|
: process.platform === "win32"
|
|
? "win32"
|
|
: "unknown";
|
|
const arch = process.arch === "arm64" ? "arm64" : process.arch === "x64" ? "x64" : "unknown";
|
|
return `${platform}-${arch}`;
|
|
}
|
|
|
|
/**
|
|
* Locate the installed node-pty native module directory in dev/workspace mode.
|
|
*
|
|
* NOTE: The fs.existsSync() calls in this function run during loader
|
|
* initialization (when a PTY is first used). This is acceptable as it only
|
|
* executes once per process lifetime, not per-request.
|
|
*/
|
|
export function findInstalledNodePtyNativeDir(): string | null {
|
|
try {
|
|
const packageJsonPath = require.resolve("node-pty/package.json");
|
|
const pkgRoot = dirname(packageJsonPath);
|
|
|
|
// @homebridge/node-pty-prebuilt-multiarch (aliased as node-pty) places the binary
|
|
// in build/Release/pty.node after prebuild-install runs at install time.
|
|
// Prefer this location as it is the fork's standard output path.
|
|
const releaseDir = join(pkgRoot, "build", "Release");
|
|
if (fs.existsSync(join(releaseDir, "pty.node"))) {
|
|
return releaseDir;
|
|
}
|
|
|
|
// Fallback: check the old prebuilds/<plat-arch>/ layout (upstream node-pty style).
|
|
const prebuildDir = join(pkgRoot, "prebuilds", getNativePrebuildName());
|
|
if (fs.existsSync(join(prebuildDir, "pty.node"))) {
|
|
return prebuildDir;
|
|
}
|
|
|
|
return null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Locate the native assets directory staged next to a Bun-compiled binary
|
|
* (packaged-binary mode). Looks for `runtime/<platform-arch>/pty.node`.
|
|
*/
|
|
export function findStagedNativeDir(): string | null {
|
|
const prebuildName = getNativePrebuildName();
|
|
|
|
// Check FUSION_RUNTIME_DIR env var first
|
|
if (process.env.FUSION_RUNTIME_DIR) {
|
|
const envPath = join(process.env.FUSION_RUNTIME_DIR, prebuildName);
|
|
if (fs.existsSync(join(envPath, "pty.node"))) {
|
|
return envPath;
|
|
}
|
|
}
|
|
|
|
// Look next to the executable
|
|
const execDir = dirname(process.execPath);
|
|
const nextToBinary = join(execDir, "runtime", prebuildName);
|
|
if (fs.existsSync(join(nextToBinary, "pty.node"))) {
|
|
return nextToBinary;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Best-effort repair of native-asset permissions so node-pty's `pty.node` and
|
|
* `spawn-helper` are executable. No-op on Windows.
|
|
*/
|
|
export function ensureNodePtyNativePermissions(): void {
|
|
if (process.platform === "win32") {
|
|
return;
|
|
}
|
|
|
|
const candidateDirs = new Set<string>();
|
|
const envNativeDir =
|
|
process.env.NODE_PTY_SPAWN_HELPER_DIR || process.env.FUSION_NATIVE_ASSETS_PATH;
|
|
if (envNativeDir) {
|
|
candidateDirs.add(envNativeDir);
|
|
}
|
|
|
|
const stagedNativeDir = findStagedNativeDir();
|
|
if (stagedNativeDir) {
|
|
candidateDirs.add(stagedNativeDir);
|
|
}
|
|
|
|
const installedNativeDir = findInstalledNodePtyNativeDir();
|
|
if (installedNativeDir) {
|
|
candidateDirs.add(installedNativeDir);
|
|
}
|
|
|
|
for (const nativeDir of candidateDirs) {
|
|
const helperPath = join(nativeDir, "spawn-helper");
|
|
const nativeModulePath = join(nativeDir, "pty.node");
|
|
|
|
try {
|
|
fs.chmodSync(helperPath, 0o755);
|
|
} catch {
|
|
// Best-effort permission repair; helper may not exist in some layouts.
|
|
}
|
|
|
|
try {
|
|
fs.chmodSync(nativeModulePath, 0o755);
|
|
} catch (err) {
|
|
// Keep diagnostics for the native module path since missing/invalid perms
|
|
// here are more likely to prevent PTY startup.
|
|
log.warn("Failed to repair node-pty native permissions:", {
|
|
nativeDir,
|
|
error: err instanceof Error ? err.message : String(err),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Lazily load the node-pty module, repairing native permissions and (for
|
|
* Bun-compiled binaries) pre-loading the native module via dlopen. The loaded
|
|
* module is cached; a load failure is cached and re-thrown on subsequent calls.
|
|
*/
|
|
export async function loadPtyModule(): Promise<typeof import("node-pty")> {
|
|
ensureNodePtyNativePermissions();
|
|
|
|
if (ptyModule) {
|
|
return ptyModule;
|
|
}
|
|
|
|
if (ptyLoadError) {
|
|
throw ptyLoadError;
|
|
}
|
|
|
|
// For Bun-compiled binary, set up native paths before loading
|
|
if (isBunBinary) {
|
|
const nativeDir = findStagedNativeDir();
|
|
if (nativeDir) {
|
|
// Set spawn-helper directory
|
|
if (process.platform !== "win32") {
|
|
process.env.NODE_PTY_SPAWN_HELPER_DIR = nativeDir;
|
|
}
|
|
// Store reference for debugging
|
|
process.env.FUSION_NATIVE_ASSETS_PATH = nativeDir;
|
|
|
|
// Try to pre-load the native module using process.dlopen
|
|
// This can help when the normal require() path fails
|
|
const nativePath = join(nativeDir, "pty.node");
|
|
if (fs.existsSync(nativePath)) {
|
|
try {
|
|
const nativeModule: { exports?: unknown } = { exports: {} };
|
|
// process.dlopen is a Node internal API
|
|
process.dlopen(nativeModule, nativePath);
|
|
log.debug("Pre-loaded native module via dlopen");
|
|
} catch (dlopenErr) {
|
|
// dlopen failed - log but continue, normal import might still work
|
|
log.debug("dlopen pre-load failed (continuing):", dlopenErr);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
try {
|
|
// Standard import path - the native-patch setup should have created
|
|
// the necessary symlink structure for node-pty to find the module
|
|
const mod = await import("node-pty");
|
|
ptyModule = mod;
|
|
return ptyModule as typeof import("node-pty");
|
|
} catch (err) {
|
|
ptyLoadError = err instanceof Error ? err : new Error(String(err));
|
|
throw ptyLoadError;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reset the cached module / error state. Intended for tests that exercise the
|
|
* loader across multiple scenarios.
|
|
*/
|
|
export function resetPtyModuleCacheForTests(): void {
|
|
ptyModule = null;
|
|
ptyLoadError = null;
|
|
}
|