Skip to content

Commit 7d207f1

Browse files
committed
Fixing failing CI tests
1 parent 04af29d commit 7d207f1

7 files changed

Lines changed: 178 additions & 167 deletions

File tree

src/core/agent/ShellSuggestionProvider.ts

Lines changed: 6 additions & 148 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,10 @@
33
* Copyright 2025 Autohand AI LLC
44
* SPDX-License-Identifier: Apache-2.0
55
*/
6-
import fs from 'fs-extra';
7-
import path from 'node:path';
8-
import { execFile } from 'node:child_process';
9-
import { promisify } from 'node:util';
10-
import { parseShellCommand } from '../../ui/shellCommand.js';
11-
import { runWithConcurrency } from '../../utils/parallel.js';
6+
import { getPrimaryShellCommandSuggestion, parseShellCommand } from '../../ui/shellCommand.js';
127
import type { AgentRuntime, LLMMessage } from '../../types.js';
138
import type { LLMProvider } from '../../providers/LLMProvider.js';
149

15-
const execFileAsync = promisify(execFile);
16-
1710
interface ShellSuggestionConversation {
1811
history(): LLMMessage[];
1912
}
@@ -59,14 +52,10 @@ export function normalizeShellSuggestionFromLlm(raw: string, partialInput: strin
5952
}
6053

6154
export class ShellSuggestionProvider {
62-
private abortController: AbortController | null = null;
63-
private packageContextCache: { value: string; expiresAt: number } | null = null;
64-
6555
constructor(private readonly options: ShellSuggestionProviderOptions) {}
6656

6757
abort(): void {
68-
this.abortController?.abort();
69-
this.abortController = null;
58+
// Shell autocomplete is local and deterministic; no in-flight model work to abort.
7059
}
7160

7261
async resolve(inputLine: string): Promise<string | null> {
@@ -80,140 +69,9 @@ export class ShellSuggestionProvider {
8069
return null;
8170
}
8271

83-
this.abortController?.abort();
84-
const controller = new AbortController();
85-
this.abortController = controller;
86-
const timeout = setTimeout(() => controller.abort(), 1800);
87-
88-
try {
89-
const [packageContext, gitStatus] = await runWithConcurrency([
90-
{ label: 'package_context', run: async () => this.getPackageContext() },
91-
{ label: 'git_status', run: async () => this.getGitStatus() },
92-
], this.options.getParallelismLimit());
93-
94-
const recentHistory = this.options.conversation
95-
.history()
96-
.slice(-6)
97-
.map((message) => {
98-
const content = String(message.content ?? '')
99-
.replace(/\s+/g, ' ')
100-
.trim()
101-
.slice(0, 220);
102-
return `${message.role}: ${content}`;
103-
})
104-
.filter(Boolean)
105-
.join('\n');
106-
107-
const completion = await this.options.getLlm().complete({
108-
messages: [
109-
{
110-
role: 'system',
111-
content: [
112-
'You are a shell autocomplete engine for a coding CLI.',
113-
'Return exactly ONE shell command completion for the current partial command.',
114-
'Output only the command line, no quotes and no markdown.',
115-
'Must start with "! " and should extend the current partial input.',
116-
'Prefer commands valid for this repo package manager and scripts.',
117-
].join(' '),
118-
},
119-
{
120-
role: 'user',
121-
content: [
122-
`Current partial input: ${trimmedInput}`,
123-
packageContext ? `Package/dependency context:\n${packageContext}` : 'Package/dependency context: unavailable',
124-
gitStatus ? `Uncommitted changes context:\n${gitStatus}` : 'Uncommitted changes context: unavailable',
125-
recentHistory ? `Recent chat context:\n${recentHistory}` : 'Recent chat context: unavailable',
126-
].join('\n\n'),
127-
},
128-
],
129-
maxTokens: 80,
130-
temperature: 0.1,
131-
signal: controller.signal,
132-
});
133-
134-
if (controller.signal.aborted) {
135-
return null;
136-
}
137-
138-
return normalizeShellSuggestionFromLlm(completion.content, trimmedInput);
139-
} catch {
140-
return null;
141-
} finally {
142-
clearTimeout(timeout);
143-
if (this.abortController === controller) {
144-
this.abortController = null;
145-
}
146-
}
147-
}
148-
149-
private async getGitStatus(): Promise<string> {
150-
try {
151-
const { stdout } = await execFileAsync(
152-
'git',
153-
['status', '--short', '--branch'],
154-
{ cwd: this.options.runtime.workspaceRoot, encoding: 'utf8', timeout: 1200 },
155-
);
156-
return String(stdout || '').trim().slice(0, 1200);
157-
} catch {
158-
return '';
159-
}
160-
}
161-
162-
private async getPackageContext(): Promise<string> {
163-
const now = Date.now();
164-
if (this.packageContextCache && this.packageContextCache.expiresAt > now) {
165-
return this.packageContextCache.value;
166-
}
167-
168-
const root = this.options.runtime.workspaceRoot;
169-
const lines: string[] = [];
170-
const existenceChecks = [
171-
{ label: 'bun.lockb', paths: ['bun.lockb', 'bun.lock'], manager: 'bun' },
172-
{ label: 'pnpm-lock.yaml', paths: ['pnpm-lock.yaml'], manager: 'pnpm' },
173-
{ label: 'yarn.lock', paths: ['yarn.lock'], manager: 'yarn' },
174-
{ label: 'package-lock.json', paths: ['package-lock.json'], manager: 'npm' },
175-
{ label: 'python-lockfiles', paths: ['pyproject.toml', 'requirements.txt', 'Pipfile'], manager: 'python' },
176-
{ label: 'Cargo.toml', paths: ['Cargo.toml'], manager: 'cargo' },
177-
{ label: 'go.mod', paths: ['go.mod'], manager: 'go' },
178-
] as const;
179-
180-
const managerChecks = await runWithConcurrency(
181-
existenceChecks.map(({ label, paths, manager }) => ({
182-
label,
183-
run: async () => ({
184-
manager,
185-
present: (await Promise.all(paths.map((rel) => fs.pathExists(path.join(root, rel))))).some(Boolean),
186-
}),
187-
})),
188-
this.options.getParallelismLimit(),
189-
);
190-
191-
const managers = managerChecks
192-
.filter((entry) => entry.present)
193-
.map((entry) => entry.manager);
194-
195-
if (managers.length > 0) {
196-
lines.push(`Detected package managers: ${Array.from(new Set(managers)).join(', ')}`);
197-
}
198-
199-
try {
200-
const packageJsonPath = path.join(root, 'package.json');
201-
if (await fs.pathExists(packageJsonPath)) {
202-
const pkg = await fs.readJson(packageJsonPath) as { scripts?: Record<string, string> };
203-
const scripts = Object.keys(pkg.scripts ?? {});
204-
if (scripts.length > 0) {
205-
lines.push(`package.json scripts: ${scripts.slice(0, 20).join(', ')}`);
206-
}
207-
}
208-
} catch {
209-
// best effort
210-
}
211-
212-
const value = lines.join('\n');
213-
this.packageContextCache = {
214-
value,
215-
expiresAt: now + 30_000,
216-
};
217-
return value;
72+
const suggestion = getPrimaryShellCommandSuggestion(trimmedInput, {
73+
cwd: this.options.runtime.workspaceRoot,
74+
});
75+
return suggestion && suggestion !== trimmedInput ? suggestion : null;
21876
}
21977
}

0 commit comments

Comments
 (0)