@shipfox/api-logs 10.1.0 → 11.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/.turbo/turbo-build.log +4 -4
  2. package/CHANGELOG.md +26 -0
  3. package/dist/core/append-logs.d.ts.map +1 -1
  4. package/dist/core/append-logs.js +13 -5
  5. package/dist/core/append-logs.js.map +1 -1
  6. package/dist/core/close-stream.d.ts.map +1 -1
  7. package/dist/core/close-stream.js +16 -1
  8. package/dist/core/close-stream.js.map +1 -1
  9. package/dist/core/entities/attempt-stream.d.ts +2 -1
  10. package/dist/core/entities/attempt-stream.d.ts.map +1 -1
  11. package/dist/core/entities/attempt-stream.js.map +1 -1
  12. package/dist/core/session/claude/rows.d.ts +15 -3
  13. package/dist/core/session/claude/rows.d.ts.map +1 -1
  14. package/dist/core/session/claude/rows.js +127 -13
  15. package/dist/core/session/claude/rows.js.map +1 -1
  16. package/dist/core/session/claude-parser.d.ts +4 -2
  17. package/dist/core/session/claude-parser.d.ts.map +1 -1
  18. package/dist/core/session/claude-parser.js +56 -10
  19. package/dist/core/session/claude-parser.js.map +1 -1
  20. package/dist/db/db.d.ts +113 -0
  21. package/dist/db/db.d.ts.map +1 -1
  22. package/dist/db/schema/attempt-streams.d.ts +113 -0
  23. package/dist/db/schema/attempt-streams.d.ts.map +1 -1
  24. package/dist/db/schema/attempt-streams.js +2 -0
  25. package/dist/db/schema/attempt-streams.js.map +1 -1
  26. package/dist/db/streams.d.ts +3 -1
  27. package/dist/db/streams.d.ts.map +1 -1
  28. package/dist/db/streams.js +6 -1
  29. package/dist/db/streams.js.map +1 -1
  30. package/dist/presentation/routes/read-logs.d.ts.map +1 -1
  31. package/dist/presentation/routes/read-logs.js +11 -5
  32. package/dist/presentation/routes/read-logs.js.map +1 -1
  33. package/dist/tsconfig.test.tsbuildinfo +1 -1
  34. package/drizzle/0002_wise_dorian_gray.sql +1 -0
  35. package/drizzle/meta/0002_snapshot.json +561 -0
  36. package/drizzle/meta/_journal.json +7 -0
  37. package/package.json +3 -3
  38. package/src/core/append-logs.test.ts +57 -0
  39. package/src/core/append-logs.ts +15 -3
  40. package/src/core/close-stream.ts +17 -1
  41. package/src/core/entities/attempt-stream.ts +2 -1
  42. package/src/core/session/claude/rows.ts +172 -9
  43. package/src/core/session/claude-parser.test.ts +453 -4
  44. package/src/core/session/claude-parser.ts +59 -10
  45. package/src/db/schema/attempt-streams.ts +3 -1
  46. package/src/db/streams.ts +10 -2
  47. package/src/presentation/routes/read-logs.test.ts +18 -4
  48. package/src/presentation/routes/read-logs.ts +9 -5
  49. package/test/fixtures/claude-session/agent-step.jsonl +12 -0
  50. package/test/fixtures/claude-session/sdk-system-subtypes.json +32 -0
  51. package/test/fixtures/claude-session.ts +45 -0
  52. package/tsconfig.build.tsbuildinfo +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/core/session/claude/rows.ts"],"sourcesContent":["import type {\n SessionViewLifecycleRow,\n SessionViewRow,\n SessionViewRowMeta,\n SessionViewToolCallRow,\n SessionViewToolResultRow,\n} from '@shipfox/api-logs-dto';\nimport type {ClaudeParseContext} from '../claude-parser.js';\nimport {asLooseObject} from '../entry-schema.js';\nimport {\n booleanField,\n field,\n formatNumber,\n isMeta,\n metaItem,\n numberField,\n stringField,\n stringifyValue,\n toJson,\n} from '../object.js';\nimport {lifecycleRow, messageRow, thinkingRow} from '../rows.js';\n\nexport const PURE_PROGRESS_CLAUDE_SYSTEM_SUBTYPES = new Set<string>([\n 'thinking_tokens',\n 'status',\n 'session_state_changed',\n 'task_progress',\n 'hook_progress',\n 'hook_started',\n 'commands_changed',\n 'files_persisted',\n 'memory_recall',\n 'local_command_output',\n 'plugin_install',\n]);\nconst OUTPUT_REPROMPT_PREFIX = 'The previous turn ended without setting required workflow outputs:';\n\ntype SystemEventMapping = {\n label: string | ((message: Record<string, unknown>) => string);\n tone:\n | SessionViewLifecycleRow['tone']\n | ((message: Record<string, unknown>) => SessionViewLifecycleRow['tone']);\n detail?: (message: Record<string, unknown>) => string | null;\n meta?: (message: Record<string, unknown>) => readonly SessionViewRowMeta[];\n};\n\nconst SYSTEM_EVENT_MAPPINGS: Record<string, SystemEventMapping> = {\n permission_denied: {\n label: 'Permission denied',\n tone: 'error',\n detail: (message) => stringField(message, 'tool_name') ?? null,\n meta: (message) =>\n [\n metaItem(\n 'reason',\n stringField(message, 'decision_reason') ?? stringField(message, 'message'),\n ),\n ].filter(isMeta),\n },\n api_retry: {\n label: 'API retry',\n tone: 'warning',\n detail: (message) => {\n const attempt = numberField(message, 'attempt');\n const maxRetries = numberField(message, 'max_retries');\n return attempt == null || maxRetries == null\n ? null\n : `attempt ${formatNumber(attempt)}/${formatNumber(maxRetries)}`;\n },\n meta: (message) =>\n [\n numberMeta(message, 'error_status', 'status'),\n numberMeta(message, 'retry_delay_ms', 'retry delay', 'ms'),\n ].filter(isMeta),\n },\n informational: {\n label: (message) => titleCase(stringField(message, 'level') ?? 'informational'),\n tone: (message) => (stringField(message, 'level') === 'warning' ? 'warning' : 'default'),\n detail: (message) => stringField(message, 'content') ?? null,\n },\n compact_boundary: {\n label: 'Context compacted',\n tone: 'default',\n meta: (message) => {\n const compactMetadata = asLooseObject(field(message, 'compact_metadata')) ?? {};\n return [\n metaItem('trigger', stringField(compactMetadata, 'trigger')),\n numberMeta(compactMetadata, 'pre_tokens', 'tokens before', 'tokens'),\n numberMeta(compactMetadata, 'post_tokens', 'tokens after', 'tokens'),\n numberMeta(compactMetadata, 'duration_ms', 'duration', 'ms'),\n ].filter(isMeta);\n },\n },\n model_refusal_fallback: {\n label: 'Model refused, fell back',\n tone: 'warning',\n },\n model_refusal_no_fallback: {\n label: 'Model refused',\n tone: 'error',\n },\n mirror_error: {\n label: 'Session mirror failed',\n tone: 'error',\n detail: (message) => stringField(message, 'error') ?? null,\n },\n worker_shutting_down: {\n label: 'Worker shutting down',\n tone: 'warning',\n },\n elicitation_complete: {\n label: 'Elicitation complete',\n tone: 'default',\n },\n notification: {\n label: 'Notification',\n tone: 'default',\n },\n task_started: {\n label: 'Task started',\n tone: 'default',\n },\n task_updated: {\n label: 'Task updated',\n tone: 'default',\n },\n task_notification: {\n label: 'Task notification',\n tone: 'default',\n },\n};\n\nexport function systemRow(\n timestamp: number,\n message: Record<string, unknown>,\n context: ClaudeParseContext,\n): SessionViewLifecycleRow {\n const subtype = stringField(message, 'subtype');\n const sessionId = stringField(message, 'session_id') ?? stringField(message, 'sessionId');\n const isInit = subtype === 'init' || message.type === 'init';\n const baseMeta = [\n metaItem('cwd', stringField(message, 'cwd'), false),\n metaItem('model', stringField(message, 'model')),\n metaItem(\n 'permission',\n stringField(message, 'permissionMode') ?? stringField(message, 'permission_mode'),\n ),\n ].filter(isMeta);\n\n if (!isInit) {\n const mapping = subtype === undefined ? undefined : SYSTEM_EVENT_MAPPINGS[subtype];\n const label =\n mapping === undefined\n ? `Session event (${subtype ?? 'unknown'})`\n : typeof mapping.label === 'function'\n ? mapping.label(message)\n : mapping.label;\n const tone =\n mapping === undefined\n ? 'default'\n : typeof mapping.tone === 'function'\n ? mapping.tone(message)\n : mapping.tone;\n const detail = mapping?.detail?.(message) ?? null;\n const meta = [...baseMeta, ...(mapping?.meta?.(message) ?? [])];\n\n return lifecycleRow(timestamp, label, detail, tone, false, meta);\n }\n\n const isNewSession =\n !context.hasInit || sessionId === undefined || sessionId !== context.sessionId;\n context.hasInit = true;\n context.sessionId = sessionId ?? null;\n context.turn = isNewSession ? 1 : context.turn + 1;\n\n const label = isNewSession ? 'Session started' : `Turn ${context.turn} started`;\n const meta = [\n metaItem('session', isNewSession ? sessionId : null),\n metaItem('turn', isNewSession ? null : String(context.turn)),\n ...baseMeta,\n ].filter(isMeta);\n\n return lifecycleRow(timestamp, label, null, 'default', false, meta);\n}\n\nfunction titleCase(value: string): string {\n return value.length === 0 ? value : `${value[0]?.toUpperCase()}${value.slice(1)}`;\n}\n\nexport function assistantRows(\n timestamp: number,\n message: Record<string, unknown>,\n): readonly SessionViewRow[] {\n const sdkMessage = asLooseObject(message.message) ?? message;\n const rows: SessionViewRow[] = [];\n const textParts: string[] = [];\n const thinkingParts: string[] = [];\n\n const pushText = () => {\n if (textParts.length === 0) return;\n rows.push(messageRow(timestamp, 'assistant', 'assistant', textParts.join('\\n\\n'), false));\n textParts.length = 0;\n };\n const pushThinking = () => {\n if (thinkingParts.length === 0) return;\n rows.push(thinkingRow(timestamp, thinkingParts.join('\\n\\n')));\n thinkingParts.length = 0;\n };\n\n for (const block of contentBlocks(sdkMessage)) {\n const type = stringField(block, 'type');\n if (type === 'tool_use' || type === 'tool-use' || type === 'toolCall') {\n pushText();\n pushThinking();\n rows.push(toolCallRow(timestamp, block));\n continue;\n }\n\n if (type === 'thinking' || type === 'reasoning') {\n pushText();\n const text = blockText(block);\n if (text) thinkingParts.push(text);\n continue;\n }\n\n pushThinking();\n const text = blockText(block);\n if (text) textParts.push(text);\n }\n\n pushText();\n pushThinking();\n\n if (rows.length > 0) return rows;\n\n const text = stringField(sdkMessage, 'content') ?? stringField(message, 'result');\n return [messageRow(timestamp, 'assistant', 'assistant', text ?? toJson(message), false)];\n}\n\nexport function userRows(\n timestamp: number,\n message: Record<string, unknown>,\n): readonly SessionViewRow[] {\n const sdkMessage = asLooseObject(message.message) ?? message;\n const role = isPlatformMessage(sdkMessage) ? 'platform' : 'user';\n const rows: SessionViewRow[] = [];\n const textParts: string[] = [];\n const pushText = () => {\n if (textParts.length === 0) return;\n rows.push(messageRow(timestamp, role, role, textParts.join('\\n\\n'), false));\n textParts.length = 0;\n };\n\n for (const block of contentBlocks(sdkMessage)) {\n const type = stringField(block, 'type');\n if (type === 'tool_result' || type === 'tool-result') {\n pushText();\n rows.push(toolResultRow(timestamp, block));\n continue;\n }\n\n const text = blockText(block);\n if (text) textParts.push(text);\n }\n\n pushText();\n\n if (rows.length > 0) return rows;\n\n const content = stringField(sdkMessage, 'content');\n return [messageRow(timestamp, role, role, content ?? toJson(message), false)];\n}\n\nexport function resultRow(\n timestamp: number,\n message: Record<string, unknown>,\n turn: number,\n isFinalResult: boolean,\n): SessionViewLifecycleRow {\n const isError = booleanField(message, 'is_error') || booleanField(message, 'isError');\n const subtype = stringField(message, 'subtype');\n const terminalFailure = isError || subtype === 'error';\n const detail =\n stringField(message, 'result') ??\n stringField(message, 'error') ??\n stringField(message, 'message') ??\n null;\n const meta = [\n metaItem('turn', !terminalFailure && !isFinalResult && turn > 0 ? String(turn) : null),\n numberMeta(message, 'duration_ms', 'duration', 'ms'),\n numberMeta(message, 'duration_api_ms', 'api duration', 'ms'),\n numberMeta(message, 'num_turns', 'turns'),\n costMeta(message),\n ].filter(isMeta);\n\n return lifecycleRow(\n timestamp,\n terminalFailure\n ? 'Session failed'\n : isFinalResult || turn === 0\n ? 'Session completed'\n : `Turn ${turn} completed`,\n detail,\n terminalFailure ? 'error' : 'default',\n terminalFailure,\n meta,\n );\n}\n\nfunction isPlatformMessage(message: Record<string, unknown>): boolean {\n const content = field(message, 'content');\n if (typeof content === 'string') return content.startsWith(OUTPUT_REPROMPT_PREFIX);\n if (!Array.isArray(content)) return false;\n\n return content.some((block) => {\n const object = asLooseObject(block);\n return stringField(object, 'text')?.startsWith(OUTPUT_REPROMPT_PREFIX) === true;\n });\n}\n\nfunction contentBlocks(message: Record<string, unknown>): Record<string, unknown>[] {\n const content = message.content;\n if (!Array.isArray(content)) return [];\n\n return content.flatMap((block) => {\n const object = asLooseObject(block);\n return object ? [object] : [];\n });\n}\n\nfunction toolCallRow(timestamp: number, block: Record<string, unknown>): SessionViewToolCallRow {\n return {\n kind: 'tool-call',\n timestamp,\n id: stringField(block, 'id') ?? null,\n name: stringField(block, 'name') ?? 'tool',\n input: stringifyValue(field(block, 'input') ?? {}),\n };\n}\n\nfunction toolResultRow(\n timestamp: number,\n block: Record<string, unknown>,\n): SessionViewToolResultRow {\n return {\n kind: 'tool-result',\n timestamp,\n toolCallId: stringField(block, 'tool_use_id') ?? stringField(block, 'toolUseId') ?? null,\n toolName: stringField(block, 'name') ?? 'tool',\n output: blockText(block) || stringifyValue(field(block, 'content') ?? ''),\n isError: booleanField(block, 'is_error') || booleanField(block, 'isError'),\n };\n}\n\nfunction blockText(block: Record<string, unknown>): string {\n const content = field(block, 'content');\n if (Array.isArray(content)) {\n return content\n .map((item) => {\n const object = asLooseObject(item);\n return object ? blockText(object) : typeof item === 'string' ? item : '';\n })\n .filter(Boolean)\n .join('\\n\\n');\n }\n\n return (\n stringField(block, 'text') ??\n stringField(block, 'thinking') ??\n (typeof content === 'string' ? content : '') ??\n ''\n );\n}\n\nfunction numberMeta(\n value: unknown,\n key: string,\n label: string,\n unit?: string,\n): SessionViewRowMeta | null {\n const raw = field(value, key);\n if (typeof raw !== 'number' || !Number.isFinite(raw)) return null;\n\n return metaItem(label, unit ? `${formatNumber(raw)} ${unit}` : formatNumber(raw));\n}\n\nfunction costMeta(message: Record<string, unknown>): SessionViewRowMeta | null {\n const value = field(message, 'total_cost_usd') ?? field(message, 'totalCostUsd');\n if (typeof value !== 'number' || !Number.isFinite(value)) return null;\n\n return metaItem('cost', `$${value.toFixed(value < 0.01 ? 4 : 2)}`);\n}\n"],"names":["asLooseObject","booleanField","field","formatNumber","isMeta","metaItem","numberField","stringField","stringifyValue","toJson","lifecycleRow","messageRow","thinkingRow","PURE_PROGRESS_CLAUDE_SYSTEM_SUBTYPES","Set","OUTPUT_REPROMPT_PREFIX","SYSTEM_EVENT_MAPPINGS","permission_denied","label","tone","detail","message","meta","filter","api_retry","attempt","maxRetries","numberMeta","informational","titleCase","compact_boundary","compactMetadata","model_refusal_fallback","model_refusal_no_fallback","mirror_error","worker_shutting_down","elicitation_complete","notification","task_started","task_updated","task_notification","systemRow","timestamp","context","subtype","sessionId","isInit","type","baseMeta","mapping","undefined","isNewSession","hasInit","turn","String","value","length","toUpperCase","slice","assistantRows","sdkMessage","rows","textParts","thinkingParts","pushText","push","join","pushThinking","block","contentBlocks","toolCallRow","text","blockText","userRows","role","isPlatformMessage","toolResultRow","content","resultRow","isFinalResult","isError","terminalFailure","costMeta","startsWith","Array","isArray","some","object","flatMap","kind","id","name","input","toolCallId","toolName","output","map","item","Boolean","key","unit","raw","Number","isFinite","toFixed"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../src/core/session/claude/rows.ts"],"sourcesContent":["import type {\n SessionViewLifecycleRow,\n SessionViewRow,\n SessionViewRowMeta,\n SessionViewToolCallRow,\n SessionViewToolResultRow,\n} from '@shipfox/api-logs-dto';\nimport type {ClaudeParseContext} from '../claude-parser.js';\nimport {asLooseObject} from '../entry-schema.js';\nimport {\n booleanField,\n field,\n formatNumber,\n isMeta,\n metaItem,\n numberField,\n stringField,\n stringifyValue,\n toJson,\n} from '../object.js';\nimport {lifecycleRow, messageRow, thinkingRow} from '../rows.js';\n\nexport const PURE_PROGRESS_CLAUDE_SYSTEM_SUBTYPES = new Set<string>([\n 'thinking_tokens',\n 'status',\n 'session_state_changed',\n 'task_progress',\n 'hook_progress',\n 'hook_started',\n 'commands_changed',\n 'files_persisted',\n 'memory_recall',\n 'local_command_output',\n 'plugin_install',\n]);\nconst OUTPUT_REPROMPT_PREFIX = 'The previous turn ended without setting required workflow outputs:';\n\ntype SystemEventMapping = {\n label: string | ((message: Record<string, unknown>) => string);\n tone:\n | SessionViewLifecycleRow['tone']\n | ((message: Record<string, unknown>) => SessionViewLifecycleRow['tone']);\n detail?: (message: Record<string, unknown>) => string | null;\n meta?: (message: Record<string, unknown>) => readonly SessionViewRowMeta[];\n};\n\nexport const SYSTEM_EVENT_MAPPINGS: Record<string, SystemEventMapping> = {\n permission_denied: {\n label: 'Permission denied',\n tone: 'error',\n detail: (message) => stringField(message, 'tool_name') ?? null,\n meta: (message) =>\n [\n metaItem(\n 'reason',\n stringField(message, 'decision_reason') ?? stringField(message, 'message'),\n ),\n ].filter(isMeta),\n },\n api_retry: {\n label: 'API retry',\n tone: 'warning',\n detail: (message) => {\n const attempt = numberField(message, 'attempt');\n const maxRetries = numberField(message, 'max_retries');\n return attempt == null || maxRetries == null\n ? null\n : `attempt ${formatNumber(attempt)}/${formatNumber(maxRetries)}`;\n },\n meta: (message) =>\n [\n numberMeta(message, 'error_status', 'status'),\n numberMeta(message, 'retry_delay_ms', 'retry delay', 'ms'),\n ].filter(isMeta),\n },\n informational: {\n label: (message) => titleCase(stringField(message, 'level') ?? 'informational'),\n tone: (message) => (stringField(message, 'level') === 'warning' ? 'warning' : 'default'),\n detail: (message) => stringField(message, 'content') ?? null,\n },\n compact_boundary: {\n label: 'Context compacted',\n tone: 'default',\n meta: (message) => {\n const compactMetadata = asLooseObject(field(message, 'compact_metadata')) ?? {};\n return [\n metaItem('trigger', stringField(compactMetadata, 'trigger')),\n numberMeta(compactMetadata, 'pre_tokens', 'tokens before', 'tokens'),\n numberMeta(compactMetadata, 'post_tokens', 'tokens after', 'tokens'),\n numberMeta(compactMetadata, 'duration_ms', 'duration', 'ms'),\n ].filter(isMeta);\n },\n },\n hook_response: {\n label: (message) => {\n const outcome = stringField(message, 'outcome');\n return outcome === 'error'\n ? 'Hook failed'\n : outcome === 'cancelled'\n ? 'Hook cancelled'\n : outcome === 'success'\n ? 'Hook completed'\n : 'Hook response';\n },\n tone: (message) => {\n const outcome = stringField(message, 'outcome');\n return outcome === 'error' ? 'error' : outcome === 'cancelled' ? 'warning' : 'default';\n },\n detail: (message) =>\n stringField(message, 'output') ??\n stringField(message, 'stderr') ??\n stringField(message, 'stdout') ??\n null,\n meta: (message) =>\n [\n metaItem('hook', stringField(message, 'hook_name')),\n numberMeta(message, 'exit_code', 'exit code'),\n ].filter(isMeta),\n },\n model_refusal_fallback: {\n label: 'Model refused, fell back',\n tone: 'warning',\n },\n model_refusal_no_fallback: {\n label: 'Model refused',\n tone: 'error',\n },\n mirror_error: {\n label: 'Session mirror failed',\n tone: 'error',\n detail: (message) => stringField(message, 'error') ?? null,\n },\n worker_shutting_down: {\n label: 'Worker shutting down',\n tone: 'warning',\n },\n elicitation_complete: {\n label: 'Elicitation complete',\n tone: 'default',\n },\n notification: {\n label: 'Notification',\n tone: 'default',\n },\n task_started: {\n label: 'Task started',\n tone: 'default',\n },\n task_updated: {\n label: 'Task updated',\n tone: 'default',\n },\n task_notification: {\n label: 'Task notification',\n tone: 'default',\n },\n};\n\nexport function systemRow(\n timestamp: number,\n message: Record<string, unknown>,\n context: ClaudeParseContext,\n): SessionViewLifecycleRow {\n const subtype = stringField(message, 'subtype');\n const sessionId = stringField(message, 'session_id') ?? stringField(message, 'sessionId');\n const isInit = subtype === 'init' || message.type === 'init';\n const baseMeta = [\n metaItem('cwd', stringField(message, 'cwd'), false),\n metaItem('model', stringField(message, 'model')),\n metaItem(\n 'permission',\n stringField(message, 'permissionMode') ?? stringField(message, 'permission_mode'),\n ),\n ].filter(isMeta);\n\n if (!isInit) {\n const mapping = subtype === undefined ? undefined : SYSTEM_EVENT_MAPPINGS[subtype];\n const label =\n mapping === undefined\n ? `Session event (${subtype ?? 'unknown'})`\n : typeof mapping.label === 'function'\n ? mapping.label(message)\n : mapping.label;\n const tone =\n mapping === undefined\n ? 'default'\n : typeof mapping.tone === 'function'\n ? mapping.tone(message)\n : mapping.tone;\n const detail = mapping?.detail?.(message) ?? null;\n const meta = [...baseMeta, ...(mapping?.meta?.(message) ?? [])];\n\n return lifecycleRow(timestamp, label, detail, tone, false, meta);\n }\n\n const isNewSession =\n !context.hasInit || sessionId === undefined || sessionId !== context.sessionId;\n if (isNewSession) {\n context.pendingToolRows.length = 0;\n context.toolCallRows.clear();\n }\n context.hasInit = true;\n context.sessionId = sessionId ?? null;\n context.turn = isNewSession ? 1 : context.turn + 1;\n\n const label = isNewSession ? 'Session started' : `Turn ${context.turn} started`;\n const meta = [\n metaItem('session', isNewSession ? sessionId : null),\n metaItem('turn', isNewSession ? null : String(context.turn)),\n ...baseMeta,\n ].filter(isMeta);\n\n return lifecycleRow(timestamp, label, null, 'default', false, meta);\n}\n\nexport function authStatusRow(\n timestamp: number,\n message: Record<string, unknown>,\n): SessionViewLifecycleRow {\n const error = stringField(message, 'error');\n const output = stringList(field(message, 'output')).join('\\n');\n const isAuthenticating = booleanField(message, 'isAuthenticating');\n\n return lifecycleRow(\n timestamp,\n error\n ? 'Authentication failed'\n : isAuthenticating\n ? 'Authenticating'\n : 'Authentication complete',\n (error ?? output) || null,\n error ? 'error' : 'default',\n false,\n );\n}\n\nconst RATE_LIMIT_STATUS_MAPPINGS: Record<\n string,\n {label: string; tone: SessionViewLifecycleRow['tone']}\n> = {\n rejected: {label: 'Rate limit exceeded', tone: 'error'},\n allowed_warning: {label: 'Rate limit warning', tone: 'warning'},\n allowed: {label: 'Rate limit available', tone: 'default'},\n};\n\nexport function rateLimitRow(\n timestamp: number,\n message: Record<string, unknown>,\n): SessionViewLifecycleRow {\n const rateLimitInfo = asLooseObject(field(message, 'rate_limit_info')) ?? {};\n const status = stringField(rateLimitInfo, 'status');\n const rateLimitType = stringField(rateLimitInfo, 'rateLimitType');\n const utilization = numberField(rateLimitInfo, 'utilization');\n const mapping = status === undefined ? undefined : RATE_LIMIT_STATUS_MAPPINGS[status];\n const meta = [\n metaItem('utilization', utilization == null ? null : `${formatNumber(utilization * 100)}%`),\n mapping === undefined ? metaItem('status', status ?? null) : null,\n ].filter(isMeta);\n\n return lifecycleRow(\n timestamp,\n mapping?.label ?? 'Rate limit updated',\n rateLimitType == null ? null : humanizeEnumValue(rateLimitType),\n mapping?.tone ?? 'warning',\n false,\n meta,\n );\n}\n\nexport function toolUseSummary(\n message: Record<string, unknown>,\n context: ClaudeParseContext,\n): boolean {\n const summary = stringField(message, 'summary');\n if (summary === undefined) return false;\n\n const precedingToolUseIds = stringList(field(message, 'preceding_tool_use_ids'));\n const toolUseId = stringField(message, 'tool_use_id');\n const candidateIds =\n precedingToolUseIds.length > 0\n ? precedingToolUseIds\n : toolUseId === undefined\n ? []\n : [toolUseId];\n\n for (const id of [...candidateIds].reverse()) {\n const row = context.toolCallRows.get(id);\n if (row === undefined) continue;\n\n row.summary = row.summary === undefined ? summary : `${row.summary}\\n\\n${summary}`;\n return true;\n }\n\n return false;\n}\n\nexport function flushPendingToolRows(context: ClaudeParseContext): readonly SessionViewRow[] {\n if (context.pendingToolRows.length === 0) return [];\n\n const rows = context.pendingToolRows;\n context.pendingToolRows = [];\n context.toolCallRows.clear();\n return rows;\n}\n\nfunction titleCase(value: string): string {\n return value.length === 0 ? value : `${value[0]?.toUpperCase()}${value.slice(1)}`;\n}\n\nfunction humanizeEnumValue(value: string): string {\n return titleCase(value.replaceAll('_', ' '));\n}\n\nfunction stringList(value: unknown): string[] {\n if (!Array.isArray(value)) return [];\n\n return value.filter((item): item is string => typeof item === 'string' && item.length > 0);\n}\n\nexport function assistantRows(\n timestamp: number,\n message: Record<string, unknown>,\n context: ClaudeParseContext,\n): readonly SessionViewRow[] {\n const sdkMessage = asLooseObject(message.message) ?? message;\n const rows: SessionViewRow[] = [];\n const textParts: string[] = [];\n const thinkingParts: string[] = [];\n let queueRows = context.toolCallRows.size > 0;\n\n const pushRow = (row: SessionViewRow) => {\n if (queueRows) {\n context.pendingToolRows.push(row);\n } else {\n rows.push(row);\n }\n };\n\n const pushText = () => {\n if (textParts.length === 0) return;\n pushRow(messageRow(timestamp, 'assistant', 'assistant', textParts.join('\\n\\n'), false));\n textParts.length = 0;\n };\n const pushThinking = () => {\n if (thinkingParts.length === 0) return;\n pushRow(thinkingRow(timestamp, thinkingParts.join('\\n\\n')));\n thinkingParts.length = 0;\n };\n\n for (const block of contentBlocks(sdkMessage)) {\n const type = stringField(block, 'type');\n if (type === 'tool_use' || type === 'tool-use' || type === 'toolCall') {\n pushText();\n pushThinking();\n const row = toolCallRow(timestamp, block);\n if (row.id === null) {\n pushRow(row);\n } else {\n queueRows = true;\n context.pendingToolRows.push(row);\n context.toolCallRows.set(row.id, row);\n }\n continue;\n }\n\n if (type === 'thinking' || type === 'reasoning') {\n pushText();\n const text = blockText(block);\n if (text) thinkingParts.push(text);\n continue;\n }\n\n pushThinking();\n const text = blockText(block);\n if (text) textParts.push(text);\n }\n\n pushText();\n pushThinking();\n\n if (rows.length > 0 || queueRows) return rows;\n\n const text = stringField(sdkMessage, 'content') ?? stringField(message, 'result');\n return [messageRow(timestamp, 'assistant', 'assistant', text ?? toJson(message), false)];\n}\n\nexport function userRows(\n timestamp: number,\n message: Record<string, unknown>,\n context: ClaudeParseContext,\n): readonly SessionViewRow[] {\n const sdkMessage = asLooseObject(message.message) ?? message;\n const role = isPlatformMessage(sdkMessage) ? 'platform' : 'user';\n const rows: SessionViewRow[] = [];\n const textParts: string[] = [];\n const hadPendingToolCall = context.toolCallRows.size > 0;\n let matchedToolResult = false;\n const pushText = () => {\n if (textParts.length === 0) return;\n rows.push(messageRow(timestamp, role, role, textParts.join('\\n\\n'), false));\n textParts.length = 0;\n };\n\n for (const block of contentBlocks(sdkMessage)) {\n const type = stringField(block, 'type');\n if (type === 'tool_result' || type === 'tool-result') {\n pushText();\n const row = toolResultRow(timestamp, block);\n if (row.toolCallId !== null && context.toolCallRows.has(row.toolCallId)) {\n matchedToolResult = true;\n }\n rows.push(row);\n continue;\n }\n\n const text = blockText(block);\n if (text) textParts.push(text);\n }\n\n pushText();\n\n if (rows.length === 0) {\n const content = stringField(sdkMessage, 'content');\n rows.push(messageRow(timestamp, role, role, content ?? toJson(message), false));\n }\n\n if (hadPendingToolCall) {\n if (matchedToolResult) {\n context.pendingToolRows.push(...rows);\n return [];\n }\n return [...flushPendingToolRows(context), ...rows];\n }\n return rows;\n}\n\nexport function resultRow(\n timestamp: number,\n message: Record<string, unknown>,\n turn: number,\n isFinalResult: boolean,\n): SessionViewLifecycleRow {\n const isError = booleanField(message, 'is_error') || booleanField(message, 'isError');\n const subtype = stringField(message, 'subtype');\n const terminalFailure = isError || subtype === 'error';\n const detail =\n stringField(message, 'result') ??\n stringField(message, 'error') ??\n stringField(message, 'message') ??\n null;\n const meta = [\n metaItem('turn', !terminalFailure && !isFinalResult && turn > 0 ? String(turn) : null),\n numberMeta(message, 'duration_ms', 'duration', 'ms'),\n numberMeta(message, 'duration_api_ms', 'api duration', 'ms'),\n numberMeta(message, 'num_turns', 'turns'),\n costMeta(message),\n ].filter(isMeta);\n\n return lifecycleRow(\n timestamp,\n terminalFailure\n ? 'Session failed'\n : isFinalResult || turn === 0\n ? 'Session completed'\n : `Turn ${turn} completed`,\n detail,\n terminalFailure ? 'error' : 'default',\n terminalFailure,\n meta,\n );\n}\n\nfunction isPlatformMessage(message: Record<string, unknown>): boolean {\n const content = field(message, 'content');\n if (typeof content === 'string') return content.startsWith(OUTPUT_REPROMPT_PREFIX);\n if (!Array.isArray(content)) return false;\n\n return content.some((block) => {\n const object = asLooseObject(block);\n return stringField(object, 'text')?.startsWith(OUTPUT_REPROMPT_PREFIX) === true;\n });\n}\n\nfunction contentBlocks(message: Record<string, unknown>): Record<string, unknown>[] {\n const content = message.content;\n if (!Array.isArray(content)) return [];\n\n return content.flatMap((block) => {\n const object = asLooseObject(block);\n return object ? [object] : [];\n });\n}\n\nfunction toolCallRow(timestamp: number, block: Record<string, unknown>): SessionViewToolCallRow {\n return {\n kind: 'tool-call',\n timestamp,\n id: stringField(block, 'id') ?? null,\n name: stringField(block, 'name') ?? 'tool',\n input: stringifyValue(field(block, 'input') ?? {}),\n };\n}\n\nfunction toolResultRow(\n timestamp: number,\n block: Record<string, unknown>,\n): SessionViewToolResultRow {\n return {\n kind: 'tool-result',\n timestamp,\n toolCallId: stringField(block, 'tool_use_id') ?? stringField(block, 'toolUseId') ?? null,\n toolName: stringField(block, 'name') ?? 'tool',\n output: blockText(block) || stringifyValue(field(block, 'content') ?? ''),\n isError: booleanField(block, 'is_error') || booleanField(block, 'isError'),\n };\n}\n\nfunction blockText(block: Record<string, unknown>): string {\n const content = field(block, 'content');\n if (Array.isArray(content)) {\n return content\n .map((item) => {\n const object = asLooseObject(item);\n return object ? blockText(object) : typeof item === 'string' ? item : '';\n })\n .filter(Boolean)\n .join('\\n\\n');\n }\n\n return (\n stringField(block, 'text') ??\n stringField(block, 'thinking') ??\n (typeof content === 'string' ? content : '') ??\n ''\n );\n}\n\nfunction numberMeta(\n value: unknown,\n key: string,\n label: string,\n unit?: string,\n): SessionViewRowMeta | null {\n const raw = field(value, key);\n if (typeof raw !== 'number' || !Number.isFinite(raw)) return null;\n\n return metaItem(label, unit ? `${formatNumber(raw)} ${unit}` : formatNumber(raw));\n}\n\nfunction costMeta(message: Record<string, unknown>): SessionViewRowMeta | null {\n const value = field(message, 'total_cost_usd') ?? field(message, 'totalCostUsd');\n if (typeof value !== 'number' || !Number.isFinite(value)) return null;\n\n return metaItem('cost', `$${value.toFixed(value < 0.01 ? 4 : 2)}`);\n}\n"],"names":["asLooseObject","booleanField","field","formatNumber","isMeta","metaItem","numberField","stringField","stringifyValue","toJson","lifecycleRow","messageRow","thinkingRow","PURE_PROGRESS_CLAUDE_SYSTEM_SUBTYPES","Set","OUTPUT_REPROMPT_PREFIX","SYSTEM_EVENT_MAPPINGS","permission_denied","label","tone","detail","message","meta","filter","api_retry","attempt","maxRetries","numberMeta","informational","titleCase","compact_boundary","compactMetadata","hook_response","outcome","model_refusal_fallback","model_refusal_no_fallback","mirror_error","worker_shutting_down","elicitation_complete","notification","task_started","task_updated","task_notification","systemRow","timestamp","context","subtype","sessionId","isInit","type","baseMeta","mapping","undefined","isNewSession","hasInit","pendingToolRows","length","toolCallRows","clear","turn","String","authStatusRow","error","output","stringList","join","isAuthenticating","RATE_LIMIT_STATUS_MAPPINGS","rejected","allowed_warning","allowed","rateLimitRow","rateLimitInfo","status","rateLimitType","utilization","humanizeEnumValue","toolUseSummary","summary","precedingToolUseIds","toolUseId","candidateIds","id","reverse","row","get","flushPendingToolRows","rows","value","toUpperCase","slice","replaceAll","Array","isArray","item","assistantRows","sdkMessage","textParts","thinkingParts","queueRows","size","pushRow","push","pushText","pushThinking","block","contentBlocks","toolCallRow","set","text","blockText","userRows","role","isPlatformMessage","hadPendingToolCall","matchedToolResult","toolResultRow","toolCallId","has","content","resultRow","isFinalResult","isError","terminalFailure","costMeta","startsWith","some","object","flatMap","kind","name","input","toolName","map","Boolean","key","unit","raw","Number","isFinite","toFixed"],"mappings":"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"}
