opencastle 0.33.0 → 0.33.1

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": "opencastle",
3
- "version": "0.33.0",
3
+ "version": "0.33.1",
4
4
  "type": "module",
5
5
  "description": "Multi-agent orchestration framework for AI coding assistants",
6
6
  "bin": {
@@ -51,21 +51,21 @@
51
51
  "name": "docs/api-v2-contract.json",
52
52
  "type": "json",
53
53
  "task_id": "api-t1",
54
- "created_at": "2026-04-04T06:06:04.354Z"
54
+ "created_at": "2026-04-05T06:40:02.269Z"
55
55
  },
56
56
  {
57
57
  "id": "artifact-demo-api-v2-reports-security-gate-failure-md",
58
58
  "name": "reports/security-gate-failure.md",
59
59
  "type": "summary",
60
60
  "task_id": "api-t3",
61
- "created_at": "2026-04-04T06:06:04.354Z"
61
+ "created_at": "2026-04-05T06:40:02.269Z"
62
62
  },
63
63
  {
64
64
  "id": "artifact-demo-api-v2-src-api-rate-limiter-ts",
65
65
  "name": "src/api/rate-limiter.ts",
66
66
  "type": "file",
67
67
  "task_id": "api-t2",
68
- "created_at": "2026-04-04T06:06:04.354Z"
68
+ "created_at": "2026-04-05T06:40:02.269Z"
69
69
  }
70
70
  ],
71
71
  "has_more_events": false,
@@ -42,28 +42,28 @@
42
42
  "name": "libs/auth/src/jwt-middleware.ts",
43
43
  "type": "file",
44
44
  "task_id": "auth-t2",
45
- "created_at": "2026-04-04T06:06:04.353Z"
45
+ "created_at": "2026-04-05T06:40:02.268Z"
46
46
  },
47
47
  {
48
48
  "id": "artifact-demo-auth-revamp-libs-auth-src-rls-policies-sql",
49
49
  "name": "libs/auth/src/rls-policies.sql",
50
50
  "type": "file",
51
51
  "task_id": "auth-t3",
52
- "created_at": "2026-04-04T06:06:04.353Z"
52
+ "created_at": "2026-04-05T06:40:02.268Z"
53
53
  },
54
54
  {
55
55
  "id": "artifact-demo-auth-revamp-reports-auth-review-summary-md",
56
56
  "name": "reports/auth-review-summary.md",
57
57
  "type": "summary",
58
58
  "task_id": "auth-t5",
59
- "created_at": "2026-04-04T06:06:04.353Z"
59
+ "created_at": "2026-04-05T06:40:02.268Z"
60
60
  },
61
61
  {
62
62
  "id": "artifact-demo-auth-revamp-tests-auth-integration-test-ts",
63
63
  "name": "tests/auth/integration.test.ts",
64
64
  "type": "file",
65
65
  "task_id": "auth-t4",
66
- "created_at": "2026-04-04T06:06:04.353Z"
66
+ "created_at": "2026-04-05T06:40:02.268Z"
67
67
  }
68
68
  ],
69
69
  "has_more_events": false,
@@ -46,47 +46,47 @@
46
46
  ],
47
47
  "artifact_count": 6,
