@shipfox/api-logs 9.3.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 +25 -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 +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 +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 +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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"}
@@ -1,3 +1,4 @@
1
+ import type { SessionViewLifecycleRow } from '@shipfox/api-logs-dto';
1
2
  import type { AttemptStream, StreamCloseReason } from '#core/entities/attempt-stream.js';
2
3
  import { type Transaction } from './db.js';
3
4
  export interface AttemptStreamIdentity {
@@ -62,18 +63,28 @@ export declare function setDeclaredTotalBytes(tx: Transaction, params: {
62
63
  streamId: string;
63
64
  declaredTotalBytes: number;
64
65
  }): Promise<void>;
66
+ export declare function setClaudeParseContext(tx: Transaction, params: {
67
+ streamId: string;
68
+ hasInit: boolean;
69
+ sessionId: string | null;
70
+ turn: number;
71
+ pendingResult: SessionViewLifecycleRow | null;
72
+ }): Promise<void>;
65
73
  /**
66
- * Guarded close: flips `open → closed` and stamps `closed_at` iff the stream is
67
- * still open, returning the closed row (or null when another path already closed
68
- * it). The `WHERE state='open'` predicate is both the row lock and the
69
- * exactly-once gate. `committed_length` is left untouched; it stays equal to the
70
- * runner spool bytes. `truncated` is set only on the timeout path.
74
+ * Guarded close: locks the open row, flips `open → closed`, clears any pending Claude result,
75
+ * and stamps `closed_at`. The pending result is returned separately so the caller can
76
+ * materialize it before publishing the close event. Returns null when another path already
77
+ * closed the stream. `committed_length` is left untouched; it stays equal to the runner spool
78
+ * bytes. `truncated` is set only on the timeout path.
71
79
  */
72
80
  export declare function markStreamClosed(tx: Transaction, params: {
73
81
  streamId: string;
74
82
  reason: StreamCloseReason;
75
83
  markTruncated: boolean;
76
- }): Promise<AttemptStream | null>;
84
+ }): Promise<{
85
+ stream: AttemptStream;
86
+ pendingClaudeResult: SessionViewLifecycleRow | null;
87
+ } | null>;
77
88
  /** Open streams of a job, for the timeout sweep to force-close after the grace period. */
78
89
  export declare function listOpenStreamsByJob(jobId: string): Promise<AttemptStream[]>;
79
90
  /** For compaction, which is driven by the closed event that carries the stream id. */
@@ -1 +1 @@
1
- {"version":3,"file":"streams.d.ts","sourceRoot":"","sources":["../../src/db/streams.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"streams.d.ts","sourceRoot":"","sources":["../../src/db/streams.ts"],"names":[],"mappings":"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"}
@@ -87,23 +87,38 @@ export async function setDeclaredTotalBytes(tx, params) {
87
87
  updatedAt: sql`now()`
88
88
  }).where(eq(attemptStreams.id, params.streamId));
89
89
  }
90
+ export async function setClaudeParseContext(tx, params) {
91
+ await tx.update(attemptStreams).set({
92
+ claudeHasInit: params.hasInit,
93
+ claudeSessionId: params.sessionId,
94
+ claudeTurn: params.turn,
95
+ claudePendingResult: params.pendingResult,
96
+ updatedAt: sql`now()`
97
+ }).where(eq(attemptStreams.id, params.streamId));
98
+ }
90
99
  /**
91
- * Guarded close: flips `open → closed` and stamps `closed_at` iff the stream is
92
- * still open, returning the closed row (or null when another path already closed
93
- * it). The `WHERE state='open'` predicate is both the row lock and the
94
- * exactly-once gate. `committed_length` is left untouched; it stays equal to the
95
- * runner spool bytes. `truncated` is set only on the timeout path.
100
+ * Guarded close: locks the open row, flips `open → closed`, clears any pending Claude result,
101
+ * and stamps `closed_at`. The pending result is returned separately so the caller can
102
+ * materialize it before publishing the close event. Returns null when another path already
103
+ * closed the stream. `committed_length` is left untouched; it stays equal to the runner spool
104
+ * bytes. `truncated` is set only on the timeout path.
96
105
  */ export async function markStreamClosed(tx, params) {
106
+ const [open] = await tx.select().from(attemptStreams).where(and(eq(attemptStreams.id, params.streamId), eq(attemptStreams.state, 'open'))).for('update');
107
+ if (!open) return null;
97
108
  const [row] = await tx.update(attemptStreams).set({
98
109
  state: 'closed',
99
110
  closeReason: params.reason,
100
111
  closedAt: sql`now()`,
101
112
  updatedAt: sql`now()`,
113
+ claudePendingResult: null,
102
114
  ...params.markTruncated ? {
103
115
  truncated: true
104
116
  } : {}
105
117
  }).where(and(eq(attemptStreams.id, params.streamId), eq(attemptStreams.state, 'open'))).returning();
106
- return row ? toAttemptStream(row) : null;
118
+ return row ? {
119
+ stream: toAttemptStream(row),
120
+ pendingClaudeResult: open.claudePendingResult ?? null
121
+ } : null;
107
122
  }
108
123
  /** Open streams of a job, for the timeout sweep to force-close after the grace period. */ export async function listOpenStreamsByJob(jobId) {
109
124
  const rows = await db().select().from(attemptStreams).where(and(eq(attemptStreams.jobId, jobId), eq(attemptStreams.state, 'open')));