@shipfox/api-logs 9.3.0 → 10.1.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 +35 -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 +137 -10
  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 +5 -5
  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 +176 -7
  47. package/src/core/session/claude-parser.test.ts +279 -2
  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,132 @@
1
1
  import { asLooseObject } from '../entry-schema.js';
2
- import { booleanField, field, formatNumber, isMeta, metaItem, stringField, stringifyValue, toJson } from '../object.js';
2
+ import { booleanField, field, formatNumber, isMeta, metaItem, numberField, 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
+ const SYSTEM_EVENT_MAPPINGS = {
19
+ permission_denied: {
20
+ label: 'Permission denied',
21
+ tone: 'error',
22
+ detail: (message)=>stringField(message, 'tool_name') ?? null,
23
+ meta: (message)=>[
24
+ metaItem('reason', stringField(message, 'decision_reason') ?? stringField(message, 'message'))
25
+ ].filter(isMeta)
26
+ },
27
+ api_retry: {
28
+ label: 'API retry',
29
+ tone: 'warning',
30
+ detail: (message)=>{
31
+ const attempt = numberField(message, 'attempt');
32
+ const maxRetries = numberField(message, 'max_retries');
33
+ return attempt == null || maxRetries == null ? null : `attempt ${formatNumber(attempt)}/${formatNumber(maxRetries)}`;
34
+ },
35
+ meta: (message)=>[
36
+ numberMeta(message, 'error_status', 'status'),
37
+ numberMeta(message, 'retry_delay_ms', 'retry delay', 'ms')
38
+ ].filter(isMeta)
39
+ },
40
+ informational: {
41
+ label: (message)=>titleCase(stringField(message, 'level') ?? 'informational'),
42
+ tone: (message)=>stringField(message, 'level') === 'warning' ? 'warning' : 'default',
43
+ detail: (message)=>stringField(message, 'content') ?? null
44
+ },
45
+ compact_boundary: {
46
+ label: 'Context compacted',
47
+ tone: 'default',
48
+ meta: (message)=>{
49
+ const compactMetadata = asLooseObject(field(message, 'compact_metadata')) ?? {};
50
+ return [
51
+ metaItem('trigger', stringField(compactMetadata, 'trigger')),
52
+ numberMeta(compactMetadata, 'pre_tokens', 'tokens before', 'tokens'),
53
+ numberMeta(compactMetadata, 'post_tokens', 'tokens after', 'tokens'),
54
+ numberMeta(compactMetadata, 'duration_ms', 'duration', 'ms')
55
+ ].filter(isMeta);
56
+ }
57
+ },
58
+ model_refusal_fallback: {
59
+ label: 'Model refused, fell back',
60
+ tone: 'warning'
61
+ },
62
+ model_refusal_no_fallback: {
63
+ label: 'Model refused',
64
+ tone: 'error'
65
+ },
66
+ mirror_error: {
67
+ label: 'Session mirror failed',
68
+ tone: 'error',
69
+ detail: (message)=>stringField(message, 'error') ?? null
70
+ },
71
+ worker_shutting_down: {
72
+ label: 'Worker shutting down',
73
+ tone: 'warning'
74
+ },
75
+ elicitation_complete: {
76
+ label: 'Elicitation complete',
77
+ tone: 'default'
78
+ },
79
+ notification: {
80
+ label: 'Notification',
81
+ tone: 'default'
82
+ },
83
+ task_started: {
84
+ label: 'Task started',
85
+ tone: 'default'
86
+ },
87
+ task_updated: {
88
+ label: 'Task updated',
89
+ tone: 'default'
90
+ },
91
+ task_notification: {
92
+ label: 'Task notification',
93
+ tone: 'default'
94
+ }
95
+ };
96
+ export function systemRow(timestamp, message, context) {
5
97
  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 = [
98
+ const sessionId = stringField(message, 'session_id') ?? stringField(message, 'sessionId');
99
+ const isInit = subtype === 'init' || message.type === 'init';
100
+ const baseMeta = [
9
101
  metaItem('cwd', stringField(message, 'cwd'), false),
10
102
  metaItem('model', stringField(message, 'model')),
11
103
  metaItem('permission', stringField(message, 'permissionMode') ?? stringField(message, 'permission_mode'))
12
104
  ].filter(isMeta);
13
- return lifecycleRow(timestamp, label, detail, 'default', false, meta);
105
+ if (!isInit) {
106
+ const mapping = subtype === undefined ? undefined : SYSTEM_EVENT_MAPPINGS[subtype];
107
+ const label = mapping === undefined ? `Session event (${subtype ?? 'unknown'})` : typeof mapping.label === 'function' ? mapping.label(message) : mapping.label;
108
+ const tone = mapping === undefined ? 'default' : typeof mapping.tone === 'function' ? mapping.tone(message) : mapping.tone;
109
+ const detail = mapping?.detail?.(message) ?? null;
110
+ const meta = [
111
+ ...baseMeta,
112
+ ...mapping?.meta?.(message) ?? []
113
+ ];
114
+ return lifecycleRow(timestamp, label, detail, tone, false, meta);
115
+ }
116
+ const isNewSession = !context.hasInit || sessionId === undefined || sessionId !== context.sessionId;
117
+ context.hasInit = true;
118
+ context.sessionId = sessionId ?? null;
119
+ context.turn = isNewSession ? 1 : context.turn + 1;
120
+ const label = isNewSession ? 'Session started' : `Turn ${context.turn} started`;
121
+ const meta = [
122
+ metaItem('session', isNewSession ? sessionId : null),
123
+ metaItem('turn', isNewSession ? null : String(context.turn)),
124
+ ...baseMeta
125
+ ].filter(isMeta);
126
+ return lifecycleRow(timestamp, label, null, 'default', false, meta);
127
+ }
128
+ function titleCase(value) {
129
+ return value.length === 0 ? value : `${value[0]?.toUpperCase()}${value.slice(1)}`;
14
130
  }
15
131
  export function assistantRows(timestamp, message) {
16
132
  const sdkMessage = asLooseObject(message.message) ?? message;
@@ -55,11 +171,12 @@ export function assistantRows(timestamp, message) {
55
171
  }
56
172
  export function userRows(timestamp, message) {
57
173
  const sdkMessage = asLooseObject(message.message) ?? message;
174
+ const role = isPlatformMessage(sdkMessage) ? 'platform' : 'user';
58
175
  const rows = [];
59
176
  const textParts = [];
60
177
  const pushText = ()=>{
61
178
  if (textParts.length === 0) return;
62
- rows.push(messageRow(timestamp, 'user', 'user', textParts.join('\n\n'), false));
179
+ rows.push(messageRow(timestamp, role, role, textParts.join('\n\n'), false));
63
180
  textParts.length = 0;
64
181
  };
65
182
  for (const block of contentBlocks(sdkMessage)){
@@ -76,21 +193,31 @@ export function userRows(timestamp, message) {
76
193
  if (rows.length > 0) return rows;
77
194
  const content = stringField(sdkMessage, 'content');
78
195
  return [
79
- messageRow(timestamp, 'user', 'user', content ?? toJson(message), false)
196
+ messageRow(timestamp, role, role, content ?? toJson(message), false)
80
197
  ];
81
198
  }
82
- export function resultRow(timestamp, message) {
199
+ export function resultRow(timestamp, message, turn, isFinalResult) {
83
200
  const isError = booleanField(message, 'is_error') || booleanField(message, 'isError');
84
201
  const subtype = stringField(message, 'subtype');
85
202
  const terminalFailure = isError || subtype === 'error';
86
203
  const detail = stringField(message, 'result') ?? stringField(message, 'error') ?? stringField(message, 'message') ?? null;
87
204
  const meta = [
205
+ metaItem('turn', !terminalFailure && !isFinalResult && turn > 0 ? String(turn) : null),
88
206
  numberMeta(message, 'duration_ms', 'duration', 'ms'),
89
207
  numberMeta(message, 'duration_api_ms', 'api duration', 'ms'),
90
208
  numberMeta(message, 'num_turns', 'turns'),
91
209
  costMeta(message)
92
210
  ].filter(isMeta);
93
- return lifecycleRow(timestamp, terminalFailure ? 'Session failed' : 'Session completed', detail, terminalFailure ? 'error' : 'default', terminalFailure, meta);
211
+ return lifecycleRow(timestamp, terminalFailure ? 'Session failed' : isFinalResult || turn === 0 ? 'Session completed' : `Turn ${turn} completed`, detail, terminalFailure ? 'error' : 'default', terminalFailure, meta);
212
+ }
213
+ function isPlatformMessage(message) {
214
+ const content = field(message, 'content');
215
+ if (typeof content === 'string') return content.startsWith(OUTPUT_REPROMPT_PREFIX);
216
+ if (!Array.isArray(content)) return false;
217
+ return content.some((block)=>{
218
+ const object = asLooseObject(block);
219
+ return stringField(object, 'text')?.startsWith(OUTPUT_REPROMPT_PREFIX) === true;
220
+ });
94
221
  }
95
222
  function contentBlocks(message) {
96
223
  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 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,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"}