48
48
  "artifacts": [
49
+ {
50
+ "id": "artifact-demo-dashboard-ui-reports-panel-review-dashboard-md",
51
+ "name": "reports/panel-review-dashboard.md",
52
+ "type": "summary",
53
+ "task_id": "ui-t7",
54
+ "created_at": "2026-04-05T06:40:02.269Z"
55
+ },
49
56
  {
50
57
  "id": "artifact-demo-dashboard-ui-reports-visual-regression-json",
51
58
  "name": "reports/visual-regression.json",
52
59
  "type": "json",
53
60
  "task_id": "ui-t6",
54
- "created_at": "2026-04-04T06:06:04.353Z"
61
+ "created_at": "2026-04-05T06:40:02.269Z"
55
62
  },
56
63
  {
57
64
  "id": "artifact-demo-dashboard-ui-src-components-DonutChart-tsx",
58
65
  "name": "src/components/DonutChart.tsx",
59
66
  "type": "file",
60
67
  "task_id": "ui-t3",
61
- "created_at": "2026-04-04T06:06:04.353Z"
68
+ "created_at": "2026-04-05T06:40:02.269Z"
62
69
  },
63
70
  {
64
71
  "id": "artifact-demo-dashboard-ui-src-components-KpiCard-tsx",
65
72
  "name": "src/components/KpiCard.tsx",
66
73
  "type": "file",
67
74
  "task_id": "ui-t2",
68
- "created_at": "2026-04-04T06:06:04.353Z"
75
+ "created_at": "2026-04-05T06:40:02.269Z"
69
76
  },
70
77
  {
71
78
  "id": "artifact-demo-dashboard-ui-src-components-design-tokens-ts",
72
79
  "name": "src/components/design-tokens.ts",
73
80
  "type": "file",
74
81
  "task_id": "ui-t1",
75
- "created_at": "2026-04-04T06:06:04.353Z"
82
+ "created_at": "2026-04-05T06:40:02.269Z"
76
83
  },
77
84
  {
78
85
  "id": "artifact-demo-dashboard-ui-src-styles-animations-css",
79
86
  "name": "src/styles/animations.css",
80
87
  "type": "file",
81
88
  "task_id": "ui-t4",
82
- "created_at": "2026-04-04T06:06:04.353Z"
83
- },
84
- {
85
- "id": "artifact-demo-dashboard-ui-reports-panel-review-dashboard-md",
86
- "name": "reports/panel-review-dashboard.md",
87
- "type": "summary",
88
- "task_id": "ui-t7",
89
- "created_at": "2026-04-04T06:06:04.354Z"
89
+ "created_at": "2026-04-05T06:40:02.269Z"
90
90
  }
91
91
  ],
92
92
  "has_more_events": false,
@@ -42,21 +42,21 @@
42
42
  "name": "src/etl/pipeline.ts",
43
43
  "type": "file",
44
44
  "task_id": "etl-t2",
45
- "created_at": "2026-04-04T06:06:04.354Z"
45
+ "created_at": "2026-04-05T06:40:02.270Z"
46
46
  },
47
47
  {
48
48
  "id": "artifact-demo-data-pipeline-src-etl-schema-ts",
49
49
  "name": "src/etl/schema.ts",
50
50
  "type": "file",
51
51
  "task_id": "etl-t1",
52
- "created_at": "2026-04-04T06:06:04.354Z"
52
+ "created_at": "2026-04-05T06:40:02.270Z"
53
53
  },
54
54
  {
55
55
  "id": "artifact-demo-data-pipeline-tests-etl-pipeline-test-ts",
56
56
  "name": "tests/etl/pipeline.test.ts",
57
57
  "type": "file",
58
58
  "task_id": "etl-t3",
59
- "created_at": "2026-04-04T06:06:04.354Z"
59
+ "created_at": "2026-04-05T06:40:02.270Z"
60
60
  }
61
61
  ],
62
62
  "has_more_events": false,
@@ -51,7 +51,7 @@
51
51
  "name": ".github/workflows/ci.yml",
52
52
  "type": "file",
53
53
  "task_id": "ci-t1",
54
- "created_at": "2026-04-04T06:06:04.355Z"
54
+ "created_at": "2026-04-05T06:40:02.270Z"
55
55
  }
56
56
  ],
57
57
  "has_more_events": false,
@@ -42,21 +42,21 @@
42
42
  "name": "docs/ARCHITECTURE.md",
43
43
  "type": "file",
44
44
  "task_id": "docs-t1",
45
- "created_at": "2026-04-04T06:06:04.355Z"
45
+ "created_at": "2026-04-05T06:40:02.270Z"
46
46
  },
47
47
  {
48
48
  "id": "artifact-demo-docs-update-docs-README-md",
49
49
  "name": "docs/README.md",
50
50
  "type": "file",
51
51
  "task_id": "docs-t1",
52
- "created_at": "2026-04-04T06:06:04.355Z"
52
+ "created_at": "2026-04-05T06:40:02.270Z"
53
53
  },
54
54
  {
55
55
  "id": "artifact-demo-docs-update-docs-api-reference-json",
56
56
  "name": "docs/api-reference.json",
57
57
  "type": "json",
58
58
  "task_id": "docs-t2",
59
- "created_at": "2026-04-04T06:06:04.355Z"
59
+ "created_at": "2026-04-05T06:40:02.270Z"
60
60
  }
61
61
  ],
