mirror of
https://github.com/cline/cline.git
synced 2026-10-25 10:26:36 +08:00
* fix(core): load embedded plugin and dashboard resources * fix(core): isolate subprocess Bun mode and retain system CA trust * fix(core): load compiled plugin resources directly from bunfs * test(desktop): compare native paths and tolerate Hub cleanup on Windows The standalone plugin test compared the child's process.execPath to realpathSync(cli), which keeps Windows 8.3 short names (RUNNER~1) while the child reports the long path. Use realpathSync.native. The dashboard test's cleanup could hit EBUSY while the stopped Hub released its cwd; retry it like the startup scenario cleanup does. Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com> * fix(core): package every declared SDK runtime export --------- Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com> Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
143 lines
4.8 KiB
TypeScript
Executable File
143 lines
4.8 KiB
TypeScript
Executable File
#!/usr/bin/env bun
|
|
|
|
import { isMainThread } from "node:worker_threads";
|
|
import { setPluginSandboxLauncher } from "@cline/core";
|
|
import {
|
|
buildTelemetrySelfcheckReport,
|
|
claimHubDaemonProcess,
|
|
claimSupervisedConnectorProcess,
|
|
createClineTelemetryServiceConfig,
|
|
disableCurrentDirectoryExecutableSearch,
|
|
disposeAll,
|
|
ensureLoopbackProxyBypass,
|
|
initVcr,
|
|
setConnectorCliLaunchSpec,
|
|
} from "@cline/shared";
|
|
import { logCliProcessError } from "./logging/errors";
|
|
import {
|
|
abortActiveRuntime,
|
|
cleanupActiveRuntime,
|
|
isAbortInProgress,
|
|
} from "./runtime/active-runtime";
|
|
import { registerClineClientIdentity } from "./utils/cline-client-identity";
|
|
import { resolveCliLaunchSpec } from "./utils/internal-launch";
|
|
import { writeErr } from "./utils/output";
|
|
|
|
// Initialize VCR before any HTTP requests are made.
|
|
// Set CLINE_VCR=record|playback and CLINE_VCR_CASSETTE=<path> to enable.
|
|
initVcr(process.env.CLINE_VCR);
|
|
|
|
// Before any personality below can spawn a child with the workspace as cwd.
|
|
disableCurrentDirectoryExecutableSearch();
|
|
|
|
// Before any personality probes the local hub over 127.0.0.1.
|
|
ensureLoopbackProxyBypass();
|
|
|
|
if (Bun.isStandaloneExecutable) {
|
|
setPluginSandboxLauncher({
|
|
command: process.execPath,
|
|
argsPrefix: ["--plugin-sandbox"],
|
|
});
|
|
}
|
|
|
|
if (!isMainThread) {
|
|
// Worker imports of the bundled CLI entrypoint should not start the CLI.
|
|
} else if (process.argv.includes("--plugin-sandbox")) {
|
|
// Boot as the compiled host so the child mounts the same read-only resources.
|
|
const bootstrap = process.argv[process.argv.indexOf("--plugin-sandbox") + 1];
|
|
if (!bootstrap) throw new Error("Missing plugin sandbox bootstrap path");
|
|
await import(bootstrap);
|
|
} else if (process.argv.includes("--telemetry-selfcheck")) {
|
|
// Inspect the config embedded in this executable without starting a hub,
|
|
// loading user settings, or initializing telemetry exporters.
|
|
process.stdout.write(
|
|
`${JSON.stringify(buildTelemetrySelfcheckReport(createClineTelemetryServiceConfig()))}\n`,
|
|
);
|
|
} else if (claimHubDaemonProcess()) {
|
|
// Claim rather than read: the sentinel is consumed here so the processes a
|
|
// daemon-hosted session spawns do not inherit it and try to become daemons.
|
|
// The hub daemon owns its process-level abort handling. Installing the CLI's
|
|
// fatal rejection handler first would make expected abort rejections exit it.
|
|
registerClineClientIdentity("cline-cli");
|
|
void import("@cline/core/hub/daemon-entry");
|
|
} else {
|
|
// Same reasoning as the daemon sentinel above: consume the supervised-connector
|
|
// marker so the processes an agent session spawns cannot inherit it and mistake
|
|
// themselves for the connector the hub is tracking.
|
|
claimSupervisedConnectorProcess();
|
|
|
|
const cliLaunchSpec = resolveCliLaunchSpec({ debugRole: "connector" });
|
|
if (cliLaunchSpec) {
|
|
setConnectorCliLaunchSpec({
|
|
launcher: cliLaunchSpec.launcher,
|
|
connectArgsPrefix: [...cliLaunchSpec.childArgsPrefix, "connect"],
|
|
cwd: process.cwd(),
|
|
});
|
|
}
|
|
|
|
let shuttingDown = false;
|
|
let handlingFatalProcessError = false;
|
|
const forwardSignalToRuntime = () => {
|
|
if (shuttingDown) {
|
|
process.exit(1);
|
|
}
|
|
shuttingDown = true;
|
|
abortActiveRuntime();
|
|
};
|
|
process.on("SIGINT", forwardSignalToRuntime);
|
|
process.on("SIGTERM", forwardSignalToRuntime);
|
|
const handleFatalProcessError = (kind: string, error: unknown) => {
|
|
if (handlingFatalProcessError) {
|
|
process.exit(1);
|
|
}
|
|
handlingFatalProcessError = true;
|
|
logCliProcessError(kind, error);
|
|
writeErr(
|
|
error instanceof Error ? (error.stack ?? error.message) : String(error),
|
|
);
|
|
cleanupActiveRuntime();
|
|
abortActiveRuntime();
|
|
void disposeAll().finally(() => {
|
|
process.exit(1);
|
|
});
|
|
};
|
|
process.on("uncaughtException", (error) => {
|
|
handleFatalProcessError("uncaughtException", error);
|
|
});
|
|
process.on("unhandledRejection", (reason, promise) => {
|
|
if (isAbortInProgress()) {
|
|
// Mark the promise as handled so OpenTUI's error overlay
|
|
// does not surface expected abort-related rejections.
|
|
promise.catch(() => {});
|
|
return;
|
|
}
|
|
handleFatalProcessError("unhandledRejection", reason);
|
|
});
|
|
|
|
void (async () => {
|
|
let exitCode = 0;
|
|
try {
|
|
const { runCli } = await import("./main");
|
|
await runCli();
|
|
} catch (err) {
|
|
logCliProcessError("runCli", err);
|
|
writeErr(err instanceof Error ? err.message : String(err));
|
|
cleanupActiveRuntime();
|
|
abortActiveRuntime();
|
|
exitCode = 1;
|
|
} finally {
|
|
await disposeAll();
|
|
}
|
|
// The explicit process.exit below means beforeExit never fires, so a
|
|
// startup-recorded auto-update must be applied here, after all runtime
|
|
// teardown. It spawns detached and only when no other CLI is attached.
|
|
try {
|
|
const { applyDeferredUpdate } = await import("./commands/update");
|
|
await applyDeferredUpdate();
|
|
} catch {
|
|
// Best-effort; never block exit on the updater.
|
|
}
|
|
process.exit(exitCode || (process.exitCode as number) || 0);
|
|
})();
|
|
}
|