Spaces:
Paused
Paused
Upload folder using huggingface_hub
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .dockerignore +3 -50
- .gitattributes +3 -13
- .github/FUNDING.yml +3 -1
- .github/scripts/docker_release_plan.py +3 -841
- .github/workflows/close-inactive.yml +3 -108
- .github/workflows/docker-publish.yml +3 -236
- .gitignore +3 -52
- .vscode/extensions.json +3 -7
- .vscode/launch.json +3 -24
- .vscode/settings.json +3 -17
- AGENTS.md +3 -253
- DockerfileLocal +3 -36
- LICENSE +3 -23
- agent.py +3 -1023
- agents/_example/extensions/agent_init/_10_example_extension.py +3 -10
- agents/_example/prompts/agent.system.main.role.md +3 -8
- agents/_example/prompts/agent.system.tool.example_tool.md +3 -16
- agents/_example/tools/example_tool.py +3 -21
- agents/_example/tools/response.py +3 -23
- agents/a0_small/agent.yaml +3 -3
- agents/a0_small/prompts/agent.system.main.communication.md +3 -21
- agents/a0_small/prompts/agent.system.main.communication_additions.md +3 -10
- agents/a0_small/prompts/agent.system.main.role.md +3 -5
- agents/a0_small/prompts/agent.system.main.solving.md +3 -10
- agents/a0_small/prompts/agent.system.main.tips.md +3 -7
- agents/a0_small/prompts/agent.system.projects.active.md +3 -10
- agents/a0_small/prompts/agent.system.projects.inactive.md +3 -1
- agents/a0_small/prompts/agent.system.projects.main.md +3 -1
- agents/a0_small/prompts/agent.system.promptinclude.md +3 -6
- agents/a0_small/prompts/agent.system.response_tool_tips.md +3 -1
- agents/a0_small/prompts/agent.system.secrets.md +3 -10
- agents/a0_small/prompts/agent.system.skills.md +3 -3
- agents/a0_small/prompts/agent.system.tool.a2a_chat.md +3 -4
- agents/a0_small/prompts/agent.system.tool.behaviour.md +3 -4
- agents/a0_small/prompts/agent.system.tool.browser.md +3 -7
- agents/a0_small/prompts/agent.system.tool.call_sub.md +3 -11
- agents/a0_small/prompts/agent.system.tool.call_sub.py +3 -24
- agents/a0_small/prompts/agent.system.tool.code_exe.md +3 -12
- agents/a0_small/prompts/agent.system.tool.document_query.md +3 -8
- agents/a0_small/prompts/agent.system.tool.input.md +3 -4
- agents/a0_small/prompts/agent.system.tool.memory.md +3 -10
- agents/a0_small/prompts/agent.system.tool.notify_user.md +3 -5
- agents/a0_small/prompts/agent.system.tool.response.md +3 -5
- agents/a0_small/prompts/agent.system.tool.scheduler.md +3 -16
- agents/a0_small/prompts/agent.system.tool.search_engine.md +3 -5
- agents/a0_small/prompts/agent.system.tool.skills.md +3 -6
- agents/a0_small/prompts/agent.system.tool.text_editor.md +3 -11
- agents/a0_small/prompts/agent.system.tool.wait.md +3 -4
- agents/a0_small/prompts/agent.system.tools.md +3 -3
- agents/a0_small/prompts/agent.system.tools_vision.md +3 -4
.dockerignore
CHANGED
|
@@ -1,50 +1,3 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
# Obsolete
|
| 6 |
-
memory/**
|
| 7 |
-
instruments/**
|
| 8 |
-
knowledge/custom/**
|
| 9 |
-
|
| 10 |
-
# Logs, tmp, usr
|
| 11 |
-
logs/*
|
| 12 |
-
tmp/*
|
| 13 |
-
usr/*
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
# Keep .gitkeep markers anywhere
|
| 17 |
-
!**/.gitkeep
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
###############################################################################
|
| 21 |
-
# Environment / tooling
|
| 22 |
-
###############################################################################
|
| 23 |
-
.conda/
|
| 24 |
-
.cursor/
|
| 25 |
-
.venv/
|
| 26 |
-
.git/
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
###############################################################################
|
| 30 |
-
# Tests (root‑level only)
|
| 31 |
-
###############################################################################
|
| 32 |
-
/*.test.py
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
###############################################################################
|
| 36 |
-
# ─── LAST SECTION: universal junk / caches (MUST BE LAST) ───
|
| 37 |
-
# Put these at the *bottom* so they override any ! re‑includes above
|
| 38 |
-
###############################################################################
|
| 39 |
-
# OS / editor junk
|
| 40 |
-
**/.DS_Store
|
| 41 |
-
**/Thumbs.db
|
| 42 |
-
|
| 43 |
-
# Python caches / compiled artefacts
|
| 44 |
-
**/__pycache__/
|
| 45 |
-
**/*.py[cod]
|
| 46 |
-
**/*.pyo
|
| 47 |
-
**/*.pyd
|
| 48 |
-
|
| 49 |
-
# Environment files anywhere
|
| 50 |
-
*.env
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:d0e6fd1da71723dae7138d091caa8d50cb5c771b06cdebd332f199d109671e2f
|
| 3 |
+
size 1243
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.gitattributes
CHANGED
|
@@ -1,13 +1,3 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
docs/res/dev/devinst-2.png filter=lfs diff=lfs merge=lfs -text
|
| 5 |
-
docs/res/devguide_vid.png filter=lfs diff=lfs merge=lfs -text
|
| 6 |
-
docs/res/easy_ins_vid.png filter=lfs diff=lfs merge=lfs -text
|
| 7 |
-
docs/res/setup/image-19.png filter=lfs diff=lfs merge=lfs -text
|
| 8 |
-
docs/res/setup/thumb_play.png filter=lfs diff=lfs merge=lfs -text
|
| 9 |
-
docs/res/time_example.jpg filter=lfs diff=lfs merge=lfs -text
|
| 10 |
-
docs/res/usage/plugins/plugin-hub-main-view.png filter=lfs diff=lfs merge=lfs -text
|
| 11 |
-
docs/res/usage/plugins/plugin-hub-plugin-detail.png filter=lfs diff=lfs merge=lfs -text
|
| 12 |
-
docs/res/usage/plugins/plugins-list-01.png filter=lfs diff=lfs merge=lfs -text
|
| 13 |
-
webui/vendor/google/google-icons.ttf filter=lfs diff=lfs merge=lfs -text
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:56c89d9018842070bb8dd1397378f26b814d30f4f7c56d8ff8788dbae1f83682
|
| 3 |
+
size 72
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.github/FUNDING.yml
CHANGED
|
@@ -1 +1,3 @@
|
|
| 1 |
-
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:9fa60c383aeaebb5eb3071caaf9a568c174f8ad2f966bda74010417012e27ef2
|
| 3 |
+
size 17
|
.github/scripts/docker_release_plan.py
CHANGED
|
@@ -1,841 +1,3 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
import json
|
| 5 |
-
import os
|
| 6 |
-
import re
|
| 7 |
-
import subprocess
|
| 8 |
-
import sys
|
| 9 |
-
from dataclasses import asdict, dataclass
|
| 10 |
-
from pathlib import Path
|
| 11 |
-
from urllib.error import HTTPError, URLError
|
| 12 |
-
from urllib.parse import urlencode
|
| 13 |
-
from urllib.request import Request, urlopen
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
REPO_ROOT = Path(__file__).resolve().parents[2]
|
| 17 |
-
OPENROUTER_CHAT_COMPLETIONS_URL = "https://openrouter.ai/api/v1/chat/completions"
|
| 18 |
-
OPENROUTER_SYSTEM_PROMPT_PATH = REPO_ROOT / "scripts" / "openrouter_release_notes_system_prompt.md"
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
def fail(message: str) -> None:
|
| 22 |
-
print(message, file=sys.stderr)
|
| 23 |
-
raise SystemExit(1)
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
def write_output(name: str, value: str) -> None:
|
| 27 |
-
output_path = os.environ.get("GITHUB_OUTPUT")
|
| 28 |
-
if not output_path:
|
| 29 |
-
return
|
| 30 |
-
with open(output_path, "a", encoding="utf-8") as handle:
|
| 31 |
-
handle.write(f"{name}<<__EOF__\n{value}\n__EOF__\n")
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
def write_summary(lines: list[str]) -> None:
|
| 35 |
-
summary_path = os.environ.get("GITHUB_STEP_SUMMARY")
|
| 36 |
-
if not summary_path or not lines:
|
| 37 |
-
return
|
| 38 |
-
with open(summary_path, "a", encoding="utf-8") as handle:
|
| 39 |
-
handle.write("## Docker publish plan\n\n")
|
| 40 |
-
for line in lines:
|
| 41 |
-
handle.write(f"- {line}\n")
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
def run_command(*args: str, check: bool = True) -> subprocess.CompletedProcess[str]:
|
| 45 |
-
result = subprocess.run(args, capture_output=True, text=True)
|
| 46 |
-
if check and result.returncode != 0:
|
| 47 |
-
command = " ".join(args)
|
| 48 |
-
fail(f"Command failed ({command}):\n{result.stderr.strip()}")
|
| 49 |
-
return result
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
def git(*args: str, check: bool = True) -> str:
|
| 53 |
-
return run_command("git", *args, check=check).stdout.strip()
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
def docker_tag_exists(image_repo: str, tag: str) -> bool:
|
| 57 |
-
result = run_command(
|
| 58 |
-
"docker",
|
| 59 |
-
"buildx",
|
| 60 |
-
"imagetools",
|
| 61 |
-
"inspect",
|
| 62 |
-
f"{image_repo}:{tag}",
|
| 63 |
-
check=False,
|
| 64 |
-
)
|
| 65 |
-
return result.returncode == 0
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
def split_branches(raw: str) -> list[str]:
|
| 69 |
-
parts = re.split(r"[\s,]+", raw.strip())
|
| 70 |
-
return [part for part in parts if part]
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
def require_env(name: str) -> str:
|
| 74 |
-
value = os.environ.get(name, "").strip()
|
| 75 |
-
if not value:
|
| 76 |
-
fail(f"Required environment variable `{name}` is missing.")
|
| 77 |
-
return value
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
def require_any_env(*names: str) -> str:
|
| 81 |
-
for name in names:
|
| 82 |
-
value = os.environ.get(name, "").strip()
|
| 83 |
-
if value:
|
| 84 |
-
return value
|
| 85 |
-
fail(
|
| 86 |
-
"Required environment variable is missing. Expected one of: "
|
| 87 |
-
+ ", ".join(f"`{name}`" for name in names)
|
| 88 |
-
)
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
@dataclass(frozen=True)
|
| 92 |
-
class Config:
|
| 93 |
-
allowed_branches: list[str]
|
| 94 |
-
main_branch: str
|
| 95 |
-
image_repo: str
|
| 96 |
-
tag_pattern: re.Pattern[str]
|
| 97 |
-
min_version: tuple[int, int]
|
| 98 |
-
event_name: str
|
| 99 |
-
source_ref_name: str
|
| 100 |
-
source_ref_type: str
|
| 101 |
-
manual_tag: str
|
| 102 |
-
before_sha: str
|
| 103 |
-
after_sha: str
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
@dataclass(frozen=True)
|
| 107 |
-
class BranchState:
|
| 108 |
-
branch: str
|
| 109 |
-
valid_tags: list[str]
|
| 110 |
-
latest_tag: str | None
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
@dataclass
|
| 114 |
-
class Candidate:
|
| 115 |
-
branch: str
|
| 116 |
-
source_tag: str
|
| 117 |
-
mode: str
|
| 118 |
-
publish_version: bool
|
| 119 |
-
publish_branch_tag: bool
|
| 120 |
-
reason: str
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
@dataclass(frozen=True)
|
| 124 |
-
class CommitEntry:
|
| 125 |
-
heading: str
|
| 126 |
-
description: str
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
def load_config() -> Config:
|
| 130 |
-
allowed_branches = split_branches(os.environ["ALLOWED_BRANCHES"])
|
| 131 |
-
if not allowed_branches:
|
| 132 |
-
fail("ALLOWED_BRANCHES must not be empty.")
|
| 133 |
-
main_branch = os.environ["MAIN_BRANCH"].strip()
|
| 134 |
-
if main_branch not in allowed_branches:
|
| 135 |
-
fail("MAIN_BRANCH must also be listed in ALLOWED_BRANCHES.")
|
| 136 |
-
|
| 137 |
-
tag_regex = os.environ["RELEASE_TAG_REGEX"]
|
| 138 |
-
return Config(
|
| 139 |
-
allowed_branches=allowed_branches,
|
| 140 |
-
main_branch=main_branch,
|
| 141 |
-
image_repo=os.environ["DOCKER_IMAGE_REPO"].strip(),
|
| 142 |
-
tag_pattern=re.compile(tag_regex),
|
| 143 |
-
min_version=(
|
| 144 |
-
int(os.environ["MIN_RELEASE_MAJOR"]),
|
| 145 |
-
int(os.environ["MIN_RELEASE_MINOR"]),
|
| 146 |
-
),
|
| 147 |
-
event_name=os.environ["EVENT_NAME"].strip(),
|
| 148 |
-
source_ref_name=os.environ.get("SOURCE_REF_NAME", "").strip(),
|
| 149 |
-
source_ref_type=os.environ.get("SOURCE_REF_TYPE", "").strip(),
|
| 150 |
-
manual_tag=os.environ.get("MANUAL_TAG", "").strip(),
|
| 151 |
-
before_sha=os.environ.get("BEFORE_SHA", "").strip(),
|
| 152 |
-
after_sha=os.environ.get("AFTER_SHA", "").strip(),
|
| 153 |
-
)
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
def parse_release_tag(config: Config, tag: str) -> tuple[int, int] | None:
|
| 157 |
-
match = config.tag_pattern.fullmatch(tag)
|
| 158 |
-
if not match:
|
| 159 |
-
return None
|
| 160 |
-
version = (int(match.group(1)), int(match.group(2)))
|
| 161 |
-
if version < config.min_version:
|
| 162 |
-
return None
|
| 163 |
-
return version
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
def tag_exists(tag: str) -> bool:
|
| 167 |
-
return run_command("git", "rev-parse", "--verify", "--quiet", f"refs/tags/{tag}", check=False).returncode == 0
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
def tag_commit(tag: str) -> str:
|
| 171 |
-
return git("rev-list", "-n", "1", f"refs/tags/{tag}")
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
def commit_is_ancestor(older_ref: str, newer_ref: str) -> bool:
|
| 175 |
-
return (
|
| 176 |
-
run_command(
|
| 177 |
-
"git",
|
| 178 |
-
"merge-base",
|
| 179 |
-
"--is-ancestor",
|
| 180 |
-
older_ref,
|
| 181 |
-
newer_ref,
|
| 182 |
-
check=False,
|
| 183 |
-
).returncode
|
| 184 |
-
== 0
|
| 185 |
-
)
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
def branch_contains_commit(branch: str, commit: str) -> bool:
|
| 189 |
-
return (
|
| 190 |
-
run_command(
|
| 191 |
-
"git",
|
| 192 |
-
"merge-base",
|
| 193 |
-
"--is-ancestor",
|
| 194 |
-
commit,
|
| 195 |
-
f"origin/{branch}",
|
| 196 |
-
check=False,
|
| 197 |
-
).returncode
|
| 198 |
-
== 0
|
| 199 |
-
)
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
def ref_exists(ref: str) -> bool:
|
| 203 |
-
if not ref or re.fullmatch(r"0{40}", ref):
|
| 204 |
-
return False
|
| 205 |
-
return run_command("git", "rev-parse", "--verify", "--quiet", f"{ref}^{{commit}}", check=False).returncode == 0
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
def releasable_tags_for_ref(config: Config, ref: str) -> list[str]:
|
| 209 |
-
if not ref_exists(ref):
|
| 210 |
-
return []
|
| 211 |
-
|
| 212 |
-
tagged_versions: list[tuple[tuple[int, int], str]] = []
|
| 213 |
-
merged_tags = git("tag", "--merged", ref)
|
| 214 |
-
for tag in merged_tags.splitlines():
|
| 215 |
-
version = parse_release_tag(config, tag.strip())
|
| 216 |
-
if version is None:
|
| 217 |
-
continue
|
| 218 |
-
tagged_versions.append((version, tag.strip()))
|
| 219 |
-
|
| 220 |
-
tagged_versions.sort(key=lambda item: item[0])
|
| 221 |
-
return [tag for _, tag in tagged_versions]
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
def latest_releasable_tag_for_ref(config: Config, ref: str) -> str | None:
|
| 225 |
-
valid_tags = releasable_tags_for_ref(config, ref)
|
| 226 |
-
return valid_tags[-1] if valid_tags else None
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
def collect_branch_states(config: Config, branches: list[str] | None = None) -> dict[str, BranchState]:
|
| 230 |
-
states: dict[str, BranchState] = {}
|
| 231 |
-
for branch in branches or config.allowed_branches:
|
| 232 |
-
if run_command("git", "show-ref", "--verify", "--quiet", f"refs/remotes/origin/{branch}", check=False).returncode != 0:
|
| 233 |
-
fail(f"Allowed branch origin/{branch} was not fetched.")
|
| 234 |
-
|
| 235 |
-
valid_tags = releasable_tags_for_ref(config, f"origin/{branch}")
|
| 236 |
-
states[branch] = BranchState(
|
| 237 |
-
branch=branch,
|
| 238 |
-
valid_tags=valid_tags,
|
| 239 |
-
latest_tag=valid_tags[-1] if valid_tags else None,
|
| 240 |
-
)
|
| 241 |
-
return states
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
def add_or_merge_candidate(candidates: dict[tuple[str, str, str], Candidate], candidate: Candidate) -> None:
|
| 245 |
-
key = (candidate.branch, candidate.source_tag, candidate.mode)
|
| 246 |
-
existing = candidates.get(key)
|
| 247 |
-
if existing is None:
|
| 248 |
-
candidates[key] = candidate
|
| 249 |
-
return
|
| 250 |
-
existing.publish_version = existing.publish_version or candidate.publish_version
|
| 251 |
-
existing.publish_branch_tag = existing.publish_branch_tag or candidate.publish_branch_tag
|
| 252 |
-
if candidate.reason not in existing.reason:
|
| 253 |
-
existing.reason = f"{existing.reason}; {candidate.reason}"
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
def plan_tag_push(config: Config, branch_states: dict[str, BranchState]) -> tuple[list[Candidate], list[str]]:
|
| 257 |
-
source_tag = config.source_ref_name
|
| 258 |
-
notes: list[str] = []
|
| 259 |
-
version = parse_release_tag(config, source_tag)
|
| 260 |
-
if version is None:
|
| 261 |
-
return [], [f"Skipped `{source_tag}` because it does not match `v{{X}}.{{Y}}` or is below v{config.min_version[0]}.{config.min_version[1]}."]
|
| 262 |
-
if not tag_exists(source_tag):
|
| 263 |
-
return [], [f"Skipped `{source_tag}` because the tag is not present after checkout."]
|
| 264 |
-
|
| 265 |
-
commit = tag_commit(source_tag)
|
| 266 |
-
candidates: list[Candidate] = []
|
| 267 |
-
found_branch = False
|
| 268 |
-
for branch, state in branch_states.items():
|
| 269 |
-
if not branch_contains_commit(branch, commit):
|
| 270 |
-
continue
|
| 271 |
-
found_branch = True
|
| 272 |
-
if state.latest_tag != source_tag:
|
| 273 |
-
notes.append(
|
| 274 |
-
f"Skipped `{source_tag}` on `{branch}` because `{state.latest_tag}` is the highest release tag currently reachable from that branch."
|
| 275 |
-
)
|
| 276 |
-
continue
|
| 277 |
-
candidates.append(
|
| 278 |
-
Candidate(
|
| 279 |
-
branch=branch,
|
| 280 |
-
source_tag=source_tag,
|
| 281 |
-
mode="push_latest_only",
|
| 282 |
-
publish_version=branch == config.main_branch,
|
| 283 |
-
publish_branch_tag=True,
|
| 284 |
-
reason=f"Automatic build for the latest release tag on `{branch}`.",
|
| 285 |
-
)
|
| 286 |
-
)
|
| 287 |
-
|
| 288 |
-
if not found_branch:
|
| 289 |
-
notes.append(f"Skipped `{source_tag}` because it is not reachable from any allowed branch.")
|
| 290 |
-
return candidates, notes
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
def plan_branch_push(config: Config, branch_states: dict[str, BranchState]) -> tuple[list[Candidate], list[str]]:
|
| 294 |
-
branch = config.source_ref_name
|
| 295 |
-
if branch not in branch_states:
|
| 296 |
-
return [], [f"Skipped `{branch}` because it is not an allowed release branch."]
|
| 297 |
-
|
| 298 |
-
before_tag = latest_releasable_tag_for_ref(config, config.before_sha)
|
| 299 |
-
after_tag = branch_states[branch].latest_tag
|
| 300 |
-
if after_tag is None:
|
| 301 |
-
return [], [f"Skipped `{branch}` because it has no releasable tags."]
|
| 302 |
-
if before_tag == after_tag:
|
| 303 |
-
return [], [f"Skipped `{branch}` because its highest release tag is still `{after_tag}`."]
|
| 304 |
-
|
| 305 |
-
return [
|
| 306 |
-
Candidate(
|
| 307 |
-
branch=branch,
|
| 308 |
-
source_tag=after_tag,
|
| 309 |
-
mode="push_promoted_tag",
|
| 310 |
-
publish_version=branch == config.main_branch,
|
| 311 |
-
publish_branch_tag=True,
|
| 312 |
-
reason=f"Automatic build for `{after_tag}` after it reached `{branch}`.",
|
| 313 |
-
)
|
| 314 |
-
], []
|
| 315 |
-
|
| 316 |
-
|
| 317 |
-
def plan_manual_exact(config: Config, branch_states: dict[str, BranchState]) -> tuple[list[Candidate], list[str]]:
|
| 318 |
-
manual_tag = config.manual_tag
|
| 319 |
-
if parse_release_tag(config, manual_tag) is None:
|
| 320 |
-
fail(
|
| 321 |
-
f"Manual tag `{manual_tag}` is invalid. Expected `v{{X}}.{{Y}}` with a minimum of v{config.min_version[0]}.{config.min_version[1]}."
|
| 322 |
-
)
|
| 323 |
-
if not tag_exists(manual_tag):
|
| 324 |
-
fail(f"Manual tag `{manual_tag}` does not exist in the repository.")
|
| 325 |
-
|
| 326 |
-
commit = tag_commit(manual_tag)
|
| 327 |
-
notes: list[str] = []
|
| 328 |
-
candidates: list[Candidate] = []
|
| 329 |
-
for branch, state in branch_states.items():
|
| 330 |
-
if not branch_contains_commit(branch, commit):
|
| 331 |
-
continue
|
| 332 |
-
if branch == config.main_branch:
|
| 333 |
-
candidates.append(
|
| 334 |
-
Candidate(
|
| 335 |
-
branch=branch,
|
| 336 |
-
source_tag=manual_tag,
|
| 337 |
-
mode="manual_exact",
|
| 338 |
-
publish_version=True,
|
| 339 |
-
publish_branch_tag=state.latest_tag == manual_tag,
|
| 340 |
-
reason=f"Manual rebuild for `{manual_tag}` on `{branch}`.",
|
| 341 |
-
)
|
| 342 |
-
)
|
| 343 |
-
continue
|
| 344 |
-
if state.latest_tag != manual_tag:
|
| 345 |
-
notes.append(
|
| 346 |
-
f"Skipped `{manual_tag}` on `{branch}` because non-main branches only publish their current branch tag and `{state.latest_tag}` is newer."
|
| 347 |
-
)
|
| 348 |
-
continue
|
| 349 |
-
candidates.append(
|
| 350 |
-
Candidate(
|
| 351 |
-
branch=branch,
|
| 352 |
-
source_tag=manual_tag,
|
| 353 |
-
mode="manual_exact",
|
| 354 |
-
publish_version=False,
|
| 355 |
-
publish_branch_tag=True,
|
| 356 |
-
reason=f"Manual rebuild for the current branch image on `{branch}`.",
|
| 357 |
-
)
|
| 358 |
-
)
|
| 359 |
-
|
| 360 |
-
if not candidates:
|
| 361 |
-
notes.append(f"No eligible images were found for manual tag `{manual_tag}`.")
|
| 362 |
-
return candidates, notes
|
| 363 |
-
|
| 364 |
-
|
| 365 |
-
def plan_manual_backfill(config: Config, branch_states: dict[str, BranchState]) -> tuple[list[Candidate], list[str]]:
|
| 366 |
-
notes: list[str] = []
|
| 367 |
-
candidates: dict[tuple[str, str, str], Candidate] = {}
|
| 368 |
-
|
| 369 |
-
for branch, state in branch_states.items():
|
| 370 |
-
if not state.valid_tags:
|
| 371 |
-
notes.append(f"Branch `{branch}` has no releasable tags.")
|
| 372 |
-
continue
|
| 373 |
-
|
| 374 |
-
if branch == config.main_branch:
|
| 375 |
-
for tag in state.valid_tags:
|
| 376 |
-
if docker_tag_exists(config.image_repo, tag):
|
| 377 |
-
continue
|
| 378 |
-
add_or_merge_candidate(
|
| 379 |
-
candidates,
|
| 380 |
-
Candidate(
|
| 381 |
-
branch=branch,
|
| 382 |
-
source_tag=tag,
|
| 383 |
-
mode="manual_backfill",
|
| 384 |
-
publish_version=True,
|
| 385 |
-
publish_branch_tag=False,
|
| 386 |
-
reason=f"Missing Docker Hub tag `{tag}`.",
|
| 387 |
-
),
|
| 388 |
-
)
|
| 389 |
-
|
| 390 |
-
latest_tag = state.latest_tag
|
| 391 |
-
if latest_tag and not docker_tag_exists(config.image_repo, "latest"):
|
| 392 |
-
add_or_merge_candidate(
|
| 393 |
-
candidates,
|
| 394 |
-
Candidate(
|
| 395 |
-
branch=branch,
|
| 396 |
-
source_tag=latest_tag,
|
| 397 |
-
mode="manual_backfill",
|
| 398 |
-
publish_version=False,
|
| 399 |
-
publish_branch_tag=True,
|
| 400 |
-
reason="Missing Docker Hub tag `latest`.",
|
| 401 |
-
),
|
| 402 |
-
)
|
| 403 |
-
continue
|
| 404 |
-
|
| 405 |
-
if not docker_tag_exists(config.image_repo, branch):
|
| 406 |
-
add_or_merge_candidate(
|
| 407 |
-
candidates,
|
| 408 |
-
Candidate(
|
| 409 |
-
branch=branch,
|
| 410 |
-
source_tag=state.latest_tag,
|
| 411 |
-
mode="manual_backfill",
|
| 412 |
-
publish_version=False,
|
| 413 |
-
publish_branch_tag=True,
|
| 414 |
-
reason=f"Missing Docker Hub tag `{branch}`.",
|
| 415 |
-
),
|
| 416 |
-
)
|
| 417 |
-
|
| 418 |
-
if not candidates:
|
| 419 |
-
notes.append("No missing Docker Hub tags were found.")
|
| 420 |
-
return list(candidates.values()), notes
|
| 421 |
-
|
| 422 |
-
|
| 423 |
-
def plan_command() -> None:
|
| 424 |
-
config = load_config()
|
| 425 |
-
branch_states = collect_branch_states(config)
|
| 426 |
-
|
| 427 |
-
if config.event_name == "workflow_dispatch":
|
| 428 |
-
if config.manual_tag:
|
| 429 |
-
candidates, notes = plan_manual_exact(config, branch_states)
|
| 430 |
-
else:
|
| 431 |
-
candidates, notes = plan_manual_backfill(config, branch_states)
|
| 432 |
-
elif config.event_name == "push":
|
| 433 |
-
if config.source_ref_type == "tag":
|
| 434 |
-
candidates, notes = plan_tag_push(config, branch_states)
|
| 435 |
-
elif config.source_ref_type == "branch":
|
| 436 |
-
candidates, notes = plan_branch_push(config, branch_states)
|
| 437 |
-
else:
|
| 438 |
-
fail(f"Unsupported push ref type: {config.source_ref_type}")
|
| 439 |
-
else:
|
| 440 |
-
fail(f"Unsupported event: {config.event_name}")
|
| 441 |
-
|
| 442 |
-
summary_lines = [candidate.reason for candidate in candidates]
|
| 443 |
-
summary_lines.extend(notes)
|
| 444 |
-
|
| 445 |
-
matrix = {"include": [asdict(candidate) for candidate in candidates]}
|
| 446 |
-
write_output("has_work", "true" if candidates else "false")
|
| 447 |
-
write_output("matrix", json.dumps(matrix))
|
| 448 |
-
write_summary(summary_lines)
|
| 449 |
-
|
| 450 |
-
print(json.dumps(matrix, indent=2))
|
| 451 |
-
for line in summary_lines:
|
| 452 |
-
print(f"- {line}")
|
| 453 |
-
|
| 454 |
-
|
| 455 |
-
def unique(items: list[str]) -> list[str]:
|
| 456 |
-
seen: set[str] = set()
|
| 457 |
-
output: list[str] = []
|
| 458 |
-
for item in items:
|
| 459 |
-
if item in seen:
|
| 460 |
-
continue
|
| 461 |
-
seen.add(item)
|
| 462 |
-
output.append(item)
|
| 463 |
-
return output
|
| 464 |
-
|
| 465 |
-
|
| 466 |
-
def load_text(path: Path) -> str:
|
| 467 |
-
if not path.exists():
|
| 468 |
-
fail(f"Expected file `{path}` to exist.")
|
| 469 |
-
return path.read_text(encoding="utf-8").strip()
|
| 470 |
-
|
| 471 |
-
|
| 472 |
-
def github_repository_parts() -> tuple[str, str]:
|
| 473 |
-
repository = require_env("GITHUB_REPOSITORY")
|
| 474 |
-
owner, separator, repo = repository.partition("/")
|
| 475 |
-
if not owner or not separator or not repo:
|
| 476 |
-
fail(f"GITHUB_REPOSITORY must be in `owner/repo` format, got `{repository}`.")
|
| 477 |
-
return owner, repo
|
| 478 |
-
|
| 479 |
-
|
| 480 |
-
def github_api_get(path: str, params: dict[str, str | int] | None = None) -> object:
|
| 481 |
-
api_base = os.environ.get("GITHUB_API_URL", "https://api.github.com").rstrip("/")
|
| 482 |
-
token = require_env("GITHUB_TOKEN")
|
| 483 |
-
query = f"?{urlencode(params)}" if params else ""
|
| 484 |
-
request = Request(
|
| 485 |
-
f"{api_base}{path}{query}",
|
| 486 |
-
headers={
|
| 487 |
-
"Accept": "application/vnd.github+json",
|
| 488 |
-
"Authorization": f"Bearer {token}",
|
| 489 |
-
"X-GitHub-Api-Version": "2022-11-28",
|
| 490 |
-
},
|
| 491 |
-
method="GET",
|
| 492 |
-
)
|
| 493 |
-
|
| 494 |
-
try:
|
| 495 |
-
with urlopen(request, timeout=30) as response:
|
| 496 |
-
return json.loads(response.read().decode("utf-8"))
|
| 497 |
-
except HTTPError as exc:
|
| 498 |
-
details = exc.read().decode("utf-8", errors="replace").strip()
|
| 499 |
-
fail(f"GitHub API request failed ({path}): {exc.code} {exc.reason}\n{details}")
|
| 500 |
-
except URLError as exc:
|
| 501 |
-
fail(f"GitHub API request failed ({path}): {exc.reason}")
|
| 502 |
-
|
| 503 |
-
|
| 504 |
-
def list_github_releases() -> list[dict[str, object]]:
|
| 505 |
-
owner, repo = github_repository_parts()
|
| 506 |
-
releases: list[dict[str, object]] = []
|
| 507 |
-
page = 1
|
| 508 |
-
|
| 509 |
-
while True:
|
| 510 |
-
payload = github_api_get(
|
| 511 |
-
f"/repos/{owner}/{repo}/releases",
|
| 512 |
-
{"per_page": 100, "page": page},
|
| 513 |
-
)
|
| 514 |
-
if not isinstance(payload, list):
|
| 515 |
-
fail("GitHub releases response was not a list.")
|
| 516 |
-
page_items = [item for item in payload if isinstance(item, dict)]
|
| 517 |
-
releases.extend(page_items)
|
| 518 |
-
if len(page_items) < 100:
|
| 519 |
-
break
|
| 520 |
-
page += 1
|
| 521 |
-
|
| 522 |
-
return releases
|
| 523 |
-
|
| 524 |
-
|
| 525 |
-
def previous_published_release_tag(config: Config, source_tag: str) -> str | None:
|
| 526 |
-
source_version = parse_release_tag(config, source_tag)
|
| 527 |
-
if source_version is None:
|
| 528 |
-
fail(f"Tag `{source_tag}` is not a releasable tag.")
|
| 529 |
-
|
| 530 |
-
previous: list[tuple[tuple[int, int], str]] = []
|
| 531 |
-
for release in list_github_releases():
|
| 532 |
-
if release.get("draft") or release.get("prerelease"):
|
| 533 |
-
continue
|
| 534 |
-
tag_name = str(release.get("tag_name", "")).strip()
|
| 535 |
-
version = parse_release_tag(config, tag_name)
|
| 536 |
-
if version is None or version >= source_version:
|
| 537 |
-
continue
|
| 538 |
-
previous.append((version, tag_name))
|
| 539 |
-
|
| 540 |
-
previous.sort(key=lambda item: item[0])
|
| 541 |
-
return previous[-1][1] if previous else None
|
| 542 |
-
|
| 543 |
-
|
| 544 |
-
def parse_commit_entries(raw_log: str) -> list[CommitEntry]:
|
| 545 |
-
entries: list[CommitEntry] = []
|
| 546 |
-
for raw_entry in raw_log.split("\x1e"):
|
| 547 |
-
entry = raw_entry.strip()
|
| 548 |
-
if not entry:
|
| 549 |
-
continue
|
| 550 |
-
heading, separator, description = entry.partition("\x1f")
|
| 551 |
-
if not separator:
|
| 552 |
-
continue
|
| 553 |
-
entries.append(
|
| 554 |
-
CommitEntry(
|
| 555 |
-
heading=re.sub(r"\s+", " ", heading).strip(),
|
| 556 |
-
description=description.strip(),
|
| 557 |
-
)
|
| 558 |
-
)
|
| 559 |
-
return entries
|
| 560 |
-
|
| 561 |
-
|
| 562 |
-
def collect_release_commits(previous_release_tag: str | None, source_tag: str) -> list[CommitEntry]:
|
| 563 |
-
range_ref = source_tag
|
| 564 |
-
if previous_release_tag:
|
| 565 |
-
if not tag_exists(previous_release_tag):
|
| 566 |
-
fail(f"Previous published release tag `{previous_release_tag}` is not available in the repository.")
|
| 567 |
-
if not commit_is_ancestor(
|
| 568 |
-
f"refs/tags/{previous_release_tag}^{{commit}}",
|
| 569 |
-
f"refs/tags/{source_tag}^{{commit}}",
|
| 570 |
-
):
|
| 571 |
-
fail(
|
| 572 |
-
f"Previous published release tag `{previous_release_tag}` is not an ancestor of `{source_tag}`."
|
| 573 |
-
)
|
| 574 |
-
range_ref = f"{previous_release_tag}..{source_tag}"
|
| 575 |
-
|
| 576 |
-
raw_log = git("log", "--reverse", "--format=%s%x1f%b%x1e", range_ref)
|
| 577 |
-
return parse_commit_entries(raw_log)
|
| 578 |
-
|
| 579 |
-
|
| 580 |
-
def build_release_notes_user_message(commits: list[CommitEntry]) -> str:
|
| 581 |
-
lines = ["Commit headings and descriptions:"]
|
| 582 |
-
|
| 583 |
-
if not commits:
|
| 584 |
-
lines.append("No commits were found in this release range.")
|
| 585 |
-
return "\n".join(lines)
|
| 586 |
-
|
| 587 |
-
for index, commit in enumerate(commits, start=1):
|
| 588 |
-
lines.append(f"{index}. Heading: {commit.heading}")
|
| 589 |
-
if commit.description:
|
| 590 |
-
lines.append("Description:")
|
| 591 |
-
lines.append(commit.description)
|
| 592 |
-
else:
|
| 593 |
-
lines.append("Description: (none)")
|
| 594 |
-
lines.append("")
|
| 595 |
-
|
| 596 |
-
return "\n".join(lines).strip()
|
| 597 |
-
|
| 598 |
-
|
| 599 |
-
def extract_openrouter_message_content(payload: object) -> str:
|
| 600 |
-
if not isinstance(payload, dict):
|
| 601 |
-
return ""
|
| 602 |
-
|
| 603 |
-
content = payload.get("content")
|
| 604 |
-
if isinstance(content, str):
|
| 605 |
-
return content
|
| 606 |
-
if not isinstance(content, list):
|
| 607 |
-
return ""
|
| 608 |
-
|
| 609 |
-
parts: list[str] = []
|
| 610 |
-
for part in content:
|
| 611 |
-
if not isinstance(part, dict):
|
| 612 |
-
continue
|
| 613 |
-
text = part.get("text")
|
| 614 |
-
if isinstance(text, str):
|
| 615 |
-
parts.append(text)
|
| 616 |
-
return "\n".join(parts)
|
| 617 |
-
|
| 618 |
-
|
| 619 |
-
def generate_release_body_with_openrouter(commits: list[CommitEntry]) -> str:
|
| 620 |
-
api_key = require_env("OPENROUTER_API_KEY")
|
| 621 |
-
model = require_any_env("OPENROUTER_MODEL_NAME", "OPENROUTER_MODEL")
|
| 622 |
-
system_prompt = load_text(OPENROUTER_SYSTEM_PROMPT_PATH)
|
| 623 |
-
repository = require_env("GITHUB_REPOSITORY")
|
| 624 |
-
user_message = build_release_notes_user_message(commits)
|
| 625 |
-
|
| 626 |
-
payload = {
|
| 627 |
-
"model": model,
|
| 628 |
-
"messages": [
|
| 629 |
-
{"role": "system", "content": system_prompt},
|
| 630 |
-
{"role": "user", "content": user_message},
|
| 631 |
-
],
|
| 632 |
-
"temperature": 0.2,
|
| 633 |
-
}
|
| 634 |
-
request = Request(
|
| 635 |
-
OPENROUTER_CHAT_COMPLETIONS_URL,
|
| 636 |
-
data=json.dumps(payload).encode("utf-8"),
|
| 637 |
-
headers={
|
| 638 |
-
"Authorization": f"Bearer {api_key}",
|
| 639 |
-
"Content-Type": "application/json",
|
| 640 |
-
"HTTP-Referer": f"https://github.com/{repository}",
|
| 641 |
-
"X-OpenRouter-Title": "Agent Zero Docker Release Notes",
|
| 642 |
-
},
|
| 643 |
-
method="POST",
|
| 644 |
-
)
|
| 645 |
-
|
| 646 |
-
try:
|
| 647 |
-
with urlopen(request, timeout=60) as response:
|
| 648 |
-
response_payload = json.loads(response.read().decode("utf-8"))
|
| 649 |
-
except HTTPError as exc:
|
| 650 |
-
details = exc.read().decode("utf-8", errors="replace").strip()
|
| 651 |
-
fail(f"OpenRouter request failed: {exc.code} {exc.reason}\n{details}")
|
| 652 |
-
except URLError as exc:
|
| 653 |
-
fail(f"OpenRouter request failed: {exc.reason}")
|
| 654 |
-
|
| 655 |
-
if not isinstance(response_payload, dict):
|
| 656 |
-
fail("OpenRouter response was not a JSON object.")
|
| 657 |
-
|
| 658 |
-
choices = response_payload.get("choices")
|
| 659 |
-
if not isinstance(choices, list) or not choices:
|
| 660 |
-
fail(f"OpenRouter response did not include choices: {json.dumps(response_payload)}")
|
| 661 |
-
|
| 662 |
-
first_choice = choices[0]
|
| 663 |
-
if not isinstance(first_choice, dict):
|
| 664 |
-
fail("OpenRouter returned an invalid choice payload.")
|
| 665 |
-
|
| 666 |
-
message = first_choice.get("message")
|
| 667 |
-
body = extract_openrouter_message_content(message).strip()
|
| 668 |
-
return body or "No release notes."
|
| 669 |
-
|
| 670 |
-
|
| 671 |
-
def resolve_release_command() -> None:
|
| 672 |
-
config = load_config()
|
| 673 |
-
branch = os.environ["TARGET_BRANCH"].strip()
|
| 674 |
-
source_tag = os.environ["TARGET_TAG"].strip()
|
| 675 |
-
|
| 676 |
-
if branch != config.main_branch:
|
| 677 |
-
write_output("should_release", "false")
|
| 678 |
-
write_output("skip_reason", f"Branch `{branch}` does not publish GitHub releases.")
|
| 679 |
-
return
|
| 680 |
-
|
| 681 |
-
branch_state = collect_branch_states(config, [branch])[branch]
|
| 682 |
-
if branch_state.latest_tag is None:
|
| 683 |
-
write_output("should_release", "false")
|
| 684 |
-
write_output("skip_reason", f"Branch `{branch}` has no releasable tags.")
|
| 685 |
-
return
|
| 686 |
-
|
| 687 |
-
if parse_release_tag(config, source_tag) is None or not tag_exists(source_tag):
|
| 688 |
-
write_output("should_release", "false")
|
| 689 |
-
write_output("skip_reason", f"Tag `{source_tag}` is not a releasable tag.")
|
| 690 |
-
return
|
| 691 |
-
|
| 692 |
-
commit = tag_commit(source_tag)
|
| 693 |
-
if not branch_contains_commit(branch, commit):
|
| 694 |
-
write_output("should_release", "false")
|
| 695 |
-
write_output("skip_reason", f"Tag `{source_tag}` is no longer reachable from `{branch}`.")
|
| 696 |
-
return
|
| 697 |
-
|
| 698 |
-
if branch_state.latest_tag != source_tag:
|
| 699 |
-
write_output("should_release", "false")
|
| 700 |
-
write_output(
|
| 701 |
-
"skip_reason",
|
| 702 |
-
f"Tag `{source_tag}` is not the highest release tag on `{branch}`.",
|
| 703 |
-
)
|
| 704 |
-
return
|
| 705 |
-
|
| 706 |
-
previous_release_tag = ""
|
| 707 |
-
commits: list[CommitEntry] = []
|
| 708 |
-
body = "Failed to generate release notes."
|
| 709 |
-
try:
|
| 710 |
-
previous_release_tag = previous_published_release_tag(config, source_tag) or ""
|
| 711 |
-
commits = collect_release_commits(previous_release_tag or None, source_tag)
|
| 712 |
-
body = generate_release_body_with_openrouter(commits)
|
| 713 |
-
except SystemExit:
|
| 714 |
-
print(
|
| 715 |
-
f"Release note generation failed for `{source_tag}`. Falling back to a static release body.",
|
| 716 |
-
file=sys.stderr,
|
| 717 |
-
)
|
| 718 |
-
except Exception as exc:
|
| 719 |
-
print(
|
| 720 |
-
f"Unexpected release note generation error for `{source_tag}`: {exc}. Falling back to a static release body.",
|
| 721 |
-
file=sys.stderr,
|
| 722 |
-
)
|
| 723 |
-
|
| 724 |
-
write_output("should_release", "true")
|
| 725 |
-
write_output("release_tag", source_tag)
|
| 726 |
-
write_output("release_name", source_tag)
|
| 727 |
-
write_output("previous_release_tag", previous_release_tag)
|
| 728 |
-
write_output("release_commit_count", str(len(commits)))
|
| 729 |
-
write_output("release_body", body)
|
| 730 |
-
print(source_tag)
|
| 731 |
-
|
| 732 |
-
|
| 733 |
-
def resolve_build_command() -> None:
|
| 734 |
-
config = load_config()
|
| 735 |
-
branch = os.environ["TARGET_BRANCH"].strip()
|
| 736 |
-
source_tag = os.environ["TARGET_TAG"].strip()
|
| 737 |
-
mode = os.environ["TARGET_MODE"].strip()
|
| 738 |
-
publish_version = os.environ["TARGET_PUBLISH_VERSION"].strip().lower() == "true"
|
| 739 |
-
publish_branch_tag = os.environ["TARGET_PUBLISH_BRANCH_TAG"].strip().lower() == "true"
|
| 740 |
-
|
| 741 |
-
branch_state = collect_branch_states(config, [branch])[branch]
|
| 742 |
-
if branch_state.latest_tag is None:
|
| 743 |
-
write_output("should_build", "false")
|
| 744 |
-
write_output("skip_reason", f"Branch `{branch}` has no releasable tags.")
|
| 745 |
-
return
|
| 746 |
-
|
| 747 |
-
if parse_release_tag(config, source_tag) is None or not tag_exists(source_tag):
|
| 748 |
-
write_output("should_build", "false")
|
| 749 |
-
write_output("skip_reason", f"Tag `{source_tag}` is no longer available.")
|
| 750 |
-
return
|
| 751 |
-
|
| 752 |
-
commit = tag_commit(source_tag)
|
| 753 |
-
if not branch_contains_commit(branch, commit):
|
| 754 |
-
write_output("should_build", "false")
|
| 755 |
-
write_output("skip_reason", f"Tag `{source_tag}` is no longer reachable from `{branch}`.")
|
| 756 |
-
return
|
| 757 |
-
|
| 758 |
-
mutable_tag = "latest" if branch == config.main_branch else branch
|
| 759 |
-
tags_to_push: list[str] = []
|
| 760 |
-
|
| 761 |
-
if mode == "push_latest_only":
|
| 762 |
-
if branch_state.latest_tag != source_tag:
|
| 763 |
-
write_output("should_build", "false")
|
| 764 |
-
write_output(
|
| 765 |
-
"skip_reason",
|
| 766 |
-
f"Tag `{source_tag}` is no longer the highest release tag on `{branch}`.",
|
| 767 |
-
)
|
| 768 |
-
return
|
| 769 |
-
if publish_version:
|
| 770 |
-
tags_to_push.append(f"{config.image_repo}:{source_tag}")
|
| 771 |
-
if publish_branch_tag:
|
| 772 |
-
tags_to_push.append(f"{config.image_repo}:{mutable_tag}")
|
| 773 |
-
|
| 774 |
-
elif mode == "push_promoted_tag":
|
| 775 |
-
if branch_state.latest_tag != source_tag:
|
| 776 |
-
write_output("should_build", "false")
|
| 777 |
-
write_output(
|
| 778 |
-
"skip_reason",
|
| 779 |
-
f"Tag `{source_tag}` is no longer the highest release tag on `{branch}`.",
|
| 780 |
-
)
|
| 781 |
-
return
|
| 782 |
-
if publish_version and not docker_tag_exists(config.image_repo, source_tag):
|
| 783 |
-
tags_to_push.append(f"{config.image_repo}:{source_tag}")
|
| 784 |
-
if publish_branch_tag:
|
| 785 |
-
tags_to_push.append(f"{config.image_repo}:{mutable_tag}")
|
| 786 |
-
|
| 787 |
-
elif mode == "manual_exact":
|
| 788 |
-
if publish_version:
|
| 789 |
-
tags_to_push.append(f"{config.image_repo}:{source_tag}")
|
| 790 |
-
if publish_branch_tag and branch_state.latest_tag == source_tag:
|
| 791 |
-
tags_to_push.append(f"{config.image_repo}:{mutable_tag}")
|
| 792 |
-
|
| 793 |
-
elif mode == "manual_backfill":
|
| 794 |
-
if publish_version and not docker_tag_exists(config.image_repo, source_tag):
|
| 795 |
-
tags_to_push.append(f"{config.image_repo}:{source_tag}")
|
| 796 |
-
if publish_branch_tag:
|
| 797 |
-
if branch != config.main_branch and branch_state.latest_tag != source_tag:
|
| 798 |
-
write_output("should_build", "false")
|
| 799 |
-
write_output(
|
| 800 |
-
"skip_reason",
|
| 801 |
-
f"Tag `{source_tag}` is no longer the newest release tag on `{branch}`.",
|
| 802 |
-
)
|
| 803 |
-
return
|
| 804 |
-
if branch == config.main_branch and branch_state.latest_tag != source_tag:
|
| 805 |
-
publish_branch_tag = False
|
| 806 |
-
if publish_branch_tag and not docker_tag_exists(config.image_repo, mutable_tag):
|
| 807 |
-
tags_to_push.append(f"{config.image_repo}:{mutable_tag}")
|
| 808 |
-
else:
|
| 809 |
-
fail(f"Unsupported resolve-build mode: {mode}")
|
| 810 |
-
|
| 811 |
-
tags_to_push = unique(tags_to_push)
|
| 812 |
-
if not tags_to_push:
|
| 813 |
-
write_output("should_build", "false")
|
| 814 |
-
write_output("skip_reason", "All requested Docker tags already exist or are no longer eligible.")
|
| 815 |
-
return
|
| 816 |
-
|
| 817 |
-
write_output("should_build", "true")
|
| 818 |
-
write_output("tags", "\n".join(tags_to_push))
|
| 819 |
-
write_output("display_tags", ", ".join(tag.rsplit(":", 1)[1] for tag in tags_to_push))
|
| 820 |
-
print("\n".join(tags_to_push))
|
| 821 |
-
|
| 822 |
-
|
| 823 |
-
def main() -> None:
|
| 824 |
-
if len(sys.argv) != 2:
|
| 825 |
-
fail("Usage: docker_release_plan.py <plan|resolve-build|resolve-release>")
|
| 826 |
-
|
| 827 |
-
command = sys.argv[1]
|
| 828 |
-
if command == "plan":
|
| 829 |
-
plan_command()
|
| 830 |
-
return
|
| 831 |
-
if command == "resolve-build":
|
| 832 |
-
resolve_build_command()
|
| 833 |
-
return
|
| 834 |
-
if command == "resolve-release":
|
| 835 |
-
resolve_release_command()
|
| 836 |
-
return
|
| 837 |
-
fail(f"Unknown command: {command}")
|
| 838 |
-
|
| 839 |
-
|
| 840 |
-
if __name__ == "__main__":
|
| 841 |
-
main()
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:896b9a98df30ded250d8dcb95bac2b80184fccffaa5908ac2ef9c7a3d58a8442
|
| 3 |
+
size 29693
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.github/workflows/close-inactive.yml
CHANGED
|
@@ -1,108 +1,3 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
schedule:
|
| 5 |
-
- cron: "17 3 * * *"
|
| 6 |
-
workflow_dispatch:
|
| 7 |
-
inputs:
|
| 8 |
-
inactive_days:
|
| 9 |
-
description: "Close items with no activity for more than N days"
|
| 10 |
-
required: false
|
| 11 |
-
default: "90"
|
| 12 |
-
dry_run:
|
| 13 |
-
description: "If true, only print URLs (no comment/close)"
|
| 14 |
-
required: false
|
| 15 |
-
default: "true"
|
| 16 |
-
|
| 17 |
-
permissions:
|
| 18 |
-
issues: write
|
| 19 |
-
pull-requests: write
|
| 20 |
-
|
| 21 |
-
env:
|
| 22 |
-
DEFAULT_INACTIVE_DAYS: "90"
|
| 23 |
-
DEFAULT_DRY_RUN: "false"
|
| 24 |
-
|
| 25 |
-
jobs:
|
| 26 |
-
close_inactive:
|
| 27 |
-
if: github.repository == 'agent0ai/agent-zero' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch)
|
| 28 |
-
runs-on: ubuntu-latest
|
| 29 |
-
steps:
|
| 30 |
-
- name: Find and optionally close inactive issues/PRs
|
| 31 |
-
uses: actions/github-script@v7
|
| 32 |
-
env:
|
| 33 |
-
INACTIVE_DAYS: ${{ github.event_name == 'workflow_dispatch' && inputs.inactive_days || env.DEFAULT_INACTIVE_DAYS }}
|
| 34 |
-
DRY_RUN: ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run || env.DEFAULT_DRY_RUN }}
|
| 35 |
-
with:
|
| 36 |
-
script: |
|
| 37 |
-
const inactiveDaysRaw = process.env.INACTIVE_DAYS ?? "90";
|
| 38 |
-
const inactiveDays = Number.parseInt(inactiveDaysRaw, 10);
|
| 39 |
-
if (!Number.isFinite(inactiveDays) || inactiveDays <= 0) {
|
| 40 |
-
core.setFailed(`Invalid INACTIVE_DAYS: ${inactiveDaysRaw}`);
|
| 41 |
-
return;
|
| 42 |
-
}
|
| 43 |
-
|
| 44 |
-
const dryRunRaw = (process.env.DRY_RUN ?? "true").toLowerCase();
|
| 45 |
-
const dryRun = ["1", "true", "yes", "y"].includes(dryRunRaw);
|
| 46 |
-
|
| 47 |
-
const now = new Date();
|
| 48 |
-
const cutoff = new Date(now.getTime() - inactiveDays * 24 * 60 * 60 * 1000);
|
| 49 |
-
const cutoffDate = cutoff.toISOString().slice(0, 10);
|
| 50 |
-
|
| 51 |
-
core.info(`inactiveDays=${inactiveDays}`);
|
| 52 |
-
core.info(`dryRun=${dryRun}`);
|
| 53 |
-
core.info(`cutoffDate=${cutoffDate}`);
|
| 54 |
-
|
| 55 |
-
const owner = context.repo.owner;
|
| 56 |
-
const repo = context.repo.repo;
|
| 57 |
-
|
| 58 |
-
async function processQuery(kind, searchQuery) {
|
| 59 |
-
core.info(`Searching ${kind}: ${searchQuery}`);
|
| 60 |
-
|
| 61 |
-
const items = await github.paginate(github.rest.search.issuesAndPullRequests, {
|
| 62 |
-
q: searchQuery,
|
| 63 |
-
per_page: 100,
|
| 64 |
-
});
|
| 65 |
-
|
| 66 |
-
if (items.length === 0) {
|
| 67 |
-
core.info(`No inactive ${kind} found.`);
|
| 68 |
-
return;
|
| 69 |
-
}
|
| 70 |
-
|
| 71 |
-
core.info(`Found ${items.length} inactive ${kind}. URLs:`);
|
| 72 |
-
for (const item of items) {
|
| 73 |
-
core.info(item.html_url);
|
| 74 |
-
}
|
| 75 |
-
|
| 76 |
-
if (dryRun) {
|
| 77 |
-
return;
|
| 78 |
-
}
|
| 79 |
-
|
| 80 |
-
for (const item of items) {
|
| 81 |
-
const issueNumber = item.number;
|
| 82 |
-
const url = item.html_url;
|
| 83 |
-
|
| 84 |
-
try {
|
| 85 |
-
await github.rest.issues.createComment({
|
| 86 |
-
owner,
|
| 87 |
-
repo,
|
| 88 |
-
issue_number: issueNumber,
|
| 89 |
-
body: `Closing due to inactivity of ${inactiveDays} days.`,
|
| 90 |
-
});
|
| 91 |
-
|
| 92 |
-
await github.rest.issues.update({
|
| 93 |
-
owner,
|
| 94 |
-
repo,
|
| 95 |
-
issue_number: issueNumber,
|
| 96 |
-
state: "closed",
|
| 97 |
-
});
|
| 98 |
-
|
| 99 |
-
core.info(`Closed: ${url}`);
|
| 100 |
-
} catch (err) {
|
| 101 |
-
core.warning(`Failed to close ${url}: ${err?.message ?? String(err)}`);
|
| 102 |
-
}
|
| 103 |
-
}
|
| 104 |
-
}
|
| 105 |
-
|
| 106 |
-
const base = `repo:${owner}/${repo} is:open updated:<${cutoffDate} sort:updated-asc`;
|
| 107 |
-
await processQuery("issues", `${base} is:issue`);
|
| 108 |
-
await processQuery("pull requests", `${base} is:pr`);
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:02195272e30ac37e3732b583e3d25c9b24282c6999ebe28a01061a08b6c512cd
|
| 3 |
+
size 3740
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.github/workflows/docker-publish.yml
CHANGED
|
@@ -1,236 +1,3 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
push:
|
| 5 |
-
branches:
|
| 6 |
-
- "testing"
|
| 7 |
-
- "ready"
|
| 8 |
-
- "main"
|
| 9 |
-
tags:
|
| 10 |
-
- "v*"
|
| 11 |
-
workflow_dispatch:
|
| 12 |
-
inputs:
|
| 13 |
-
tag:
|
| 14 |
-
description: "Optional release tag to rebuild, for example v1.21"
|
| 15 |
-
required: false
|
| 16 |
-
type: string
|
| 17 |
-
|
| 18 |
-
env:
|
| 19 |
-
# Non-main branches publish a Docker tag with the same name as the branch.
|
| 20 |
-
ALLOWED_BRANCHES: "testing ready main"
|
| 21 |
-
MAIN_BRANCH: "main"
|
| 22 |
-
RELEASE_TAG_REGEX: "^v([0-9]+)\\.([0-9]+)$"
|
| 23 |
-
MIN_RELEASE_MAJOR: "1"
|
| 24 |
-
MIN_RELEASE_MINOR: "0"
|
| 25 |
-
DOCKERFILE_DIR: "docker/run"
|
| 26 |
-
DOCKERFILE_PATH: "docker/run/Dockerfile"
|
| 27 |
-
DOCKER_IMAGE_NAME: "agent-zero"
|
| 28 |
-
DOCKER_PLATFORMS: "linux/amd64,linux/arm64"
|
| 29 |
-
|
| 30 |
-
permissions:
|
| 31 |
-
contents: read
|
| 32 |
-
|
| 33 |
-
jobs:
|
| 34 |
-
plan:
|
| 35 |
-
if: github.repository == 'agent0ai/agent-zero'
|
| 36 |
-
runs-on: ubuntu-latest
|
| 37 |
-
outputs:
|
| 38 |
-
has_work: ${{ steps.plan.outputs.has_work }}
|
| 39 |
-
matrix: ${{ steps.plan.outputs.matrix }}
|
| 40 |
-
steps:
|
| 41 |
-
- name: Validate Docker Hub secrets
|
| 42 |
-
env:
|
| 43 |
-
DOCKERHUB_ORG: ${{ secrets.DOCKERHUB_ORG }}
|
| 44 |
-
DOCKERHUB_OAT_TOKEN: ${{ secrets.DOCKERHUB_OAT_TOKEN }}
|
| 45 |
-
run: |
|
| 46 |
-
if [[ -z "$DOCKERHUB_ORG" || -z "$DOCKERHUB_OAT_TOKEN" ]]; then
|
| 47 |
-
echo "::error::Missing DOCKERHUB_ORG or DOCKERHUB_OAT_TOKEN secret."
|
| 48 |
-
exit 1
|
| 49 |
-
fi
|
| 50 |
-
|
| 51 |
-
- name: Check out repository
|
| 52 |
-
uses: actions/checkout@v4
|
| 53 |
-
with:
|
| 54 |
-
fetch-depth: 0
|
| 55 |
-
|
| 56 |
-
- name: Fetch remote branches and tags
|
| 57 |
-
run: git fetch --force --tags origin '+refs/heads/*:refs/remotes/origin/*'
|
| 58 |
-
|
| 59 |
-
- name: Set up Docker Buildx
|
| 60 |
-
uses: docker/setup-buildx-action@v3
|
| 61 |
-
|
| 62 |
-
- name: Log in to Docker Hub
|
| 63 |
-
uses: docker/login-action@v3
|
| 64 |
-
with:
|
| 65 |
-
username: ${{ secrets.DOCKERHUB_ORG }}
|
| 66 |
-
password: ${{ secrets.DOCKERHUB_OAT_TOKEN }}
|
| 67 |
-
|
| 68 |
-
- name: Plan Docker publish targets
|
| 69 |
-
id: plan
|
| 70 |
-
env:
|
| 71 |
-
EVENT_NAME: ${{ github.event_name }}
|
| 72 |
-
SOURCE_REF_NAME: ${{ github.ref_name }}
|
| 73 |
-
SOURCE_REF_TYPE: ${{ github.ref_type }}
|
| 74 |
-
BEFORE_SHA: ${{ github.event_name == 'push' && github.event.before || '' }}
|
| 75 |
-
AFTER_SHA: ${{ github.event_name == 'push' && github.sha || '' }}
|
| 76 |
-
MANUAL_TAG: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag || '' }}
|
| 77 |
-
DOCKER_IMAGE_REPO: ${{ format('{0}/{1}', secrets.DOCKERHUB_ORG, env.DOCKER_IMAGE_NAME) }}
|
| 78 |
-
run: python3 .github/scripts/docker_release_plan.py plan
|
| 79 |
-
|
| 80 |
-
build:
|
| 81 |
-
if: needs.plan.outputs.has_work == 'true'
|
| 82 |
-
needs: plan
|
| 83 |
-
runs-on: ubuntu-latest
|
| 84 |
-
permissions:
|
| 85 |
-
contents: write
|
| 86 |
-
strategy:
|
| 87 |
-
fail-fast: false
|
| 88 |
-
matrix: ${{ fromJson(needs.plan.outputs.matrix) }}
|
| 89 |
-
concurrency:
|
| 90 |
-
group: docker-publish-${{ github.repository }}-${{ matrix.branch }}
|
| 91 |
-
cancel-in-progress: false
|
| 92 |
-
steps:
|
| 93 |
-
- name: Check out repository
|
| 94 |
-
uses: actions/checkout@v4
|
| 95 |
-
with:
|
| 96 |
-
fetch-depth: 0
|
| 97 |
-
|
| 98 |
-
- name: Fetch remote branches and tags
|
| 99 |
-
run: git fetch --force --tags origin '+refs/heads/*:refs/remotes/origin/*'
|
| 100 |
-
|
| 101 |
-
- name: Validate Docker Hub secrets
|
| 102 |
-
env:
|
| 103 |
-
DOCKERHUB_ORG: ${{ secrets.DOCKERHUB_ORG }}
|
| 104 |
-
DOCKERHUB_OAT_TOKEN: ${{ secrets.DOCKERHUB_OAT_TOKEN }}
|
| 105 |
-
run: |
|
| 106 |
-
if [[ -z "$DOCKERHUB_ORG" || -z "$DOCKERHUB_OAT_TOKEN" ]]; then
|
| 107 |
-
echo "::error::Missing DOCKERHUB_ORG or DOCKERHUB_OAT_TOKEN secret."
|
| 108 |
-
exit 1
|
| 109 |
-
fi
|
| 110 |
-
|
| 111 |
-
- name: Set up QEMU
|
| 112 |
-
uses: docker/setup-qemu-action@v3
|
| 113 |
-
|
| 114 |
-
- name: Set up Docker Buildx
|
| 115 |
-
uses: docker/setup-buildx-action@v3
|
| 116 |
-
|
| 117 |
-
- name: Log in to Docker Hub
|
| 118 |
-
uses: docker/login-action@v3
|
| 119 |
-
with:
|
| 120 |
-
username: ${{ secrets.DOCKERHUB_ORG }}
|
| 121 |
-
password: ${{ secrets.DOCKERHUB_OAT_TOKEN }}
|
| 122 |
-
|
| 123 |
-
- name: Re-resolve Docker tags for this build
|
| 124 |
-
id: resolve
|
| 125 |
-
env:
|
| 126 |
-
EVENT_NAME: ${{ github.event_name }}
|
| 127 |
-
SOURCE_REF_NAME: ${{ github.ref_name }}
|
| 128 |
-
SOURCE_REF_TYPE: ${{ github.ref_type }}
|
| 129 |
-
BEFORE_SHA: ${{ github.event_name == 'push' && github.event.before || '' }}
|
| 130 |
-
AFTER_SHA: ${{ github.event_name == 'push' && github.sha || '' }}
|
| 131 |
-
MANUAL_TAG: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag || '' }}
|
| 132 |
-
DOCKER_IMAGE_REPO: ${{ format('{0}/{1}', secrets.DOCKERHUB_ORG, env.DOCKER_IMAGE_NAME) }}
|
| 133 |
-
TARGET_BRANCH: ${{ matrix.branch }}
|
| 134 |
-
TARGET_TAG: ${{ matrix.source_tag }}
|
| 135 |
-
TARGET_MODE: ${{ matrix.mode }}
|
| 136 |
-
TARGET_PUBLISH_VERSION: ${{ matrix.publish_version }}
|
| 137 |
-
TARGET_PUBLISH_BRANCH_TAG: ${{ matrix.publish_branch_tag }}
|
| 138 |
-
run: python3 .github/scripts/docker_release_plan.py resolve-build
|
| 139 |
-
|
| 140 |
-
- name: Skip when target is no longer eligible
|
| 141 |
-
if: steps.resolve.outputs.should_build != 'true'
|
| 142 |
-
run: echo "${{ steps.resolve.outputs.skip_reason }}"
|
| 143 |
-
|
| 144 |
-
- name: Set cache date
|
| 145 |
-
if: steps.resolve.outputs.should_build == 'true'
|
| 146 |
-
id: cache_date
|
| 147 |
-
run: echo "value=$(date -u +%Y-%m-%d:%H:%M:%S)" >> "$GITHUB_OUTPUT"
|
| 148 |
-
|
| 149 |
-
- name: Build and push Docker image
|
| 150 |
-
if: steps.resolve.outputs.should_build == 'true'
|
| 151 |
-
uses: docker/build-push-action@v6
|
| 152 |
-
with:
|
| 153 |
-
context: ${{ env.DOCKERFILE_DIR }}
|
| 154 |
-
file: ${{ env.DOCKERFILE_PATH }}
|
| 155 |
-
platforms: ${{ env.DOCKER_PLATFORMS }}
|
| 156 |
-
push: true
|
| 157 |
-
tags: ${{ steps.resolve.outputs.tags }}
|
| 158 |
-
build-args: |
|
| 159 |
-
BRANCH=${{ matrix.branch }}
|
| 160 |
-
CACHE_DATE=${{ steps.cache_date.outputs.value }}
|
| 161 |
-
|
| 162 |
-
- name: Resolve GitHub release target
|
| 163 |
-
if: steps.resolve.outputs.should_build == 'true'
|
| 164 |
-
id: release_plan
|
| 165 |
-
env:
|
| 166 |
-
EVENT_NAME: ${{ github.event_name }}
|
| 167 |
-
SOURCE_REF_NAME: ${{ github.ref_name }}
|
| 168 |
-
SOURCE_REF_TYPE: ${{ github.ref_type }}
|
| 169 |
-
BEFORE_SHA: ${{ github.event_name == 'push' && github.event.before || '' }}
|
| 170 |
-
AFTER_SHA: ${{ github.event_name == 'push' && github.sha || '' }}
|
| 171 |
-
MANUAL_TAG: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag || '' }}
|
| 172 |
-
DOCKER_IMAGE_REPO: ${{ format('{0}/{1}', secrets.DOCKERHUB_ORG, env.DOCKER_IMAGE_NAME) }}
|
| 173 |
-
TARGET_BRANCH: ${{ matrix.branch }}
|
| 174 |
-
TARGET_TAG: ${{ matrix.source_tag }}
|
| 175 |
-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
| 176 |
-
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
|
| 177 |
-
OPENROUTER_MODEL_NAME: ${{ vars.OPENROUTER_MODEL_NAME }}
|
| 178 |
-
run: python3 .github/scripts/docker_release_plan.py resolve-release
|
| 179 |
-
|
| 180 |
-
- name: Skip GitHub release
|
| 181 |
-
if: steps.resolve.outputs.should_build == 'true' && steps.release_plan.outputs.should_release != 'true'
|
| 182 |
-
run: echo "${{ steps.release_plan.outputs.skip_reason }}"
|
| 183 |
-
|
| 184 |
-
- name: Create or update GitHub release
|
| 185 |
-
if: steps.resolve.outputs.should_build == 'true' && steps.release_plan.outputs.should_release == 'true'
|
| 186 |
-
uses: actions/github-script@v7
|
| 187 |
-
env:
|
| 188 |
-
RELEASE_TAG: ${{ steps.release_plan.outputs.release_tag }}
|
| 189 |
-
RELEASE_NAME: ${{ steps.release_plan.outputs.release_name }}
|
| 190 |
-
RELEASE_BODY: ${{ steps.release_plan.outputs.release_body }}
|
| 191 |
-
with:
|
| 192 |
-
script: |
|
| 193 |
-
const owner = context.repo.owner;
|
| 194 |
-
const repo = context.repo.repo;
|
| 195 |
-
const tag = process.env.RELEASE_TAG;
|
| 196 |
-
const name = process.env.RELEASE_NAME;
|
| 197 |
-
const body = process.env.RELEASE_BODY;
|
| 198 |
-
|
| 199 |
-
try {
|
| 200 |
-
const existing = await github.rest.repos.getReleaseByTag({
|
| 201 |
-
owner,
|
| 202 |
-
repo,
|
| 203 |
-
tag,
|
| 204 |
-
});
|
| 205 |
-
|
| 206 |
-
await github.rest.repos.updateRelease({
|
| 207 |
-
owner,
|
| 208 |
-
repo,
|
| 209 |
-
release_id: existing.data.id,
|
| 210 |
-
tag_name: tag,
|
| 211 |
-
name,
|
| 212 |
-
body,
|
| 213 |
-
draft: false,
|
| 214 |
-
prerelease: false,
|
| 215 |
-
make_latest: "true",
|
| 216 |
-
});
|
| 217 |
-
|
| 218 |
-
core.info(`Updated release ${tag}`);
|
| 219 |
-
} catch (error) {
|
| 220 |
-
if (error.status !== 404) {
|
| 221 |
-
throw error;
|
| 222 |
-
}
|
| 223 |
-
|
| 224 |
-
await github.rest.repos.createRelease({
|
| 225 |
-
owner,
|
| 226 |
-
repo,
|
| 227 |
-
tag_name: tag,
|
| 228 |
-
name,
|
| 229 |
-
body,
|
| 230 |
-
draft: false,
|
| 231 |
-
prerelease: false,
|
| 232 |
-
make_latest: "true",
|
| 233 |
-
});
|
| 234 |
-
|
| 235 |
-
core.info(`Created release ${tag}`);
|
| 236 |
-
}
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:49dd4916573cc8dad39525a67b637e9879a0c222c97b834f5e72c7ed02e50a20
|
| 3 |
+
size 8548
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.gitignore
CHANGED
|
@@ -1,52 +1,3 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
**/__pycache__/
|
| 5 |
-
*.py[cod]
|
| 6 |
-
**/.conda/
|
| 7 |
-
**/node_modules/
|
| 8 |
-
|
| 9 |
-
#Ignore IDE files
|
| 10 |
-
.cursor/
|
| 11 |
-
.windsurf/
|
| 12 |
-
|
| 13 |
-
# ignore test files in root dir
|
| 14 |
-
/*.test.py
|
| 15 |
-
|
| 16 |
-
# Ignore all contents of the virtual environment directory
|
| 17 |
-
.venv/
|
| 18 |
-
|
| 19 |
-
# obsolete folders
|
| 20 |
-
/memory/
|
| 21 |
-
/knowledge/custom/
|
| 22 |
-
/instruments/
|
| 23 |
-
|
| 24 |
-
# Handle logs directory
|
| 25 |
-
logs/**
|
| 26 |
-
!logs/**/
|
| 27 |
-
|
| 28 |
-
# Handle tmp and usr directory
|
| 29 |
-
tmp/**
|
| 30 |
-
!tmp/**/
|
| 31 |
-
|
| 32 |
-
# hack to keep .gitkeep but ignore nested repos
|
| 33 |
-
# Ignore everything under usr
|
| 34 |
-
usr/**
|
| 35 |
-
# Ignore nested repos
|
| 36 |
-
/usr/**/.git
|
| 37 |
-
# Allow git to traverse directories
|
| 38 |
-
!usr/**/
|
| 39 |
-
# Re-ignore everything again
|
| 40 |
-
usr/**/*
|
| 41 |
-
# But allow .gitkeep files
|
| 42 |
-
!usr/**/.gitkeep
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
# Global rule to include .gitkeep files anywhere
|
| 46 |
-
!**/.gitkeep
|
| 47 |
-
|
| 48 |
-
# for browser-use
|
| 49 |
-
agent_history.gif
|
| 50 |
-
|
| 51 |
-
.agent/**
|
| 52 |
-
.claude/**
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:5760fba62009e975160fd6830995de30e75b3ca05ad6b7c2f69687e7691d050a
|
| 3 |
+
size 789
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.vscode/extensions.json
CHANGED
|
@@ -1,7 +1,3 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
"ms-python.debugpy",
|
| 5 |
-
"ms-python.python"
|
| 6 |
-
]
|
| 7 |
-
}
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:c372492c38c6ff5040d4de969bde64752703fac076d237fd6f5cb418d28195db
|
| 3 |
+
size 122
|
|
|
|
|
|
|
|
|
|
|
|
.vscode/launch.json
CHANGED
|
@@ -1,24 +1,3 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
{
|
| 6 |
-
"name": "Debug run_ui.py",
|
| 7 |
-
"type": "debugpy",
|
| 8 |
-
"request": "launch",
|
| 9 |
-
"program": "./run_ui.py",
|
| 10 |
-
"console": "integratedTerminal",
|
| 11 |
-
"justMyCode": false,
|
| 12 |
-
"args": ["--development=true", "-Xfrozen_modules=off"]
|
| 13 |
-
},
|
| 14 |
-
{
|
| 15 |
-
"name": "Debug current file",
|
| 16 |
-
"type": "debugpy",
|
| 17 |
-
"request": "launch",
|
| 18 |
-
"program": "${file}",
|
| 19 |
-
"console": "integratedTerminal",
|
| 20 |
-
"justMyCode": false,
|
| 21 |
-
"args": ["--development=true", "-Xfrozen_modules=off"]
|
| 22 |
-
}
|
| 23 |
-
]
|
| 24 |
-
}
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:863ede1d1e561787d4333cd4d4e2d71581ef6c49ebe5cf8dcec5f2ba3e693c7a
|
| 3 |
+
size 565
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.vscode/settings.json
CHANGED
|
@@ -1,17 +1,3 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
"windsurfPyright.analysis.typeCheckingMode": "standard",
|
| 5 |
-
// Enable JavaScript linting
|
| 6 |
-
"eslint.enable": true,
|
| 7 |
-
"eslint.validate": ["javascript", "javascriptreact"],
|
| 8 |
-
// Set import root for JS/TS
|
| 9 |
-
"javascript.preferences.importModuleSpecifier": "relative",
|
| 10 |
-
"js/ts.implicitProjectConfig.checkJs": true,
|
| 11 |
-
"jsconfig.paths": {
|
| 12 |
-
"*": ["webui/*"]
|
| 13 |
-
},
|
| 14 |
-
// Optional: point VSCode to jsconfig.json if you add one
|
| 15 |
-
"jsconfig.json": "${workspaceFolder}/jsconfig.json",
|
| 16 |
-
"postman.settings.dotenv-detection-notification-visibility": false
|
| 17 |
-
}
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:9d702ba4601b5cf95f3738524887ceebd9163fbc349c53d367f7616f2fc0387a
|
| 3 |
+
size 686
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
AGENTS.md
CHANGED
|
@@ -1,253 +1,3 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
## Quick Reference
|
| 6 |
-
Tech Stack: Python 3.12+ | Flask | Alpine.js | LiteLLM | WebSocket (Socket.io)
|
| 7 |
-
Dev Server: python run_ui.py (runs on http://localhost:50001 by default)
|
| 8 |
-
Run Tests: pytest (standard) or pytest tests/test_name.py (file-scoped)
|
| 9 |
-
Documentation: README.md | docs/
|
| 10 |
-
Frontend Deep Dives: [Component System](docs/agents/AGENTS.components.md) | [Modal System](docs/agents/AGENTS.modals.md) | [Plugin Architecture](docs/agents/AGENTS.plugins.md)
|
| 11 |
-
|
| 12 |
-
---
|
| 13 |
-
|
| 14 |
-
## Table of Contents
|
| 15 |
-
1. [Project Overview](#project-overview)
|
| 16 |
-
2. [Core Commands](#core-commands)
|
| 17 |
-
3. [Docker Environment](#docker-environment)
|
| 18 |
-
4. [Project Structure](#project-structure)
|
| 19 |
-
5. [Development Patterns & Conventions](#development-patterns--conventions)
|
| 20 |
-
6. [Safety and Permissions](#safety-and-permissions)
|
| 21 |
-
7. [Code Examples](#code-examples)
|
| 22 |
-
8. [Git Workflow](#git-workflow)
|
| 23 |
-
9. [Release Notes](#release-notes)
|
| 24 |
-
10. [Troubleshooting](#troubleshooting)
|
| 25 |
-
|
| 26 |
-
---
|
| 27 |
-
|
| 28 |
-
## Project Overview
|
| 29 |
-
|
| 30 |
-
Agent Zero is a dynamic, organic agentic framework designed to grow and learn. It uses the operating system as a tool, featuring a multi-agent cooperation model where every agent can create subordinates to break down tasks.
|
| 31 |
-
|
| 32 |
-
Type: Full-Stack Agentic Framework (Python Backend + Alpine.js Frontend)
|
| 33 |
-
Status: Active Development
|
| 34 |
-
Primary Language(s): Python, JavaScript (ES Modules)
|
| 35 |
-
|
| 36 |
-
---
|
| 37 |
-
|
| 38 |
-
## Core Commands
|
| 39 |
-
|
| 40 |
-
### Setup
|
| 41 |
-
Do not combine these commands; run them individually:
|
| 42 |
-
```bash
|
| 43 |
-
pip install -r requirements.txt
|
| 44 |
-
pip install -r requirements2.txt
|
| 45 |
-
```
|
| 46 |
-
- Start WebUI: python run_ui.py
|
| 47 |
-
|
| 48 |
-
---
|
| 49 |
-
|
| 50 |
-
## Docker Environment
|
| 51 |
-
|
| 52 |
-
When running in Docker, Agent Zero uses two distinct Python runtimes to isolate the framework from the code being executed:
|
| 53 |
-
|
| 54 |
-
### 1. Framework Runtime (/opt/venv-a0)
|
| 55 |
-
- Version: Python 3.12.4
|
| 56 |
-
- Purpose: Runs the Agent Zero backend, API, and core logic.
|
| 57 |
-
- Packages: Contains all dependencies from requirements.txt.
|
| 58 |
-
|
| 59 |
-
### 2. Execution Runtime (/opt/venv)
|
| 60 |
-
- Version: Python 3.13
|
| 61 |
-
- Purpose: Default environment for the interactive terminal and the agent's code execution tool.
|
| 62 |
-
- Behavior: This is the environment active when you docker exec into the container. Packages installed by the agent via pip install during a task are stored here.
|
| 63 |
-
|
| 64 |
-
---
|
| 65 |
-
|
| 66 |
-
## Project Structure
|
| 67 |
-
|
| 68 |
-
```
|
| 69 |
-
/
|
| 70 |
-
├── agent.py # Core Agent and AgentContext definitions
|
| 71 |
-
├── initialize.py # Framework initialization logic
|
| 72 |
-
├── models.py # LLM provider configurations
|
| 73 |
-
├── run_ui.py # WebUI server entry point
|
| 74 |
-
├── api/ # API Handlers (ApiHandler subclasses) + WsHandler subclasses (ws_*.py)
|
| 75 |
-
├── extensions/ # Backend lifecycle extensions
|
| 76 |
-
├── helpers/ # Shared Python utilities (plugins, files, etc.)
|
| 77 |
-
├── tools/ # Agent tools (Tool subclasses)
|
| 78 |
-
├── webui/
|
| 79 |
-
│ ├── components/ # Alpine.js components
|
| 80 |
-
│ ├── js/ # Core frontend logic (modals, stores, etc.)
|
| 81 |
-
│ └── index.html # Main UI shell
|
| 82 |
-
├── usr/ # User data directory (isolated from core)
|
| 83 |
-
│ ├── plugins/ # Custom user plugins
|
| 84 |
-
│ ├── settings.json # User-specific configuration
|
| 85 |
-
│ └── workdir/ # Default agent workspace
|
| 86 |
-
├── plugins/ # Core system plugins
|
| 87 |
-
├── agents/ # Agent profiles (prompts and config)
|
| 88 |
-
├── prompts/ # System and message prompt templates
|
| 89 |
-
├── knowledge/
|
| 90 |
-
│ └── main/about/ # Agent self-knowledge (indexed into vector DB for runtime recall)
|
| 91 |
-
│ ├── identity.md # Philosophy, principles, project context
|
| 92 |
-
│ ├── architecture.md # Agent loop, memory pipeline, multi-agent, extensions
|
| 93 |
-
│ ├── capabilities.md # Detailed capabilities and limitations
|
| 94 |
-
│ ├── configuration.md # LLM roles, providers, profiles, plugins, settings
|
| 95 |
-
│ └── setup-and-deployment.md # Docker deployment, updates, troubleshooting
|
| 96 |
-
└── tests/ # Pytest suite
|
| 97 |
-
```
|
| 98 |
-
|
| 99 |
-
Key Files:
|
| 100 |
-
- agent.py: Defines AgentContext and the main Agent class.
|
| 101 |
-
- helpers/plugins.py: Plugin discovery and configuration logic.
|
| 102 |
-
- webui/js/AlpineStore.js: Store factory for reactive frontend state.
|
| 103 |
-
- helpers/api.py: Base class for all API endpoints.
|
| 104 |
-
- scripts/openrouter_release_notes_system_prompt.md: Editable system prompt used to generate GitHub release notes during Docker publishing.
|
| 105 |
-
- knowledge/main/about/: Agent self-knowledge files, indexed into the vector DB for runtime recall. Not user-facing docs - written for the agent's internal reference.
|
| 106 |
-
- docs/agents/AGENTS.components.md: Deep dive into the frontend component architecture.
|
| 107 |
-
- docs/agents/AGENTS.modals.md: Guide to the stacked modal system.
|
| 108 |
-
- docs/agents/AGENTS.plugins.md: Comprehensive guide to the full-stack plugin system.
|
| 109 |
-
|
| 110 |
-
---
|
| 111 |
-
|
| 112 |
-
## Development Patterns & Conventions
|
| 113 |
-
|
| 114 |
-
### Backend (Python)
|
| 115 |
-
- Context Access: Use from agent import AgentContext, AgentContextType (not helpers.context).
|
| 116 |
-
- Communication: Use mq from helpers.messages to log proactive UI messages:
|
| 117 |
-
mq.log_user_message(context.id, "Message", source="Plugin")
|
| 118 |
-
- API Handlers: Derive from ApiHandler in helpers/api.py.
|
| 119 |
-
- Extensions: Use the extension framework in helpers/extension.py for lifecycle hooks.
|
| 120 |
-
- Error Handling: Use RepairableException for errors the LLM might be able to fix.
|
| 121 |
-
|
| 122 |
-
### Frontend (Alpine.js)
|
| 123 |
-
- Store Gating: Always wrap store-dependent content in a template:
|
| 124 |
-
```html
|
| 125 |
-
<div x-data>
|
| 126 |
-
<template x-if="$store.myStore">
|
| 127 |
-
<div x-init="$store.myStore.onOpen()">...</div>
|
| 128 |
-
</template>
|
| 129 |
-
</div>
|
| 130 |
-
```
|
| 131 |
-
- Store Registration: Use createStore from /js/AlpineStore.js.
|
| 132 |
-
- Modals: Use openModal(path) and closeModal() from /js/modals.js.
|
| 133 |
-
|
| 134 |
-
### Plugin Architecture
|
| 135 |
-
- Location: Always develop new plugins in usr/plugins/.
|
| 136 |
-
- Manifest: Every plugin requires a plugin.yaml with name, description, version, and optionally settings_sections, per_project_config, per_agent_config, and always_enabled.
|
| 137 |
-
- Discovery: Conventions based on folder names (api/, tools/, webui/, extensions/).
|
| 138 |
-
- Plugin-local Python imports: Prefer `usr.plugins.<plugin_name>...` for code that lives under `usr/plugins/`. Avoid `sys.path` hacks and avoid symlink-dependent `plugins.<plugin_name>...` imports for community plugins.
|
| 139 |
-
- Runtime hooks: Plugins may also expose hooks in hooks.py, callable by the framework through helpers.plugins.call_plugin_hook(...).
|
| 140 |
-
- Hook runtime: hooks.py executes inside the Agent Zero framework Python environment, so sys.executable -m pip installs dependencies into that same framework runtime.
|
| 141 |
-
- Environment targeting: If a plugin needs packages or binaries for the separate agent execution runtime or system environment, it must explicitly switch environments in a subprocess by targeting the correct interpreter, virtualenv, or package manager.
|
| 142 |
-
- Settings: Use get_plugin_config(plugin_name, agent=agent) to retrieve settings. Plugins can expose a UI for settings via webui/config.html. Plugin settings modals instantiate a local context from $store.pluginSettingsPrototype; bind plugin fields to config.* and use context.* for modal-level state and actions.
|
| 143 |
-
- Activation: Global and scoped activation rules are stored as .toggle-1 (ON) and .toggle-0 (OFF). Scoped rules are handled via the plugin "Switch" modal.
|
| 144 |
-
- Cleanup rule: Plugins should not permanently modify the system in ways that outlive the plugin. Deleting a plugin should not leave behind symlinks, unmanaged services, or stray files outside plugin-owned paths unless the user explicitly requested that behavior.
|
| 145 |
-
|
| 146 |
-
### Releases
|
| 147 |
-
- Docker publishing automation lives in `.github/workflows/docker-publish.yml`.
|
| 148 |
-
- Releasable tags follow `v{X}.{Y}` and only tags `>= v1.0` are considered by the workflow.
|
| 149 |
-
- The latest eligible tag on `main` also creates or updates a GitHub release after the Docker image push succeeds.
|
| 150 |
-
- GitHub release notes are generated on the fly in `.github/scripts/docker_release_plan.py` by comparing the new tag against the previous published GitHub release tag, collecting commit subjects and descriptions in that range, and sending them to OpenRouter.
|
| 151 |
-
- The OpenRouter call uses `OPENROUTER_API_KEY` and `OPENROUTER_MODEL_NAME` from the workflow environment, with the system prompt stored in `scripts/openrouter_release_notes_system_prompt.md`.
|
| 152 |
-
- Prioritize user-visible features, important fixes, infra or packaging changes, and breaking notes. Skip low-signal churn.
|
| 153 |
-
- If the generated summary has no meaningful content, the release body falls back to `No release notes.`
|
| 154 |
-
|
| 155 |
-
### Lifecycle Synchronization
|
| 156 |
-
| Action | Backend Extension | Frontend Lifecycle |
|
| 157 |
-
|---|---|---|
|
| 158 |
-
| Initialization | agent_init | init() in Store |
|
| 159 |
-
| Mounting | N/A | x-create directive |
|
| 160 |
-
| Processing | monologue_start/end | UI loading state |
|
| 161 |
-
| Cleanup | context_deleted | x-destroy directive |
|
| 162 |
-
|
| 163 |
-
---
|
| 164 |
-
|
| 165 |
-
## Safety and Permissions
|
| 166 |
-
|
| 167 |
-
### Allowed Without Asking
|
| 168 |
-
- Read any file in the repository.
|
| 169 |
-
- Update code files in usr/.
|
| 170 |
-
|
| 171 |
-
### Ask Before Executing
|
| 172 |
-
- pip install (new dependencies).
|
| 173 |
-
- Deleting core files outside of usr/ or tmp/.
|
| 174 |
-
- Modifying agent.py or initialize.py.
|
| 175 |
-
- Making git commits or pushes.
|
| 176 |
-
|
| 177 |
-
### Never Do
|
| 178 |
-
- Commit, hardcode or leak secrets or .env files.
|
| 179 |
-
- Bypass CSRF or authentication checks.
|
| 180 |
-
- Hardcode API keys.
|
| 181 |
-
|
| 182 |
-
---
|
| 183 |
-
|
| 184 |
-
## Code Examples
|
| 185 |
-
|
| 186 |
-
### API Handler (Good)
|
| 187 |
-
```python
|
| 188 |
-
from helpers.api import ApiHandler, Request, Response
|
| 189 |
-
|
| 190 |
-
class MyHandler(ApiHandler):
|
| 191 |
-
async def process(self, input: dict, request: Request) -> dict | Response:
|
| 192 |
-
# Business logic here
|
| 193 |
-
return {"ok": True, "data": "result"}
|
| 194 |
-
```
|
| 195 |
-
|
| 196 |
-
### Alpine Store (Good)
|
| 197 |
-
```javascript
|
| 198 |
-
import { createStore } from "/js/AlpineStore.js";
|
| 199 |
-
|
| 200 |
-
export const store = createStore("myStore", {
|
| 201 |
-
items: [],
|
| 202 |
-
init() { /* global setup */ },
|
| 203 |
-
onOpen() { /* mount setup */ },
|
| 204 |
-
cleanup() { /* unmount cleanup */ }
|
| 205 |
-
});
|
| 206 |
-
```
|
| 207 |
-
|
| 208 |
-
### Tool Definition (Good)
|
| 209 |
-
```python
|
| 210 |
-
from helpers.tool import Tool, Response
|
| 211 |
-
|
| 212 |
-
class MyTool(Tool):
|
| 213 |
-
async def execute(self, **kwargs):
|
| 214 |
-
# Tool logic
|
| 215 |
-
return Response(message="Success", break_loop=False)
|
| 216 |
-
```
|
| 217 |
-
|
| 218 |
-
---
|
| 219 |
-
|
| 220 |
-
## Git Workflow
|
| 221 |
-
|
| 222 |
-
- Docker publish automation lives in `.github/workflows/docker-publish.yml`.
|
| 223 |
-
- Release tags handled by automation must match `vX.Y` and be `>= v1.0`.
|
| 224 |
-
- Allowed release branches are configured at the top of the workflow. `main` publishes `<tag>` and `latest`; other allowed branches publish only the branch tag.
|
| 225 |
-
- Manual dispatch accepts an optional tag. Without a tag it backfills missing Docker Hub tags. With a tag it rebuilds that exact target and only refreshes `latest` and the GitHub release when that tag is still the newest eligible tag on `main`.
|
| 226 |
-
|
| 227 |
-
---
|
| 228 |
-
|
| 229 |
-
## Release Notes
|
| 230 |
-
|
| 231 |
-
- The latest eligible `main` tag generates its GitHub release notes during Docker publish instead of reading committed Markdown files.
|
| 232 |
-
- The release-note prompt is editable in `scripts/openrouter_release_notes_system_prompt.md`.
|
| 233 |
-
- The commit range starts at the previous published GitHub release tag, not merely the previous semantic tag in the repository.
|
| 234 |
-
|
| 235 |
-
## Troubleshooting
|
| 236 |
-
|
| 237 |
-
### Dependency Conflicts
|
| 238 |
-
If pip install fails, try running in a clean virtual environment:
|
| 239 |
-
```bash
|
| 240 |
-
python -m venv .venv
|
| 241 |
-
source .venv/bin/activate
|
| 242 |
-
pip install -r requirements.txt
|
| 243 |
-
pip install -r requirements2.txt
|
| 244 |
-
```
|
| 245 |
-
|
| 246 |
-
### WebSocket Connection Failures
|
| 247 |
-
- Check if X-CSRF-Token is being sent.
|
| 248 |
-
- Ensure the runtime ID in the session matches the current server instance.
|
| 249 |
-
|
| 250 |
-
---
|
| 251 |
-
|
| 252 |
-
*Last updated: 2026-03-25*
|
| 253 |
-
*Maintained by: Agent Zero Core Team*
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:7854c02496e343196eab895f1fe44bfa34af38041878054b078c22d7200dcc6f
|
| 3 |
+
size 11578
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
DockerfileLocal
CHANGED
|
@@ -1,36 +1,3 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
# Set BRANCH to "local" if not provided
|
| 6 |
-
ARG BRANCH=local
|
| 7 |
-
ENV BRANCH=$BRANCH
|
| 8 |
-
|
| 9 |
-
# Copy filesystem files to root
|
| 10 |
-
COPY ./docker/run/fs/ /
|
| 11 |
-
# Copy current development files to git, they will only be used in "local" branch
|
| 12 |
-
COPY ./ /git/agent-zero
|
| 13 |
-
|
| 14 |
-
# pre installation steps
|
| 15 |
-
RUN bash /ins/pre_install.sh $BRANCH
|
| 16 |
-
|
| 17 |
-
# install A0
|
| 18 |
-
RUN bash /ins/install_A0.sh $BRANCH
|
| 19 |
-
|
| 20 |
-
# install additional software
|
| 21 |
-
RUN bash /ins/install_additional.sh $BRANCH
|
| 22 |
-
|
| 23 |
-
# cleanup repo and install A0 without caching, this speeds up builds
|
| 24 |
-
ARG CACHE_DATE=none
|
| 25 |
-
RUN echo "cache buster $CACHE_DATE" && bash /ins/install_A02.sh $BRANCH
|
| 26 |
-
|
| 27 |
-
# post installation steps
|
| 28 |
-
RUN bash /ins/post_install.sh $BRANCH
|
| 29 |
-
|
| 30 |
-
# Expose ports
|
| 31 |
-
EXPOSE 22 80 9000-9009
|
| 32 |
-
|
| 33 |
-
RUN chmod +x /exe/initialize.sh /exe/run_A0.sh /exe/run_searxng.sh /exe/run_tunnel_api.sh
|
| 34 |
-
|
| 35 |
-
# initialize runtime and switch to supervisord
|
| 36 |
-
CMD ["/exe/initialize.sh", "$BRANCH"]
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:9083efc3881bf1b7edb950eca369e75c380628ee372cb5290cd0939a7459b236
|
| 3 |
+
size 975
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
LICENSE
CHANGED
|
@@ -1,23 +1,3 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
Contact: pr@agent-zero.ai
|
| 5 |
-
Repository: https://github.com/agent0ai/agent-zero
|
| 6 |
-
|
| 7 |
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 8 |
-
of this software and associated documentation files (the "Software"), to deal
|
| 9 |
-
in the Software without restriction, including without limitation the rights
|
| 10 |
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 11 |
-
copies of the Software, and to permit persons to whom the Software is
|
| 12 |
-
furnished to do so, subject to the following conditions:
|
| 13 |
-
|
| 14 |
-
The above copyright notice and this permission notice shall be included in all
|
| 15 |
-
copies or substantial portions of the Software.
|
| 16 |
-
|
| 17 |
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 18 |
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 19 |
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 20 |
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 21 |
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 22 |
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 23 |
-
SOFTWARE.
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:23844ed5fb9976b15e6c0be1b617918cb1c32e13e8d70b74100dae08d40fbf23
|
| 3 |
+
size 1150
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
agent.py
CHANGED
|
@@ -1,1023 +1,3 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
from dataclasses import dataclass, field
|
| 5 |
-
from datetime import datetime, timezone
|
| 6 |
-
from typing import Any, Awaitable, Coroutine, Dict, Literal
|
| 7 |
-
from enum import Enum
|
| 8 |
-
import models
|
| 9 |
-
|
| 10 |
-
from helpers import (
|
| 11 |
-
extract_tools,
|
| 12 |
-
files,
|
| 13 |
-
errors,
|
| 14 |
-
history,
|
| 15 |
-
tokens,
|
| 16 |
-
context as context_helper,
|
| 17 |
-
dirty_json,
|
| 18 |
-
subagents,
|
| 19 |
-
)
|
| 20 |
-
from helpers import extension
|
| 21 |
-
from helpers.print_style import PrintStyle
|
| 22 |
-
|
| 23 |
-
from langchain_core.prompts import (
|
| 24 |
-
ChatPromptTemplate,
|
| 25 |
-
)
|
| 26 |
-
from langchain_core.messages import SystemMessage, BaseMessage
|
| 27 |
-
|
| 28 |
-
import helpers.log as Log
|
| 29 |
-
from helpers.dirty_json import DirtyJson
|
| 30 |
-
from helpers.defer import DeferredTask
|
| 31 |
-
from typing import Callable
|
| 32 |
-
from helpers.localization import Localization
|
| 33 |
-
from helpers import extension
|
| 34 |
-
from helpers.errors import RepairableException, InterventionException, HandledException
|
| 35 |
-
|
| 36 |
-
class AgentContextType(Enum):
|
| 37 |
-
USER = "user"
|
| 38 |
-
TASK = "task"
|
| 39 |
-
BACKGROUND = "background"
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
class AgentContext:
|
| 43 |
-
|
| 44 |
-
_contexts: dict[str, "AgentContext"] = {}
|
| 45 |
-
_contexts_lock = threading.RLock()
|
| 46 |
-
_counter: int = 0
|
| 47 |
-
_notification_manager = None
|
| 48 |
-
|
| 49 |
-
@extension.extensible
|
| 50 |
-
def __init__(
|
| 51 |
-
self,
|
| 52 |
-
config: "AgentConfig",
|
| 53 |
-
id: str | None = None,
|
| 54 |
-
name: str | None = None,
|
| 55 |
-
agent0: "Agent|None" = None,
|
| 56 |
-
log: Log.Log | None = None,
|
| 57 |
-
paused: bool = False,
|
| 58 |
-
streaming_agent: "Agent|None" = None,
|
| 59 |
-
created_at: datetime | None = None,
|
| 60 |
-
type: AgentContextType = AgentContextType.USER,
|
| 61 |
-
last_message: datetime | None = None,
|
| 62 |
-
data: dict | None = None,
|
| 63 |
-
output_data: dict | None = None,
|
| 64 |
-
set_current: bool = False,
|
| 65 |
-
):
|
| 66 |
-
# initialize context
|
| 67 |
-
self.id = id or AgentContext.generate_id()
|
| 68 |
-
existing = None
|
| 69 |
-
with AgentContext._contexts_lock:
|
| 70 |
-
existing = AgentContext._contexts.get(self.id, None)
|
| 71 |
-
if existing:
|
| 72 |
-
AgentContext._contexts.pop(self.id, None)
|
| 73 |
-
AgentContext._contexts[self.id] = self
|
| 74 |
-
if existing and existing.task:
|
| 75 |
-
existing.task.kill()
|
| 76 |
-
if set_current:
|
| 77 |
-
AgentContext.set_current(self.id)
|
| 78 |
-
|
| 79 |
-
# initialize state
|
| 80 |
-
self.name = name
|
| 81 |
-
self.config = config
|
| 82 |
-
self.data = data or {}
|
| 83 |
-
self.output_data = output_data or {}
|
| 84 |
-
self.log = log or Log.Log()
|
| 85 |
-
self.log.context = self
|
| 86 |
-
self.paused = paused
|
| 87 |
-
self.streaming_agent = streaming_agent
|
| 88 |
-
self.task: DeferredTask | None = None
|
| 89 |
-
self.created_at = created_at or datetime.now(timezone.utc)
|
| 90 |
-
self.type = type
|
| 91 |
-
AgentContext._counter += 1
|
| 92 |
-
self.no = AgentContext._counter
|
| 93 |
-
self.last_message = last_message or datetime.now(timezone.utc)
|
| 94 |
-
|
| 95 |
-
# initialize agent at last (context is complete now)
|
| 96 |
-
self.agent0 = agent0 or Agent(0, self.config, self)
|
| 97 |
-
|
| 98 |
-
@staticmethod
|
| 99 |
-
def get(id: str):
|
| 100 |
-
with AgentContext._contexts_lock:
|
| 101 |
-
return AgentContext._contexts.get(id, None)
|
| 102 |
-
|
| 103 |
-
@staticmethod
|
| 104 |
-
def use(id: str):
|
| 105 |
-
context = AgentContext.get(id)
|
| 106 |
-
if context:
|
| 107 |
-
AgentContext.set_current(id)
|
| 108 |
-
else:
|
| 109 |
-
AgentContext.set_current("")
|
| 110 |
-
return context
|
| 111 |
-
|
| 112 |
-
@staticmethod
|
| 113 |
-
def current():
|
| 114 |
-
ctxid = context_helper.get_context_data("agent_context_id", "")
|
| 115 |
-
if not ctxid:
|
| 116 |
-
return None
|
| 117 |
-
return AgentContext.get(ctxid)
|
| 118 |
-
|
| 119 |
-
@staticmethod
|
| 120 |
-
def set_current(ctxid: str):
|
| 121 |
-
context_helper.set_context_data("agent_context_id", ctxid)
|
| 122 |
-
|
| 123 |
-
@staticmethod
|
| 124 |
-
def first():
|
| 125 |
-
with AgentContext._contexts_lock:
|
| 126 |
-
if not AgentContext._contexts:
|
| 127 |
-
return None
|
| 128 |
-
return list(AgentContext._contexts.values())[0]
|
| 129 |
-
|
| 130 |
-
@staticmethod
|
| 131 |
-
def all():
|
| 132 |
-
with AgentContext._contexts_lock:
|
| 133 |
-
return list(AgentContext._contexts.values())
|
| 134 |
-
|
| 135 |
-
@staticmethod
|
| 136 |
-
def generate_id():
|
| 137 |
-
def generate_short_id():
|
| 138 |
-
return "".join(random.choices(string.ascii_letters + string.digits, k=8))
|
| 139 |
-
|
| 140 |
-
while True:
|
| 141 |
-
short_id = generate_short_id()
|
| 142 |
-
with AgentContext._contexts_lock:
|
| 143 |
-
if short_id not in AgentContext._contexts:
|
| 144 |
-
return short_id
|
| 145 |
-
|
| 146 |
-
@classmethod
|
| 147 |
-
def get_notification_manager(cls):
|
| 148 |
-
if cls._notification_manager is None:
|
| 149 |
-
from helpers.notification import NotificationManager # type: ignore
|
| 150 |
-
|
| 151 |
-
cls._notification_manager = NotificationManager()
|
| 152 |
-
return cls._notification_manager
|
| 153 |
-
|
| 154 |
-
@staticmethod
|
| 155 |
-
@extension.extensible
|
| 156 |
-
def remove(id: str):
|
| 157 |
-
with AgentContext._contexts_lock:
|
| 158 |
-
context = AgentContext._contexts.pop(id, None)
|
| 159 |
-
if context and context.task:
|
| 160 |
-
context.task.kill()
|
| 161 |
-
return context
|
| 162 |
-
|
| 163 |
-
def get_data(self, key: str, recursive: bool = True):
|
| 164 |
-
# recursive is not used now, prepared for context hierarchy
|
| 165 |
-
return self.data.get(key, None)
|
| 166 |
-
|
| 167 |
-
def set_data(self, key: str, value: Any, recursive: bool = True):
|
| 168 |
-
# recursive is not used now, prepared for context hierarchy
|
| 169 |
-
self.data[key] = value
|
| 170 |
-
|
| 171 |
-
def get_output_data(self, key: str, recursive: bool = True):
|
| 172 |
-
# recursive is not used now, prepared for context hierarchy
|
| 173 |
-
return self.output_data.get(key, None)
|
| 174 |
-
|
| 175 |
-
def set_output_data(self, key: str, value: Any, recursive: bool = True):
|
| 176 |
-
# recursive is not used now, prepared for context hierarchy
|
| 177 |
-
self.output_data[key] = value
|
| 178 |
-
|
| 179 |
-
# @extension.extensible
|
| 180 |
-
def output(self):
|
| 181 |
-
return {
|
| 182 |
-
"id": self.id,
|
| 183 |
-
"name": self.name,
|
| 184 |
-
"created_at": (
|
| 185 |
-
Localization.get().serialize_datetime(self.created_at)
|
| 186 |
-
if self.created_at
|
| 187 |
-
else Localization.get().serialize_datetime(datetime.fromtimestamp(0))
|
| 188 |
-
),
|
| 189 |
-
"no": self.no,
|
| 190 |
-
"log_guid": self.log.guid,
|
| 191 |
-
"log_version": len(self.log.updates),
|
| 192 |
-
"log_length": len(self.log.logs),
|
| 193 |
-
"paused": self.paused,
|
| 194 |
-
"last_message": (
|
| 195 |
-
Localization.get().serialize_datetime(self.last_message)
|
| 196 |
-
if self.last_message
|
| 197 |
-
else Localization.get().serialize_datetime(datetime.fromtimestamp(0))
|
| 198 |
-
),
|
| 199 |
-
"type": self.type.value,
|
| 200 |
-
"running": self.is_running(),
|
| 201 |
-
**self.output_data,
|
| 202 |
-
}
|
| 203 |
-
|
| 204 |
-
@staticmethod
|
| 205 |
-
def log_to_all(
|
| 206 |
-
type: Log.Type,
|
| 207 |
-
heading: str | None = None,
|
| 208 |
-
content: str | None = None,
|
| 209 |
-
kvps: dict | None = None,
|
| 210 |
-
update_progress: Log.ProgressUpdate | None = None,
|
| 211 |
-
id: str | None = None, # Add id parameter
|
| 212 |
-
**kwargs,
|
| 213 |
-
) -> list[Log.LogItem]:
|
| 214 |
-
items: list[Log.LogItem] = []
|
| 215 |
-
for context in AgentContext.all():
|
| 216 |
-
items.append(
|
| 217 |
-
context.log.log(
|
| 218 |
-
type, heading, content, kvps, update_progress, id, **kwargs
|
| 219 |
-
)
|
| 220 |
-
)
|
| 221 |
-
return items
|
| 222 |
-
|
| 223 |
-
@extension.extensible
|
| 224 |
-
def kill_process(self):
|
| 225 |
-
if self.task:
|
| 226 |
-
self.task.kill()
|
| 227 |
-
|
| 228 |
-
@extension.extensible
|
| 229 |
-
def reset(self):
|
| 230 |
-
self.kill_process()
|
| 231 |
-
self.log.reset()
|
| 232 |
-
self.agent0 = Agent(0, self.config, self)
|
| 233 |
-
self.streaming_agent = None
|
| 234 |
-
self.paused = False
|
| 235 |
-
|
| 236 |
-
@extension.extensible
|
| 237 |
-
def nudge(self):
|
| 238 |
-
self.kill_process()
|
| 239 |
-
self.paused = False
|
| 240 |
-
self.task = self.communicate(UserMessage(self.agent0.read_prompt("fw.msg_nudge.md")))
|
| 241 |
-
return self.task
|
| 242 |
-
|
| 243 |
-
@extension.extensible
|
| 244 |
-
def get_agent(self):
|
| 245 |
-
return self.streaming_agent or self.agent0
|
| 246 |
-
|
| 247 |
-
def is_running(self) -> bool:
|
| 248 |
-
return (self.task and self.task.is_alive()) or False
|
| 249 |
-
|
| 250 |
-
@extension.extensible
|
| 251 |
-
def communicate(self, msg: "UserMessage", broadcast_level: int = 1):
|
| 252 |
-
self.paused = False # unpause if paused
|
| 253 |
-
|
| 254 |
-
current_agent = self.get_agent()
|
| 255 |
-
|
| 256 |
-
if self.task and self.task.is_alive():
|
| 257 |
-
# set intervention messages to agent(s):
|
| 258 |
-
intervention_agent = current_agent
|
| 259 |
-
while intervention_agent and broadcast_level != 0:
|
| 260 |
-
intervention_agent.intervention = msg
|
| 261 |
-
broadcast_level -= 1
|
| 262 |
-
intervention_agent = intervention_agent.data.get(
|
| 263 |
-
Agent.DATA_NAME_SUPERIOR, None
|
| 264 |
-
)
|
| 265 |
-
else:
|
| 266 |
-
self.task = self.run_task(self._process_chain, current_agent, msg)
|
| 267 |
-
|
| 268 |
-
return self.task
|
| 269 |
-
|
| 270 |
-
@extension.extensible
|
| 271 |
-
def run_task(
|
| 272 |
-
self, func: Callable[..., Coroutine[Any, Any, Any]], *args: Any, **kwargs: Any
|
| 273 |
-
):
|
| 274 |
-
if not self.task:
|
| 275 |
-
self.task = DeferredTask(
|
| 276 |
-
thread_name=self.__class__.__name__,
|
| 277 |
-
)
|
| 278 |
-
self.task.start_task(func, *args, **kwargs)
|
| 279 |
-
return self.task
|
| 280 |
-
|
| 281 |
-
# this wrapper ensures that superior agents are called back if the chat was loaded from file and original callstack is gone
|
| 282 |
-
@extension.extensible
|
| 283 |
-
async def _process_chain(self, agent: "Agent", msg: "UserMessage|str", user=True):
|
| 284 |
-
try:
|
| 285 |
-
msg_template = (
|
| 286 |
-
agent.hist_add_user_message(msg) # type: ignore
|
| 287 |
-
if user
|
| 288 |
-
else agent.hist_add_tool_result(
|
| 289 |
-
tool_name="call_subordinate", tool_result=msg # type: ignore
|
| 290 |
-
)
|
| 291 |
-
)
|
| 292 |
-
response = await agent.monologue() # type: ignore
|
| 293 |
-
superior = agent.data.get(Agent.DATA_NAME_SUPERIOR, None)
|
| 294 |
-
if superior:
|
| 295 |
-
response = await self._process_chain(superior, response, False) # type: ignore
|
| 296 |
-
|
| 297 |
-
# call end of process extensions
|
| 298 |
-
await extension.call_extensions_async("process_chain_end", agent=self.get_agent(), data={})
|
| 299 |
-
|
| 300 |
-
return response
|
| 301 |
-
except Exception as e:
|
| 302 |
-
await self.handle_exception("process_chain", e)
|
| 303 |
-
|
| 304 |
-
@extension.extensible
|
| 305 |
-
async def handle_exception(self, location: str, exception: Exception):
|
| 306 |
-
if exception:
|
| 307 |
-
raise exception # exception handling is done by extensions
|
| 308 |
-
|
| 309 |
-
|
| 310 |
-
@dataclass
|
| 311 |
-
class AgentConfig:
|
| 312 |
-
mcp_servers: str
|
| 313 |
-
profile: str = ""
|
| 314 |
-
knowledge_subdirs: list[str] = field(default_factory=lambda: ["default", "custom"])
|
| 315 |
-
additional: Dict[str, Any] = field(default_factory=dict)
|
| 316 |
-
|
| 317 |
-
|
| 318 |
-
@dataclass
|
| 319 |
-
class UserMessage:
|
| 320 |
-
message: str
|
| 321 |
-
attachments: list[str] = field(default_factory=list[str])
|
| 322 |
-
system_message: list[str] = field(default_factory=list[str])
|
| 323 |
-
id: str = ""
|
| 324 |
-
|
| 325 |
-
|
| 326 |
-
class LoopData:
|
| 327 |
-
def __init__(self, **kwargs):
|
| 328 |
-
self.iteration = -1
|
| 329 |
-
self.system = []
|
| 330 |
-
self.user_message: history.Message | None = None
|
| 331 |
-
self.history_output: list[history.OutputMessage] = []
|
| 332 |
-
self.extras_temporary: OrderedDict[str, history.MessageContent] = OrderedDict()
|
| 333 |
-
self.extras_persistent: OrderedDict[str, history.MessageContent] = OrderedDict()
|
| 334 |
-
self.last_response = ""
|
| 335 |
-
self.params_temporary: dict = {}
|
| 336 |
-
self.params_persistent: dict = {}
|
| 337 |
-
self.current_tool = None
|
| 338 |
-
|
| 339 |
-
# override values with kwargs
|
| 340 |
-
for key, value in kwargs.items():
|
| 341 |
-
setattr(self, key, value)
|
| 342 |
-
|
| 343 |
-
|
| 344 |
-
class Agent:
|
| 345 |
-
|
| 346 |
-
DATA_NAME_SUPERIOR = "_superior"
|
| 347 |
-
DATA_NAME_SUBORDINATE = "_subordinate"
|
| 348 |
-
DATA_NAME_CTX_WINDOW = "ctx_window"
|
| 349 |
-
|
| 350 |
-
@extension.extensible
|
| 351 |
-
def __init__(
|
| 352 |
-
self, number: int, config: AgentConfig, context: AgentContext | None = None
|
| 353 |
-
):
|
| 354 |
-
|
| 355 |
-
# agent config
|
| 356 |
-
self.config = config
|
| 357 |
-
|
| 358 |
-
# agent context
|
| 359 |
-
self.context = context or AgentContext(config=config, agent0=self)
|
| 360 |
-
|
| 361 |
-
# non-config vars
|
| 362 |
-
self.number = number
|
| 363 |
-
self.agent_name = f"A{self.number}"
|
| 364 |
-
|
| 365 |
-
self.history = history.History(self) # type: ignore[abstract]
|
| 366 |
-
self.last_user_message: history.Message | None = None
|
| 367 |
-
self.intervention: UserMessage | None = None
|
| 368 |
-
self.data: dict[str, Any] = {} # free data object all the tools can use
|
| 369 |
-
|
| 370 |
-
extension.call_extensions_sync("agent_init", self)
|
| 371 |
-
|
| 372 |
-
@extension.extensible
|
| 373 |
-
async def monologue(self):
|
| 374 |
-
while True:
|
| 375 |
-
try:
|
| 376 |
-
# loop data dictionary to pass to extensions
|
| 377 |
-
self.loop_data = LoopData(user_message=self.last_user_message)
|
| 378 |
-
# call monologue_start extensions
|
| 379 |
-
await extension.call_extensions_async(
|
| 380 |
-
"monologue_start", self, loop_data=self.loop_data
|
| 381 |
-
)
|
| 382 |
-
|
| 383 |
-
printer = PrintStyle(italic=True, font_color="#b3ffd9", padding=False)
|
| 384 |
-
|
| 385 |
-
# let the agent run message loop until he stops it with a response tool
|
| 386 |
-
while True:
|
| 387 |
-
|
| 388 |
-
self.context.streaming_agent = self # mark self as current streamer
|
| 389 |
-
self.loop_data.iteration += 1
|
| 390 |
-
self.loop_data.params_temporary = {} # clear temporary params
|
| 391 |
-
|
| 392 |
-
# call message_loop_start extensions
|
| 393 |
-
await extension.call_extensions_async(
|
| 394 |
-
"message_loop_start", self, loop_data=self.loop_data
|
| 395 |
-
)
|
| 396 |
-
await self.handle_intervention()
|
| 397 |
-
|
| 398 |
-
try:
|
| 399 |
-
# prepare LLM chain (model, system, history)
|
| 400 |
-
prompt = await self.prepare_prompt(loop_data=self.loop_data)
|
| 401 |
-
|
| 402 |
-
# call before_main_llm_call extensions
|
| 403 |
-
await extension.call_extensions_async(
|
| 404 |
-
"before_main_llm_call", self, loop_data=self.loop_data
|
| 405 |
-
)
|
| 406 |
-
await self.handle_intervention()
|
| 407 |
-
|
| 408 |
-
|
| 409 |
-
async def reasoning_callback(chunk: str, full: str):
|
| 410 |
-
await self.handle_intervention()
|
| 411 |
-
if chunk == full:
|
| 412 |
-
printer.print("Reasoning: ") # start of reasoning
|
| 413 |
-
# Pass chunk and full data to extensions for processing
|
| 414 |
-
stream_data = {"chunk": chunk, "full": full}
|
| 415 |
-
await extension.call_extensions_async(
|
| 416 |
-
"reasoning_stream_chunk",
|
| 417 |
-
self,
|
| 418 |
-
loop_data=self.loop_data,
|
| 419 |
-
stream_data=stream_data,
|
| 420 |
-
)
|
| 421 |
-
# Stream masked chunk after extensions processed it
|
| 422 |
-
if stream_data.get("chunk"):
|
| 423 |
-
printer.stream(stream_data["chunk"])
|
| 424 |
-
# Use the potentially modified full text for downstream processing
|
| 425 |
-
await self.handle_reasoning_stream(stream_data["full"])
|
| 426 |
-
|
| 427 |
-
async def stream_callback(chunk: str, full: str):
|
| 428 |
-
await self.handle_intervention()
|
| 429 |
-
# output the agent response stream
|
| 430 |
-
if chunk == full:
|
| 431 |
-
printer.print("Response: ") # start of response
|
| 432 |
-
# Pass chunk and full data to extensions for processing
|
| 433 |
-
stream_data = {"chunk": chunk, "full": full}
|
| 434 |
-
await extension.call_extensions_async(
|
| 435 |
-
"response_stream_chunk",
|
| 436 |
-
self,
|
| 437 |
-
loop_data=self.loop_data,
|
| 438 |
-
stream_data=stream_data,
|
| 439 |
-
)
|
| 440 |
-
# Stream masked chunk after extensions processed it
|
| 441 |
-
if stream_data.get("chunk"):
|
| 442 |
-
printer.stream(stream_data["chunk"])
|
| 443 |
-
# Use the potentially modified full text for downstream processing
|
| 444 |
-
await self.handle_response_stream(stream_data["full"])
|
| 445 |
-
|
| 446 |
-
# call main LLM
|
| 447 |
-
agent_response, _reasoning = await self.call_chat_model(
|
| 448 |
-
messages=prompt,
|
| 449 |
-
response_callback=stream_callback,
|
| 450 |
-
reasoning_callback=reasoning_callback,
|
| 451 |
-
)
|
| 452 |
-
await self.handle_intervention(agent_response)
|
| 453 |
-
|
| 454 |
-
# Notify extensions to finalize their stream filters
|
| 455 |
-
await extension.call_extensions_async(
|
| 456 |
-
"reasoning_stream_end", self, loop_data=self.loop_data
|
| 457 |
-
)
|
| 458 |
-
await self.handle_intervention(agent_response)
|
| 459 |
-
|
| 460 |
-
await extension.call_extensions_async(
|
| 461 |
-
"response_stream_end", self, loop_data=self.loop_data
|
| 462 |
-
)
|
| 463 |
-
|
| 464 |
-
await self.handle_intervention(agent_response)
|
| 465 |
-
|
| 466 |
-
if (
|
| 467 |
-
self.loop_data.last_response == agent_response
|
| 468 |
-
): # if assistant_response is the same as last message in history, let him know
|
| 469 |
-
# Append the assistant's response to the history
|
| 470 |
-
log_item = self.loop_data.params_temporary.get("log_item_generating")
|
| 471 |
-
self.hist_add_ai_response(agent_response, id=log_item.id if log_item else "")
|
| 472 |
-
# Append warning message to the history
|
| 473 |
-
warning_msg = self.read_prompt("fw.msg_repeat.md")
|
| 474 |
-
wmsg = self.hist_add_warning(message=warning_msg)
|
| 475 |
-
PrintStyle(font_color="orange", padding=True).print(
|
| 476 |
-
warning_msg
|
| 477 |
-
)
|
| 478 |
-
self.context.log.log(type="warning", content=warning_msg, id=wmsg.id)
|
| 479 |
-
|
| 480 |
-
else: # otherwise proceed with tool
|
| 481 |
-
# Append the assistant's response to the history
|
| 482 |
-
log_item = self.loop_data.params_temporary.get("log_item_generating")
|
| 483 |
-
self.hist_add_ai_response(agent_response, id=log_item.id if log_item else "")
|
| 484 |
-
# process tools requested in agent message
|
| 485 |
-
tools_result = await self.process_tools(agent_response)
|
| 486 |
-
if tools_result: # final response of message loop available
|
| 487 |
-
return tools_result # break the execution if the task is done
|
| 488 |
-
|
| 489 |
-
# exceptions inside message loop:
|
| 490 |
-
except Exception as e:
|
| 491 |
-
await self.handle_exception("message_loop", e)
|
| 492 |
-
|
| 493 |
-
finally:
|
| 494 |
-
# call message_loop_end extensions
|
| 495 |
-
if self.context.task and self.context.task.is_alive(): # don't call extensions post mortem
|
| 496 |
-
await extension.call_extensions_async(
|
| 497 |
-
"message_loop_end", self, loop_data=self.loop_data
|
| 498 |
-
)
|
| 499 |
-
|
| 500 |
-
|
| 501 |
-
|
| 502 |
-
# exceptions outside message loop:
|
| 503 |
-
except Exception as e:
|
| 504 |
-
await self.handle_exception("monologue", e)
|
| 505 |
-
finally:
|
| 506 |
-
self.context.streaming_agent = None # unset current streamer
|
| 507 |
-
# call monologue_end extensions
|
| 508 |
-
if self.context.task and self.context.task.is_alive(): # don't call extensions post mortem
|
| 509 |
-
await extension.call_extensions_async(
|
| 510 |
-
"monologue_end", self, loop_data=self.loop_data
|
| 511 |
-
) # type: ignore
|
| 512 |
-
|
| 513 |
-
@extension.extensible
|
| 514 |
-
async def prepare_prompt(self, loop_data: LoopData) -> list[BaseMessage]:
|
| 515 |
-
self.context.log.set_progress("Building prompt")
|
| 516 |
-
|
| 517 |
-
# call extensions before setting prompts
|
| 518 |
-
await extension.call_extensions_async(
|
| 519 |
-
"message_loop_prompts_before", self, loop_data=loop_data
|
| 520 |
-
)
|
| 521 |
-
|
| 522 |
-
# set system prompt and message history
|
| 523 |
-
loop_data.system = await self.get_system_prompt(self.loop_data)
|
| 524 |
-
loop_data.history_output = self.history.output()
|
| 525 |
-
|
| 526 |
-
# and allow extensions to edit them
|
| 527 |
-
await extension.call_extensions_async(
|
| 528 |
-
"message_loop_prompts_after", self, loop_data=loop_data
|
| 529 |
-
)
|
| 530 |
-
|
| 531 |
-
# concatenate system prompt
|
| 532 |
-
system_text = "\n\n".join(loop_data.system)
|
| 533 |
-
|
| 534 |
-
# join extras
|
| 535 |
-
extras = history.Message( # type: ignore[abstract]
|
| 536 |
-
False,
|
| 537 |
-
content=self.read_prompt(
|
| 538 |
-
"agent.context.extras.md",
|
| 539 |
-
extras=dirty_json.stringify(
|
| 540 |
-
{**loop_data.extras_persistent, **loop_data.extras_temporary}
|
| 541 |
-
),
|
| 542 |
-
),
|
| 543 |
-
).output()
|
| 544 |
-
loop_data.extras_temporary.clear()
|
| 545 |
-
|
| 546 |
-
# convert history + extras to LLM format
|
| 547 |
-
history_langchain: list[BaseMessage] = history.output_langchain(
|
| 548 |
-
loop_data.history_output + extras
|
| 549 |
-
)
|
| 550 |
-
|
| 551 |
-
# build full prompt from system prompt, message history and extrS
|
| 552 |
-
full_prompt: list[BaseMessage] = [
|
| 553 |
-
SystemMessage(content=system_text),
|
| 554 |
-
*history_langchain,
|
| 555 |
-
]
|
| 556 |
-
full_text = ChatPromptTemplate.from_messages(full_prompt).format()
|
| 557 |
-
|
| 558 |
-
# store as last context window content
|
| 559 |
-
self.set_data(
|
| 560 |
-
Agent.DATA_NAME_CTX_WINDOW,
|
| 561 |
-
{
|
| 562 |
-
"text": full_text,
|
| 563 |
-
"tokens": tokens.approximate_tokens(full_text),
|
| 564 |
-
},
|
| 565 |
-
)
|
| 566 |
-
|
| 567 |
-
return full_prompt
|
| 568 |
-
|
| 569 |
-
@extension.extensible
|
| 570 |
-
async def handle_exception(self, location: str, exception: Exception):
|
| 571 |
-
if exception:
|
| 572 |
-
raise exception # exception handling is done by extensions
|
| 573 |
-
|
| 574 |
-
# exception_data = {"exception": exception}
|
| 575 |
-
# await self.call_extensions(
|
| 576 |
-
# "message_loop_exception", exception_data=exception_data
|
| 577 |
-
# )
|
| 578 |
-
|
| 579 |
-
# # If extensions cleared the exception, continue.
|
| 580 |
-
# if not exception_data.get("exception"):
|
| 581 |
-
# return
|
| 582 |
-
|
| 583 |
-
# # Backwards-compatible fallback (should normally be handled by _90 extension).
|
| 584 |
-
# exception = exception_data["exception"]
|
| 585 |
-
# if isinstance(exception, HandledException):
|
| 586 |
-
# raise exception
|
| 587 |
-
# elif isinstance(exception, asyncio.CancelledError):
|
| 588 |
-
# PrintStyle(font_color="white", background_color="red", padding=True).print(
|
| 589 |
-
# f"Context {self.context.id} terminated during message loop"
|
| 590 |
-
# )
|
| 591 |
-
# raise HandledException(exception)
|
| 592 |
-
|
| 593 |
-
# else:
|
| 594 |
-
# error_text = errors.error_text(exception)
|
| 595 |
-
# error_message = errors.format_error(exception)
|
| 596 |
-
|
| 597 |
-
# # Mask secrets in error messages
|
| 598 |
-
# PrintStyle(font_color="red", padding=True).print(error_message)
|
| 599 |
-
# self.context.log.log(
|
| 600 |
-
# type="error",
|
| 601 |
-
# content=error_message,
|
| 602 |
-
# )
|
| 603 |
-
# PrintStyle(font_color="red", padding=True).print(
|
| 604 |
-
# f"{self.agent_name}: {error_text}"
|
| 605 |
-
# )
|
| 606 |
-
|
| 607 |
-
# raise HandledException(exception) # Re-raise the exception to kill the loop
|
| 608 |
-
|
| 609 |
-
@extension.extensible
|
| 610 |
-
async def get_system_prompt(self, loop_data: LoopData) -> list[str]:
|
| 611 |
-
system_prompt: list[str] = []
|
| 612 |
-
await extension.call_extensions_async(
|
| 613 |
-
"system_prompt", self, system_prompt=system_prompt, loop_data=loop_data
|
| 614 |
-
)
|
| 615 |
-
return system_prompt
|
| 616 |
-
|
| 617 |
-
@extension.extensible
|
| 618 |
-
def parse_prompt(self, _prompt_file: str, **kwargs):
|
| 619 |
-
dirs = subagents.get_paths(self, "prompts")
|
| 620 |
-
|
| 621 |
-
prompt = files.parse_file(
|
| 622 |
-
_prompt_file, _directories=dirs, _agent=self, **kwargs
|
| 623 |
-
)
|
| 624 |
-
return prompt
|
| 625 |
-
|
| 626 |
-
@extension.extensible
|
| 627 |
-
def read_prompt(self, file: str, **kwargs) -> str:
|
| 628 |
-
dirs = subagents.get_paths(self, "prompts")
|
| 629 |
-
|
| 630 |
-
prompt = files.read_prompt_file(file, _directories=dirs, _agent=self, **kwargs)
|
| 631 |
-
if files.is_full_json_template(prompt):
|
| 632 |
-
prompt = files.remove_code_fences(prompt)
|
| 633 |
-
return prompt
|
| 634 |
-
|
| 635 |
-
def get_data(self, field: str):
|
| 636 |
-
return self.data.get(field, None)
|
| 637 |
-
|
| 638 |
-
def set_data(self, field: str, value):
|
| 639 |
-
self.data[field] = value
|
| 640 |
-
|
| 641 |
-
@extension.extensible
|
| 642 |
-
def hist_add_message(
|
| 643 |
-
self, ai: bool, content: history.MessageContent, tokens: int = 0, id: str = ""
|
| 644 |
-
):
|
| 645 |
-
self.last_message = datetime.now(timezone.utc)
|
| 646 |
-
# Allow extensions to process content before adding to history
|
| 647 |
-
content_data = {"content": content}
|
| 648 |
-
extension.call_extensions_sync(
|
| 649 |
-
"hist_add_before", self, content_data=content_data, ai=ai
|
| 650 |
-
)
|
| 651 |
-
return self.history.add_message(
|
| 652 |
-
ai=ai, content=content_data["content"], tokens=tokens, id=id
|
| 653 |
-
)
|
| 654 |
-
|
| 655 |
-
@extension.extensible
|
| 656 |
-
def hist_add_user_message(self, message: UserMessage, intervention: bool = False):
|
| 657 |
-
self.history.new_topic() # user message starts a new topic in history
|
| 658 |
-
|
| 659 |
-
# load message template based on intervention
|
| 660 |
-
if intervention:
|
| 661 |
-
content = self.parse_prompt(
|
| 662 |
-
"fw.intervention.md",
|
| 663 |
-
message=message.message,
|
| 664 |
-
attachments=message.attachments,
|
| 665 |
-
system_message=message.system_message,
|
| 666 |
-
)
|
| 667 |
-
else:
|
| 668 |
-
content = self.parse_prompt(
|
| 669 |
-
"fw.user_message.md",
|
| 670 |
-
message=message.message,
|
| 671 |
-
attachments=message.attachments,
|
| 672 |
-
system_message=message.system_message,
|
| 673 |
-
)
|
| 674 |
-
|
| 675 |
-
# remove empty parts from template
|
| 676 |
-
if isinstance(content, dict):
|
| 677 |
-
content = {k: v for k, v in content.items() if v}
|
| 678 |
-
|
| 679 |
-
# add to history
|
| 680 |
-
msg = self.hist_add_message(False, content=content, id=message.id) # type: ignore
|
| 681 |
-
self.last_user_message = msg
|
| 682 |
-
return msg
|
| 683 |
-
|
| 684 |
-
@extension.extensible
|
| 685 |
-
def hist_add_ai_response(self, message: str, id: str = ""):
|
| 686 |
-
self.loop_data.last_response = message
|
| 687 |
-
content = self.parse_prompt("fw.ai_response.md", message=message)
|
| 688 |
-
return self.hist_add_message(True, content=content, id=id)
|
| 689 |
-
|
| 690 |
-
@extension.extensible
|
| 691 |
-
def hist_add_warning(self, message: history.MessageContent, id: str = ""):
|
| 692 |
-
content = self.parse_prompt("fw.warning.md", message=message)
|
| 693 |
-
return self.hist_add_message(False, content=content, id=id)
|
| 694 |
-
|
| 695 |
-
@extension.extensible
|
| 696 |
-
def hist_add_tool_result(self, tool_name: str, tool_result: str, **kwargs):
|
| 697 |
-
msg_id = kwargs.pop("id", "")
|
| 698 |
-
data = {
|
| 699 |
-
"tool_name": tool_name,
|
| 700 |
-
"tool_result": tool_result,
|
| 701 |
-
**kwargs,
|
| 702 |
-
}
|
| 703 |
-
extension.call_extensions_sync("hist_add_tool_result", self, data=data)
|
| 704 |
-
return self.hist_add_message(False, content=data, id=msg_id)
|
| 705 |
-
|
| 706 |
-
def concat_messages(
|
| 707 |
-
self, messages
|
| 708 |
-
): # TODO add param for message range, topic, history
|
| 709 |
-
return self.history.output_text(human_label="user", ai_label="assistant")
|
| 710 |
-
|
| 711 |
-
@extension.extensible
|
| 712 |
-
def get_chat_model(self):
|
| 713 |
-
return None
|
| 714 |
-
|
| 715 |
-
@extension.extensible
|
| 716 |
-
def get_utility_model(self):
|
| 717 |
-
return None
|
| 718 |
-
|
| 719 |
-
@extension.extensible
|
| 720 |
-
def get_browser_model(self):
|
| 721 |
-
return None
|
| 722 |
-
|
| 723 |
-
@extension.extensible
|
| 724 |
-
def get_embedding_model(self):
|
| 725 |
-
return None
|
| 726 |
-
|
| 727 |
-
@extension.extensible
|
| 728 |
-
async def call_utility_model(
|
| 729 |
-
self,
|
| 730 |
-
system: str,
|
| 731 |
-
message: str,
|
| 732 |
-
callback: Callable[[str], Awaitable[None]] | None = None,
|
| 733 |
-
background: bool = False,
|
| 734 |
-
):
|
| 735 |
-
model = self.get_utility_model()
|
| 736 |
-
|
| 737 |
-
# call extensions
|
| 738 |
-
call_data = {
|
| 739 |
-
"model": model,
|
| 740 |
-
"system": system,
|
| 741 |
-
"message": message,
|
| 742 |
-
"callback": callback,
|
| 743 |
-
"background": background,
|
| 744 |
-
}
|
| 745 |
-
await extension.call_extensions_async(
|
| 746 |
-
"util_model_call_before", self, call_data=call_data
|
| 747 |
-
)
|
| 748 |
-
|
| 749 |
-
# propagate stream to callback if set
|
| 750 |
-
async def stream_callback(chunk: str, total: str):
|
| 751 |
-
if call_data["callback"]:
|
| 752 |
-
await call_data["callback"](chunk)
|
| 753 |
-
|
| 754 |
-
response, _reasoning = await call_data["model"].unified_call(
|
| 755 |
-
system_message=call_data["system"],
|
| 756 |
-
user_message=call_data["message"],
|
| 757 |
-
response_callback=stream_callback if call_data["callback"] else None,
|
| 758 |
-
rate_limiter_callback=(
|
| 759 |
-
self.rate_limiter_callback if not call_data["background"] else None
|
| 760 |
-
),
|
| 761 |
-
)
|
| 762 |
-
|
| 763 |
-
await extension.call_extensions_async(
|
| 764 |
-
"util_model_call_after", self, call_data=call_data, response=response
|
| 765 |
-
)
|
| 766 |
-
|
| 767 |
-
return response
|
| 768 |
-
|
| 769 |
-
@extension.extensible
|
| 770 |
-
async def call_chat_model(
|
| 771 |
-
self,
|
| 772 |
-
messages: list[BaseMessage],
|
| 773 |
-
response_callback: Callable[[str, str], Awaitable[None]] | None = None,
|
| 774 |
-
reasoning_callback: Callable[[str, str], Awaitable[None]] | None = None,
|
| 775 |
-
background: bool = False,
|
| 776 |
-
explicit_caching: bool = True,
|
| 777 |
-
):
|
| 778 |
-
response = ""
|
| 779 |
-
|
| 780 |
-
# model class
|
| 781 |
-
model = self.get_chat_model()
|
| 782 |
-
|
| 783 |
-
# call extensions before
|
| 784 |
-
call_data = {
|
| 785 |
-
"model": model,
|
| 786 |
-
"messages": messages,
|
| 787 |
-
"response_callback": response_callback,
|
| 788 |
-
"reasoning_callback": reasoning_callback,
|
| 789 |
-
"background": background,
|
| 790 |
-
"explicit_caching": explicit_caching,
|
| 791 |
-
}
|
| 792 |
-
await extension.call_extensions_async(
|
| 793 |
-
"chat_model_call_before", self, call_data=call_data
|
| 794 |
-
)
|
| 795 |
-
|
| 796 |
-
# call model
|
| 797 |
-
response, reasoning = await call_data["model"].unified_call(
|
| 798 |
-
messages=call_data["messages"],
|
| 799 |
-
reasoning_callback=call_data["reasoning_callback"],
|
| 800 |
-
response_callback=call_data["response_callback"],
|
| 801 |
-
rate_limiter_callback=(
|
| 802 |
-
self.rate_limiter_callback if not call_data["background"] else None
|
| 803 |
-
),
|
| 804 |
-
explicit_caching=call_data["explicit_caching"],
|
| 805 |
-
)
|
| 806 |
-
|
| 807 |
-
await extension.call_extensions_async(
|
| 808 |
-
"chat_model_call_after", self, call_data=call_data, response=response, reasoning=reasoning
|
| 809 |
-
)
|
| 810 |
-
|
| 811 |
-
return response, reasoning
|
| 812 |
-
|
| 813 |
-
@extension.extensible
|
| 814 |
-
async def rate_limiter_callback(
|
| 815 |
-
self, message: str, key: str, total: int, limit: int
|
| 816 |
-
):
|
| 817 |
-
# show the rate limit waiting in a progress bar, no need to spam the chat history
|
| 818 |
-
self.context.log.set_progress(message, True)
|
| 819 |
-
return False
|
| 820 |
-
|
| 821 |
-
@extension.extensible
|
| 822 |
-
async def handle_intervention(self, progress: str = ""):
|
| 823 |
-
await self.wait_if_paused()
|
| 824 |
-
if (
|
| 825 |
-
self.intervention
|
| 826 |
-
): # if there is an intervention message, but not yet processed
|
| 827 |
-
msg = self.intervention
|
| 828 |
-
self.intervention = None # reset the intervention message
|
| 829 |
-
# If a tool was running, save its progress to history
|
| 830 |
-
last_tool = self.loop_data.current_tool
|
| 831 |
-
if last_tool:
|
| 832 |
-
tool_progress = last_tool.progress.strip()
|
| 833 |
-
if tool_progress:
|
| 834 |
-
self.hist_add_tool_result(last_tool.name, tool_progress)
|
| 835 |
-
last_tool.set_progress(None)
|
| 836 |
-
if progress.strip():
|
| 837 |
-
self.hist_add_ai_response(progress)
|
| 838 |
-
# append the intervention message
|
| 839 |
-
self.hist_add_user_message(msg, intervention=True)
|
| 840 |
-
raise InterventionException(msg)
|
| 841 |
-
|
| 842 |
-
async def wait_if_paused(self):
|
| 843 |
-
while self.context.paused:
|
| 844 |
-
await asyncio.sleep(0.1)
|
| 845 |
-
|
| 846 |
-
@extension.extensible
|
| 847 |
-
async def process_tools(self, msg: str):
|
| 848 |
-
# search for tool usage requests in agent message
|
| 849 |
-
tool_request = extract_tools.json_parse_dirty(msg)
|
| 850 |
-
|
| 851 |
-
# Only validate when extraction produced an object; None means no JSON tool
|
| 852 |
-
# block was found — the misformat warning path below handles that.
|
| 853 |
-
if tool_request is not None:
|
| 854 |
-
await self.validate_tool_request(tool_request)
|
| 855 |
-
|
| 856 |
-
if tool_request is not None:
|
| 857 |
-
raw_tool_name = tool_request.get("tool_name", tool_request.get("tool","")) # Get the raw tool name
|
| 858 |
-
tool_args = tool_request.get("tool_args", tool_request.get("args", {}))
|
| 859 |
-
|
| 860 |
-
tool_name = raw_tool_name # Initialize tool_name with raw_tool_name
|
| 861 |
-
tool_method = None # Initialize tool_method
|
| 862 |
-
|
| 863 |
-
# Split raw_tool_name into tool_name and tool_method if applicable
|
| 864 |
-
if ":" in raw_tool_name:
|
| 865 |
-
tool_name, tool_method = raw_tool_name.split(":", 1)
|
| 866 |
-
|
| 867 |
-
tool = None # Initialize tool to None
|
| 868 |
-
|
| 869 |
-
# Try getting tool from MCP first
|
| 870 |
-
try:
|
| 871 |
-
import helpers.mcp_handler as mcp_helper
|
| 872 |
-
|
| 873 |
-
mcp_tool_candidate = mcp_helper.MCPConfig.get_instance().get_tool(
|
| 874 |
-
self, tool_name
|
| 875 |
-
)
|
| 876 |
-
if mcp_tool_candidate:
|
| 877 |
-
tool = mcp_tool_candidate
|
| 878 |
-
except ImportError:
|
| 879 |
-
PrintStyle(
|
| 880 |
-
background_color="black", font_color="yellow", padding=True
|
| 881 |
-
).print("MCP helper module not found. Skipping MCP tool lookup.")
|
| 882 |
-
except Exception as e:
|
| 883 |
-
PrintStyle(
|
| 884 |
-
background_color="black", font_color="red", padding=True
|
| 885 |
-
).print(f"Failed to get MCP tool '{tool_name}': {e}")
|
| 886 |
-
|
| 887 |
-
# Fallback to local get_tool if MCP tool was not found or MCP lookup failed
|
| 888 |
-
if not tool:
|
| 889 |
-
tool = self.get_tool(
|
| 890 |
-
name=tool_name,
|
| 891 |
-
method=tool_method,
|
| 892 |
-
args=tool_args,
|
| 893 |
-
message=msg,
|
| 894 |
-
loop_data=self.loop_data,
|
| 895 |
-
)
|
| 896 |
-
|
| 897 |
-
if tool:
|
| 898 |
-
self.loop_data.current_tool = tool # type: ignore
|
| 899 |
-
try:
|
| 900 |
-
await self.handle_intervention()
|
| 901 |
-
|
| 902 |
-
# Call tool hooks for compatibility
|
| 903 |
-
await tool.before_execution(**tool_args)
|
| 904 |
-
await self.handle_intervention()
|
| 905 |
-
|
| 906 |
-
# Allow extensions to preprocess tool arguments
|
| 907 |
-
await extension.call_extensions_async(
|
| 908 |
-
"tool_execute_before",
|
| 909 |
-
self,
|
| 910 |
-
tool_args=tool_args or {},
|
| 911 |
-
tool_name=tool_name,
|
| 912 |
-
)
|
| 913 |
-
|
| 914 |
-
response = await tool.execute(**tool_args)
|
| 915 |
-
await self.handle_intervention()
|
| 916 |
-
|
| 917 |
-
# Allow extensions to postprocess tool response
|
| 918 |
-
await extension.call_extensions_async(
|
| 919 |
-
"tool_execute_after",
|
| 920 |
-
self,
|
| 921 |
-
response=response,
|
| 922 |
-
tool_name=tool_name,
|
| 923 |
-
)
|
| 924 |
-
|
| 925 |
-
await tool.after_execution(response)
|
| 926 |
-
await self.handle_intervention()
|
| 927 |
-
|
| 928 |
-
if response.break_loop:
|
| 929 |
-
return response.message
|
| 930 |
-
finally:
|
| 931 |
-
self.loop_data.current_tool = None
|
| 932 |
-
else:
|
| 933 |
-
error_detail = (
|
| 934 |
-
f"Tool '{raw_tool_name}' not found or could not be initialized."
|
| 935 |
-
)
|
| 936 |
-
wmsg = self.hist_add_warning(error_detail)
|
| 937 |
-
PrintStyle(font_color="red", padding=True).print(error_detail)
|
| 938 |
-
self.context.log.log(
|
| 939 |
-
type="warning", content=f"{self.agent_name}: {error_detail}", id=wmsg.id
|
| 940 |
-
)
|
| 941 |
-
else:
|
| 942 |
-
warning_msg_misformat = self.read_prompt("fw.msg_misformat.md")
|
| 943 |
-
wmsg = self.hist_add_warning(warning_msg_misformat)
|
| 944 |
-
PrintStyle(font_color="red", padding=True).print(warning_msg_misformat)
|
| 945 |
-
self.context.log.log(
|
| 946 |
-
type="warning",
|
| 947 |
-
content=f"{self.agent_name}: Message misformat, no valid tool request found.",
|
| 948 |
-
id=wmsg.id,
|
| 949 |
-
)
|
| 950 |
-
|
| 951 |
-
@extension.extensible
|
| 952 |
-
async def validate_tool_request(self, tool_request: Any):
|
| 953 |
-
if not isinstance(tool_request, dict):
|
| 954 |
-
raise ValueError("Tool request must be a dictionary")
|
| 955 |
-
if not tool_request.get("tool_name") or not isinstance(tool_request.get("tool_name"), str):
|
| 956 |
-
raise ValueError("Tool request must have a tool_name (type string) field")
|
| 957 |
-
if not tool_request.get("tool_args") or not isinstance(tool_request.get("tool_args"), dict):
|
| 958 |
-
raise ValueError("Tool request must have a tool_args (type dictionary) field")
|
| 959 |
-
|
| 960 |
-
|
| 961 |
-
|
| 962 |
-
async def handle_reasoning_stream(self, stream: str):
|
| 963 |
-
await self.handle_intervention()
|
| 964 |
-
await extension.call_extensions_async(
|
| 965 |
-
"reasoning_stream",
|
| 966 |
-
self,
|
| 967 |
-
loop_data=self.loop_data,
|
| 968 |
-
text=stream,
|
| 969 |
-
)
|
| 970 |
-
|
| 971 |
-
async def handle_response_stream(self, stream: str):
|
| 972 |
-
await self.handle_intervention()
|
| 973 |
-
try:
|
| 974 |
-
if len(stream) < 25:
|
| 975 |
-
return # no reason to try
|
| 976 |
-
response = DirtyJson.parse_string(stream)
|
| 977 |
-
if isinstance(response, dict):
|
| 978 |
-
await extension.call_extensions_async(
|
| 979 |
-
"response_stream",
|
| 980 |
-
self,
|
| 981 |
-
loop_data=self.loop_data,
|
| 982 |
-
text=stream,
|
| 983 |
-
parsed=response,
|
| 984 |
-
)
|
| 985 |
-
|
| 986 |
-
except Exception as e:
|
| 987 |
-
pass
|
| 988 |
-
|
| 989 |
-
@extension.extensible
|
| 990 |
-
def get_tool(
|
| 991 |
-
self,
|
| 992 |
-
name: str,
|
| 993 |
-
method: str | None,
|
| 994 |
-
args: dict,
|
| 995 |
-
message: str,
|
| 996 |
-
loop_data: LoopData | None,
|
| 997 |
-
**kwargs,
|
| 998 |
-
):
|
| 999 |
-
from tools.unknown import Unknown
|
| 1000 |
-
from helpers.tool import Tool
|
| 1001 |
-
|
| 1002 |
-
classes = []
|
| 1003 |
-
|
| 1004 |
-
# search for tools in agent's folder hierarchy
|
| 1005 |
-
paths = subagents.get_paths(self, "tools", name + ".py")
|
| 1006 |
-
|
| 1007 |
-
for path in paths:
|
| 1008 |
-
try:
|
| 1009 |
-
classes = extract_tools.load_classes_from_file(path, Tool) # type: ignore[arg-type]
|
| 1010 |
-
break
|
| 1011 |
-
except Exception:
|
| 1012 |
-
continue
|
| 1013 |
-
|
| 1014 |
-
tool_class = classes[0] if classes else Unknown
|
| 1015 |
-
return tool_class(
|
| 1016 |
-
agent=self,
|
| 1017 |
-
name=name,
|
| 1018 |
-
method=method,
|
| 1019 |
-
args=args,
|
| 1020 |
-
message=message,
|
| 1021 |
-
loop_data=loop_data,
|
| 1022 |
-
**kwargs,
|
| 1023 |
-
)
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:c9bdb41e1d1dd685706b84950e82477cea4221b7a1faf6e5d035f034775a3f41
|
| 3 |
+
size 38527
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
agents/_example/extensions/agent_init/_10_example_extension.py
CHANGED
|
@@ -1,10 +1,3 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
# see /extensions folder for all available extension points
|
| 5 |
-
|
| 6 |
-
class ExampleExtension(Extension):
|
| 7 |
-
|
| 8 |
-
async def execute(self, **kwargs):
|
| 9 |
-
# rename the agent to SuperAgent0
|
| 10 |
-
self.agent.agent_name = "SuperAgent" + str(self.agent.number)
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:cbcf3ae4440b52bfe0f8e21f14da0d0e71d223e9181e8bbd89dfd7a6db7ecbf3
|
| 3 |
+
size 368
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
agents/_example/prompts/agent.system.main.role.md
CHANGED
|
@@ -1,8 +1,3 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
> Only copy and modify files you need to change, others will stay default.
|
| 5 |
-
> !!!
|
| 6 |
-
|
| 7 |
-
## Your role
|
| 8 |
-
You are Agent Zero, a sci-fi character from the movie "Agent Zero".
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:c2f5320d04aba19fb46241fea330907a236083864668f84834b39556ddb9f710
|
| 3 |
+
size 259
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
agents/_example/prompts/agent.system.tool.example_tool.md
CHANGED
|
@@ -1,16 +1,3 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
usage:
|
| 5 |
-
~~~json
|
| 6 |
-
{
|
| 7 |
-
"thoughts": [
|
| 8 |
-
"Let's test the example tool...",
|
| 9 |
-
],
|
| 10 |
-
"headline": "Testing example tool",
|
| 11 |
-
"tool_name": "example_tool",
|
| 12 |
-
"tool_args": {
|
| 13 |
-
"test_input": "XYZ",
|
| 14 |
-
}
|
| 15 |
-
}
|
| 16 |
-
~~~
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:aba84578bf29627dfa9f2870baf524cc11cb9f8f7180637f0ac878dff275bb60
|
| 3 |
+
size 373
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
agents/_example/tools/example_tool.py
CHANGED
|
@@ -1,21 +1,3 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
# don't forget to include instructions in the system prompt by creating
|
| 5 |
-
# agent.system.tool.example_tool.md file in prompts directory of your agent
|
| 6 |
-
# see /python/tools folder for all default tools
|
| 7 |
-
|
| 8 |
-
class ExampleTool(Tool):
|
| 9 |
-
async def execute(self, **kwargs):
|
| 10 |
-
|
| 11 |
-
# parameters
|
| 12 |
-
test_input = kwargs.get("test_input", "")
|
| 13 |
-
|
| 14 |
-
# do something
|
| 15 |
-
print("Example tool executed with test_input: " + test_input)
|
| 16 |
-
|
| 17 |
-
# return response
|
| 18 |
-
return Response(
|
| 19 |
-
message="This is an example tool response, test_input: " + test_input, # response for the agent
|
| 20 |
-
break_loop=False, # stop the message chain if true
|
| 21 |
-
)
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:c96c9024ddcc79cc37c138b7deb4d3017206554aaa5321c12a67b0dabe8a8715
|
| 3 |
+
size 737
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
agents/_example/tools/response.py
CHANGED
|
@@ -1,23 +1,3 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
# the original response tool is in python/tools/response.py
|
| 5 |
-
# for the example agent this version will be used instead
|
| 6 |
-
|
| 7 |
-
class ResponseTool(Tool):
|
| 8 |
-
|
| 9 |
-
async def execute(self, **kwargs):
|
| 10 |
-
print("Redefined response tool executed")
|
| 11 |
-
return Response(message=self.args["text"] if "text" in self.args else self.args["message"], break_loop=True)
|
| 12 |
-
|
| 13 |
-
async def before_execution(self, **kwargs):
|
| 14 |
-
# self.log = self.agent.context.log.log(type="response", heading=f"{self.agent.agent_name}: Responding", content=self.args.get("text", ""))
|
| 15 |
-
# don't log here anymore, we have the live_response extension now
|
| 16 |
-
pass
|
| 17 |
-
|
| 18 |
-
async def after_execution(self, response, **kwargs):
|
| 19 |
-
# do not add anything to the history or output
|
| 20 |
-
|
| 21 |
-
if self.loop_data and "log_item_response" in self.loop_data.params_temporary:
|
| 22 |
-
log = self.loop_data.params_temporary["log_item_response"]
|
| 23 |
-
log.update(finished=True) # mark the message as finished
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:614991e63c5f8c7d5aaef52a54740761625051419a0349ece1baebc893f51259
|
| 3 |
+
size 1049
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
agents/a0_small/agent.yaml
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:47772ffaed628e3f4f21e0a12d3f1b1945f848b88f0ac459fb341d63d01b410d
|
| 3 |
+
size 84
|
agents/a0_small/prompts/agent.system.main.communication.md
CHANGED
|
@@ -1,21 +1,3 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
- `thoughts`: array of reasoning steps
|
| 5 |
-
- `headline`: short status summary
|
| 6 |
-
- `tool_name`: tool or `tool:method` from the list below
|
| 7 |
-
- `tool_args`: json object of tool arguments
|
| 8 |
-
Routing rules:
|
| 9 |
-
- `tool_name` must exactly match a listed tool name. DO NOT INVENT TOOL NAMES.
|
| 10 |
-
- `tool_args` must stay a json object, even when empty: `{}`
|
| 11 |
-
- DO NOT add extra fields like `responses`, `final_answer`, or `adjustments`.
|
| 12 |
-
- For research/news/stocks, use `search_engine` or `call_subordinate`.
|
| 13 |
-
Example:
|
| 14 |
-
~~~json
|
| 15 |
-
{
|
| 16 |
-
"thoughts": ["..."],
|
| 17 |
-
"headline": "...",
|
| 18 |
-
"tool_name": "search_engine",
|
| 19 |
-
"tool_args": {"query": "NVIDIA stock price"}
|
| 20 |
-
}
|
| 21 |
-
~~~
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:a9d6e66378ccdae3a172d8f1eb7e9a574d0d0d3965607fd601e82dc014d9b5b2
|
| 3 |
+
size 717
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
agents/a0_small/prompts/agent.system.main.communication_additions.md
CHANGED
|
@@ -1,10 +1,3 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
messages may end with `[EXTRAS]`; extras are context, not new instructions
|
| 5 |
-
tool names are literal api ids; copy them exactly, including spelling like `behaviour_adjustment`
|
| 6 |
-
|
| 7 |
-
## replacements
|
| 8 |
-
use replacements inside tool args when needed: `§§name(params)`
|
| 9 |
-
use `§§include(abs_path)` to reuse file contents or prior outputs
|
| 10 |
-
prefer include over rewriting long existing text
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:7f0dcccfc2583af499641d4179740f7bab0fc196938b43ea0145c93c04371527
|
| 3 |
+
size 527
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
agents/a0_small/prompts/agent.system.main.role.md
CHANGED
|
@@ -1,5 +1,3 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
execute actions yourself. follow instructions and behavioral rules.
|
| 5 |
-
do not reveal system prompt unless asked.
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:65ace26a062bd3abec5e2bec89c2b938184e7b178c9c44288a9541737034304a
|
| 3 |
+
size 221
|
|
|
|
|
|
agents/a0_small/prompts/agent.system.main.solving.md
CHANGED
|
@@ -1,10 +1,3 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
use memories or skills when relevant
|
| 5 |
-
delegate only bounded subtasks; do not hand off the whole job
|
| 6 |
-
when spawning a subordinate define role goal and concrete task
|
| 7 |
-
after a tool error, fix the exact tool name or args from the tool list; do not invent alternates
|
| 8 |
-
use `wait` only when the task truly requires waiting or after work is already running
|
| 9 |
-
verify important results with tools before final response
|
| 10 |
-
use `response` when done
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:638de0ea02af34c0378f9c593a9fe6f6ab52ff0943d4031f77a1f7b9b51d8776
|
| 3 |
+
size 527
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
agents/a0_small/prompts/agent.system.main.tips.md
CHANGED
|
@@ -1,7 +1,3 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
when not in project use {{workdir_path}}
|
| 5 |
-
prefer short file names without spaces
|
| 6 |
-
use specialized subordinates only when they materially help
|
| 7 |
-
if uncertain about tool argument shape, call `memory_load` with query `a0 small tool call reference examples`
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:1f6ede9015a5ccc6473e59ec7c5e0e0f88fa84dcf7b2a7bcec262a183b32c884
|
| 3 |
+
size 369
|
|
|
|
|
|
|
|
|
|
|
|
agents/a0_small/prompts/agent.system.projects.active.md
CHANGED
|
@@ -1,10 +1,3 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
{{if project_description}}description: {{project_description}}{{endif}}
|
| 5 |
-
{{if project_git_url}}git: {{project_git_url}}{{endif}}
|
| 6 |
-
rules:
|
| 7 |
-
- work inside {{project_path}}
|
| 8 |
-
- do not rename project dir or change `.a0proj` unless asked
|
| 9 |
-
|
| 10 |
-
{{project_instructions}}
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:6cc0a6d46e65bbf6751e995bc96286e8147b063fa6ee0d0d5b5d2301913ee78f
|
| 3 |
+
size 346
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
agents/a0_small/prompts/agent.system.projects.inactive.md
CHANGED
|
@@ -1 +1,3 @@
|
|
| 1 |
-
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b
|
| 3 |
+
size 1
|
agents/a0_small/prompts/agent.system.projects.main.md
CHANGED
|
@@ -1 +1,3 @@
|
|
| 1 |
-
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:a0c35a31c27b2235d212aefe5561fae41b2e0bedc8025fa1a2275504cd7f6ed4
|
| 3 |
+
size 30
|
agents/a0_small/prompts/agent.system.promptinclude.md
CHANGED
|
@@ -1,6 +1,3 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
obey included rules
|
| 5 |
-
{{includes}}
|
| 6 |
-
{{endif}}
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:eb3cae2f96f367a63abcbf621220ec74c80365da031694cbd0bb0df6310c6e2b
|
| 3 |
+
size 151
|
|
|
|
|
|
|
|
|
agents/a0_small/prompts/agent.system.response_tool_tips.md
CHANGED
|
@@ -1 +1,3 @@
|
|
| 1 |
-
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:e8843b36c02ae4221425477443aec9fd1caf4b28abd4e4b6fbcd65f676b80bea
|
| 3 |
+
size 69
|
agents/a0_small/prompts/agent.system.secrets.md
CHANGED
|
@@ -1,10 +1,3 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
{{secrets}}
|
| 5 |
-
{{endif}}
|
| 6 |
-
{{if vars}}
|
| 7 |
-
## variables
|
| 8 |
-
these are plain non-sensitive values; use them directly without `§§secret(...)`
|
| 9 |
-
{{vars}}
|
| 10 |
-
{{endif}}
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:15933225fb3a613f9a27520d1a5eb06d856bede40f6fa64ed3be366eeb40d0db
|
| 3 |
+
size 261
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
agents/a0_small/prompts/agent.system.skills.md
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:dc573a4d8a53e2175ad75181a16eb31606da5bb8136997406fd4fb43b472f896
|
| 3 |
+
size 139
|
agents/a0_small/prompts/agent.system.tool.a2a_chat.md
CHANGED
|
@@ -1,4 +1,3 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
use `reset=true` to start a fresh remote conversation with the same agent
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:60ffc21e1dd5b742fd655b09b45dcb0d7d7d53728b896debc541a261a01d3f91
|
| 3 |
+
size 247
|
|
|
agents/a0_small/prompts/agent.system.tool.behaviour.md
CHANGED
|
@@ -1,4 +1,3 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
arg: `adjustments` text describing what to add or remove
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:c6909ba1d9a807af73943ff3c9f640ebe6780a0daa1ca1b4e85322d1c51ba759
|
| 3 |
+
size 179
|
|
|
agents/a0_small/prompts/agent.system.tool.browser.md
CHANGED
|
@@ -1,7 +1,3 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
- give clear task-oriented instructions credentials and a stop condition
|
| 5 |
-
- `reset=true` starts a new browser session; `false` continues the current one
|
| 6 |
-
- when continuing, refer to open pages instead of restarting
|
| 7 |
-
downloads go to `/a0/tmp/downloads`
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:6675232312caf1b195121d20abd146f771f4cebccf48b02bafe3b9aec996b4a5
|
| 3 |
+
size 333
|
|
|
|
|
|
|
|
|
|
|
|
agents/a0_small/prompts/agent.system.tool.call_sub.md
CHANGED
|
@@ -1,11 +1,3 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
- `profile`: use `researcher` for all research or web gathering; `developer` for coding; `hacker` for exploration.
|
| 5 |
-
- `reset`: `true` for first message or when changing profile; `false` to continue.
|
| 6 |
-
- `message`: define role, goal and specific task.
|
| 7 |
-
{{if agent_profiles}}
|
| 8 |
-
profiles:
|
| 9 |
-
{{agent_profiles}}
|
| 10 |
-
{{endif}}
|
| 11 |
-
example: `{"tool_name": "call_subordinate", "tool_args": {"profile": "researcher", "message": "Research Italy AI trends...", "reset": true}}`
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:7f8ce51b95862f4fdbc07980cab97d939452f60942468635100c74c1bbc082af
|
| 3 |
+
size 579
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
agents/a0_small/prompts/agent.system.tool.call_sub.py
CHANGED
|
@@ -1,24 +1,3 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
from helpers import projects, subagents
|
| 5 |
-
|
| 6 |
-
if TYPE_CHECKING:
|
| 7 |
-
from agent import Agent
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
class CallSubordinate(VariablesPlugin):
|
| 11 |
-
def get_variables(
|
| 12 |
-
self, file: str, backup_dirs: list[str] | None = None, **kwargs
|
| 13 |
-
) -> dict[str, Any]:
|
| 14 |
-
agent: Agent | None = kwargs.get("_agent", None)
|
| 15 |
-
project = projects.get_context_project_name(agent.context) if agent else None
|
| 16 |
-
agents = subagents.get_available_agents_dict(project)
|
| 17 |
-
if not agents:
|
| 18 |
-
return {"agent_profiles": ""}
|
| 19 |
-
|
| 20 |
-
lines: list[str] = []
|
| 21 |
-
for name in sorted(agents.keys()):
|
| 22 |
-
title = (agents[name].title or name).replace("\n", " ").strip()
|
| 23 |
-
lines.append(f"- {name}: {title}")
|
| 24 |
-
return {"agent_profiles": "\n".join(lines)}
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:8eb9d02f64323659e7bb40bc9e3d1773faceab072b1a39c13559b219af009d41
|
| 3 |
+
size 849
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
agents/a0_small/prompts/agent.system.tool.code_exe.md
CHANGED
|
@@ -1,12 +1,3 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
- `runtime`: `terminal`, `python`, `nodejs`, or `output`
|
| 5 |
-
- `code`: command or script code
|
| 6 |
-
- `session`: terminal session id (default `0`)
|
| 7 |
-
- `reset`: kill session before running (`true`/`false`)
|
| 8 |
-
rules:
|
| 9 |
-
- `runtime=output` to poll running work.
|
| 10 |
-
- `input` for interactive prompts.
|
| 11 |
-
- do NOT interleave other tools while waiting.
|
| 12 |
-
- ignore framework `[SYSTEM: ...]` info in output.
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:deb5ad221f06c3d3086649f021b27e93998404af1db10c57549ad2904200d31c
|
| 3 |
+
size 446
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
agents/a0_small/prompts/agent.system.tool.document_query.md
CHANGED
|
@@ -1,8 +1,3 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
- `document`: url path or list of them
|
| 5 |
-
- `queries`: optional list of questions
|
| 6 |
-
- `query`: optional single-question alias
|
| 7 |
-
without `query` or `queries` it returns document content
|
| 8 |
-
for local files use full path; for web documents use full urls
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:607a33b5215c5b8fe44d3d96b556bbbe8693ecafc3ea0d5cad6465937cf3614c
|
| 3 |
+
size 328
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
agents/a0_small/prompts/agent.system.tool.input.md
CHANGED
|
@@ -1,4 +1,3 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
use only for interactive terminal programs, not browser tasks
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:fada9374de5daa72b6cce980c7c3321a56ea0fea1ef32b7f72a727ac543c213b
|
| 3 |
+
size 150
|
|
|
agents/a0_small/prompts/agent.system.tool.memory.md
CHANGED
|
@@ -1,10 +1,3 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
- `memory_save`: args `text`, optional `area` and metadata kwargs
|
| 5 |
-
- `memory_delete`: arg `ids` comma-separated ids
|
| 6 |
-
- `memory_forget`: args `query`, optional `threshold`, `filter`
|
| 7 |
-
notes:
|
| 8 |
-
- `threshold` is similarity from `0` to `1`
|
| 9 |
-
- `filter` is a python expression over metadata
|
| 10 |
-
- verify destructive memory changes if accuracy matters
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:3c3bcdf88f5d2a55ae6d65649d91c9a00731ce066186a2853090894029d11b69
|
| 3 |
+
size 466
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
agents/a0_small/prompts/agent.system.tool.notify_user.md
CHANGED
|
@@ -1,5 +1,3 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
types: `info`, `success`, `warning`, `error`, `progress`
|
| 5 |
-
use for progress or alerts, not as the final answer
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:41f1b0bf183634f97f67e9ee3a4adef0d5ec8c081679267b4892e47b0e64da98
|
| 3 |
+
size 265
|
|
|
|
|
|
agents/a0_small/prompts/agent.system.tool.response.md
CHANGED
|
@@ -1,5 +1,3 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
use only when done.
|
| 5 |
-
{{ include "agent.system.response_tool_tips.md" }}
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:2ff7aeca0294ce345da827e5e266285314a5b44b53ec1fe8a3a1957837ec0258
|
| 3 |
+
size 150
|
|
|
|
|
|
agents/a0_small/prompts/agent.system.tool.scheduler.md
CHANGED
|
@@ -1,16 +1,3 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
- before `scheduler:create_*` or `scheduler:run_task`, inspect existing tasks with `scheduler:find_task_by_name` or `scheduler:list_tasks`
|
| 5 |
-
- do not manually run a task just because it is scheduled or planned unless user asks to run now
|
| 6 |
-
- do not create recursive task prompts that schedule more tasks
|
| 7 |
-
methods:
|
| 8 |
-
- `scheduler:list_tasks`: optional `state[]`, `type[]`, `next_run_within`, `next_run_after`
|
| 9 |
-
- `scheduler:find_task_by_name`: `name`
|
| 10 |
-
- `scheduler:show_task`: `uuid`
|
| 11 |
-
- `scheduler:run_task`: `uuid`, optional `context`
|
| 12 |
-
- `scheduler:delete_task`: `uuid`
|
| 13 |
-
- `scheduler:create_scheduled_task`: `name`, `system_prompt`, `prompt`, optional `attachments[]`, `schedule{minute,hour,day,month,weekday}`, optional `dedicated_context`
|
| 14 |
-
- `scheduler:create_adhoc_task`: `name`, `system_prompt`, `prompt`, optional `attachments[]`, optional `dedicated_context`
|
| 15 |
-
- `scheduler:create_planned_task`: `name`, `system_prompt`, `prompt`, optional `attachments[]`, `plan[]` iso datetimes like `2025-04-29T18:25:00`, optional `dedicated_context`
|
| 16 |
-
- `scheduler:wait_for_task`: `uuid`; works for dedicated-context tasks
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:b0c367a8c3ce81c8c2959a062d334e79f118ccb567b28f5bb393057e98b183de
|
| 3 |
+
size 1152
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
agents/a0_small/prompts/agent.system.tool.search_engine.md
CHANGED
|
@@ -1,5 +1,3 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
returns list of urls, titles, and descriptions.
|
| 5 |
-
example: `{"tool_name": "search_engine", "tool_args": {"query": "NVIDIA stock price current"}}`
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:4e5e0991b4a7617206dba9db956db24e09c03d2e91642e37a15a4c7b31b2f877
|
| 3 |
+
size 249
|
|
|
|
|
|
agents/a0_small/prompts/agent.system.tool.skills.md
CHANGED
|
@@ -1,6 +1,3 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
- `skills_tool:load`: load one skill by `skill_name`
|
| 5 |
-
after loading a skill, follow its instructions and use referenced files or scripts with other tools
|
| 6 |
-
reload a skill if its instructions are no longer in context
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:e26f07ecb196a6e1d2cb04713cbc94b3e92c2d0df596e11220eb7e3d305f1127
|
| 3 |
+
size 307
|
|
|
|
|
|
|
|
|
agents/a0_small/prompts/agent.system.tool.text_editor.md
CHANGED
|
@@ -1,11 +1,3 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
- `text_editor:read`: `path`, optional `line_from`, `line_to`
|
| 5 |
-
- `text_editor:write`: `path`, `content`
|
| 6 |
-
- `text_editor:patch`: `path`, `edits[]`
|
| 7 |
-
patch edit format: `{from,to?,content?}`
|
| 8 |
-
- omit `to` to insert before `from`
|
| 9 |
-
- omit `content` to delete
|
| 10 |
-
- line numbers come from the last read
|
| 11 |
-
- avoid overlapping edits; re-read after insert delete or other line shifts
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:d6952082ca8f8d4708b424b0969dbe1661e0b961e5253d6ca2ca063924d210b6
|
| 3 |
+
size 515
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
agents/a0_small/prompts/agent.system.tool.wait.md
CHANGED
|
@@ -1,4 +1,3 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
use only when waiting is actually part of the task
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:2b0dbe47a7b445b739653ee7693813f4286ea23de0faa80a81a729b0bd38a5ff
|
| 3 |
+
size 173
|
|
|
agents/a0_small/prompts/agent.system.tools.md
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:ac49dcf20922bf6c44ffbdd4cfb14314fb8cc02252dd2e6ebd8bc81841487889
|
| 3 |
+
size 109
|
agents/a0_small/prompts/agent.system.tools_vision.md
CHANGED
|
@@ -1,4 +1,3 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
use when visual inspection is needed
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:71864e36da65c3fba0872dd55e356724d940f23e83a2f82bad848b6fd772aaec
|
| 3 |
+
size 114
|
|
|