feat(openclaw): upgrade fleet to DeepSeek V4.1 Flash 1017 and harden operations

- Upgrade OpenClaw primary model from deepseek-v4.1-flash to deepseek-v4.1-flash-1017 across all interactive agents, heartbeats, compaction, and cron jobs.
- Configure hardened OpenRouter provider routing for 1017 (deepinfra, inference-net, parasail, cloudflare) with strict fp8/bf16 quantization and tool_choice verification.
- Retain deepseek-v4.1-flash (0910), z-ai/glm-5.3-flash, and qwen3.8-flash in the automated fallback chain.
- Update ADR 0002 with 1017 economics (30% price reduction on prompt/cache, +60% intelligence benchmark increase) and a 7-point provider qualification playbook.
- Add operational scripts and tooling for session compaction, subagent vector deduplication, gateway preflight, and triage diagnostics in k8s/openclaw.
- Update DuckDB backend engine, git-sync logic, and test suites.
- Enhance Mila tracking UI components and add container build configuration.
- Sync Terraform infrastructure state for Authentik.
This commit is contained in:
Moritz Graf
2026-10-18 10:54:59 +02:00
parent 7ce1da18ab
commit dd60a095c6
26 changed files with 1786 additions and 1301 deletions
+4
View File
@@ -0,0 +1,4 @@
node_modules
dist
.git
.DS_Store
+69 -3
View File
@@ -178,7 +178,16 @@ const liveCounterStr = computed(() => {
// Wake Window status evaluation (sweet spot 135 to 180 min, i.e. 2h15m to 3h00m)
const wakeWindowStatus = computed(() => {
if (!data.value || data.value.live_status.is_sleeping) return null
const mins = data.value.live_status.wake_window_minutes ?? 0
const ls = data.value.live_status
let mins = ls.wake_window_minutes ?? 0
if (ls.last_wake_iso) {
try {
const wakeTime = new Date(ls.last_wake_iso).getTime()
if (!isNaN(wakeTime)) {
mins = Math.max(0, Math.floor((nowEpoch.value - wakeTime) / 60000))
}
} catch {}
}
if (mins < 135) {
return {
text: 'Entspannt wach',
@@ -255,8 +264,21 @@ const lastFeedDetail = computed(() => {
const diaperStatus = computed(() => {
if (!data.value) return null
const ls = data.value.live_status
const elapsed = ls.last_diaper_since_minutes ?? (ls as any).last_diaper_elapsed_minutes
const elapsedStool = (ls as any).last_stool_elapsed_minutes
let elapsed = ls.last_diaper_since_minutes ?? (ls as any).last_diaper_elapsed_minutes
let elapsedStool = (ls as any).last_stool_elapsed_minutes
if ((ls as any).last_diaper_iso) {
try {
const t = new Date((ls as any).last_diaper_iso).getTime()
if (!isNaN(t)) elapsed = Math.max(0, Math.floor((nowEpoch.value - t) / 60000))
} catch {}
}
if ((ls as any).last_stool_iso) {
try {
const t = new Date((ls as any).last_stool_iso).getTime()
if (!isNaN(t)) elapsedStool = Math.max(0, Math.floor((nowEpoch.value - t) / 60000))
} catch {}
}
const formatElapsed = (mins: number | null | undefined) => {
if (mins === null || mins === undefined) return null
@@ -549,6 +571,50 @@ const currentAgeMonths = computed(() => {
</button>
<div v-show="isTodayTimelineOpen" class="space-y-4 pt-2 border-t border-zinc-800/60">
<!-- Night Sleep Breakdown (if started today) -->
<div v-if="data.today.nights && data.today.nights.length > 0" class="space-y-2">
<div class="text-xs font-bold text-indigo-400 uppercase tracking-wider flex items-center gap-1.5">
<span>🌙</span>
<span>Nachtschlaf ({{ data.today.nights.length }} erfasst)</span>
</div>
<div class="space-y-2">
<div
v-for="(ns, idx) in data.today.nights"
:key="ns.id || idx"
class="flex items-center justify-between p-3 rounded-xl bg-zinc-900/60 border border-zinc-800/60 hover:border-indigo-500/30 transition text-xs sm:text-sm"
>
<div class="flex items-center gap-3">
<div class="w-6 h-6 rounded-lg bg-indigo-500/10 border border-indigo-500/20 flex items-center justify-center text-indigo-400 font-mono text-xs font-bold">
🌙
</div>
<div>
<div class="font-semibold text-zinc-200 flex items-center gap-2">
<span>{{ ns.start }} – {{ ns.end || 'schläft gerade' }} Uhr</span>
<span v-if="ns.is_open" class="w-2 h-2 rounded-full bg-indigo-400 animate-pulse"></span>
</div>
<div class="text-[11px] text-zinc-400">
<span v-if="ns.location">({{ ns.location }})</span>
<span v-if="ns.note"> · {{ ns.note }}</span>
</div>
</div>
</div>
<div class="flex items-center gap-2">
<span class="font-bold text-indigo-400 font-mono">
{{ ns.duration_minutes ? `${ns.duration_minutes} min` : 'in Ausführung' }}
</span>
<button
v-if="ns.id"
@click="handleDeleteItem(ns.id)"
title="Nachtschlaf löschen"
class="p-1 rounded-lg text-zinc-500 hover:text-red-400 hover:bg-zinc-800 transition"
>
<Trash2 class="w-3.5 h-3.5" />
</button>
</div>
</div>
</div>
</div>
<!-- Naps Breakdown -->
<div class="space-y-2">
<div class="text-xs font-bold text-emerald-400 uppercase tracking-wider flex items-center gap-1.5">
+75 -21
View File
@@ -57,7 +57,10 @@ const bars = computed(() => {
let nightHours = 0
let napHours = 0
if (day.sessions && day.sessions.length > 0) {
if (typeof day.night_minutes === 'number' || typeof day.nap_minutes === 'number') {
nightHours = Math.max(0, (day.night_minutes || 0) / 60)
napHours = Math.max(0, (day.nap_minutes || 0) / 60)
} else if (day.sessions && day.sessions.length > 0) {
for (const s of day.sessions) {
const mins = s.duration_minutes || 0
if (s.type === 'night') {
@@ -71,7 +74,7 @@ const bars = computed(() => {
napHours = Math.max(0, day.total_hours - nightHours)
}
const totalHours = Math.max(0, day.total_hours)
const totalHours = Math.max(0, day.total_hours, nightHours + napHours)
const yTotal = getY(totalHours)
const yNight = getY(nightHours)
const nightHeight = Math.max(0, (paddingTop + innerHeight) - yNight)
@@ -172,6 +175,59 @@ function getMinuteFromTime(timeStr: string): number {
return parseInt(parts[0], 10) * 60 + parseInt(parts[1], 10)
}
interface RasterBlock {
startMin: number
durationMin: number
type: 'night' | 'nap'
label: string
title: string
}
function getDay24hBlocks(day: HistoryRow, dayIdx: number): RasterBlock[] {
const blocks: RasterBlock[] = []
// 1. Check previous calendar day (props.history[dayIdx + 1]) for overnight sleep spilling into 00:00..wake_time
const prevDay = props.history[dayIdx + 1]
if (prevDay?.sessions) {
for (const s of prevDay.sessions) {
if (!s.start) continue
const sMin = getMinuteFromTime(s.start)
const dur = s.duration_minutes || 60
if (sMin + dur > 1440) {
const morningMin = Math.min(1440, (sMin + dur) - 1440)
if (morningMin > 0) {
blocks.push({
startMin: 0,
durationMin: morningMin,
type: s.type === 'night' ? 'night' : 'nap',
label: `${morningMin}m`,
title: `Nachtschlaf (Vorabend ${s.start} – ${s.end || 'morgens'}): ${morningMin} min nach 00:00 Uhr (${s.duration_minutes} min gesamt)`,
})
}
}
}
}
// 2. Add sessions starting on this calendar day (clamped to 24:00)
if (day.sessions) {
for (const s of day.sessions) {
if (!s.start) continue
const sMin = getMinuteFromTime(s.start)
const dur = s.duration_minutes || 60
const clampedDur = Math.max(1, Math.min(dur, 1440 - sMin))
blocks.push({
startMin: sMin,
durationMin: clampedDur,
type: s.type === 'night' ? 'night' : 'nap',
label: `${clampedDur}m`,
title: `${s.type === 'night' ? 'Nachtschlaf' : 'Nap'}: ${s.start}–${s.end || 'jetzt'} (${s.duration_minutes} min)`,
})
}
}
return blocks
}
function handlePointer(event: MouseEvent | TouchEvent) {
const svg = (event.currentTarget as HTMLElement).getBoundingClientRect()
const clientX = 'touches' in event ? event.touches[0].clientX : event.clientX
@@ -474,7 +530,7 @@ function handlePointer(event: MouseEvent | TouchEvent) {
<!-- Raster Bars -->
<div class="space-y-1.5">
<div
v-for="day in rasterDays"
v-for="(day, dayIdx) in rasterDays"
:key="day.date"
class="flex items-center gap-2 group"
>
@@ -494,24 +550,22 @@ function handlePointer(event: MouseEvent | TouchEvent) {
</div>
<!-- Sessions Blocks -->
<template v-if="day.sessions && day.sessions.length > 0">
<template v-for="(s, sIdx) in day.sessions" :key="sIdx">
<!-- If valid start time -->
<div
v-if="s.start"
:style="{
left: `${(getMinuteFromTime(s.start) / 1440) * 100}%`,
width: `${Math.max(1.2, ((s.duration_minutes || 60) / 1440) * 100)}%`,
}"
:class="s.type === 'night' ? 'bg-indigo-600/90 border-indigo-400/50 text-indigo-100' : 'bg-emerald-500/90 border-emerald-300/50 text-emerald-100'"
class="absolute top-0 bottom-0 rounded-[3px] border border-opacity-40 flex items-center justify-center text-[9px] font-mono font-bold overflow-hidden cursor-help"
:title="`${s.type === 'night' ? 'Nachtschlaf' : 'Nap'}: ${s.start}–${s.end || 'jetzt'} (${s.duration_minutes} min)`"
>
<span v-if="(s.duration_minutes || 0) >= 45" class="truncate px-0.5">
{{ s.duration_minutes }}m
</span>
</div>
</template>
<template v-if="getDay24hBlocks(day, dayIdx).length > 0">
<div
v-for="(b, bIdx) in getDay24hBlocks(day, dayIdx)"
:key="bIdx"
:style="{
left: `${(b.startMin / 1440) * 100}%`,
width: `${Math.max(1.2, (b.durationMin / 1440) * 100)}%`,
}"
:class="b.type === 'night' ? 'bg-indigo-600/90 border-indigo-400/50 text-indigo-100' : 'bg-emerald-500/90 border-emerald-300/50 text-emerald-100'"
class="absolute top-0 bottom-0 rounded-[3px] border border-opacity-40 flex items-center justify-center text-[9px] font-mono font-bold overflow-hidden cursor-help"
:title="b.title"
>
<span v-if="b.durationMin >= 45" class="truncate px-0.5">
{{ b.label }}
</span>
</div>
</template>
<div v-else class="text-[10px] text-zinc-600 italic px-2 py-0.5">Keine Einzelschläfchen</div>
</div>
+2
View File
@@ -36,6 +36,8 @@ export interface HistoryRow {
formatted_date: string
is_today: boolean
total_hours: number
night_minutes?: number
nap_minutes?: number
night_duration_str: string
nap_str: string
feed_bottles_ml: number