diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 30cc75a..a0917a3 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -19,7 +19,26 @@ All notable changes to this project will be documented in this file. ## Unreleased -### v0.11.0 +### Added + +- **Scheduled steps** — CaseNodeUpdate gains `scheduled_at`, `scheduled_method`, `scheduled_params`, `scheduled_status` fields. Steps with `scheduled_at` are deferred until the time arrives. A background executor polls every 60s and calls the agent method. Workbench shows countdown, "Execute now", and "Cancel" controls on pending scheduled steps. Enables time-based orchestration (call scheduling, retries with backoff, follow-up actions). + - Model: `CaseNodeUpdate.scheduled_at/scheduled_method/scheduled_params/scheduled_status` + - Executor: `_run_scheduled_step_loop` in server.py (local mode) + - Routes: `POST/PATCH .../steps/{case_id}/{step_index}/execute|cancel|schedule` + - UI: status badges, execute now / cancel buttons in workbench monitor + - Case: `cancel_scheduled_steps()` for job stop cascading + - Cases: `get_due_scheduled_steps()` for executor polling + +### Fixed + +- **OpenAPI / JSON Schema (Pydantic 2.12+)** — Building the full FastAPI schema (`GET /openapi.json`, Swagger UI) could raise `PydanticInvalidForJsonSchema: … CallableSchema`. Causes and fixes: + - **`AgentMethodAbstract.model_config["example_dict"]`** — The sample field dict used `"type": str` (Python’s `str` builtin). Pydantic’s JSON Schema generator walks that value and emits a callable schema for builtins. **Fix:** use the string `"str"` (documentation-only example data, not a type annotation). + - **`AgentResponse` nested schemas** — `AgentResponse` (which embeds `AgentMethods` → `CaseNodes` → `CaseNode`) must be rebuilt after other models so OpenAPI sees consistent inner definitions. **Fix:** call `AgentResponse.model_rebuild()` after `Case.model_rebuild()` in `supervaizer/__init__.py`. + - **`CaseNode.factory`** — Runtime value is `Callable[..., CaseNodeUpdate] | None`, which cannot appear in JSON Schema. `Annotated[..., SkipJsonSchema()]` was insufficient: Pydantic 2.12 still registered a `Callable` core definition for `$ref` resolution and OpenAPI failed. **Fix:** declare the field as `Any` (documented in code); behaviour and `registration_info` are unchanged. + +- **Custom routes** — Agents can mount their own FastAPI routers via `custom_routes` field on Agent. Supervaizer mounts them at `/agents/{slug}/api/` without inspecting or managing the routes. Enables agents to expose tool endpoints, webhooks, or any HTTP API alongside the workbench. + +## v0.11.0 - **Job Poll mechanism** — New optional `job_poll` method in `AgentMethods` for manual external service polling. When defined, the workbench shows a "Check for updates" button on active jobs. Clicking it calls the agent's poll handler, which checks external services (email inboxes, call status APIs, etc.) and updates cases accordingly. Enables local development without webhooks — production uses real-time webhooks, local mode uses the poll button. - `AgentMethods`: new `job_poll: AgentMethod | None` field diff --git a/justfile b/justfile index e6905d2..297e475 100644 --- a/justfile +++ b/justfile @@ -56,6 +56,12 @@ env_sync_all: build: hatch build +# Toggle PEP 440 .dev0 on canonical version (syncs src/supervaizer/__version__.py + pyproject [tool.bumpversion]). +# Does not edit CHANGELOG or historical docs. After `on`, avoid `just tag_version` until `off` (tags should be release-only). +# Usage: just version-dev on | just version-dev off +version-dev cmd: + uv run python tools/dev_version.py {{cmd}} + # Reusable recipe to bump version _bump_version bump_type: @echo "VERSION BUMP IN CICD - not running: hatch version {{bump_type}} " diff --git a/pyproject.toml b/pyproject.toml index 2e6be28..b37a3fb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -126,16 +126,22 @@ mypy_path = "src" disallow_any_expr = false [tool.bumpversion] -current_version = "0.10.29" +current_version = "0.11.0.dev0" commit = true tag = true tag_name = "v{new_version}" tag_message = "Release {new_version}" message = "Bump version: {current_version} → {new_version}" -parse = "(?P\\d+)\\.(?P\\d+)\\.(?P\\d+)" +# Optional .devN is stripped on bump (CI publishes X.Y.Z only); use `just version-dev on` locally. +parse = "(?P\\d+)\\.(?P\\d+)\\.(?P\\d+)(\\.dev(?P\\d+))?" serialize = ["{major}.{minor}.{patch}"] [[tool.bumpversion.files]] filename = "src/supervaizer/__version__.py" search = 'VERSION = "{current_version}"' replace = 'VERSION = "{new_version}"' + +[[tool.bumpversion.files]] +filename = "pyproject.toml" +search = 'current_version = "{current_version}"' +replace = 'current_version = "{new_version}"' diff --git a/src/supervaizer/__init__.py b/src/supervaizer/__init__.py index c54d6a0..7244bfa 100644 --- a/src/supervaizer/__init__.py +++ b/src/supervaizer/__init__.py @@ -14,6 +14,7 @@ AgentMethodParams, AgentMethods, AgentMethodField, + AgentResponse, FieldTypeEnum, ) from supervaizer.case import ( @@ -99,3 +100,4 @@ # Rebuild models to resolve forward references after all imports are done Case.model_rebuild() +AgentResponse.model_rebuild() diff --git a/src/supervaizer/__version__.py b/src/supervaizer/__version__.py index 9613d90..400b500 100644 --- a/src/supervaizer/__version__.py +++ b/src/supervaizer/__version__.py @@ -5,6 +5,6 @@ # https://mozilla.org/MPL/2.0/. -VERSION = "0.10.29" +VERSION = "0.11.0.dev0" API_VERSION = "v1" TELEMETRY_VERSION = "v1" diff --git a/src/supervaizer/admin/static/js/workbench-form.js b/src/supervaizer/admin/static/js/workbench-form.js index f1d3bb8..392ffe7 100644 --- a/src/supervaizer/admin/static/js/workbench-form.js +++ b/src/supervaizer/admin/static/js/workbench-form.js @@ -151,6 +151,43 @@ class WorkbenchForm { } } + async executeStepNow(caseId, stepIndex) { + if (!this.activeJobId) return; + try { + const response = await fetch( + `${this.basePath}/jobs/${this.activeJobId}/steps/${caseId}/${stepIndex}/execute`, + { method: 'POST', headers: { 'X-API-Key': this.getApiKey() } }, + ); + const result = await response.json(); + if (!response.ok) { + this.onError(result.detail || 'Execute failed'); + } else { + this.onError(''); + this.refreshMonitor(true); + } + } catch (e) { + this.onError(`Network error: ${e.message}`); + } + } + + async cancelStep(caseId, stepIndex) { + if (!this.activeJobId) return; + try { + const response = await fetch( + `${this.basePath}/jobs/${this.activeJobId}/steps/${caseId}/${stepIndex}/cancel`, + { method: 'POST', headers: { 'X-API-Key': this.getApiKey() } }, + ); + if (!response.ok) { + this.onError('Cancel failed'); + } else { + this.onError(''); + this.refreshMonitor(true); + } + } catch (e) { + this.onError(`Network error: ${e.message}`); + } + } + async stopJob(jobId) { const targetJobId = jobId || this.activeJobId; if (!targetJobId) return; diff --git a/src/supervaizer/admin/templates/components/dialog_renderer.html b/src/supervaizer/admin/templates/components/dialog_renderer.html index 8075fbe..6fa2651 100644 --- a/src/supervaizer/admin/templates/components/dialog_renderer.html +++ b/src/supervaizer/admin/templates/components/dialog_renderer.html @@ -77,7 +77,45 @@ {% elif content_type == "code" %}
{{ content_raw }}
{% else %} -
{{ content_raw }}
+ {# Text/markdown: render with lightweight markdown via Alpine #} +
'; inList = true; } + html += '
  • ' + trimmed.slice(2) + '
  • '; + } else if (trimmed === '') { + if (inList) { html += ''; inList = false; } + html += '
    '; + } else { + if (inList) { html += ''; inList = false; } + html += '

    ' + trimmed + '

    '; + } + } + if (inList) html += ''; + // Inline formatting: bold, italic, code + html = html.replace(/\*\*(.+?)\*\*/g, '$1'); + html = html.replace(/\*(.+?)\*/g, '$1'); + html = html.replace(/`(.+?)`/g, '$1'); + rendered = html; + " + x-html="rendered"> +
    {% endif %} {% endif %} @@ -178,7 +216,27 @@ {% else %} -
    {{ content_raw }}
    +
    • ' + t.slice(2) + '

    '; + else if (t === '') html += '
    '; + else html += '

    ' + t + '

    '; + } + html = html.replace(/\*\*(.+?)\*\*/g, '$1'); + html = html.replace(/`(.+?)`/g, '$1'); + rendered = html; + " + x-html="rendered"> +
    {% endif %} {% endmacro %} diff --git a/src/supervaizer/admin/templates/workbench.html b/src/supervaizer/admin/templates/workbench.html index bdb7ca5..6643c3e 100644 --- a/src/supervaizer/admin/templates/workbench.html +++ b/src/supervaizer/admin/templates/workbench.html @@ -389,7 +389,7 @@

    Execution Monitor

    {# end workbenchPage #} {# ── Workbench JS ────────────────────────────────────────────────── #} - +