From a Flash run: after report(done) it showed a before and after and asked whether the user is happy, and both went into a new task. A panel, question or note within 5 minutes of the AI's own report(done) now opens that task again (unless the user started or wrote to another); a look still leaves it ended. "Just done" now means ended by a report, not that the report is the last step. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
58 lines
2.8 KiB
Python
58 lines
2.8 KiB
Python
"""Select a task to resume from unread user input, without changing task state or ownership;
|
|
whether a look belongs to the task just done; how many of a task's failures still stand.
|
|
|
|
Called under ai_tasks' lock when a caller has no recent open task. Explicit task selection
|
|
and the user's task waiting for the next AI take precedence in ai_tasks.
|
|
"""
|
|
|
|
REPLIES = {"user_message", "user_marks", "user_pictures", "user_toolbox"}
|
|
REPLY_FRESH = 2 * 3600
|
|
AFTER_DONE = 5 * 60 # a look this soon after the AI said its task is done is part of that task, not a new one
|
|
|
|
|
|
def _reply_at(task, now):
|
|
"""Latest unread user input; a tool still running keeps its task from automatic takeover."""
|
|
if any(step.get("state") == "running" for step in task["steps"]):
|
|
return 0
|
|
replies = []
|
|
for step in task["steps"]:
|
|
if step.get("kind") == "user" and step.get("tool") in REPLIES:
|
|
replies.append(step)
|
|
elif step.get("kind") == "panel":
|
|
replies.extend((step.get("answers") or {}).values())
|
|
return max((r.get("at", 0) for r in replies if not r.get("delivered") and not r.get("recommended_on_timeout")
|
|
and 0 <= now - r.get("at", 0) < REPLY_FRESH), default=0)
|
|
|
|
|
|
def latest_replied(tasks, client, now):
|
|
"""The task of this app with the latest eligible reply, or None."""
|
|
candidates = ((at, task) for task in tasks.values() if task.get("client") == client and (at := _reply_at(task, now)))
|
|
_, task = max(candidates, key=lambda item: item[0], default=(0, None))
|
|
return task
|
|
|
|
|
|
def just_done(task, tasks, waiting, client, now):
|
|
"""`task` (the app's last one) when it said it is done (report done) moments ago: a look the AI
|
|
takes right after (checking its result) goes there, the task staying ended, and what it shows or
|
|
asks the user about it (a before and after, "happy with it?") opens it again; no new task is made
|
|
for either. None when the user started a task for the next AI (`waiting`) or wrote to one."""
|
|
if not task or not task.get("ended") or not 0 <= now - task["ended"] < AFTER_DONE:
|
|
return None
|
|
if not any(step.get("tool") == "report" and step.get("at") == task["ended"] for step in task["steps"]):
|
|
return None # it ended by a pause or another task, not by the AI saying it is done
|
|
if (waiting and not waiting.get("ended")) or latest_replied(tasks, client, now):
|
|
return None
|
|
return task
|
|
|
|
|
|
def unresolved(steps):
|
|
"""Failed steps the AI did not get past: no later call of the same tool went through (a wrong
|
|
argument corrected on the next call, a run refused and then run, is no failure of the task)."""
|
|
later_ok, count = set(), 0
|
|
for step in reversed(steps):
|
|
if step.get("ok"):
|
|
later_ok.add(step.get("tool"))
|
|
elif step.get("tool") not in later_ok:
|
|
count += 1
|
|
return count
|