@musnows/scriverse 1.0.5 → 1.0.7

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.
@@ -5393,6 +5393,26 @@ function addSelectedLinesAsCitation() {
5393
5393
  toast(`已引用《${citation.chapterTitle}》第 ${citation.startLine}${citation.startLine === citation.endLine ? "" : `-${citation.endLine}`} 行`);
5394
5394
  }
5395
5395
 
5396
+ function addChapterAsAiReference(chapterId) {
5397
+ if (!state.work || !canWritePermissionModule(state.work, "ai-chat")) return toast("当前账户没有创作助手编辑权限", "error");
5398
+ const volume = state.work.volumes.find((item) => item.chapters.some((chapter) => chapter.id === chapterId));
5399
+ const chapter = volume?.chapters.find((item) => item.id === chapterId);
5400
+ if (!volume || !chapter) return toast("章节不存在或已被删除", "error");
5401
+ const reference = { kind: "chapter", id: String(chapter.id), name: `${volume.title} / ${chapter.title}` };
5402
+ if (state.aiReferences.some((item) => aiReferenceKey(item) === aiReferenceKey(reference))) {
5403
+ ensureAiPanelExpanded();
5404
+ renderAiReferences();
5405
+ return toast(`《${chapter.title}》已在助手引用中`);
5406
+ }
5407
+ const chapterReferenceCount = state.aiReferences.filter((item) => item.kind === "chapter").length;
5408
+ if (chapterReferenceCount >= 20) return toast("一次最多添加 20 个章节助手引用", "error");
5409
+ state.aiReferences.push(reference);
5410
+ ensureAiPanelExpanded();
5411
+ renderAiReferences();
5412
+ persistActiveAiChatTab();
5413
+ toast(`已将《${chapter.title}》添加到助手引用`);
5414
+ }
5415
+
5396
5416
  async function createSelectedLineAnnotation(kind) {
5397
5417
  const permissionModule = kind === "todo" ? "todos" : "comments";
5398
5418
  if (!state.chapter || !chapterLineSelection || !canWritePermissionModule(state.work, permissionModule)) return;
@@ -5909,6 +5929,7 @@ function createClientError(payload, fallbackMessage, fallbackStatus = null) {
5909
5929
  const error = new Error(typeof source.message === "string" ? source.message : fallbackMessage);
5910
5930
  error.code = typeof source.code === "string" ? source.code : undefined;
5911
5931
  error.status = Number.isInteger(source.status) ? source.status : fallbackStatus;
5932
+ error.failureOrigin = source.failureOrigin === "platform" || source.failureOrigin === "provider" ? source.failureOrigin : undefined;
5912
5933
  error.details = source.details;
5913
5934
  error.failure = typeof source.failure === "string" ? source.failure : undefined;
5914
5935
  error.callId = typeof source.callId === "string" ? source.callId : undefined;
@@ -5946,21 +5967,31 @@ function formatAiFailureMessage(error) {
5946
5967
  const providerName = typeof error?.providerName === "string" ? error.providerName : typeof details.providerName === "string" ? details.providerName : "";
5947
5968
  const providerId = typeof error?.providerId === "string" ? error.providerId : typeof details.providerId === "string" ? details.providerId : "";
5948
5969
  const modelId = typeof error?.modelId === "string" ? error.modelId : typeof details.modelId === "string" ? details.modelId : "";
5970
+ const failureOrigin = aiFailureOrigin(error, details);
5971
+ const providerLabel = providerName || providerId;
5949
5972
  if (code) lines.push(`错误码:${code}`);
5950
- if (status) lines.push(`服务端状态:HTTP ${status}`);
5973
+ lines.push(`错误来源:${failureOrigin === "provider" ? `LLM 供应商${providerLabel ? `(${providerLabel})` : ""}` : "叙界平台"}`);
5974
+ if (status) lines.push(`叙界响应状态:HTTP ${status}`);
5951
5975
  if (details.platformLimited === true) {
5952
5976
  const limitSource = details.limitScope === "provider"
5953
5977
  ? `配置的供应商额度${providerName ? `(${providerName})` : ""}`
5954
5978
  : "单个小说额度";
5955
5979
  lines.push(`叙界平台限制来源:${limitSource}`);
5956
5980
  }
5957
- if (providerName || providerId) lines.push(`模型供应商:${providerName || providerId}`);
5958
- if (modelId) lines.push(`模型 ID:${modelId}`);
5981
+ if (failureOrigin === "platform" && providerLabel) lines.push(`请求模型供应商:${providerLabel}`);
5982
+ if (modelId) lines.push(`请求模型 ID:${modelId}`);
5959
5983
  if (callId) lines.push(`调用 ID:${callId}`);
5960
- if (failure && failure !== message) lines.push(`详细原因:${failure}`);
5984
+ if (failure && failure !== message) lines.push(`${failureOrigin === "provider" ? "LLM 供应商详情" : "平台详情"}:${failure}`);
5961
5985
  return lines.join("\n");
5962
5986
  }
5963
5987
 
5988
+ function aiFailureOrigin(error, details) {
5989
+ if (error?.failureOrigin === "platform" || error?.failureOrigin === "provider") return error.failureOrigin;
5990
+ if (details.failureOrigin === "platform" || details.failureOrigin === "provider") return details.failureOrigin;
5991
+ if (details.platformLimited === true || error?.code !== "AI_CALL_FAILED") return "platform";
5992
+ return "provider";
5993
+ }
5994
+
5964
5995
  function aiFailureMessageMetadata(error) {
5965
5996
  const details = error?.details && typeof error.details === "object" && !Array.isArray(error.details) ? error.details : {};
5966
5997
  return {
@@ -6074,6 +6105,12 @@ function invalidateModuleRequestsAfterMutation(path, method) {
6074
6105
  function applyProductHealthMetadata(health) {
6075
6106
  const version = String(health?.version ?? "").trim();
6076
6107
  const versionLabel = String(health?.versionLabel ?? "").trim();
6108
+ const iconPath = health?.development === true ? "/icon-dev.svg?v=20260910" : "/icon.svg?v=20260712";
6109
+ document.querySelectorAll(".brand-mark").forEach((element) => {
6110
+ element.src = iconPath;
6111
+ });
6112
+ const favicon = document.querySelector('link[rel="icon"]');
6113
+ if (favicon) favicon.href = iconPath;
6077
6114
  document.querySelectorAll("[data-product-footer-version]").forEach((element) => {
6078
6115
  element.textContent = versionLabel || (version ? `v${version}` : "v—");
6079
6116
  });
@@ -8833,10 +8870,17 @@ function renderTree() {
8833
8870
  }
8834
8871
  });
8835
8872
  button.addEventListener("contextmenu", (event) => {
8836
- if (!canEditProse()) return;
8873
+ if (!canEditProse() && !canWritePermissionModule(state.work, "ai-chat")) return;
8837
8874
  event.preventDefault();
8838
8875
  openChapterTypeMenu(button.dataset.chapterId, event.clientX, event.clientY);
8839
8876
  });
8877
+ button.addEventListener("keydown", (event) => {
8878
+ if (!canEditProse() && !canWritePermissionModule(state.work, "ai-chat")) return;
8879
+ if (event.key !== "ContextMenu" && !(event.shiftKey && event.key === "F10")) return;
8880
+ event.preventDefault();
8881
+ const rect = button.getBoundingClientRect();
8882
+ openChapterTypeMenu(button.dataset.chapterId, rect.left, rect.bottom);
8883
+ });
8840
8884
  if (proseEditable) {
8841
8885
  button.addEventListener("dragstart", (event) => {
8842
8886
  event.dataTransfer?.setData("text/plain", button.dataset.chapterId);
@@ -9059,7 +9103,11 @@ function openChapterTypeMenu(chapterId, clientX, clientY) {
9059
9103
  if (!chapter) return;
9060
9104
  state.contextChapterId = chapterId;
9061
9105
  const menu = $("#chapter-type-menu");
9062
- menu.querySelector("strong").textContent = `标记“${chapter.title}”`;
9106
+ const canManageChapter = canEditProse();
9107
+ const canAddAiReference = canWritePermissionModule(state.work, "ai-chat");
9108
+ menu.querySelector("strong").textContent = `操作“${chapter.title}”`;
9109
+ menu.querySelectorAll("[data-chapter-type], [data-delete-chapter]").forEach((button) => button.classList.toggle("hidden", !canManageChapter));
9110
+ menu.querySelector("[data-add-chapter-ai-reference]")?.classList.toggle("hidden", !canAddAiReference);
9063
9111
  menu.querySelectorAll("[data-chapter-type]").forEach((button) => {
9064
9112
  button.classList.toggle("active", button.dataset.chapterType === (chapter.chapterType || "正文"));
9065
9113
  button.setAttribute("aria-checked", String(button.classList.contains("active")));
@@ -13973,7 +14021,7 @@ async function renderBookAiSettings() {
13973
14021
  const remoteMcpStatusText = remoteMcpServers.length > 0
13974
14022
  ? `已验证 ${remoteMcpServers.length} 个远程 MCP Server,共发现 ${remoteMcpToolCount} 个工具。`
13975
14023
  : "尚未配置远程 MCP Server。";
13976
- const maximumAgentToolCallLimit = Math.max(5, Number(settings.agentToolCallLimitMaximum) || 80);
14024
+ const maximumAgentToolCallLimit = Math.max(10, Number(settings.agentToolCallLimitMaximum) || 300);
13977
14025
  const agentTools = new Set(settings.agentTools ?? ["story_index", "read_chapters", "grep", "search_story_entities", "read_character_sections", "search_drafts", "image", "calculate_time"]);
13978
14026
  const dailyTokenQuota = settings.dailyTokenQuota === null ? null : Number(settings.dailyTokenQuota);
13979
14027
  const quotaUsedTokens = Number(usage?.quota?.usedTokens) || 0;
@@ -13995,7 +14043,7 @@ async function renderBookAiSettings() {
13995
14043
  host.innerHTML = `<section class="config-section">${tokenUsageOverviewMarkup(usage, {
13996
14044
  title: "本书 Token 用量",
13997
14045
  description: `仅统计《${state.work.title}》迄今产生的 AI Token 消耗与缓存命中情况。`
13998
- })}</section><section class="config-section"><div class="config-section-header"><div><h2>每日 Token 额度</h2><p>限制本书在后端部署时区(${esc(quotaTimezone)})每个自然日可使用的输入与输出 Token 总量。额度必须设置为大于 0 的整数;低于 10,000 时仅提示风险;达到额度后,新的 AI 请求会等到后端时区的次日零点重置后再执行。</p></div></div><div class="config-inline-save"><label class="checkbox-field config-checkbox-field"><input id="daily-token-quota-enabled" type="checkbox" ${dailyTokenQuota === null ? "" : "checked"}>启用每日额度</label><label class="daily-token-quota-field">每日额度<input id="daily-token-quota" type="number" min="1" max="2000000000" step="1" value="${esc(String(dailyTokenQuota ?? 10000))}" aria-label="本书每日 Token 额度" ${dailyTokenQuota === null ? "disabled" : ""}></label><button id="save-daily-token-quota" class="ghost-button config-save-button" type="button">保存</button></div><p id="daily-token-quota-status" class="usage-measurement-note" role="status">${esc(quotaStatusText)}</p></section><section class="config-section"><div class="config-section-header"><div><h2>本书系统提示词</h2><p>会追加在内置系统提示词和平台全局系统提示词之后,只影响《${esc(state.work.title)}》的 AI 请求。</p></div></div><div class="field-label"><textarea id="work-system-prompt" rows="8" aria-label="本书系统提示词" placeholder="例如:叙事使用第三人称,哥斯拉不得离开地球。">${esc(settings.systemPrompt)}</textarea></div><div class="card-actions"><button id="save-work-system-prompt" class="ghost-button config-save-button" type="button">保存本书提示词</button></div></section><section class="config-section"><div class="config-section-header"><div><h2>人物关系拼音索引</h2><p>平时由系统记录增量任务;“同步增量队列”只处理发生变化的来源,“完整重建索引”会将本书全部正文和设定来源重新排队。</p></div></div><div id="relationship-search-index-status" role="status" aria-live="polite">${relationshipIndexStatusMarkup(relationshipIndex)}</div><div class="relationship-index-actions"><button id="sync-relationship-search-index" class="primary-button config-save-button" type="button">同步增量队列</button><button id="refresh-relationship-search-index" class="ghost-button" type="button">刷新状态</button><button id="rebuild-relationship-search-index" class="ghost-button config-save-button" type="button">完整重建索引</button></div></section><section class="config-section"><div class="config-section-header"><div><h2>全书概要引用配额</h2><p>引用全书概要时按分卷保留覆盖,并优先加入与当前问题相关的章节概要;该比例控制概要可使用的上下文预算。</p></div></div><div class="config-inline-save"><label class="book-summary-context-percent-field">上下文占比(%)<input id="book-summary-context-percent" type="number" min="1" max="90" value="${esc(String(settings.bookSummaryContextPercent ?? 50))}" aria-label="全书概要引用上下文占比"></label><button id="save-book-summary-context-percent" class="ghost-button config-save-button" type="button">保存</button></div></section><section class="config-section"><div class="config-section-header"><div><h2>对话上下文 Compact</h2><p>该阈值按对话历史的独立预算计算,用于显示可选择压缩或忽略的提醒;整次请求达到模型上下文窗口 95% 时仍会强制压缩较早消息,并尽量保留最近八条原文。</p></div></div><div class="config-inline-save"><label class="context-compact-threshold-field">Compact 阈值(%)<input id="context-compact-threshold" type="number" min="50" max="90" value="${esc(String(settings.contextCompactThreshold ?? 85))}" aria-label="对话上下文 compact 阈值"></label><button id="save-context-compact-threshold" class="ghost-button config-save-button" type="button">保存</button></div></section><section class="config-section"><div class="config-section-header"><div><h2>设定上下文注入</h2><p>开启后,本书的普通 AI 请求会自动注入锁定设定、组织、种族与相关约束;即使本轮同时使用“@注入上下文设定”,也只会注入一次。</p></div></div><div class="config-inline-save"><label class="checkbox-field config-checkbox-field"><input id="always-include-setting-info" type="checkbox" ${settings.alwaysIncludeSettingInfo ? "checked" : ""}>是否注入设定</label><button id="save-always-include-setting-info" class="ghost-button config-save-button" type="button">保存</button></div></section><section class="config-section"><div class="config-section-header"><div><h2>Agent 工具调用上限</h2><p>限制单次回答里 Agent 可调用工具的次数,并用「全局倍数」给整次回答加一道不会因 Compact 重置的熔断阀,防止工具死循环空耗 Token。调用上限 5–48(默认 12);全局倍数 1–6(默认 3,全局上限 = 调用上限 × 倍数)。<a class="config-doc-link" href="https://scriverse.top/docs/global-tool-call-limit.html" target="_blank" rel="noopener noreferrer">了解原理与推荐设置</a></p></div></div><div class="config-inline-save"><label class="agent-tool-call-limit-field">调用上限<input id="agent-tool-call-limit" type="number" min="5" max="48" value="${esc(String(settings.agentToolCallLimit ?? 12))}" aria-label="Agent 工具调用上限"></label><div class="agent-tool-call-global-multiplier-field"><span id="agent-tool-call-global-multiplier-label">全局倍数</span><div class="settings-layout-toggle agent-tool-call-global-multiplier-toggle" role="group" aria-labelledby="agent-tool-call-global-multiplier-label">${[1, 2, 3, 4, 5, 6].map((value) => `<button type="button" data-global-multiplier="${value}" aria-pressed="${Number(settings.agentToolCallGlobalMultiplier ?? 3) === value}">${value}</button>`).join("")}</div><input id="agent-tool-call-global-multiplier" type="hidden" value="${esc(String(Math.min(6, Math.max(1, Number(settings.agentToolCallGlobalMultiplier ?? 3) || 3))))}" aria-label="Agent 工具调用全局倍数"></div><button id="save-agent-tool-call-limit" class="ghost-button config-save-button" type="button">保存</button></div></section><section class="config-section ai-agent-tools-section"><div class="config-section-header"><div><h2>AI 查询工具</h2><p>工具默认可用,作为已有上下文的补充。关闭后模型不会看到对应能力;所有工具只读且有数量、篇幅与调用轮次限制。已开始的对话会锁定创建时的工具集,修改后仅对新对话生效,避免打断 prompt cache。</p></div></div><div class="ai-agent-tools"><label><input name="agent-tool" type="checkbox" value="story_index" ${agentTools.has("story_index") ? "checked" : ""}><span><strong>作品目录与章节概要</strong><small>分页获取卷章、章节 ID 和当前概要,不返回正文。</small></span></label><label><input name="agent-tool" type="checkbox" value="read_chapters" ${agentTools.has("read_chapters") ? "checked" : ""}><span><strong>读取章节</strong><small>按章节 ID 获取概要或正文,每次最多 3 章。</small></span></label><label><input name="agent-tool" type="checkbox" value="search_story_entities" ${agentTools.has("search_story_entities") ? "checked" : ""}><span><strong>搜索作品实体</strong><small>按实体名、拼音或短关键词混合检索设定、人物、组织、时间线、关系、大纲和伏笔;非语义问答。</small></span></label></div><div class="card-actions"><button id="save-agent-tools" class="ghost-button config-save-button" type="button">保存工具设置</button></div></section>${renderTaskDefaults(models, providers, taskDefaults, settings)}`;
14046
+ })}</section><section class="config-section"><div class="config-section-header"><div><h2>每日 Token 额度</h2><p>限制本书在后端部署时区(${esc(quotaTimezone)})每个自然日可使用的输入与输出 Token 总量。额度必须设置为大于 0 的整数;低于 10,000 时仅提示风险;达到额度后,新的 AI 请求会等到后端时区的次日零点重置后再执行。</p></div></div><div class="config-inline-save"><label class="checkbox-field config-checkbox-field"><input id="daily-token-quota-enabled" type="checkbox" ${dailyTokenQuota === null ? "" : "checked"}>启用每日额度</label><label class="daily-token-quota-field">每日额度<input id="daily-token-quota" type="number" min="1" max="2000000000" step="1" value="${esc(String(dailyTokenQuota ?? 10000))}" aria-label="本书每日 Token 额度" ${dailyTokenQuota === null ? "disabled" : ""}></label><button id="save-daily-token-quota" class="ghost-button config-save-button" type="button">保存</button></div><p id="daily-token-quota-status" class="usage-measurement-note" role="status">${esc(quotaStatusText)}</p></section><section class="config-section"><div class="config-section-header"><div><h2>本书系统提示词</h2><p>会追加在内置系统提示词和平台全局系统提示词之后,只影响《${esc(state.work.title)}》的 AI 请求。</p></div></div><div class="field-label"><textarea id="work-system-prompt" rows="8" aria-label="本书系统提示词" placeholder="例如:叙事使用第三人称,哥斯拉不得离开地球。">${esc(settings.systemPrompt)}</textarea></div><div class="card-actions"><button id="save-work-system-prompt" class="ghost-button config-save-button" type="button">保存本书提示词</button></div></section><section class="config-section"><div class="config-section-header"><div><h2>人物关系拼音索引</h2><p>平时由系统记录增量任务;“同步增量队列”只处理发生变化的来源,“完整重建索引”会将本书全部正文和设定来源重新排队。</p></div></div><div id="relationship-search-index-status" role="status" aria-live="polite">${relationshipIndexStatusMarkup(relationshipIndex)}</div><div class="relationship-index-actions"><button id="sync-relationship-search-index" class="primary-button config-save-button" type="button">同步增量队列</button><button id="refresh-relationship-search-index" class="ghost-button" type="button">刷新状态</button><button id="rebuild-relationship-search-index" class="ghost-button config-save-button" type="button">完整重建索引</button></div></section><section class="config-section"><div class="config-section-header"><div><h2>全书概要引用配额</h2><p>引用全书概要时按分卷保留覆盖,并优先加入与当前问题相关的章节概要;该比例控制概要可使用的上下文预算。</p></div></div><div class="config-inline-save"><label class="book-summary-context-percent-field">上下文占比(%)<input id="book-summary-context-percent" type="number" min="1" max="90" value="${esc(String(settings.bookSummaryContextPercent ?? 50))}" aria-label="全书概要引用上下文占比"></label><button id="save-book-summary-context-percent" class="ghost-button config-save-button" type="button">保存</button></div></section><section class="config-section"><div class="config-section-header"><div><h2>对话上下文 Compact</h2><p>该阈值按对话历史的独立预算计算,用于显示可选择压缩或忽略的提醒;整次请求达到模型上下文窗口 95% 时仍会强制压缩较早消息,并尽量保留最近八条原文。</p></div></div><div class="config-inline-save"><label class="context-compact-threshold-field">Compact 阈值(%)<input id="context-compact-threshold" type="number" min="50" max="90" value="${esc(String(settings.contextCompactThreshold ?? 85))}" aria-label="对话上下文 compact 阈值"></label><button id="save-context-compact-threshold" class="ghost-button config-save-button" type="button">保存</button></div></section><section class="config-section"><div class="config-section-header"><div><h2>设定上下文注入</h2><p>开启后,本书的普通 AI 请求会自动注入锁定设定、组织、种族与相关约束;即使本轮同时使用“@注入上下文设定”,也只会注入一次。</p></div></div><div class="config-inline-save"><label class="checkbox-field config-checkbox-field"><input id="always-include-setting-info" type="checkbox" ${settings.alwaysIncludeSettingInfo ? "checked" : ""}>是否注入设定</label><button id="save-always-include-setting-info" class="ghost-button config-save-button" type="button">保存</button></div></section><section class="config-section"><div class="config-section-header"><div><h2>Agent 工具调用上限</h2><p>限制单次回答里 Agent 可调用工具的次数,并用「全局倍数」给整次回答加一道不会因 Compact 重置的熔断阀,防止工具死循环空耗 Token。调用上限 10–${maximumAgentToolCallLimit}(默认 20);全局倍数 1–6(默认 3,全局上限 = 调用上限 × 倍数)。<a class="config-doc-link" href="https://scriverse.top/docs/global-tool-call-limit.html" target="_blank" rel="noopener noreferrer">了解原理与推荐设置</a></p></div></div><div class="config-inline-save"><label class="agent-tool-call-limit-field">调用上限<input id="agent-tool-call-limit" type="number" min="10" max="${maximumAgentToolCallLimit}" value="${esc(String(settings.agentToolCallLimit ?? 20))}" aria-label="Agent 工具调用上限"></label><div class="agent-tool-call-global-multiplier-field"><span id="agent-tool-call-global-multiplier-label">全局倍数</span><div class="settings-layout-toggle agent-tool-call-global-multiplier-toggle" role="group" aria-labelledby="agent-tool-call-global-multiplier-label">${[1, 2, 3, 4, 5, 6].map((value) => `<button type="button" data-global-multiplier="${value}" aria-pressed="${Number(settings.agentToolCallGlobalMultiplier ?? 3) === value}">${value}</button>`).join("")}</div><input id="agent-tool-call-global-multiplier" type="hidden" value="${esc(String(Math.min(6, Math.max(1, Number(settings.agentToolCallGlobalMultiplier ?? 3) || 3))))}" aria-label="Agent 工具调用全局倍数"></div><button id="save-agent-tool-call-limit" class="ghost-button config-save-button" type="button">保存</button></div></section><section class="config-section ai-agent-tools-section"><div class="config-section-header"><div><h2>AI 查询工具</h2><p>工具默认可用,作为已有上下文的补充。关闭后模型不会看到对应能力;所有工具只读且有数量、篇幅与调用轮次限制。已开始的对话会锁定创建时的工具集,修改后仅对新对话生效,避免打断 prompt cache。</p></div></div><div class="ai-agent-tools"><label><input name="agent-tool" type="checkbox" value="story_index" ${agentTools.has("story_index") ? "checked" : ""}><span><strong>作品目录与章节概要</strong><small>分页获取卷章、章节 ID 和当前概要,不返回正文。</small></span></label><label><input name="agent-tool" type="checkbox" value="read_chapters" ${agentTools.has("read_chapters") ? "checked" : ""}><span><strong>读取章节</strong><small>按章节 ID 获取概要或正文,每次最多 3 章。</small></span></label><label><input name="agent-tool" type="checkbox" value="search_story_entities" ${agentTools.has("search_story_entities") ? "checked" : ""}><span><strong>搜索作品实体</strong><small>按实体名、拼音或短关键词混合检索设定、人物、组织、时间线、关系、大纲和伏笔;非语义问答。</small></span></label></div><div class="card-actions"><button id="save-agent-tools" class="ghost-button config-save-button" type="button">保存工具设置</button></div></section>${renderTaskDefaults(models, providers, taskDefaults, settings)}`;
13999
14047
  const workSystemPromptSection = host.querySelector("#work-system-prompt")?.closest(".config-section");
14000
14048
  workSystemPromptSection?.insertAdjacentHTML("afterend", `<section class="config-section remote-mcp-settings"><div class="config-section-header"><div><h2>远程 MCP 工具</h2><p>填写标准的 <code>mcpServers</code> JSON 配置。保存前会逐个检查 JSON、远程传输、安全地址、MCP 握手与工具列表;任一 Server 失败时都不会覆盖当前配置。</p></div></div><label class="field-label remote-mcp-config-field"><span>mcpServers JSON</span><textarea id="remote-mcp-config" rows="12" spellcheck="false" autocapitalize="off" autocomplete="off" aria-describedby="remote-mcp-config-help remote-mcp-status" placeholder='{"mcpServers":{"example":{"url":"https://example.com/mcp"}}}'>${esc(remoteMcpConfigText)}</textarea></label><small id="remote-mcp-config-help" class="remote-mcp-config-help">仅支持远程 MCP 工具(SSE / Streamable HTTP),不支持会执行本地命令的 stdio 配置。敏感 Header 会加密保存,页面中的 ${esc("********")} 掩码再次保存时会保留原值。</small><p id="remote-mcp-status" class="remote-mcp-status" role="status" aria-live="polite">${esc(remoteMcpStatusText)}</p><div class="card-actions"><button id="save-remote-mcp-config" class="ghost-button config-save-button" type="button">测试并保存 MCP 配置</button></div></section>`);
14001
14049
  const semanticModelOptions = (kind, selectedId) => semanticModels
@@ -14035,10 +14083,6 @@ async function renderBookAiSettings() {
14035
14083
  };
14036
14084
  configureTokenQuotaInput(host.querySelector("#daily-token-quota"), "daily-token-quota-warning", 10_000);
14037
14085
  configureTokenQuotaInput(host.querySelector("#monthly-token-quota"), "monthly-token-quota-warning", 1_000_000);
14038
- const agentToolCallLimitInput = host.querySelector("#agent-tool-call-limit");
14039
- agentToolCallLimitInput?.setAttribute("max", String(maximumAgentToolCallLimit));
14040
- const agentToolCallDescription = agentToolCallLimitInput?.closest(".config-section")?.querySelector(".config-section-header p");
14041
- if (agentToolCallDescription?.firstChild) agentToolCallDescription.firstChild.nodeValue = agentToolCallDescription.firstChild.nodeValue.replace("5–48", `5–${maximumAgentToolCallLimit}`);
14042
14086
  host.querySelectorAll(".config-section").forEach((section) => {
14043
14087
  if (section.querySelector("h2")?.textContent === "Agent 工具调用上限") section.id = "agent-tool-call-limit-settings";
14044
14088
  });
@@ -14398,9 +14442,9 @@ async function renderBookAiSettings() {
14398
14442
  const button = $("#save-agent-tool-call-limit");
14399
14443
  const input = $("#agent-tool-call-limit");
14400
14444
  const value = Number(input.value);
14401
- const maximum = Number(input.max) || 80;
14402
- if (!Number.isInteger(value) || value < 5) {
14403
- toast(`Agent 工具调用上限必须是 5 到 ${maximum} 之间的整数`, "error");
14445
+ const maximum = Number(input.max) || 300;
14446
+ if (!Number.isInteger(value) || value < 10) {
14447
+ toast(`Agent 工具调用上限必须是 10 到 ${maximum} 之间的整数`, "error");
14404
14448
  input.focus();
14405
14449
  return;
14406
14450
  }
@@ -21051,6 +21095,13 @@ $("#cover-file").addEventListener("change", async (event) => {
21051
21095
  }
21052
21096
  });
21053
21097
  $("#chapter-type-menu").addEventListener("click", async (event) => {
21098
+ const aiReferenceButton = event.target.closest("[data-add-chapter-ai-reference]");
21099
+ if (aiReferenceButton) {
21100
+ const chapterId = state.contextChapterId;
21101
+ closeChapterTypeMenu();
21102
+ if (chapterId) addChapterAsAiReference(chapterId);
21103
+ return;
21104
+ }
21054
21105
  const deleteButton = event.target.closest("[data-delete-chapter]");
21055
21106
  if (deleteButton) {
21056
21107
  const chapterId = state.contextChapterId;
@@ -0,0 +1,10 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-labelledby="title description">
2
+ <title id="title">叙界</title>
3
+ <desc id="description">一本展开的书与一颗星,代表小说创作与灵感</desc>
4
+ <rect width="64" height="64" rx="14" fill="#bb911a"/>
5
+ <path d="M10 16.5c8.7-1.6 15.7.4 21 6v29c-5.4-4.8-12.4-6.5-21-5.1V16.5Z" fill="#fffaf0"/>
6
+ <path d="M54 16.5c-8.7-1.6-15.7.4-21 6v29c5.4-4.8 12.4-6.5 21-5.1V16.5Z" fill="#fffaf0"/>
7
+ <path d="M31 22.5h2V52h-2z" fill="#c79b32"/>
8
+ <path d="m48 9.5 1.7 4.1 4.3 1.7-4.3 1.7-1.7 4.1-1.7-4.1-4.3-1.7 4.3-1.7L48 9.5Z" fill="#ffe6a0"/>
9
+ <path d="M15.5 25.5c4.2-.3 7.8.6 10.8 2.7M15.5 32c4.2-.3 7.8.6 10.8 2.7M48.5 25.5c-4.2-.3-7.8.6-10.8 2.7M48.5 32c-4.2-.3-7.8.6-10.8 2.7" fill="none" stroke="#b69048" stroke-linecap="round" stroke-width="2"/>
10
+ </svg>
@@ -522,6 +522,7 @@
522
522
  <button type="button" role="menuitemradio" data-chapter-type="设定">设定</button>
523
523
  <button type="button" role="menuitemradio" data-chapter-type="作者的话">作者的话</button>
524
524
  <button type="button" role="menuitemradio" data-chapter-type="其他">其他</button>
525
+ <button type="button" role="menuitem" data-add-chapter-ai-reference>添加到助手引用</button>
525
526
  <button class="danger-button" type="button" role="menuitem" data-delete-chapter>删除章节</button>
526
527
  </div>
527
528
  </div>
@@ -1425,6 +1426,6 @@
1425
1426
  </dialog>
1426
1427
 
1427
1428
  <div id="auth-loading" class="auth-loading" role="status" aria-label="正在载入工作台"></div>
1428
- <script type="module" src="/app.js?v=20260816-extended-thinking-effort-v1&feature=ai-history-rename-v1&feature=ai-tool-call-copy-feedback-v2&feature=ai-send-control-v2&feature=analysis-task-queue-refresh-v1&feature=character-gender-v1&feature=character-filter-state-v1&feature=relationship-canvas-scale-v1&feature=relationship-table-scroll-v1&feature=ai-session-id-copy-v2&feature=galaxy-motion-mode-v3&feature=galaxy-edge-label-threshold-v1&feature=calculate-time-tool-v2&feature=analysis-task-expired-toast-v1&feature=global-replace-volume-v1&feature=chapter-search-replace-v1&feature=chapter-save-toast-v1&feature=character-relationship-delete-v1&feature=character-relationship-group-v1&feature=analysis-task-stability-delay-v1&feature=ai-assistant-workspace-v2&feature=volume-detail-icon-v1&feature=volume-story-order-v1&feature=reader-manual-chapter-navigation-v1&feature=ai-message-reference-badges-v1&feature=ai-roleplay-message-reference-v1&feature=ai-message-actions-v1&feature=assistant-responsive-navigation-v3&feature=annotation-permissions-v1&feature=annotation-line-counts-v1&feature=ai-relationship-roleplay-v1&feature=ai-roleplay-user-character-visibility-v1&feature=ai-roleplay-story-recall-v1&feature=ai-model-picker-v1&feature=ai-fork-model-unlock-v1&feature=context-percent-format-v1&feature=annotation-precise-locate-v1&feature=markdown-word-count-stable-v1&feature=ai-stream-character-count-stable-v2&feature=ai-process-empty-intermediate-v1&feature=ai-feed-scroll-follow-v2&feature=ai-message-retry-v1&feature=ai-stream-idle-timeout-v2&feature=ai-config-delete-v1&feature=ai-provider-protocol-options-v1&feature=ai-provider-thinking-type-v1&feature=ai-chat-image-attachments-v8&feature=ai-message-image-preview-v1&feature=ai-thinking-scroll-v2&feature=ai-image-conversation-model-lock-v1&feature=toast-click-dismiss-v2&feature=system-restart-dialog-delay-v1&feature=character-avatar-v6&feature=character-death-position-v1&feature=admin-ai-conversations-v1&feature=ai-usage-pricing-v1&feature=ai-usage-pricing-badge-v2&feature=ai-usage-pricing-label-v1&feature=ai-usage-token-breakdown-v1&feature=ai-usage-pricing-cache-v2&feature=ai-usage-pricing-manual-refresh-v1&feature=ai-monthly-token-quota-v1&feature=ai-provider-token-quota-v1&feature=ai-token-quota-positive-v6&feature=ai-model-thinking-label-v1&feature=ai-model-picker-focus-v1&feature=ai-provider-model-import-v1&feature=ai-assistant-brain-icon-v1&feature=ai-token-usage-details-v1&feature=ai-token-usage-details-centered-v1&feature=ai-token-usage-raw-input-v1&feature=ai-stream-connection-seconds-v1&feature=phone-client-entry-v1&feature=ai-usage-pricing-cache-v1&feature=ai-model-thinking-label-v3&feature=ai-roleplay-speaker-label-v1&feature=toast-modal-host-v1&feature=api-key-copy-existing-v1&feature=character-favorite-v1&feature=record-favorites-v1&feature=ai-token-usage-estimated-price-v1&feature=entity-detail-favorites-v1&feature=entity-pin-v1&feature=entity-pin-icon-v1&feature=roleplay-favorite-label-v1&feature=ai-roleplay-knowledge-tools-v1&feature=character-persona-summary-v1&feature=ai-roleplay-scene-turn-v2&feature=admin-account-identity-v2&feature=ai-provider-analysis-timeout-v1&feature=task-detail-failure-orange-v1&feature=book-import-progress-v1&feature=presence-multiple-users-v1&feature=ai-context-input-output-v1&feature=editor-blank-lines-preserved-v1&feature=reader-first-frame-v1&feature=vditor-lazy-load-v1&feature=ui-module-preload-v1&feature=reader-initial-prefetch-v1&feature=editor-preview-toggle-v2&feature=vditor-fullscreen-disabled-v1&feature=chapter-auto-indent-v1&feature=chapter-centered-scroll-v1&feature=work-editor-preferences-v1&feature=ai-context-output-usage-v1&feature=ai-roleplay-memory-v4&feature=ai-roleplay-memory-v5&feature=roleplay-memory-header-actions-v1&feature=roleplay-memory-header-toolbar-v1&feature=roleplay-memory-action-icons-v1&feature=roleplay-memory-action-colors-v1&feature=annotation-line-anchor-v1&feature=stable-line-ids-v1&feature=live-annotation-anchors-v1&feature=chapter-comment-filters-v1&feature=ai-write-tools-v3&feature=ai-question-option-supplement-v1&feature=ai-question-selection-highlight-v1&feature=ai-question-continuation-ui-v3&feature=ai-background-stream-v1&feature=ai-question-tool-result-v1&feature=ai-question-tool-summary-v1&feature=ai-question-submit-guidance-v1&feature=ai-question-batch-v1&feature=ai-question-answer-limit-v1&feature=semantic-search-v6&feature=chapter-title-renumber-v1&feature=ai-writing-skills-v3&feature=ai-cancel-preserve-process-v2&feature=remote-mcp-v1&feature=character-alias-chips-v2&feature=character-title-input-v1&feature=character-detail-value-wrap-v2&feature=chapter-batch-editor-refresh-v1&feature=ai-usage-stat-display-v1&feature=ai-usage-year-v1&feature=semantic-search-rag-label-v1&feature=ai-skill-slash-menu-v1&feature=ai-roleplay-scene-bubble-v1&feature=ai-roleplay-scene-collapse-v1&feature=markdown-adjacent-blockquotes-v1&feature=chapter-save-shortcut-v2&feature=ai-message-citation-popover-v1&feature=line-citation-menu-separator-v1&feature=global-im-v106&feature=im-sidebar-compact-v1&feature=ai-fork-progress-toast-v1&feature=im-settings-gear-v1&feature=compact-sidebar-directory-v2&feature=chapter-word-count-consistency-v1&feature=continuation-guard-failure-details-v1&feature=ai-all-message-references-v2&feature=ai-question-render-recovery-v3&feature=ai-suggestion-editor-boundary-v1&feature=setting-category-preservation-v1&feature=toast-stack-v1&feature=toast-stack-label-v2"></script>
1429
+ <script type="module" src="/app.js?v=20260816-extended-thinking-effort-v1&feature=ai-history-rename-v1&feature=ai-tool-call-copy-feedback-v2&feature=ai-send-control-v2&feature=analysis-task-queue-refresh-v1&feature=character-gender-v1&feature=character-filter-state-v1&feature=relationship-canvas-scale-v1&feature=relationship-table-scroll-v1&feature=ai-session-id-copy-v2&feature=galaxy-motion-mode-v3&feature=galaxy-edge-label-threshold-v1&feature=calculate-time-tool-v2&feature=analysis-task-expired-toast-v1&feature=global-replace-volume-v1&feature=chapter-search-replace-v1&feature=chapter-save-toast-v1&feature=character-relationship-delete-v1&feature=character-relationship-group-v1&feature=analysis-task-stability-delay-v1&feature=ai-assistant-workspace-v2&feature=volume-detail-icon-v1&feature=volume-story-order-v1&feature=reader-manual-chapter-navigation-v1&feature=ai-message-reference-badges-v1&feature=ai-roleplay-message-reference-v1&feature=ai-message-actions-v1&feature=assistant-responsive-navigation-v3&feature=annotation-permissions-v1&feature=annotation-line-counts-v1&feature=ai-relationship-roleplay-v1&feature=ai-roleplay-user-character-visibility-v1&feature=ai-roleplay-story-recall-v1&feature=ai-model-picker-v1&feature=ai-fork-model-unlock-v1&feature=context-percent-format-v1&feature=annotation-precise-locate-v1&feature=markdown-word-count-stable-v1&feature=ai-stream-character-count-stable-v2&feature=ai-process-empty-intermediate-v1&feature=ai-feed-scroll-follow-v2&feature=ai-message-retry-v1&feature=ai-stream-idle-timeout-v2&feature=ai-config-delete-v1&feature=ai-provider-protocol-options-v1&feature=ai-provider-thinking-type-v1&feature=ai-chat-image-attachments-v8&feature=ai-message-image-preview-v1&feature=ai-thinking-scroll-v2&feature=ai-image-conversation-model-lock-v1&feature=toast-click-dismiss-v2&feature=system-restart-dialog-delay-v1&feature=character-avatar-v6&feature=character-death-position-v1&feature=admin-ai-conversations-v1&feature=ai-usage-pricing-v1&feature=ai-usage-pricing-badge-v2&feature=ai-usage-pricing-label-v1&feature=ai-usage-token-breakdown-v1&feature=ai-usage-pricing-cache-v2&feature=ai-usage-pricing-manual-refresh-v1&feature=ai-monthly-token-quota-v1&feature=ai-provider-token-quota-v1&feature=ai-token-quota-positive-v6&feature=ai-model-thinking-label-v1&feature=ai-model-picker-focus-v1&feature=ai-provider-model-import-v1&feature=ai-assistant-brain-icon-v1&feature=ai-token-usage-details-v1&feature=ai-token-usage-details-centered-v1&feature=ai-token-usage-raw-input-v1&feature=ai-stream-connection-seconds-v1&feature=phone-client-entry-v1&feature=ai-usage-pricing-cache-v1&feature=ai-model-thinking-label-v3&feature=ai-roleplay-speaker-label-v1&feature=toast-modal-host-v1&feature=api-key-copy-existing-v1&feature=character-favorite-v1&feature=record-favorites-v1&feature=ai-token-usage-estimated-price-v1&feature=entity-detail-favorites-v1&feature=entity-pin-v1&feature=entity-pin-icon-v1&feature=roleplay-favorite-label-v1&feature=ai-roleplay-knowledge-tools-v1&feature=character-persona-summary-v1&feature=ai-roleplay-scene-turn-v2&feature=admin-account-identity-v2&feature=ai-provider-analysis-timeout-v1&feature=task-detail-failure-orange-v1&feature=book-import-progress-v1&feature=presence-multiple-users-v1&feature=ai-context-input-output-v1&feature=editor-blank-lines-preserved-v1&feature=reader-first-frame-v1&feature=vditor-lazy-load-v1&feature=ui-module-preload-v1&feature=reader-initial-prefetch-v1&feature=editor-preview-toggle-v2&feature=vditor-fullscreen-disabled-v1&feature=chapter-auto-indent-v1&feature=chapter-centered-scroll-v1&feature=work-editor-preferences-v1&feature=ai-context-output-usage-v1&feature=ai-roleplay-memory-v4&feature=ai-roleplay-memory-v5&feature=roleplay-memory-header-actions-v1&feature=roleplay-memory-header-toolbar-v1&feature=roleplay-memory-action-icons-v1&feature=roleplay-memory-action-colors-v1&feature=annotation-line-anchor-v1&feature=stable-line-ids-v1&feature=live-annotation-anchors-v1&feature=chapter-comment-filters-v1&feature=ai-write-tools-v3&feature=ai-question-option-supplement-v1&feature=ai-question-selection-highlight-v1&feature=ai-question-continuation-ui-v3&feature=ai-background-stream-v1&feature=ai-question-tool-result-v1&feature=ai-question-tool-summary-v1&feature=ai-question-submit-guidance-v1&feature=ai-question-batch-v1&feature=ai-question-answer-limit-v1&feature=semantic-search-v6&feature=chapter-title-renumber-v1&feature=ai-writing-skills-v3&feature=ai-cancel-preserve-process-v2&feature=remote-mcp-v1&feature=character-alias-chips-v2&feature=character-title-input-v1&feature=character-detail-value-wrap-v2&feature=chapter-batch-editor-refresh-v1&feature=ai-usage-stat-display-v1&feature=ai-usage-year-v1&feature=semantic-search-rag-label-v1&feature=ai-skill-slash-menu-v1&feature=ai-roleplay-scene-bubble-v1&feature=ai-roleplay-scene-collapse-v1&feature=markdown-adjacent-blockquotes-v1&feature=chapter-save-shortcut-v2&feature=ai-message-citation-popover-v1&feature=line-citation-menu-separator-v1&feature=global-im-v106&feature=im-sidebar-compact-v1&feature=ai-fork-progress-toast-v1&feature=im-settings-gear-v1&feature=compact-sidebar-directory-v2&feature=chapter-word-count-consistency-v1&feature=continuation-guard-failure-details-v1&feature=ai-all-message-references-v2&feature=ai-question-render-recovery-v3&feature=ai-suggestion-editor-boundary-v1&feature=setting-category-preservation-v1&feature=toast-stack-v1&feature=toast-stack-label-v2&feature=agent-tool-limits-300-v1&feature=server-dev-logo-v1&feature=chapter-directory-ai-reference-v2&feature=ai-error-origin-v1"></script>
1429
1430
  </body>
1430
1431
  </html>
package/dist/store.js CHANGED
@@ -11,7 +11,7 @@ import { currentRequestActor } from "./request-context.js";
11
11
  import { canReadWorkModule, canWriteWorkModule, classifyWorkModulePermissions, emptyWorkModulePermissions, fullWorkModulePermissions, storedWorkModulePermissions } from "./work-permissions.js";
12
12
  import { countWords, documentShortSearchTerms, escapeSqlLikePattern, id, json, normalizeDocumentSearchText, now, splitDocumentParagraphs } from "./utils.js";
13
13
  import { buildWritingCalendar, writingDateKey } from "./writing-progress-time.js";
14
- import { resolveMaxAgentToolCallLimit } from "./ai-tool-results.js";
14
+ import { DEFAULT_AGENT_TOOL_CALL_LIMIT, MIN_AGENT_TOOL_CALL_LIMIT, resolveMaxAgentToolCallLimit } from "./ai-tool-results.js";
15
15
  import { DEFAULT_AI_STREAM_IDLE_TIMEOUT_SECONDS, normalizeAiStreamIdleTimeoutSeconds } from "./ai-stream-timeout.js";
16
16
  import { normalizeRoleplayScenePin, roleplayUserTurnDisplayText, roleplayUserTurnTitleSource } from "./roleplay-turn.js";
17
17
  import { chapterAnnotationLineHashes, createChapterLineIds, MAX_CHAPTER_LINE_IDS, parseChapterAnnotationLineIds, parseChapterAnnotationLineHashes, parseChapterLineIds, reconcileChapterLineIds, reanchorChapterAnnotations } from "./chapter-annotation-anchor.js";
@@ -1104,7 +1104,7 @@ export class Store {
1104
1104
  bookSummaryContextPercent: Math.min(90, Math.max(1, Number(row?.book_summary_context_percent ?? 50) || 50)),
1105
1105
  contextCompactThreshold: Math.min(90, Math.max(50, Number(row?.context_compact_threshold ?? 85) || 85)),
1106
1106
  agentToolCallLimitMaximum: maximumAgentToolCallLimit,
1107
- agentToolCallLimit: Math.min(maximumAgentToolCallLimit, Math.max(5, Number(row?.agent_tool_call_limit ?? 12) || 12)),
1107
+ agentToolCallLimit: Math.min(maximumAgentToolCallLimit, Math.max(MIN_AGENT_TOOL_CALL_LIMIT, Number(row?.agent_tool_call_limit ?? DEFAULT_AGENT_TOOL_CALL_LIMIT) || DEFAULT_AGENT_TOOL_CALL_LIMIT)),
1108
1108
  agentToolCallGlobalMultiplier: Math.min(6, Math.max(1, Number(row?.agent_tool_call_global_multiplier ?? 3) || 3)),
1109
1109
  agentTools: normalizeWorkAgentTools(row?.agent_tools_json),
1110
1110
  imageToolModelId: row?.image_tool_model_id === null || row?.image_tool_model_id === undefined
@@ -1190,7 +1190,7 @@ export class Store {
1190
1190
  title_generation_model_id = excluded.title_generation_model_id,
1191
1191
  image_tool_model_id = excluded.image_tool_model_id,
1192
1192
  always_include_setting_info = excluded.always_include_setting_info,
1193
- updated_at = excluded.updated_at`, workId, nextPrompt, nextSystemPromptOverride ? 1 : 0, nextDailyTokenQuota, nextMonthlyTokenQuota, nextEnabled ? 1 : 0, Math.min(8, Math.max(1, nextConcurrency)), Math.min(200, Math.max(1, nextBatchLimit)), Math.min(10_000, Math.max(0, nextDailyTaskLimit)), Math.min(10, Math.max(1, nextFailureThreshold)), Math.min(120, Math.max(1, nextStabilityDelayMinutes)), current.autoRunPaused ? 1 : 0, String(current.autoRunPauseReason ?? ""), current.autoRunResumeAt === null ? null : String(current.autoRunResumeAt), Math.max(0, Number(current.autoRunConsecutiveFailures) || 0), Math.min(90, Math.max(1, nextBookSummaryContextPercent)), Math.min(90, Math.max(50, nextContextCompactThreshold)), Math.min(maximumAgentToolCallLimit, Math.max(5, nextAgentToolCallLimit)), Math.min(6, Math.max(1, nextAgentToolCallGlobalMultiplier)), JSON.stringify(nextAgentTools), nextTitleGenerationModelId, nextImageToolModelId, nextAlwaysIncludeSettingInfo ? 1 : 0, timestamp);
1193
+ updated_at = excluded.updated_at`, workId, nextPrompt, nextSystemPromptOverride ? 1 : 0, nextDailyTokenQuota, nextMonthlyTokenQuota, nextEnabled ? 1 : 0, Math.min(8, Math.max(1, nextConcurrency)), Math.min(200, Math.max(1, nextBatchLimit)), Math.min(10_000, Math.max(0, nextDailyTaskLimit)), Math.min(10, Math.max(1, nextFailureThreshold)), Math.min(120, Math.max(1, nextStabilityDelayMinutes)), current.autoRunPaused ? 1 : 0, String(current.autoRunPauseReason ?? ""), current.autoRunResumeAt === null ? null : String(current.autoRunResumeAt), Math.max(0, Number(current.autoRunConsecutiveFailures) || 0), Math.min(90, Math.max(1, nextBookSummaryContextPercent)), Math.min(90, Math.max(50, nextContextCompactThreshold)), Math.min(maximumAgentToolCallLimit, Math.max(MIN_AGENT_TOOL_CALL_LIMIT, nextAgentToolCallLimit)), Math.min(6, Math.max(1, nextAgentToolCallGlobalMultiplier)), JSON.stringify(nextAgentTools), nextTitleGenerationModelId, nextImageToolModelId, nextAlwaysIncludeSettingInfo ? 1 : 0, timestamp);
1194
1194
  this.audit(workId, "work.ai-settings.updated", "work-ai-settings", workId, {
1195
1195
  systemPromptChanged: input.systemPrompt !== undefined,
1196
1196
  systemPromptOverride: nextSystemPromptOverride,
@@ -1204,7 +1204,7 @@ export class Store {
1204
1204
  autoRunStabilityDelayMinutes: Math.min(120, Math.max(1, nextStabilityDelayMinutes)),
1205
1205
  bookSummaryContextPercent: Math.min(90, Math.max(1, nextBookSummaryContextPercent)),
1206
1206
  contextCompactThreshold: Math.min(90, Math.max(50, nextContextCompactThreshold)),
1207
- agentToolCallLimit: Math.min(maximumAgentToolCallLimit, Math.max(5, nextAgentToolCallLimit)),
1207
+ agentToolCallLimit: Math.min(maximumAgentToolCallLimit, Math.max(MIN_AGENT_TOOL_CALL_LIMIT, nextAgentToolCallLimit)),
1208
1208
  agentToolCallGlobalMultiplier: Math.min(6, Math.max(1, nextAgentToolCallGlobalMultiplier)),
1209
1209
  agentTools: nextAgentTools,
1210
1210
  imageToolModelId: nextImageToolModelId,
@@ -1233,8 +1233,8 @@ export class Store {
1233
1233
  this.db.run(`INSERT INTO work_ai_settings (
1234
1234
  work_id, semantic_search_enabled, semantic_embedding_model_id, semantic_rerank_model_id,
1235
1235
  semantic_vector_dimension, semantic_recall_limit, semantic_result_limit,
1236
- semantic_budget_tokens, semantic_channel_weight, updated_at
1237
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1236
+ semantic_budget_tokens, semantic_channel_weight, updated_at, agent_tool_call_limit
1237
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1238
1238
  ON CONFLICT(work_id) DO UPDATE SET
1239
1239
  semantic_search_enabled = excluded.semantic_search_enabled,
1240
1240
  semantic_embedding_model_id = excluded.semantic_embedding_model_id,
@@ -1244,7 +1244,7 @@ export class Store {
1244
1244
  semantic_result_limit = excluded.semantic_result_limit,
1245
1245
  semantic_budget_tokens = excluded.semantic_budget_tokens,
1246
1246
  semantic_channel_weight = excluded.semantic_channel_weight,
1247
- updated_at = excluded.updated_at`, workId, enabled ? 1 : 0, embeddingModelId, rerankModelId, vectorDimension, recallLimit, resultLimit, budgetTokens, channelWeight, timestamp);
1247
+ updated_at = excluded.updated_at`, workId, enabled ? 1 : 0, embeddingModelId, rerankModelId, vectorDimension, recallLimit, resultLimit, budgetTokens, channelWeight, timestamp, Number(current.agentToolCallLimit));
1248
1248
  this.audit(workId, "semantic.settings.updated", "work-ai-settings", workId, {
1249
1249
  enabled,
1250
1250
  embeddingModelId,