diff --git a/README.md b/README.md index bea638c..d47aea3 100644 --- a/README.md +++ b/README.md @@ -122,9 +122,9 @@ Settings are organized into three tabs: | Tab | Key settings | | --- | --- | -| **Knowledge** | Q&A path, Concepts path, Questions index path, Reading path, auto-classify, concept depth, vault QA source limit | -| **Execution** | Require preview, log path, scheduled tasks, safety policy | -| **Auxiliary** | API key, Base URL, model, output style, Baidu sync, UI preferences | +| **Knowledge** | Q&A path, Concepts path, Questions index path, knowledge index status + rebuild | +| **Execution** | Execution log path (read-only), scheduled tasks status (v2.1) | +| **Auxiliary** | API key, Base URL, model, output style, Baidu sync (App ID/Secret, remote path, auto-backup) | --- @@ -150,12 +150,14 @@ src/ core/ llm/ Unified LLM client + JSON extractor knowledge/ KnowledgeIndexService (metadata index) - execution/ ExecutionPlan, PlanBuilder, PlanExecutor - scheduler/ ScheduledTask types + runner (WIP) + execution/ ExecutionPlan, PlanBuilder, PlanExecutor, PlanDraftStore + artifact/ ExecutionArtifact types, prompt, validation, rendering + scheduler/ ScheduledTask types + runner (disabled in v2.0) schema.ts SCHEMA_VERSION + helpers ai/ AI feature modules (assistant, classifier, planner, ...) features/ assistant/ AI assistant modals + artifact/ Artifact builder, preview, feature controller concept/ Concept completion + page manager question/ Question classify + graph manager reading/ Reading project manager diff --git a/src/core/artifact/ArtifactPromptBuilder.ts b/src/core/artifact/ArtifactPromptBuilder.ts index 8d38c0b..ab1f780 100644 --- a/src/core/artifact/ArtifactPromptBuilder.ts +++ b/src/core/artifact/ArtifactPromptBuilder.ts @@ -20,8 +20,11 @@ const SYSTEM_PROMPT = `你是一个知识到执行资产的结构化转换助手 3. 如果没有明确来源,标记 inferred: true。 4. 高风险或不确定内容设置 riskLevel: "watch" 或 "high"。 5. 不要生成医学诊断、法律结论或投资决策。 -6. 每个条目的 id 使用 "item-1", "item-2" 等顺序编号。 -7. 严格输出 JSON,不要输出解释文本。 +6. 不要编造 sourceLinks。sourceLinks 只能来自"来源内容"中出现过的路径或文档标题。只返回路径或 path|alias,不要包含 [[ ]]。 + 正确:"Reading/Book/Chapter1.md|第1章" + 错误:"[[Reading/Book/Chapter1.md|第1章]]" +7. 每个条目的 id 使用 "item-1", "item-2" 等顺序编号。 +8. 严格输出 JSON,不要输出解释文本。 返回格式: { diff --git a/src/core/artifact/ArtifactRenderer.ts b/src/core/artifact/ArtifactRenderer.ts index f764907..bb748f4 100644 --- a/src/core/artifact/ArtifactRenderer.ts +++ b/src/core/artifact/ArtifactRenderer.ts @@ -26,12 +26,12 @@ export class ArtifactRenderer { `type: execution-artifact-${docType}`, `schema_version: ${SCHEMA_VERSION}`, `artifact_type: ${artifact.artifactType}`, - `title: "${artifact.title}"`, + `title: ${this.yamlStr(artifact.title)}`, `usage_mode: ${artifact.usageMode}`, `source_scope: ${artifact.sourceScope}`, `evidence_policy: ${artifact.evidencePolicy}`, ]; - if (artifact.target) lines.push(`target: "${artifact.target}"`); + if (artifact.target) lines.push(`target: ${this.yamlStr(artifact.target)}`); if (artifact.frequency) lines.push(`frequency: ${artifact.frequency}`); lines.push(`status: draft`); lines.push(`created_at: ${todayIso()}`); @@ -45,7 +45,7 @@ export class ArtifactRenderer { `type: execution-artifact-run`, `schema_version: ${SCHEMA_VERSION}`, `artifact_type: ${artifact.artifactType}`, - `template: "${artifact.title}"`, + `template: ${this.yamlStr(artifact.title)}`, `date: ${date}`, `status: open`, "---", @@ -65,10 +65,15 @@ export class ArtifactRenderer { artifact.target ? `> 对象:${artifact.target}` : null, artifact.frequency ? `> 频率:${artifact.frequency}` : null, artifact.sourceLinks.length > 0 - ? `> 来源:${artifact.sourceLinks.map((l) => `[[${l}]]`).join("、")}` + ? `> 来源:${artifact.sourceLinks.map((l) => this.wikiLink(l)).join("、")}` : null, ].filter(Boolean).join("\n"); + // Safety disclaimer for sensitive domains + const disclaimer = this.needsSafetyDisclaimer(artifact) + ? `\n> [!warning] 使用边界\n> 该执行资产用于记录、观察和复盘,不构成医学、法律或投资建议。出现异常情况请咨询专业人士。\n` + : ""; + // Group items by category const grouped = this.groupByCategory(artifact.items); const sections: string[] = []; @@ -87,7 +92,7 @@ export class ArtifactRenderer { inferredSection = `\n## 未验证项\n\n> [!warning]\n> 以下 ${inferredItems.length} 个条目由 AI 推断生成,未找到明确来源,建议人工确认。\n\n${inferredItems.map((i) => `- ${i.title}`).join("\n")}\n`; } - return `${title}\n\n${meta}\n${sections.join("\n")}${inferredSection}`; + return `${title}\n\n${meta}${disclaimer}\n${sections.join("\n")}${inferredSection}`; } private renderItem(item: ArtifactItem, isRun: boolean): string { @@ -98,7 +103,7 @@ export class ArtifactRenderer { lines.push(`${checkbox} ${item.title}${riskMark}`); if (item.sourceLinks.length > 0) { - lines.push(` - 依据:${item.sourceLinks.map((l) => `[[${l}]]`).join("、")}`); + lines.push(` - 依据:${item.sourceLinks.map((l) => this.wikiLink(l)).join("、")}`); } else if (item.inferred) { lines.push(` - 依据:*AI 推断,建议确认*`); } @@ -120,6 +125,23 @@ export class ArtifactRenderer { return `\n## 今日问题\n\n- \n\n## 明日调整\n\n- \n`; } + /** Safely encode a string for YAML (uses JSON quoting). */ + private yamlStr(value: string): string { + return JSON.stringify(value); + } + + private needsSafetyDisclaimer(artifact: ExecutionArtifact): boolean { + const sensitiveKeywords = [ + "婴儿", "母亲", "健康", "医疗", "诊断", "症状", "用药", "治疗", + "法律", "合同", "诉讼", "投资", "理财", "基金", "股票", + "baby", "infant", "health", "medical", "diagnosis", "legal", "investment", + ]; + const text = `${artifact.title} ${artifact.target ?? ""}`.toLowerCase(); + const hasHighRisk = artifact.items.some((i) => i.riskLevel === "high" || i.riskLevel === "watch"); + const hasSensitiveKeyword = sensitiveKeywords.some((k) => text.includes(k)); + return hasHighRisk || hasSensitiveKeyword; + } + private groupByCategory(items: ArtifactItem[]): Map { const map = new Map(); for (const item of items) { @@ -129,4 +151,13 @@ export class ArtifactRenderer { } return map; } + + /** Ensure a link string becomes a proper [[wikilink]] without double-wrapping. */ + private wikiLink(link: string): string { + const trimmed = link.trim(); + if (trimmed.startsWith("[[") && trimmed.endsWith("]]")) return trimmed; + // Strip accidental [[ ]] if partially wrapped + const cleaned = trimmed.replace(/^\[\[/, "").replace(/\]\]$/, ""); + return `[[${cleaned}]]`; + } } diff --git a/src/features/artifact/ArtifactPreviewModal.ts b/src/features/artifact/ArtifactPreviewModal.ts index cdadc6f..f025dd3 100644 --- a/src/features/artifact/ArtifactPreviewModal.ts +++ b/src/features/artifact/ArtifactPreviewModal.ts @@ -35,10 +35,10 @@ export class ArtifactPreviewModal extends Modal { if (stats.highRisk > 0) statsEl.createSpan({ text: `高关注:${stats.highRisk}`, attr: { style: "color: var(--text-error);" } }); // File impact notice - contentEl.createEl("p", { - text: `保存后将创建 1-2 个文件(模板 + 可选的今日记录)`, - attr: { style: "font-size: 12px; color: var(--text-muted); margin-bottom: 8px;" }, - }); + const safeName = this.artifact.title.replace(/[\\/:*?"<>|#[\]]/g, "-").slice(0, 50); + contentEl.createEl("div", { + attr: { style: "font-size: 12px; color: var(--text-muted); margin-bottom: 8px; padding: 6px 8px; background: var(--background-secondary); border-radius: 4px;" }, + }).innerHTML = `将创建:
Knowledge/Artifacts/${safeName}.md
Knowledge/Artifact Runs/${new Date().toISOString().slice(0, 10)} ${safeName}.md(仅"保存并生成今日记录"时)`; // Warnings if (this.validation.warnings.length > 0) { diff --git a/tsconfig.json b/tsconfig.json index 72d12ec..3a40415 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -17,7 +17,7 @@ "strictFunctionTypes": true, "strictNullChecks": true, "noFallthroughCasesInSwitch": true, - "lib": ["ES6", "DOM"] + "lib": ["ES2018", "DOM"] }, "include": ["src/**/*.ts"] }