github-issue-tower-defence-management 2.19.0 → 2.19.2

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 (783) hide show
  1. package/README.md +1 -1
  2. package/bin/adapter/entry-points/cli/index.js +0 -0
  3. package/bin/domain/usecases/OauthTokenSelectUseCase.js +1 -1
  4. package/package.json +5 -1
  5. package/types/domain/usecases/OauthTokenSelectUseCase.d.ts +1 -1
  6. package/.env.example +0 -7
  7. package/.gitattributes +0 -2
  8. package/.github/CODEOWNERS +0 -6
  9. package/.github/copilot-instructions.md +0 -6
  10. package/.github/dependabot.yml +0 -19
  11. package/.github/instructions/code-review.instructions.md +0 -50
  12. package/.github/workflows/api-created_issue_pr.yml +0 -97
  13. package/.github/workflows/ci-failure-issue-creator.yml +0 -48
  14. package/.github/workflows/close-manual-prs.yml +0 -80
  15. package/.github/workflows/codeql.yml +0 -107
  16. package/.github/workflows/commit-lint.yml +0 -85
  17. package/.github/workflows/configs/commitlint.config.cjs +0 -32
  18. package/.github/workflows/configs/commitlint.config.js +0 -32
  19. package/.github/workflows/console-ui.yml +0 -68
  20. package/.github/workflows/create-pr.yml +0 -116
  21. package/.github/workflows/empty-format-test-job.yml +0 -34
  22. package/.github/workflows/format.yml +0 -28
  23. package/.github/workflows/publish.yml +0 -111
  24. package/.github/workflows/secret-scan.yml +0 -51
  25. package/.github/workflows/test.yml +0 -58
  26. package/.github/workflows/umino-project.yml +0 -207
  27. package/.gitleaks.toml +0 -22
  28. package/.prettierignore +0 -31
  29. package/.prettierrc +0 -5
  30. package/CHANGELOG.md +0 -4155
  31. package/CONTRIBUTING.md +0 -60
  32. package/bin/adapter/entry-points/handlers/resolveUnansweredOwnerCalls.js +0 -12
  33. package/bin/adapter/entry-points/handlers/resolveUnansweredOwnerCalls.js.map +0 -1
  34. package/bin/adapter/repositories/FileSystemSilentSessionNotifiedStateRepository.js +0 -125
  35. package/bin/adapter/repositories/FileSystemSilentSessionNotifiedStateRepository.js.map +0 -1
  36. package/bin/adapter/repositories/issue/issueTimelineUtils.js +0 -38
  37. package/bin/adapter/repositories/issue/issueTimelineUtils.js.map +0 -1
  38. package/bin/domain/entities/UnansweredOwnerCall.js +0 -3
  39. package/bin/domain/entities/UnansweredOwnerCall.js.map +0 -1
  40. package/bin/domain/entities/WorkingTime.js +0 -3
  41. package/bin/domain/entities/WorkingTime.js.map +0 -1
  42. package/bin/domain/usecases/ChangeStatusLongInReviewIssueUseCase.js +0 -37
  43. package/bin/domain/usecases/ChangeStatusLongInReviewIssueUseCase.js.map +0 -1
  44. package/bin/domain/usecases/ConvertCheckboxToIssueInStoryIssueUseCase.js +0 -118
  45. package/bin/domain/usecases/ConvertCheckboxToIssueInStoryIssueUseCase.js.map +0 -1
  46. package/bin/domain/usecases/GenerateWorkingTimeReportUseCase.js +0 -180
  47. package/bin/domain/usecases/GenerateWorkingTimeReportUseCase.js.map +0 -1
  48. package/bin/domain/usecases/TriagerApprovalDispatchUseCase.js +0 -160
  49. package/bin/domain/usecases/TriagerApprovalDispatchUseCase.js.map +0 -1
  50. package/bin/domain/usecases/adapter-interfaces/SilentSessionNotifiedStateRepository.js +0 -3
  51. package/bin/domain/usecases/adapter-interfaces/SilentSessionNotifiedStateRepository.js.map +0 -1
  52. package/bin/domain/usecases/awaitingOwnerApprovalMessage.js +0 -7
  53. package/bin/domain/usecases/awaitingOwnerApprovalMessage.js.map +0 -1
  54. package/bin/domain/usecases/findLastAgentDeclaringReport.js +0 -12
  55. package/bin/domain/usecases/findLastAgentDeclaringReport.js.map +0 -1
  56. package/bin/domain/usecases/isPullRequestDeclaredUnnecessary.js +0 -25
  57. package/bin/domain/usecases/isPullRequestDeclaredUnnecessary.js.map +0 -1
  58. package/bin/domain/usecases/isWaitingForOwnerApproval.js +0 -16
  59. package/bin/domain/usecases/isWaitingForOwnerApproval.js.map +0 -1
  60. package/bin/domain/usecases/returnedToAwaitingWorkspaceMessage.js +0 -7
  61. package/bin/domain/usecases/returnedToAwaitingWorkspaceMessage.js.map +0 -1
  62. package/commitlint.config.js +0 -6
  63. package/eslint.config.mjs +0 -76
  64. package/jest.config.js +0 -84
  65. package/jest.setup.js +0 -1
  66. package/lib/buffer-equal-constant-time-compat/index.js +0 -17
  67. package/lib/buffer-equal-constant-time-compat/index.test.js +0 -54
  68. package/lib/buffer-equal-constant-time-compat/package.json +0 -5
  69. package/lib/typescript-compat/bin/tsc +0 -2
  70. package/lib/typescript-compat/bin/tsserver +0 -2
  71. package/lib/typescript-compat/lib/typescript.js +0 -1
  72. package/lib/typescript-compat/package.json +0 -13
  73. package/renovate.json +0 -46
  74. package/scripts/copyConsoleUiDist.mjs +0 -35
  75. package/scripts/defaultBranchTipVerify.sh +0 -23
  76. package/scripts/testWorkflowRunVerify.sh +0 -47
  77. package/scripts/verifyConsoleUiDist.mjs +0 -96
  78. package/src/adapter/ci/consoleUiWorkflowTimeout.test.ts +0 -61
  79. package/src/adapter/ci/publishTestWorkflowGate.test.ts +0 -653
  80. package/src/adapter/ci/workflowRunCancellation.test.ts +0 -72
  81. package/src/adapter/entry-points/cli/fleetConfig.test.ts +0 -553
  82. package/src/adapter/entry-points/cli/fleetConfig.ts +0 -266
  83. package/src/adapter/entry-points/cli/index.test.ts +0 -2837
  84. package/src/adapter/entry-points/cli/index.ts +0 -1488
  85. package/src/adapter/entry-points/cli/projectConfig.test.ts +0 -342
  86. package/src/adapter/entry-points/cli/projectConfig.ts +0 -500
  87. package/src/adapter/entry-points/console/consoleAirplaneSnapshotApi.test.ts +0 -658
  88. package/src/adapter/entry-points/console/consoleAirplaneSnapshotApi.ts +0 -482
  89. package/src/adapter/entry-points/console/consoleDataDelivery.test.ts +0 -222
  90. package/src/adapter/entry-points/console/consoleDataDelivery.ts +0 -134
  91. package/src/adapter/entry-points/console/consoleDoneStore.test.ts +0 -169
  92. package/src/adapter/entry-points/console/consoleDoneStore.ts +0 -139
  93. package/src/adapter/entry-points/console/consoleGithubTokenResolver.test.ts +0 -275
  94. package/src/adapter/entry-points/console/consoleGithubTokenResolver.ts +0 -113
  95. package/src/adapter/entry-points/console/consoleImageProxy.test.ts +0 -140
  96. package/src/adapter/entry-points/console/consoleImageProxy.ts +0 -111
  97. package/src/adapter/entry-points/console/consoleItemUrlLookup.test.ts +0 -63
  98. package/src/adapter/entry-points/console/consoleItemUrlLookup.ts +0 -66
  99. package/src/adapter/entry-points/console/consoleOperationApi.test.ts +0 -2523
  100. package/src/adapter/entry-points/console/consoleOperationApi.ts +0 -936
  101. package/src/adapter/entry-points/console/consoleProjectResolver.test.ts +0 -246
  102. package/src/adapter/entry-points/console/consoleProjectResolver.ts +0 -111
  103. package/src/adapter/entry-points/console/consoleReadApi.test.ts +0 -714
  104. package/src/adapter/entry-points/console/consoleReadApi.ts +0 -361
  105. package/src/adapter/entry-points/console/consoleTabNames.test.ts +0 -52
  106. package/src/adapter/entry-points/console/consoleTabNames.ts +0 -37
  107. package/src/adapter/entry-points/console/consoleUiDistSync.test.ts +0 -66
  108. package/src/adapter/entry-points/console/dashboardComposeService.test.ts +0 -595
  109. package/src/adapter/entry-points/console/dashboardComposeService.ts +0 -256
  110. package/src/adapter/entry-points/console/dashboardEmitterComposerKey.test.ts +0 -180
  111. package/src/adapter/entry-points/console/ensureConsoleRunning.test.ts +0 -115
  112. package/src/adapter/entry-points/console/ensureConsoleRunning.ts +0 -64
  113. package/src/adapter/entry-points/console/ui/.storybook/main.ts +0 -12
  114. package/src/adapter/entry-points/console/ui/.storybook/preview.ts +0 -15
  115. package/src/adapter/entry-points/console/ui/biome.json +0 -53
  116. package/src/adapter/entry-points/console/ui/components.json +0 -20
  117. package/src/adapter/entry-points/console/ui/e2e/consoleScenario.spec.ts +0 -725
  118. package/src/adapter/entry-points/console/ui/e2e/consoleTestHarness.ts +0 -831
  119. package/src/adapter/entry-points/console/ui/e2e/playwright.config.ts +0 -30
  120. package/src/adapter/entry-points/console/ui/e2e/tsconfig.json +0 -18
  121. package/src/adapter/entry-points/console/ui/index.html +0 -12
  122. package/src/adapter/entry-points/console/ui/jest-globals.cjs +0 -26
  123. package/src/adapter/entry-points/console/ui/jest.setup.ts +0 -1
  124. package/src/adapter/entry-points/console/ui/jest.styleMock.js +0 -1
  125. package/src/adapter/entry-points/console/ui/public/sw.js +0 -60
  126. package/src/adapter/entry-points/console/ui/src/components/ui/badge.stories.tsx +0 -35
  127. package/src/adapter/entry-points/console/ui/src/components/ui/badge.tsx +0 -28
  128. package/src/adapter/entry-points/console/ui/src/components/ui/button.stories.tsx +0 -34
  129. package/src/adapter/entry-points/console/ui/src/components/ui/button.tsx +0 -50
  130. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleCopyCodeButton.stories.tsx +0 -28
  131. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleCopyCodeButton.test.tsx +0 -65
  132. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleCopyCodeButton.tsx +0 -19
  133. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleMarkdownContent.stories.tsx +0 -61
  134. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleMarkdownContent.test.tsx +0 -199
  135. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleMarkdownContent.tsx +0 -166
  136. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleMermaidDiagram.stories.tsx +0 -22
  137. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleMermaidDiagram.test.tsx +0 -74
  138. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleMermaidDiagram.tsx +0 -84
  139. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleMermaidDiagramModalScreen.stories.tsx +0 -27
  140. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleMermaidDiagramModalScreen.test.tsx +0 -85
  141. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleMermaidDiagramModalScreen.tsx +0 -97
  142. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleReferenceLink.stories.tsx +0 -76
  143. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleReferenceLink.test.tsx +0 -56
  144. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleReferenceLink.tsx +0 -39
  145. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleChangedFileList.stories.tsx +0 -32
  146. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleChangedFileList.test.tsx +0 -105
  147. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleChangedFileList.tsx +0 -157
  148. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommentComposer.stories.tsx +0 -68
  149. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommentComposer.test.tsx +0 -612
  150. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommentComposer.tsx +0 -276
  151. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommentList.stories.tsx +0 -33
  152. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommentList.test.tsx +0 -57
  153. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommentList.tsx +0 -76
  154. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommitList.stories.tsx +0 -25
  155. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommitList.test.tsx +0 -55
  156. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommitList.tsx +0 -49
  157. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCopyUrlButton.stories.tsx +0 -24
  158. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCopyUrlButton.test.tsx +0 -63
  159. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCopyUrlButton.tsx +0 -19
  160. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleFetchFailureAlert.stories.tsx +0 -33
  161. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleFetchFailureAlert.test.tsx +0 -62
  162. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleFetchFailureAlert.tsx +0 -59
  163. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleFileDiff.stories.tsx +0 -38
  164. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleFileDiff.test.tsx +0 -151
  165. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleFileDiff.tsx +0 -245
  166. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleItemDetail.stories.tsx +0 -514
  167. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleItemDetail.test.tsx +0 -727
  168. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleItemDetail.tsx +0 -387
  169. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleItemIcon.stories.tsx +0 -82
  170. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleItemIcon.test.tsx +0 -52
  171. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleItemIcon.tsx +0 -32
  172. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestDetail.stories.tsx +0 -48
  173. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestDetail.test.tsx +0 -235
  174. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestDetail.tsx +0 -145
  175. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestMergeableChip.stories.tsx +0 -29
  176. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestMergeableChip.test.tsx +0 -30
  177. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestMergeableChip.tsx +0 -50
  178. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestStatusBadges.stories.tsx +0 -38
  179. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestStatusBadges.test.tsx +0 -54
  180. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestStatusBadges.tsx +0 -60
  181. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleAirplaneModeButton.stories.tsx +0 -56
  182. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleAirplaneModeButton.test.tsx +0 -111
  183. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleAirplaneModeButton.tsx +0 -92
  184. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsolePanel.stories.tsx +0 -51
  185. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsolePanel.test.tsx +0 -59
  186. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsolePanel.tsx +0 -39
  187. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTabList.stories.tsx +0 -207
  188. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTabList.test.tsx +0 -332
  189. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTabList.tsx +0 -211
  190. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTimerSettingsModalDialog.stories.tsx +0 -49
  191. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTimerSettingsModalDialog.test.tsx +0 -157
  192. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTimerSettingsModalDialog.tsx +0 -118
  193. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleCopyStoryNameButton.stories.tsx +0 -23
  194. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleCopyStoryNameButton.test.tsx +0 -40
  195. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleCopyStoryNameButton.tsx +0 -17
  196. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleItemList.stories.tsx +0 -101
  197. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleItemList.test.tsx +0 -102
  198. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleItemList.tsx +0 -75
  199. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleItemSummary.stories.tsx +0 -40
  200. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleItemSummary.test.tsx +0 -213
  201. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleItemSummary.tsx +0 -115
  202. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleQueuedItemRow.stories.tsx +0 -50
  203. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleQueuedItemRow.test.tsx +0 -98
  204. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleQueuedItemRow.tsx +0 -66
  205. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleQueuedList.stories.tsx +0 -69
  206. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleQueuedList.test.tsx +0 -101
  207. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleQueuedList.tsx +0 -75
  208. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryList.stories.tsx +0 -137
  209. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryList.test.tsx +0 -554
  210. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryList.tsx +0 -400
  211. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryReorderPanel.stories.tsx +0 -30
  212. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryReorderPanel.test.tsx +0 -111
  213. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryReorderPanel.tsx +0 -91
  214. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStorySummary.stories.tsx +0 -27
  215. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStorySummary.test.tsx +0 -24
  216. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStorySummary.tsx +0 -28
  217. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleCloseActions.stories.tsx +0 -14
  218. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleCloseActions.test.tsx +0 -21
  219. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleCloseActions.tsx +0 -26
  220. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleDangerousActions.stories.tsx +0 -14
  221. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleDangerousActions.test.tsx +0 -40
  222. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleDangerousActions.tsx +0 -37
  223. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleErrorToast.stories.tsx +0 -33
  224. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleNextActionDateActions.stories.tsx +0 -20
  225. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleNextActionDateActions.test.tsx +0 -48
  226. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleNextActionDateActions.tsx +0 -42
  227. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleOfflinePendingActionsPanel.stories.tsx +0 -115
  228. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleOfflinePendingActionsPanel.test.tsx +0 -262
  229. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleOfflinePendingActionsPanel.tsx +0 -123
  230. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleOperationMenu.stories.tsx +0 -65
  231. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleOperationMenu.test.tsx +0 -159
  232. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleOperationMenu.tsx +0 -55
  233. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsolePullRequestReviewActions.stories.tsx +0 -20
  234. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsolePullRequestReviewActions.test.tsx +0 -93
  235. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsolePullRequestReviewActions.tsx +0 -45
  236. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleStatusActions.stories.tsx +0 -17
  237. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleStatusActions.test.tsx +0 -49
  238. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleStatusActions.tsx +0 -66
  239. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleUndoToast.stories.tsx +0 -47
  240. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleUndoToast.test.tsx +0 -95
  241. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleUndoToast.tsx +0 -62
  242. package/src/adapter/entry-points/console/ui/src/features/console/components/shared/ConsoleClipboardCopyButton.stories.tsx +0 -32
  243. package/src/adapter/entry-points/console/ui/src/features/console/components/shared/ConsoleClipboardCopyButton.test.tsx +0 -101
  244. package/src/adapter/entry-points/console/ui/src/features/console/components/shared/ConsoleClipboardCopyButton.tsx +0 -80
  245. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useAirplaneMode.test.ts +0 -304
  246. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useAirplaneMode.ts +0 -167
  247. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleActionQueue.test.ts +0 -498
  248. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleActionQueue.ts +0 -277
  249. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleCaches.test.ts +0 -99
  250. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleCaches.ts +0 -90
  251. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleDetailPrefetch.test.ts +0 -110
  252. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleDetailPrefetch.ts +0 -83
  253. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleFeaturesConfig.test.ts +0 -51
  254. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleFeaturesConfig.ts +0 -39
  255. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleItemDetailData.test.ts +0 -229
  256. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleItemDetailData.ts +0 -194
  257. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleNavigation.test.ts +0 -151
  258. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleNavigation.ts +0 -128
  259. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleOperations.test.ts +0 -629
  260. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleOperations.ts +0 -417
  261. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleOverlay.test.ts +0 -40
  262. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleOverlay.ts +0 -71
  263. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsolePjcode.test.ts +0 -24
  264. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsolePjcode.ts +0 -17
  265. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleProjectList.test.ts +0 -97
  266. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleProjectList.ts +0 -44
  267. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleProjectTimer.test.ts +0 -59
  268. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleProjectTimer.ts +0 -26
  269. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleReferenceLink.test.ts +0 -48
  270. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleReferenceLink.ts +0 -39
  271. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleResource.test.ts +0 -41
  272. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleResource.ts +0 -57
  273. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleSwipeNavigation.test.ts +0 -115
  274. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleSwipeNavigation.ts +0 -115
  275. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleTabData.test.ts +0 -546
  276. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleTabData.ts +0 -278
  277. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleTimerSettings.test.ts +0 -110
  278. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleTimerSettings.ts +0 -70
  279. package/src/adapter/entry-points/console/ui/src/features/console/lib/airplaneSnapshot.test.ts +0 -387
  280. package/src/adapter/entry-points/console/ui/src/features/console/lib/airplaneSnapshot.ts +0 -407
  281. package/src/adapter/entry-points/console/ui/src/features/console/lib/consoleApi.test.ts +0 -446
  282. package/src/adapter/entry-points/console/ui/src/features/console/lib/consoleApi.ts +0 -533
  283. package/src/adapter/entry-points/console/ui/src/features/console/lib/imageProxy.test.ts +0 -78
  284. package/src/adapter/entry-points/console/ui/src/features/console/lib/imageProxy.ts +0 -34
  285. package/src/adapter/entry-points/console/ui/src/features/console/lib/markdown.test.ts +0 -252
  286. package/src/adapter/entry-points/console/ui/src/features/console/lib/markdown.ts +0 -207
  287. package/src/adapter/entry-points/console/ui/src/features/console/lib/mermaidLoader.test.ts +0 -27
  288. package/src/adapter/entry-points/console/ui/src/features/console/lib/mermaidLoader.ts +0 -71
  289. package/src/adapter/entry-points/console/ui/src/features/console/lib/mermaidZoom.test.ts +0 -88
  290. package/src/adapter/entry-points/console/ui/src/features/console/lib/mermaidZoom.ts +0 -34
  291. package/src/adapter/entry-points/console/ui/src/features/console/lib/navigation.ts +0 -7
  292. package/src/adapter/entry-points/console/ui/src/features/console/lib/resourceCache.test.ts +0 -104
  293. package/src/adapter/entry-points/console/ui/src/features/console/lib/resourceCache.ts +0 -79
  294. package/src/adapter/entry-points/console/ui/src/features/console/logic/actionToast.test.ts +0 -200
  295. package/src/adapter/entry-points/console/ui/src/features/console/logic/actionToast.ts +0 -122
  296. package/src/adapter/entry-points/console/ui/src/features/console/logic/clipboard.test.ts +0 -90
  297. package/src/adapter/entry-points/console/ui/src/features/console/logic/clipboard.ts +0 -37
  298. package/src/adapter/entry-points/console/ui/src/features/console/logic/colors.test.ts +0 -34
  299. package/src/adapter/entry-points/console/ui/src/features/console/logic/colors.ts +0 -85
  300. package/src/adapter/entry-points/console/ui/src/features/console/logic/diff.test.ts +0 -81
  301. package/src/adapter/entry-points/console/ui/src/features/console/logic/diff.ts +0 -61
  302. package/src/adapter/entry-points/console/ui/src/features/console/logic/fileStatus.test.ts +0 -35
  303. package/src/adapter/entry-points/console/ui/src/features/console/logic/fileStatus.ts +0 -21
  304. package/src/adapter/entry-points/console/ui/src/features/console/logic/fileTree.test.ts +0 -96
  305. package/src/adapter/entry-points/console/ui/src/features/console/logic/fileTree.ts +0 -93
  306. package/src/adapter/entry-points/console/ui/src/features/console/logic/grouping.test.ts +0 -229
  307. package/src/adapter/entry-points/console/ui/src/features/console/logic/grouping.ts +0 -106
  308. package/src/adapter/entry-points/console/ui/src/features/console/logic/imageFile.test.ts +0 -18
  309. package/src/adapter/entry-points/console/ui/src/features/console/logic/imageFile.ts +0 -16
  310. package/src/adapter/entry-points/console/ui/src/features/console/logic/itemIcons.test.ts +0 -97
  311. package/src/adapter/entry-points/console/ui/src/features/console/logic/itemIcons.ts +0 -95
  312. package/src/adapter/entry-points/console/ui/src/features/console/logic/navigation.test.ts +0 -53
  313. package/src/adapter/entry-points/console/ui/src/features/console/logic/navigation.ts +0 -33
  314. package/src/adapter/entry-points/console/ui/src/features/console/logic/operations.test.ts +0 -72
  315. package/src/adapter/entry-points/console/ui/src/features/console/logic/operations.ts +0 -61
  316. package/src/adapter/entry-points/console/ui/src/features/console/logic/overlay.test.ts +0 -325
  317. package/src/adapter/entry-points/console/ui/src/features/console/logic/overlay.ts +0 -94
  318. package/src/adapter/entry-points/console/ui/src/features/console/logic/postedComments.test.ts +0 -44
  319. package/src/adapter/entry-points/console/ui/src/features/console/logic/postedComments.ts +0 -17
  320. package/src/adapter/entry-points/console/ui/src/features/console/logic/references.test.ts +0 -103
  321. package/src/adapter/entry-points/console/ui/src/features/console/logic/references.ts +0 -64
  322. package/src/adapter/entry-points/console/ui/src/features/console/logic/relativeTime.test.ts +0 -74
  323. package/src/adapter/entry-points/console/ui/src/features/console/logic/relativeTime.ts +0 -53
  324. package/src/adapter/entry-points/console/ui/src/features/console/logic/swipe.test.ts +0 -34
  325. package/src/adapter/entry-points/console/ui/src/features/console/logic/swipe.ts +0 -28
  326. package/src/adapter/entry-points/console/ui/src/features/console/logic/tabAdvance.test.ts +0 -89
  327. package/src/adapter/entry-points/console/ui/src/features/console/logic/tabAdvance.ts +0 -30
  328. package/src/adapter/entry-points/console/ui/src/features/console/logic/timerSettings.test.ts +0 -61
  329. package/src/adapter/entry-points/console/ui/src/features/console/logic/timerSettings.ts +0 -77
  330. package/src/adapter/entry-points/console/ui/src/features/console/logic/types.ts +0 -173
  331. package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsoleItemDetailContainer.test.tsx +0 -629
  332. package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsoleItemDetailContainer.tsx +0 -351
  333. package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.test.tsx +0 -1469
  334. package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.tsx +0 -736
  335. package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsoleReferenceLinkContainer.tsx +0 -25
  336. package/src/adapter/entry-points/console/ui/src/features/console/testing/fixtures.ts +0 -418
  337. package/src/adapter/entry-points/console/ui/src/index.css +0 -1592
  338. package/src/adapter/entry-points/console/ui/src/lib/utils.ts +0 -4
  339. package/src/adapter/entry-points/console/ui/src/main.tsx +0 -21
  340. package/src/adapter/entry-points/console/ui/src/vite-env.d.ts +0 -1
  341. package/src/adapter/entry-points/console/ui/tsconfig.json +0 -26
  342. package/src/adapter/entry-points/console/ui/vite.config.ts +0 -24
  343. package/src/adapter/entry-points/console/ui-dist/assets/index-BYDbK1Fj.js +0 -88
  344. package/src/adapter/entry-points/console/ui-dist/assets/index-KyQISRuQ.css +0 -2
  345. package/src/adapter/entry-points/console/ui-dist/index.html +0 -13
  346. package/src/adapter/entry-points/console/ui-dist/sw.js +0 -60
  347. package/src/adapter/entry-points/console/webServer.test.ts +0 -2945
  348. package/src/adapter/entry-points/console/webServer.ts +0 -940
  349. package/src/adapter/entry-points/function/getStoryObjectMap.ts +0 -3
  350. package/src/adapter/entry-points/handlers/FileSystemConsoleTabsRepository.test.ts +0 -218
  351. package/src/adapter/entry-points/handlers/FileSystemConsoleTabsRepository.ts +0 -113
  352. package/src/adapter/entry-points/handlers/GetStoryObjectMapUseCaseHandler.test.ts +0 -110
  353. package/src/adapter/entry-points/handlers/GetStoryObjectMapUseCaseHandler.ts +0 -116
  354. package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.test.ts +0 -859
  355. package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.ts +0 -984
  356. package/src/adapter/entry-points/handlers/InTmuxByHumanSessionTokenCountHandler.test.ts +0 -126
  357. package/src/adapter/entry-points/handlers/InTmuxByHumanSessionTokenCountHandler.ts +0 -80
  358. package/src/adapter/entry-points/handlers/LiveSessionOauthTokenSelectHandler.test.ts +0 -491
  359. package/src/adapter/entry-points/handlers/LiveSessionOauthTokenSelectHandler.ts +0 -137
  360. package/src/adapter/entry-points/handlers/OauthTokenSelectHandler.test.ts +0 -508
  361. package/src/adapter/entry-points/handlers/OauthTokenSelectHandler.ts +0 -156
  362. package/src/adapter/entry-points/handlers/consoleListsWriter.test.ts +0 -332
  363. package/src/adapter/entry-points/handlers/consoleListsWriter.ts +0 -60
  364. package/src/adapter/entry-points/handlers/dashboardRowWriter.test.ts +0 -112
  365. package/src/adapter/entry-points/handlers/dashboardRowWriter.ts +0 -51
  366. package/src/adapter/entry-points/handlers/inTmuxByHumanDataWriter.test.ts +0 -285
  367. package/src/adapter/entry-points/handlers/inTmuxByHumanDataWriter.ts +0 -109
  368. package/src/adapter/entry-points/handlers/inTmuxByHumanSessionReconciler.test.ts +0 -170
  369. package/src/adapter/entry-points/handlers/inTmuxByHumanSessionReconciler.ts +0 -40
  370. package/src/adapter/entry-points/handlers/machineStatusWriter.test.ts +0 -191
  371. package/src/adapter/entry-points/handlers/machineStatusWriter.ts +0 -110
  372. package/src/adapter/entry-points/handlers/notifySilentTmuxSessions.test.ts +0 -548
  373. package/src/adapter/entry-points/handlers/notifySilentTmuxSessions.ts +0 -192
  374. package/src/adapter/entry-points/handlers/ownerCallFileCleaner.test.ts +0 -111
  375. package/src/adapter/entry-points/handlers/ownerCallFileCleaner.ts +0 -25
  376. package/src/adapter/entry-points/handlers/ownerCallFileStore.test.ts +0 -286
  377. package/src/adapter/entry-points/handlers/ownerCallFileStore.ts +0 -145
  378. package/src/adapter/entry-points/handlers/ownerReplyMarkerDirectoryResolve.test.ts +0 -55
  379. package/src/adapter/entry-points/handlers/ownerReplyMarkerDirectoryResolve.ts +0 -36
  380. package/src/adapter/entry-points/handlers/resetDegeneratedTmuxSessions.test.ts +0 -72
  381. package/src/adapter/entry-points/handlers/resetDegeneratedTmuxSessions.ts +0 -74
  382. package/src/adapter/entry-points/handlers/rotationOrderFileWriter.test.ts +0 -181
  383. package/src/adapter/entry-points/handlers/rotationOrderFileWriter.ts +0 -18
  384. package/src/adapter/entry-points/handlers/situationFileWriter.test.ts +0 -489
  385. package/src/adapter/entry-points/handlers/situationFileWriter.ts +0 -176
  386. package/src/adapter/entry-points/handlers/staleTmuxSessionCleaner.test.ts +0 -169
  387. package/src/adapter/entry-points/handlers/staleTmuxSessionCleaner.ts +0 -32
  388. package/src/adapter/entry-points/handlers/subscriptionDisabledTokenHandler.ts +0 -41
  389. package/src/adapter/entry-points/handlers/tokenExhaustionHandover.ts +0 -81
  390. package/src/adapter/entry-points/handlers/tokenStatusWriter.test.ts +0 -266
  391. package/src/adapter/entry-points/handlers/tokenStatusWriter.ts +0 -262
  392. package/src/adapter/proxy/ClaudeMessageResponseParser.test.ts +0 -211
  393. package/src/adapter/proxy/ClaudeMessageResponseParser.ts +0 -181
  394. package/src/adapter/proxy/RateLimitCache.test.ts +0 -1077
  395. package/src/adapter/proxy/RateLimitCache.ts +0 -385
  396. package/src/adapter/proxy/TokenListLoader.test.ts +0 -183
  397. package/src/adapter/proxy/TokenListLoader.ts +0 -75
  398. package/src/adapter/proxy/ensureProxyRunning.test.ts +0 -85
  399. package/src/adapter/proxy/ensureProxyRunning.ts +0 -41
  400. package/src/adapter/proxy/proxyEntry.test.ts +0 -552
  401. package/src/adapter/proxy/proxyEntry.ts +0 -214
  402. package/src/adapter/repositories/BaseGitHubRepository.test.ts +0 -26
  403. package/src/adapter/repositories/BaseGitHubRepository.ts +0 -26
  404. package/src/adapter/repositories/BrowserGitHubProjectRepository.test.ts +0 -108
  405. package/src/adapter/repositories/BrowserGitHubProjectRepository.ts +0 -118
  406. package/src/adapter/repositories/CliGitHubGraphqlRateLimitRepository.test.ts +0 -71
  407. package/src/adapter/repositories/CliGitHubGraphqlRateLimitRepository.ts +0 -21
  408. package/src/adapter/repositories/ConfigurableSilentSessionMessageComposer.test.ts +0 -324
  409. package/src/adapter/repositories/ConfigurableSilentSessionMessageComposer.ts +0 -148
  410. package/src/adapter/repositories/FetchWebhookRepository.ts +0 -8
  411. package/src/adapter/repositories/FileHandoverStateRepository.test.ts +0 -114
  412. package/src/adapter/repositories/FileHandoverStateRepository.ts +0 -73
  413. package/src/adapter/repositories/FileSystemInteractiveLiveSessionTranscriptResolver.test.ts +0 -514
  414. package/src/adapter/repositories/FileSystemInteractiveLiveSessionTranscriptResolver.ts +0 -109
  415. package/src/adapter/repositories/FileSystemKevReportWatermarkRepository.test.ts +0 -284
  416. package/src/adapter/repositories/FileSystemKevReportWatermarkRepository.ts +0 -128
  417. package/src/adapter/repositories/FileSystemSessionAssistantTurnsRepository.test.ts +0 -138
  418. package/src/adapter/repositories/FileSystemSessionAssistantTurnsRepository.ts +0 -113
  419. package/src/adapter/repositories/FileSystemSessionDegenerationCooldownStateRepository.test.ts +0 -121
  420. package/src/adapter/repositories/FileSystemSessionDegenerationCooldownStateRepository.ts +0 -103
  421. package/src/adapter/repositories/FileSystemSessionOutputActivityRepository.test.ts +0 -360
  422. package/src/adapter/repositories/FileSystemSessionOutputActivityRepository.ts +0 -77
  423. package/src/adapter/repositories/FileSystemSessionRecordReader.test.ts +0 -59
  424. package/src/adapter/repositories/FileSystemSessionRecordReader.ts +0 -31
  425. package/src/adapter/repositories/FileSystemSilentSessionCandidateStateRepository.test.ts +0 -287
  426. package/src/adapter/repositories/FileSystemSilentSessionCandidateStateRepository.ts +0 -125
  427. package/src/adapter/repositories/FileSystemSilentSessionHubTaskStatusCacheRepository.test.ts +0 -285
  428. package/src/adapter/repositories/FileSystemSilentSessionHubTaskStatusCacheRepository.ts +0 -119
  429. package/src/adapter/repositories/FileSystemSubAgentLivenessResolver.test.ts +0 -237
  430. package/src/adapter/repositories/FileSystemSubAgentLivenessResolver.ts +0 -57
  431. package/src/adapter/repositories/FileSystemSubAgentSilentSecondsResolver.test.ts +0 -56
  432. package/src/adapter/repositories/FileSystemSubAgentSilentSecondsResolver.ts +0 -32
  433. package/src/adapter/repositories/FileSystemSubAgentTranscriptDirectoryResolver.test.ts +0 -68
  434. package/src/adapter/repositories/FileSystemSubAgentTranscriptDirectoryResolver.ts +0 -23
  435. package/src/adapter/repositories/GitHubIssueCheckpointRepository.test.ts +0 -51
  436. package/src/adapter/repositories/GitHubIssueCheckpointRepository.ts +0 -47
  437. package/src/adapter/repositories/GitHubIssueCommentRepository.test.ts +0 -278
  438. package/src/adapter/repositories/GitHubIssueCommentRepository.ts +0 -109
  439. package/src/adapter/repositories/GoogleSpreadsheetRepository.integration.test.ts +0 -265
  440. package/src/adapter/repositories/GoogleSpreadsheetRepository.test.ts +0 -431
  441. package/src/adapter/repositories/GoogleSpreadsheetRepository.ts +0 -268
  442. package/src/adapter/repositories/GraphqlProjectRepository.diskCache.test.ts +0 -272
  443. package/src/adapter/repositories/GraphqlProjectRepository.errorHandling.test.ts +0 -111
  444. package/src/adapter/repositories/GraphqlProjectRepository.fetchProjectId.test.ts +0 -254
  445. package/src/adapter/repositories/GraphqlProjectRepository.test.ts +0 -209
  446. package/src/adapter/repositories/GraphqlProjectRepository.ts +0 -707
  447. package/src/adapter/repositories/GraphqlProjectRepositoryProjectLocation.test.ts +0 -297
  448. package/src/adapter/repositories/KyHttpRepository.ts +0 -8
  449. package/src/adapter/repositories/KySlackRepository.test.ts +0 -172
  450. package/src/adapter/repositories/KySlackRepository.ts +0 -298
  451. package/src/adapter/repositories/LocalCommandIssueAttachmentRepository.test.ts +0 -193
  452. package/src/adapter/repositories/LocalCommandIssueAttachmentRepository.ts +0 -106
  453. package/src/adapter/repositories/LocalProcessLiveSessionProcessSnapshotProvider.test.ts +0 -190
  454. package/src/adapter/repositories/LocalProcessLiveSessionProcessSnapshotProvider.ts +0 -102
  455. package/src/adapter/repositories/LocalStorageCacheRepository.test.ts +0 -265
  456. package/src/adapter/repositories/LocalStorageCacheRepository.ts +0 -79
  457. package/src/adapter/repositories/LocalStorageRepository.integration.test.ts +0 -142
  458. package/src/adapter/repositories/LocalStorageRepository.test.ts +0 -173
  459. package/src/adapter/repositories/LocalStorageRepository.ts +0 -30
  460. package/src/adapter/repositories/NoUnansweredOwnerCallStatusProvider.test.ts +0 -15
  461. package/src/adapter/repositories/NoUnansweredOwnerCallStatusProvider.ts +0 -9
  462. package/src/adapter/repositories/NodeLocalCommandRunner.largeOutput.test.ts +0 -16
  463. package/src/adapter/repositories/NodeLocalCommandRunner.test.ts +0 -118
  464. package/src/adapter/repositories/NodeLocalCommandRunner.ts +0 -66
  465. package/src/adapter/repositories/NodeProcessSignalRepository.test.ts +0 -82
  466. package/src/adapter/repositories/NodeProcessSignalRepository.ts +0 -47
  467. package/src/adapter/repositories/NodeSubAgentProcessLister.test.ts +0 -44
  468. package/src/adapter/repositories/NodeSubAgentProcessLister.ts +0 -40
  469. package/src/adapter/repositories/NodeTmuxSessionRepository.test.ts +0 -546
  470. package/src/adapter/repositories/NodeTmuxSessionRepository.ts +0 -245
  471. package/src/adapter/repositories/OauthAPIClaudeRepository.ts +0 -279
  472. package/src/adapter/repositories/OauthAPIProxyClaudeRepository.test.ts +0 -166
  473. package/src/adapter/repositories/OauthAPIProxyClaudeRepository.ts +0 -32
  474. package/src/adapter/repositories/OauthProxyClaudeRepository.test.ts +0 -428
  475. package/src/adapter/repositories/OauthProxyClaudeRepository.ts +0 -124
  476. package/src/adapter/repositories/ProcClaudeHandoverSessionRepository.test.ts +0 -242
  477. package/src/adapter/repositories/ProcClaudeHandoverSessionRepository.ts +0 -261
  478. package/src/adapter/repositories/ProcClaudeInteractiveSessionRepository.test.ts +0 -184
  479. package/src/adapter/repositories/ProcClaudeInteractiveSessionRepository.ts +0 -138
  480. package/src/adapter/repositories/ProcClaudeLiveSessionRepository.test.ts +0 -208
  481. package/src/adapter/repositories/ProcClaudeLiveSessionRepository.ts +0 -130
  482. package/src/adapter/repositories/ProcFsProcessEnvironReader.test.ts +0 -60
  483. package/src/adapter/repositories/ProcFsProcessEnvironReader.ts +0 -38
  484. package/src/adapter/repositories/ProcHostMetricsRepository.test.ts +0 -207
  485. package/src/adapter/repositories/ProcHostMetricsRepository.ts +0 -181
  486. package/src/adapter/repositories/ProcTakeOwnershipSpawnRepository.test.ts +0 -231
  487. package/src/adapter/repositories/ProcTakeOwnershipSpawnRepository.ts +0 -134
  488. package/src/adapter/repositories/ProcessListSessionSubAgentActivityRepository.test.ts +0 -121
  489. package/src/adapter/repositories/ProcessListSessionSubAgentActivityRepository.ts +0 -58
  490. package/src/adapter/repositories/ProjectIssuesCacheRepository.test.ts +0 -218
  491. package/src/adapter/repositories/ProjectIssuesCacheRepository.ts +0 -141
  492. package/src/adapter/repositories/ProxyClaudeTokenUsageRepository.test.ts +0 -1195
  493. package/src/adapter/repositories/ProxyClaudeTokenUsageRepository.ts +0 -185
  494. package/src/adapter/repositories/ProxyRateLimitCacheRepository.test.ts +0 -116
  495. package/src/adapter/repositories/ProxyRateLimitCacheRepository.ts +0 -67
  496. package/src/adapter/repositories/RateLimitSnapshotRepository.ts +0 -50
  497. package/src/adapter/repositories/RealSleeper.test.ts +0 -19
  498. package/src/adapter/repositories/RealSleeper.ts +0 -12
  499. package/src/adapter/repositories/RestProjectRepository.test.ts +0 -277
  500. package/src/adapter/repositories/RestProjectRepository.ts +0 -129
  501. package/src/adapter/repositories/SqliteClaudeMessageResponseRepository.test.ts +0 -313
  502. package/src/adapter/repositories/SqliteClaudeMessageResponseRepository.ts +0 -164
  503. package/src/adapter/repositories/SystemDateRepository.test.ts +0 -60
  504. package/src/adapter/repositories/SystemDateRepository.ts +0 -34
  505. package/src/adapter/repositories/TmuxSilentSessionNotificationRepository.test.ts +0 -253
  506. package/src/adapter/repositories/TmuxSilentSessionNotificationRepository.ts +0 -158
  507. package/src/adapter/repositories/TranscriptOwnerCallStatusProvider.queuedReply.test.ts +0 -171
  508. package/src/adapter/repositories/TranscriptOwnerCallStatusProvider.singleScan.test.ts +0 -86
  509. package/src/adapter/repositories/TranscriptOwnerCallStatusProvider.test.ts +0 -996
  510. package/src/adapter/repositories/TranscriptOwnerCallStatusProvider.ts +0 -396
  511. package/src/adapter/repositories/TranscriptRefusalTailStatusProvider.test.ts +0 -208
  512. package/src/adapter/repositories/TranscriptRefusalTailStatusProvider.ts +0 -80
  513. package/src/adapter/repositories/TranscriptSessionSubAgentActivityRepository.test.ts +0 -1674
  514. package/src/adapter/repositories/TranscriptSessionSubAgentActivityRepository.ts +0 -459
  515. package/src/adapter/repositories/clSessionScopeUnitName.test.ts +0 -76
  516. package/src/adapter/repositories/clSessionScopeUnitName.ts +0 -5
  517. package/src/adapter/repositories/clSessionScopeUnitNameFromCgroupContent.test.ts +0 -34
  518. package/src/adapter/repositories/clSessionScopeUnitNameFromCgroupContent.ts +0 -8
  519. package/src/adapter/repositories/githubGraphqlClient.test.ts +0 -479
  520. package/src/adapter/repositories/githubGraphqlClient.ts +0 -243
  521. package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.test.ts +0 -5182
  522. package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.ts +0 -3303
  523. package/src/adapter/repositories/issue/ApiV3IssueRepository.test.ts +0 -46
  524. package/src/adapter/repositories/issue/ApiV3IssueRepository.ts +0 -88
  525. package/src/adapter/repositories/issue/GraphqlProjectItemRepository.test.ts +0 -1776
  526. package/src/adapter/repositories/issue/GraphqlProjectItemRepository.ts +0 -1552
  527. package/src/adapter/repositories/issue/RestIssueRepository.test.ts +0 -393
  528. package/src/adapter/repositories/issue/RestIssueRepository.ts +0 -252
  529. package/src/adapter/repositories/issue/gitHubRawUrl.test.ts +0 -29
  530. package/src/adapter/repositories/issue/gitHubRawUrl.ts +0 -30
  531. package/src/adapter/repositories/issue/githubRateLimitRetry.test.ts +0 -167
  532. package/src/adapter/repositories/issue/githubRateLimitRetry.ts +0 -109
  533. package/src/adapter/repositories/localStorageCacheDirectory.test.ts +0 -81
  534. package/src/adapter/repositories/localStorageCacheDirectory.ts +0 -13
  535. package/src/adapter/repositories/projectFieldDefinition.test.ts +0 -191
  536. package/src/adapter/repositories/projectFieldDefinition.ts +0 -144
  537. package/src/adapter/repositories/utils.test.ts +0 -55
  538. package/src/adapter/repositories/utils.ts +0 -45
  539. package/src/domain/entities/ClaudeHandoverSession.ts +0 -12
  540. package/src/domain/entities/ClaudeMessageResponse.ts +0 -31
  541. package/src/domain/entities/ClaudeTokenUsage.ts +0 -17
  542. package/src/domain/entities/ClaudeWindowUsage.ts +0 -5
  543. package/src/domain/entities/Comment.ts +0 -5
  544. package/src/domain/entities/InteractiveLiveSession.ts +0 -6
  545. package/src/domain/entities/Issue.ts +0 -31
  546. package/src/domain/entities/KevReportWatermark.ts +0 -4
  547. package/src/domain/entities/LiveSessionActivitySnapshot.ts +0 -14
  548. package/src/domain/entities/LiveSessionOutputActivity.ts +0 -4
  549. package/src/domain/entities/LiveSessionProcessSnapshot.ts +0 -18
  550. package/src/domain/entities/LiveTmuxSession.ts +0 -4
  551. package/src/domain/entities/Member.ts +0 -3
  552. package/src/domain/entities/OutputDegeneration.ts +0 -10
  553. package/src/domain/entities/Project.ts +0 -88
  554. package/src/domain/entities/ProjectField.ts +0 -3
  555. package/src/domain/entities/ProjectFieldName.ts +0 -2
  556. package/src/domain/entities/ProjectFieldSingleSelect.ts +0 -8
  557. package/src/domain/entities/ProjectFieldSingleSelectOption.ts +0 -8
  558. package/src/domain/entities/RequiredProjectField.ts +0 -68
  559. package/src/domain/entities/SearchedIssue.ts +0 -11
  560. package/src/domain/entities/StoryObjectMap.ts +0 -12
  561. package/src/domain/entities/TokenExhaustionHandoverState.ts +0 -8
  562. package/src/domain/entities/WorkflowStatus.ts +0 -66
  563. package/src/domain/usecases/ActionAnnouncementUseCase.test.ts +0 -91
  564. package/src/domain/usecases/ActionAnnouncementUseCase.ts +0 -78
  565. package/src/domain/usecases/AgentDesignationLabelAdoptUseCase.test.ts +0 -222
  566. package/src/domain/usecases/AgentDesignationLabelAdoptUseCase.ts +0 -76
  567. package/src/domain/usecases/AnalyzeProblemByIssueUseCase.test.ts +0 -118
  568. package/src/domain/usecases/AnalyzeProblemByIssueUseCase.ts +0 -112
  569. package/src/domain/usecases/AnalyzeStoriesUseCase.test.ts +0 -82
  570. package/src/domain/usecases/AnalyzeStoriesUseCase.ts +0 -296
  571. package/src/domain/usecases/AssignNoAssigneeIssueToManagerUseCase.test.ts +0 -578
  572. package/src/domain/usecases/AssignNoAssigneeIssueToManagerUseCase.ts +0 -137
  573. package/src/domain/usecases/CONTRIBUTING_FOR_TEST.md +0 -259
  574. package/src/domain/usecases/ChangeStatusByStoryColorUseCase.test.ts +0 -437
  575. package/src/domain/usecases/ChangeStatusByStoryColorUseCase.ts +0 -80
  576. package/src/domain/usecases/ChangeTargetPullRequestApprover.test.ts +0 -248
  577. package/src/domain/usecases/ChangeTargetPullRequestApprover.ts +0 -69
  578. package/src/domain/usecases/CheckIssueReviewReadinessUseCase.test.ts +0 -411
  579. package/src/domain/usecases/CheckIssueReviewReadinessUseCase.ts +0 -142
  580. package/src/domain/usecases/ClearDependedIssueURLUseCase.test.ts +0 -1120
  581. package/src/domain/usecases/ClearDependedIssueURLUseCase.ts +0 -161
  582. package/src/domain/usecases/ClearPastNextActionDateHourUseCase.test.ts +0 -345
  583. package/src/domain/usecases/ClearPastNextActionDateHourUseCase.ts +0 -81
  584. package/src/domain/usecases/CliErrorReportUseCase.test.ts +0 -162
  585. package/src/domain/usecases/CliErrorReportUseCase.ts +0 -72
  586. package/src/domain/usecases/ConflictedIssueRevertUseCase.test.ts +0 -886
  587. package/src/domain/usecases/ConflictedIssueRevertUseCase.ts +0 -146
  588. package/src/domain/usecases/CreateEstimationIssueUseCase.test.ts +0 -227
  589. package/src/domain/usecases/CreateEstimationIssueUseCase.ts +0 -205
  590. package/src/domain/usecases/CreateNewStoryByLabelUseCase.test.ts +0 -1084
  591. package/src/domain/usecases/CreateNewStoryByLabelUseCase.ts +0 -146
  592. package/src/domain/usecases/DailySecurityScanUseCase.test.ts +0 -1709
  593. package/src/domain/usecases/DailySecurityScanUseCase.ts +0 -476
  594. package/src/domain/usecases/DefaultSilentSessionMessageComposer.test.ts +0 -533
  595. package/src/domain/usecases/DefaultSilentSessionMessageComposer.ts +0 -122
  596. package/src/domain/usecases/GetStoryObjectMapUseCase.test.ts +0 -330
  597. package/src/domain/usecases/GetStoryObjectMapUseCase.ts +0 -79
  598. package/src/domain/usecases/HandleScheduledEventUseCase.test.ts +0 -1913
  599. package/src/domain/usecases/HandleScheduledEventUseCase.ts +0 -846
  600. package/src/domain/usecases/InTmuxByHumanSessionTokenCountUseCase.test.ts +0 -175
  601. package/src/domain/usecases/InTmuxByHumanSessionTokenCountUseCase.ts +0 -63
  602. package/src/domain/usecases/IssueNoStatusUpdateUseCase.test.ts +0 -165
  603. package/src/domain/usecases/IssueNoStatusUpdateUseCase.ts +0 -44
  604. package/src/domain/usecases/IssueRejectionEvaluator.test.ts +0 -1137
  605. package/src/domain/usecases/IssueRejectionEvaluator.ts +0 -340
  606. package/src/domain/usecases/LiveSessionOauthTokenSelectUseCase.test.ts +0 -679
  607. package/src/domain/usecases/LiveSessionOauthTokenSelectUseCase.ts +0 -178
  608. package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.test.ts +0 -5441
  609. package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.ts +0 -891
  610. package/src/domain/usecases/NotifySilentLiveSessionsUseCase.test.ts +0 -2069
  611. package/src/domain/usecases/NotifySilentLiveSessionsUseCase.ts +0 -489
  612. package/src/domain/usecases/OauthTokenSelectUseCase.test.ts +0 -651
  613. package/src/domain/usecases/OauthTokenSelectUseCase.ts +0 -274
  614. package/src/domain/usecases/OutputDegenerationDetector.test.ts +0 -167
  615. package/src/domain/usecases/OutputDegenerationDetector.ts +0 -169
  616. package/src/domain/usecases/ProjectRequiredFieldCreateUseCase.test.ts +0 -711
  617. package/src/domain/usecases/ProjectRequiredFieldCreateUseCase.ts +0 -146
  618. package/src/domain/usecases/QualityCheckAdvanceUseCase.test.ts +0 -421
  619. package/src/domain/usecases/QualityCheckAdvanceUseCase.ts +0 -76
  620. package/src/domain/usecases/ReopenedDoneIssueRevertUseCase.test.ts +0 -188
  621. package/src/domain/usecases/ReopenedDoneIssueRevertUseCase.ts +0 -57
  622. package/src/domain/usecases/ResolveInteractiveLiveSessionsUseCase.test.ts +0 -303
  623. package/src/domain/usecases/ResolveInteractiveLiveSessionsUseCase.ts +0 -155
  624. package/src/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.test.ts +0 -1838
  625. package/src/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.ts +0 -221
  626. package/src/domain/usecases/RevertOrphanedPreparationUseCase.test.ts +0 -2392
  627. package/src/domain/usecases/RevertOrphanedPreparationUseCase.ts +0 -499
  628. package/src/domain/usecases/SessionOutputDegenerationRecoveryUseCase.test.ts +0 -224
  629. package/src/domain/usecases/SessionOutputDegenerationRecoveryUseCase.ts +0 -142
  630. package/src/domain/usecases/SetDependedIssueUrlForOpenTaskPRsUseCase.test.ts +0 -360
  631. package/src/domain/usecases/SetDependedIssueUrlForOpenTaskPRsUseCase.ts +0 -70
  632. package/src/domain/usecases/SetNoStoryIssueToStoryUseCase.test.ts +0 -400
  633. package/src/domain/usecases/SetNoStoryIssueToStoryUseCase.ts +0 -48
  634. package/src/domain/usecases/SetWorkflowManagementIssueToStoryUseCase.test.ts +0 -696
  635. package/src/domain/usecases/SetWorkflowManagementIssueToStoryUseCase.ts +0 -201
  636. package/src/domain/usecases/SetupTowerDefenceProjectUseCase.test.ts +0 -1363
  637. package/src/domain/usecases/SetupTowerDefenceProjectUseCase.ts +0 -215
  638. package/src/domain/usecases/StaleTaskPullRequestCloseUseCase.test.ts +0 -170
  639. package/src/domain/usecases/StaleTaskPullRequestCloseUseCase.ts +0 -38
  640. package/src/domain/usecases/StaleTmuxSessionKillUseCase.test.ts +0 -283
  641. package/src/domain/usecases/StaleTmuxSessionKillUseCase.ts +0 -98
  642. package/src/domain/usecases/StartPreparationUseCase.test.ts +0 -7822
  643. package/src/domain/usecases/StartPreparationUseCase.ts +0 -876
  644. package/src/domain/usecases/SubscriptionDisabledIssueUseCase.test.ts +0 -200
  645. package/src/domain/usecases/SubscriptionDisabledIssueUseCase.ts +0 -74
  646. package/src/domain/usecases/TokenExhaustionHandoverUseCase.test.ts +0 -838
  647. package/src/domain/usecases/TokenExhaustionHandoverUseCase.ts +0 -439
  648. package/src/domain/usecases/UpdateIssueStatusByLabelUseCase.test.ts +0 -306
  649. package/src/domain/usecases/UpdateIssueStatusByLabelUseCase.ts +0 -64
  650. package/src/domain/usecases/UpdateRateLimitCacheUseCase.test.ts +0 -178
  651. package/src/domain/usecases/UpdateRateLimitCacheUseCase.ts +0 -22
  652. package/src/domain/usecases/adapter-interfaces/ClaudeHandoverSessionRepository.ts +0 -5
  653. package/src/domain/usecases/adapter-interfaces/ClaudeInteractiveSessionRepository.ts +0 -9
  654. package/src/domain/usecases/adapter-interfaces/ClaudeLiveSessionRepository.ts +0 -8
  655. package/src/domain/usecases/adapter-interfaces/ClaudeMessageResponseRepository.ts +0 -5
  656. package/src/domain/usecases/adapter-interfaces/ClaudeRepository.ts +0 -6
  657. package/src/domain/usecases/adapter-interfaces/ClaudeTokenUsageRepository.ts +0 -8
  658. package/src/domain/usecases/adapter-interfaces/ConsoleTabsRepository.ts +0 -12
  659. package/src/domain/usecases/adapter-interfaces/CopilotRepository.ts +0 -3
  660. package/src/domain/usecases/adapter-interfaces/DateRepository.ts +0 -7
  661. package/src/domain/usecases/adapter-interfaces/GitHubGraphqlRateLimitRepository.ts +0 -3
  662. package/src/domain/usecases/adapter-interfaces/HttpRepository.ts +0 -3
  663. package/src/domain/usecases/adapter-interfaces/InteractiveLiveSessionTranscriptResolver.ts +0 -7
  664. package/src/domain/usecases/adapter-interfaces/IssueAttachmentRepository.ts +0 -9
  665. package/src/domain/usecases/adapter-interfaces/IssueCheckpointRepository.ts +0 -3
  666. package/src/domain/usecases/adapter-interfaces/IssueCommentRepository.ts +0 -7
  667. package/src/domain/usecases/adapter-interfaces/IssueRepository.ts +0 -229
  668. package/src/domain/usecases/adapter-interfaces/KevReportWatermarkRepository.ts +0 -11
  669. package/src/domain/usecases/adapter-interfaces/LiveSessionProcessSnapshotProvider.ts +0 -5
  670. package/src/domain/usecases/adapter-interfaces/LocalCommandRunner.ts +0 -16
  671. package/src/domain/usecases/adapter-interfaces/OwnerCallStatusProvider.ts +0 -5
  672. package/src/domain/usecases/adapter-interfaces/ProcessEnvironReader.ts +0 -3
  673. package/src/domain/usecases/adapter-interfaces/ProcessSignalRepository.ts +0 -5
  674. package/src/domain/usecases/adapter-interfaces/ProjectRepository.ts +0 -29
  675. package/src/domain/usecases/adapter-interfaces/RateLimitCacheRepository.ts +0 -10
  676. package/src/domain/usecases/adapter-interfaces/RefusalTailStatusProvider.ts +0 -5
  677. package/src/domain/usecases/adapter-interfaces/SessionAssistantTurnsRepository.ts +0 -6
  678. package/src/domain/usecases/adapter-interfaces/SessionDegenerationCooldownStateRepository.ts +0 -4
  679. package/src/domain/usecases/adapter-interfaces/SessionOutputActivityRepository.ts +0 -7
  680. package/src/domain/usecases/adapter-interfaces/SessionRecordReader.ts +0 -3
  681. package/src/domain/usecases/adapter-interfaces/SessionSubAgentActivityRepository.ts +0 -8
  682. package/src/domain/usecases/adapter-interfaces/SilentSessionCandidateStateRepository.ts +0 -10
  683. package/src/domain/usecases/adapter-interfaces/SilentSessionHubTaskStatusCacheRepository.ts +0 -20
  684. package/src/domain/usecases/adapter-interfaces/SilentSessionMessageComposer.ts +0 -18
  685. package/src/domain/usecases/adapter-interfaces/SilentSessionNotificationRepository.ts +0 -6
  686. package/src/domain/usecases/adapter-interfaces/SlackRepository.ts +0 -20
  687. package/src/domain/usecases/adapter-interfaces/Sleeper.ts +0 -3
  688. package/src/domain/usecases/adapter-interfaces/SpreadsheetRepository.ts +0 -18
  689. package/src/domain/usecases/adapter-interfaces/StatusDefaultRepository.ts +0 -5
  690. package/src/domain/usecases/adapter-interfaces/SubAgentLivenessResolver.ts +0 -6
  691. package/src/domain/usecases/adapter-interfaces/SubAgentProcessLister.ts +0 -8
  692. package/src/domain/usecases/adapter-interfaces/SubAgentSilentSecondsResolver.ts +0 -3
  693. package/src/domain/usecases/adapter-interfaces/SubAgentTranscriptDirectoryResolver.ts +0 -6
  694. package/src/domain/usecases/adapter-interfaces/TakeOwnershipSpawnRepository.ts +0 -9
  695. package/src/domain/usecases/adapter-interfaces/TmuxSessionRepository.ts +0 -20
  696. package/src/domain/usecases/adapter-interfaces/TokenExhaustionHandoverStateRepository.ts +0 -6
  697. package/src/domain/usecases/adapter-interfaces/TokenRateLimitSnapshotRepository.ts +0 -22
  698. package/src/domain/usecases/adapter-interfaces/WebhookRepository.ts +0 -3
  699. package/src/domain/usecases/agentReportPrefix.ts +0 -1
  700. package/src/domain/usecases/autoStatusCheckComments.ts +0 -1
  701. package/src/domain/usecases/console/.gitkeep +0 -0
  702. package/src/domain/usecases/console/GenerateConsoleListsUseCase.test.ts +0 -887
  703. package/src/domain/usecases/console/GenerateConsoleListsUseCase.ts +0 -368
  704. package/src/domain/usecases/dashboard/ComposeDashboardUseCase.test.ts +0 -654
  705. package/src/domain/usecases/dashboard/ComposeDashboardUseCase.ts +0 -318
  706. package/src/domain/usecases/dashboard/DashboardProjectCode.test.ts +0 -34
  707. package/src/domain/usecases/dashboard/DashboardProjectCode.ts +0 -27
  708. package/src/domain/usecases/dashboard/GenerateDashboardRowUseCase.test.ts +0 -160
  709. package/src/domain/usecases/dashboard/GenerateDashboardRowUseCase.ts +0 -69
  710. package/src/domain/usecases/dashboard/GenerateTokenStatusUseCase.test.ts +0 -362
  711. package/src/domain/usecases/dashboard/GenerateTokenStatusUseCase.ts +0 -234
  712. package/src/domain/usecases/ensureAgentOptionAndGetId.ts +0 -47
  713. package/src/domain/usecases/extractFencedJsonBlocks.test.ts +0 -40
  714. package/src/domain/usecases/extractFencedJsonBlocks.ts +0 -21
  715. package/src/domain/usecases/extractNextStepAgent.test.ts +0 -58
  716. package/src/domain/usecases/extractNextStepAgent.ts +0 -18
  717. package/src/domain/usecases/extractNextStepAgentFromComments.ts +0 -13
  718. package/src/domain/usecases/findLastAgentReport.test.ts +0 -121
  719. package/src/domain/usecases/findLastAgentReport.ts +0 -14
  720. package/src/domain/usecases/intmux/GenerateInTmuxByHumanDataUseCase.test.ts +0 -339
  721. package/src/domain/usecases/intmux/GenerateInTmuxByHumanDataUseCase.ts +0 -188
  722. package/src/domain/usecases/intmux/InTmuxByHumanSessionReconcileUseCase.test.ts +0 -389
  723. package/src/domain/usecases/intmux/InTmuxByHumanSessionReconcileUseCase.ts +0 -103
  724. package/src/domain/usecases/intmux/OwnerCallFile.test.ts +0 -184
  725. package/src/domain/usecases/intmux/OwnerCallFile.ts +0 -80
  726. package/src/domain/usecases/isAgentReportBody.test.ts +0 -108
  727. package/src/domain/usecases/isAgentReportBody.ts +0 -52
  728. package/src/domain/usecases/isAuthorAuthorizedForAutoStatusCheck.ts +0 -12
  729. package/src/domain/usecases/isHumanComment.test.ts +0 -77
  730. package/src/domain/usecases/isHumanComment.ts +0 -23
  731. package/src/domain/usecases/isRecord.ts +0 -2
  732. package/src/domain/usecases/issueReactivationTriggerIsPending.test.ts +0 -541
  733. package/src/domain/usecases/issueReactivationTriggerIsPending.ts +0 -25
  734. package/src/domain/usecases/nextStepAgentDispatchRepeatedMessage.ts +0 -2
  735. package/src/domain/usecases/normalizeReportBody.test.ts +0 -24
  736. package/src/domain/usecases/normalizeReportBody.ts +0 -2
  737. package/src/domain/usecases/resolveAllowedIssueAuthors.test.ts +0 -51
  738. package/src/domain/usecases/resolveAllowedIssueAuthors.ts +0 -6
  739. package/src/domain/usecases/resolveLabelsAsLlmAgentName.test.ts +0 -42
  740. package/src/domain/usecases/resolveLabelsAsLlmAgentName.ts +0 -6
  741. package/src/domain/usecases/resolveLabelsNotRequiringPullRequest.test.ts +0 -33
  742. package/src/domain/usecases/resolveLabelsNotRequiringPullRequest.ts +0 -7
  743. package/src/domain/usecases/resolveNextStepAgentDispatchRepetition.test.ts +0 -337
  744. package/src/domain/usecases/resolveNextStepAgentDispatchRepetition.ts +0 -133
  745. package/src/domain/usecases/silentSessionReminderSentinel.ts +0 -9
  746. package/src/domain/usecases/triagerAgentName.ts +0 -4
  747. package/src/domain/usecases/utils.test.ts +0 -35
  748. package/src/domain/usecases/utils.ts +0 -6
  749. package/src/index.test.ts +0 -8
  750. package/src/index.ts +0 -10
  751. package/src/releaseConfig.test.ts +0 -40
  752. package/tsconfig.build.json +0 -18
  753. package/tsconfig.json +0 -25
  754. package/types/adapter/entry-points/handlers/resolveUnansweredOwnerCalls.d.ts +0 -13
  755. package/types/adapter/entry-points/handlers/resolveUnansweredOwnerCalls.d.ts.map +0 -1
  756. package/types/adapter/repositories/FileSystemSilentSessionNotifiedStateRepository.d.ts +0 -16
  757. package/types/adapter/repositories/FileSystemSilentSessionNotifiedStateRepository.d.ts.map +0 -1
  758. package/types/adapter/repositories/issue/issueTimelineUtils.d.ts +0 -12
  759. package/types/adapter/repositories/issue/issueTimelineUtils.d.ts.map +0 -1
  760. package/types/domain/entities/UnansweredOwnerCall.d.ts +0 -5
  761. package/types/domain/entities/UnansweredOwnerCall.d.ts.map +0 -1
  762. package/types/domain/entities/WorkingTime.d.ts +0 -8
  763. package/types/domain/entities/WorkingTime.d.ts.map +0 -1
  764. package/types/domain/usecases/ChangeStatusLongInReviewIssueUseCase.d.ts +0 -17
  765. package/types/domain/usecases/ChangeStatusLongInReviewIssueUseCase.d.ts.map +0 -1
  766. package/types/domain/usecases/ConvertCheckboxToIssueInStoryIssueUseCase.d.ts +0 -23
  767. package/types/domain/usecases/ConvertCheckboxToIssueInStoryIssueUseCase.d.ts.map +0 -1
  768. package/types/domain/usecases/GenerateWorkingTimeReportUseCase.d.ts +0 -50
  769. package/types/domain/usecases/GenerateWorkingTimeReportUseCase.d.ts.map +0 -1
  770. package/types/domain/usecases/TriagerApprovalDispatchUseCase.d.ts +0 -15
  771. package/types/domain/usecases/TriagerApprovalDispatchUseCase.d.ts.map +0 -1
  772. package/types/domain/usecases/adapter-interfaces/SilentSessionNotifiedStateRepository.d.ts +0 -11
  773. package/types/domain/usecases/adapter-interfaces/SilentSessionNotifiedStateRepository.d.ts.map +0 -1
  774. package/types/domain/usecases/awaitingOwnerApprovalMessage.d.ts +0 -3
  775. package/types/domain/usecases/awaitingOwnerApprovalMessage.d.ts.map +0 -1
  776. package/types/domain/usecases/findLastAgentDeclaringReport.d.ts +0 -5
  777. package/types/domain/usecases/findLastAgentDeclaringReport.d.ts.map +0 -1
  778. package/types/domain/usecases/isPullRequestDeclaredUnnecessary.d.ts +0 -5
  779. package/types/domain/usecases/isPullRequestDeclaredUnnecessary.d.ts.map +0 -1
  780. package/types/domain/usecases/isWaitingForOwnerApproval.d.ts +0 -2
  781. package/types/domain/usecases/isWaitingForOwnerApproval.d.ts.map +0 -1
  782. package/types/domain/usecases/returnedToAwaitingWorkspaceMessage.d.ts +0 -3
  783. 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
- });