62
62
  "has_more_events": false,
@@ -42,28 +42,28 @@
42
42
  "name": "reports/bundle-analysis.json",
43
43
  "type": "json",
44
44
  "task_id": "perf-t1",
45
- "created_at": "2026-04-04T06:06:04.354Z"
46
- },
47
- {
48
- "id": "artifact-demo-perf-opt-reports-web-vitals-improvement-md",
49
- "name": "reports/web-vitals-improvement.md",
50
- "type": "summary",
51
- "task_id": "perf-t4",
52
- "created_at": "2026-04-04T06:06:04.354Z"
45
+ "created_at": "2026-04-05T06:40:02.269Z"
53
46
  },
54
47
  {
55
48
  "id": "artifact-demo-perf-opt-src-charts-index-ts",
56
49
  "name": "src/charts/index.ts",
57
50
  "type": "file",
58
51
  "task_id": "perf-t2",
59
- "created_at": "2026-04-04T06:06:04.354Z"
52
+ "created_at": "2026-04-05T06:40:02.269Z"
53
+ },
54
+ {
55
+ "id": "artifact-demo-perf-opt-reports-web-vitals-improvement-md",
56
+ "name": "reports/web-vitals-improvement.md",
57
+ "type": "summary",
58
+ "task_id": "perf-t4",
59
+ "created_at": "2026-04-05T06:40:02.270Z"
60
60
  },
61
61
  {
62
62
  "id": "artifact-demo-perf-opt-src-utils-image-loader-ts",
63
63
  "name": "src/utils/image-loader.ts",
64
64
  "type": "file",
65
65
  "task_id": "perf-t3",
66
- "created_at": "2026-04-04T06:06:04.354Z"
66
+ "created_at": "2026-04-05T06:40:02.270Z"
67
67
  }
68
68
  ],
69
69
  "has_more_events": false,
@@ -97,6 +97,7 @@ Export
97
97
  // ── Active Convoy Polling ──────────────────────────────────
98
98
  let _activePollingTimer = null;
99
99
  let _activePollingConvoyId = null;
100
+ let _lastPolledConvoyStatus = null;
100
101
 
