Files
DemonGatanjieu-Anomalous_Mo…/api/ai_privacy.py
T
DemonGatanjieuandClaude Fable 5.2 99ee111c81 Give a private task's AI the gist to read, keep its model names as tokens
Privacy mode left the AI blind twice over: the whole prompt was a token, so
it could not help with light, lens or style, and model and LoRA file names
still went out as written.

- The prompt box has a first field, the gist: the picture in words the user
  writes for the AI to read. seal_answer hands it on as written beside the
  tokens of the private prompts; either alone can be handed in.
- ai_sealed_models.py: while the Gates window's "Model names in a private
  task" is on (the default), each model of the library is one token ([M1],
  sealed as its model_id) however an answer writes it: model_id, loader
  name, file name in a text, card name. A model's own entry drops name,
  file, folder, links and hash and keeps type, base model, the author's
  settings and the user's note. expand writes a model token back as the
  model_id, as the loader's name among a node's values, as the card's name
  inside the AI's words. The task's menu lists what each token stands for.
- The model editor has "Note for the AI" (ai_note, note_for_ai in the
  tools, searched by search_models): what a model is for, in words the AI
  may read.
- A combo's name is sealed too: unless renamed it is made of its model's
  name and the start of its prompt.
- The privacy skill says how to write around a gist and how to pick and
  pass on model tokens.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-11 18:36:20 +08:00

122 lines
5.3 KiB
Python

"""Privacy mode (隐私模式): a task the user keeps private sends the AI none of its pictures and no prompt
text. The AI still builds and runs workflows and knows the models; it hears how many pictures came out
and whether the run went well, and the user looks at them in Anomalous. Prompts and picture names
reach it as tokens it passes on (ai_sealed.py); the user writes prompts in a prompt box. Each task has its switch (task["privacy"],
in the task's header); new tasks follow the default the user sets in the Gates window
(user/anomalous/ai_privacy.json, {default, models}). Turning it on or off is a step the AI is told of, and one of the
task's decisions (ai_decisions.py). A private task keeps its models' names too, as tokens the AI picks by type,
base model and the user's note (ai_sealed_models.py), unless the user lets the names through in the Gates window.
All of it happens at one place: mcp_server.py puts the texts back into a call's arguments
(ai_sealed.expand) and hands every tool result to guard() before it leaves, whatever tool or later
step (run pictures, the user's messages) put a picture or a prompt in it. A call that
cannot be tied to a task is private while the default is on or any private task is unfinished:
when in doubt, nothing is shown. The card in the AI app (mcp_apps.py) shows no pictures either.
"""
import json
import os
import time
import uuid
from . import ai_sealed, ai_tasks
from .path_utils import atomic_write_json
NOTE = ("Privacy mode: no pictures or prompt text reach you, only tokens like [P1], [img1] and [M1] (a model) to pass "
"on as they are. get_toolbox(skill=\"privacy\") first.")
def _path():
return os.path.join(os.path.dirname(ai_tasks.tasks_dir()), "ai_privacy.json")
def _kept():
try:
with open(_path(), encoding="utf-8") as source:
data = json.load(source)
except (OSError, ValueError):
return {}
return data if isinstance(data, dict) else {}
def default_on():
"""Whether new tasks are private (the Gates window); off unless the user turned it on."""
return _kept().get("default") is True
def hides_models():
"""Whether a private task keeps its models' names too (the Gates window); it does unless the user lets them through."""
return _kept().get("models") is not False
def set_hides_models(on):
if not isinstance(on, bool):
raise ValueError("privacy_models is true or false")
with ai_tasks._lock:
atomic_write_json(_path(), {**_kept(), "models": on})
return on
def set_default(on):
"""The default for new tasks. Turned off, the tasks that were private by it stay private."""
if not isinstance(on, bool):
raise ValueError("privacy is true or false")
with ai_tasks._lock:
if not on and default_on():
for task in ai_tasks._load().values():
if "privacy" not in task:
task["privacy"] = True
ai_tasks._save(task)
atomic_write_json(_path(), {**_kept(), "default": on})
return on
def is_private(task):
"""The task's own choice, else the default."""
value = (task or {}).get("privacy")
return value if isinstance(value, bool) else default_on()
def set_private(task_id, on):
"""The user's choice for one task, told to the AI as a step of theirs. Returns it."""
if not isinstance(on, bool):
raise ValueError("privacy is true or false")
with ai_tasks._lock:
task = ai_tasks._load().get(task_id)
if not task:
raise ValueError("No such task")
if is_private(task) != on:
ai_tasks._add_step(task, {"id": uuid.uuid4().hex[:10], "at": time.time(), "ms": 0, "tool": "user_privacy", "kind": "user",
"ok": True, "state": "done", "summary": "user_privacy", "privacy": on, "delivered": False, "results": [],
"text": "The user turned privacy mode on for this task. " + NOTE if on else
"The user turned privacy mode off for this task: you get the pictures again."})
task["privacy"] = on
ai_tasks._save(task)
ai_tasks._notify(task_id)
return on
def applies(task_id):
"""Whether a call of task `task_id` (None: none known) is private."""
task = ai_tasks.get_task(task_id) if task_id else None
if task:
return is_private(task)
if default_on():
return True
with ai_tasks._lock:
return any(item.get("privacy") is True and not item.get("ended") for item in ai_tasks._load().values())
def guard(result, private, task_id=None, info=None, tool=""):
"""`result` as it may leave: a private call's pictures taken out (with a word on why), its prompt
text, picture names and (hides_models) models as tokens (ai_sealed.mask; `info`: ComfyUI's node list)."""
if not private or not isinstance(result, dict):
return result
content = [item for item in result.get("content") or [] if not (isinstance(item, dict) and item.get("type") == "image")]
if len(content) != len(result.get("content") or []):
content.append({"type": "text", "text": NOTE})
result["content"] = content
if isinstance(result.get("structuredContent"), dict):
result["structuredContent"] = {**result["structuredContent"], "privacy": NOTE}
return ai_sealed.mask(result, task_id, info, tool, hides_models())