mirror of
https://github.com/agent0ai/agent-zero.git
synced 2026-07-09 17:08:29 +00:00
Add built-in What's New showcase plugin
Introduce an always-enabled _whats_new plugin that shows a version-gated feature showcase modal with bundled media assets for parallel tool calls, the redesigned MCP configuration UI, and Skills Scanner. Track the plugin in DOX and add static coverage for the modal copy, assets, manifest, and startup trigger.
This commit is contained in:
parent
98e51fbb2a
commit
2a81e37499
10 changed files with 609 additions and 0 deletions
|
|
@ -93,4 +93,5 @@ Direct child DOX files:
|
|||
| [_text_editor/AGENTS.md](_text_editor/AGENTS.md) | Native text read, write, and patch tool. |
|
||||
| [_time_travel/AGENTS.md](_time_travel/AGENTS.md) | Workspace history, diff, travel, snapshot, and revert flows. |
|
||||
| [_whatsapp_integration/AGENTS.md](_whatsapp_integration/AGENTS.md) | WhatsApp Baileys bridge integration. |
|
||||
| [_whats_new/AGENTS.md](_whats_new/AGENTS.md) | Version-gated What's New showcase modal and startup trigger. |
|
||||
| [_whisper_stt/AGENTS.md](_whisper_stt/AGENTS.md) | Whisper speech-to-text integration. |
|
||||
|
|
|
|||
33
plugins/_whats_new/AGENTS.md
Normal file
33
plugins/_whats_new/AGENTS.md
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
# What's New Plugin DOX
|
||||
|
||||
## Purpose
|
||||
|
||||
- Own the built-in version-gated "What's New" modal for showcasing new Agent Zero features after updates.
|
||||
|
||||
## Ownership
|
||||
|
||||
- `plugin.yaml` owns metadata and always-enabled status.
|
||||
- `webui/` owns the modal markup, Alpine store, copy, and showcase media assets.
|
||||
- `extensions/webui/initFw_end/` owns the startup trigger that opens the modal when the installed version is newer than the locally seen version.
|
||||
|
||||
## Local Contracts
|
||||
|
||||
- Do not add a "Don't show this again" control; dismissal records the current installed version as seen.
|
||||
- Keep the modal copy concise, left-aligned, and paired with feature media.
|
||||
- Keep modal actions in the pinned footer using the shared Agent Zero button classes.
|
||||
- Store the seen-version marker in browser-local state only; do not persist this under `usr/`.
|
||||
|
||||
## Work Guidance
|
||||
|
||||
- Add showcase assets under `webui/assets/` and reference them through `/plugins/_whats_new/webui/assets/...`.
|
||||
- Keep the startup extension idempotent and tolerant of missing or non-comparable version labels.
|
||||
- Prefer release-line comparisons over development commit-distance comparisons so local development builds do not reopen the modal on every commit.
|
||||
|
||||
## Verification
|
||||
|
||||
- Run `pytest tests/test_whats_new_static.py` after changing the modal, trigger, or assets.
|
||||
- Smoke-test startup display, slide navigation, dismissal, and same-version reload behavior in the WebUI.
|
||||
|
||||
## Child DOX Index
|
||||
|
||||
No child DOX files.
|
||||
137
plugins/_whats_new/extensions/webui/initFw_end/whats-new.js
Normal file
137
plugins/_whats_new/extensions/webui/initFw_end/whats-new.js
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
import { getModalStack, isModalOpen, openModal } from "/js/modals.js";
|
||||
|
||||
const MODAL_PATH = "/plugins/_whats_new/webui/whats-new.html";
|
||||
const STORAGE_KEY = "a0_whats_new_seen_version";
|
||||
const STARTUP_DELAY_MS = 1200;
|
||||
const RETRY_DELAY_MS = 900;
|
||||
const MAX_BUSY_RETRIES = 20;
|
||||
|
||||
let initialized = false;
|
||||
let busyRetries = 0;
|
||||
let openedForVersion = null;
|
||||
|
||||
function cleanPath(path = "") {
|
||||
return String(path || "").replace(/^\/+/, "");
|
||||
}
|
||||
|
||||
function parseVersion(value) {
|
||||
const raw = String(value || "").trim();
|
||||
if (!raw || raw.toLowerCase() === "unknown") return null;
|
||||
|
||||
const match = /(?:^|[\s(])v?(\d+)\.(\d+)(?:\.(\d+))?(?:\+(\d+))?/.exec(raw);
|
||||
if (!match) return null;
|
||||
|
||||
return {
|
||||
raw,
|
||||
parts: [
|
||||
Number.parseInt(match[1], 10),
|
||||
Number.parseInt(match[2], 10),
|
||||
Number.parseInt(match[3] || "0", 10),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
function compareVersions(left, right) {
|
||||
if (!left || !right) return null;
|
||||
for (let index = 0; index < left.parts.length; index += 1) {
|
||||
if (left.parts[index] !== right.parts[index]) {
|
||||
return left.parts[index] - right.parts[index];
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function currentVersion() {
|
||||
return parseVersion(globalThis.gitinfo?.version || "");
|
||||
}
|
||||
|
||||
function storedSeenVersion() {
|
||||
try {
|
||||
const rawValue = globalThis.localStorage?.getItem(STORAGE_KEY) || "";
|
||||
if (!rawValue) return null;
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(rawValue);
|
||||
return parseVersion(parsed?.version || parsed?.raw || rawValue);
|
||||
} catch {
|
||||
return parseVersion(rawValue);
|
||||
}
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function shouldShowWhatsNew(version) {
|
||||
if (!version) return false;
|
||||
|
||||
const seen = storedSeenVersion();
|
||||
if (!seen) return true;
|
||||
|
||||
const comparison = compareVersions(version, seen);
|
||||
return comparison !== null && comparison > 0;
|
||||
}
|
||||
|
||||
function markVersionSeen(version = currentVersion()) {
|
||||
if (!version) return;
|
||||
try {
|
||||
globalThis.localStorage?.setItem(
|
||||
STORAGE_KEY,
|
||||
JSON.stringify({
|
||||
version: version.raw,
|
||||
seenAt: new Date().toISOString(),
|
||||
}),
|
||||
);
|
||||
} catch {
|
||||
// localStorage may be unavailable in private or locked-down browser modes.
|
||||
}
|
||||
}
|
||||
|
||||
function anotherModalIsOpen() {
|
||||
return getModalStack().length > 0 || Boolean(document.querySelector(".modal.show"));
|
||||
}
|
||||
|
||||
function scheduleRetry() {
|
||||
if (busyRetries >= MAX_BUSY_RETRIES) return;
|
||||
busyRetries += 1;
|
||||
window.setTimeout(() => {
|
||||
maybeOpenWhatsNew();
|
||||
}, RETRY_DELAY_MS);
|
||||
}
|
||||
|
||||
function maybeOpenWhatsNew() {
|
||||
const version = currentVersion();
|
||||
if (!shouldShowWhatsNew(version)) return;
|
||||
|
||||
if (isModalOpen(MODAL_PATH)) return;
|
||||
if (anotherModalIsOpen()) {
|
||||
scheduleRetry();
|
||||
return;
|
||||
}
|
||||
|
||||
openedForVersion = version;
|
||||
void openModal(MODAL_PATH);
|
||||
}
|
||||
|
||||
function handleModalClosed(event) {
|
||||
const closedPath = event?.detail?.modalPath || "";
|
||||
if (cleanPath(closedPath) === cleanPath(MODAL_PATH)) {
|
||||
markVersionSeen(openedForVersion || currentVersion());
|
||||
openedForVersion = null;
|
||||
return;
|
||||
}
|
||||
|
||||
window.setTimeout(() => {
|
||||
maybeOpenWhatsNew();
|
||||
}, RETRY_DELAY_MS);
|
||||
}
|
||||
|
||||
export default function initWhatsNew() {
|
||||
if (initialized) return;
|
||||
initialized = true;
|
||||
|
||||
document.addEventListener("modal-closed", handleModalClosed);
|
||||
|
||||
window.setTimeout(() => {
|
||||
maybeOpenWhatsNew();
|
||||
}, STARTUP_DELAY_MS);
|
||||
}
|
||||
8
plugins/_whats_new/plugin.yaml
Normal file
8
plugins/_whats_new/plugin.yaml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
name: _whats_new
|
||||
title: What's New
|
||||
description: Built-in version-gated showcase modal for new Agent Zero features.
|
||||
version: 1.0.0
|
||||
settings_sections: []
|
||||
always_enabled: true
|
||||
per_project_config: false
|
||||
per_agent_config: false
|
||||
BIN
plugins/_whats_new/webui/assets/mcp-servers.png
Normal file
BIN
plugins/_whats_new/webui/assets/mcp-servers.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 93 KiB |
BIN
plugins/_whats_new/webui/assets/parallel-subs.webm
Normal file
BIN
plugins/_whats_new/webui/assets/parallel-subs.webm
Normal file
Binary file not shown.
BIN
plugins/_whats_new/webui/assets/skills-scanner.png
Normal file
BIN
plugins/_whats_new/webui/assets/skills-scanner.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 175 KiB |
114
plugins/_whats_new/webui/whats-new-store.js
Normal file
114
plugins/_whats_new/webui/whats-new-store.js
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
import { createStore } from "/js/AlpineStore.js";
|
||||
import { closeModal } from "/js/modals.js";
|
||||
|
||||
const ASSET_BASE = "/plugins/_whats_new/webui/assets";
|
||||
|
||||
const slides = [
|
||||
{
|
||||
id: "parallel-tools",
|
||||
eyebrow: "Parallel execution",
|
||||
title: "Parallel tool calls and subagents",
|
||||
summary:
|
||||
"Agent Zero can now split work across parallel tool and subagents calls and combine concurrent steps results.",
|
||||
mediaType: "video",
|
||||
media: `${ASSET_BASE}/parallel-subs.webm`,
|
||||
mediaLabel:
|
||||
"Four Agent Zero subagents working in parallel while the parent agent coordinates the result.",
|
||||
bullets: [
|
||||
"Launch coordinated subagents to explore separate paths at the same time.",
|
||||
"Run mixed batches together: search queries, code execution, file reads, writes, and more.",
|
||||
"Merge the results back into one answer without waiting through every call in sequence.",
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "mcp-configuration",
|
||||
eyebrow: "MCP configuration",
|
||||
title: "Redesigned MCP configuration UI",
|
||||
summary:
|
||||
"Global Settings and Projects now share a cleaner MCP setup flow for command and Remote URL transports.",
|
||||
mediaType: "image",
|
||||
media: `${ASSET_BASE}/mcp-servers.png`,
|
||||
mediaLabel:
|
||||
"The redesigned MCP servers screen showing server cards, transport controls, and raw JSON mode.",
|
||||
bullets: [
|
||||
"Configure npx, uvx, or custom command servers with clearer fields.",
|
||||
"Connect Remote URL transports from the same accessible editor.",
|
||||
"Switch to Raw JSON when you want to paste or move configurations between clients.",
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "skills-scanner",
|
||||
eyebrow: "Agent security",
|
||||
title: "Skills Scanner powered by Snyk Agent Scan",
|
||||
summary:
|
||||
"Scan your agent skills and MCP-connected surfaces for prompt injections and vulnerabilities.",
|
||||
mediaType: "image",
|
||||
media: `${ASSET_BASE}/skills-scanner.png`,
|
||||
mediaLabel:
|
||||
"The Skills Scanner screen showing Snyk Agent Scan controls and scan guidance.",
|
||||
bullets: [
|
||||
"Review skills with the same scanning flow you already use for plugins.",
|
||||
"Find prompt-injection risks and vulnerable instructions before they reach runtime.",
|
||||
"Catch risky skill instructions early, before they become part of an agent workflow.",
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export const store = createStore("whatsNew", {
|
||||
slides,
|
||||
currentIndex: 0,
|
||||
|
||||
onOpen() {
|
||||
this.currentIndex = 0;
|
||||
},
|
||||
|
||||
cleanup() {},
|
||||
|
||||
get currentSlide() {
|
||||
return this.slides[this.currentIndex] || this.slides[0];
|
||||
},
|
||||
|
||||
isFirst() {
|
||||
return this.currentIndex <= 0;
|
||||
},
|
||||
|
||||
isLast() {
|
||||
return this.currentIndex >= this.slides.length - 1;
|
||||
},
|
||||
|
||||
progressLabel() {
|
||||
return `${this.currentIndex + 1} of ${this.slides.length}`;
|
||||
},
|
||||
|
||||
dotLabel(index) {
|
||||
const slide = this.slides[index];
|
||||
return slide ? `Show ${slide.title}` : `Show item ${index + 1}`;
|
||||
},
|
||||
|
||||
goTo(index) {
|
||||
const nextIndex = Number(index);
|
||||
if (!Number.isInteger(nextIndex)) return;
|
||||
if (nextIndex < 0 || nextIndex >= this.slides.length) return;
|
||||
this.currentIndex = nextIndex;
|
||||
},
|
||||
|
||||
previous() {
|
||||
if (!this.isFirst()) this.currentIndex -= 1;
|
||||
},
|
||||
|
||||
next() {
|
||||
if (this.isLast()) {
|
||||
this.finish();
|
||||
return;
|
||||
}
|
||||
this.currentIndex += 1;
|
||||
},
|
||||
|
||||
finish() {
|
||||
closeModal();
|
||||
},
|
||||
|
||||
skip() {
|
||||
closeModal();
|
||||
},
|
||||
});
|
||||
259
plugins/_whats_new/webui/whats-new.html
Normal file
259
plugins/_whats_new/webui/whats-new.html
Normal file
|
|
@ -0,0 +1,259 @@
|
|||
<html class="whats-new-modal">
|
||||
<head>
|
||||
<title>What's New in Agent Zero</title>
|
||||
<script type="module">
|
||||
import { store } from "/plugins/_whats_new/webui/whats-new-store.js";
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div x-data>
|
||||
<template x-if="$store.whatsNew">
|
||||
<div class="whats-new-shell" x-init="$store.whatsNew.onOpen()" x-destroy="$store.whatsNew.cleanup()">
|
||||
<section class="whats-new-media-panel" :aria-label="$store.whatsNew.currentSlide.mediaLabel">
|
||||
<template x-if="$store.whatsNew.currentSlide.mediaType === 'video'">
|
||||
<video
|
||||
class="whats-new-media"
|
||||
:src="$store.whatsNew.currentSlide.media"
|
||||
:aria-label="$store.whatsNew.currentSlide.mediaLabel"
|
||||
autoplay
|
||||
loop
|
||||
muted
|
||||
playsinline
|
||||
></video>
|
||||
</template>
|
||||
<template x-if="$store.whatsNew.currentSlide.mediaType === 'image'">
|
||||
<img
|
||||
class="whats-new-media"
|
||||
:src="$store.whatsNew.currentSlide.media"
|
||||
:alt="$store.whatsNew.currentSlide.mediaLabel"
|
||||
/>
|
||||
</template>
|
||||
</section>
|
||||
|
||||
<section class="whats-new-copy">
|
||||
<div class="whats-new-eyebrow" x-text="$store.whatsNew.currentSlide.eyebrow"></div>
|
||||
<h3 x-text="$store.whatsNew.currentSlide.title"></h3>
|
||||
<p class="whats-new-summary" x-text="$store.whatsNew.currentSlide.summary"></p>
|
||||
<ul class="whats-new-bullets">
|
||||
<template x-for="item in $store.whatsNew.currentSlide.bullets" :key="item">
|
||||
<li x-text="item"></li>
|
||||
</template>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<div class="modal-footer whats-new-footer" data-modal-footer>
|
||||
<div class="whats-new-footer-left">
|
||||
<span class="whats-new-progress-label" x-text="$store.whatsNew.progressLabel()"></span>
|
||||
<div class="whats-new-progress-dots" role="tablist" aria-label="What's New slides">
|
||||
<template x-for="(slide, index) in $store.whatsNew.slides" :key="slide.id">
|
||||
<button
|
||||
type="button"
|
||||
class="whats-new-dot"
|
||||
role="tab"
|
||||
:class="{ 'active': index === $store.whatsNew.currentIndex }"
|
||||
:aria-label="$store.whatsNew.dotLabel(index)"
|
||||
:aria-selected="index === $store.whatsNew.currentIndex ? 'true' : 'false'"
|
||||
@click="$store.whatsNew.goTo(index)"
|
||||
></button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div class="whats-new-footer-actions">
|
||||
<button type="button" class="btn btn-cancel" @click="$store.whatsNew.skip()">Skip</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-field"
|
||||
x-show="!$store.whatsNew.isFirst()"
|
||||
@click="$store.whatsNew.previous()"
|
||||
>
|
||||
Back
|
||||
</button>
|
||||
<button type="button" class="btn btn-ok" @click="$store.whatsNew.next()">
|
||||
<span x-text="$store.whatsNew.isLast() ? 'Done' : 'Next'"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.modal-inner.whats-new-modal {
|
||||
width: min(92vw, 760px);
|
||||
max-height: min(88vh, 820px);
|
||||
}
|
||||
|
||||
.whats-new-shell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.whats-new-media-panel {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
aspect-ratio: 1476 / 842;
|
||||
min-height: 180px;
|
||||
max-height: min(44vh, 360px);
|
||||
overflow: hidden;
|
||||
border: 1px solid color-mix(in srgb, var(--color-border) 70%, transparent);
|
||||
border-radius: 7px;
|
||||
background:
|
||||
radial-gradient(circle at 18% 12%, color-mix(in srgb, #4248f1 18%, transparent), transparent 34%),
|
||||
color-mix(in srgb, var(--color-panel) 78%, #000 22%);
|
||||
}
|
||||
|
||||
.whats-new-media {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
background: color-mix(in srgb, var(--color-panel) 82%, #000 18%);
|
||||
}
|
||||
|
||||
.whats-new-copy {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.55rem;
|
||||
min-width: 0;
|
||||
color: var(--color-text);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.whats-new-eyebrow {
|
||||
color: color-mix(in srgb, #2196f3 86%, var(--color-text));
|
||||
font-size: 0.74rem;
|
||||
font-weight: 760;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.whats-new-copy h3 {
|
||||
margin: 0;
|
||||
color: var(--color-text);
|
||||
font-size: clamp(1.35rem, 2.5vw, 1.8rem);
|
||||
font-weight: 760;
|
||||
line-height: 1.12;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.whats-new-summary {
|
||||
max-width: 62ch;
|
||||
margin: 0;
|
||||
color: var(--color-text-muted);
|
||||
font-size: 0.98rem;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.whats-new-bullets {
|
||||
display: grid;
|
||||
gap: 0.45rem;
|
||||
margin: 0.25rem 0 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.whats-new-bullets li {
|
||||
position: relative;
|
||||
min-width: 0;
|
||||
padding-left: 1.1rem;
|
||||
color: var(--color-text-muted);
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.42;
|
||||
}
|
||||
|
||||
.whats-new-bullets li::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0.58em;
|
||||
width: 0.42rem;
|
||||
height: 0.42rem;
|
||||
border-radius: 999px;
|
||||
background: color-mix(in srgb, #4248f1 72%, #2196f3 28%);
|
||||
box-shadow: 0 0 0 3px color-mix(in srgb, #4248f1 18%, transparent);
|
||||
}
|
||||
|
||||
.whats-new-footer {
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.whats-new-footer-left,
|
||||
.whats-new-footer-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.65rem;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.whats-new-progress-label {
|
||||
color: var(--color-text-muted);
|
||||
font-size: 0.78rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.whats-new-progress-dots {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
}
|
||||
|
||||
.whats-new-dot {
|
||||
width: 0.48rem;
|
||||
height: 0.48rem;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
border-radius: 999px;
|
||||
background: color-mix(in srgb, var(--color-text-muted) 45%, transparent);
|
||||
cursor: pointer;
|
||||
transition: transform 0.16s ease, background-color 0.16s ease, width 0.16s ease;
|
||||
}
|
||||
|
||||
.whats-new-dot.active {
|
||||
width: 1.2rem;
|
||||
background: color-mix(in srgb, #4248f1 74%, #2196f3 26%);
|
||||
}
|
||||
|
||||
.whats-new-dot:hover {
|
||||
transform: translateY(-1px);
|
||||
background: color-mix(in srgb, #2196f3 72%, var(--color-text));
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.modal-inner.whats-new-modal {
|
||||
width: min(94vw, 760px);
|
||||
}
|
||||
|
||||
.whats-new-media-panel {
|
||||
min-height: 150px;
|
||||
max-height: 32vh;
|
||||
}
|
||||
|
||||
.whats-new-footer,
|
||||
.whats-new-footer-left,
|
||||
.whats-new-footer-actions {
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.whats-new-footer {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.whats-new-footer-left {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.whats-new-footer-actions {
|
||||
justify-content: flex-end;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</body>
|
||||
</html>
|
||||
57
tests/test_whats_new_static.py
Normal file
57
tests/test_whats_new_static.py
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
from pathlib import Path
|
||||
|
||||
|
||||
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
def test_whats_new_modal_uses_showcase_assets_and_branded_footer():
|
||||
html = (PROJECT_ROOT / "plugins/_whats_new/webui/whats-new.html").read_text(encoding="utf-8")
|
||||
store = (PROJECT_ROOT / "plugins/_whats_new/webui/whats-new-store.js").read_text(encoding="utf-8")
|
||||
|
||||
assert "What's New in Agent Zero" in html
|
||||
assert "data-modal-footer" in html
|
||||
assert "btn btn-ok" in html
|
||||
assert "btn btn-field" in html
|
||||
assert "/plugins/_whats_new/webui/whats-new-store.js" in html
|
||||
assert "/plugins/_whats_new/webui/assets" in store
|
||||
assert "Don't show this again" not in html + store
|
||||
|
||||
for asset in ["parallel-subs.webm", "mcp-servers.png", "skills-scanner.png"]:
|
||||
assert asset in html + store
|
||||
assert (PROJECT_ROOT / "plugins/_whats_new/webui/assets" / asset).exists()
|
||||
|
||||
assert "Parallel tool calls and subagents" in store
|
||||
assert (
|
||||
"Agent Zero can now split work across parallel tool and subagents calls and combine concurrent steps results."
|
||||
in store
|
||||
)
|
||||
assert "Redesigned MCP configuration UI" in store
|
||||
assert "Skills Scanner powered by Snyk Agent Scan" in store
|
||||
assert "Remote URL transports" in store
|
||||
assert "Raw JSON" in store
|
||||
assert "prompt-injection risks" in store
|
||||
assert "Catch risky skill instructions early" in store
|
||||
assert "Include MCP servers in the same pass" not in store
|
||||
|
||||
|
||||
def test_whats_new_startup_trigger_is_version_gated():
|
||||
content = (
|
||||
PROJECT_ROOT / "plugins/_whats_new/extensions/webui/initFw_end/whats-new.js"
|
||||
).read_text(encoding="utf-8")
|
||||
|
||||
assert "globalThis.gitinfo?.version" in content
|
||||
assert "a0_whats_new_seen_version" in content
|
||||
assert "/plugins/_whats_new/webui/whats-new.html" in content
|
||||
assert "compareVersions" in content
|
||||
assert "shouldShowWhatsNew" in content
|
||||
assert "modal-closed" in content
|
||||
assert "markVersionSeen" in content
|
||||
assert "Don't show this again" not in content
|
||||
|
||||
|
||||
def test_whats_new_plugin_manifest_is_always_enabled():
|
||||
manifest = (PROJECT_ROOT / "plugins/_whats_new/plugin.yaml").read_text(encoding="utf-8")
|
||||
|
||||
assert "name: _whats_new" in manifest
|
||||
assert "always_enabled: true" in manifest
|
||||
assert "settings_sections: []" in manifest
|
||||
Loading…
Add table
Add a link
Reference in a new issue