refactor: use subsystem logger in workspace-sync handler

This commit is contained in:
Ash Brener 2026-01-28 10:48:28 +02:00
parent 0d8c321d45
commit 5bdcbc7e32
2 changed files with 35 additions and 26 deletions

View File

@ -1,8 +1,23 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import workspaceSyncHandler from "./handler.js";
import * as rclone from "../../../infra/rclone.js"; import * as rclone from "../../../infra/rclone.js";
const mockLog = vi.hoisted(() => ({
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
debug: vi.fn(),
trace: vi.fn(),
fatal: vi.fn(),
raw: vi.fn(),
child: vi.fn(),
subsystem: "workspace-sync",
}));
vi.mock("../../../logging/subsystem.js", () => ({
createSubsystemLogger: vi.fn(() => mockLog),
}));
vi.mock("../../../infra/rclone.js", () => ({ vi.mock("../../../infra/rclone.js", () => ({
isRcloneInstalled: vi.fn(), isRcloneInstalled: vi.fn(),
isRcloneConfigured: vi.fn(), isRcloneConfigured: vi.fn(),
@ -18,6 +33,9 @@ vi.mock("../../../routing/session-key.js", () => ({
resolveAgentIdFromSessionKey: vi.fn(() => "default"), resolveAgentIdFromSessionKey: vi.fn(() => "default"),
})); }));
// Import after mocks are set up
const { default: workspaceSyncHandler } = await import("./handler.js");
describe("workspace-sync hook handler", () => { describe("workspace-sync hook handler", () => {
beforeEach(() => { beforeEach(() => {
vi.clearAllMocks(); vi.clearAllMocks();
@ -126,7 +144,6 @@ describe("workspace-sync hook handler", () => {
it("warns when rclone is not installed", async () => { it("warns when rclone is not installed", async () => {
vi.mocked(rclone.isRcloneInstalled).mockResolvedValue(false); vi.mocked(rclone.isRcloneInstalled).mockResolvedValue(false);
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
const event = { const event = {
type: "session", type: "session",
@ -148,7 +165,7 @@ describe("workspace-sync hook handler", () => {
await workspaceSyncHandler(event as never); await workspaceSyncHandler(event as never);
expect(rclone.isRcloneInstalled).toHaveBeenCalled(); expect(rclone.isRcloneInstalled).toHaveBeenCalled();
expect(warnSpy).toHaveBeenCalledWith("[workspace-sync] rclone not installed, skipping sync"); expect(mockLog.warn).toHaveBeenCalledWith("rclone not installed, skipping sync");
}); });
it("warns when rclone is not configured", async () => { it("warns when rclone is not configured", async () => {
@ -166,7 +183,6 @@ describe("workspace-sync hook handler", () => {
onSessionStart: true, onSessionStart: true,
onSessionEnd: false, onSessionEnd: false,
}); });
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
const event = { const event = {
type: "session", type: "session",
@ -187,7 +203,7 @@ describe("workspace-sync hook handler", () => {
await workspaceSyncHandler(event as never); await workspaceSyncHandler(event as never);
expect(warnSpy).toHaveBeenCalledWith( expect(mockLog.warn).toHaveBeenCalledWith(
expect.stringContaining('rclone not configured for remote "cloud"'), expect.stringContaining('rclone not configured for remote "cloud"'),
); );
}); });
@ -208,7 +224,6 @@ describe("workspace-sync hook handler", () => {
onSessionEnd: false, onSessionEnd: false,
}); });
vi.mocked(rclone.runBisync).mockResolvedValue({ ok: true }); vi.mocked(rclone.runBisync).mockResolvedValue({ ok: true });
vi.spyOn(console, "log").mockImplementation(() => {});
const event = { const event = {
type: "session", type: "session",
@ -255,7 +270,6 @@ describe("workspace-sync hook handler", () => {
onSessionEnd: true, onSessionEnd: true,
}); });
vi.mocked(rclone.runBisync).mockResolvedValue({ ok: true }); vi.mocked(rclone.runBisync).mockResolvedValue({ ok: true });
vi.spyOn(console, "log").mockImplementation(() => {});
const event = { const event = {
type: "session", type: "session",
@ -298,8 +312,6 @@ describe("workspace-sync hook handler", () => {
ok: false, ok: false,
error: "bisync requires --resync on first run", error: "bisync requires --resync on first run",
}); });
vi.spyOn(console, "log").mockImplementation(() => {});
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
const event = { const event = {
type: "session", type: "session",
@ -320,8 +332,8 @@ describe("workspace-sync hook handler", () => {
await workspaceSyncHandler(event as never); await workspaceSyncHandler(event as never);
expect(warnSpy).toHaveBeenCalledWith( expect(mockLog.warn).toHaveBeenCalledWith(
expect.stringContaining("First sync requires manual --resync"), expect.stringContaining("first sync requires manual --resync"),
); );
}); });
}); });

View File

@ -8,6 +8,7 @@
import type { MoltbotConfig } from "../../../config/config.js"; import type { MoltbotConfig } from "../../../config/config.js";
import { resolveAgentWorkspaceDir } from "../../../agents/agent-scope.js"; import { resolveAgentWorkspaceDir } from "../../../agents/agent-scope.js";
import { resolveAgentIdFromSessionKey } from "../../../routing/session-key.js"; import { resolveAgentIdFromSessionKey } from "../../../routing/session-key.js";
import { createSubsystemLogger } from "../../../logging/subsystem.js";
import type { HookHandler } from "../../hooks.js"; import type { HookHandler } from "../../hooks.js";
import { import {
isRcloneInstalled, isRcloneInstalled,
@ -16,6 +17,8 @@ import {
runBisync, runBisync,
} from "../../../infra/rclone.js"; } from "../../../infra/rclone.js";
const log = createSubsystemLogger("workspace-sync");
/** /**
* Sync workspace on session start or end * Sync workspace on session start or end
*/ */
@ -55,13 +58,13 @@ const workspaceSyncHandler: HookHandler = async (event) => {
return; return;
} }
console.log(`[workspace-sync] Triggered on session ${event.action}`); log.info(`triggered on session ${event.action}`);
try { try {
// Check if rclone is installed // Check if rclone is installed
const installed = await isRcloneInstalled(); const installed = await isRcloneInstalled();
if (!installed) { if (!installed) {
console.warn("[workspace-sync] rclone not installed, skipping sync"); log.warn("rclone not installed, skipping sync");
return; return;
} }
@ -74,15 +77,11 @@ const workspaceSyncHandler: HookHandler = async (event) => {
// Check if rclone is configured // Check if rclone is configured
if (!isRcloneConfigured(resolved.configPath, resolved.remoteName)) { if (!isRcloneConfigured(resolved.configPath, resolved.remoteName)) {
console.warn( log.warn(`rclone not configured for remote "${resolved.remoteName}", skipping sync`);
`[workspace-sync] rclone not configured for remote "${resolved.remoteName}", skipping sync`,
);
return; return;
} }
console.log( log.info(`syncing ${resolved.remoteName}:${resolved.remotePath} <-> ${resolved.localPath}`);
`[workspace-sync] Syncing ${resolved.remoteName}:${resolved.remotePath} <-> ${resolved.localPath}`,
);
// Run sync // Run sync
const result = await runBisync({ const result = await runBisync({
@ -96,22 +95,20 @@ const workspaceSyncHandler: HookHandler = async (event) => {
}); });
if (result.ok) { if (result.ok) {
console.log("[workspace-sync] Sync completed successfully"); log.info("sync completed successfully");
if (result.filesTransferred) { if (result.filesTransferred) {
console.log(`[workspace-sync] Files transferred: ${result.filesTransferred}`); log.info(`files transferred: ${result.filesTransferred}`);
} }
} else { } else {
// Check if this is a first-run issue // Check if this is a first-run issue
if (result.error?.includes("--resync")) { if (result.error?.includes("--resync")) {
console.warn( log.warn("first sync requires manual --resync. Run: moltbot workspace sync --resync");
"[workspace-sync] First sync requires manual --resync. Run: moltbot workspace sync --resync",
);
} else { } else {
console.error(`[workspace-sync] Sync failed: ${result.error}`); log.error(`sync failed: ${result.error}`);
} }
} }
} catch (err) { } catch (err) {
console.error("[workspace-sync] Error:", err instanceof Error ? err.message : String(err)); log.error(`error: ${err instanceof Error ? err.message : String(err)}`);
} }
}; };