feat(heartbeat): custom evaluator prompt

This commit is contained in:
Arthur K.
2026-07-15 01:19:48 +08:00
committed by chengyongru
parent 88c38e9b38
commit dcb37259fa
7 changed files with 278 additions and 17 deletions
+68
View File
@@ -143,6 +143,14 @@ BUILTIN_COMMAND_SPECS: tuple[BuiltinCommandSpec, ...] = (
"[init]",
accepts_args=True,
),
BuiltinCommandSpec(
"/evaluator-prompt",
"Heartbeat evaluator",
"Customize the heartbeat notification gate prompt for this workspace.",
"file-text",
"[init]",
accepts_args=True,
),
BuiltinCommandSpec(
"/skill",
"List skills",
@@ -515,6 +523,64 @@ async def cmd_dream_prompt(ctx: CommandContext) -> OutboundMessage:
)
async def cmd_evaluator_prompt(ctx: CommandContext) -> OutboundMessage:
"""Show or set up the workspace heartbeat evaluator prompt."""
from nanobot.utils.evaluator import (
default_evaluator_prompt,
evaluator_prompt_file,
has_evaluator_prompt_override,
)
workspace = ctx.loop.context.memory.workspace
path = evaluator_prompt_file(workspace)
display_path = path.relative_to(workspace).as_posix()
args = ctx.args.strip().lower()
if args == "init":
try:
prompt_exists_with_content = path.exists() and (
not path.is_file() or bool(path.read_text(encoding="utf-8").strip())
)
except OSError:
prompt_exists_with_content = True
if prompt_exists_with_content:
content = (
f"Heartbeat evaluator prompt already exists at `{display_path}`.\n\n"
"Edit that file, or delete/empty it to return to nanobot's default."
)
else:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(default_evaluator_prompt() + "\n", encoding="utf-8")
content = (
f"Created heartbeat evaluator prompt at `{display_path}`.\n\n"
"Edit that file to control when the heartbeat notification gate speaks. "
"It must still instruct the model to call the `evaluate_notification` tool, "
"otherwise the gate fails closed and stays silent. "
"Delete or empty it to return to nanobot's default."
)
elif args:
content = "Usage: /evaluator-prompt [init]"
elif has_evaluator_prompt_override(workspace):
content = (
"Heartbeat evaluator prompt: custom for this workspace\n\n"
f"- Path: `{display_path}`\n"
"- Delete or empty this file to return to nanobot's default."
)
else:
content = (
"Heartbeat evaluator prompt: nanobot default\n\n"
f"- Editable file: `{display_path}`\n"
"- Run `/evaluator-prompt init` to create an editable copy."
)
return OutboundMessage(
channel=ctx.msg.channel,
chat_id=ctx.msg.chat_id,
content=content,
metadata={**dict(ctx.msg.metadata or {}), "render_as": "text"},
)
def _format_dream_no_input_message() -> str:
return "\n".join([
"Dream has no conversation history to process yet.",
@@ -954,6 +1020,8 @@ def register_builtin_commands(router: CommandRouter) -> None:
router.prefix("/dream-restore ", cmd_dream_restore)
router.exact("/dream-prompt", cmd_dream_prompt)
router.prefix("/dream-prompt ", cmd_dream_prompt)
router.exact("/evaluator-prompt", cmd_evaluator_prompt)
router.prefix("/evaluator-prompt ", cmd_evaluator_prompt)
router.exact("/skill", cmd_skill)
router.exact("/help", cmd_help)
router.exact("/pairing", cmd_pairing)