@shipfox/api-logs 9.2.0 → 10.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 +38 -0
  3. package/dist/core/append-logs.d.ts.map +1 -1
  4. package/dist/core/append-logs.js +100 -12
  5. package/dist/core/append-logs.js.map +1 -1
  6. package/dist/core/close-stream.d.ts +6 -5
  7. package/dist/core/close-stream.d.ts.map +1 -1
  8. package/dist/core/close-stream.js +25 -7
  9. package/dist/core/close-stream.js.map +1 -1
  10. package/dist/core/entities/attempt-stream.d.ts +5 -0
  11. package/dist/core/entities/attempt-stream.d.ts.map +1 -1
  12. package/dist/core/entities/attempt-stream.js +1 -1
  13. package/dist/core/entities/attempt-stream.js.map +1 -1
  14. package/dist/core/session/claude/rows.d.ts +4 -2
  15. package/dist/core/session/claude/rows.d.ts.map +1 -1
  16. package/dist/core/session/claude/rows.js +44 -9
  17. package/dist/core/session/claude/rows.js.map +1 -1
  18. package/dist/core/session/claude-parser.d.ts +8 -1
  19. package/dist/core/session/claude-parser.d.ts.map +1 -1
  20. package/dist/core/session/claude-parser.js +30 -4
  21. package/dist/core/session/claude-parser.js.map +1 -1
  22. package/dist/core/session/parse-session.d.ts +6 -1
  23. package/dist/core/session/parse-session.d.ts.map +1 -1
  24. package/dist/core/session/parse-session.js +2 -2
  25. package/dist/core/session/parse-session.js.map +1 -1
  26. package/dist/db/db.d.ts +94 -0
  27. package/dist/db/db.d.ts.map +1 -1
  28. package/dist/db/schema/attempt-streams.d.ts +94 -0
  29. package/dist/db/schema/attempt-streams.d.ts.map +1 -1
  30. package/dist/db/schema/attempt-streams.js +9 -1
  31. package/dist/db/schema/attempt-streams.js.map +1 -1
  32. package/dist/db/streams.d.ts +17 -6
  33. package/dist/db/streams.d.ts.map +1 -1
  34. package/dist/db/streams.js +21 -6
  35. package/dist/db/streams.js.map +1 -1
  36. package/dist/tsconfig.test.tsbuildinfo +1 -1
  37. package/drizzle/0001_strong_major_mapleleaf.sql +4 -0
  38. package/drizzle/meta/0001_snapshot.json +555 -0
  39. package/drizzle/meta/_journal.json +7 -0
  40. package/package.json +7 -7
  41. package/src/core/append-logs.test.ts +144 -4
  42. package/src/core/append-logs.ts +158 -6
  43. package/src/core/close-stream.ts +26 -7
  44. package/src/core/entities/attempt-stream.ts +6 -0
  45. package/src/core/finalize-attempt-stream.test.ts +57 -0
  46. package/src/core/session/claude/rows.ts +57 -7
  47. package/src/core/session/claude-parser.test.ts +118 -1
  48. package/src/core/session/claude-parser.ts +55 -4
  49. package/src/core/session/parse-session.ts +10 -1
  50. package/src/db/schema/attempt-streams.ts +10 -0
  51. package/src/db/streams.ts +43 -7
  52. package/tsconfig.build.tsbuildinfo +1 -1
@@ -1,16 +1,40 @@
1
1
  import { asLooseObject } from '../entry-schema.js';
2
2
  import { booleanField, field, formatNumber, isMeta, metaItem, stringField, stringifyValue, toJson } from '../object.js';
3
3
  import { lifecycleRow, messageRow, thinkingRow } from '../rows.js';
