@musnows/scriverse 0.5.7 → 0.5.9

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/app.js CHANGED
@@ -16,6 +16,7 @@ import { Database } from "./database.js";
16
16
  import { assertSafeDocxArchive } from "./docx-security.js";
17
17
  import { TASK_TYPES } from "./domain.js";
18
18
  import { AppError } from "./errors.js";
19
+ import { HYBRID_SEARCH_TYPES } from "./hybrid-search.js";
19
20
  import { applyImportFileHints, parseNovelText } from "./parser.js";
20
21
  import { Store, versionedEntityTypes } from "./store.js";
21
22
  import { parsePagination } from "./pagination.js";
@@ -30,7 +31,7 @@ import { currentRequestActor, runWithRequestActor } from "./request-context.js";
30
31
  import { APP_VERSION } from "./version.js";
31
32
  import { fullWorkModulePermissions, proseReplacementPermissionModules } from "./work-permissions.js";
32
33
  import { CollaborationPresence, entityEditorPageKey, presencePageKinds } from "./collaboration-presence.js";
33
- import { clearSessionCookie, createCliApiScopeMiddleware, createUserSessionMiddleware, createWorkAuthorizationMiddleware, relationshipAnalysisReadModules, setSessionCookie, UserAuthService } from "./user-auth.js";
34
+ import { analysisTaskReadModules, clearSessionCookie, createCliApiScopeMiddleware, createUserSessionMiddleware, createWorkAuthorizationMiddleware, relationshipAnalysisReadModules, setSessionCookie, UserAuthService } from "./user-auth.js";
34
35
  const nonEmpty = z.string().trim().min(1);
35
36
  const identifier = z.string().trim().min(1).max(200);
36
37
  const optionalStrings = z.array(z.string()).optional();
@@ -239,6 +240,7 @@ const raceSchema = z.object({
239
240
  settingsSections: knowledgeSectionsSchema.optional(),
240
241
  memberIds: z.array(identifier).max(1000).optional()
241
242
  }).strict();