101
102
  function startActiveConvoyPolling() {
102
103
  if (_activePollingTimer) return; // already polling
@@ -126,6 +127,7 @@ Export
126
127
  // If we haven't navigated to any convoy yet, navigate now
127
128
  if (!_activePollingConvoyId) {
128
129
  _activePollingConvoyId = convoy.id;
130
+ _lastPolledConvoyStatus = null;
129
131
  // Refresh ETL data so convoy detail JSON exists
130
132
  await fetch(base + 'data/refresh');
131
133
  // Re-fetch the convoy list and stats
@@ -137,36 +139,32 @@ Export
137
139
  // If we're on a convoy and a new one started (pipeline chain), follow it
138
140
  if (_activePollingConvoyId !== convoy.id && isRunningOrPending) {
139
141
  _activePollingConvoyId = convoy.id;
142
+ _lastPolledConvoyStatus = null;
140
143
  await fetch(base + 'data/refresh');
141
144
  await refreshDashboardData();
142
145
  showConvoyDetailView(convoy.id, convoy.name || convoy.id);
143
146
  }
144
147
 
145
- // If we're viewing the active convoy, refresh its detail data
148
+ // If we're viewing the active convoy, refresh when it's running or just transitioned
146
149
  if (_activePollingConvoyId === convoy.id) {
147
- await fetch(base + 'data/refresh');
148
- await refreshDashboardData();
149
- // Reload convoy detail to show latest task progress
150
- await loadConvoyDetail(convoy.id);
151
-
152
- // Update pipeline chain breadcrumb if applicable
153
- if (data.pipeline && data.pipeline.length > 1) {
154
- renderPipelineChain(data.pipeline, convoy.id);
155
- }
156
- }
157
-
158
- // Stop polling when the convoy (and any pipeline) is finished
159
- if (!isRunningOrPending) {
160
- // In pipeline mode, check if all convoys in the chain are done
161
- if (data.pipeline) {
162
- const anyActive = data.pipeline.some(c => c.status === 'running' || c.status === 'pending');
163
- if (!anyActive) {
164
- stopActiveConvoyPolling();
150
+ const statusChanged = _lastPolledConvoyStatus !== null && _lastPolledConvoyStatus !== convoy.status;
151
+ _lastPolledConvoyStatus = convoy.status;
152
+
153
+ if (isRunningOrPending || statusChanged) {
154
+ await fetch(base + 'data/refresh');
155
+ await refreshDashboardData();
156
+ // Reload convoy detail to show latest task progress
157
+ await loadConvoyDetail(convoy.id);
158
+
159
+ // Update pipeline chain breadcrumb if applicable
160
+ if (data.pipeline && data.pipeline.length > 1) {
161
+ renderPipelineChain(data.pipeline, convoy.id);
165
162
  }
166
- } else {
167
- stopActiveConvoyPolling();
168
163
  }
169
164
  }
165
+
166
+ // NOTE: In 'active' mode we intentionally keep polling even after a convoy finishes,
167
+ // so that subsequent convoys are detected when they start.
170
168
  } catch {
171
169
  // Polling errors are non-fatal — just retry next interval
172
170
  }
@@ -1817,11 +1815,9 @@ Export
1817
1815
  const target = running || latest;
1818
1816
  if (target) {
1819
1817
  _activePollingConvoyId = target.id;
1818
+ _lastPolledConvoyStatus = null;
1820
1819
  showConvoyDetailView(target.id, target.name || target.id);
1821
- // If still running, start polling for live updates
1822
- if (target.status === 'running' || target.status === 'pending') {
1823
- startActiveConvoyPolling();
1824
- }
1820
+ startActiveConvoyPolling();
1825
1821
  } else {
1826
1822
  // No convoy exists yet — show waiting state and poll
1827
1823
  showWaitingBanner();
@@ -1,25 +1,25 @@
1
1
  {
2
- "hash": "61d3f837",
2
+ "hash": "d345d9ae",
3
3
  "configHash": "30f8ea04",
4
- "lockfileHash": "4e96edd0",
5
- "browserHash": "8dc3766c",
4
+ "lockfileHash": "ea94e1c1",
5
+ "browserHash": "c13f12ac",
6
6
  "optimized": {
7
7
  "astro > cssesc": {
8
8
  "src": "../../../../../node_modules/cssesc/cssesc.js",
9
9
  "file": "astro___cssesc.js",
10
- "fileHash": "55e707a4",
10
+ "fileHash": "1d552a66",
11
11
  "needsInterop": true
12
12
  },
13
13
  "astro > aria-query": {
14
14
  "src": "../../../../../node_modules/aria-query/lib/index.js",
15
15
  "file": "astro___aria-query.js",
16
- "fileHash": "45b77171",
16
+ "fileHash": "6c55b8e9",
17
17
  "needsInterop": true
18
18
  },
19
19
  "astro > axobject-query": {
20
20
  "src": "../../../../../node_modules/axobject-query/lib/index.js",
21
21
  "file": "astro___axobject-query.js",
22
- "fileHash": "1be87e34",
22
+ "fileHash": "a912fe24",
23
23
  "needsInterop": true
24
24
  }
25
25
  },
@@ -51,21 +51,21 @@
51
51
  "name": "docs/api-v2-contract.json",
52
52
  "type": "json",
53
53
  "task_id": "api-t1",
54
- "created_at": "2026-04-04T06:06:04.354Z"
54
+ "created_at": "2026-04-05T06:40:02.269Z"
55
55
  },
56
56
  {
57
57
  "id": "artifact-demo-api-v2-reports-security-gate-failure-md",
58
58
  "name": "reports/security-gate-failure.md",
59
59
  "type": "summary",
60
60
  "task_id": "api-t3",
61
- "created_at": "2026-04-04T06:06:04.354Z"
61
+ "created_at": "2026-04-05T06:40:02.269Z"
62
62
  },
63
63
  {
64
64
  "id": "artifact-demo-api-v2-src-api-rate-limiter-ts",
65
65
  "name": "src/api/rate-limiter.ts",
66
66
  "type": "file",
67
67
  "task_id": "api-t2",
68
- "created_at": "2026-04-04T06:06:04.354Z"
68
+ "created_at": "2026-04-05T06:40:02.269Z"
69
69
  }
70
70
  ],
71
71
  "has_more_events": false,
@@ -42,28 +42,28 @@
42
42
  "name": "libs/auth/src/jwt-middleware.ts",
43
43
  "type": "file",
44
44
  "task_id": "auth-t2",
45
- "created_at": "2026-04-04T06:06:04.353Z"
45
+ "created_at": "2026-04-05T06:40:02.268Z"
46
46
  },
47
47
  {
48
48
  "id": "artifact-demo-auth-revamp-libs-auth-src-rls-policies-sql",
49
49
  "name": "libs/auth/src/rls-policies.sql",
50
50
  "type": "file",
51
51
  "task_id": "auth-t3",
52
- "created_at": "2026-04-04T06:06:04.353Z"
52
+ "created_at": "2026-04-05T06:40:02.268Z"
53
53
  },
54
54
  {
55
55
  "id": "artifact-demo-auth-revamp-reports-auth-review-summary-md",
56
56
  "name": "reports/auth-review-summary.md",
57
57
  "type": "summary",
58
58
  "task_id": "auth-t5",
59
- "created_at": "2026-04-04T06:06:04.353Z"
59
+ "created_at": "2026-04-05T06:40:02.268Z"
60
60
  },
61
61
  {
62
62
  "id": "artifact-demo-auth-revamp-tests-auth-integration-test-ts",
63
63
  "name": "tests/auth/integration.test.ts",
64
64
  "type": "file",
65
65
  "task_id": "auth-t4",
66
- "created_at": "2026-04-04T06:06:04.353Z"
66
+ "created_at": "2026-04-05T06:40:02.268Z"
67
67
  }
68
68
  ],
69
69
  "has_more_events": false,
@@ -46,47 +46,47 @@
46
46
  ],
