Vest: the bot activates a passed evaluation's funded account, and a passed one no longer holds the sync

A passed Vest evaluation sits at GOAL_PASSED (status 3) until someone presses
"Go to funded account", which sends POST /v3/capital/activate-funded
{eval_account_id} on the evaluation's own token (read from the Vest app's
bundle, 2026-10-29); Vest then opens the funded account and marks the
evaluation CLAIMED (6). The 5 evaluations that passed on 2026-10-29 waited
for a hand activation.

runActivations now asks for it, the way runClaims claims: from the
scheduler's tick (not awaited, so a slow ask doesn't hold the lane's entry)
and from the claim watcher while the scheduler is off, for evaluations with
an account type only, 15 minutes apart after a failure. Off with the
auto_activate setting at 'off' (dashboard: "Auto-activate funded"). The
client asks for an evaluation once at a time, never again after Vest
answered, and after a timeout not until a list read a minute on says either
way; a refusal frees it at once. The rule is the Vest app's own: an
evaluation at GOAL_PASSED, not blocked, on its plan's last step.

Vest's active balance list leaves a passed evaluation out, and a missing
balance made every sync short of good (15:31-15:34Z), which holds and then
quarantines the lane. Only a live account's missing balance does now. Log
lines name Vest's statuses.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Brandon Li
2026-10-29 12:09:49 -05:00
co-authored by Claude Opus 5.5
parent 0eb56b3949
commit 8a5b2a64e7
8 changed files with 380 additions and 18 deletions
+25 -1
View File
@@ -744,6 +744,7 @@ export default function Home() {
const [tradeLoading, setTradeLoading] = useState(false);
const [stopAfterAll, setStopAfterAll] = useState(false);
const [autoClaim, setAutoClaim] = useState(true);
const [autoActivate, setAutoActivate] = useState(true);
const handleSort = (col: SortKey) => {
if (sortKey === col) {
@@ -813,9 +814,10 @@ export default function Home() {
fetch('/api/settings')
.then((r) => r.json())
.then((s: { tick_interval_seconds?: string | null; auto_claim?: string | null }) => {
.then((s: { tick_interval_seconds?: string | null; auto_claim?: string | null; auto_activate?: string | null }) => {
if (s.tick_interval_seconds != null) setTickInterval(s.tick_interval_seconds);
setAutoClaim(s.auto_claim !== 'off');
setAutoActivate(s.auto_activate !== 'off');
})
.catch(() => {});
@@ -1024,6 +1026,28 @@ export default function Home() {
<span className="text-xs text-slate-400 whitespace-nowrap">Auto-claim payouts</span>
</label>
)}
{config.some((f) => f.platform === 'vest') && (
<label
className="flex items-center gap-1.5 ml-2 cursor-pointer select-none"
title="Activate a passed evaluation's funded account as soon as it passes (Vest) — whether or not the scheduler is running"
>
<input
type="checkbox"
checked={autoActivate}
onChange={(e) => {
const on = e.target.checked;
setAutoActivate(on);
fetch('/api/settings', {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ auto_activate: on ? 'on' : 'off' }),
}).then((r) => { if (!r.ok) setAutoActivate(!on); }).catch(() => setAutoActivate(!on));
}}
className="rounded border-slate-300 text-emerald-500 focus:ring-emerald-500"
/>
<span className="text-xs text-slate-400 whitespace-nowrap">Auto-activate funded</span>
</label>
)}
<div className="ml-auto flex items-center gap-2">
{hasAnyPosition && (
<button