mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-03 16:02:12 +00:00
fix: surface raw app build errors to the session AI (#11415)
* fix: surface raw app build errors to the session AI Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix: route app build state per preview and report pending builds Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix: name open previews when app logs are ambiguous, re-wait on retry Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
c1b59f70dd
commit
50ffad52d7
@@ -1189,7 +1189,13 @@ const getRuntimeLogsSchema = z.object({
|
||||
.min(1)
|
||||
.max(100)
|
||||
.optional()
|
||||
.describe('How many of the most recent runtime log lines to return. Defaults to 10.')
|
||||
.describe('How many of the most recent runtime log lines to return. Defaults to 10.'),
|
||||
app_path: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
'Path of the raw app whose preview to read. Pass it when several raw app previews are open; defaults to the visible one.'
|
||||
)
|
||||
})
|
||||
|
||||
const listAppRunsSchema = z.object({
|
||||
@@ -1466,6 +1472,7 @@ ${pipelineBullet}`
|
||||
- Building a data pipeline: call open_preview(kind="pipeline", path="<folder>") as the FIRST step, before creating any node — this opens the pipeline editor the user reviews in. path is the folder, not an item; an empty ${when(canCreateFolder, 'or not-yet-created ')}folder is fine${when(canCreateFolder, ' (create_folder first if needed, then open it)')}. Opening it registers build_pipeline_node / edit_pipeline_node — use ONLY those to add or change pipeline nodes, never write_script for a pipeline node — they apply directly as unsaved drafts on the canvas (no separate accept/reject step) that the user reviews and deploys. Do not write pipeline scripts without first opening the editor.`
|
||||
)}
|
||||
- When debugging a running raw app, call get_app_runtime_logs to read the live preview's browser console output. It needs the raw app preview open (open_preview kind="raw_app").
|
||||
- Writing an app file does not compile it: the open preview rebuilds it afterwards. After editing a raw app's frontend files with its preview open, call get_app_runtime_logs to check the build — when it failed, it returns the build errors (e.g. syntax or import errors) and bundler logs to fix.
|
||||
- To inspect what actually rendered in a running raw app (verify an edit landed on screen, diagnose a blank/empty or wrong view, answer "what's showing"), use search_dom (regex over the live HTML) and read_dom (a line-numbered window). Pass a \`selector\` to scope to an element — prefer the selector from a DOM element chip the user attached — or omit it for the whole page. When a chip lists an \`app_path\`, pass it too so the RIGHT app is read (several previews can be open; a query without \`app_path\` hits the visible one). The DOM is read live and is never in context; no match means the element isn't rendered. Both need the raw app preview open.
|
||||
- get_app_runtime_logs only shows the app's browser console. For the server-side logs of a backend runnable the app invoked (a backend.<id> call), call list_app_runs to get that run's job_id from the live preview, then get_run with it. Use this when a backend call errors or returns something unexpected.
|
||||
${
|
||||
@@ -4438,7 +4445,7 @@ export const globalTools: SessionTool<{}>[] = [
|
||||
def: createToolDef(
|
||||
getRuntimeLogsSchema,
|
||||
'get_app_runtime_logs',
|
||||
'Fetch the most recent browser console logs (and uncaught errors) from the raw app preview currently open in this AI session.'
|
||||
'Fetch the most recent browser console logs (and uncaught errors) from the raw app preview currently open in this AI session. Also reports the build: right after an edit it waits (up to 20s) for the rebuild, and when the build failed it returns the build error and bundler logs first.'
|
||||
),
|
||||
planModeSafe: true,
|
||||
showDetails: true,
|
||||
@@ -4446,7 +4453,11 @@ export const globalTools: SessionTool<{}>[] = [
|
||||
fn: async (ctx) => {
|
||||
const parsed = getRuntimeLogsSchema.parse(ctx.args)
|
||||
ctx.toolCallbacks.setToolStatus(ctx.toolId, { content: 'Reading app runtime logs...' })
|
||||
const result = await getSessionRuntimeLogs(parsed.limit ?? 10, sessionIdFromCtx(ctx))
|
||||
const result = await getSessionRuntimeLogs(
|
||||
parsed.limit ?? 10,
|
||||
sessionIdFromCtx(ctx),
|
||||
parsed.app_path
|
||||
)
|
||||
ctx.toolCallbacks.setToolStatus(ctx.toolId, {
|
||||
content: result.uiMessage,
|
||||
result: result.toolResult
|
||||
@@ -4810,6 +4821,7 @@ function closeSessionPreviewTabs(
|
||||
export type GetRuntimeLogsHandler = (req: {
|
||||
sessionId: string | undefined
|
||||
limit: number
|
||||
appPath?: string
|
||||
}) => Promise<SessionToolResult>
|
||||
|
||||
let getRuntimeLogsHandler: GetRuntimeLogsHandler | undefined
|
||||
@@ -4820,7 +4832,8 @@ export function setGetRuntimeLogsHandler(handler: GetRuntimeLogsHandler | undefi
|
||||
|
||||
function getSessionRuntimeLogs(
|
||||
limit: number,
|
||||
sessionId: string | undefined
|
||||
sessionId: string | undefined,
|
||||
appPath: string | undefined
|
||||
): Promise<SessionToolResult> {
|
||||
if (!getRuntimeLogsHandler) {
|
||||
return Promise.resolve({
|
||||
@@ -4830,7 +4843,7 @@ function getSessionRuntimeLogs(
|
||||
toolResult: 'Runtime logs unavailable'
|
||||
})
|
||||
}
|
||||
return getRuntimeLogsHandler({ sessionId, limit })
|
||||
return getRuntimeLogsHandler({ sessionId, limit, appPath })
|
||||
}
|
||||
|
||||
export type ListAppRunsHandler = (req: {
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
WMILL_TS_PATH,
|
||||
genWmillTs,
|
||||
normalizeRawAppRuntimeLogs,
|
||||
createRawAppBuildTracker,
|
||||
type Runnable,
|
||||
type RawAppRuntimeLogEntry,
|
||||
type RawAppRuntimeLogRequester,
|
||||
@@ -406,6 +407,9 @@
|
||||
|
||||
// Latest UI Builder error; cleared on next successful build.
|
||||
let buildError = $state<string | undefined>(undefined)
|
||||
const buildTracker = createRawAppBuildTracker()
|
||||
const PREVIEW_SETTLE_MS = 1000
|
||||
let editorDestroyed = false
|
||||
// Latest uncaught runtime error thrown by the rendered app; cleared on next build.
|
||||
let runtimeError = $state<string | undefined>(undefined)
|
||||
// Set when a build ran cleanly but never mounted anything into #root — the
|
||||
@@ -909,6 +913,7 @@
|
||||
if (!target) return
|
||||
iframeFiles = { ...newFiles }
|
||||
iframeFocusPending = undefined
|
||||
buildTracker.start()
|
||||
const files = Object.fromEntries(
|
||||
Object.entries(newFiles).filter(([path, _]) => !path.endsWith('/'))
|
||||
)
|
||||
@@ -928,6 +933,7 @@
|
||||
iframeFiles = { ...newFiles }
|
||||
const focus = iframeFocusPending === pathToSelect
|
||||
iframeFocusPending = undefined
|
||||
buildTracker.start()
|
||||
const files = Object.fromEntries(
|
||||
Object.entries(newFiles).filter(([path, _]) => !path.endsWith('/'))
|
||||
)
|
||||
@@ -1356,6 +1362,9 @@
|
||||
lastBuild = { css: e.data.css, js: e.data.js }
|
||||
feedPreviewIframe(lastBuild)
|
||||
syncExternalPreview()
|
||||
// Give the fed app a moment to run, so a console read after the wait sees its output.
|
||||
const gen = buildTracker.generation
|
||||
setTimeout(() => buildTracker.settle(gen), PREVIEW_SETTLE_MS)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1376,6 +1385,8 @@
|
||||
// `message: undefined` arrives on the next successful build and clears the banner.
|
||||
if (fromUiBuilder && e.data.type === 'buildError') {
|
||||
buildError = typeof e.data.message === 'string' ? e.data.message : undefined
|
||||
// A successful build settles once the preview has it (see `preview` above).
|
||||
if (buildError !== undefined) buildTracker.settle()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1713,7 +1724,20 @@
|
||||
pending.resolve(entries)
|
||||
}
|
||||
|
||||
const requestRuntimeLogs: RawAppRuntimeLogRequester = (limit) => {
|
||||
const requestRuntimeLogs: RawAppRuntimeLogRequester = async (limit) => {
|
||||
await buildTracker.wait()
|
||||
if (editorDestroyed) {
|
||||
return { entries: undefined, buildError: undefined, buildPending: false, buildLogs: '' }
|
||||
}
|
||||
return {
|
||||
entries: await requestPreviewConsoleLogs(limit),
|
||||
buildError,
|
||||
buildPending: buildTracker.pending,
|
||||
buildLogs: logs
|
||||
}
|
||||
}
|
||||
|
||||
function requestPreviewConsoleLogs(limit: number) {
|
||||
const win = previewIframe?.contentWindow
|
||||
if (!win || !previewIframeLoaded) return Promise.resolve(undefined)
|
||||
const requestId = randomUUID()
|
||||
@@ -2029,6 +2053,8 @@
|
||||
onScreenshotRequester?.(undefined)
|
||||
for (const requestId of Array.from(pendingRuntimeLogReqs.keys()))
|
||||
resolvePendingRuntimeLogRequest(requestId, undefined)
|
||||
editorDestroyed = true
|
||||
buildTracker.release()
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import {
|
||||
canonicalRawAppDiffValue,
|
||||
createRawAppBuildTracker,
|
||||
formatBuildFailureForChat,
|
||||
formatRuntimeLogsForChat,
|
||||
genWmillTs,
|
||||
normalizeRawAppRuntimeLogs,
|
||||
@@ -85,6 +87,58 @@ describe('normalizeRawAppRuntimeLogs', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('formatBuildFailureForChat', () => {
|
||||
it('reports the build error with the bundler log tail, without install noise', () => {
|
||||
const logs = [
|
||||
'Installing react …',
|
||||
'Using cached resolution for react@19.0.0: 19.0.0',
|
||||
'Resolved react@19.0.0',
|
||||
'Using idb cache for react@19.0.0 …',
|
||||
'[esbuild] Build started...',
|
||||
'[esbuild] Build failed: Build failed with 1 error:',
|
||||
'App.tsx:1:15: ERROR: Unexpected ";"'
|
||||
].join('\n')
|
||||
const report = formatBuildFailureForChat('App.tsx:1:15: ERROR: Unexpected ";"', logs)
|
||||
|
||||
expect(report).toContain('Build error:\nApp.tsx:1:15: ERROR: Unexpected ";"')
|
||||
expect(report).toContain('[esbuild] Build failed')
|
||||
expect(report).toContain('Installing react')
|
||||
expect(report).not.toContain('Using cached resolution')
|
||||
expect(report).not.toContain('Resolved react')
|
||||
})
|
||||
})
|
||||
|
||||
describe('createRawAppBuildTracker', () => {
|
||||
it('keeps a timed-out build pending until it settles', async () => {
|
||||
vi.useFakeTimers()
|
||||
try {
|
||||
const tracker = createRawAppBuildTracker(1000)
|
||||
tracker.start()
|
||||
const first = tracker.wait()
|
||||
await vi.advanceTimersByTimeAsync(1000)
|
||||
await first
|
||||
expect(tracker.pending).toBe(true)
|
||||
|
||||
let secondDone = false
|
||||
void tracker.wait().then(() => (secondDone = true))
|
||||
tracker.settle()
|
||||
await vi.advanceTimersByTimeAsync(0)
|
||||
expect(secondDone).toBe(true)
|
||||
expect(tracker.pending).toBe(false)
|
||||
} finally {
|
||||
vi.useRealTimers()
|
||||
}
|
||||
})
|
||||
|
||||
it('ignores a settle for a generation an edit has superseded', () => {
|
||||
const tracker = createRawAppBuildTracker(1000)
|
||||
const gen = tracker.start()
|
||||
tracker.start()
|
||||
tracker.settle(gen)
|
||||
expect(tracker.pending).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
// A deployed raw-app row as returned by getAppByPath: nested `value`, plus the
|
||||
// server-managed columns and a recomputed inline-script lock.
|
||||
function deployedRow() {
|
||||
|
||||
@@ -83,9 +83,71 @@ export type RawAppRuntimeLogEntry = {
|
||||
message: string
|
||||
ts: number
|
||||
}
|
||||
export type RawAppRuntimeLogRequester = (
|
||||
limit: number
|
||||
) => Promise<RawAppRuntimeLogEntry[] | undefined>
|
||||
export type RawAppPreviewLogs = {
|
||||
/** Console output of the rendered app; undefined when the preview document isn't loaded. */
|
||||
entries: RawAppRuntimeLogEntry[] | undefined
|
||||
/** Set while the latest UI Builder build failed. A failed build never reaches the preview
|
||||
* document, so its console cannot report it. */
|
||||
buildError: string | undefined
|
||||
/** Files were sent to the UI Builder and its build has not reported back in time. */
|
||||
buildPending: boolean
|
||||
buildLogs: string
|
||||
}
|
||||
export type RawAppRuntimeLogRequester = (limit: number) => Promise<RawAppPreviewLogs>
|
||||
|
||||
export const RAW_APP_BUILD_WAIT_MS = 20_000
|
||||
|
||||
/** Whether files sent to the UI Builder have produced a build result yet, so a read made
|
||||
* right after an edit waits for that edit's build instead of reporting the previous one.
|
||||
* The UI Builder carries no build id: when edits land while a build runs, the result of
|
||||
* the older build can settle the wait. */
|
||||
export function createRawAppBuildTracker(timeoutMs = RAW_APP_BUILD_WAIT_MS) {
|
||||
// Stays set after a timed-out wait: the UI Builder never builds an app with no
|
||||
// entrypoint, so a build may never report, and a later read must not call it done.
|
||||
let pending = false
|
||||
let generation = 0
|
||||
let waiters: (() => void)[] = []
|
||||
function release() {
|
||||
const toRelease = waiters
|
||||
waiters = []
|
||||
toRelease.forEach((w) => w())
|
||||
}
|
||||
return {
|
||||
get pending() {
|
||||
return pending
|
||||
},
|
||||
/** Files were sent for a build; returns the generation to pass to `settle`. */
|
||||
start(): number {
|
||||
pending = true
|
||||
return ++generation
|
||||
},
|
||||
/** A build reported. With `gen`, only settles if no edit was sent since. */
|
||||
settle(gen?: number) {
|
||||
if (gen !== undefined && gen !== generation) return
|
||||
pending = false
|
||||
release()
|
||||
},
|
||||
/** Ends every wait without settling, e.g. when the editor unmounts. */
|
||||
release,
|
||||
get generation() {
|
||||
return generation
|
||||
},
|
||||
wait(): Promise<void> {
|
||||
if (!pending) return Promise.resolve()
|
||||
return new Promise((resolve) => {
|
||||
const done = () => {
|
||||
clearTimeout(timer)
|
||||
resolve()
|
||||
}
|
||||
const timer = setTimeout(() => {
|
||||
waiters = waiters.filter((w) => w !== done)
|
||||
resolve()
|
||||
}, timeoutMs)
|
||||
waiters.push(done)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const RAW_APP_RUNTIME_LOG_LEVELS = new Set<RawAppRuntimeLogLevel>([
|
||||
'log',
|
||||
@@ -127,6 +189,34 @@ export function formatRuntimeLogsForChat(entries: RawAppRuntimeLogEntry[]): stri
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
const BUILD_LOG_TAIL_LINES = 40
|
||||
const BUILD_LOG_TAIL_CHARS = 6000
|
||||
// Per-package install progress: dozens of lines per build that would crowd the errors out of the tail.
|
||||
const ROUTINE_INSTALL_LOG_LINE = /^\s*(Using cached resolution|Using idb cache|Resolved \S+@|Extract(ing|ed) )/
|
||||
|
||||
export function formatBuildLogTailForChat(buildLogs: string): string {
|
||||
// The bundler log accumulates every install and build since the editor opened.
|
||||
const lines = buildLogs
|
||||
.split('\n')
|
||||
.filter((line) => line.trim() && !ROUTINE_INSTALL_LOG_LINE.test(line))
|
||||
.slice(-BUILD_LOG_TAIL_LINES)
|
||||
let tail = lines.join('\n')
|
||||
while (tail.length > BUILD_LOG_TAIL_CHARS && lines.length > 1) {
|
||||
lines.shift()
|
||||
tail = lines.join('\n')
|
||||
}
|
||||
tail = tail.slice(-BUILD_LOG_TAIL_CHARS)
|
||||
return tail ? `Recent bundler logs:\n${tail}` : 'No bundler logs yet.'
|
||||
}
|
||||
|
||||
export function formatBuildFailureForChat(buildError: string, buildLogs: string): string {
|
||||
return [
|
||||
'The app build FAILED, so the preview is not running the current code. Fix these errors in the frontend files, then call get_app_runtime_logs again to confirm the build passes.',
|
||||
`Build error:\n${buildError}`,
|
||||
formatBuildLogTailForChat(buildLogs)
|
||||
].join('\n\n')
|
||||
}
|
||||
|
||||
export type RawAppRunSummary = {
|
||||
job_id: string
|
||||
component: string
|
||||
|
||||
@@ -79,9 +79,17 @@
|
||||
invalidateWorkspaceDrafts(workspaceId)
|
||||
}
|
||||
|
||||
let runtimeLogRequester = $state<RawAppRuntimeLogRequester | undefined>(undefined)
|
||||
function registerRuntimeLogRequester(requester: RawAppRuntimeLogRequester | undefined) {
|
||||
runtime.setRuntimeLogRequester(requester)
|
||||
runtimeLogRequester = requester
|
||||
}
|
||||
$effect(() => {
|
||||
const p = path
|
||||
const r = runtimeLogRequester
|
||||
if (!r) return
|
||||
runtime.registerRuntimeLogRequester(p, r)
|
||||
return () => runtime.unregisterRuntimeLogRequester(p, r)
|
||||
})
|
||||
|
||||
function registerRunsProvider(provider: RawAppRunsProvider | undefined) {
|
||||
runtime.setAppRunsProvider(provider)
|
||||
|
||||
@@ -86,8 +86,11 @@ import {
|
||||
} from '$lib/components/copilot/chat/global/core'
|
||||
import {
|
||||
formatRuntimeLogsForChat,
|
||||
formatBuildFailureForChat,
|
||||
formatBuildLogTailForChat,
|
||||
RAW_APP_BUILD_WAIT_MS,
|
||||
formatAppRunsForChat,
|
||||
type RawAppRuntimeLogEntry,
|
||||
type RawAppPreviewLogs,
|
||||
type RawAppRuntimeLogRequester,
|
||||
type RawAppRunSummary,
|
||||
type RawAppRunsProvider,
|
||||
@@ -209,8 +212,16 @@ export interface SessionRuntime {
|
||||
force?: boolean,
|
||||
deployedOnly?: boolean
|
||||
): Promise<void>
|
||||
setRuntimeLogRequester(requester: RawAppRuntimeLogRequester | undefined): void
|
||||
requestRuntimeLogs(limit: number): Promise<RawAppRuntimeLogEntry[] | undefined>
|
||||
/** Register a mounted raw-app preview's log requester, keyed by app path, like
|
||||
* `registerDomRequester`: build state is per editor, so reads route to the app edited. */
|
||||
registerRuntimeLogRequester(appPath: string, requester: RawAppRuntimeLogRequester): void
|
||||
unregisterRuntimeLogRequester(appPath: string, requester: RawAppRuntimeLogRequester): void
|
||||
requestRuntimeLogs(
|
||||
limit: number,
|
||||
appPath?: string
|
||||
): Promise<
|
||||
RawAppPreviewLogs | { closedAppPath: string } | { ambiguousAppPaths: string[] } | undefined
|
||||
>
|
||||
/** Register a mounted raw-app preview's DOM requester, keyed by app path.
|
||||
* ALL mounted preview tabs register (hidden ones stay mounted), so a
|
||||
* DOM-scoped turn can read its own app even when another tab is visible. */
|
||||
@@ -571,7 +582,7 @@ function createRuntime(session: Session): SessionRuntime {
|
||||
// the pane unmounts on hide, and a component-local store would be discarded.
|
||||
const pipelineEditorState = new PipelineEditorState()
|
||||
|
||||
let runtimeLogRequester: RawAppRuntimeLogRequester | undefined = undefined
|
||||
const runtimeLogRequesters = new Map<string, RawAppRuntimeLogRequester>()
|
||||
// appPath → requester, one entry per mounted raw-app preview tab.
|
||||
const domRequesters = new Map<string, RawAppDomRequester>()
|
||||
let activeDomAppPath: string | undefined = undefined
|
||||
@@ -925,11 +936,24 @@ function createRuntime(session: Session): SessionRuntime {
|
||||
armRestartOnFirstInteraction(workspace, kind, path)
|
||||
},
|
||||
|
||||
setRuntimeLogRequester(requester) {
|
||||
runtimeLogRequester = requester
|
||||
registerRuntimeLogRequester(appPath, requester) {
|
||||
runtimeLogRequesters.set(appPath, requester)
|
||||
},
|
||||
async requestRuntimeLogs(limit) {
|
||||
return runtimeLogRequester ? runtimeLogRequester(limit) : undefined
|
||||
unregisterRuntimeLogRequester(appPath, requester) {
|
||||
if (runtimeLogRequesters.get(appPath) === requester) runtimeLogRequesters.delete(appPath)
|
||||
},
|
||||
async requestRuntimeLogs(limit, appPath) {
|
||||
const path =
|
||||
appPath ??
|
||||
activeDomAppPath ??
|
||||
(runtimeLogRequesters.size === 1 ? [...runtimeLogRequesters.keys()][0] : undefined)
|
||||
if (path === undefined) {
|
||||
return runtimeLogRequesters.size > 1
|
||||
? { ambiguousAppPaths: [...runtimeLogRequesters.keys()] }
|
||||
: undefined
|
||||
}
|
||||
const requester = runtimeLogRequesters.get(path)
|
||||
return requester ? requester(limit) : { closedAppPath: path }
|
||||
},
|
||||
registerDomRequester(appPath, requester) {
|
||||
domRequesters.set(appPath, requester)
|
||||
@@ -1286,7 +1310,7 @@ setDeployedInSessionHandler(({ sessionId: callerSessionId, kind, path }) => {
|
||||
runtime.syncPreviewWithDeployed(session.workspace_id, kind, path)
|
||||
})
|
||||
|
||||
setGetRuntimeLogsHandler(async ({ sessionId: callerSessionId, limit }) => {
|
||||
setGetRuntimeLogsHandler(async ({ sessionId: callerSessionId, limit, appPath }) => {
|
||||
const sessionId = callerSessionId ?? sessionState.currentSessionId
|
||||
const runtime = sessionId ? runtimes.get(sessionId) : undefined
|
||||
if (!runtime) {
|
||||
@@ -1297,7 +1321,46 @@ setGetRuntimeLogsHandler(async ({ sessionId: callerSessionId, limit }) => {
|
||||
toolResult: 'Runtime logs unavailable'
|
||||
}
|
||||
}
|
||||
const entries = await runtime.requestRuntimeLogs(limit)
|
||||
const previewLogs = await runtime.requestRuntimeLogs(limit, appPath)
|
||||
if (previewLogs && 'closedAppPath' in previewLogs) {
|
||||
return {
|
||||
aiResult: `The preview for "${previewLogs.closedAppPath}" is not open, so its logs can't be read. Call open_preview with kind="raw_app" and that path, then call get_app_runtime_logs again.`,
|
||||
uiMessage: 'Runtime logs unavailable',
|
||||
toolResult: 'Runtime logs unavailable'
|
||||
}
|
||||
}
|
||||
if (previewLogs && 'ambiguousAppPaths' in previewLogs) {
|
||||
return {
|
||||
aiResult: `Several raw app previews are open (${previewLogs.ambiguousAppPaths.join(', ')}) and none is visible. Call get_app_runtime_logs again with app_path set to the app you want.`,
|
||||
uiMessage: 'Runtime logs unavailable',
|
||||
toolResult: 'Runtime logs unavailable'
|
||||
}
|
||||
}
|
||||
// Checked before `buildError`: while a build is pending, that error belongs to the previous build.
|
||||
if (previewLogs?.buildPending) {
|
||||
const previous = previewLogs.buildError
|
||||
? `\n\nThe PREVIOUS build failed with:\n${previewLogs.buildError}`
|
||||
: ''
|
||||
return {
|
||||
aiResult:
|
||||
`The app is still building (or its build never started, e.g. no index entrypoint file) — no build result arrived within ${RAW_APP_BUILD_WAIT_MS / 1000} seconds, so the preview may still run an older build. Check the bundler logs below, wait, then call get_app_runtime_logs again.${previous}\n\n` +
|
||||
formatBuildLogTailForChat(previewLogs.buildLogs),
|
||||
uiMessage: 'App build still running',
|
||||
toolResult: 'App build still running'
|
||||
}
|
||||
}
|
||||
if (previewLogs?.buildError !== undefined) {
|
||||
const report = formatBuildFailureForChat(previewLogs.buildError, previewLogs.buildLogs)
|
||||
const consoleLogs = previewLogs.entries?.length
|
||||
? `\n\nConsole output of the last successful build still shown in the preview (stale until the build is fixed):\n${formatRuntimeLogsForChat(previewLogs.entries.slice(-limit))}`
|
||||
: ''
|
||||
return {
|
||||
aiResult: `${report}${consoleLogs}`,
|
||||
uiMessage: 'App build failed',
|
||||
toolResult: report
|
||||
}
|
||||
}
|
||||
const entries = previewLogs?.entries
|
||||
if (entries === undefined) {
|
||||
return {
|
||||
aiResult:
|
||||
|
||||
Reference in New Issue
Block a user