From 7d896b5f67c788ff5fefb7708de68dd4a99ba173 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 6 Jan 2026 06:01:11 +0000 Subject: [PATCH] fix: doctor memory hint --- CHANGELOG.md | 2 ++ src/cli/program.ts | 11 +++++-- src/commands/doctor.ts | 54 +++++++++++++++++++++++++++++++++- src/gateway/protocol/index.ts | 1 - src/types/proper-lockfile.d.ts | 29 ++++++++++++++++++ src/wizard/onboarding.ts | 14 --------- 6 files changed, 93 insertions(+), 18 deletions(-) create mode 100644 src/types/proper-lockfile.d.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 051868266..dcb1d0825 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ - Auto-reply: treat steer during compaction as a follow-up, queued until compaction completes. - Auth: lock auth profile refreshes to avoid multi-instance OAuth logouts; keep credentials on refresh failure. - Onboarding: prompt immediately for OpenAI Codex redirect URL on remote/headless logins. +- Doctor: suggest adding the workspace memory system when missing (opt-out via `--no-workspace-suggestions`). +- Build: fix duplicate protocol export, align Codex OAuth options, and add proper-lockfile typings. - Typing indicators: stop typing once the reply dispatcher drains to prevent stuck typing across Discord/Telegram/WhatsApp. - Typing indicators: fix a race that could keep the typing indicator stuck after quick replies. Thanks @thewilloftheshadow for PR #270. - Google: merge consecutive messages to satisfy strict role alternation for Google provider models. Thanks @Asleep123 for PR #266. diff --git a/src/cli/program.ts b/src/cli/program.ts index 0b46a84ab..7c7210a0b 100644 --- a/src/cli/program.ts +++ b/src/cli/program.ts @@ -289,9 +289,16 @@ export function buildProgram() { program .command("doctor") .description("Health checks + quick fixes for the gateway and providers") - .action(async () => { + .option( + "--no-workspace-suggestions", + "Disable workspace memory system suggestions", + false, + ) + .action(async (opts) => { try { - await doctorCommand(defaultRuntime); + await doctorCommand(defaultRuntime, { + workspaceSuggestions: opts.workspaceSuggestions, + }); } catch (err) { defaultRuntime.error(String(err)); defaultRuntime.exit(1); diff --git a/src/commands/doctor.ts b/src/commands/doctor.ts index 9c960ce2a..360344cda 100644 --- a/src/commands/doctor.ts +++ b/src/commands/doctor.ts @@ -9,6 +9,7 @@ import { DEFAULT_SANDBOX_COMMON_IMAGE, DEFAULT_SANDBOX_IMAGE, } from "../agents/sandbox.js"; +import { DEFAULT_AGENTS_FILENAME } from "../agents/workspace.js"; import { buildWorkspaceSkillStatus } from "../agents/skills-status.js"; import type { ClawdbotConfig } from "../config/config.js"; import { @@ -128,6 +129,45 @@ async function runSandboxScript( return true; } +type DoctorOptions = { + workspaceSuggestions?: boolean; +}; + +const MEMORY_SYSTEM_PROMPT = [ + "Memory system not found in workspace.", + "Paste this into your agent:", + "", + "Install the memory system by applying:", + "https://github.com/clawdbot/clawdbot/commit/9ffea23f31ca1df5183b25668f8f814bee0fb34e", + "https://github.com/clawdbot/clawdbot/commit/7d1fee70e76f2f634f1b41fca927ee663914183a", +].join("\n"); + +async function shouldSuggestMemorySystem(workspaceDir: string): Promise { + const memoryPaths = [ + path.join(workspaceDir, "MEMORY.md"), + path.join(workspaceDir, "memory.md"), + ]; + + for (const memoryPath of memoryPaths) { + try { + await fs.promises.access(memoryPath); + return false; + } catch { + // keep scanning + } + } + + const agentsPath = path.join(workspaceDir, DEFAULT_AGENTS_FILENAME); + try { + const content = await fs.promises.readFile(agentsPath, "utf-8"); + if (/memory\.md/i.test(content)) return false; + } catch { + // no AGENTS.md or unreadable; treat as missing memory guidance + } + + return true; +} + async function isDockerAvailable(): Promise { try { await runExec("docker", ["version", "--format", "{{.Server.Version}}"], { @@ -546,7 +586,10 @@ async function maybeMigrateLegacyGatewayService( }); } -export async function doctorCommand(runtime: RuntimeEnv = defaultRuntime) { +export async function doctorCommand( + runtime: RuntimeEnv = defaultRuntime, + options: DoctorOptions = {}, +) { printWizardHeader(runtime); intro("Clawdbot doctor"); @@ -694,5 +737,14 @@ export async function doctorCommand(runtime: RuntimeEnv = defaultRuntime) { await writeConfigFile(cfg); runtime.log(`Updated ${CONFIG_PATH_CLAWDBOT}`); + if (options.workspaceSuggestions !== false) { + const workspaceDir = resolveUserPath( + cfg.agent?.workspace ?? DEFAULT_WORKSPACE, + ); + if (await shouldSuggestMemorySystem(workspaceDir)) { + note(MEMORY_SYSTEM_PROMPT, "Workspace"); + } + } + outro("Doctor complete."); } diff --git a/src/gateway/protocol/index.ts b/src/gateway/protocol/index.ts index 16b0b2176..dd45750c3 100644 --- a/src/gateway/protocol/index.ts +++ b/src/gateway/protocol/index.ts @@ -394,5 +394,4 @@ export type { CronRunParams, CronRunsParams, CronRunLogEntry, - PollParams, }; diff --git a/src/types/proper-lockfile.d.ts b/src/types/proper-lockfile.d.ts new file mode 100644 index 000000000..b54b3ed70 --- /dev/null +++ b/src/types/proper-lockfile.d.ts @@ -0,0 +1,29 @@ +declare module "proper-lockfile" { + export type RetryOptions = { + retries?: number; + factor?: number; + minTimeout?: number; + maxTimeout?: number; + randomize?: boolean; + }; + + export type LockOptions = { + retries?: number | RetryOptions; + stale?: number; + update?: number; + realpath?: boolean; + }; + + export type ReleaseFn = () => Promise; + + export function lock( + path: string, + options?: LockOptions, + ): Promise; + + const lockfile: { + lock: typeof lock; + }; + + export default lockfile; +} diff --git a/src/wizard/onboarding.ts b/src/wizard/onboarding.ts index 724dfec01..35af38387 100644 --- a/src/wizard/onboarding.ts +++ b/src/wizard/onboarding.ts @@ -386,20 +386,6 @@ export async function runOnboardingWizard( }); return String(code); }, - onManualCodeInput: isRemote - ? () => { - if (!manualCodePromise) { - manualCodePromise = prompter - .text({ - message: "Paste the redirect URL (or authorization code)", - validate: (value) => - value?.trim() ? undefined : "Required", - }) - .then((value) => String(value)); - } - return manualCodePromise; - } - : undefined, onProgress: (msg) => spin.update(msg), }); spin.stop("OpenAI OAuth complete");