dsh-novel 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +149 -0
  3. package/cordis.patch.yml +3 -0
  4. package/lib/client.js +1560 -0
  5. package/lib/client.js.map +1 -0
  6. package/lib/index.js +1436 -0
  7. package/lib/index.js.map +1 -0
  8. package/lib/types/client/NovelWorkspaceView.d.ts +3 -0
  9. package/lib/types/client/entity-client.d.ts +17 -0
  10. package/lib/types/client/index.d.ts +3 -0
  11. package/lib/types/client/noveel-shapes.d.ts +1 -0
  12. package/lib/types/client/novel-api.d.ts +1 -0
  13. package/lib/types/client/section-icons.d.ts +5 -0
  14. package/lib/types/client/sections/ChapterSection.d.ts +2 -0
  15. package/lib/types/client/sections/LevelSection.d.ts +2 -0
  16. package/lib/types/client/sections/ListDetailSection.d.ts +13 -0
  17. package/lib/types/client/sections/OutlineSection.d.ts +2 -0
  18. package/lib/types/client/sections/OverviewSection.d.ts +2 -0
  19. package/lib/types/client/sections/SkillSection.d.ts +3 -0
  20. package/lib/types/client/sections/TimelineSection.d.ts +2 -0
  21. package/lib/types/client/sections/WorldSection.d.ts +2 -0
  22. package/lib/types/client/sections/owned-sections.d.ts +4 -0
  23. package/lib/types/client/sections/types.d.ts +14 -0
  24. package/lib/types/client/styles.d.ts +1 -0
  25. package/lib/types/client/ui/EntitySidebar.d.ts +10 -0
  26. package/lib/types/client/ui/RelationPanel.d.ts +6 -0
  27. package/lib/types/client/ui/TemplateRead.d.ts +13 -0
  28. package/lib/types/client/ui/TimelinePanel.d.ts +8 -0
  29. package/lib/types/client/ui/controls.d.ts +22 -0
  30. package/lib/types/client/ui/live-reload.d.ts +2 -0
  31. package/lib/types/index.d.ts +5 -0
  32. package/lib/types/novel/contract.d.ts +2 -0
  33. package/lib/types/novel/defaults.d.ts +66 -0
  34. package/lib/types/novel/domains.d.ts +12 -0
  35. package/lib/types/novel/host-skills.d.ts +58 -0
  36. package/lib/types/novel/http.d.ts +5 -0
  37. package/lib/types/novel/normalize-entity.d.ts +18 -0
  38. package/lib/types/novel/noveel-shapes.d.ts +98 -0
  39. package/lib/types/novel/outline-labels.d.ts +10 -0
  40. package/lib/types/novel/schema.d.ts +3 -0
  41. package/lib/types/novel/seed.d.ts +27 -0
  42. package/lib/types/novel/store.d.ts +96 -0
  43. package/lib/types/novel/template-fields.d.ts +17 -0
  44. package/lib/types/novel/timeline-labels.d.ts +2 -0
  45. package/lib/types/novel/tools.d.ts +3 -0
  46. package/package.json +77 -0
  47. package/seed/skills/chapter//345/216/273AI/345/221/263/344/270/216/350/264/250/346/243/200.md +34 -0
  48. package/seed/skills/chapter//345/257/271/350/257/235/344/270/216/346/203/205/347/273/252.md +52 -0
  49. package/seed/skills/chapter//347/253/240/350/212/202/346/225/260/346/215/256/347/273/264/346/212/244.md +61 -0
  50. package/seed/skills/chapter//347/253/240/350/212/202/350/212/202/345/245/217/344/270/216/345/274/200/345/244/264.md +61 -0
  51. package/seed/skills/chapter//347/253/240/351/246/226/351/222/251/345/255/220.md +40 -0
  52. package/seed/skills/creature//345/274/202/345/205/275/344/270/216/347/201/265/346/244/215.md +11 -0
  53. package/seed/skills/creature//347/224/237/347/211/251/346/241/243/346/241/210/350/246/201/347/264/240.md +13 -0
  54. package/seed/skills/genres//344/273/231/344/276/240/overview//351/242/230/346/235/220/350/247/204/345/210/231.md +32 -0
  55. package/seed/skills/genres//344/273/231/344/276/240/review/outline//345/244/247/347/272/262/345/256/241/346/237/245.md +21 -0
  56. package/seed/skills/genres//345/216/206/345/217/262/overview//351/242/230/346/235/220/350/247/204/345/210/231.md +32 -0
  57. package/seed/skills/genres//345/216/206/345/217/262/review/outline//345/244/247/347/272/262/345/256/241/346/237/245.md +21 -0
  58. package/seed/skills/genres//346/202/254/347/226/221/overview//351/242/230/346/235/220/350/247/204/345/210/231.md +33 -0
  59. package/seed/skills/genres//346/202/254/347/226/221/review/outline//345/244/247/347/272/262/345/256/241/346/237/245.md +21 -0
  60. package/seed/skills/genres//346/234/253/344/270/226/overview//351/242/230/346/235/220/350/247/204/345/210/231.md +33 -0
  61. package/seed/skills/genres//346/234/253/344/270/226/review/outline//345/244/247/347/272/262/345/256/241/346/237/245.md +21 -0
  62. package/seed/skills/genres//347/216/204/345/271/273/overview//351/242/230/346/235/220/350/247/204/345/210/231.md +32 -0
  63. package/seed/skills/genres//347/216/204/345/271/273/review/outline//345/244/247/347/272/262/345/256/241/346/237/245.md +22 -0
  64. package/seed/skills/genres//347/247/221/345/271/273/overview//351/242/230/346/235/220/350/247/204/345/210/231.md +33 -0
  65. package/seed/skills/genres//347/247/221/345/271/273/review/outline//345/244/247/347/272/262/345/256/241/346/237/245.md +21 -0
  66. package/seed/skills/genres//350/250/200/346/203/205/overview//351/242/230/346/235/220/350/247/204/345/210/231.md +31 -0
  67. package/seed/skills/genres//350/250/200/346/203/205/review/outline//345/244/247/347/272/262/345/256/241/346/237/245.md +21 -0
  68. package/seed/skills/genres//351/203/275/345/270/202/overview//351/242/230/346/235/220/350/247/204/345/210/231.md +32 -0
  69. package/seed/skills/genres//351/203/275/345/270/202/review/outline//345/244/247/347/272/262/345/256/241/346/237/245.md +21 -0
  70. package/seed/skills/item//346/265/201/350/275/254/344/270/216/345/275/222/345/261/236.md +22 -0
  71. package/seed/skills/item//347/211/251/345/223/201/346/241/243/346/241/210/350/246/201/347/264/240.md +13 -0
  72. package/seed/skills/level//346/210/220/351/225/277/344/270/216/347/210/275/347/202/271.md +44 -0
  73. package/seed/skills/level//350/203/275/345/212/233/344/270/216/344/273/243/344/273/267.md +42 -0
  74. package/seed/skills/outline//345/211/247/346/203/205/351/252/250/346/236/266/346/265/201/346/260/264/347/272/277.md +51 -0
  75. package/seed/skills/outline//347/273/223/346/236/204/344/270/216/345/215/267/347/253/240/346/250/241/346/235/277.md +68 -0
  76. package/seed/skills/outline//351/273/204/351/207/221/344/270/211/347/253/240/344/270/216/351/252/214/346/224/266.md +42 -0
  77. package/seed/skills/overview//345/215/226/347/202/271/344/270/216/345/256/232/344/275/215.md +52 -0
  78. package/seed/skills/overview//345/274/200/344/271/246/347/247/215/345/255/220.md +60 -0
  79. package/seed/skills/overview//346/240/207/351/242/230/345/210/233/344/275/234.md +43 -0
  80. package/seed/skills/review/chapter//347/253/240/350/212/202/345/256/241/346/237/245.md +72 -0
  81. package/seed/skills/review/outline//345/244/247/347/272/262/345/256/241/346/237/245.md +74 -0
  82. package/seed/skills/role//344/272/272/347/211/251/345/241/221/351/200/240.md +52 -0
  83. package/seed/skills/role//345/205/263/347/263/273/344/270/216/351/205/215/350/247/222.md +53 -0
  84. package/seed/skills/role//350/247/222/350/211/262/346/241/243/346/241/210.md +52 -0
  85. package/seed/skills/timeline//344/272/213/344/273/266/350/275/264/350/247/204/345/210/222.md +33 -0
  86. package/seed/skills/timeline//347/212/266/346/200/201/345/220/214/346/255/245.md +41 -0
  87. package/seed/skills/world//344/270/226/347/225/214/350/247/204/345/210/231.md +30 -0
  88. package/seed/skills/world//344/274/217/347/254/224/350/264/246/346/234/254.md +44 -0
  89. package/seed/skills/world//345/212/277/345/212/233/344/270/216/345/234/260/345/233/276.md +52 -0
  90. package/seed/skills/writing-style/da-feng-da-geng-ren/STYLE.md +132 -0
  91. package/seed/skills/writing-style/da-feng-da-geng-ren/meta.json +6 -0
  92. package/seed/skills/writing-style/hei-dao-da-feng/STYLE.md +144 -0
  93. package/seed/skills/writing-style/hei-dao-da-feng/meta.json +6 -0
  94. package/seed/skills/writing-style/hei-dao-xie-huang/STYLE.md +139 -0
  95. package/seed/skills/writing-style/hei-dao-xie-huang/meta.json +6 -0
  96. package/seed/templates//344/273/231/344/276/240.json +1322 -0
  97. package/seed/templates//345/216/206/345/217/262.json +1318 -0
  98. package/seed/templates//346/202/254/347/226/221.json +1318 -0
  99. package/seed/templates//346/234/253/344/270/226.json +1127 -0
  100. package/seed/templates//347/216/204/345/271/273.json +1322 -0
  101. package/seed/templates//347/247/221/345/271/273.json +1322 -0
  102. package/seed/templates//350/250/200/346/203/205.json +1318 -0
  103. package/seed/templates//351/203/275/345/270/202.json +1318 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/novel/contract.ts","../src/novel/domains.ts","../src/novel/schema.ts","../src/novel/defaults.ts","../src/novel/template-fields.ts","../src/novel/seed.ts","../src/novel/outline-labels.ts","../src/novel/normalize-entity.ts","../src/novel/store.ts","../src/novel/http.ts","../src/novel/host-skills.ts","../src/novel/tools.ts","../src/index.ts"],"sourcesContent":["/** Same-origin HTTP prefix for the novel Host API. */\nexport const NOVEL_API_PREFIX = '/dsh-novel/api'\n","/** Entity domains aligned with Noveel registry names. */\nexport const ENTITY_DOMAINS = [\n 'role',\n 'roleRelation',\n 'roleTimeline',\n 'creature',\n 'creatureTimeline',\n 'item',\n 'itemTimeline',\n 'level',\n 'world',\n 'worldTimeline',\n 'timeline',\n 'outline',\n 'chapter',\n] as const\n\nexport type EntityDomain = (typeof ENTITY_DOMAINS)[number]\n\nexport const DOMAIN_LABELS: Record<EntityDomain | 'overview' | 'skill' | 'info', string> = {\n info: '基本信息',\n overview: '概述',\n world: '世界',\n role: '人物',\n creature: '生物',\n item: '物品',\n level: '等级',\n timeline: '时间线',\n outline: '大纲',\n chapter: '章节',\n skill: '技能',\n roleRelation: '人物关系',\n roleTimeline: '人物时间线',\n creatureTimeline: '生物时间线',\n itemTimeline: '物品时间线',\n worldTimeline: '世界时间线',\n}\n\n/** Conversation「内容」Tab 内的竖向分区;基本信息并入概述。模板为内置,不展示。 */\nexport const CONTENT_SECTIONS = [\n 'overview',\n 'world',\n 'role',\n 'creature',\n 'item',\n 'level',\n 'timeline',\n 'outline',\n 'chapter',\n 'skill',\n] as const\n\nexport type ContentSection = (typeof CONTENT_SECTIONS)[number]\n\nexport const TREE_SECTIONS = new Set<ContentSection>(['world', 'outline'])\n\nexport const LIST_SECTIONS = new Set<ContentSection>([\n 'world', 'role', 'creature', 'item', 'level', 'timeline', 'outline', 'chapter', 'skill',\n])\n\nexport const PANEL_TABS = CONTENT_SECTIONS\n\nexport type PanelTab = ContentSection\n\nexport function isEntityDomain(value: string): value is EntityDomain {\n return (ENTITY_DOMAINS as readonly string[]).includes(value)\n}\n","import type { PGlite } from '@electric-sql/pglite'\n\n/** Create the single-novel schema used inside one workspace PGlite directory. */\nexport async function initNovelSchema(db: PGlite): Promise<void> {\n await db.exec(`\n CREATE TABLE IF NOT EXISTS novels (\n id TEXT PRIMARY KEY,\n title TEXT NOT NULL,\n description TEXT,\n cover TEXT,\n category TEXT NOT NULL DEFAULT '玄幻',\n writing_style_id TEXT,\n chapter_count INTEGER NOT NULL DEFAULT 0,\n word_count INTEGER NOT NULL DEFAULT 0,\n blueprint_json JSONB NOT NULL DEFAULT '{}'::jsonb,\n outline_draft_md TEXT,\n outline_final_md TEXT,\n outline_status TEXT NOT NULL DEFAULT 'none',\n created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()\n );\n\n CREATE TABLE IF NOT EXISTS writing_styles (\n id TEXT PRIMARY KEY,\n slug TEXT NOT NULL UNIQUE,\n name TEXT NOT NULL,\n summary TEXT NOT NULL DEFAULT '',\n content TEXT NOT NULL DEFAULT '',\n origin TEXT NOT NULL DEFAULT 'user',\n created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()\n );\n\n CREATE TABLE IF NOT EXISTS form_templates (\n id TEXT PRIMARY KEY,\n novel_id TEXT NOT NULL UNIQUE,\n config JSONB NOT NULL DEFAULT '{}'::jsonb,\n created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()\n );\n\n CREATE TABLE IF NOT EXISTS novel_entities (\n domain TEXT NOT NULL,\n id TEXT PRIMARY KEY,\n name TEXT,\n data JSONB NOT NULL DEFAULT '{}'::jsonb,\n created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()\n );\n CREATE INDEX IF NOT EXISTS idx_novel_entities_domain ON novel_entities (domain);\n CREATE INDEX IF NOT EXISTS idx_novel_entities_name ON novel_entities (domain, name);\n\n CREATE TABLE IF NOT EXISTS novel_skills (\n id TEXT PRIMARY KEY,\n section TEXT NOT NULL,\n folder TEXT NOT NULL DEFAULT '',\n agent_kind TEXT NOT NULL DEFAULT 'write',\n title TEXT NOT NULL,\n body TEXT NOT NULL DEFAULT '',\n created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()\n );\n ALTER TABLE novel_skills ADD COLUMN IF NOT EXISTS folder TEXT NOT NULL DEFAULT '';\n UPDATE novel_skills SET folder = CASE\n WHEN folder <> '' THEN folder\n WHEN agent_kind = 'review' AND section IN ('outline') THEN '审查/大纲'\n WHEN agent_kind = 'review' AND section IN ('chapters', 'chapter') THEN '审查/章节'\n WHEN section = 'overview' THEN '概述'\n WHEN section = 'role' THEN '人物'\n WHEN section = 'creature' THEN '生物'\n WHEN section = 'item' THEN '物品'\n WHEN section = 'level' THEN '等级'\n WHEN section = 'world' THEN '世界'\n WHEN section = 'timeline' THEN '时间线'\n WHEN section = 'outline' THEN '大纲'\n WHEN section IN ('chapter', 'chapters') THEN '章节'\n ELSE '其他'\n END\n WHERE folder = '';\n\n\n CREATE TABLE IF NOT EXISTS novel_chapter_change_batches (\n id TEXT PRIMARY KEY,\n chapter_id TEXT NOT NULL,\n summary TEXT NOT NULL DEFAULT '',\n ops_json JSONB NOT NULL DEFAULT '[]'::jsonb,\n created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n rolled_back_at TIMESTAMPTZ\n );\n `)\n await db.exec(`\n DELETE FROM novel_skills WHERE folder = '题材' OR folder LIKE '题材/%'\n `)\n}\n","/** Minimal form template so a new workspace is immediately writable. */\nexport const DEFAULT_FORM_TEMPLATE = {\n version: 1,\n overview: {\n fields: [\n { key: 'genre', label: '小说类型', component: 'text' },\n { key: 'tone', label: '基调', component: 'text' },\n { key: 'coreConflict', label: '核心冲突', component: 'textarea' },\n { key: 'logline', label: '一句话故事', component: 'textarea' },\n ],\n },\n role: { detailFields: [{ key: 'summary', label: '简介', component: 'textarea' }] },\n creature: { detailFields: [{ key: 'summary', label: '简介', component: 'textarea' }] },\n item: { detailFields: [{ key: 'summary', label: '简介', component: 'textarea' }] },\n level: { detailFields: [{ key: 'summary', label: '体系说明', component: 'textarea' }] },\n world: { detailFields: [{ key: 'summary', label: '说明', component: 'textarea' }] },\n timeline: { detailFields: [{ key: 'content', label: '内容', component: 'textarea' }] },\n outline: { detailFields: [{ key: 'summary', label: '说明', component: 'textarea' }] },\n}\n\nexport const DEFAULT_SKILLS = [\n {\n section: 'overview',\n agentKind: 'write',\n title: '写设定',\n body: '根据用户意图更新概述蓝图,保持与已有世界/人物一致。',\n },\n {\n section: 'chapters',\n agentKind: 'write',\n title: '写章节',\n body: '按大纲写正文,不要改动未点名的设定。写完后用 mutate 工具落库 chapter。',\n },\n {\n section: 'chapters',\n agentKind: 'review',\n title: '审章节',\n body: '检查人物口吻、时间线与已有设定是否冲突。',\n },\n]\n","import type { ContentSection } from './domains.ts'\n\nexport const TEMPLATE_PRESET_MAIN_CATEGORIES = [\n '玄幻',\n '都市',\n '科幻',\n '末世',\n '仙侠',\n '言情',\n '悬疑',\n '历史',\n] as const\n\nexport type TemplatePresetMainCategory = (typeof TEMPLATE_PRESET_MAIN_CATEGORIES)[number]\n\nconst CATEGORY_ALIASES: Record<string, TemplatePresetMainCategory> = {\n 玄幻: '玄幻',\n 东方玄幻: '玄幻',\n 异世大陆: '玄幻',\n 高武世界: '玄幻',\n 都市: '都市',\n 都市异能: '都市',\n 科幻: '科幻',\n 星际: '科幻',\n 末世: '末世',\n 末日: '末世',\n 仙侠: '仙侠',\n 修真: '仙侠',\n 言情: '言情',\n 悬疑: '悬疑',\n 推理: '悬疑',\n 历史: '历史',\n}\n\nexport function normalizeTemplatePresetCategory(raw: string | null | undefined): TemplatePresetMainCategory {\n const t = (raw ?? '').trim()\n if (t.length === 0) return '玄幻'\n const direct = CATEGORY_ALIASES[t]\n if (direct !== undefined) return direct\n for (const main of TEMPLATE_PRESET_MAIN_CATEGORIES) {\n if (t.includes(main)) return main\n }\n return '玄幻'\n}\n\nexport interface FormField {\n key: string\n label: string\n component?: string\n options?: Array<{ label: string, value: string }>\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value)\n ? value as Record<string, unknown>\n : {}\n}\n\nfunction asFields(raw: unknown): FormField[] {\n if (!Array.isArray(raw)) return []\n return raw.flatMap((row): FormField[] => {\n if (row === null || typeof row !== 'object') return []\n const rec = row as Record<string, unknown>\n if (typeof rec.key !== 'string' || typeof rec.label !== 'string') return []\n const options = flattenOptions(rec.options)\n const field: FormField = { key: rec.key, label: rec.label }\n if (typeof rec.component === 'string') field.component = rec.component\n if (options.length > 0) field.options = options\n return [field]\n })\n}\n\nfunction flattenOptions(raw: unknown): Array<{ label: string, value: string }> {\n if (!Array.isArray(raw)) return []\n const out: Array<{ label: string, value: string }> = []\n for (const entry of raw) {\n if (entry === null || typeof entry !== 'object') continue\n const rec = entry as Record<string, unknown>\n if (typeof rec.value === 'string' && typeof rec.label === 'string') {\n out.push({ label: rec.label, value: rec.value })\n continue\n }\n if (Array.isArray(rec.options)) {\n for (const nested of rec.options) {\n if (nested === null || typeof nested !== 'object') continue\n const item = nested as Record<string, unknown>\n if (typeof item.value === 'string' && typeof item.label === 'string') {\n out.push({ label: item.label, value: item.value })\n }\n }\n }\n }\n return out\n}\n\n/** Pick human-editable fields from a Noveel form_templates.config blob. */\nexport function fieldsOf(template: unknown, section: ContentSection, extra?: 'chapterSegment' | 'timeline'): FormField[] {\n const root = asRecord(template)\n const block = asRecord(root[section])\n switch (section) {\n case 'overview':\n return asFields(block.fields)\n case 'role':\n case 'creature':\n case 'item':\n return extra === 'timeline' ? asFields(asRecord(block.timeline).extraFields) : asFields(block.detailFields)\n case 'world':\n return extra === 'timeline'\n ? asFields(asRecord(block.timeline).extraFields)\n : asFields(block.defaultDetailFields)\n case 'level':\n return asFields(block.levelFields)\n case 'timeline':\n return asFields(block.extraFields)\n case 'outline':\n return extra === 'chapterSegment' ? asFields(block.chapterSegmentFields) : asFields(block.volumeFields)\n default:\n return asFields(block.fields ?? block.detailFields)\n }\n}\n\nexport function getByPath(obj: Record<string, unknown>, path: string): unknown {\n const parts = path.split('.').filter(Boolean)\n let cur: unknown = obj\n for (const part of parts) {\n if (cur == null || typeof cur !== 'object' || Array.isArray(cur)) return undefined\n cur = (cur as Record<string, unknown>)[part]\n }\n return cur\n}\n\nexport function setByPath(obj: Record<string, unknown>, path: string, value: unknown): Record<string, unknown> {\n const next = structuredClone(obj)\n const parts = path.split('.').filter(Boolean)\n if (parts.length === 0) return next\n let cur: Record<string, unknown> = next\n for (let i = 0; i < parts.length - 1; i++) {\n const part = parts[i]!\n const child = cur[part]\n if (child == null || typeof child !== 'object' || Array.isArray(child)) cur[part] = {}\n cur = cur[part] as Record<string, unknown>\n }\n cur[parts[parts.length - 1]!] = value\n return next\n}\n","import { createHash, randomUUID } from 'node:crypto'\nimport { existsSync, readdirSync, readFileSync } from 'node:fs'\nimport { basename, dirname, join } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport type { PGlite } from '@electric-sql/pglite'\nimport { normalizeTemplatePresetCategory } from './template-fields.ts'\n\nfunction packageRoot(): string {\n const here = dirname(fileURLToPath(import.meta.url))\n if (basename(here) === 'novel') return join(here, '..', '..')\n return join(here, '..')\n}\n\nexport function seedTemplatesDir(): string {\n return join(packageRoot(), 'seed', 'templates')\n}\n\nexport function seedSkillsDir(): string {\n return join(packageRoot(), 'seed', 'skills')\n}\n\nexport function loadRecommendedTemplate(category?: string | null): unknown {\n const main = normalizeTemplatePresetCategory(category)\n const primary = join(seedTemplatesDir(), `${main}.json`)\n const fallback = join(seedTemplatesDir(), '玄幻.json')\n const path = existsSync(primary) ? primary : fallback\n if (!existsSync(path)) throw new Error(`未找到推荐模板:seed/templates/${main}.json`)\n return JSON.parse(readFileSync(path, 'utf8')) as unknown\n}\n\nexport function listBuiltinWritingStylePresets(): Array<{ id: string, slug: string, name: string }> {\n const root = join(seedSkillsDir(), 'writing-style')\n if (!existsSync(root)) return []\n const out: Array<{ id: string, slug: string, name: string }> = []\n for (const ent of readdirSync(root, { withFileTypes: true })) {\n if (!ent.isDirectory() || ent.name.startsWith('_')) continue\n const stylePath = join(root, ent.name, 'STYLE.md')\n if (!existsSync(stylePath)) continue\n const metaPath = join(root, ent.name, 'meta.json')\n let name = ent.name\n if (existsSync(metaPath)) {\n try {\n const meta = JSON.parse(readFileSync(metaPath, 'utf8')) as { name?: string }\n if (typeof meta.name === 'string' && meta.name.trim().length > 0) name = meta.name.trim()\n } catch {\n // keep slug\n }\n }\n const id = createHash('sha256').update(`builtin-ws:${ent.name}`).digest('hex').slice(0, 8)\n out.push({ id, slug: ent.name, name })\n }\n return out\n}\n\nexport async function seedWritingStyleBuiltins(db: PGlite): Promise<void> {\n const root = join(seedSkillsDir(), 'writing-style')\n if (!existsSync(root)) return\n for (const preset of listBuiltinWritingStylePresets()) {\n const content = readFileSync(join(root, preset.slug, 'STYLE.md'), 'utf8')\n const metaPath = join(root, preset.slug, 'meta.json')\n let summary = ''\n if (existsSync(metaPath)) {\n try {\n const meta = JSON.parse(readFileSync(metaPath, 'utf8')) as { summary?: string }\n summary = meta.summary?.trim() ?? ''\n } catch {\n summary = ''\n }\n }\n await db.query(\n `INSERT INTO writing_styles (id, slug, name, summary, content, origin)\n VALUES ($1, $2, $3, $4, $5, 'builtin')\n ON CONFLICT (slug) DO UPDATE SET\n name = EXCLUDED.name,\n summary = EXCLUDED.summary,\n content = EXCLUDED.content,\n updated_at = NOW()`,\n [preset.id, preset.slug, preset.name, summary, content],\n )\n }\n}\n\nconst DIR_TO_SECTION: Record<string, string> = {\n overview: 'overview',\n role: 'role',\n creature: 'creature',\n level: 'level',\n world: 'world',\n timeline: 'timeline',\n outline: 'outline',\n chapter: 'chapters',\n item: 'item',\n}\n\nexport const SECTION_TO_FOLDER: Record<string, string> = {\n overview: '概述',\n role: '人物',\n creature: '生物',\n item: '物品',\n level: '等级',\n world: '世界',\n timeline: '时间线',\n outline: '大纲',\n chapter: '章节',\n chapters: '章节',\n}\n\nexport interface SeedSkillRow {\n section: string\n folder: string\n agent_kind: string\n title: string\n body: string\n}\n\nfunction readMdDir(dir: string, section: string, agentKind: string, folder: string): SeedSkillRow[] {\n if (!existsSync(dir)) return []\n const out: SeedSkillRow[] = []\n for (const file of readdirSync(dir).filter(name => name.endsWith('.md'))) {\n const body = readFileSync(join(dir, file), 'utf8')\n if (body.trim().length === 0) continue\n out.push({ section, folder, agent_kind: agentKind, title: basename(file, '.md'), body })\n }\n return out\n}\n\n/** Disk skill markdown used both to seed a workspace and to advertise module skills before init. */\nexport function listSeedSkillRows(): SeedSkillRow[] {\n const ref = seedSkillsDir()\n const rows: SeedSkillRow[] = []\n for (const [dir, section] of Object.entries(DIR_TO_SECTION)) {\n rows.push(...readMdDir(join(ref, dir), section, 'write', SECTION_TO_FOLDER[dir] ?? dir))\n }\n rows.push(...readMdDir(join(ref, 'review', 'outline'), 'outline', 'review', '审查/大纲'))\n rows.push(...readMdDir(join(ref, 'review', 'chapter'), 'chapters', 'review', '审查/章节'))\n return rows\n}\n\n/** Seed generic skill markdown (genre-specific files under seed/skills/genres are not loaded). */\nexport async function seedNovelSkills(db: PGlite, _category: string): Promise<void> {\n for (const row of listSeedSkillRows()) {\n await db.query(\n `INSERT INTO novel_skills (id, section, folder, agent_kind, title, body)\n VALUES ($1, $2, $3, $4, $5, $6)`,\n [randomUUID(), row.section, row.folder, row.agent_kind, row.title, row.body],\n )\n }\n}\n\nexport async function listWritingStyles(db: PGlite): Promise<Array<{ id: string, name: string, slug: string }>> {\n const { rows } = await db.query<{ id: string, name: string, slug: string }>(\n `SELECT id, name, slug FROM writing_styles ORDER BY origin, name`,\n )\n return rows\n}\n","export function asOrdinal(value: unknown): number | undefined {\n if (typeof value === 'number' && Number.isFinite(value)) return value\n if (typeof value === 'string' && value.trim() !== '') {\n const n = Number(value.trim())\n if (Number.isFinite(n)) return n\n }\n return undefined\n}\n\nexport function canonicalizeOutlineKind(raw: unknown): 'volume' | 'chapter_segment' | undefined {\n if (typeof raw !== 'string') return undefined\n const key = raw.trim()\n if (key === 'chapter_segment' || key === 'chapter' || key === '章') return 'chapter_segment'\n if (key === 'volume' || key === '卷') return 'volume'\n return undefined\n}\n\nexport function outlineKindOf(data: Record<string, unknown>): 'volume' | 'chapter_segment' {\n const explicit = canonicalizeOutlineKind(data.kind ?? data.nodeKind)\n if (explicit !== undefined) return explicit\n const parent = typeof data.parentId === 'string' && data.parentId.length > 0\n || typeof data.parentName === 'string' && data.parentName.length > 0\n return parent ? 'chapter_segment' : 'volume'\n}\n\nexport function volumeChapterLabel(ordinal: unknown, unit: '卷' | '章', name: string): string {\n const title = name.trim().length > 0 ? name.trim() : '未命名'\n const n = asOrdinal(ordinal)\n if (n === undefined) return title\n return `第${n}${unit} ${title}`\n}\n\nfunction shortName(data: Record<string, unknown>, fallback: string): string {\n return typeof data.name === 'string' && data.name.trim().length > 0 ? data.name.trim() : fallback\n}\n\nexport function outlineNodeLabel(data: Record<string, unknown>, fallback: string): string {\n const unit = outlineKindOf(data) === 'chapter_segment' ? '章' : '卷'\n return volumeChapterLabel(data.ordinal, unit, shortName(data, fallback))\n}\n\nexport function chapterNodeLabel(data: Record<string, unknown>, fallback: string): string {\n return volumeChapterLabel(data.ordinal ?? data.chapterNo, '章', shortName(data, fallback))\n}\n\nexport function compareOrdinal(a: unknown, b: unknown): number {\n const oa = asOrdinal(a)\n const ob = asOrdinal(b)\n if (oa === undefined && ob === undefined) return 0\n if (oa === undefined) return 1\n if (ob === undefined) return -1\n return oa - ob\n}\n\nexport function sortByOrdinalName<T extends { name?: string | null }>(\n items: T[],\n ordinalOf: (item: T) => unknown,\n): T[] {\n return [...items].sort((a, b) => {\n const cmp = compareOrdinal(ordinalOf(a), ordinalOf(b))\n if (cmp !== 0) return cmp\n return (a.name ?? '').localeCompare(b.name ?? '', 'zh')\n })\n}\n","import type { EntityDomain } from './domains.ts'\nimport { asOrdinal, outlineKindOf } from './outline-labels.ts'\n\nexport function asRecord(value: unknown): Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value)\n ? value as Record<string, unknown>\n : {}\n}\n\nfunction asNonEmptyString(value: unknown): string | undefined {\n if (typeof value !== 'string') return undefined\n const trimmed = value.trim()\n return trimmed.length > 0 ? trimmed : undefined\n}\n\n/** Merge nested `profile` onto the root; existing top-level keys win. */\nexport function hoistProfile(data: Record<string, unknown>): Record<string, unknown> {\n return { ...asRecord(data.profile), ...data }\n}\n\n/** Merge outline `extra` onto the root; existing top-level keys win. */\nexport function hoistOutlineExtra(data: Record<string, unknown>): Record<string, unknown> {\n return { ...asRecord(data.extra), ...data }\n}\n\n/** Shape used by the content tab: hoist profile/extra so template keys resolve. */\nexport function displayEntityData(data: Record<string, unknown>): Record<string, unknown> {\n return hoistOutlineExtra(hoistProfile(data))\n}\n\nexport function levelNameOf(data: Record<string, unknown>): string | undefined {\n return asNonEmptyString(data.name)\n ?? asNonEmptyString(data.title)\n ?? asNonEmptyString(asRecord(data.system).name)\n}\n\nconst FROM_KEYS = ['roleId', 'from', 'fromName', 'source', 'fromRole'] as const\nconst TO_KEYS = ['targetRoleId', 'to', 'toName', 'target', 'toRole'] as const\n\nexport function pickFirstString(data: Record<string, unknown>, keys: readonly string[]): string | undefined {\n for (const key of keys) {\n const value = asNonEmptyString(data[key])\n if (value !== undefined) return value\n }\n return undefined\n}\n\nexport interface NormalizeLookups {\n worldIdByName: Map<string, string>\n roleIdByRef: Map<string, string>\n outlineIdByName: Map<string, string>\n outlineChapterIdByName: Map<string, string>\n}\n\nfunction omitKeys(data: Record<string, unknown>, keys: string[]): Record<string, unknown> {\n const next = { ...data }\n for (const key of keys) delete next[key]\n return next\n}\n\nfunction resolveRef(ref: string, map: Map<string, string>, label: string): string {\n const id = map.get(ref)\n if (id === undefined) throw new Error(`${label} not found: ${ref}`)\n return id\n}\n\n/** Canonicalize mutate payloads so the content tab can read template keys. */\nexport function normalizeEntityPayload(\n domain: EntityDomain,\n raw: Record<string, unknown>,\n lookups: NormalizeLookups,\n): Record<string, unknown> {\n let data = hoistProfile(raw)\n if (domain === 'outline') data = hoistOutlineExtra(data)\n\n if (domain === 'level') {\n const name = levelNameOf(data)\n if (name !== undefined) data = { ...data, name }\n }\n\n if (domain === 'world') {\n const parentId = asNonEmptyString(data.parentId)\n const parentName = asNonEmptyString(data.parentName)\n if (parentId === undefined && parentName !== undefined) {\n data = { ...data, parentId: resolveRef(parentName, lookups.worldIdByName, 'world parent') }\n }\n data = omitKeys(data, ['parentName'])\n }\n\n if (domain === 'outline') {\n const parentId = asNonEmptyString(data.parentId)\n const parentName = asNonEmptyString(data.parentName)\n if (parentId === undefined && parentName !== undefined) {\n data = { ...data, parentId: resolveRef(parentName, lookups.outlineIdByName, 'outline parent') }\n }\n const ordinal = asOrdinal(data.ordinal) ?? asOrdinal(data.chapterNo)\n data = {\n ...data,\n kind: outlineKindOf(data),\n ...(ordinal !== undefined ? { ordinal } : {}),\n }\n data = omitKeys(data, ['parentName', 'nodeKind', 'volumeNo', 'chapterNo'])\n }\n\n if (domain === 'chapter') {\n const relatedId = asNonEmptyString(data.relatedOutlineId)\n const relatedName = asNonEmptyString(data.relatedOutlineName)\n if (relatedId === undefined && relatedName !== undefined) {\n const id = lookups.outlineChapterIdByName.get(relatedName) ?? lookups.outlineIdByName.get(relatedName)\n if (id === undefined) throw new Error(`outline chapter not found: ${relatedName}`)\n data = { ...data, relatedOutlineId: id }\n }\n const ordinal = asOrdinal(data.ordinal) ?? asOrdinal(data.chapterNo)\n if (ordinal !== undefined) data = { ...data, ordinal }\n data = omitKeys(data, ['relatedOutlineName', 'chapterNo', 'volumeNo'])\n }\n\n if (domain === 'roleRelation') {\n const fromRef = pickFirstString(data, FROM_KEYS)\n const toRef = pickFirstString(data, TO_KEYS)\n if (fromRef === undefined || toRef === undefined) {\n throw new Error('roleRelation requires both ends (roleId/from and targetRoleId/to)')\n }\n data = {\n ...data,\n roleId: resolveRef(fromRef, lookups.roleIdByRef, 'role'),\n targetRoleId: resolveRef(toRef, lookups.roleIdByRef, 'role'),\n }\n }\n\n return data\n}\n","import { access, mkdir, readFile, rename, writeFile } from 'node:fs/promises'\nimport { basename, join } from 'node:path'\nimport { randomUUID } from 'node:crypto'\nimport { PGlite } from '@electric-sql/pglite'\nimport { ENTITY_DOMAINS, isEntityDomain, type EntityDomain } from './domains.ts'\nimport { initNovelSchema } from './schema.ts'\nimport { DEFAULT_FORM_TEMPLATE } from './defaults.ts'\nimport { loadRecommendedTemplate, seedNovelSkills, seedWritingStyleBuiltins, listWritingStyles, SECTION_TO_FOLDER } from './seed.ts'\nimport { normalizeTemplatePresetCategory } from './template-fields.ts'\nimport { asRecord, levelNameOf, normalizeEntityPayload } from './normalize-entity.ts'\nimport { outlineKindOf } from './outline-labels.ts'\n\nexport const NOVEL_DIR = '.dsh-novel'\nexport const LEGACY_NOVEL_DIR = '.noveel'\nexport const META_FILE = 'meta.json'\n\nexport interface NovelMeta {\n version: 1\n novelId: string\n createdAt: string\n}\n\nexport interface NovelRecord {\n id: string\n title: string\n description: string | null\n cover: string | null\n category: string\n writing_style_id: string | null\n chapter_count: number\n word_count: number\n blueprint_json: unknown\n outline_draft_md: string | null\n outline_final_md: string | null\n outline_status: string\n created_at: string\n updated_at: string\n}\n\nexport interface EntityRecord {\n domain: string\n id: string\n name: string | null\n data: unknown\n created_at: string\n updated_at: string\n}\n\nexport interface SkillRecord {\n id: string\n section: string\n folder: string\n agent_kind: string\n title: string\n body: string\n created_at: string\n updated_at: string\n}\n\nfunction novelRoot(workspacePath: string): string {\n return join(workspacePath, NOVEL_DIR)\n}\n\nasync function migrateLegacyNovelDir(workspacePath: string): Promise<void> {\n const next = join(workspacePath, NOVEL_DIR)\n const legacy = join(workspacePath, LEGACY_NOVEL_DIR)\n try {\n await access(next)\n return\n } catch {\n // new dir not present\n }\n try {\n await access(legacy)\n } catch {\n return\n }\n await rename(legacy, next)\n}\n\nfunction pgliteDir(workspacePath: string): string {\n return join(novelRoot(workspacePath), 'pglite')\n}\n\nfunction metaPath(workspacePath: string): string {\n return join(novelRoot(workspacePath), META_FILE)\n}\n\n/** Return whether a workspace directory has been provisioned as a novel. */\nexport async function isNovelWorkspace(workspacePath: string): Promise<boolean> {\n await migrateLegacyNovelDir(workspacePath)\n try {\n const raw = await readFile(metaPath(workspacePath), 'utf8')\n const parsed = JSON.parse(raw) as { version?: unknown }\n return parsed.version === 1\n } catch {\n return false\n }\n}\n\nconst connections = new Map<string, Promise<PGlite>>()\n\nasync function openDb(workspacePath: string): Promise<PGlite> {\n await migrateLegacyNovelDir(workspacePath)\n const existing = connections.get(workspacePath)\n if (existing !== undefined) return existing\n const pending = (async () => {\n const db = await PGlite.create(pgliteDir(workspacePath))\n await initNovelSchema(db)\n return db\n })()\n connections.set(workspacePath, pending)\n try {\n return await pending\n } catch (cause) {\n connections.delete(workspacePath)\n throw cause\n }\n}\n\n/** Initialize `.dsh-novel` + PGlite + default novel row for one workspace directory. */\nexport async function provisionNovelWorkspace(\n workspacePath: string,\n input: {\n title?: string | undefined\n category?: string | undefined\n writingStyleId?: string | undefined\n } = {},\n): Promise<{ meta: NovelMeta, novel: NovelRecord, alreadyInitialized: boolean }> {\n await migrateLegacyNovelDir(workspacePath)\n if (await isNovelWorkspace(workspacePath)) {\n const novel = await getNovel(workspacePath)\n const meta = JSON.parse(await readFile(metaPath(workspacePath), 'utf8')) as NovelMeta\n return { meta, novel, alreadyInitialized: true }\n }\n const novelId = randomUUID().replace(/-/g, '').slice(0, 8)\n const title = input.title?.trim() || basename(workspacePath)\n const category = normalizeTemplatePresetCategory(input.category)\n const createdAt = new Date().toISOString()\n const meta: NovelMeta = { version: 1, novelId, createdAt }\n await mkdir(pgliteDir(workspacePath), { recursive: true })\n await writeFile(metaPath(workspacePath), `${JSON.stringify(meta, null, 2)}\\n`)\n const db = await openDb(workspacePath)\n await seedWritingStyleBuiltins(db)\n const writingStyleId = input.writingStyleId?.trim() || null\n const template = (() => {\n try {\n return loadRecommendedTemplate(category)\n } catch {\n return DEFAULT_FORM_TEMPLATE\n }\n })()\n await db.query(\n `INSERT INTO novels (id, title, category, writing_style_id) VALUES ($1, $2, $3, $4)`,\n [novelId, title, category, writingStyleId],\n )\n await db.query(\n `INSERT INTO form_templates (id, novel_id, config) VALUES ($1, $2, $3::jsonb)`,\n [randomUUID(), novelId, JSON.stringify(template)],\n )\n await seedNovelSkills(db, category)\n return { meta, novel: await getNovel(workspacePath), alreadyInitialized: false }\n}\n\n/** Load the singleton novel row. */\nexport async function getNovel(workspacePath: string): Promise<NovelRecord> {\n const db = await openDb(workspacePath)\n const { rows } = await db.query<NovelRecord>(`SELECT * FROM novels LIMIT 1`)\n const row = rows[0]\n if (row === undefined) throw new Error('novel row missing; re-provision this workspace')\n return row\n}\n\n/** Patch novel metadata / blueprint. */\nexport async function updateNovel(\n workspacePath: string,\n patch: Partial<Pick<NovelRecord, 'title' | 'description' | 'cover' | 'category' | 'writing_style_id' | 'blueprint_json' | 'outline_draft_md' | 'outline_final_md' | 'outline_status'>>,\n): Promise<NovelRecord> {\n const current = await getNovel(workspacePath)\n const next = { ...current, ...patch, updated_at: new Date().toISOString() }\n const db = await openDb(workspacePath)\n await db.query(\n `UPDATE novels SET\n title = $1, description = $2, cover = $3, category = $4, writing_style_id = $5,\n blueprint_json = $6::jsonb, outline_draft_md = $7, outline_final_md = $8,\n outline_status = $9, updated_at = NOW()\n WHERE id = $10`,\n [\n next.title,\n next.description,\n next.cover,\n next.category,\n next.writing_style_id,\n JSON.stringify(next.blueprint_json ?? {}),\n next.outline_draft_md,\n next.outline_final_md,\n next.outline_status,\n current.id,\n ],\n )\n return getNovel(workspacePath)\n}\n\n/** List entities in one domain. */\nexport async function listEntities(workspacePath: string, domain: EntityDomain): Promise<EntityRecord[]> {\n const db = await openDb(workspacePath)\n const { rows } = await db.query<EntityRecord>(\n `SELECT * FROM novel_entities WHERE domain = $1 ORDER BY name NULLS LAST, updated_at DESC`,\n [domain],\n )\n return rows\n}\n\n/** Load one entity. */\nexport async function getEntity(workspacePath: string, id: string): Promise<EntityRecord | undefined> {\n const db = await openDb(workspacePath)\n const { rows } = await db.query<EntityRecord>(`SELECT * FROM novel_entities WHERE id = $1`, [id])\n return rows[0]\n}\n\nasync function lookupsFor(workspacePath: string, domain: EntityDomain, entityId: string) {\n const worldIdByName = new Map<string, string>()\n const roleIdByRef = new Map<string, string>()\n const outlineIdByName = new Map<string, string>()\n const outlineChapterIdByName = new Map<string, string>()\n if (domain === 'world') {\n for (const row of await listEntities(workspacePath, 'world')) {\n if (row.id === entityId) continue\n if (typeof row.name === 'string' && row.name.length > 0) worldIdByName.set(row.name, row.id)\n }\n }\n if (domain === 'roleRelation') {\n for (const row of await listEntities(workspacePath, 'role')) {\n roleIdByRef.set(row.id, row.id)\n if (typeof row.name === 'string' && row.name.length > 0) roleIdByRef.set(row.name, row.id)\n }\n }\n if (domain === 'outline' || domain === 'chapter') {\n const rows = await listEntities(workspacePath, 'outline')\n for (const row of rows) {\n if (row.id === entityId) continue\n if (typeof row.name !== 'string' || row.name.length === 0) continue\n outlineIdByName.set(row.name, row.id)\n if (outlineKindOf(asRecord(row.data)) === 'chapter_segment') {\n outlineChapterIdByName.set(row.name, row.id)\n }\n }\n for (const row of rows) {\n if (row.id === entityId) continue\n if (typeof row.name !== 'string' || row.name.length === 0) continue\n if (outlineKindOf(asRecord(row.data)) === 'volume') outlineIdByName.set(row.name, row.id)\n }\n }\n return { worldIdByName, roleIdByRef, outlineIdByName, outlineChapterIdByName }\n}\n\n/** Insert or replace an entity. */\nexport async function upsertEntity(\n workspacePath: string,\n domain: EntityDomain,\n data: Record<string, unknown>,\n id?: string,\n): Promise<EntityRecord> {\n if (!isEntityDomain(domain)) throw new Error(`unknown domain ${domain}`)\n const entityId = id ?? (typeof data.id === 'string' && data.id.length > 0 ? data.id : randomUUID())\n const normalized = normalizeEntityPayload(domain, data, await lookupsFor(workspacePath, domain, entityId))\n const name = typeof normalized.name === 'string'\n ? normalized.name\n : typeof normalized.title === 'string'\n ? normalized.title\n : levelNameOf(normalized) ?? null\n const db = await openDb(workspacePath)\n await db.query(\n `INSERT INTO novel_entities (domain, id, name, data)\n VALUES ($1, $2, $3, $4::jsonb)\n ON CONFLICT (id) DO UPDATE SET\n domain = EXCLUDED.domain,\n name = EXCLUDED.name,\n data = EXCLUDED.data,\n updated_at = NOW()`,\n [domain, entityId, name, JSON.stringify({ ...normalized, id: entityId })],\n )\n const row = await getEntity(workspacePath, entityId)\n if (row === undefined) throw new Error('entity upsert failed')\n return row\n}\n\n/** Delete one entity. */\nexport async function deleteEntity(workspacePath: string, id: string): Promise<boolean> {\n const db = await openDb(workspacePath)\n const { rows } = await db.query<{ id: string }>(`DELETE FROM novel_entities WHERE id = $1 RETURNING id`, [id])\n return rows.length > 0\n}\n\n/** Form template JSON for the workspace novel. */\nexport async function getTemplate(workspacePath: string): Promise<unknown> {\n const db = await openDb(workspacePath)\n const { rows } = await db.query<{ config: unknown }>(`SELECT config FROM form_templates LIMIT 1`)\n return rows[0]?.config ?? DEFAULT_FORM_TEMPLATE\n}\n\n/** Replace the form template. */\nexport async function saveTemplate(workspacePath: string, config: unknown): Promise<unknown> {\n const novel = await getNovel(workspacePath)\n const db = await openDb(workspacePath)\n await db.query(\n `INSERT INTO form_templates (id, novel_id, config) VALUES ($1, $2, $3::jsonb)\n ON CONFLICT (novel_id) DO UPDATE SET config = EXCLUDED.config, updated_at = NOW()`,\n [randomUUID(), novel.id, JSON.stringify(config)],\n )\n return getTemplate(workspacePath)\n}\n\n/** List writing skills. */\nexport async function listSkills(workspacePath: string): Promise<SkillRecord[]> {\n const db = await openDb(workspacePath)\n const { rows } = await db.query<SkillRecord>(\n `SELECT * FROM novel_skills\n WHERE folder <> '题材' AND folder NOT LIKE '题材/%'\n ORDER BY folder, title`,\n )\n return rows\n}\n\n/** Create or update a skill. */\nexport async function upsertSkill(\n workspacePath: string,\n skill: { id?: string | undefined, section: string, folder?: string | undefined, agentKind?: string | undefined, title: string, body: string },\n): Promise<SkillRecord> {\n const id = skill.id ?? randomUUID()\n const folder = skill.folder ?? SECTION_TO_FOLDER[skill.section] ?? '其他'\n const db = await openDb(workspacePath)\n await db.query(\n `INSERT INTO novel_skills (id, section, folder, agent_kind, title, body)\n VALUES ($1, $2, $3, $4, $5, $6)\n ON CONFLICT (id) DO UPDATE SET\n section = EXCLUDED.section,\n folder = EXCLUDED.folder,\n agent_kind = EXCLUDED.agent_kind,\n title = EXCLUDED.title,\n body = EXCLUDED.body,\n updated_at = NOW()`,\n [id, skill.section, folder, skill.agentKind ?? 'write', skill.title, skill.body],\n )\n const { rows } = await db.query<SkillRecord>(`SELECT * FROM novel_skills WHERE id = $1`, [id])\n const row = rows[0]\n if (row === undefined) throw new Error('skill upsert failed')\n return row\n}\n\n/** Delete a skill. */\nexport async function deleteSkill(workspacePath: string, id: string): Promise<boolean> {\n const db = await openDb(workspacePath)\n const { rows } = await db.query<{ id: string }>(`DELETE FROM novel_skills WHERE id = $1 RETURNING id`, [id])\n return rows.length > 0\n}\n\n/** Static write contract for tools. Book-specific field keys still come from get_template. */\nexport function describeSchema(): {\n domains: typeof ENTITY_DOMAINS\n notes: string\n write: Record<string, string>\n} {\n return {\n domains: ENTITY_DOMAINS,\n notes: 'One DSH workspace is one novel. overview lives on novels.blueprint_json. Call get_template for this book\\'s field keys, then write those keys at the top level (not nested under profile).',\n write: {\n overview: 'update_novel blueprint_json; keys from get_template overview.fields (genre, tone, coreConflict, …).',\n world: 'name, kind, parentId or parentName (resolved to parentId), geography/culture/history/factions at top level.',\n role: 'name plus get_template role.detailFields keys at top level; faction must match an existing world name.',\n roleRelation: 'one directed edge: roleId/targetRoleId or from/to names, relationType, content. UI shows the edge on both people; do not duplicate a reverse row.',\n level: 'system.name and system.levels[] (name, promotionCondition, abilities, subLevels). Entity name = system.name.',\n item: 'name plus get_template item.detailFields keys at top level.',\n creature: 'name plus get_template creature.detailFields keys at top level.',\n timeline: 'title, content, timeLabel, timePoint, eventType; optional isResolved / relatedChapterNo.',\n outline: 'identity: ordinal + name (UI shows 第N卷/章 + name). Volume = no parent; chapter = parentId or parentName. Optional theme/conflict/majorEvents/content at top level (or extra, which is hoisted). Do not put 第N卷/章 into name. No volumeNo/chapterNo.',\n chapter: 'identity: ordinal + name (UI shows 第N章 + name; chapterNo is an alias for ordinal). Optional content (body) and relatedOutlineId or relatedOutlineName.',\n },\n }\n}\n\nexport { ENTITY_DOMAINS, isEntityDomain }\n\nexport async function listWorkspaceWritingStyles(\n workspacePath: string,\n): Promise<Array<{ id: string, name: string, slug: string }>> {\n const db = await openDb(workspacePath)\n await seedWritingStyleBuiltins(db)\n return listWritingStyles(db)\n}\n","import type { IncomingMessage, ServerResponse } from 'node:http'\nimport { NOVEL_API_PREFIX } from './contract.ts'\nimport { ENTITY_DOMAINS, isEntityDomain } from './domains.ts'\nimport {\n deleteEntity,\n deleteSkill,\n describeSchema,\n getEntity,\n getNovel,\n getTemplate,\n isNovelWorkspace,\n listEntities,\n listSkills,\n listWorkspaceWritingStyles,\n provisionNovelWorkspace,\n saveTemplate,\n updateNovel,\n upsertEntity,\n upsertSkill,\n} from './store.ts'\nimport { listBuiltinWritingStylePresets } from './seed.ts'\n\nfunction finish(res: ServerResponse, status: number, body?: unknown): void {\n res.statusCode = status\n if (body === undefined) {\n res.end()\n return\n }\n res.setHeader('content-type', 'application/json; charset=utf-8')\n res.end(JSON.stringify(body))\n}\n\nasync function readJson(req: IncomingMessage): Promise<unknown> {\n const chunks: Buffer[] = []\n for await (const chunk of req) {\n chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk as Uint8Array))\n }\n if (chunks.length === 0) return {}\n return JSON.parse(Buffer.concat(chunks).toString('utf8')) as unknown\n}\n\nfunction pathOf(req: IncomingMessage): { pathname: string, search: URLSearchParams } {\n const url = new URL(req.url ?? '/', 'http://127.0.0.1')\n return { pathname: url.pathname, search: url.searchParams }\n}\n\nfunction requirePath(value: unknown): string {\n if (typeof value !== 'string' || value.length === 0) throw new Error('path is required')\n return value\n}\n\n/** Same-origin JSON API for the novel client and tools. */\nexport async function handleNovelApi(\n req: IncomingMessage,\n res: ServerResponse,\n expectedOrigin: string,\n): Promise<void> {\n if (req.headers.origin !== undefined && req.headers.origin !== expectedOrigin) {\n finish(res, 403)\n return\n }\n try {\n const { pathname, search } = pathOf(req)\n const method = req.method ?? 'GET'\n const rel = pathname.slice(NOVEL_API_PREFIX.length) || '/'\n\n if (method === 'GET' && rel === '/status') {\n const workspacePath = requirePath(search.get('path'))\n finish(res, 200, { novel: await isNovelWorkspace(workspacePath) })\n return\n }\n if (method === 'POST' && rel === '/provision') {\n const body = await readJson(req) as Record<string, unknown>\n const workspacePath = requirePath(body.path)\n const result = await provisionNovelWorkspace(workspacePath, {\n title: typeof body.title === 'string' ? body.title : undefined,\n category: typeof body.category === 'string' ? body.category : undefined,\n writingStyleId: typeof body.writingStyleId === 'string' ? body.writingStyleId : undefined,\n })\n finish(res, 200, result)\n return\n }\n if (method === 'GET' && rel === '/schema') {\n finish(res, 200, describeSchema())\n return\n }\n if (method === 'GET' && rel === '/novel') {\n const workspacePath = requirePath(search.get('path'))\n finish(res, 200, await getNovel(workspacePath))\n return\n }\n if (method === 'PATCH' && rel === '/novel') {\n const body = await readJson(req) as Record<string, unknown>\n const workspacePath = requirePath(body.path)\n const patch = {\n ...(typeof body.title === 'string' ? { title: body.title } : {}),\n ...(typeof body.description === 'string' ? { description: body.description } : {}),\n ...(typeof body.cover === 'string' ? { cover: body.cover } : {}),\n ...(typeof body.category === 'string' ? { category: body.category } : {}),\n ...(body.writing_style_id === null || typeof body.writing_style_id === 'string'\n ? { writing_style_id: body.writing_style_id as string | null }\n : {}),\n ...(body.blueprint_json !== undefined ? { blueprint_json: body.blueprint_json } : {}),\n ...(typeof body.outline_draft_md === 'string' ? { outline_draft_md: body.outline_draft_md } : {}),\n ...(typeof body.outline_final_md === 'string' ? { outline_final_md: body.outline_final_md } : {}),\n ...(typeof body.outline_status === 'string' ? { outline_status: body.outline_status } : {}),\n }\n finish(res, 200, await updateNovel(workspacePath, patch))\n return\n }\n if (method === 'GET' && rel === '/entities') {\n const workspacePath = requirePath(search.get('path'))\n const domain = search.get('domain') ?? ''\n const id = search.get('id')\n if (id !== null) {\n finish(res, 200, await getEntity(workspacePath, id) ?? null)\n return\n }\n if (!isEntityDomain(domain)) {\n finish(res, 400, { error: `unknown domain; expected one of ${ENTITY_DOMAINS.join(', ')}` })\n return\n }\n finish(res, 200, await listEntities(workspacePath, domain))\n return\n }\n if ((method === 'POST' || method === 'PATCH') && rel === '/entities') {\n const body = await readJson(req) as Record<string, unknown>\n const workspacePath = requirePath(body.path)\n const domain = String(body.domain ?? '')\n if (!isEntityDomain(domain)) throw new Error('unknown domain')\n const data = (body.data ?? {}) as Record<string, unknown>\n const id = typeof body.id === 'string' ? body.id : undefined\n finish(res, 200, await upsertEntity(workspacePath, domain, data, id))\n return\n }\n if (method === 'DELETE' && rel === '/entities') {\n const body = await readJson(req) as Record<string, unknown>\n finish(res, 200, { deleted: await deleteEntity(requirePath(body.path), String(body.id ?? '')) })\n return\n }\n if (method === 'GET' && rel === '/template') {\n finish(res, 200, await getTemplate(requirePath(search.get('path'))))\n return\n }\n if (method === 'PUT' && rel === '/template') {\n const body = await readJson(req) as Record<string, unknown>\n finish(res, 200, await saveTemplate(requirePath(body.path), body.config))\n return\n }\n if (method === 'GET' && rel === '/style-presets') {\n finish(res, 200, listBuiltinWritingStylePresets())\n return\n }\n if (method === 'GET' && rel === '/styles') {\n finish(res, 200, await listWorkspaceWritingStyles(requirePath(search.get('path'))))\n return\n }\n if (method === 'GET' && rel === '/skills') {\n finish(res, 200, await listSkills(requirePath(search.get('path'))))\n return\n }\n if ((method === 'POST' || method === 'PATCH') && rel === '/skills') {\n const body = await readJson(req) as Record<string, unknown>\n finish(res, 200, await upsertSkill(requirePath(body.path), {\n id: typeof body.id === 'string' ? body.id : undefined,\n section: String(body.section ?? 'overview'),\n folder: typeof body.folder === 'string' ? body.folder : undefined,\n agentKind: typeof body.agentKind === 'string' ? body.agentKind : undefined,\n title: String(body.title ?? ''),\n body: String(body.body ?? ''),\n }))\n return\n }\n if (method === 'DELETE' && rel === '/skills') {\n const body = await readJson(req) as Record<string, unknown>\n finish(res, 200, { deleted: await deleteSkill(requirePath(body.path), String(body.id ?? '')) })\n return\n }\n finish(res, 404, { error: 'not found' })\n } catch (cause) {\n finish(res, 400, { error: cause instanceof Error ? cause.message : String(cause) })\n }\n}\n\nexport { NOVEL_API_PREFIX }\n","import type { Context } from '@deepseek-ai/cordis'\nimport {\n BUNDLED_SKILL_RANK,\n type SkillCandidate,\n type SkillDefinition,\n type SkillLookupOptions,\n} from '@deepseek-ai/dsh-skill'\nimport { isNovelWorkspace, listSkills, type SkillRecord } from './store.ts'\nimport { listSeedSkillRows } from './seed.ts'\n\nexport const NOVEL_CREATE_SKILL_NAME = 'novel-create'\n\nexport const MODULE_SKILL_SPECS = [\n { name: 'novel-overview', root: '概述', description: '概述与开书:标题、卖点、种子。' },\n { name: 'novel-world', root: '世界', description: '世界规则、势力与伏笔。' },\n { name: 'novel-role', root: '人物', description: '人物档案、塑造与关系。' },\n { name: 'novel-creature', root: '生物', description: '生物与灵植档案。' },\n { name: 'novel-item', root: '物品', description: '物品档案与归属。' },\n { name: 'novel-level', root: '等级', description: '等级、能力与代价。' },\n { name: 'novel-timeline', root: '时间线', description: '事件轴与状态同步。' },\n { name: 'novel-outline', root: '大纲', description: '大纲结构与剧情骨架。' },\n { name: 'novel-chapter', root: '章节', description: '章节节奏、对话与质检。' },\n { name: 'novel-review', root: '审查', description: '大纲与章节审查。' },\n] as const\n\nexport type ModuleSkillName = (typeof MODULE_SKILL_SPECS)[number]['name']\n\nconst PROVIDER = 'dsh-novel'\n\nexport const NOVEL_CREATE_CONTENT = [\n '# 创建小说工作区',\n '',\n '对当前会话工作区目录调用工具 `novel_create`(可选 `title`、`category`)。',\n '该工具会写入 `.dsh-novel`、建 PGlite、播种模板与技能。',\n '若已经是小说工作区,工具返回 `alreadyInitialized: true`:告知用户已忽略,**不要**改书名或题材。',\n '',\n '然后只做开书设定(不要写大纲或章节):',\n '1. 先 `get_template`,按返回的字段 **顶层 key** 写入(不要包在 `profile` 里)。',\n '2. 加载 `novel-overview`,以及按用户素材选用 `novel-world` / `novel-role` / `novel-level` / `novel-item` / `novel-timeline`。',\n '3. 写入概述 blueprint、世界树(先父后子,子节点用 `parentName`)、人物顶层字段、`roleRelation`、等级 `system`。',\n '4. 按素材写入全书 `timeline` 关键事件:`title` + `content` + `timeLabel` + `timePoint` + `eventType`。伏笔/冲突/悬念必须带 `eventType`(`foreshadow` / `conflict` / `suspense`)和 `isResolved`(开书多为 `false`)。',\n '5. **禁止**加载 `novel-outline` / `novel-chapter`,禁止 mutate `outline` 或 `chapter`。',\n '6. 设定写完即停,告诉用户:大纲用 `/novel-outline`,章节用 `/novel-chapter`。',\n].join('\\n')\n\nexport function moduleSkillInvocation(name: ModuleSkillName): { modelInvocable: true, userInvocable: boolean } {\n const userInvocable = name === 'novel-outline' || name === 'novel-chapter'\n return { modelInvocable: true, userInvocable }\n}\n\n/** Filter DB skill rows that belong to one DSH module skill. */\nexport function rowsForModule(rows: SkillRecord[], name: ModuleSkillName): SkillRecord[] {\n return rows.filter(row => folderMatchesModule(row.folder, name))\n}\n\n/** Module skills that currently have at least one row. */\nexport function moduleSkillsFromRows(rows: SkillRecord[]): ModuleSkillName[] {\n return MODULE_SKILL_SPECS\n .filter(spec => rowsForModule(rows, spec.name).length > 0)\n .map(spec => spec.name)\n}\n\nexport function composeModuleSkillBody(rows: SkillRecord[], name: ModuleSkillName): string {\n const parts = rowsForModule(rows, name)\n return parts.map(row => `## ${row.title}\\n\\n${row.body}`).join('\\n\\n')\n}\n\nexport function folderMatchesModule(folder: string, name: ModuleSkillName): boolean {\n if (name === 'novel-review') return isReviewFolder(folder)\n const spec = MODULE_SKILL_SPECS.find(item => item.name === name)\n if (spec === undefined) return false\n if (isReviewFolder(folder)) return false\n return folder === spec.root || folder.startsWith(`${spec.root}/`)\n}\n\nfunction isReviewFolder(folder: string): boolean {\n return folder === '审查' || folder.startsWith('审查/') || /\\/审查(?:\\/|$)/u.test(folder)\n}\n\n/** Register /novel-create and cwd-scoped module skills from the novel database. */\nexport function registerNovelSkills(ctx: Context): void {\n ctx.effect(\n () => ctx.skills.register({\n name: NOVEL_CREATE_SKILL_NAME,\n description: '把当前工作区初始化为小说工作区(已初始化则忽略)。',\n whenToUse: '用户要开新书、初始化小说目录,或输入 /novel-create。',\n source: 'bundled',\n content: NOVEL_CREATE_CONTENT,\n invocation: { modelInvocable: true, userInvocable: true },\n }),\n 'dsh-plugin-novel: novel-create skill',\n )\n\n ctx.effect(\n () => ctx.skills.registerProvider(() => ({\n name: PROVIDER,\n async list(options: SkillLookupOptions) {\n const rows = await loadRows(options.cwd)\n return moduleSkillsFromRows(rows).map(name => candidateOf(name))\n },\n async get(candidate: SkillCandidate, options: SkillLookupOptions) {\n const name = asModuleName(candidate.locator)\n if (name === undefined) return undefined\n const content = composeModuleSkillBody(await loadRows(options.cwd), name)\n if (content.length === 0) return undefined\n return definitionOf(name, content)\n },\n })),\n 'dsh-plugin-novel: module skill provider',\n )\n}\n\nasync function loadRows(cwd: string | undefined): Promise<SkillRecord[]> {\n if (typeof cwd === 'string' && cwd.length > 0 && await isNovelWorkspace(cwd)) {\n return listSkills(cwd)\n }\n return listSeedSkillRows().map(row => ({\n id: `seed:${row.folder}/${row.title}`,\n section: row.section,\n folder: row.folder,\n agent_kind: row.agent_kind,\n title: row.title,\n body: row.body,\n created_at: '',\n updated_at: '',\n }))\n}\n\nfunction asModuleName(locator: unknown): ModuleSkillName | undefined {\n if (typeof locator !== 'string') return undefined\n return MODULE_SKILL_SPECS.some(spec => spec.name === locator)\n ? locator as ModuleSkillName\n : undefined\n}\n\nfunction candidateOf(name: ModuleSkillName): SkillCandidate {\n const spec = MODULE_SKILL_SPECS.find(item => item.name === name)!\n return {\n name: spec.name,\n description: spec.description,\n invocation: moduleSkillInvocation(name),\n source: 'bundled',\n provider: PROVIDER,\n rank: BUNDLED_SKILL_RANK,\n locator: spec.name,\n }\n}\n\nfunction definitionOf(name: ModuleSkillName, content: string): SkillDefinition {\n const spec = MODULE_SKILL_SPECS.find(item => item.name === name)!\n return {\n name: spec.name,\n description: spec.description,\n invocation: moduleSkillInvocation(name),\n source: 'bundled',\n provider: PROVIDER,\n content,\n }\n}\n","import type { Context } from '@deepseek-ai/cordis'\nimport { defineTool } from '@deepseek-ai/dsh-tools'\nimport type {} from '@deepseek-ai/dsh-system-prompt'\nimport { ENTITY_DOMAINS, isEntityDomain } from './domains.ts'\nimport { registerNovelSkills } from './host-skills.ts'\nimport {\n deleteEntity,\n deleteSkill,\n describeSchema,\n getEntity,\n getNovel,\n getTemplate,\n isNovelWorkspace,\n listEntities,\n listSkills,\n listWorkspaceWritingStyles,\n provisionNovelWorkspace,\n saveTemplate,\n updateNovel,\n upsertEntity,\n upsertSkill,\n} from './store.ts'\n\nimport type { JsonValue } from '@deepseek-ai/dsh-session'\n\nfunction jsonText(value: unknown): string {\n return JSON.stringify(value, null, 2)\n}\n\nfunction asJson(value: unknown): JsonValue {\n return JSON.parse(JSON.stringify(value)) as JsonValue\n}\n\nfunction workspaceFrom(\n args: { workspace_path?: string },\n exec: { agent?: { cwd?: string, session?: { header?: { cwd?: string } } } },\n): string {\n const path = args.workspace_path ?? exec.agent?.cwd ?? exec.agent?.session?.header?.cwd\n if (typeof path !== 'string' || path.length === 0) {\n throw new Error('workspace_path is required (or the session must have a cwd)')\n }\n return path\n}\n\nfunction jsonOutput() {\n return {\n schema: { type: 'json' as const },\n render: (_args: unknown, value: JsonValue) => [{ type: 'text' as const, text: jsonText(value) }],\n }\n}\n\n/** Register novel CRUD tools, DSH skills, and a system-prompt context dump. */\nexport function registerNovelHost(ctx: Context): void {\n registerNovelSkills(ctx)\n\n ctx.tools.register(defineTool({\n name: 'novel_status',\n description: 'Check whether the current workspace is a novel workspace (.dsh-novel).',\n parameters: {\n workspace_path: { type: 'string', description: 'Workspace directory; defaults to session cwd' },\n },\n output: jsonOutput(),\n async execute(args, exec) {\n const path = workspaceFrom(args, exec)\n return asJson({ path, novel: await isNovelWorkspace(path) })\n },\n }))\n\n ctx.tools.register(defineTool({\n name: 'novel_create',\n description: 'Initialize the current directory as a novel workspace. If it already is one, returns alreadyInitialized and does not change title or category.',\n parameters: {\n workspace_path: { type: 'string', description: 'Workspace directory; defaults to session cwd' },\n title: { type: 'string', description: 'Novel title' },\n category: { type: 'string', description: 'Genre such as 玄幻' },\n },\n output: jsonOutput(),\n async execute(args, exec) {\n const path = workspaceFrom(args, exec)\n const result = await provisionNovelWorkspace(path, {\n title: args.title,\n category: args.category,\n })\n if (!result.alreadyInitialized) {\n const registry = ctx.get('workspaceRegistry') as\n | { create?: (workspacePath: string, title?: string) => Promise<unknown> }\n | undefined\n try {\n await registry?.create?.(path, result.novel.title)\n } catch {\n // Official UI may already own this path.\n }\n }\n return asJson(result)\n },\n }))\n\n ctx.tools.register(defineTool({\n name: 'get_novel',\n description: 'Read the current novel metadata and blueprint (overview).',\n parameters: {\n workspace_path: { type: 'string' },\n },\n output: jsonOutput(),\n async execute(args, exec) {\n return asJson(await getNovel(workspaceFrom(args, exec)))\n },\n }))\n\n ctx.tools.register(defineTool({\n name: 'update_novel',\n description: 'Update novel title, category, description, or blueprint_json overview.',\n parameters: {\n workspace_path: { type: 'string' },\n title: { type: 'string' },\n category: { type: 'string' },\n description: { type: 'string' },\n blueprint_json: { type: 'string', description: 'JSON object string for the overview blueprint' },\n },\n output: jsonOutput(),\n async execute(args, exec) {\n const path = workspaceFrom(args, exec)\n const patch: Record<string, unknown> = {}\n if (args.title !== undefined) patch.title = args.title\n if (args.category !== undefined) patch.category = args.category\n if (args.description !== undefined) patch.description = args.description\n if (args.blueprint_json !== undefined) patch.blueprint_json = JSON.parse(args.blueprint_json)\n return asJson(await updateNovel(path, patch as Parameters<typeof updateNovel>[1]))\n },\n }))\n\n ctx.tools.register(defineTool({\n name: 'describe_schema',\n description: 'Describe novel entity domains available in this workspace.',\n parameters: {},\n output: jsonOutput(),\n async execute() {\n return asJson(describeSchema())\n },\n }))\n\n ctx.tools.register(defineTool({\n name: 'query_entities',\n description: `List entities in a domain (${ENTITY_DOMAINS.join(', ')}).`,\n parameters: {\n workspace_path: { type: 'string' },\n domain: { type: 'string', required: true },\n },\n output: jsonOutput(),\n async execute(args, exec) {\n const domain = args.domain\n if (!isEntityDomain(domain)) throw new Error(`unknown domain ${domain}`)\n return asJson({ items: await listEntities(workspaceFrom(args, exec), domain) })\n },\n }))\n\n ctx.tools.register(defineTool({\n name: 'get_entities',\n description: 'Load one entity by id.',\n parameters: {\n workspace_path: { type: 'string' },\n id: { type: 'string', required: true },\n },\n output: jsonOutput(),\n async execute(args, exec) {\n return asJson(await getEntity(workspaceFrom(args, exec), args.id) ?? { missing: true })\n },\n }))\n\n ctx.tools.register(defineTool({\n name: 'mutate_entities',\n description: 'Create or update an entity. Pass domain and a JSON object string as data.',\n parameters: {\n workspace_path: { type: 'string' },\n domain: { type: 'string', required: true },\n data: { type: 'string', required: true, description: 'JSON object, typically including name' },\n id: { type: 'string' },\n },\n output: jsonOutput(),\n async execute(args, exec) {\n if (!isEntityDomain(args.domain)) throw new Error(`unknown domain ${args.domain}`)\n const data = JSON.parse(args.data) as Record<string, unknown>\n return asJson(await upsertEntity(workspaceFrom(args, exec), args.domain, data, args.id))\n },\n }))\n\n ctx.tools.register(defineTool({\n name: 'delete_entity',\n description: 'Delete one entity by id.',\n parameters: {\n workspace_path: { type: 'string' },\n id: { type: 'string', required: true },\n },\n output: jsonOutput(),\n async execute(args, exec) {\n return asJson({ deleted: await deleteEntity(workspaceFrom(args, exec), args.id) })\n },\n }))\n\n ctx.tools.register(defineTool({\n name: 'skill_list',\n description: 'List writing/review skills for the current novel (per-file rows in the database).',\n parameters: {\n workspace_path: { type: 'string' },\n },\n output: jsonOutput(),\n async execute(args, exec) {\n return asJson({ items: await listSkills(workspaceFrom(args, exec)) })\n },\n }))\n\n ctx.tools.register(defineTool({\n name: 'upsert_skill',\n description: 'Create or update one skill row in this novel. Changes the book skill table; do not overwrite the whole catalog without asking.',\n parameters: {\n workspace_path: { type: 'string' },\n id: { type: 'string' },\n section: { type: 'string', required: true },\n folder: { type: 'string', description: 'Chinese folder path such as 概述' },\n agentKind: { type: 'string' },\n title: { type: 'string', required: true },\n body: { type: 'string', required: true },\n },\n output: jsonOutput(),\n async execute(args, exec) {\n return asJson(await upsertSkill(workspaceFrom(args, exec), {\n id: args.id,\n section: args.section,\n folder: args.folder,\n agentKind: args.agentKind,\n title: args.title,\n body: args.body,\n }))\n },\n }))\n\n ctx.tools.register(defineTool({\n name: 'delete_skill',\n description: 'Delete one skill row by id. Destructive; confirm with the user first.',\n parameters: {\n workspace_path: { type: 'string' },\n id: { type: 'string', required: true },\n },\n output: jsonOutput(),\n async execute(args, exec) {\n return asJson({ deleted: await deleteSkill(workspaceFrom(args, exec), args.id) })\n },\n }))\n\n ctx.tools.register(defineTool({\n name: 'get_template',\n description: 'Read the form template JSON used to render field labels for this novel.',\n parameters: {\n workspace_path: { type: 'string' },\n },\n output: jsonOutput(),\n async execute(args, exec) {\n return asJson(await getTemplate(workspaceFrom(args, exec)))\n },\n }))\n\n ctx.tools.register(defineTool({\n name: 'save_template',\n description: 'Replace the entire form template JSON for this novel. Destructive; do not overwrite the whole template without asking.',\n parameters: {\n workspace_path: { type: 'string' },\n config: { type: 'string', required: true, description: 'JSON object string for form_templates.config' },\n },\n output: jsonOutput(),\n async execute(args, exec) {\n return asJson(await saveTemplate(workspaceFrom(args, exec), JSON.parse(args.config)))\n },\n }))\n\n ctx.tools.register(defineTool({\n name: 'list_writing_styles',\n description: 'List writing styles available in this novel workspace.',\n parameters: {\n workspace_path: { type: 'string' },\n },\n output: jsonOutput(),\n async execute(args, exec) {\n return asJson({ items: await listWorkspaceWritingStyles(workspaceFrom(args, exec)) })\n },\n }))\n\n ctx.effect(\n () => ctx.systemPrompt.section({\n name: 'novel-authoring',\n order: 80,\n text: [\n 'You are helping write a novel bound to the current DSH workspace.',\n 'One workspace directory is one novel, stored in `.dsh-novel/pglite`.',\n 'To create a novel workspace and write setting (overview/world/roles/levels), use /novel-create. Do not write outline or chapters in that command.',\n 'To write the outline, the user must invoke /novel-outline. To write chapters, /novel-chapter.',\n 'Before writing a section, load the matching skill novel-overview, novel-world, novel-role, novel-creature, novel-item, novel-level, novel-timeline, novel-outline, novel-chapter, or novel-review; or call skill_list for a single file.',\n 'Call get_template and write template keys at the top level of mutate_entities data (not nested under profile). World children use parentName; relations use from/to names or roleId/targetRoleId.',\n 'Use get_novel, query_entities, mutate_entities, and update_novel to read and write canon.',\n 'Prefer existing names; do not invent duplicate characters.',\n ].join('\\n'),\n }),\n 'dsh-plugin-novel: system prompt section',\n )\n\n ctx.effect(\n () => ctx.systemPrompt.context({\n name: 'novel-snapshot',\n order: 80,\n text: () => 'Novel data is in the workspace `.dsh-novel` database. Call get_novel and query_entities before writing.',\n }),\n 'dsh-plugin-novel: system prompt context',\n )\n}\n","/** DSH Novel capability plugin: workspace PGlite, tools, skills, and HTTP API. */\n\nimport type { Context } from '@deepseek-ai/cordis'\nimport type {} from '@deepseek-ai/dsh-host-webserver'\nimport type {} from '@deepseek-ai/dsh-tools'\nimport type {} from '@deepseek-ai/dsh-system-prompt'\nimport type {} from '@deepseek-ai/dsh-skill'\nimport { handleNovelApi, NOVEL_API_PREFIX } from './novel/http.ts'\nimport { registerNovelHost } from './novel/tools.ts'\n\nexport const name = 'novel'\n\nexport const inject = ['webServer', 'tools', 'systemPrompt', 'skills']\n\nexport function apply(ctx: Context): void {\n const rendererOrigin = `http://${ctx.webServer.host}:${String(ctx.webServer.port)}`\n ctx.effect(\n () => ctx.webServer.register({\n kind: 'prefix',\n path: NOVEL_API_PREFIX,\n handler: (req, res) => { void handleNovelApi(req, res, rendererOrigin) },\n }),\n 'dsh-novel: JSON API',\n )\n registerNovelHost(ctx)\n}\n"],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { ConvViewProps } from '@deepseek-ai/dsh-client-ui-conversation/client';