47
47
  "artifact_count": 6,
48
48
  "artifacts": [
49
+ {
50
+ "id": "artifact-demo-dashboard-ui-reports-panel-review-dashboard-md",
51
+ "name": "reports/panel-review-dashboard.md",
52
+ "type": "summary",
53
+ "task_id": "ui-t7",
54
+ "created_at": "2026-04-05T06:40:02.269Z"
55
+ },
49
56
  {
50
57
  "id": "artifact-demo-dashboard-ui-reports-visual-regression-json",
51
58
  "name": "reports/visual-regression.json",
52
59
  "type": "json",
53
60
  "task_id": "ui-t6",
54
- "created_at": "2026-04-04T06:06:04.353Z"
61
+ "created_at": "2026-04-05T06:40:02.269Z"
55
62
  },
56
63
  {
57
64
  "id": "artifact-demo-dashboard-ui-src-components-DonutChart-tsx",
58
65
  "name": "src/components/DonutChart.tsx",
59
66
  "type": "file",
60
67
  "task_id": "ui-t3",
61
- "created_at": "2026-04-04T06:06:04.353Z"
68
+ "created_at": "2026-04-05T06:40:02.269Z"
62
69
  },
63
70
  {
64
71
  "id": "artifact-demo-dashboard-ui-src-components-KpiCard-tsx",
65
72
  "name": "src/components/KpiCard.tsx",
66
73
  "type": "file",
67
74
  "task_id": "ui-t2",
68
- "created_at": "2026-04-04T06:06:04.353Z"
75
+ "created_at": "2026-04-05T06:40:02.269Z"
69
76
  },
70
77
  {
71
78
  "id": "artifact-demo-dashboard-ui-src-components-design-tokens-ts",
72
79
  "name": "src/components/design-tokens.ts",
73
80
  "type": "file",
74
81
  "task_id": "ui-t1",
75
- "created_at": "2026-04-04T06:06:04.353Z"
82
+ "created_at": "2026-04-05T06:40:02.269Z"
76
83
  },
77
84
  {
78
85
  "id": "artifact-demo-dashboard-ui-src-styles-animations-css",
79
86
  "name": "src/styles/animations.css",
80
87
  "type": "file",
81
88
  "task_id": "ui-t4",
82
- "created_at": "2026-04-04T06:06:04.353Z"
83
- },
84
- {
85
- "id": "artifact-demo-dashboard-ui-reports-panel-review-dashboard-md",
86
- "name": "reports/panel-review-dashboard.md",
87
- "type": "summary",
88
- "task_id": "ui-t7",
89
- "created_at": "2026-04-04T06:06:04.354Z"
89
+ "created_at": "2026-04-05T06:40:02.269Z"
90
90
  }
91
91
  ],
92
92
  "has_more_events": false,
