Files
DemonGatanjieu-Anomalous_Mo…/api/ai_task_changes.py
T
DemonGatanjieuandClaude Opus 5.5 ac2aa471e9 AI workbench: pictures deleted since say so
A task's pictures no longer in the output folder are marked gone when the
task is read and shown as "Picture deleted" (timeline, results strip,
stage panels and picks) instead of a broken picture; "@" no longer offers
them. The AI hears plainly that the user deleted one, and a tool reading a
gone picture says it may have been deleted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 15:56:44 +08:00

63 lines
3.1 KiB
Python

"""The user's own changes in Anomalous while an AI works on a task: each activity log entry the
user makes (activity_log.py tells note_activity of every new entry) becomes a step of the task
the AI called within USER_STEP_SECONDS, and the entry names that task back. The AI is told of it
with its next call (ai_panels.attach_pending, a message of kind "change"), so it keeps the user's
change instead of working over it. The AI's own canvas changes (via mcp) are its steps already;
which actions count, and their stage, is action_catalog.USER_ACTIONS."""
import time
import uuid
from . import action_catalog, activity_log, ai_tasks
USER_STEP_SECONDS = 10 * 60
def _live_task(now):
"""The task an AI is working on now (it called within USER_STEP_SECONDS), or None."""
live = [task for task in ai_tasks._load().values() if not task.get("ended") and not task.get("waiting")
and now - task.get("updated", 0) < USER_STEP_SECONDS]
return max(live, key=lambda task: task.get("updated", 0)) if live else None
def _change_text(entry):
"""What the user changed, in words for the AI."""
if entry.get("action") == "image_delete" and entry.get("target"):
return (f"Deleted the picture {entry['target']} (to the Recycle Bin): it is gone from the output folder, "
"so do not use, show or hand it in again; another picture is needed for what it was for.")
if entry.get("source") != "canvas":
words = str(entry.get("action", "")).replace("_", " ")
return f"{words}: {entry['target']}" if entry.get("target") else words
lines = []
for change in (entry.get("detail") or {}).get("changes") or []:
if change.get("kind") == "changed":
lines.append(f"{change.get('node')} {change.get('widget')}: {change.get('before')} -> {change.get('after')}")
elif change.get("kind") == "opened":
lines.append(f"opened the workflow {change.get('node')}")
else:
lines.append(f"{change.get('kind')} {change.get('node')}")
return "Changed on the canvas: " + "; ".join(lines[:8])
def note_activity(entry):
"""A new activity log entry: a step of the AI's task when the user made it while the AI worked."""
stage = action_catalog.USER_ACTIONS.get(entry.get("action"))
if stage is None or (entry.get("detail") or {}).get("via") == "mcp":
return None
at = entry.get("time") or time.time()
with ai_tasks._lock:
task = _live_task(at)
if not task:
return None
step = {"id": uuid.uuid4().hex[:10], "at": at, "ms": 0, "tool": "user_activity", "kind": "user", "stage": stage,
"ok": True, "state": "done", "summary": entry.get("action", ""),
"text": ai_tasks._short(_change_text(entry), ai_tasks.MAX_NOTE), "activity": entry["id"],
"delivered": False, "results": []}
# Appended without moving "updated": the user's own changes do not keep the AI's task open.
task["steps"].append(step)
del task["steps"][:-ai_tasks.MAX_STEPS]
ai_tasks._save(task)
activity_log.tag(entry["id"], task["id"])
ai_tasks._notify(task["id"])
return step