@@ -1,11 +1,13 @@
1
- import type { SessionViewRow } from '@shipfox/api-logs-dto';
1
+ import type { SessionViewRow, SessionViewToolCallRow } from '@shipfox/api-logs-dto';
2
2
  import type { AgentSessionRecord } from './session-record.js';
3
3
  export interface ClaudeParseContext {
4
4
  hasInit: boolean;
5
5
  sessionId: string | null;
6
6
  turn: number;
7
+ pendingToolRows: SessionViewRow[];
8
+ toolCallRows: Map<string, SessionViewToolCallRow>;
7
9
  }
8
- export declare function createClaudeParseContext(): ClaudeParseContext;
10
+ export declare function createClaudeParseContext(pendingToolRows?: readonly SessionViewRow[], state?: Pick<ClaudeParseContext, 'hasInit' | 'sessionId' | 'turn'>): ClaudeParseContext;
9
11
  export declare function claudeInitSessionId(record: AgentSessionRecord): string | undefined;
10
12
  export declare function parseClaudeSessionRecord(record: AgentSessionRecord, context?: ClaudeParseContext, isFinalResult?: boolean): readonly SessionViewRow[];
11
13
  //# sourceMappingURL=claude-parser.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"claude-parser.d.ts","sourceRoot":"","sources":["../../../src/core/session/claude-parser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,uBAAuB,CAAC;AAW1D,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,qBAAqB,CAAC;AAQ5D,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,wBAAgB,wBAAwB,IAAI,kBAAkB,CAE7D;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,kBAAkB,GAAG,MAAM,GAAG,SAAS,CAoBlF;AAED,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,kBAAkB,EAC1B,OAAO,GAAE,kBAA+C,EACxD,aAAa,UAAO,GACnB,SAAS,cAAc,EAAE,CAiC3B"}
