fix(agent+ui): whatsapp session audit — approvals, stuck indicator, stale execs
P1: add docker compose (logs|ps|top|config|images|port|cp) to read-only allowlist. docker compose logs was classified as config_mutation, causing individual approval cards for read-only inspection commands. P2: remove approval entries from activityLog. They were always status=running and never transitioned to done (the derived store builds from tool-call text, not execution status), causing AgentIndicator to latch onto a stale 'Approval: ...' entry and never clear — even after the session completed. P3: remove InlineApproval from Chat.svelte. The green 'Completed in 1s on lxc:...' boxes were noise in the chat stream. Approval UX belongs in the Operations page (already has it via Ops.svelte), not inline in the chat. P4: stale execution cleanup. Startup sweep (mark >1hr non-terminal as cancelled) + 5-min periodic sweep (mark >10min non-terminal as cancelled). 98 orphaned executions accumulated from eval testing (39 running from apt_upgrade:audit timeouts, 19 pending_approval, 3 approved). P5: refuse second config_mutation run when an approval is already pending for the session. Without this, the agent queues N individual approvals before the operator can respond — confirmed in session 20757eb9 (two approval cards for what should have been one plan-level approval). VERSION 0.7.0 → 0.7.1
This commit is contained in:
@@ -8,8 +8,7 @@ export interface ActivityEntry {
|
||||
id: string
|
||||
type: 'goal' | 'plan' | 'step_running' | 'step_done' | 'step_failed' |
|
||||
'tool_running' | 'tool_done' | 'tool_error' |
|
||||
'knowledge' | 'complete' | 'question' | 'error' |
|
||||
'approval'
|
||||
'knowledge' | 'complete' | 'question' | 'error'
|
||||
description: string
|
||||
detail?: string
|
||||
timestamp: number
|
||||
@@ -128,26 +127,14 @@ export const activityLog = derived([messages, planSteps, currentTask], ([$msgs,
|
||||
})
|
||||
}
|
||||
|
||||
// Approvals — detect from tool results containing 'requires approval'
|
||||
for (let mi = 0; mi < $msgs.length; mi++) {
|
||||
for (const t of $msgs[mi].tools) {
|
||||
if (t.type !== 'tool_result') continue
|
||||
const text = typeof t.result === 'string' ? t.result : JSON.stringify(t.result ?? '')
|
||||
if (text.includes('requires approval')) {
|
||||
const m = text.match(/execution\s+([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/i)
|
||||
const execId = m ? m[1] : ''
|
||||
const target = (t.args as any)?.target ?? ''
|
||||
const purpose = (t.args as any)?.purpose ?? ''
|
||||
entries.push({
|
||||
id: execId || `approval_${mi}`,
|
||||
type: 'approval',
|
||||
description: purpose ? `Approval: ${purpose.slice(0, 60)}` : `Approval required${target ? ` for ${target}` : ''}`,
|
||||
timestamp: now - ($msgs.length - mi) * 1000,
|
||||
status: 'running'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
// Note: approval entries were removed from activityLog (2026-07-15).
|
||||
// They were always `status: 'running'` and never transitioned to 'done'
|
||||
// (the derived store builds from tool-call text, not execution status),
|
||||
// which caused the AgentIndicator to latch onto a stale "Approval: ..."
|
||||
// entry and never clear — even after the session completed. Approvals
|
||||
// are tracked via the REST /approvals endpoint (context.ts, Ops.svelte)
|
||||
// and rendered as InlineApproval cards in the chat (or Ops page), not
|
||||
// in the activity log.
|
||||
|
||||
// Sort oldest first
|
||||
entries.sort((a, b) => a.timestamp - b.timestamp)
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import { activityLog } from '$lib/stores/activity'
|
||||
import SessionRail from '$lib/components/SessionRail.svelte'
|
||||
import TaskContextPanel from '$lib/components/TaskContextPanel.svelte'
|
||||
import InlineApproval from '$lib/components/InlineApproval.svelte'
|
||||
import AgentIndicator from '$lib/components/AgentIndicator.svelte'
|
||||
import { Button } from '$lib/components/ui/button'
|
||||
import { Textarea } from '$lib/components/ui/textarea'
|
||||
@@ -141,9 +140,6 @@
|
||||
{@html render(msg.text)}
|
||||
</div>
|
||||
{/if}
|
||||
{#if msg.pendingApprovals.length > 0}
|
||||
<InlineApproval approvals={msg.pendingApprovals} />
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user