243
+ const raceHierarchyScopeSchema = z.enum(["roots", "descendants"]).optional();
242
244
  const chapterOutlineSchema = z.object({
243
245
  goal: z.string().max(100_000).optional(),
244
246
  conflict: z.string().max(100_000).optional(),
@@ -355,6 +357,8 @@ const workAiSettingsSchema = z.object({
355
357
  autoRunEnabled: z.boolean().optional(),
356
358
  autoRunConcurrency: z.number().int().min(1).max(8).optional(),
357
359
  autoRunBatchLimit: z.number().int().min(1).max(200).optional(),
360
+ autoRunDailyTaskLimit: z.number().int().min(0).max(10_000).optional(),
361
+ autoRunFailureThreshold: z.number().int().min(1).max(10).optional(),
358
362
  bookSummaryContextPercent: z.number().int().min(1).max(90).optional(),
359
363
  contextCompactThreshold: z.number().int().min(50).max(90).optional(),
360
364
  agentTools: z.array(z.enum(["story_index", "read_chapters", "grep", "search_story_entities", "read_character_sections"])).max(5).optional()
@@ -626,11 +630,7 @@ export function createRuntime(options) {
626
630
  };
627
631
  const captcha = new ImageCaptchaService({ revealAnswer: options.revealCaptchaAnswer === true });
628
632
  const ai = new AiManager(store, new CredentialVault(options.masterSecret), options.fetchImpl ?? fetch, options.security ? (url) => assertSafeAiEndpoint(url, options.security?.allowPrivateAiEndpoints) : undefined, (task, actor) => {
629
- if (task.taskType !== "relationship-analysis")
630
- return;
631
- const requiredModules = relationshipAnalysisReadModules(task.scope);
632
- if (requiredModules.length === 0)
633
- return;
633
+ const requiredModules = analysisTaskReadModules(task.taskType, task.scope);
634
634
  const creator = actor ? null : database.get("SELECT created_by_user_id FROM analysis_tasks WHERE id = ?", String(task.id));
635
635
  const userId = actor?.userId ?? (typeof creator?.created_by_user_id === "string" ? creator.created_by_user_id : null);
636
636
  if (!userId)
@@ -1013,6 +1013,11 @@ export function createRuntime(options) {
1013
1013
  store.deleteChapter(request.params.chapterId, input.expectedVersionNo);
1014
1014
  noContent(response);
1015
1015
  });
1016
+ app.delete("/api/chapters/:chapterId/permanent", (request, response) => {
1017
+ const input = parse(z.object({ expectedVersionNo: expectedVersionNoSchema }).strict(), request.body ?? {});
1018
+ store.permanentlyDeleteChapter(request.params.chapterId, input.expectedVersionNo);
1019
+ noContent(response);
1020
+ });
1016
1021
  app.get("/api/chapters/:chapterId/versions", (request, response) => {
1017
1022
  const pagination = parsePagination(request.query);
1018
1023
  data(response, pagination ? store.listChapterVersionsPage(request.params.chapterId, pagination) : store.listChapterVersions(request.params.chapterId));
@@ -1275,9 +1280,19 @@ export function createRuntime(options) {
1275
1280
  noContent(response);
1276
1281
  });
1277
1282
  app.get("/api/works/:workId/races", (request, response) => {
1283
+ const hierarchyScope = parse(raceHierarchyScopeSchema, request.query.scope);
1278
1284
  const pagination = parsePagination(request.query);
1285
+ if (hierarchyScope && pagination) {
1286
+ throw new AppError(400, "RACE_HIERARCHY_PAGINATION_CONFLICT", "分层种族请求不能同时使用分页参数");
1287
+ }
1279
1288
  const includeMarkdown = request.query.includeContent === "true";
1280
- const races = pagination ? store.listRacesPage(request.params.workId, pagination, includeMarkdown) : store.listRaces(request.params.workId, includeMarkdown);
1289
+ const races = hierarchyScope
1290
+ ? hierarchyScope === "roots"
1291
+ ? { items: store.listRacesByHierarchyScope(request.params.workId, hierarchyScope, includeMarkdown), total: store.countRaces(request.params.workId) }
1292
+ : store.listRacesByHierarchyScope(request.params.workId, hierarchyScope, includeMarkdown)
1293
+ : pagination
1294
+ ? store.listRacesPage(request.params.workId, pagination, includeMarkdown)
1295
+ : store.listRaces(request.params.workId, includeMarkdown);
1281
1296
  const permissions = requestPermissions(request, request.params.workId);
1282
1297
  data(response, mapRecords(races, (race) => redactRaceMembers(race, permissions)));
1283
1298
  });
@@ -1453,6 +1468,7 @@ export function createRuntime(options) {
1453
1468
  data(response, pagination ? store.listReviewItemsPage(request.params.workId, pagination, status) : store.listReviewItems(request.params.workId, status));
1454
1469
  });
1455
1470
  app.post("/api/works/:workId/reviews", (request, response) => data(response, store.createReviewItem(request.params.workId, parse(reviewSchema, request.body)), 201));
1471
+ app.get("/api/reviews/:reviewId", (request, response) => data(response, store.getReviewItem(request.params.reviewId)));
1456
1472
  app.patch("/api/reviews/:reviewId", (request, response) => data(response, store.updateReviewItem(request.params.reviewId, parse(reviewSchema.partial(), request.body))));
1457
1473
  app.post("/api/reviews/:reviewId/character-resolution", (request, response) => {
1458
1474
  const input = parse(z.discriminatedUnion("action", [
@@ -1497,17 +1513,15 @@ export function createRuntime(options) {
1497
1513
  const permissions = requestPermissions(request, request.params.workId);
1498
1514
  for (const taskId of store.listOldestPendingTaskIds(request.params.workId, store.countPendingTasks(request.params.workId))) {
1499
1515
  const task = store.getTask(taskId);
1500
- if (task.taskType !== "relationship-analysis")
1501
- continue;
1502
- const deniedModules = relationshipAnalysisReadModules(task.scope).filter((module) => permissions[module] === "none");
1516
+ const deniedModules = analysisTaskReadModules(task.taskType, task.scope).filter((module) => permissions[module] === "none");
1503
1517
  if (deniedModules.length > 0) {
1504
- throw new AppError(403, "WORK_MODULE_READ_DENIED", "你没有读取待运行定向人物关系分析所需资料模块的权限", {
1518
+ throw new AppError(403, "WORK_MODULE_READ_DENIED", "你没有读取待运行分析任务所需资料模块的权限", {
1505
1519
  taskId,
1506
1520
  modules: deniedModules
1507
1521
  });
1508
1522
  }
1509
1523
  }
1510
- data(response, ai.startAutoRunBatch(request.params.workId));
1524
+ data(response, ai.resumeAutoRun(request.params.workId));
1511
1525
  });
1512
1526
  app.get("/api/tasks/:taskId/detail", (request, response) => data(response, redactTaskCharacterNames(store.getTaskDetail(request.params.taskId), requestPermissions(request))));
1513
1527
  app.get("/api/tasks/:taskId/result", (request, response) => {
@@ -1526,14 +1540,12 @@ export function createRuntime(options) {
1526
1540
  app.post("/api/tasks/:taskId/run", async (request, response) => {
1527
1541
  const input = parse(z.object({ modelId: identifier.optional() }), request.body ?? {});
1528
1542
  const task = store.getTask(request.params.taskId);
1529
- if (task.taskType === "relationship-analysis") {
1530
- const permissions = requestPermissions(request, String(task.workId));
1531
- const deniedModules = relationshipAnalysisReadModules(task.scope).filter((module) => permissions[module] === "none");
1532
- if (deniedModules.length > 0) {
1533
- throw new AppError(403, "WORK_MODULE_READ_DENIED", "你没有读取本次定向人物关系分析所需资料模块的权限", {
1534
- modules: deniedModules
1535
- });
1536
- }
1543
+ const permissions = requestPermissions(request, String(task.workId));
1544
+ const deniedModules = analysisTaskReadModules(task.taskType, task.scope).filter((module) => permissions[module] === "none");
1545
+ if (deniedModules.length > 0) {
1546
+ throw new AppError(403, "WORK_MODULE_READ_DENIED", "你没有读取本次分析所需资料模块的权限", {
1547
+ modules: deniedModules
1548
+ });
1537
1549
  }
1538
1550
  data(response, redactTaskCharacterNames(await ai.runTask(request.params.taskId, input.modelId, request.authUser ? {
1539
1551
  userId: request.authUser.userId,
@@ -1543,14 +1555,12 @@ export function createRuntime(options) {
1543
1555
  app.post("/api/tasks/:taskId/rerun", (request, response) => {
1544
1556
  parse(z.object({}).strict(), request.body ?? {});
1545
1557
  const task = store.getTask(request.params.taskId);
1546
- if (task.taskType === "relationship-analysis") {
1547
- const permissions = requestPermissions(request, String(task.workId));
1548
- const deniedModules = relationshipAnalysisReadModules(task.scope).filter((module) => permissions[module] === "none");
1549
- if (deniedModules.length > 0) {
1550
- throw new AppError(403, "WORK_MODULE_READ_DENIED", "你没有读取本次定向人物关系分析所需资料模块的权限", {
1551
- modules: deniedModules
1552
- });
1553
- }
1558
+ const permissions = requestPermissions(request, String(task.workId));
1559
+ const deniedModules = analysisTaskReadModules(task.taskType, task.scope).filter((module) => permissions[module] === "none");
1560
+ if (deniedModules.length > 0) {
1561
+ throw new AppError(403, "WORK_MODULE_READ_DENIED", "你没有读取本次分析所需资料模块的权限", {
1562
+ modules: deniedModules
1563
+ });
1554
1564
  }
1555
1565
  data(response, redactTaskCharacterNames(ai.rerunTask(request.params.taskId), requestPermissions(request)), 201);
1556
1566
  });
@@ -1610,12 +1620,14 @@ export function createRuntime(options) {
1610
1620
  });
1611
1621
  app.patch("/api/works/:workId/ai-settings", (request, response) => {
1612
1622
  const workId = request.params.workId;
1623
+ const input = parse(workAiSettingsSchema, request.body);
1613
1624
  const before = store.getWorkAiSettings(workId);
1614
- const updated = store.updateWorkAiSettings(workId, parse(workAiSettingsSchema, request.body));
1625
+ let updated = store.updateWorkAiSettings(workId, input);
1615
1626
  if (updated.autoRunEnabled) {
1616
- if (!before.autoRunEnabled)
1617
- ai.resetAutoRunBatch(workId);
1618
- ai.scheduleAutoRun(workId);
1627
+ if (input.autoRunEnabled === true && !before.autoRunEnabled)
1628
+ updated = ai.resumeAutoRun(workId);
1629
+ else
1630
+ ai.scheduleAutoRun(workId);
1619
1631
  }
1620
1632
  data(response, updated);
1621
1633
  });
@@ -1853,9 +1865,13 @@ export function createRuntime(options) {
1853
1865
  const pagination = parsePagination(request.query);
1854
1866
  data(response, pagination ? ai.listCallsPage(request.params.workId, pagination) : ai.listCalls(request.params.workId));
1855
1867
  });
1856
- app.get("/api/works/:workId/search", (request, response) => {
1857
- const query = parse(z.string().trim().min(1).max(500), request.query.q);
1858
- data(response, store.search(request.params.workId, query));
1868
+ app.get("/api/works/:workId/search", async (request, response) => {
1869
+ const query = parse(z.object({
1870
+ q: z.string().trim().min(1).max(500),
1871
+ type: z.enum(HYBRID_SEARCH_TYPES).optional(),
1872
+ limit: z.coerce.number().int().min(1).max(100).optional()
1873
+ }).strict(), request.query);
1874
+ data(response, await ai.searchWork(request.params.workId, query.q, { type: query.type, limit: query.limit }));
1859
1875
  });
1860
1876
  app.get("/api/works/:workId/export", async (request, response) => {
1861
1877
  const format = parse(z.enum(["json", "txt", "markdown"]), request.query.format ?? "json");