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/.a2a-manifest.json +70 -0
- package/.claude/a2a-skill-reference.md +462 -0
- package/.claude/commands/a2a-app.md +42 -0
- package/.claude/commands/a2a-call.md +26 -0
- package/.claude/commands/a2a-contacts.md +31 -0
- package/.claude/commands/a2a-conversations.md +47 -0
- package/.claude/commands/a2a-gui.md +30 -0
- package/.claude/commands/a2a-invite.md +63 -0
- package/.claude/commands/a2a-setup.md +30 -0
- package/.claude/commands/a2a-skills.md +27 -0
- package/.claude/commands/a2a-status.md +46 -0
- package/.claude/commands/a2a-uninstall.md +36 -0
- package/.claude/commands/a2a-update.md +41 -0
- package/ARCHITECTURE.md +92 -0
- package/CONVENTIONS.md +78 -0
- package/docs/plans/2026-02-18-a2a-42-e2e-persistence.md +879 -0
- package/package.json +1 -1
- package/scripts/run-e2e.sh +44 -0
- package/src/dashboard/public/app.js +97 -0
- package/src/dashboard/public/index.html +22 -0
- package/src/routes/dashboard.js +42 -0
package/package.json
CHANGED
|
@@ -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, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
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 ·
|
|
2154
|
+
<strong>Passed:</strong> ${summary.passed || 0} ·
|
|
2155
|
+
<strong>Failed:</strong> ${summary.failed || 0} ·
|
|
2156
|
+
<strong>Skipped:</strong> ${summary.skipped || 0} ·
|
|
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' ? '✅' : s.status === 'fail' ? '❌' : '⏭';
|
|
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>
|
package/src/routes/dashboard.js
CHANGED
|
@@ -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);
|