init: story-skill - standardized novel continuation agent workflow

- Agent identity + mandatory workflow (AGENTS.md)
- Style continuation skill (SKILL.md)
- 5 reference file templates (characters/world_building/timeline/style/plot_state)
- Batch reading protocol with sequential sub-agents + spot-check review
- Bridge script for Gateway API calls with auto novel copy
- METHODOLOGY.md explaining the three pillars
- README.md with installation guide
This commit is contained in:
root
2026-05-31 17:45:01 +08:00
commit cf8f68533c
11 changed files with 1003 additions and 0 deletions

50
bridge/SKILL.md Normal file
View File

@ -0,0 +1,50 @@
---
name: novelist-bridge
description: 调用小说家 agent 进行小说风格续写。自动复制小说原文到 novelist 工作区。
---
# Novelist Bridge
通过 Gateway API 同步调用 `novelist` agent 进行小说创作。
**自动将小说原文复制到 novelist 工作区**,让 novelist 能在本地直接读取原文正文,而不是仅靠标题推断。
## 脚本位置
`scripts/ask_novelist.py`
## 用法
```bash
# 基本用法(自动查找默认小说文件)
python scripts/ask_novelist.py "续写请求内容"
# 指定小说文件路径
python scripts/ask_novelist.py --novel /path/to/novel.txt "续写请求内容"
# 从 stdin 读取
echo "续写内容" | python scripts/ask_novelist.py
```
## 配置
修改 `ask_novelist.py` 顶部的常量:
| 变量 | 说明 |
|------|------|
| `GATEWAY_URL` | Gateway API 地址 |
| `GATEWAY_TOKEN` | Gateway API Token |
| `NOVELIST_MODEL` | novelist agent model ID |
| `NOVELIST_WORKSPACE` | novelist 工作区路径 |
| `NOVEL_TARGET_NAME` | 复制到工作区后的文件名 |
## 调用链路
```
caller agent → ask_novelist.py → 复制小说到 novelist 工作区 → Gateway API → novelist agent → 回复
```
## 注意事项
- 超时默认 180 秒
- 如果 novelist 正在忙,会排队等待
- 脚本自动跳过已存在的相同文件(按文件大小比对),减少磁盘写操作

127
bridge/ask_novelist.py Normal file
View File

@ -0,0 +1,127 @@
#!/usr/bin/env python3
"""
Bridge script: caller agent → novelist agent (via Gateway API)
Automatically copies the novel source text to the novelist's workspace so that
the novelist agent can read the actual body text locally, not just chapter titles.
Then sends the request via Gateway API synchronously.
Usage:
python ask_novelist.py "continuation request"
python ask_novelist.py --novel /path/to/novel.txt "continuation request"
cat content.txt | python ask_novelist.py
"""
import sys
import os
import shutil
import json
import requests
# Config — customize these for your setup
GATEWAY_URL = "http://127.0.0.1:18789/v1/chat/completions"
GATEWAY_TOKEN = "<your-gateway-token>"
NOVELIST_MODEL = "openclaw/novelist"
NOVELIST_WORKSPACE = os.environ.get("NOVELIST_WORKSPACE", "/home/program/novelist-workspace")
NOVEL_TARGET_NAME = "novel_source.txt"
def ensure_novel_in_workspace(novel_path: str = None) -> str | None:
"""Copy the novel file to the novelist's workspace for local reading."""
if not novel_path or not os.path.isfile(novel_path):
# Try common default paths
candidates = [
"/home/program/wechat-workspace/穿越未来之男人不好当.txt",
"./novel.txt",
os.path.expanduser("~/novel.txt"),
]
for c in candidates:
if os.path.isfile(c):
novel_path = c
break
else:
return None
target = os.path.join(NOVELIST_WORKSPACE, NOVEL_TARGET_NAME)
try:
if not os.path.isfile(target) or os.path.getsize(target) != os.path.getsize(novel_path):
shutil.copy2(novel_path, target)
print(f"[桥接] 已复制小说到 {target}", file=sys.stderr)
return target
except Exception as e:
print(f"[警告] 复制小说文件失败: {e}", file=sys.stderr)
return None
def ask(question: str, timeout: int = 180) -> str:
"""Synchronous Gateway API call to novelist."""
payload = {
"model": NOVELIST_MODEL,
"messages": [{"role": "user", "content": question}],
"stream": False,
}
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {GATEWAY_TOKEN}",
}
try:
resp = requests.post(GATEWAY_URL, json=payload, headers=headers, timeout=timeout)
if resp.status_code == 200:
data = resp.json()
choices = data.get("choices", [])
if choices:
return choices[0].get("message", {}).get("content", "")
return "[novelist] 返回为空"
return f"[novelist] 请求失败({resp.status_code}): {resp.text[:200]}"
except requests.exceptions.Timeout:
return "[novelist] 超时,请稍后重试"
except requests.exceptions.ConnectionError:
return "[novelist] 连接失败,请检查 Gateway 服务状态"
except Exception as e:
return f"[novelist] 请求异常: {str(e)}"
def build_prompt(novel_path: str | None, user_question: str) -> str:
"""Build the prompt sent to novelist. Includes novel file path hint."""
if novel_path:
return (
f"小说原文已放在你的工作区,路径为:{novel_path}\n\n"
f"请先阅读原文正文(不要只看标题),完成要求。\n\n"
f"要求如下:\n{user_question}"
)
return user_question
def main():
novel_path = None
args = sys.argv[1:]
if args and args[0] == "--novel":
if len(args) >= 2:
novel_path = args[1]
question = " ".join(args[2:]) if len(args) > 2 else ""
else:
print("错误:--novel 需要提供文件路径", file=sys.stderr)
sys.exit(1)
elif args:
question = " ".join(args)
if not question:
question = sys.stdin.read().strip()
if not question:
print("错误:未提供请求内容", file=sys.stderr)
sys.exit(1)
copied_path = ensure_novel_in_workspace(novel_path)
if copied_path:
novel_file = os.path.join(NOVELIST_WORKSPACE, NOVEL_TARGET_NAME)
else:
novel_file = None
prompt = build_prompt(novel_file, question)
result = ask(prompt)
print(result)
if __name__ == "__main__":
main()