diff --git a/Plugin/unraid/pages/setup.php b/Plugin/unraid/pages/setup.php
index b5ac2fa..30bac92 100644
--- a/Plugin/unraid/pages/setup.php
+++ b/Plugin/unraid/pages/setup.php
@@ -37,6 +37,12 @@
// Step 2: auto-populate + guide + checklist.
// master.conf pull (for partner servers) lives in the checklist, not here.
+// The assistant is a bonus on this page, never a dependency: vv_ai_ui_on() is false on a node
+// with AI off or no model reachable, which is the normal state of the fresh install this wizard
+// exists to serve. The card simply is not rendered and setup proceeds exactly as before.
+require_once dirname(__DIR__) . '/include/ai_chat.php';
+if (vv_ai_ui_on()) vv_ai_chat_assets();
+
$detectedHostname = vv_get_hostname();
// ── Identity already on disk? ────────────────────────────────────────────────────────────────
@@ -313,6 +319,21 @@ hr.vv-hr { border: none; border-top: 1px solid #1e1e1e; margin: 22px 0; }
+
+
+
+ 'varaverk',
+ 'compact' => true,
+ 'title' => 'Setup assistant',
+ 'height' => '260px',
+ 'tall' => '460px',
+ 'empty' => 'Stuck on a step? Ask what it wants and why — "what is the API key for", '
+ . '"why does SSH need the other host", "what does populate actually do".',
+ ]); ?>
+
+
+
Skip →
@@ -443,6 +464,7 @@ function vvShowStep2(redirect, apiKey) {
_vvRedirect = redirect || '?tab=scheduler';
document.getElementById('vv-step1').style.display = 'none';
document.getElementById('vv-step2').style.display = 'block';
+ vvSetupAiInit(); // card is visible now, so it can measure itself correctly
if (apiKey && apiKey.ok) {
const btn = document.getElementById('vv-key-btn');
const status = document.getElementById('vv-key-status');
@@ -676,6 +698,47 @@ function vvStartPhase2Watch(sinceMs) {
_vvPartnerPoll = setInterval(check, 6000);
}
+// ── Setup assistant ───────────────────────────────────────────────────────────
+// Scoped to the step the operator is actually stuck on, so "what does this want" resolves without
+// them naming it. The worker turns a non-empty scope into a line saying what is open in the
+// WebGUI; the varaverk profile holds no scoped_log capability, so a checklist id never triggers a
+// log lookup and cannot produce a "log missing" note for something that was never a script.
+//
+// Re-read at send time, not captured: the checklist re-polls while the page is open and the step
+// they are on can change between opening the composer and pressing Ask.
+//
+// First not-ok, not-deferred item — deferred means "I have decided to skip this", which is not
+// where they are stuck. Falls back to the bare page when the list is clean or has not loaded.
+function vvSetupScope() {
+ const d = _vvLastChecklist;
+ if (!d || !Array.isArray(d.items)) return 'Setup';
+ const stuck = d.items.find(i => !i.ok && !i.deferred);
+ return stuck && stuck.id ? 'Setup/' + stuck.id : 'Setup';
+}
+
+// Mounted when Step 2 is revealed, not at parse time. This is the only page where the card starts
+// inside a display:none block, and a chat that measured itself while hidden would come up wrong
+// with nothing to correct it — the component carries no resize observer. Idempotent because
+// vvShowStep2 is reachable more than once.
+let _vvSetupAiUp = false;
+function vvSetupAiInit() {
+ if (_vvSetupAiUp) return;
+ if (typeof VvAiChat !== 'function') return;
+ if (!document.getElementById('vv-su-ai-chat')) return; // AI off — card was never rendered
+ _vvSetupAiUp = true;
+ VvAiChat({
+ prefix: 'vv-su-ai',
+ profile: 'varaverk',
+ // Pinned, for the same reason the Partnership card pins it: resuming whatever thread was last
+ // touched anywhere in the UI could land a first-run operator mid-way through someone else's
+ // Scheduler conversation, on a card with no picker to get back from.
+ resumeProfile: 'varaverk',
+ scope: vvSetupScope,
+ scopeLabel: 'Setup',
+ empty: 'Stuck on a step? Ask what it wants and why.',
+ });
+}
+
function vvLoadChecklist() {
fetch('/plugins/varaverk/api/checklist.php?_=' + Date.now())
.then(r => r.json()).then(d => {