fix(server): apply lifecycle reply state optimistically (heartbeat lag)
The agent reply is authoritative for the action just taken; the fleet DB only updates on the next heartbeat (~10s), so the immediate refetch read a stale state and reverted the UI (Start -> still Stopped). Now apply the reply's state/uptime directly to the instance. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -68,8 +68,10 @@ export const useInstancesStore = defineStore('instances', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a lifecycle command to the current instance. Returns the agent's
|
* Send a lifecycle command to the current instance and apply the agent's
|
||||||
* reply (which carries the new state); refetches so the list reflects it.
|
* reply state OPTIMISTICALLY. The reply is authoritative for the action just
|
||||||
|
* taken; the fleet DB only catches up on the next heartbeat (~10s), so an
|
||||||
|
* immediate refetch would read a stale state and clobber the result.
|
||||||
* Throws on failure so the view can toast.
|
* Throws on failure so the view can toast.
|
||||||
*/
|
*/
|
||||||
async function lifecycle(action: LifecycleAction): Promise<Record<string, unknown>> {
|
async function lifecycle(action: LifecycleAction): Promise<Record<string, unknown>> {
|
||||||
@@ -78,13 +80,26 @@ export const useInstancesStore = defineStore('instances', () => {
|
|||||||
acting.value = action
|
acting.value = action
|
||||||
try {
|
try {
|
||||||
const res = await api.post<Record<string, unknown>>(`/instances/${id}/lifecycle`, { action })
|
const res = await api.post<Record<string, unknown>>(`/instances/${id}/lifecycle`, { action })
|
||||||
await fetchInstances()
|
applyReplyState(id, res)
|
||||||
return res
|
return res
|
||||||
} finally {
|
} finally {
|
||||||
acting.value = null
|
acting.value = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Update an instance's state/uptime from a lifecycle/status reply. */
|
||||||
|
function applyReplyState(id: string, res: Record<string, unknown>): void {
|
||||||
|
if ((res as { status?: string }).status !== 'success') return
|
||||||
|
const stateObj = (res as { state?: { state?: string } }).state
|
||||||
|
const newState = stateObj?.state
|
||||||
|
const inst = instances.value.find((i) => i.id === id)
|
||||||
|
if (inst && typeof newState === 'string') {
|
||||||
|
inst.state = newState
|
||||||
|
const up = (res as { uptime_seconds?: number }).uptime_seconds
|
||||||
|
inst.uptime_seconds = typeof up === 'number' ? up : newState === 'running' ? inst.uptime_seconds : 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function rcon(command: string): Promise<Record<string, unknown>> {
|
async function rcon(command: string): Promise<Record<string, unknown>> {
|
||||||
const id = currentId.value
|
const id = currentId.value
|
||||||
if (!id) throw new Error('No instance selected')
|
if (!id) throw new Error('No instance selected')
|
||||||
|
|||||||
Reference in New Issue
Block a user