@musnows/scriverse 0.5.10 → 0.5.12

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.
package/dist/ai.js CHANGED
@@ -88,7 +88,7 @@ function thinkingParameters(provider, model) {
88
88
  return {};
89
89
  return { thinking: { type: boolValue(model, "thinking_enabled") ? "enabled" : "disabled" } };
90
90
  }
91
- const AGENT_TOOL_IDS = ["story_index", "read_chapters", "grep", "search_story_entities", "read_character_sections"];
91
+ const AGENT_TOOL_IDS = ["story_index", "read_chapters", "grep", "search_story_entities", "read_character_sections", "search_drafts"];
92
92
  function traceRecord(value) {
93
93
  return value && typeof value === "object" && !Array.isArray(value) ? value : {};
94
94
  }
@@ -204,6 +204,11 @@ const readCharacterSectionsArguments = z.object({
204
204
  sectionIds: z.array(z.string().min(1).max(300)).min(1).max(3),
205
205
  include: z.enum(["summary", "content", "both"]).default("both")
206
206
  }).strict();
207
+ const searchDraftsArguments = z.object({
208
+ query: z.string().trim().max(200).default(""),
209
+ draftType: z.enum(["all", "prose", "setting"]).default("all"),
210
+ limit: z.number().int().min(1).max(30).default(20)
211
+ }).strict();
207
212
  const AGENT_TOOL_DEFINITIONS = {
208
213
  story_index: {
209
214
  type: "function",
@@ -244,6 +249,14 @@ const AGENT_TOOL_DEFINITIONS = {
244
249
  description: "读取指定人物 Markdown 档案章节的摘要或原文。先通过 search_story_entities 获取 sectionId;每次最多读取 3 个章节。",
245
250
  parameters: { type: "object", properties: { sectionIds: { type: "array", items: { type: "string" }, minItems: 1, maxItems: 3 }, include: { type: "string", enum: ["summary", "content", "both"] } }, required: ["sectionIds"], additionalProperties: false }
246
251
  }
252
+ },
253
+ search_drafts: {
254
+ type: "function",
255
+ function: {
256
+ name: "search_drafts",
257
+ description: "搜索当前作品的作者草稿。草稿只是用于记录可能采用、也可能永远不会写入正文或正式设定的临时想法,不是已确认的故事事实,不能当作正文或设定依据。可按关键词和“正文草稿/设定草稿”类型筛选;query 为空时返回最近更新的草稿。",
258
+ parameters: { type: "object", properties: { query: { type: "string", maxLength: 200, default: "" }, draftType: { type: "string", enum: ["all", "prose", "setting"], default: "all" }, limit: { type: "integer", minimum: 1, maximum: 30, default: 20 } }, additionalProperties: false }
259
+ }
247
260
  }
248
261
  };
