@shipfox/api-logs 10.0.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.
- package/.turbo/turbo-build.log +4 -4
- package/CHANGELOG.md +10 -0
- package/dist/core/session/claude/rows.d.ts.map +1 -1
- package/dist/core/session/claude/rows.js +95 -3
- package/dist/core/session/claude/rows.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/core/session/claude/rows.ts +125 -6
- package/src/core/session/claude-parser.test.ts +167 -7
- package/tsconfig.build.tsbuildinfo +1 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shipfox/api-logs",
|
|
3
3
|
"license": "MIT",
|
|
4
|
-
"version": "10.
|
|
4
|
+
"version": "10.1.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/ShipfoxHQ/shipfox.git",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"@temporalio/workflow": "1.18.1",
|
|
26
26
|
"drizzle-orm": "^0.45.2",
|
|
27
27
|
"zod": "^4.4.3",
|
|
28
|
-
"@shipfox/api-auth-context": "10.
|
|
28
|
+
"@shipfox/api-auth-context": "10.1.0",
|
|
29
29
|
"@shipfox/api-logs-dto": "9.0.2",
|
|
30
30
|
"@shipfox/api-workflows-dto": "10.0.0",
|
|
31
31
|
"@shipfox/workflow-document": "2.1.3",
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
formatNumber,
|
|
14
14
|
isMeta,
|
|
15
15
|
metaItem,
|
|
16
|
+
numberField,
|
|
16
17
|
stringField,
|
|
17
18
|
stringifyValue,
|
|
18
19
|
toJson,
|
|
@@ -34,6 +35,101 @@ export const PURE_PROGRESS_CLAUDE_SYSTEM_SUBTYPES = new Set<string>([
|
|
|
34
35
|
]);
|
|
35
36
|
const OUTPUT_REPROMPT_PREFIX = 'The previous turn ended without setting required workflow outputs:';
|
|
36
37
|
|
|
38
|
+
type SystemEventMapping = {
|
|
39
|
+
label: string | ((message: Record<string, unknown>) => string);
|
|
40
|
+
tone:
|
|
41
|
+
| SessionViewLifecycleRow['tone']
|
|
42
|
+
| ((message: Record<string, unknown>) => SessionViewLifecycleRow['tone']);
|
|
43
|
+
detail?: (message: Record<string, unknown>) => string | null;
|
|
44
|
+
meta?: (message: Record<string, unknown>) => readonly SessionViewRowMeta[];
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const SYSTEM_EVENT_MAPPINGS: Record<string, SystemEventMapping> = {
|
|
48
|
+
permission_denied: {
|
|
49
|
+
label: 'Permission denied',
|
|
50
|
+
tone: 'error',
|
|
51
|
+
detail: (message) => stringField(message, 'tool_name') ?? null,
|
|
52
|
+
meta: (message) =>
|
|
53
|
+
[
|
|
54
|
+
metaItem(
|
|
55
|
+
'reason',
|
|
56
|
+
stringField(message, 'decision_reason') ?? stringField(message, 'message'),
|
|
57
|
+
),
|
|
58
|
+
].filter(isMeta),
|
|
59
|
+
},
|
|
60
|
+
api_retry: {
|
|
61
|
+
label: 'API retry',
|
|
62
|
+
tone: 'warning',
|
|
63
|
+
detail: (message) => {
|
|
64
|
+
const attempt = numberField(message, 'attempt');
|
|
65
|
+
const maxRetries = numberField(message, 'max_retries');
|
|
66
|
+
return attempt == null || maxRetries == null
|
|
67
|
+
? null
|
|
68
|
+
: `attempt ${formatNumber(attempt)}/${formatNumber(maxRetries)}`;
|
|
69
|
+
},
|
|
70
|
+
meta: (message) =>
|
|
71
|
+
[
|
|
72
|
+
numberMeta(message, 'error_status', 'status'),
|
|
73
|
+
numberMeta(message, 'retry_delay_ms', 'retry delay', 'ms'),
|
|
74
|
+
].filter(isMeta),
|
|
75
|
+
},
|
|
76
|
+
informational: {
|
|
77
|
+
label: (message) => titleCase(stringField(message, 'level') ?? 'informational'),
|
|
78
|
+
tone: (message) => (stringField(message, 'level') === 'warning' ? 'warning' : 'default'),
|
|
79
|
+
detail: (message) => stringField(message, 'content') ?? null,
|
|
80
|
+
},
|
|
81
|
+
compact_boundary: {
|
|
82
|
+
label: 'Context compacted',
|
|
83
|
+
tone: 'default',
|
|
84
|
+
meta: (message) => {
|
|
85
|
+
const compactMetadata = asLooseObject(field(message, 'compact_metadata')) ?? {};
|
|
86
|
+
return [
|
|
87
|
+
metaItem('trigger', stringField(compactMetadata, 'trigger')),
|
|
88
|
+
numberMeta(compactMetadata, 'pre_tokens', 'tokens before', 'tokens'),
|
|
89
|
+
numberMeta(compactMetadata, 'post_tokens', 'tokens after', 'tokens'),
|
|
90
|
+
numberMeta(compactMetadata, 'duration_ms', 'duration', 'ms'),
|
|
91
|
+
].filter(isMeta);
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
model_refusal_fallback: {
|
|
95
|
+
label: 'Model refused, fell back',
|
|
96
|
+
tone: 'warning',
|
|
97
|
+
},
|
|
98
|
+
model_refusal_no_fallback: {
|
|
99
|
+
label: 'Model refused',
|
|
100
|
+
tone: 'error',
|
|
101
|
+
},
|
|
102
|
+
mirror_error: {
|
|
103
|
+
label: 'Session mirror failed',
|
|
104
|
+
tone: 'error',
|
|
105
|
+
detail: (message) => stringField(message, 'error') ?? null,
|
|
106
|
+
},
|
|
107
|
+
worker_shutting_down: {
|
|
108
|
+
label: 'Worker shutting down',
|
|
109
|
+
tone: 'warning',
|
|
110
|
+
},
|
|
111
|
+
elicitation_complete: {
|
|
112
|
+
label: 'Elicitation complete',
|
|
113
|
+
tone: 'default',
|
|
114
|
+
},
|
|
115
|
+
notification: {
|
|
116
|
+
label: 'Notification',
|
|
117
|
+
tone: 'default',
|
|
118
|
+
},
|
|
119
|
+
task_started: {
|
|
120
|
+
label: 'Task started',
|
|
121
|
+
tone: 'default',
|
|
122
|
+
},
|
|
123
|
+
task_updated: {
|
|
124
|
+
label: 'Task updated',
|
|
125
|
+
tone: 'default',
|
|
126
|
+
},
|
|
127
|
+
task_notification: {
|
|
128
|
+
label: 'Task notification',
|
|
129
|
+
tone: 'default',
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
|
|
37
133
|
export function systemRow(
|
|
38
134
|
timestamp: number,
|
|
39
135
|
message: Record<string, unknown>,
|
|
@@ -51,8 +147,25 @@ export function systemRow(
|
|
|
51
147
|
),
|
|
52
148
|
].filter(isMeta);
|
|
53
149
|
|
|
54
|
-
if (!isInit)
|
|
55
|
-
|
|
150
|
+
if (!isInit) {
|
|
151
|
+
const mapping = subtype === undefined ? undefined : SYSTEM_EVENT_MAPPINGS[subtype];
|
|
152
|
+
const label =
|
|
153
|
+
mapping === undefined
|
|
154
|
+
? `Session event (${subtype ?? 'unknown'})`
|
|
155
|
+
: typeof mapping.label === 'function'
|
|
156
|
+
? mapping.label(message)
|
|
157
|
+
: mapping.label;
|
|
158
|
+
const tone =
|
|
159
|
+
mapping === undefined
|
|
160
|
+
? 'default'
|
|
161
|
+
: typeof mapping.tone === 'function'
|
|
162
|
+
? mapping.tone(message)
|
|
163
|
+
: mapping.tone;
|
|
164
|
+
const detail = mapping?.detail?.(message) ?? null;
|
|
165
|
+
const meta = [...baseMeta, ...(mapping?.meta?.(message) ?? [])];
|
|
166
|
+
|
|
167
|
+
return lifecycleRow(timestamp, label, detail, tone, false, meta);
|
|
168
|
+
}
|
|
56
169
|
|
|
57
170
|
const isNewSession =
|
|
58
171
|
!context.hasInit || sessionId === undefined || sessionId !== context.sessionId;
|
|
@@ -61,11 +174,17 @@ export function systemRow(
|
|
|
61
174
|
context.turn = isNewSession ? 1 : context.turn + 1;
|
|
62
175
|
|
|
63
176
|
const label = isNewSession ? 'Session started' : `Turn ${context.turn} started`;
|
|
64
|
-
const meta = [
|
|
65
|
-
|
|
66
|
-
|
|
177
|
+
const meta = [
|
|
178
|
+
metaItem('session', isNewSession ? sessionId : null),
|
|
179
|
+
metaItem('turn', isNewSession ? null : String(context.turn)),
|
|
180
|
+
...baseMeta,
|
|
181
|
+
].filter(isMeta);
|
|
182
|
+
|
|
183
|
+
return lifecycleRow(timestamp, label, null, 'default', false, meta);
|
|
184
|
+
}
|
|
67
185
|
|
|
68
|
-
|
|
186
|
+
function titleCase(value: string): string {
|
|
187
|
+
return value.length === 0 ? value : `${value[0]?.toUpperCase()}${value.slice(1)}`;
|
|
69
188
|
}
|
|
70
189
|
|
|
71
190
|
export function assistantRows(
|
|
@@ -5,6 +5,148 @@ const record = (data: unknown, ts = 1) => ({
|
|
|
5
5
|
ts,
|
|
6
6
|
data: typeof data === 'string' ? data : JSON.stringify(data),
|
|
7
7
|
});
|
|
8
|
+
const meta = (label: string, value: string, inline?: boolean) =>
|
|
9
|
+
inline == null ? {label, value} : {label, value, inline};
|
|
10
|
+
|
|
11
|
+
const namedSystemEvents = [
|
|
12
|
+
{
|
|
13
|
+
subtype: 'permission_denied',
|
|
14
|
+
data: {tool_name: 'mcp__shipfox_outputs__set_output', decision_reason: 'Permission mode'},
|
|
15
|
+
expected: {
|
|
16
|
+
label: 'Permission denied',
|
|
17
|
+
detail: 'mcp__shipfox_outputs__set_output',
|
|
18
|
+
meta: [meta('reason', 'Permission mode')],
|
|
19
|
+
tone: 'error',
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
subtype: 'api_retry',
|
|
24
|
+
data: {attempt: 2, max_retries: 3, error_status: 429, retry_delay_ms: 1_500},
|
|
25
|
+
expected: {
|
|
26
|
+
label: 'API retry',
|
|
27
|
+
detail: 'attempt 2/3',
|
|
28
|
+
meta: [meta('status', '429'), meta('retry delay', '1,500 ms')],
|
|
29
|
+
tone: 'warning',
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
subtype: 'informational',
|
|
34
|
+
data: {level: 'warning', content: 'The hook blocked continuation.'},
|
|
35
|
+
expected: {
|
|
36
|
+
label: 'Warning',
|
|
37
|
+
detail: 'The hook blocked continuation.',
|
|
38
|
+
meta: [],
|
|
39
|
+
tone: 'warning',
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
subtype: 'compact_boundary',
|
|
44
|
+
data: {
|
|
45
|
+
compact_metadata: {trigger: 'auto', pre_tokens: 12_000, post_tokens: 4_000, duration_ms: 250},
|
|
46
|
+
},
|
|
47
|
+
expected: {
|
|
48
|
+
label: 'Context compacted',
|
|
49
|
+
detail: null,
|
|
50
|
+
meta: [
|
|
51
|
+
meta('trigger', 'auto'),
|
|
52
|
+
meta('tokens before', '12K tokens'),
|
|
53
|
+
meta('tokens after', '4,000 tokens'),
|
|
54
|
+
meta('duration', '250 ms'),
|
|
55
|
+
],
|
|
56
|
+
tone: 'default',
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
subtype: 'model_refusal_fallback',
|
|
61
|
+
data: {},
|
|
62
|
+
expected: {
|
|
63
|
+
label: 'Model refused, fell back',
|
|
64
|
+
detail: null,
|
|
65
|
+
meta: [],
|
|
66
|
+
tone: 'warning',
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
subtype: 'model_refusal_no_fallback',
|
|
71
|
+
data: {},
|
|
72
|
+
expected: {
|
|
73
|
+
label: 'Model refused',
|
|
74
|
+
detail: null,
|
|
75
|
+
meta: [],
|
|
76
|
+
tone: 'error',
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
subtype: 'mirror_error',
|
|
81
|
+
data: {error: 'Session store unavailable'},
|
|
82
|
+
expected: {
|
|
83
|
+
label: 'Session mirror failed',
|
|
84
|
+
detail: 'Session store unavailable',
|
|
85
|
+
meta: [],
|
|
86
|
+
tone: 'error',
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
subtype: 'worker_shutting_down',
|
|
91
|
+
data: {},
|
|
92
|
+
expected: {
|
|
93
|
+
label: 'Worker shutting down',
|
|
94
|
+
detail: null,
|
|
95
|
+
meta: [],
|
|
96
|
+
tone: 'warning',
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
subtype: 'elicitation_complete',
|
|
101
|
+
data: {},
|
|
102
|
+
expected: {
|
|
103
|
+
label: 'Elicitation complete',
|
|
104
|
+
detail: null,
|
|
105
|
+
meta: [],
|
|
106
|
+
tone: 'default',
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
subtype: 'notification',
|
|
111
|
+
data: {},
|
|
112
|
+
expected: {
|
|
113
|
+
label: 'Notification',
|
|
114
|
+
detail: null,
|
|
115
|
+
meta: [],
|
|
116
|
+
tone: 'default',
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
subtype: 'task_started',
|
|
121
|
+
data: {},
|
|
122
|
+
expected: {
|
|
123
|
+
label: 'Task started',
|
|
124
|
+
detail: null,
|
|
125
|
+
meta: [],
|
|
126
|
+
tone: 'default',
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
subtype: 'task_updated',
|
|
131
|
+
data: {},
|
|
132
|
+
expected: {
|
|
133
|
+
label: 'Task updated',
|
|
134
|
+
detail: null,
|
|
135
|
+
meta: [],
|
|
136
|
+
tone: 'default',
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
subtype: 'task_notification',
|
|
141
|
+
data: {},
|
|
142
|
+
expected: {
|
|
143
|
+
label: 'Task notification',
|
|
144
|
+
detail: null,
|
|
145
|
+
meta: [],
|
|
146
|
+
tone: 'default',
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
] as const;
|
|
8
150
|
|
|
9
151
|
describe('parseClaudeSessionRecord', () => {
|
|
10
152
|
it('returns a raw row for malformed JSON', () => {
|
|
@@ -44,8 +186,9 @@ describe('parseClaudeSessionRecord', () => {
|
|
|
44
186
|
kind: 'lifecycle',
|
|
45
187
|
timestamp: 1,
|
|
46
188
|
label: 'Session started',
|
|
47
|
-
detail:
|
|
189
|
+
detail: null,
|
|
48
190
|
meta: [
|
|
191
|
+
{label: 'session', value: 'session-1'},
|
|
49
192
|
{label: 'cwd', value: '/workspace', inline: false},
|
|
50
193
|
{label: 'model', value: 'claude-opus-4-8'},
|
|
51
194
|
],
|
|
@@ -63,6 +206,23 @@ describe('parseClaudeSessionRecord', () => {
|
|
|
63
206
|
expect(rows).toEqual([]);
|
|
64
207
|
});
|
|
65
208
|
|
|
209
|
+
it.each(namedSystemEvents)('names and tones $subtype system events', ({
|
|
210
|
+
subtype,
|
|
211
|
+
data,
|
|
212
|
+
expected,
|
|
213
|
+
}) => {
|
|
214
|
+
const rows = parseClaudeSessionRecord(record({type: 'system', subtype, ...data}));
|
|
215
|
+
|
|
216
|
+
expect(rows).toEqual([
|
|
217
|
+
{
|
|
218
|
+
kind: 'lifecycle',
|
|
219
|
+
timestamp: 1,
|
|
220
|
+
...expected,
|
|
221
|
+
terminalFailure: false,
|
|
222
|
+
},
|
|
223
|
+
]);
|
|
224
|
+
});
|
|
225
|
+
|
|
66
226
|
it('reports repeated init and result records as turns within one session', () => {
|
|
67
227
|
const context = createClaudeParseContext();
|
|
68
228
|
const rows = [
|
|
@@ -90,8 +250,8 @@ describe('parseClaudeSessionRecord', () => {
|
|
|
90
250
|
kind: 'lifecycle',
|
|
91
251
|
timestamp: 1,
|
|
92
252
|
label: 'Session started',
|
|
93
|
-
detail:
|
|
94
|
-
meta: [],
|
|
253
|
+
detail: null,
|
|
254
|
+
meta: [meta('session', 'session-1')],
|
|
95
255
|
tone: 'default',
|
|
96
256
|
terminalFailure: false,
|
|
97
257
|
},
|
|
@@ -108,7 +268,7 @@ describe('parseClaudeSessionRecord', () => {
|
|
|
108
268
|
kind: 'lifecycle',
|
|
109
269
|
timestamp: 1,
|
|
110
270
|
label: 'Turn 2 started',
|
|
111
|
-
detail:
|
|
271
|
+
detail: null,
|
|
112
272
|
meta: [{label: 'turn', value: '2'}],
|
|
113
273
|
tone: 'default',
|
|
114
274
|
terminalFailure: false,
|
|
@@ -125,7 +285,7 @@ describe('parseClaudeSessionRecord', () => {
|
|
|
125
285
|
]);
|
|
126
286
|
});
|
|
127
287
|
|
|
128
|
-
it('
|
|
288
|
+
it('qualifies unmapped system events instead of using a bare generic label', () => {
|
|
129
289
|
const rows = parseClaudeSessionRecord(
|
|
130
290
|
record({type: 'system', subtype: 'custom_event', session_id: 'session-1'}),
|
|
131
291
|
);
|
|
@@ -134,8 +294,8 @@ describe('parseClaudeSessionRecord', () => {
|
|
|
134
294
|
{
|
|
135
295
|
kind: 'lifecycle',
|
|
136
296
|
timestamp: 1,
|
|
137
|
-
label: 'Session event',
|
|
138
|
-
detail:
|
|
297
|
+
label: 'Session event (custom_event)',
|
|
298
|
+
detail: null,
|
|
139
299
|
meta: [],
|
|
140
300
|
tone: 'default',
|
|
141
301
|
terminalFailure: false,
|