github-issue-tower-defence-management 2.19.1 → 2.19.3

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 (795) hide show
  1. package/bin/adapter/entry-points/cli/index.js +1 -3
  2. package/bin/adapter/entry-points/cli/index.js.map +1 -1
  3. package/bin/adapter/entry-points/console/consoleOperationApi.js +6 -12
  4. package/bin/adapter/entry-points/console/consoleOperationApi.js.map +1 -1
  5. package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js +1 -1
  6. package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js.map +1 -1
  7. package/bin/adapter/repositories/issue/RestIssueRepository.js +9 -2
  8. package/bin/adapter/repositories/issue/RestIssueRepository.js.map +1 -1
  9. package/package.json +5 -1
  10. package/types/adapter/entry-points/cli/index.d.ts.map +1 -1
  11. package/types/adapter/entry-points/console/consoleOperationApi.d.ts.map +1 -1
  12. package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts +1 -1
  13. package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts.map +1 -1
  14. package/types/adapter/repositories/issue/RestIssueRepository.d.ts +5 -1
  15. package/types/adapter/repositories/issue/RestIssueRepository.d.ts.map +1 -1
  16. package/types/domain/usecases/adapter-interfaces/IssueRepository.d.ts +1 -1
  17. package/types/domain/usecases/adapter-interfaces/IssueRepository.d.ts.map +1 -1
  18. package/.env.example +0 -7
  19. package/.gitattributes +0 -2
  20. package/.github/CODEOWNERS +0 -6
  21. package/.github/copilot-instructions.md +0 -6
  22. package/.github/dependabot.yml +0 -19
  23. package/.github/instructions/code-review.instructions.md +0 -50
  24. package/.github/workflows/api-created_issue_pr.yml +0 -97
  25. package/.github/workflows/ci-failure-issue-creator.yml +0 -48
  26. package/.github/workflows/close-manual-prs.yml +0 -80
  27. package/.github/workflows/codeql.yml +0 -107
  28. package/.github/workflows/commit-lint.yml +0 -85
  29. package/.github/workflows/configs/commitlint.config.cjs +0 -32
  30. package/.github/workflows/configs/commitlint.config.js +0 -32
  31. package/.github/workflows/console-ui.yml +0 -68
  32. package/.github/workflows/create-pr.yml +0 -116
  33. package/.github/workflows/empty-format-test-job.yml +0 -34
  34. package/.github/workflows/format.yml +0 -28
  35. package/.github/workflows/publish.yml +0 -111
  36. package/.github/workflows/secret-scan.yml +0 -51
  37. package/.github/workflows/test.yml +0 -58
  38. package/.github/workflows/umino-project.yml +0 -207
  39. package/.gitleaks.toml +0 -22
  40. package/.prettierignore +0 -31
  41. package/.prettierrc +0 -5
  42. package/CHANGELOG.md +0 -4162
  43. package/CONTRIBUTING.md +0 -60
  44. package/bin/adapter/entry-points/handlers/resolveUnansweredOwnerCalls.js +0 -12
  45. package/bin/adapter/entry-points/handlers/resolveUnansweredOwnerCalls.js.map +0 -1
  46. package/bin/adapter/repositories/FileSystemSilentSessionNotifiedStateRepository.js +0 -125
  47. package/bin/adapter/repositories/FileSystemSilentSessionNotifiedStateRepository.js.map +0 -1
  48. package/bin/adapter/repositories/issue/issueTimelineUtils.js +0 -38
  49. package/bin/adapter/repositories/issue/issueTimelineUtils.js.map +0 -1
  50. package/bin/domain/entities/UnansweredOwnerCall.js +0 -3
  51. package/bin/domain/entities/UnansweredOwnerCall.js.map +0 -1
  52. package/bin/domain/entities/WorkingTime.js +0 -3
  53. package/bin/domain/entities/WorkingTime.js.map +0 -1
  54. package/bin/domain/usecases/ChangeStatusLongInReviewIssueUseCase.js +0 -37
  55. package/bin/domain/usecases/ChangeStatusLongInReviewIssueUseCase.js.map +0 -1
  56. package/bin/domain/usecases/ConvertCheckboxToIssueInStoryIssueUseCase.js +0 -118
  57. package/bin/domain/usecases/ConvertCheckboxToIssueInStoryIssueUseCase.js.map +0 -1
  58. package/bin/domain/usecases/GenerateWorkingTimeReportUseCase.js +0 -180
  59. package/bin/domain/usecases/GenerateWorkingTimeReportUseCase.js.map +0 -1
  60. package/bin/domain/usecases/TriagerApprovalDispatchUseCase.js +0 -160
  61. package/bin/domain/usecases/TriagerApprovalDispatchUseCase.js.map +0 -1
  62. package/bin/domain/usecases/adapter-interfaces/SilentSessionNotifiedStateRepository.js +0 -3
  63. package/bin/domain/usecases/adapter-interfaces/SilentSessionNotifiedStateRepository.js.map +0 -1
  64. package/bin/domain/usecases/awaitingOwnerApprovalMessage.js +0 -7
  65. package/bin/domain/usecases/awaitingOwnerApprovalMessage.js.map +0 -1
  66. package/bin/domain/usecases/findLastAgentDeclaringReport.js +0 -12
  67. package/bin/domain/usecases/findLastAgentDeclaringReport.js.map +0 -1
  68. package/bin/domain/usecases/isPullRequestDeclaredUnnecessary.js +0 -25
  69. package/bin/domain/usecases/isPullRequestDeclaredUnnecessary.js.map +0 -1
  70. package/bin/domain/usecases/isWaitingForOwnerApproval.js +0 -16
  71. package/bin/domain/usecases/isWaitingForOwnerApproval.js.map +0 -1
  72. package/bin/domain/usecases/returnedToAwaitingWorkspaceMessage.js +0 -7
  73. package/bin/domain/usecases/returnedToAwaitingWorkspaceMessage.js.map +0 -1
  74. package/commitlint.config.js +0 -6
  75. package/eslint.config.mjs +0 -76
  76. package/jest.config.js +0 -84
  77. package/jest.setup.js +0 -1
  78. package/lib/buffer-equal-constant-time-compat/index.js +0 -17
  79. package/lib/buffer-equal-constant-time-compat/index.test.js +0 -54
  80. package/lib/buffer-equal-constant-time-compat/package.json +0 -5
  81. package/lib/typescript-compat/bin/tsc +0 -2
  82. package/lib/typescript-compat/bin/tsserver +0 -2
  83. package/lib/typescript-compat/lib/typescript.js +0 -1
  84. package/lib/typescript-compat/package.json +0 -13
  85. package/renovate.json +0 -46
  86. package/scripts/copyConsoleUiDist.mjs +0 -35
  87. package/scripts/defaultBranchTipVerify.sh +0 -23
  88. package/scripts/testWorkflowRunVerify.sh +0 -47
  89. package/scripts/verifyConsoleUiDist.mjs +0 -96
  90. package/src/adapter/ci/consoleUiWorkflowTimeout.test.ts +0 -61
  91. package/src/adapter/ci/publishTestWorkflowGate.test.ts +0 -653
  92. package/src/adapter/ci/workflowRunCancellation.test.ts +0 -72
  93. package/src/adapter/entry-points/cli/fleetConfig.test.ts +0 -553
  94. package/src/adapter/entry-points/cli/fleetConfig.ts +0 -266
  95. package/src/adapter/entry-points/cli/index.test.ts +0 -2837
  96. package/src/adapter/entry-points/cli/index.ts +0 -1488
  97. package/src/adapter/entry-points/cli/projectConfig.test.ts +0 -342
  98. package/src/adapter/entry-points/cli/projectConfig.ts +0 -500
  99. package/src/adapter/entry-points/console/consoleAirplaneSnapshotApi.test.ts +0 -658
  100. package/src/adapter/entry-points/console/consoleAirplaneSnapshotApi.ts +0 -482
  101. package/src/adapter/entry-points/console/consoleDataDelivery.test.ts +0 -222
  102. package/src/adapter/entry-points/console/consoleDataDelivery.ts +0 -134
  103. package/src/adapter/entry-points/console/consoleDoneStore.test.ts +0 -169
  104. package/src/adapter/entry-points/console/consoleDoneStore.ts +0 -139
  105. package/src/adapter/entry-points/console/consoleGithubTokenResolver.test.ts +0 -275
  106. package/src/adapter/entry-points/console/consoleGithubTokenResolver.ts +0 -113
  107. package/src/adapter/entry-points/console/consoleImageProxy.test.ts +0 -140
  108. package/src/adapter/entry-points/console/consoleImageProxy.ts +0 -111
  109. package/src/adapter/entry-points/console/consoleItemUrlLookup.test.ts +0 -63
  110. package/src/adapter/entry-points/console/consoleItemUrlLookup.ts +0 -66
  111. package/src/adapter/entry-points/console/consoleOperationApi.test.ts +0 -2523
  112. package/src/adapter/entry-points/console/consoleOperationApi.ts +0 -936
  113. package/src/adapter/entry-points/console/consoleProjectResolver.test.ts +0 -246
  114. package/src/adapter/entry-points/console/consoleProjectResolver.ts +0 -111
  115. package/src/adapter/entry-points/console/consoleReadApi.test.ts +0 -714
  116. package/src/adapter/entry-points/console/consoleReadApi.ts +0 -361
  117. package/src/adapter/entry-points/console/consoleTabNames.test.ts +0 -52
  118. package/src/adapter/entry-points/console/consoleTabNames.ts +0 -37
  119. package/src/adapter/entry-points/console/consoleUiDistSync.test.ts +0 -66
  120. package/src/adapter/entry-points/console/dashboardComposeService.test.ts +0 -595
  121. package/src/adapter/entry-points/console/dashboardComposeService.ts +0 -256
  122. package/src/adapter/entry-points/console/dashboardEmitterComposerKey.test.ts +0 -180
  123. package/src/adapter/entry-points/console/ensureConsoleRunning.test.ts +0 -115
  124. package/src/adapter/entry-points/console/ensureConsoleRunning.ts +0 -64
  125. package/src/adapter/entry-points/console/ui/.storybook/main.ts +0 -12
  126. package/src/adapter/entry-points/console/ui/.storybook/preview.ts +0 -15
  127. package/src/adapter/entry-points/console/ui/biome.json +0 -53
  128. package/src/adapter/entry-points/console/ui/components.json +0 -20
  129. package/src/adapter/entry-points/console/ui/e2e/consoleScenario.spec.ts +0 -725
  130. package/src/adapter/entry-points/console/ui/e2e/consoleTestHarness.ts +0 -831
  131. package/src/adapter/entry-points/console/ui/e2e/playwright.config.ts +0 -30
  132. package/src/adapter/entry-points/console/ui/e2e/tsconfig.json +0 -18
  133. package/src/adapter/entry-points/console/ui/index.html +0 -12
  134. package/src/adapter/entry-points/console/ui/jest-globals.cjs +0 -26
  135. package/src/adapter/entry-points/console/ui/jest.setup.ts +0 -1
  136. package/src/adapter/entry-points/console/ui/jest.styleMock.js +0 -1
  137. package/src/adapter/entry-points/console/ui/public/sw.js +0 -60
  138. package/src/adapter/entry-points/console/ui/src/components/ui/badge.stories.tsx +0 -35
  139. package/src/adapter/entry-points/console/ui/src/components/ui/badge.tsx +0 -28
  140. package/src/adapter/entry-points/console/ui/src/components/ui/button.stories.tsx +0 -34
  141. package/src/adapter/entry-points/console/ui/src/components/ui/button.tsx +0 -50
  142. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleCopyCodeButton.stories.tsx +0 -28
  143. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleCopyCodeButton.test.tsx +0 -65
  144. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleCopyCodeButton.tsx +0 -19
  145. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleMarkdownContent.stories.tsx +0 -61
  146. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleMarkdownContent.test.tsx +0 -199
  147. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleMarkdownContent.tsx +0 -166
  148. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleMermaidDiagram.stories.tsx +0 -22
  149. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleMermaidDiagram.test.tsx +0 -74
  150. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleMermaidDiagram.tsx +0 -84
  151. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleMermaidDiagramModalScreen.stories.tsx +0 -27
  152. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleMermaidDiagramModalScreen.test.tsx +0 -85
  153. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleMermaidDiagramModalScreen.tsx +0 -97
  154. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleReferenceLink.stories.tsx +0 -76
  155. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleReferenceLink.test.tsx +0 -56
  156. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleReferenceLink.tsx +0 -39
  157. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleChangedFileList.stories.tsx +0 -32
  158. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleChangedFileList.test.tsx +0 -105
  159. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleChangedFileList.tsx +0 -157
  160. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommentComposer.stories.tsx +0 -68
  161. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommentComposer.test.tsx +0 -612
  162. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommentComposer.tsx +0 -276
  163. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommentList.stories.tsx +0 -33
  164. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommentList.test.tsx +0 -57
  165. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommentList.tsx +0 -76
  166. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommitList.stories.tsx +0 -25
  167. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommitList.test.tsx +0 -55
  168. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommitList.tsx +0 -49
  169. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCopyUrlButton.stories.tsx +0 -24
  170. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCopyUrlButton.test.tsx +0 -63
  171. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCopyUrlButton.tsx +0 -19
  172. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleFetchFailureAlert.stories.tsx +0 -33
  173. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleFetchFailureAlert.test.tsx +0 -62
  174. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleFetchFailureAlert.tsx +0 -59
  175. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleFileDiff.stories.tsx +0 -38
  176. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleFileDiff.test.tsx +0 -151
  177. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleFileDiff.tsx +0 -245
  178. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleItemDetail.stories.tsx +0 -514
  179. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleItemDetail.test.tsx +0 -727
  180. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleItemDetail.tsx +0 -387
  181. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleItemIcon.stories.tsx +0 -82
  182. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleItemIcon.test.tsx +0 -52
  183. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleItemIcon.tsx +0 -32
  184. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestDetail.stories.tsx +0 -48
  185. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestDetail.test.tsx +0 -235
  186. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestDetail.tsx +0 -145
  187. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestMergeableChip.stories.tsx +0 -29
  188. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestMergeableChip.test.tsx +0 -30
  189. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestMergeableChip.tsx +0 -50
  190. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestStatusBadges.stories.tsx +0 -38
  191. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestStatusBadges.test.tsx +0 -54
  192. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestStatusBadges.tsx +0 -60
  193. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleAirplaneModeButton.stories.tsx +0 -56
  194. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleAirplaneModeButton.test.tsx +0 -111
  195. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleAirplaneModeButton.tsx +0 -92
  196. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsolePanel.stories.tsx +0 -51
  197. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsolePanel.test.tsx +0 -59
  198. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsolePanel.tsx +0 -39
  199. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTabList.stories.tsx +0 -207
  200. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTabList.test.tsx +0 -332
  201. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTabList.tsx +0 -211
  202. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTimerSettingsModalDialog.stories.tsx +0 -49
  203. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTimerSettingsModalDialog.test.tsx +0 -157
  204. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTimerSettingsModalDialog.tsx +0 -118
  205. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleCopyStoryNameButton.stories.tsx +0 -23
  206. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleCopyStoryNameButton.test.tsx +0 -40
  207. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleCopyStoryNameButton.tsx +0 -17
  208. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleItemList.stories.tsx +0 -101
  209. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleItemList.test.tsx +0 -102
  210. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleItemList.tsx +0 -75
  211. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleItemSummary.stories.tsx +0 -40
  212. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleItemSummary.test.tsx +0 -213
  213. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleItemSummary.tsx +0 -115
  214. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleQueuedItemRow.stories.tsx +0 -50
  215. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleQueuedItemRow.test.tsx +0 -98
  216. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleQueuedItemRow.tsx +0 -66
  217. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleQueuedList.stories.tsx +0 -69
  218. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleQueuedList.test.tsx +0 -101
  219. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleQueuedList.tsx +0 -75
  220. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryList.stories.tsx +0 -137
  221. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryList.test.tsx +0 -554
  222. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryList.tsx +0 -400
  223. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryReorderPanel.stories.tsx +0 -30
  224. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryReorderPanel.test.tsx +0 -111
  225. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryReorderPanel.tsx +0 -91
  226. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStorySummary.stories.tsx +0 -27
  227. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStorySummary.test.tsx +0 -24
  228. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStorySummary.tsx +0 -28
  229. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleCloseActions.stories.tsx +0 -14
  230. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleCloseActions.test.tsx +0 -21
  231. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleCloseActions.tsx +0 -26
  232. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleDangerousActions.stories.tsx +0 -14
  233. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleDangerousActions.test.tsx +0 -40
  234. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleDangerousActions.tsx +0 -37
  235. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleErrorToast.stories.tsx +0 -33
  236. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleNextActionDateActions.stories.tsx +0 -20
  237. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleNextActionDateActions.test.tsx +0 -48
  238. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleNextActionDateActions.tsx +0 -42
  239. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleOfflinePendingActionsPanel.stories.tsx +0 -115
  240. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleOfflinePendingActionsPanel.test.tsx +0 -262
  241. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleOfflinePendingActionsPanel.tsx +0 -123
  242. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleOperationMenu.stories.tsx +0 -65
  243. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleOperationMenu.test.tsx +0 -159
  244. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleOperationMenu.tsx +0 -55
  245. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsolePullRequestReviewActions.stories.tsx +0 -20
  246. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsolePullRequestReviewActions.test.tsx +0 -93
  247. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsolePullRequestReviewActions.tsx +0 -45
  248. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleStatusActions.stories.tsx +0 -17
  249. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleStatusActions.test.tsx +0 -49
  250. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleStatusActions.tsx +0 -66
  251. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleUndoToast.stories.tsx +0 -47
  252. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleUndoToast.test.tsx +0 -95
  253. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleUndoToast.tsx +0 -62
  254. package/src/adapter/entry-points/console/ui/src/features/console/components/shared/ConsoleClipboardCopyButton.stories.tsx +0 -32
  255. package/src/adapter/entry-points/console/ui/src/features/console/components/shared/ConsoleClipboardCopyButton.test.tsx +0 -101
  256. package/src/adapter/entry-points/console/ui/src/features/console/components/shared/ConsoleClipboardCopyButton.tsx +0 -80
  257. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useAirplaneMode.test.ts +0 -304
  258. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useAirplaneMode.ts +0 -167
  259. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleActionQueue.test.ts +0 -498
  260. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleActionQueue.ts +0 -277
  261. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleCaches.test.ts +0 -99
  262. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleCaches.ts +0 -90
  263. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleDetailPrefetch.test.ts +0 -110
  264. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleDetailPrefetch.ts +0 -83
  265. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleFeaturesConfig.test.ts +0 -51
  266. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleFeaturesConfig.ts +0 -39
  267. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleItemDetailData.test.ts +0 -229
  268. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleItemDetailData.ts +0 -194
  269. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleNavigation.test.ts +0 -151
  270. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleNavigation.ts +0 -128
  271. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleOperations.test.ts +0 -629
  272. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleOperations.ts +0 -417
  273. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleOverlay.test.ts +0 -40
  274. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleOverlay.ts +0 -71
  275. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsolePjcode.test.ts +0 -24
  276. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsolePjcode.ts +0 -17
  277. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleProjectList.test.ts +0 -97
  278. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleProjectList.ts +0 -44
  279. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleProjectTimer.test.ts +0 -59
  280. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleProjectTimer.ts +0 -26
  281. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleReferenceLink.test.ts +0 -48
  282. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleReferenceLink.ts +0 -39
  283. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleResource.test.ts +0 -41
  284. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleResource.ts +0 -57
  285. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleSwipeNavigation.test.ts +0 -115
  286. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleSwipeNavigation.ts +0 -115
  287. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleTabData.test.ts +0 -546
  288. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleTabData.ts +0 -278
  289. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleTimerSettings.test.ts +0 -110
  290. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleTimerSettings.ts +0 -70
  291. package/src/adapter/entry-points/console/ui/src/features/console/lib/airplaneSnapshot.test.ts +0 -387
  292. package/src/adapter/entry-points/console/ui/src/features/console/lib/airplaneSnapshot.ts +0 -407
  293. package/src/adapter/entry-points/console/ui/src/features/console/lib/consoleApi.test.ts +0 -446
  294. package/src/adapter/entry-points/console/ui/src/features/console/lib/consoleApi.ts +0 -533
  295. package/src/adapter/entry-points/console/ui/src/features/console/lib/imageProxy.test.ts +0 -78
  296. package/src/adapter/entry-points/console/ui/src/features/console/lib/imageProxy.ts +0 -34
  297. package/src/adapter/entry-points/console/ui/src/features/console/lib/markdown.test.ts +0 -252
  298. package/src/adapter/entry-points/console/ui/src/features/console/lib/markdown.ts +0 -207
  299. package/src/adapter/entry-points/console/ui/src/features/console/lib/mermaidLoader.test.ts +0 -27
  300. package/src/adapter/entry-points/console/ui/src/features/console/lib/mermaidLoader.ts +0 -71
  301. package/src/adapter/entry-points/console/ui/src/features/console/lib/mermaidZoom.test.ts +0 -88
  302. package/src/adapter/entry-points/console/ui/src/features/console/lib/mermaidZoom.ts +0 -34
  303. package/src/adapter/entry-points/console/ui/src/features/console/lib/navigation.ts +0 -7
  304. package/src/adapter/entry-points/console/ui/src/features/console/lib/resourceCache.test.ts +0 -104
  305. package/src/adapter/entry-points/console/ui/src/features/console/lib/resourceCache.ts +0 -79
  306. package/src/adapter/entry-points/console/ui/src/features/console/logic/actionToast.test.ts +0 -200
  307. package/src/adapter/entry-points/console/ui/src/features/console/logic/actionToast.ts +0 -122
  308. package/src/adapter/entry-points/console/ui/src/features/console/logic/clipboard.test.ts +0 -90
  309. package/src/adapter/entry-points/console/ui/src/features/console/logic/clipboard.ts +0 -37
  310. package/src/adapter/entry-points/console/ui/src/features/console/logic/colors.test.ts +0 -34
  311. package/src/adapter/entry-points/console/ui/src/features/console/logic/colors.ts +0 -85
  312. package/src/adapter/entry-points/console/ui/src/features/console/logic/diff.test.ts +0 -81
  313. package/src/adapter/entry-points/console/ui/src/features/console/logic/diff.ts +0 -61
  314. package/src/adapter/entry-points/console/ui/src/features/console/logic/fileStatus.test.ts +0 -35
  315. package/src/adapter/entry-points/console/ui/src/features/console/logic/fileStatus.ts +0 -21
  316. package/src/adapter/entry-points/console/ui/src/features/console/logic/fileTree.test.ts +0 -96
  317. package/src/adapter/entry-points/console/ui/src/features/console/logic/fileTree.ts +0 -93
  318. package/src/adapter/entry-points/console/ui/src/features/console/logic/grouping.test.ts +0 -229
  319. package/src/adapter/entry-points/console/ui/src/features/console/logic/grouping.ts +0 -106
  320. package/src/adapter/entry-points/console/ui/src/features/console/logic/imageFile.test.ts +0 -18
  321. package/src/adapter/entry-points/console/ui/src/features/console/logic/imageFile.ts +0 -16
  322. package/src/adapter/entry-points/console/ui/src/features/console/logic/itemIcons.test.ts +0 -97
  323. package/src/adapter/entry-points/console/ui/src/features/console/logic/itemIcons.ts +0 -95
  324. package/src/adapter/entry-points/console/ui/src/features/console/logic/navigation.test.ts +0 -53
  325. package/src/adapter/entry-points/console/ui/src/features/console/logic/navigation.ts +0 -33
  326. package/src/adapter/entry-points/console/ui/src/features/console/logic/operations.test.ts +0 -72
  327. package/src/adapter/entry-points/console/ui/src/features/console/logic/operations.ts +0 -61
  328. package/src/adapter/entry-points/console/ui/src/features/console/logic/overlay.test.ts +0 -325
  329. package/src/adapter/entry-points/console/ui/src/features/console/logic/overlay.ts +0 -94
  330. package/src/adapter/entry-points/console/ui/src/features/console/logic/postedComments.test.ts +0 -44
  331. package/src/adapter/entry-points/console/ui/src/features/console/logic/postedComments.ts +0 -17
  332. package/src/adapter/entry-points/console/ui/src/features/console/logic/references.test.ts +0 -103
  333. package/src/adapter/entry-points/console/ui/src/features/console/logic/references.ts +0 -64
  334. package/src/adapter/entry-points/console/ui/src/features/console/logic/relativeTime.test.ts +0 -74
  335. package/src/adapter/entry-points/console/ui/src/features/console/logic/relativeTime.ts +0 -53
  336. package/src/adapter/entry-points/console/ui/src/features/console/logic/swipe.test.ts +0 -34
  337. package/src/adapter/entry-points/console/ui/src/features/console/logic/swipe.ts +0 -28
  338. package/src/adapter/entry-points/console/ui/src/features/console/logic/tabAdvance.test.ts +0 -89
  339. package/src/adapter/entry-points/console/ui/src/features/console/logic/tabAdvance.ts +0 -30
  340. package/src/adapter/entry-points/console/ui/src/features/console/logic/timerSettings.test.ts +0 -61
  341. package/src/adapter/entry-points/console/ui/src/features/console/logic/timerSettings.ts +0 -77
  342. package/src/adapter/entry-points/console/ui/src/features/console/logic/types.ts +0 -173
  343. package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsoleItemDetailContainer.test.tsx +0 -629
  344. package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsoleItemDetailContainer.tsx +0 -351
  345. package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.test.tsx +0 -1469
  346. package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.tsx +0 -736
  347. package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsoleReferenceLinkContainer.tsx +0 -25
  348. package/src/adapter/entry-points/console/ui/src/features/console/testing/fixtures.ts +0 -418
  349. package/src/adapter/entry-points/console/ui/src/index.css +0 -1592
  350. package/src/adapter/entry-points/console/ui/src/lib/utils.ts +0 -4
  351. package/src/adapter/entry-points/console/ui/src/main.tsx +0 -21
  352. package/src/adapter/entry-points/console/ui/src/vite-env.d.ts +0 -1
  353. package/src/adapter/entry-points/console/ui/tsconfig.json +0 -26
  354. package/src/adapter/entry-points/console/ui/vite.config.ts +0 -24
  355. package/src/adapter/entry-points/console/ui-dist/assets/index-BYDbK1Fj.js +0 -88
  356. package/src/adapter/entry-points/console/ui-dist/assets/index-KyQISRuQ.css +0 -2
  357. package/src/adapter/entry-points/console/ui-dist/index.html +0 -13
  358. package/src/adapter/entry-points/console/ui-dist/sw.js +0 -60
  359. package/src/adapter/entry-points/console/webServer.test.ts +0 -2945
  360. package/src/adapter/entry-points/console/webServer.ts +0 -940
  361. package/src/adapter/entry-points/function/getStoryObjectMap.ts +0 -3
  362. package/src/adapter/entry-points/handlers/FileSystemConsoleTabsRepository.test.ts +0 -218
  363. package/src/adapter/entry-points/handlers/FileSystemConsoleTabsRepository.ts +0 -113
  364. package/src/adapter/entry-points/handlers/GetStoryObjectMapUseCaseHandler.test.ts +0 -110
  365. package/src/adapter/entry-points/handlers/GetStoryObjectMapUseCaseHandler.ts +0 -116
  366. package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.test.ts +0 -859
  367. package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.ts +0 -984
  368. package/src/adapter/entry-points/handlers/InTmuxByHumanSessionTokenCountHandler.test.ts +0 -126
  369. package/src/adapter/entry-points/handlers/InTmuxByHumanSessionTokenCountHandler.ts +0 -80
  370. package/src/adapter/entry-points/handlers/LiveSessionOauthTokenSelectHandler.test.ts +0 -491
  371. package/src/adapter/entry-points/handlers/LiveSessionOauthTokenSelectHandler.ts +0 -137
  372. package/src/adapter/entry-points/handlers/OauthTokenSelectHandler.test.ts +0 -508
  373. package/src/adapter/entry-points/handlers/OauthTokenSelectHandler.ts +0 -156
  374. package/src/adapter/entry-points/handlers/consoleListsWriter.test.ts +0 -332
  375. package/src/adapter/entry-points/handlers/consoleListsWriter.ts +0 -60
  376. package/src/adapter/entry-points/handlers/dashboardRowWriter.test.ts +0 -112
  377. package/src/adapter/entry-points/handlers/dashboardRowWriter.ts +0 -51
  378. package/src/adapter/entry-points/handlers/inTmuxByHumanDataWriter.test.ts +0 -285
  379. package/src/adapter/entry-points/handlers/inTmuxByHumanDataWriter.ts +0 -109
  380. package/src/adapter/entry-points/handlers/inTmuxByHumanSessionReconciler.test.ts +0 -170
  381. package/src/adapter/entry-points/handlers/inTmuxByHumanSessionReconciler.ts +0 -40
  382. package/src/adapter/entry-points/handlers/machineStatusWriter.test.ts +0 -191
  383. package/src/adapter/entry-points/handlers/machineStatusWriter.ts +0 -110
  384. package/src/adapter/entry-points/handlers/notifySilentTmuxSessions.test.ts +0 -548
  385. package/src/adapter/entry-points/handlers/notifySilentTmuxSessions.ts +0 -192
  386. package/src/adapter/entry-points/handlers/ownerCallFileCleaner.test.ts +0 -111
  387. package/src/adapter/entry-points/handlers/ownerCallFileCleaner.ts +0 -25
  388. package/src/adapter/entry-points/handlers/ownerCallFileStore.test.ts +0 -286
  389. package/src/adapter/entry-points/handlers/ownerCallFileStore.ts +0 -145
  390. package/src/adapter/entry-points/handlers/ownerReplyMarkerDirectoryResolve.test.ts +0 -55
  391. package/src/adapter/entry-points/handlers/ownerReplyMarkerDirectoryResolve.ts +0 -36
  392. package/src/adapter/entry-points/handlers/resetDegeneratedTmuxSessions.test.ts +0 -72
  393. package/src/adapter/entry-points/handlers/resetDegeneratedTmuxSessions.ts +0 -74
  394. package/src/adapter/entry-points/handlers/rotationOrderFileWriter.test.ts +0 -181
  395. package/src/adapter/entry-points/handlers/rotationOrderFileWriter.ts +0 -18
  396. package/src/adapter/entry-points/handlers/situationFileWriter.test.ts +0 -489
  397. package/src/adapter/entry-points/handlers/situationFileWriter.ts +0 -176
  398. package/src/adapter/entry-points/handlers/staleTmuxSessionCleaner.test.ts +0 -169
  399. package/src/adapter/entry-points/handlers/staleTmuxSessionCleaner.ts +0 -32
  400. package/src/adapter/entry-points/handlers/subscriptionDisabledTokenHandler.ts +0 -41
  401. package/src/adapter/entry-points/handlers/tokenExhaustionHandover.ts +0 -81
  402. package/src/adapter/entry-points/handlers/tokenStatusWriter.test.ts +0 -266
  403. package/src/adapter/entry-points/handlers/tokenStatusWriter.ts +0 -262
  404. package/src/adapter/proxy/ClaudeMessageResponseParser.test.ts +0 -211
  405. package/src/adapter/proxy/ClaudeMessageResponseParser.ts +0 -181
  406. package/src/adapter/proxy/RateLimitCache.test.ts +0 -1077
  407. package/src/adapter/proxy/RateLimitCache.ts +0 -385
  408. package/src/adapter/proxy/TokenListLoader.test.ts +0 -183
  409. package/src/adapter/proxy/TokenListLoader.ts +0 -75
  410. package/src/adapter/proxy/ensureProxyRunning.test.ts +0 -85
  411. package/src/adapter/proxy/ensureProxyRunning.ts +0 -41
  412. package/src/adapter/proxy/proxyEntry.test.ts +0 -552
  413. package/src/adapter/proxy/proxyEntry.ts +0 -214
  414. package/src/adapter/repositories/BaseGitHubRepository.test.ts +0 -26
  415. package/src/adapter/repositories/BaseGitHubRepository.ts +0 -26
  416. package/src/adapter/repositories/BrowserGitHubProjectRepository.test.ts +0 -108
  417. package/src/adapter/repositories/BrowserGitHubProjectRepository.ts +0 -118
  418. package/src/adapter/repositories/CliGitHubGraphqlRateLimitRepository.test.ts +0 -71
  419. package/src/adapter/repositories/CliGitHubGraphqlRateLimitRepository.ts +0 -21
  420. package/src/adapter/repositories/ConfigurableSilentSessionMessageComposer.test.ts +0 -324
  421. package/src/adapter/repositories/ConfigurableSilentSessionMessageComposer.ts +0 -148
  422. package/src/adapter/repositories/FetchWebhookRepository.ts +0 -8
  423. package/src/adapter/repositories/FileHandoverStateRepository.test.ts +0 -114
  424. package/src/adapter/repositories/FileHandoverStateRepository.ts +0 -73
  425. package/src/adapter/repositories/FileSystemInteractiveLiveSessionTranscriptResolver.test.ts +0 -514
  426. package/src/adapter/repositories/FileSystemInteractiveLiveSessionTranscriptResolver.ts +0 -109
  427. package/src/adapter/repositories/FileSystemKevReportWatermarkRepository.test.ts +0 -284
  428. package/src/adapter/repositories/FileSystemKevReportWatermarkRepository.ts +0 -128
  429. package/src/adapter/repositories/FileSystemSessionAssistantTurnsRepository.test.ts +0 -138
  430. package/src/adapter/repositories/FileSystemSessionAssistantTurnsRepository.ts +0 -113
  431. package/src/adapter/repositories/FileSystemSessionDegenerationCooldownStateRepository.test.ts +0 -121
  432. package/src/adapter/repositories/FileSystemSessionDegenerationCooldownStateRepository.ts +0 -103
  433. package/src/adapter/repositories/FileSystemSessionOutputActivityRepository.test.ts +0 -360
  434. package/src/adapter/repositories/FileSystemSessionOutputActivityRepository.ts +0 -77
  435. package/src/adapter/repositories/FileSystemSessionRecordReader.test.ts +0 -59
  436. package/src/adapter/repositories/FileSystemSessionRecordReader.ts +0 -31
  437. package/src/adapter/repositories/FileSystemSilentSessionCandidateStateRepository.test.ts +0 -287
  438. package/src/adapter/repositories/FileSystemSilentSessionCandidateStateRepository.ts +0 -125
  439. package/src/adapter/repositories/FileSystemSilentSessionHubTaskStatusCacheRepository.test.ts +0 -285
  440. package/src/adapter/repositories/FileSystemSilentSessionHubTaskStatusCacheRepository.ts +0 -119
  441. package/src/adapter/repositories/FileSystemSubAgentLivenessResolver.test.ts +0 -237
  442. package/src/adapter/repositories/FileSystemSubAgentLivenessResolver.ts +0 -57
  443. package/src/adapter/repositories/FileSystemSubAgentSilentSecondsResolver.test.ts +0 -56
  444. package/src/adapter/repositories/FileSystemSubAgentSilentSecondsResolver.ts +0 -32
  445. package/src/adapter/repositories/FileSystemSubAgentTranscriptDirectoryResolver.test.ts +0 -68
  446. package/src/adapter/repositories/FileSystemSubAgentTranscriptDirectoryResolver.ts +0 -23
  447. package/src/adapter/repositories/GitHubIssueCheckpointRepository.test.ts +0 -51
  448. package/src/adapter/repositories/GitHubIssueCheckpointRepository.ts +0 -47
  449. package/src/adapter/repositories/GitHubIssueCommentRepository.test.ts +0 -278
  450. package/src/adapter/repositories/GitHubIssueCommentRepository.ts +0 -109
  451. package/src/adapter/repositories/GoogleSpreadsheetRepository.integration.test.ts +0 -265
  452. package/src/adapter/repositories/GoogleSpreadsheetRepository.test.ts +0 -431
  453. package/src/adapter/repositories/GoogleSpreadsheetRepository.ts +0 -268
  454. package/src/adapter/repositories/GraphqlProjectRepository.diskCache.test.ts +0 -272
  455. package/src/adapter/repositories/GraphqlProjectRepository.errorHandling.test.ts +0 -111
  456. package/src/adapter/repositories/GraphqlProjectRepository.fetchProjectId.test.ts +0 -254
  457. package/src/adapter/repositories/GraphqlProjectRepository.test.ts +0 -209
  458. package/src/adapter/repositories/GraphqlProjectRepository.ts +0 -707
  459. package/src/adapter/repositories/GraphqlProjectRepositoryProjectLocation.test.ts +0 -297
  460. package/src/adapter/repositories/KyHttpRepository.ts +0 -8
  461. package/src/adapter/repositories/KySlackRepository.test.ts +0 -172
  462. package/src/adapter/repositories/KySlackRepository.ts +0 -298
  463. package/src/adapter/repositories/LocalCommandIssueAttachmentRepository.test.ts +0 -193
  464. package/src/adapter/repositories/LocalCommandIssueAttachmentRepository.ts +0 -106
  465. package/src/adapter/repositories/LocalProcessLiveSessionProcessSnapshotProvider.test.ts +0 -190
  466. package/src/adapter/repositories/LocalProcessLiveSessionProcessSnapshotProvider.ts +0 -102
  467. package/src/adapter/repositories/LocalStorageCacheRepository.test.ts +0 -265
  468. package/src/adapter/repositories/LocalStorageCacheRepository.ts +0 -79
  469. package/src/adapter/repositories/LocalStorageRepository.integration.test.ts +0 -142
  470. package/src/adapter/repositories/LocalStorageRepository.test.ts +0 -173
  471. package/src/adapter/repositories/LocalStorageRepository.ts +0 -30
  472. package/src/adapter/repositories/NoUnansweredOwnerCallStatusProvider.test.ts +0 -15
  473. package/src/adapter/repositories/NoUnansweredOwnerCallStatusProvider.ts +0 -9
  474. package/src/adapter/repositories/NodeLocalCommandRunner.largeOutput.test.ts +0 -16
  475. package/src/adapter/repositories/NodeLocalCommandRunner.test.ts +0 -118
  476. package/src/adapter/repositories/NodeLocalCommandRunner.ts +0 -66
  477. package/src/adapter/repositories/NodeProcessSignalRepository.test.ts +0 -82
  478. package/src/adapter/repositories/NodeProcessSignalRepository.ts +0 -47
  479. package/src/adapter/repositories/NodeSubAgentProcessLister.test.ts +0 -44
  480. package/src/adapter/repositories/NodeSubAgentProcessLister.ts +0 -40
  481. package/src/adapter/repositories/NodeTmuxSessionRepository.test.ts +0 -546
  482. package/src/adapter/repositories/NodeTmuxSessionRepository.ts +0 -245
  483. package/src/adapter/repositories/OauthAPIClaudeRepository.ts +0 -279
  484. package/src/adapter/repositories/OauthAPIProxyClaudeRepository.test.ts +0 -166
  485. package/src/adapter/repositories/OauthAPIProxyClaudeRepository.ts +0 -32
  486. package/src/adapter/repositories/OauthProxyClaudeRepository.test.ts +0 -428
  487. package/src/adapter/repositories/OauthProxyClaudeRepository.ts +0 -124
  488. package/src/adapter/repositories/ProcClaudeHandoverSessionRepository.test.ts +0 -242
  489. package/src/adapter/repositories/ProcClaudeHandoverSessionRepository.ts +0 -261
  490. package/src/adapter/repositories/ProcClaudeInteractiveSessionRepository.test.ts +0 -184
  491. package/src/adapter/repositories/ProcClaudeInteractiveSessionRepository.ts +0 -138
  492. package/src/adapter/repositories/ProcClaudeLiveSessionRepository.test.ts +0 -208
  493. package/src/adapter/repositories/ProcClaudeLiveSessionRepository.ts +0 -130
  494. package/src/adapter/repositories/ProcFsProcessEnvironReader.test.ts +0 -60
  495. package/src/adapter/repositories/ProcFsProcessEnvironReader.ts +0 -38
  496. package/src/adapter/repositories/ProcHostMetricsRepository.test.ts +0 -207
  497. package/src/adapter/repositories/ProcHostMetricsRepository.ts +0 -181
  498. package/src/adapter/repositories/ProcTakeOwnershipSpawnRepository.test.ts +0 -231
  499. package/src/adapter/repositories/ProcTakeOwnershipSpawnRepository.ts +0 -134
  500. package/src/adapter/repositories/ProcessListSessionSubAgentActivityRepository.test.ts +0 -121
  501. package/src/adapter/repositories/ProcessListSessionSubAgentActivityRepository.ts +0 -58
  502. package/src/adapter/repositories/ProjectIssuesCacheRepository.test.ts +0 -218
  503. package/src/adapter/repositories/ProjectIssuesCacheRepository.ts +0 -141
  504. package/src/adapter/repositories/ProxyClaudeTokenUsageRepository.test.ts +0 -1195
  505. package/src/adapter/repositories/ProxyClaudeTokenUsageRepository.ts +0 -185
  506. package/src/adapter/repositories/ProxyRateLimitCacheRepository.test.ts +0 -116
  507. package/src/adapter/repositories/ProxyRateLimitCacheRepository.ts +0 -67
  508. package/src/adapter/repositories/RateLimitSnapshotRepository.ts +0 -50
  509. package/src/adapter/repositories/RealSleeper.test.ts +0 -19
  510. package/src/adapter/repositories/RealSleeper.ts +0 -12
  511. package/src/adapter/repositories/RestProjectRepository.test.ts +0 -277
  512. package/src/adapter/repositories/RestProjectRepository.ts +0 -129
  513. package/src/adapter/repositories/SqliteClaudeMessageResponseRepository.test.ts +0 -313
  514. package/src/adapter/repositories/SqliteClaudeMessageResponseRepository.ts +0 -164
  515. package/src/adapter/repositories/SystemDateRepository.test.ts +0 -60
  516. package/src/adapter/repositories/SystemDateRepository.ts +0 -34
  517. package/src/adapter/repositories/TmuxSilentSessionNotificationRepository.test.ts +0 -253
  518. package/src/adapter/repositories/TmuxSilentSessionNotificationRepository.ts +0 -158
  519. package/src/adapter/repositories/TranscriptOwnerCallStatusProvider.queuedReply.test.ts +0 -171
  520. package/src/adapter/repositories/TranscriptOwnerCallStatusProvider.singleScan.test.ts +0 -86
  521. package/src/adapter/repositories/TranscriptOwnerCallStatusProvider.test.ts +0 -996
  522. package/src/adapter/repositories/TranscriptOwnerCallStatusProvider.ts +0 -396
  523. package/src/adapter/repositories/TranscriptRefusalTailStatusProvider.test.ts +0 -208
  524. package/src/adapter/repositories/TranscriptRefusalTailStatusProvider.ts +0 -80
  525. package/src/adapter/repositories/TranscriptSessionSubAgentActivityRepository.test.ts +0 -1674
  526. package/src/adapter/repositories/TranscriptSessionSubAgentActivityRepository.ts +0 -459
  527. package/src/adapter/repositories/clSessionScopeUnitName.test.ts +0 -76
  528. package/src/adapter/repositories/clSessionScopeUnitName.ts +0 -5
  529. package/src/adapter/repositories/clSessionScopeUnitNameFromCgroupContent.test.ts +0 -34
  530. package/src/adapter/repositories/clSessionScopeUnitNameFromCgroupContent.ts +0 -8
  531. package/src/adapter/repositories/githubGraphqlClient.test.ts +0 -479
  532. package/src/adapter/repositories/githubGraphqlClient.ts +0 -243
  533. package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.test.ts +0 -5182
  534. package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.ts +0 -3303
  535. package/src/adapter/repositories/issue/ApiV3IssueRepository.test.ts +0 -46
  536. package/src/adapter/repositories/issue/ApiV3IssueRepository.ts +0 -88
  537. package/src/adapter/repositories/issue/GraphqlProjectItemRepository.test.ts +0 -1776
  538. package/src/adapter/repositories/issue/GraphqlProjectItemRepository.ts +0 -1552
  539. package/src/adapter/repositories/issue/RestIssueRepository.test.ts +0 -393
  540. package/src/adapter/repositories/issue/RestIssueRepository.ts +0 -252
  541. package/src/adapter/repositories/issue/gitHubRawUrl.test.ts +0 -29
  542. package/src/adapter/repositories/issue/gitHubRawUrl.ts +0 -30
  543. package/src/adapter/repositories/issue/githubRateLimitRetry.test.ts +0 -167
  544. package/src/adapter/repositories/issue/githubRateLimitRetry.ts +0 -109
  545. package/src/adapter/repositories/localStorageCacheDirectory.test.ts +0 -81
  546. package/src/adapter/repositories/localStorageCacheDirectory.ts +0 -13
  547. package/src/adapter/repositories/projectFieldDefinition.test.ts +0 -191
  548. package/src/adapter/repositories/projectFieldDefinition.ts +0 -144
  549. package/src/adapter/repositories/utils.test.ts +0 -55
  550. package/src/adapter/repositories/utils.ts +0 -45
  551. package/src/domain/entities/ClaudeHandoverSession.ts +0 -12
  552. package/src/domain/entities/ClaudeMessageResponse.ts +0 -31
  553. package/src/domain/entities/ClaudeTokenUsage.ts +0 -17
  554. package/src/domain/entities/ClaudeWindowUsage.ts +0 -5
  555. package/src/domain/entities/Comment.ts +0 -5
  556. package/src/domain/entities/InteractiveLiveSession.ts +0 -6
  557. package/src/domain/entities/Issue.ts +0 -31
  558. package/src/domain/entities/KevReportWatermark.ts +0 -4
  559. package/src/domain/entities/LiveSessionActivitySnapshot.ts +0 -14
  560. package/src/domain/entities/LiveSessionOutputActivity.ts +0 -4
  561. package/src/domain/entities/LiveSessionProcessSnapshot.ts +0 -18
  562. package/src/domain/entities/LiveTmuxSession.ts +0 -4
  563. package/src/domain/entities/Member.ts +0 -3
  564. package/src/domain/entities/OutputDegeneration.ts +0 -10
  565. package/src/domain/entities/Project.ts +0 -88
  566. package/src/domain/entities/ProjectField.ts +0 -3
  567. package/src/domain/entities/ProjectFieldName.ts +0 -2
  568. package/src/domain/entities/ProjectFieldSingleSelect.ts +0 -8
  569. package/src/domain/entities/ProjectFieldSingleSelectOption.ts +0 -8
  570. package/src/domain/entities/RequiredProjectField.ts +0 -68
  571. package/src/domain/entities/SearchedIssue.ts +0 -11
  572. package/src/domain/entities/StoryObjectMap.ts +0 -12
  573. package/src/domain/entities/TokenExhaustionHandoverState.ts +0 -8
  574. package/src/domain/entities/WorkflowStatus.ts +0 -66
  575. package/src/domain/usecases/ActionAnnouncementUseCase.test.ts +0 -91
  576. package/src/domain/usecases/ActionAnnouncementUseCase.ts +0 -78
  577. package/src/domain/usecases/AgentDesignationLabelAdoptUseCase.test.ts +0 -222
  578. package/src/domain/usecases/AgentDesignationLabelAdoptUseCase.ts +0 -76
  579. package/src/domain/usecases/AnalyzeProblemByIssueUseCase.test.ts +0 -118
  580. package/src/domain/usecases/AnalyzeProblemByIssueUseCase.ts +0 -112
  581. package/src/domain/usecases/AnalyzeStoriesUseCase.test.ts +0 -82
  582. package/src/domain/usecases/AnalyzeStoriesUseCase.ts +0 -296
  583. package/src/domain/usecases/AssignNoAssigneeIssueToManagerUseCase.test.ts +0 -578
  584. package/src/domain/usecases/AssignNoAssigneeIssueToManagerUseCase.ts +0 -137
  585. package/src/domain/usecases/CONTRIBUTING_FOR_TEST.md +0 -259
  586. package/src/domain/usecases/ChangeStatusByStoryColorUseCase.test.ts +0 -437
  587. package/src/domain/usecases/ChangeStatusByStoryColorUseCase.ts +0 -80
  588. package/src/domain/usecases/ChangeTargetPullRequestApprover.test.ts +0 -248
  589. package/src/domain/usecases/ChangeTargetPullRequestApprover.ts +0 -69
  590. package/src/domain/usecases/CheckIssueReviewReadinessUseCase.test.ts +0 -411
  591. package/src/domain/usecases/CheckIssueReviewReadinessUseCase.ts +0 -142
  592. package/src/domain/usecases/ClearDependedIssueURLUseCase.test.ts +0 -1120
  593. package/src/domain/usecases/ClearDependedIssueURLUseCase.ts +0 -161
  594. package/src/domain/usecases/ClearPastNextActionDateHourUseCase.test.ts +0 -345
  595. package/src/domain/usecases/ClearPastNextActionDateHourUseCase.ts +0 -81
  596. package/src/domain/usecases/CliErrorReportUseCase.test.ts +0 -162
  597. package/src/domain/usecases/CliErrorReportUseCase.ts +0 -72
  598. package/src/domain/usecases/ConflictedIssueRevertUseCase.test.ts +0 -886
  599. package/src/domain/usecases/ConflictedIssueRevertUseCase.ts +0 -146
  600. package/src/domain/usecases/CreateEstimationIssueUseCase.test.ts +0 -227
  601. package/src/domain/usecases/CreateEstimationIssueUseCase.ts +0 -205
  602. package/src/domain/usecases/CreateNewStoryByLabelUseCase.test.ts +0 -1084
  603. package/src/domain/usecases/CreateNewStoryByLabelUseCase.ts +0 -146
  604. package/src/domain/usecases/DailySecurityScanUseCase.test.ts +0 -1709
  605. package/src/domain/usecases/DailySecurityScanUseCase.ts +0 -476
  606. package/src/domain/usecases/DefaultSilentSessionMessageComposer.test.ts +0 -533
  607. package/src/domain/usecases/DefaultSilentSessionMessageComposer.ts +0 -122
  608. package/src/domain/usecases/GetStoryObjectMapUseCase.test.ts +0 -330
  609. package/src/domain/usecases/GetStoryObjectMapUseCase.ts +0 -79
  610. package/src/domain/usecases/HandleScheduledEventUseCase.test.ts +0 -1913
  611. package/src/domain/usecases/HandleScheduledEventUseCase.ts +0 -846
  612. package/src/domain/usecases/InTmuxByHumanSessionTokenCountUseCase.test.ts +0 -175
  613. package/src/domain/usecases/InTmuxByHumanSessionTokenCountUseCase.ts +0 -63
  614. package/src/domain/usecases/IssueNoStatusUpdateUseCase.test.ts +0 -165
  615. package/src/domain/usecases/IssueNoStatusUpdateUseCase.ts +0 -44
  616. package/src/domain/usecases/IssueRejectionEvaluator.test.ts +0 -1137
  617. package/src/domain/usecases/IssueRejectionEvaluator.ts +0 -340
  618. package/src/domain/usecases/LiveSessionOauthTokenSelectUseCase.test.ts +0 -679
  619. package/src/domain/usecases/LiveSessionOauthTokenSelectUseCase.ts +0 -178
  620. package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.test.ts +0 -5441
  621. package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.ts +0 -891
  622. package/src/domain/usecases/NotifySilentLiveSessionsUseCase.test.ts +0 -2069
  623. package/src/domain/usecases/NotifySilentLiveSessionsUseCase.ts +0 -489
  624. package/src/domain/usecases/OauthTokenSelectUseCase.test.ts +0 -662
  625. package/src/domain/usecases/OauthTokenSelectUseCase.ts +0 -274
  626. package/src/domain/usecases/OutputDegenerationDetector.test.ts +0 -167
  627. package/src/domain/usecases/OutputDegenerationDetector.ts +0 -169
  628. package/src/domain/usecases/ProjectRequiredFieldCreateUseCase.test.ts +0 -711
  629. package/src/domain/usecases/ProjectRequiredFieldCreateUseCase.ts +0 -146
  630. package/src/domain/usecases/QualityCheckAdvanceUseCase.test.ts +0 -421
  631. package/src/domain/usecases/QualityCheckAdvanceUseCase.ts +0 -76
  632. package/src/domain/usecases/ReopenedDoneIssueRevertUseCase.test.ts +0 -188
  633. package/src/domain/usecases/ReopenedDoneIssueRevertUseCase.ts +0 -57
  634. package/src/domain/usecases/ResolveInteractiveLiveSessionsUseCase.test.ts +0 -303
  635. package/src/domain/usecases/ResolveInteractiveLiveSessionsUseCase.ts +0 -155
  636. package/src/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.test.ts +0 -1838
  637. package/src/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.ts +0 -221
  638. package/src/domain/usecases/RevertOrphanedPreparationUseCase.test.ts +0 -2392
  639. package/src/domain/usecases/RevertOrphanedPreparationUseCase.ts +0 -499
  640. package/src/domain/usecases/SessionOutputDegenerationRecoveryUseCase.test.ts +0 -224
  641. package/src/domain/usecases/SessionOutputDegenerationRecoveryUseCase.ts +0 -142
  642. package/src/domain/usecases/SetDependedIssueUrlForOpenTaskPRsUseCase.test.ts +0 -360
  643. package/src/domain/usecases/SetDependedIssueUrlForOpenTaskPRsUseCase.ts +0 -70
  644. package/src/domain/usecases/SetNoStoryIssueToStoryUseCase.test.ts +0 -400
  645. package/src/domain/usecases/SetNoStoryIssueToStoryUseCase.ts +0 -48
  646. package/src/domain/usecases/SetWorkflowManagementIssueToStoryUseCase.test.ts +0 -696
  647. package/src/domain/usecases/SetWorkflowManagementIssueToStoryUseCase.ts +0 -201
  648. package/src/domain/usecases/SetupTowerDefenceProjectUseCase.test.ts +0 -1363
  649. package/src/domain/usecases/SetupTowerDefenceProjectUseCase.ts +0 -215
  650. package/src/domain/usecases/StaleTaskPullRequestCloseUseCase.test.ts +0 -170
  651. package/src/domain/usecases/StaleTaskPullRequestCloseUseCase.ts +0 -38
  652. package/src/domain/usecases/StaleTmuxSessionKillUseCase.test.ts +0 -283
  653. package/src/domain/usecases/StaleTmuxSessionKillUseCase.ts +0 -98
  654. package/src/domain/usecases/StartPreparationUseCase.test.ts +0 -7822
  655. package/src/domain/usecases/StartPreparationUseCase.ts +0 -876
  656. package/src/domain/usecases/SubscriptionDisabledIssueUseCase.test.ts +0 -200
  657. package/src/domain/usecases/SubscriptionDisabledIssueUseCase.ts +0 -74
  658. package/src/domain/usecases/TokenExhaustionHandoverUseCase.test.ts +0 -838
  659. package/src/domain/usecases/TokenExhaustionHandoverUseCase.ts +0 -439
  660. package/src/domain/usecases/UpdateIssueStatusByLabelUseCase.test.ts +0 -306
  661. package/src/domain/usecases/UpdateIssueStatusByLabelUseCase.ts +0 -64
  662. package/src/domain/usecases/UpdateRateLimitCacheUseCase.test.ts +0 -178
  663. package/src/domain/usecases/UpdateRateLimitCacheUseCase.ts +0 -22
  664. package/src/domain/usecases/adapter-interfaces/ClaudeHandoverSessionRepository.ts +0 -5
  665. package/src/domain/usecases/adapter-interfaces/ClaudeInteractiveSessionRepository.ts +0 -9
  666. package/src/domain/usecases/adapter-interfaces/ClaudeLiveSessionRepository.ts +0 -8
  667. package/src/domain/usecases/adapter-interfaces/ClaudeMessageResponseRepository.ts +0 -5
  668. package/src/domain/usecases/adapter-interfaces/ClaudeRepository.ts +0 -6
  669. package/src/domain/usecases/adapter-interfaces/ClaudeTokenUsageRepository.ts +0 -8
  670. package/src/domain/usecases/adapter-interfaces/ConsoleTabsRepository.ts +0 -12
  671. package/src/domain/usecases/adapter-interfaces/CopilotRepository.ts +0 -3
  672. package/src/domain/usecases/adapter-interfaces/DateRepository.ts +0 -7
  673. package/src/domain/usecases/adapter-interfaces/GitHubGraphqlRateLimitRepository.ts +0 -3
  674. package/src/domain/usecases/adapter-interfaces/HttpRepository.ts +0 -3
  675. package/src/domain/usecases/adapter-interfaces/InteractiveLiveSessionTranscriptResolver.ts +0 -7
  676. package/src/domain/usecases/adapter-interfaces/IssueAttachmentRepository.ts +0 -9
  677. package/src/domain/usecases/adapter-interfaces/IssueCheckpointRepository.ts +0 -3
  678. package/src/domain/usecases/adapter-interfaces/IssueCommentRepository.ts +0 -7
  679. package/src/domain/usecases/adapter-interfaces/IssueRepository.ts +0 -229
  680. package/src/domain/usecases/adapter-interfaces/KevReportWatermarkRepository.ts +0 -11
  681. package/src/domain/usecases/adapter-interfaces/LiveSessionProcessSnapshotProvider.ts +0 -5
  682. package/src/domain/usecases/adapter-interfaces/LocalCommandRunner.ts +0 -16
  683. package/src/domain/usecases/adapter-interfaces/OwnerCallStatusProvider.ts +0 -5
  684. package/src/domain/usecases/adapter-interfaces/ProcessEnvironReader.ts +0 -3
  685. package/src/domain/usecases/adapter-interfaces/ProcessSignalRepository.ts +0 -5
  686. package/src/domain/usecases/adapter-interfaces/ProjectRepository.ts +0 -29
  687. package/src/domain/usecases/adapter-interfaces/RateLimitCacheRepository.ts +0 -10
  688. package/src/domain/usecases/adapter-interfaces/RefusalTailStatusProvider.ts +0 -5
  689. package/src/domain/usecases/adapter-interfaces/SessionAssistantTurnsRepository.ts +0 -6
  690. package/src/domain/usecases/adapter-interfaces/SessionDegenerationCooldownStateRepository.ts +0 -4
  691. package/src/domain/usecases/adapter-interfaces/SessionOutputActivityRepository.ts +0 -7
  692. package/src/domain/usecases/adapter-interfaces/SessionRecordReader.ts +0 -3
  693. package/src/domain/usecases/adapter-interfaces/SessionSubAgentActivityRepository.ts +0 -8
  694. package/src/domain/usecases/adapter-interfaces/SilentSessionCandidateStateRepository.ts +0 -10
  695. package/src/domain/usecases/adapter-interfaces/SilentSessionHubTaskStatusCacheRepository.ts +0 -20
  696. package/src/domain/usecases/adapter-interfaces/SilentSessionMessageComposer.ts +0 -18
  697. package/src/domain/usecases/adapter-interfaces/SilentSessionNotificationRepository.ts +0 -6
  698. package/src/domain/usecases/adapter-interfaces/SlackRepository.ts +0 -20
  699. package/src/domain/usecases/adapter-interfaces/Sleeper.ts +0 -3
  700. package/src/domain/usecases/adapter-interfaces/SpreadsheetRepository.ts +0 -18
  701. package/src/domain/usecases/adapter-interfaces/StatusDefaultRepository.ts +0 -5
  702. package/src/domain/usecases/adapter-interfaces/SubAgentLivenessResolver.ts +0 -6
  703. package/src/domain/usecases/adapter-interfaces/SubAgentProcessLister.ts +0 -8
  704. package/src/domain/usecases/adapter-interfaces/SubAgentSilentSecondsResolver.ts +0 -3
  705. package/src/domain/usecases/adapter-interfaces/SubAgentTranscriptDirectoryResolver.ts +0 -6
  706. package/src/domain/usecases/adapter-interfaces/TakeOwnershipSpawnRepository.ts +0 -9
  707. package/src/domain/usecases/adapter-interfaces/TmuxSessionRepository.ts +0 -20
  708. package/src/domain/usecases/adapter-interfaces/TokenExhaustionHandoverStateRepository.ts +0 -6
  709. package/src/domain/usecases/adapter-interfaces/TokenRateLimitSnapshotRepository.ts +0 -22
  710. package/src/domain/usecases/adapter-interfaces/WebhookRepository.ts +0 -3
  711. package/src/domain/usecases/agentReportPrefix.ts +0 -1
  712. package/src/domain/usecases/autoStatusCheckComments.ts +0 -1
  713. package/src/domain/usecases/console/.gitkeep +0 -0
  714. package/src/domain/usecases/console/GenerateConsoleListsUseCase.test.ts +0 -887
  715. package/src/domain/usecases/console/GenerateConsoleListsUseCase.ts +0 -368
  716. package/src/domain/usecases/dashboard/ComposeDashboardUseCase.test.ts +0 -654
  717. package/src/domain/usecases/dashboard/ComposeDashboardUseCase.ts +0 -318
  718. package/src/domain/usecases/dashboard/DashboardProjectCode.test.ts +0 -34
  719. package/src/domain/usecases/dashboard/DashboardProjectCode.ts +0 -27
  720. package/src/domain/usecases/dashboard/GenerateDashboardRowUseCase.test.ts +0 -160
  721. package/src/domain/usecases/dashboard/GenerateDashboardRowUseCase.ts +0 -69
  722. package/src/domain/usecases/dashboard/GenerateTokenStatusUseCase.test.ts +0 -362
  723. package/src/domain/usecases/dashboard/GenerateTokenStatusUseCase.ts +0 -234
  724. package/src/domain/usecases/ensureAgentOptionAndGetId.ts +0 -47
  725. package/src/domain/usecases/extractFencedJsonBlocks.test.ts +0 -40
  726. package/src/domain/usecases/extractFencedJsonBlocks.ts +0 -21
  727. package/src/domain/usecases/extractNextStepAgent.test.ts +0 -58
  728. package/src/domain/usecases/extractNextStepAgent.ts +0 -18
  729. package/src/domain/usecases/extractNextStepAgentFromComments.ts +0 -13
  730. package/src/domain/usecases/findLastAgentReport.test.ts +0 -121
  731. package/src/domain/usecases/findLastAgentReport.ts +0 -14
  732. package/src/domain/usecases/intmux/GenerateInTmuxByHumanDataUseCase.test.ts +0 -339
  733. package/src/domain/usecases/intmux/GenerateInTmuxByHumanDataUseCase.ts +0 -188
  734. package/src/domain/usecases/intmux/InTmuxByHumanSessionReconcileUseCase.test.ts +0 -389
  735. package/src/domain/usecases/intmux/InTmuxByHumanSessionReconcileUseCase.ts +0 -103
  736. package/src/domain/usecases/intmux/OwnerCallFile.test.ts +0 -184
  737. package/src/domain/usecases/intmux/OwnerCallFile.ts +0 -80
  738. package/src/domain/usecases/isAgentReportBody.test.ts +0 -108
  739. package/src/domain/usecases/isAgentReportBody.ts +0 -52
  740. package/src/domain/usecases/isAuthorAuthorizedForAutoStatusCheck.ts +0 -12
  741. package/src/domain/usecases/isHumanComment.test.ts +0 -77
  742. package/src/domain/usecases/isHumanComment.ts +0 -23
  743. package/src/domain/usecases/isRecord.ts +0 -2
  744. package/src/domain/usecases/issueReactivationTriggerIsPending.test.ts +0 -541
  745. package/src/domain/usecases/issueReactivationTriggerIsPending.ts +0 -25
  746. package/src/domain/usecases/nextStepAgentDispatchRepeatedMessage.ts +0 -2
  747. package/src/domain/usecases/normalizeReportBody.test.ts +0 -24
  748. package/src/domain/usecases/normalizeReportBody.ts +0 -2
  749. package/src/domain/usecases/resolveAllowedIssueAuthors.test.ts +0 -51
  750. package/src/domain/usecases/resolveAllowedIssueAuthors.ts +0 -6
  751. package/src/domain/usecases/resolveLabelsAsLlmAgentName.test.ts +0 -42
  752. package/src/domain/usecases/resolveLabelsAsLlmAgentName.ts +0 -6
  753. package/src/domain/usecases/resolveLabelsNotRequiringPullRequest.test.ts +0 -33
  754. package/src/domain/usecases/resolveLabelsNotRequiringPullRequest.ts +0 -7
  755. package/src/domain/usecases/resolveNextStepAgentDispatchRepetition.test.ts +0 -337
  756. package/src/domain/usecases/resolveNextStepAgentDispatchRepetition.ts +0 -133
  757. package/src/domain/usecases/silentSessionReminderSentinel.ts +0 -9
  758. package/src/domain/usecases/triagerAgentName.ts +0 -4
  759. package/src/domain/usecases/utils.test.ts +0 -35
  760. package/src/domain/usecases/utils.ts +0 -6
  761. package/src/index.test.ts +0 -8
  762. package/src/index.ts +0 -10
  763. package/src/releaseConfig.test.ts +0 -40
  764. package/tsconfig.build.json +0 -18
  765. package/tsconfig.json +0 -25
  766. package/types/adapter/entry-points/handlers/resolveUnansweredOwnerCalls.d.ts +0 -13
  767. package/types/adapter/entry-points/handlers/resolveUnansweredOwnerCalls.d.ts.map +0 -1
  768. package/types/adapter/repositories/FileSystemSilentSessionNotifiedStateRepository.d.ts +0 -16
  769. package/types/adapter/repositories/FileSystemSilentSessionNotifiedStateRepository.d.ts.map +0 -1
  770. package/types/adapter/repositories/issue/issueTimelineUtils.d.ts +0 -12
  771. package/types/adapter/repositories/issue/issueTimelineUtils.d.ts.map +0 -1
  772. package/types/domain/entities/UnansweredOwnerCall.d.ts +0 -5
  773. package/types/domain/entities/UnansweredOwnerCall.d.ts.map +0 -1
  774. package/types/domain/entities/WorkingTime.d.ts +0 -8
  775. package/types/domain/entities/WorkingTime.d.ts.map +0 -1
  776. package/types/domain/usecases/ChangeStatusLongInReviewIssueUseCase.d.ts +0 -17
  777. package/types/domain/usecases/ChangeStatusLongInReviewIssueUseCase.d.ts.map +0 -1
  778. package/types/domain/usecases/ConvertCheckboxToIssueInStoryIssueUseCase.d.ts +0 -23
  779. package/types/domain/usecases/ConvertCheckboxToIssueInStoryIssueUseCase.d.ts.map +0 -1
  780. package/types/domain/usecases/GenerateWorkingTimeReportUseCase.d.ts +0 -50
  781. package/types/domain/usecases/GenerateWorkingTimeReportUseCase.d.ts.map +0 -1
  782. package/types/domain/usecases/TriagerApprovalDispatchUseCase.d.ts +0 -15
  783. package/types/domain/usecases/TriagerApprovalDispatchUseCase.d.ts.map +0 -1
  784. package/types/domain/usecases/adapter-interfaces/SilentSessionNotifiedStateRepository.d.ts +0 -11
  785. package/types/domain/usecases/adapter-interfaces/SilentSessionNotifiedStateRepository.d.ts.map +0 -1
  786. package/types/domain/usecases/awaitingOwnerApprovalMessage.d.ts +0 -3
  787. package/types/domain/usecases/awaitingOwnerApprovalMessage.d.ts.map +0 -1
  788. package/types/domain/usecases/findLastAgentDeclaringReport.d.ts +0 -5
  789. package/types/domain/usecases/findLastAgentDeclaringReport.d.ts.map +0 -1
  790. package/types/domain/usecases/isPullRequestDeclaredUnnecessary.d.ts +0 -5
  791. package/types/domain/usecases/isPullRequestDeclaredUnnecessary.d.ts.map +0 -1
  792. package/types/domain/usecases/isWaitingForOwnerApproval.d.ts +0 -2
  793. package/types/domain/usecases/isWaitingForOwnerApproval.d.ts.map +0 -1
  794. package/types/domain/usecases/returnedToAwaitingWorkspaceMessage.d.ts +0 -3
  795. package/types/domain/usecases/returnedToAwaitingWorkspaceMessage.d.ts.map +0 -1
