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