4
- export function systemRow(timestamp, message) {
4
+ export const PURE_PROGRESS_CLAUDE_SYSTEM_SUBTYPES = new Set([
5
+ 'thinking_tokens',
6
+ 'status',
7
+ 'session_state_changed',
8
+ 'task_progress',
9
+ 'hook_progress',
10
+ 'hook_started',
11
+ 'commands_changed',
12
+ 'files_persisted',
13
+ 'memory_recall',
14
+ 'local_command_output',
15
+ 'plugin_install'
16
+ ]);
17
+ const OUTPUT_REPROMPT_PREFIX = 'The previous turn ended without setting required workflow outputs:';
18
+ export function systemRow(timestamp, message, context) {
5
19
  const subtype = stringField(message, 'subtype');
6
- const label = subtype === 'init' || message.type === 'init' ? 'Session started' : 'Session event';
7
- const detail = stringField(message, 'session_id') ?? stringField(message, 'sessionId') ?? null;
8
- const meta = [
20
+ const sessionId = stringField(message, 'session_id') ?? stringField(message, 'sessionId');
21
+ const isInit = subtype === 'init' || message.type === 'init';
22
+ const baseMeta = [
9
23
  metaItem('cwd', stringField(message, 'cwd'), false),
10
24
  metaItem('model', stringField(message, 'model')),
11
25
  metaItem('permission', stringField(message, 'permissionMode') ?? stringField(message, 'permission_mode'))
12
26
  ].filter(isMeta);
13
- return lifecycleRow(timestamp, label, detail, 'default', false, meta);
27
+ if (!isInit) return lifecycleRow(timestamp, 'Session event', sessionId ?? null, 'default', false, baseMeta);
28
+ const isNewSession = !context.hasInit || sessionId === undefined || sessionId !== context.sessionId;
29
+ context.hasInit = true;
30
+ context.sessionId = sessionId ?? null;
31
+ context.turn = isNewSession ? 1 : context.turn + 1;
32
+ const label = isNewSession ? 'Session started' : `Turn ${context.turn} started`;
33
+ const meta = [
34
+ metaItem('turn', isNewSession ? null : String(context.turn)),
35
+ ...baseMeta
36
+ ].filter(isMeta);
37
+ return lifecycleRow(timestamp, label, sessionId ?? null, 'default', false, meta);
14
38
  }
15
39
  export function assistantRows(timestamp, message) {
16
40
  const sdkMessage = asLooseObject(message.message) ?? message;
@@ -55,11 +79,12 @@ export function assistantRows(timestamp, message) {
55
79
  }
56
80
  export function userRows(timestamp, message) {
57
81
  const sdkMessage = asLooseObject(message.message) ?? message;
82
+ const role = isPlatformMessage(sdkMessage) ? 'platform' : 'user';
58
83
  const rows = [];
59
84
  const textParts = [];
60
85
  const pushText = ()=>{
61
86
  if (textParts.length === 0) return;
62
- rows.push(messageRow(timestamp, 'user', 'user', textParts.join('\n\n'), false));
87
+ rows.push(messageRow(timestamp, role, role, textParts.join('\n\n'), false));
63
88
  textParts.length = 0;
64
89
  };
65
90
  for (const block of contentBlocks(sdkMessage)){
@@ -76,21 +101,31 @@ export function userRows(timestamp, message) {
76
101
  if (rows.length > 0) return rows;
77
102
  const content = stringField(sdkMessage, 'content');
78
103
  return [
79
- messageRow(timestamp, 'user', 'user', content ?? toJson(message), false)
104
+ messageRow(timestamp, role, role, content ?? toJson(message), false)
80
105
  ];
81
106
  }
82
- export function resultRow(timestamp, message) {
107
+ export function resultRow(timestamp, message, turn, isFinalResult) {
83
108
  const isError = booleanField(message, 'is_error') || booleanField(message, 'isError');
84
109
  const subtype = stringField(message, 'subtype');
85
110
  const terminalFailure = isError || subtype === 'error';
86
111
  const detail = stringField(message, 'result') ?? stringField(message, 'error') ?? stringField(message, 'message') ?? null;
87
112
  const meta = [
113
+ metaItem('turn', !terminalFailure && !isFinalResult && turn > 0 ? String(turn) : null),
88
114
  numberMeta(message, 'duration_ms', 'duration', 'ms'),
89
115
  numberMeta(message, 'duration_api_ms', 'api duration', 'ms'),
90
116
  numberMeta(message, 'num_turns', 'turns'),
91
117
  costMeta(message)
92
118
  ].filter(isMeta);
93
- return lifecycleRow(timestamp, terminalFailure ? 'Session failed' : 'Session completed', detail, terminalFailure ? 'error' : 'default', terminalFailure, meta);
119
+ return lifecycleRow(timestamp, terminalFailure ? 'Session failed' : isFinalResult || turn === 0 ? 'Session completed' : `Turn ${turn} completed`, detail, terminalFailure ? 'error' : 'default', terminalFailure, meta);
120
+ }
121
+ function isPlatformMessage(message) {
122
+ const content = field(message, 'content');
123
+ if (typeof content === 'string') return content.startsWith(OUTPUT_REPROMPT_PREFIX);
124
+ if (!Array.isArray(content)) return false;
125
+ return content.some((block)=>{
126
+ const object = asLooseObject(block);
127
+ return stringField(object, 'text')?.startsWith(OUTPUT_REPROMPT_PREFIX) === true;
128
+ });
94
129
  }
95
130
  function contentBlocks(message) {
96
131
  const content = message.content;
@@ -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 {asLooseObject} from '../entry-schema.js';\nimport {\n booleanField,\n field,\n formatNumber,\n isMeta,\n metaItem,\n stringField,\n stringifyValue,\n toJson,\n} from '../object.js';\nimport {lifecycleRow, messageRow, thinkingRow} from '../rows.js';\n\nexport function systemRow(\n timestamp: number,\n message: Record<string, unknown>,\n): SessionViewLifecycleRow {\n const subtype = stringField(message, 'subtype');\n const label = subtype === 'init' || message.type === 'init' ? 'Session started' : 'Session event';\n const detail = stringField(message, 'session_id') ?? stringField(message, 'sessionId') ?? null;\n const meta = [\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 return lifecycleRow(timestamp, label, detail, 'default', false, meta);\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 rows: SessionViewRow[] = [];\n const textParts: string[] = [];\n const pushText = () => {\n if (textParts.length === 0) return;\n rows.push(messageRow(timestamp, 'user', 'user', 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, 'user', 'user', content ?? toJson(message), false)];\n}\n\nexport function resultRow(\n timestamp: number,\n message: Record<string, unknown>,\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 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 ? 'Session failed' : 'Session completed',\n detail,\n terminalFailure ? 'error' : 'default',\n terminalFailure,\n meta,\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","stringField","stringifyValue","toJson","lifecycleRow","messageRow","thinkingRow","systemRow","timestamp","message","subtype","label","type","detail","meta","filter","assistantRows","sdkMessage","rows","textParts","thinkingParts","pushText","length","push","join","pushThinking","block","contentBlocks","toolCallRow","text","blockText","userRows","toolResultRow","content","resultRow","isError","terminalFailure","numberMeta","costMeta","Array","isArray","flatMap","object","kind","id","name","input","toolCallId","toolName","output","map","item","Boolean","value","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 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\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 return lifecycleRow(timestamp, 'Session event', sessionId ?? null, 'default', false, baseMeta);\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 = [metaItem('turn', isNewSession ? null : String(context.turn)), ...baseMeta].filter(\n isMeta,\n );\n\n return lifecycleRow(timestamp, label, sessionId ?? null, 'default', false, meta);\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","stringField","stringifyValue","toJson","lifecycleRow","messageRow","thinkingRow","PURE_PROGRESS_CLAUDE_SYSTEM_SUBTYPES","Set","OUTPUT_REPROMPT_PREFIX","systemRow","timestamp","message","context","subtype","sessionId","isInit","type","baseMeta","filter","isNewSession","hasInit","undefined","turn","label","meta","String","assistantRows","sdkMessage","rows","textParts","thinkingParts","pushText","length","push","join","pushThinking","block","contentBlocks","toolCallRow","text","blockText","userRows","role","isPlatformMessage","toolResultRow","content","resultRow","isFinalResult","isError","terminalFailure","detail","numberMeta","costMeta","startsWith","Array","isArray","some","object","flatMap","kind","id","name","input","toolCallId","toolName","output","map","item","Boolean","value","key","unit","raw","Number","isFinite","toFixed"],"mappings":"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"}
@@ -1,4 +1,11 @@
1
1
  import type { SessionViewRow } from '@shipfox/api-logs-dto';
2
2
  import type { AgentSessionRecord } from './session-record.js';
3
- export declare function parseClaudeSessionRecord(record: AgentSessionRecord): readonly SessionViewRow[];
3
+ export interface ClaudeParseContext {
4
+ hasInit: boolean;
5
+ sessionId: string | null;
6
+ turn: number;
7
+ }
8
+ export declare function createClaudeParseContext(): ClaudeParseContext;
9
+ export declare function claudeInitSessionId(record: AgentSessionRecord): string | undefined;
10
+ export declare function parseClaudeSessionRecord(record: AgentSessionRecord, context?: ClaudeParseContext, isFinalResult?: boolean): readonly SessionViewRow[];
4
11
  //# 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;AAI1D,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,qBAAqB,CAAC;AAQ5D,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,kBAAkB,GAAG,SAAS,cAAc,EAAE,CAyB9F"}
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,10 +1,33 @@
1
1
  import { z } from 'zod';
2
- import { assistantRows, resultRow, systemRow, userRows } from './claude/rows.js';
2
+ import { assistantRows, PURE_PROGRESS_CLAUDE_SYSTEM_SUBTYPES, resultRow, systemRow, userRows } from './claude/rows.js';
3
+ import { stringField } from './object.js';
3
4
  import { rawRecordRow } from './rows.js';
4
5
  const claudeMessageSchema = z.object({
5
6
  type: z.string().min(1)
6
7
  }).catchall(z.unknown());
7
- export function parseClaudeSessionRecord(record) {
8
+ export function createClaudeParseContext() {
9
+ return {
10
+ hasInit: false,
11
+ sessionId: null,
12
+ turn: 0
13
+ };
14
+ }
15
+ export function claudeInitSessionId(record) {
16
+ let json;
17
+ try {
18
+ json = JSON.parse(record.data);
19
+ } catch {
20
+ return undefined;
21
+ }
22
+ const parsed = claudeMessageSchema.safeParse(json);
23
+ if (!parsed.success) return undefined;
24
+ const message = parsed.data;
25
+ if (message.type !== 'init' && !(message.type === 'system' && stringField(message, 'subtype') === 'init')) {
26
+ return undefined;
27
+ }
28
+ return stringField(message, 'session_id') ?? stringField(message, 'sessionId');
29
+ }
30
+ export function parseClaudeSessionRecord(record, context = createClaudeParseContext(), isFinalResult = true) {
8
31
  let json;
9
32
  try {
10
33
  json = JSON.parse(record.data);
@@ -18,11 +41,14 @@ export function parseClaudeSessionRecord(record) {
18
41
  rawRecordRow(record, 'Unsupported Claude message')
19
42
  ];
20
43
  const message = parsed.data;
44
+ if (message.type === 'system' && typeof message.subtype === 'string' && PURE_PROGRESS_CLAUDE_SYSTEM_SUBTYPES.has(message.subtype)) {
45
+ return [];
46
+ }
21
47
  switch(message.type){
22
48
  case 'system':
23
49
  case 'init':
24
50
  return [
25
- systemRow(record.ts, message)
51
+ systemRow(record.ts, message, context)
26
52
  ];
27
53
  case 'assistant':
28
54
  return assistantRows(record.ts, message);
@@ -30,7 +56,7 @@ export function parseClaudeSessionRecord(record) {
30
56
  return userRows(record.ts, message);
31
57
  case 'result':
32
58
  return [
33
- resultRow(record.ts, message)
59
+ resultRow(record.ts, message, context.turn, isFinalResult)
34
60
  ];
35
61
  default:
36
62
  return [
@@ -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 {assistantRows, resultRow, systemRow, userRows} from './claude/rows.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 function parseClaudeSessionRecord(record: AgentSessionRecord): 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 switch (message.type) {\n case 'system':\n case 'init':\n return [systemRow(record.ts, message)];\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)];\n default:\n return [rawRecordRow(record, `Unknown Claude message: ${message.type}`)];\n }\n}\n"],"names":["z","assistantRows","resultRow","systemRow","userRows","rawRecordRow","claudeMessageSchema","object","type","string","min","catchall","unknown","parseClaudeSessionRecord","record","json","JSON","parse","data","parsed","safeParse","success","message","ts"],"mappings":"AACA,SAAQA,CAAC,QAAO,MAAM;AACtB,SAAQC,aAAa,EAAEC,SAAS,EAAEC,SAAS,EAAEC,QAAQ,QAAO,mBAAmB;AAC/E,SAAQC,YAAY,QAAO,YAAY;AAGvC,MAAMC,sBAAsBN,EACzBO,MAAM,CAAC;IACNC,MAAMR,EAAES,MAAM,GAAGC,GAAG,CAAC;AACvB,GACCC,QAAQ,CAACX,EAAEY,OAAO;AAErB,OAAO,SAASC,yBAAyBC,MAA0B;IACjE,IAAIC;IACJ,IAAI;QACFA,OAAOC,KAAKC,KAAK,CAACH,OAAOI,IAAI;IAC/B,EAAE,OAAM;QACN,OAAO;YAACb,aAAaS,QAAQ;SAA2B;IAC1D;IAEA,MAAMK,SAASb,oBAAoBc,SAAS,CAACL;IAC7C,IAAI,CAACI,OAAOE,OAAO,EAAE,OAAO;QAAChB,aAAaS,QAAQ;KAA8B;IAEhF,MAAMQ,UAAUH,OAAOD,IAAI;IAC3B,OAAQI,QAAQd,IAAI;QAClB,KAAK;QACL,KAAK;YACH,OAAO;gBAACL,UAAUW,OAAOS,EAAE,EAAED;aAAS;QACxC,KAAK;YACH,OAAOrB,cAAca,OAAOS,EAAE,EAAED;QAClC,KAAK;YACH,OAAOlB,SAASU,OAAOS,EAAE,EAAED;QAC7B,KAAK;YACH,OAAO;gBAACpB,UAAUY,OAAOS,EAAE,EAAED;aAAS;QACxC;YACE,OAAO;gBAACjB,aAAaS,QAAQ,CAAC,wBAAwB,EAAEQ,QAAQd,IAAI,EAAE;aAAE;IAC5E;AACF"}
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,5 +1,10 @@
1
1
  import type { SessionViewRow } from '@shipfox/api-logs-dto';
2
2
  import type { Harness } from '@shipfox/workflow-document';
3
+ import type { ClaudeParseContext } from './claude-parser.js';
3
4
  import type { AgentSessionRecord } from './session-record.js';
4
- export declare function parseSessionRecord(record: AgentSessionRecord, harness: Harness): readonly SessionViewRow[];
5
+ export interface SessionParseContext {
6
+ claude?: ClaudeParseContext;
7
+ isFinalResult?: boolean;
8
+ }
9
+ export declare function parseSessionRecord(record: AgentSessionRecord, harness: Harness, context?: SessionParseContext): readonly SessionViewRow[];
5
10
  //# sourceMappingURL=parse-session.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"parse-session.d.ts","sourceRoot":"","sources":["../../../src/core/session/parse-session.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,uBAAuB,CAAC;AAC1D,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,4BAA4B,CAAC;AAGxD,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,qBAAqB,CAAC;AAE5D,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,kBAAkB,EAC1B,OAAO,EAAE,OAAO,GACf,SAAS,cAAc,EAAE,CAa3B"}
1
+ {"version":3,"file":"parse-session.d.ts","sourceRoot":"","sources":["../../../src/core/session/parse-session.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,uBAAuB,CAAC;AAC1D,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,4BAA4B,CAAC;AACxD,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,oBAAoB,CAAC;AAG3D,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,qBAAqB,CAAC;AAE5D,MAAM,WAAW,mBAAmB;IAClC,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAC5B,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,kBAAkB,EAC1B,OAAO,EAAE,OAAO,EAChB,OAAO,CAAC,EAAE,mBAAmB,GAC5B,SAAS,cAAc,EAAE,CAe3B"}
@@ -1,8 +1,8 @@
1
1
  import { parseClaudeSessionRecord } from './claude-parser.js';
2
2
  import { parsePiSessionRecord } from './pi-parser.js';
3
- export function parseSessionRecord(record, harness) {
3
+ export function parseSessionRecord(record, harness, context) {
4
4
  try {
5
- return harness === 'claude' ? parseClaudeSessionRecord(record) : parsePiSessionRecord(record);
5
+ return harness === 'claude' ? parseClaudeSessionRecord(record, context?.claude, context?.isFinalResult ?? true) : parsePiSessionRecord(record);
6
6
  } catch {
7
7
  return [
8
8
  {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/core/session/parse-session.ts"],"sourcesContent":["import type {SessionViewRow} from '@shipfox/api-logs-dto';\nimport type {Harness} from '@shipfox/workflow-document';\nimport {parseClaudeSessionRecord} from './claude-parser.js';\nimport {parsePiSessionRecord} from './pi-parser.js';\nimport type {AgentSessionRecord} from './session-record.js';\n\nexport function parseSessionRecord(\n record: AgentSessionRecord,\n harness: Harness,\n): readonly SessionViewRow[] {\n try {\n return harness === 'claude' ? parseClaudeSessionRecord(record) : parsePiSessionRecord(record);\n } catch {\n return [\n {\n kind: 'raw',\n timestamp: record.ts,\n label: 'Unsupported session entry',\n raw: record.data,\n },\n ];\n }\n}\n"],"names":["parseClaudeSessionRecord","parsePiSessionRecord","parseSessionRecord","record","harness","kind","timestamp","ts","label","raw","data"],"mappings":"AAEA,SAAQA,wBAAwB,QAAO,qBAAqB;AAC5D,SAAQC,oBAAoB,QAAO,iBAAiB;AAGpD,OAAO,SAASC,mBACdC,MAA0B,EAC1BC,OAAgB;IAEhB,IAAI;QACF,OAAOA,YAAY,WAAWJ,yBAAyBG,UAAUF,qBAAqBE;IACxF,EAAE,OAAM;QACN,OAAO;YACL;gBACEE,MAAM;gBACNC,WAAWH,OAAOI,EAAE;gBACpBC,OAAO;gBACPC,KAAKN,OAAOO,IAAI;YAClB;SACD;IACH;AACF"}
1
+ {"version":3,"sources":["../../../src/core/session/parse-session.ts"],"sourcesContent":["import type {SessionViewRow} from '@shipfox/api-logs-dto';\nimport type {Harness} from '@shipfox/workflow-document';\nimport type {ClaudeParseContext} from './claude-parser.js';\nimport {parseClaudeSessionRecord} from './claude-parser.js';\nimport {parsePiSessionRecord} from './pi-parser.js';\nimport type {AgentSessionRecord} from './session-record.js';\n\nexport interface SessionParseContext {\n claude?: ClaudeParseContext;\n isFinalResult?: boolean;\n}\n\nexport function parseSessionRecord(\n record: AgentSessionRecord,\n harness: Harness,\n context?: SessionParseContext,\n): readonly SessionViewRow[] {\n try {\n return harness === 'claude'\n ? parseClaudeSessionRecord(record, context?.claude, context?.isFinalResult ?? true)\n : parsePiSessionRecord(record);\n } catch {\n return [\n {\n kind: 'raw',\n timestamp: record.ts,\n label: 'Unsupported session entry',\n raw: record.data,\n },\n ];\n }\n}\n"],"names":["parseClaudeSessionRecord","parsePiSessionRecord","parseSessionRecord","record","harness","context","claude","isFinalResult","kind","timestamp","ts","label","raw","data"],"mappings":"AAGA,SAAQA,wBAAwB,QAAO,qBAAqB;AAC5D,SAAQC,oBAAoB,QAAO,iBAAiB;AAQpD,OAAO,SAASC,mBACdC,MAA0B,EAC1BC,OAAgB,EAChBC,OAA6B;IAE7B,IAAI;QACF,OAAOD,YAAY,WACfJ,yBAAyBG,QAAQE,SAASC,QAAQD,SAASE,iBAAiB,QAC5EN,qBAAqBE;IAC3B,EAAE,OAAM;QACN,OAAO;YACL;gBACEK,MAAM;gBACNC,WAAWN,OAAOO,EAAE;gBACpBC,OAAO;gBACPC,KAAKT,OAAOU,IAAI;YAClB;SACD;IACH;AACF"}
package/dist/db/db.d.ts CHANGED
@@ -317,6 +317,100 @@ export declare const schema: {
317
317
  identity: undefined;
318
318
  generated: undefined;
319
319
  }, {}, {}>;
320
+ claudeHasInit: import("drizzle-orm/pg-core").PgColumn<{
321
+ name: "claude_has_init";
322
+ tableName: "attempt_streams";
323
+ dataType: "boolean";
324
+ columnType: "PgBoolean";
325
+ data: boolean;
326
+ driverParam: boolean;
327
+ notNull: true;
328
+ hasDefault: true;
329
+ isPrimaryKey: false;
330
+ isAutoincrement: false;
331
+ hasRuntimeDefault: false;
332
+ enumValues: undefined;
333
+ baseColumn: never;
334
+ identity: undefined;
335
+ generated: undefined;
336
+ }, {}, {}>;
337
+ claudeSessionId: import("drizzle-orm/pg-core").PgColumn<{
338
+ name: "claude_session_id";
339
+ tableName: "attempt_streams";
340
+ dataType: "string";
341
+ columnType: "PgText";
342
+ data: string;
343
+ driverParam: string;
344
+ notNull: false;
345
+ hasDefault: false;
346
+ isPrimaryKey: false;
347
+ isAutoincrement: false;
348
+ hasRuntimeDefault: false;
349
+ enumValues: [string, ...string[]];
350
+ baseColumn: never;
351
+ identity: undefined;
352
+ generated: undefined;
353
+ }, {}, {}>;
354
+ claudeTurn: import("drizzle-orm/pg-core").PgColumn<{
355
+ name: "claude_turn";
356
+ tableName: "attempt_streams";
357
+ dataType: "number";
358
+ columnType: "PgInteger";
359
+ data: number;
360
+ driverParam: string | number;
361
+ notNull: true;
362
+ hasDefault: true;
363
+ isPrimaryKey: false;
364
+ isAutoincrement: false;
365
+ hasRuntimeDefault: false;
366
+ enumValues: undefined;
367
+ baseColumn: never;
368
+ identity: undefined;
369
+ generated: undefined;
370
+ }, {}, {}>;
371
+ claudePendingResult: import("drizzle-orm/pg-core").PgColumn<{
372
+ name: "claude_pending_result";
373
+ tableName: "attempt_streams";
374
+ dataType: "json";
375
+ columnType: "PgJsonb";
376
+ data: {
377
+ kind: "lifecycle";
378
+ label: string;
379
+ detail: string | null;
380
+ meta: readonly {
381
+ label: string;
382
+ value: string;
383
+ inline?: boolean | undefined;
384
+ }[];
385
+ tone: "default" | "error" | "warning";
386
+ terminalFailure: boolean;
387
+ timestamp: number;
388
+ };
389
+ driverParam: unknown;
390
+ notNull: false;
391
+ hasDefault: false;
392
+ isPrimaryKey: false;
393
+ isAutoincrement: false;
394
+ hasRuntimeDefault: false;
395
+ enumValues: undefined;
396
+ baseColumn: never;
397
+ identity: undefined;
398
+ generated: undefined;
399
+ }, {}, {
400
+ $type: {
401
+ kind: "lifecycle";
402
+ label: string;
403
+ detail: string | null;
404
+ meta: readonly {
405
+ label: string;
406
+ value: string;
407
+ inline?: boolean | undefined;
408
+ }[];
409
+ tone: "default" | "error" | "warning";
410
+ terminalFailure: boolean;
411
+ timestamp: number;
412
+ };
413
+ }>;
320
414
  truncated: import("drizzle-orm/pg-core").PgColumn<{
321
415
  name: "truncated";
322
416
  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"}
@@ -210,6 +210,100 @@ export declare const attemptStreams: import("drizzle-orm/pg-core").PgTableWithCo
210
210
  identity: undefined;
211
211
  generated: undefined;
212
212
  }, {}, {}>;
213
+ claudeHasInit: import("drizzle-orm/pg-core").PgColumn<{
214
+ name: "claude_has_init";
215
+ tableName: "attempt_streams";
216
+ dataType: "boolean";
217
+ columnType: "PgBoolean";
218
+ data: boolean;
219
+ driverParam: boolean;
220
+ notNull: true;
221
+ hasDefault: true;
222
+ isPrimaryKey: false;
223
+ isAutoincrement: false;
224
+ hasRuntimeDefault: false;
225
+ enumValues: undefined;
226
+ baseColumn: never;
227
+ identity: undefined;
228
+ generated: undefined;
229
+ }, {}, {}>;
230
+ claudeSessionId: import("drizzle-orm/pg-core").PgColumn<{
231
+ name: "claude_session_id";
232
+ tableName: "attempt_streams";
233
+ dataType: "string";
234
+ columnType: "PgText";
235
+ data: string;
236
+ driverParam: string;
237
+ notNull: false;
238
+ hasDefault: false;
239
+ isPrimaryKey: false;
240
+ isAutoincrement: false;
241
+ hasRuntimeDefault: false;
242
+ enumValues: [string, ...string[]];
243
+ baseColumn: never;
244
+ identity: undefined;
245
+ generated: undefined;
246
+ }, {}, {}>;
247
+ claudeTurn: import("drizzle-orm/pg-core").PgColumn<{
248
+ name: "claude_turn";
249
+ tableName: "attempt_streams";
250
+ dataType: "number";
251
+ columnType: "PgInteger";
252
+ data: number;
253
+ driverParam: string | number;
254
+ notNull: true;
255
+ hasDefault: true;
256
+ isPrimaryKey: false;
257
+ isAutoincrement: false;
258
+ hasRuntimeDefault: false;
259
+ enumValues: undefined;
260
+ baseColumn: never;
261
+ identity: undefined;
262
+ generated: undefined;
263
+ }, {}, {}>;
264
+ claudePendingResult: import("drizzle-orm/pg-core").PgColumn<{
265
+ name: "claude_pending_result";
266
+ tableName: "attempt_streams";
267
+ dataType: "json";
268
+ columnType: "PgJsonb";
269
+ data: {
270
+ kind: "lifecycle";
271
+ label: string;
272
+ detail: string | null;
273
+ meta: readonly {
274
+ label: string;
275
+ value: string;
276
+ inline?: boolean | undefined;
277
+ }[];
278
+ tone: "default" | "error" | "warning";
279
+ terminalFailure: boolean;
280
+ timestamp: number;
281
+ };
282
+ driverParam: unknown;
283
+ notNull: false;
284
+ hasDefault: false;
285
+ isPrimaryKey: false;
286
+ isAutoincrement: false;
287
+ hasRuntimeDefault: false;
288
+ enumValues: undefined;
289
+ baseColumn: never;
290
+ identity: undefined;
291
+ generated: undefined;
292
+ }, {}, {
293
+ $type: {
294
+ kind: "lifecycle";
295
+ label: string;
296
+ detail: string | null;
297
+ meta: readonly {
298
+ label: string;
299
+ value: string;
300
+ inline?: boolean | undefined;
301
+ }[];
302
+ tone: "default" | "error" | "warning";
303
+ terminalFailure: boolean;
304
+ timestamp: number;
305
+ };
306
+ }>;
213
307
  truncated: import("drizzle-orm/pg-core").PgColumn<{
214
308
  name: "truncated";
215
309
  tableName: "attempt_streams";
@@ -1 +1 @@
1
- {"version":3,"file":"attempt-streams.d.ts","sourceRoot":"","sources":["../../../src/db/schema/attempt-streams.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,kCAAkC,CAAC;AAGpE;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA8C1B,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,CAmBnE"}
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,6 +1,6 @@
1
1
  import { uuidv7PrimaryKey } from '@shipfox/node-drizzle';
2
2
  import { sql } from 'drizzle-orm';
3
- import { bigint, boolean, index, integer, text, timestamp, uniqueIndex, uuid } from 'drizzle-orm/pg-core';
3
+ import { bigint, boolean, index, integer, jsonb, text, timestamp, uniqueIndex, uuid } from 'drizzle-orm/pg-core';
4
4
  import { pgTable } from './common.js';
5
5
  /**
6
6
  * One stream per (job, step, attempt). Identity is scoped by `job_id` (from the
@@ -47,6 +47,10 @@ import { pgTable } from './common.js';
47
47
  declaredTotalBytes: bigint('declared_total_bytes', {
48
48
  mode: 'number'
49
49
  }),
50
+ claudeHasInit: boolean('claude_has_init').notNull().default(false),
51
+ claudeSessionId: text('claude_session_id'),
52
+ claudeTurn: integer('claude_turn').notNull().default(0),
53
+ claudePendingResult: jsonb('claude_pending_result').$type(),
50
54
  truncated: boolean('truncated').notNull().default(false),
51
55
  objectKey: text('object_key'),
52
56
  createdAt: timestamp('created_at', {
@@ -89,6 +93,10 @@ export function toAttemptStream(row) {
89
93
  state: row.state,
90
94
  closeReason: row.closeReason,
91
95
  declaredTotalBytes: row.declaredTotalBytes,
96
+ claudeHasInit: row.claudeHasInit,
97
+ claudeSessionId: row.claudeSessionId,
98
+ claudeTurn: row.claudeTurn,
99
+ claudePendingResult: row.claudePendingResult ?? null,
92
100
  truncated: row.truncated,
93
101
  objectKey: row.objectKey,
94
102
  createdAt: row.createdAt,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/db/schema/attempt-streams.ts"],"sourcesContent":["import {uuidv7PrimaryKey} from '@shipfox/node-drizzle';\nimport {sql} from 'drizzle-orm';\nimport {\n bigint,\n boolean,\n index,\n integer,\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 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 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","text","timestamp","uniqueIndex","uuid","pgTable","attemptStreams","id","jobId","notNull","stepId","attempt","workspaceId","projectId","workflowRunAttemptId","committedLength","mode","default","state","enum","closeReason","declaredTotalBytes","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} 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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"}