Files
DemonGatanjieu-Anomalous_Mo…/api/mcp_situation.py
T
DemonGatanjieuandClaude Opus 5.5 f96a3d1056 Make plans what the AI reaches for, keep the user's base pick, take models past Tile patches
From six Gemini 3.8 Flash runs of one question (Harem 1.5, then a Kirazuri v4 Tile redraw, then
faces and hands), recorded in tool-exam.md:
- begin_task's ways and the empty-canvas hint name add_nodes(plan=[...]); a route without its
  models and "several samplers" say what to do instead.
- A plan is two steps or more (no one-step probe card for the user); a model may be named by its
  name when only one main model has it; a way for a handed-over picture (fix_hands) gives way to
  its use's way that goes on from the step before.
- The user's rule is kept in run_workflow: a plan's first run is three bases, and the whole is
  refused once until they pick (an AI told "go on without" calls again).
- canvas_roles takes a "...model" path past size-bound patches (LLLite, ControlNet, T2I-Adapter):
  face detailing after a Tile redraw took the LLLite-patched model and crashed ComfyUI.
Last run: plan, build, three bases, pick, the whole from the picked base, no crash; 27 calls,
4 failed (each an empty first try).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 02:41:01 +08:00

106 lines
6.1 KiB
Python

"""get_situation: where an AI's task stands, in one answer small enough to stay in its chat.
The task (what the user asked last, questions and gates still waiting for them, decisions so
far, pictures made, steps done outside Anomalous), the canvas in brief (web/modules/
mcp_canvas_brief.js: flow chains, brief node lines, missing inputs and outputs that fit,
ComfyUI's errors), the latest run, and the one thing to do next. For an AI starting a new chat
on a task or unsure where it is: a small model reads this instead of describe_canvas, find_nodes
and the task's history. Unread messages of the user's come with it as with every answer
(ai_panels.attach_pending).
"""
from . import ai_decisions, ai_task_runs, ai_tasks, mcp_routes
from .mcp_bridge import PageRefused, PageUnavailable, ask_page
from .mcp_tools import _schema, tool_text
BUDGET = 3600 # characters: Antigravity files away an answer past about 4,000 and has the AI open it
EMPTY_CANVAS = ("The canvas is empty: run_workflow(workflow=<name>) opens a saved workflow and runs it in one call; open_workflow "
"opens one to change first, or a gallery picture's own by its file name; list_workflows names them and the examples. "
"A picture in several steps (the picture, a redraw on another model, details, an enlargement): add_nodes(plan=[…]).")
MAX_TEXT = 200
def _last_asked(task):
"""The user's latest words or marks in the task, in short."""
for step in reversed(task.get("steps") or []):
if step.get("kind") == "user" and step.get("tool") in ("user_message", "user_marks", "user_pictures"):
return {"what": step["tool"].removeprefix("user_"), "text": str(step.get("text") or "")[:MAX_TEXT], "id": step["id"]}
return None
def _canvas():
try:
return ask_page("describe_canvas", {"brief": True}, timeout=15)
except PageUnavailable as error:
return {"unavailable": str(error)}
except PageRefused as error: # no workflow open, or the task's tab cannot be opened
return {"unavailable": str(error)}
def _next(summary, run, canvas, record=None):
if summary["asks"]:
return f"{summary['asks']} question(s) or gate(s) wait for the user: get_answers waits for their answer."
if run.get("state") in ("queued", "running", "asking", "allowed"):
return "A run is going: run_status(wait_seconds=55) waits for it; do not run again."
if any("not in list" in str(item.get("error")) for item in canvas.get("errors") or ()):
return ("A model or file the workflow names is not on this computer: check_workflow_models says which and what "
"can stand in (fix_workflow_models, download_missing_models); do not guess other choices.")
if canvas.get("errors"):
return "ComfyUI shows errors on the canvas: fix the nodes in canvas.errors (set_values, connect_nodes, run_workflow image=), then run_workflow."
if canvas.get("unwired"):
return "Wire canvas.unwired with connect_nodes (fits lists outputs that fit each), then run_workflow."
if run.get("state") == "done" and run.get("pictures"):
return "Look at the run's pictures (zoom_image) and tell the user with report what came out."
if record and mcp_routes.waiting(record):
return mcp_routes.TOOLBOX_NOTE
if canvas.get("node_count") == 0:
return EMPTY_CANVAS
return "Go on with what the user asked (task.asked); run_workflow runs the canvas, report tells the user."
def get_situation(ctx, task=None):
client, session = ctx.get("client") or "AI", ctx.get("session") or ""
task_id = ai_tasks.active_task_id(client, session, task or ctx.get("task"))
record = ai_tasks.get_task(task_id) if task_id else None
run = {"state": "none"}
answer = {}
if record:
summary = ai_tasks.task_summary(record)
pictures = [item["filename"] for step in record.get("steps") or [] for item in step.get("results") or [] if item.get("type") == "image"]
answer["task"] = {"id": record["id"], "title": record.get("title") or "", "steps": summary["steps"],
"asked": _last_asked(record), "waiting_for_user": summary["asks"],
"decisions": ai_decisions.lines(record, limit=4), "pictures": pictures[-3:],
"outside_anomalous": summary["kinds"].get("outside", 0)}
status = ai_task_runs.status(client, session, task_id)
run = {key: status[key] for key in ("state", "summary", "pictures") if status.get(key)}
else:
summary = {"asks": 0}
answer["task"] = None
answer["task_note"] = "No task yet: begin_task names what the user asked for."
canvas = _canvas()
answer.update(run=run, canvas=canvas)
answer["next"] = _next(summary, run, canvas, record)
# Trim detail before chains and decisions; preserve actionable missing inputs and errors.
while len(tool_text(answer)) > BUDGET and canvas.get("lines"):
canvas["lines"].pop()
canvas["more_nodes"] = canvas.get("more_nodes", 0) + 1
while len(tool_text(answer)) > BUDGET and canvas.get("chains"):
canvas["chains"].pop()
canvas["more_chains"] = canvas.get("more_chains", 0) + 1
while len(tool_text(answer)) > BUDGET and record and answer["task"]["decisions"]:
answer["task"]["decisions"].pop(0)
return answer
SITUATION_TOOLS = {
"get_situation": {"run": get_situation, "context": True, "spec": {
"name": "get_situation", "title": "Where things stand",
"description": "The whole picture in one call: the task (what the user asked last, what waits for them, decisions, "
"pictures), the canvas in brief (flow chains, inputs nothing feeds and what fits them, errors), the "
"latest run, and next: the one thing to do now. Call it first in a new chat, and whenever unsure.",
"inputSchema": _schema({"task": {"type": "string", "description": "begin_task's id (optional)."}}),
"annotations": {"title": "Where things stand", "readOnlyHint": True, "destructiveHint": False,
"idempotentHint": True, "openWorldHint": False},
}},
}