Files
DemonGatanjieu-Anomalous_Mo…/api/mcp_situation.py
T
DemonGatanjieuandClaude Opus 5.5 f7d8a69e75 Shorten the daily tools' descriptions and begin_task's rules
The descriptions of the daily set are rewritten without the repeats and
roundabout phrasing. Their rules and don'ts stay: no cropping or editing
pictures with code, no reading ComfyUI's folders, ask the user in the
workbench. So do the shared property texts: the query words, node ids, the
task id. tools/list for the daily set drops from about 27,300 to 22,200
characters, and the full one from 46,000 to 40,700. begin_task's note, read at
the start of every task, drops from about 1,900 to 900 characters. The
replacements were made with ast, so each description's adjacent literals
changed as one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 15:54:42 +08:00

99 lines
5.5 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
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
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):
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 refused the last run: fix the nodes in canvas.errors (set_values, connect_nodes), 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."
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)
# 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},
}},
}