Repository navigation
Clear stuck agent "Running" badge when the shell returns to its prompt #5129
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10984,6 +10984,9 @@ final class Workspace: Identifiable, ObservableObject { | |
| shellState: state | ||
| ) | ||
| } | ||
| if state == .promptIdle { | ||
| reconcileStuckAgentStatusOnPromptIdle(panelId: panelId) | ||
| } | ||
|
Comment on lines
+10987
to
+10989
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Locate and print the reconciliation method + PID-liveness helper
ast-grep --pattern $'func reconcileStuckAgentStatusOnPromptIdle(panelId: $_) {
$$$
}'
ast-grep --pattern $'func isAgentProcessAlive($_) -> $_ {
$$$
}'
# Inspect any full-collection iteration over status/PID maps inside the reconciliation path
rg -nP -C3 '\b(statusEntries|agentPIDs)\b\s*\.\s*(forEach|map|filter|keys|values|reduce|first|contains)\b' -g 'Sources/Workspace+PanelLifecycle.swift'Repository: manaflow-ai/cmux Length of output: 2538 🏁 Script executed: #!/bin/bash
set -euo pipefail
# 1) Find the actual implementation(s)
rg -n "reconcileStuckAgentStatusOnPromptIdle" -S Sources/Workspace+PanelLifecycle.swift
# 2) Print the full function body region around the match (up to a safe window)
# (We keep this bounded to avoid dumping huge files.)
match_line=$(rg -n "reconcileStuckAgentStatusOnPromptIdle" -S Sources/Workspace+PanelLifecycle.swift | head -n1 | cut -d: -f1)
start=$((match_line-40))
end=$((match_line+200))
sed -n "${start},${end}p" Sources/Workspace+PanelLifecycle.swift | nl -ba | sed 's/^/| /'
# 3) Within that region, look for full-collection scans over statusEntries/agentPIDs
# (restricted to the same region window)
sed -n "${start},${end}p" Sources/Workspace+PanelLifecycle.swift | \
rg -n "\b(statusEntries|agentPIDs|agentPIDPanelIdsByKey)\b.*\.(forEach|map|filter|keys|values|reduce|contains|first)\b"Repository: manaflow-ai/cmux Length of output: 198 🏁 Script executed: #!/bin/bash
set -euo pipefail
FILE="Sources/Workspace+PanelLifecycle.swift"
python3 - <<'PY'
import re
path = "Sources/Workspace+PanelLifecycle.swift"
with open(path, "r", encoding="utf-8") as f:
lines = f.readlines()
pat = re.compile(r'^\s*func\s+reconcileStuckAgentStatusOnPromptIdle\s*\(\s*panelId:\s*UUID\s*\)\s*->\s*Bool\s*{')
start = None
for i,l in enumerate(lines):
if pat.search(l):
start = i
break
if start is None:
# fallback: find any reconcileStuckAgentStatusOnPromptIdle line
for i,l in enumerate(lines):
if "reconcileStuckAgentStatusOnPromptIdle" in l and "(" in l:
start = i
break
if start is None:
print("Could not find function.")
raise SystemExit(1)
# brace counting from first '{' in the starting line
text = "".join(lines[start:])
# Find first '{' after start
m = re.search(r'{', lines[start])
if not m:
print("No opening brace on start line.")
raise SystemExit(1)
brace = 0
end = None
for j in range(start, len(lines)):
brace += lines[j].count('{')
brace -= lines[j].count('}')
if j > start and brace == 0:
end = j
break
if end is None:
end = min(start+250, len(lines)-1)
for k in range(start, end+1):
print(f"{k+1:6d}: {lines[k].rstrip()}")
# simple scan for collection-walk patterns in that range
joined = "\n".join(lines[start:end+1])
patterns = [
r'\bstatusEntries\b\s*\.\s*(values|keys)\b',
r'\bagentPIDs\b\s*\.\s*(keys|values)\b',
r'\bagentPIDPanelIdsByKey\b\s*\.\s*(keys|values)\b',
r'\.\s*filter\s*{',
r'\.\s*map\s*{',
r'\.\s*forEach\s*{',
r'\.\s*reduce\s*\(',
r'\bfor\s+\w+\s+in\s+(statusEntries|agentPIDs|agentPIDPanelIdsByKey)\b',
r'\bfor\s+\w+\s+in\s+.+(statusEntries|agentPIDs|agentPIDPanelIdsByKey).+',
]
print("\n--- MATCHES (within function range) ---")
for p in patterns:
if re.search(p, joined):
print(p)
PYRepository: manaflow-ai/cmux Length of output: 1981 🏁 Script executed: #!/bin/bash
set -euo pipefail
FILE="Sources/Workspace+PanelLifecycle.swift"
python3 - <<'PY'
import re
def extract_func(lines, name):
# locate line with func name
pat = re.compile(r'^\s*(private\s+)?func\s+'+re.escape(name)+r'\s*\(')
start=None
for i,l in enumerate(lines):
if pat.search(l):
start=i
break
if start is None:
print(f"NOT FOUND: {name}")
return
# find first '{' on/after start line
brace_start = None
for j in range(start, min(start+5, len(lines))):
if '{' in lines[j]:
brace_start=j
break
if brace_start is None:
# fallback: first '{' after start
txt="".join(lines[start:])
m=re.search(r'{', txt)
if not m:
print(f"NO OPEN BRACE for {name}")
return
# compute absolute index
# approximate by counting braces in chunk
brace=0
end=None
for j in range(start, len(lines)):
brace += lines[j].count('{')
brace -= lines[j].count('}')
if j>start and brace==0:
end=j
break
if end is None:
end=min(start+220, len(lines)-1)
for k in range(start, end+1):
print(f"{k+1:6d}: {lines[k].rstrip()}")
with open("Sources/Workspace+PanelLifecycle.swift","r",encoding="utf-8") as f:
lines=f.readlines()
for name in [
"hasAgentRuntime",
"clearAgentPID",
"clearAgentLifecycle",
"refreshTrackedAgentPorts",
]:
print("\n\n===== "+name+" =====")
extract_func(lines, name)
PYRepository: manaflow-ai/cmux Length of output: 1093 🏁 Script executed: #!/bin/bash
set -euo pipefail
FILE="Sources/Workspace+PanelLifecycle.swift"
# 1) Find the real name/location of the lifecycle clearer used by reconcileStuckAgentStatusOnPromptIdle
rg -n "clearAgentLifecycle" -S "$FILE"
# 2) Extract a larger window around clearAgentPID to capture its full body
start=$(rg -n "func clearAgentPID\(" -S "$FILE" | head -n1 | cut -d: -f1)
start=$((start-20))
end=$((start+120))
sed -n "${start},${end}p" "$FILE" | nl -ba | sed 's/^/| /'Repository: manaflow-ai/cmux Length of output: 340 🏁 Script executed: #!/bin/bash
set -euo pipefail
python3 - <<'PY'
import re
path="Sources/Workspace+PanelLifecycle.swift"
lines=open(path,'r',encoding='utf-8').read().splitlines()
def find_func(name):
# try common forms: func name( ... ) { OR private func name(
pat=re.compile(r'^\s*(private\s+)?func\s+'+re.escape(name)+r'\s*\(')
for i,l in enumerate(lines):
if pat.search(l):
return i
return None
def extract_func(start):
# find opening brace from start line onward
brace=None
for j in range(start, min(start+30,len(lines))):
if '{' in lines[j]:
brace=j
break
if brace is None:
brace=start
depth=0
end=None
for j in range(start, len(lines)):
depth += lines[j].count('{')
depth -= lines[j].count('}')
if j>start and depth==0:
end=j
break
if end is None: end=min(start+300,len(lines)-1)
for k in range(start,end+1):
print(f"{k+1:6d}: {lines[k]}")
return end
targets=[
"clearAgentPID",
"clearAgentLifecycle",
"clearAgentLifecycleStates",
]
for t in targets:
print("\n\n===== "+t+" =====")
idx=find_func(t)
if idx is None:
print("NOT FOUND:", t)
continue
extract_func(idx)
PYRepository: manaflow-ai/cmux Length of output: 291 🏁 Script executed: #!/bin/bash
set -euo pipefail
FILE="Sources/Workspace+PanelLifecycle.swift"
# Find likely definitions (not just call sites)
echo "== Definitions containing 'func clearAgentPID' =="
rg -n "func\s+clearAgentPID" -S "$FILE" || true
echo "== Definitions containing 'func clearAgentLifecycle' =="
rg -n "func\s+clearAgentLifecycle" -S "$FILE" || true
echo "== Definitions containing 'clearAgentLifecycle' near 'func' =="
rg -n "func[^{\n]*clearAgentLifecycle" -S "$FILE" || trueRepository: manaflow-ai/cmux Length of output: 257 🏁 Script executed: #!/bin/bash
set -euo pipefail
rg -n "clearAgentLifecycle\s*\(" -S . \
--glob '!**/node_modules/**' --glob '!**/build/**'
rg -n "clearAgentLifecycleStates\s*\(" -S . \
--glob '!**/node_modules/**' --glob '!**/build/**'Repository: manaflow-ai/cmux Length of output: 675 🏁 Script executed: #!/bin/bash
set -euo pipefail
python3 - <<'PY'
import re
def extract_func(path, name, start_hint=None):
lines=open(path,'r',encoding='utf-8').read().splitlines()
pat=re.compile(r'^\s*(private\s+)?func\s+'+re.escape(name)+r'\s*\(')
start=None
if start_hint is not None:
start=start_hint-1
if start is None:
for i,l in enumerate(lines):
if pat.search(l):
start=i
break
if start is None:
print(f"NOT FOUND {name} in {path}")
return
# find opening brace after start line
brace_found=False
for j in range(start, min(start+20,len(lines))):
if '{' in lines[j]:
brace_found=True
depth=0
end=None
for k in range(start, len(lines)):
depth += lines[k].count('{')
depth -= lines[k].count('}')
if k>start and depth==0:
end=k
break
if end is None:
end=min(start+250,len(lines)-1)
for k in range(start,end+1):
print(f"{k+1:6d}: {lines[k]}")
return
# fallback
print(f"NO BRACE BLOCK FOUND QUICKLY for {name} in {path}")
for k in range(start, min(start+80,len(lines))):
print(f"{k+1:6d}: {lines[k]}")
extract_func("Sources/Workspace.swift", "clearAgentLifecycle", start_hint=11010)
print("\n\n===== clearAgentLifecycleStates =====")
extract_func("Sources/Workspace.swift", "clearAgentLifecycleStates", start_hint=11025)
print("\n\n===== clearAgentPID (if present in Workspace.swift) =====")
# Locate by searching definitions
import subprocess, sys, os
PYRepository: manaflow-ai/cmux Length of output: 1189 Reconciliation is panel-scoped for mutations, but runtime-checking scans globally per orphan lifecycle key
Change 🤖 Prompt for AI Agents |
||
| #if DEBUG | ||
| cmuxDebugLog( | ||
| "surface.shellState workspace=\(id.uuidString.prefix(5)) " + | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isAgentProcessAliveaccesses no Workspace instance or class-level state — it's a pure POSIX probe. As aprivate staticon an@MainActorclass it inherits main-actor isolation, which prevents the compiler from calling it from non-isolated ornonisolated asynccontexts even though there's nothing actor-sensitive inside it. Marking itnonisolatedremoves that coupling, matches the cmux convention for pure static helpers, and lets the optimizer emit the call without an actor hop.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!