forked from retoor/devplacepy
chore: add agents/reports to gitignore and update agent infrastructure with timestamp streaming, write budget, and codename generation
- Add `agents/reports/` to `.gitignore` to prevent generated agent report files from being tracked - Implement `_TimestampStream` class and `install_timestamps()` function in `agents/agent.py` for prefixing stdout/stderr with timestamps and elapsed time - Export `install_timestamps`, `set_write_budget`, `clear_write_budget`, and `set_shell_restricted` from `agents/base.py`; add `_RUN_LOCK`, `AGENT_ICONS` dictionary, and `WRITE_BUDGET = 20` constant - Add `report_codename()` function and `CODENAME_ADJECTIVES`/`CODENAME_ANIMALS` tuples to `agents/core/__init__.py` for generating random agent report codenames - Expand `WRITE_TOOLS` tuple in `agents/core/__init__.py` to include `replace_lines`, `insert_lines`, and `delete_lines`; remove `SWARM_TOOLS` from `payloads_for` exclusion list - Update `CLAUDE.md` and `AGENTS.md` documentation with dataset column initialization rules and new agent infrastructure details - Reorder route table in `README.md` to list `/uploads` before `/messages` and document Swagger/ReDoc/OpenAPI schema endpoints
This commit is contained in:
@@ -94,6 +94,7 @@ ACTIONS: tuple[Action, ...] = (
|
||||
method="GET",
|
||||
path="/feed",
|
||||
summary="View the activity feed",
|
||||
requires_auth=False,
|
||||
params=(
|
||||
query("tab", "Feed tab to view."),
|
||||
query("topic", "Filter by topic."),
|
||||
@@ -123,6 +124,7 @@ ACTIONS: tuple[Action, ...] = (
|
||||
method="GET",
|
||||
path="/posts/{post_slug}",
|
||||
summary="View a single post by slug",
|
||||
requires_auth=False,
|
||||
params=(
|
||||
path(
|
||||
"post_slug",
|
||||
@@ -191,6 +193,7 @@ ACTIONS: tuple[Action, ...] = (
|
||||
method="GET",
|
||||
path="/projects",
|
||||
summary="List projects",
|
||||
requires_auth=False,
|
||||
params=(
|
||||
query("tab", "Projects tab."),
|
||||
query("search", "Search query."),
|
||||
@@ -204,6 +207,7 @@ ACTIONS: tuple[Action, ...] = (
|
||||
method="GET",
|
||||
path="/projects/{project_slug}",
|
||||
summary="View a project by slug",
|
||||
requires_auth=False,
|
||||
params=(
|
||||
path(
|
||||
"project_slug",
|
||||
@@ -489,6 +493,22 @@ ACTIONS: tuple[Action, ...] = (
|
||||
params=(path("project_slug", "Project slug or uid."),),
|
||||
requires_auth=False,
|
||||
),
|
||||
Action(
|
||||
name="project_files_zip",
|
||||
method="POST",
|
||||
path="/projects/{project_slug}/files/zip",
|
||||
summary="Build a downloadable zip archive of a project subdirectory",
|
||||
description=(
|
||||
"Queues a background zip job for a subpath inside the project and returns "
|
||||
"{uid, status_url}. Poll the status_url with zip_status until status is 'done', "
|
||||
"then give the user the download_url. When path is empty the whole project is zipped."
|
||||
),
|
||||
params=(
|
||||
path("project_slug", "Project slug or uid."),
|
||||
query("path", "Relative subpath inside the project to zip; empty for the whole project."),
|
||||
),
|
||||
requires_auth=False,
|
||||
),
|
||||
Action(
|
||||
name="zip_status",
|
||||
method="GET",
|
||||
@@ -540,6 +560,7 @@ ACTIONS: tuple[Action, ...] = (
|
||||
method="GET",
|
||||
path="/profile/{username}",
|
||||
summary="View a user profile",
|
||||
requires_auth=False,
|
||||
params=(
|
||||
path("username", "Username to view."),
|
||||
query("tab", "Profile tab."),
|
||||
@@ -566,6 +587,13 @@ ACTIONS: tuple[Action, ...] = (
|
||||
"Returns the new api_key. Warning: this immediately invalidates any key currently "
|
||||
"used for authentication, so confirm with the user before calling it."
|
||||
),
|
||||
params=(
|
||||
body(
|
||||
"confirm",
|
||||
"Must be true, set only after the user has explicitly confirmed.",
|
||||
required=True,
|
||||
),
|
||||
),
|
||||
),
|
||||
Action(
|
||||
name="list_messages",
|
||||
@@ -607,6 +635,7 @@ ACTIONS: tuple[Action, ...] = (
|
||||
method="GET",
|
||||
path="/notifications/counts",
|
||||
summary="Get unread notification and message counts",
|
||||
requires_auth=False,
|
||||
),
|
||||
Action(
|
||||
name="open_notification",
|
||||
@@ -763,12 +792,14 @@ ACTIONS: tuple[Action, ...] = (
|
||||
method="GET",
|
||||
path="/leaderboard",
|
||||
summary="View the leaderboard",
|
||||
requires_auth=False,
|
||||
),
|
||||
Action(
|
||||
name="list_bugs",
|
||||
method="GET",
|
||||
path="/bugs",
|
||||
summary="List reported bugs",
|
||||
requires_auth=False,
|
||||
),
|
||||
Action(
|
||||
name="create_bug",
|
||||
@@ -786,6 +817,7 @@ ACTIONS: tuple[Action, ...] = (
|
||||
method="GET",
|
||||
path="/gists",
|
||||
summary="List gists",
|
||||
requires_auth=False,
|
||||
params=(
|
||||
query("language", "Filter by language."),
|
||||
query("user_uid", "Filter by owner uid."),
|
||||
@@ -797,6 +829,7 @@ ACTIONS: tuple[Action, ...] = (
|
||||
method="GET",
|
||||
path="/gists/{gist_slug}",
|
||||
summary="View a gist by slug",
|
||||
requires_auth=False,
|
||||
params=(
|
||||
path(
|
||||
"gist_slug",
|
||||
@@ -850,6 +883,7 @@ ACTIONS: tuple[Action, ...] = (
|
||||
method="GET",
|
||||
path="/news",
|
||||
summary="List news articles",
|
||||
requires_auth=False,
|
||||
params=(query("before", "Pagination cursor."),),
|
||||
),
|
||||
Action(
|
||||
@@ -857,6 +891,7 @@ ACTIONS: tuple[Action, ...] = (
|
||||
method="GET",
|
||||
path="/news/{news_slug}",
|
||||
summary="View a news article by slug",
|
||||
requires_auth=False,
|
||||
params=(
|
||||
path(
|
||||
"news_slug",
|
||||
@@ -904,6 +939,7 @@ ACTIONS: tuple[Action, ...] = (
|
||||
method="GET",
|
||||
path="/admin",
|
||||
summary="View the admin overview",
|
||||
requires_admin=True,
|
||||
),
|
||||
Action(
|
||||
name="site_analytics",
|
||||
@@ -946,6 +982,7 @@ ACTIONS: tuple[Action, ...] = (
|
||||
path="/admin/users",
|
||||
summary="List users for administration",
|
||||
params=(query("page", "Page number."),),
|
||||
requires_admin=True,
|
||||
),
|
||||
Action(
|
||||
name="admin_set_user_role",
|
||||
@@ -956,6 +993,7 @@ ACTIONS: tuple[Action, ...] = (
|
||||
path("uid", "User uid."),
|
||||
body("role", "New role.", required=True),
|
||||
),
|
||||
requires_admin=True,
|
||||
),
|
||||
Action(
|
||||
name="admin_set_user_password",
|
||||
@@ -966,6 +1004,7 @@ ACTIONS: tuple[Action, ...] = (
|
||||
path("uid", "User uid."),
|
||||
body("password", "New password.", required=True),
|
||||
),
|
||||
requires_admin=True,
|
||||
),
|
||||
Action(
|
||||
name="admin_toggle_user",
|
||||
@@ -973,12 +1012,14 @@ ACTIONS: tuple[Action, ...] = (
|
||||
path="/admin/users/{uid}/toggle",
|
||||
summary="Toggle a user's active state",
|
||||
params=(path("uid", "User uid."),),
|
||||
requires_admin=True,
|
||||
),
|
||||
Action(
|
||||
name="admin_get_settings",
|
||||
method="GET",
|
||||
path="/admin/settings",
|
||||
summary="View site settings",
|
||||
requires_admin=True,
|
||||
),
|
||||
Action(
|
||||
name="admin_save_settings",
|
||||
@@ -1000,6 +1041,7 @@ ACTIONS: tuple[Action, ...] = (
|
||||
body("maintenance_mode", "Whether maintenance mode is on."),
|
||||
body("maintenance_message", "Maintenance message."),
|
||||
),
|
||||
requires_admin=True,
|
||||
),
|
||||
Action(
|
||||
name="admin_list_news",
|
||||
@@ -1007,6 +1049,7 @@ ACTIONS: tuple[Action, ...] = (
|
||||
path="/admin/news",
|
||||
summary="List news for administration",
|
||||
params=(query("page", "Page number."),),
|
||||
requires_admin=True,
|
||||
),
|
||||
Action(
|
||||
name="admin_toggle_news",
|
||||
@@ -1014,6 +1057,7 @@ ACTIONS: tuple[Action, ...] = (
|
||||
path="/admin/news/{uid}/toggle",
|
||||
summary="Toggle a news article",
|
||||
params=(path("uid", "News uid."),),
|
||||
requires_admin=True,
|
||||
),
|
||||
Action(
|
||||
name="admin_publish_news",
|
||||
@@ -1021,6 +1065,7 @@ ACTIONS: tuple[Action, ...] = (
|
||||
path="/admin/news/{uid}/publish",
|
||||
summary="Publish a news article",
|
||||
params=(path("uid", "News uid."),),
|
||||
requires_admin=True,
|
||||
),
|
||||
Action(
|
||||
name="admin_landing_news",
|
||||
@@ -1028,6 +1073,7 @@ ACTIONS: tuple[Action, ...] = (
|
||||
path="/admin/news/{uid}/landing",
|
||||
summary="Set a news article as landing content",
|
||||
params=(path("uid", "News uid."),),
|
||||
requires_admin=True,
|
||||
),
|
||||
Action(
|
||||
name="admin_delete_news",
|
||||
@@ -1035,18 +1081,21 @@ ACTIONS: tuple[Action, ...] = (
|
||||
path="/admin/news/{uid}/delete",
|
||||
summary="Delete a news article",
|
||||
params=(path("uid", "News uid."),),
|
||||
requires_admin=True,
|
||||
),
|
||||
Action(
|
||||
name="admin_list_services",
|
||||
method="GET",
|
||||
path="/admin/services",
|
||||
summary="View managed services",
|
||||
requires_admin=True,
|
||||
),
|
||||
Action(
|
||||
name="admin_services_data",
|
||||
method="GET",
|
||||
path="/admin/services/data",
|
||||
summary="Get live status, metrics, and log tail for every background service",
|
||||
requires_admin=True,
|
||||
),
|
||||
Action(
|
||||
name="admin_service_status",
|
||||
@@ -1054,6 +1103,7 @@ ACTIONS: tuple[Action, ...] = (
|
||||
path="/admin/services/{name}/data",
|
||||
summary="Get live status, metrics, and log tail for one background service",
|
||||
params=(path("name", "Service name (e.g. news, bots, openai)."),),
|
||||
requires_admin=True,
|
||||
),
|
||||
Action(
|
||||
name="admin_start_service",
|
||||
@@ -1061,6 +1111,7 @@ ACTIONS: tuple[Action, ...] = (
|
||||
path="/admin/services/{name}/start",
|
||||
summary="Start a managed service",
|
||||
params=(path("name", "Service name."),),
|
||||
requires_admin=True,
|
||||
),
|
||||
Action(
|
||||
name="admin_stop_service",
|
||||
@@ -1068,6 +1119,7 @@ ACTIONS: tuple[Action, ...] = (
|
||||
path="/admin/services/{name}/stop",
|
||||
summary="Stop a managed service",
|
||||
params=(path("name", "Service name."),),
|
||||
requires_admin=True,
|
||||
),
|
||||
Action(
|
||||
name="admin_run_service",
|
||||
@@ -1075,6 +1127,7 @@ ACTIONS: tuple[Action, ...] = (
|
||||
path="/admin/services/{name}/run",
|
||||
summary="Run a managed service once",
|
||||
params=(path("name", "Service name."),),
|
||||
requires_admin=True,
|
||||
),
|
||||
Action(
|
||||
name="admin_clear_service_logs",
|
||||
@@ -1082,6 +1135,7 @@ ACTIONS: tuple[Action, ...] = (
|
||||
path="/admin/services/{name}/clear-logs",
|
||||
summary="Clear a managed service's logs",
|
||||
params=(path("name", "Service name."),),
|
||||
requires_admin=True,
|
||||
),
|
||||
Action(
|
||||
name="admin_config_service",
|
||||
@@ -1090,6 +1144,62 @@ ACTIONS: tuple[Action, ...] = (
|
||||
summary="Update a managed service's configuration",
|
||||
params=(path("name", "Service name."),),
|
||||
freeform_body=True,
|
||||
requires_admin=True,
|
||||
),
|
||||
Action(
|
||||
name="admin_user_ai_usage",
|
||||
method="GET",
|
||||
path="/admin/users/{uid}/ai-usage",
|
||||
summary="Get a user's AI gateway usage (admin only)",
|
||||
description="Returns per-user AI usage data including token counts, cost, and request volume for the given lookback window.",
|
||||
params=(
|
||||
path("uid", "User uid."),
|
||||
query("hours", "Lookback window in hours (default 24)."),
|
||||
),
|
||||
requires_admin=True,
|
||||
),
|
||||
Action(
|
||||
name="admin_reset_user_ai_quota",
|
||||
method="POST",
|
||||
path="/admin/users/{uid}/reset-ai-quota",
|
||||
summary="Reset a user's AI quota (admin only)",
|
||||
description="Clears the AI quota ledger for a specific user, allowing them to use AI features again.",
|
||||
params=(path("uid", "User uid."),),
|
||||
requires_admin=True,
|
||||
),
|
||||
Action(
|
||||
name="admin_media_purge",
|
||||
method="POST",
|
||||
path="/admin/media/{uid}/purge",
|
||||
summary="Permanently remove soft-deleted media (admin only)",
|
||||
description="Permanently deletes a soft-deleted attachment from the database and filesystem.",
|
||||
params=(path("uid", "Attachment uid to purge."),),
|
||||
requires_admin=True,
|
||||
),
|
||||
Action(
|
||||
name="admin_reset_guest_ai_quota",
|
||||
method="POST",
|
||||
path="/admin/ai-quota/reset-guests",
|
||||
summary="Reset all guest AI quotas (admin only)",
|
||||
description="Clears the AI quota ledger for all guest sessions.",
|
||||
requires_admin=True,
|
||||
),
|
||||
Action(
|
||||
name="admin_reset_all_ai_quota",
|
||||
method="POST",
|
||||
path="/admin/ai-quota/reset-all",
|
||||
summary="Reset ALL AI quotas including member quotas (admin only)",
|
||||
description="Clears the AI quota ledger for every user and guest. Use with caution.",
|
||||
requires_admin=True,
|
||||
),
|
||||
Action(
|
||||
name="restore_media",
|
||||
method="POST",
|
||||
path="/media/{uid}/restore",
|
||||
summary="Restore a soft-deleted media attachment (admin only)",
|
||||
description="Restores a previously soft-deleted attachment, making it visible again.",
|
||||
params=(path("uid", "Attachment uid to restore."),),
|
||||
requires_admin=True,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ from .spec import Action, Catalog
|
||||
MUTATING_METHODS = ("POST", "DELETE", "PUT", "PATCH")
|
||||
|
||||
CONFIRM_REQUIRED = {
|
||||
"project_set_private",
|
||||
"project_set_readonly",
|
||||
"customize_set_css",
|
||||
"customize_set_js",
|
||||
@@ -39,6 +40,11 @@ CONFIRM_REQUIRED = {
|
||||
"project_delete_file",
|
||||
"delete_project",
|
||||
"delete_media",
|
||||
"regenerate_api_key",
|
||||
"delete_post",
|
||||
"delete_comment",
|
||||
"delete_gist",
|
||||
"delete_attachment",
|
||||
}
|
||||
|
||||
DESTRUCTIVE_COMMAND = re.compile(
|
||||
@@ -112,6 +118,13 @@ def confirmation_error(name: str, arguments: dict[str, Any]) -> ToolInputError |
|
||||
"Deleting customizations cannot be undone. Ask the user to confirm, then call again with "
|
||||
"confirm=true."
|
||||
)
|
||||
if name == "project_set_private":
|
||||
value = str(arguments.get("value", "")).strip()
|
||||
return ToolInputError(
|
||||
f"Setting a project {'private' if value == 'true' else 'public'} "
|
||||
"hides it from (or exposes it to) other members. Ask the user to confirm this "
|
||||
"change explicitly, then call again with confirm=true."
|
||||
)
|
||||
if name == "project_set_readonly":
|
||||
return ToolInputError(
|
||||
"Setting a project read-only makes every file immutable and blocks all further "
|
||||
@@ -135,6 +148,36 @@ def confirmation_error(name: str, arguments: dict[str, Any]) -> ToolInputError |
|
||||
"project. Show the user the exact media, get explicit confirmation, then call again "
|
||||
"with confirm=true."
|
||||
)
|
||||
if name == "regenerate_api_key":
|
||||
return ToolInputError(
|
||||
"Regenerating your API key immediately invalidates the current key, which may break "
|
||||
"scripts or services that use it. Ask the user to confirm explicitly, then call again "
|
||||
"with confirm=true."
|
||||
)
|
||||
if name == "delete_post":
|
||||
slug = str(arguments.get("slug", arguments.get("post_slug", ""))).strip()
|
||||
return ToolInputError(
|
||||
f"Deleting the post '{slug}' is permanent and removes it from the site. "
|
||||
"Ask the user to confirm explicitly, then call again with confirm=true."
|
||||
)
|
||||
if name == "delete_comment":
|
||||
uid = str(arguments.get("uid", "")).strip() or "(unspecified)"
|
||||
return ToolInputError(
|
||||
f"Deleting comment '{uid}' is permanent and cannot be undone. "
|
||||
"Ask the user to confirm explicitly, then call again with confirm=true."
|
||||
)
|
||||
if name == "delete_gist":
|
||||
uid = str(arguments.get("uid", "")).strip() or "(unspecified)"
|
||||
return ToolInputError(
|
||||
f"Deleting gist '{uid}' is permanent. Show the user the exact gist, "
|
||||
"get explicit confirmation, then call again with confirm=true."
|
||||
)
|
||||
if name == "delete_attachment":
|
||||
uid = str(arguments.get("uid", "")).strip() or "(unspecified)"
|
||||
return ToolInputError(
|
||||
f"Deleting attachment '{uid}' is permanent and removes it from its parent. "
|
||||
"Ask the user to confirm explicitly, then call again with confirm=true."
|
||||
)
|
||||
if (
|
||||
name == "container_instance_action"
|
||||
and str(arguments.get("action", "")).strip().lower() == "delete"
|
||||
|
||||
Reference in New Issue
Block a user