2
+ /** Novel workspace occupying the conversation.view tab. Does not switch the active view. */
3
+ export declare function NovelWorkspaceView({ sessionId, useSessions }: ConvViewProps): import("react").JSX.Element;
@@ -0,0 +1,17 @@
1
+ export interface EntityItem {
2
+ id: string;
3
+ name?: string | null;
4
+ data?: unknown;
5
+ }
6
+ export declare function asRecord(value: unknown): Record<string, unknown>;
7
+ export declare function listEntities(path: string, domain: string): Promise<EntityItem[]>;
8
+ export declare function upsertEntity(path: string, domain: string, data: Record<string, unknown>, id?: string): Promise<EntityItem>;
9
+ export declare function deleteEntity(path: string, id: string): Promise<void>;
10
+ export declare function dataOf(item: EntityItem): Record<string, unknown>;
11
+ export declare function viewData(item: EntityItem | undefined): Record<string, unknown>;
12
+ export declare function parentIdOf(item: EntityItem, items: EntityItem[]): string | undefined;
13
+ export declare function filterOwned(items: EntityItem[], ownerKey: string, ownerId: string): EntityItem[];
14
+ export declare function cascadeDelete(path: string, ownerId: string, related: Array<{
15
+ domain: string;
16
+ ownerKey: string;
17
+ }>): Promise<void>;
@@ -0,0 +1,3 @@
1
+ import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client';
2
+ export declare const inject: string[];
3
+ export declare function apply(ctx: ClientContext): void;
@@ -0,0 +1 @@
1
+ export { WORLD_KINDS, emptyLevelSystem } from '../novel/noveel-shapes.ts';
@@ -0,0 +1 @@
1
+ export declare function novelApi<T>(path: string, init?: RequestInit): Promise<T>;
@@ -0,0 +1,5 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { ContentSection } from '../novel/domains.ts';
3
+ export declare function SectionIcon({ section }: {
4
+ section: ContentSection;
5
+ }): ReactNode;
@@ -0,0 +1,2 @@
1
+ import type { SectionPageProps } from './types.ts';
2
+ export declare function ChapterSection({ path }: SectionPageProps): import("react").JSX.Element;
@@ -0,0 +1,2 @@
1
+ import type { SectionPageProps } from './types.ts';
2
+ export declare function LevelSection({ path }: SectionPageProps): import("react").JSX.Element;
@@ -0,0 +1,13 @@
1
+ import { type EntityItem } from '../entity-client.ts';
2
+ import type { ContentSection } from '../../novel/domains.ts';
3
+ export declare function ListDetailSection({ path, domain, template, templateSection, chapters, tree, tabs, timelineDomain, ownerKey, }: {
4
+ path: string;
5
+ domain: string;
6
+ template: unknown;
7
+ templateSection: ContentSection;
8
+ chapters: EntityItem[];
9
+ tree?: boolean | undefined;
10
+ tabs: Array<'detail' | 'timeline' | 'relation'>;
11
+ timelineDomain?: string | undefined;
12
+ ownerKey?: string | undefined;
13
+ }): import("react").JSX.Element;
@@ -0,0 +1,2 @@
1
+ import type { SectionPageProps } from './types.ts';
2
+ export declare function OutlineSection({ path, template }: SectionPageProps): import("react").JSX.Element;
@@ -0,0 +1,2 @@
1
+ import type { OverviewPageProps } from './types.ts';
2
+ export declare function OverviewSection({ path, template, styles }: OverviewPageProps): import("react").JSX.Element;
@@ -0,0 +1,3 @@
1
+ export declare function SkillSection({ path }: {
2
+ path: string;
3
+ }): import("react").JSX.Element;
@@ -0,0 +1,2 @@
1
+ import type { SectionPageProps } from './types.ts';
2
+ export declare function TimelineSection({ path, template, chapters }: SectionPageProps): import("react").JSX.Element;
@@ -0,0 +1,2 @@
1
+ import type { SectionPageProps } from './types.ts';
2
+ export declare function WorldSection({ path, template, chapters }: SectionPageProps): import("react").JSX.Element;
@@ -0,0 +1,4 @@
1
+ import type { SectionPageProps } from './types.ts';
2
+ export declare function RoleSection(props: SectionPageProps): import("react").JSX.Element;
3
+ export declare function CreatureSection(props: SectionPageProps): import("react").JSX.Element;
4
+ export declare function ItemSection(props: SectionPageProps): import("react").JSX.Element;
@@ -0,0 +1,14 @@
1
+ import type { EntityItem } from '../entity-client.ts';
2
+ export interface SectionPageProps {
3
+ path: string;
4
+ template: unknown;
5
+ chapters: EntityItem[];
6
+ }
7
+ export interface OverviewPageProps {
8
+ path: string;
9
+ template: unknown;
10
+ styles: Array<{
11
+ id: string;
12
+ name: string;
13
+ }>;
14
+ }
@@ -0,0 +1 @@
1
+ export declare function installNovelStyles(): () => void;
@@ -0,0 +1,10 @@
1
+ import { type EntityItem } from '../entity-client.ts';
2
+ export declare function EntitySidebar({ items, selectedId, onSelect, tree, label, className, files, }: {
3
+ items: EntityItem[];
4
+ selectedId?: string | undefined;
5
+ onSelect: (id: string) => void;
6
+ tree?: boolean | undefined;
7
+ label?: ((item: EntityItem) => string) | undefined;
8
+ className?: string | undefined;
9
+ files?: boolean | undefined;
10
+ }): import("react").JSX.Element;
@@ -0,0 +1,6 @@
1
+ import { type EntityItem } from '../entity-client.ts';
2
+ export declare function RelationPanel({ path, ownerId, roles, }: {
3
+ path: string;
4
+ ownerId: string;
5
+ roles: EntityItem[];
6
+ }): import("react").JSX.Element;
@@ -0,0 +1,13 @@
1
+ import type { ContentSection } from '../../novel/domains.ts';
2
+ export declare function ReadField({ label, children }: {
3
+ label: string;
4
+ children: React.ReactNode;
5
+ }): import("react").JSX.Element;
6
+ export declare function displayText(value: unknown): string;
7
+ export declare function TemplateRead({ template, section, extra, value, omitKeys, }: {
8
+ template: unknown;
9
+ section: ContentSection;
10
+ extra?: 'chapterSegment' | 'timeline' | undefined;
11
+ value: Record<string, unknown>;
12
+ omitKeys?: string[] | undefined;
13
+ }): import("react").JSX.Element;
@@ -0,0 +1,8 @@
1
+ import { type EntityItem } from '../entity-client.ts';
2
+ export declare function TimelinePanel({ path, domain, ownerKey, ownerId, chapters, }: {
3
+ path: string;
4
+ domain: string;
5
+ ownerKey: string;
6
+ ownerId: string;
7
+ chapters: EntityItem[];
8
+ }): import("react").JSX.Element;
@@ -0,0 +1,22 @@
1
+ import type { InputHTMLAttributes, ReactNode, SelectHTMLAttributes, TextareaHTMLAttributes } from 'react';
2
+ export declare function Field({ label, htmlFor, children }: {
3
+ label: string;
4
+ htmlFor?: string;
5
+ children: React.ReactNode;
6
+ }): import("react").JSX.Element;
7
+ export declare function TextInput({ className, ...rest }: InputHTMLAttributes<HTMLInputElement>): import("react").JSX.Element;
8
+ export declare function SearchField({ className: _className, ...rest }: InputHTMLAttributes<HTMLInputElement>): import("react").JSX.Element;
9
+ export declare function TextArea(props: TextareaHTMLAttributes<HTMLTextAreaElement>): import("react").JSX.Element;
10
+ export declare function Select(props: SelectHTMLAttributes<HTMLSelectElement>): import("react").JSX.Element;
11
+ export declare function DetailTabs<T extends string>({ tabs, value, onChange, }: {
12
+ tabs: Array<{
13
+ id: T;
14
+ label: string;
15
+ }>;
16
+ value: T;
17
+ onChange: (id: T) => void;
18
+ }): import("react").JSX.Element;
19
+ export declare function SectionToolbar({ tabs, actions }: {
20
+ tabs?: ReactNode | undefined;
21
+ actions?: ReactNode | undefined;
22
+ }): import("react").JSX.Element;
@@ -0,0 +1,2 @@
1
+ export declare function emptyHint(hasFilter?: boolean): string;
2
+ export declare function useLiveReload(reload: () => void | Promise<void>, ms?: number): void;
@@ -0,0 +1,5 @@
1
+ /** DSH Novel capability plugin: workspace PGlite, tools, skills, and HTTP API. */
2
+ import type { Context } from '@deepseek-ai/cordis';
3
+ export declare const name = "novel";
4
+ export declare const inject: string[];
5
+ export declare function apply(ctx: Context): void;
@@ -0,0 +1,2 @@
1
+ /** Same-origin HTTP prefix for the novel Host API. */
2
+ export declare const NOVEL_API_PREFIX = "/dsh-novel/api";
@@ -0,0 +1,66 @@
1
+ /** Minimal form template so a new workspace is immediately writable. */
2
+ export declare const DEFAULT_FORM_TEMPLATE: {
3
+ version: number;
4
+ overview: {
5
+ fields: {
6
+ key: string;
7
+ label: string;
8
+ component: string;
9
+ }[];
10
+ };
11
+ role: {
12
+ detailFields: {
13
+ key: string;
14
+ label: string;
15
+ component: string;
16
+ }[];
17
+ };
18
+ creature: {
19
+ detailFields: {
20
+ key: string;
21
+ label: string;
22
+ component: string;
23
+ }[];
24
+ };
25
+ item: {
26
+ detailFields: {
27
+ key: string;
28
+ label: string;
29
+ component: string;
30
+ }[];
31
+ };
32
+ level: {
33
+ detailFields: {
34
+ key: string;
35
+ label: string;
36
+ component: string;
37
+ }[];
38
+ };
39
+ world: {
40
+ detailFields: {
41
+ key: string;
42
+ label: string;
43
+ component: string;
44
+ }[];
45
+ };
46
+ timeline: {
47
+ detailFields: {
48
+ key: string;
49
+ label: string;
50
+ component: string;
51
+ }[];
52
+ };
53
+ outline: {
54
+ detailFields: {
55
+ key: string;
56
+ label: string;
57
+ component: string;
58
+ }[];
59
+ };
60
+ };
61
+ export declare const DEFAULT_SKILLS: {
62
+ section: string;
63
+ agentKind: string;
64
+ title: string;
65
+ body: string;
66
+ }[];
@@ -0,0 +1,12 @@
1
+ /** Entity domains aligned with Noveel registry names. */
2
+ export declare const ENTITY_DOMAINS: readonly ["role", "roleRelation", "roleTimeline", "creature", "creatureTimeline", "item", "itemTimeline", "level", "world", "worldTimeline", "timeline", "outline", "chapter"];
3
+ export type EntityDomain = (typeof ENTITY_DOMAINS)[number];
4
+ export declare const DOMAIN_LABELS: Record<EntityDomain | 'overview' | 'skill' | 'info', string>;
5
+ /** Conversation「内容」Tab 内的竖向分区;基本信息并入概述。模板为内置,不展示。 */
6
+ export declare const CONTENT_SECTIONS: readonly ["overview", "world", "role", "creature", "item", "level", "timeline", "outline", "chapter", "skill"];
7
+ export type ContentSection = (typeof CONTENT_SECTIONS)[number];
8
+ export declare const TREE_SECTIONS: Set<"role" | "creature" | "item" | "level" | "world" | "timeline" | "outline" | "chapter" | "overview" | "skill">;
9
+ export declare const LIST_SECTIONS: Set<"role" | "creature" | "item" | "level" | "world" | "timeline" | "outline" | "chapter" | "overview" | "skill">;
10
+ export declare const PANEL_TABS: readonly ["overview", "world", "role", "creature", "item", "level", "timeline", "outline", "chapter", "skill"];
11
+ export type PanelTab = ContentSection;
12
+ export declare function isEntityDomain(value: string): value is EntityDomain;
@@ -0,0 +1,58 @@
1
+ import type { Context } from '@deepseek-ai/cordis';
2
+ import { type SkillRecord } from './store.ts';
3
+ export declare const NOVEL_CREATE_SKILL_NAME = "novel-create";
4
+ export declare const MODULE_SKILL_SPECS: readonly [{
5
+ readonly name: "novel-overview";
6
+ readonly root: "概述";
7
+ readonly description: "概述与开书:标题、卖点、种子。";
8
+ }, {
9
+ readonly name: "novel-world";
10
+ readonly root: "世界";
11
+ readonly description: "世界规则、势力与伏笔。";
12
+ }, {
13
+ readonly name: "novel-role";
14
+ readonly root: "人物";
15
+ readonly description: "人物档案、塑造与关系。";
16
+ }, {
17
+ readonly name: "novel-creature";
18
+ readonly root: "生物";
19
+ readonly description: "生物与灵植档案。";
20
+ }, {
21
+ readonly name: "novel-item";
22
+ readonly root: "物品";
23
+ readonly description: "物品档案与归属。";
24
+ }, {
25
+ readonly name: "novel-level";
26
+ readonly root: "等级";
27
+ readonly description: "等级、能力与代价。";
28
+ }, {
29
+ readonly name: "novel-timeline";
30
+ readonly root: "时间线";
31
+ readonly description: "事件轴与状态同步。";
32
+ }, {
33
+ readonly name: "novel-outline";
34
+ readonly root: "大纲";
35
+ readonly description: "大纲结构与剧情骨架。";
36
+ }, {
37
+ readonly name: "novel-chapter";
38
+ readonly root: "章节";
39
+ readonly description: "章节节奏、对话与质检。";
40
+ }, {
41
+ readonly name: "novel-review";
42
+ readonly root: "审查";
43
+ readonly description: "大纲与章节审查。";
44
+ }];
45
+ export type ModuleSkillName = (typeof MODULE_SKILL_SPECS)[number]['name'];
46
+ export declare const NOVEL_CREATE_CONTENT: string;
47
+ export declare function moduleSkillInvocation(name: ModuleSkillName): {
48
+ modelInvocable: true;
49
+ userInvocable: boolean;
50
+ };
51
+ /** Filter DB skill rows that belong to one DSH module skill. */
52
+ export declare function rowsForModule(rows: SkillRecord[], name: ModuleSkillName): SkillRecord[];
53
+ /** Module skills that currently have at least one row. */
54
+ export declare function moduleSkillsFromRows(rows: SkillRecord[]): ModuleSkillName[];
55
+ export declare function composeModuleSkillBody(rows: SkillRecord[], name: ModuleSkillName): string;
56
+ export declare function folderMatchesModule(folder: string, name: ModuleSkillName): boolean;
57
+ /** Register /novel-create and cwd-scoped module skills from the novel database. */
58
+ export declare function registerNovelSkills(ctx: Context): void;
@@ -0,0 +1,5 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ import { NOVEL_API_PREFIX } from './contract.ts';
3
+ /** Same-origin JSON API for the novel client and tools. */
4
+ export declare function handleNovelApi(req: IncomingMessage, res: ServerResponse, expectedOrigin: string): Promise<void>;
5
+ export { NOVEL_API_PREFIX };
@@ -0,0 +1,18 @@
1
+ import type { EntityDomain } from './domains.ts';
2
+ export declare function asRecord(value: unknown): Record<string, unknown>;
3
+ /** Merge nested `profile` onto the root; existing top-level keys win. */
4
+ export declare function hoistProfile(data: Record<string, unknown>): Record<string, unknown>;
5
+ /** Merge outline `extra` onto the root; existing top-level keys win. */
6
+ export declare function hoistOutlineExtra(data: Record<string, unknown>): Record<string, unknown>;
7
+ /** Shape used by the content tab: hoist profile/extra so template keys resolve. */
8
+ export declare function displayEntityData(data: Record<string, unknown>): Record<string, unknown>;
9
+ export declare function levelNameOf(data: Record<string, unknown>): string | undefined;
10
+ export declare function pickFirstString(data: Record<string, unknown>, keys: readonly string[]): string | undefined;
11
+ export interface NormalizeLookups {
12
+ worldIdByName: Map<string, string>;
13
+ roleIdByRef: Map<string, string>;
14
+ outlineIdByName: Map<string, string>;
15
+ outlineChapterIdByName: Map<string, string>;
16
+ }
17
+ /** Canonicalize mutate payloads so the content tab can read template keys. */
18
+ export declare function normalizeEntityPayload(domain: EntityDomain, raw: Record<string, unknown>, lookups: NormalizeLookups): Record<string, unknown>;