1
+ {"version":3,"file":"claude-parser.d.ts","sourceRoot":"","sources":["../../../src/core/session/claude-parser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,cAAc,EAAE,sBAAsB,EAAC,MAAM,uBAAuB,CAAC;AAelF,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,qBAAqB,CAAC;AAQ5D,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,eAAe,EAAE,cAAc,EAAE,CAAC;IAClC,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC;CACnD;AAED,wBAAgB,wBAAwB,CACtC,eAAe,GAAE,SAAS,cAAc,EAAO,EAC/C,KAAK,GAAE,IAAI,CAAC,kBAAkB,EAAE,SAAS,GAAG,WAAW,GAAG,MAAM,CAI/D,GACA,kBAAkB,CAQpB;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,kBAAkB,GAAG,MAAM,GAAG,SAAS,CAoBlF;AAED,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,kBAAkB,EAC1B,OAAO,GAAE,kBAA+C,EACxD,aAAa,UAAO,GACnB,SAAS,cAAc,EAAE,CAwD3B"}
@@ -1,15 +1,26 @@
1
1
  import { z } from 'zod';
2
- import { assistantRows, PURE_PROGRESS_CLAUDE_SYSTEM_SUBTYPES, resultRow, systemRow, userRows } from './claude/rows.js';
2
+ import { assistantRows, authStatusRow, flushPendingToolRows, PURE_PROGRESS_CLAUDE_SYSTEM_SUBTYPES, rateLimitRow, resultRow, systemRow, toolUseSummary, userRows } from './claude/rows.js';
3
3
  import { stringField } from './object.js';
4
4
  import { rawRecordRow } from './rows.js';
5
5
  const claudeMessageSchema = z.object({
6
6
  type: z.string().min(1)
7
7
  }).catchall(z.unknown());
8
- export function createClaudeParseContext() {
8
+ export function createClaudeParseContext(pendingToolRows = [], state = {
9
+ hasInit: false,
10
+ sessionId: null,
11
+ turn: 0
12
+ }) {
13
+ const rows = [
14
+ ...pendingToolRows
15
+ ];
16
+ const toolCallRows = new Map();
17
+ for (const row of rows){
18
+ if (row.kind === 'tool-call' && row.id !== null) toolCallRows.set(row.id, row);
19
+ }
9
20
  return {
10
- hasInit: false,
11
- sessionId: null,
12
- turn: 0
21
+ ...state,
22
+ pendingToolRows: rows,
23
+ toolCallRows
13
24
  };
14
25
  }
15
26
  export function claudeInitSessionId(record) {
@@ -33,36 +44,71 @@ export function parseClaudeSessionRecord(record, context = createClaudeParseCont
33
44
  json = JSON.parse(record.data);
34
45
  } catch {
35
46
  return [
47
+ ...flushPendingToolRows(context),
36
48
  rawRecordRow(record, 'Malformed session entry')
37
49
  ];
38
50
  }
39
51
  const parsed = claudeMessageSchema.safeParse(json);
40
- if (!parsed.success) return [
41
- rawRecordRow(record, 'Unsupported Claude message')
42
- ];
52
+ if (!parsed.success) {
53
+ return [
54
+ ...flushPendingToolRows(context),
55
+ rawRecordRow(record, 'Unsupported Claude message')
56
+ ];
57
+ }
43
58
  const message = parsed.data;
44
59
  if (message.type === 'system' && typeof message.subtype === 'string' && PURE_PROGRESS_CLAUDE_SYSTEM_SUBTYPES.has(message.subtype)) {
45
60
  return [];
46
61
  }
47
62
  switch(message.type){
48
63
  case 'system':
64
+ if (stringField(message, 'subtype') === 'init') {
65
+ return [
66
+ ...flushPendingToolRows(context),
67
+ systemRow(record.ts, message, context)
68
+ ];
69
+ }
70
+ return deferRow(context, systemRow(record.ts, message, context));
49
71
  case 'init':
50
72
  return [
73
+ ...flushPendingToolRows(context),
51
74
  systemRow(record.ts, message, context)
52
75
  ];
76
+ case 'tool_progress':
77
+ case 'prompt_suggestion':
78
+ // These messages describe an already-emitted tool call or an interactive client state.
79
+ // They have no standalone row representation and must not look like parser failures.
80
+ return [];
81
+ case 'tool_use_summary':
82
+ return toolUseSummary(message, context) ? flushPendingToolRows(context) : [];
83
+ case 'auth_status':
84
+ return deferRow(context, authStatusRow(record.ts, message));
85
+ case 'rate_limit_event':
86
+ return deferRow(context, rateLimitRow(record.ts, message));
53
87
  case 'assistant':
54
- return assistantRows(record.ts, message);
88
+ return [
89
+ ...flushPendingToolRows(context),
90
+ ...assistantRows(record.ts, message, context)
91
+ ];
55
92
  case 'user':
56
- return userRows(record.ts, message);
93
+ return userRows(record.ts, message, context);
57
94
  case 'result':
58
95
  return [
96
+ ...flushPendingToolRows(context),
59
97
  resultRow(record.ts, message, context.turn, isFinalResult)
60
98
  ];
61
99
  default:
62
100
  return [
101
+ ...flushPendingToolRows(context),
63
102
  rawRecordRow(record, `Unknown Claude message: ${message.type}`)
64
103
  ];
65
104
  }
66
105
  }
106
+ function deferRow(context, row) {
107
+ if (context.toolCallRows.size === 0) return [
108
+ row
109
+ ];
110
+ context.pendingToolRows.push(row);
111
+ return [];
112
+ }
67
113
 
68
114
  //# sourceMappingURL=claude-parser.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/core/session/claude-parser.ts"],"sourcesContent":["import type {SessionViewRow} from '@shipfox/api-logs-dto';\nimport {z} from 'zod';\nimport {\n assistantRows,\n PURE_PROGRESS_CLAUDE_SYSTEM_SUBTYPES,\n resultRow,\n systemRow,\n userRows,\n} from './claude/rows.js';\nimport {stringField} from './object.js';\nimport {rawRecordRow} from './rows.js';\nimport type {AgentSessionRecord} from './session-record.js';\n\nconst claudeMessageSchema = z\n .object({\n type: z.string().min(1),\n })\n .catchall(z.unknown());\n\nexport interface ClaudeParseContext {\n hasInit: boolean;\n sessionId: string | null;\n turn: number;\n}\n\nexport function createClaudeParseContext(): ClaudeParseContext {\n return {hasInit: false, sessionId: null, turn: 0};\n}\n\nexport function claudeInitSessionId(record: AgentSessionRecord): string | undefined {\n let json: unknown;\n try {\n json = JSON.parse(record.data);\n } catch {\n return undefined;\n }\n\n const parsed = claudeMessageSchema.safeParse(json);\n if (!parsed.success) return undefined;\n\n const message = parsed.data;\n if (\n message.type !== 'init' &&\n !(message.type === 'system' && stringField(message, 'subtype') === 'init')\n ) {\n return undefined;\n }\n\n return stringField(message, 'session_id') ?? stringField(message, 'sessionId');\n}\n\nexport function parseClaudeSessionRecord(\n record: AgentSessionRecord,\n context: ClaudeParseContext = createClaudeParseContext(),\n isFinalResult = true,\n): readonly SessionViewRow[] {\n let json: unknown;\n try {\n json = JSON.parse(record.data);\n } catch {\n return [rawRecordRow(record, 'Malformed session entry')];\n }\n\n const parsed = claudeMessageSchema.safeParse(json);\n if (!parsed.success) return [rawRecordRow(record, 'Unsupported Claude message')];\n\n const message = parsed.data;\n if (\n message.type === 'system' &&\n typeof message.subtype === 'string' &&\n PURE_PROGRESS_CLAUDE_SYSTEM_SUBTYPES.has(message.subtype)\n ) {\n return [];\n }\n\n switch (message.type) {\n case 'system':\n case 'init':\n return [systemRow(record.ts, message, context)];\n case 'assistant':\n return assistantRows(record.ts, message);\n case 'user':\n return userRows(record.ts, message);\n case 'result':\n return [resultRow(record.ts, message, context.turn, isFinalResult)];\n default:\n return [rawRecordRow(record, `Unknown Claude message: ${message.type}`)];\n }\n}\n"],"names":["z","assistantRows","PURE_PROGRESS_CLAUDE_SYSTEM_SUBTYPES","resultRow","systemRow","userRows","stringField","rawRecordRow","claudeMessageSchema","object","type","string","min","catchall","unknown","createClaudeParseContext","hasInit","sessionId","turn","claudeInitSessionId","record","json","JSON","parse","data","undefined","parsed","safeParse","success","message","parseClaudeSessionRecord","context","isFinalResult","subtype","has","ts"],"mappings":"AACA,SAAQA,CAAC,QAAO,MAAM;AACtB,SACEC,aAAa,EACbC,oCAAoC,EACpCC,SAAS,EACTC,SAAS,EACTC,QAAQ,QACH,mBAAmB;AAC1B,SAAQC,WAAW,QAAO,cAAc;AACxC,SAAQC,YAAY,QAAO,YAAY;AAGvC,MAAMC,sBAAsBR,EACzBS,MAAM,CAAC;IACNC,MAAMV,EAAEW,MAAM,GAAGC,GAAG,CAAC;AACvB,GACCC,QAAQ,CAACb,EAAEc,OAAO;AAQrB,OAAO,SAASC;IACd,OAAO;QAACC,SAAS;QAAOC,WAAW;QAAMC,MAAM;IAAC;AAClD;AAEA,OAAO,SAASC,oBAAoBC,MAA0B;IAC5D,IAAIC;IACJ,IAAI;QACFA,OAAOC,KAAKC,KAAK,CAACH,OAAOI,IAAI;IAC/B,EAAE,OAAM;QACN,OAAOC;IACT;IAEA,MAAMC,SAASlB,oBAAoBmB,SAAS,CAACN;IAC7C,IAAI,CAACK,OAAOE,OAAO,EAAE,OAAOH;IAE5B,MAAMI,UAAUH,OAAOF,IAAI;IAC3B,IACEK,QAAQnB,IAAI,KAAK,UACjB,CAAEmB,CAAAA,QAAQnB,IAAI,KAAK,YAAYJ,YAAYuB,SAAS,eAAe,MAAK,GACxE;QACA,OAAOJ;IACT;IAEA,OAAOnB,YAAYuB,SAAS,iBAAiBvB,YAAYuB,SAAS;AACpE;AAEA,OAAO,SAASC,yBACdV,MAA0B,EAC1BW,UAA8BhB,0BAA0B,EACxDiB,gBAAgB,IAAI;IAEpB,IAAIX;IACJ,IAAI;QACFA,OAAOC,KAAKC,KAAK,CAACH,OAAOI,IAAI;IAC/B,EAAE,OAAM;QACN,OAAO;YAACjB,aAAaa,QAAQ;SAA2B;IAC1D;IAEA,MAAMM,SAASlB,oBAAoBmB,SAAS,CAACN;IAC7C,IAAI,CAACK,OAAOE,OAAO,EAAE,OAAO;QAACrB,aAAaa,QAAQ;KAA8B;IAEhF,MAAMS,UAAUH,OAAOF,IAAI;IAC3B,IACEK,QAAQnB,IAAI,KAAK,YACjB,OAAOmB,QAAQI,OAAO,KAAK,YAC3B/B,qCAAqCgC,GAAG,CAACL,QAAQI,OAAO,GACxD;QACA,OAAO,EAAE;IACX;IAEA,OAAQJ,QAAQnB,IAAI;QAClB,KAAK;QACL,KAAK;YACH,OAAO;gBAACN,UAAUgB,OAAOe,EAAE,EAAEN,SAASE;aAAS;QACjD,KAAK;YACH,OAAO9B,cAAcmB,OAAOe,EAAE,EAAEN;QAClC,KAAK;YACH,OAAOxB,SAASe,OAAOe,EAAE,EAAEN;QAC7B,KAAK;YACH,OAAO;gBAAC1B,UAAUiB,OAAOe,EAAE,EAAEN,SAASE,QAAQb,IAAI,EAAEc;aAAe;QACrE;YACE,OAAO;gBAACzB,aAAaa,QAAQ,CAAC,wBAAwB,EAAES,QAAQnB,IAAI,EAAE;aAAE;IAC5E;AACF"}
1
+ {"version":3,"sources":["../../../src/core/session/claude-parser.ts"],"sourcesContent":["import type {SessionViewRow, SessionViewToolCallRow} from '@shipfox/api-logs-dto';\nimport {z} from 'zod';\nimport {\n assistantRows,\n authStatusRow,\n flushPendingToolRows,\n PURE_PROGRESS_CLAUDE_SYSTEM_SUBTYPES,\n rateLimitRow,\n resultRow,\n systemRow,\n toolUseSummary,\n userRows,\n} from './claude/rows.js';\nimport {stringField} from './object.js';\nimport {rawRecordRow} from './rows.js';\nimport type {AgentSessionRecord} from './session-record.js';\n\nconst claudeMessageSchema = z\n .object({\n type: z.string().min(1),\n })\n .catchall(z.unknown());\n\nexport interface ClaudeParseContext {\n hasInit: boolean;\n sessionId: string | null;\n turn: number;\n pendingToolRows: SessionViewRow[];\n toolCallRows: Map<string, SessionViewToolCallRow>;\n}\n\nexport function createClaudeParseContext(\n pendingToolRows: readonly SessionViewRow[] = [],\n state: Pick<ClaudeParseContext, 'hasInit' | 'sessionId' | 'turn'> = {\n hasInit: false,\n sessionId: null,\n turn: 0,\n },\n): ClaudeParseContext {\n const rows = [...pendingToolRows];\n const toolCallRows = new Map<string, SessionViewToolCallRow>();\n for (const row of rows) {\n if (row.kind === 'tool-call' && row.id !== null) toolCallRows.set(row.id, row);\n }\n\n return {...state, pendingToolRows: rows, toolCallRows};\n}\n\nexport function claudeInitSessionId(record: AgentSessionRecord): string | undefined {\n let json: unknown;\n try {\n json = JSON.parse(record.data);\n } catch {\n return undefined;\n }\n\n const parsed = claudeMessageSchema.safeParse(json);\n if (!parsed.success) return undefined;\n\n const message = parsed.data;\n if (\n message.type !== 'init' &&\n !(message.type === 'system' && stringField(message, 'subtype') === 'init')\n ) {\n return undefined;\n }\n\n return stringField(message, 'session_id') ?? stringField(message, 'sessionId');\n}\n\nexport function parseClaudeSessionRecord(\n record: AgentSessionRecord,\n context: ClaudeParseContext = createClaudeParseContext(),\n isFinalResult = true,\n): readonly SessionViewRow[] {\n let json: unknown;\n try {\n json = JSON.parse(record.data);\n } catch {\n return [...flushPendingToolRows(context), rawRecordRow(record, 'Malformed session entry')];\n }\n\n const parsed = claudeMessageSchema.safeParse(json);\n if (!parsed.success) {\n return [...flushPendingToolRows(context), rawRecordRow(record, 'Unsupported Claude message')];\n }\n\n const message = parsed.data;\n if (\n message.type === 'system' &&\n typeof message.subtype === 'string' &&\n PURE_PROGRESS_CLAUDE_SYSTEM_SUBTYPES.has(message.subtype)\n ) {\n return [];\n }\n\n switch (message.type) {\n case 'system':\n if (stringField(message, 'subtype') === 'init') {\n return [...flushPendingToolRows(context), systemRow(record.ts, message, context)];\n }\n return deferRow(context, systemRow(record.ts, message, context));\n case 'init':\n return [...flushPendingToolRows(context), systemRow(record.ts, message, context)];\n case 'tool_progress':\n case 'prompt_suggestion':\n // These messages describe an already-emitted tool call or an interactive client state.\n // They have no standalone row representation and must not look like parser failures.\n return [];\n case 'tool_use_summary':\n return toolUseSummary(message, context) ? flushPendingToolRows(context) : [];\n case 'auth_status':\n return deferRow(context, authStatusRow(record.ts, message));\n case 'rate_limit_event':\n return deferRow(context, rateLimitRow(record.ts, message));\n case 'assistant':\n return [...flushPendingToolRows(context), ...assistantRows(record.ts, message, context)];\n case 'user':\n return userRows(record.ts, message, context);\n case 'result':\n return [\n ...flushPendingToolRows(context),\n resultRow(record.ts, message, context.turn, isFinalResult),\n ];\n default:\n return [\n ...flushPendingToolRows(context),\n rawRecordRow(record, `Unknown Claude message: ${message.type}`),\n ];\n }\n}\n\nfunction deferRow(context: ClaudeParseContext, row: SessionViewRow): readonly SessionViewRow[] {\n if (context.toolCallRows.size === 0) return [row];\n\n context.pendingToolRows.push(row);\n return [];\n}\n"],"names":["z","assistantRows","authStatusRow","flushPendingToolRows","PURE_PROGRESS_CLAUDE_SYSTEM_SUBTYPES","rateLimitRow","resultRow","systemRow","toolUseSummary","userRows","stringField","rawRecordRow","claudeMessageSchema","object","type","string","min","catchall","unknown","createClaudeParseContext","pendingToolRows","state","hasInit","sessionId","turn","rows","toolCallRows","Map","row","kind","id","set","claudeInitSessionId","record","json","JSON","parse","data","undefined","parsed","safeParse","success","message","parseClaudeSessionRecord","context","isFinalResult","subtype","has","ts","deferRow","size","push"],"mappings":"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"}
package/dist/db/db.d.ts CHANGED
@@ -411,6 +411,119 @@ export declare const schema: {
411
411
  timestamp: number;
412
412
  };
413
413
  }>;
414
+ claudePendingToolRows: import("drizzle-orm/pg-core").PgColumn<{
415
+ name: "claude_pending_tool_rows";
416
+ tableName: "attempt_streams";
417
+ dataType: "json";
418
+ columnType: "PgJsonb";
419
+ data: ({
420
+ kind: "lifecycle";
421
+ label: string;
422
+ detail: string | null;
423
+ meta: readonly {
424
+ label: string;
425
+ value: string;
426
+ inline?: boolean | undefined;
427
+ }[];
428
+ tone: "default" | "error" | "warning";
429
+ terminalFailure: boolean;
430
+ timestamp: number;
431
+ } | {
432
+ kind: "message";
433
+ role: string;
434
+ label: string;
435
+ meta: readonly {
436
+ label: string;
437
+ value: string;
438
+ inline?: boolean | undefined;
439
+ }[];
440
+ text: string;
441
+ terminalFailure: boolean;
442
+ timestamp: number;
443
+ } | {
444
+ kind: "thinking";
445
+ text: string;
446
+ timestamp: number;
447
+ } | {
448
+ kind: "tool-call";
449
+ id: string | null;
450
+ name: string;
451
+ input: string;
452
+ timestamp: number;
453
+ summary?: string | undefined;
454
+ } | {
455
+ kind: "tool-result";
456
+ toolCallId: string | null;
457
+ toolName: string;
458
+ output: string;
459
+ isError: boolean;
460
+ timestamp: number;
461
+ } | {
462
+ kind: "raw";
463
+ label: string;
464
+ raw: string;
465
+ timestamp: number;
466
+ })[];
467
+ driverParam: unknown;
468
+ notNull: false;
469
+ hasDefault: false;
470
+ isPrimaryKey: false;
471
+ isAutoincrement: false;
472
+ hasRuntimeDefault: false;
473
+ enumValues: undefined;
474
+ baseColumn: never;
475
+ identity: undefined;
476
+ generated: undefined;
477
+ }, {}, {
478
+ $type: ({
479
+ kind: "lifecycle";
480
+ label: string;
481
+ detail: string | null;
482
+ meta: readonly {
483
+ label: string;
484
+ value: string;
485
+ inline?: boolean | undefined;
486
+ }[];
487
+ tone: "default" | "error" | "warning";
488
+ terminalFailure: boolean;
489
+ timestamp: number;
490
+ } | {
491
+ kind: "message";
492
+ role: string;
493
+ label: string;
494
+ meta: readonly {
495
+ label: string;
496
+ value: string;
497
+ inline?: boolean | undefined;
498
+ }[];
499
+ text: string;
500
+ terminalFailure: boolean;
501
+ timestamp: number;
502
+ } | {
503
+ kind: "thinking";
504
+ text: string;
505
+ timestamp: number;
506
+ } | {
507
+ kind: "tool-call";
508
+ id: string | null;
509
+ name: string;
510
+ input: string;
511
+ timestamp: number;
512
+ summary?: string | undefined;
513
+ } | {
514
+ kind: "tool-result";
515
+ toolCallId: string | null;
516
+ toolName: string;
517
+ output: string;
518
+ isError: boolean;
519
+ timestamp: number;
520
+ } | {
521
+ kind: "raw";
522
+ label: string;
523
+ raw: string;
524
+ timestamp: number;
525
+ })[];
526
+ }>;
414
527
  truncated: import("drizzle-orm/pg-core").PgColumn<{
415
528
  name: "truncated";
416
529
  tableName: "attempt_streams";
@@ -1 +1 @@
1
- {"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/db/db.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,cAAc,EAAC,MAAM,uBAAuB,CAAC;AAOnE,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAAyD,CAAC;AAE7E,MAAM,MAAM,QAAQ,GAAG,cAAc,CAAC,OAAO,MAAM,CAAC,CAAC;AACrD,MAAM,MAAM,WAAW,GAAG,UAAU,CAAC,UAAU,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAIhF,wBAAgB,EAAE,aAGjB;AAED,wBAAgB,OAAO,IAAI,IAAI,CAE9B"}
1
+ {"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/db/db.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,cAAc,EAAC,MAAM,uBAAuB,CAAC;AAOnE,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAAyD,CAAC;AAE7E,MAAM,MAAM,QAAQ,GAAG,cAAc,CAAC,OAAO,MAAM,CAAC,CAAC;AACrD,MAAM,MAAM,WAAW,GAAG,UAAU,CAAC,UAAU,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAIhF,wBAAgB,EAAE,aAGjB;AAED,wBAAgB,OAAO,IAAI,IAAI,CAE9B"}
@@ -304,6 +304,119 @@ export declare const attemptStreams: import("drizzle-orm/pg-core").PgTableWithCo
304
304
  timestamp: number;
305
305
  };
306
306
  }>;
