@gracefultools/astrid-sdk 0.6.1 → 0.7.1
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/bin/cli.js +170 -47
- package/dist/bin/cli.js.map +1 -1
- package/dist/executors/terminal-base.d.ts +105 -0
- package/dist/executors/terminal-base.d.ts.map +1 -0
- package/dist/executors/terminal-base.js +113 -0
- package/dist/executors/terminal-base.js.map +1 -0
- package/dist/executors/terminal-claude.d.ts +16 -28
- package/dist/executors/terminal-claude.d.ts.map +1 -1
- package/dist/executors/terminal-claude.js +122 -75
- package/dist/executors/terminal-claude.js.map +1 -1
- package/dist/executors/terminal-executors.test.d.ts +8 -0
- package/dist/executors/terminal-executors.test.d.ts.map +1 -0
- package/dist/executors/terminal-executors.test.js +279 -0
- package/dist/executors/terminal-executors.test.js.map +1 -0
- package/dist/executors/terminal-gemini.d.ts +48 -0
- package/dist/executors/terminal-gemini.d.ts.map +1 -0
- package/dist/executors/terminal-gemini.js +509 -0
- package/dist/executors/terminal-gemini.js.map +1 -0
- package/dist/executors/terminal-openai.d.ts +48 -0
- package/dist/executors/terminal-openai.d.ts.map +1 -0
- package/dist/executors/terminal-openai.js +531 -0
- package/dist/executors/terminal-openai.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -1
- package/dist/index.js.map +1 -1
- package/package.json +7 -5
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal Executor Base Interface
|
|
3
|
+
*
|
|
4
|
+
* Common interface for all terminal executors (Claude, OpenAI, Gemini).
|
|
5
|
+
* Terminal mode executes tasks using local tools instead of remote APIs.
|
|
6
|
+
*/
|
|
7
|
+
import type { Session } from '../server/session-manager.js';
|
|
8
|
+
/**
|
|
9
|
+
* Result from terminal execution
|
|
10
|
+
*/
|
|
11
|
+
export interface TerminalExecutionResult {
|
|
12
|
+
exitCode: number | null;
|
|
13
|
+
stdout: string;
|
|
14
|
+
stderr: string;
|
|
15
|
+
sessionId?: string;
|
|
16
|
+
gitDiff?: string;
|
|
17
|
+
modifiedFiles?: string[];
|
|
18
|
+
prUrl?: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Context for terminal task execution
|
|
22
|
+
*/
|
|
23
|
+
export interface TerminalTaskContext {
|
|
24
|
+
comments?: Array<{
|
|
25
|
+
authorName: string;
|
|
26
|
+
content: string;
|
|
27
|
+
createdAt: string;
|
|
28
|
+
}>;
|
|
29
|
+
prUrl?: string;
|
|
30
|
+
repository?: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Parsed output from terminal execution
|
|
34
|
+
*/
|
|
35
|
+
export interface ParsedOutput {
|
|
36
|
+
summary?: string;
|
|
37
|
+
files?: string[];
|
|
38
|
+
prUrl?: string;
|
|
39
|
+
error?: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Progress callback function
|
|
43
|
+
*/
|
|
44
|
+
export type TerminalProgressCallback = (message: string) => void;
|
|
45
|
+
/**
|
|
46
|
+
* Common interface for all terminal executors.
|
|
47
|
+
*
|
|
48
|
+
* Terminal executors process tasks using local tool execution:
|
|
49
|
+
* - Claude: Spawns local Claude Code CLI
|
|
50
|
+
* - OpenAI: Uses OpenAI API with local tool execution
|
|
51
|
+
* - Gemini: Uses Gemini API with local tool execution
|
|
52
|
+
*/
|
|
53
|
+
export interface TerminalExecutor {
|
|
54
|
+
/**
|
|
55
|
+
* Check if the executor is available (e.g., CLI installed, API key set)
|
|
56
|
+
*/
|
|
57
|
+
checkAvailable(): Promise<boolean>;
|
|
58
|
+
/**
|
|
59
|
+
* Start a new session to process a task
|
|
60
|
+
*
|
|
61
|
+
* @param session - The session containing task details
|
|
62
|
+
* @param prompt - Optional custom prompt (uses default if not provided)
|
|
63
|
+
* @param context - Optional context including comments and PR info
|
|
64
|
+
* @param onProgress - Optional callback for progress updates
|
|
65
|
+
* @returns Execution result with output, modified files, and PR URL
|
|
66
|
+
*/
|
|
67
|
+
startSession(session: Session, prompt?: string, context?: TerminalTaskContext, onProgress?: TerminalProgressCallback): Promise<TerminalExecutionResult>;
|
|
68
|
+
/**
|
|
69
|
+
* Resume an existing session with new input
|
|
70
|
+
*
|
|
71
|
+
* @param session - The session to resume
|
|
72
|
+
* @param input - New input from user (e.g., follow-up message)
|
|
73
|
+
* @param context - Optional updated context
|
|
74
|
+
* @param onProgress - Optional callback for progress updates
|
|
75
|
+
* @returns Execution result
|
|
76
|
+
*/
|
|
77
|
+
resumeSession(session: Session, input: string, context?: TerminalTaskContext, onProgress?: TerminalProgressCallback): Promise<TerminalExecutionResult>;
|
|
78
|
+
/**
|
|
79
|
+
* Parse output to extract key information
|
|
80
|
+
*
|
|
81
|
+
* @param output - Raw output string from execution
|
|
82
|
+
* @returns Parsed output with summary, files, PR URL, and error
|
|
83
|
+
*/
|
|
84
|
+
parseOutput(output: string): ParsedOutput;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Extract PR URL from output text
|
|
88
|
+
*/
|
|
89
|
+
export declare function extractPrUrl(output: string): string | undefined;
|
|
90
|
+
/**
|
|
91
|
+
* Format comment history for context
|
|
92
|
+
*/
|
|
93
|
+
export declare function formatCommentHistory(comments?: TerminalTaskContext['comments']): string;
|
|
94
|
+
/**
|
|
95
|
+
* Capture git changes in a repository
|
|
96
|
+
*/
|
|
97
|
+
export declare function captureGitChanges(projectPath: string): Promise<{
|
|
98
|
+
diff: string;
|
|
99
|
+
files: string[];
|
|
100
|
+
}>;
|
|
101
|
+
/**
|
|
102
|
+
* Build default prompt for a task
|
|
103
|
+
*/
|
|
104
|
+
export declare function buildDefaultPrompt(session: Session, context?: TerminalTaskContext): string;
|
|
105
|
+
//# sourceMappingURL=terminal-base.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"terminal-base.d.ts","sourceRoot":"","sources":["../../src/executors/terminal-base.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,8BAA8B,CAAA;AAM3D;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,EAAE,KAAK,CAAC;QACf,UAAU,EAAE,MAAM,CAAA;QAClB,OAAO,EAAE,MAAM,CAAA;QACf,SAAS,EAAE,MAAM,CAAA;KAClB,CAAC,CAAA;IACF,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED;;GAEG;AACH,MAAM,MAAM,wBAAwB,GAAG,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;AAMhE;;;;;;;GAOG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;OAEG;IACH,cAAc,IAAI,OAAO,CAAC,OAAO,CAAC,CAAA;IAElC;;;;;;;;OAQG;IACH,YAAY,CACV,OAAO,EAAE,OAAO,EAChB,MAAM,CAAC,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,mBAAmB,EAC7B,UAAU,CAAC,EAAE,wBAAwB,GACpC,OAAO,CAAC,uBAAuB,CAAC,CAAA;IAEnC;;;;;;;;OAQG;IACH,aAAa,CACX,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,mBAAmB,EAC7B,UAAU,CAAC,EAAE,wBAAwB,GACpC,OAAO,CAAC,uBAAuB,CAAC,CAAA;IAEnC;;;;;OAKG;IACH,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,CAAA;CAC1C;AAMD;;GAEG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAe/D;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,CAAC,EAAE,mBAAmB,CAAC,UAAU,CAAC,GAAG,MAAM,CASvF;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAuCvG;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,OAAO,EAChB,OAAO,CAAC,EAAE,mBAAmB,GAC5B,MAAM,CAyBR"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Terminal Executor Base Interface
|
|
4
|
+
*
|
|
5
|
+
* Common interface for all terminal executors (Claude, OpenAI, Gemini).
|
|
6
|
+
* Terminal mode executes tasks using local tools instead of remote APIs.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.extractPrUrl = extractPrUrl;
|
|
10
|
+
exports.formatCommentHistory = formatCommentHistory;
|
|
11
|
+
exports.captureGitChanges = captureGitChanges;
|
|
12
|
+
exports.buildDefaultPrompt = buildDefaultPrompt;
|
|
13
|
+
// ============================================================================
|
|
14
|
+
// HELPER FUNCTIONS
|
|
15
|
+
// ============================================================================
|
|
16
|
+
/**
|
|
17
|
+
* Extract PR URL from output text
|
|
18
|
+
*/
|
|
19
|
+
function extractPrUrl(output) {
|
|
20
|
+
const prUrlPatterns = [
|
|
21
|
+
/https:\/\/github\.com\/[^\/]+\/[^\/]+\/pull\/\d+/g,
|
|
22
|
+
/PR URL:\s*(https:\/\/[^\s]+)/i,
|
|
23
|
+
/Pull Request:\s*(https:\/\/[^\s]+)/i
|
|
24
|
+
];
|
|
25
|
+
for (const pattern of prUrlPatterns) {
|
|
26
|
+
const match = output.match(pattern);
|
|
27
|
+
if (match) {
|
|
28
|
+
return match[0].replace(/PR URL:\s*/i, '').replace(/Pull Request:\s*/i, '');
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Format comment history for context
|
|
35
|
+
*/
|
|
36
|
+
function formatCommentHistory(comments) {
|
|
37
|
+
if (!comments || comments.length === 0)
|
|
38
|
+
return '';
|
|
39
|
+
const formatted = comments
|
|
40
|
+
.slice(-10)
|
|
41
|
+
.map(c => `**${c.authorName}** (${new Date(c.createdAt).toLocaleString()}):\n${c.content}`)
|
|
42
|
+
.join('\n\n---\n\n');
|
|
43
|
+
return `\n\n## Previous Discussion\n\n${formatted}`;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Capture git changes in a repository
|
|
47
|
+
*/
|
|
48
|
+
async function captureGitChanges(projectPath) {
|
|
49
|
+
const { execSync } = await import('child_process');
|
|
50
|
+
try {
|
|
51
|
+
// Get modified files (staged and unstaged)
|
|
52
|
+
const statusOutput = execSync('git status --porcelain', {
|
|
53
|
+
cwd: projectPath,
|
|
54
|
+
encoding: 'utf-8',
|
|
55
|
+
timeout: 10000
|
|
56
|
+
});
|
|
57
|
+
const files = statusOutput
|
|
58
|
+
.split('\n')
|
|
59
|
+
.filter(line => line.trim())
|
|
60
|
+
.map(line => line.slice(3).trim()); // Remove status prefix
|
|
61
|
+
// Get diff (staged and unstaged, limited to 5000 chars)
|
|
62
|
+
let diff = '';
|
|
63
|
+
try {
|
|
64
|
+
diff = execSync('git diff HEAD --no-color', {
|
|
65
|
+
cwd: projectPath,
|
|
66
|
+
encoding: 'utf-8',
|
|
67
|
+
timeout: 10000,
|
|
68
|
+
maxBuffer: 1024 * 1024 // 1MB max
|
|
69
|
+
});
|
|
70
|
+
// Truncate if too long
|
|
71
|
+
if (diff.length > 5000) {
|
|
72
|
+
diff = diff.slice(0, 5000) + '\n\n[... diff truncated ...]';
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// No diff or not a git repo
|
|
77
|
+
}
|
|
78
|
+
console.log(`📊 Git changes: ${files.length} files modified`);
|
|
79
|
+
return { diff, files };
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return { diff: '', files: [] };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Build default prompt for a task
|
|
87
|
+
*/
|
|
88
|
+
function buildDefaultPrompt(session, context) {
|
|
89
|
+
const commentHistory = formatCommentHistory(context?.comments);
|
|
90
|
+
return `# Task: ${session.title}
|
|
91
|
+
|
|
92
|
+
${session.description || ''}
|
|
93
|
+
${commentHistory}
|
|
94
|
+
|
|
95
|
+
## Workflow Requirements
|
|
96
|
+
|
|
97
|
+
1. Understand the task and verify the platform (iOS = ios-app/, Web = components/, app/)
|
|
98
|
+
2. Locate relevant code and make ONLY the requested changes
|
|
99
|
+
3. Run predeploy tests: \`npm run predeploy\`
|
|
100
|
+
4. Create PR: \`gh pr create\` with a clear title
|
|
101
|
+
|
|
102
|
+
## Output Requirements
|
|
103
|
+
|
|
104
|
+
Your response MUST include:
|
|
105
|
+
1. Task understanding: What was requested
|
|
106
|
+
2. Actual changes made: What you changed and WHY
|
|
107
|
+
3. Files modified: List each file path
|
|
108
|
+
4. Test results: Output from \`npm run predeploy\`
|
|
109
|
+
5. PR URL: The pull request URL (REQUIRED)
|
|
110
|
+
|
|
111
|
+
Begin by analyzing the task.`;
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=terminal-base.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"terminal-base.js","sourceRoot":"","sources":["../../src/executors/terminal-base.ts"],"names":[],"mappings":"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"}
|
|
@@ -11,29 +11,13 @@
|
|
|
11
11
|
* - Extracts PR URLs and git changes from output
|
|
12
12
|
*/
|
|
13
13
|
import type { Session } from '../server/session-manager.js';
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
stdout: string;
|
|
17
|
-
stderr: string;
|
|
18
|
-
sessionId?: string;
|
|
19
|
-
gitDiff?: string;
|
|
20
|
-
modifiedFiles?: string[];
|
|
21
|
-
prUrl?: string;
|
|
22
|
-
}
|
|
14
|
+
import type { TerminalExecutor, TerminalTaskContext, ParsedOutput, TerminalExecutionResult } from './terminal-base.js';
|
|
15
|
+
export type { TerminalExecutionResult, TerminalTaskContext } from './terminal-base.js';
|
|
23
16
|
export interface TerminalClaudeOptions {
|
|
24
17
|
model?: string;
|
|
25
18
|
maxTurns?: number;
|
|
26
19
|
timeout?: number;
|
|
27
20
|
}
|
|
28
|
-
export interface TerminalTaskContext {
|
|
29
|
-
comments?: Array<{
|
|
30
|
-
authorName: string;
|
|
31
|
-
content: string;
|
|
32
|
-
createdAt: string;
|
|
33
|
-
}>;
|
|
34
|
-
prUrl?: string;
|
|
35
|
-
repository?: string;
|
|
36
|
-
}
|
|
37
21
|
/**
|
|
38
22
|
* Simple file-based session store for terminal mode.
|
|
39
23
|
* Stores Claude session IDs for resumption support.
|
|
@@ -50,7 +34,7 @@ declare class TerminalSessionStore {
|
|
|
50
34
|
deleteSession(taskId: string): Promise<void>;
|
|
51
35
|
}
|
|
52
36
|
export declare const terminalSessionStore: TerminalSessionStore;
|
|
53
|
-
export declare class TerminalClaudeExecutor {
|
|
37
|
+
export declare class TerminalClaudeExecutor implements TerminalExecutor {
|
|
54
38
|
private model;
|
|
55
39
|
private maxTurns;
|
|
56
40
|
private timeout;
|
|
@@ -76,34 +60,38 @@ export declare class TerminalClaudeExecutor {
|
|
|
76
60
|
formatCommentHistory(comments?: TerminalTaskContext['comments']): string;
|
|
77
61
|
/**
|
|
78
62
|
* Build prompt from task details
|
|
63
|
+
*
|
|
64
|
+
* IMPORTANT: Keep prompts SIMPLE! Claude Code CLI has a bug where complex
|
|
65
|
+
* markdown prompts with multiple sections cause it to hang indefinitely.
|
|
66
|
+
* Simple one-line prompts work reliably.
|
|
67
|
+
*
|
|
68
|
+
* Claude Code automatically reads CLAUDE.md from the working directory,
|
|
69
|
+
* so we don't need to include workflow instructions here.
|
|
79
70
|
*/
|
|
80
71
|
buildPrompt(session: Session, userMessage?: string, context?: TerminalTaskContext): Promise<string>;
|
|
81
72
|
/**
|
|
82
|
-
* Start a new Claude Code session
|
|
73
|
+
* Start a new Claude Code session with retry logic
|
|
83
74
|
*/
|
|
84
75
|
startSession(session: Session, prompt?: string, context?: TerminalTaskContext, onProgress?: (message: string) => void): Promise<TerminalExecutionResult>;
|
|
85
76
|
/**
|
|
86
|
-
* Resume an existing Claude Code session
|
|
77
|
+
* Resume an existing Claude Code session with retry logic
|
|
87
78
|
*/
|
|
88
79
|
resumeSession(session: Session, input: string, context?: TerminalTaskContext, onProgress?: (message: string) => void): Promise<TerminalExecutionResult>;
|
|
89
80
|
/**
|
|
90
81
|
* Execute Claude Code CLI
|
|
82
|
+
*
|
|
83
|
+
* Uses stdin for prompt input instead of command-line args to avoid
|
|
84
|
+
* issues with long prompts, special characters, and newlines.
|
|
91
85
|
*/
|
|
92
86
|
private runClaude;
|
|
93
87
|
/**
|
|
94
88
|
* Parse Claude Code output to extract key information
|
|
95
89
|
*/
|
|
96
|
-
parseOutput(output: string):
|
|
97
|
-
summary?: string;
|
|
98
|
-
files?: string[];
|
|
99
|
-
prUrl?: string;
|
|
100
|
-
error?: string;
|
|
101
|
-
};
|
|
90
|
+
parseOutput(output: string): ParsedOutput;
|
|
102
91
|
/**
|
|
103
92
|
* Check if Claude Code CLI is available
|
|
104
93
|
*/
|
|
105
94
|
checkAvailable(): Promise<boolean>;
|
|
106
95
|
}
|
|
107
96
|
export declare const terminalClaudeExecutor: TerminalClaudeExecutor;
|
|
108
|
-
export {};
|
|
109
97
|
//# sourceMappingURL=terminal-claude.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"terminal-claude.d.ts","sourceRoot":"","sources":["../../src/executors/terminal-claude.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAMH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,8BAA8B,CAAA;
|
|
1
|
+
{"version":3,"file":"terminal-claude.d.ts","sourceRoot":"","sources":["../../src/executors/terminal-claude.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAMH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,8BAA8B,CAAA;AAC3D,OAAO,KAAK,EACV,gBAAgB,EAChB,mBAAmB,EAEnB,YAAY,EACZ,uBAAuB,EACxB,MAAM,oBAAoB,CAAA;AAG3B,YAAY,EAAE,uBAAuB,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAA;AAMtF,MAAM,WAAW,qBAAqB;IACpC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAMD;;;GAGG;AACH,cAAM,oBAAoB;IACxB,OAAO,CAAC,WAAW,CAAQ;IAC3B,OAAO,CAAC,QAAQ,CAAiC;IACjD,OAAO,CAAC,MAAM,CAAQ;;IAShB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAwBrB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAWrB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAK/D,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAO1E,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAOnD;AAGD,eAAO,MAAM,oBAAoB,sBAA6B,CAAA;AAM9D,qBAAa,sBAAuB,YAAW,gBAAgB;IAC7D,OAAO,CAAC,KAAK,CAAQ;IACrB,OAAO,CAAC,QAAQ,CAAQ;IACxB,OAAO,CAAC,OAAO,CAAQ;gBAEX,OAAO,GAAE,qBAA0B;IAO/C;;OAEG;IACG,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IAyCxF;;OAEG;IACH,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAkBhD;;OAEG;IACG,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAwB9D;;OAEG;IACH,oBAAoB,CAAC,QAAQ,CAAC,EAAE,mBAAmB,CAAC,UAAU,CAAC,GAAG,MAAM;IAWxE;;;;;;;;;OASG;IACG,WAAW,CACf,OAAO,EAAE,OAAO,EAChB,WAAW,CAAC,EAAE,MAAM,EACpB,OAAO,CAAC,EAAE,mBAAmB,GAC5B,OAAO,CAAC,MAAM,CAAC;IAclB;;OAEG;IACG,YAAY,CAChB,OAAO,EAAE,OAAO,EAChB,MAAM,CAAC,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,mBAAmB,EAC7B,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GACrC,OAAO,CAAC,uBAAuB,CAAC;IAqEnC;;OAEG;IACG,aAAa,CACjB,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,mBAAmB,EAC7B,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GACrC,OAAO,CAAC,uBAAuB,CAAC;IAsEnC;;;;;OAKG;IACH,OAAO,CAAC,SAAS;IAmKjB;;OAEG;IACH,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY;IAsCzC;;OAEG;IACG,cAAc,IAAI,OAAO,CAAC,OAAO,CAAC;CAezC;AAGD,eAAO,MAAM,sBAAsB,wBAA+B,CAAA"}
|
|
@@ -200,80 +200,86 @@ class TerminalClaudeExecutor {
|
|
|
200
200
|
}
|
|
201
201
|
/**
|
|
202
202
|
* Build prompt from task details
|
|
203
|
+
*
|
|
204
|
+
* IMPORTANT: Keep prompts SIMPLE! Claude Code CLI has a bug where complex
|
|
205
|
+
* markdown prompts with multiple sections cause it to hang indefinitely.
|
|
206
|
+
* Simple one-line prompts work reliably.
|
|
207
|
+
*
|
|
208
|
+
* Claude Code automatically reads CLAUDE.md from the working directory,
|
|
209
|
+
* so we don't need to include workflow instructions here.
|
|
203
210
|
*/
|
|
204
211
|
async buildPrompt(session, userMessage, context) {
|
|
205
212
|
if (userMessage) {
|
|
206
|
-
|
|
207
|
-
return
|
|
213
|
+
// For follow-up messages, just return the message directly
|
|
214
|
+
return userMessage;
|
|
208
215
|
}
|
|
209
|
-
//
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
}
|
|
214
|
-
const commentHistory = this.formatCommentHistory(context?.comments);
|
|
215
|
-
return `# Task: ${session.title}
|
|
216
|
-
|
|
217
|
-
${session.description || ''}
|
|
218
|
-
${commentHistory}
|
|
219
|
-
${projectContext}
|
|
220
|
-
|
|
221
|
-
## Workflow Requirements
|
|
222
|
-
|
|
223
|
-
1. Understand the task and verify the platform (iOS = ios-app/, Web = components/, app/)
|
|
224
|
-
2. Locate relevant code and make ONLY the requested changes
|
|
225
|
-
3. Run predeploy tests: \`npm run predeploy\`
|
|
226
|
-
4. Create PR: \`gh pr create\` with a clear title
|
|
227
|
-
|
|
228
|
-
## Output Requirements
|
|
229
|
-
|
|
230
|
-
Your response MUST include:
|
|
231
|
-
1. Task understanding: What was requested
|
|
232
|
-
2. Actual changes made: What you changed and WHY
|
|
233
|
-
3. Files modified: List each file path
|
|
234
|
-
4. Test results: Output from \`npm run predeploy\`
|
|
235
|
-
5. PR URL: The pull request URL (REQUIRED)`;
|
|
216
|
+
// Build a SIMPLE prompt - complex markdown causes Claude CLI to hang!
|
|
217
|
+
// Claude Code reads CLAUDE.md automatically for workflow instructions.
|
|
218
|
+
const description = session.description?.trim() || '';
|
|
219
|
+
const descPart = description ? `: ${description}` : '';
|
|
220
|
+
return `Complete this task: ${session.title}${descPart}`;
|
|
236
221
|
}
|
|
237
222
|
/**
|
|
238
|
-
* Start a new Claude Code session
|
|
223
|
+
* Start a new Claude Code session with retry logic
|
|
239
224
|
*/
|
|
240
225
|
async startSession(session, prompt, context, onProgress) {
|
|
241
226
|
const taskPrompt = prompt || await this.buildPrompt(session, undefined, context);
|
|
242
|
-
const args = [
|
|
243
|
-
'--print',
|
|
244
|
-
'--model', this.model,
|
|
245
|
-
'--max-turns', String(this.maxTurns),
|
|
246
|
-
'--output-format', 'text',
|
|
247
|
-
'--dangerously-skip-permissions',
|
|
248
|
-
];
|
|
249
227
|
// Truncate prompt if too long (avoid ARG_MAX issues)
|
|
250
228
|
const MAX_PROMPT_LENGTH = 50000;
|
|
251
229
|
let finalPrompt = taskPrompt;
|
|
252
230
|
if (taskPrompt.length > MAX_PROMPT_LENGTH) {
|
|
253
231
|
finalPrompt = taskPrompt.slice(0, MAX_PROMPT_LENGTH) + '\n\n[... prompt truncated ...]';
|
|
254
232
|
}
|
|
255
|
-
args.push('-p', finalPrompt);
|
|
256
233
|
console.log(`🚀 Starting new Claude Code session for task: ${session.title}`);
|
|
257
234
|
if (session.projectPath) {
|
|
258
235
|
console.log(`📁 Working directory: ${session.projectPath}`);
|
|
259
236
|
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
237
|
+
// Retry logic for intermittent Claude Code hangs
|
|
238
|
+
const MAX_RETRIES = 3;
|
|
239
|
+
let lastError;
|
|
240
|
+
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
241
|
+
if (attempt > 1) {
|
|
242
|
+
const delay = Math.min(1000 * Math.pow(2, attempt - 1), 10000);
|
|
243
|
+
console.log(`🔄 Retry attempt ${attempt}/${MAX_RETRIES} after ${delay}ms delay...`);
|
|
244
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
245
|
+
}
|
|
246
|
+
// Use stdin for prompt - more reliable than command-line args for long prompts
|
|
247
|
+
const args = [
|
|
248
|
+
'--print',
|
|
249
|
+
'--model', this.model,
|
|
250
|
+
'--output-format', 'text',
|
|
251
|
+
'--dangerously-skip-permissions',
|
|
252
|
+
];
|
|
253
|
+
const result = await this.runClaude(args, session, onProgress, finalPrompt);
|
|
254
|
+
// Check if we got actual output (not a timeout/hang)
|
|
255
|
+
if (result.stdout.length > 0 || result.exitCode === 0) {
|
|
256
|
+
// Store session ID for resumption
|
|
257
|
+
if (result.sessionId) {
|
|
258
|
+
await exports.terminalSessionStore.setClaudeSessionId(session.taskId, result.sessionId);
|
|
259
|
+
}
|
|
260
|
+
// Capture git changes
|
|
261
|
+
if (session.projectPath) {
|
|
262
|
+
const changes = await this.captureGitChanges(session.projectPath);
|
|
263
|
+
result.gitDiff = changes.diff;
|
|
264
|
+
result.modifiedFiles = changes.files;
|
|
265
|
+
}
|
|
266
|
+
// Extract PR URL
|
|
267
|
+
result.prUrl = this.extractPrUrl(result.stdout);
|
|
268
|
+
return result;
|
|
269
|
+
}
|
|
270
|
+
console.log(`⚠️ Attempt ${attempt} failed (no output received)`);
|
|
271
|
+
lastError = new Error(`No output received from Claude Code (attempt ${attempt})`);
|
|
270
272
|
}
|
|
271
|
-
//
|
|
272
|
-
|
|
273
|
-
return
|
|
273
|
+
// All retries failed, return empty result
|
|
274
|
+
console.error(`❌ All ${MAX_RETRIES} attempts failed`);
|
|
275
|
+
return {
|
|
276
|
+
exitCode: -1,
|
|
277
|
+
stdout: '',
|
|
278
|
+
stderr: lastError?.message || 'All retry attempts failed',
|
|
279
|
+
};
|
|
274
280
|
}
|
|
275
281
|
/**
|
|
276
|
-
* Resume an existing Claude Code session
|
|
282
|
+
* Resume an existing Claude Code session with retry logic
|
|
277
283
|
*/
|
|
278
284
|
async resumeSession(session, input, context, onProgress) {
|
|
279
285
|
// Get stored Claude session ID
|
|
@@ -283,36 +289,61 @@ Your response MUST include:
|
|
|
283
289
|
return this.startSession(session, input, context, onProgress);
|
|
284
290
|
}
|
|
285
291
|
const promptWithContext = await this.buildPrompt(session, input, context);
|
|
286
|
-
const args = [
|
|
287
|
-
'--print',
|
|
288
|
-
'--resume', claudeSessionId,
|
|
289
|
-
'--output-format', 'text',
|
|
290
|
-
'--dangerously-skip-permissions',
|
|
291
|
-
];
|
|
292
|
-
args.push('-p', promptWithContext);
|
|
293
292
|
console.log(`🔄 Resuming Claude Code session ${claudeSessionId}`);
|
|
294
293
|
if (session.projectPath) {
|
|
295
294
|
console.log(`📁 Working directory: ${session.projectPath}`);
|
|
296
295
|
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
296
|
+
// Retry logic for intermittent Claude Code hangs
|
|
297
|
+
const MAX_RETRIES = 3;
|
|
298
|
+
let lastError;
|
|
299
|
+
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
300
|
+
if (attempt > 1) {
|
|
301
|
+
const delay = Math.min(1000 * Math.pow(2, attempt - 1), 10000);
|
|
302
|
+
console.log(`🔄 Retry attempt ${attempt}/${MAX_RETRIES} after ${delay}ms delay...`);
|
|
303
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
304
|
+
}
|
|
305
|
+
// Use stdin for prompt - more reliable than command-line args
|
|
306
|
+
const args = [
|
|
307
|
+
'--print',
|
|
308
|
+
'--resume', claudeSessionId,
|
|
309
|
+
'--output-format', 'text',
|
|
310
|
+
'--dangerously-skip-permissions',
|
|
311
|
+
];
|
|
312
|
+
const result = await this.runClaude(args, session, onProgress, promptWithContext);
|
|
313
|
+
// Check if we got actual output
|
|
314
|
+
if (result.stdout.length > 0 || result.exitCode === 0) {
|
|
315
|
+
// Update session ID if we got a new one
|
|
316
|
+
if (result.sessionId) {
|
|
317
|
+
await exports.terminalSessionStore.setClaudeSessionId(session.taskId, result.sessionId);
|
|
318
|
+
}
|
|
319
|
+
// Capture git changes
|
|
320
|
+
if (session.projectPath) {
|
|
321
|
+
const changes = await this.captureGitChanges(session.projectPath);
|
|
322
|
+
result.gitDiff = changes.diff;
|
|
323
|
+
result.modifiedFiles = changes.files;
|
|
324
|
+
}
|
|
325
|
+
// Extract PR URL
|
|
326
|
+
result.prUrl = this.extractPrUrl(result.stdout);
|
|
327
|
+
return result;
|
|
328
|
+
}
|
|
329
|
+
console.log(`⚠️ Attempt ${attempt} failed (no output received)`);
|
|
330
|
+
lastError = new Error(`No output received from Claude Code (attempt ${attempt})`);
|
|
307
331
|
}
|
|
308
|
-
//
|
|
309
|
-
|
|
310
|
-
return
|
|
332
|
+
// All retries failed
|
|
333
|
+
console.error(`❌ All ${MAX_RETRIES} attempts failed`);
|
|
334
|
+
return {
|
|
335
|
+
exitCode: -1,
|
|
336
|
+
stdout: '',
|
|
337
|
+
stderr: lastError?.message || 'All retry attempts failed',
|
|
338
|
+
};
|
|
311
339
|
}
|
|
312
340
|
/**
|
|
313
341
|
* Execute Claude Code CLI
|
|
342
|
+
*
|
|
343
|
+
* Uses stdin for prompt input instead of command-line args to avoid
|
|
344
|
+
* issues with long prompts, special characters, and newlines.
|
|
314
345
|
*/
|
|
315
|
-
runClaude(args, session, onProgress) {
|
|
346
|
+
runClaude(args, session, onProgress, stdinInput) {
|
|
316
347
|
return new Promise((resolve, reject) => {
|
|
317
348
|
let stdout = '';
|
|
318
349
|
let stderr = '';
|
|
@@ -323,7 +354,11 @@ Your response MUST include:
|
|
|
323
354
|
let initialTimeoutHandle = null;
|
|
324
355
|
const INITIAL_TIMEOUT = 120000; // 2 minutes for first output
|
|
325
356
|
const STALL_TIMEOUT = 300000; // 5 minutes of no output = stalled
|
|
326
|
-
|
|
357
|
+
// Log command (without prompt for readability)
|
|
358
|
+
console.log(`🤖 Running: claude ${args.join(' ')}`);
|
|
359
|
+
if (stdinInput) {
|
|
360
|
+
console.log(`📝 Prompt via stdin: ${stdinInput.length} chars (first 100: ${stdinInput.slice(0, 100).replace(/\n/g, '\\n')}...)`);
|
|
361
|
+
}
|
|
327
362
|
console.log(`⏱️ Timeouts: initial=${INITIAL_TIMEOUT / 1000}s, stall=${STALL_TIMEOUT / 1000}s, max=${this.timeout / 1000}s`);
|
|
328
363
|
const proc = (0, child_process_1.spawn)('claude', args, {
|
|
329
364
|
cwd: session.projectPath || process.cwd(),
|
|
@@ -331,13 +366,20 @@ Your response MUST include:
|
|
|
331
366
|
...process.env,
|
|
332
367
|
CLAUDE_CODE_ENTRYPOINT: 'cli',
|
|
333
368
|
},
|
|
334
|
-
stdio: ['ignore', 'pipe', 'pipe']
|
|
369
|
+
stdio: [stdinInput ? 'pipe' : 'ignore', 'pipe', 'pipe']
|
|
335
370
|
});
|
|
336
371
|
console.log(`🚀 Claude process spawned with PID: ${proc.pid}`);
|
|
337
372
|
if (!proc.pid) {
|
|
338
373
|
reject(new Error('Failed to spawn Claude process'));
|
|
339
374
|
return;
|
|
340
375
|
}
|
|
376
|
+
// Write prompt to stdin if provided
|
|
377
|
+
if (stdinInput && proc.stdin) {
|
|
378
|
+
console.log(`📤 Writing prompt to stdin...`);
|
|
379
|
+
proc.stdin.write(stdinInput);
|
|
380
|
+
proc.stdin.end();
|
|
381
|
+
console.log(`✅ Stdin closed`);
|
|
382
|
+
}
|
|
341
383
|
// Heartbeat: log status every 30 seconds
|
|
342
384
|
heartbeatInterval = setInterval(() => {
|
|
343
385
|
const elapsed = Math.round((Date.now() - lastOutputTime) / 1000);
|
|
@@ -362,6 +404,11 @@ Your response MUST include:
|
|
|
362
404
|
if (initialTimeoutHandle)
|
|
363
405
|
clearTimeout(initialTimeoutHandle);
|
|
364
406
|
};
|
|
407
|
+
if (!proc.stdout || !proc.stderr) {
|
|
408
|
+
cleanup();
|
|
409
|
+
reject(new Error('Failed to get stdout/stderr pipes from Claude process'));
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
365
412
|
proc.stdout.on('data', (data) => {
|
|
366
413
|
const chunk = data.toString();
|
|
367
414
|
stdout += chunk;
|