pixivflow 2.18.0 → 2.19.0

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.
Files changed (510) hide show
  1. package/dist/commands/SchedulerCommand.js +105 -45
  2. package/dist/commands/SchedulerIdleLifecycle.d.ts +82 -0
  3. package/dist/commands/SchedulerIdleLifecycle.js +135 -0
  4. package/dist/commands/scheduler-runtime.d.ts +6 -0
  5. package/dist/commands/scheduler-runtime.js +77 -9
  6. package/dist/config/types.d.ts +33 -0
  7. package/dist/config/validation.js +10 -1
  8. package/dist/download/DownloadManager.d.ts +9 -0
  9. package/dist/download/DownloadManager.js +18 -1
  10. package/dist/package.json +1 -1
  11. package/dist/pixiv-client/PixivFlowPixivClient.d.ts +7 -1
  12. package/dist/pixiv-client/PixivFlowPixivClient.js +14 -8
  13. package/dist/pixiv-client/TargetSearchRunner.d.ts +2 -2
  14. package/dist/pixiv-client/TargetSearchRunner.js +21 -14
  15. package/dist/scheduler/MultiScheduleManager.d.ts +33 -1
  16. package/dist/scheduler/MultiScheduleManager.js +115 -2
  17. package/dist/scheduler/ScheduleTriggerServer.d.ts +32 -9
  18. package/dist/scheduler/ScheduleTriggerServer.js +34 -6
  19. package/dist/scheduler/Scheduler.d.ts +28 -1
  20. package/dist/scheduler/Scheduler.js +82 -7
  21. package/dist/scheduler/SlotCoordinator.d.ts +28 -3
  22. package/dist/scheduler/SlotCoordinator.js +31 -5
  23. package/dist/storage/repositories/SlotRepository.d.ts +28 -0
  24. package/dist/storage/repositories/SlotRepository.js +42 -4
  25. package/dist/topic/TopicPipeline.d.ts +15 -1
  26. package/dist/topic/TopicPipeline.js +18 -2
  27. package/dist/topic/TopicResolver.d.ts +5 -1
  28. package/dist/topic/TopicResolver.js +32 -6
  29. package/dist/topic/createTopicPipeline.d.ts +2 -2
  30. package/dist/topic/createTopicPipeline.js +5 -5
  31. package/dist/topic/types.d.ts +5 -1
  32. package/dist/utils/errors.d.ts +18 -0
  33. package/dist/utils/errors.js +30 -0
  34. package/dist/version.js +1 -1
  35. package/dist/webui/package.json +1 -1
  36. package/package.json +2 -1
  37. package/dist/auth/PixivAuth.d.ts.map +0 -1
  38. package/dist/auth/PixivAuth.js.map +0 -1
  39. package/dist/batch/executionResult.d.ts.map +0 -1
  40. package/dist/batch/executionResult.js.map +0 -1
  41. package/dist/batch/runDiagnostics.d.ts.map +0 -1
  42. package/dist/batch/runDiagnostics.js.map +0 -1
  43. package/dist/cli/ArgumentParser.d.ts.map +0 -1
  44. package/dist/cli/ArgumentParser.js.map +0 -1
  45. package/dist/cli/InteractivePrompt.d.ts.map +0 -1
  46. package/dist/cli/InteractivePrompt.js.map +0 -1
  47. package/dist/commands/BackupCommand.d.ts.map +0 -1
  48. package/dist/commands/BackupCommand.js.map +0 -1
  49. package/dist/commands/Command.d.ts.map +0 -1
  50. package/dist/commands/Command.js.map +0 -1
  51. package/dist/commands/CommandRegistry.d.ts.map +0 -1
  52. package/dist/commands/CommandRegistry.js.map +0 -1
  53. package/dist/commands/ConfigCommand.d.ts.map +0 -1
  54. package/dist/commands/ConfigCommand.js.map +0 -1
  55. package/dist/commands/DiagnoseEgressCommand.d.ts.map +0 -1
  56. package/dist/commands/DiagnoseEgressCommand.js.map +0 -1
  57. package/dist/commands/DirsCommand.d.ts.map +0 -1
  58. package/dist/commands/DirsCommand.js.map +0 -1
  59. package/dist/commands/DoctorCommand.d.ts.map +0 -1
  60. package/dist/commands/DoctorCommand.js.map +0 -1
  61. package/dist/commands/DownloadCommand.d.ts.map +0 -1
  62. package/dist/commands/DownloadCommand.js.map +0 -1
  63. package/dist/commands/ExecuteSlotCommand.d.ts.map +0 -1
  64. package/dist/commands/ExecuteSlotCommand.js.map +0 -1
  65. package/dist/commands/HealthCommand.d.ts.map +0 -1
  66. package/dist/commands/HealthCommand.js.map +0 -1
  67. package/dist/commands/HelpCommand.d.ts.map +0 -1
  68. package/dist/commands/HelpCommand.js.map +0 -1
  69. package/dist/commands/LoginCommand.d.ts.map +0 -1
  70. package/dist/commands/LoginCommand.js.map +0 -1
  71. package/dist/commands/LoginHeadlessCommand.d.ts.map +0 -1
  72. package/dist/commands/LoginHeadlessCommand.js.map +0 -1
  73. package/dist/commands/LogsCommand.d.ts.map +0 -1
  74. package/dist/commands/LogsCommand.js.map +0 -1
  75. package/dist/commands/MaintainCommand.d.ts.map +0 -1
  76. package/dist/commands/MaintainCommand.js.map +0 -1
  77. package/dist/commands/MigrateConfigCommand.d.ts.map +0 -1
  78. package/dist/commands/MigrateConfigCommand.js.map +0 -1
  79. package/dist/commands/MonitorCommand.d.ts.map +0 -1
  80. package/dist/commands/MonitorCommand.js.map +0 -1
  81. package/dist/commands/NormalizeCommand.d.ts.map +0 -1
  82. package/dist/commands/NormalizeCommand.js.map +0 -1
  83. package/dist/commands/OutboxCommand.d.ts.map +0 -1
  84. package/dist/commands/OutboxCommand.js.map +0 -1
  85. package/dist/commands/RandomDownloadCommand.d.ts.map +0 -1
  86. package/dist/commands/RandomDownloadCommand.js.map +0 -1
  87. package/dist/commands/ReconcileCommand.d.ts.map +0 -1
  88. package/dist/commands/ReconcileCommand.js.map +0 -1
  89. package/dist/commands/RefreshCommand.d.ts.map +0 -1
  90. package/dist/commands/RefreshCommand.js.map +0 -1
  91. package/dist/commands/RunsCommand.d.ts.map +0 -1
  92. package/dist/commands/RunsCommand.js.map +0 -1
  93. package/dist/commands/SchedulerCommand.d.ts.map +0 -1
  94. package/dist/commands/SchedulerCommand.js.map +0 -1
  95. package/dist/commands/SchedulerRunOnceCommand.d.ts.map +0 -1
  96. package/dist/commands/SchedulerRunOnceCommand.js.map +0 -1
  97. package/dist/commands/SetupCommand.d.ts.map +0 -1
  98. package/dist/commands/SetupCommand.js.map +0 -1
  99. package/dist/commands/StatusCommand.d.ts.map +0 -1
  100. package/dist/commands/StatusCommand.js.map +0 -1
  101. package/dist/commands/TagsCommand.d.ts.map +0 -1
  102. package/dist/commands/TagsCommand.js.map +0 -1
  103. package/dist/commands/TopicCommand.d.ts.map +0 -1
  104. package/dist/commands/TopicCommand.js.map +0 -1
  105. package/dist/commands/VersionCommand.d.ts.map +0 -1
  106. package/dist/commands/VersionCommand.js.map +0 -1
  107. package/dist/commands/WebUICommand.d.ts.map +0 -1
  108. package/dist/commands/WebUICommand.js.map +0 -1
  109. package/dist/commands/index.d.ts.map +0 -1
  110. package/dist/commands/index.js.map +0 -1
  111. package/dist/commands/metadata.d.ts.map +0 -1
  112. package/dist/commands/metadata.js.map +0 -1
  113. package/dist/commands/scheduler-runtime.d.ts.map +0 -1
  114. package/dist/commands/scheduler-runtime.js.map +0 -1
  115. package/dist/commands/suggestions.d.ts.map +0 -1
  116. package/dist/commands/suggestions.js.map +0 -1
  117. package/dist/commands/types.d.ts.map +0 -1
  118. package/dist/commands/types.js.map +0 -1
  119. package/dist/config/defaults.d.ts.map +0 -1
  120. package/dist/config/defaults.js.map +0 -1
  121. package/dist/config/environment.d.ts.map +0 -1
  122. package/dist/config/environment.js.map +0 -1
  123. package/dist/config/loader.d.ts.map +0 -1
  124. package/dist/config/loader.js.map +0 -1
  125. package/dist/config/path-resolution.d.ts.map +0 -1
  126. package/dist/config/path-resolution.js.map +0 -1
  127. package/dist/config/placeholders.d.ts.map +0 -1
  128. package/dist/config/placeholders.js.map +0 -1
  129. package/dist/config/types.d.ts.map +0 -1
  130. package/dist/config/types.js.map +0 -1
  131. package/dist/config/validation.d.ts.map +0 -1
  132. package/dist/config/validation.js.map +0 -1
  133. package/dist/config.d.ts.map +0 -1
  134. package/dist/config.js.map +0 -1
  135. package/dist/delivery/DeliveryAck.d.ts.map +0 -1
  136. package/dist/delivery/DeliveryAck.js.map +0 -1
  137. package/dist/delivery/DeliveryDispatcher.d.ts.map +0 -1
  138. package/dist/delivery/DeliveryDispatcher.js.map +0 -1
  139. package/dist/delivery/DeliveryService.d.ts.map +0 -1
  140. package/dist/delivery/DeliveryService.js.map +0 -1
  141. package/dist/delivery/HttpMultipartDelivery.d.ts.map +0 -1
  142. package/dist/delivery/HttpMultipartDelivery.js.map +0 -1
  143. package/dist/delivery/LegacyOutboxMigration.d.ts.map +0 -1
  144. package/dist/delivery/LegacyOutboxMigration.js.map +0 -1
  145. package/dist/delivery/OutboxWorker.d.ts.map +0 -1
  146. package/dist/delivery/OutboxWorker.js.map +0 -1
  147. package/dist/delivery/TelegramReviewDelivery.d.ts.map +0 -1
  148. package/dist/delivery/TelegramReviewDelivery.js.map +0 -1
  149. package/dist/delivery/errorClass.d.ts.map +0 -1
  150. package/dist/delivery/errorClass.js.map +0 -1
  151. package/dist/delivery/types.d.ts.map +0 -1
  152. package/dist/delivery/types.js.map +0 -1
  153. package/dist/di/Container.d.ts.map +0 -1
  154. package/dist/di/Container.js.map +0 -1
  155. package/dist/di/index.d.ts.map +0 -1
  156. package/dist/di/index.js.map +0 -1
  157. package/dist/download/DownloadManager.d.ts.map +0 -1
  158. package/dist/download/DownloadManager.js.map +0 -1
  159. package/dist/download/FileNormalizationService.d.ts.map +0 -1
  160. package/dist/download/FileNormalizationService.js.map +0 -1
  161. package/dist/download/FileService.d.ts.map +0 -1
  162. package/dist/download/FileService.js.map +0 -1
  163. package/dist/download/IllustrationDownloader.d.ts.map +0 -1
  164. package/dist/download/IllustrationDownloader.js.map +0 -1
  165. package/dist/download/NovelDownloader.d.ts.map +0 -1
  166. package/dist/download/NovelDownloader.js.map +0 -1
  167. package/dist/download/RankingService.d.ts.map +0 -1
  168. package/dist/download/RankingService.js.map +0 -1
  169. package/dist/download/UgoiraConverter.d.ts.map +0 -1
  170. package/dist/download/UgoiraConverter.js.map +0 -1
  171. package/dist/download/exec/DownloadExecutor.d.ts.map +0 -1
  172. package/dist/download/exec/DownloadExecutor.js.map +0 -1
  173. package/dist/download/handlers/IllustrationTargetHandler.d.ts.map +0 -1
  174. package/dist/download/handlers/IllustrationTargetHandler.js.map +0 -1
  175. package/dist/download/handlers/NovelTargetHandler.d.ts.map +0 -1
  176. package/dist/download/handlers/NovelTargetHandler.js.map +0 -1
  177. package/dist/download/normalization/FileNameNormalizer.d.ts.map +0 -1
  178. package/dist/download/normalization/FileNameNormalizer.js.map +0 -1
  179. package/dist/download/normalization/FileOperationUtils.d.ts.map +0 -1
  180. package/dist/download/normalization/FileOperationUtils.js.map +0 -1
  181. package/dist/download/normalization/MetadataLoader.d.ts.map +0 -1
  182. package/dist/download/normalization/MetadataLoader.js.map +0 -1
  183. package/dist/download/pipeline/DownloadPipeline.d.ts.map +0 -1
  184. package/dist/download/pipeline/DownloadPipeline.js.map +0 -1
  185. package/dist/download/plan/DownloadPlanner.d.ts.map +0 -1
  186. package/dist/download/plan/DownloadPlanner.js.map +0 -1
  187. package/dist/download/recovery/ErrorRecovery.d.ts.map +0 -1
  188. package/dist/download/recovery/ErrorRecovery.js.map +0 -1
  189. package/dist/download/report/ProgressReporter.d.ts.map +0 -1
  190. package/dist/download/report/ProgressReporter.js.map +0 -1
  191. package/dist/download-specific.d.ts.map +0 -1
  192. package/dist/download-specific.js.map +0 -1
  193. package/dist/index.d.ts.map +0 -1
  194. package/dist/index.js.map +0 -1
  195. package/dist/interfaces/IDatabase.d.ts.map +0 -1
  196. package/dist/interfaces/IDatabase.js.map +0 -1
  197. package/dist/interfaces/IDownloadManager.d.ts.map +0 -1
  198. package/dist/interfaces/IDownloadManager.js.map +0 -1
  199. package/dist/interfaces/IFileService.d.ts.map +0 -1
  200. package/dist/interfaces/IFileService.js.map +0 -1
  201. package/dist/interfaces/IPixivAuth.d.ts.map +0 -1
  202. package/dist/interfaces/IPixivAuth.js.map +0 -1
  203. package/dist/interfaces/IPixivClient.d.ts.map +0 -1
  204. package/dist/interfaces/IPixivClient.js.map +0 -1
  205. package/dist/interfaces/index.d.ts.map +0 -1
  206. package/dist/interfaces/index.js.map +0 -1
  207. package/dist/logger.d.ts.map +0 -1
  208. package/dist/logger.js.map +0 -1
  209. package/dist/notification/NotificationPolicy.d.ts.map +0 -1
  210. package/dist/notification/NotificationPolicy.js.map +0 -1
  211. package/dist/pixiv/AuthClient.d.ts.map +0 -1
  212. package/dist/pixiv/AuthClient.js.map +0 -1
  213. package/dist/pixiv/PixivClient.d.ts.map +0 -1
  214. package/dist/pixiv/PixivClient.js.map +0 -1
  215. package/dist/pixiv-client/PixivAuthTokenProvider.d.ts.map +0 -1
  216. package/dist/pixiv-client/PixivAuthTokenProvider.js.map +0 -1
  217. package/dist/pixiv-client/PixivFlowPixivClient.d.ts.map +0 -1
  218. package/dist/pixiv-client/PixivFlowPixivClient.js.map +0 -1
  219. package/dist/pixiv-client/TargetSearchRunner.d.ts.map +0 -1
  220. package/dist/pixiv-client/TargetSearchRunner.js.map +0 -1
  221. package/dist/pixiv-client/createPixivFlowClient.d.ts.map +0 -1
  222. package/dist/pixiv-client/createPixivFlowClient.js.map +0 -1
  223. package/dist/pixiv-client/query-mapper.d.ts.map +0 -1
  224. package/dist/pixiv-client/query-mapper.js.map +0 -1
  225. package/dist/pixiv-token-getter-adapter.d.ts.map +0 -1
  226. package/dist/pixiv-token-getter-adapter.js.map +0 -1
  227. package/dist/puppeteer-login-adapter/auth-code.d.ts.map +0 -1
  228. package/dist/puppeteer-login-adapter/auth-code.js.map +0 -1
  229. package/dist/puppeteer-login-adapter/browser-launch.d.ts.map +0 -1
  230. package/dist/puppeteer-login-adapter/browser-launch.js.map +0 -1
  231. package/dist/puppeteer-login-adapter/constants.d.ts.map +0 -1
  232. package/dist/puppeteer-login-adapter/constants.js.map +0 -1
  233. package/dist/puppeteer-login-adapter/environment.d.ts.map +0 -1
  234. package/dist/puppeteer-login-adapter/environment.js.map +0 -1
  235. package/dist/puppeteer-login-adapter/login-headless.d.ts.map +0 -1
  236. package/dist/puppeteer-login-adapter/login-headless.js.map +0 -1
  237. package/dist/puppeteer-login-adapter/login-interactive.d.ts.map +0 -1
  238. package/dist/puppeteer-login-adapter/login-interactive.js.map +0 -1
  239. package/dist/puppeteer-login-adapter/pkce.d.ts.map +0 -1
  240. package/dist/puppeteer-login-adapter/pkce.js.map +0 -1
  241. package/dist/puppeteer-login-adapter/proxy.d.ts.map +0 -1
  242. package/dist/puppeteer-login-adapter/proxy.js.map +0 -1
  243. package/dist/puppeteer-login-adapter/token-exchange.d.ts.map +0 -1
  244. package/dist/puppeteer-login-adapter/token-exchange.js.map +0 -1
  245. package/dist/puppeteer-login-adapter.d.ts.map +0 -1
  246. package/dist/puppeteer-login-adapter.js.map +0 -1
  247. package/dist/python-login-adapter/login-scripts.d.ts.map +0 -1
  248. package/dist/python-login-adapter/login-scripts.js.map +0 -1
  249. package/dist/python-login-adapter/proxy.d.ts.map +0 -1
  250. package/dist/python-login-adapter/proxy.js.map +0 -1
  251. package/dist/python-login-adapter/python-environment.d.ts.map +0 -1
  252. package/dist/python-login-adapter/python-environment.js.map +0 -1
  253. package/dist/python-login-adapter/response-converter.d.ts.map +0 -1
  254. package/dist/python-login-adapter/response-converter.js.map +0 -1
  255. package/dist/python-login-adapter.d.ts.map +0 -1
  256. package/dist/python-login-adapter.js.map +0 -1
  257. package/dist/random-download.d.ts.map +0 -1
  258. package/dist/random-download.js.map +0 -1
  259. package/dist/scheduler/MultiScheduleManager.d.ts.map +0 -1
  260. package/dist/scheduler/MultiScheduleManager.js.map +0 -1
  261. package/dist/scheduler/OccurrenceResolver.d.ts.map +0 -1
  262. package/dist/scheduler/OccurrenceResolver.js.map +0 -1
  263. package/dist/scheduler/ScheduleTriggerServer.d.ts.map +0 -1
  264. package/dist/scheduler/ScheduleTriggerServer.js.map +0 -1
  265. package/dist/scheduler/Scheduler.d.ts.map +0 -1
  266. package/dist/scheduler/Scheduler.js.map +0 -1
  267. package/dist/scheduler/SlotCoordinator.d.ts.map +0 -1
  268. package/dist/scheduler/SlotCoordinator.js.map +0 -1
  269. package/dist/scheduler/SlotStateMachine.d.ts.map +0 -1
  270. package/dist/scheduler/SlotStateMachine.js.map +0 -1
  271. package/dist/scheduler/TargetOutcome.d.ts.map +0 -1
  272. package/dist/scheduler/TargetOutcome.js.map +0 -1
  273. package/dist/scheduler/schedules.d.ts.map +0 -1
  274. package/dist/scheduler/schedules.js.map +0 -1
  275. package/dist/setup-wizard.d.ts.map +0 -1
  276. package/dist/setup-wizard.js.map +0 -1
  277. package/dist/storage/Database.d.ts.map +0 -1
  278. package/dist/storage/Database.js.map +0 -1
  279. package/dist/storage/DatabaseMigration.d.ts.map +0 -1
  280. package/dist/storage/DatabaseMigration.js.map +0 -1
  281. package/dist/storage/drivers/NodeSqliteDriver.d.ts.map +0 -1
  282. package/dist/storage/drivers/NodeSqliteDriver.js.map +0 -1
  283. package/dist/storage/drivers/SqliteDriver.d.ts.map +0 -1
  284. package/dist/storage/drivers/SqliteDriver.js.map +0 -1
  285. package/dist/storage/repositories/BaseRepository.d.ts.map +0 -1
  286. package/dist/storage/repositories/BaseRepository.js.map +0 -1
  287. package/dist/storage/repositories/ConfigHistoryRepository.d.ts.map +0 -1
  288. package/dist/storage/repositories/ConfigHistoryRepository.js.map +0 -1
  289. package/dist/storage/repositories/DeliveryRepository.d.ts.map +0 -1
  290. package/dist/storage/repositories/DeliveryRepository.js.map +0 -1
  291. package/dist/storage/repositories/DownloadQueryRepository.d.ts.map +0 -1
  292. package/dist/storage/repositories/DownloadQueryRepository.js.map +0 -1
  293. package/dist/storage/repositories/DownloadRepository.d.ts.map +0 -1
  294. package/dist/storage/repositories/DownloadRepository.js.map +0 -1
  295. package/dist/storage/repositories/DownloadStatsRepository.d.ts.map +0 -1
  296. package/dist/storage/repositories/DownloadStatsRepository.js.map +0 -1
  297. package/dist/storage/repositories/DownloadWriteRepository.d.ts.map +0 -1
  298. package/dist/storage/repositories/DownloadWriteRepository.js.map +0 -1
  299. package/dist/storage/repositories/ExecutionRepository.d.ts.map +0 -1
  300. package/dist/storage/repositories/ExecutionRepository.js.map +0 -1
  301. package/dist/storage/repositories/MetadataRepository.d.ts.map +0 -1
  302. package/dist/storage/repositories/MetadataRepository.js.map +0 -1
  303. package/dist/storage/repositories/OutboxRepository.d.ts.map +0 -1
  304. package/dist/storage/repositories/OutboxRepository.js.map +0 -1
  305. package/dist/storage/repositories/RateLimitStateRepository.d.ts.map +0 -1
  306. package/dist/storage/repositories/RateLimitStateRepository.js.map +0 -1
  307. package/dist/storage/repositories/SchedulerRepository.d.ts.map +0 -1
  308. package/dist/storage/repositories/SchedulerRepository.js.map +0 -1
  309. package/dist/storage/repositories/SlotRepository.d.ts.map +0 -1
  310. package/dist/storage/repositories/SlotRepository.js.map +0 -1
  311. package/dist/storage/repositories/TaskHistoryRepository.d.ts.map +0 -1
  312. package/dist/storage/repositories/TaskHistoryRepository.js.map +0 -1
  313. package/dist/storage/repositories/TokenRepository.d.ts.map +0 -1
  314. package/dist/storage/repositories/TokenRepository.js.map +0 -1
  315. package/dist/tags/TagDiscoveryService.d.ts.map +0 -1
  316. package/dist/tags/TagDiscoveryService.js.map +0 -1
  317. package/dist/tags/TagDiscoveryStore.d.ts.map +0 -1
  318. package/dist/tags/TagDiscoveryStore.js.map +0 -1
  319. package/dist/tags/TagPlanApplier.d.ts.map +0 -1
  320. package/dist/tags/TagPlanApplier.js.map +0 -1
  321. package/dist/tags/types.d.ts.map +0 -1
  322. package/dist/tags/types.js.map +0 -1
  323. package/dist/terminal-login/adapter-selector.d.ts.map +0 -1
  324. package/dist/terminal-login/adapter-selector.js.map +0 -1
  325. package/dist/terminal-login/constants.d.ts.map +0 -1
  326. package/dist/terminal-login/constants.js.map +0 -1
  327. package/dist/terminal-login/credentials.d.ts.map +0 -1
  328. package/dist/terminal-login/credentials.js.map +0 -1
  329. package/dist/terminal-login/output.d.ts.map +0 -1
  330. package/dist/terminal-login/output.js.map +0 -1
  331. package/dist/terminal-login/pixiv-terminal-auth.d.ts.map +0 -1
  332. package/dist/terminal-login/pixiv-terminal-auth.js.map +0 -1
  333. package/dist/terminal-login/terminal-login.d.ts.map +0 -1
  334. package/dist/terminal-login/terminal-login.js.map +0 -1
  335. package/dist/terminal-login/token-refresh.d.ts.map +0 -1
  336. package/dist/terminal-login/token-refresh.js.map +0 -1
  337. package/dist/terminal-login/types.d.ts.map +0 -1
  338. package/dist/terminal-login/types.js.map +0 -1
  339. package/dist/terminal-login.d.ts.map +0 -1
  340. package/dist/terminal-login.js.map +0 -1
  341. package/dist/topic/TopicCache.d.ts.map +0 -1
  342. package/dist/topic/TopicCache.js.map +0 -1
  343. package/dist/topic/TopicPipeline.d.ts.map +0 -1
  344. package/dist/topic/TopicPipeline.js.map +0 -1
  345. package/dist/topic/TopicResolver.d.ts.map +0 -1
  346. package/dist/topic/TopicResolver.js.map +0 -1
  347. package/dist/topic/TopicTagScorer.d.ts.map +0 -1
  348. package/dist/topic/TopicTagScorer.js.map +0 -1
  349. package/dist/topic/createTopicPipeline.d.ts.map +0 -1
  350. package/dist/topic/createTopicPipeline.js.map +0 -1
  351. package/dist/topic/types.d.ts.map +0 -1
  352. package/dist/topic/types.js.map +0 -1
  353. package/dist/utils/ai-detection.d.ts.map +0 -1
  354. package/dist/utils/ai-detection.js.map +0 -1
  355. package/dist/utils/concurrency.d.ts.map +0 -1
  356. package/dist/utils/concurrency.js.map +0 -1
  357. package/dist/utils/config-manager/config-normalization.d.ts.map +0 -1
  358. package/dist/utils/config-manager/config-normalization.js.map +0 -1
  359. package/dist/utils/config-manager/config-persistence.d.ts.map +0 -1
  360. package/dist/utils/config-manager/config-persistence.js.map +0 -1
  361. package/dist/utils/config-manager/config-repair.d.ts.map +0 -1
  362. package/dist/utils/config-manager/config-repair.js.map +0 -1
  363. package/dist/utils/config-manager/file-discovery.d.ts.map +0 -1
  364. package/dist/utils/config-manager/file-discovery.js.map +0 -1
  365. package/dist/utils/config-manager/file-operations.d.ts.map +0 -1
  366. package/dist/utils/config-manager/file-operations.js.map +0 -1
  367. package/dist/utils/config-manager.d.ts.map +0 -1
  368. package/dist/utils/config-manager.js.map +0 -1
  369. package/dist/utils/config-parser/field-definitions.d.ts.map +0 -1
  370. package/dist/utils/config-parser/field-definitions.js.map +0 -1
  371. package/dist/utils/config-parser/parser.d.ts.map +0 -1
  372. package/dist/utils/config-parser/parser.js.map +0 -1
  373. package/dist/utils/config-parser/reporter.d.ts.map +0 -1
  374. package/dist/utils/config-parser/reporter.js.map +0 -1
  375. package/dist/utils/config-parser/validator.d.ts.map +0 -1
  376. package/dist/utils/config-parser/validator.js.map +0 -1
  377. package/dist/utils/config-parser.d.ts.map +0 -1
  378. package/dist/utils/config-parser.js.map +0 -1
  379. package/dist/utils/config-path-migrator/auto-fix.d.ts.map +0 -1
  380. package/dist/utils/config-path-migrator/auto-fix.js.map +0 -1
  381. package/dist/utils/config-path-migrator/path-migration-core.d.ts.map +0 -1
  382. package/dist/utils/config-path-migrator/path-migration-core.js.map +0 -1
  383. package/dist/utils/config-path-migrator/storage-path-migration.d.ts.map +0 -1
  384. package/dist/utils/config-path-migrator/storage-path-migration.js.map +0 -1
  385. package/dist/utils/config-path-migrator.d.ts.map +0 -1
  386. package/dist/utils/config-path-migrator.js.map +0 -1
  387. package/dist/utils/config-validator-unified.d.ts.map +0 -1
  388. package/dist/utils/config-validator-unified.js.map +0 -1
  389. package/dist/utils/date-utils.d.ts.map +0 -1
  390. package/dist/utils/date-utils.js.map +0 -1
  391. package/dist/utils/directory-info.d.ts.map +0 -1
  392. package/dist/utils/directory-info.js.map +0 -1
  393. package/dist/utils/errors.d.ts.map +0 -1
  394. package/dist/utils/errors.js.map +0 -1
  395. package/dist/utils/fs.d.ts.map +0 -1
  396. package/dist/utils/fs.js.map +0 -1
  397. package/dist/utils/language-detection.d.ts.map +0 -1
  398. package/dist/utils/language-detection.js.map +0 -1
  399. package/dist/utils/login-helper.d.ts.map +0 -1
  400. package/dist/utils/login-helper.js.map +0 -1
  401. package/dist/utils/pixiv-date-utils.d.ts.map +0 -1
  402. package/dist/utils/pixiv-date-utils.js.map +0 -1
  403. package/dist/utils/pixiv-sort.d.ts.map +0 -1
  404. package/dist/utils/pixiv-sort.js.map +0 -1
  405. package/dist/utils/pixiv-url-parser.d.ts.map +0 -1
  406. package/dist/utils/pixiv-url-parser.js.map +0 -1
  407. package/dist/utils/pixiv-utils.d.ts.map +0 -1
  408. package/dist/utils/pixiv-utils.js.map +0 -1
  409. package/dist/utils/project-root.d.ts.map +0 -1
  410. package/dist/utils/project-root.js.map +0 -1
  411. package/dist/utils/redact.d.ts.map +0 -1
  412. package/dist/utils/redact.js.map +0 -1
  413. package/dist/utils/resource-manager.d.ts.map +0 -1
  414. package/dist/utils/resource-manager.js.map +0 -1
  415. package/dist/utils/stdin.d.ts.map +0 -1
  416. package/dist/utils/stdin.js.map +0 -1
  417. package/dist/utils/target-label.d.ts.map +0 -1
  418. package/dist/utils/target-label.js.map +0 -1
  419. package/dist/utils/token-maintenance.d.ts.map +0 -1
  420. package/dist/utils/token-maintenance.js.map +0 -1
  421. package/dist/utils/token-manager.d.ts.map +0 -1
  422. package/dist/utils/token-manager.js.map +0 -1
  423. package/dist/utils/token-revocation.d.ts.map +0 -1
  424. package/dist/utils/token-revocation.js.map +0 -1
  425. package/dist/utils/type-guards.d.ts.map +0 -1
  426. package/dist/utils/type-guards.js.map +0 -1
  427. package/dist/version.d.ts.map +0 -1
  428. package/dist/version.js.map +0 -1
  429. package/dist/webui/index.d.ts.map +0 -1
  430. package/dist/webui/index.js.map +0 -1
  431. package/dist/webui/ports.d.ts.map +0 -1
  432. package/dist/webui/ports.js.map +0 -1
  433. package/dist/webui/routes/auth.d.ts.map +0 -1
  434. package/dist/webui/routes/auth.js.map +0 -1
  435. package/dist/webui/routes/config-utils.d.ts.map +0 -1
  436. package/dist/webui/routes/config-utils.js.map +0 -1
  437. package/dist/webui/routes/config.d.ts.map +0 -1
  438. package/dist/webui/routes/config.js.map +0 -1
  439. package/dist/webui/routes/download.d.ts.map +0 -1
  440. package/dist/webui/routes/download.js.map +0 -1
  441. package/dist/webui/routes/files.d.ts.map +0 -1
  442. package/dist/webui/routes/files.js.map +0 -1
  443. package/dist/webui/routes/handlers/auth/auth-login-handler.d.ts.map +0 -1
  444. package/dist/webui/routes/handlers/auth/auth-login-handler.js.map +0 -1
  445. package/dist/webui/routes/handlers/auth/auth-logout-handler.d.ts.map +0 -1
  446. package/dist/webui/routes/handlers/auth/auth-logout-handler.js.map +0 -1
  447. package/dist/webui/routes/handlers/auth/auth-status-handler.d.ts.map +0 -1
  448. package/dist/webui/routes/handlers/auth/auth-status-handler.js.map +0 -1
  449. package/dist/webui/routes/handlers/auth/auth-token-handler.d.ts.map +0 -1
  450. package/dist/webui/routes/handlers/auth/auth-token-handler.js.map +0 -1
  451. package/dist/webui/routes/handlers/auth/auth-utils.d.ts.map +0 -1
  452. package/dist/webui/routes/handlers/auth/auth-utils.js.map +0 -1
  453. package/dist/webui/routes/handlers/auth-handlers.d.ts.map +0 -1
  454. package/dist/webui/routes/handlers/auth-handlers.js.map +0 -1
  455. package/dist/webui/routes/handlers/config-backup-handlers.d.ts.map +0 -1
  456. package/dist/webui/routes/handlers/config-backup-handlers.js.map +0 -1
  457. package/dist/webui/routes/handlers/config-basic-handlers.d.ts.map +0 -1
  458. package/dist/webui/routes/handlers/config-basic-handlers.js.map +0 -1
  459. package/dist/webui/routes/handlers/config-diagnostic-handlers.d.ts.map +0 -1
  460. package/dist/webui/routes/handlers/config-diagnostic-handlers.js.map +0 -1
  461. package/dist/webui/routes/handlers/config-files-handlers.d.ts.map +0 -1
  462. package/dist/webui/routes/handlers/config-files-handlers.js.map +0 -1
  463. package/dist/webui/routes/handlers/config-history-handlers.d.ts.map +0 -1
  464. package/dist/webui/routes/handlers/config-history-handlers.js.map +0 -1
  465. package/dist/webui/routes/handlers/download-basic-handlers.d.ts.map +0 -1
  466. package/dist/webui/routes/handlers/download-basic-handlers.js.map +0 -1
  467. package/dist/webui/routes/handlers/download-batch-handlers.d.ts.map +0 -1
  468. package/dist/webui/routes/handlers/download-batch-handlers.js.map +0 -1
  469. package/dist/webui/routes/handlers/download-incomplete-handlers.d.ts.map +0 -1
  470. package/dist/webui/routes/handlers/download-incomplete-handlers.js.map +0 -1
  471. package/dist/webui/routes/handlers/download-url-handlers.d.ts.map +0 -1
  472. package/dist/webui/routes/handlers/download-url-handlers.js.map +0 -1
  473. package/dist/webui/routes/handlers/files-list-handlers.d.ts.map +0 -1
  474. package/dist/webui/routes/handlers/files-list-handlers.js.map +0 -1
  475. package/dist/webui/routes/handlers/files-operations-handlers.d.ts.map +0 -1
  476. package/dist/webui/routes/handlers/files-operations-handlers.js.map +0 -1
  477. package/dist/webui/routes/handlers/logs-handlers.d.ts.map +0 -1
  478. package/dist/webui/routes/handlers/logs-handlers.js.map +0 -1
  479. package/dist/webui/routes/handlers/stats-handlers.d.ts.map +0 -1
  480. package/dist/webui/routes/handlers/stats-handlers.js.map +0 -1
  481. package/dist/webui/routes/logs.d.ts.map +0 -1
  482. package/dist/webui/routes/logs.js.map +0 -1
  483. package/dist/webui/routes/stats.d.ts.map +0 -1
  484. package/dist/webui/routes/stats.js.map +0 -1
  485. package/dist/webui/server/auth-middleware.d.ts.map +0 -1
  486. package/dist/webui/server/auth-middleware.js.map +0 -1
  487. package/dist/webui/server/server-middleware.d.ts.map +0 -1
  488. package/dist/webui/server/server-middleware.js.map +0 -1
  489. package/dist/webui/server/server-routes.d.ts.map +0 -1
  490. package/dist/webui/server/server-routes.js.map +0 -1
  491. package/dist/webui/server/server-static.d.ts.map +0 -1
  492. package/dist/webui/server/server-static.js.map +0 -1
  493. package/dist/webui/server/server-utils.d.ts.map +0 -1
  494. package/dist/webui/server/server-utils.js.map +0 -1
  495. package/dist/webui/server/server.d.ts.map +0 -1
  496. package/dist/webui/server/server.js.map +0 -1
  497. package/dist/webui/server/types.d.ts.map +0 -1
  498. package/dist/webui/server/types.js.map +0 -1
  499. package/dist/webui/services/DownloadTaskManager.d.ts.map +0 -1
  500. package/dist/webui/services/DownloadTaskManager.js.map +0 -1
  501. package/dist/webui/utils/config-validator.d.ts.map +0 -1
  502. package/dist/webui/utils/config-validator.js.map +0 -1
  503. package/dist/webui/utils/error-codes.d.ts.map +0 -1
  504. package/dist/webui/utils/error-codes.js.map +0 -1
  505. package/dist/webui/utils/task-serialize.d.ts.map +0 -1
  506. package/dist/webui/utils/task-serialize.js.map +0 -1
  507. package/dist/webui/websocket/DownloadStatus.d.ts.map +0 -1
  508. package/dist/webui/websocket/DownloadStatus.js.map +0 -1
  509. package/dist/webui/websocket/LogStream.d.ts.map +0 -1
  510. package/dist/webui/websocket/LogStream.js.map +0 -1
