From 073352b21e55a922393546e6013b1dd12c8f9aa8 Mon Sep 17 00:00:00 2001 From: Gmer4Lfe Date: Sun, 9 Aug 2026 00:16:09 -0400 Subject: [PATCH] Put the Scheduler dock on the same conversation store as everything else --- Plugin/unraid/api/ai.php | 8 +++- Plugin/unraid/include/ai.php | 10 ++++- Plugin/unraid/include/ai_chat.php | 70 ++++++++++++++++++++++++++++--- Plugin/unraid/pages/scheduler.php | 26 +++++++++++- 4 files changed, 105 insertions(+), 9 deletions(-) diff --git a/Plugin/unraid/api/ai.php b/Plugin/unraid/api/ai.php index a42d44e..1167db0 100644 --- a/Plugin/unraid/api/ai.php +++ b/Plugin/unraid/api/ai.php @@ -256,7 +256,13 @@ if ($action === 'chat_save') { $cap = vv_ai_profiles_max_turns() * 2; if (count($clean) > $cap) $clean = array_slice($clean, -$cap); - $r = vv_ai_chat_save(trim($_POST['id'] ?? ''), $profile, $clean); + // Whitelisted exactly as ask's is, and for the same reason: a scope is only ever a name from + // a page's own view state, it is stored and later replayed into a prompt, and anything + // richer than a file name is an instruction-injection surface for no benefit. + $scope = trim($_POST['scope'] ?? ''); + if ($scope !== '' && !vv_ai_scope_ok($scope)) $scope = ''; + + $r = vv_ai_chat_save(trim($_POST['id'] ?? ''), $profile, $clean, $scope); vv_ai_log('chat_save ' . ($r['ok'] ? 'ok id=' . substr($r['id'], 0, 12) : 'FAILED: ' . $r['error'])); echo json_encode($r); diff --git a/Plugin/unraid/include/ai.php b/Plugin/unraid/include/ai.php index 9b8c10d..753faaa 100644 --- a/Plugin/unraid/include/ai.php +++ b/Plugin/unraid/include/ai.php @@ -1207,6 +1207,7 @@ function vv_ai_chats_list(): array { 'id' => $d['id'], 'ts' => (int)($d['ts'] ?? 0), 'profile' => (string)($d['profile'] ?? 'chat'), + 'scope' => (string)($d['scope'] ?? ''), 'title' => (string)($d['title'] ?? 'Untitled conversation'), 'turns' => (int)($d['turns'] ?? count($d['messages'] ?? [])), ]; @@ -1244,7 +1245,7 @@ function vv_ai_chats_prune(?int $max = null): int { // Writes a whole conversation. An empty id mints one; a known id overwrites in place, which is // what makes a continued conversation stay one row in the list instead of breeding a new one per // turn. Written to a temp file and renamed, so a reader never sees half a transcript. -function vv_ai_chat_save(string $id, string $profile, array $messages): array { +function vv_ai_chat_save(string $id, string $profile, array $messages, string $scope = ''): array { if (!$messages) return ['ok' => false, 'error' => 'Nothing to save']; if ($id === '') $id = bin2hex(random_bytes(16)); @@ -1257,12 +1258,19 @@ function vv_ai_chat_save(string $id, string $profile, array $messages): array { $prev = vv_ai_chat_read($id); $created = (int)($prev['created'] ?? time()); + // Scope travels with the conversation. A Scheduler dock thread is bound to what the operator + // had open — a script, a log, a conf key — and its turns carry log excerpts chosen for that + // thing. Storing the scope means reopening the thread anywhere restores the context it was + // reasoned in, rather than silently continuing a troubleshooting conversation against + // whatever happens to be on screen. The isolation the dock enforces in memory becomes a + // property of the record instead of something lost the moment it is saved. $rec = [ 'id' => $id, 'created' => $created, 'ts' => $created, 'updated' => time(), 'profile' => $profile, + 'scope' => $scope, 'title' => vv_ai_chat_title($messages), 'turns' => (int)floor(count($messages) / 2), 'messages' => $messages, diff --git a/Plugin/unraid/include/ai_chat.php b/Plugin/unraid/include/ai_chat.php index 788893d..7d5f44d 100644 --- a/Plugin/unraid/include/ai_chat.php +++ b/Plugin/unraid/include/ai_chat.php @@ -70,10 +70,51 @@ require_once __DIR__ . '/ai_profiles.php'; // Emitted once even if two instances are rendered. A second copy of the script would re-register // the factories harmlessly but would also install a second Escape handler and a second copy of // every keyframe, so the guard is cheaper than reasoning about whether it matters. +// The conversation store, on its own, with no styling and no chat widget attached. +// +// Emitted separately because the surface that most needs it is the one that does not want the +// rest: the Scheduler dock draws its own one-line bar, with its own scope chip and fix flow, and +// pulling in the full transcript stylesheet to reach a save function would restyle a component +// that was deliberately built to look different. +// +// Where a conversation lives is not a presentation decision, so it gets one answer for every +// surface. Anything added to the store — a new field, a new cap, a changed prune rule — arrives +// everywhere at once because there is only one writer. +function vv_ai_chat_store_script(): void { + static $done = false; + if ($done) return; + $done = true; + ?> + +