mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-23 08:00:45 +00:00
feat(ai): show lint details on demand in the chat
The lint tool call rendered as a bare summary line with nothing behind it. Mark it as having details so the individual problems can be opened, and pass the formatted output as the tool result — without it the details panel reads "No result yet". Details stay collapsed on success, since the header already carries the counts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -245,6 +245,19 @@ vi.mock('./rawAppBundlerBridge', () => ({
|
||||
}))
|
||||
}))
|
||||
|
||||
// Monaco cannot load in the node test environment, and core.ts imports the lint
|
||||
// service lazily precisely to keep it out of this import graph.
|
||||
vi.mock('$lib/components/lint/headlessLint', () => ({
|
||||
canLintHeadless: (lang: string) => lang === 'bun',
|
||||
lintCode: vi.fn(async () => ({
|
||||
errorCount: 1,
|
||||
warningCount: 0,
|
||||
errors: [{ startLineNumber: 2, message: "Type 'string' is not assignable to type 'number'." }],
|
||||
warnings: [],
|
||||
contentMismatch: false
|
||||
}))
|
||||
}))
|
||||
|
||||
vi.mock('$lib/infer', async () => ({
|
||||
...(await vi.importActual<any>('$lib/infer')),
|
||||
// Avoid the wasm parser in unit tests: the script deploy path infers the arg
|
||||
@@ -433,6 +446,26 @@ describe('global AI tools', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('surfaces lint output as the tool result so the details panel can show it', async () => {
|
||||
vi.mocked(ScriptService.getScriptByPath).mockResolvedValueOnce({
|
||||
path: 'u/admin/probe',
|
||||
content: 'const a: number = "x"',
|
||||
language: 'bun',
|
||||
summary: ''
|
||||
} as any)
|
||||
|
||||
const result = await callGlobalTool('get_lint_errors', {
|
||||
kind: 'script',
|
||||
path: 'u/admin/probe'
|
||||
})
|
||||
|
||||
expect(result).toContain("Type 'string' is not assignable to type 'number'.")
|
||||
expect(toolCallbacks.setToolStatus).toHaveBeenCalledWith(
|
||||
'test-get_lint_errors',
|
||||
expect.objectContaining({ result })
|
||||
)
|
||||
})
|
||||
|
||||
it('defaults list_runs to 30 results when no limit is given', async () => {
|
||||
await callGlobalTool('list_runs', {})
|
||||
expect(JobService.listJobs).toHaveBeenCalledWith(
|
||||
|
||||
@@ -2751,6 +2751,9 @@ export const globalTools: Tool<{}>[] = [
|
||||
'get_lint_errors',
|
||||
'Type-check a script, flow module, or raw app backend runnable and report its errors and warnings. Supports TypeScript, JavaScript, Python, Go, Deno and Bash.'
|
||||
),
|
||||
// The header carries the counts; the individual problems are worth a look but not
|
||||
// worth the vertical space by default.
|
||||
showDetails: true,
|
||||
fn: async (ctx) => {
|
||||
const parsed = getLintErrorsSchema.parse(ctx.args)
|
||||
return getLintErrors(parsed, ctx)
|
||||
@@ -3812,12 +3815,20 @@ async function checkAppFrontend(path: string, ctx: WriteDraftCtx): Promise<strin
|
||||
const value = await loadAppValueForRead(path, workspace)
|
||||
try {
|
||||
await bundleRawAppDraft({ workspace, files: value.files })
|
||||
toolCallbacks.setToolStatus(toolId, { content: `Frontend of app "${path}" compiles` })
|
||||
return '✅ The app frontend compiles with no errors.'
|
||||
const response = '✅ The app frontend compiles with no errors.'
|
||||
toolCallbacks.setToolStatus(toolId, {
|
||||
content: `Frontend of app "${path}" compiles`,
|
||||
result: response
|
||||
})
|
||||
return response
|
||||
} catch (e) {
|
||||
const message = e instanceof Error ? e.message : String(e)
|
||||
toolCallbacks.setToolStatus(toolId, { content: `Frontend of app "${path}" failed to compile` })
|
||||
return `❌ The app frontend failed to compile:\n\n${message}`
|
||||
const response = `❌ The app frontend failed to compile:\n\n${message}`
|
||||
toolCallbacks.setToolStatus(toolId, {
|
||||
content: `Frontend of app "${path}" failed to compile`,
|
||||
result: response
|
||||
})
|
||||
return response
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3835,10 +3846,12 @@ async function getLintErrors(args: LintTargetArgs, ctx: WriteDraftCtx): Promise<
|
||||
const { canLintHeadless, lintCode } = await import('$lib/components/lint/headlessLint')
|
||||
|
||||
if (!canLintHeadless(target.language)) {
|
||||
const response = `Linting ${target.language} is not supported. Do not retry; verify the code by test-running it instead.`
|
||||
toolCallbacks.setToolStatus(toolId, {
|
||||
content: `Lint unavailable for ${target.language}`
|
||||
content: `Lint unavailable for ${target.language}`,
|
||||
result: response
|
||||
})
|
||||
return `Linting ${target.language} is not supported. Do not retry; verify the code by test-running it instead.`
|
||||
return response
|
||||
}
|
||||
|
||||
toolCallbacks.setToolStatus(toolId, { content: `Linting ${target.label}...` })
|
||||
@@ -3855,8 +3868,6 @@ async function getLintErrors(args: LintTargetArgs, ctx: WriteDraftCtx): Promise<
|
||||
: result.warningCount > 0
|
||||
? `${result.warningCount} warning(s)`
|
||||
: 'no issues'
|
||||
toolCallbacks.setToolStatus(toolId, { content: `Linted ${target.label}: ${summary}` })
|
||||
|
||||
let response = formatScriptLintResult(result)
|
||||
if (result.unavailableServers?.length) {
|
||||
response += `\n\nNote: the ${result.unavailableServers.join(' and ')} language server${result.unavailableServers.length > 1 ? 's did' : ' did'} not respond, so some problems may not be listed. Treat a clean result as inconclusive.`
|
||||
@@ -3864,6 +3875,11 @@ async function getLintErrors(args: LintTargetArgs, ctx: WriteDraftCtx): Promise<
|
||||
if (result.contentMismatch) {
|
||||
response += `\n\nNote: an editor is currently open on this code and its buffer differs from the draft. The results above are for what that editor shows.`
|
||||
}
|
||||
// The result has to reach the tool display, or its details panel reads "No result yet".
|
||||
toolCallbacks.setToolStatus(toolId, {
|
||||
content: `Linted ${target.label}: ${summary}`,
|
||||
result: response
|
||||
})
|
||||
return response
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user