@shipfox/client-logs 4.0.0 → 6.0.1
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/CHANGELOG.md +27 -0
- package/dist/components/agent-session-rows.d.ts +1 -1
- package/dist/components/agent-session-rows.d.ts.map +1 -1
- package/dist/components/agent-session-rows.js.map +1 -1
- package/dist/components/log-view.d.ts +1 -1
- package/dist/components/log-view.d.ts.map +1 -1
- package/dist/components/log-view.js.map +1 -1
- package/dist/components/system-markers.js +2 -2
- package/dist/components/system-markers.js.map +1 -1
- package/dist/core/log-model.d.ts +93 -0
- package/dist/core/log-model.d.ts.map +1 -0
- package/dist/core/log-model.js +3 -0
- package/dist/core/log-model.js.map +1 -0
- package/dist/core/log-read.d.ts +7 -14
- package/dist/core/log-read.d.ts.map +1 -1
- package/dist/core/log-read.js +9 -13
- package/dist/core/log-read.js.map +1 -1
- package/dist/core/log-tree.d.ts +1 -1
- package/dist/core/log-tree.d.ts.map +1 -1
- package/dist/core/log-tree.js +3 -3
- package/dist/core/log-tree.js.map +1 -1
- package/dist/hooks/api/log-mapper.d.ts +10 -0
- package/dist/hooks/api/log-mapper.d.ts.map +1 -0
- package/dist/hooks/api/log-mapper.js +92 -0
- package/dist/hooks/api/log-mapper.js.map +1 -0
- package/dist/hooks/api/step-logs.d.ts +7 -2
- package/dist/hooks/api/step-logs.d.ts.map +1 -1
- package/dist/hooks/api/step-logs.js +14 -5
- package/dist/hooks/api/step-logs.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +7 -7
- package/src/components/agent-session-rows.tsx +1 -1
- package/src/components/log-group.stories.tsx +2 -2
- package/src/components/log-view.stories.tsx +10 -10
- package/src/components/log-view.test.tsx +3 -3
- package/src/components/log-view.tsx +1 -1
- package/src/components/system-markers.stories.tsx +3 -3
- package/src/components/system-markers.tsx +2 -2
- package/src/core/log-model.ts +76 -0
- package/src/core/log-read.test.ts +58 -139
- package/src/core/log-read.ts +12 -25
- package/src/core/log-tree.test.ts +10 -10
- package/src/core/log-tree.ts +4 -4
- package/src/hooks/api/log-mapper.test.ts +56 -0
- package/src/hooks/api/log-mapper.ts +75 -0
- package/src/hooks/api/step-logs.test.ts +21 -1
- package/src/hooks/api/step-logs.ts +24 -7
- package/src/index.ts +7 -0
- package/tsconfig.build.tsbuildinfo +1 -1
- package/vercel.json +5 -0
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
$ shipfox-swc
|
|
2
|
-
Successfully compiled:
|
|
2
|
+
Successfully compiled: 13 files with swc (354.85ms)
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# @shipfox/client-logs
|
|
2
2
|
|
|
3
|
+
## 6.0.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 475ce59: Republishes all public packages after restoring release authorization.
|
|
8
|
+
- Updated dependencies [475ce59]
|
|
9
|
+
- Updated dependencies [3f8f1cb]
|
|
10
|
+
- @shipfox/api-logs-dto@9.0.1
|
|
11
|
+
- @shipfox/client-api@6.0.1
|
|
12
|
+
- @shipfox/react-ui@0.3.6
|
|
13
|
+
|
|
14
|
+
## 6.0.0
|
|
15
|
+
|
|
16
|
+
### Patch Changes
|
|
17
|
+
|
|
18
|
+
- Updated dependencies [24be269]
|
|
19
|
+
- Updated dependencies [c02ac42]
|
|
20
|
+
- @shipfox/client-api@6.0.0
|
|
21
|
+
- @shipfox/api-logs-dto@5.0.0
|
|
22
|
+
- @shipfox/react-ui@0.3.5
|
|
23
|
+
|
|
24
|
+
## 5.0.0
|
|
25
|
+
|
|
26
|
+
### Major Changes
|
|
27
|
+
|
|
28
|
+
- ee9d641: Converges step-log snapshots on package-owned camelCase records validated at the transport boundary.
|
|
29
|
+
|
|
3
30
|
## 4.0.0
|
|
4
31
|
|
|
5
32
|
### Patch Changes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-session-rows.d.ts","sourceRoot":"","sources":["../../src/components/agent-session-rows.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"agent-session-rows.d.ts","sourceRoot":"","sources":["../../src/components/agent-session-rows.tsx"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAC,cAAc,EAAqB,MAAM,oBAAoB,CAAC;AAO3E,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,SAAS,cAAc,EAAE,CAAC;IAChC,mBAAmB,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACzC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,gBAAgB,CAAC,EAAC,IAAI,EAAE,mBAAmB,EAAE,MAAM,EAAC,EAAE,qBAAqB,iCAU1F"}
|
|
@@ -1 +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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"}
|
|
1
|
+
{"version":3,"sources":["../../src/components/agent-session-rows.tsx"],"sourcesContent":["'use client';\n\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';\nimport type {SessionViewRow, SessionViewRowMeta} from '#core/log-model.js';\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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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { LogRecord } from '@shipfox/api-logs-dto';
|
|
2
1
|
import { type LogTimestampMode } from '@shipfox/react-ui/log';
|
|
3
2
|
import { type UIEventHandler } from 'react';
|
|
3
|
+
import type { LogRecord } from '#core/log-model.js';
|
|
4
4
|
export interface LogViewProps {
|
|
5
5
|
records: readonly LogRecord[];
|
|
6
6
|
timestamps?: LogTimestampMode;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"log-view.d.ts","sourceRoot":"","sources":["../../src/components/log-view.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"log-view.d.ts","sourceRoot":"","sources":["../../src/components/log-view.tsx"],"names":[],"mappings":"AAGA,OAAO,EAA8B,KAAK,gBAAgB,EAAC,MAAM,uBAAuB,CAAC;AAEzF,OAAO,EAAiB,KAAK,cAAc,EAA6B,MAAM,OAAO,CAAC;AACtF,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,oBAAoB,CAAC;AAalD,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"}
|
|
@@ -1 +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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"}
|
|
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 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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"}
|
|
@@ -68,7 +68,7 @@ const toneText = {
|
|
|
68
68
|
/** Clean end of the log: line count + output bytes (+ overall duration). `total_bytes` is payload bytes. */ export function EndMarker({ record, lineCount, durationMs = null }) {
|
|
69
69
|
const meta = [
|
|
70
70
|
`${lineCount} ${lineCount === 1 ? 'line' : 'lines'}`,
|
|
71
|
-
formatBytes(record.
|
|
71
|
+
formatBytes(record.totalBytes),
|
|
72
72
|
...durationMs != null ? [
|
|
73
73
|
formatDuration(durationMs)
|
|
74
74
|
] : []
|
|
@@ -86,7 +86,7 @@ const toneText = {
|
|
|
86
86
|
icon: "errorWarningLine",
|
|
87
87
|
tone: "warning",
|
|
88
88
|
timestamp: new Date(record.ts),
|
|
89
|
-
detail: `the runner fell behind and dropped ${formatBytes(record.
|
|
89
|
+
detail: `the runner fell behind and dropped ${formatBytes(record.droppedBytes)}`,
|
|
90
90
|
children: "Output missing"
|
|
91
91
|
});
|
|
92
92
|
}
|
|
@@ -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-neutral-muted',\n warning: 'text-orange-600 dark:text-orange-400',\n error: 'text-red-600 dark:text-red-400',\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-8\">\n <Icon name={icon} className=\"size-14 flex-none\" aria-hidden=\"true\" />\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.
|
|
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-neutral-muted',\n warning: 'text-orange-600 dark:text-orange-400',\n error: 'text-red-600 dark:text-red-400',\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-8\">\n <Icon name={icon} className=\"size-14 flex-none\" aria-hidden=\"true\" />\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","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;AAcA;;;;;;CAMC,GACD,SAASC,aAAa,EACpBC,IAAI,EACJC,IAAI,EACJC,YAAY,IAAI,EAChBC,MAAM,EACNC,IAAI,EACJC,kBAAkB,KAAK,EACvBC,QAAQ,EACU;IAClB,qBACE,KAACf;QACCgB,YAAY;QACZL,WAAWA;QACXD,MAAMA;QACNO,6BAA2BH,kBAAkB,SAASI;kBAEtD,cAAA,KAACnB;YAAWoB,WAAWlB,GAAG,SAASG,QAAQ,CAACM,KAAK;sBAC/C,cAAA,MAACU;gBAAKD,WAAU;;kCACd,KAACrB;wBAAKuB,MAAMZ;wBAAMU,WAAU;wBAAoBG,eAAY;;kCAK5D,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;QACpDvB,YAAYsB,OAAOG,UAAU;WACzBD,cAAc,OAAO;YAACvB,eAAeuB;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,EAAEV,YAAYsB,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,93 @@
|
|
|
1
|
+
export interface SessionViewRowMeta {
|
|
2
|
+
label: string;
|
|
3
|
+
value: string;
|
|
4
|
+
inline?: boolean | undefined;
|
|
5
|
+
}
|
|
6
|
+
export type SessionViewRow = {
|
|
7
|
+
kind: 'message';
|
|
8
|
+
timestamp: number;
|
|
9
|
+
role: string;
|
|
10
|
+
label: string;
|
|
11
|
+
meta: readonly SessionViewRowMeta[];
|
|
12
|
+
text: string;
|
|
13
|
+
terminalFailure: boolean;
|
|
14
|
+
} | {
|
|
15
|
+
kind: 'thinking';
|
|
16
|
+
timestamp: number;
|
|
17
|
+
text: string;
|
|
18
|
+
} | {
|
|
19
|
+
kind: 'tool-call';
|
|
20
|
+
timestamp: number;
|
|
21
|
+
id: string | null;
|
|
22
|
+
name: string;
|
|
23
|
+
input: string;
|
|
24
|
+
} | {
|
|
25
|
+
kind: 'tool-result';
|
|
26
|
+
timestamp: number;
|
|
27
|
+
toolCallId: string | null;
|
|
28
|
+
toolName: string;
|
|
29
|
+
output: string;
|
|
30
|
+
isError: boolean;
|
|
31
|
+
} | {
|
|
32
|
+
kind: 'lifecycle';
|
|
33
|
+
timestamp: number;
|
|
34
|
+
label: string;
|
|
35
|
+
detail: string | null;
|
|
36
|
+
meta: readonly SessionViewRowMeta[];
|
|
37
|
+
tone: 'default' | 'warning' | 'error';
|
|
38
|
+
terminalFailure: boolean;
|
|
39
|
+
} | {
|
|
40
|
+
kind: 'raw';
|
|
41
|
+
timestamp: number;
|
|
42
|
+
label: string;
|
|
43
|
+
raw: string;
|
|
44
|
+
};
|
|
45
|
+
interface LogRecordBase {
|
|
46
|
+
v: 1;
|
|
47
|
+
ts: number;
|
|
48
|
+
}
|
|
49
|
+
export type LogRecord = (LogRecordBase & {
|
|
50
|
+
type: 'output';
|
|
51
|
+
stream: 'stdout' | 'stderr';
|
|
52
|
+
data: string;
|
|
53
|
+
}) | (LogRecordBase & {
|
|
54
|
+
type: 'group_start';
|
|
55
|
+
groupId: string;
|
|
56
|
+
parentGroupId: string | null;
|
|
57
|
+
name: string;
|
|
58
|
+
}) | (LogRecordBase & {
|
|
59
|
+
type: 'group_end';
|
|
60
|
+
groupId: string;
|
|
61
|
+
}) | (LogRecordBase & {
|
|
62
|
+
type: 'end';
|
|
63
|
+
totalBytes: number;
|
|
64
|
+
}) | (LogRecordBase & {
|
|
65
|
+
type: 'gap';
|
|
66
|
+
droppedBytes: number;
|
|
67
|
+
}) | (LogRecordBase & {
|
|
68
|
+
type: 'agent_session';
|
|
69
|
+
row: SessionViewRow;
|
|
70
|
+
}) | (LogRecordBase & {
|
|
71
|
+
type: 'capped';
|
|
72
|
+
}) | (LogRecordBase & {
|
|
73
|
+
type: 'runner_lost';
|
|
74
|
+
});
|
|
75
|
+
export type LogSource = 'inline' | 'presigned';
|
|
76
|
+
export type LogState = 'open' | 'closed' | 'compacted';
|
|
77
|
+
export interface InlineLogRead {
|
|
78
|
+
mode: 'inline';
|
|
79
|
+
ndjson: string;
|
|
80
|
+
nextCursor: number;
|
|
81
|
+
hasMore: boolean;
|
|
82
|
+
state: 'open' | 'closed';
|
|
83
|
+
truncated: boolean;
|
|
84
|
+
}
|
|
85
|
+
export interface PresignedLogRead {
|
|
86
|
+
mode: 'presigned';
|
|
87
|
+
url: string;
|
|
88
|
+
expiresAt: string;
|
|
89
|
+
totalBytes: number;
|
|
90
|
+
truncated: boolean;
|
|
91
|
+
}
|
|
92
|
+
export {};
|
|
93
|
+
//# sourceMappingURL=log-model.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log-model.d.ts","sourceRoot":"","sources":["../../src/core/log-model.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/core/log-model.ts"],"sourcesContent":["export interface SessionViewRowMeta {\n label: string;\n value: string;\n inline?: boolean | undefined;\n}\n\nexport type SessionViewRow =\n | {\n kind: 'message';\n timestamp: number;\n role: string;\n label: string;\n meta: readonly SessionViewRowMeta[];\n text: string;\n terminalFailure: boolean;\n }\n | {kind: 'thinking'; timestamp: number; text: string}\n | {kind: 'tool-call'; timestamp: number; id: string | null; name: string; input: string}\n | {\n kind: 'tool-result';\n timestamp: number;\n toolCallId: string | null;\n toolName: string;\n output: string;\n isError: boolean;\n }\n | {\n kind: 'lifecycle';\n timestamp: number;\n label: string;\n detail: string | null;\n meta: readonly SessionViewRowMeta[];\n tone: 'default' | 'warning' | 'error';\n terminalFailure: boolean;\n }\n | {kind: 'raw'; timestamp: number; label: string; raw: string};\n\ninterface LogRecordBase {\n v: 1;\n ts: number;\n}\n\nexport type LogRecord =\n | (LogRecordBase & {type: 'output'; stream: 'stdout' | 'stderr'; data: string})\n | (LogRecordBase & {\n type: 'group_start';\n groupId: string;\n parentGroupId: string | null;\n name: string;\n })\n | (LogRecordBase & {type: 'group_end'; groupId: string})\n | (LogRecordBase & {type: 'end'; totalBytes: number})\n | (LogRecordBase & {type: 'gap'; droppedBytes: number})\n | (LogRecordBase & {type: 'agent_session'; row: SessionViewRow})\n | (LogRecordBase & {type: 'capped'})\n | (LogRecordBase & {type: 'runner_lost'});\n\nexport type LogSource = 'inline' | 'presigned';\nexport type LogState = 'open' | 'closed' | 'compacted';\n\nexport interface InlineLogRead {\n mode: 'inline';\n ndjson: string;\n nextCursor: number;\n hasMore: boolean;\n state: 'open' | 'closed';\n truncated: boolean;\n}\n\nexport interface PresignedLogRead {\n mode: 'presigned';\n url: string;\n expiresAt: string;\n totalBytes: number;\n truncated: boolean;\n}\n"],"names":[],"mappings":"AAqEA,WAMC"}
|
package/dist/core/log-read.d.ts
CHANGED
|
@@ -1,17 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { InlineLogRead, LogRecord, LogSource, LogState, PresignedLogRead } from './log-model.js';
|
|
2
2
|
export declare const STEP_LOG_DRAIN_REFETCH_MS = 250;
|
|
3
3
|
export declare const STEP_LOG_LIVE_REFETCH_MS = 2000;
|
|
4
|
-
type InlineReadLogsResponse = Extract<ReadLogsResponseDto, {
|
|
5
|
-
mode: 'inline';
|
|
6
|
-
}>;
|
|
7
|
-
type PresignedReadLogsResponse = Extract<ReadLogsResponseDto, {
|
|
8
|
-
mode: 'presigned';
|
|
9
|
-
}>;
|
|
10
4
|
export interface StepLogSnapshot {
|
|
11
5
|
records: LogRecord[];
|
|
12
6
|
nextCursor: number;
|
|
13
|
-
source:
|
|
14
|
-
state:
|
|
7
|
+
source: LogSource;
|
|
8
|
+
state: LogState;
|
|
15
9
|
complete: boolean;
|
|
16
10
|
hasMore: boolean;
|
|
17
11
|
truncated: boolean;
|
|
@@ -20,14 +14,13 @@ export interface StepLogSnapshot {
|
|
|
20
14
|
}
|
|
21
15
|
export type ResolvedStepLogRead = {
|
|
22
16
|
mode: 'inline';
|
|
23
|
-
response:
|
|
17
|
+
response: InlineLogRead;
|
|
18
|
+
records: readonly LogRecord[];
|
|
24
19
|
} | {
|
|
25
20
|
mode: 'presigned';
|
|
26
|
-
response:
|
|
27
|
-
|
|
21
|
+
response: PresignedLogRead;
|
|
22
|
+
records: readonly LogRecord[];
|
|
28
23
|
};
|
|
29
|
-
export declare function parseLogNdjson(ndjson: string): LogRecord[];
|
|
30
24
|
export declare function mergeLogRead(previous: StepLogSnapshot | undefined, read: ResolvedStepLogRead): StepLogSnapshot;
|
|
31
25
|
export declare function stepLogRefetchInterval(snapshot: StepLogSnapshot | undefined, lastFetchErrored?: boolean): number | false;
|
|
32
|
-
export {};
|
|
33
26
|
//# sourceMappingURL=log-read.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"log-read.d.ts","sourceRoot":"","sources":["../../src/core/log-read.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,
|
|
1
|
+
{"version":3,"file":"log-read.d.ts","sourceRoot":"","sources":["../../src/core/log-read.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,aAAa,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,gBAAgB,EAAC,MAAM,gBAAgB,CAAC;AAEpG,eAAO,MAAM,yBAAyB,MAAM,CAAC;AAC7C,eAAO,MAAM,wBAAwB,OAAQ,CAAC;AAE9C,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,SAAS,EAAE,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,SAAS,CAAC;IAClB,KAAK,EAAE,QAAQ,CAAC;IAChB,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,MAAM,MAAM,mBAAmB,GAC3B;IAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,EAAE,aAAa,CAAC;IAAC,OAAO,EAAE,SAAS,SAAS,EAAE,CAAA;CAAC,GACxE;IAAC,IAAI,EAAE,WAAW,CAAC;IAAC,QAAQ,EAAE,gBAAgB,CAAC;IAAC,OAAO,EAAE,SAAS,SAAS,EAAE,CAAA;CAAC,CAAC;AAEnF,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,eAAe,GAAG,SAAS,EACrC,IAAI,EAAE,mBAAmB,GACxB,eAAe,CA4BjB;AAED,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,eAAe,GAAG,SAAS,EACrC,gBAAgB,UAAQ,GACvB,MAAM,GAAG,KAAK,CAQhB"}
|
package/dist/core/log-read.js
CHANGED
|
@@ -1,36 +1,32 @@
|
|
|
1
|
-
import { parseLogRecordLine } from '@shipfox/api-logs-dto';
|
|
2
1
|
export const STEP_LOG_DRAIN_REFETCH_MS = 250;
|
|
3
2
|
export const STEP_LOG_LIVE_REFETCH_MS = 2_000;
|
|
4
|
-
const NDJSON_LINE_BREAK = /\r?\n/;
|
|
5
|
-
export function parseLogNdjson(ndjson) {
|
|
6
|
-
return ndjson.split(NDJSON_LINE_BREAK).filter((line)=>line.length > 0).map(parseLogRecordLine);
|
|
7
|
-
}
|
|
8
3
|
export function mergeLogRead(previous, read) {
|
|
9
4
|
if (read.mode === 'presigned') {
|
|
10
5
|
return {
|
|
11
|
-
records:
|
|
6
|
+
records: [
|
|
7
|
+
...read.records
|
|
8
|
+
],
|
|
12
9
|
nextCursor: 0,
|
|
13
10
|
source: 'presigned',
|
|
14
11
|
state: 'compacted',
|
|
15
12
|
complete: true,
|
|
16
13
|
hasMore: false,
|
|
17
14
|
truncated: read.response.truncated,
|
|
18
|
-
totalBytes: read.response.
|
|
19
|
-
expiresAt: read.response.
|
|
15
|
+
totalBytes: read.response.totalBytes,
|
|
16
|
+
expiresAt: read.response.expiresAt
|
|
20
17
|
};
|
|
21
18
|
}
|
|
22
|
-
const
|
|
23
|
-
const complete = read.response.state === 'closed' && !read.response.has_more;
|
|
19
|
+
const complete = read.response.state === 'closed' && !read.response.hasMore;
|
|
24
20
|
return {
|
|
25
21
|
records: [
|
|
26
22
|
...previous?.records ?? [],
|
|
27
|
-
...records
|
|
23
|
+
...read.records
|
|
28
24
|
],
|
|
29
|
-
nextCursor: read.response.
|
|
25
|
+
nextCursor: read.response.nextCursor,
|
|
30
26
|
source: 'inline',
|
|
31
27
|
state: read.response.state,
|
|
32
28
|
complete,
|
|
33
|
-
hasMore: read.response.
|
|
29
|
+
hasMore: read.response.hasMore,
|
|
34
30
|
truncated: read.response.truncated,
|
|
35
31
|
totalBytes: null,
|
|
36
32
|
expiresAt: null
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/core/log-read.ts"],"sourcesContent":["import
|
|
1
|
+
{"version":3,"sources":["../../src/core/log-read.ts"],"sourcesContent":["import type {InlineLogRead, LogRecord, LogSource, LogState, PresignedLogRead} from './log-model.js';\n\nexport const STEP_LOG_DRAIN_REFETCH_MS = 250;\nexport const STEP_LOG_LIVE_REFETCH_MS = 2_000;\n\nexport interface StepLogSnapshot {\n records: LogRecord[];\n nextCursor: number;\n source: LogSource;\n state: LogState;\n complete: boolean;\n hasMore: boolean;\n truncated: boolean;\n totalBytes: number | null;\n expiresAt: string | null;\n}\n\nexport type ResolvedStepLogRead =\n | {mode: 'inline'; response: InlineLogRead; records: readonly LogRecord[]}\n | {mode: 'presigned'; response: PresignedLogRead; records: readonly LogRecord[]};\n\nexport function mergeLogRead(\n previous: StepLogSnapshot | undefined,\n read: ResolvedStepLogRead,\n): StepLogSnapshot {\n if (read.mode === 'presigned') {\n return {\n records: [...read.records],\n nextCursor: 0,\n source: 'presigned',\n state: 'compacted',\n complete: true,\n hasMore: false,\n truncated: read.response.truncated,\n totalBytes: read.response.totalBytes,\n expiresAt: read.response.expiresAt,\n };\n }\n\n const complete = read.response.state === 'closed' && !read.response.hasMore;\n\n return {\n records: [...(previous?.records ?? []), ...read.records],\n nextCursor: read.response.nextCursor,\n source: 'inline',\n state: read.response.state,\n complete,\n hasMore: read.response.hasMore,\n truncated: read.response.truncated,\n totalBytes: null,\n expiresAt: null,\n };\n}\n\nexport function stepLogRefetchInterval(\n snapshot: StepLogSnapshot | undefined,\n lastFetchErrored = false,\n): number | false {\n // A persistent failure (deleted step, 5xx, an unparseable record line) never advances\n // the cursor, so without this guard the interval re-polls the same dead cursor forever.\n // Stop once a fetch errors out (React Query's own retry absorbs transient blips first);\n // refetchOnWindowFocus/Reconnect (gated on !complete) resume it.\n if (lastFetchErrored) return false;\n if (!snapshot || snapshot.complete) return false;\n return snapshot.hasMore ? STEP_LOG_DRAIN_REFETCH_MS : STEP_LOG_LIVE_REFETCH_MS;\n}\n"],"names":["STEP_LOG_DRAIN_REFETCH_MS","STEP_LOG_LIVE_REFETCH_MS","mergeLogRead","previous","read","mode","records","nextCursor","source","state","complete","hasMore","truncated","response","totalBytes","expiresAt","stepLogRefetchInterval","snapshot","lastFetchErrored"],"mappings":"AAEA,OAAO,MAAMA,4BAA4B,IAAI;AAC7C,OAAO,MAAMC,2BAA2B,MAAM;AAkB9C,OAAO,SAASC,aACdC,QAAqC,EACrCC,IAAyB;IAEzB,IAAIA,KAAKC,IAAI,KAAK,aAAa;QAC7B,OAAO;YACLC,SAAS;mBAAIF,KAAKE,OAAO;aAAC;YAC1BC,YAAY;YACZC,QAAQ;YACRC,OAAO;YACPC,UAAU;YACVC,SAAS;YACTC,WAAWR,KAAKS,QAAQ,CAACD,SAAS;YAClCE,YAAYV,KAAKS,QAAQ,CAACC,UAAU;YACpCC,WAAWX,KAAKS,QAAQ,CAACE,SAAS;QACpC;IACF;IAEA,MAAML,WAAWN,KAAKS,QAAQ,CAACJ,KAAK,KAAK,YAAY,CAACL,KAAKS,QAAQ,CAACF,OAAO;IAE3E,OAAO;QACLL,SAAS;eAAKH,UAAUG,WAAW,EAAE;eAAMF,KAAKE,OAAO;SAAC;QACxDC,YAAYH,KAAKS,QAAQ,CAACN,UAAU;QACpCC,QAAQ;QACRC,OAAOL,KAAKS,QAAQ,CAACJ,KAAK;QAC1BC;QACAC,SAASP,KAAKS,QAAQ,CAACF,OAAO;QAC9BC,WAAWR,KAAKS,QAAQ,CAACD,SAAS;QAClCE,YAAY;QACZC,WAAW;IACb;AACF;AAEA,OAAO,SAASC,uBACdC,QAAqC,EACrCC,mBAAmB,KAAK;IAExB,sFAAsF;IACtF,wFAAwF;IACxF,wFAAwF;IACxF,iEAAiE;IACjE,IAAIA,kBAAkB,OAAO;IAC7B,IAAI,CAACD,YAAYA,SAASP,QAAQ,EAAE,OAAO;IAC3C,OAAOO,SAASN,OAAO,GAAGX,4BAA4BC;AACxD"}
|
package/dist/core/log-tree.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { LogRecord } from '
|
|
1
|
+
import type { LogRecord } from './log-model.js';
|
|
2
2
|
/**
|
|
3
3
|
* Pure render transform for the step-log read stream. The runner emits a flat,
|
|
4
4
|
* ordered NDJSON record list; `group_start`/`group_end` form a tree that the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"log-tree.d.ts","sourceRoot":"","sources":["../../src/core/log-tree.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,
|
|
1
|
+
{"version":3,"file":"log-tree.d.ts","sourceRoot":"","sources":["../../src/core/log-tree.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,gBAAgB,CAAC;AAE9C;;;;;;;;;;;GAWG;AAEH,MAAM,MAAM,eAAe,GAAG,OAAO,CAAC,SAAS,EAAE;IAAC,IAAI,EAAE,QAAQ,CAAA;CAAC,CAAC,CAAC;AACnE,MAAM,MAAM,mBAAmB,GAAG,OAAO,CAAC,SAAS,EAAE;IAAC,IAAI,EAAE,aAAa,CAAA;CAAC,CAAC,CAAC;AAC5E,MAAM,MAAM,YAAY,GAAG,OAAO,CAAC,SAAS,EAAE;IAAC,IAAI,EAAE,KAAK,CAAA;CAAC,CAAC,CAAC;AAC7D,MAAM,MAAM,YAAY,GAAG,OAAO,CAAC,SAAS,EAAE;IAAC,IAAI,EAAE,KAAK,CAAA;CAAC,CAAC,CAAC;AAC7D,MAAM,MAAM,eAAe,GAAG,OAAO,CAAC,SAAS,EAAE;IAAC,IAAI,EAAE,QAAQ,CAAA;CAAC,CAAC,CAAC;AACnE,MAAM,MAAM,mBAAmB,GAAG,OAAO,CAAC,SAAS,EAAE;IAAC,IAAI,EAAE,aAAa,CAAA;CAAC,CAAC,CAAC;AAC5E,MAAM,MAAM,qBAAqB,GAAG,OAAO,CAAC,SAAS,EAAE;IAAC,IAAI,EAAE,eAAe,CAAA;CAAC,CAAC,CAAC;AAChF,MAAM,MAAM,eAAe,GAAG,YAAY,GAAG,YAAY,GAAG,eAAe,GAAG,mBAAmB,CAAC;AAElG;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,aAAc,SAAQ,WAAW;IAChD,IAAI,EAAE,QAAQ,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,eAAe,CAAC;CACzB;AAED,MAAM,WAAW,aAAc,SAAQ,WAAW;IAChD,IAAI,EAAE,QAAQ,CAAC;IACf,MAAM,EAAE,eAAe,CAAC;CACzB;AAED,MAAM,WAAW,YAAa,SAAQ,WAAW;IAC/C,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,mBAAmB,CAAC;IAC5B,kFAAkF;IAClF,MAAM,EAAE,OAAO,CAAC;IAChB,wEAAwE;IACxE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,qIAAqI;IACrI,QAAQ,EAAE,OAAO,CAAC;IAClB,+EAA+E;IAC/E,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,cAAe,SAAQ,WAAW;IACjD,IAAI,EAAE,SAAS,CAAC;IAChB,MAAM,EAAE,qBAAqB,CAAC;CAC/B;AAED,MAAM,MAAM,OAAO,GAAG,aAAa,GAAG,aAAa,GAAG,YAAY,GAAG,cAAc,CAAC;AAEpF,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,OAAO,EAAE,CAAC;IACjB,6EAA6E;IAC7E,UAAU,EAAE,OAAO,CAAC;IACpB,uFAAuF;IACvF,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,oFAAoF;IACpF,SAAS,EAAE,MAAM,CAAC;CACnB;AAID,sHAAsH;AACtH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,KAAK,GAAG,KAAK,CAE/C;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,SAAS,SAAS,EAAE,GAAG,OAAO,CAgHnE"}
|
package/dist/core/log-tree.js
CHANGED
|
@@ -43,11 +43,11 @@ export function buildLogTree(records) {
|
|
|
43
43
|
// own `group_end` was dropped under backlog pressure. Orphan-close those frames so a
|
|
44
44
|
// dropped end never mis-parents the groups that follow. A parent whose own start was
|
|
45
45
|
// dropped is not on the stack: it falls through to best-effort root placement.
|
|
46
|
-
const parentId = record.
|
|
46
|
+
const parentId = record.parentGroupId;
|
|
47
47
|
let parentIndex = -1;
|
|
48
48
|
if (parentId !== null) {
|
|
49
49
|
for(let i = stack.length - 1; i >= 0; i -= 1){
|
|
50
|
-
if (stack[i]?.record.
|
|
50
|
+
if (stack[i]?.record.groupId === parentId) {
|
|
51
51
|
parentIndex = i;
|
|
52
52
|
break;
|
|
53
53
|
}
|
|
@@ -78,7 +78,7 @@ export function buildLogTree(records) {
|
|
|
78
78
|
// group_end close with it. An end with no matching open start is ignored.
|
|
79
79
|
let matchIndex = -1;
|
|
80
80
|
for(let i = stack.length - 1; i >= 0; i -= 1){
|
|
81
|
-
if (stack[i]?.record.
|
|
81
|
+
if (stack[i]?.record.groupId === record.groupId) {
|
|
82
82
|
matchIndex = i;
|
|
83
83
|
break;
|
|
84
84
|
}
|