@shipfox/client-logs 16.0.0 → 21.0.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/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-type.log +1 -0
- package/CHANGELOG.md +29 -0
- package/dist/components/agent-session-rows.d.ts +2 -1
- package/dist/components/agent-session-rows.d.ts.map +1 -1
- package/dist/components/agent-session-rows.js +33 -18
- package/dist/components/agent-session-rows.js.map +1 -1
- package/dist/components/log-group.d.ts +3 -2
- package/dist/components/log-group.d.ts.map +1 -1
- package/dist/components/log-group.js +12 -4
- package/dist/components/log-group.js.map +1 -1
- package/dist/components/log-view.d.ts +3 -1
- package/dist/components/log-view.d.ts.map +1 -1
- package/dist/components/log-view.js +76 -22
- package/dist/components/log-view.js.map +1 -1
- package/dist/components/output-log-row.d.ts.map +1 -1
- package/dist/components/output-log-row.js +1 -1
- package/dist/components/output-log-row.js.map +1 -1
- package/dist/components/system-markers.d.ts.map +1 -1
- package/dist/components/system-markers.js +9 -4
- package/dist/components/system-markers.js.map +1 -1
- package/dist/core/log-search.d.ts +7 -0
- package/dist/core/log-search.d.ts.map +1 -0
- package/dist/core/log-search.js +124 -0
- package/dist/core/log-search.js.map +1 -0
- package/dist/hooks/api/step-logs.d.ts +169 -1
- package/dist/hooks/api/step-logs.d.ts.map +1 -1
- package/dist/hooks/api/step-logs.js +23 -3
- package/dist/hooks/api/step-logs.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/components/agent-session-rows.tsx +35 -23
- package/src/components/log-group.tsx +18 -5
- package/src/components/log-view.test.tsx +152 -18
- package/src/components/log-view.tsx +83 -17
- package/src/components/output-log-row.tsx +5 -1
- package/src/components/system-markers.tsx +14 -4
- package/src/core/log-search.test.ts +96 -0
- package/src/core/log-search.ts +121 -0
- package/src/hooks/api/step-logs-query.test.tsx +42 -0
- package/src/hooks/api/step-logs.ts +19 -3
- package/tsconfig.build.tsbuildinfo +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/log-view.tsx"],"sourcesContent":["'use client';\n\nimport {Icon} from '@shipfox/react-ui/icon';\nimport {LogContent, LogRow, LogRows, type LogTimestampMode} from '@shipfox/react-ui/log';\nimport {Skeleton} from '@shipfox/react-ui/skeleton';\nimport {type ReactNode, type UIEventHandler, useEffect, useMemo, useRef} from 'react';\nimport type {LogRecord} from '#core/log-model.js';\nimport {\n assertNever,\n buildLogTree,\n type LogNode,\n type LogTree,\n type MarkerLogRecord,\n} from '#core/log-tree.js';\nimport {AgentSessionRows} from './agent-session-rows.js';\nimport {LogGroup} from './log-group.js';\nimport {OutputLogRow} from './output-log-row.js';\nimport {CappedMarker, EndMarker, GapMarker, RunnerLostMarker} from './system-markers.js';\n\nexport interface LogViewProps {\n records: readonly LogRecord[];\n timestamps?: LogTimestampMode;\n wrap?: boolean;\n showLineNumbers?: boolean;\n emptyState?: 'complete' | 'pending';\n defaultGroupsOpen?: boolean;\n anchorToFailure?: boolean;\n className?: string | undefined;\n onScroll?: UIEventHandler<HTMLDivElement> | undefined;\n}\n\nexport interface LogViewSkeletonProps\n extends Pick<LogViewProps, 'timestamps' | 'wrap' | 'showLineNumbers' | 'className'> {\n rows?: number;\n}\n\nexport function LogView({\n records,\n timestamps = 'off',\n wrap = false,\n showLineNumbers = true,\n emptyState = 'complete',\n defaultGroupsOpen = false,\n anchorToFailure = false,\n className,\n onScroll,\n}: LogViewProps) {\n const rowsRef = useRef<HTMLDivElement>(null);\n const tree = useMemo(() => buildLogTree(records), [records]);\n const resolvedToolCalls = useMemo(() => collectResolvedToolCalls(tree.nodes), [tree.nodes]);\n const noOutputState = getNoOutputState(tree, emptyState);\n const anchorRecordCount = records.length;\n\n useEffect(() => {\n if (!anchorToFailure) return;\n if (anchorRecordCount === 0) return;\n\n const frame = scheduleAnimationFrame(() => {\n const rows = rowsRef.current;\n if (!rows) return;\n\n const failure = rows.querySelector<HTMLElement>('[data-log-terminal-failure=\"true\"]');\n if (failure) {\n failure.scrollIntoView({block: 'center'});\n return;\n }\n\n rows.scrollTop = rows.scrollHeight;\n });\n\n return () => cancelScheduledFrame(frame);\n }, [anchorToFailure, anchorRecordCount]);\n\n return (\n <LogRows\n ref={rowsRef}\n timestamps={timestamps}\n wrap={wrap}\n showLineNumbers={showLineNumbers}\n className={className}\n onScroll={onScroll}\n {...(tree.originTs != null ? {timestampOrigin: new Date(tree.originTs)} : {})}\n >\n {noOutputState ? <NoOutputRow state={noOutputState} /> : null}\n {renderNodes(tree.nodes, 0, tree, defaultGroupsOpen, resolvedToolCalls)}\n </LogRows>\n );\n}\n\nexport function LogViewSkeleton({\n rows = 5,\n timestamps = 'off',\n wrap = false,\n showLineNumbers = true,\n className,\n}: LogViewSkeletonProps) {\n const widths = ['w-[62%]', 'w-[44%]', 'w-[74%]', 'w-[36%]', 'w-[55%]'];\n const skeletonRows = getSkeletonRows(rows);\n\n return (\n <LogRows\n timestamps={timestamps}\n wrap={wrap}\n showLineNumbers={showLineNumbers}\n className={className}\n role=\"presentation\"\n aria-live=\"off\"\n aria-hidden=\"true\"\n >\n {skeletonRows.map((row) => (\n <LogRow key={row.id} lineNumber={row.lineNumber}>\n <Skeleton\n className={`my-[4px] h-12 ${widths[(row.lineNumber - 1) % widths.length] ?? 'w-[48%]'}`}\n />\n </LogRow>\n ))}\n </LogRows>\n );\n}\n\nfunction getSkeletonRows(rows: number): {id: string; lineNumber: number}[] {\n return Array.from({length: rows}, (_, index) => {\n const lineNumber = index + 1;\n return {id: `log-view-skeleton-row-${lineNumber}`, lineNumber};\n });\n}\n\nfunction getNoOutputState(\n tree: LogTree,\n emptyState: NonNullable<LogViewProps['emptyState']>,\n): LogViewProps['emptyState'] | null {\n if (tree.nodes.length === 0) return emptyState;\n\n if (tree.lineCount !== 0) return null;\n if (tree.nodes.length !== 1) return null;\n\n const [node] = tree.nodes;\n if (node?.kind === 'marker' && node.record.type === 'end') return 'complete';\n\n return null;\n}\n\nfunction NoOutputRow({state}: {state: NonNullable<LogViewProps['emptyState']>}) {\n const copy =\n state === 'pending'\n ? {\n title: 'No output yet',\n detail: 'New lines will appear here as the step writes them.',\n }\n : {\n title: 'Step produced no output',\n detail: 'This log stream closed without session entries or process output.',\n };\n\n return (\n <LogRow lineNumber={null}>\n <LogContent className=\"text-foreground-neutral-muted\">\n <span className=\"inline-flex min-w-0 items-center gap-inline\">\n <Icon name=\"info\" className=\"size-14 flex-none\" aria-hidden=\"true\" />\n <span className=\"min-w-0\">\n <span className=\"font-medium\">{copy.title}</span>\n {' · '}\n <span className=\"text-foreground-neutral-subtle\">{copy.detail}</span>\n </span>\n </span>\n </LogContent>\n </LogRow>\n );\n}\n\nfunction renderNodes(\n nodes: readonly LogNode[],\n depth: number,\n tree: LogTree,\n defaultGroupsOpen: boolean,\n resolvedToolCalls: ResolvedToolCalls,\n): ReactNode[] {\n // `node.seq` is the stable, unique render key (see `LogNodeBase`): a concatenated\n // multi-step/retry stream can repeat a `group_id` or a marker's `(type, ts)` at one\n // level, which a key derived from those fields would collide on.\n return nodes.map((node): ReactNode => {\n switch (node.kind) {\n case 'output':\n return (\n <OutputLogRow\n key={node.seq}\n record={node.record}\n lineNumber={node.lineNumber}\n indent={depth}\n />\n );\n case 'group':\n return (\n <LogGroup\n key={node.seq}\n node={node}\n depth={depth}\n terminated={tree.terminated}\n defaultOpen={defaultGroupsOpen}\n >\n {renderNodes(node.children, depth + 1, tree, defaultGroupsOpen, resolvedToolCalls)}\n </LogGroup>\n );\n case 'marker':\n return <MarkerRow key={node.seq} record={node.record} tree={tree} />;\n case 'session':\n return (\n <AgentSessionRows\n key={node.seq}\n rows={[node.record.row]}\n resolvedToolCallIds={resolvedToolCalls.ids}\n toolCallNames={resolvedToolCalls.names}\n indent={depth}\n />\n );\n default:\n return assertNever(node);\n }\n });\n}\n\ninterface ResolvedToolCalls {\n ids: ReadonlySet<string>;\n names: ReadonlyMap<string, string>;\n}\n\nfunction collectResolvedToolCalls(nodes: readonly LogNode[]): ResolvedToolCalls {\n const ids = new Set<string>();\n const names = new Map<string, string>();\n collectResolvedToolCallsInto(nodes, ids, names);\n return {ids, names};\n}\n\nfunction collectResolvedToolCallsInto(\n nodes: readonly LogNode[],\n ids: Set<string>,\n names: Map<string, string>,\n): void {\n for (const node of nodes) {\n switch (node.kind) {\n case 'session':\n if (node.record.row.kind === 'tool-call' && node.record.row.id != null) {\n names.set(node.record.row.id, node.record.row.name);\n } else if (node.record.row.kind === 'tool-result' && node.record.row.toolCallId != null) {\n ids.add(node.record.row.toolCallId);\n }\n break;\n case 'group':\n collectResolvedToolCallsInto(node.children, ids, names);\n break;\n case 'output':\n case 'marker':\n break;\n default:\n assertNever(node);\n }\n }\n}\n\nfunction scheduleAnimationFrame(callback: FrameRequestCallback): number {\n if (typeof globalThis.requestAnimationFrame === 'function') {\n return globalThis.requestAnimationFrame(callback);\n }\n return window.setTimeout(() => callback(Date.now()), 0);\n}\n\nfunction cancelScheduledFrame(frame: number) {\n if (typeof globalThis.cancelAnimationFrame === 'function') {\n globalThis.cancelAnimationFrame(frame);\n return;\n }\n window.clearTimeout(frame);\n}\n\nfunction MarkerRow({record, tree}: {record: MarkerLogRecord; tree: LogTree}): ReactNode {\n switch (record.type) {\n case 'end':\n return (\n <EndMarker\n record={record}\n lineCount={tree.lineCount}\n durationMs={tree.originTs != null ? record.ts - tree.originTs : null}\n />\n );\n case 'gap':\n return <GapMarker record={record} />;\n case 'capped':\n return <CappedMarker record={record} />;\n case 'runner_lost':\n return <RunnerLostMarker record={record} />;\n default:\n return assertNever(record);\n }\n}\n"],"names":["Icon","LogContent","LogRow","LogRows","Skeleton","useEffect","useMemo","useRef","assertNever","buildLogTree","AgentSessionRows","LogGroup","OutputLogRow","CappedMarker","EndMarker","GapMarker","RunnerLostMarker","LogView","records","timestamps","wrap","showLineNumbers","emptyState","defaultGroupsOpen","anchorToFailure","className","onScroll","rowsRef","tree","resolvedToolCalls","collectResolvedToolCalls","nodes","noOutputState","getNoOutputState","anchorRecordCount","length","frame","scheduleAnimationFrame","rows","current","failure","querySelector","scrollIntoView","block","scrollTop","scrollHeight","cancelScheduledFrame","ref","originTs","timestampOrigin","Date","NoOutputRow","state","renderNodes","LogViewSkeleton","widths","skeletonRows","getSkeletonRows","role","aria-live","aria-hidden","map","row","lineNumber","id","Array","from","_","index","lineCount","node","kind","record","type","copy","title","detail","span","name","depth","indent","seq","terminated","defaultOpen","children","MarkerRow","resolvedToolCallIds","ids","toolCallNames","names","Set","Map","collectResolvedToolCallsInto","set","toolCallId","add","callback","globalThis","requestAnimationFrame","window","setTimeout","now","cancelAnimationFrame","clearTimeout","durationMs","ts"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/components/log-view.tsx"],"sourcesContent":["'use client';\n\nimport {Icon} from '@shipfox/react-ui/icon';\nimport {LogContent, LogRow, LogRows, type LogTimestampMode} from '@shipfox/react-ui/log';\nimport {Skeleton} from '@shipfox/react-ui/skeleton';\nimport {\n type ReactNode,\n type UIEventHandler,\n useDeferredValue,\n useEffect,\n useMemo,\n useRef,\n} from 'react';\nimport type {LogRecord} from '#core/log-model.js';\nimport {buildLogSearchIndex, filterLogNodes} from '#core/log-search.js';\nimport {\n assertNever,\n buildLogTree,\n type LogNode,\n type LogTree,\n type MarkerLogRecord,\n} from '#core/log-tree.js';\nimport {AgentSessionRows} from './agent-session-rows.js';\nimport {LogGroup} from './log-group.js';\nimport {OutputLogRow} from './output-log-row.js';\nimport {CappedMarker, EndMarker, GapMarker, RunnerLostMarker} from './system-markers.js';\n\nexport interface LogViewProps {\n records: readonly LogRecord[];\n timestamps?: LogTimestampMode;\n wrap?: boolean;\n showLineNumbers?: boolean;\n emptyState?: 'complete' | 'pending';\n defaultGroupsOpen?: boolean;\n anchorToFailure?: boolean;\n search?: string;\n ariaLive?: 'off' | 'polite' | 'assertive';\n className?: string | undefined;\n onScroll?: UIEventHandler<HTMLDivElement> | undefined;\n}\n\nexport interface LogViewSkeletonProps\n extends Pick<LogViewProps, 'timestamps' | 'wrap' | 'showLineNumbers' | 'className'> {\n rows?: number;\n}\n\nexport function LogView({\n records,\n timestamps = 'off',\n wrap = false,\n showLineNumbers = true,\n emptyState = 'complete',\n defaultGroupsOpen = false,\n anchorToFailure = false,\n search = '',\n ariaLive = 'polite',\n className,\n onScroll,\n}: LogViewProps) {\n const rowsRef = useRef<HTMLDivElement>(null);\n const tree = useMemo(() => buildLogTree(records), [records]);\n const deferredSearch = useDeferredValue(search);\n const normalizedSearch = deferredSearch.trim().toLowerCase();\n const searchIndex = useMemo(() => buildLogSearchIndex(tree.nodes), [tree.nodes]);\n const visibleNodes = useMemo(\n () =>\n normalizedSearch ? filterLogNodes(tree.nodes, normalizedSearch, searchIndex) : tree.nodes,\n [normalizedSearch, searchIndex, tree.nodes],\n );\n const resolvedToolCalls = useMemo(() => collectResolvedToolCalls(tree.nodes), [tree.nodes]);\n const noOutputState = normalizedSearch ? null : getNoOutputState(tree, emptyState);\n const anchorRecordCount = records.length;\n const searchStatus = normalizedSearch\n ? visibleNodes.length === 0\n ? `No log lines match “${deferredSearch.trim()}”.`\n : `Log search updated for “${deferredSearch.trim()}”.`\n : null;\n\n useEffect(() => {\n if (!anchorToFailure) return;\n if (anchorRecordCount === 0) return;\n\n const frame = scheduleAnimationFrame(() => {\n const rows = rowsRef.current;\n if (!rows) return;\n\n const failure = rows.querySelector<HTMLElement>('[data-log-terminal-failure=\"true\"]');\n if (failure) {\n failure.scrollIntoView({block: 'center'});\n return;\n }\n\n rows.scrollTop = rows.scrollHeight;\n });\n\n return () => cancelScheduledFrame(frame);\n }, [anchorToFailure, anchorRecordCount]);\n\n return (\n <>\n {searchStatus ? (\n <div role=\"status\" aria-live=\"polite\" aria-atomic=\"true\" className=\"sr-only\">\n {searchStatus}\n </div>\n ) : null}\n <LogRows\n ref={rowsRef}\n timestamps={timestamps}\n wrap={wrap}\n showLineNumbers={showLineNumbers}\n aria-live={normalizedSearch ? 'off' : ariaLive}\n className={className}\n onScroll={onScroll}\n {...(tree.originTs != null ? {timestampOrigin: new Date(tree.originTs)} : {})}\n >\n {noOutputState ? <NoOutputRow state={noOutputState} /> : null}\n {normalizedSearch && visibleNodes.length === 0 ? (\n <NoSearchMatchesRow query={deferredSearch.trim()} />\n ) : null}\n {renderNodes(\n visibleNodes,\n 0,\n tree,\n defaultGroupsOpen,\n Boolean(normalizedSearch),\n resolvedToolCalls,\n )}\n </LogRows>\n </>\n );\n}\n\nexport function LogViewSkeleton({\n rows = 5,\n timestamps = 'off',\n wrap = false,\n showLineNumbers = true,\n className,\n}: LogViewSkeletonProps) {\n const widths = ['w-[62%]', 'w-[44%]', 'w-[74%]', 'w-[36%]', 'w-[55%]'];\n const skeletonRows = getSkeletonRows(rows);\n\n return (\n <LogRows\n timestamps={timestamps}\n wrap={wrap}\n showLineNumbers={showLineNumbers}\n className={className}\n role=\"presentation\"\n aria-live=\"off\"\n aria-hidden=\"true\"\n >\n {skeletonRows.map((row) => (\n <LogRow key={row.id} lineNumber={row.lineNumber}>\n <Skeleton\n className={`my-[4px] h-12 ${widths[(row.lineNumber - 1) % widths.length] ?? 'w-[48%]'}`}\n />\n </LogRow>\n ))}\n </LogRows>\n );\n}\n\nfunction getSkeletonRows(rows: number): {id: string; lineNumber: number}[] {\n return Array.from({length: rows}, (_, index) => {\n const lineNumber = index + 1;\n return {id: `log-view-skeleton-row-${lineNumber}`, lineNumber};\n });\n}\n\nfunction getNoOutputState(\n tree: LogTree,\n emptyState: NonNullable<LogViewProps['emptyState']>,\n): LogViewProps['emptyState'] | null {\n if (tree.nodes.length === 0) return emptyState;\n\n if (tree.lineCount !== 0) return null;\n if (tree.nodes.length !== 1) return null;\n\n const [node] = tree.nodes;\n if (node?.kind === 'marker' && node.record.type === 'end') return 'complete';\n\n return null;\n}\n\nfunction NoOutputRow({state}: {state: NonNullable<LogViewProps['emptyState']>}) {\n const copy =\n state === 'pending'\n ? {\n title: 'No output yet',\n detail: 'New lines will appear here as the step writes them.',\n }\n : {\n title: 'Step produced no output',\n detail: 'This log stream closed without session entries or process output.',\n };\n\n return (\n <LogRow lineNumber={null}>\n <LogContent className=\"text-foreground-contrast-secondary\">\n <span className=\"inline-flex min-w-0 items-center gap-inline\">\n <Icon name=\"info\" className=\"size-14 flex-none\" aria-hidden=\"true\" />\n <span className=\"min-w-0\">\n <span className=\"font-medium\">{copy.title}</span>\n {' · '}\n <span className=\"text-foreground-contrast-secondary\">{copy.detail}</span>\n </span>\n </span>\n </LogContent>\n </LogRow>\n );\n}\n\nfunction NoSearchMatchesRow({query}: {query: string}) {\n return (\n <LogRow lineNumber={null}>\n <LogContent className=\"text-foreground-contrast-secondary\">\n <span className=\"inline-flex min-w-0 items-center gap-inline\">\n <Icon name=\"searchLine\" className=\"size-14 flex-none\" aria-hidden=\"true\" />\n <span>No log lines match “{query}”.</span>\n </span>\n </LogContent>\n </LogRow>\n );\n}\n\nfunction renderNodes(\n nodes: readonly LogNode[],\n depth: number,\n tree: LogTree,\n defaultGroupsOpen: boolean,\n forceOpen: boolean,\n resolvedToolCalls: ResolvedToolCalls,\n): ReactNode[] {\n // `node.seq` is the stable, unique render key (see `LogNodeBase`): a concatenated\n // multi-step/retry stream can repeat a `group_id` or a marker's `(type, ts)` at one\n // level, which a key derived from those fields would collide on.\n return nodes.map((node): ReactNode => {\n switch (node.kind) {\n case 'output':\n return (\n <OutputLogRow\n key={node.seq}\n record={node.record}\n lineNumber={node.lineNumber}\n indent={depth}\n />\n );\n case 'group':\n return (\n <LogGroup\n key={node.seq}\n node={node}\n depth={depth}\n terminated={tree.terminated}\n defaultOpen={defaultGroupsOpen}\n forceOpen={forceOpen}\n >\n {renderNodes(\n node.children,\n depth + 1,\n tree,\n defaultGroupsOpen,\n forceOpen,\n resolvedToolCalls,\n )}\n </LogGroup>\n );\n case 'marker':\n return <MarkerRow key={node.seq} record={node.record} tree={tree} />;\n case 'session':\n return (\n <AgentSessionRows\n key={node.seq}\n rows={[node.record.row]}\n resolvedToolCallIds={resolvedToolCalls.ids}\n toolCallNames={resolvedToolCalls.names}\n indent={depth}\n forceOpen={forceOpen}\n />\n );\n default:\n return assertNever(node);\n }\n });\n}\n\ninterface ResolvedToolCalls {\n ids: ReadonlySet<string>;\n names: ReadonlyMap<string, string>;\n}\n\nfunction collectResolvedToolCalls(nodes: readonly LogNode[]): ResolvedToolCalls {\n const ids = new Set<string>();\n const names = new Map<string, string>();\n collectResolvedToolCallsInto(nodes, ids, names);\n return {ids, names};\n}\n\nfunction collectResolvedToolCallsInto(\n nodes: readonly LogNode[],\n ids: Set<string>,\n names: Map<string, string>,\n): void {\n for (const node of nodes) {\n switch (node.kind) {\n case 'session':\n if (node.record.row.kind === 'tool-call' && node.record.row.id != null) {\n names.set(node.record.row.id, node.record.row.name);\n } else if (node.record.row.kind === 'tool-result' && node.record.row.toolCallId != null) {\n ids.add(node.record.row.toolCallId);\n }\n break;\n case 'group':\n collectResolvedToolCallsInto(node.children, ids, names);\n break;\n case 'output':\n case 'marker':\n break;\n default:\n assertNever(node);\n }\n }\n}\n\nfunction scheduleAnimationFrame(callback: FrameRequestCallback): number {\n if (typeof globalThis.requestAnimationFrame === 'function') {\n return globalThis.requestAnimationFrame(callback);\n }\n return window.setTimeout(() => callback(Date.now()), 0);\n}\n\nfunction cancelScheduledFrame(frame: number) {\n if (typeof globalThis.cancelAnimationFrame === 'function') {\n globalThis.cancelAnimationFrame(frame);\n return;\n }\n window.clearTimeout(frame);\n}\n\nfunction MarkerRow({record, tree}: {record: MarkerLogRecord; tree: LogTree}): ReactNode {\n switch (record.type) {\n case 'end':\n return (\n <EndMarker\n record={record}\n lineCount={tree.lineCount}\n durationMs={tree.originTs != null ? record.ts - tree.originTs : null}\n />\n );\n case 'gap':\n return <GapMarker record={record} />;\n case 'capped':\n return <CappedMarker record={record} />;\n case 'runner_lost':\n return <RunnerLostMarker record={record} />;\n default:\n return assertNever(record);\n }\n}\n"],"names":["Icon","LogContent","LogRow","LogRows","Skeleton","useDeferredValue","useEffect","useMemo","useRef","buildLogSearchIndex","filterLogNodes","assertNever","buildLogTree","AgentSessionRows","LogGroup","OutputLogRow","CappedMarker","EndMarker","GapMarker","RunnerLostMarker","LogView","records","timestamps","wrap","showLineNumbers","emptyState","defaultGroupsOpen","anchorToFailure","search","ariaLive","className","onScroll","rowsRef","tree","deferredSearch","normalizedSearch","trim","toLowerCase","searchIndex","nodes","visibleNodes","resolvedToolCalls","collectResolvedToolCalls","noOutputState","getNoOutputState","anchorRecordCount","length","searchStatus","frame","scheduleAnimationFrame","rows","current","failure","querySelector","scrollIntoView","block","scrollTop","scrollHeight","cancelScheduledFrame","div","role","aria-live","aria-atomic","ref","originTs","timestampOrigin","Date","NoOutputRow","state","NoSearchMatchesRow","query","renderNodes","Boolean","LogViewSkeleton","widths","skeletonRows","getSkeletonRows","aria-hidden","map","row","lineNumber","id","Array","from","_","index","lineCount","node","kind","record","type","copy","title","detail","span","name","depth","forceOpen","indent","seq","terminated","defaultOpen","children","MarkerRow","resolvedToolCallIds","ids","toolCallNames","names","Set","Map","collectResolvedToolCallsInto","set","toolCallId","add","callback","globalThis","requestAnimationFrame","window","setTimeout","now","cancelAnimationFrame","clearTimeout","durationMs","ts"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"output-log-row.d.ts","sourceRoot":"","sources":["../../src/components/output-log-row.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAC,KAAK,eAAe,EAAuB,MAAM,mBAAmB,CAAC;AAE7E,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,eAAe,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,wBAAgB,YAAY,CAAC,EAC3B,MAAM,EACN,UAAiB,EACjB,MAAU,EACV,QAAgB,GACjB,EAAE,iBAAiB,+
|
|
1
|
+
{"version":3,"file":"output-log-row.d.ts","sourceRoot":"","sources":["../../src/components/output-log-row.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAC,KAAK,eAAe,EAAuB,MAAM,mBAAmB,CAAC;AAE7E,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,eAAe,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,wBAAgB,YAAY,CAAC,EAC3B,MAAM,EACN,UAAiB,EACjB,MAAU,EACV,QAAgB,GACjB,EAAE,iBAAiB,+BAsBnB"}
|
|
@@ -15,7 +15,7 @@ export function OutputLogRow({ record, lineNumber = null, indent = 0, selected =
|
|
|
15
15
|
children: /*#__PURE__*/ _jsx(LogContent, {
|
|
16
16
|
variant: "code",
|
|
17
17
|
ansi: true,
|
|
18
|
-
className: cn(isStderr && 'text-foreground-
|
|
18
|
+
className: cn(isStderr && 'text-foreground-contrast-secondary'),
|
|
19
19
|
children: stripTrailingNewline(record.data)
|
|
20
20
|
})
|
|
21
21
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/output-log-row.tsx"],"sourcesContent":["import {LogContent, LogRow} from '@shipfox/react-ui/log';\nimport {cn} from '@shipfox/react-ui/utils';\nimport {type OutputLogRecord, stripTrailingNewline} from '#core/log-tree.js';\n\nexport interface OutputLogRowProps {\n record: OutputLogRecord;\n lineNumber?: number | null;\n indent?: number;\n selected?: boolean;\n}\n\nexport function OutputLogRow({\n record,\n lineNumber = null,\n indent = 0,\n selected = false,\n}: OutputLogRowProps) {\n const isStderr = record.stream === 'stderr';\n // Stderr uses a neutral channel rule; it is a stream, not a severity, so it never reads as an error.\n\n return (\n <LogRow\n lineNumber={lineNumber}\n timestamp={new Date(record.ts)}\n indent={indent}\n selected={selected}\n data-stream={record.stream}\n className={cn(isStderr && 'shadow-[inset_2px_0_0_var(--color-border-neutral-strong)]')}\n >\n <LogContent
|
|
1
|
+
{"version":3,"sources":["../../src/components/output-log-row.tsx"],"sourcesContent":["import {LogContent, LogRow} from '@shipfox/react-ui/log';\nimport {cn} from '@shipfox/react-ui/utils';\nimport {type OutputLogRecord, stripTrailingNewline} from '#core/log-tree.js';\n\nexport interface OutputLogRowProps {\n record: OutputLogRecord;\n lineNumber?: number | null;\n indent?: number;\n selected?: boolean;\n}\n\nexport function OutputLogRow({\n record,\n lineNumber = null,\n indent = 0,\n selected = false,\n}: OutputLogRowProps) {\n const isStderr = record.stream === 'stderr';\n // Stderr uses a neutral channel rule; it is a stream, not a severity, so it never reads as an error.\n\n return (\n <LogRow\n lineNumber={lineNumber}\n timestamp={new Date(record.ts)}\n indent={indent}\n selected={selected}\n data-stream={record.stream}\n className={cn(isStderr && 'shadow-[inset_2px_0_0_var(--color-border-neutral-strong)]')}\n >\n <LogContent\n variant=\"code\"\n ansi\n className={cn(isStderr && 'text-foreground-contrast-secondary')}\n >\n {stripTrailingNewline(record.data)}\n </LogContent>\n </LogRow>\n );\n}\n"],"names":["LogContent","LogRow","cn","stripTrailingNewline","OutputLogRow","record","lineNumber","indent","selected","isStderr","stream","timestamp","Date","ts","data-stream","className","variant","ansi","data"],"mappings":";AAAA,SAAQA,UAAU,EAAEC,MAAM,QAAO,wBAAwB;AACzD,SAAQC,EAAE,QAAO,0BAA0B;AAC3C,SAA8BC,oBAAoB,QAAO,oBAAoB;AAS7E,OAAO,SAASC,aAAa,EAC3BC,MAAM,EACNC,aAAa,IAAI,EACjBC,SAAS,CAAC,EACVC,WAAW,KAAK,EACE;IAClB,MAAMC,WAAWJ,OAAOK,MAAM,KAAK;IACnC,qGAAqG;IAErG,qBACE,KAACT;QACCK,YAAYA;QACZK,WAAW,IAAIC,KAAKP,OAAOQ,EAAE;QAC7BN,QAAQA;QACRC,UAAUA;QACVM,eAAaT,OAAOK,MAAM;QAC1BK,WAAWb,GAAGO,YAAY;kBAE1B,cAAA,KAACT;YACCgB,SAAQ;YACRC,IAAI;YACJF,WAAWb,GAAGO,YAAY;sBAEzBN,qBAAqBE,OAAOa,IAAI;;;AAIzC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"system-markers.d.ts","sourceRoot":"","sources":["../../src/components/system-markers.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,eAAe,EACf,YAAY,EACZ,YAAY,EACZ,mBAAmB,EACpB,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"system-markers.d.ts","sourceRoot":"","sources":["../../src/components/system-markers.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,eAAe,EACf,YAAY,EACZ,YAAY,EACZ,mBAAmB,EACpB,MAAM,mBAAmB,CAAC;AAuF3B,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,YAAY,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED,4GAA4G;AAC5G,wBAAgB,SAAS,CAAC,EAAC,MAAM,EAAE,SAAS,EAAE,UAAiB,EAAC,EAAE,cAAc,+BAY/E;AAED,oGAAoG;AACpG,wBAAgB,SAAS,CAAC,EAAC,MAAM,EAAC,EAAE;IAAC,MAAM,EAAE,YAAY,CAAA;CAAC,+BAWzD;AAED,mGAAmG;AACnG,wBAAgB,YAAY,CAAC,EAAC,MAAM,EAAC,EAAE;IAAC,MAAM,EAAE,eAAe,CAAA;CAAC,+BAW/D;AAED,uGAAuG;AACvG,wBAAgB,gBAAgB,CAAC,EAAC,MAAM,EAAC,EAAE;IAAC,MAAM,EAAE,mBAAmB,CAAA;CAAC,+BAYvE"}
|
|
@@ -3,9 +3,14 @@ import { Icon } from '@shipfox/react-ui/icon';
|
|
|
3
3
|
import { LogContent, LogRow } from '@shipfox/react-ui/log';
|
|
4
4
|
import { cn, formatBytes, formatDuration } from '@shipfox/react-ui/utils';
|
|
5
5
|
const toneText = {
|
|
6
|
-
default: 'text-foreground-
|
|
7
|
-
warning: 'text-
|
|
8
|
-
error: 'text-
|
|
6
|
+
default: 'text-foreground-contrast-secondary',
|
|
7
|
+
warning: 'text-foreground-contrast-primary',
|
|
8
|
+
error: 'text-foreground-contrast-primary'
|
|
9
|
+
};
|
|
10
|
+
const toneIcon = {
|
|
11
|
+
default: 'text-foreground-contrast-secondary',
|
|
12
|
+
warning: 'text-tag-warning-icon',
|
|
13
|
+
error: 'text-tag-error-icon'
|
|
9
14
|
};
|
|
10
15
|
/**
|
|
11
16
|
* Shared timeline-marker row: a non-numbered line with a leading icon, a bold label, an
|
|
@@ -26,7 +31,7 @@ const toneText = {
|
|
|
26
31
|
children: [
|
|
27
32
|
/*#__PURE__*/ _jsx(Icon, {
|
|
28
33
|
name: icon,
|
|
29
|
-
className:
|
|
34
|
+
className: cn('size-14 flex-none', toneIcon[tone]),
|
|
30
35
|
"aria-hidden": "true"
|
|
31
36
|
}),
|
|
32
37
|
/*#__PURE__*/ _jsxs("span", {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/system-markers.tsx"],"sourcesContent":["import {Icon, type IconName} from '@shipfox/react-ui/icon';\nimport {LogContent, LogRow} from '@shipfox/react-ui/log';\nimport {cn, formatBytes, formatDuration} from '@shipfox/react-ui/utils';\nimport type {\n CappedLogRecord,\n EndLogRecord,\n GapLogRecord,\n RunnerLostLogRecord,\n} from '#core/log-tree.js';\n\ntype MarkerTone = 'default' | 'warning' | 'error';\n\nconst toneText: Record<MarkerTone, string> = {\n default: 'text-foreground-
|
|
1
|
+
{"version":3,"sources":["../../src/components/system-markers.tsx"],"sourcesContent":["import {Icon, type IconName} from '@shipfox/react-ui/icon';\nimport {LogContent, LogRow} from '@shipfox/react-ui/log';\nimport {cn, formatBytes, formatDuration} from '@shipfox/react-ui/utils';\nimport type {\n CappedLogRecord,\n EndLogRecord,\n GapLogRecord,\n RunnerLostLogRecord,\n} from '#core/log-tree.js';\n\ntype MarkerTone = 'default' | 'warning' | 'error';\n\nconst toneText: Record<MarkerTone, string> = {\n default: 'text-foreground-contrast-secondary',\n warning: 'text-foreground-contrast-primary',\n error: 'text-foreground-contrast-primary',\n};\n\nconst toneIcon: Record<MarkerTone, string> = {\n default: 'text-foreground-contrast-secondary',\n warning: 'text-tag-warning-icon',\n error: 'text-tag-error-icon',\n};\n\ninterface LogMarkerRowProps {\n icon: IconName;\n tone: MarkerTone;\n timestamp?: Date | null;\n /** Plain-language clause after the label: what it means / what to do. */\n detail?: string;\n /** Right-aligned `font-code` figures (bytes, line count, duration). */\n meta?: string;\n terminalFailure?: boolean;\n children: string;\n}\n\n/**\n * Shared timeline-marker row: a non-numbered line with a leading icon, a bold label, an\n * optional plain-language detail clause, a dashed divider, and optional right-aligned\n * monospace figures. The detail explains the consequence to the operator (the label\n * names the event, the icon/tone carry severity), so the copy reads helpfully rather\n * than mechanically.\n */\nfunction LogMarkerRow({\n icon,\n tone,\n timestamp = null,\n detail,\n meta,\n terminalFailure = false,\n children,\n}: LogMarkerRowProps) {\n return (\n <LogRow\n lineNumber={null}\n timestamp={timestamp}\n tone={tone}\n data-log-terminal-failure={terminalFailure ? 'true' : undefined}\n >\n <LogContent className={cn('block', toneText[tone])}>\n <span className=\"inline-flex w-full items-center gap-inline\">\n <Icon\n name={icon}\n className={cn('size-14 flex-none', toneIcon[tone])}\n aria-hidden=\"true\"\n />\n {/* Label, detail, and figures share one text cluster joined by a literal\n \" · \". Flex `gap` is visual only and would copy with no separator, so the\n separators are real inline text. The dashed rule trails after the text\n (an aria-hidden flex filler) and contributes nothing to a selection. */}\n <span className=\"min-w-0\">\n <span className=\"font-medium\">{children}</span>\n {detail != null && (\n <>\n {' · '}\n <span className=\"font-normal opacity-80\">{detail}</span>\n </>\n )}\n {meta != null && (\n <>\n {' · '}\n <span className=\"font-code tabular-nums opacity-80\">{meta}</span>\n </>\n )}\n </span>\n <span\n aria-hidden=\"true\"\n className=\"h-px flex-1 border-t border-dashed border-current opacity-30\"\n />\n </span>\n </LogContent>\n </LogRow>\n );\n}\n\nexport interface EndMarkerProps {\n record: EndLogRecord;\n lineCount: number;\n durationMs?: number | null;\n}\n\n/** Clean end of the log: line count + output bytes (+ overall duration). `total_bytes` is payload bytes. */\nexport function EndMarker({record, lineCount, durationMs = null}: EndMarkerProps) {\n const meta = [\n `${lineCount} ${lineCount === 1 ? 'line' : 'lines'}`,\n formatBytes(record.totalBytes),\n ...(durationMs != null ? [formatDuration(durationMs)] : []),\n ].join(' · ');\n\n return (\n <LogMarkerRow icon=\"flagLine\" tone=\"default\" timestamp={new Date(record.ts)} meta={meta}>\n End of log\n </LogMarkerRow>\n );\n}\n\n/** The runner's local backlog shed bytes before upload, so some output never arrived. A warning. */\nexport function GapMarker({record}: {record: GapLogRecord}) {\n return (\n <LogMarkerRow\n icon=\"errorWarningLine\"\n tone=\"warning\"\n timestamp={new Date(record.ts)}\n detail={`the runner fell behind and dropped ${formatBytes(record.droppedBytes)}`}\n >\n Output missing\n </LogMarkerRow>\n );\n}\n\n/** The job hit its shared log size limit; logging stopped but the step kept running. A warning. */\nexport function CappedMarker({record}: {record: CappedLogRecord}) {\n return (\n <LogMarkerRow\n icon=\"forbidLine\"\n tone=\"warning\"\n timestamp={new Date(record.ts)}\n detail=\"later output isn't shown; the step kept running\"\n >\n Log size limit reached\n </LogMarkerRow>\n );\n}\n\n/** The runner disappeared and the stream was force-closed. A terminal failure (status taxonomy §9). */\nexport function RunnerLostMarker({record}: {record: RunnerLostLogRecord}) {\n return (\n <LogMarkerRow\n icon=\"closeCircleLine\"\n tone=\"error\"\n timestamp={new Date(record.ts)}\n detail=\"the log ends here and may be incomplete\"\n terminalFailure\n >\n Runner disconnected\n </LogMarkerRow>\n );\n}\n"],"names":["Icon","LogContent","LogRow","cn","formatBytes","formatDuration","toneText","default","warning","error","toneIcon","LogMarkerRow","icon","tone","timestamp","detail","meta","terminalFailure","children","lineNumber","data-log-terminal-failure","undefined","className","span","name","aria-hidden","EndMarker","record","lineCount","durationMs","totalBytes","join","Date","ts","GapMarker","droppedBytes","CappedMarker","RunnerLostMarker"],"mappings":";AAAA,SAAQA,IAAI,QAAsB,yBAAyB;AAC3D,SAAQC,UAAU,EAAEC,MAAM,QAAO,wBAAwB;AACzD,SAAQC,EAAE,EAAEC,WAAW,EAAEC,cAAc,QAAO,0BAA0B;AAUxE,MAAMC,WAAuC;IAC3CC,SAAS;IACTC,SAAS;IACTC,OAAO;AACT;AAEA,MAAMC,WAAuC;IAC3CH,SAAS;IACTC,SAAS;IACTC,OAAO;AACT;AAcA;;;;;;CAMC,GACD,SAASE,aAAa,EACpBC,IAAI,EACJC,IAAI,EACJC,YAAY,IAAI,EAChBC,MAAM,EACNC,IAAI,EACJC,kBAAkB,KAAK,EACvBC,QAAQ,EACU;IAClB,qBACE,KAAChB;QACCiB,YAAY;QACZL,WAAWA;QACXD,MAAMA;QACNO,6BAA2BH,kBAAkB,SAASI;kBAEtD,cAAA,KAACpB;YAAWqB,WAAWnB,GAAG,SAASG,QAAQ,CAACO,KAAK;sBAC/C,cAAA,MAACU;gBAAKD,WAAU;;kCACd,KAACtB;wBACCwB,MAAMZ;wBACNU,WAAWnB,GAAG,qBAAqBO,QAAQ,CAACG,KAAK;wBACjDY,eAAY;;kCAMd,MAACF;wBAAKD,WAAU;;0CACd,KAACC;gCAAKD,WAAU;0CAAeJ;;4BAC9BH,UAAU,sBACT;;oCACG;kDACD,KAACQ;wCAAKD,WAAU;kDAA0BP;;;;4BAG7CC,QAAQ,sBACP;;oCACG;kDACD,KAACO;wCAAKD,WAAU;kDAAqCN;;;;;;kCAI3D,KAACO;wBACCE,eAAY;wBACZH,WAAU;;;;;;AAMtB;AAQA,0GAA0G,GAC1G,OAAO,SAASI,UAAU,EAACC,MAAM,EAAEC,SAAS,EAAEC,aAAa,IAAI,EAAiB;IAC9E,MAAMb,OAAO;QACX,GAAGY,UAAU,CAAC,EAAEA,cAAc,IAAI,SAAS,SAAS;QACpDxB,YAAYuB,OAAOG,UAAU;WACzBD,cAAc,OAAO;YAACxB,eAAewB;SAAY,GAAG,EAAE;KAC3D,CAACE,IAAI,CAAC;IAEP,qBACE,KAACpB;QAAaC,MAAK;QAAWC,MAAK;QAAUC,WAAW,IAAIkB,KAAKL,OAAOM,EAAE;QAAGjB,MAAMA;kBAAM;;AAI7F;AAEA,kGAAkG,GAClG,OAAO,SAASkB,UAAU,EAACP,MAAM,EAAyB;IACxD,qBACE,KAAChB;QACCC,MAAK;QACLC,MAAK;QACLC,WAAW,IAAIkB,KAAKL,OAAOM,EAAE;QAC7BlB,QAAQ,CAAC,mCAAmC,EAAEX,YAAYuB,OAAOQ,YAAY,GAAG;kBACjF;;AAIL;AAEA,iGAAiG,GACjG,OAAO,SAASC,aAAa,EAACT,MAAM,EAA4B;IAC9D,qBACE,KAAChB;QACCC,MAAK;QACLC,MAAK;QACLC,WAAW,IAAIkB,KAAKL,OAAOM,EAAE;QAC7BlB,QAAO;kBACR;;AAIL;AAEA,qGAAqG,GACrG,OAAO,SAASsB,iBAAiB,EAACV,MAAM,EAAgC;IACtE,qBACE,KAAChB;QACCC,MAAK;QACLC,MAAK;QACLC,WAAW,IAAIkB,KAAKL,OAAOM,EAAE;QAC7BlB,QAAO;QACPE,eAAe;kBAChB;;AAIL"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type LogNode } from './log-tree.js';
|
|
2
|
+
export interface LogSearchIndex {
|
|
3
|
+
textBySeq: ReadonlyMap<number, string>;
|
|
4
|
+
}
|
|
5
|
+
export declare function buildLogSearchIndex(nodes: readonly LogNode[]): LogSearchIndex;
|
|
6
|
+
export declare function filterLogNodes(nodes: readonly LogNode[], query: string, index: LogSearchIndex): LogNode[];
|
|
7
|
+
//# sourceMappingURL=log-search.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log-search.d.ts","sourceRoot":"","sources":["../../src/core/log-search.ts"],"names":[],"mappings":"AACA,OAAO,EAAc,KAAK,OAAO,EAAuB,MAAM,eAAe,CAAC;AAI9E,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACxC;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,SAAS,OAAO,EAAE,GAAG,cAAc,CAI7E;AAED,wBAAgB,cAAc,CAC5B,KAAK,EAAE,SAAS,OAAO,EAAE,EACzB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,cAAc,GACpB,OAAO,EAAE,CAGX"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { assertNever, stripTrailingNewline } from './log-tree.js';
|
|
2
|
+
const ANSI_SGR_SEQUENCE = new RegExp(`${String.fromCodePoint(0x1b)}\\[[0-9;]*m`, 'g');
|
|
3
|
+
export function buildLogSearchIndex(nodes) {
|
|
4
|
+
const textBySeq = new Map();
|
|
5
|
+
indexNodes(nodes, textBySeq);
|
|
6
|
+
return {
|
|
7
|
+
textBySeq
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
export function filterLogNodes(nodes, query, index) {
|
|
11
|
+
const normalizedQuery = query.trim().toLowerCase();
|
|
12
|
+
return filterLogNodesInternal(nodes, normalizedQuery, index);
|
|
13
|
+
}
|
|
14
|
+
function filterLogNodesInternal(nodes, query, index) {
|
|
15
|
+
return nodes.flatMap((node)=>{
|
|
16
|
+
const matches = index.textBySeq.get(node.seq)?.includes(query) ?? false;
|
|
17
|
+
if (node.kind !== 'group') return matches ? [
|
|
18
|
+
node
|
|
19
|
+
] : [];
|
|
20
|
+
const children = matches ? node.children : filterLogNodesInternal(node.children, query, index);
|
|
21
|
+
if (!matches && children.length === 0) return [];
|
|
22
|
+
return [
|
|
23
|
+
{
|
|
24
|
+
...node,
|
|
25
|
+
children,
|
|
26
|
+
lineCount: matches ? node.lineCount : countOutputLines(children)
|
|
27
|
+
}
|
|
28
|
+
];
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
function indexNodes(nodes, textBySeq) {
|
|
32
|
+
for (const node of nodes){
|
|
33
|
+
textBySeq.set(node.seq, searchableNodeText(node).toLowerCase());
|
|
34
|
+
if (node.kind === 'group') indexNodes(node.children, textBySeq);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function searchableNodeText(node) {
|
|
38
|
+
switch(node.kind){
|
|
39
|
+
case 'output':
|
|
40
|
+
return stripAnsi(stripTrailingNewline(node.record.data));
|
|
41
|
+
case 'group':
|
|
42
|
+
return node.record.name;
|
|
43
|
+
case 'marker':
|
|
44
|
+
return markerText(node.record.type);
|
|
45
|
+
case 'session':
|
|
46
|
+
return sessionRowText(node.record.row);
|
|
47
|
+
default:
|
|
48
|
+
return assertNever(node);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function markerText(type) {
|
|
52
|
+
switch(type){
|
|
53
|
+
case 'end':
|
|
54
|
+
return 'End of log';
|
|
55
|
+
case 'gap':
|
|
56
|
+
return 'Output missing';
|
|
57
|
+
case 'capped':
|
|
58
|
+
return 'Log size limit reached';
|
|
59
|
+
case 'runner_lost':
|
|
60
|
+
return 'Runner disconnected';
|
|
61
|
+
default:
|
|
62
|
+
return assertNever(type);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function sessionRowText(row) {
|
|
66
|
+
switch(row.kind){
|
|
67
|
+
case 'message':
|
|
68
|
+
return stripAnsi([
|
|
69
|
+
row.label,
|
|
70
|
+
row.text,
|
|
71
|
+
...row.meta.flatMap((meta)=>[
|
|
72
|
+
meta.label,
|
|
73
|
+
meta.value
|
|
74
|
+
])
|
|
75
|
+
].join(' '));
|
|
76
|
+
case 'thinking':
|
|
77
|
+
return stripAnsi([
|
|
78
|
+
'thinking',
|
|
79
|
+
row.text
|
|
80
|
+
].join(' '));
|
|
81
|
+
case 'tool-call':
|
|
82
|
+
return stripAnsi([
|
|
83
|
+
'tool',
|
|
84
|
+
row.name,
|
|
85
|
+
row.summary,
|
|
86
|
+
row.input
|
|
87
|
+
].filter(Boolean).join(' '));
|
|
88
|
+
case 'tool-result':
|
|
89
|
+
return stripAnsi([
|
|
90
|
+
'result',
|
|
91
|
+
row.toolName,
|
|
92
|
+
row.output,
|
|
93
|
+
row.isError ? 'error' : 'ok'
|
|
94
|
+
].join(' '));
|
|
95
|
+
case 'lifecycle':
|
|
96
|
+
return stripAnsi([
|
|
97
|
+
row.label,
|
|
98
|
+
row.detail,
|
|
99
|
+
...row.meta.flatMap((meta)=>[
|
|
100
|
+
meta.label,
|
|
101
|
+
meta.value
|
|
102
|
+
])
|
|
103
|
+
].filter(Boolean).join(' '));
|
|
104
|
+
case 'raw':
|
|
105
|
+
return stripAnsi([
|
|
106
|
+
row.label,
|
|
107
|
+
row.raw
|
|
108
|
+
].join(' '));
|
|
109
|
+
default:
|
|
110
|
+
return assertNever(row);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function stripAnsi(value) {
|
|
114
|
+
return value.replace(ANSI_SGR_SEQUENCE, '');
|
|
115
|
+
}
|
|
116
|
+
function countOutputLines(nodes) {
|
|
117
|
+
return nodes.reduce((count, node)=>{
|
|
118
|
+
if (node.kind === 'output') return count + 1;
|
|
119
|
+
if (node.kind === 'group') return count + node.lineCount;
|
|
120
|
+
return count;
|
|
121
|
+
}, 0);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
//# sourceMappingURL=log-search.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/core/log-search.ts"],"sourcesContent":["import type {SessionViewRow} from './log-model.js';\nimport {assertNever, type LogNode, stripTrailingNewline} from './log-tree.js';\n\nconst ANSI_SGR_SEQUENCE = new RegExp(`${String.fromCodePoint(0x1b)}\\\\[[0-9;]*m`, 'g');\n\nexport interface LogSearchIndex {\n textBySeq: ReadonlyMap<number, string>;\n}\n\nexport function buildLogSearchIndex(nodes: readonly LogNode[]): LogSearchIndex {\n const textBySeq = new Map<number, string>();\n indexNodes(nodes, textBySeq);\n return {textBySeq};\n}\n\nexport function filterLogNodes(\n nodes: readonly LogNode[],\n query: string,\n index: LogSearchIndex,\n): LogNode[] {\n const normalizedQuery = query.trim().toLowerCase();\n return filterLogNodesInternal(nodes, normalizedQuery, index);\n}\n\nfunction filterLogNodesInternal(\n nodes: readonly LogNode[],\n query: string,\n index: LogSearchIndex,\n): LogNode[] {\n return nodes.flatMap((node): LogNode[] => {\n const matches = index.textBySeq.get(node.seq)?.includes(query) ?? false;\n if (node.kind !== 'group') return matches ? [node] : [];\n\n const children = matches ? node.children : filterLogNodesInternal(node.children, query, index);\n if (!matches && children.length === 0) return [];\n\n return [\n {\n ...node,\n children,\n lineCount: matches ? node.lineCount : countOutputLines(children),\n },\n ];\n });\n}\n\nfunction indexNodes(nodes: readonly LogNode[], textBySeq: Map<number, string>): void {\n for (const node of nodes) {\n textBySeq.set(node.seq, searchableNodeText(node).toLowerCase());\n if (node.kind === 'group') indexNodes(node.children, textBySeq);\n }\n}\n\nfunction searchableNodeText(node: LogNode): string {\n switch (node.kind) {\n case 'output':\n return stripAnsi(stripTrailingNewline(node.record.data));\n case 'group':\n return node.record.name;\n case 'marker':\n return markerText(node.record.type);\n case 'session':\n return sessionRowText(node.record.row);\n default:\n return assertNever(node);\n }\n}\n\nfunction markerText(type: 'end' | 'gap' | 'capped' | 'runner_lost'): string {\n switch (type) {\n case 'end':\n return 'End of log';\n case 'gap':\n return 'Output missing';\n case 'capped':\n return 'Log size limit reached';\n case 'runner_lost':\n return 'Runner disconnected';\n default:\n return assertNever(type);\n }\n}\n\nfunction sessionRowText(row: SessionViewRow): string {\n switch (row.kind) {\n case 'message':\n return stripAnsi(\n [row.label, row.text, ...row.meta.flatMap((meta) => [meta.label, meta.value])].join(' '),\n );\n case 'thinking':\n return stripAnsi(['thinking', row.text].join(' '));\n case 'tool-call':\n return stripAnsi(['tool', row.name, row.summary, row.input].filter(Boolean).join(' '));\n case 'tool-result':\n return stripAnsi(\n ['result', row.toolName, row.output, row.isError ? 'error' : 'ok'].join(' '),\n );\n case 'lifecycle':\n return stripAnsi(\n [row.label, row.detail, ...row.meta.flatMap((meta) => [meta.label, meta.value])]\n .filter(Boolean)\n .join(' '),\n );\n case 'raw':\n return stripAnsi([row.label, row.raw].join(' '));\n default:\n return assertNever(row);\n }\n}\n\nfunction stripAnsi(value: string): string {\n return value.replace(ANSI_SGR_SEQUENCE, '');\n}\n\nfunction countOutputLines(nodes: readonly LogNode[]): number {\n return nodes.reduce((count, node) => {\n if (node.kind === 'output') return count + 1;\n if (node.kind === 'group') return count + node.lineCount;\n return count;\n }, 0);\n}\n"],"names":["assertNever","stripTrailingNewline","ANSI_SGR_SEQUENCE","RegExp","String","fromCodePoint","buildLogSearchIndex","nodes","textBySeq","Map","indexNodes","filterLogNodes","query","index","normalizedQuery","trim","toLowerCase","filterLogNodesInternal","flatMap","node","matches","get","seq","includes","kind","children","length","lineCount","countOutputLines","set","searchableNodeText","stripAnsi","record","data","name","markerText","type","sessionRowText","row","label","text","meta","value","join","summary","input","filter","Boolean","toolName","output","isError","detail","raw","replace","reduce","count"],"mappings":"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"}
|
|
@@ -24,6 +24,174 @@ export interface UseStepAttemptLogsQueryOptions {
|
|
|
24
24
|
* when the step, attempt, or retry budget changes. Keep that lifecycle state here
|
|
25
25
|
* rather than exporting query options that could accidentally share it between views.
|
|
26
26
|
*/
|
|
27
|
-
export declare function useStepAttemptLogsQuery(stepId: string | undefined, attempt: number | undefined, options?: UseStepAttemptLogsQueryOptions):
|
|
27
|
+
export declare function useStepAttemptLogsQuery(stepId: string | undefined, attempt: number | undefined, options?: UseStepAttemptLogsQueryOptions): {
|
|
28
|
+
dataUpdatedAt: number;
|
|
29
|
+
errorUpdatedAt: number;
|
|
30
|
+
failureCount: number;
|
|
31
|
+
failureReason: Error | null;
|
|
32
|
+
errorUpdateCount: number;
|
|
33
|
+
isFetched: boolean;
|
|
34
|
+
isFetchedAfterMount: boolean;
|
|
35
|
+
isFetching: boolean;
|
|
36
|
+
isInitialLoading: boolean;
|
|
37
|
+
isPaused: boolean;
|
|
38
|
+
isRefetching: boolean;
|
|
39
|
+
isStale: boolean;
|
|
40
|
+
isEnabled: boolean;
|
|
41
|
+
refetch: (options?: import("@tanstack/react-query").RefetchOptions) => Promise<import("@tanstack/react-query").QueryObserverResult<NoInfer<StepLogSnapshot>, Error>>;
|
|
42
|
+
fetchStatus: import("@tanstack/react-query").FetchStatus;
|
|
43
|
+
promise: Promise<NoInfer<StepLogSnapshot>>;
|
|
44
|
+
data: undefined;
|
|
45
|
+
error: Error;
|
|
46
|
+
isError: true;
|
|
47
|
+
isPending: false;
|
|
48
|
+
isLoading: false;
|
|
49
|
+
isLoadingError: true;
|
|
50
|
+
isRefetchError: false;
|
|
51
|
+
isSuccess: false;
|
|
52
|
+
isPlaceholderData: false;
|
|
53
|
+
status: 'error';
|
|
54
|
+
refetchLogs: () => Promise<import("@tanstack/react-query").QueryObserverResult<NoInfer<StepLogSnapshot>, Error>>;
|
|
55
|
+
} | {
|
|
56
|
+
dataUpdatedAt: number;
|
|
57
|
+
errorUpdatedAt: number;
|
|
58
|
+
failureCount: number;
|
|
59
|
+
failureReason: Error | null;
|
|
60
|
+
errorUpdateCount: number;
|
|
61
|
+
isFetched: boolean;
|
|
62
|
+
isFetchedAfterMount: boolean;
|
|
63
|
+
isFetching: boolean;
|
|
64
|
+
isInitialLoading: boolean;
|
|
65
|
+
isPaused: boolean;
|
|
66
|
+
isRefetching: boolean;
|
|
67
|
+
isStale: boolean;
|
|
68
|
+
isEnabled: boolean;
|
|
69
|
+
refetch: (options?: import("@tanstack/react-query").RefetchOptions) => Promise<import("@tanstack/react-query").QueryObserverResult<NoInfer<StepLogSnapshot>, Error>>;
|
|
70
|
+
fetchStatus: import("@tanstack/react-query").FetchStatus;
|
|
71
|
+
promise: Promise<NoInfer<StepLogSnapshot>>;
|
|
72
|
+
data: undefined;
|
|
73
|
+
error: null;
|
|
74
|
+
isError: false;
|
|
75
|
+
isPending: true;
|
|
76
|
+
isLoading: true;
|
|
77
|
+
isLoadingError: false;
|
|
78
|
+
isRefetchError: false;
|
|
79
|
+
isSuccess: false;
|
|
80
|
+
isPlaceholderData: false;
|
|
81
|
+
status: 'pending';
|
|
82
|
+
refetchLogs: () => Promise<import("@tanstack/react-query").QueryObserverResult<NoInfer<StepLogSnapshot>, Error>>;
|
|
83
|
+
} | {
|
|
84
|
+
dataUpdatedAt: number;
|
|
85
|
+
errorUpdatedAt: number;
|
|
86
|
+
failureCount: number;
|
|
87
|
+
failureReason: Error | null;
|
|
88
|
+
errorUpdateCount: number;
|
|
89
|
+
isFetched: boolean;
|
|
90
|
+
isFetchedAfterMount: boolean;
|
|
91
|
+
isFetching: boolean;
|
|
92
|
+
isLoading: boolean;
|
|
93
|
+
isInitialLoading: boolean;
|
|
94
|
+
isPaused: boolean;
|
|
95
|
+
isRefetching: boolean;
|
|
96
|
+
isStale: boolean;
|
|
97
|
+
isEnabled: boolean;
|
|
98
|
+
refetch: (options?: import("@tanstack/react-query").RefetchOptions) => Promise<import("@tanstack/react-query").QueryObserverResult<NoInfer<StepLogSnapshot>, Error>>;
|
|
99
|
+
fetchStatus: import("@tanstack/react-query").FetchStatus;
|
|
100
|
+
promise: Promise<NoInfer<StepLogSnapshot>>;
|
|
101
|
+
data: undefined;
|
|
102
|
+
error: null;
|
|
103
|
+
isError: false;
|
|
104
|
+
isPending: true;
|
|
105
|
+
isLoadingError: false;
|
|
106
|
+
isRefetchError: false;
|
|
107
|
+
isSuccess: false;
|
|
108
|
+
isPlaceholderData: false;
|
|
109
|
+
status: 'pending';
|
|
110
|
+
refetchLogs: () => Promise<import("@tanstack/react-query").QueryObserverResult<NoInfer<StepLogSnapshot>, Error>>;
|
|
111
|
+
} | {
|
|
112
|
+
dataUpdatedAt: number;
|
|
113
|
+
errorUpdatedAt: number;
|
|
114
|
+
failureCount: number;
|
|
115
|
+
failureReason: Error | null;
|
|
116
|
+
errorUpdateCount: number;
|
|
117
|
+
isFetched: boolean;
|
|
118
|
+
isFetchedAfterMount: boolean;
|
|
119
|
+
isFetching: boolean;
|
|
120
|
+
isInitialLoading: boolean;
|
|
121
|
+
isPaused: boolean;
|
|
122
|
+
isRefetching: boolean;
|
|
123
|
+
isStale: boolean;
|
|
124
|
+
isEnabled: boolean;
|
|
125
|
+
refetch: (options?: import("@tanstack/react-query").RefetchOptions) => Promise<import("@tanstack/react-query").QueryObserverResult<NoInfer<StepLogSnapshot>, Error>>;
|
|
126
|
+
fetchStatus: import("@tanstack/react-query").FetchStatus;
|
|
127
|
+
promise: Promise<NoInfer<StepLogSnapshot>>;
|
|
128
|
+
data: NoInfer<StepLogSnapshot>;
|
|
129
|
+
isError: false;
|
|
130
|
+
error: null;
|
|
131
|
+
isPending: false;
|
|
132
|
+
isLoading: false;
|
|
133
|
+
isLoadingError: false;
|
|
134
|
+
isRefetchError: false;
|
|
135
|
+
isSuccess: true;
|
|
136
|
+
isPlaceholderData: true;
|
|
137
|
+
status: 'success';
|
|
138
|
+
refetchLogs: () => Promise<import("@tanstack/react-query").QueryObserverResult<NoInfer<StepLogSnapshot>, Error>>;
|
|
139
|
+
} | {
|
|
140
|
+
dataUpdatedAt: number;
|
|
141
|
+
errorUpdatedAt: number;
|
|
142
|
+
failureCount: number;
|
|
143
|
+
failureReason: Error | null;
|
|
144
|
+
errorUpdateCount: number;
|
|
145
|
+
isFetched: boolean;
|
|
146
|
+
isFetchedAfterMount: boolean;
|
|
147
|
+
isFetching: boolean;
|
|
148
|
+
isInitialLoading: boolean;
|
|
149
|
+
isPaused: boolean;
|
|
150
|
+
isRefetching: boolean;
|
|
151
|
+
isStale: boolean;
|
|
152
|
+
isEnabled: boolean;
|
|
153
|
+
refetch: (options?: import("@tanstack/react-query").RefetchOptions) => Promise<import("@tanstack/react-query").QueryObserverResult<NoInfer<StepLogSnapshot>, Error>>;
|
|
154
|
+
fetchStatus: import("@tanstack/react-query").FetchStatus;
|
|
155
|
+
promise: Promise<NoInfer<StepLogSnapshot>>;
|
|
156
|
+
data: NoInfer<StepLogSnapshot>;
|
|
157
|
+
error: Error;
|
|
158
|
+
isError: true;
|
|
159
|
+
isPending: false;
|
|
160
|
+
isLoading: false;
|
|
161
|
+
isLoadingError: false;
|
|
162
|
+
isRefetchError: true;
|
|
163
|
+
isSuccess: false;
|
|
164
|
+
isPlaceholderData: false;
|
|
165
|
+
status: 'error';
|
|
166
|
+
refetchLogs: () => Promise<import("@tanstack/react-query").QueryObserverResult<NoInfer<StepLogSnapshot>, Error>>;
|
|
167
|
+
} | {
|
|
168
|
+
dataUpdatedAt: number;
|
|
169
|
+
errorUpdatedAt: number;
|
|
170
|
+
failureCount: number;
|
|
171
|
+
failureReason: Error | null;
|
|
172
|
+
errorUpdateCount: number;
|
|
173
|
+
isFetched: boolean;
|
|
174
|
+
isFetchedAfterMount: boolean;
|
|
175
|
+
isFetching: boolean;
|
|
176
|
+
isInitialLoading: boolean;
|
|
177
|
+
isPaused: boolean;
|
|
178
|
+
isRefetching: boolean;
|
|
179
|
+
isStale: boolean;
|
|
180
|
+
isEnabled: boolean;
|
|
181
|
+
refetch: (options?: import("@tanstack/react-query").RefetchOptions) => Promise<import("@tanstack/react-query").QueryObserverResult<NoInfer<StepLogSnapshot>, Error>>;
|
|
182
|
+
fetchStatus: import("@tanstack/react-query").FetchStatus;
|
|
183
|
+
promise: Promise<NoInfer<StepLogSnapshot>>;
|
|
184
|
+
data: NoInfer<StepLogSnapshot>;
|
|
185
|
+
error: null;
|
|
186
|
+
isError: false;
|
|
187
|
+
isPending: false;
|
|
188
|
+
isLoading: false;
|
|
189
|
+
isLoadingError: false;
|
|
190
|
+
isRefetchError: false;
|
|
191
|
+
isSuccess: true;
|
|
192
|
+
isPlaceholderData: false;
|
|
193
|
+
status: 'success';
|
|
194
|
+
refetchLogs: () => Promise<import("@tanstack/react-query").QueryObserverResult<NoInfer<StepLogSnapshot>, Error>>;
|
|
195
|
+
};
|
|
28
196
|
export {};
|
|
29
197
|
//# sourceMappingURL=step-logs.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"step-logs.d.ts","sourceRoot":"","sources":["../../../src/hooks/api/step-logs.ts"],"names":[],"mappings":"AAIA,OAAO,EAGL,KAAK,eAAe,EAErB,MAAM,mBAAmB,CAAC;AAG3B,eAAO,MAAM,iBAAiB;IAC5B,GAAG,YAAG,WAAW;IACjB,MAAM,WAAW,MAAM,WAAW,MAAM;CAEzC,CAAC;AAEF,UAAU,6BAA6B;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,wBAAsB,uBAAuB,CAAC,EAC5C,MAAM,EACN,OAAO,EACP,MAAM,EACN,MAAM,GACP,EAAE,6BAA6B,iHAQ/B;AAkBD,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAEnE;AAED,MAAM,WAAW,8BAA8B;IAC7C,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,uBAAuB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7C,yBAAyB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/C,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,wBAAwB,CAAC,EAAE,MAAM,CAAC;CACnC;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,OAAO,GAAE,8BAAmC
|
|
1
|
+
{"version":3,"file":"step-logs.d.ts","sourceRoot":"","sources":["../../../src/hooks/api/step-logs.ts"],"names":[],"mappings":"AAIA,OAAO,EAGL,KAAK,eAAe,EAErB,MAAM,mBAAmB,CAAC;AAG3B,eAAO,MAAM,iBAAiB;IAC5B,GAAG,YAAG,WAAW;IACjB,MAAM,WAAW,MAAM,WAAW,MAAM;CAEzC,CAAC;AAEF,UAAU,6BAA6B;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,wBAAsB,uBAAuB,CAAC,EAC5C,MAAM,EACN,OAAO,EACP,MAAM,EACN,MAAM,GACP,EAAE,6BAA6B,iHAQ/B;AAkBD,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAEnE;AAED,MAAM,WAAW,8BAA8B;IAC7C,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,uBAAuB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7C,yBAAyB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/C,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,wBAAwB,CAAC,EAAE,MAAM,CAAC;CACnC;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,OAAO,GAAE,8BAAmC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA2G7C"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readLogsResponseSchema } from '@shipfox/api-logs-dto';
|
|
2
2
|
import { ApiError, checkedApiRequest } from '@shipfox/client-api';
|
|
3
3
|
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
|
4
|
-
import { useRef } from 'react';
|
|
4
|
+
import { useCallback, useRef } from 'react';
|
|
5
5
|
import { mergeLogRead, STEP_LOG_LIVE_REFETCH_MS, stepLogRefetchInterval } from '#core/log-read.js';
|
|
6
6
|
import { parseLogNdjson, toLogRead } from './log-mapper.js';
|
|
7
7
|
export const stepLogsQueryKeys = {
|
|
@@ -50,6 +50,7 @@ export function isMissingStepLogStreamError(error) {
|
|
|
50
50
|
const queryClient = useQueryClient();
|
|
51
51
|
const missingStreamFailureCountRef = useRef(0);
|
|
52
52
|
const missingStreamScopeRef = useRef(null);
|
|
53
|
+
const manualRefetchRef = useRef(false);
|
|
53
54
|
const enabled = Boolean(stepId && attempt && Number.isInteger(attempt) && attempt > 0);
|
|
54
55
|
const queryKey = enabled && stepId && attempt ? stepLogsQueryKeys.detail(stepId, attempt) : [
|
|
55
56
|
...stepLogsQueryKeys.all,
|
|
@@ -63,11 +64,13 @@ export function isMissingStepLogStreamError(error) {
|
|
|
63
64
|
const initialErrorRetryCount = options.initialErrorRetryCount ?? 0;
|
|
64
65
|
const initialErrorRetryDelayMs = options.initialErrorRetryDelayMs ?? STEP_LOG_LIVE_REFETCH_MS;
|
|
65
66
|
const missingStreamRetryDelayMs = options.missingStreamRetryDelayMs ?? STEP_LOG_LIVE_REFETCH_MS;
|
|
66
|
-
|
|
67
|
+
const query = useQuery({
|
|
67
68
|
queryKey,
|
|
68
69
|
enabled,
|
|
69
70
|
queryFn: async ({ signal })=>{
|
|
70
71
|
const previous = queryClient.getQueryData(queryKey);
|
|
72
|
+
const manualRefetch = manualRefetchRef.current;
|
|
73
|
+
manualRefetchRef.current = false;
|
|
71
74
|
let response;
|
|
72
75
|
try {
|
|
73
76
|
response = await readStepAttemptLogsPage({
|
|
@@ -77,7 +80,7 @@ export function isMissingStepLogStreamError(error) {
|
|
|
77
80
|
signal
|
|
78
81
|
});
|
|
79
82
|
} catch (error) {
|
|
80
|
-
if (options.retryMissingStream && previous === undefined && isMissingStepLogStreamError(error)) {
|
|
83
|
+
if (options.retryMissingStream && previous === undefined && isMissingStepLogStreamError(error) && !manualRefetch) {
|
|
81
84
|
const retryCount = options.missingStreamRetryCount;
|
|
82
85
|
if (retryCount === undefined) throw error;
|
|
83
86
|
if (missingStreamFailureCountRef.current >= retryCount) {
|
|
@@ -120,6 +123,23 @@ export function isMissingStepLogStreamError(error) {
|
|
|
120
123
|
refetchOnWindowFocus: (query)=>!query.state.data?.complete,
|
|
121
124
|
refetchOnReconnect: (query)=>!query.state.data?.complete
|
|
122
125
|
});
|
|
126
|
+
const refetchLogs = useCallback(()=>{
|
|
127
|
+
// React Query reuses an in-flight request when it has no cached data. Only
|
|
128
|
+
// mark a refetch as manual when this call can start a new request; otherwise
|
|
129
|
+
// the marker would be consumed by the next automatic poll.
|
|
130
|
+
if (!query.isFetching || query.data !== undefined) {
|
|
131
|
+
manualRefetchRef.current = true;
|
|
132
|
+
}
|
|
133
|
+
return query.refetch();
|
|
134
|
+
}, [
|
|
135
|
+
query.data,
|
|
136
|
+
query.isFetching,
|
|
137
|
+
query.refetch
|
|
138
|
+
]);
|
|
139
|
+
return {
|
|
140
|
+
...query,
|
|
141
|
+
refetchLogs
|
|
142
|
+
};
|
|
123
143
|
}
|
|
124
144
|
function emptyCompleteLogSnapshot() {
|
|
125
145
|
return {
|