@@ -1,2945 +0,0 @@
1
- import * as http from 'http';
2
- import * as fs from 'fs';
3
- import * as os from 'os';
4
- import * as path from 'path';
5
- import { mock } from 'jest-mock-extended';
6
- import { parseAllDocuments } from 'yaml';
7
- import {
8
- DEFAULT_WEB_PORT,
9
- CONSOLE_TOKEN_HEADER,
10
- CONSOLE_TOKEN_COOKIE,
11
- hasDotSegment,
12
- requiresToken,
13
- isTokenValid,
14
- isConsoleAppRoute,
15
- extractProvidedToken,
16
- extractCookieToken,
17
- isOwnerCallFileRequestPath,
18
- resolveFlatInTmuxFilePath,
19
- resolveDashboardFilePath,
20
- startWebServer,
21
- } from './webServer';
22
- import type { ImageFetcher } from './consoleImageProxy';
23
- import { IssueTitleStateCache, PullRequestStatusCache } from './consoleReadApi';
24
- import { GitHubRateLimitError } from '../../repositories/issue/githubRateLimitRetry';
25
- import { readDoneProjectItemIds } from './consoleDoneStore';
26
- import { IssueRepository } from '../../../domain/usecases/adapter-interfaces/IssueRepository';
27
- import { Project } from '../../../domain/entities/Project';
28
- import { Issue } from '../../../domain/entities/Issue';
29
- import { ownerCallFileRelativePath } from '../../../domain/usecases/intmux/OwnerCallFile';
30
- import { toTmuxSessionName } from '../../../domain/usecases/intmux/InTmuxByHumanSessionReconcileUseCase';
31
- import { ownerCallFileAppend } from '../handlers/ownerCallFileStore';
32
-
33
- describe('webServer pure helpers', () => {
34
- describe('DEFAULT_WEB_PORT', () => {
35
- it('is 9980', () => {
36
- expect(DEFAULT_WEB_PORT).toBe(9980);
37
- });
38
- });
39
-
40
- describe('hasDotSegment', () => {
41
- it('returns true for a top-level dot segment', () => {
42
- expect(hasDotSegment('/.git')).toBe(true);
43
- expect(hasDotSegment('/.env')).toBe(true);
44
- });
45
-
46
- it('returns true for a nested dot segment', () => {
47
- expect(hasDotSegment('/foo/.bar')).toBe(true);
48
- expect(hasDotSegment('/a/b/.hidden/c')).toBe(true);
49
- });
50
-
51
- it('returns false for paths without dot segments', () => {
52
- expect(hasDotSegment('/')).toBe(false);
53
- expect(hasDotSegment('/index.html')).toBe(false);
54
- expect(hasDotSegment('/assets/app.js')).toBe(false);
55
- expect(hasDotSegment('/api/itembody')).toBe(false);
56
- });
57
- });
58
-
59
- describe('requiresToken', () => {
60
- it('requires a token for .json paths', () => {
61
- expect(requiresToken('/data/situation.json')).toBe(true);
62
- });
63
-
64
- it('requires a token for /api/* paths', () => {
65
- expect(requiresToken('/api/review')).toBe(true);
66
- expect(requiresToken('/api')).toBe(true);
67
- });
68
-
69
- it('does not require a token for bootstrap assets', () => {
70
- expect(requiresToken('/')).toBe(false);
71
- expect(requiresToken('/index.html')).toBe(false);
72
- expect(requiresToken('/assets/app.js')).toBe(false);
73
- });
74
- });
75
-
76
- describe('isConsoleAppRoute', () => {
77
- it('matches a per-project root route', () => {
78
- expect(isConsoleAppRoute('/projects/acme')).toBe(true);
79
- expect(isConsoleAppRoute('/projects/acme/')).toBe(true);
80
- });
81
-
82
- it('matches a per-project tab route for every list tab', () => {
83
- expect(isConsoleAppRoute('/projects/acme/workflow-blocker')).toBe(true);
84
- expect(isConsoleAppRoute('/projects/acme/prs')).toBe(true);
85
- expect(isConsoleAppRoute('/projects/globex/todo-by-agent')).toBe(true);
86
- expect(isConsoleAppRoute('/projects/initech/todo-by-human')).toBe(true);
87
- expect(isConsoleAppRoute('/projects/umbrella/failed-preparation')).toBe(
88
- true,
89
- );
90
- expect(isConsoleAppRoute('/projects/umbrella/todo-by-human')).toBe(true);
91
- expect(isConsoleAppRoute('/projects/umbrella/todo-by-agent')).toBe(true);
92
- });
93
-
94
- it('does not match data, api, or unknown tab routes', () => {
95
- expect(isConsoleAppRoute('/projects/acme/prs/list.json')).toBe(false);
96
- expect(isConsoleAppRoute('/projects/acme/unknown')).toBe(false);
97
- expect(isConsoleAppRoute('/projects')).toBe(false);
98
- expect(isConsoleAppRoute('/api/review')).toBe(false);
99
- expect(isConsoleAppRoute('/')).toBe(false);
100
- });
101
-
102
- it('does not match a dot-prefixed pjcode', () => {
103
- expect(isConsoleAppRoute('/projects/.git')).toBe(false);
104
- });
105
- });
106
-
107
- describe('isTokenValid', () => {
108
- it('accepts a matching token', () => {
109
- expect(isTokenValid('expected', 'expected')).toBe(true);
110
- });
111
-
112
- it('rejects a missing or mismatched token', () => {
113
- expect(isTokenValid('expected', null)).toBe(false);
114
- expect(isTokenValid('expected', 'other')).toBe(false);
115
- expect(isTokenValid('expected', '')).toBe(false);
116
- });
117
- });
118
-
119
- describe('extractProvidedToken', () => {
120
- it('prefers the query token', () => {
121
- expect(
122
- extractProvidedToken('fromQuery', 'fromHeader', 'fromCookie'),
123
- ).toBe('fromQuery');
124
- });
125
-
126
- it('falls back to the header token before the cookie token', () => {
127
- expect(extractProvidedToken(null, 'fromHeader', 'fromCookie')).toBe(
128
- 'fromHeader',
129
- );
130
- });
131
-
132
- it('falls back to the cookie token when query and header are absent', () => {
133
- expect(extractProvidedToken(null, undefined, 'fromCookie')).toBe(
134
- 'fromCookie',
135
- );
136
- });
137
-
138
- it('returns null when none is present', () => {
139
- expect(extractProvidedToken(null, undefined, null)).toBeNull();
140
- expect(extractProvidedToken('', undefined, '')).toBeNull();
141
- });
142
- });
143
-
144
- describe('extractCookieToken', () => {
145
- it('reads the token cookie value from a cookie header', () => {
146
- expect(extractCookieToken(`${CONSOLE_TOKEN_COOKIE}=cookie-value`)).toBe(
147
- 'cookie-value',
148
- );
149
- });
150
-
151
- it('reads the token cookie when other cookies are present', () => {
152
- expect(
153
- extractCookieToken(
154
- `other=1; ${CONSOLE_TOKEN_COOKIE}=cookie-value; a=b`,
155
- ),
156
- ).toBe('cookie-value');
157
- });
158
-
159
- it('decodes a percent-encoded cookie value', () => {
160
- expect(extractCookieToken(`${CONSOLE_TOKEN_COOKIE}=a%20b`)).toBe('a b');
161
- });
162
-
163
- it('returns null when the token cookie is absent, empty, or undefined', () => {
164
- expect(extractCookieToken('other=1')).toBeNull();
165
- expect(extractCookieToken(`${CONSOLE_TOKEN_COOKIE}=`)).toBeNull();
166
- expect(extractCookieToken(undefined)).toBeNull();
167
- expect(extractCookieToken('')).toBeNull();
168
- });
169
- });
170
- });
171
-
172
- describe('webServer integration', () => {
173
- const testToken = 'integration-test-token-value';
174
-
175
- const requestServer = (
176
- server: http.Server,
177
- requestPath: string,
178
- headers: http.OutgoingHttpHeaders = {},
179
- ): Promise<{
180
- statusCode: number;
181
- body: string;
182
- cacheControl: string | undefined;
183
- contentType: string | undefined;
184
- }> => {
185
- const address = server.address();
186
- if (address === null || typeof address === 'string') {
187
- throw new Error('server is not listening on a TCP port');
188
- }
189
- const port = address.port;
190
- return new Promise((resolve, reject) => {
191
- const request = http.request(
192
- { host: '127.0.0.1', port, path: requestPath, headers },
193
- (response) => {
194
- const chunks: Uint8Array[] = [];
195
- response.on('data', (chunk: Uint8Array) => chunks.push(chunk));
196
- response.on('end', () => {
197
- resolve({
198
- statusCode: response.statusCode ?? 0,
199
- body: Buffer.concat(chunks).toString('utf-8'),
200
- cacheControl: response.headers['cache-control'],
201
- contentType: response.headers['content-type'],
202
- });
203
- });
204
- },
205
- );
206
- request.on('error', reject);
207
- request.end();
208
- });
209
- };
210
-
211
- const closeServer = (server: http.Server): Promise<void> =>
212
- new Promise((resolve, reject) => {
213
- server.close((error) => {
214
- if (error) {
215
- reject(error);
216
- return;
217
- }
218
- resolve();
219
- });
220
- });
221
-
222
- it('starts on an ephemeral port and closes gracefully', async () => {
223
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'console-server-'));
224
- const server = await startWebServer({
225
- accessToken: testToken,
226
- uiDistDir: path.join(tmpDir, 'ui-dist'),
227
- consoleDataOutputDir: null,
228
- inTmuxDataDir: null,
229
- dashboardDir: null,
230
- dashboardDataDir: null,
231
- dashboardProjectNames: [],
232
- port: 0,
233
- });
234
- const address = server.address();
235
- expect(address).not.toBeNull();
236
- expect(typeof address).not.toBe('string');
237
- await closeServer(server);
238
- fs.rmSync(tmpDir, { recursive: true, force: true });
239
- });
240
-
241
- it('serves the placeholder index without a token when ui-dist is absent', async () => {
242
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'console-server-'));
243
- const server = await startWebServer({
244
- accessToken: testToken,
245
- uiDistDir: path.join(tmpDir, 'missing-ui-dist'),
246
- consoleDataOutputDir: null,
247
- inTmuxDataDir: null,
248
- dashboardDir: null,
249
- dashboardDataDir: null,
250
- dashboardProjectNames: [],
251
- port: 0,
252
- });
253
- try {
254
- const root = await requestServer(server, '/');
255
- expect(root.statusCode).toBe(200);
256
- expect(root.body).toContain('TDPM Console');
257
- expect(root.cacheControl).toBe('no-store');
258
- expect(root.contentType).toContain('text/html');
259
-
260
- const indexHtml = await requestServer(server, '/index.html');
261
- expect(indexHtml.statusCode).toBe(200);
262
- expect(indexHtml.cacheControl).toBe('no-store');
263
- } finally {
264
- await closeServer(server);
265
- fs.rmSync(tmpDir, { recursive: true, force: true });
266
- }
267
- });
268
-
269
- it('returns 404 for a missing non-index file when ui-dist is absent', async () => {
270
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'console-server-'));
271
- const server = await startWebServer({
272
- accessToken: testToken,
273
- uiDistDir: path.join(tmpDir, 'missing-ui-dist'),
274
- consoleDataOutputDir: null,
275
- inTmuxDataDir: null,
276
- dashboardDir: null,
277
- dashboardDataDir: null,
278
- dashboardProjectNames: [],
279
- port: 0,
280
- });
281
- try {
282
- const missing = await requestServer(server, '/assets/app.js');
283
- expect(missing.statusCode).toBe(404);
284
- expect(missing.cacheControl).toBe('no-store');
285
- } finally {
286
- await closeServer(server);
287
- fs.rmSync(tmpDir, { recursive: true, force: true });
288
- }
289
- });
290
-
291
- it('serves built bootstrap assets without a token', async () => {
292
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'console-server-'));
293
- const uiDistDir = path.join(tmpDir, 'ui-dist');
294
- fs.mkdirSync(path.join(uiDistDir, 'assets'), { recursive: true });
295
- fs.writeFileSync(
296
- path.join(uiDistDir, 'index.html'),
297
- '<!DOCTYPE html><title>built</title>',
298
- );
299
- fs.writeFileSync(
300
- path.join(uiDistDir, 'assets', 'app.js'),
301
- 'console.log("app");',
302
- );
303
- const server = await startWebServer({
304
- accessToken: testToken,
305
- uiDistDir,
306
- consoleDataOutputDir: null,
307
- inTmuxDataDir: null,
308
- dashboardDir: null,
309
- dashboardDataDir: null,
310
- dashboardProjectNames: [],
311
- port: 0,
312
- });
313
- try {
314
- const index = await requestServer(server, '/');
315
- expect(index.statusCode).toBe(200);
316
- expect(index.body).toContain('built');
317
-
318
- const appJs = await requestServer(server, '/assets/app.js');
319
- expect(appJs.statusCode).toBe(200);
320
- expect(appJs.body).toContain('app');
321
- expect(appJs.contentType).toContain('text/javascript');
322
- expect(appJs.cacheControl).toBe('no-store');
323
- } finally {
324
- await closeServer(server);
325
- fs.rmSync(tmpDir, { recursive: true, force: true });
326
- }
327
- });
328
-
329
- it('serves the SPA index for per-project app routes without a token', async () => {
330
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'console-server-'));
331
- const uiDistDir = path.join(tmpDir, 'ui-dist');
332
- fs.mkdirSync(uiDistDir, { recursive: true });
333
- fs.writeFileSync(
334
- path.join(uiDistDir, 'index.html'),
335
- '<!DOCTYPE html><title>spa</title><div id="root"></div>',
336
- );
337
- const server = await startWebServer({
338
- accessToken: testToken,
339
- uiDistDir,
340
- consoleDataOutputDir: null,
341
- inTmuxDataDir: null,
342
- dashboardDir: null,
343
- dashboardDataDir: null,
344
- dashboardProjectNames: [],
345
- port: 0,
346
- });
347
- try {
348
- const projectRoot = await requestServer(server, '/projects/acme');
349
- expect(projectRoot.statusCode).toBe(200);
350
- expect(projectRoot.body).toContain('spa');
351
- expect(projectRoot.contentType).toContain('text/html');
352
- expect(projectRoot.cacheControl).toBe('no-store');
353
-
354
- const projectTab = await requestServer(server, '/projects/globex/prs');
355
- expect(projectTab.statusCode).toBe(200);
356
- expect(projectTab.body).toContain('spa');
357
-
358
- const unknownTab = await requestServer(
359
- server,
360
- '/projects/globex/unknown',
361
- );
362
- expect(unknownTab.statusCode).toBe(404);
363
- } finally {
364
- await closeServer(server);
365
- fs.rmSync(tmpDir, { recursive: true, force: true });
366
- }
367
- });
368
-
369
- it('serves the placeholder index for per-project routes when ui-dist is absent', async () => {
370
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'console-server-'));
371
- const server = await startWebServer({
372
- accessToken: testToken,
373
- uiDistDir: path.join(tmpDir, 'missing-ui-dist'),
374
- consoleDataOutputDir: null,
375
- inTmuxDataDir: null,
376
- dashboardDir: null,
377
- dashboardDataDir: null,
378
- dashboardProjectNames: [],
379
- port: 0,
380
- });
381
- try {
382
- const projectRoot = await requestServer(server, '/projects/acme/prs');
383
- expect(projectRoot.statusCode).toBe(200);
384
- expect(projectRoot.body).toContain('TDPM Console');
385
- } finally {
386
- await closeServer(server);
387
- fs.rmSync(tmpDir, { recursive: true, force: true });
388
- }
389
- });
390
-
391
- it('rejects dot-prefixed paths with 404', async () => {
392
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'console-server-'));
393
- const uiDistDir = path.join(tmpDir, 'ui-dist');
394
- fs.mkdirSync(uiDistDir, { recursive: true });
395
- fs.writeFileSync(path.join(uiDistDir, '.env'), 'SECRET=should-not-serve');
396
- const server = await startWebServer({
397
- accessToken: testToken,
398
- uiDistDir,
399
- consoleDataOutputDir: null,
400
- inTmuxDataDir: null,
401
- dashboardDir: null,
402
- dashboardDataDir: null,
403
- dashboardProjectNames: [],
404
- port: 0,
405
- });
406
- try {
407
- const dotEnv = await requestServer(server, '/.env');
408
- expect(dotEnv.statusCode).toBe(404);
409
- expect(dotEnv.body).not.toContain('SECRET');
410
-
411
- const dotGit = await requestServer(server, '/.git/config');
412
- expect(dotGit.statusCode).toBe(404);
413
-
414
- const nestedDot = await requestServer(server, '/foo/.bar');
415
- expect(nestedDot.statusCode).toBe(404);
416
- } finally {
417
- await closeServer(server);
418
- fs.rmSync(tmpDir, { recursive: true, force: true });
419
- }
420
- });
421
-
422
- it('rejects .json and /api/* without a token and passes the gate with a valid token', async () => {
423
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'console-server-'));
424
- const server = await startWebServer({
425
- accessToken: testToken,
426
- uiDistDir: path.join(tmpDir, 'ui-dist'),
427
- consoleDataOutputDir: null,
428
- inTmuxDataDir: null,
429
- dashboardDir: null,
430
- dashboardDataDir: null,
431
- dashboardProjectNames: [],
432
- port: 0,
433
- });
434
- try {
435
- const jsonNoToken = await requestServer(server, '/data/situation.json');
436
- expect(jsonNoToken.statusCode).toBe(401);
437
- expect(jsonNoToken.cacheControl).toBe('no-store');
438
-
439
- const apiNoToken = await requestServer(server, '/api/review');
440
- expect(apiNoToken.statusCode).toBe(401);
441
-
442
- const jsonWithQueryToken = await requestServer(
443
- server,
444
- `/data/situation.json?k=${testToken}`,
445
- );
446
- expect(jsonWithQueryToken.statusCode).toBe(404);
447
-
448
- const apiWithQueryToken = await requestServer(
449
- server,
450
- `/api/review?k=${testToken}`,
451
- );
452
- expect(apiWithQueryToken.statusCode).toBe(404);
453
-
454
- const apiWithHeaderToken = await requestServer(server, '/api/review', {
455
- [CONSOLE_TOKEN_HEADER]: testToken,
456
- });
457
- expect(apiWithHeaderToken.statusCode).toBe(404);
458
-
459
- const apiWithWrongToken = await requestServer(
460
- server,
461
- '/api/review?k=wrong-token',
462
- );
463
- expect(apiWithWrongToken.statusCode).toBe(401);
464
- } finally {
465
- await closeServer(server);
466
- fs.rmSync(tmpDir, { recursive: true, force: true });
467
- }
468
- });
469
- });
470
-
471
- describe('webServer new routes integration', () => {
472
- const testToken = 'integration-test-token-value';
473
-
474
- const closeServer = (server: http.Server): Promise<void> =>
475
- new Promise((resolve, reject) => {
476
- server.close((error) => {
477
- if (error) {
478
- reject(error);
479
- return;
480
- }
481
- resolve();
482
- });
483
- });
484
-
485
- const request = (
486
- server: http.Server,
487
- method: string,
488
- requestPath: string,
489
- body?: unknown,
490
- ): Promise<{ statusCode: number; body: string }> => {
491
- const address = server.address();
492
- if (address === null || typeof address === 'string') {
493
- throw new Error('server is not listening on a TCP port');
494
- }
495
- const port = address.port;
496
- const payload = body === undefined ? null : JSON.stringify(body);
497
- return new Promise((resolve, reject) => {
498
- const httpRequest = http.request(
499
- {
500
- host: '127.0.0.1',
501
- port,
502
- path: requestPath,
503
- method,
504
- headers:
505
- payload === null
506
- ? {}
507
- : {
508
- 'Content-Type': 'application/json',
509
- 'Content-Length': Buffer.byteLength(payload),
510
- },
511
- },
512
- (response) => {
513
- const chunks: Uint8Array[] = [];
514
- response.on('data', (chunk: Uint8Array) => chunks.push(chunk));
515
- response.on('end', () => {
516
- resolve({
517
- statusCode: response.statusCode ?? 0,
518
- body: Buffer.concat(chunks).toString('utf-8'),
519
- });
520
- });
521
- },
522
- );
523
- httpRequest.on('error', reject);
524
- if (payload !== null) {
525
- httpRequest.write(payload);
526
- }
527
- httpRequest.end();
528
- });
529
- };
530
-
531
- const buildProject = (): Project => ({
532
- ...mock<Project>(),
533
- id: 'PVT_1',
534
- status: {
535
- name: 'Status',
536
- fieldId: 'statusField',
537
- statuses: [
538
- {
539
- id: 'status_aw',
540
- name: 'Awaiting workspace',
541
- color: 'GRAY',
542
- description: '',
543
- },
544
- ],
545
- },
546
- });
547
-
548
- it('serves a data list file through the token gate without done filtering', async () => {
549
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'console-server-'));
550
- const dataDir = path.join(tmpDir, 'data');
551
- const listDir = path.join(dataDir, 'acme', 'prs');
552
- fs.mkdirSync(listDir, { recursive: true });
553
- fs.writeFileSync(
554
- path.join(listDir, 'list.json'),
555
- JSON.stringify({
556
- pjcode: 'acme',
557
- items: [
558
- { projectItemId: 'PVTI_keep' },
559
- { projectItemId: 'PVTI_also_keep' },
560
- ],
561
- }),
562
- );
563
- fs.writeFileSync(
564
- path.join(listDir, '.done.json'),
565
- JSON.stringify({ projectItemIds: ['PVTI_also_keep'] }),
566
- );
567
- const server = await startWebServer({
568
- accessToken: testToken,
569
- uiDistDir: path.join(tmpDir, 'ui-dist'),
570
- consoleDataOutputDir: dataDir,
571
- inTmuxDataDir: null,
572
- dashboardDir: null,
573
- dashboardDataDir: null,
574
- dashboardProjectNames: [],
575
- port: 0,
576
- });
577
- try {
578
- const unauthorized = await request(
579
- server,
580
- 'GET',
581
- '/projects/acme/prs/list.json',
582
- );
583
- expect(unauthorized.statusCode).toBe(401);
584
-
585
- const authorized = await request(
586
- server,
587
- 'GET',
588
- `/projects/acme/prs/list.json?k=${testToken}`,
589
- );
590
- expect(authorized.statusCode).toBe(200);
591
- const parsed: unknown = JSON.parse(authorized.body);
592
- expect(parsed).toEqual({
593
- pjcode: 'acme',
594
- items: [
595
- { projectItemId: 'PVTI_keep' },
596
- { projectItemId: 'PVTI_also_keep' },
597
- ],
598
- });
599
- } finally {
600
- await closeServer(server);
601
- fs.rmSync(tmpDir, { recursive: true, force: true });
602
- }
603
- });
604
-
605
- it('serves a read api response when an issue repository is injected', async () => {
606
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'console-server-'));
607
- const issueRepository = mock<IssueRepository>();
608
- issueRepository.getIssueOrPullRequestBody.mockResolvedValue('body text');
609
- const server = await startWebServer({
610
- accessToken: testToken,
611
- uiDistDir: path.join(tmpDir, 'ui-dist'),
612
- consoleDataOutputDir: null,
613
- inTmuxDataDir: null,
614
- dashboardDir: null,
615
- dashboardDataDir: null,
616
- dashboardProjectNames: [],
617
- issueRepository,
618
- issueTitleStateCache: new IssueTitleStateCache(),
619
- port: 0,
620
- });
621
- try {
622
- const response = await request(
623
- server,
624
- 'GET',
625
- `/api/itembody?k=${testToken}&url=https://github.com/o/r/issues/1`,
626
- );
627
- expect(response.statusCode).toBe(200);
628
- expect(JSON.parse(response.body)).toEqual({ body: 'body text' });
629
- } finally {
630
- await closeServer(server);
631
- fs.rmSync(tmpDir, { recursive: true, force: true });
632
- }
633
- });
634
-
635
- it('reads through the issue repository resolved from the requested url', async () => {
636
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'console-server-'));
637
- const defaultIssueRepository = mock<IssueRepository>();
638
- defaultIssueRepository.getIssueOrPullRequestBody.mockResolvedValue(
639
- 'default token body',
640
- );
641
- const ownerIssueRepository = mock<IssueRepository>();
642
- ownerIssueRepository.getIssueOrPullRequestBody.mockResolvedValue(
643
- 'owner token body',
644
- );
645
- const resolvedUrls: string[] = [];
646
- const server = await startWebServer({
647
- accessToken: testToken,
648
- uiDistDir: path.join(tmpDir, 'ui-dist'),
649
- consoleDataOutputDir: null,
650
- inTmuxDataDir: null,
651
- dashboardDir: null,
652
- dashboardDataDir: null,
653
- dashboardProjectNames: [],
654
- issueRepository: defaultIssueRepository,
655
- resolveIssueRepository: (issueOrPullRequestUrl: string) => {
656
- resolvedUrls.push(issueOrPullRequestUrl);
657
- return ownerIssueRepository;
658
- },
659
- issueTitleStateCache: new IssueTitleStateCache(),
660
- port: 0,
661
- });
662
- try {
663
- const response = await request(
664
- server,
665
- 'GET',
666
- `/api/itembody?k=${testToken}&url=https://github.com/acme-labs/acme-portal-mock/issues/178`,
667
- );
668
- expect(JSON.parse(response.body)).toEqual({ body: 'owner token body' });
669
- expect(resolvedUrls).toEqual([
670
- 'https://github.com/acme-labs/acme-portal-mock/issues/178',
671
- ]);
672
- expect(
673
- defaultIssueRepository.getIssueOrPullRequestBody,
674
- ).not.toHaveBeenCalled();
675
- } finally {
676
- await closeServer(server);
677
- fs.rmSync(tmpDir, { recursive: true, force: true });
678
- }
679
- });
680
-
681
- it('returns 429 with the rate limit error message instead of 500 when the repository throws a GitHub rate limit error', async () => {
682
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'console-server-'));
683
- const issueRepository = mock<IssueRepository>();
684
- const rateLimitMessage =
685
- 'Failed to fetch body for https://github.com/o/r/issues/1: HTTP 403 GitHub rate limit exceeded, please retry shortly (resets at 2026-01-01T01:00:00.000Z)';
686
- issueRepository.getIssueOrPullRequestBody.mockRejectedValue(
687
- new GitHubRateLimitError(rateLimitMessage),
688
- );
689
- const server = await startWebServer({
690
- accessToken: testToken,
691
- uiDistDir: path.join(tmpDir, 'ui-dist'),
692
- consoleDataOutputDir: null,
693
- inTmuxDataDir: null,
694
- dashboardDir: null,
695
- dashboardDataDir: null,
696
- dashboardProjectNames: [],
697
- issueRepository,
698
- issueTitleStateCache: new IssueTitleStateCache(),
699
- port: 0,
700
- });
701
- try {
702
- const response = await request(
703
- server,
704
- 'GET',
705
- `/api/itembody?k=${testToken}&url=https://github.com/o/r/issues/1`,
706
- );
707
- expect(response.statusCode).toBe(429);
708
- expect(JSON.parse(response.body)).toEqual({ error: rateLimitMessage });
709
- } finally {
710
- await closeServer(server);
711
- fs.rmSync(tmpDir, { recursive: true, force: true });
712
- }
713
- });
714
-
715
- it('serves the pull request status read api when a status cache is injected', async () => {
716
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'console-server-'));
717
- const issueRepository = mock<IssueRepository>();
718
- issueRepository.getOpenPullRequestCiStatus.mockResolvedValue({
719
- url: 'https://github.com/o/r/pull/1',
720
- isConflicted: true,
721
- mergeable: 'CONFLICTING',
722
- isPassedAllCiJob: false,
723
- isCiStateSuccess: false,
724
- isBranchOutOfDate: true,
725
- missingRequiredCheckNames: ['build'],
726
- });
727
- const server = await startWebServer({
728
- accessToken: testToken,
729
- uiDistDir: path.join(tmpDir, 'ui-dist'),
730
- consoleDataOutputDir: null,
731
- inTmuxDataDir: null,
732
- dashboardDir: null,
733
- dashboardDataDir: null,
734
- dashboardProjectNames: [],
735
- issueRepository,
736
- issueTitleStateCache: new IssueTitleStateCache(),
737
- pullRequestStatusCache: new PullRequestStatusCache(),
738
- port: 0,
739
- });
740
- try {
741
- const response = await request(
742
- server,
743
- 'GET',
744
- `/api/pullrequeststatus?k=${testToken}&url=https://github.com/o/r/pull/1`,
745
- );
746
- expect(response.statusCode).toBe(200);
747
- expect(JSON.parse(response.body)).toEqual({
748
- found: true,
749
- status: {
750
- isConflicted: true,
751
- mergeableStatus: 'CONFLICTING',
752
- isPassedAllCiJob: false,
753
- isCiStateSuccess: false,
754
- isBranchOutOfDate: true,
755
- missingRequiredCheckNames: ['build'],
756
- },
757
- });
758
- } finally {
759
- await closeServer(server);
760
- fs.rmSync(tmpDir, { recursive: true, force: true });
761
- }
762
- });
763
-
764
- it('posts a comment through the comment operation api', async () => {
765
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'console-server-'));
766
- const issueRepository = mock<IssueRepository>();
767
- issueRepository.getIssueOrPullRequestComments.mockResolvedValue([
768
- {
769
- author: 'HiromiShikata',
770
- body: 'Thanks, this resolves the parity gap.',
771
- createdAt: new Date('2026-06-18T03:21:00.000Z'),
772
- },
773
- ]);
774
- const server = await startWebServer({
775
- accessToken: testToken,
776
- uiDistDir: path.join(tmpDir, 'ui-dist'),
777
- consoleDataOutputDir: null,
778
- inTmuxDataDir: null,
779
- dashboardDir: null,
780
- dashboardDataDir: null,
781
- dashboardProjectNames: [],
782
- issueRepository,
783
- resolveProject: async (pjcode) =>
784
- pjcode === 'acme' ? { pjcode, project: buildProject() } : null,
785
- isPjcodeConfigured: (pjcode) => pjcode === 'acme',
786
- port: 0,
787
- });
788
- try {
789
- const response = await request(
790
- server,
791
- 'POST',
792
- `/api/comment?k=${testToken}`,
793
- {
794
- pjcode: 'acme',
795
- url: 'https://github.com/o/r/issues/1',
796
- body: 'Thanks, this resolves the parity gap.',
797
- },
798
- );
799
- expect(response.statusCode).toBe(200);
800
- expect(issueRepository.createCommentByUrl).toHaveBeenCalledWith(
801
- 'https://github.com/o/r/issues/1',
802
- 'Thanks, this resolves the parity gap.',
803
- );
804
- expect(JSON.parse(response.body)).toEqual({
805
- ok: true,
806
- comment: {
807
- author: 'HiromiShikata',
808
- body: 'Thanks, this resolves the parity gap.',
809
- createdAt: '2026-06-18T03:21:00.000Z',
810
- },
811
- });
812
- } finally {
813
- await closeServer(server);
814
- fs.rmSync(tmpDir, { recursive: true, force: true });
815
- }
816
- });
817
-
818
- it('runs an operation api and records the done exclusion', async () => {
819
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'console-server-'));
820
- const dataDir = path.join(tmpDir, 'data');
821
- const issueRepository = mock<IssueRepository>();
822
- issueRepository.get.mockResolvedValue({
823
- ...mock<Issue>(),
824
- itemId: 'PVTI_loaded',
825
- });
826
- issueRepository.getOpenPullRequest.mockResolvedValue({
827
- url: 'https://github.com/o/r/pull/1',
828
- branchName: null,
829
- createdAt: new Date(0),
830
- isDraft: false,
831
- isConflicted: false,
832
- mergeable: 'MERGEABLE',
833
- isPassedAllCiJob: true,
834
- isCiStateSuccess: true,
835
- isResolvedAllReviewComments: true,
836
- isBranchOutOfDate: false,
837
- missingRequiredCheckNames: [],
838
- });
839
- issueRepository.getPullRequestDetail.mockResolvedValue({
840
- title: 'Test PR',
841
- state: 'open',
842
- merged: false,
843
- isDraft: false,
844
- additions: 0,
845
- deletions: 0,
846
- changedFiles: 0,
847
- headRefName: 'feature',
848
- baseRefName: 'main',
849
- author: 'other-user',
850
- files: [],
851
- });
852
- issueRepository.getAuthenticatedUserLogin.mockResolvedValue(
853
- 'authenticated-user',
854
- );
855
- const server = await startWebServer({
856
- accessToken: testToken,
857
- uiDistDir: path.join(tmpDir, 'ui-dist'),
858
- consoleDataOutputDir: dataDir,
859
- inTmuxDataDir: null,
860
- dashboardDir: null,
861
- dashboardDataDir: null,
862
- dashboardProjectNames: [],
863
- issueRepository,
864
- resolveProject: async (pjcode) =>
865
- pjcode === 'acme' ? { pjcode, project: buildProject() } : null,
866
- isPjcodeConfigured: (pjcode) => pjcode === 'acme',
867
- port: 0,
868
- });
869
- try {
870
- const response = await request(
871
- server,
872
- 'POST',
873
- `/api/review?k=${testToken}`,
874
- {
875
- pjcode: 'acme',
876
- action: 'approve_and_merge',
877
- prUrl: 'https://github.com/o/r/pull/1',
878
- projectItemId: 'PVTI_op',
879
- },
880
- );
881
- expect(response.statusCode).toBe(200);
882
- expect(issueRepository.approvePullRequest).toHaveBeenCalledWith(
883
- 'https://github.com/o/r/pull/1',
884
- );
885
- expect(issueRepository.mergePullRequest).toHaveBeenCalledWith(
886
- 'https://github.com/o/r/pull/1',
887
- );
888
- expect(readDoneProjectItemIds(dataDir, 'acme', 'prs')).toContain(
889
- 'PVTI_op',
890
- );
891
- } finally {
892
- await closeServer(server);
893
- fs.rmSync(tmpDir, { recursive: true, force: true });
894
- }
895
- });
896
-
897
- it('merges the pull request and returns 200 when approvePullRequest returns HTTP 422', async () => {
898
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'console-server-'));
899
- const issueRepository = mock<IssueRepository>();
900
- issueRepository.get.mockResolvedValue({
901
- ...mock<Issue>(),
902
- itemId: 'PVTI_loaded',
903
- });
904
- issueRepository.getOpenPullRequest.mockResolvedValue({
905
- url: 'https://github.com/o/r/pull/1',
906
- branchName: null,
907
- createdAt: new Date(0),
908
- isDraft: false,
909
- isConflicted: false,
910
- mergeable: 'MERGEABLE',
911
- isPassedAllCiJob: true,
912
- isCiStateSuccess: true,
913
- isResolvedAllReviewComments: true,
914
- isBranchOutOfDate: false,
915
- missingRequiredCheckNames: [],
916
- });
917
- issueRepository.getPullRequestDetail.mockResolvedValue(null);
918
- issueRepository.getAuthenticatedUserLogin.mockResolvedValue(
919
- 'authenticated-user',
920
- );
921
- issueRepository.approvePullRequest.mockRejectedValue(
922
- new Error(
923
- 'Failed to approve PR https://github.com/o/r/pull/1: HTTP 422 Review Can not approve your own pull request',
924
- ),
925
- );
926
- const server = await startWebServer({
927
- accessToken: testToken,
928
- uiDistDir: path.join(tmpDir, 'ui-dist'),
929
- consoleDataOutputDir: null,
930
- inTmuxDataDir: null,
931
- dashboardDir: null,
932
- dashboardDataDir: null,
933
- dashboardProjectNames: [],
934
- issueRepository,
935
- resolveProject: async (pjcode) =>
936
- pjcode === 'acme' ? { pjcode, project: buildProject() } : null,
937
- isPjcodeConfigured: (pjcode) => pjcode === 'acme',
938
- port: 0,
939
- });
940
- try {
941
- const response = await request(
942
- server,
943
- 'POST',
944
- `/api/review?k=${testToken}`,
945
- {
946
- pjcode: 'acme',
947
- action: 'approve_and_merge',
948
- prUrl: 'https://github.com/o/r/pull/1',
949
- projectItemId: 'PVTI_op',
950
- },
951
- );
952
- expect(response.statusCode).toBe(200);
953
- expect(issueRepository.mergePullRequest).toHaveBeenCalledWith(
954
- 'https://github.com/o/r/pull/1',
955
- );
956
- } finally {
957
- await closeServer(server);
958
- fs.rmSync(tmpDir, { recursive: true, force: true });
959
- }
960
- });
961
-
962
- it('returns 502 with the underlying error message when an operation rejects', async () => {
963
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'console-server-'));
964
- const issueRepository = mock<IssueRepository>();
965
- issueRepository.get.mockResolvedValue({
966
- ...mock<Issue>(),
967
- itemId: 'PVTI_loaded',
968
- });
969
- issueRepository.getOpenPullRequest.mockResolvedValue({
970
- url: 'https://github.com/o/r/pull/1',
971
- branchName: null,
972
- createdAt: new Date(0),
973
- isDraft: false,
974
- isConflicted: false,
975
- mergeable: 'MERGEABLE',
976
- isPassedAllCiJob: true,
977
- isCiStateSuccess: true,
978
- isResolvedAllReviewComments: true,
979
- isBranchOutOfDate: false,
980
- missingRequiredCheckNames: [],
981
- });
982
- issueRepository.getPullRequestDetail.mockResolvedValue({
983
- title: 'Test PR',
984
- state: 'open',
985
- merged: false,
986
- isDraft: false,
987
- additions: 0,
988
- deletions: 0,
989
- changedFiles: 0,
990
- headRefName: 'feature',
991
- baseRefName: 'main',
992
- author: 'other-user',
993
- files: [],
994
- });
995
- issueRepository.getAuthenticatedUserLogin.mockResolvedValue(
996
- 'authenticated-user',
997
- );
998
- issueRepository.mergePullRequest.mockRejectedValue(
999
- new Error('Failed to merge PR https://github.com/o/r/pull/1: HTTP 405'),
1000
- );
1001
- const server = await startWebServer({
1002
- accessToken: testToken,
1003
- uiDistDir: path.join(tmpDir, 'ui-dist'),
1004
- consoleDataOutputDir: null,
1005
- inTmuxDataDir: null,
1006
- dashboardDir: null,
1007
- dashboardDataDir: null,
1008
- dashboardProjectNames: [],
1009
- issueRepository,
1010
- resolveProject: async (pjcode) =>
1011
- pjcode === 'acme' ? { pjcode, project: buildProject() } : null,
1012
- isPjcodeConfigured: (pjcode) => pjcode === 'acme',
1013
- port: 0,
1014
- });
1015
- try {
1016
- const response = await request(
1017
- server,
1018
- 'POST',
1019
- `/api/review?k=${testToken}`,
1020
- {
1021
- pjcode: 'acme',
1022
- action: 'approve_and_merge',
1023
- prUrl: 'https://github.com/o/r/pull/1',
1024
- projectItemId: 'PVTI_op',
1025
- },
1026
- );
1027
- expect(response.statusCode).toBe(502);
1028
- expect(JSON.parse(response.body)).toEqual({
1029
- error: 'Failed to merge PR https://github.com/o/r/pull/1: HTTP 405',
1030
- });
1031
- } finally {
1032
- await closeServer(server);
1033
- fs.rmSync(tmpDir, { recursive: true, force: true });
1034
- }
1035
- });
1036
-
1037
- it('rejects an operation api with a malformed json body', async () => {
1038
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'console-server-'));
1039
- const issueRepository = mock<IssueRepository>();
1040
- const server = await startWebServer({
1041
- accessToken: testToken,
1042
- uiDistDir: path.join(tmpDir, 'ui-dist'),
1043
- consoleDataOutputDir: null,
1044
- inTmuxDataDir: null,
1045
- dashboardDir: null,
1046
- dashboardDataDir: null,
1047
- dashboardProjectNames: [],
1048
- issueRepository,
1049
- resolveProject: async (pjcode) =>
1050
- pjcode === 'acme' ? { pjcode, project: buildProject() } : null,
1051
- isPjcodeConfigured: (pjcode) => pjcode === 'acme',
1052
- port: 0,
1053
- });
1054
- try {
1055
- const address = server.address();
1056
- if (address === null || typeof address === 'string') {
1057
- throw new Error('server is not listening on a TCP port');
1058
- }
1059
- const malformed = await new Promise<{ statusCode: number }>(
1060
- (resolve, reject) => {
1061
- const payload = '{ not json';
1062
- const httpRequest = http.request(
1063
- {
1064
- host: '127.0.0.1',
1065
- port: address.port,
1066
- path: `/api/review?k=${testToken}`,
1067
- method: 'POST',
1068
- headers: {
1069
- 'Content-Type': 'application/json',
1070
- 'Content-Length': Buffer.byteLength(payload),
1071
- },
1072
- },
1073
- (response) => {
1074
- response.on('data', () => undefined);
1075
- response.on('end', () =>
1076
- resolve({ statusCode: response.statusCode ?? 0 }),
1077
- );
1078
- },
1079
- );
1080
- httpRequest.on('error', reject);
1081
- httpRequest.write(payload);
1082
- httpRequest.end();
1083
- },
1084
- );
1085
- expect(malformed.statusCode).toBe(400);
1086
- } finally {
1087
- await closeServer(server);
1088
- fs.rmSync(tmpDir, { recursive: true, force: true });
1089
- }
1090
- });
1091
-
1092
- it('returns 404 for a read api when no repository is injected', async () => {
1093
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'console-server-'));
1094
- const server = await startWebServer({
1095
- accessToken: testToken,
1096
- uiDistDir: path.join(tmpDir, 'ui-dist'),
1097
- consoleDataOutputDir: null,
1098
- inTmuxDataDir: null,
1099
- dashboardDir: null,
1100
- dashboardDataDir: null,
1101
- dashboardProjectNames: [],
1102
- port: 0,
1103
- });
1104
- try {
1105
- const response = await request(
1106
- server,
1107
- 'GET',
1108
- `/api/itembody?k=${testToken}&url=https://github.com/o/r/issues/1`,
1109
- );
1110
- expect(response.statusCode).toBe(404);
1111
- } finally {
1112
- await closeServer(server);
1113
- fs.rmSync(tmpDir, { recursive: true, force: true });
1114
- }
1115
- });
1116
-
1117
- it('routes POST /api/addstory to handleStoryAdd and calls updateStoryList', async () => {
1118
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'console-server-'));
1119
- const updateStoryList = jest.fn().mockResolvedValue([]);
1120
- const projectWithStory: Project = {
1121
- ...mock<Project>(),
1122
- id: 'PVT_1',
1123
- url: 'https://github.com/orgs/acme-labs/projects/1',
1124
- status: {
1125
- name: 'Status',
1126
- fieldId: 'statusField',
1127
- statuses: [
1128
- {
1129
- id: 'status_aw',
1130
- name: 'Awaiting workspace',
1131
- color: 'GRAY',
1132
- description: '',
1133
- },
1134
- ],
1135
- },
1136
- story: {
1137
- name: 'Story',
1138
- fieldId: 'storyField',
1139
- databaseId: 1,
1140
- stories: [
1141
- {
1142
- id: 'opt_blue',
1143
- name: 'Existing story',
1144
- color: 'BLUE',
1145
- description: '',
1146
- },
1147
- ],
1148
- workflowManagementStory: { id: 'wms', name: 'workflow' },
1149
- },
1150
- };
1151
- const issueRepository = mock<IssueRepository>();
1152
- const server = await startWebServer({
1153
- accessToken: testToken,
1154
- uiDistDir: path.join(tmpDir, 'ui-dist'),
1155
- consoleDataOutputDir: null,
1156
- inTmuxDataDir: null,
1157
- dashboardDir: null,
1158
- dashboardDataDir: null,
1159
- dashboardProjectNames: [],
1160
- issueRepository,
1161
- resolveProject: async (pjcode) =>
1162
- pjcode === 'acme' ? { pjcode, project: projectWithStory } : null,
1163
- isPjcodeConfigured: (pjcode) => pjcode === 'acme',
1164
- resolveProjectRepository: () => ({ updateStoryList }),
1165
- port: 0,
1166
- });
1167
- try {
1168
- const response = await request(
1169
- server,
1170
- 'POST',
1171
- `/api/addstory?k=${testToken}`,
1172
- { pjcode: 'acme', storyName: 'My new story' },
1173
- );
1174
- expect(response.statusCode).toBe(200);
1175
- expect(JSON.parse(response.body)).toEqual({ ok: true });
1176
- expect(updateStoryList).toHaveBeenCalledWith(projectWithStory, [
1177
- {
1178
- id: 'opt_blue',
1179
- name: 'Existing story',
1180
- color: 'BLUE',
1181
- description: '',
1182
- },
1183
- { id: null, name: 'My new story', color: 'RED', description: '' },
1184
- ]);
1185
- } finally {
1186
- await closeServer(server);
1187
- fs.rmSync(tmpDir, { recursive: true, force: true });
1188
- }
1189
- });
1190
-
1191
- it('routes POST /api/reorderstory to handleReorderStory and calls resolveProjectRepository.updateStoryList', async () => {
1192
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'console-server-'));
1193
- const updateStoryList = jest.fn().mockResolvedValue([]);
1194
- const projectWithTwoStories: Project = {
1195
- ...mock<Project>(),
1196
- id: 'PVT_1',
1197
- url: 'https://github.com/orgs/acme-labs/projects/1',
1198
- status: {
1199
- name: 'Status',
1200
- fieldId: 'statusField',
1201
- statuses: [
1202
- {
1203
- id: 'status_aw',
1204
- name: 'Awaiting workspace',
1205
- color: 'GRAY',
1206
- description: '',
1207
- },
1208
- ],
1209
- },
1210
- story: {
1211
- name: 'Story',
1212
- fieldId: 'storyField',
1213
- databaseId: 1,
1214
- stories: [
1215
- {
1216
- id: 'opt_first',
1217
- name: 'First story',
1218
- color: 'BLUE',
1219
- description: '',
1220
- },
1221
- {
1222
- id: 'opt_second',
1223
- name: 'Second story',
1224
- color: 'GREEN',
1225
- description: '',
1226
- },
1227
- ],
1228
- workflowManagementStory: { id: 'wms', name: 'workflow' },
1229
- },
1230
- };
1231
- const issueRepository = mock<IssueRepository>();
1232
- const server = await startWebServer({
1233
- accessToken: testToken,
1234
- uiDistDir: path.join(tmpDir, 'ui-dist'),
1235
- consoleDataOutputDir: null,
1236
- inTmuxDataDir: null,
1237
- dashboardDir: null,
1238
- dashboardDataDir: null,
1239
- dashboardProjectNames: [],
1240
- issueRepository,
1241
- resolveProject: async (pjcode) =>
1242
- pjcode === 'acme' ? { pjcode, project: projectWithTwoStories } : null,
1243
- isPjcodeConfigured: (pjcode) => pjcode === 'acme',
1244
- resolveProjectRepository: () => ({ updateStoryList }),
1245
- port: 0,
1246
- });
1247
- try {
1248
- const response = await request(
1249
- server,
1250
- 'POST',
1251
- `/api/reorderstory?k=${testToken}`,
1252
- { pjcode: 'acme', storyOptionId: 'opt_first', direction: 'down' },
1253
- );
1254
- expect(response.statusCode).toBe(200);
1255
- expect(JSON.parse(response.body)).toEqual({ ok: true });
1256
- expect(updateStoryList).toHaveBeenCalledWith(projectWithTwoStories, [
1257
- {
1258
- id: 'opt_second',
1259
- name: 'Second story',
1260
- color: 'GREEN',
1261
- description: '',
1262
- },
1263
- {
1264
- id: 'opt_first',
1265
- name: 'First story',
1266
- color: 'BLUE',
1267
- description: '',
1268
- },
1269
- ]);
1270
- } finally {
1271
- await closeServer(server);
1272
- fs.rmSync(tmpDir, { recursive: true, force: true });
1273
- }
1274
- });
1275
- });
1276
-
1277
- describe('resolveFlatInTmuxFilePath', () => {
1278
- const baseDir = path.join(os.tmpdir(), 'in-tmux-data');
1279
-
1280
- it('resolves a flat .json file under the in-tmux data dir', () => {
1281
- const resolved = resolveFlatInTmuxFilePath(
1282
- baseDir,
1283
- '/in-tmux-by-human/index.v4.json',
1284
- );
1285
- expect(resolved).toBe(path.join(path.resolve(baseDir), 'index.v4.json'));
1286
- });
1287
-
1288
- it('returns null for paths outside the flat in-tmux prefix', () => {
1289
- expect(resolveFlatInTmuxFilePath(baseDir, '/index.v4.json')).toBeNull();
1290
- expect(
1291
- resolveFlatInTmuxFilePath(baseDir, '/projects/acme/in-tmux-by-human/x'),
1292
- ).toBeNull();
1293
- });
1294
-
1295
- it('returns null for non-json or nested file names', () => {
1296
- expect(
1297
- resolveFlatInTmuxFilePath(baseDir, '/in-tmux-by-human/index.txt'),
1298
- ).toBeNull();
1299
- expect(
1300
- resolveFlatInTmuxFilePath(baseDir, '/in-tmux-by-human/sub/index.json'),
1301
- ).toBeNull();
1302
- expect(resolveFlatInTmuxFilePath(baseDir, '/in-tmux-by-human/')).toBeNull();
1303
- });
1304
-
1305
- it('returns null for path traversal attempts', () => {
1306
- expect(
1307
- resolveFlatInTmuxFilePath(baseDir, '/in-tmux-by-human/../secret.json'),
1308
- ).toBeNull();
1309
- expect(
1310
- resolveFlatInTmuxFilePath(baseDir, '/in-tmux-by-human/..%2fsecret.json'),
1311
- ).toBeNull();
1312
- });
1313
-
1314
- it('resolves an owner call file that sits one directory deep', () => {
1315
- expect(
1316
- resolveFlatInTmuxFilePath(
1317
- baseDir,
1318
- '/in-tmux-by-human/call-to-user/umino/secretary.yaml',
1319
- ),
1320
- ).toBe(
1321
- path.join(
1322
- path.resolve(baseDir),
1323
- 'call-to-user',
1324
- 'umino',
1325
- 'secretary.yaml',
1326
- ),
1327
- );
1328
- });
1329
-
1330
- it('returns null for a yaml file outside the owner call directory shape', () => {
1331
- expect(
1332
- resolveFlatInTmuxFilePath(baseDir, '/in-tmux-by-human/secretary.yaml'),
1333
- ).toBeNull();
1334
- expect(
1335
- resolveFlatInTmuxFilePath(
1336
- baseDir,
1337
- '/in-tmux-by-human/other-directory/umino/secretary.yaml',
1338
- ),
1339
- ).toBeNull();
1340
- expect(
1341
- resolveFlatInTmuxFilePath(
1342
- baseDir,
1343
- '/in-tmux-by-human/call-to-user/umino/nested/secretary.yaml',
1344
- ),
1345
- ).toBeNull();
1346
- expect(
1347
- resolveFlatInTmuxFilePath(
1348
- baseDir,
1349
- '/in-tmux-by-human/call-to-user/umino/secretary.json',
1350
- ),
1351
- ).toBeNull();
1352
- });
1353
- });
1354
-
1355
- describe('resolveDashboardFilePath', () => {
1356
- const baseDir = path.join(os.tmpdir(), 'dashboard-static');
1357
-
1358
- it('resolves /tdpm.txt under the dashboard dir', () => {
1359
- expect(resolveDashboardFilePath(baseDir, '/tdpm.txt')).toBe(
1360
- path.join(path.resolve(baseDir), 'tdpm.txt'),
1361
- );
1362
- });
1363
-
1364
- it('returns null for any other path', () => {
1365
- expect(resolveDashboardFilePath(baseDir, '/tdpm.html')).toBeNull();
1366
- expect(resolveDashboardFilePath(baseDir, '/other.txt')).toBeNull();
1367
- expect(resolveDashboardFilePath(baseDir, '/')).toBeNull();
1368
- expect(resolveDashboardFilePath(baseDir, '/sub/tdpm.txt')).toBeNull();
1369
- });
1370
- });
1371
-
1372
- describe('webServer flat in-tmux-by-human route integration', () => {
1373
- const testToken = 'integration-test-token-value';
1374
-
1375
- const requestServer = (
1376
- server: http.Server,
1377
- requestPath: string,
1378
- headers: http.OutgoingHttpHeaders = {},
1379
- ): Promise<{
1380
- statusCode: number;
1381
- body: string;
1382
- cacheControl: string | undefined;
1383
- contentType: string | undefined;
1384
- contentLength: string | undefined;
1385
- transferEncoding: string | undefined;
1386
- }> => {
1387
- const address = server.address();
1388
- if (address === null || typeof address === 'string') {
1389
- throw new Error('server is not listening on a TCP port');
1390
- }
1391
- const port = address.port;
1392
- return new Promise((resolve, reject) => {
1393
- const httpRequest = http.request(
1394
- { host: '127.0.0.1', port, path: requestPath, headers },
1395
- (response) => {
1396
- const chunks: Uint8Array[] = [];
1397
- response.on('data', (chunk: Uint8Array) => chunks.push(chunk));
1398
- response.on('end', () => {
1399
- resolve({
1400
- statusCode: response.statusCode ?? 0,
1401
- body: Buffer.concat(chunks).toString('utf-8'),
1402
- cacheControl: response.headers['cache-control'],
1403
- contentType: response.headers['content-type'],
1404
- contentLength: response.headers['content-length'],
1405
- transferEncoding: response.headers['transfer-encoding'],
1406
- });
1407
- });
1408
- },
1409
- );
1410
- httpRequest.on('error', reject);
1411
- httpRequest.end();
1412
- });
1413
- };
1414
-
1415
- const closeServer = (server: http.Server): Promise<void> =>
1416
- new Promise((resolve, reject) => {
1417
- server.close((error) => {
1418
- if (error) {
1419
- reject(error);
1420
- return;
1421
- }
1422
- resolve();
1423
- });
1424
- });
1425
-
1426
- const indexV4Raw =
1427
- '{"version":4,"projects":[{"name":"acme","path":"/in-tmux-by-human/acme.v4.json?k=secret"}]}\n';
1428
- const indexV3Raw = '{"version":3,"projects":["acme"]}\n';
1429
-
1430
- const startWithFixture = async (): Promise<{
1431
- server: http.Server;
1432
- tmpDir: string;
1433
- inTmuxDataDir: string;
1434
- }> => {
1435
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'console-server-'));
1436
- const inTmuxDataDir = path.join(tmpDir, 'in-tmux-by-human');
1437
- fs.mkdirSync(inTmuxDataDir, { recursive: true });
1438
- fs.writeFileSync(path.join(inTmuxDataDir, 'index.v4.json'), indexV4Raw);
1439
- fs.writeFileSync(path.join(inTmuxDataDir, 'index.v3.json'), indexV3Raw);
1440
- fs.writeFileSync(path.join(tmpDir, 'secret.json'), '{"secret":true}');
1441
- const server = await startWebServer({
1442
- accessToken: testToken,
1443
- uiDistDir: path.join(tmpDir, 'ui-dist'),
1444
- consoleDataOutputDir: null,
1445
- inTmuxDataDir,
1446
- dashboardDir: null,
1447
- dashboardDataDir: null,
1448
- dashboardProjectNames: [],
1449
- port: 0,
1450
- });
1451
- return { server, tmpDir, inTmuxDataDir };
1452
- };
1453
-
1454
- it('serves the flat index.v4.json byte-for-byte with a valid token', async () => {
1455
- const { server, tmpDir } = await startWithFixture();
1456
- try {
1457
- const response = await requestServer(
1458
- server,
1459
- `/in-tmux-by-human/index.v4.json?k=${testToken}`,
1460
- );
1461
- expect(response.statusCode).toBe(200);
1462
- expect(response.body).toBe(indexV4Raw);
1463
- expect(response.contentType).toContain('application/json');
1464
- expect(response.cacheControl).toBe('no-store');
1465
- } finally {
1466
- await closeServer(server);
1467
- fs.rmSync(tmpDir, { recursive: true, force: true });
1468
- }
1469
- });
1470
-
1471
- it('sends an explicit Content-Length and no chunked encoding for the flat index.v4.json', async () => {
1472
- const { server, tmpDir } = await startWithFixture();
1473
- try {
1474
- const response = await requestServer(
1475
- server,
1476
- `/in-tmux-by-human/index.v4.json?k=${testToken}`,
1477
- );
1478
- expect(response.statusCode).toBe(200);
1479
- expect(response.contentLength).toBe(
1480
- String(Buffer.byteLength(indexV4Raw)),
1481
- );
1482
- expect(response.transferEncoding).toBeUndefined();
1483
- expect(response.body).toBe(indexV4Raw);
1484
- } finally {
1485
- await closeServer(server);
1486
- fs.rmSync(tmpDir, { recursive: true, force: true });
1487
- }
1488
- });
1489
-
1490
- it('rejects the flat route without a token', async () => {
1491
- const { server, tmpDir } = await startWithFixture();
1492
- try {
1493
- const response = await requestServer(
1494
- server,
1495
- '/in-tmux-by-human/index.v4.json',
1496
- );
1497
- expect(response.statusCode).toBe(401);
1498
- } finally {
1499
- await closeServer(server);
1500
- fs.rmSync(tmpDir, { recursive: true, force: true });
1501
- }
1502
- });
1503
-
1504
- it('serves a v3 file for backward compatibility', async () => {
1505
- const { server, tmpDir } = await startWithFixture();
1506
- try {
1507
- const response = await requestServer(
1508
- server,
1509
- `/in-tmux-by-human/index.v3.json?k=${testToken}`,
1510
- );
1511
- expect(response.statusCode).toBe(200);
1512
- expect(response.body).toBe(indexV3Raw);
1513
- } finally {
1514
- await closeServer(server);
1515
- fs.rmSync(tmpDir, { recursive: true, force: true });
1516
- }
1517
- });
1518
-
1519
- it('rejects path traversal attempts with 404 and does not disclose files', async () => {
1520
- const { server, tmpDir } = await startWithFixture();
1521
- try {
1522
- const traversal = await requestServer(
1523
- server,
1524
- `/in-tmux-by-human/../secret.json?k=${testToken}`,
1525
- );
1526
- expect(traversal.statusCode).toBe(404);
1527
- expect(traversal.body).not.toContain('secret');
1528
-
1529
- const encodedTraversal = await requestServer(
1530
- server,
1531
- `/in-tmux-by-human/..%2fsecret.json?k=${testToken}`,
1532
- );
1533
- expect(encodedTraversal.statusCode).toBe(404);
1534
- expect(encodedTraversal.body).not.toContain('secret');
1535
- } finally {
1536
- await closeServer(server);
1537
- fs.rmSync(tmpDir, { recursive: true, force: true });
1538
- }
1539
- });
1540
-
1541
- it('returns 404 for a non-existent flat file', async () => {
1542
- const { server, tmpDir } = await startWithFixture();
1543
- try {
1544
- const response = await requestServer(
1545
- server,
1546
- `/in-tmux-by-human/missing.v4.json?k=${testToken}`,
1547
- );
1548
- expect(response.statusCode).toBe(404);
1549
- } finally {
1550
- await closeServer(server);
1551
- fs.rmSync(tmpDir, { recursive: true, force: true });
1552
- }
1553
- });
1554
-
1555
- it('returns 404 for the flat route when inTmuxDataDir is null', async () => {
1556
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'console-server-'));
1557
- const server = await startWebServer({
1558
- accessToken: testToken,
1559
- uiDistDir: path.join(tmpDir, 'ui-dist'),
1560
- consoleDataOutputDir: null,
1561
- inTmuxDataDir: null,
1562
- dashboardDir: null,
1563
- dashboardDataDir: null,
1564
- dashboardProjectNames: [],
1565
- port: 0,
1566
- });
1567
- try {
1568
- const response = await requestServer(
1569
- server,
1570
- `/in-tmux-by-human/index.v4.json?k=${testToken}`,
1571
- );
1572
- expect(response.statusCode).toBe(404);
1573
- } finally {
1574
- await closeServer(server);
1575
- fs.rmSync(tmpDir, { recursive: true, force: true });
1576
- }
1577
- });
1578
- });
1579
-
1580
- describe('webServer dashboard /tdpm.txt route integration', () => {
1581
- const testToken = 'integration-test-token-value';
1582
-
1583
- const requestServer = (
1584
- server: http.Server,
1585
- requestPath: string,
1586
- method = 'GET',
1587
- headers?: Record<string, string>,
1588
- ): Promise<{
1589
- statusCode: number;
1590
- body: string;
1591
- cacheControl: string | undefined;
1592
- contentType: string | undefined;
1593
- contentLength: string | undefined;
1594
- transferEncoding: string | undefined;
1595
- }> => {
1596
- const address = server.address();
1597
- if (address === null || typeof address === 'string') {
1598
- throw new Error('server is not listening on a TCP port');
1599
- }
1600
- const port = address.port;
1601
- return new Promise((resolve, reject) => {
1602
- const httpRequest = http.request(
1603
- { host: '127.0.0.1', port, path: requestPath, method, headers },
1604
- (response) => {
1605
- const chunks: Uint8Array[] = [];
1606
- response.on('data', (chunk: Uint8Array) => chunks.push(chunk));
1607
- response.on('end', () => {
1608
- resolve({
1609
- statusCode: response.statusCode ?? 0,
1610
- body: Buffer.concat(chunks).toString('utf-8'),
1611
- cacheControl: response.headers['cache-control'],
1612
- contentType: response.headers['content-type'],
1613
- contentLength: response.headers['content-length'],
1614
- transferEncoding: response.headers['transfer-encoding'],
1615
- });
1616
- });
1617
- },
1618
- );
1619
- httpRequest.on('error', reject);
1620
- httpRequest.end();
1621
- });
1622
- };
1623
-
1624
- const closeServer = (server: http.Server): Promise<void> =>
1625
- new Promise((resolve, reject) => {
1626
- server.close((error) => {
1627
- if (error) {
1628
- reject(error);
1629
- return;
1630
- }
1631
- resolve();
1632
- });
1633
- });
1634
-
1635
- const writeDataFiles = (dataDir: string): void => {
1636
- fs.mkdirSync(path.join(dataDir, 'projects'), { recursive: true });
1637
- fs.writeFileSync(
1638
- path.join(dataDir, 'projects', 'acme.json'),
1639
- JSON.stringify({
1640
- pjcode: 'acme',
1641
- capturedAt: '2026-06-26T00:00:00.000Z',
1642
- todo: 1,
1643
- qc: 2,
1644
- fail: 0,
1645
- pr: 0,
1646
- ws: 4,
1647
- dep: 1,
1648
- blocker: 0,
1649
- }),
1650
- );
1651
- fs.writeFileSync(
1652
- path.join(dataDir, 'projects', 'initech.json'),
1653
- JSON.stringify({
1654
- pjcode: 'initech',
1655
- capturedAt: '2026-06-26T00:00:00.000Z',
1656
- todo: 0,
1657
- qc: 0,
1658
- fail: 0,
1659
- pr: 0,
1660
- ws: 2,
1661
- dep: 0,
1662
- blocker: 0,
1663
- }),
1664
- );
1665
- fs.writeFileSync(
1666
- path.join(dataDir, 'machine-status.json'),
1667
- JSON.stringify({
1668
- memPct: 55,
1669
- cpuPct: 62,
1670
- diskPct: 89,
1671
- load: [16, 23, 40],
1672
- cycleMinutes: 14,
1673
- capturedAt: '2026-06-26T00:00:00.000Z',
1674
- }),
1675
- );
1676
- fs.writeFileSync(
1677
- path.join(dataDir, 'token-status.json'),
1678
- JSON.stringify({
1679
- tokens: [
1680
- {
1681
- name: 'alice',
1682
- fiveHourUtilizationPercent: 10,
1683
- fiveHourResetSeconds: 3600,
1684
- sevenDayUtilizationPercent: 12,
1685
- sevenDayResetSeconds: 432000,
1686
- color: 'G',
1687
- prep: 2,
1688
- hum: 1,
1689
- },
1690
- ],
1691
- capturedAt: '2026-06-26T00:00:00.000Z',
1692
- }),
1693
- );
1694
- };
1695
-
1696
- const expectedComposed =
1697
- '<tt>M55%&nbsp;C62%&nbsp;D89%&nbsp;cy14</tt><br>\n' +
1698
- '<tt>LA&nbsp;16&nbsp;23&nbsp;40</tt><br>\n' +
1699
- '<tt>pj&nbsp;&nbsp;&nbsp;tdo&nbsp;aqc&nbsp;fal&nbsp;prp&nbsp;aws&nbsp;dep</tt><br>\n' +
1700
- '<tt>🟢ac&nbsp;&nbsp;&nbsp;1&nbsp;&nbsp;&nbsp;2&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;4&nbsp;&nbsp;&nbsp;1</tt><br>\n' +
1701
- '<tt>🟢in&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;2&nbsp;&nbsp;&nbsp;0</tt><br>\n' +
1702
- '<tt></tt><br>\n' +
1703
- '<tt>🟢alice&nbsp;&nbsp;10%&nbsp;0d01h00&nbsp;&nbsp;12%&nbsp;5d00h00&nbsp;2&nbsp;1</tt><br>\n';
1704
-
1705
- const staticDashboardRaw =
1706
- '<tt>STATIC&nbsp;DASHBOARD</tt><br>\n<tt>pj&nbsp;unr&nbsp;tdo</tt><br>\n';
1707
-
1708
- const writeStaticDashboard = (staticDir: string): void => {
1709
- fs.writeFileSync(path.join(staticDir, 'tdpm.txt'), staticDashboardRaw);
1710
- };
1711
-
1712
- const startServer = async (overrides: {
1713
- dashboardDir: string | null;
1714
- dashboardDataDir: string | null;
1715
- }): Promise<{ server: http.Server; tmpDir: string }> => {
1716
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'console-server-'));
1717
- const server = await startWebServer({
1718
- accessToken: testToken,
1719
- uiDistDir: path.join(tmpDir, 'ui-dist'),
1720
- consoleDataOutputDir: null,
1721
- inTmuxDataDir: null,
1722
- dashboardDir: overrides.dashboardDir,
1723
- dashboardDataDir: overrides.dashboardDataDir,
1724
- dashboardProjectNames: ['acme', 'initech'],
1725
- port: 0,
1726
- });
1727
- return { server, tmpDir };
1728
- };
1729
-
1730
- it('composes /tdpm.txt from the data files when dashboardDataDir is set and every file is present', async () => {
1731
- const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), 'dashboard-data-'));
1732
- const staticDir = fs.mkdtempSync(
1733
- path.join(os.tmpdir(), 'dashboard-static-'),
1734
- );
1735
- writeDataFiles(dataDir);
1736
- writeStaticDashboard(staticDir);
1737
- const { server, tmpDir } = await startServer({
1738
- dashboardDir: staticDir,
1739
- dashboardDataDir: dataDir,
1740
- });
1741
- try {
1742
- const response = await requestServer(server, `/tdpm.txt?k=${testToken}`);
1743
- expect(response.statusCode).toBe(200);
1744
- expect(response.body).toBe(expectedComposed);
1745
- expect(response.contentType).toBe('text/html; charset=utf-8');
1746
- expect(response.contentLength).toBe(
1747
- String(Buffer.byteLength(expectedComposed)),
1748
- );
1749
- expect(response.transferEncoding).toBeUndefined();
1750
- expect(response.cacheControl).toBe('no-store');
1751
- } finally {
1752
- await closeServer(server);
1753
- fs.rmSync(tmpDir, { recursive: true, force: true });
1754
- fs.rmSync(dataDir, { recursive: true, force: true });
1755
- fs.rmSync(staticDir, { recursive: true, force: true });
1756
- }
1757
- });
1758
-
1759
- it('serves the static tdpm.txt unchanged when dashboardDataDir is unset', async () => {
1760
- const staticDir = fs.mkdtempSync(
1761
- path.join(os.tmpdir(), 'dashboard-static-'),
1762
- );
1763
- writeStaticDashboard(staticDir);
1764
- const { server, tmpDir } = await startServer({
1765
- dashboardDir: staticDir,
1766
- dashboardDataDir: null,
1767
- });
1768
- try {
1769
- const response = await requestServer(server, `/tdpm.txt?k=${testToken}`);
1770
- expect(response.statusCode).toBe(200);
1771
- expect(response.body).toBe(staticDashboardRaw);
1772
- expect(response.contentType).toBe('text/html; charset=utf-8');
1773
- expect(response.contentLength).toBe(
1774
- String(Buffer.byteLength(staticDashboardRaw)),
1775
- );
1776
- expect(response.cacheControl).toBe('no-store');
1777
- } finally {
1778
- await closeServer(server);
1779
- fs.rmSync(tmpDir, { recursive: true, force: true });
1780
- fs.rmSync(staticDir, { recursive: true, force: true });
1781
- }
1782
- });
1783
-
1784
- it('falls back to the static tdpm.txt when dashboardDataDir is set but the data files are absent', async () => {
1785
- const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), 'dashboard-data-'));
1786
- const staticDir = fs.mkdtempSync(
1787
- path.join(os.tmpdir(), 'dashboard-static-'),
1788
- );
1789
- writeStaticDashboard(staticDir);
1790
- const { server, tmpDir } = await startServer({
1791
- dashboardDir: staticDir,
1792
- dashboardDataDir: dataDir,
1793
- });
1794
- try {
1795
- const response = await requestServer(server, `/tdpm.txt?k=${testToken}`);
1796
- expect(response.statusCode).toBe(200);
1797
- expect(response.body).toBe(staticDashboardRaw);
1798
- } finally {
1799
- await closeServer(server);
1800
- fs.rmSync(tmpDir, { recursive: true, force: true });
1801
- fs.rmSync(dataDir, { recursive: true, force: true });
1802
- fs.rmSync(staticDir, { recursive: true, force: true });
1803
- }
1804
- });
1805
-
1806
- it('falls back to the static tdpm.txt when only some data files are present', async () => {
1807
- const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), 'dashboard-data-'));
1808
- const staticDir = fs.mkdtempSync(
1809
- path.join(os.tmpdir(), 'dashboard-static-'),
1810
- );
1811
- fs.writeFileSync(
1812
- path.join(dataDir, 'machine-status.json'),
1813
- JSON.stringify({
1814
- memPct: 55,
1815
- cpuPct: 62,
1816
- diskPct: 89,
1817
- load: [16, 23, 40],
1818
- cycleMinutes: 14,
1819
- capturedAt: '2026-06-26T00:00:00.000Z',
1820
- }),
1821
- );
1822
- writeStaticDashboard(staticDir);
1823
- const { server, tmpDir } = await startServer({
1824
- dashboardDir: staticDir,
1825
- dashboardDataDir: dataDir,
1826
- });
1827
- try {
1828
- const response = await requestServer(server, `/tdpm.txt?k=${testToken}`);
1829
- expect(response.statusCode).toBe(200);
1830
- expect(response.body).toBe(staticDashboardRaw);
1831
- } finally {
1832
- await closeServer(server);
1833
- fs.rmSync(tmpDir, { recursive: true, force: true });
1834
- fs.rmSync(dataDir, { recursive: true, force: true });
1835
- fs.rmSync(staticDir, { recursive: true, force: true });
1836
- }
1837
- });
1838
-
1839
- it('returns 404 for /tdpm.txt when the static file is absent and compose mode is inactive', async () => {
1840
- const staticDir = fs.mkdtempSync(
1841
- path.join(os.tmpdir(), 'dashboard-static-'),
1842
- );
1843
- const { server, tmpDir } = await startServer({
1844
- dashboardDir: staticDir,
1845
- dashboardDataDir: null,
1846
- });
1847
- try {
1848
- const response = await requestServer(server, `/tdpm.txt?k=${testToken}`);
1849
- expect(response.statusCode).toBe(404);
1850
- } finally {
1851
- await closeServer(server);
1852
- fs.rmSync(tmpDir, { recursive: true, force: true });
1853
- fs.rmSync(staticDir, { recursive: true, force: true });
1854
- }
1855
- });
1856
-
1857
- it('returns 404 for /tdpm.txt when both dashboardDir and dashboardDataDir are null', async () => {
1858
- const { server, tmpDir } = await startServer({
1859
- dashboardDir: null,
1860
- dashboardDataDir: null,
1861
- });
1862
- try {
1863
- const response = await requestServer(server, `/tdpm.txt?k=${testToken}`);
1864
- expect(response.statusCode).toBe(404);
1865
- } finally {
1866
- await closeServer(server);
1867
- fs.rmSync(tmpDir, { recursive: true, force: true });
1868
- }
1869
- });
1870
-
1871
- it('rejects a non-GET method on /tdpm.txt with 404', async () => {
1872
- const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), 'dashboard-data-'));
1873
- const staticDir = fs.mkdtempSync(
1874
- path.join(os.tmpdir(), 'dashboard-static-'),
1875
- );
1876
- writeDataFiles(dataDir);
1877
- writeStaticDashboard(staticDir);
1878
- const { server, tmpDir } = await startServer({
1879
- dashboardDir: staticDir,
1880
- dashboardDataDir: dataDir,
1881
- });
1882
- try {
1883
- const response = await requestServer(server, '/tdpm.txt', 'POST');
1884
- expect(response.statusCode).toBe(404);
1885
- } finally {
1886
- await closeServer(server);
1887
- fs.rmSync(tmpDir, { recursive: true, force: true });
1888
- fs.rmSync(dataDir, { recursive: true, force: true });
1889
- fs.rmSync(staticDir, { recursive: true, force: true });
1890
- }
1891
- });
1892
-
1893
- it('returns 401 for GET /tdpm.txt without a token', async () => {
1894
- const staticDir = fs.mkdtempSync(
1895
- path.join(os.tmpdir(), 'dashboard-static-'),
1896
- );
1897
- writeStaticDashboard(staticDir);
1898
- const { server, tmpDir } = await startServer({
1899
- dashboardDir: staticDir,
1900
- dashboardDataDir: null,
1901
- });
1902
- try {
1903
- const response = await requestServer(server, '/tdpm.txt');
1904
- expect(response.statusCode).toBe(401);
1905
- } finally {
1906
- await closeServer(server);
1907
- fs.rmSync(tmpDir, { recursive: true, force: true });
1908
- fs.rmSync(staticDir, { recursive: true, force: true });
1909
- }
1910
- });
1911
-
1912
- it('returns 401 for GET /tdpm.txt with wrong token via query param', async () => {
1913
- const staticDir = fs.mkdtempSync(
1914
- path.join(os.tmpdir(), 'dashboard-static-'),
1915
- );
1916
- writeStaticDashboard(staticDir);
1917
- const { server, tmpDir } = await startServer({
1918
- dashboardDir: staticDir,
1919
- dashboardDataDir: null,
1920
- });
1921
- try {
1922
- const response = await requestServer(server, '/tdpm.txt?k=wrong-token');
1923
- expect(response.statusCode).toBe(401);
1924
- } finally {
1925
- await closeServer(server);
1926
- fs.rmSync(tmpDir, { recursive: true, force: true });
1927
- fs.rmSync(staticDir, { recursive: true, force: true });
1928
- }
1929
- });
1930
-
1931
- it('returns 200 for GET /tdpm.txt with valid token via x-pv-token header', async () => {
1932
- const staticDir = fs.mkdtempSync(
1933
- path.join(os.tmpdir(), 'dashboard-static-'),
1934
- );
1935
- writeStaticDashboard(staticDir);
1936
- const { server, tmpDir } = await startServer({
1937
- dashboardDir: staticDir,
1938
- dashboardDataDir: null,
1939
- });
1940
- try {
1941
- const response = await requestServer(server, '/tdpm.txt', 'GET', {
1942
- [CONSOLE_TOKEN_HEADER]: testToken,
1943
- });
1944
- expect(response.statusCode).toBe(200);
1945
- expect(response.body).toBe(staticDashboardRaw);
1946
- } finally {
1947
- await closeServer(server);
1948
- fs.rmSync(tmpDir, { recursive: true, force: true });
1949
- fs.rmSync(staticDir, { recursive: true, force: true });
1950
- }
1951
- });
1952
- });
1953
-
1954
- describe('webServer image proxy', () => {
1955
- const testToken = 'image-proxy-token-value';
1956
- const githubToken = 'gh-token-value';
1957
- const allowedUrl = 'https://github.com/user-attachments/assets/abc-123';
1958
- const itemUrl = 'https://github.com/owner-name/repo-name/issues/12';
1959
- const itemUrlParam = `&itemUrl=${encodeURIComponent(itemUrl)}`;
1960
-
1961
- const pngBytes = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a]);
1962
-
1963
- const stubFetcher: ImageFetcher = async () => ({
1964
- status: 200,
1965
- contentType: 'image/png',
1966
- body: pngBytes,
1967
- });
1968
-
1969
- const closeServer = (server: http.Server): Promise<void> =>
1970
- new Promise((resolve, reject) => {
1971
- server.close((error) => {
1972
- if (error) {
1973
- reject(error);
1974
- return;
1975
- }
1976
- resolve();
1977
- });
1978
- });
1979
-
1980
- const requestImage = (
1981
- server: http.Server,
1982
- requestPath: string,
1983
- ): Promise<{
1984
- statusCode: number;
1985
- contentType: string | undefined;
1986
- contentLength: string | undefined;
1987
- cacheControl: string | undefined;
1988
- body: Buffer;
1989
- }> => {
1990
- const address = server.address();
1991
- if (address === null || typeof address === 'string') {
1992
- throw new Error('server is not listening on a TCP port');
1993
- }
1994
- const port = address.port;
1995
- return new Promise((resolve, reject) => {
1996
- const request = http.request(
1997
- { host: '127.0.0.1', port, path: requestPath, method: 'GET' },
1998
- (response) => {
1999
- const chunks: Uint8Array[] = [];
2000
- response.on('data', (chunk: Uint8Array) => chunks.push(chunk));
2001
- response.on('end', () => {
2002
- resolve({
2003
- statusCode: response.statusCode ?? 0,
2004
- contentType: response.headers['content-type'],
2005
- contentLength: response.headers['content-length'],
2006
- cacheControl: response.headers['cache-control'],
2007
- body: Buffer.concat(chunks),
2008
- });
2009
- });
2010
- },
2011
- );
2012
- request.on('error', reject);
2013
- request.end();
2014
- });
2015
- };
2016
-
2017
- const startProxyServer = (
2018
- fetcher: ImageFetcher | null = stubFetcher,
2019
- token: string | null = githubToken,
2020
- ): Promise<{ server: http.Server; tmpDir: string }> =>
2021
- (async () => {
2022
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'console-server-'));
2023
- const server = await startWebServer({
2024
- accessToken: testToken,
2025
- uiDistDir: path.join(tmpDir, 'ui-dist'),
2026
- consoleDataOutputDir: null,
2027
- inTmuxDataDir: null,
2028
- dashboardDir: null,
2029
- dashboardDataDir: null,
2030
- dashboardProjectNames: [],
2031
- resolveGithubToken: token === null ? null : (): string => token,
2032
- imageFetcher: fetcher,
2033
- port: 0,
2034
- });
2035
- return { server, tmpDir };
2036
- })();
2037
-
2038
- it('returns image bytes for an allow-listed url with a valid token', async () => {
2039
- const { server, tmpDir } = await startProxyServer();
2040
- try {
2041
- const response = await requestImage(
2042
- server,
2043
- `/api/img?url=${encodeURIComponent(allowedUrl)}${itemUrlParam}&k=${testToken}`,
2044
- );
2045
- expect(response.statusCode).toBe(200);
2046
- expect(response.contentType).toBe('image/png');
2047
- expect(response.contentLength).toBe(String(pngBytes.length));
2048
- expect(response.cacheControl).toBe('private, max-age=300');
2049
- expect(Array.from(response.body)).toEqual(Array.from(pngBytes));
2050
- } finally {
2051
- await closeServer(server);
2052
- fs.rmSync(tmpDir, { recursive: true, force: true });
2053
- }
2054
- });
2055
-
2056
- it('rejects a non-allow-listed url with 400', async () => {
2057
- const { server, tmpDir } = await startProxyServer();
2058
- try {
2059
- const response = await requestImage(
2060
- server,
2061
- `/api/img?url=${encodeURIComponent('https://example.com/x.png')}${itemUrlParam}&k=${testToken}`,
2062
- );
2063
- expect(response.statusCode).toBe(400);
2064
- expect(response.body.toString('utf-8')).toContain(
2065
- 'not in allowed domain',
2066
- );
2067
- } finally {
2068
- await closeServer(server);
2069
- fs.rmSync(tmpDir, { recursive: true, force: true });
2070
- }
2071
- });
2072
-
2073
- it('rejects a request without a token with 401', async () => {
2074
- const { server, tmpDir } = await startProxyServer();
2075
- try {
2076
- const response = await requestImage(
2077
- server,
2078
- `/api/img?url=${encodeURIComponent(allowedUrl)}`,
2079
- );
2080
- expect(response.statusCode).toBe(401);
2081
- } finally {
2082
- await closeServer(server);
2083
- fs.rmSync(tmpDir, { recursive: true, force: true });
2084
- }
2085
- });
2086
-
2087
- it('rejects a request with an invalid token with 401', async () => {
2088
- const { server, tmpDir } = await startProxyServer();
2089
- try {
2090
- const response = await requestImage(
2091
- server,
2092
- `/api/img?url=${encodeURIComponent(allowedUrl)}&k=wrong-token`,
2093
- );
2094
- expect(response.statusCode).toBe(401);
2095
- } finally {
2096
- await closeServer(server);
2097
- fs.rmSync(tmpDir, { recursive: true, force: true });
2098
- }
2099
- });
2100
-
2101
- it('returns 502 when the github token is not configured', async () => {
2102
- const { server, tmpDir } = await startProxyServer(stubFetcher, null);
2103
- try {
2104
- const response = await requestImage(
2105
- server,
2106
- `/api/img?url=${encodeURIComponent(allowedUrl)}${itemUrlParam}&k=${testToken}`,
2107
- );
2108
- expect(response.statusCode).toBe(502);
2109
- expect(response.body.toString('utf-8')).toContain(
2110
- 'github token is not configured',
2111
- );
2112
- } finally {
2113
- await closeServer(server);
2114
- fs.rmSync(tmpDir, { recursive: true, force: true });
2115
- }
2116
- });
2117
-
2118
- it('fetches the image with the token resolved for the repository owner of the item', async () => {
2119
- const receivedHeaders: Record<string, string>[] = [];
2120
- const recordingFetcher: ImageFetcher = async (_url, headers) => {
2121
- receivedHeaders.push(headers);
2122
- return { status: 200, contentType: 'image/png', body: pngBytes };
2123
- };
2124
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'console-server-'));
2125
- const server = await startWebServer({
2126
- accessToken: testToken,
2127
- uiDistDir: path.join(tmpDir, 'ui-dist'),
2128
- consoleDataOutputDir: null,
2129
- inTmuxDataDir: null,
2130
- dashboardDir: null,
2131
- dashboardDataDir: null,
2132
- dashboardProjectNames: [],
2133
- resolveGithubToken: (repositoryOwner: string): string =>
2134
- repositoryOwner === 'acme' ? 'acme-token' : githubToken,
2135
- imageFetcher: recordingFetcher,
2136
- port: 0,
2137
- });
2138
- try {
2139
- const response = await requestImage(
2140
- server,
2141
- `/api/img?url=${encodeURIComponent(allowedUrl)}&itemUrl=${encodeURIComponent('https://github.com/acme/repo/issues/7')}&k=${testToken}`,
2142
- );
2143
- expect(response.statusCode).toBe(200);
2144
- expect(receivedHeaders).toHaveLength(1);
2145
- expect(receivedHeaders[0]['Authorization']).toBe('token acme-token');
2146
- } finally {
2147
- await closeServer(server);
2148
- fs.rmSync(tmpDir, { recursive: true, force: true });
2149
- }
2150
- });
2151
-
2152
- it('rejects an image request whose item url is missing with 400', async () => {
2153
- const { server, tmpDir } = await startProxyServer();
2154
- try {
2155
- const response = await requestImage(
2156
- server,
2157
- `/api/img?url=${encodeURIComponent(allowedUrl)}&k=${testToken}`,
2158
- );
2159
- expect(response.statusCode).toBe(400);
2160
- expect(response.body.toString('utf-8')).toContain('itemUrl');
2161
- } finally {
2162
- await closeServer(server);
2163
- fs.rmSync(tmpDir, { recursive: true, force: true });
2164
- }
2165
- });
2166
-
2167
- it('returns 502 when the upstream image fetch fails', async () => {
2168
- const failingFetcher: ImageFetcher = async () => ({
2169
- status: 404,
2170
- contentType: null,
2171
- body: Buffer.alloc(0),
2172
- });
2173
- const { server, tmpDir } = await startProxyServer(failingFetcher);
2174
- try {
2175
- const response = await requestImage(
2176
- server,
2177
- `/api/img?url=${encodeURIComponent(allowedUrl)}${itemUrlParam}&k=${testToken}`,
2178
- );
2179
- expect(response.statusCode).toBe(502);
2180
- expect(response.body.toString('utf-8')).toContain('upstream 404');
2181
- } finally {
2182
- await closeServer(server);
2183
- fs.rmSync(tmpDir, { recursive: true, force: true });
2184
- }
2185
- });
2186
- });
2187
-
2188
- describe('webServer token cookie redirect', () => {
2189
- const testToken = 'integration-test-token-value';
2190
-
2191
- const closeServer = (server: http.Server): Promise<void> =>
2192
- new Promise((resolve, reject) => {
2193
- server.close((error) => {
2194
- if (error) {
2195
- reject(error);
2196
- return;
2197
- }
2198
- resolve();
2199
- });
2200
- });
2201
-
2202
- const requestServer = (
2203
- server: http.Server,
2204
- requestPath: string,
2205
- headers: http.OutgoingHttpHeaders = {},
2206
- ): Promise<{
2207
- statusCode: number;
2208
- body: string;
2209
- location: string | undefined;
2210
- setCookie: string[] | undefined;
2211
- referrerPolicy: string | string[] | undefined;
2212
- cacheControl: string | undefined;
2213
- }> => {
2214
- const address = server.address();
2215
- if (address === null || typeof address === 'string') {
2216
- throw new Error('server is not listening on a TCP port');
2217
- }
2218
- const port = address.port;
2219
- return new Promise((resolve, reject) => {
2220
- const httpRequest = http.request(
2221
- { host: '127.0.0.1', port, path: requestPath, headers },
2222
- (response) => {
2223
- const chunks: Uint8Array[] = [];
2224
- response.on('data', (chunk: Uint8Array) => chunks.push(chunk));
2225
- response.on('end', () => {
2226
- resolve({
2227
- statusCode: response.statusCode ?? 0,
2228
- body: Buffer.concat(chunks).toString('utf-8'),
2229
- location: response.headers['location'],
2230
- setCookie: response.headers['set-cookie'],
2231
- referrerPolicy: response.headers['referrer-policy'],
2232
- cacheControl: response.headers['cache-control'],
2233
- });
2234
- });
2235
- },
2236
- );
2237
- httpRequest.on('error', reject);
2238
- httpRequest.end();
2239
- });
2240
- };
2241
-
2242
- const startWithUiDist = async (): Promise<{
2243
- server: http.Server;
2244
- tmpDir: string;
2245
- }> => {
2246
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'console-server-'));
2247
- const uiDistDir = path.join(tmpDir, 'ui-dist');
2248
- fs.mkdirSync(uiDistDir, { recursive: true });
2249
- fs.writeFileSync(
2250
- path.join(uiDistDir, 'index.html'),
2251
- '<!DOCTYPE html><title>spa</title><div id="root"></div>',
2252
- );
2253
- const server = await startWebServer({
2254
- accessToken: testToken,
2255
- uiDistDir,
2256
- consoleDataOutputDir: null,
2257
- inTmuxDataDir: null,
2258
- dashboardDir: null,
2259
- dashboardDataDir: null,
2260
- dashboardProjectNames: [],
2261
- port: 0,
2262
- });
2263
- return { server, tmpDir };
2264
- };
2265
-
2266
- const firstSetCookie = (setCookie: string[] | undefined): string => {
2267
- expect(setCookie).toBeDefined();
2268
- expect(setCookie).toHaveLength(1);
2269
- return (setCookie ?? [''])[0];
2270
- };
2271
-
2272
- it('redirects a per-project app route carrying ?k= to the keyless path and sets an HttpOnly SameSite=Strict cookie', async () => {
2273
- const { server, tmpDir } = await startWithUiDist();
2274
- try {
2275
- const response = await requestServer(
2276
- server,
2277
- `/projects/acme?k=${testToken}`,
2278
- );
2279
- expect(response.statusCode).toBe(302);
2280
- expect(response.location).toBe('/projects/acme');
2281
- expect(response.body).not.toContain(testToken);
2282
- const cookie = firstSetCookie(response.setCookie);
2283
- expect(cookie).toContain(`${CONSOLE_TOKEN_COOKIE}=${testToken}`);
2284
- expect(cookie).toContain('HttpOnly');
2285
- expect(cookie).toContain('SameSite=Strict');
2286
- expect(cookie).toContain('Path=/');
2287
- expect(response.referrerPolicy).toBe('no-referrer');
2288
- } finally {
2289
- await closeServer(server);
2290
- fs.rmSync(tmpDir, { recursive: true, force: true });
2291
- }
2292
- });
2293
-
2294
- it('sets a token cookie that survives a browser restart by carrying a long Max-Age', async () => {
2295
- const { server, tmpDir } = await startWithUiDist();
2296
- try {
2297
- const response = await requestServer(
2298
- server,
2299
- `/projects/acme?k=${testToken}`,
2300
- );
2301
- const cookie = firstSetCookie(response.setCookie);
2302
- const maxAgeAttribute = /(?:^|;\s*)Max-Age=(\d+)\s*(?:;|$)/i.exec(cookie);
2303
- expect(maxAgeAttribute).not.toBeNull();
2304
- expect(Number((maxAgeAttribute ?? ['', '0'])[1])).toBeGreaterThanOrEqual(
2305
- 30 * 24 * 60 * 60,
2306
- );
2307
- } finally {
2308
- await closeServer(server);
2309
- fs.rmSync(tmpDir, { recursive: true, force: true });
2310
- }
2311
- });
2312
-
2313
- it('redirects a per-project tab route carrying ?k= and preserves other query parameters', async () => {
2314
- const { server, tmpDir } = await startWithUiDist();
2315
- try {
2316
- const response = await requestServer(
2317
- server,
2318
- `/projects/globex/prs?k=${testToken}&foo=bar`,
2319
- );
2320
- expect(response.statusCode).toBe(302);
2321
- expect(response.location).toBe('/projects/globex/prs?foo=bar');
2322
- const cookie = firstSetCookie(response.setCookie);
2323
- expect(cookie).toContain(`${CONSOLE_TOKEN_COOKIE}=${testToken}`);
2324
- } finally {
2325
- await closeServer(server);
2326
- fs.rmSync(tmpDir, { recursive: true, force: true });
2327
- }
2328
- });
2329
-
2330
- it('redirects the root and index.html routes carrying ?k= to a keyless path', async () => {
2331
- const { server, tmpDir } = await startWithUiDist();
2332
- try {
2333
- const root = await requestServer(server, `/?k=${testToken}`);
2334
- expect(root.statusCode).toBe(302);
2335
- expect(root.location).toBe('/');
2336
- expect(firstSetCookie(root.setCookie)).toContain(
2337
- `${CONSOLE_TOKEN_COOKIE}=${testToken}`,
2338
- );
2339
-
2340
- const indexHtml = await requestServer(
2341
- server,
2342
- `/index.html?k=${testToken}`,
2343
- );
2344
- expect(indexHtml.statusCode).toBe(302);
2345
- expect(indexHtml.location).toBe('/index.html');
2346
- } finally {
2347
- await closeServer(server);
2348
- fs.rmSync(tmpDir, { recursive: true, force: true });
2349
- }
2350
- });
2351
-
2352
- it('serves the app route without a token and never leaks a token in the URL', async () => {
2353
- const { server, tmpDir } = await startWithUiDist();
2354
- try {
2355
- const response = await requestServer(server, '/projects/acme');
2356
- expect(response.statusCode).toBe(200);
2357
- expect(response.body).toContain('spa');
2358
- expect(response.setCookie).toBeUndefined();
2359
- expect(response.referrerPolicy).toBe('no-referrer');
2360
- } finally {
2361
- await closeServer(server);
2362
- fs.rmSync(tmpDir, { recursive: true, force: true });
2363
- }
2364
- });
2365
-
2366
- it('sets Referrer-Policy: no-referrer on the console HTML responses', async () => {
2367
- const { server, tmpDir } = await startWithUiDist();
2368
- try {
2369
- const root = await requestServer(server, '/');
2370
- expect(root.statusCode).toBe(200);
2371
- expect(root.referrerPolicy).toBe('no-referrer');
2372
-
2373
- const projectTab = await requestServer(server, '/projects/globex/prs');
2374
- expect(projectTab.statusCode).toBe(200);
2375
- expect(projectTab.referrerPolicy).toBe('no-referrer');
2376
- } finally {
2377
- await closeServer(server);
2378
- fs.rmSync(tmpDir, { recursive: true, force: true });
2379
- }
2380
- });
2381
-
2382
- it('authenticates a data list request through the cookie without a token in the URL', async () => {
2383
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'console-server-'));
2384
- const dataDir = path.join(tmpDir, 'data');
2385
- const listDir = path.join(dataDir, 'acme', 'prs');
2386
- fs.mkdirSync(listDir, { recursive: true });
2387
- fs.writeFileSync(
2388
- path.join(listDir, 'list.json'),
2389
- JSON.stringify({ pjcode: 'acme', items: [] }),
2390
- );
2391
- const server = await startWebServer({
2392
- accessToken: testToken,
2393
- uiDistDir: path.join(tmpDir, 'ui-dist'),
2394
- consoleDataOutputDir: dataDir,
2395
- inTmuxDataDir: null,
2396
- dashboardDir: null,
2397
- dashboardDataDir: null,
2398
- dashboardProjectNames: [],
2399
- port: 0,
2400
- });
2401
- try {
2402
- const withCookie = await requestServer(
2403
- server,
2404
- '/projects/acme/prs/list.json',
2405
- { Cookie: `${CONSOLE_TOKEN_COOKIE}=${testToken}` },
2406
- );
2407
- expect(withCookie.statusCode).toBe(200);
2408
- expect(JSON.parse(withCookie.body)).toEqual({
2409
- pjcode: 'acme',
2410
- items: [],
2411
- });
2412
- } finally {
2413
- await closeServer(server);
2414
- fs.rmSync(tmpDir, { recursive: true, force: true });
2415
- }
2416
- });
2417
-
2418
- it('rejects a data or api request when no token and no cookie are present', async () => {
2419
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'console-server-'));
2420
- const server = await startWebServer({
2421
- accessToken: testToken,
2422
- uiDistDir: path.join(tmpDir, 'ui-dist'),
2423
- consoleDataOutputDir: null,
2424
- inTmuxDataDir: null,
2425
- dashboardDir: null,
2426
- dashboardDataDir: null,
2427
- dashboardProjectNames: [],
2428
- port: 0,
2429
- });
2430
- try {
2431
- const noToken = await requestServer(server, '/data/situation.json');
2432
- expect(noToken.statusCode).toBe(401);
2433
-
2434
- const wrongCookie = await requestServer(server, '/api/review', {
2435
- Cookie: `${CONSOLE_TOKEN_COOKIE}=wrong-token`,
2436
- });
2437
- expect(wrongCookie.statusCode).toBe(401);
2438
- } finally {
2439
- await closeServer(server);
2440
- fs.rmSync(tmpDir, { recursive: true, force: true });
2441
- }
2442
- });
2443
- });
2444
-
2445
- const toPlainValue = (document: { toJS: () => unknown }): unknown =>
2446
- document.toJS();
2447
-
2448
- describe('webServer owner call file route', () => {
2449
- const testToken = 'owner-call-token-value';
2450
- const sessionName = toTmuxSessionName(
2451
- 'https://github.com/OWNER/REPO/issues/1',
2452
- );
2453
- const ownerCall = {
2454
- sessionName,
2455
- calledAt: '2026-08-14T04:22:28Z',
2456
- body: ' the first line of the call body.\n\na later line of the call body.\n',
2457
- };
2458
-
2459
- const requestServer = (
2460
- server: http.Server,
2461
- requestPath: string,
2462
- method = 'GET',
2463
- ): Promise<{
2464
- statusCode: number;
2465
- body: string;
2466
- contentType: string | undefined;
2467
- }> => {
2468
- const address = server.address();
2469
- if (address === null || typeof address === 'string') {
2470
- throw new Error('server is not listening on a TCP port');
2471
- }
2472
- return new Promise((resolve, reject) => {
2473
- const httpRequest = http.request(
2474
- { host: '127.0.0.1', port: address.port, path: requestPath, method },
2475
- (response) => {
2476
- const chunks: Uint8Array[] = [];
2477
- response.on('data', (chunk: Uint8Array) => chunks.push(chunk));
2478
- response.on('end', () => {
2479
- resolve({
2480
- statusCode: response.statusCode ?? 0,
2481
- body: Buffer.concat(chunks).toString('utf-8'),
2482
- contentType: response.headers['content-type'],
2483
- });
2484
- });
2485
- },
2486
- );
2487
- httpRequest.on('error', reject);
2488
- httpRequest.end();
2489
- });
2490
- };
2491
-
2492
- const closeServer = (server: http.Server): Promise<void> =>
2493
- new Promise((resolve, reject) => {
2494
- server.close((error) => {
2495
- if (error) {
2496
- reject(error);
2497
- return;
2498
- }
2499
- resolve();
2500
- });
2501
- });
2502
-
2503
- const startWithOwnerCallFile = async (
2504
- projectCode: string | null,
2505
- ): Promise<{
2506
- server: http.Server;
2507
- tmpDir: string;
2508
- inTmuxDataDir: string;
2509
- requestPath: string;
2510
- }> => {
2511
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'owner-call-server-'));
2512
- const inTmuxDataDir = path.join(tmpDir, 'in-tmux-by-human');
2513
- fs.mkdirSync(inTmuxDataDir, { recursive: true });
2514
- fs.writeFileSync(path.join(tmpDir, 'secret.yaml'), 'secret: true\n');
2515
- ownerCallFileAppend({ dataDir: inTmuxDataDir, projectCode, ownerCall });
2516
- const server = await startWebServer({
2517
- accessToken: testToken,
2518
- uiDistDir: path.join(tmpDir, 'ui-dist'),
2519
- consoleDataOutputDir: null,
2520
- inTmuxDataDir,
2521
- dashboardDir: null,
2522
- dashboardDataDir: null,
2523
- dashboardProjectNames: [],
2524
- port: 0,
2525
- });
2526
- return {
2527
- server,
2528
- tmpDir,
2529
- inTmuxDataDir,
2530
- requestPath: `/in-tmux-by-human/${ownerCallFileRelativePath(
2531
- projectCode,
2532
- sessionName,
2533
- )}`,
2534
- };
2535
- };
2536
-
2537
- it('requires a token for the owner call file path', () => {
2538
- expect(
2539
- requiresToken(
2540
- `/in-tmux-by-human/${ownerCallFileRelativePath('umino', sessionName)}`,
2541
- ),
2542
- ).toBe(true);
2543
- });
2544
-
2545
- it('recognizes only the owner call file path as deletable', () => {
2546
- expect(
2547
- isOwnerCallFileRequestPath(
2548
- `/in-tmux-by-human/${ownerCallFileRelativePath('umino', sessionName)}`,
2549
- ),
2550
- ).toBe(true);
2551
- expect(isOwnerCallFileRequestPath('/in-tmux-by-human/index.v4.json')).toBe(
2552
- false,
2553
- );
2554
- expect(
2555
- isOwnerCallFileRequestPath('/in-tmux-by-human/call-to-user/umino.yaml'),
2556
- ).toBe(false);
2557
- });
2558
-
2559
- it('resolves the owner call file the append entry point writes, without a hardcoded path', () => {
2560
- const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), 'owner-call-path-'));
2561
- try {
2562
- ownerCallFileAppend({
2563
- dataDir,
2564
- projectCode: 'umino',
2565
- ownerCall,
2566
- });
2567
- const relativePath = ownerCallFileRelativePath('umino', sessionName);
2568
-
2569
- const resolved = resolveFlatInTmuxFilePath(
2570
- dataDir,
2571
- `/in-tmux-by-human/${relativePath}`,
2572
- );
2573
-
2574
- expect(resolved).not.toBeNull();
2575
- expect(fs.readFileSync(String(resolved), 'utf-8')).toBe(
2576
- fs.readFileSync(path.join(dataDir, relativePath), 'utf-8'),
2577
- );
2578
- } finally {
2579
- fs.rmSync(dataDir, { recursive: true, force: true });
2580
- }
2581
- });
2582
-
2583
- it('serves, then deletes, the file the append entry point wrote', async () => {
2584
- const { server, tmpDir, requestPath } =
2585
- await startWithOwnerCallFile('umino');
2586
- try {
2587
- const fetched = await requestServer(
2588
- server,
2589
- `${requestPath}?k=${testToken}`,
2590
- );
2591
- expect(fetched.statusCode).toBe(200);
2592
- expect(fetched.contentType).toContain('text/yaml');
2593
- const documents = parseAllDocuments(fetched.body);
2594
- expect(documents).toHaveLength(1);
2595
- expect(documents[0].toJS()).toEqual(ownerCall);
2596
-
2597
- const deleted = await requestServer(
2598
- server,
2599
- `${requestPath}?k=${testToken}`,
2600
- 'DELETE',
2601
- );
2602
- expect(deleted.statusCode).toBe(204);
2603
-
2604
- const refetched = await requestServer(
2605
- server,
2606
- `${requestPath}?k=${testToken}`,
2607
- );
2608
- expect(refetched.statusCode).toBe(404);
2609
-
2610
- const deletedAgain = await requestServer(
2611
- server,
2612
- `${requestPath}?k=${testToken}`,
2613
- 'DELETE',
2614
- );
2615
- expect(deletedAgain.statusCode).toBe(204);
2616
- } finally {
2617
- await closeServer(server);
2618
- fs.rmSync(tmpDir, { recursive: true, force: true });
2619
- }
2620
- });
2621
-
2622
- it('serves two appended documents oldest first', async () => {
2623
- const { server, tmpDir, inTmuxDataDir, requestPath } =
2624
- await startWithOwnerCallFile(null);
2625
- try {
2626
- ownerCallFileAppend({
2627
- dataDir: inTmuxDataDir,
2628
- projectCode: null,
2629
- ownerCall: {
2630
- sessionName,
2631
- calledAt: '2026-08-14T05:00:00Z',
2632
- body: 'the newer call\n',
2633
- },
2634
- });
2635
-
2636
- const fetched = await requestServer(
2637
- server,
2638
- `${requestPath}?k=${testToken}`,
2639
- );
2640
- expect(fetched.statusCode).toBe(200);
2641
- expect(parseAllDocuments(fetched.body).map(toPlainValue)).toEqual([
2642
- ownerCall,
2643
- {
2644
- sessionName,
2645
- calledAt: '2026-08-14T05:00:00Z',
2646
- body: 'the newer call\n',
2647
- },
2648
- ]);
2649
- } finally {
2650
- await closeServer(server);
2651
- fs.rmSync(tmpDir, { recursive: true, force: true });
2652
- }
2653
- });
2654
-
2655
- it('rejects a request that carries no access token', async () => {
2656
- const { server, tmpDir, requestPath } =
2657
- await startWithOwnerCallFile('umino');
2658
- try {
2659
- expect((await requestServer(server, requestPath)).statusCode).toBe(401);
2660
- expect(
2661
- (await requestServer(server, requestPath, 'DELETE')).statusCode,
2662
- ).toBe(401);
2663
- } finally {
2664
- await closeServer(server);
2665
- fs.rmSync(tmpDir, { recursive: true, force: true });
2666
- }
2667
- });
2668
-
2669
- it('rejects a request whose path escapes the data directory and leaves the outside file untouched', async () => {
2670
- const { server, tmpDir } = await startWithOwnerCallFile('umino');
2671
- const escapingPath = '/in-tmux-by-human/call-to-user/../../secret.yaml';
2672
- try {
2673
- const fetched = await requestServer(
2674
- server,
2675
- `${escapingPath}?k=${testToken}`,
2676
- );
2677
- expect(fetched.statusCode).toBe(404);
2678
- expect(fetched.body).not.toContain('secret: true');
2679
-
2680
- const deleted = await requestServer(
2681
- server,
2682
- `${escapingPath}?k=${testToken}`,
2683
- 'DELETE',
2684
- );
2685
- expect(deleted.statusCode).toBe(404);
2686
- expect(fs.existsSync(path.join(tmpDir, 'secret.yaml'))).toBe(true);
2687
- } finally {
2688
- await closeServer(server);
2689
- fs.rmSync(tmpDir, { recursive: true, force: true });
2690
- }
2691
- });
2692
-
2693
- it('refuses to delete a flat in-tmux json file through the owner call route', async () => {
2694
- const { server, tmpDir, inTmuxDataDir } =
2695
- await startWithOwnerCallFile('umino');
2696
- fs.writeFileSync(
2697
- path.join(inTmuxDataDir, 'index.v4.json'),
2698
- '{"version":4,"projects":[]}\n',
2699
- );
2700
- try {
2701
- const deleted = await requestServer(
2702
- server,
2703
- `/in-tmux-by-human/index.v4.json?k=${testToken}`,
2704
- 'DELETE',
2705
- );
2706
- expect(deleted.statusCode).toBe(404);
2707
- expect(fs.existsSync(path.join(inTmuxDataDir, 'index.v4.json'))).toBe(
2708
- true,
2709
- );
2710
- } finally {
2711
- await closeServer(server);
2712
- fs.rmSync(tmpDir, { recursive: true, force: true });
2713
- }
2714
- });
2715
- });
2716
-
2717
- describe('webServer GET /api/projects', () => {
2718
- const testToken = 'integration-test-token-value';
2719
-
2720
- const closeServer = (server: http.Server): Promise<void> =>
2721
- new Promise((resolve, reject) => {
2722
- server.close((error) => {
2723
- if (error) {
2724
- reject(error);
2725
- return;
2726
- }
2727
- resolve();
2728
- });
2729
- });
2730
-
2731
- const request = (
2732
- server: http.Server,
2733
- method: string,
2734
- requestPath: string,
2735
- ): Promise<{ statusCode: number; body: string }> => {
2736
- const address = server.address();
2737
- if (address === null || typeof address === 'string') {
2738
- throw new Error('server is not listening on a TCP port');
2739
- }
2740
- const port = address.port;
2741
- return new Promise((resolve, reject) => {
2742
- const httpRequest = http.request(
2743
- { host: '127.0.0.1', port, path: requestPath, method },
2744
- (response) => {
2745
- const chunks: Uint8Array[] = [];
2746
- response.on('data', (chunk: Uint8Array) => chunks.push(chunk));
2747
- response.on('end', () => {
2748
- resolve({
2749
- statusCode: response.statusCode ?? 0,
2750
- body: Buffer.concat(chunks).toString('utf-8'),
2751
- });
2752
- });
2753
- },
2754
- );
2755
- httpRequest.on('error', reject);
2756
- httpRequest.end();
2757
- });
2758
- };
2759
-
2760
- it('returns the dashboardProjectNames with a valid token and null workflowImprovementIssueUrl when not configured', async () => {
2761
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'console-server-'));
2762
- const server = await startWebServer({
2763
- accessToken: testToken,
2764
- uiDistDir: path.join(tmpDir, 'ui-dist'),
2765
- consoleDataOutputDir: null,
2766
- inTmuxDataDir: null,
2767
- dashboardDir: null,
2768
- dashboardDataDir: null,
2769
- dashboardProjectNames: ['alpha', 'beta', 'gamma'],
2770
- port: 0,
2771
- });
2772
- try {
2773
- const response = await request(
2774
- server,
2775
- 'GET',
2776
- `/api/projects?k=${testToken}`,
2777
- );
2778
- expect(response.statusCode).toBe(200);
2779
- expect(JSON.parse(response.body)).toEqual({
2780
- pjcodes: ['alpha', 'beta', 'gamma'],
2781
- workflowImprovementIssueUrl: null,
2782
- });
2783
- } finally {
2784
- await closeServer(server);
2785
- fs.rmSync(tmpDir, { recursive: true, force: true });
2786
- }
2787
- });
2788
-
2789
- it('returns the workflowImprovementIssueUrl when configured', async () => {
2790
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'console-server-'));
2791
- const server = await startWebServer({
2792
- accessToken: testToken,
2793
- uiDistDir: path.join(tmpDir, 'ui-dist'),
2794
- consoleDataOutputDir: null,
2795
- inTmuxDataDir: null,
2796
- dashboardDir: null,
2797
- dashboardDataDir: null,
2798
- dashboardProjectNames: ['alpha'],
2799
- workflowImprovementIssueUrl:
2800
- 'https://github.com/owner/repo/issues/new?assignees=someone',
2801
- port: 0,
2802
- });
2803
- try {
2804
- const response = await request(
2805
- server,
2806
- 'GET',
2807
- `/api/projects?k=${testToken}`,
2808
- );
2809
- expect(response.statusCode).toBe(200);
2810
- expect(JSON.parse(response.body)).toEqual({
2811
- pjcodes: ['alpha'],
2812
- workflowImprovementIssueUrl:
2813
- 'https://github.com/owner/repo/issues/new?assignees=someone',
2814
- });
2815
- } finally {
2816
- await closeServer(server);
2817
- fs.rmSync(tmpDir, { recursive: true, force: true });
2818
- }
2819
- });
2820
-
2821
- it('returns 401 without a valid token', async () => {
2822
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'console-server-'));
2823
- const server = await startWebServer({
2824
- accessToken: testToken,
2825
- uiDistDir: path.join(tmpDir, 'ui-dist'),
2826
- consoleDataOutputDir: null,
2827
- inTmuxDataDir: null,
2828
- dashboardDir: null,
2829
- dashboardDataDir: null,
2830
- dashboardProjectNames: ['alpha'],
2831
- port: 0,
2832
- });
2833
- try {
2834
- const response = await request(server, 'GET', '/api/projects');
2835
- expect(response.statusCode).toBe(401);
2836
- } finally {
2837
- await closeServer(server);
2838
- fs.rmSync(tmpDir, { recursive: true, force: true });
2839
- }
2840
- });
2841
-
2842
- it('GET /api/features returns { airplaneMode: false } when enableAirplaneMode is absent', async () => {
2843
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'console-server-'));
2844
- const server = await startWebServer({
2845
- accessToken: testToken,
2846
- uiDistDir: path.join(tmpDir, 'ui-dist'),
2847
- consoleDataOutputDir: null,
2848
- inTmuxDataDir: null,
2849
- dashboardDir: null,
2850
- dashboardDataDir: null,
2851
- dashboardProjectNames: [],
2852
- port: 0,
2853
- });
2854
- try {
2855
- const response = await request(
2856
- server,
2857
- 'GET',
2858
- `/api/features?k=${testToken}`,
2859
- );
2860
- expect(response.statusCode).toBe(200);
2861
- expect(JSON.parse(response.body)).toEqual({ airplaneMode: false });
2862
- } finally {
2863
- await closeServer(server);
2864
- fs.rmSync(tmpDir, { recursive: true, force: true });
2865
- }
2866
- });
2867
-
2868
- it('GET /api/features returns { airplaneMode: true } when enableAirplaneMode is true', async () => {
2869
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'console-server-'));
2870
- const server = await startWebServer({
2871
- accessToken: testToken,
2872
- uiDistDir: path.join(tmpDir, 'ui-dist'),
2873
- consoleDataOutputDir: null,
2874
- inTmuxDataDir: null,
2875
- dashboardDir: null,
2876
- dashboardDataDir: null,
2877
- dashboardProjectNames: [],
2878
- enableAirplaneMode: true,
2879
- port: 0,
2880
- });
2881
- try {
2882
- const response = await request(
2883
- server,
2884
- 'GET',
2885
- `/api/features?k=${testToken}`,
2886
- );
2887
- expect(response.statusCode).toBe(200);
2888
- expect(JSON.parse(response.body)).toEqual({ airplaneMode: true });
2889
- } finally {
2890
- await closeServer(server);
2891
- fs.rmSync(tmpDir, { recursive: true, force: true });
2892
- }
2893
- });
2894
-
2895
- it('GET /api/airplanesync returns 404 when enableAirplaneMode is absent', async () => {
2896
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'console-server-'));
2897
- const server = await startWebServer({
2898
- accessToken: testToken,
2899
- uiDistDir: path.join(tmpDir, 'ui-dist'),
2900
- consoleDataOutputDir: null,
2901
- inTmuxDataDir: null,
2902
- dashboardDir: null,
2903
- dashboardDataDir: null,
2904
- dashboardProjectNames: [],
2905
- port: 0,
2906
- });
2907
- try {
2908
- const response = await request(
2909
- server,
2910
- 'GET',
2911
- `/api/airplanesync?k=${testToken}`,
2912
- );
2913
- expect(response.statusCode).toBe(404);
2914
- } finally {
2915
- await closeServer(server);
2916
- fs.rmSync(tmpDir, { recursive: true, force: true });
2917
- }
2918
- });
2919
-
2920
- it('GET /api/airplanesync returns 404 when enableAirplaneMode is false', async () => {
2921
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'console-server-'));
2922
- const server = await startWebServer({
2923
- accessToken: testToken,
2924
- uiDistDir: path.join(tmpDir, 'ui-dist'),
2925
- consoleDataOutputDir: null,
2926
- inTmuxDataDir: null,
2927
- dashboardDir: null,
2928
- dashboardDataDir: null,
2929
- dashboardProjectNames: [],
2930
- enableAirplaneMode: false,
2931
- port: 0,
2932
- });
2933
- try {
2934
- const response = await request(
2935
- server,
2936
- 'GET',
2937
- `/api/airplanesync?k=${testToken}`,
2938
- );
2939
- expect(response.statusCode).toBe(404);
2940
- } finally {
2941
- await closeServer(server);
2942
- fs.rmSync(tmpDir, { recursive: true, force: true });
2943
- }
2944
- });
2945
- });