a2acalling 0.6.60 → 0.6.62

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": "a2acalling",
3
- "version": "0.6.60",
3
+ "version": "0.6.62",
4
4
  "description": "Agent-to-agent calling for OpenClaw - A2A agent communication",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env bash
2
+ # A2A-42: Cron/CI wrapper for E2E test orchestrator.
3
+ # Runs orchestration, persists results, optionally alerts on failure.
4
+ #
5
+ # Usage:
6
+ # scripts/run-e2e.sh # run + persist
7
+ # scripts/run-e2e.sh --alert # run + persist + alert on failure
8
+ #
9
+ # Cron example (every 6 hours):
10
+ # 0 */6 * * * /root/a2acalling/scripts/run-e2e.sh --alert >> /var/log/a2a-e2e.log 2>&1
11
+
12
+ set -euo pipefail
13
+
14
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
15
+ PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
16
+ ALERT_SCRIPT="/root/maestro/scripts/alert.sh"
17
+ ALERT_ON_FAILURE=false
18
+
19
+ for arg in "$@"; do
20
+ case "$arg" in
21
+ --alert) ALERT_ON_FAILURE=true ;;
22
+ esac
23
+ done
24
+
25
+ echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] Starting E2E orchestration..."
26
+
27
+ cd "$PROJECT_DIR"
28
+
29
+ # A2A-42: Run orchestrator with JSON output and persistence.
30
+ # stdout (JSON) goes to /dev/null; stderr (regression messages, logs) passes through
31
+ # so cron log captures warnings like "REGRESSION DETECTED: ..."
32
+ if node test/e2e/orchestrate.js --json --persist > /dev/null; then
33
+ echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] E2E: PASSED"
34
+ exit 0
35
+ else
36
+ EXIT_CODE=$?
37
+ echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] E2E: FAILED (exit $EXIT_CODE)"
38
+
39
+ if [ "$ALERT_ON_FAILURE" = true ] && [ -x "$ALERT_SCRIPT" ]; then
40
+ "$ALERT_SCRIPT" error "E2E test failure detected — check ~/.config/openclaw/test-results/latest.json"
41
+ fi
42
+
43
+ exit "$EXIT_CODE"
44
+ fi
@@ -2080,6 +2080,7 @@ const tabLoaders = {
2080
2080
  logs: () => { loadLogs(); loadLogStats(); },
2081
2081
  permissions: () => {},
2082
2082
  invites: loadInvites,
2083
+ health: loadHealth,
2083
2084
  };
2084
2085
 