@@ -10,7 +10,9 @@ const config_1 = require("../config");
10
10
  const MultiScheduleManager_1 = require("../scheduler/MultiScheduleManager");
11
11
  const ScheduleTriggerServer_1 = require("../scheduler/ScheduleTriggerServer");
12
12
  const SlotCoordinator_1 = require("../scheduler/SlotCoordinator");
13
+ const schedules_1 = require("../scheduler/schedules");
13
14
  const scheduler_runtime_1 = require("./scheduler-runtime");
15
+ const SchedulerIdleLifecycle_1 = require("./SchedulerIdleLifecycle");
14
16
  /**
15
17
  * Decide whether the authenticated HTTP trigger server should be mounted. It is
16
18
  * always up in `external` mode (the external clock needs it); in `internal`
@@ -51,6 +53,7 @@ class SchedulerCommand extends Command_1.BaseCommand {
51
53
  execute: runtime.runJob,
52
54
  database: runtime.database,
53
55
  onFailure: runtime.notifyScheduleFailure,
56
+ onAbandoned: (_config, _schedule, abandoned) => runtime.abandonActiveRun(abandoned.errorMessage ?? 'abandoned after timeout'),
54
57
  telemetry: {
55
58
  beginRun: () => runtime.database.getOverviewStats().totalDownloads,
56
59
  endRun: () => runtime.database.getOverviewStats().totalDownloads,
@@ -65,19 +68,24 @@ class SchedulerCommand extends Command_1.BaseCommand {
65
68
  // Authenticated HTTP trigger. Mounted in external mode (the external clock
66
69
  // wakes the machine here) and, opt-in, in internal mode for manual/ops
67
70
  // triggers. Business logic lives in runJob/SlotCoordinator; this handler
68
- // only authenticates, resolves the occurrence and delegates synchronously
69
- // so the open request is the activity lease for the whole run.
71
+ // only authenticates, records the occurrence durably and hands it to the
72
+ // shared scheduler, then answers. It deliberately does not await the run:
73
+ // the open request is NOT an activity lease, and a 10-40 minute download
74
+ // would outlive the clock/proxy/client timeout carrying it.
70
75
  let triggerServer = null;
71
76
  if (triggerEnabled(runtime.config)) {
72
77
  const rt = runtime.config.schedulerRuntime;
73
78
  const coordinator = new SlotCoordinator_1.SlotCoordinator(runtime.database);
74
79
  const resolveConfig = () => manager['activeConfig'] ?? runtime.config;
75
80
  const findPlan = (cfg, scheduleId) => cfg.schedules?.find((s) => s.id === scheduleId && s.enabled !== false);
76
- // In-process singleflight: coalesce concurrent triggers of the same
77
- // occurrence on THIS process. Optimization only the DB unique
78
- // constraints + ledger are the real correctness guarantee across
79
- // restarts/processes.
80
- const inFlight = new Map();
81
+ // Durable dispatch. The trigger endpoint records the occurrence FIRST,
82
+ // then hands it to the shared scheduler and answers immediately. It must
83
+ // never await the download itself: a 10-40 minute run outlives the
84
+ // router/proxy/clock timeouts, and a request that dies mid-run used to
85
+ // take the occurrence with it. Idempotency comes from the slot ledger and
86
+ // the cross-process lease, so concurrent clocks (Cloudflare + watchdog +
87
+ // manual) converge on one worker instead of needing an in-process
88
+ // singleflight map.
81
89
  triggerServer = new ScheduleTriggerServer_1.ScheduleTriggerServer(ScheduleTriggerServer_1.ScheduleTriggerServer.resolveToken(rt.trigger?.token), {
82
90
  listSchedules: () => manager.scheduleIds(),
83
91
  resolve: (scheduleId, source, at, label) => {
@@ -90,41 +98,61 @@ class SchedulerCommand extends Command_1.BaseCommand {
90
98
  return { error: resolved.error ?? 'could not resolve occurrence', status: resolved.status ?? 400 };
91
99
  return { context: resolved.context };
92
100
  },
93
- run: (scheduleId, context) => {
94
- const key = context.slotId;
95
- const existing = inFlight.get(key);
96
- if (existing)
97
- return existing;
101
+ run: async (scheduleId, context) => {
98
102
  const cfg = resolveConfig();
99
103
  const plan = findPlan(cfg, scheduleId);
100
- const promise = (async () => {
101
- try {
102
- if (!plan)
103
- return { scheduleId, slotId: context.slotId, status: 'failed', alreadyCompleted: false };
104
- // Synchronous: awaited so the HTTP response does not return
105
- // (and release the activity lease) until the run finishes.
106
- await runtime.runJob(cfg, plan, { triggerSource: 'http', slot: context });
107
- const rec = runtime.database.slots.getSlot(context.slotId);
108
- const cells = runtime.database.slots.getCells(context.slotId).map((c) => ({
109
- targetId: c.targetId,
110
- status: c.status,
111
- workId: c.workId,
112
- error: c.lastError,
113
- }));
114
- return {
115
- scheduleId,
116
- slotId: context.slotId,
117
- status: rec?.status ?? 'failed',
118
- alreadyCompleted: rec?.status === 'success' || rec?.status === 'partial',
119
- cells,
120
- };
121
- }
122
- finally {
123
- inFlight.delete(key);
124
- }
125
- })();
126
- inFlight.set(key, promise);
127
- return promise;
104
+ if (!plan) {
105
+ return { scheduleId, slotId: context.slotId, disposition: 'rejected', status: 'failed' };
106
+ }
107
+ const targets = (0, schedules_1.selectScheduleTargets)(cfg.targets, plan);
108
+ if (targets.length === 0) {
109
+ return { scheduleId, slotId: context.slotId, disposition: 'rejected', status: 'pending' };
110
+ }
111
+ // Step 1: make the occurrence durable BEFORE answering. A crash
112
+ // between here and the claim leaves a `pending`, lease-less row that
113
+ // the recovery loop re-dispatches, so an accepted trigger can never
114
+ // be silently dropped.
115
+ const prepared = coordinator.prepare(context, plan, targets);
116
+ if (prepared.alreadyCompleted) {
117
+ return {
118
+ ...coordinator.completedSummary(context.slotId, plan),
119
+ disposition: 'already_completed',
120
+ };
121
+ }
122
+ // Step 2: admit to the scheduler. Fire-and-forget by contract — the
123
+ // boolean only reports whether this process took the work.
124
+ const admitted = manager.triggerSchedule(scheduleId, {
125
+ triggerSource: 'http',
126
+ slot: context,
127
+ });
128
+ const rec = runtime.database.slots.getSlot(context.slotId);
129
+ if (admitted) {
130
+ return {
131
+ scheduleId,
132
+ slotId: context.slotId,
133
+ disposition: 'accepted',
134
+ status: rec?.status ?? 'pending',
135
+ };
136
+ }
137
+ // Not admitted locally. A live lease means another worker already
138
+ // owns the occurrence; anything else is a refusal this process could
139
+ // not take (busy/stopped/limit), which the clock may retry and the
140
+ // recovery loop will also pick up.
141
+ const lease = runtime.database.slots.getSlotLease(context.slotId);
142
+ if (lease.owner && lease.until && lease.until > Date.now()) {
143
+ return {
144
+ scheduleId,
145
+ slotId: context.slotId,
146
+ disposition: 'already_running',
147
+ status: 'running',
148
+ };
149
+ }
150
+ return {
151
+ scheduleId,
152
+ slotId: context.slotId,
153
+ disposition: 'rejected',
154
+ status: rec?.status ?? 'pending',
155
+ };
128
156
  },
129
157
  drainOutbox: () => runtime.drainOutbox(),
130
158
  status: (scheduleId) => {
@@ -145,15 +173,47 @@ class SchedulerCommand extends Command_1.BaseCommand {
145
173
  ? 'External scheduler mode: internal cron disabled, awaiting authenticated schedule triggers'
146
174
  : 'Internal scheduler mode with authenticated HTTP trigger enabled', { port });
147
175
  }
148
- const cleanup = () => {
149
- context.logger.info('Shutting down PixivFlow');
176
+ // Run-to-completion lifecycle for the external clock. The machine was woken
177
+ // by a trigger and has to return to `stopped` by itself. Platform-side
178
+ // auto-stop cannot decide that: the trigger answers long before the run
179
+ // finishes, so the proxy sees an idle socket while downloads are still in
180
+ // flight. The worker therefore reads its OWN durable ledger (see
181
+ // SchedulerIdleLifecycle). Wired after the trigger server so the port is
182
+ // already being served when the first idle probe runs.
183
+ let idleLifecycle = null;
184
+ const shutdown = (reason, code = 0) => {
185
+ context.logger.info('Shutting down PixivFlow', { reason });
186
+ idleLifecycle?.stop();
150
187
  triggerServer?.stop();
151
188
  manager.stop();
152
189
  runtime.close();
153
- process.exit(0);
190
+ process.exit(code);
154
191
  };
155
- process.on('SIGINT', cleanup);
156
- process.on('SIGTERM', cleanup);
192
+ const scheduling = runtime.config.schedulerRuntime;
193
+ if (scheduling?.mode === 'external' && scheduling.exitWhenIdle) {
194
+ idleLifecycle = new SchedulerIdleLifecycle_1.SchedulerIdleLifecycle({
195
+ snapshot: () => {
196
+ const outbox = runtime.database.outbox.counts();
197
+ return {
198
+ activeSlots: runtime.database.slots.countActiveSlots(),
199
+ processingOutbox: outbox.processing,
200
+ // `pending` covers pending + retry_wait: a delivery still backing
201
+ // off is active work. Backoff is bounded (rows flip to `dead` once
202
+ // attempts are exhausted, which does not block exit), so this
203
+ // cannot hold the machine open indefinitely.
204
+ pendingOutbox: outbox.pending,
205
+ };
206
+ },
207
+ idleGraceMs: scheduling.idleGraceMs,
208
+ maxLifetimeMs: scheduling.maxLifetimeMs,
209
+ onExit: (reason) => shutdown(reason === 'idle'
210
+ ? 'external worker idle: slot ledger and outbox are both empty'
211
+ : 'external worker exceeded the maxLifetimeMs backstop'),
212
+ });
213
+ idleLifecycle.start();
214
+ }
215
+ process.on('SIGINT', () => shutdown('SIGINT'));
216
+ process.on('SIGTERM', () => shutdown('SIGTERM'));
157
217
  process.on('SIGHUP', () => {
158
218
  context.logger.info('Received SIGHUP; reloading scheduler configuration');
159
219
  manager.reload();
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Run-to-completion lifecycle for the external-clock (autosleep) deployment.
3
+ *
4
+ * A PixivFlow machine can be woken by an authenticated HTTP trigger, execute a
5
+ * schedule, deliver through the durable outbox, and must then return to
6
+ * `stopped` on its own. Two things make that non-trivial:
7
+ *
8
+ * - Platform-side auto-stop cannot be used. The trigger endpoint answers as
9
+ * soon as the occurrence is durable (deliberately — a 10-40 minute run cannot
10
+ * survive a router/proxy/client timeout), so from the proxy's point of view
11
+ * the machine goes idle while the run is still in progress. Auto-stop would
12
+ * kill a download mid-flight.
13
+ * - "The request returned" and "nobody connected recently" are both wrong
14
+ * signals. HTTP activity says nothing about whether a download is still
15
+ * running, a Slot is still open, or an outbox row is still retrying.
16
+ *
17
+ * So this reads the worker's OWN durable ledger instead, and exits only when
18
+ * that ledger says nothing is left: no non-terminal Slot, no in-flight outbox
19
+ * row, no undelivered outbox row. It is deliberately the only thing it does —
20
+ * no second business state machine, no scheduler of its own, no queue.
21
+ *
22
+ * The idle grace is a merge window, not a timeout: two schedules ten minutes
23
+ * apart are normally served by a single wake-up, and a delivery retry that lands
24
+ * just after the run is drained without paying for a second cold start.
25
+ *
26
+ * `maxLifetimeMs` is a backstop, never the normal path. It exists so a worker
27
+ * that somehow never reaches idle (a wedged run, a Slot that recovery cannot
28
+ * re-dispatch) cannot bill indefinitely. Reaching it deletes nothing: the
29
+ * process closes its database cleanly and the next wake resumes from the same
30
+ * Slot/outbox rows.
31
+ */
32
+ export type IdleExitReason = 'idle' | 'max-lifetime';
33
+ /**
34
+ * A read-only view of the worker's own authoritative state. Every field must be
35
+ * derived from durable rows, never from in-memory scheduling or HTTP activity.
36
+ */
37
+ export interface IdleSnapshot {
38
+ /** Non-terminal Slots (pending/running): work this ledger still owes. */
39
+ activeSlots: number;
40
+ /** Outbox rows currently claimed by this or another live worker. */
41
+ processingOutbox: number;
42
+ /** Undelivered outbox rows (pending, or waiting on a bounded retry backoff). */
43
+ pendingOutbox: number;
44
+ }
45
+ /** True only when the worker has no work of any kind left. */
46
+ export declare function isIdle(snapshot: IdleSnapshot): boolean;
47
+ /** Default idle grace: long enough to merge two schedules ~10 minutes apart. */
48
+ export declare const DEFAULT_IDLE_GRACE_MS: number;
49
+ /** Default backstop on awake time; the idle detector is the normal exit path. */
50
+ export declare const DEFAULT_MAX_LIFETIME_MS: number;
51
+ /** How often the durable ledger is re-read. */
52
+ export declare const DEFAULT_IDLE_POLL_MS: number;
53
+ export interface IdleLifecycleOptions {
54
+ /** Reads the worker's authoritative state. Must not mutate anything. */
55
+ snapshot(): IdleSnapshot;
56
+ /**
57
+ * Invoked at most once, either on real idleness or on the hard backstop. The
58
+ * caller owns the shutdown itself (stop serving, flush, exit).
59
+ */
60
+ onExit(reason: IdleExitReason): void;
61
+ idleGraceMs?: number;
62
+ maxLifetimeMs?: number;
63
+ pollMs?: number;
64
+ }
65
+ export declare class SchedulerIdleLifecycle {
66
+ private readonly options;
67
+ private readonly idleGraceMs;
68
+ private readonly maxLifetimeMs;
69
+ private readonly pollMs;
70
+ private pollTimer;
71
+ private lifetimeTimer;
72
+ private idleSince;
73
+ private exiting;
74
+ constructor(options: IdleLifecycleOptions);
75
+ /** Arm polling and the backstop. Idempotent. */
76
+ start(): void;
77
+ /** Disarm without exiting. Idempotent; used by the signal shutdown path. */
78
+ stop(): void;
79
+ private poll;
80
+ private exit;
81
+ }
82
+ //# sourceMappingURL=SchedulerIdleLifecycle.d.ts.map
@@ -0,0 +1,135 @@
1
+ "use strict";
2
+ /**
3
+ * Run-to-completion lifecycle for the external-clock (autosleep) deployment.
4
+ *
5
+ * A PixivFlow machine can be woken by an authenticated HTTP trigger, execute a
6
+ * schedule, deliver through the durable outbox, and must then return to
7
+ * `stopped` on its own. Two things make that non-trivial:
8
+ *
9
+ * - Platform-side auto-stop cannot be used. The trigger endpoint answers as
10
+ * soon as the occurrence is durable (deliberately — a 10-40 minute run cannot
11
+ * survive a router/proxy/client timeout), so from the proxy's point of view
12
+ * the machine goes idle while the run is still in progress. Auto-stop would
13
+ * kill a download mid-flight.
14
+ * - "The request returned" and "nobody connected recently" are both wrong
15
+ * signals. HTTP activity says nothing about whether a download is still
16
+ * running, a Slot is still open, or an outbox row is still retrying.
17
+ *
18
+ * So this reads the worker's OWN durable ledger instead, and exits only when
19
+ * that ledger says nothing is left: no non-terminal Slot, no in-flight outbox
20
+ * row, no undelivered outbox row. It is deliberately the only thing it does —
21
+ * no second business state machine, no scheduler of its own, no queue.
22
+ *
23
+ * The idle grace is a merge window, not a timeout: two schedules ten minutes
24
+ * apart are normally served by a single wake-up, and a delivery retry that lands
25
+ * just after the run is drained without paying for a second cold start.
26
+ *
27
+ * `maxLifetimeMs` is a backstop, never the normal path. It exists so a worker
28
+ * that somehow never reaches idle (a wedged run, a Slot that recovery cannot
29
+ * re-dispatch) cannot bill indefinitely. Reaching it deletes nothing: the
30
+ * process closes its database cleanly and the next wake resumes from the same
31
+ * Slot/outbox rows.
32
+ */
33
+ Object.defineProperty(exports, "__esModule", { value: true });
34
+ exports.SchedulerIdleLifecycle = exports.DEFAULT_IDLE_POLL_MS = exports.DEFAULT_MAX_LIFETIME_MS = exports.DEFAULT_IDLE_GRACE_MS = void 0;
35
+ exports.isIdle = isIdle;
36
+ const logger_1 = require("../logger");
37
+ /** True only when the worker has no work of any kind left. */
38
+ function isIdle(snapshot) {
39
+ return (snapshot.activeSlots === 0 &&
40
+ snapshot.processingOutbox === 0 &&
41
+ snapshot.pendingOutbox === 0);
42
+ }
43
+ /** Default idle grace: long enough to merge two schedules ~10 minutes apart. */
44
+ exports.DEFAULT_IDLE_GRACE_MS = 10 * 60 * 1000;
45
+ /** Default backstop on awake time; the idle detector is the normal exit path. */
46
+ exports.DEFAULT_MAX_LIFETIME_MS = 3 * 60 * 60 * 1000;
47
+ /** How often the durable ledger is re-read. */
48
+ exports.DEFAULT_IDLE_POLL_MS = 15 * 1000;
49
+ class SchedulerIdleLifecycle {
50
+ options;
51
+ idleGraceMs;
52
+ maxLifetimeMs;
53
+ pollMs;
54
+ pollTimer = null;
55
+ lifetimeTimer = null;
56
+ idleSince = null;
57
+ exiting = false;
58
+ constructor(options) {
59
+ this.options = options;
60
+ this.idleGraceMs = options.idleGraceMs ?? exports.DEFAULT_IDLE_GRACE_MS;
61
+ this.maxLifetimeMs = options.maxLifetimeMs ?? exports.DEFAULT_MAX_LIFETIME_MS;
62
+ this.pollMs = options.pollMs ?? exports.DEFAULT_IDLE_POLL_MS;
63
+ }
64
+ /** Arm polling and the backstop. Idempotent. */
65
+ start() {
66
+ if (this.pollTimer || this.lifetimeTimer)
67
+ return;
68
+ logger_1.logger.info('External worker lifecycle armed; will exit once real idleness is confirmed', {
69
+ idleGraceMs: this.idleGraceMs,
70
+ maxLifetimeMs: this.maxLifetimeMs,
71
+ pollMs: this.pollMs,
72
+ });
73
+ this.lifetimeTimer = setTimeout(() => this.exit('max-lifetime'), this.maxLifetimeMs);
74
+ this.pollTimer = setInterval(() => this.poll(), this.pollMs);
75
+ }
76
+ /** Disarm without exiting. Idempotent; used by the signal shutdown path. */
77
+ stop() {
78
+ if (this.pollTimer) {
79
+ clearInterval(this.pollTimer);
80
+ this.pollTimer = null;
81
+ }
82
+ if (this.lifetimeTimer) {
83
+ clearTimeout(this.lifetimeTimer);
84
+ this.lifetimeTimer = null;
85
+ }
86
+ }
87
+ poll() {
88
+ if (this.exiting)
89
+ return;
90
+ let snapshot;
91
+ try {
92
+ snapshot = this.options.snapshot();
93
+ }
94
+ catch (error) {
95
+ // A failed read is NOT evidence of idleness. Stay awake and retry; the
96
+ // backstop still bounds how long a broken probe can hold the machine.
97
+ logger_1.logger.warn('Idle probe failed; staying awake and will retry', {
98
+ error: error instanceof Error ? error.message : String(error),
99
+ });
100
+ return;
101
+ }
102
+ if (!isIdle(snapshot)) {
103
+ if (this.idleSince !== null) {
104
+ logger_1.logger.info('External worker picked up new work; idle grace window cancelled', { ...snapshot });
105
+ }
106
+ this.idleSince = null;
107
+ return;
108
+ }
109
+ if (this.idleGraceMs === 0) {
110
+ this.exit('idle');
111
+ return;
112
+ }
113
+ const now = Date.now();
114
+ if (this.idleSince === null) {
115
+ this.idleSince = now;
116
+ logger_1.logger.info('External worker idle; starting exit grace window', {
117
+ idleGraceMs: this.idleGraceMs,
118
+ ...snapshot,
119
+ });
120
+ return;
121
+ }
122
+ if (now - this.idleSince >= this.idleGraceMs) {
123
+ this.exit('idle');
124
+ }
125
+ }
126
+ exit(reason) {
127
+ if (this.exiting)
128
+ return;
129
+ this.exiting = true;
130
+ this.stop();
131
+ this.options.onExit(reason);
132
+ }
133
+ }
134
+ exports.SchedulerIdleLifecycle = SchedulerIdleLifecycle;
135
+ //# sourceMappingURL=SchedulerIdleLifecycle.js.map
@@ -61,6 +61,12 @@ export interface SchedulerRuntime {
61
61
  runJob(snapshot: StandaloneConfig, schedule: ScheduleConfig, options?: RunJobOptions): Promise<void>;
62
62
  /** Cancel the in-flight download plan, if any. */
63
63
  cancelActive(reason: string): void;
64
+ /**
65
+ * The active run never settled after its timeout and drain window. Stop
66
+ * renewing its lease and take its Slot terminal so recovery cannot re-dispatch
67
+ * the same occurrence alongside the still-running job.
68
+ */
69
+ abandonActiveRun(reason: string): void;
64
70
  /** Notify the affected review group after a failed scheduled run. */
65
71
  notifyScheduleFailure(snapshot: StandaloneConfig, schedule: ScheduleConfig, failure: JobFailure): Promise<void>;
66
72
  /** Start the independent outbox pump (long-running daemon). */
@@ -203,6 +203,12 @@ async function createSchedulerRuntime(configPathArg) {
203
203
  tokenMaintenance.start();
204
204
  }
205
205
  let activeDownloadManager = null;
206
+ /**
207
+ * Lease bookkeeping of the run that currently owns a Slot, exposed to
208
+ * cancelActive()/abandonActiveRun() (defined below, outside runJob's closure).
209
+ * Set while a lease is held and cleared as soon as it is released.
210
+ */
211
+ let activeLeaseHooks = null;
206
212
  // Independently-pumped durable outbox (content + notifications). Started in
207
213
  // the long-running scheduler daemon; run-once drains explicitly before exit.
208
214
  const deliveryDispatcher = new DeliveryDispatcher_1.DeliveryDispatcher(config.delivery, buildProxyUrl(config.network));
@@ -252,6 +258,12 @@ async function createSchedulerRuntime(configPathArg) {
252
258
  // resolve a canonical occurrence and converge on one durable Slot.
253
259
  let slotCtx = null;
254
260
  let varReleaseLease = null;
261
+ /**
262
+ * Set when the run was abandoned past its drain window. A wedged job can
263
+ * still return much later; it must not roll the slot up again (which would
264
+ * overwrite the terminal `failed` record the abandon path wrote).
265
+ */
266
+ let slotAbandoned = false;
255
267
  if (!adhoc) {
256
268
  if (providedSlot) {
257
269
  slotCtx = providedSlot;
@@ -271,19 +283,20 @@ async function createSchedulerRuntime(configPathArg) {
271
283
  }
272
284
  const activeSlot = slotCtx;
273
285
  const runOwner = `run-${process.pid}-${(0, node_crypto_1.randomUUID)().slice(0, 8)}`;
274
- const leaseMs = Math.max(60_000, (schedule.timeout ?? 30 * 60_000) + 60_000);
275
- const begin = coordinator.begin(slotCtx, schedule, targets);
276
- if (begin.alreadyCompleted && !onlyTarget) {
286
+ const prepared = coordinator.prepare(slotCtx, schedule, targets);
287
+ if (prepared.alreadyCompleted && !onlyTarget) {
277
288
  logger_1.logger.info('Slot already terminal; nothing to do', {
278
289
  slot: activeSlot.slotId,
279
- status: begin.slotRec.status,
290
+ status: prepared.slotRec.status,
280
291
  });
281
292
  return;
282
293
  }
283
294
  // Cross-process lease: a concurrent Cloudflare + watchdog + manual
284
295
  // trigger on a SECOND process sees an active lease and converges instead
285
- // of running the same targets in parallel.
286
- const claimed = coordinator.claimRunLease(activeSlot.slotId, runOwner, leaseMs);
296
+ // of running the same targets in parallel. The lease TTL bounds how long a
297
+ // DEAD worker blocks its slot; it is intentionally independent of
298
+ // schedule.timeout (which bounds how long a live run may take).
299
+ const claimed = coordinator.claimRunLease(activeSlot.slotId, runOwner, SlotCoordinator_1.SLOT_LEASE_TTL_MS);
287
300
  if (!claimed) {
288
301
  const lease = database.slots.getSlotLease(activeSlot.slotId);
289
302
  logger_1.logger.info('Slot run already leased by another worker; converging', {
@@ -295,12 +308,48 @@ async function createSchedulerRuntime(configPathArg) {
295
308
  await outboxWorker.drainOnce(1);
296
309
  return;
297
310
  }
298
- const heartbeat = setInterval(() => coordinator.heartbeatLease(activeSlot.slotId, runOwner, leaseMs), Math.floor(leaseMs / 3));
311
+ // Only the process that actually owns the lease may report the slot as
312
+ // running; the accepting adapter records it as pending instead.
313
+ coordinator.markRunning(activeSlot.slotId);
314
+ let cancelled = false;
315
+ const heartbeat = setInterval(() => {
316
+ // A cancelled/timed-out run must stop renewing its lease. An infinitely
317
+ // renewed lease is precisely what made a wedged run unrecoverable: the
318
+ // heartbeat outlived the scheduler timeout, so no other worker could ever
319
+ // claim the slot and the occurrence stayed stuck in `running` forever.
320
+ if (cancelled) {
321
+ clearInterval(heartbeat);
322
+ return;
323
+ }
324
+ coordinator.heartbeatLease(activeSlot.slotId, runOwner, SlotCoordinator_1.SLOT_LEASE_TTL_MS);
325
+ }, SlotCoordinator_1.SLOT_HEARTBEAT_MS);
299
326
  heartbeat.unref?.();
300
327
  varReleaseLease = () => {
301
328
  clearInterval(heartbeat);
329
+ activeLeaseHooks = null;
302
330
  coordinator.releaseRunLease(activeSlot.slotId, runOwner);
303
331
  };
332
+ activeLeaseHooks = {
333
+ stopHeartbeat: () => {
334
+ cancelled = true;
335
+ clearInterval(heartbeat);
336
+ },
337
+ /**
338
+ * The run will never settle: stop heart-beating AND leave the ledger
339
+ * terminal, so the recovery sweep (which only acts on `pending`/`running`
340
+ * slots with no live lease) cannot re-dispatch this occurrence next to a
341
+ * still-running job. Merely dropping the lease would be wrong here —
342
+ * unlike a crash, this worker is alive.
343
+ */
344
+ abandon: (reason) => {
345
+ cancelled = true;
346
+ slotAbandoned = true;
347
+ clearInterval(heartbeat);
348
+ database.slots.markSlotStatus(activeSlot.slotId, 'failed', `abandoned after scheduler timeout; no delivery (${reason})`);
349
+ coordinator.releaseRunLease(activeSlot.slotId, runOwner);
350
+ activeLeaseHooks = null;
351
+ },
352
+ };
304
353
  }
305
354
  // For a scheduled run, skip cells already in a terminal state (resume never
306
355
  // re-runs a finished cell — that is what prevents a second post). Membership
@@ -310,6 +359,10 @@ async function createSchedulerRuntime(configPathArg) {
310
359
  if (slotCtx && runTargets.length === 0) {
311
360
  logger_1.logger.info('All slot cells already complete', { slot: slotCtx.slotId });
312
361
  coordinator.finish(slotCtx, schedule, targets);
362
+ // Release LAST: the slot must stay owned until its aggregate state has
363
+ // been rolled up, otherwise a concurrent trigger could claim and re-run it
364
+ // against a half-finished ledger.
365
+ varReleaseLease?.();
313
366
  return;
314
367
  }
315
368
  // Ad-hoc refetch with a filter that matched no target (or all filtered out):
@@ -398,6 +451,10 @@ async function createSchedulerRuntime(configPathArg) {
398
451
  }
399
452
  catch (error) {
400
453
  if (!slotCtx || !(error instanceof Error) || !/^All \d+ target\(s\) failed\./.test(error.message)) {
454
+ // Abnormal abort: no roll-up is possible, so hand the slot back now
455
+ // instead of holding it until the lease TTL expires. Recovery sees a
456
+ // non-terminal slot with no live lease and resumes the SAME occurrence.
457
+ releaseLease?.();
401
458
  throw error;
402
459
  }
403
460
  // Scheduled Slots treat terminal target failures (failed/no_candidate) as
@@ -408,10 +465,9 @@ async function createSchedulerRuntime(configPathArg) {
408
465
  finally {
409
466
  if (activeDownloadManager === downloadManager)
410
467
  activeDownloadManager = null;
411
- releaseLease?.();
412
468
  }
413
469
  const duration = Math.round((Date.now() - startTime) / 1000);
414
- if (slotCtx) {
470
+ if (slotCtx && !slotAbandoned) {
415
471
  const summary = coordinator.finish(slotCtx, schedule, targets);
416
472
  notificationPolicy.sendSlotSummary(slotCtx, schedule, summary.cells.map((c) => {
417
473
  const t = targets.find((x) => x.id === c.targetId);
@@ -437,6 +493,17 @@ async function createSchedulerRuntime(configPathArg) {
437
493
  };
438
494
  const cancelActive = (reason) => {
439
495
  activeDownloadManager?.cancel(reason);
496
+ // Scheduler timeout / process shutdown also stop the lease heartbeat. The run
497
+ // is on its way out (or about to be killed with the process), so it must not
498
+ // keep the slot locked while it unwinds. A shutdown deliberately leaves the
499
+ // slot NON-terminal: recovery resumes the same occurrence after restart.
500
+ activeLeaseHooks?.stopHeartbeat();
501
+ };
502
+ const abandonActiveRun = (reason) => {
503
+ // Cancellation did not take effect inside the drain window — a request that
504
+ // ignores the abort. This run will never settle, so finish the lease
505
+ // bookkeeping it cannot do itself.
506
+ activeLeaseHooks?.abandon(reason);
440
507
  };
441
508
  const close = () => {
442
509
  outboxWorker.stop();
@@ -454,6 +521,7 @@ async function createSchedulerRuntime(configPathArg) {
454
521
  tokenMaintenance,
455
522
  runJob,
456
523
  cancelActive,
524
+ abandonActiveRun,
457
525
  startOutboxWorker: () => outboxWorker.start(),
458
526
  drainOutbox: () => outboxWorker.drainOnce(),
459
527
  notifyScheduleFailure: (snapshot, schedule, failure) => notifyScheduleFailure(snapshot, database, schedule, failure),
@@ -426,6 +426,39 @@ export interface SchedulerRuntimeConfig {
426
426
  * One pending run per plan is retained; extra ticks are skipped.
427
427
  */
428
428
  queueLimit?: number;
429
+ /**
430
+ * External-mode run-to-completion lifecycle. When true, the daemon exits the
431
+ * process once its OWN durable ledger says there is nothing left to do, so a
432
+ * machine that was woken by an external clock returns to `stopped` by itself
433
+ * instead of idling on a fixed clock until the platform stops it.
434
+ *
435
+ * The platform's proxy-side auto-stop cannot make this call. The HTTP trigger
436
+ * answers as soon as the occurrence is durable (deliberately — a 10-40 minute
437
+ * run cannot survive a router/proxy/client timeout), so the proxy sees an idle
438
+ * socket while the real run is still going and would stop the machine
439
+ * mid-download. Only this process knows when its work is finished.
440
+ *
441
+ * Ignored in `internal` mode, where cron owns the process lifetime.
442
+ * Default: false.
443
+ */
444
+ exitWhenIdle?: boolean;
445
+ /**
446
+ * How long to stay awake after the last observed work, before exiting in
447
+ * `exitWhenIdle` mode. This is a merge window rather than a timeout: two
448
+ * schedules ten minutes apart are normally served by ONE wake-up, and a
449
+ * delivery retry that lands shortly after the run is drained without paying
450
+ * for a second cold start. Default: 600000ms (10 minutes).
451
+ */
452
+ idleGraceMs?: number;
453
+ /**
454
+ * Hard backstop on total awake time in `exitWhenIdle` mode. The native idle
455
+ * detector is the normal exit path and this should never fire; it exists so a
456
+ * worker that never reaches idle (a wedged run, a Slot recovery cannot
457
+ * re-dispatch) cannot bill indefinitely. On expiry the daemon logs the reason,
458
+ * closes its database cleanly and exits. Nothing is deleted — the next wake
459
+ * resumes from the same durable Slot/outbox rows. Default: 10800000ms (3h).
460
+ */
461
+ maxLifetimeMs?: number;
429
462
  /**
430
463
  * External-mode HTTP trigger settings. Ignored in internal mode.
431
464
  */