@@ -42,21 +42,21 @@
42
42
  "name": "src/etl/pipeline.ts",
43
43
  "type": "file",
44
44
  "task_id": "etl-t2",
45
- "created_at": "2026-04-04T06:06:04.354Z"
45
+ "created_at": "2026-04-05T06:40:02.270Z"
46
46
  },
47
47
  {
48
48
  "id": "artifact-demo-data-pipeline-src-etl-schema-ts",
49
49
  "name": "src/etl/schema.ts",
50
50
  "type": "file",
51
51
  "task_id": "etl-t1",
52
- "created_at": "2026-04-04T06:06:04.354Z"
52
+ "created_at": "2026-04-05T06:40:02.270Z"
53
53
  },
54
54
  {
55
55
  "id": "artifact-demo-data-pipeline-tests-etl-pipeline-test-ts",
56
56
  "name": "tests/etl/pipeline.test.ts",
57
57
  "type": "file",
58
58
  "task_id": "etl-t3",
59
- "created_at": "2026-04-04T06:06:04.354Z"
59
+ "created_at": "2026-04-05T06:40:02.270Z"
60
60
  }
61
61
  ],
62
62
  "has_more_events": false,
@@ -51,7 +51,7 @@
51
51
  "name": ".github/workflows/ci.yml",
52
52
  "type": "file",
53
53
  "task_id": "ci-t1",
54
- "created_at": "2026-04-04T06:06:04.355Z"
54
+ "created_at": "2026-04-05T06:40:02.270Z"
55
55
  }
56
56
  ],
57
57
  "has_more_events": false,
@@ -42,21 +42,21 @@
42
42
  "name": "docs/ARCHITECTURE.md",
43
43
  "type": "file",
44
44
  "task_id": "docs-t1",
45
- "created_at": "2026-04-04T06:06:04.355Z"
45
+ "created_at": "2026-04-05T06:40:02.270Z"
46
46
  },
47
47
  {
48
48
  "id": "artifact-demo-docs-update-docs-README-md",
49
49
  "name": "docs/README.md",
50
50
  "type": "file",
51
51
  "task_id": "docs-t1",
52
- "created_at": "2026-04-04T06:06:04.355Z"
52
+ "created_at": "2026-04-05T06:40:02.270Z"
53
53
  },
54
54
  {
55
55
  "id": "artifact-demo-docs-update-docs-api-reference-json",
56
56
  "name": "docs/api-reference.json",
57
57
  "type": "json",
58
58
  "task_id": "docs-t2",
59
- "created_at": "2026-04-04T06:06:04.355Z"
59
+ "created_at": "2026-04-05T06:40:02.270Z"
60
60
  }
61
61
  ],
62
62
  "has_more_events": false,
@@ -42,28 +42,28 @@
42
42
  "name": "reports/bundle-analysis.json",
43
43
  "type": "json",
44
44
  "task_id": "perf-t1",
45
- "created_at": "2026-04-04T06:06:04.354Z"
46
- },
47
- {
48
- "id": "artifact-demo-perf-opt-reports-web-vitals-improvement-md",
49
- "name": "reports/web-vitals-improvement.md",
50
- "type": "summary",
51
- "task_id": "perf-t4",
52
- "created_at": "2026-04-04T06:06:04.354Z"
45
+ "created_at": "2026-04-05T06:40:02.269Z"
53
46
  },
54
47
  {
55
48
  "id": "artifact-demo-perf-opt-src-charts-index-ts",
56
49
  "name": "src/charts/index.ts",
57
50
  "type": "file",
58
51
  "task_id": "perf-t2",
59
- "created_at": "2026-04-04T06:06:04.354Z"
52
+ "created_at": "2026-04-05T06:40:02.269Z"
53
+ },
54
+ {
55
+ "id": "artifact-demo-perf-opt-reports-web-vitals-improvement-md",
56
+ "name": "reports/web-vitals-improvement.md",
57
+ "type": "summary",
58
+ "task_id": "perf-t4",
59
+ "created_at": "2026-04-05T06:40:02.270Z"
60
60
  },
61
61
  {
62
62
  "id": "artifact-demo-perf-opt-src-utils-image-loader-ts",
63
63
  "name": "src/utils/image-loader.ts",
64
64
  "type": "file",
65
65
  "task_id": "perf-t3",
66
- "created_at": "2026-04-04T06:06:04.354Z"
66
+ "created_at": "2026-04-05T06:40:02.270Z"
67
67
  }