249
262
  export function estimateAiTokens(value) {
@@ -1875,7 +1888,29 @@ export class AiManager {
1875
1888
  const suggestionId = id("suggestion");
1876
1889
  this.store.db.run(`INSERT INTO ai_suggestions (id, call_id, work_id, chapter_id, chapter_version, task_type, instruction,
1877
1890
  source_text, content, action, status, created_at, created_by_user_id) VALUES (?, ?, ?, ?, ?, 'chat', ?, ?, ?, 'note', 'pending', ?, ?)`, suggestionId, generated.callId, input.workId, chapter ? String(chapter.id) : null, chapter ? Number(chapter.versionNo) : null, input.instruction, input.scope.selection ?? "", generated.content, now(), currentRequestActor()?.userId ?? null);
1878
- return { ...this.getSuggestion(suggestionId), outputTokens: generated.outputTokens, ...(generated.cacheHitPercent === undefined ? {} : { cacheHitPercent: generated.cacheHitPercent }), toolCalls: generated.toolCalls, processSteps: generated.processSteps };
1891
+ const modelDisplayName = typeof generated.model.displayName === "string" ? generated.model.displayName : undefined;
1892
+ const conversationMessage = input.conversationId && input.assistantMessageRequestId
1893
+ ? this.store.addAiConversationMessage(input.conversationId, {
1894
+ role: "assistant",
1895
+ content: generated.content,
1896
+ requestId: input.assistantMessageRequestId,
1897
+ metadata: {
1898
+ ...(modelDisplayName ? { modelDisplayName } : {}),
1899
+ outputTokens: generated.outputTokens,
1900
+ ...(generated.cacheHitPercent === undefined ? {} : { cacheHitPercent: generated.cacheHitPercent }),
1901
+ toolCalls: generated.toolCalls,
1902
+ processSteps: generated.processSteps
1903
+ }
1904
+ })
1905
+ : null;
1906
+ return {
1907
+ ...this.getSuggestion(suggestionId),
1908
+ outputTokens: generated.outputTokens,
1909
+ ...(generated.cacheHitPercent === undefined ? {} : { cacheHitPercent: generated.cacheHitPercent }),
1910
+ toolCalls: generated.toolCalls,
1911
+ processSteps: generated.processSteps,
1912
+ ...(conversationMessage ? { conversationMessage } : {})
1913
+ };
1879
1914
  }
1880
1915
  async runSuggestionGuard(suggestionId, candidateContent) {
1881
1916
  const suggestion = this.getSuggestion(suggestionId);
@@ -2404,7 +2439,7 @@ export class AiManager {
2404
2439
  ? [
2405
2440
  `当前可用作品查询工具:${enabledToolIds.join("、")}。`,
2406
2441
  "当作者询问当前作品、项目、章节、情节、人物、关系、世界观或设定,而预加载上下文为空或不足时,必须先调用工具主动查询;不得直接声称没有上下文,也不得先要求作者补充本系统已经能够查询的信息。",
2407
- "整体介绍、作品基本信息、目录或章节定位优先调用 story_index;按关键字定位正文段落时调用 grep;已知章节 ID 且需要原文事实或精确措辞时调用 read_chapters;查找设定、人物、组织、时间线、关系、大纲或伏笔时调用 search_story_entities(可传入短实体名、拼音或关键词,勿用自然语言整句);人物匹配结果包含 sectionId 且需要背景故事、能力或经历原文时调用 read_character_sections",
2442
+ "整体介绍、作品基本信息、目录或章节定位优先调用 story_index;按关键字定位正文段落时调用 grep;已知章节 ID 且需要原文事实或精确措辞时调用 read_chapters;查找设定、人物、组织、时间线、关系、大纲或伏笔时调用 search_story_entities(可传入短实体名、拼音或关键词,勿用自然语言整句);人物匹配结果包含 sectionId 且需要背景故事、能力或经历原文时调用 read_character_sections;作者询问尚未定稿的想法、备选方向或明确提到草稿时调用 search_drafts。草稿可能永远不会进入正文或设定,必须明确标注为未确认想法,不得把它当作故事事实。",
2408
2443
  "根据问题选择最少且必要的工具。工具结果仍不足时才说明未知,并明确已经查询过什么;不要重复无效调用。"
2409
2444
  ].join("\n")
2410
2445
  : "";
@@ -2458,7 +2493,10 @@ export class AiManager {
2458
2493
  const enabled = new Set(this.store.getWorkAiSettings(workId).agentTools
2459
2494
  .filter((item) => typeof item === "string" && AGENT_TOOL_IDS.includes(item)));
2460
2495
  const requested = requestedToolIds ? new Set(requestedToolIds) : null;
2461
- return AGENT_TOOL_IDS.filter((toolId) => enabled.has(toolId) && (!requested || requested.has(toolId)));
2496
+ const permissions = this.store.getWork(workId).modulePermissions;
2497
+ return AGENT_TOOL_IDS.filter((toolId) => enabled.has(toolId)
2498
+ && (!requested || requested.has(toolId))
2499
+ && (toolId !== "search_drafts" || permissions.drafts === "read" || permissions.drafts === "write"));
2462
2500
  }
2463
2501
  enabledAgentTools(workId, taskType, requestedToolIds) {
2464
2502
  return this.enabledAgentToolIds(workId, taskType, requestedToolIds).map((toolId) => AGENT_TOOL_DEFINITIONS[toolId]);
@@ -2490,7 +2528,8 @@ export class AiManager {
2490
2528
  : name === "grep" ? grepArguments
2491
2529
  : name === "search_story_entities" ? searchStoryEntitiesArguments
2492
2530
  : name === "read_character_sections" ? readCharacterSectionsArguments
2493
- : null;
2531
+ : name === "search_drafts" ? searchDraftsArguments
2532
+ : null;
2494
2533
  if (!schema) {
2495
2534
  return {
2496
2535
  id: toolCall.id,
@@ -2647,6 +2686,42 @@ export class AiManager {
2647
2686
  });
2648
2687
  return { id: toolCall.id, name, calledAt, arguments: { sectionIds, include }, status: "completed", result: { ok: true, data: { sections, contentLimitChars: 48_000 } } };
2649
2688
  }
2689
+ if (name === "search_drafts") {
2690
+ const { query, draftType, limit } = args;
2691
+ let remainingChars = 36_000;
2692
+ const matches = this.store.searchDrafts(workId, query, draftType === "all" ? undefined : draftType, limit).map((draft) => {
2693
+ const content = collapseAiBlankLines(String(draft.content));
2694
+ const excerpt = content.slice(0, Math.max(0, Math.min(12_000, remainingChars)));
2695
+ remainingChars -= excerpt.length;
2696
+ return {
2697
+ id: draft.id,
2698
+ draftType: draft.draftType,
2699
+ draftTypeLabel: draft.draftType === "prose" ? "正文草稿" : "设定草稿",
2700
+ title: draft.title,
2701
+ content: excerpt,
2702
+ contentTruncated: excerpt.length < content.length,
2703
+ versionNo: draft.versionNo,
2704
+ updatedAt: draft.updatedAt
2705
+ };
2706
+ });
2707
+ return {
2708
+ id: toolCall.id,
2709
+ name,
2710
+ calledAt,
2711
+ arguments: { query, draftType, limit },
2712
+ status: "completed",
2713
+ result: {
2714
+ ok: true,
2715
+ data: {
2716
+ meaning: "这些内容是作者记录的未确认临时想法,可能采用,也可能永远不会写入正文或正式设定;不得视为故事事实。",
2717
+ query,
2718
+ draftType,
2719
+ matches,
2720
+ contentLimitChars: 36_000
2721
+ }
2722
+ }
2723
+ };
2724
+ }
2650
2725
  throw new Error(`Unhandled agent tool: ${name}`);
2651
2726
  }
2652
2727
  constrainParametersForContext(model, messages, parameters) {