307
+ claudePendingToolRows: import("drizzle-orm/pg-core").PgColumn<{
308
+ name: "claude_pending_tool_rows";
309
+ tableName: "attempt_streams";
310
+ dataType: "json";
311
+ columnType: "PgJsonb";
312
+ data: ({
313
+ kind: "lifecycle";
314
+ label: string;
315
+ detail: string | null;
316
+ meta: readonly {
317
+ label: string;
318
+ value: string;
319
+ inline?: boolean | undefined;
320
+ }[];
321
+ tone: "default" | "error" | "warning";
322
+ terminalFailure: boolean;
323
+ timestamp: number;
324
+ } | {
325
+ kind: "message";
326
+ role: string;
327
+ label: string;
328
+ meta: readonly {
329
+ label: string;
330
+ value: string;
331
+ inline?: boolean | undefined;
332
+ }[];
333
+ text: string;
334
+ terminalFailure: boolean;
335
+ timestamp: number;
336
+ } | {
337
+ kind: "thinking";
338
+ text: string;
339
+ timestamp: number;
340
+ } | {
341
+ kind: "tool-call";
342
+ id: string | null;
343
+ name: string;
344
+ input: string;
345
+ timestamp: number;
346
+ summary?: string | undefined;
347
+ } | {
348
+ kind: "tool-result";
349
+ toolCallId: string | null;
350
+ toolName: string;
351
+ output: string;
352
+ isError: boolean;
353
+ timestamp: number;
354
+ } | {
355
+ kind: "raw";
356
+ label: string;
357
+ raw: string;
358
+ timestamp: number;
359
+ })[];
360
+ driverParam: unknown;
361
+ notNull: false;
362
+ hasDefault: false;
363
+ isPrimaryKey: false;
364
+ isAutoincrement: false;
365
+ hasRuntimeDefault: false;
366
+ enumValues: undefined;
367
+ baseColumn: never;
368
+ identity: undefined;
369
+ generated: undefined;
370
+ }, {}, {
371
+ $type: ({
372
+ kind: "lifecycle";
373
+ label: string;
374
+ detail: string | null;
375
+ meta: readonly {
376
+ label: string;
377
+ value: string;
378
+ inline?: boolean | undefined;
379
+ }[];
380
+ tone: "default" | "error" | "warning";
381
+ terminalFailure: boolean;
382
+ timestamp: number;
383
+ } | {
384
+ kind: "message";
385
+ role: string;
386
+ label: string;
387
+ meta: readonly {
388
+ label: string;
389
+ value: string;
390
+ inline?: boolean | undefined;
391
+ }[];
392
+ text: string;
393
+ terminalFailure: boolean;
394
+ timestamp: number;
395
+ } | {
396
+ kind: "thinking";
397
+ text: string;
398
+ timestamp: number;
399
+ } | {
400
+ kind: "tool-call";
401
+ id: string | null;
402
+ name: string;
403
+ input: string;
404
+ timestamp: number;
405
+ summary?: string | undefined;
406
+ } | {
407
+ kind: "tool-result";
408
+ toolCallId: string | null;
409
+ toolName: string;
410
+ output: string;
411
+ isError: boolean;
412
+ timestamp: number;
413
+ } | {
414
+ kind: "raw";
415
+ label: string;
416
+ raw: string;
417
+ timestamp: number;
418
+ })[];
419
+ }>;
307
420
  truncated: import("drizzle-orm/pg-core").PgColumn<{
308
421
  name: "truncated";
309
422
  tableName: "attempt_streams";
@@ -1 +1 @@
1
- {"version":3,"file":"attempt-streams.d.ts","sourceRoot":"","sources":["../../../src/db/schema/attempt-streams.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,kCAAkC,CAAC;AAGpE;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAkD1B,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,OAAO,cAAc,CAAC,YAAY,CAAC;AACjE,MAAM,MAAM,qBAAqB,GAAG,OAAO,cAAc,CAAC,YAAY,CAAC;AAEvE,wBAAgB,eAAe,CAAC,GAAG,EAAE,eAAe,GAAG,aAAa,CAuBnE"}
1
+ {"version":3,"file":"attempt-streams.d.ts","sourceRoot":"","sources":["../../../src/db/schema/attempt-streams.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,kCAAkC,CAAC;AAGpE;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAmD1B,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,OAAO,cAAc,CAAC,YAAY,CAAC;AACjE,MAAM,MAAM,qBAAqB,GAAG,OAAO,cAAc,CAAC,YAAY,CAAC;AAEvE,wBAAgB,eAAe,CAAC,GAAG,EAAE,eAAe,GAAG,aAAa,CAwBnE"}
@@ -51,6 +51,7 @@ import { pgTable } from './common.js';
51
51
  claudeSessionId: text('claude_session_id'),
52
52
  claudeTurn: integer('claude_turn').notNull().default(0),
53
53
  claudePendingResult: jsonb('claude_pending_result').$type(),
54
+ claudePendingToolRows: jsonb('claude_pending_tool_rows').$type(),
54
55
  truncated: boolean('truncated').notNull().default(false),
55
56
  objectKey: text('object_key'),
56
57
  createdAt: timestamp('created_at', {
@@ -97,6 +98,7 @@ export function toAttemptStream(row) {
97
98
  claudeSessionId: row.claudeSessionId,
98
99
  claudeTurn: row.claudeTurn,
99
100
  claudePendingResult: row.claudePendingResult ?? null,
101
+ claudePendingToolRows: row.claudePendingToolRows ?? [],
100
102
  truncated: row.truncated,
101
103
  objectKey: row.objectKey,
102
104
  createdAt: row.createdAt,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/db/schema/attempt-streams.ts"],"sourcesContent":["import type {SessionViewLifecycleRow} from '@shipfox/api-logs-dto';\nimport {uuidv7PrimaryKey} from '@shipfox/node-drizzle';\nimport {sql} from 'drizzle-orm';\nimport {\n bigint,\n boolean,\n index,\n integer,\n jsonb,\n text,\n timestamp,\n uniqueIndex,\n uuid,\n} from 'drizzle-orm/pg-core';\nimport type {AttemptStream} from '#core/entities/attempt-stream.js';\nimport {pgTable} from './common.js';\n\n/**\n * One stream per (job, step, attempt). Identity is scoped by `job_id` (from the\n * lease), so a lease can only ever reach its own job's streams — cross-job log\n * injection is structurally impossible. `workspace_id`, `project_id`, and `workflow_run_attempt_id`\n * are stamped from the lease at create time; they are functionally determined by\n * `job_id` via workflows FKs, so they are denormalized here for self-contained\n * authorization (per-project read filtering, audit) without joining back to\n * workflows. `committed_length` is the offset-CAS axis (raw NDJSON spool bytes the\n * server has durably accepted from the runner).\n *\n * `truncated` is an out-of-band terminal flag set when the timeout sweep\n * force-closes a stream the runner never ended; the `AttemptStream` entity is the\n * canonical reference for what it means.\n *\n * Per-row `committed_length` and `declared_total_bytes` are bounded by the\n * per-job budget, so `mode: 'number'` is safe on the hot path. Any cross-row\n * aggregate (workspace or system-wide totals) MUST read as bigint at the\n * query site — the global sum is unbounded and would silently lose precision\n * past 2^53 as a JS number.\n */\nexport const attemptStreams = pgTable(\n 'attempt_streams',\n {\n id: uuidv7PrimaryKey(),\n jobId: uuid('job_id').notNull(),\n stepId: uuid('step_id').notNull(),\n attempt: integer('attempt').notNull(),\n workspaceId: uuid('workspace_id').notNull(),\n projectId: uuid('project_id').notNull(),\n workflowRunAttemptId: uuid('workflow_run_attempt_id').notNull(),\n committedLength: bigint('committed_length', {mode: 'number'}).notNull().default(0),\n state: text('state', {enum: ['open', 'closed']})\n .notNull()\n .default('open'),\n closeReason: text('close_reason', {enum: ['declared', 'timeout']}),\n declaredTotalBytes: bigint('declared_total_bytes', {mode: 'number'}),\n claudeHasInit: boolean('claude_has_init').notNull().default(false),\n claudeSessionId: text('claude_session_id'),\n claudeTurn: integer('claude_turn').notNull().default(0),\n claudePendingResult: jsonb('claude_pending_result').$type<SessionViewLifecycleRow>(),\n truncated: boolean('truncated').notNull().default(false),\n objectKey: text('object_key'),\n createdAt: timestamp('created_at', {withTimezone: true}).notNull().defaultNow(),\n updatedAt: timestamp('updated_at', {withTimezone: true}).notNull().defaultNow(),\n closedAt: timestamp('closed_at', {withTimezone: true}),\n },\n (table) => [\n uniqueIndex('logs_attempt_streams_identity_unique').on(\n table.jobId,\n table.stepId,\n table.attempt,\n ),\n // The session read path looks up a stream by (step, attempt) without the job id, so the\n // identity unique (which leads with job_id) can't serve it. This index keeps that lookup\n // off a sequential scan as the table grows.\n index('logs_attempt_streams_step_attempt_idx').on(table.stepId, table.attempt),\n // Timeout-close sweeps an open job's streams by job_id; the partial index keeps\n // it off the closed tail.\n index('logs_attempt_streams_open_by_job_idx').on(table.jobId).where(sql`\"state\" = 'open'`),\n // Open-age reaping needs an index that stays off the closed tail; created_at is\n // immutable, so appends do not churn it.\n index('logs_attempt_streams_open_age_idx').on(table.createdAt).where(sql`\"state\" = 'open'`),\n // Retention scans closed streams by close age; partial so it never carries the\n // open (in-flight) set.\n index('logs_attempt_streams_retention_idx').on(table.closedAt).where(sql`\"state\" = 'closed'`),\n // Reconcile re-drives closed streams that never got an object key.\n index('logs_attempt_streams_uncompacted_idx')\n .on(table.closedAt)\n .where(sql`\"state\" = 'closed' and \"object_key\" is null`),\n ],\n);\n\nexport type AttemptStreamDb = typeof attemptStreams.$inferSelect;\nexport type AttemptStreamInsertDb = typeof attemptStreams.$inferInsert;\n\nexport function toAttemptStream(row: AttemptStreamDb): AttemptStream {\n return {\n id: row.id,\n jobId: row.jobId,\n stepId: row.stepId,\n attempt: row.attempt,\n workspaceId: row.workspaceId,\n projectId: row.projectId,\n workflowRunAttemptId: row.workflowRunAttemptId,\n committedLength: row.committedLength,\n state: row.state,\n closeReason: row.closeReason,\n declaredTotalBytes: row.declaredTotalBytes,\n claudeHasInit: row.claudeHasInit,\n claudeSessionId: row.claudeSessionId,\n claudeTurn: row.claudeTurn,\n claudePendingResult: row.claudePendingResult ?? null,\n truncated: row.truncated,\n objectKey: row.objectKey,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n closedAt: row.closedAt,\n };\n}\n"],"names":["uuidv7PrimaryKey","sql","bigint","boolean","index","integer","jsonb","text","timestamp","uniqueIndex","uuid","pgTable","attemptStreams","id","jobId","notNull","stepId","attempt","workspaceId","projectId","workflowRunAttemptId","committedLength","mode","default","state","enum","closeReason","declaredTotalBytes","claudeHasInit","claudeSessionId","claudeTurn","claudePendingResult","$type","truncated","objectKey","createdAt","withTimezone","defaultNow","updatedAt","closedAt","table","on","where","toAttemptStream","row"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/db/schema/attempt-streams.ts"],"sourcesContent":["import type {SessionViewLifecycleRow, SessionViewRow} from '@shipfox/api-logs-dto';\nimport {uuidv7PrimaryKey} from '@shipfox/node-drizzle';\nimport {sql} from 'drizzle-orm';\nimport {\n bigint,\n boolean,\n index,\n integer,\n jsonb,\n text,\n timestamp,\n uniqueIndex,\n uuid,\n} from 'drizzle-orm/pg-core';\nimport type {AttemptStream} from '#core/entities/attempt-stream.js';\nimport {pgTable} from './common.js';\n\n/**\n * One stream per (job, step, attempt). Identity is scoped by `job_id` (from the\n * lease), so a lease can only ever reach its own job's streams — cross-job log\n * injection is structurally impossible. `workspace_id`, `project_id`, and `workflow_run_attempt_id`\n * are stamped from the lease at create time; they are functionally determined by\n * `job_id` via workflows FKs, so they are denormalized here for self-contained\n * authorization (per-project read filtering, audit) without joining back to\n * workflows. `committed_length` is the offset-CAS axis (raw NDJSON spool bytes the\n * server has durably accepted from the runner).\n *\n * `truncated` is an out-of-band terminal flag set when the timeout sweep\n * force-closes a stream the runner never ended; the `AttemptStream` entity is the\n * canonical reference for what it means.\n *\n * Per-row `committed_length` and `declared_total_bytes` are bounded by the\n * per-job budget, so `mode: 'number'` is safe on the hot path. Any cross-row\n * aggregate (workspace or system-wide totals) MUST read as bigint at the\n * query site — the global sum is unbounded and would silently lose precision\n * past 2^53 as a JS number.\n */\nexport const attemptStreams = pgTable(\n 'attempt_streams',\n {\n id: uuidv7PrimaryKey(),\n jobId: uuid('job_id').notNull(),\n stepId: uuid('step_id').notNull(),\n attempt: integer('attempt').notNull(),\n workspaceId: uuid('workspace_id').notNull(),\n projectId: uuid('project_id').notNull(),\n workflowRunAttemptId: uuid('workflow_run_attempt_id').notNull(),\n committedLength: bigint('committed_length', {mode: 'number'}).notNull().default(0),\n state: text('state', {enum: ['open', 'closed']})\n .notNull()\n .default('open'),\n closeReason: text('close_reason', {enum: ['declared', 'timeout']}),\n declaredTotalBytes: bigint('declared_total_bytes', {mode: 'number'}),\n claudeHasInit: boolean('claude_has_init').notNull().default(false),\n claudeSessionId: text('claude_session_id'),\n claudeTurn: integer('claude_turn').notNull().default(0),\n claudePendingResult: jsonb('claude_pending_result').$type<SessionViewLifecycleRow>(),\n claudePendingToolRows: jsonb('claude_pending_tool_rows').$type<SessionViewRow[]>(),\n truncated: boolean('truncated').notNull().default(false),\n objectKey: text('object_key'),\n createdAt: timestamp('created_at', {withTimezone: true}).notNull().defaultNow(),\n updatedAt: timestamp('updated_at', {withTimezone: true}).notNull().defaultNow(),\n closedAt: timestamp('closed_at', {withTimezone: true}),\n },\n (table) => [\n uniqueIndex('logs_attempt_streams_identity_unique').on(\n table.jobId,\n table.stepId,\n table.attempt,\n ),\n // The session read path looks up a stream by (step, attempt) without the job id, so the\n // identity unique (which leads with job_id) can't serve it. This index keeps that lookup\n // off a sequential scan as the table grows.\n index('logs_attempt_streams_step_attempt_idx').on(table.stepId, table.attempt),\n // Timeout-close sweeps an open job's streams by job_id; the partial index keeps\n // it off the closed tail.\n index('logs_attempt_streams_open_by_job_idx').on(table.jobId).where(sql`\"state\" = 'open'`),\n // Open-age reaping needs an index that stays off the closed tail; created_at is\n // immutable, so appends do not churn it.\n index('logs_attempt_streams_open_age_idx').on(table.createdAt).where(sql`\"state\" = 'open'`),\n // Retention scans closed streams by close age; partial so it never carries the\n // open (in-flight) set.\n index('logs_attempt_streams_retention_idx').on(table.closedAt).where(sql`\"state\" = 'closed'`),\n // Reconcile re-drives closed streams that never got an object key.\n index('logs_attempt_streams_uncompacted_idx')\n .on(table.closedAt)\n .where(sql`\"state\" = 'closed' and \"object_key\" is null`),\n ],\n);\n\nexport type AttemptStreamDb = typeof attemptStreams.$inferSelect;\nexport type AttemptStreamInsertDb = typeof attemptStreams.$inferInsert;\n\nexport function toAttemptStream(row: AttemptStreamDb): AttemptStream {\n return {\n id: row.id,\n jobId: row.jobId,\n stepId: row.stepId,\n attempt: row.attempt,\n workspaceId: row.workspaceId,\n projectId: row.projectId,\n workflowRunAttemptId: row.workflowRunAttemptId,\n committedLength: row.committedLength,\n state: row.state,\n closeReason: row.closeReason,\n declaredTotalBytes: row.declaredTotalBytes,\n claudeHasInit: row.claudeHasInit,\n claudeSessionId: row.claudeSessionId,\n claudeTurn: row.claudeTurn,\n claudePendingResult: row.claudePendingResult ?? null,\n claudePendingToolRows: row.claudePendingToolRows ?? [],\n truncated: row.truncated,\n objectKey: row.objectKey,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n closedAt: row.closedAt,\n };\n}\n"],"names":["uuidv7PrimaryKey","sql","bigint","boolean","index","integer","jsonb","text","timestamp","uniqueIndex","uuid","pgTable","attemptStreams","id","jobId","notNull","stepId","attempt","workspaceId","projectId","workflowRunAttemptId","committedLength","mode","default","state","enum","closeReason","declaredTotalBytes","claudeHasInit","claudeSessionId","claudeTurn","claudePendingResult","$type","claudePendingToolRows","truncated","objectKey","createdAt","withTimezone","defaultNow","updatedAt","closedAt","table","on","where","toAttemptStream","row"],"mappings":"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"}