68
68
  ],
69
69
  "has_more_events": false,
@@ -447,6 +447,7 @@ try {
447
447
  // ── Active Convoy Polling ──────────────────────────────────
448
448
  let _activePollingTimer = null;
449
449
  let _activePollingConvoyId = null;
450
+ let _lastPolledConvoyStatus = null;
450
451
 
451
452
  function startActiveConvoyPolling() {
452
453
  if (_activePollingTimer) return; // already polling
@@ -476,6 +477,7 @@ try {
476
477
  // If we haven't navigated to any convoy yet, navigate now
477
478
  if (!_activePollingConvoyId) {
478
479
  _activePollingConvoyId = convoy.id;
480
+ _lastPolledConvoyStatus = null;
479
481
  // Refresh ETL data so convoy detail JSON exists
480
482
  await fetch(base + 'data/refresh');
481
483
  // Re-fetch the convoy list and stats
@@ -487,36 +489,32 @@ try {
487
489
  // If we're on a convoy and a new one started (pipeline chain), follow it
488
490
  if (_activePollingConvoyId !== convoy.id && isRunningOrPending) {
489
491
  _activePollingConvoyId = convoy.id;
492
+ _lastPolledConvoyStatus = null;
490
493
  await fetch(base + 'data/refresh');
491
494
  await refreshDashboardData();
492
495
  showConvoyDetailView(convoy.id, convoy.name || convoy.id);
493
496
  }
494
497
 
495
- // If we're viewing the active convoy, refresh its detail data
498
+ // If we're viewing the active convoy, refresh when it's running or just transitioned
496
499
  if (_activePollingConvoyId === convoy.id) {
497
- await fetch(base + 'data/refresh');
498
- await refreshDashboardData();
499
- // Reload convoy detail to show latest task progress
500
- await loadConvoyDetail(convoy.id);
501
-
502
- // Update pipeline chain breadcrumb if applicable
503
- if (data.pipeline && data.pipeline.length > 1) {
504
- renderPipelineChain(data.pipeline, convoy.id);
505
- }
506
- }
507
-
508
- // Stop polling when the convoy (and any pipeline) is finished
509
- if (!isRunningOrPending) {
510
- // In pipeline mode, check if all convoys in the chain are done
511
- if (data.pipeline) {
512
- const anyActive = data.pipeline.some(c => c.status === 'running' || c.status === 'pending');
513
- if (!anyActive) {
514
- stopActiveConvoyPolling();
500
+ const statusChanged = _lastPolledConvoyStatus !== null && _lastPolledConvoyStatus !== convoy.status;
501
+ _lastPolledConvoyStatus = convoy.status;
502
+
503
+ if (isRunningOrPending || statusChanged) {
504
+ await fetch(base + 'data/refresh');
505
+ await refreshDashboardData();
506
+ // Reload convoy detail to show latest task progress
507
+ await loadConvoyDetail(convoy.id);
508
+
509
+ // Update pipeline chain breadcrumb if applicable
510
+ if (data.pipeline && data.pipeline.length > 1) {
511
+ renderPipelineChain(data.pipeline, convoy.id);
515
512
  }
516
- } else {
517
- stopActiveConvoyPolling();
518
513
  }
519
514
  }
515
+
516
+ // NOTE: In 'active' mode we intentionally keep polling even after a convoy finishes,
517
+ // so that subsequent convoys are detected when they start.
520
518
  } catch {
521
519
  // Polling errors are non-fatal — just retry next interval
522
520
  }
@@ -2167,11 +2165,9 @@ try {
2167
2165
  const target = running || latest;
2168
2166
  if (target) {
2169
2167
  _activePollingConvoyId = target.id;
2168
+ _lastPolledConvoyStatus = null;
2170
2169
  showConvoyDetailView(target.id, target.name || target.id);
2171
- // If still running, start polling for live updates
2172
- if (target.status === 'running' || target.status === 'pending') {
2173
- startActiveConvoyPolling();
2174
- }
2170
+ startActiveConvoyPolling();
2175
2171
  } else {
2176
2172
  // No convoy exists yet — show waiting state and poll
2177
2173
  showWaitingBanner();