@musnows/scriverse 0.5.8 → 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)
@@ -1280,9 +1280,19 @@ export function createRuntime(options) {
1280
1280
  noContent(response);
1281
1281
  });
1282
1282
  app.get("/api/works/:workId/races", (request, response) => {
1283
+ const hierarchyScope = parse(raceHierarchyScopeSchema, request.query.scope);
1283
1284
  const pagination = parsePagination(request.query);
1285
+ if (hierarchyScope && pagination) {
1286
+ throw new AppError(400, "RACE_HIERARCHY_PAGINATION_CONFLICT", "分层种族请求不能同时使用分页参数");
1287
+ }
1284
1288
  const includeMarkdown = request.query.includeContent === "true";
1285
- 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);
1286
1296
  const permissions = requestPermissions(request, request.params.workId);
1287
1297
  data(response, mapRecords(races, (race) => redactRaceMembers(race, permissions)));
1288
1298
  });
@@ -1458,6 +1468,7 @@ export function createRuntime(options) {
1458
1468
  data(response, pagination ? store.listReviewItemsPage(request.params.workId, pagination, status) : store.listReviewItems(request.params.workId, status));
1459
1469
  });
1460
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)));
1461
1472
  app.patch("/api/reviews/:reviewId", (request, response) => data(response, store.updateReviewItem(request.params.reviewId, parse(reviewSchema.partial(), request.body))));
1462
1473
  app.post("/api/reviews/:reviewId/character-resolution", (request, response) => {
1463
1474
  const input = parse(z.discriminatedUnion("action", [
@@ -1502,17 +1513,15 @@ export function createRuntime(options) {
1502
1513
  const permissions = requestPermissions(request, request.params.workId);
1503
1514
  for (const taskId of store.listOldestPendingTaskIds(request.params.workId, store.countPendingTasks(request.params.workId))) {
1504
1515
  const task = store.getTask(taskId);
1505
- if (task.taskType !== "relationship-analysis")
1506
- continue;
1507
- const deniedModules = relationshipAnalysisReadModules(task.scope).filter((module) => permissions[module] === "none");
1516
+ const deniedModules = analysisTaskReadModules(task.taskType, task.scope).filter((module) => permissions[module] === "none");
1508
1517
  if (deniedModules.length > 0) {
1509
- throw new AppError(403, "WORK_MODULE_READ_DENIED", "你没有读取待运行定向人物关系分析所需资料模块的权限", {
1518
+ throw new AppError(403, "WORK_MODULE_READ_DENIED", "你没有读取待运行分析任务所需资料模块的权限", {
1510
1519
  taskId,
1511
1520
  modules: deniedModules
1512
1521
  });
1513
1522
  }
1514
1523
  }
1515
- data(response, ai.startAutoRunBatch(request.params.workId));
1524
+ data(response, ai.resumeAutoRun(request.params.workId));
1516
1525
  });
1517
1526
  app.get("/api/tasks/:taskId/detail", (request, response) => data(response, redactTaskCharacterNames(store.getTaskDetail(request.params.taskId), requestPermissions(request))));
1518
1527
  app.get("/api/tasks/:taskId/result", (request, response) => {
@@ -1531,14 +1540,12 @@ export function createRuntime(options) {
1531
1540
  app.post("/api/tasks/:taskId/run", async (request, response) => {
1532
1541
  const input = parse(z.object({ modelId: identifier.optional() }), request.body ?? {});
1533
1542
  const task = store.getTask(request.params.taskId);
1534
- if (task.taskType === "relationship-analysis") {
1535
- const permissions = requestPermissions(request, String(task.workId));
1536
- const deniedModules = relationshipAnalysisReadModules(task.scope).filter((module) => permissions[module] === "none");
1537
- if (deniedModules.length > 0) {
1538
- throw new AppError(403, "WORK_MODULE_READ_DENIED", "你没有读取本次定向人物关系分析所需资料模块的权限", {
1539
- modules: deniedModules
1540
- });
1541
- }
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
+ });
1542
1549
  }
1543
1550
  data(response, redactTaskCharacterNames(await ai.runTask(request.params.taskId, input.modelId, request.authUser ? {
1544
1551
  userId: request.authUser.userId,
@@ -1548,14 +1555,12 @@ export function createRuntime(options) {
1548
1555
  app.post("/api/tasks/:taskId/rerun", (request, response) => {
1549
1556
  parse(z.object({}).strict(), request.body ?? {});
1550
1557
  const task = store.getTask(request.params.taskId);
1551
- if (task.taskType === "relationship-analysis") {
1552
- const permissions = requestPermissions(request, String(task.workId));
1553
- const deniedModules = relationshipAnalysisReadModules(task.scope).filter((module) => permissions[module] === "none");
1554
- if (deniedModules.length > 0) {
1555
- throw new AppError(403, "WORK_MODULE_READ_DENIED", "你没有读取本次定向人物关系分析所需资料模块的权限", {
1556
- modules: deniedModules
1557
- });
1558
- }
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
+ });
1559
1564
  }
1560
1565
  data(response, redactTaskCharacterNames(ai.rerunTask(request.params.taskId), requestPermissions(request)), 201);
1561
1566
  });
@@ -1615,12 +1620,14 @@ export function createRuntime(options) {
1615
1620
  });
1616
1621
  app.patch("/api/works/:workId/ai-settings", (request, response) => {
1617
1622
  const workId = request.params.workId;
1623
+ const input = parse(workAiSettingsSchema, request.body);
1618
1624
  const before = store.getWorkAiSettings(workId);
1619
- const updated = store.updateWorkAiSettings(workId, parse(workAiSettingsSchema, request.body));
1625
+ let updated = store.updateWorkAiSettings(workId, input);
1620
1626
  if (updated.autoRunEnabled) {
1621
- if (!before.autoRunEnabled)
1622
- ai.resetAutoRunBatch(workId);
1623
- ai.scheduleAutoRun(workId);
1627
+ if (input.autoRunEnabled === true && !before.autoRunEnabled)
1628
+ updated = ai.resumeAutoRun(workId);
1629
+ else
1630
+ ai.scheduleAutoRun(workId);
1624
1631
  }
1625
1632
  data(response, updated);
1626
1633
  });
@@ -1858,9 +1865,13 @@ export function createRuntime(options) {
1858
1865
  const pagination = parsePagination(request.query);
1859
1866
  data(response, pagination ? ai.listCallsPage(request.params.workId, pagination) : ai.listCalls(request.params.workId));
1860
1867
  });
1861
- app.get("/api/works/:workId/search", (request, response) => {
1862
- const query = parse(z.string().trim().min(1).max(500), request.query.q);
1863
- 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 }));
1864
1875
  });
1865
1876
  app.get("/api/works/:workId/export", async (request, response) => {
1866
1877
  const format = parse(z.enum(["json", "txt", "markdown"]), request.query.format ?? "json");