




















@@ -1,12 +1,13 @@
11import fs from "node:fs";
22import os from "node:os";
33import path from "node:path";
4-import { describe, expect, it } from "vitest";
4+import { afterEach, describe, expect, it, vi } from "vitest";
55import {
66resolveBundledRuntimeDependencyPackageInstallRoot,
77scanBundledPluginRuntimeDeps,
88type BundledRuntimeDepsInstallParams,
99} from "../plugins/bundled-runtime-deps.js";
10+import type { RuntimeEnv } from "../runtime.js";
1011import { maybeRepairBundledPluginRuntimeDeps } from "./doctor-bundled-plugin-runtime-deps.js";
1112import type { DoctorPrompter } from "./doctor-prompter.js";
1213@@ -84,7 +85,25 @@ function createNonInteractivePrompter(
8485} as DoctorPrompter;
8586}
868788+function createRuntime(options: { logs?: string[]; errors?: string[] } = {}): RuntimeEnv {
89+return {
90+log: (message: unknown) => {
91+options.logs?.push(String(message));
92+},
93+error: (message: unknown) => {
94+options.errors?.push(String(message));
95+},
96+exit: (code: number) => {
97+throw new Error(`Unexpected runtime exit ${code}`);
98+},
99+};
100+}
101+87102describe("doctor bundled plugin runtime deps", () => {
103+afterEach(() => {
104+vi.useRealTimers();
105+});
106+88107it("skips source checkouts", () => {
89108const root = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-doctor-bundled-"));
90109fs.mkdirSync(path.join(root, ".git"));
@@ -410,7 +429,7 @@ describe("doctor bundled plugin runtime deps", () => {
410429const installed = createInstalledRuntimeDeps();
411430412431await maybeRepairBundledPluginRuntimeDeps({
413-runtime: { error: () => {} } as never,
432+runtime: createRuntime(),
414433prompter: createNonInteractivePrompter(),
415434packageRoot: root,
416435config: {
@@ -441,7 +460,7 @@ describe("doctor bundled plugin runtime deps", () => {
441460const installed = createInstalledRuntimeDeps();
442461443462await maybeRepairBundledPluginRuntimeDeps({
444-runtime: { error: () => {} } as never,
463+runtime: createRuntime(),
445464prompter: createNonInteractivePrompter(),
446465packageRoot: root,
447466config: {
@@ -472,7 +491,7 @@ describe("doctor bundled plugin runtime deps", () => {
472491const installed = createInstalledRuntimeDeps();
473492474493await maybeRepairBundledPluginRuntimeDeps({
475-runtime: { error: () => {} } as never,
494+runtime: createRuntime(),
476495prompter: createNonInteractivePrompter(),
477496packageRoot: root,
478497config: {
@@ -496,14 +515,72 @@ describe("doctor bundled plugin runtime deps", () => {
496515expect(readRetainedRuntimeDepsManifest(installRoot)).toEqual(["grammy@1.37.0"]);
497516});
498517518+it("logs runtime dependency repair progress before and after install", async () => {
519+const root = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-doctor-bundled-"));
520+writeJson(path.join(root, "package.json"), { name: "openclaw" });
521+writeBundledChannelPlugin(root, "telegram", { grammy: "1.37.0" });
522+const logs: string[] = [];
523+524+await maybeRepairBundledPluginRuntimeDeps({
525+runtime: createRuntime({ logs }),
526+prompter: createNonInteractivePrompter(),
527+packageRoot: root,
528+config: {
529+plugins: { enabled: true },
530+channels: { telegram: { enabled: true } },
531+},
532+installDeps: async () => {},
533+});
534+535+expect(logs).toEqual(
536+expect.arrayContaining([
537+expect.stringContaining(
538+"Installing bundled plugin runtime deps (1 missing, 1 install specs): grammy@1.37.0",
539+),
540+expect.stringContaining("Installed bundled plugin runtime deps in"),
541+]),
542+);
543+});
544+545+it("logs runtime dependency repair heartbeats while install is pending", async () => {
546+vi.useFakeTimers();
547+const root = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-doctor-bundled-"));
548+writeJson(path.join(root, "package.json"), { name: "openclaw" });
549+writeBundledChannelPlugin(root, "telegram", { grammy: "1.37.0" });
550+const logs: string[] = [];
551+let finishInstall!: () => void;
552+553+const repair = maybeRepairBundledPluginRuntimeDeps({
554+runtime: createRuntime({ logs }),
555+prompter: createNonInteractivePrompter(),
556+packageRoot: root,
557+config: {
558+plugins: { enabled: true },
559+channels: { telegram: { enabled: true } },
560+},
561+installDeps: async () =>
562+await new Promise<void>((resolve) => {
563+finishInstall = resolve;
564+}),
565+});
566+567+await Promise.resolve();
568+expect(logs).toEqual([expect.stringContaining("Installing bundled plugin runtime deps")]);
569+await vi.advanceTimersByTimeAsync(15_000);
570+expect(logs).toContain("Still installing bundled plugin runtime deps after 15s...");
571+572+finishInstall();
573+await repair;
574+});
575+499576it("repairs deps for configured channel owner plugins", async () => {
500577const root = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-doctor-bundled-"));
501578writeJson(path.join(root, "package.json"), { name: "openclaw" });
502579writeBundledChannelOwnerPlugin(root, "chat-bridge", ["telegram"], { grammy: "1.37.0" });
503580const installed = createInstalledRuntimeDeps();
504581505582await maybeRepairBundledPluginRuntimeDeps({
506-runtime: { error: () => {} } as never,
583+runtime: createRuntime(),
507584prompter: createNonInteractivePrompter(),
508585packageRoot: root,
509586config: {
@@ -533,7 +610,7 @@ describe("doctor bundled plugin runtime deps", () => {
533610534611await expect(
535612maybeRepairBundledPluginRuntimeDeps({
536-runtime: { error: (message: string) => errors.push(message) } as never,
613+runtime: createRuntime({ errors }),
537614prompter: createNonInteractivePrompter(),
538615packageRoot: root,
539616config: {
@@ -558,7 +635,7 @@ describe("doctor bundled plugin runtime deps", () => {
558635const installed = createInstalledRuntimeDeps();
559636560637await maybeRepairBundledPluginRuntimeDeps({
561-runtime: { error: () => {} } as never,
638+runtime: createRuntime(),
562639prompter: createNonInteractivePrompter({ updateInProgress: true }),
563640packageRoot: root,
564641includeConfiguredChannels: true,
@@ -591,7 +668,7 @@ describe("doctor bundled plugin runtime deps", () => {
591668const installed = createInstalledRuntimeDeps();
592669593670await maybeRepairBundledPluginRuntimeDeps({
594-runtime: { error: () => {} } as never,
671+runtime: createRuntime(),
595672prompter: createNonInteractivePrompter(),
596673 env,
597674packageRoot: root,
@@ -641,7 +718,7 @@ describe("doctor bundled plugin runtime deps", () => {
641718const installed = createInstalledRuntimeDeps();
642719643720await maybeRepairBundledPluginRuntimeDeps({
644-runtime: { error: () => {} } as never,
721+runtime: createRuntime(),
645722prompter: createNonInteractivePrompter(),
646723 env,
647724packageRoot: root,
@@ -677,7 +754,7 @@ describe("doctor bundled plugin runtime deps", () => {
677754const installed = createInstalledRuntimeDeps();
678755679756await maybeRepairBundledPluginRuntimeDeps({
680-runtime: { error: () => {} } as never,
757+runtime: createRuntime(),
681758prompter: createNonInteractivePrompter(),
682759packageRoot: root,
683760includeConfiguredChannels: true,
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。