@assistkick/create 1.9.0 → 1.11.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.
- package/dist/src/scaffolder.d.ts +12 -1
- package/dist/src/scaffolder.js +40 -3
- package/dist/src/scaffolder.js.map +1 -1
- package/package.json +1 -1
- package/templates/assistkick-product-system/package.json +1 -1
- package/templates/assistkick-product-system/packages/backend/package.json +1 -0
- package/templates/assistkick-product-system/packages/backend/src/mcp/permission_mcp_server.ts +196 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/agents.ts +31 -7
- package/templates/assistkick-product-system/packages/backend/src/routes/auth.ts +15 -12
- package/templates/assistkick-product-system/packages/backend/src/routes/chat_files.test.ts +95 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/chat_files.ts +97 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/chat_permission.ts +94 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/chat_sessions.ts +189 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/chat_upload.test.ts +131 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/chat_upload.ts +94 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/files.test.ts +12 -3
- package/templates/assistkick-product-system/packages/backend/src/routes/files.ts +2 -2
- package/templates/assistkick-product-system/packages/backend/src/routes/git.ts +391 -23
- package/templates/assistkick-product-system/packages/backend/src/routes/git_branches.test.ts +306 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/git_connect.test.ts +133 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/pipeline.ts +66 -9
- package/templates/assistkick-product-system/packages/backend/src/routes/preview.ts +204 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/projects.test.ts +205 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/projects.ts +37 -9
- package/templates/assistkick-product-system/packages/backend/src/routes/skills.test.ts +139 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/skills.ts +95 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/terminal.ts +5 -4
- package/templates/assistkick-product-system/packages/backend/src/routes/users.ts +4 -4
- package/templates/assistkick-product-system/packages/backend/src/routes/video.ts +8 -8
- package/templates/assistkick-product-system/packages/backend/src/routes/workflow_groups.ts +5 -5
- package/templates/assistkick-product-system/packages/backend/src/routes/workflows.ts +6 -6
- package/templates/assistkick-product-system/packages/backend/src/server.ts +107 -27
- package/templates/assistkick-product-system/packages/backend/src/services/agent_service.test.ts +105 -203
- package/templates/assistkick-product-system/packages/backend/src/services/agent_service.ts +76 -266
- package/templates/assistkick-product-system/packages/backend/src/services/chat_cli_bridge.test.ts +427 -0
- package/templates/assistkick-product-system/packages/backend/src/services/chat_cli_bridge.ts +345 -0
- package/templates/assistkick-product-system/packages/backend/src/services/chat_message_repository.test.ts +170 -0
- package/templates/assistkick-product-system/packages/backend/src/services/chat_message_repository.ts +106 -0
- package/templates/assistkick-product-system/packages/backend/src/services/chat_session_service.test.ts +217 -0
- package/templates/assistkick-product-system/packages/backend/src/services/chat_session_service.ts +188 -0
- package/templates/assistkick-product-system/packages/backend/src/services/chat_ws_handler.test.ts +1243 -0
- package/templates/assistkick-product-system/packages/backend/src/services/chat_ws_handler.ts +894 -0
- package/templates/assistkick-product-system/packages/backend/src/services/coherence-review.ts +3 -3
- package/templates/assistkick-product-system/packages/backend/src/services/dev_command_detector.test.ts +85 -0
- package/templates/assistkick-product-system/packages/backend/src/services/dev_command_detector.ts +54 -0
- package/templates/assistkick-product-system/packages/backend/src/services/email_service.ts +13 -10
- package/templates/assistkick-product-system/packages/backend/src/services/init.ts +11 -3
- package/templates/assistkick-product-system/packages/backend/src/services/invitation_service.ts +1 -1
- package/templates/assistkick-product-system/packages/backend/src/services/password_reset_service.ts +1 -1
- package/templates/assistkick-product-system/packages/backend/src/services/permission_service.test.ts +243 -0
- package/templates/assistkick-product-system/packages/backend/src/services/permission_service.ts +259 -0
- package/templates/assistkick-product-system/packages/backend/src/services/preview_server_manager.test.ts +172 -0
- package/templates/assistkick-product-system/packages/backend/src/services/preview_server_manager.ts +225 -0
- package/templates/assistkick-product-system/packages/backend/src/services/project_service.test.ts +29 -0
- package/templates/assistkick-product-system/packages/backend/src/services/project_service.ts +17 -0
- package/templates/assistkick-product-system/packages/backend/src/services/project_workspace_service.test.ts +255 -0
- package/templates/assistkick-product-system/packages/backend/src/services/project_workspace_service.ts +300 -25
- package/templates/assistkick-product-system/packages/backend/src/services/pty_session_manager.test.ts +44 -0
- package/templates/assistkick-product-system/packages/backend/src/services/pty_session_manager.ts +62 -7
- package/templates/assistkick-product-system/packages/backend/src/services/ssh_key_service.test.ts +77 -6
- package/templates/assistkick-product-system/packages/backend/src/services/ssh_key_service.ts +149 -14
- package/templates/assistkick-product-system/packages/backend/src/services/terminal_ws_handler.ts +2 -1
- package/templates/assistkick-product-system/packages/backend/src/services/title_generator_service.test.ts +45 -0
- package/templates/assistkick-product-system/packages/backend/src/services/title_generator_service.ts +157 -0
- package/templates/assistkick-product-system/packages/backend/src/services/tts_service.ts +4 -3
- package/templates/assistkick-product-system/packages/backend/src/services/video_render_service.ts +3 -3
- package/templates/assistkick-product-system/packages/frontend/package.json +5 -0
- package/templates/assistkick-product-system/packages/frontend/src/App.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/api/client.ts +336 -5
- package/templates/assistkick-product-system/packages/frontend/src/components/AgentsView.tsx +192 -12
- package/templates/assistkick-product-system/packages/frontend/src/components/AttachmentPreviewList.tsx +98 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/AutocompleteDropdown.tsx +65 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ChatAttachButton.tsx +56 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ChatDropZone.tsx +80 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ChatMessageBubble.tsx +155 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ChatMessageContent.tsx +182 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ChatMessageInput.tsx +233 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ChatSessionSidebar.tsx +218 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ChatStopButton.tsx +32 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ChatTodoSidebar.tsx +113 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ChatView.tsx +842 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/CommitMessageModal.tsx +82 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/DiagramOverlay.tsx +160 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/EditorTabBar.tsx +5 -5
- package/templates/assistkick-product-system/packages/frontend/src/components/FileTree.tsx +9 -10
- package/templates/assistkick-product-system/packages/frontend/src/components/FileTreeInlineInput.tsx +5 -5
- package/templates/assistkick-product-system/packages/frontend/src/components/FilesView.tsx +112 -41
- package/templates/assistkick-product-system/packages/frontend/src/components/GraphLegend.tsx +2 -2
- package/templates/assistkick-product-system/packages/frontend/src/components/HighlightedText.tsx +87 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ImageLightbox.tsx +192 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/KanbanView.tsx +2 -2
- package/templates/assistkick-product-system/packages/frontend/src/components/MentionPill.tsx +33 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/MermaidBlock.tsx +148 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/PermissionDialog.tsx +91 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/PermissionModeSelector.tsx +229 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ProjectSelector.tsx +249 -83
- package/templates/assistkick-product-system/packages/frontend/src/components/QueuedMessageBubble.tsx +38 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/SidePanel.tsx +212 -117
- package/templates/assistkick-product-system/packages/frontend/src/components/SystemPromptAccordion.tsx +48 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/TaskIcon.tsx +11 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/TerminalView.tsx +25 -9
- package/templates/assistkick-product-system/packages/frontend/src/components/ToolDiffView.tsx +114 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ToolResultCard.tsx +87 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ToolUseCard.tsx +149 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/Toolbar.tsx +25 -8
- package/templates/assistkick-product-system/packages/frontend/src/components/UnifiedGitWidget.tsx +722 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/GroupNode.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/NodePalette.tsx +2 -1
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/ProgrammableNode.tsx +178 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/WorkflowCanvas.tsx +3 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/WorkflowMonitorModal.tsx +103 -9
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/monitor_nodes.tsx +26 -2
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/workflow_types.ts +42 -1
- package/templates/assistkick-product-system/packages/frontend/src/hooks/useDocumentTitle.ts +11 -0
- package/templates/assistkick-product-system/packages/frontend/src/hooks/useProjects.ts +1 -0
- package/templates/assistkick-product-system/packages/frontend/src/hooks/use_chat_stream.ts +826 -0
- package/templates/assistkick-product-system/packages/frontend/src/hooks/use_file_tree_cache.ts +69 -0
- package/templates/assistkick-product-system/packages/frontend/src/hooks/use_mention_autocomplete.ts +284 -0
- package/templates/assistkick-product-system/packages/frontend/src/lib/attachment_manager.test.ts +183 -0
- package/templates/assistkick-product-system/packages/frontend/src/lib/attachment_manager.ts +150 -0
- package/templates/assistkick-product-system/packages/frontend/src/lib/chat_message_helpers.test.ts +305 -0
- package/templates/assistkick-product-system/packages/frontend/src/lib/chat_message_helpers.ts +113 -0
- package/templates/assistkick-product-system/packages/frontend/src/lib/context_usage_helpers.test.ts +157 -0
- package/templates/assistkick-product-system/packages/frontend/src/lib/context_usage_helpers.ts +95 -0
- package/templates/assistkick-product-system/packages/frontend/src/lib/mermaid_helpers.test.ts +65 -0
- package/templates/assistkick-product-system/packages/frontend/src/lib/mermaid_helpers.ts +110 -0
- package/templates/assistkick-product-system/packages/frontend/src/lib/message_queue.ts +66 -0
- package/templates/assistkick-product-system/packages/frontend/src/lib/tool_use_summary.test.ts +124 -0
- package/templates/assistkick-product-system/packages/frontend/src/lib/tool_use_summary.ts +112 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/AgentsRoute.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/ChatRoute.tsx +8 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/CoherenceRoute.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/DashboardLayout.tsx +0 -4
- package/templates/assistkick-product-system/packages/frontend/src/routes/DesignSystemRoute.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/FilesRoute.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/GraphRoute.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/KanbanRoute.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/TerminalRoute.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/UsersRoute.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/VideographyRoute.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/WorkflowsRoute.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/accept_invitation.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/forgot_password.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/login.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/register.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/reset_password.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/stores/useAttachmentStore.ts +66 -0
- package/templates/assistkick-product-system/packages/frontend/src/stores/useChatSessionStore.ts +107 -0
- package/templates/assistkick-product-system/packages/frontend/src/stores/useMessageQueueStore.ts +110 -0
- package/templates/assistkick-product-system/packages/frontend/src/stores/usePreviewStore.ts +78 -0
- package/templates/assistkick-product-system/packages/frontend/src/stores/useProjectStore.ts +7 -0
- package/templates/assistkick-product-system/packages/frontend/src/stores/useSidePanelStore.ts +6 -1
- package/templates/assistkick-product-system/packages/frontend/src/styles/index.css +30 -357
- package/templates/assistkick-product-system/packages/frontend/src/utils/parse_node_markdown.test.ts +115 -0
- package/templates/assistkick-product-system/packages/frontend/src/utils/parse_node_markdown.ts +91 -0
- package/templates/assistkick-product-system/packages/frontend/src/utils/preview_utils.test.ts +30 -0
- package/templates/assistkick-product-system/packages/frontend/src/utils/preview_utils.ts +3 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0015_magenta_jazinda.sql +1 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0016_giant_xorn.sql +1 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0017_sloppy_mentor.sql +6 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0018_vengeful_kabuki.sql +9 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0019_careful_sentinels.sql +8 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0020_clever_spot.sql +27 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0021_graceful_hex.sql +1 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0022_short_kingpin.sql +1 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0023_ambiguous_sharon_carter.sql +1 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0024_fat_unus.sql +1 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0015_snapshot.json +1552 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0016_snapshot.json +1560 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0017_snapshot.json +1598 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0018_snapshot.json +1657 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0019_snapshot.json +1709 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0020_snapshot.json +1733 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0021_snapshot.json +1740 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0022_snapshot.json +1755 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0023_snapshot.json +1762 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0024_snapshot.json +1769 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/_journal.json +70 -0
- package/templates/assistkick-product-system/packages/shared/db/schema.ts +40 -1
- package/templates/assistkick-product-system/packages/shared/lib/claude-service.test.ts +236 -0
- package/templates/assistkick-product-system/packages/shared/lib/claude-service.ts +46 -5
- package/templates/assistkick-product-system/packages/shared/lib/git_workflow.ts +65 -39
- package/templates/assistkick-product-system/packages/shared/lib/programmable_node_executor.test.ts +173 -0
- package/templates/assistkick-product-system/packages/shared/lib/programmable_node_executor.ts +213 -0
- package/templates/assistkick-product-system/packages/shared/lib/validator.test.ts +70 -0
- package/templates/assistkick-product-system/packages/shared/lib/validator.ts +17 -1
- package/templates/assistkick-product-system/packages/shared/lib/workflow_engine.test.ts +803 -27
- package/templates/assistkick-product-system/packages/shared/lib/workflow_engine.ts +502 -68
- package/templates/assistkick-product-system/packages/shared/lib/workflow_orchestrator.ts +4 -4
- package/templates/assistkick-product-system/packages/shared/package.json +2 -1
- package/templates/assistkick-product-system/packages/shared/test_fixtures/hanging_stream.mjs +46 -0
- package/templates/assistkick-product-system/packages/shared/tools/add_node.test.ts +44 -0
- package/templates/assistkick-product-system/packages/shared/tools/add_node.ts +7 -0
- package/templates/assistkick-product-system/packages/shared/tools/remove_node.ts +2 -1
- package/templates/assistkick-product-system/packages/shared/tools/resolve_question.ts +2 -1
- package/templates/assistkick-product-system/packages/shared/tools/update_node.ts +2 -1
- package/templates/assistkick-product-system/tests/message_queue.test.ts +178 -0
- package/templates/assistkick-product-system/tests/message_queue_per_session.test.ts +143 -0
- package/templates/skills/assistkick-bootstrap/SKILL.md +26 -26
- package/templates/skills/assistkick-code-reviewer/SKILL.md +45 -46
- package/templates/skills/assistkick-db-explorer/SKILL.md +13 -13
- package/templates/skills/assistkick-debugger/SKILL.md +23 -23
- package/templates/skills/assistkick-developer/SKILL.md +59 -63
- package/templates/skills/assistkick-interview/SKILL.md +26 -26
- package/templates/skills/assistkick-video-composition-agent/SKILL.md +231 -0
- package/templates/skills/assistkick-video-script-writer/SKILL.md +136 -0
|
@@ -55,6 +55,15 @@ export class ProjectWorkspaceService {
|
|
|
55
55
|
}
|
|
56
56
|
};
|
|
57
57
|
|
|
58
|
+
/** Verify that git is installed and available on the system. Throws if not. */
|
|
59
|
+
verifyGitAvailable = async (): Promise<void> => {
|
|
60
|
+
try {
|
|
61
|
+
await this.claudeService.spawnCommand('git', ['--version'], this.workspacesDir);
|
|
62
|
+
} catch {
|
|
63
|
+
throw new Error('Git is required to create a project. Please install git and try again.');
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
58
67
|
/** Initialize a new empty local git repository for a project. */
|
|
59
68
|
initWorkspace = async (projectId: string): Promise<string> => {
|
|
60
69
|
const wsPath = this.getWorkspacePath(projectId);
|
|
@@ -74,20 +83,31 @@ export class ProjectWorkspaceService {
|
|
|
74
83
|
return wsPath;
|
|
75
84
|
};
|
|
76
85
|
|
|
77
|
-
/**
|
|
86
|
+
/**
|
|
87
|
+
* Connect a remote repository using git remote add + git fetch.
|
|
88
|
+
* Replaces the old rm -rf + git clone pattern.
|
|
89
|
+
* If the workspace doesn't have a git repo yet, initializes one first.
|
|
90
|
+
*/
|
|
78
91
|
cloneRepo = async (projectId: string, cloneUrl: string): Promise<string> => {
|
|
79
92
|
const wsPath = this.getWorkspacePath(projectId);
|
|
80
|
-
await this.ensureDir(
|
|
93
|
+
await this.ensureDir(wsPath);
|
|
81
94
|
|
|
82
|
-
//
|
|
83
|
-
if (existsSync(wsPath)) {
|
|
84
|
-
this.
|
|
85
|
-
await this.claudeService.spawnCommand('rm', ['-rf', wsPath], this.workspacesDir);
|
|
95
|
+
// Ensure there's a local git repo
|
|
96
|
+
if (!existsSync(join(wsPath, '.git'))) {
|
|
97
|
+
await this.initWorkspace(projectId);
|
|
86
98
|
}
|
|
87
99
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
100
|
+
// Add the remote (replace if it already exists)
|
|
101
|
+
try {
|
|
102
|
+
await this.claudeService.spawnCommand('git', ['remote', 'remove', 'origin'], wsPath);
|
|
103
|
+
} catch {
|
|
104
|
+
// No existing origin — that's fine
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
this.log('WORKSPACE', `Adding remote origin ${cloneUrl} for project ${projectId}`);
|
|
108
|
+
await this.claudeService.spawnCommand('git', ['remote', 'add', 'origin', cloneUrl], wsPath);
|
|
109
|
+
await this.claudeService.spawnCommand('git', ['fetch', 'origin'], wsPath);
|
|
110
|
+
this.log('WORKSPACE', `Remote added and fetched for project ${projectId}`);
|
|
91
111
|
return wsPath;
|
|
92
112
|
};
|
|
93
113
|
|
|
@@ -103,16 +123,16 @@ export class ProjectWorkspaceService {
|
|
|
103
123
|
// Returns e.g. "origin/main" — strip the "origin/" prefix
|
|
104
124
|
const branch = result.trim().replace(/^origin\//, '');
|
|
105
125
|
if (branch) return branch;
|
|
106
|
-
} catch {
|
|
107
|
-
|
|
126
|
+
} catch (err: any) {
|
|
127
|
+
this.log('WORKSPACE', `symbolic-ref lookup failed (trying fallback): ${err.message}`);
|
|
108
128
|
}
|
|
109
129
|
|
|
110
130
|
try {
|
|
111
131
|
const result = await this.claudeService.spawnCommand('git', ['branch', '--show-current'], wsPath);
|
|
112
132
|
const branch = result.trim();
|
|
113
133
|
if (branch) return branch;
|
|
114
|
-
} catch {
|
|
115
|
-
|
|
134
|
+
} catch (err: any) {
|
|
135
|
+
this.log('WORKSPACE', `branch --show-current failed (using default 'main'): ${err.message}`);
|
|
116
136
|
}
|
|
117
137
|
|
|
118
138
|
return 'main';
|
|
@@ -135,7 +155,7 @@ export class ProjectWorkspaceService {
|
|
|
135
155
|
this.log('WORKSPACE', `Pull completed for project ${projectId}`);
|
|
136
156
|
} catch (err: any) {
|
|
137
157
|
// Pull may fail if there's no remote or no upstream — that's OK for local-only repos
|
|
138
|
-
this.log('WORKSPACE', `Pull failed (non-fatal)
|
|
158
|
+
this.log('WORKSPACE', `Pull failed (non-fatal):`, err.stack || err.message);
|
|
139
159
|
}
|
|
140
160
|
|
|
141
161
|
if (token && repoFullName) {
|
|
@@ -192,21 +212,32 @@ export class ProjectWorkspaceService {
|
|
|
192
212
|
}
|
|
193
213
|
};
|
|
194
214
|
|
|
195
|
-
/**
|
|
215
|
+
/**
|
|
216
|
+
* Connect a remote repository using SSH key authentication.
|
|
217
|
+
* Uses git remote add + git fetch instead of rm -rf + git clone.
|
|
218
|
+
*/
|
|
196
219
|
cloneRepoSsh = async (projectId: string, sshCloneUrl: string, encryptedPrivateKey: string): Promise<string> => {
|
|
197
220
|
const wsPath = this.getWorkspacePath(projectId);
|
|
198
|
-
await this.ensureDir(
|
|
221
|
+
await this.ensureDir(wsPath);
|
|
199
222
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
await this.
|
|
223
|
+
// Ensure there's a local git repo
|
|
224
|
+
if (!existsSync(join(wsPath, '.git'))) {
|
|
225
|
+
await this.initWorkspace(projectId);
|
|
203
226
|
}
|
|
204
227
|
|
|
205
|
-
|
|
228
|
+
// Add the remote (replace if it already exists)
|
|
229
|
+
try {
|
|
230
|
+
await this.claudeService.spawnCommand('git', ['remote', 'remove', 'origin'], wsPath);
|
|
231
|
+
} catch {
|
|
232
|
+
// No existing origin — that's fine
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
this.log('WORKSPACE', `SSH adding remote origin ${sshCloneUrl} for project ${projectId}`);
|
|
236
|
+
await this.claudeService.spawnCommand('git', ['remote', 'add', 'origin', sshCloneUrl], wsPath);
|
|
206
237
|
await this.withSshKey(encryptedPrivateKey, () =>
|
|
207
|
-
this.claudeService.spawnCommand('git', ['
|
|
238
|
+
this.claudeService.spawnCommand('git', ['fetch', 'origin'], wsPath),
|
|
208
239
|
);
|
|
209
|
-
this.log('WORKSPACE', `SSH
|
|
240
|
+
this.log('WORKSPACE', `SSH remote added and fetched for project ${projectId}`);
|
|
210
241
|
return wsPath;
|
|
211
242
|
};
|
|
212
243
|
|
|
@@ -222,7 +253,7 @@ export class ProjectWorkspaceService {
|
|
|
222
253
|
);
|
|
223
254
|
this.log('WORKSPACE', `SSH pull completed for project ${projectId}`);
|
|
224
255
|
} catch (err: any) {
|
|
225
|
-
this.log('WORKSPACE', `SSH pull failed (non-fatal)
|
|
256
|
+
this.log('WORKSPACE', `SSH pull failed (non-fatal):`, err.stack || err.message);
|
|
226
257
|
}
|
|
227
258
|
};
|
|
228
259
|
|
|
@@ -255,7 +286,9 @@ export class ProjectWorkspaceService {
|
|
|
255
286
|
try {
|
|
256
287
|
const result = await this.claudeService.spawnCommand('git', ['branch', '--show-current'], wsPath);
|
|
257
288
|
currentBranch = result.trim() || null;
|
|
258
|
-
} catch {
|
|
289
|
+
} catch (err: any) {
|
|
290
|
+
this.log('WORKSPACE', `Failed to get current branch for ${projectId}: ${err.message}`);
|
|
291
|
+
}
|
|
259
292
|
|
|
260
293
|
let remoteUrl: string | null = null;
|
|
261
294
|
let hasRemote = false;
|
|
@@ -263,8 +296,250 @@ export class ProjectWorkspaceService {
|
|
|
263
296
|
const result = await this.claudeService.spawnCommand('git', ['remote', 'get-url', 'origin'], wsPath);
|
|
264
297
|
remoteUrl = result.trim() || null;
|
|
265
298
|
hasRemote = !!remoteUrl;
|
|
266
|
-
} catch {
|
|
299
|
+
} catch (err: any) {
|
|
300
|
+
this.log('WORKSPACE', `No remote origin for ${projectId}: ${err.message}`);
|
|
301
|
+
}
|
|
267
302
|
|
|
268
303
|
return { hasWorkspace: true, hasRemote, currentBranch, remoteUrl };
|
|
269
304
|
};
|
|
305
|
+
|
|
306
|
+
/** List all local and remote branches. */
|
|
307
|
+
listBranches = async (projectId: string): Promise<{
|
|
308
|
+
current: string;
|
|
309
|
+
local: string[];
|
|
310
|
+
remote: string[];
|
|
311
|
+
}> => {
|
|
312
|
+
const wsPath = this.getWorkspacePath(projectId);
|
|
313
|
+
|
|
314
|
+
// Get current branch
|
|
315
|
+
let current = 'main';
|
|
316
|
+
try {
|
|
317
|
+
const result = await this.claudeService.spawnCommand('git', ['branch', '--show-current'], wsPath);
|
|
318
|
+
current = result.trim() || 'main';
|
|
319
|
+
} catch {}
|
|
320
|
+
|
|
321
|
+
// Get local branches
|
|
322
|
+
const local: string[] = [];
|
|
323
|
+
try {
|
|
324
|
+
const result = await this.claudeService.spawnCommand('git', ['branch', '--format=%(refname:short)'], wsPath);
|
|
325
|
+
for (const line of result.trim().split('\n')) {
|
|
326
|
+
const name = line.trim();
|
|
327
|
+
if (name) local.push(name);
|
|
328
|
+
}
|
|
329
|
+
} catch {}
|
|
330
|
+
|
|
331
|
+
// Get remote branches
|
|
332
|
+
const remote: string[] = [];
|
|
333
|
+
try {
|
|
334
|
+
const result = await this.claudeService.spawnCommand('git', ['branch', '-r', '--format=%(refname:short)'], wsPath);
|
|
335
|
+
for (const line of result.trim().split('\n')) {
|
|
336
|
+
const name = line.trim();
|
|
337
|
+
// Skip HEAD pointer and empty lines
|
|
338
|
+
if (name && !name.endsWith('/HEAD')) remote.push(name);
|
|
339
|
+
}
|
|
340
|
+
} catch {}
|
|
341
|
+
|
|
342
|
+
return { current, local, remote };
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
/** Check if there are uncommitted changes in the workspace. */
|
|
346
|
+
hasUncommittedChanges = async (projectId: string): Promise<boolean> => {
|
|
347
|
+
const wsPath = this.getWorkspacePath(projectId);
|
|
348
|
+
try {
|
|
349
|
+
const result = await this.claudeService.spawnCommand('git', ['status', '--porcelain'], wsPath);
|
|
350
|
+
return result.trim().length > 0;
|
|
351
|
+
} catch {
|
|
352
|
+
return false;
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
/** Commit all current changes with a message. */
|
|
357
|
+
commitAll = async (projectId: string, message: string): Promise<void> => {
|
|
358
|
+
const wsPath = this.getWorkspacePath(projectId);
|
|
359
|
+
await this.claudeService.spawnCommand('git', ['add', '-A'], wsPath);
|
|
360
|
+
await this.claudeService.spawnCommand('git', ['commit', '-m', message], wsPath);
|
|
361
|
+
this.log('WORKSPACE', `Committed all changes for project ${projectId}`);
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
/** Checkout an existing branch. */
|
|
365
|
+
checkoutBranch = async (projectId: string, branchName: string): Promise<void> => {
|
|
366
|
+
const wsPath = this.getWorkspacePath(projectId);
|
|
367
|
+
// If it's a remote branch (e.g. origin/feature), check it out locally
|
|
368
|
+
if (branchName.includes('/')) {
|
|
369
|
+
const localName = branchName.replace(/^[^/]+\//, '');
|
|
370
|
+
await this.claudeService.spawnCommand('git', ['checkout', '-b', localName, branchName], wsPath);
|
|
371
|
+
this.log('WORKSPACE', `Checked out remote branch ${branchName} as ${localName} for project ${projectId}`);
|
|
372
|
+
} else {
|
|
373
|
+
await this.claudeService.spawnCommand('git', ['checkout', branchName], wsPath);
|
|
374
|
+
this.log('WORKSPACE', `Checked out branch ${branchName} for project ${projectId}`);
|
|
375
|
+
}
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
/** Create and checkout a new branch. */
|
|
379
|
+
createBranch = async (projectId: string, branchName: string): Promise<void> => {
|
|
380
|
+
const wsPath = this.getWorkspacePath(projectId);
|
|
381
|
+
await this.claudeService.spawnCommand('git', ['checkout', '-b', branchName], wsPath);
|
|
382
|
+
this.log('WORKSPACE', `Created and checked out new branch ${branchName} for project ${projectId}`);
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
/** List all configured remotes with their URLs. */
|
|
386
|
+
listRemotes = async (projectId: string): Promise<Array<{ name: string; url: string }>> => {
|
|
387
|
+
const wsPath = this.getWorkspacePath(projectId);
|
|
388
|
+
const remotes: Array<{ name: string; url: string }> = [];
|
|
389
|
+
try {
|
|
390
|
+
const result = await this.claudeService.spawnCommand('git', ['remote', '-v'], wsPath);
|
|
391
|
+
const seen = new Set<string>();
|
|
392
|
+
for (const line of result.trim().split('\n')) {
|
|
393
|
+
const match = line.match(/^(\S+)\s+(\S+)\s+\(fetch\)$/);
|
|
394
|
+
if (match && !seen.has(match[1])) {
|
|
395
|
+
seen.add(match[1]);
|
|
396
|
+
remotes.push({ name: match[1], url: match[2] });
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
} catch {
|
|
400
|
+
// No remotes
|
|
401
|
+
}
|
|
402
|
+
return remotes;
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
/** Add a new remote. */
|
|
406
|
+
addRemote = async (projectId: string, name: string, url: string): Promise<void> => {
|
|
407
|
+
const wsPath = this.getWorkspacePath(projectId);
|
|
408
|
+
await this.claudeService.spawnCommand('git', ['remote', 'add', name, url], wsPath);
|
|
409
|
+
this.log('WORKSPACE', `Added remote ${name} → ${url} for project ${projectId}`);
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
/** Fetch from a remote to update tracking branches. */
|
|
413
|
+
fetchRemote = async (projectId: string, remote: string = 'origin'): Promise<void> => {
|
|
414
|
+
const wsPath = this.getWorkspacePath(projectId);
|
|
415
|
+
this.log('WORKSPACE', `Fetching ${remote} for project ${projectId}`);
|
|
416
|
+
await this.claudeService.spawnCommand('git', ['fetch', remote], wsPath);
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
/** Fetch from a remote using SSH key authentication. */
|
|
420
|
+
fetchRemoteSsh = async (projectId: string, encryptedPrivateKey: string, remote: string = 'origin'): Promise<void> => {
|
|
421
|
+
const wsPath = this.getWorkspacePath(projectId);
|
|
422
|
+
this.log('WORKSPACE', `SSH fetching ${remote} for project ${projectId}`);
|
|
423
|
+
await this.withSshKey(encryptedPrivateKey, () =>
|
|
424
|
+
this.claudeService.spawnCommand('git', ['fetch', remote], wsPath),
|
|
425
|
+
);
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
/** Pull current branch from remote. */
|
|
429
|
+
pullBranch = async (projectId: string, token?: string, repoFullName?: string): Promise<void> => {
|
|
430
|
+
const wsPath = this.getWorkspacePath(projectId);
|
|
431
|
+
const branch = await this.getCurrentBranch(projectId);
|
|
432
|
+
|
|
433
|
+
if (token && repoFullName) {
|
|
434
|
+
const authUrl = `https://x-access-token:${token}@github.com/${repoFullName}.git`;
|
|
435
|
+
await this.claudeService.spawnCommand('git', ['remote', 'set-url', 'origin', authUrl], wsPath);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
try {
|
|
439
|
+
this.log('WORKSPACE', `Pulling ${branch} for project ${projectId}`);
|
|
440
|
+
await this.claudeService.spawnCommand('git', ['pull', 'origin', branch], wsPath);
|
|
441
|
+
} finally {
|
|
442
|
+
if (token && repoFullName) {
|
|
443
|
+
await this.claudeService.spawnCommand(
|
|
444
|
+
'git', ['remote', 'set-url', 'origin', `https://github.com/${repoFullName}.git`], wsPath,
|
|
445
|
+
).catch(() => {});
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
/** Pull current branch from remote using SSH. */
|
|
451
|
+
pullBranchSsh = async (projectId: string, encryptedPrivateKey: string): Promise<void> => {
|
|
452
|
+
const wsPath = this.getWorkspacePath(projectId);
|
|
453
|
+
const branch = await this.getCurrentBranch(projectId);
|
|
454
|
+
|
|
455
|
+
this.log('WORKSPACE', `SSH pulling ${branch} for project ${projectId}`);
|
|
456
|
+
await this.withSshKey(encryptedPrivateKey, () =>
|
|
457
|
+
this.claudeService.spawnCommand('git', ['pull', 'origin', branch], wsPath),
|
|
458
|
+
);
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
/** Push current branch to remote. */
|
|
462
|
+
pushBranch = async (projectId: string, token?: string, repoFullName?: string): Promise<void> => {
|
|
463
|
+
const wsPath = this.getWorkspacePath(projectId);
|
|
464
|
+
const branch = await this.getCurrentBranch(projectId);
|
|
465
|
+
|
|
466
|
+
if (token && repoFullName) {
|
|
467
|
+
const authUrl = `https://x-access-token:${token}@github.com/${repoFullName}.git`;
|
|
468
|
+
await this.claudeService.spawnCommand('git', ['remote', 'set-url', 'origin', authUrl], wsPath);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
try {
|
|
472
|
+
this.log('WORKSPACE', `Pushing ${branch} for project ${projectId}`);
|
|
473
|
+
await this.claudeService.spawnCommand('git', ['push', '-u', 'origin', branch], wsPath);
|
|
474
|
+
} finally {
|
|
475
|
+
if (token && repoFullName) {
|
|
476
|
+
await this.claudeService.spawnCommand(
|
|
477
|
+
'git', ['remote', 'set-url', 'origin', `https://github.com/${repoFullName}.git`], wsPath,
|
|
478
|
+
).catch(() => {});
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
/** Push current branch to remote using SSH. */
|
|
484
|
+
pushBranchSsh = async (projectId: string, encryptedPrivateKey: string): Promise<void> => {
|
|
485
|
+
const wsPath = this.getWorkspacePath(projectId);
|
|
486
|
+
const branch = await this.getCurrentBranch(projectId);
|
|
487
|
+
|
|
488
|
+
this.log('WORKSPACE', `SSH pushing ${branch} for project ${projectId}`);
|
|
489
|
+
await this.withSshKey(encryptedPrivateKey, () =>
|
|
490
|
+
this.claudeService.spawnCommand('git', ['push', '-u', 'origin', branch], wsPath),
|
|
491
|
+
);
|
|
492
|
+
};
|
|
493
|
+
|
|
494
|
+
/** Get the current branch name. */
|
|
495
|
+
getCurrentBranch = async (projectId: string): Promise<string> => {
|
|
496
|
+
const wsPath = this.getWorkspacePath(projectId);
|
|
497
|
+
try {
|
|
498
|
+
const result = await this.claudeService.spawnCommand('git', ['branch', '--show-current'], wsPath);
|
|
499
|
+
return result.trim() || 'main';
|
|
500
|
+
} catch {
|
|
501
|
+
return 'main';
|
|
502
|
+
}
|
|
503
|
+
};
|
|
504
|
+
|
|
505
|
+
/** Get ahead/behind counts relative to the remote tracking branch. */
|
|
506
|
+
getAheadBehind = async (projectId: string): Promise<{ ahead: number; behind: number }> => {
|
|
507
|
+
const wsPath = this.getWorkspacePath(projectId);
|
|
508
|
+
try {
|
|
509
|
+
const branch = await this.getCurrentBranch(projectId);
|
|
510
|
+
const result = await this.claudeService.spawnCommand(
|
|
511
|
+
'git', ['rev-list', '--left-right', '--count', `${branch}...origin/${branch}`], wsPath,
|
|
512
|
+
);
|
|
513
|
+
const parts = result.trim().split(/\s+/);
|
|
514
|
+
return { ahead: parseInt(parts[0], 10) || 0, behind: parseInt(parts[1], 10) || 0 };
|
|
515
|
+
} catch {
|
|
516
|
+
return { ahead: 0, behind: 0 };
|
|
517
|
+
}
|
|
518
|
+
};
|
|
519
|
+
|
|
520
|
+
/** Check if the workspace has only the initial empty commit (auto-init). */
|
|
521
|
+
hasOnlyInitialCommit = async (projectId: string): Promise<boolean> => {
|
|
522
|
+
const wsPath = this.getWorkspacePath(projectId);
|
|
523
|
+
try {
|
|
524
|
+
const result = await this.claudeService.spawnCommand('git', ['rev-list', '--count', 'HEAD'], wsPath);
|
|
525
|
+
const count = parseInt(result.trim(), 10);
|
|
526
|
+
return count <= 1;
|
|
527
|
+
} catch {
|
|
528
|
+
return true;
|
|
529
|
+
}
|
|
530
|
+
};
|
|
531
|
+
|
|
532
|
+
/** Reset current branch to match a remote branch. Used when pulling from a populated remote. */
|
|
533
|
+
resetToRemoteBranch = async (projectId: string, remoteBranch: string = 'origin/main'): Promise<void> => {
|
|
534
|
+
const wsPath = this.getWorkspacePath(projectId);
|
|
535
|
+
this.log('WORKSPACE', `Resetting to ${remoteBranch} for project ${projectId}`);
|
|
536
|
+
await this.claudeService.spawnCommand('git', ['reset', '--hard', remoteBranch], wsPath);
|
|
537
|
+
};
|
|
538
|
+
|
|
539
|
+
/** Delete a local branch. */
|
|
540
|
+
deleteBranch = async (projectId: string, branchName: string): Promise<void> => {
|
|
541
|
+
const wsPath = this.getWorkspacePath(projectId);
|
|
542
|
+
await this.claudeService.spawnCommand('git', ['branch', '-D', branchName], wsPath);
|
|
543
|
+
this.log('WORKSPACE', `Deleted branch ${branchName} for project ${projectId}`);
|
|
544
|
+
};
|
|
270
545
|
}
|
|
@@ -157,6 +157,50 @@ describe('PtySessionManager', () => {
|
|
|
157
157
|
});
|
|
158
158
|
});
|
|
159
159
|
|
|
160
|
+
describe('dual session types', () => {
|
|
161
|
+
it('creates a claude session by default', async () => {
|
|
162
|
+
const session = await manager.createSession(PROJECT_ID, PROJECT_NAME, 80, 24);
|
|
163
|
+
assert.equal(session.sessionType, 'claude');
|
|
164
|
+
|
|
165
|
+
const [cmd] = spawnMock.mock.calls[0].arguments as [string, string[]];
|
|
166
|
+
assert.equal(cmd, 'claude');
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('creates a claude session when explicitly requested', async () => {
|
|
170
|
+
const session = await manager.createSession(PROJECT_ID, PROJECT_NAME, 80, 24, 'claude');
|
|
171
|
+
assert.equal(session.sessionType, 'claude');
|
|
172
|
+
|
|
173
|
+
const [cmd, args] = spawnMock.mock.calls[0].arguments as [string, string[]];
|
|
174
|
+
assert.equal(cmd, 'claude');
|
|
175
|
+
assert.ok(args.includes('--session-id'));
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it('creates a terminal session that spawns /bin/bash', async () => {
|
|
179
|
+
const session = await manager.createSession(PROJECT_ID, PROJECT_NAME, 80, 24, 'terminal');
|
|
180
|
+
assert.equal(session.sessionType, 'terminal');
|
|
181
|
+
|
|
182
|
+
const [cmd, args] = spawnMock.mock.calls[0].arguments as [string, string[]];
|
|
183
|
+
assert.equal(cmd, '/bin/bash');
|
|
184
|
+
assert.deepEqual(args, []);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('terminal session does not include claude flags', async () => {
|
|
188
|
+
await manager.createSession(PROJECT_ID, PROJECT_NAME, 80, 24, 'terminal');
|
|
189
|
+
|
|
190
|
+
const [, args] = spawnMock.mock.calls[0].arguments as [string, string[]];
|
|
191
|
+
assert.ok(!args.includes('--dangerously-skip-permissions'));
|
|
192
|
+
assert.ok(!args.includes('--append-system-prompt'));
|
|
193
|
+
assert.ok(!args.includes('--session-id'));
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('persists sessionType in the DB record', async () => {
|
|
197
|
+
await manager.createSession(PROJECT_ID, PROJECT_NAME, 80, 24, 'terminal');
|
|
198
|
+
|
|
199
|
+
const dbRow = mockDb._rows[0];
|
|
200
|
+
assert.equal(dbRow.sessionType, 'terminal');
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
|
|
160
204
|
describe('formatSessionName', () => {
|
|
161
205
|
it('formats session name as ProjectName - DD/MM/YY - HH:MM', () => {
|
|
162
206
|
const date = new Date(2026, 2, 6, 14, 30); // 2026-03-06 14:30
|
package/templates/assistkick-product-system/packages/backend/src/services/pty_session_manager.ts
CHANGED
|
@@ -31,12 +31,15 @@ interface LivePty {
|
|
|
31
31
|
rows: number;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
export type SessionType = 'claude' | 'terminal';
|
|
35
|
+
|
|
34
36
|
export interface SessionInfo {
|
|
35
37
|
id: string;
|
|
36
38
|
claudeSessionId: string;
|
|
37
39
|
name: string;
|
|
38
40
|
projectId: string;
|
|
39
41
|
projectName: string;
|
|
42
|
+
sessionType: SessionType;
|
|
40
43
|
state: 'suspended' | 'running';
|
|
41
44
|
createdAt: string;
|
|
42
45
|
lastUsedAt: string;
|
|
@@ -82,6 +85,7 @@ export class PtySessionManager {
|
|
|
82
85
|
name: r.name,
|
|
83
86
|
projectId: r.projectId,
|
|
84
87
|
projectName: r.projectName,
|
|
88
|
+
sessionType: (r.sessionType || 'claude') as SessionType,
|
|
85
89
|
state: this.live.has(r.id) ? 'running' as const : 'suspended' as const,
|
|
86
90
|
createdAt: r.createdAt,
|
|
87
91
|
lastUsedAt: r.lastUsedAt,
|
|
@@ -100,14 +104,15 @@ export class PtySessionManager {
|
|
|
100
104
|
name: r.name,
|
|
101
105
|
projectId: r.projectId,
|
|
102
106
|
projectName: r.projectName,
|
|
107
|
+
sessionType: (r.sessionType || 'claude') as SessionType,
|
|
103
108
|
state: this.live.has(r.id) ? 'running' as const : 'suspended' as const,
|
|
104
109
|
createdAt: r.createdAt,
|
|
105
110
|
lastUsedAt: r.lastUsedAt,
|
|
106
111
|
};
|
|
107
112
|
};
|
|
108
113
|
|
|
109
|
-
/** Create a new session — persists to DB and spawns
|
|
110
|
-
createSession = async (projectId: string, projectName: string, cols: number, rows: number): Promise<SessionInfo> => {
|
|
114
|
+
/** Create a new session — persists to DB and spawns the appropriate process. */
|
|
115
|
+
createSession = async (projectId: string, projectName: string, cols: number, rows: number, sessionType: SessionType = 'claude'): Promise<SessionInfo> => {
|
|
111
116
|
const id = this.generateId();
|
|
112
117
|
const claudeSessionId = randomUUID();
|
|
113
118
|
const createdAt = new Date();
|
|
@@ -122,14 +127,18 @@ export class PtySessionManager {
|
|
|
122
127
|
projectId,
|
|
123
128
|
projectName,
|
|
124
129
|
name,
|
|
130
|
+
sessionType,
|
|
125
131
|
createdAt: now,
|
|
126
132
|
lastUsedAt: now,
|
|
127
133
|
});
|
|
128
134
|
|
|
129
|
-
this.log('PTY', `Created session "${name}" (${id}) for project ${projectId}
|
|
135
|
+
this.log('PTY', `Created ${sessionType} session "${name}" (${id}) for project ${projectId}`);
|
|
130
136
|
|
|
131
|
-
|
|
132
|
-
|
|
137
|
+
if (sessionType === 'claude') {
|
|
138
|
+
this.spawnClaude(id, claudeSessionId, projectId, cols, rows, false);
|
|
139
|
+
} else {
|
|
140
|
+
this.spawnShell(id, cols, rows);
|
|
141
|
+
}
|
|
133
142
|
|
|
134
143
|
return {
|
|
135
144
|
id,
|
|
@@ -137,6 +146,7 @@ export class PtySessionManager {
|
|
|
137
146
|
name,
|
|
138
147
|
projectId,
|
|
139
148
|
projectName,
|
|
149
|
+
sessionType,
|
|
140
150
|
state: 'running',
|
|
141
151
|
createdAt: now,
|
|
142
152
|
lastUsedAt: now,
|
|
@@ -159,8 +169,12 @@ export class PtySessionManager {
|
|
|
159
169
|
.set({ lastUsedAt: new Date().toISOString() })
|
|
160
170
|
.where(eq(terminalSessions.id, sessionId));
|
|
161
171
|
|
|
162
|
-
// Resume
|
|
163
|
-
|
|
172
|
+
// Resume based on session type
|
|
173
|
+
if (session.sessionType === 'terminal') {
|
|
174
|
+
this.spawnShell(sessionId, cols, rows);
|
|
175
|
+
} else {
|
|
176
|
+
this.spawnClaude(sessionId, session.claudeSessionId, session.projectId, cols, rows, true);
|
|
177
|
+
}
|
|
164
178
|
return true;
|
|
165
179
|
};
|
|
166
180
|
|
|
@@ -319,6 +333,47 @@ export class PtySessionManager {
|
|
|
319
333
|
});
|
|
320
334
|
};
|
|
321
335
|
|
|
336
|
+
private spawnShell = (
|
|
337
|
+
sessionId: string,
|
|
338
|
+
cols: number,
|
|
339
|
+
rows: number,
|
|
340
|
+
): void => {
|
|
341
|
+
this.log('PTY', `Launching plain shell for session ${sessionId}`);
|
|
342
|
+
|
|
343
|
+
let spawnedPty: IPty;
|
|
344
|
+
try {
|
|
345
|
+
spawnedPty = this.spawn('/bin/bash', [], {
|
|
346
|
+
name: 'xterm-256color',
|
|
347
|
+
cols,
|
|
348
|
+
rows,
|
|
349
|
+
cwd: this.projectRoot,
|
|
350
|
+
env: this.buildEnv(),
|
|
351
|
+
});
|
|
352
|
+
} catch (err) {
|
|
353
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
354
|
+
this.log('PTY', `Failed to spawn shell for session ${sessionId}: ${msg}`);
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const entry: LivePty = {
|
|
359
|
+
pty: spawnedPty,
|
|
360
|
+
outputBuffer: '',
|
|
361
|
+
listeners: new Set(),
|
|
362
|
+
cols,
|
|
363
|
+
rows,
|
|
364
|
+
};
|
|
365
|
+
this.live.set(sessionId, entry);
|
|
366
|
+
|
|
367
|
+
spawnedPty.onData((data: string) => {
|
|
368
|
+
this.emitOutput(entry, data);
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
spawnedPty.onExit(({ exitCode }) => {
|
|
372
|
+
this.log('PTY', `Shell exited with code ${exitCode} for session ${sessionId}`);
|
|
373
|
+
this.live.delete(sessionId);
|
|
374
|
+
});
|
|
375
|
+
};
|
|
376
|
+
|
|
322
377
|
private buildEnv = (): Record<string, string> => {
|
|
323
378
|
const env = { ...process.env } as Record<string, string>;
|
|
324
379
|
const home = env.HOME || '/root';
|