@assistkick/create 1.6.0 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (214) hide show
  1. package/package.json +2 -2
  2. package/templates/assistkick-product-system/.env.example +1 -0
  3. package/templates/assistkick-product-system/local.db +0 -0
  4. package/templates/assistkick-product-system/package.json +4 -2
  5. package/templates/assistkick-product-system/packages/backend/package.json +2 -0
  6. package/templates/assistkick-product-system/packages/backend/src/routes/agents.ts +165 -0
  7. package/templates/assistkick-product-system/packages/backend/src/routes/files.test.ts +358 -0
  8. package/templates/assistkick-product-system/packages/backend/src/routes/files.ts +356 -0
  9. package/templates/assistkick-product-system/packages/backend/src/routes/git.ts +96 -1
  10. package/templates/assistkick-product-system/packages/backend/src/routes/graph.ts +1 -0
  11. package/templates/assistkick-product-system/packages/backend/src/routes/kanban.ts +43 -4
  12. package/templates/assistkick-product-system/packages/backend/src/routes/pipeline.ts +200 -84
  13. package/templates/assistkick-product-system/packages/backend/src/routes/projects.ts +6 -3
  14. package/templates/assistkick-product-system/packages/backend/src/routes/terminal.ts +53 -17
  15. package/templates/assistkick-product-system/packages/backend/src/routes/video.ts +218 -0
  16. package/templates/assistkick-product-system/packages/backend/src/routes/workflow_groups.ts +119 -0
  17. package/templates/assistkick-product-system/packages/backend/src/routes/workflows.ts +154 -0
  18. package/templates/assistkick-product-system/packages/backend/src/server.ts +81 -9
  19. package/templates/assistkick-product-system/packages/backend/src/services/agent_service.test.ts +489 -0
  20. package/templates/assistkick-product-system/packages/backend/src/services/agent_service.ts +416 -0
  21. package/templates/assistkick-product-system/packages/backend/src/services/bundle_service.test.ts +189 -0
  22. package/templates/assistkick-product-system/packages/backend/src/services/bundle_service.ts +182 -0
  23. package/templates/assistkick-product-system/packages/backend/src/services/init.ts +28 -78
  24. package/templates/assistkick-product-system/packages/backend/src/services/project_service.test.ts +16 -0
  25. package/templates/assistkick-product-system/packages/backend/src/services/project_service.ts +73 -2
  26. package/templates/assistkick-product-system/packages/backend/src/services/project_workspace_service.test.ts +4 -4
  27. package/templates/assistkick-product-system/packages/backend/src/services/project_workspace_service.ts +87 -11
  28. package/templates/assistkick-product-system/packages/backend/src/services/pty_session_manager.test.ts +210 -69
  29. package/templates/assistkick-product-system/packages/backend/src/services/pty_session_manager.ts +210 -215
  30. package/templates/assistkick-product-system/packages/backend/src/services/ssh_key_service.test.ts +162 -0
  31. package/templates/assistkick-product-system/packages/backend/src/services/ssh_key_service.ts +148 -0
  32. package/templates/assistkick-product-system/packages/backend/src/services/terminal_ws_handler.ts +11 -5
  33. package/templates/assistkick-product-system/packages/backend/src/services/tts_service.test.ts +64 -0
  34. package/templates/assistkick-product-system/packages/backend/src/services/tts_service.ts +134 -0
  35. package/templates/assistkick-product-system/packages/backend/src/services/video_render_service.test.ts +256 -0
  36. package/templates/assistkick-product-system/packages/backend/src/services/video_render_service.ts +258 -0
  37. package/templates/assistkick-product-system/packages/backend/src/services/workflow_group_service.ts +106 -0
  38. package/templates/assistkick-product-system/packages/backend/src/services/workflow_service.test.ts +275 -0
  39. package/templates/assistkick-product-system/packages/backend/src/services/workflow_service.ts +222 -0
  40. package/templates/assistkick-product-system/packages/frontend/index.html +3 -0
  41. package/templates/assistkick-product-system/packages/frontend/package-lock.json +800 -11
  42. package/templates/assistkick-product-system/packages/frontend/package.json +11 -1
  43. package/templates/assistkick-product-system/packages/frontend/src/App.tsx +24 -7
  44. package/templates/assistkick-product-system/packages/frontend/src/api/client.ts +456 -16
  45. package/templates/assistkick-product-system/packages/frontend/src/api/client_files.test.ts +172 -0
  46. package/templates/assistkick-product-system/packages/frontend/src/api/client_video.test.ts +238 -0
  47. package/templates/assistkick-product-system/packages/frontend/src/components/AgentsView.tsx +307 -0
  48. package/templates/assistkick-product-system/packages/frontend/src/components/CoherenceView.tsx +82 -66
  49. package/templates/assistkick-product-system/packages/frontend/src/components/CompositionPlaceholder.tsx +97 -0
  50. package/templates/assistkick-product-system/packages/frontend/src/components/DesignSystemView.tsx +383 -0
  51. package/templates/assistkick-product-system/packages/frontend/src/components/EditorTabBar.tsx +57 -0
  52. package/templates/assistkick-product-system/packages/frontend/src/components/FileTree.tsx +313 -0
  53. package/templates/assistkick-product-system/packages/frontend/src/components/FileTreeContextMenu.tsx +61 -0
  54. package/templates/assistkick-product-system/packages/frontend/src/components/FileTreeInlineInput.tsx +73 -0
  55. package/templates/assistkick-product-system/packages/frontend/src/components/FilesView.tsx +404 -0
  56. package/templates/assistkick-product-system/packages/frontend/src/components/GitRepoModal.tsx +193 -64
  57. package/templates/assistkick-product-system/packages/frontend/src/components/GraphLegend.tsx +71 -73
  58. package/templates/assistkick-product-system/packages/frontend/src/components/GraphSettings.tsx +8 -8
  59. package/templates/assistkick-product-system/packages/frontend/src/components/GraphView.tsx +1 -1
  60. package/templates/assistkick-product-system/packages/frontend/src/components/InviteUserDialog.tsx +15 -11
  61. package/templates/assistkick-product-system/packages/frontend/src/components/KanbanView.tsx +226 -291
  62. package/templates/assistkick-product-system/packages/frontend/src/components/LoginPage.tsx +14 -14
  63. package/templates/assistkick-product-system/packages/frontend/src/components/ProjectSelector.tsx +54 -33
  64. package/templates/assistkick-product-system/packages/frontend/src/components/QaIssueSheet.tsx +40 -66
  65. package/templates/assistkick-product-system/packages/frontend/src/components/SidePanel.tsx +55 -115
  66. package/templates/assistkick-product-system/packages/frontend/src/components/TerminalView.tsx +121 -52
  67. package/templates/assistkick-product-system/packages/frontend/src/components/Toolbar.tsx +155 -77
  68. package/templates/assistkick-product-system/packages/frontend/src/components/UsersView.tsx +52 -52
  69. package/templates/assistkick-product-system/packages/frontend/src/components/VideoGallery.tsx +313 -0
  70. package/templates/assistkick-product-system/packages/frontend/src/components/VideographyView.tsx +250 -0
  71. package/templates/assistkick-product-system/packages/frontend/src/components/WorkflowsView.tsx +474 -0
  72. package/templates/assistkick-product-system/packages/frontend/src/components/ds/AccentBorderList.tsx +53 -0
  73. package/templates/assistkick-product-system/packages/frontend/src/components/ds/Button.tsx +87 -0
  74. package/templates/assistkick-product-system/packages/frontend/src/components/ds/ButtonGroup.tsx +29 -0
  75. package/templates/assistkick-product-system/packages/frontend/src/components/ds/ButtonShowcase.tsx +221 -0
  76. package/templates/assistkick-product-system/packages/frontend/src/components/ds/CardGlass.tsx +141 -0
  77. package/templates/assistkick-product-system/packages/frontend/src/components/ds/CompletionRing.tsx +30 -0
  78. package/templates/assistkick-product-system/packages/frontend/src/components/ds/ContentCard.tsx +34 -0
  79. package/templates/assistkick-product-system/packages/frontend/src/components/ds/IconButton.tsx +74 -0
  80. package/templates/assistkick-product-system/packages/frontend/src/components/ds/KanbanCard.tsx +270 -0
  81. package/templates/assistkick-product-system/packages/frontend/src/components/ds/KanbanCardShowcase.tsx +37 -0
  82. package/templates/assistkick-product-system/packages/frontend/src/components/ds/Kbd.tsx +11 -0
  83. package/templates/assistkick-product-system/packages/frontend/src/components/ds/KindBadge.tsx +21 -0
  84. package/templates/assistkick-product-system/packages/frontend/src/components/ds/NavBarSidekick.tsx +207 -0
  85. package/templates/assistkick-product-system/packages/frontend/src/components/ds/SidePanelShowcase.tsx +370 -0
  86. package/templates/assistkick-product-system/packages/frontend/src/components/ds/SideSheet.tsx +64 -0
  87. package/templates/assistkick-product-system/packages/frontend/src/components/ds/StatusDot.tsx +18 -0
  88. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/CheckCardPositionNode.tsx +36 -0
  89. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/CheckCycleCountNode.tsx +60 -0
  90. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/EndNode.tsx +42 -0
  91. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/GroupNode.tsx +189 -0
  92. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/NodePalette.tsx +123 -0
  93. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/RunAgentNode.tsx +51 -0
  94. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/SetCardMetadataNode.tsx +53 -0
  95. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/StartNode.tsx +18 -0
  96. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/TransitionCardNode.tsx +59 -0
  97. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/WorkflowCanvas.tsx +335 -0
  98. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/WorkflowMonitorModal.tsx +634 -0
  99. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/autoLayout.ts +103 -0
  100. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/edgeColors.ts +35 -0
  101. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/monitor_nodes.tsx +208 -0
  102. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/workflow_types.test.ts +119 -0
  103. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/workflow_types.ts +107 -0
  104. package/templates/assistkick-product-system/packages/frontend/src/constants/graph.ts +13 -11
  105. package/templates/assistkick-product-system/packages/frontend/src/hooks/useAutoSave.ts +75 -0
  106. package/templates/assistkick-product-system/packages/frontend/src/hooks/useGraph.ts +6 -21
  107. package/templates/assistkick-product-system/packages/frontend/src/hooks/useProjects.ts +15 -80
  108. package/templates/assistkick-product-system/packages/frontend/src/hooks/useToast.tsx +16 -3
  109. package/templates/assistkick-product-system/packages/frontend/src/pages/accept_invitation_page.tsx +30 -27
  110. package/templates/assistkick-product-system/packages/frontend/src/pages/forgot_password_page.tsx +18 -15
  111. package/templates/assistkick-product-system/packages/frontend/src/pages/register_page.tsx +21 -18
  112. package/templates/assistkick-product-system/packages/frontend/src/pages/reset_password_page.tsx +28 -25
  113. package/templates/assistkick-product-system/packages/frontend/src/routes/AgentsRoute.tsx +6 -0
  114. package/templates/assistkick-product-system/packages/frontend/src/routes/CoherenceRoute.tsx +19 -0
  115. package/templates/assistkick-product-system/packages/frontend/src/routes/DashboardLayout.tsx +54 -0
  116. package/templates/assistkick-product-system/packages/frontend/src/routes/DesignSystemRoute.tsx +6 -0
  117. package/templates/assistkick-product-system/packages/frontend/src/routes/FilesRoute.tsx +13 -0
  118. package/templates/assistkick-product-system/packages/frontend/src/routes/GraphRoute.tsx +93 -0
  119. package/templates/assistkick-product-system/packages/frontend/src/routes/KanbanRoute.tsx +30 -0
  120. package/templates/assistkick-product-system/packages/frontend/src/routes/TerminalRoute.tsx +9 -0
  121. package/templates/assistkick-product-system/packages/frontend/src/routes/UsersRoute.tsx +6 -0
  122. package/templates/assistkick-product-system/packages/frontend/src/routes/VideographyRoute.tsx +13 -0
  123. package/templates/assistkick-product-system/packages/frontend/src/routes/WorkflowsRoute.tsx +6 -0
  124. package/templates/assistkick-product-system/packages/frontend/src/stores/useGitModalStore.ts +14 -0
  125. package/templates/assistkick-product-system/packages/frontend/src/stores/useGraphStore.ts +36 -0
  126. package/templates/assistkick-product-system/packages/frontend/src/stores/useGraphUIStore.ts +25 -0
  127. package/templates/assistkick-product-system/packages/frontend/src/stores/useProjectStore.ts +90 -0
  128. package/templates/assistkick-product-system/packages/frontend/src/stores/useQaSheetStore.ts +27 -0
  129. package/templates/assistkick-product-system/packages/frontend/src/stores/useSidePanelStore.ts +76 -0
  130. package/templates/assistkick-product-system/packages/frontend/src/styles/index.css +336 -3632
  131. package/templates/assistkick-product-system/packages/frontend/src/utils/auto_save_service.test.ts +167 -0
  132. package/templates/assistkick-product-system/packages/frontend/src/utils/auto_save_service.ts +101 -0
  133. package/templates/assistkick-product-system/packages/frontend/src/utils/composition_matcher.test.ts +42 -0
  134. package/templates/assistkick-product-system/packages/frontend/src/utils/composition_matcher.ts +17 -0
  135. package/templates/assistkick-product-system/packages/frontend/src/utils/file_utils.test.ts +145 -0
  136. package/templates/assistkick-product-system/packages/frontend/src/utils/file_utils.ts +42 -0
  137. package/templates/assistkick-product-system/packages/frontend/src/utils/task_status.test.ts +4 -10
  138. package/templates/assistkick-product-system/packages/frontend/src/utils/task_status.ts +19 -1
  139. package/templates/assistkick-product-system/packages/frontend/vite.config.ts +7 -1
  140. package/templates/assistkick-product-system/packages/shared/db/local.db +0 -0
  141. package/templates/assistkick-product-system/packages/shared/db/migrations/0004_tidy_matthew_murdock.sql +9 -0
  142. package/templates/assistkick-product-system/packages/shared/db/migrations/0005_mysterious_falcon.sql +692 -0
  143. package/templates/assistkick-product-system/packages/shared/db/migrations/0006_next_venom.sql +9 -0
  144. package/templates/assistkick-product-system/packages/shared/db/migrations/0007_deep_barracuda.sql +39 -0
  145. package/templates/assistkick-product-system/packages/shared/db/migrations/0008_puzzling_hannibal_king.sql +1 -0
  146. package/templates/assistkick-product-system/packages/shared/db/migrations/0009_amused_beast.sql +8 -0
  147. package/templates/assistkick-product-system/packages/shared/db/migrations/0010_spotty_moira_mactaggert.sql +9 -0
  148. package/templates/assistkick-product-system/packages/shared/db/migrations/0011_goofy_snowbird.sql +3 -0
  149. package/templates/assistkick-product-system/packages/shared/db/migrations/0011_supreme_doctor_octopus.sql +3 -0
  150. package/templates/assistkick-product-system/packages/shared/db/migrations/0013_reflective_prowler.sql +15 -0
  151. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0004_snapshot.json +921 -0
  152. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0005_snapshot.json +1042 -0
  153. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0006_snapshot.json +1101 -0
  154. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0007_snapshot.json +1336 -0
  155. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0008_snapshot.json +1275 -0
  156. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0009_snapshot.json +1327 -0
  157. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0010_snapshot.json +1393 -0
  158. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0011_snapshot.json +1436 -0
  159. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0013_snapshot.json +1538 -0
  160. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/_journal.json +70 -0
  161. package/templates/assistkick-product-system/packages/shared/db/schema.ts +113 -0
  162. package/templates/assistkick-product-system/packages/shared/lib/claude-service.ts +32 -7
  163. package/templates/assistkick-product-system/packages/shared/lib/constants.ts +9 -0
  164. package/templates/assistkick-product-system/packages/shared/lib/git_workflow.ts +12 -4
  165. package/templates/assistkick-product-system/packages/shared/lib/graph.ts +16 -5
  166. package/templates/assistkick-product-system/packages/shared/lib/workflow_engine.test.ts +1753 -0
  167. package/templates/assistkick-product-system/packages/shared/lib/workflow_engine.ts +1281 -0
  168. package/templates/assistkick-product-system/packages/shared/lib/workflow_orchestrator.ts +211 -0
  169. package/templates/assistkick-product-system/packages/shared/tools/add_node.test.ts +43 -0
  170. package/templates/assistkick-product-system/packages/shared/tools/add_node.ts +13 -2
  171. package/templates/assistkick-product-system/packages/shared/tools/get_kanban.ts +1 -1
  172. package/templates/assistkick-product-system/packages/shared/tools/migrate_epics.test.ts +226 -0
  173. package/templates/assistkick-product-system/packages/shared/tools/migrate_epics.ts +251 -0
  174. package/templates/assistkick-product-system/packages/shared/tools/update_node.ts +2 -2
  175. package/templates/assistkick-product-system/packages/shared/utils/hello_workflow.test.ts +10 -0
  176. package/templates/assistkick-product-system/packages/shared/utils/hello_workflow.ts +6 -0
  177. package/templates/assistkick-product-system/packages/video/Root.tsx +85 -0
  178. package/templates/assistkick-product-system/packages/video/components/email_scene.tsx +231 -0
  179. package/templates/assistkick-product-system/packages/video/components/outro_scene.tsx +153 -0
  180. package/templates/assistkick-product-system/packages/video/components/part_divider.tsx +90 -0
  181. package/templates/assistkick-product-system/packages/video/components/scene.tsx +226 -0
  182. package/templates/assistkick-product-system/packages/video/components/theme.ts +22 -0
  183. package/templates/assistkick-product-system/packages/video/components/title_scene.tsx +169 -0
  184. package/templates/assistkick-product-system/packages/video/components/video_split_layout.tsx +84 -0
  185. package/templates/assistkick-product-system/packages/video/compositions/.gitkeep +0 -0
  186. package/templates/assistkick-product-system/packages/video/index.ts +4 -0
  187. package/templates/assistkick-product-system/packages/video/package.json +28 -0
  188. package/templates/assistkick-product-system/packages/video/remotion.config.ts +11 -0
  189. package/templates/assistkick-product-system/packages/video/scripts/process_script.test.ts +326 -0
  190. package/templates/assistkick-product-system/packages/video/scripts/process_script.ts +630 -0
  191. package/templates/assistkick-product-system/packages/video/style.css +1 -0
  192. package/templates/assistkick-product-system/packages/video/tsconfig.json +18 -0
  193. package/templates/assistkick-product-system/tests/graph_legend.test.ts +2 -1
  194. package/templates/assistkick-product-system/tests/video_render_service.test.ts +179 -0
  195. package/templates/assistkick-product-system/tests/web_terminal.test.ts +219 -455
  196. package/templates/assistkick-product-system/tests/workflow_integration.test.ts +341 -0
  197. package/templates/skills/assistkick-bootstrap/SKILL.md +3 -3
  198. package/templates/skills/assistkick-code-reviewer/SKILL.md +2 -2
  199. package/templates/skills/assistkick-debugger/SKILL.md +2 -2
  200. package/templates/skills/assistkick-developer/SKILL.md +6 -3
  201. package/templates/skills/assistkick-developer/references/react_development_guidelines.md +225 -0
  202. package/templates/skills/assistkick-interview/SKILL.md +2 -2
  203. package/templates/skills/product-system/graph.json +1890 -0
  204. package/templates/skills/product-system/kanban.json +304 -0
  205. package/templates/skills/product-system/nodes/comp_001.md +56 -0
  206. package/templates/skills/product-system/nodes/comp_002.md +57 -0
  207. package/templates/skills/product-system/nodes/data_001.md +51 -0
  208. package/templates/skills/product-system/nodes/data_002.md +40 -0
  209. package/templates/skills/product-system/nodes/data_004.md +38 -0
  210. package/templates/skills/product-system/nodes/dec_001.md +34 -0
  211. package/templates/skills/product-system/nodes/dec_016.md +32 -0
  212. package/templates/skills/product-system/nodes/feat_008.md +30 -0
  213. package/templates/skills/video-composition-agent/SKILL.md +232 -0
  214. package/templates/skills/video-script-writer/SKILL.md +136 -0
