bugpatrol 0.1.4 → 0.1.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bugpatrol",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "AI agents that explore your app, find bugs, and fix them.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
package/src/ui/app.js CHANGED
@@ -20,6 +20,7 @@ const state = {
20
20
  sessions: [],
21
21
  selectedSessionId: null,
22
22
  sessionDetail: null,
23
+ flow: null,
23
24
  appmap: null,
24
25
  selectedScreenId: null,
25
26
  routines: [],
@@ -103,6 +104,15 @@ async function refresh({ keepSelection = true } = {}) {
103
104
  state.sessionDetail = null;
104
105
  }
105
106
  }
107
+ if (state.view === 'flow') {
108
+ state.sessions = await getJson('/api/sessions');
109
+ if (!state.sessions.some((session) => session.id === state.selectedSessionId)) {
110
+ state.selectedSessionId = state.sessions[0]?.id ?? null;
111
+ }
112
+ state.flow = state.selectedSessionId
113
+ ? await getJson(`/api/flow/${encodeURIComponent(state.selectedSessionId)}`).catch(() => null)
114
+ : null;
115
+ }
106
116
  if (state.view === 'screens') {
107
117
  state.appmap = await getJson('/api/appmap');
108
118
  state.issues = await getJson('/api/issues');
@@ -143,6 +153,7 @@ function render() {
143
153
  overview: renderOverview,
144
154
  issues: renderIssues,
145
155
  activity: renderActivity,
156
+ flow: renderFlow,
146
157
  screens: renderScreens,
147
158
  memory: renderMemory,
148
159
  checks: renderRuns,
@@ -1159,6 +1170,78 @@ function renderEventRow(event) {
1159
1170
  ]);
1160
1171
  }
1161
1172
 
1173
+ // The flow view is what a human reads when triaging: the agent's actions, the
1174
+ // failed requests the app reported, and the backend logs that followed, merged
1175
+ // in time order. A log close behind a failure is marked correlated.
1176
+ function renderFlow() {
1177
+ const list = el('section', { class: 'card panel' }, [
1178
+ title('Flow'),
1179
+ ...state.sessions.map((session) =>
1180
+ el(
1181
+ 'button',
1182
+ {
1183
+ class: `list-row ${session.id === state.selectedSessionId ? 'active' : ''}`,
1184
+ onclick: async () => {
1185
+ state.selectedSessionId = session.id;
1186
+ await refresh();
1187
+ },
1188
+ },
1189
+ [
1190
+ roleIcon(session.role),
1191
+ el('span', { class: 'grow' }, [
1192
+ el('strong', { text: firstSentence(session.summary) || `${capital(session.role)} session` }),
1193
+ el('small', {
1194
+ class: 'muted',
1195
+ text: `${capital(session.role)} · ${relativeTime(session.startedAt)} · ${duration(session)}`,
1196
+ }),
1197
+ ]),
1198
+ ],
1199
+ ),
1200
+ ),
1201
+ ]);
1202
+ const flow = state.flow;
1203
+ if (!flow) {
1204
+ return el('div', { class: 'master-detail' }, [
1205
+ list,
1206
+ el('section', {
1207
+ class: 'card panel empty',
1208
+ text: 'No flow for this session. Add a `logs:` source to bugpatrol.yml and run a patrol.',
1209
+ }),
1210
+ ]);
1211
+ }
1212
+ const sources = flow.sources.length
1213
+ ? flow.sources.map((source) =>
1214
+ el('div', {
1215
+ text: `${source.name} (${source.kind}): ${source.collected} line${source.collected === 1 ? '' : 's'}`,
1216
+ }),
1217
+ )
1218
+ : [
1219
+ el('div', {
1220
+ text: 'No log sources configured. Add `logs:` to bugpatrol.yml to see backend logs beside each action.',
1221
+ }),
1222
+ ];
1223
+ return el('div', { class: 'master-detail' }, [
1224
+ list,
1225
+ el('section', { class: 'card panel' }, [
1226
+ title('Flow', `${capital(flow.role)} · ${relativeTime(flow.startedAt)}`),
1227
+ el('div', { class: 'muted session-usage' }, sources),
1228
+ flow.entries.length
1229
+ ? el('div', { class: 'event-list' }, flow.entries.map(renderFlowRow))
1230
+ : el('div', { class: 'muted', text: 'This session recorded nothing.' }),
1231
+ ]),
1232
+ ]);
1233
+ }
1234
+
1235
+ function renderFlowRow(entry) {
1236
+ return el('div', { class: `event-row flow-row flow-${entry.kind}${entry.correlated ? ' correlated' : ''}` }, [
1237
+ el('time', { text: new Date(entry.at).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) }),
1238
+ el('span', { class: 'flow-kind', text: entry.kind }),
1239
+ el('span', { class: 'grow truncate', text: entry.summary }),
1240
+ entry.level ? el('small', { class: 'muted', text: entry.level }) : null,
1241
+ entry.screenshot ? image(entry.screenshot, entry.summary, 'event-thumb') : null,
1242
+ ]);
1243
+ }
1244
+
1162
1245
  async function openScreen(id) {
1163
1246
  state.view = 'screens';
1164
1247
  state.selectedScreenId = id;
package/src/ui/index.html CHANGED
@@ -16,6 +16,7 @@
16
16
  <button type="button" data-view="overview" class="active">Overview</button>
17
17
  <button type="button" data-view="issues">Issues</button>
18
18
  <button type="button" data-view="activity">Activity</button>
19
+ <button type="button" data-view="flow">Flow</button>
19
20
  <button type="button" data-view="screens">Screens</button>
20
21
  <button type="button" data-view="memory">Memory</button>
21
22
  <button type="button" data-view="checks">Checks</button>
package/src/ui/style.css CHANGED
@@ -749,6 +749,29 @@ h1 {
749
749
  font-size: 0.75rem;
750
750
  white-space: nowrap;
751
751
  }
752
+ .flow-row {
753
+ gap: 0.6rem;
754
+ }
755
+ .flow-row.correlated {
756
+ background: color-mix(in srgb, #b93038 12%, transparent);
757
+ }
758
+ .flow-kind {
759
+ flex: none;
760
+ width: 4.2rem;
761
+ font-size: 0.72rem;
762
+ text-transform: uppercase;
763
+ letter-spacing: 0.04em;
764
+ color: var(--muted);
765
+ }
766
+ .flow-action .flow-kind {
767
+ color: #3b6ea5;
768
+ }
769
+ .flow-request .flow-kind {
770
+ color: #ae551c;
771
+ }
772
+ .flow-log .flow-kind {
773
+ color: #4f7a4f;
774
+ }
752
775
  .event-row details {
753
776
  flex: none;
754
777
  font-size: 0.75rem;