2085
2086
  function startPolling() {
@@ -2102,6 +2103,102 @@ function onTabSwitch(tabName) {
2102
2103
  startPolling(); // reset the 5s timer
2103
2104
  }
2104
2105
 
2106
+ // === Health Tab (A2A-42) ===
2107
+
2108
+ // A2A-42: Escape HTML entities for safe innerHTML rendering of step names/errors.
2109
+ function escapeHtml(s) {
2110
+ return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
2111
+ }
2112
+
2113
+ async function loadHealth() {
2114
+ try {
2115
+ const data = await request('/test-results');
2116
+ renderHealthLatest(data.latest);
2117
+ renderHealthHistory(data.history || []);
2118
+ } catch (err) {
2119
+ renderHealthLatest(null);
2120
+ renderHealthHistory([]);
2121
+ }
2122
+ }
2123
+
2124
+ function renderHealthLatest(latest) {
2125
+ const card = document.getElementById('health-latest');
2126
+ if (!card) return;
2127
+
2128
+ if (!latest) {
2129
+ card.innerHTML = '<p>No test results available. Run <code>node test/e2e/orchestrate.js --persist</code> to generate.</p>';
2130
+ return;
2131
+ }
2132
+
2133
+ const statusBadge = latest.status === 'passed'
2134
+ ? '<sl-badge variant="success">PASSED</sl-badge>'
2135
+ : '<sl-badge variant="danger">FAILED</sl-badge>';
2136
+
2137
+ const regression = latest.regression;
2138
+ let regressionHtml = '';
2139
+ if (regression && regression.detected) {
2140
+ regressionHtml = `<p><sl-badge variant="warning">REGRESSION</sl-badge> New failures: ${regression.newFailures.map(escapeHtml).join(', ')}</p>`;
2141
+ }
2142
+ if (regression && regression.fixedTests && regression.fixedTests.length > 0) {
2143
+ regressionHtml += `<p><sl-badge variant="success">FIXED</sl-badge> ${regression.fixedTests.map(escapeHtml).join(', ')}</p>`;
2144
+ }
2145
+
2146
+ const ts = latest.finishedAt ? new Date(latest.finishedAt).toLocaleString() : 'unknown';
2147
+ const summary = latest.summary || {};
2148
+
2149
+ card.innerHTML = `
2150
+ <div class="row">
2151
+ <strong>Latest Run</strong> ${statusBadge}
2152
+ </div>
2153
+ <p><strong>Duration:</strong> ${latest.duration || 0}ms &middot;
2154
+ <strong>Passed:</strong> ${summary.passed || 0} &middot;
2155
+ <strong>Failed:</strong> ${summary.failed || 0} &middot;
2156
+ <strong>Skipped:</strong> ${summary.skipped || 0} &middot;
2157
+ <strong>Time:</strong> ${ts}</p>
2158
+ ${regressionHtml}
2159
+ <details>
2160
+ <summary>Steps (${(latest.steps || []).length})</summary>
2161
+ <ul>
2162
+ ${(latest.steps || []).map(s => {
2163
+ const icon = s.status === 'pass' ? '&#x2705;' : s.status === 'fail' ? '&#x274C;' : '&#x23ED;';
2164
+ const err = s.error ? ` — <code>${escapeHtml(String(s.error).slice(0, 120))}</code>` : '';
2165
+ return `<li>${icon} ${escapeHtml(s.name)}${err}</li>`;
2166
+ }).join('')}
2167
+ </ul>
2168
+ </details>
2169
+ `;
2170
+ }
2171
+
2172
+ function renderHealthHistory(history) {
2173
+ const tbody = document.querySelector('#health-history-table tbody');
2174
+ if (!tbody) return;
2175
+
2176
+ if (!history || history.length === 0) {
2177
+ tbody.innerHTML = '<tr><td colspan="6">No history</td></tr>';
2178
+ return;
2179
+ }
2180
+
2181
+ tbody.innerHTML = history.map(r => {
2182
+ const badge = r.status === 'passed'
2183
+ ? '<sl-badge variant="success" size="small">PASS</sl-badge>'
2184
+ : '<sl-badge variant="danger" size="small">FAIL</sl-badge>';
2185
+ const summary = r.summary || {};
2186
+ const regression = r.regression;
2187
+ const regText = regression && regression.detected
2188
+ ? `<sl-badge variant="warning" size="small">${regression.newFailures.length} new</sl-badge>`
2189
+ : '-';
2190
+ const ts = r.finishedAt ? new Date(r.finishedAt).toLocaleString() : '-';
2191
+ return `<tr>
2192
+ <td>${badge}</td>
2193
+ <td>${r.duration || 0}ms</td>
2194
+ <td>${summary.passed || 0}</td>
2195
+ <td>${summary.failed || 0}</td>
2196
+ <td>${regText}</td>
2197
+ <td>${ts}</td>
2198
+ </tr>`;
2199
+ }).join('');
2200
+ }
2201
+
2105
2202
  async function bootstrap() {
2106
2203
  bindTabs();
2107
2204
  bindContactsActions();
@@ -21,6 +21,7 @@
21
21
  <sl-tab slot="nav" panel="permissions">Permissions</sl-tab>
22
22
  <sl-tab slot="nav" panel="invites">Invites</sl-tab>
23
23
  <sl-tab slot="nav" panel="logs">Logs</sl-tab>
24
+ <sl-tab slot="nav" panel="health">Health</sl-tab>
24
25
 
25
26
  <sl-tab-panel name="contacts">
26
27
  <h2>Contacts</h2>
@@ -254,6 +255,27 @@
254
255
 
255
256
  <sl-card id="trace-detail"></sl-card>
256
257
  </sl-tab-panel>
258
+
259
+ <sl-tab-panel name="health">
260
+ <h2>E2E Health</h2>
261
+ <sl-card id="health-latest">
262
+ <p>No test results available. Run <code>node test/e2e/orchestrate.js --persist</code> to generate results.</p>
263
+ </sl-card>
264
+ <h3>History</h3>
265
+ <table id="health-history-table">
266
+ <thead>
267
+ <tr>
268
+ <th>Status</th>
269
+ <th>Duration</th>
270
+ <th>Passed</th>
271
+ <th>Failed</th>
272
+ <th>Regression</th>
273
+ <th>Time</th>
274
+ </tr>
275
+ </thead>
276
+ <tbody></tbody>
277
+ </table>
278
+ </sl-tab-panel>
257
279
  </sl-tab-group>
258
280
 
259
281
  <div id="notice"></div>
@@ -448,6 +448,16 @@ function createDashboardApiRouter(options = {}) {
448
448
  const router = express.Router();
449
449
  const context = buildContext(options);
450
450
  router.use(express.json());
451
+
452
+ // A2A-42: Load E2E persist layer for Health tab. Gracefully null if not available
453
+ // (e.g., installed as npm package without test files).
454
+ let persistModule = null;
455
+ try {
456
+ persistModule = require(path.join(__dirname, '..', '..', 'test', 'e2e', 'persist'));
457
+ } catch {
458
+ // test/e2e/persist.js not available — Health tab will show "no results"
459
+ }
460
+
451
461
  const ensureDashboardAccess = makeEnsureDashboardAccess(context);
452
462
  const writeSseEvent = (res, event) => {
453
463
  const eventName = sanitizeString(event?.type || 'message', 80) || 'message';
@@ -883,6 +893,38 @@ function createDashboardApiRouter(options = {}) {
883
893
  return res.json({ success: true, stats });
884
894
  });
885
895
 
896
+ // A2A-42: Serve E2E test results for the Health tab.
897
+ // Reads from local persist layer — no external dependencies.
898
+ router.get('/test-results', (req, res) => {
899
+ if (!persistModule) {
900
+ return res.json({
901
+ success: true,
902
+ latest: null,
903
+ history: [],
904
+ has_results: false,
905
+ message: 'Test results module not available'
906
+ });
907
+ }
908
+
909
+ const latest = persistModule.getLatest();
910
+ const limit = Math.min(20, Math.max(1, Number.parseInt(req.query.limit || '10', 10) || 10));
911
+ const history = persistModule.getHistory(limit);
912
+
913
+ return res.json({
914
+ success: true,
915
+ latest,
916
+ history: history.map(r => ({
917
+ status: r.status,
918
+ duration: r.duration,
919
+ startedAt: r.startedAt,
920
+ finishedAt: r.finishedAt,
921
+ summary: r.summary,
922
+ regression: r.regression || null
923
+ })),
924
+ has_results: latest !== null
925
+ });
926
+ });
927
+
886
928
  router.get('/debug/call', (req, res) => {
887
929
  const traceId = sanitizeString(req.query.trace_id || req.query.traceId || '', 120);
888
930
  const conversationId = sanitizeString(req.query.conversation_id || req.query.conversationId || '', 120);