@@ -1,4 +1,4 @@
1
- import { describe, it, mock, beforeEach } from 'node:test';
1
+ import { describe, it, mock, beforeEach, afterEach } from 'node:test';
2
2
  import assert from 'node:assert/strict';
3
3
  import { PtySessionManager } from './pty_session_manager.ts';
4
4
 
@@ -14,11 +14,50 @@ const createMockPty = () => {
14
14
  };
15
15
  };
16
16
 
17
+ /** In-memory mock DB that mimics Drizzle's API surface for terminal_sessions. */
18
+ const createMockDb = () => {
19
+ const rows: any[] = [];
20
+
21
+ const where = (pred: any) => ({
22
+ // select().from().where() returns matching rows
23
+ then: (resolve: Function) => resolve(rows),
24
+ });
25
+
26
+ return {
27
+ _rows: rows,
28
+ select: () => ({
29
+ from: () => ({
30
+ where: (_pred: any) => Promise.resolve(rows.filter(() => true)),
31
+ then: (resolve: Function) => resolve(rows),
32
+ }),
33
+ }),
34
+ insert: () => ({
35
+ values: (val: any) => {
36
+ rows.push(val);
37
+ return Promise.resolve();
38
+ },
39
+ }),
40
+ update: () => ({
41
+ set: () => ({
42
+ where: () => Promise.resolve(),
43
+ }),
44
+ }),
45
+ delete: () => ({
46
+ where: () => {
47
+ // Remove all rows (simplified for tests)
48
+ rows.length = 0;
49
+ return Promise.resolve();
50
+ },
51
+ }),
52
+ };
53
+ };
54
+
17
55
  describe('PtySessionManager', () => {
18
56
  let manager: PtySessionManager;
19
57
  let spawnMock: ReturnType<typeof mock.fn>;
20
58
  let logMock: ReturnType<typeof mock.fn>;
21
59
  let mockPty: ReturnType<typeof createMockPty>;
60
+ let mockDb: ReturnType<typeof createMockDb>;
22
61
  const PROJECT_ROOT = '/test/project/root';
23
62
  const PROJECT_ID = 'proj_test';
24
63
  const PROJECT_NAME = 'Test Project';
@@ -27,125 +66,94 @@ describe('PtySessionManager', () => {
27
66
  mockPty = createMockPty();
28
67
  spawnMock = mock.fn(() => mockPty);
29
68
  logMock = mock.fn();
30
- manager = new PtySessionManager({ spawn: spawnMock as any, log: logMock, projectRoot: PROJECT_ROOT });
69
+ mockDb = createMockDb();
70
+ manager = new PtySessionManager({
71
+ spawn: spawnMock as any,
72
+ log: logMock,
73
+ projectRoot: PROJECT_ROOT,
74
+ getDb: () => mockDb,
75
+ });
31
76
  });
32
77
 
33
78
  describe('spawnCommand uses projectRoot as cwd', () => {
34
- it('passes projectRoot as cwd when auto-launching claude', () => {
35
- const session = manager.createSession(PROJECT_ID, PROJECT_NAME, 80, 24);
79
+ it('passes projectRoot as cwd when auto-launching claude', async () => {
80
+ await manager.createSession(PROJECT_ID, PROJECT_NAME, 80, 24);
36
81
 
37
- // Auto-launch is triggered on session creation
38
82
  assert.equal(spawnMock.mock.calls.length, 1);
39
83
  const spawnOptions = spawnMock.mock.calls[0].arguments[2] as Record<string, unknown>;
40
84
  assert.equal(spawnOptions.cwd, PROJECT_ROOT);
41
85
  });
42
86
  });
43
87
 
44
- describe('validateCommand', () => {
45
- it('allows "claude" command', () => {
46
- const result = manager.validateCommand('claude');
47
- assert.equal(result.valid, true);
48
- });
49
-
50
- it('allows "claude" with arguments', () => {
51
- const result = manager.validateCommand('claude --help');
52
- assert.equal(result.valid, true);
53
- });
54
-
55
- it('rejects disallowed commands', () => {
56
- const result = manager.validateCommand('rm -rf /');
57
- assert.equal(result.valid, false);
58
- assert.ok(result.error?.includes('not allowed'));
59
- });
60
-
61
- it('rejects empty command', () => {
62
- const result = manager.validateCommand('');
63
- assert.equal(result.valid, false);
64
- });
65
- });
66
-
67
88
  describe('session lifecycle', () => {
68
- it('creates a new named session with project binding', () => {
69
- const session = manager.createSession(PROJECT_ID, PROJECT_NAME, 80, 24);
89
+ it('creates a new named session with project binding', async () => {
90
+ const session = await manager.createSession(PROJECT_ID, PROJECT_NAME, 80, 24);
70
91
  assert.equal(session.projectId, PROJECT_ID);
71
92
  assert.equal(session.projectName, PROJECT_NAME);
72
- assert.equal(session.state, 'running'); // auto-launch sets state to running
93
+ assert.equal(session.state, 'running');
73
94
  assert.ok(session.id.startsWith('term_'));
74
95
  assert.ok(session.name.startsWith(PROJECT_NAME));
75
96
  });
76
97
 
77
- it('creates separate sessions for the same project', () => {
78
- const s1 = manager.createSession(PROJECT_ID, PROJECT_NAME, 80, 24);
79
- const s2 = manager.createSession(PROJECT_ID, PROJECT_NAME, 80, 24);
98
+ it('creates separate sessions for the same project', async () => {
99
+ const s1 = await manager.createSession(PROJECT_ID, PROJECT_NAME, 80, 24);
100
+ const s2 = await manager.createSession(PROJECT_ID, PROJECT_NAME, 80, 24);
80
101
  assert.notEqual(s1.id, s2.id);
81
102
  });
82
103
 
83
- it('destroys session and kills pty', () => {
84
- const session = manager.createSession(PROJECT_ID, PROJECT_NAME, 80, 24);
85
- // session.pty is already set by auto-launch
86
- manager.destroySession(session.id);
104
+ it('destroys session and kills pty', async () => {
105
+ const session = await manager.createSession(PROJECT_ID, PROJECT_NAME, 80, 24);
106
+ await manager.destroySession(session.id);
87
107
  assert.equal(mockPty.kill.mock.calls.length, 1);
88
- assert.equal(manager.getSession(session.id), undefined);
89
- });
90
-
91
- it('lists all active sessions', () => {
92
- const s1 = manager.createSession(PROJECT_ID, PROJECT_NAME, 80, 24);
93
- const s2 = manager.createSession('proj_other', 'Other', 80, 24);
94
- const list = manager.listSessions();
95
- assert.equal(list.length, 2);
96
- assert.ok(list.some(s => s.id === s1.id));
97
- assert.ok(list.some(s => s.id === s2.id));
98
108
  });
99
109
 
100
- it('session removed from list after destroy', () => {
101
- const session = manager.createSession(PROJECT_ID, PROJECT_NAME, 80, 24);
102
- manager.destroySession(session.id);
103
- const list = manager.listSessions();
104
- assert.equal(list.length, 0);
110
+ it('session state is running immediately after creation', async () => {
111
+ const session = await manager.createSession(PROJECT_ID, PROJECT_NAME, 80, 24);
112
+ assert.equal(session.state, 'running');
105
113
  });
106
114
  });
107
115
 
108
116
  describe('auto-launch claude on session creation', () => {
109
- it('auto-launches claude with --dangerously-skip-permissions and --append-system-prompt on session creation', () => {
110
- const session = manager.createSession(PROJECT_ID, PROJECT_NAME, 80, 24);
117
+ it('auto-launches claude with --dangerously-skip-permissions, --append-system-prompt, and --session-id', async () => {
118
+ const session = await manager.createSession(PROJECT_ID, PROJECT_NAME, 80, 24);
111
119
 
112
120
  assert.equal(spawnMock.mock.calls.length, 1);
113
121
  const [cmd, args] = spawnMock.mock.calls[0].arguments as [string, string[]];
114
122
  assert.equal(cmd, 'claude');
115
123
  assert.ok(args.includes('--dangerously-skip-permissions'));
116
124
  assert.ok(args.includes('--append-system-prompt'));
125
+ assert.ok(args.includes('--session-id'));
126
+ assert.ok(args.includes(session.claudeSessionId));
117
127
  assert.ok(args.includes(`We are working on project-id ${PROJECT_ID}`));
118
128
  });
119
129
 
120
- it('uses --append-system-prompt (not --system-prompt) to preserve default prompt', () => {
121
- manager.createSession(PROJECT_ID, PROJECT_NAME, 80, 24);
130
+ it('uses --append-system-prompt (not --system-prompt) to preserve default prompt', async () => {
131
+ await manager.createSession(PROJECT_ID, PROJECT_NAME, 80, 24);
122
132
 
123
133
  const [, args] = spawnMock.mock.calls[0].arguments as [string, string[]];
124
134
  assert.ok(args.includes('--append-system-prompt'));
125
- assert.ok(!args.includes('--system-prompt') || args.indexOf('--system-prompt') === args.indexOf('--append-system-prompt'));
126
135
  });
127
136
 
128
- it('includes the session projectId in the system prompt', () => {
137
+ it('includes the session projectId in the system prompt', async () => {
129
138
  const customProjectId = 'proj_custom_123';
130
- manager.createSession(customProjectId, PROJECT_NAME, 80, 24);
139
+ await manager.createSession(customProjectId, PROJECT_NAME, 80, 24);
131
140
 
132
141
  const [, args] = spawnMock.mock.calls[0].arguments as [string, string[]];
133
142
  const promptIndex = args.indexOf('--append-system-prompt');
134
143
  assert.ok(promptIndex !== -1);
135
144
  assert.ok(args[promptIndex + 1].includes(customProjectId));
136
145
  });
146
+ });
137
147
 
138
- it('does not re-launch claude when reconnecting to an existing session', () => {
139
- const session = manager.createSession(PROJECT_ID, PROJECT_NAME, 80, 24);
140
- // Reconnect is simulated by getSession no new spawn
141
- const existing = manager.getSession(session.id);
142
- assert.ok(existing);
148
+ describe('ensureRunning resumes suspended sessions', () => {
149
+ it('returns true and does not re-spawn for already live session', async () => {
150
+ const session = await manager.createSession(PROJECT_ID, PROJECT_NAME, 80, 24);
143
151
  assert.equal(spawnMock.mock.calls.length, 1);
144
- });
145
152
 
146
- it('session state is running immediately after creation', () => {
147
- const session = manager.createSession(PROJECT_ID, PROJECT_NAME, 80, 24);
148
- assert.equal(session.state, 'running');
153
+ const result = await manager.ensureRunning(session.id, 80, 24);
154
+ assert.equal(result, true);
155
+ // No extra spawn
156
+ assert.equal(spawnMock.mock.calls.length, 1);
149
157
  });
150
158
  });
151
159
 
@@ -156,4 +164,137 @@ describe('PtySessionManager', () => {
156
164
  assert.equal(name, 'My Project - 06/03/26 - 14:30');
157
165
  });
158
166
  });
167
+
168
+ describe('orphan session cleanup', () => {
169
+ afterEach(() => {
170
+ mock.timers.reset();
171
+ });
172
+
173
+ it('starts idle timer when last listener disconnects', async () => {
174
+ mock.timers.enable({ apis: ['setTimeout'] });
175
+ const session = await manager.createSession(PROJECT_ID, PROJECT_NAME, 80, 24);
176
+ const listener = mock.fn();
177
+
178
+ manager.addListener(session.id, listener);
179
+ manager.removeListener(session.id, listener);
180
+
181
+ assert.equal(manager.hasDisconnectTimer(session.id), true);
182
+ });
183
+
184
+ it('does not start timer when other listeners remain', async () => {
185
+ mock.timers.enable({ apis: ['setTimeout'] });
186
+ const session = await manager.createSession(PROJECT_ID, PROJECT_NAME, 80, 24);
187
+ const listener1 = mock.fn();
188
+ const listener2 = mock.fn();
189
+
190
+ manager.addListener(session.id, listener1);
191
+ manager.addListener(session.id, listener2);
192
+ manager.removeListener(session.id, listener1);
193
+
194
+ assert.equal(manager.hasDisconnectTimer(session.id), false);
195
+ });
196
+
197
+ it('cancels timer when client reconnects before timeout', async () => {
198
+ mock.timers.enable({ apis: ['setTimeout'] });
199
+ const session = await manager.createSession(PROJECT_ID, PROJECT_NAME, 80, 24);
200
+ const listener1 = mock.fn();
201
+ const listener2 = mock.fn();
202
+
203
+ manager.addListener(session.id, listener1);
204
+ manager.removeListener(session.id, listener1);
205
+ assert.equal(manager.hasDisconnectTimer(session.id), true);
206
+
207
+ // Reconnect before timer fires
208
+ manager.addListener(session.id, listener2);
209
+ assert.equal(manager.hasDisconnectTimer(session.id), false);
210
+
211
+ // PTY should still be live
212
+ assert.equal(manager.isLive(session.id), true);
213
+ });
214
+
215
+ it('kills PTY when idle timer fires with zero listeners', async () => {
216
+ mock.timers.enable({ apis: ['setTimeout'] });
217
+ const session = await manager.createSession(PROJECT_ID, PROJECT_NAME, 80, 24);
218
+ const listener = mock.fn();
219
+
220
+ manager.addListener(session.id, listener);
221
+ manager.removeListener(session.id, listener);
222
+
223
+ // Advance time past the 30-minute timeout
224
+ mock.timers.tick(1_800_000);
225
+
226
+ // PTY should be killed (removed from live map), but DB record persists
227
+ assert.equal(manager.isLive(session.id), false);
228
+ assert.equal(manager.hasDisconnectTimer(session.id), false);
229
+ });
230
+
231
+ it('does not kill PTY if listener reconnects before timer fires', async () => {
232
+ mock.timers.enable({ apis: ['setTimeout'] });
233
+ const session = await manager.createSession(PROJECT_ID, PROJECT_NAME, 80, 24);
234
+ const listener1 = mock.fn();
235
+ const listener2 = mock.fn();
236
+
237
+ manager.addListener(session.id, listener1);
238
+ manager.removeListener(session.id, listener1);
239
+
240
+ // Advance time partially
241
+ mock.timers.tick(900_000); // 15 minutes
242
+
243
+ // Reconnect
244
+ manager.addListener(session.id, listener2);
245
+
246
+ // Advance past original timeout
247
+ mock.timers.tick(900_001);
248
+
249
+ // PTY should still be live
250
+ assert.equal(manager.isLive(session.id), true);
251
+ });
252
+
253
+ it('clears pending timer when session is manually destroyed', async () => {
254
+ mock.timers.enable({ apis: ['setTimeout'] });
255
+ const session = await manager.createSession(PROJECT_ID, PROJECT_NAME, 80, 24);
256
+ const listener = mock.fn();
257
+
258
+ manager.addListener(session.id, listener);
259
+ manager.removeListener(session.id, listener);
260
+ assert.equal(manager.hasDisconnectTimer(session.id), true);
261
+
262
+ await manager.destroySession(session.id);
263
+ assert.equal(manager.hasDisconnectTimer(session.id), false);
264
+ assert.equal(manager.getDisconnectTimerCount(), 0);
265
+ });
266
+
267
+ it('clears all pending timers on destroyAllPty', async () => {
268
+ mock.timers.enable({ apis: ['setTimeout'] });
269
+ const s1 = await manager.createSession(PROJECT_ID, PROJECT_NAME, 80, 24);
270
+ const s2 = await manager.createSession('proj_other', 'Other', 80, 24);
271
+ const l1 = mock.fn();
272
+ const l2 = mock.fn();
273
+
274
+ manager.addListener(s1.id, l1);
275
+ manager.addListener(s2.id, l2);
276
+ manager.removeListener(s1.id, l1);
277
+ manager.removeListener(s2.id, l2);
278
+
279
+ assert.equal(manager.getDisconnectTimerCount(), 2);
280
+
281
+ manager.destroyAllPty();
282
+ assert.equal(manager.getDisconnectTimerCount(), 0);
283
+ });
284
+
285
+ it('only starts one timer per session even with multiple removeListener calls', async () => {
286
+ mock.timers.enable({ apis: ['setTimeout'] });
287
+ const session = await manager.createSession(PROJECT_ID, PROJECT_NAME, 80, 24);
288
+ const listener = mock.fn();
289
+
290
+ manager.addListener(session.id, listener);
291
+ manager.removeListener(session.id, listener);
292
+
293
+ // Try removing again (no-op since listener already removed)
294
+ manager.removeListener(session.id, listener);
295
+
296
+ // Should still have exactly one timer
297
+ assert.equal(manager.hasDisconnectTimer(session.id), true);
298
+ });
299
+ });
159
300
  });