@musnows/scriverse 0.5.8 → 0.5.10

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/store.js CHANGED
@@ -660,6 +660,12 @@ export class Store {
660
660
  autoRunEnabled: Number(row?.auto_run_enabled ?? 0) === 1,
661
661
  autoRunConcurrency: Math.min(8, Math.max(1, Number(row?.auto_run_concurrency ?? 2) || 2)),
662
662
  autoRunBatchLimit: Math.min(200, Math.max(1, Number(row?.auto_run_batch_limit ?? 20) || 20)),
663
+ autoRunDailyTaskLimit: Math.min(10_000, Math.max(0, Number(row?.auto_run_daily_task_limit ?? 0) || 0)),
664
+ autoRunFailureThreshold: Math.min(10, Math.max(1, Number(row?.auto_run_failure_threshold ?? 3) || 3)),
665
+ autoRunPaused: Number(row?.auto_run_paused ?? 0) === 1,
666
+ autoRunPauseReason: String(row?.auto_run_pause_reason ?? ""),
667
+ autoRunResumeAt: row?.auto_run_resume_at === null || row?.auto_run_resume_at === undefined ? null : String(row.auto_run_resume_at),
668
+ autoRunConsecutiveFailures: Math.max(0, Number(row?.auto_run_consecutive_failures ?? 0) || 0),
663
669
  bookSummaryContextPercent: Math.min(90, Math.max(1, Number(row?.book_summary_context_percent ?? 50) || 50)),
664
670
  contextCompactThreshold: Math.min(90, Math.max(50, Number(row?.context_compact_threshold ?? 85) || 85)),
665
671
  agentTools: json(String(row?.agent_tools_json ?? '["story_index","read_chapters","search_story_entities","grep","read_character_sections"]'), ["story_index", "read_chapters", "search_story_entities", "grep", "read_character_sections"])
@@ -676,32 +682,92 @@ export class Store {
676
682
  const nextEnabled = input.autoRunEnabled ?? Boolean(current.autoRunEnabled);
677
683
  const nextConcurrency = input.autoRunConcurrency ?? Number(current.autoRunConcurrency);
678
684
  const nextBatchLimit = input.autoRunBatchLimit ?? Number(current.autoRunBatchLimit);
685
+ const nextDailyTaskLimit = input.autoRunDailyTaskLimit ?? Number(current.autoRunDailyTaskLimit);
686
+ const nextFailureThreshold = input.autoRunFailureThreshold ?? Number(current.autoRunFailureThreshold);
679
687
  const nextBookSummaryContextPercent = input.bookSummaryContextPercent ?? Number(current.bookSummaryContextPercent);
680
688
  const nextContextCompactThreshold = input.contextCompactThreshold ?? Number(current.contextCompactThreshold);
681
689
  const nextAgentTools = input.agentTools ?? current.agentTools;
682
690
  this.db.run(`INSERT INTO work_ai_settings (
683
- work_id, system_prompt, auto_run_enabled, auto_run_concurrency, auto_run_batch_limit, book_summary_context_percent, context_compact_threshold, agent_tools_json, updated_at
684
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
691
+ work_id, system_prompt, auto_run_enabled, auto_run_concurrency, auto_run_batch_limit,
692
+ auto_run_daily_task_limit, auto_run_failure_threshold, auto_run_paused, auto_run_pause_reason,
693
+ auto_run_resume_at, auto_run_consecutive_failures, book_summary_context_percent,
694
+ context_compact_threshold, agent_tools_json, updated_at
695
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
685
696
  ON CONFLICT(work_id) DO UPDATE SET
686
697
  system_prompt = excluded.system_prompt,
687
698
  auto_run_enabled = excluded.auto_run_enabled,
688
699
  auto_run_concurrency = excluded.auto_run_concurrency,
689
700
  auto_run_batch_limit = excluded.auto_run_batch_limit,
701
+ auto_run_daily_task_limit = excluded.auto_run_daily_task_limit,
702
+ auto_run_failure_threshold = excluded.auto_run_failure_threshold,
703
+ auto_run_paused = excluded.auto_run_paused,
704
+ auto_run_pause_reason = excluded.auto_run_pause_reason,
705
+ auto_run_resume_at = excluded.auto_run_resume_at,
706
+ auto_run_consecutive_failures = excluded.auto_run_consecutive_failures,
690
707
  book_summary_context_percent = excluded.book_summary_context_percent,
691
708
  context_compact_threshold = excluded.context_compact_threshold,
692
709
  agent_tools_json = excluded.agent_tools_json,
693
- updated_at = excluded.updated_at`, workId, nextPrompt, nextEnabled ? 1 : 0, Math.min(8, Math.max(1, nextConcurrency)), Math.min(200, Math.max(1, nextBatchLimit)), Math.min(90, Math.max(1, nextBookSummaryContextPercent)), Math.min(90, Math.max(50, nextContextCompactThreshold)), JSON.stringify(nextAgentTools), timestamp);
710
+ updated_at = excluded.updated_at`, workId, nextPrompt, 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)), 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)), JSON.stringify(nextAgentTools), timestamp);
694
711
  this.audit(workId, "work.ai-settings.updated", "work-ai-settings", workId, {
695
712
  systemPromptChanged: input.systemPrompt !== undefined,
696
713
  autoRunEnabled: nextEnabled,
697
714
  autoRunConcurrency: Math.min(8, Math.max(1, nextConcurrency)),
698
715
  autoRunBatchLimit: Math.min(200, Math.max(1, nextBatchLimit)),
716
+ autoRunDailyTaskLimit: Math.min(10_000, Math.max(0, nextDailyTaskLimit)),
717
+ autoRunFailureThreshold: Math.min(10, Math.max(1, nextFailureThreshold)),
699
718
  bookSummaryContextPercent: Math.min(90, Math.max(1, nextBookSummaryContextPercent)),
700
719
  contextCompactThreshold: Math.min(90, Math.max(50, nextContextCompactThreshold)),
701
720
  agentTools: nextAgentTools
702
721
  });
703
722
  return this.getWorkAiSettings(workId);
704
723
  }
724
+ clearAutoRunPause(workId) {
725
+ this.getWork(workId);
726
+ const current = this.getWorkAiSettings(workId);
727
+ this.db.run(`UPDATE work_ai_settings
728
+ SET auto_run_paused = 0, auto_run_pause_reason = '', auto_run_resume_at = NULL,
729
+ auto_run_consecutive_failures = 0, updated_at = ?
730
+ WHERE work_id = ?`, now(), workId);
731
+ if (current.autoRunPaused) {
732
+ this.audit(workId, "task.auto-run.resumed", "work-ai-settings", workId, {
733
+ previousReason: current.autoRunPauseReason
734
+ });
735
+ }
736
+ return this.getWorkAiSettings(workId);
737
+ }
738
+ pauseAutoRun(workId, reason, resumeAt = null) {
739
+ this.getWork(workId);
740
+ this.db.run(`UPDATE work_ai_settings
741
+ SET auto_run_paused = 1, auto_run_pause_reason = ?, auto_run_resume_at = ?, updated_at = ?
742
+ WHERE work_id = ?`, reason.slice(0, 500), resumeAt, now(), workId);
743
+ this.audit(workId, "task.auto-run.paused", "work-ai-settings", workId, { reason, resumeAt });
744
+ return this.getWorkAiSettings(workId);
745
+ }
746
+ recordAutoRunSuccess(workId) {
747
+ this.getWork(workId);
748
+ this.db.run("UPDATE work_ai_settings SET auto_run_consecutive_failures = 0, updated_at = ? WHERE work_id = ?", now(), workId);
749
+ return this.getWorkAiSettings(workId);
750
+ }
751
+ recordAutoRunFailure(workId, message, pauseImmediately = false) {
752
+ return this.db.transaction(() => {
753
+ const current = this.getWorkAiSettings(workId);
754
+ const consecutiveFailures = Number(current.autoRunConsecutiveFailures) + 1;
755
+ const shouldPause = pauseImmediately || consecutiveFailures >= Number(current.autoRunFailureThreshold);
756
+ this.db.run(`UPDATE work_ai_settings
757
+ SET auto_run_consecutive_failures = ?, auto_run_paused = ?,
758
+ auto_run_pause_reason = CASE WHEN ? = 1 THEN ? ELSE auto_run_pause_reason END,
759
+ auto_run_resume_at = CASE WHEN ? = 1 THEN NULL ELSE auto_run_resume_at END,
760
+ updated_at = ? WHERE work_id = ?`, consecutiveFailures, shouldPause ? 1 : 0, shouldPause ? 1 : 0, `连续任务失败,自动执行已暂停:${message}`.slice(0, 500), shouldPause ? 1 : 0, now(), workId);
761
+ if (shouldPause) {
762
+ this.audit(workId, "task.auto-run.paused", "work-ai-settings", workId, {
763
+ reason: "consecutive-failures",
764
+ consecutiveFailures,
765
+ message
766
+ });
767
+ }
768
+ return this.getWorkAiSettings(workId);
769
+ });
770
+ }
705
771
  updateWork(workId, input, expectedVersionNo, source = "manual", sourceRef = null, changeNote = "") {
706
772
  this.db.transaction(() => {
707
773
  const current = this.getWork(workId);
@@ -2208,6 +2274,19 @@ export class Store {
2208
2274
  this.getWork(workId);
2209
2275
  return this.db.all("SELECT * FROM races WHERE work_id = ? ORDER BY name", workId).map((row) => this.mapRace(row, includeMarkdown));
2210
2276
  }
2277
+ listRacesByHierarchyScope(workId, scope, includeMarkdown = true) {
2278
+ this.getWork(workId);
2279
+ const hierarchyCondition = scope === "roots" ? "IS NULL" : "IS NOT NULL";
2280
+ return this.db.all(`SELECT race.*,
2281
+ (SELECT COUNT(*) FROM races child WHERE child.parent_race_id = race.id) AS child_count
2282
+ FROM races race
2283
+ WHERE race.work_id = ? AND race.parent_race_id ${hierarchyCondition}
2284
+ ORDER BY race.name`, workId).map((row) => this.mapRace(row, includeMarkdown));
2285
+ }
2286
+ countRaces(workId) {
2287
+ this.getWork(workId);
2288
+ return numberValue(this.db.get("SELECT COUNT(*) AS count FROM races WHERE work_id = ?", workId) ?? {}, "count");
2289
+ }
2211
2290
  listRacesPage(workId, pagination, includeMarkdown = true) {
2212
2291
  this.getWork(workId);
2213
2292
  const page = paginationSql(pagination);
@@ -2339,6 +2418,7 @@ export class Store {
2339
2418
  parentRaceId: optionalString(row, "parent_race_id"),
2340
2419
  name: requiredString(row, "name"),
2341
2420
  description: requiredString(row, "description"),
2421
+ ...(row.child_count === undefined ? {} : { childCount: numberValue(row, "child_count") }),
2342
2422
  ...(includeMarkdown
2343
2423
  ? { settings, settingsMarkdown: settingsMarkdownFromList(settings), settingsSections }
2344
2424
  : { settings: [], settingsCount: settingsSections.length }),
@@ -4129,7 +4209,8 @@ export class Store {
4129
4209
  FROM analysis_tasks WHERE work_id = ?`, workId) ?? {};
4130
4210
  const total = numberValue(statsRow, "total");
4131
4211
  const page = paginationSql(pagination);
4132
- const rows = this.db.all(`SELECT task.id, task.model_id, task.task_type, task.scope_json, task.status, task.progress, task.created_at, task.updated_at,
4212
+ const rows = this.db.all(`SELECT task.id, task.model_id, task.task_type, task.scope_json, task.status, task.progress,
4213
+ task.attempt_count, task.next_attempt_at, task.last_attempt_at, task.created_at, task.updated_at,
4133
4214
  model.display_name AS model_display_name, model.model_id AS model_api_id
4134
4215
  FROM analysis_tasks task
4135
4216
  LEFT JOIN models model ON model.id = task.model_id
@@ -4165,11 +4246,56 @@ export class Store {
4165
4246
  const row = this.db.get("SELECT COUNT(*) AS value FROM analysis_tasks WHERE work_id = ? AND status = 'running'", workId);
4166
4247
  return numberValue(row ?? {}, "value");
4167
4248
  }
4249
+ listAutoRunWorkIds() {
4250
+ return this.db.all("SELECT work_id FROM work_ai_settings WHERE auto_run_enabled = 1 ORDER BY work_id").map((row) => requiredString(row, "work_id"));
4251
+ }
4252
+ claimPendingTask(taskId, runningLimit) {
4253
+ return this.db.transaction(() => {
4254
+ const current = this.getTask(taskId);
4255
+ if (current.status !== "pending")
4256
+ return null;
4257
+ if (current.nextAttemptAt && String(current.nextAttemptAt) > now())
4258
+ return null;
4259
+ if (runningLimit !== undefined && this.countRunningTasks(String(current.workId)) >= runningLimit)
4260
+ return null;
4261
+ const timestamp = now();
4262
+ const claimed = this.db.run(`UPDATE analysis_tasks
4263
+ SET status = 'running', progress = 5, attempt_count = attempt_count + 1,
4264
+ next_attempt_at = NULL, last_attempt_at = ?, updated_at = ?
4265
+ WHERE id = ? AND status = 'pending' AND (next_attempt_at IS NULL OR next_attempt_at <= ?)`, timestamp, timestamp, taskId, timestamp);
4266
+ return claimed.changes === 1 ? this.getTask(taskId) : null;
4267
+ });
4268
+ }
4168
4269
  listOldestPendingTaskIds(workId, limit) {
4169
4270
  if (limit <= 0)
4170
4271
  return [];
4171
4272
  return this.db.all(`SELECT id FROM analysis_tasks WHERE work_id = ? AND status = 'pending'
4172
- ORDER BY created_at ASC, id ASC LIMIT ?`, workId, limit).map((row) => requiredString(row, "id"));
4273
+ AND (next_attempt_at IS NULL OR next_attempt_at <= ?)
4274
+ ORDER BY created_at ASC, id ASC LIMIT ?`, workId, now(), limit).map((row) => requiredString(row, "id"));
4275
+ }
4276
+ nextPendingTaskAttemptAt(workId) {
4277
+ const row = this.db.get(`SELECT MIN(next_attempt_at) AS value FROM analysis_tasks
4278
+ WHERE work_id = ? AND status = 'pending' AND next_attempt_at IS NOT NULL`, workId);
4279
+ return row?.value === null || row?.value === undefined ? null : String(row.value);
4280
+ }
4281
+ countAutoRunAttemptsToday(workId) {
4282
+ const row = this.db.get(`SELECT COUNT(*) AS value FROM analysis_tasks
4283
+ WHERE work_id = ? AND last_attempt_at >= strftime('%Y-%m-%dT00:00:00.000Z', 'now')`, workId);
4284
+ return numberValue(row ?? {}, "value");
4285
+ }
4286
+ rescheduleTask(taskId, failure, nextAttemptAt) {
4287
+ const current = this.getTask(taskId);
4288
+ if (current.status !== "running")
4289
+ return current;
4290
+ const failures = Array.isArray(current.failures) ? [...current.failures, failure].slice(-10) : [failure];
4291
+ this.db.run(`UPDATE analysis_tasks
4292
+ SET status = 'pending', progress = 0, failure_json = ?, next_attempt_at = ?, updated_at = ?
4293
+ WHERE id = ? AND status = 'running'`, JSON.stringify(failures), nextAttemptAt, now(), taskId);
4294
+ this.audit(String(current.workId), "task.retry-scheduled", "analysis-task", taskId, {
4295
+ attemptCount: current.attemptCount,
4296
+ nextAttemptAt
4297
+ });
4298
+ return this.getTask(taskId);
4173
4299
  }
4174
4300
  countPendingTasks(workId) {
4175
4301
  const row = this.db.get("SELECT COUNT(*) AS value FROM analysis_tasks WHERE work_id = ? AND status = 'pending'", workId);
@@ -5059,6 +5185,9 @@ export class Store {
5059
5185
  result: taskResult,
5060
5186
  failures: json(requiredString(row, "failure_json"), []),
5061
5187
  sourceVersions: json(requiredString(row, "source_versions_json"), {}),
5188
+ attemptCount: numberValue(row, "attempt_count"),
5189
+ nextAttemptAt: row.next_attempt_at === null || row.next_attempt_at === undefined ? null : String(row.next_attempt_at),
5190
+ lastAttemptAt: row.last_attempt_at === null || row.last_attempt_at === undefined ? null : String(row.last_attempt_at),
5062
5191
  createdAt: requiredString(row, "created_at"),
5063
5192
  updatedAt: requiredString(row, "updated_at")
5064
5193
  };
@@ -5073,6 +5202,9 @@ export class Store {
5073
5202
  scopeSummaryWithoutCharacterNames: this.taskScopeSummaryFromMaps(scope, chapterSummaries, volumeTitles, new Map(), false),
5074
5203
  status: requiredString(row, "status"),
5075
5204
  progress: numberValue(row, "progress"),
5205
+ attemptCount: numberValue(row, "attempt_count"),
5206
+ nextAttemptAt: row.next_attempt_at === null || row.next_attempt_at === undefined ? null : String(row.next_attempt_at),
5207
+ lastAttemptAt: row.last_attempt_at === null || row.last_attempt_at === undefined ? null : String(row.last_attempt_at),
5076
5208
  createdAt: requiredString(row, "created_at"),
5077
5209
  updatedAt: requiredString(row, "updated_at")
5078
5210
  };