@shipfox/client-logs 0.2.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.
Files changed (80) hide show
  1. package/.storybook/main.ts +1 -0
  2. package/.storybook/preview.css +9 -0
  3. package/.storybook/preview.tsx +56 -0
  4. package/.swcrc +42 -0
  5. package/.turbo/turbo-build.log +2 -0
  6. package/.turbo/turbo-type$colon$emit.log +1 -0
  7. package/.turbo/turbo-type.log +1 -0
  8. package/CHANGELOG.md +93 -0
  9. package/LICENSE +21 -0
  10. package/dist/components/agent-session-rows.d.ts +8 -0
  11. package/dist/components/agent-session-rows.d.ts.map +1 -0
  12. package/dist/components/agent-session-rows.js +374 -0
  13. package/dist/components/agent-session-rows.js.map +1 -0
  14. package/dist/components/index.d.ts +5 -0
  15. package/dist/components/index.d.ts.map +1 -0
  16. package/dist/components/index.js +6 -0
  17. package/dist/components/index.js.map +1 -0
  18. package/dist/components/log-group.d.ts +11 -0
  19. package/dist/components/log-group.d.ts.map +1 -0
  20. package/dist/components/log-group.js +57 -0
  21. package/dist/components/log-group.js.map +1 -0
  22. package/dist/components/log-view.d.ts +20 -0
  23. package/dist/components/log-view.d.ts.map +1 -0
  24. package/dist/components/log-view.js +243 -0
  25. package/dist/components/log-view.js.map +1 -0
  26. package/dist/components/output-log-row.d.ts +9 -0
  27. package/dist/components/output-log-row.d.ts.map +1 -0
  28. package/dist/components/output-log-row.js +24 -0
  29. package/dist/components/output-log-row.js.map +1 -0
  30. package/dist/components/system-markers.d.ts +21 -0
  31. package/dist/components/system-markers.d.ts.map +1 -0
  32. package/dist/components/system-markers.js +113 -0
  33. package/dist/components/system-markers.js.map +1 -0
  34. package/dist/core/log-read.d.ts +33 -0
  35. package/dist/core/log-read.d.ts.map +1 -0
  36. package/dist/core/log-read.js +49 -0
  37. package/dist/core/log-read.js.map +1 -0
  38. package/dist/core/log-tree.d.ts +85 -0
  39. package/dist/core/log-tree.d.ts.map +1 -0
  40. package/dist/core/log-tree.js +139 -0
  41. package/dist/core/log-tree.js.map +1 -0
  42. package/dist/env.d.js +2 -0
  43. package/dist/env.d.js.map +1 -0
  44. package/dist/hooks/api/step-logs.d.ts +24 -0
  45. package/dist/hooks/api/step-logs.d.ts.map +1 -0
  46. package/dist/hooks/api/step-logs.js +129 -0
  47. package/dist/hooks/api/step-logs.js.map +1 -0
  48. package/dist/index.d.ts +5 -0
  49. package/dist/index.d.ts.map +1 -0
  50. package/dist/index.js +5 -0
  51. package/dist/index.js.map +1 -0
  52. package/dist/tsconfig.test.tsbuildinfo +1 -0
  53. package/package.json +84 -0
  54. package/src/components/agent-session-rows.tsx +341 -0
  55. package/src/components/index.ts +15 -0
  56. package/src/components/log-group.stories.tsx +204 -0
  57. package/src/components/log-group.tsx +54 -0
  58. package/src/components/log-view.stories.tsx +584 -0
  59. package/src/components/log-view.test.tsx +266 -0
  60. package/src/components/log-view.tsx +281 -0
  61. package/src/components/output-log-row.stories.tsx +76 -0
  62. package/src/components/output-log-row.tsx +35 -0
  63. package/src/components/system-markers.stories.tsx +46 -0
  64. package/src/components/system-markers.tsx +148 -0
  65. package/src/core/log-read.test.ts +219 -0
  66. package/src/core/log-read.ts +79 -0
  67. package/src/core/log-tree.test.ts +380 -0
  68. package/src/core/log-tree.ts +200 -0
  69. package/src/env.d.ts +7 -0
  70. package/src/hooks/api/step-logs-query.test.tsx +283 -0
  71. package/src/hooks/api/step-logs.test.ts +66 -0
  72. package/src/hooks/api/step-logs.ts +166 -0
  73. package/src/index.ts +18 -0
  74. package/test/setup.ts +3 -0
  75. package/tsconfig.build.json +9 -0
  76. package/tsconfig.build.tsbuildinfo +1 -0
  77. package/tsconfig.json +3 -0
  78. package/tsconfig.test.json +8 -0
  79. package/vercel.json +8 -0
  80. package/vitest.config.ts +70 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/agent-session-rows.tsx"],"sourcesContent":["'use client';\n\nimport type {SessionViewRow, SessionViewRowMeta} from '@shipfox/api-logs-dto';\nimport {Icon} from '@shipfox/react-ui/icon';\nimport {\n LogContent,\n LogDisclosure,\n LogDisclosureContent,\n LogDisclosureTrigger,\n LogRow,\n} from '@shipfox/react-ui/log';\nimport {Tooltip, TooltipContent, TooltipTrigger} from '@shipfox/react-ui/tooltip';\nimport {cn} from '@shipfox/react-ui/utils';\nimport {Fragment, useState} from 'react';\n\nconst PREVIEW_CHAR_LIMIT = 1200;\nconst WORD_SUMMARY_CHAR_LIMIT = 5000;\nconst WHITESPACE = /\\s+/g;\nconst WORD_SEPARATOR = /\\s+/;\n\nexport interface AgentSessionRowsProps {\n rows: readonly SessionViewRow[];\n resolvedToolCallIds: ReadonlySet<string>;\n indent: number;\n}\n\nexport function AgentSessionRows({rows, resolvedToolCallIds, indent}: AgentSessionRowsProps) {\n return rows.map((row, index) => (\n <AgentSessionRowView\n // biome-ignore lint/suspicious/noArrayIndexKey: session rows are immutable and never reordered, so the index is stable; content keys would balloon to megabyte strings and collide on repeated id-less tool calls.\n key={`${row.kind}-${index}`}\n row={row}\n resolvedToolCallIds={resolvedToolCallIds}\n indent={indent}\n />\n ));\n}\n\nfunction AgentSessionRowView({\n row,\n resolvedToolCallIds,\n indent,\n}: {\n row: SessionViewRow;\n resolvedToolCallIds: ReadonlySet<string>;\n indent: number;\n}) {\n switch (row.kind) {\n case 'message':\n return (\n <LogRow\n lineNumber={null}\n timestamp={new Date(row.timestamp)}\n indent={indent}\n tone={row.terminalFailure ? 'error' : 'default'}\n data-log-terminal-failure={row.terminalFailure ? 'true' : undefined}\n >\n <LogContent className=\"text-foreground-neutral-base\">\n <span className=\"flex min-w-0 items-start gap-8\">\n <MessageIcon role={row.role} terminalFailure={row.terminalFailure} />\n <span className=\"flex min-w-0 flex-1 flex-col gap-2\">\n <span className=\"flex min-w-0 items-center gap-8\">\n <MessageRoleLabel label={row.label} terminalFailure={row.terminalFailure} />\n <RowMetadata meta={row.meta} className=\"ml-auto flex-none\" />\n </span>\n <span className=\"block min-w-0\">\n <PreviewText text={row.text} />\n </span>\n </span>\n </span>\n </LogContent>\n </LogRow>\n );\n case 'thinking':\n return (\n <LogDisclosure indent={indent}>\n <LogDisclosureTrigger\n summary={wordSummary(row.text)}\n timestamp={new Date(row.timestamp)}\n className=\"text-foreground-neutral-subtle\"\n >\n thinking\n </LogDisclosureTrigger>\n <LogDisclosureContent className=\"text-foreground-neutral-subtle\">\n <LogContent className=\"text-foreground-neutral-subtle\">\n <PreviewText text={row.text} />\n </LogContent>\n </LogDisclosureContent>\n </LogDisclosure>\n );\n case 'tool-call': {\n const awaitingResult = row.id != null && !resolvedToolCallIds.has(row.id);\n return (\n <LogDisclosure indent={indent}>\n <LogDisclosureTrigger\n timestamp={new Date(row.timestamp)}\n summary={compactPreview(row.input)}\n trailing={\n awaitingResult ? (\n <span className=\"inline-flex items-center gap-4\">\n <Icon\n name=\"loader4Line\"\n className=\"size-12 motion-safe:animate-spin\"\n aria-hidden=\"true\"\n />\n awaiting result\n </span>\n ) : null\n }\n >\n <span className=\"inline-flex min-w-0 items-center gap-6\">\n <Icon name=\"terminalBoxLine\" className=\"size-14 flex-none\" aria-hidden=\"true\" />\n <span className=\"truncate\">tool {row.name}</span>\n </span>\n </LogDisclosureTrigger>\n <LogDisclosureContent>\n <LogContent variant=\"code\">\n <PreviewText text={row.input} />\n </LogContent>\n </LogDisclosureContent>\n </LogDisclosure>\n );\n }\n case 'tool-result':\n return (\n <LogDisclosure indent={indent}>\n <LogDisclosureTrigger\n timestamp={new Date(row.timestamp)}\n summary={compactPreview(row.output)}\n trailing={\n <span\n className={cn(\n 'inline-flex items-center gap-4',\n row.isError ? 'text-red-600 dark:text-red-400' : 'text-foreground-neutral-muted',\n )}\n >\n <Icon\n name={row.isError ? 'closeCircleLine' : 'checkLine'}\n className=\"size-12\"\n aria-hidden=\"true\"\n />\n {row.isError ? 'error' : 'ok'}\n </span>\n }\n >\n <span className=\"inline-flex min-w-0 items-center gap-6\">\n <Icon name=\"terminalWindowLine\" className=\"size-14 flex-none\" aria-hidden=\"true\" />\n <span className=\"truncate\">result {row.toolName}</span>\n </span>\n </LogDisclosureTrigger>\n <LogDisclosureContent>\n <LogContent\n variant=\"code\"\n className={cn(row.isError && 'text-red-600 dark:text-red-400')}\n >\n <PreviewText text={row.output} />\n </LogContent>\n </LogDisclosureContent>\n </LogDisclosure>\n );\n case 'lifecycle':\n return (\n <LogRow\n lineNumber={null}\n timestamp={new Date(row.timestamp)}\n indent={indent}\n tone={row.tone}\n data-log-terminal-failure={row.terminalFailure ? 'true' : undefined}\n >\n <LogContent className=\"text-foreground-neutral-muted\">\n <span className=\"inline-flex w-full items-center gap-8\">\n <Icon name=\"informationLine\" className=\"size-14 flex-none\" aria-hidden=\"true\" />\n <span className=\"min-w-0\">\n <span className=\"font-medium\">{row.label}</span>\n {row.detail != null ? (\n <>\n {' · '}\n <span className=\"text-foreground-neutral-subtle\">{row.detail}</span>\n </>\n ) : null}\n </span>\n <span\n aria-hidden=\"true\"\n className=\"h-px flex-1 border-t border-dashed border-current opacity-30\"\n />\n <RowMetadata meta={row.meta} />\n </span>\n </LogContent>\n </LogRow>\n );\n case 'raw':\n return (\n <LogDisclosure indent={indent}>\n <LogDisclosureTrigger\n timestamp={new Date(row.timestamp)}\n summary={compactPreview(row.raw)}\n className=\"text-orange-600 dark:text-orange-400\"\n >\n <span className=\"inline-flex min-w-0 items-center gap-6\">\n <Icon name=\"errorWarningLine\" className=\"size-14 flex-none\" aria-hidden=\"true\" />\n <span className=\"truncate\">{row.label}</span>\n </span>\n </LogDisclosureTrigger>\n <LogDisclosureContent>\n <LogContent variant=\"code\">\n <PreviewText text={row.raw} />\n </LogContent>\n </LogDisclosureContent>\n </LogDisclosure>\n );\n default:\n return assertNever(row);\n }\n}\n\nfunction MessageIcon({role, terminalFailure}: {role: string; terminalFailure: boolean}) {\n const name = terminalFailure\n ? 'closeCircleLine'\n : role === 'user'\n ? 'userLine'\n : role === 'assistant'\n ? 'robot2Line'\n : 'message2Line';\n\n return (\n <Icon\n name={name}\n className={cn(\n 'mt-2 size-14 flex-none',\n terminalFailure ? 'text-red-600 dark:text-red-400' : 'text-foreground-neutral-muted',\n )}\n aria-hidden=\"true\"\n />\n );\n}\n\nfunction MessageRoleLabel({label, terminalFailure}: {label: string; terminalFailure: boolean}) {\n return (\n <span\n className={cn(\n 'min-w-0 font-code text-foreground-neutral-muted',\n terminalFailure && 'text-foreground-highlight-error',\n )}\n >\n <span className=\"truncate\">{label}</span>\n </span>\n );\n}\n\nfunction RowMetadata({meta, className}: {meta: readonly SessionViewRowMeta[]; className?: string}) {\n if (meta.length === 0) return null;\n\n const inlineMeta = meta.length === 1 && meta[0]?.inline !== false ? meta[0] : null;\n if (inlineMeta != null) {\n return (\n <span\n className={cn('font-code text-xs text-foreground-neutral-muted', className)}\n title={`${inlineMeta.label}: ${inlineMeta.value}`}\n >\n {inlineMeta.value}\n </span>\n );\n }\n\n return (\n <span className={className}>\n <MetadataTrigger meta={meta} />\n </span>\n );\n}\n\nfunction MetadataTrigger({meta}: {meta: readonly SessionViewRowMeta[]}) {\n return (\n <Tooltip>\n <TooltipTrigger asChild>\n <button\n type=\"button\"\n className=\"inline-flex size-20 flex-none items-center justify-center rounded-4 text-foreground-neutral-muted opacity-60 transition-opacity hover:bg-background-components-hover hover:text-foreground-neutral-base hover:opacity-100 focus-visible:opacity-100 focus-visible:shadow-[inset_0_0_0_2px_var(--color-primary-500)] group-hover/log-row:opacity-100\"\n aria-label=\"Show message metadata\"\n >\n <Icon name=\"informationLine\" className=\"size-12\" aria-hidden=\"true\" />\n </button>\n </TooltipTrigger>\n <TooltipContent align=\"end\" variant=\"inverted\" className=\"max-w-360 px-8 py-6\">\n <span className=\"grid grid-cols-[max-content_minmax(0,1fr)] gap-x-8 gap-y-2 font-code text-xs\">\n {meta.map((item) => (\n <Fragment key={`${item.label}-${item.value}`}>\n <span className=\"text-foreground-contrast-secondary\">{item.label}</span>\n <span className=\"min-w-0 break-all text-foreground-contrast-primary\">\n {item.value}\n </span>\n </Fragment>\n ))}\n </span>\n </TooltipContent>\n </Tooltip>\n );\n}\n\nfunction PreviewText({text}: {text: string}) {\n const [expanded, setExpanded] = useState(false);\n const truncated = text.length > PREVIEW_CHAR_LIMIT;\n const visible = truncated && !expanded ? `${text.slice(0, PREVIEW_CHAR_LIMIT)}…` : text;\n\n return (\n <>\n {visible}\n {truncated ? (\n <button\n type=\"button\"\n aria-expanded={expanded}\n className=\"ml-8 inline-flex min-h-24 items-center rounded-4 px-6 font-display text-xs text-foreground-highlight-interactive focus-visible:shadow-[inset_0_0_0_2px_var(--color-primary-500)]\"\n onClick={() => setExpanded((value) => !value)}\n >\n {expanded ? 'show less' : 'show more'}\n </button>\n ) : null}\n </>\n );\n}\n\nfunction compactPreview(value: string): string {\n // Normalize only a bounded head: tool output can be megabytes, and this runs\n // per render for every disclosure trigger.\n const head = value.length > 200 ? value.slice(0, 200) : value;\n const singleLine = head.replace(WHITESPACE, ' ').trim();\n if (singleLine.length <= 80) return singleLine;\n return `${singleLine.slice(0, 80)}…`;\n}\n\nfunction wordSummary(value: string): string {\n const truncated = value.length > WORD_SUMMARY_CHAR_LIMIT;\n const head = truncated ? value.slice(0, WORD_SUMMARY_CHAR_LIMIT) : value;\n const count = head.trim().split(WORD_SEPARATOR).filter(Boolean).length;\n const marker = truncated ? '+' : '';\n return `${count}${marker} ${count === 1 ? 'word' : 'words'}`;\n}\n\nfunction assertNever(value: never): never {\n throw new Error(`unexpected agent session row: ${JSON.stringify(value)}`);\n}\n"],"names":["Icon","LogContent","LogDisclosure","LogDisclosureContent","LogDisclosureTrigger","LogRow","Tooltip","TooltipContent","TooltipTrigger","cn","Fragment","useState","PREVIEW_CHAR_LIMIT","WORD_SUMMARY_CHAR_LIMIT","WHITESPACE","WORD_SEPARATOR","AgentSessionRows","rows","resolvedToolCallIds","indent","map","row","index","AgentSessionRowView","kind","lineNumber","timestamp","Date","tone","terminalFailure","data-log-terminal-failure","undefined","className","span","MessageIcon","role","MessageRoleLabel","label","RowMetadata","meta","PreviewText","text","summary","wordSummary","awaitingResult","id","has","compactPreview","input","trailing","name","aria-hidden","variant","output","isError","toolName","detail","raw","assertNever","length","inlineMeta","inline","title","value","MetadataTrigger","asChild","button","type","aria-label","align","item","expanded","setExpanded","truncated","visible","slice","aria-expanded","onClick","head","singleLine","replace","trim","count","split","filter","Boolean","marker","Error","JSON","stringify"],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ export { LogGroup, type LogGroupProps } from './log-group.js';
2
+ export { LogView, type LogViewProps, LogViewSkeleton, type LogViewSkeletonProps, } from './log-view.js';
3
+ export { OutputLogRow, type OutputLogRowProps } from './output-log-row.js';
4
+ export { CappedMarker, EndMarker, type EndMarkerProps, GapMarker, RunnerLostMarker, } from './system-markers.js';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,QAAQ,EAAE,KAAK,aAAa,EAAC,MAAM,gBAAgB,CAAC;AAC5D,OAAO,EACL,OAAO,EACP,KAAK,YAAY,EACjB,eAAe,EACf,KAAK,oBAAoB,GAC1B,MAAM,eAAe,CAAC;AACvB,OAAO,EAAC,YAAY,EAAE,KAAK,iBAAiB,EAAC,MAAM,qBAAqB,CAAC;AACzE,OAAO,EACL,YAAY,EACZ,SAAS,EACT,KAAK,cAAc,EACnB,SAAS,EACT,gBAAgB,GACjB,MAAM,qBAAqB,CAAC"}
@@ -0,0 +1,6 @@
1
+ export { LogGroup } from './log-group.js';
2
+ export { LogView, LogViewSkeleton } from './log-view.js';
3
+ export { OutputLogRow } from './output-log-row.js';
4
+ export { CappedMarker, EndMarker, GapMarker, RunnerLostMarker } from './system-markers.js';
5
+
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/index.ts"],"sourcesContent":["export {LogGroup, type LogGroupProps} from './log-group.js';\nexport {\n LogView,\n type LogViewProps,\n LogViewSkeleton,\n type LogViewSkeletonProps,\n} from './log-view.js';\nexport {OutputLogRow, type OutputLogRowProps} from './output-log-row.js';\nexport {\n CappedMarker,\n EndMarker,\n type EndMarkerProps,\n GapMarker,\n RunnerLostMarker,\n} from './system-markers.js';\n"],"names":["LogGroup","LogView","LogViewSkeleton","OutputLogRow","CappedMarker","EndMarker","GapMarker","RunnerLostMarker"],"mappings":"AAAA,SAAQA,QAAQ,QAA2B,iBAAiB;AAC5D,SACEC,OAAO,EAEPC,eAAe,QAEV,gBAAgB;AACvB,SAAQC,YAAY,QAA+B,sBAAsB;AACzE,SACEC,YAAY,EACZC,SAAS,EAETC,SAAS,EACTC,gBAAgB,QACX,sBAAsB"}
@@ -0,0 +1,11 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { GroupLogNode } from '#core/log-tree.js';
3
+ export interface LogGroupProps {
4
+ node: GroupLogNode;
5
+ depth: number;
6
+ terminated: boolean;
7
+ children: ReactNode;
8
+ defaultOpen?: boolean;
9
+ }
10
+ export declare function LogGroup({ node, depth, terminated, children, defaultOpen }: LogGroupProps): import("react").JSX.Element;
11
+ //# sourceMappingURL=log-group.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log-group.d.ts","sourceRoot":"","sources":["../../src/components/log-group.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,OAAO,CAAC;AACrC,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,mBAAmB,CAAC;AAEpD,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,YAAY,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,OAAO,CAAC;IACpB,QAAQ,EAAE,SAAS,CAAC;IACpB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,wBAAgB,QAAQ,CAAC,EAAC,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,WAAmB,EAAC,EAAE,aAAa,+BAe/F"}
@@ -0,0 +1,57 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Icon } from '@shipfox/react-ui/icon';
4
+ import { LogDisclosure, LogDisclosureContent, LogDisclosureTrigger } from '@shipfox/react-ui/log';
5
+ import { cn, formatDuration } from '@shipfox/react-ui/utils';
6
+ export function LogGroup({ node, depth, terminated, children, defaultOpen = false }) {
7
+ const lineLabel = `${node.lineCount} ${node.lineCount === 1 ? 'line' : 'lines'}`;
8
+ return /*#__PURE__*/ _jsxs(LogDisclosure, {
9
+ indent: depth,
10
+ defaultOpen: defaultOpen,
11
+ children: [
12
+ /*#__PURE__*/ _jsx(LogDisclosureTrigger, {
13
+ summary: lineLabel,
14
+ trailing: /*#__PURE__*/ _jsx(GroupStatus, {
15
+ node: node,
16
+ terminated: terminated
17
+ }),
18
+ className: cn(node.hasError && 'shadow-[inset_2px_0_0_var(--color-red-500)]'),
19
+ children: node.record.name
20
+ }),
21
+ /*#__PURE__*/ _jsx(LogDisclosureContent, {
22
+ rail: false,
23
+ children: children
24
+ })
25
+ ]
26
+ });
27
+ }
28
+ function GroupStatus({ node, terminated }) {
29
+ if (node.closed && node.endTs != null) {
30
+ return /*#__PURE__*/ _jsx("span", {
31
+ className: "font-code tabular-nums",
32
+ children: formatDuration(node.endTs - node.record.ts)
33
+ });
34
+ }
35
+ // No clean end: either still open under a terminated stream (runner died mid-group), or
36
+ // closed only because an ancestor's group_end cascaded (its own end was dropped, so endTs
37
+ // is null). Either way show "incomplete" rather than a blank slot or a forever spinner.
38
+ if (node.closed || terminated) {
39
+ return /*#__PURE__*/ _jsx("span", {
40
+ className: "text-foreground-neutral-muted",
41
+ children: "incomplete"
42
+ });
43
+ }
44
+ return /*#__PURE__*/ _jsxs("span", {
45
+ className: "inline-flex items-center gap-4 text-foreground-neutral-muted",
46
+ children: [
47
+ /*#__PURE__*/ _jsx(Icon, {
48
+ name: "loader4Line",
49
+ className: "size-12 motion-safe:animate-spin",
50
+ "aria-hidden": "true"
51
+ }),
52
+ "running"
53
+ ]
54
+ });
55
+ }
56
+
57
+ //# sourceMappingURL=log-group.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/log-group.tsx"],"sourcesContent":["'use client';\n\nimport {Icon} from '@shipfox/react-ui/icon';\nimport {LogDisclosure, LogDisclosureContent, LogDisclosureTrigger} from '@shipfox/react-ui/log';\nimport {cn, formatDuration} from '@shipfox/react-ui/utils';\nimport type {ReactNode} from 'react';\nimport type {GroupLogNode} from '#core/log-tree.js';\n\nexport interface LogGroupProps {\n node: GroupLogNode;\n depth: number;\n terminated: boolean;\n children: ReactNode;\n defaultOpen?: boolean;\n}\n\nexport function LogGroup({node, depth, terminated, children, defaultOpen = false}: LogGroupProps) {\n const lineLabel = `${node.lineCount} ${node.lineCount === 1 ? 'line' : 'lines'}`;\n\n return (\n <LogDisclosure indent={depth} defaultOpen={defaultOpen}>\n <LogDisclosureTrigger\n summary={lineLabel}\n trailing={<GroupStatus node={node} terminated={terminated} />}\n className={cn(node.hasError && 'shadow-[inset_2px_0_0_var(--color-red-500)]')}\n >\n {node.record.name}\n </LogDisclosureTrigger>\n <LogDisclosureContent rail={false}>{children}</LogDisclosureContent>\n </LogDisclosure>\n );\n}\n\nfunction GroupStatus({node, terminated}: {node: GroupLogNode; terminated: boolean}): ReactNode {\n if (node.closed && node.endTs != null) {\n return (\n <span className=\"font-code tabular-nums\">{formatDuration(node.endTs - node.record.ts)}</span>\n );\n }\n\n // No clean end: either still open under a terminated stream (runner died mid-group), or\n // closed only because an ancestor's group_end cascaded (its own end was dropped, so endTs\n // is null). Either way show \"incomplete\" rather than a blank slot or a forever spinner.\n if (node.closed || terminated) {\n return <span className=\"text-foreground-neutral-muted\">incomplete</span>;\n }\n\n return (\n <span className=\"inline-flex items-center gap-4 text-foreground-neutral-muted\">\n <Icon name=\"loader4Line\" className=\"size-12 motion-safe:animate-spin\" aria-hidden=\"true\" />\n running\n </span>\n );\n}\n"],"names":["Icon","LogDisclosure","LogDisclosureContent","LogDisclosureTrigger","cn","formatDuration","LogGroup","node","depth","terminated","children","defaultOpen","lineLabel","lineCount","indent","summary","trailing","GroupStatus","className","hasError","record","name","rail","closed","endTs","span","ts","aria-hidden"],"mappings":"AAAA;;AAEA,SAAQA,IAAI,QAAO,yBAAyB;AAC5C,SAAQC,aAAa,EAAEC,oBAAoB,EAAEC,oBAAoB,QAAO,wBAAwB;AAChG,SAAQC,EAAE,EAAEC,cAAc,QAAO,0BAA0B;AAY3D,OAAO,SAASC,SAAS,EAACC,IAAI,EAAEC,KAAK,EAAEC,UAAU,EAAEC,QAAQ,EAAEC,cAAc,KAAK,EAAgB;IAC9F,MAAMC,YAAY,GAAGL,KAAKM,SAAS,CAAC,CAAC,EAAEN,KAAKM,SAAS,KAAK,IAAI,SAAS,SAAS;IAEhF,qBACE,MAACZ;QAAca,QAAQN;QAAOG,aAAaA;;0BACzC,KAACR;gBACCY,SAASH;gBACTI,wBAAU,KAACC;oBAAYV,MAAMA;oBAAME,YAAYA;;gBAC/CS,WAAWd,GAAGG,KAAKY,QAAQ,IAAI;0BAE9BZ,KAAKa,MAAM,CAACC,IAAI;;0BAEnB,KAACnB;gBAAqBoB,MAAM;0BAAQZ;;;;AAG1C;AAEA,SAASO,YAAY,EAACV,IAAI,EAAEE,UAAU,EAA4C;IAChF,IAAIF,KAAKgB,MAAM,IAAIhB,KAAKiB,KAAK,IAAI,MAAM;QACrC,qBACE,KAACC;YAAKP,WAAU;sBAA0Bb,eAAeE,KAAKiB,KAAK,GAAGjB,KAAKa,MAAM,CAACM,EAAE;;IAExF;IAEA,wFAAwF;IACxF,0FAA0F;IAC1F,wFAAwF;IACxF,IAAInB,KAAKgB,MAAM,IAAId,YAAY;QAC7B,qBAAO,KAACgB;YAAKP,WAAU;sBAAgC;;IACzD;IAEA,qBACE,MAACO;QAAKP,WAAU;;0BACd,KAAClB;gBAAKqB,MAAK;gBAAcH,WAAU;gBAAmCS,eAAY;;YAAS;;;AAIjG"}
@@ -0,0 +1,20 @@
1
+ import type { LogRecord } from '@shipfox/api-logs-dto';
2
+ import { type LogTimestampMode } from '@shipfox/react-ui/log';
3
+ import { type UIEventHandler } from 'react';
4
+ export interface LogViewProps {
5
+ records: readonly LogRecord[];
6
+ timestamps?: LogTimestampMode;
7
+ wrap?: boolean;
8
+ showLineNumbers?: boolean;
9
+ emptyState?: 'complete' | 'pending';
10
+ defaultGroupsOpen?: boolean;
11
+ anchorToFailure?: boolean;
12
+ className?: string | undefined;
13
+ onScroll?: UIEventHandler<HTMLDivElement> | undefined;
14
+ }
15
+ export interface LogViewSkeletonProps extends Pick<LogViewProps, 'timestamps' | 'wrap' | 'showLineNumbers' | 'className'> {
16
+ rows?: number;
17
+ }
18
+ export declare function LogView({ records, timestamps, wrap, showLineNumbers, emptyState, defaultGroupsOpen, anchorToFailure, className, onScroll, }: LogViewProps): import("react").JSX.Element;
19
+ export declare function LogViewSkeleton({ rows, timestamps, wrap, showLineNumbers, className, }: LogViewSkeletonProps): import("react").JSX.Element;
20
+ //# sourceMappingURL=log-view.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log-view.d.ts","sourceRoot":"","sources":["../../src/components/log-view.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAErD,OAAO,EAA8B,KAAK,gBAAgB,EAAC,MAAM,uBAAuB,CAAC;AAEzF,OAAO,EAAiB,KAAK,cAAc,EAA6B,MAAM,OAAO,CAAC;AAatF,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,SAAS,SAAS,EAAE,CAAC;IAC9B,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,UAAU,CAAC,EAAE,UAAU,GAAG,SAAS,CAAC;IACpC,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,QAAQ,CAAC,EAAE,cAAc,CAAC,cAAc,CAAC,GAAG,SAAS,CAAC;CACvD;AAED,MAAM,WAAW,oBACf,SAAQ,IAAI,CAAC,YAAY,EAAE,YAAY,GAAG,MAAM,GAAG,iBAAiB,GAAG,WAAW,CAAC;IACnF,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,OAAO,CAAC,EACtB,OAAO,EACP,UAAkB,EAClB,IAAY,EACZ,eAAsB,EACtB,UAAuB,EACvB,iBAAyB,EACzB,eAAuB,EACvB,SAAS,EACT,QAAQ,GACT,EAAE,YAAY,+BAyCd;AAED,wBAAgB,eAAe,CAAC,EAC9B,IAAQ,EACR,UAAkB,EAClB,IAAY,EACZ,eAAsB,EACtB,SAAS,GACV,EAAE,oBAAoB,+BAuBtB"}
@@ -0,0 +1,243 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Icon } from '@shipfox/react-ui/icon';
4
+ import { LogContent, LogRow, LogRows } from '@shipfox/react-ui/log';
5
+ import { Skeleton } from '@shipfox/react-ui/skeleton';
6
+ import { useEffect, useMemo, useRef } from 'react';
7
+ import { assertNever, buildLogTree } from '#core/log-tree.js';
8
+ import { AgentSessionRows } from './agent-session-rows.js';
9
+ import { LogGroup } from './log-group.js';
10
+ import { OutputLogRow } from './output-log-row.js';
11
+ import { CappedMarker, EndMarker, GapMarker, RunnerLostMarker } from './system-markers.js';
12
+ export function LogView({ records, timestamps = 'off', wrap = false, showLineNumbers = true, emptyState = 'complete', defaultGroupsOpen = false, anchorToFailure = false, className, onScroll }) {
13
+ const rowsRef = useRef(null);
14
+ const tree = useMemo(()=>buildLogTree(records), [
15
+ records
16
+ ]);
17
+ const resolvedToolCallIds = useMemo(()=>collectResolvedToolCallIds(tree.nodes), [
18
+ tree.nodes
19
+ ]);
20
+ const noOutputState = getNoOutputState(tree, emptyState);
21
+ const anchorRecordCount = records.length;
22
+ useEffect(()=>{
23
+ if (!anchorToFailure) return;
24
+ if (anchorRecordCount === 0) return;
25
+ const frame = scheduleAnimationFrame(()=>{
26
+ const rows = rowsRef.current;
27
+ if (!rows) return;
28
+ const failure = rows.querySelector('[data-log-terminal-failure="true"]');
29
+ if (failure) {
30
+ failure.scrollIntoView({
31
+ block: 'center'
32
+ });
33
+ return;
34
+ }
35
+ rows.scrollTop = rows.scrollHeight;
36
+ });
37
+ return ()=>cancelScheduledFrame(frame);
38
+ }, [
39
+ anchorToFailure,
40
+ anchorRecordCount
41
+ ]);
42
+ return /*#__PURE__*/ _jsxs(LogRows, {
43
+ ref: rowsRef,
44
+ timestamps: timestamps,
45
+ wrap: wrap,
46
+ showLineNumbers: showLineNumbers,
47
+ className: className,
48
+ onScroll: onScroll,
49
+ ...tree.originTs != null ? {
50
+ timestampOrigin: new Date(tree.originTs)
51
+ } : {},
52
+ children: [
53
+ noOutputState ? /*#__PURE__*/ _jsx(NoOutputRow, {
54
+ state: noOutputState
55
+ }) : null,
56
+ renderNodes(tree.nodes, 0, tree, defaultGroupsOpen, resolvedToolCallIds)
57
+ ]
58
+ });
59
+ }
60
+ export function LogViewSkeleton({ rows = 5, timestamps = 'off', wrap = false, showLineNumbers = true, className }) {
61
+ const widths = [
62
+ 'w-[62%]',
63
+ 'w-[44%]',
64
+ 'w-[74%]',
65
+ 'w-[36%]',
66
+ 'w-[55%]'
67
+ ];
68
+ const skeletonRows = getSkeletonRows(rows);
69
+ return /*#__PURE__*/ _jsx(LogRows, {
70
+ timestamps: timestamps,
71
+ wrap: wrap,
72
+ showLineNumbers: showLineNumbers,
73
+ className: className,
74
+ role: "presentation",
75
+ "aria-live": "off",
76
+ "aria-hidden": "true",
77
+ children: skeletonRows.map((row)=>/*#__PURE__*/ _jsx(LogRow, {
78
+ lineNumber: row.lineNumber,
79
+ children: /*#__PURE__*/ _jsx(Skeleton, {
80
+ className: `my-4 h-12 ${widths[(row.lineNumber - 1) % widths.length] ?? 'w-[48%]'}`
81
+ })
82
+ }, row.id))
83
+ });
84
+ }
85
+ function getSkeletonRows(rows) {
86
+ return Array.from({
87
+ length: rows
88
+ }, (_, index)=>{
89
+ const lineNumber = index + 1;
90
+ return {
91
+ id: `log-view-skeleton-row-${lineNumber}`,
92
+ lineNumber
93
+ };
94
+ });
95
+ }
96
+ function getNoOutputState(tree, emptyState) {
97
+ if (tree.nodes.length === 0) return emptyState;
98
+ if (tree.lineCount !== 0) return null;
99
+ if (tree.nodes.length !== 1) return null;
100
+ const [node] = tree.nodes;
101
+ if (node?.kind === 'marker' && node.record.type === 'end') return 'complete';
102
+ return null;
103
+ }
104
+ function NoOutputRow({ state }) {
105
+ const copy = state === 'pending' ? {
106
+ title: 'No output yet',
107
+ detail: 'New lines will appear here as the step writes them.'
108
+ } : {
109
+ title: 'Step produced no output',
110
+ detail: 'This log stream closed without session entries or process output.'
111
+ };
112
+ return /*#__PURE__*/ _jsx(LogRow, {
113
+ lineNumber: null,
114
+ children: /*#__PURE__*/ _jsx(LogContent, {
115
+ className: "text-foreground-neutral-muted",
116
+ children: /*#__PURE__*/ _jsxs("span", {
117
+ className: "inline-flex min-w-0 items-center gap-8",
118
+ children: [
119
+ /*#__PURE__*/ _jsx(Icon, {
120
+ name: "info",
121
+ className: "size-14 flex-none",
122
+ "aria-hidden": "true"
123
+ }),
124
+ /*#__PURE__*/ _jsxs("span", {
125
+ className: "min-w-0",
126
+ children: [
127
+ /*#__PURE__*/ _jsx("span", {
128
+ className: "font-medium",
129
+ children: copy.title
130
+ }),
131
+ ' · ',
132
+ /*#__PURE__*/ _jsx("span", {
133
+ className: "text-foreground-neutral-subtle",
134
+ children: copy.detail
135
+ })
136
+ ]
137
+ })
138
+ ]
139
+ })
140
+ })
141
+ });
142
+ }
143
+ function renderNodes(nodes, depth, tree, defaultGroupsOpen, resolvedToolCallIds) {
144
+ // `node.seq` is the stable, unique render key (see `LogNodeBase`): a concatenated
145
+ // multi-step/retry stream can repeat a `group_id` or a marker's `(type, ts)` at one
146
+ // level, which a key derived from those fields would collide on.
147
+ return nodes.map((node)=>{
148
+ switch(node.kind){
149
+ case 'output':
150
+ return /*#__PURE__*/ _jsx(OutputLogRow, {
151
+ record: node.record,
152
+ lineNumber: node.lineNumber,
153
+ indent: depth
154
+ }, node.seq);
155
+ case 'group':
156
+ return /*#__PURE__*/ _jsx(LogGroup, {
157
+ node: node,
158
+ depth: depth,
159
+ terminated: tree.terminated,
160
+ defaultOpen: defaultGroupsOpen,
161
+ children: renderNodes(node.children, depth + 1, tree, defaultGroupsOpen, resolvedToolCallIds)
162
+ }, node.seq);
163
+ case 'marker':
164
+ return /*#__PURE__*/ _jsx(MarkerRow, {
165
+ record: node.record,
166
+ tree: tree
167
+ }, node.seq);
168
+ case 'session':
169
+ return /*#__PURE__*/ _jsx(AgentSessionRows, {
170
+ rows: [
171
+ node.record.row
172
+ ],
173
+ resolvedToolCallIds: resolvedToolCallIds,
174
+ indent: depth
175
+ }, node.seq);
176
+ default:
177
+ return assertNever(node);
178
+ }
179
+ });
180
+ }
181
+ function collectResolvedToolCallIds(nodes) {
182
+ const ids = new Set();
183
+ collectResolvedToolCallIdsInto(nodes, ids);
184
+ return ids;
185
+ }
186
+ function collectResolvedToolCallIdsInto(nodes, ids) {
187
+ for (const node of nodes){
188
+ switch(node.kind){
189
+ case 'session':
190
+ if (node.record.row.kind === 'tool-result' && node.record.row.toolCallId != null) {
191
+ ids.add(node.record.row.toolCallId);
192
+ }
193
+ break;
194
+ case 'group':
195
+ collectResolvedToolCallIdsInto(node.children, ids);
196
+ break;
197
+ case 'output':
198
+ case 'marker':
199
+ break;
200
+ default:
201
+ assertNever(node);
202
+ }
203
+ }
204
+ }
205
+ function scheduleAnimationFrame(callback) {
206
+ if (typeof globalThis.requestAnimationFrame === 'function') {
207
+ return globalThis.requestAnimationFrame(callback);
208
+ }
209
+ return window.setTimeout(()=>callback(Date.now()), 0);
210
+ }
211
+ function cancelScheduledFrame(frame) {
212
+ if (typeof globalThis.cancelAnimationFrame === 'function') {
213
+ globalThis.cancelAnimationFrame(frame);
214
+ return;
215
+ }
216
+ window.clearTimeout(frame);
217
+ }
218
+ function MarkerRow({ record, tree }) {
219
+ switch(record.type){
220
+ case 'end':
221
+ return /*#__PURE__*/ _jsx(EndMarker, {
222
+ record: record,
223
+ lineCount: tree.lineCount,
224
+ durationMs: tree.originTs != null ? record.ts - tree.originTs : null
225
+ });
226
+ case 'gap':
227
+ return /*#__PURE__*/ _jsx(GapMarker, {
228
+ record: record
229
+ });
230
+ case 'capped':
231
+ return /*#__PURE__*/ _jsx(CappedMarker, {
232
+ record: record
233
+ });
234
+ case 'runner_lost':
235
+ return /*#__PURE__*/ _jsx(RunnerLostMarker, {
236
+ record: record
237
+ });
238
+ default:
239
+ return assertNever(record);
240
+ }
241
+ }
242
+
243
+ //# sourceMappingURL=log-view.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/log-view.tsx"],"sourcesContent":["'use client';\n\nimport type {LogRecord} from '@shipfox/api-logs-dto';\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 {\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 resolvedToolCallIds = useMemo(() => collectResolvedToolCallIds(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, resolvedToolCallIds)}\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-4 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-8\">\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 resolvedToolCallIds: ReadonlySet<string>,\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, resolvedToolCallIds)}\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={resolvedToolCallIds}\n indent={depth}\n />\n );\n default:\n return assertNever(node);\n }\n });\n}\n\nfunction collectResolvedToolCallIds(nodes: readonly LogNode[]): ReadonlySet<string> {\n const ids = new Set<string>();\n collectResolvedToolCallIdsInto(nodes, ids);\n return ids;\n}\n\nfunction collectResolvedToolCallIdsInto(nodes: readonly LogNode[], ids: Set<string>): void {\n for (const node of nodes) {\n switch (node.kind) {\n case 'session':\n 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 collectResolvedToolCallIdsInto(node.children, ids);\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","resolvedToolCallIds","collectResolvedToolCallIds","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","ids","Set","collectResolvedToolCallIdsInto","toolCallId","add","callback","globalThis","requestAnimationFrame","window","setTimeout","now","cancelAnimationFrame","clearTimeout","durationMs","ts"],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import { type OutputLogRecord } from '#core/log-tree.js';
2
+ export interface OutputLogRowProps {
3
+ record: OutputLogRecord;
4
+ lineNumber?: number | null;
5
+ indent?: number;
6
+ selected?: boolean;
7
+ }
8
+ export declare function OutputLogRow({ record, lineNumber, indent, selected, }: OutputLogRowProps): import("react").JSX.Element;
9
+ //# sourceMappingURL=output-log-row.d.ts.map
@@ -0,0 +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,+BAkBnB"}
@@ -0,0 +1,24 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { LogContent, LogRow } from '@shipfox/react-ui/log';
3
+ import { cn } from '@shipfox/react-ui/utils';
4
+ import { stripTrailingNewline } from '#core/log-tree.js';
5
+ export function OutputLogRow({ record, lineNumber = null, indent = 0, selected = false }) {
6
+ const isStderr = record.stream === 'stderr';
7
+ // Stderr uses a neutral channel rule; it is a stream, not a severity, so it never reads as an error.
8
+ return /*#__PURE__*/ _jsx(LogRow, {
9
+ lineNumber: lineNumber,
10
+ timestamp: new Date(record.ts),
11
+ indent: indent,
12
+ selected: selected,
13
+ "data-stream": record.stream,
14
+ className: cn(isStderr && 'shadow-[inset_2px_0_0_var(--color-border-neutral-strong)]'),
15
+ children: /*#__PURE__*/ _jsx(LogContent, {
16
+ variant: "code",
17
+ ansi: true,
18
+ className: cn(isStderr && 'text-foreground-neutral-subtle'),
19
+ children: stripTrailingNewline(record.data)
20
+ })
21
+ });
22
+ }
23
+
24
+ //# sourceMappingURL=output-log-row.js.map
@@ -0,0 +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 variant=\"code\" ansi className={cn(isStderr && 'text-foreground-neutral-subtle')}>\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;YAAWgB,SAAQ;YAAOC,IAAI;YAACF,WAAWb,GAAGO,YAAY;sBACvDN,qBAAqBE,OAAOa,IAAI;;;AAIzC"}
@@ -0,0 +1,21 @@
1
+ import type { CappedLogRecord, EndLogRecord, GapLogRecord, RunnerLostLogRecord } from '#core/log-tree.js';
2
+ export interface EndMarkerProps {
3
+ record: EndLogRecord;
4
+ lineCount: number;
5
+ durationMs?: number | null;
6
+ }
7
+ /** Clean end of the log: line count + output bytes (+ overall duration). `total_bytes` is payload bytes. */
8
+ export declare function EndMarker({ record, lineCount, durationMs }: EndMarkerProps): import("react").JSX.Element;
9
+ /** The runner's local backlog shed bytes before upload, so some output never arrived. A warning. */
10
+ export declare function GapMarker({ record }: {
11
+ record: GapLogRecord;
12
+ }): import("react").JSX.Element;
13
+ /** The job hit its shared log size limit; logging stopped but the step kept running. A warning. */
14
+ export declare function CappedMarker({ record }: {
15
+ record: CappedLogRecord;
16
+ }): import("react").JSX.Element;
17
+ /** The runner disappeared and the stream was force-closed. A terminal failure (status taxonomy §9). */
18
+ export declare function RunnerLostMarker({ record }: {
19
+ record: RunnerLostLogRecord;
20
+ }): import("react").JSX.Element;
21
+ //# sourceMappingURL=system-markers.d.ts.map
@@ -0,0 +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;AA6E3B,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"}