feat: AIUI chat mode integration with iframe, context broker, overnight loop

- Chat mode: AIUI loads in sandboxed iframe at /dashboard/chat with transparent bg
- Mode switcher: Easy + Pro tabs only, Chat is a launcher button
- Keyboard shortcuts: Cmd+1 (Easy), Cmd+2 (Pro), Cmd+3 (Chat), Cmd+M (cycle)
- Directional transitions: chat slides from/to left, dashboard from/to right
- Context broker: postMessage protocol for quarantined AIUI communication
- AI permissions store: user-controlled toggles for data access categories
- Settings UI: AI Data Access section with per-category toggles
- AIUI container manifest and nginx proxy config for /aiui/
- Deploy script builds AIUI with /aiui/ base path
- Overnight loop infrastructure (loop.sh, prepare.sh, plan.md, prompt.md)
- Security hooks for autonomous overnight runs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-04 12:06:20 +00:00
parent 7b044d22ef
commit 584ce646e1
23 changed files with 1528 additions and 77 deletions

View File

@@ -0,0 +1,106 @@
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import type { AIContextCategory } from '@/types/aiui-protocol'
const STORAGE_KEY = 'archipelago-ai-permissions'
export interface AIPermissionCategory {
id: AIContextCategory
label: string
description: string
icon: string
}
export const AI_PERMISSION_CATEGORIES: AIPermissionCategory[] = [
{
id: 'apps',
label: 'Installed Apps',
description: 'App names, status, and health — no credentials or config details',
icon: 'M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z',
},
{
id: 'system',
label: 'System Stats',
description: 'CPU, RAM, disk usage — no file paths or IP addresses',
icon: 'M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z',
},
{
id: 'network',
label: 'Network Status',
description: 'Connection status, peer count — no IP addresses or keys',
icon: 'M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 00-2 2v4a2 2 0 002 2h14a2 2 0 002-2v-4a2 2 0 00-2-2m-2-4h.01M17 16h.01',
},
{
id: 'wallet',
label: 'Wallet Overview',
description: 'Balance, channel count — no private keys, seeds, or addresses',
icon: 'M17 9V7a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2m2 4h10a2 2 0 002-2v-6a2 2 0 00-2-2H9a2 2 0 00-2 2v6a2 2 0 002 2zm7-5a2 2 0 11-4 0 2 2 0 014 0z',
},
{
id: 'files',
label: 'File Names',
description: 'Folder and file names in Cloud — no file contents',
icon: 'M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z',
},
]
export const useAIPermissionsStore = defineStore('aiPermissions', () => {
const enabled = ref<Set<AIContextCategory>>(loadFromStorage())
function loadFromStorage(): Set<AIContextCategory> {
try {
const stored = localStorage.getItem(STORAGE_KEY)
if (stored) {
const parsed = JSON.parse(stored) as AIContextCategory[]
return new Set(parsed.filter(c => AI_PERMISSION_CATEGORIES.some(cat => cat.id === c)))
}
} catch {
// ignore
}
return new Set()
}
function save() {
localStorage.setItem(STORAGE_KEY, JSON.stringify([...enabled.value]))
}
function isEnabled(category: AIContextCategory): boolean {
return enabled.value.has(category)
}
function toggle(category: AIContextCategory) {
if (enabled.value.has(category)) {
enabled.value.delete(category)
} else {
enabled.value.add(category)
}
// Trigger reactivity
enabled.value = new Set(enabled.value)
save()
}
function enableAll() {
enabled.value = new Set(AI_PERMISSION_CATEGORIES.map(c => c.id))
save()
}
function disableAll() {
enabled.value = new Set()
save()
}
const enabledCategories = computed(() => [...enabled.value])
const allEnabled = computed(() => enabled.value.size === AI_PERMISSION_CATEGORIES.length)
const noneEnabled = computed(() => enabled.value.size === 0)
return {
enabled,
isEnabled,
toggle,
enableAll,
disableAll,
enabledCategories,
allEnabled,
noneEnabled,
}
})

View File

@@ -25,9 +25,17 @@ export const useUIModeStore = defineStore('uiMode', () => {
localStorage.setItem(STORAGE_KEY, newMode)
}
function cycleMode(): UIMode {
const order: UIMode[] = ['easy', 'gamer']
const idx = order.indexOf(mode.value)
const next = order[(idx >= 0 ? idx + 1 : 0) % order.length] as UIMode
setMode(next)
return next
}
const isGamer = computed(() => mode.value === 'gamer')
const isEasy = computed(() => mode.value === 'easy')
const isChat = computed(() => mode.value === 'chat')
return { mode, setMode, syncFromBackend, isGamer, isEasy, isChat }
return { mode, setMode, cycleMode, syncFromBackend, isGamer, isEasy, isChat }
})