From 51efa763234c2f6ce8d1c8ff257564d28d77c6f2 Mon Sep 17 00:00:00 2001 From: Michael Brooks Date: Thu, 18 Jun 2026 18:00:37 -0700 Subject: [PATCH 1/2] feat: add agent_view feature property to v1 and v2 schemas Mirrors the canonical agent_view definition from webapp's manifest schema. Sibling of assistant_view; supports agent_description, suggested_prompts (max 4), and actions. Adds local-schema test coverage so edits to the versioned schema files are validated before release. Existing tests run through the GitHub-pinned schema and would not catch local changes. --- schemas/manifest.schema.1.0.0.json | 54 +++++++++++++++++++ schemas/manifest.schema.2.0.0.json | 54 +++++++++++++++++++ tests/test_manifest_v1_schema.py | 84 +++++++++++++++++++++++++++++- tests/test_manifest_v2_schema.py | 80 +++++++++++++++++++++++++++- tests/utils.py | 10 ++++ 5 files changed, 280 insertions(+), 2 deletions(-) diff --git a/schemas/manifest.schema.1.0.0.json b/schemas/manifest.schema.1.0.0.json index c256ffa..9fd0fc4 100644 --- a/schemas/manifest.schema.1.0.0.json +++ b/schemas/manifest.schema.1.0.0.json @@ -422,6 +422,57 @@ } } }, + "app-manifests.v1.features.agent_view": { + "type": "object", + "description": "Settings related to agent view for apps using agent features.", + "additionalProperties": false, + "properties": { + "agent_description": { + "type": "string", + "maxLength": 300, + "description": "A description specific to the agent, used to communicate to end users what agent functionality the app provides." + }, + "suggested_prompts": { + "type": "array", + "maxItems": 4, + "description": "An array of pre-written prompts designed to guide users when interacting with the agent.", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["title", "message"], + "properties": { + "title": { + "type": "string", + "description": "The prompt title." + }, + "message": { + "type": "string", + "description": "The prompt content." + } + } + } + }, + "actions": { + "type": "array", + "description": "Quick actions surfaced in the agent UI.", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["name", "description"], + "properties": { + "name": { + "type": "string", + "description": "The action name." + }, + "description": { + "type": "string", + "description": "A short description of what the action does." + } + } + } + } + } + }, "app-manifests.v1.features.schema": { "type": "object", "description": "Key app features driving interactivity and functionality.", @@ -450,6 +501,9 @@ }, "assistant_view": { "$ref": "#/definitions/app-manifests.v1.features.assistant_view" + }, + "agent_view": { + "$ref": "#/definitions/app-manifests.v1.features.agent_view" } } }, diff --git a/schemas/manifest.schema.2.0.0.json b/schemas/manifest.schema.2.0.0.json index 08cf2ac..2a69e11 100644 --- a/schemas/manifest.schema.2.0.0.json +++ b/schemas/manifest.schema.2.0.0.json @@ -444,6 +444,57 @@ } } }, + "app-manifests.v1.features.agent_view": { + "type": "object", + "description": "Settings related to agent view for apps using agent features.", + "additionalProperties": false, + "properties": { + "agent_description": { + "type": "string", + "maxLength": 300, + "description": "A description specific to the agent, used to communicate to end users what agent functionality the app provides." + }, + "suggested_prompts": { + "type": "array", + "maxItems": 4, + "description": "An array of pre-written prompts designed to guide users when interacting with the agent.", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["title", "message"], + "properties": { + "title": { + "type": "string", + "description": "The prompt title." + }, + "message": { + "type": "string", + "description": "The prompt content." + } + } + } + }, + "actions": { + "type": "array", + "description": "Quick actions surfaced in the agent UI.", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["name", "description"], + "properties": { + "name": { + "type": "string", + "description": "The action name." + }, + "description": { + "type": "string", + "description": "A short description of what the action does." + } + } + } + } + } + }, "app-manifests.v1.features.schema": { "type": "object", "properties": { @@ -470,6 +521,9 @@ }, "assistant_view": { "$ref": "#/definitions/app-manifests.v1.features.assistant_view" + }, + "agent_view": { + "$ref": "#/definitions/app-manifests.v1.features.agent_view" } }, "description": "A group of settings corresponding to the Features section of the app config pages.", diff --git a/tests/test_manifest_v1_schema.py b/tests/test_manifest_v1_schema.py index 57f548e..61c348c 100644 --- a/tests/test_manifest_v1_schema.py +++ b/tests/test_manifest_v1_schema.py @@ -1,7 +1,7 @@ import pytest from jsonschema import ValidationError, validate -from .utils import get_json, get_schema +from .utils import get_json, get_schema, get_schema_v1 class TestManifestV1Schema: @@ -36,3 +36,85 @@ def test_invalid(self): with pytest.raises(ValidationError): validate(manifest, schema) # THEN validation exception is raised + + +def _agent_view_fixture(): + return { + "agent_description": "A sample agent that demonstrates agent_view manifest settings.", + "suggested_prompts": [ + { + "title": "Summarize this thread", + "message": "Please summarize the conversation in this thread.", + }, + { + "title": "Draft a reply", + "message": "Draft a reply to the most recent message.", + }, + ], + "actions": [ + {"name": "open_settings", "description": "Open the agent settings panel."}, + ], + } + + +class TestManifestV1AgentView: + """Validates the local v1 schema (not the GitHub-pinned router) so changes + to schemas/manifest.schema.1.0.0.json are exercised before release.""" + + def _manifest_with_agent_view(self): + manifest = get_json("tests/manifests/v1/manifest.valid.json") + manifest["features"]["agent_view"] = _agent_view_fixture() + return manifest + + def test_agent_view_valid(self): + # GIVEN — manifest with a fully populated agent_view block + schema = get_schema_v1() + manifest = self._manifest_with_agent_view() + # WHEN + try: + validate(manifest, schema) + except ValidationError as e: + # THEN + assert False, f"validation failed: {e}" + + def test_agent_view_too_many_prompts(self): + # GIVEN — suggested_prompts is capped at 4 + schema = get_schema_v1() + manifest = self._manifest_with_agent_view() + manifest["features"]["agent_view"]["suggested_prompts"] = [ + {"title": f"prompt {i}", "message": f"message {i}"} for i in range(5) + ] + # WHEN + with pytest.raises(ValidationError): + validate(manifest, schema) + # THEN validation exception is raised + + def test_agent_view_action_missing_description(self): + # GIVEN — actions[].description is required + schema = get_schema_v1() + manifest = self._manifest_with_agent_view() + manifest["features"]["agent_view"]["actions"] = [{"name": "open_settings"}] + # WHEN + with pytest.raises(ValidationError): + validate(manifest, schema) + # THEN validation exception is raised + + def test_agent_view_description_too_long(self): + # GIVEN — agent_description is capped at 300 chars + schema = get_schema_v1() + manifest = self._manifest_with_agent_view() + manifest["features"]["agent_view"]["agent_description"] = "x" * 301 + # WHEN + with pytest.raises(ValidationError): + validate(manifest, schema) + # THEN validation exception is raised + + def test_agent_view_additional_property_rejected(self): + # GIVEN — additionalProperties is false on agent_view + schema = get_schema_v1() + manifest = self._manifest_with_agent_view() + manifest["features"]["agent_view"]["unexpected_field"] = True + # WHEN + with pytest.raises(ValidationError): + validate(manifest, schema) + # THEN validation exception is raised diff --git a/tests/test_manifest_v2_schema.py b/tests/test_manifest_v2_schema.py index 0f99a2c..c2acb2a 100644 --- a/tests/test_manifest_v2_schema.py +++ b/tests/test_manifest_v2_schema.py @@ -1,7 +1,7 @@ import pytest from jsonschema import ValidationError, validate -from .utils import get_json, get_schema +from .utils import get_json, get_schema, get_schema_v2 class TestManifestV2Schema: @@ -34,3 +34,81 @@ def test_invalid(self): with pytest.raises(ValidationError): validate(manifest, schema) # THEN validation exception is raised + + +def _agent_view_fixture(): + return { + "agent_description": "A sample agent that demonstrates agent_view manifest settings.", + "suggested_prompts": [ + { + "title": "Summarize this thread", + "message": "Please summarize the conversation in this thread.", + }, + ], + "actions": [ + {"name": "open_settings", "description": "Open the agent settings panel."}, + ], + } + + +class TestManifestV2AgentView: + """Validates the local v2 schema (not the GitHub-pinned router) so changes + to schemas/manifest.schema.2.0.0.json are exercised before release.""" + + def _manifest_with_agent_view(self): + manifest = get_json("tests/manifests/v2/manifest.valid.json") + manifest["features"]["agent_view"] = _agent_view_fixture() + return manifest + + def test_agent_view_valid(self): + # GIVEN — manifest with a fully populated agent_view block + schema = get_schema_v2() + manifest = self._manifest_with_agent_view() + # WHEN + try: + validate(manifest, schema) + except ValidationError as e: + # THEN + assert False, f"validation failed: {e}" + + def test_agent_view_too_many_prompts(self): + # GIVEN — suggested_prompts is capped at 4 + schema = get_schema_v2() + manifest = self._manifest_with_agent_view() + manifest["features"]["agent_view"]["suggested_prompts"] = [ + {"title": f"prompt {i}", "message": f"message {i}"} for i in range(5) + ] + # WHEN + with pytest.raises(ValidationError): + validate(manifest, schema) + # THEN validation exception is raised + + def test_agent_view_action_missing_description(self): + # GIVEN — actions[].description is required + schema = get_schema_v2() + manifest = self._manifest_with_agent_view() + manifest["features"]["agent_view"]["actions"] = [{"name": "open_settings"}] + # WHEN + with pytest.raises(ValidationError): + validate(manifest, schema) + # THEN validation exception is raised + + def test_agent_view_description_too_long(self): + # GIVEN — agent_description is capped at 300 chars + schema = get_schema_v2() + manifest = self._manifest_with_agent_view() + manifest["features"]["agent_view"]["agent_description"] = "x" * 301 + # WHEN + with pytest.raises(ValidationError): + validate(manifest, schema) + # THEN validation exception is raised + + def test_agent_view_additional_property_rejected(self): + # GIVEN — additionalProperties is false on agent_view + schema = get_schema_v2() + manifest = self._manifest_with_agent_view() + manifest["features"]["agent_view"]["unexpected_field"] = True + # WHEN + with pytest.raises(ValidationError): + validate(manifest, schema) + # THEN validation exception is raised diff --git a/tests/utils.py b/tests/utils.py index 8cb9d60..8416e01 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -2,6 +2,8 @@ from typing import Dict MANIFEST_SCHEMA_PATH = "manifest.schema.json" +MANIFEST_SCHEMA_V1_PATH = "schemas/manifest.schema.1.0.0.json" +MANIFEST_SCHEMA_V2_PATH = "schemas/manifest.schema.2.0.0.json" def get_json(path: str) -> Dict: @@ -11,3 +13,11 @@ def get_json(path: str) -> Dict: def get_schema() -> Dict: return get_json(MANIFEST_SCHEMA_PATH) + + +def get_schema_v1() -> Dict: + return get_json(MANIFEST_SCHEMA_V1_PATH) + + +def get_schema_v2() -> Dict: + return get_json(MANIFEST_SCHEMA_V2_PATH) From 314754d2ca5bfe403f806a7a3dc6c84787ca803a Mon Sep 17 00:00:00 2001 From: Michael Brooks Date: Thu, 18 Jun 2026 21:03:07 -0700 Subject: [PATCH 2/2] test: defer agent_view test coverage to follow-up PR Reverts the local-schema tests added in the previous commit so this PR matches the existing schema-only feature pattern (mcp_servers, is_mcp_enabled, optional scopes). Coverage will land in a separate PR that refactors the test suite to run every case against both the hosted router schema and the local versioned files. --- tests/test_manifest_v1_schema.py | 84 +------------------------------- tests/test_manifest_v2_schema.py | 80 +----------------------------- tests/utils.py | 10 ---- 3 files changed, 2 insertions(+), 172 deletions(-) diff --git a/tests/test_manifest_v1_schema.py b/tests/test_manifest_v1_schema.py index 61c348c..57f548e 100644 --- a/tests/test_manifest_v1_schema.py +++ b/tests/test_manifest_v1_schema.py @@ -1,7 +1,7 @@ import pytest from jsonschema import ValidationError, validate -from .utils import get_json, get_schema, get_schema_v1 +from .utils import get_json, get_schema class TestManifestV1Schema: @@ -36,85 +36,3 @@ def test_invalid(self): with pytest.raises(ValidationError): validate(manifest, schema) # THEN validation exception is raised - - -def _agent_view_fixture(): - return { - "agent_description": "A sample agent that demonstrates agent_view manifest settings.", - "suggested_prompts": [ - { - "title": "Summarize this thread", - "message": "Please summarize the conversation in this thread.", - }, - { - "title": "Draft a reply", - "message": "Draft a reply to the most recent message.", - }, - ], - "actions": [ - {"name": "open_settings", "description": "Open the agent settings panel."}, - ], - } - - -class TestManifestV1AgentView: - """Validates the local v1 schema (not the GitHub-pinned router) so changes - to schemas/manifest.schema.1.0.0.json are exercised before release.""" - - def _manifest_with_agent_view(self): - manifest = get_json("tests/manifests/v1/manifest.valid.json") - manifest["features"]["agent_view"] = _agent_view_fixture() - return manifest - - def test_agent_view_valid(self): - # GIVEN — manifest with a fully populated agent_view block - schema = get_schema_v1() - manifest = self._manifest_with_agent_view() - # WHEN - try: - validate(manifest, schema) - except ValidationError as e: - # THEN - assert False, f"validation failed: {e}" - - def test_agent_view_too_many_prompts(self): - # GIVEN — suggested_prompts is capped at 4 - schema = get_schema_v1() - manifest = self._manifest_with_agent_view() - manifest["features"]["agent_view"]["suggested_prompts"] = [ - {"title": f"prompt {i}", "message": f"message {i}"} for i in range(5) - ] - # WHEN - with pytest.raises(ValidationError): - validate(manifest, schema) - # THEN validation exception is raised - - def test_agent_view_action_missing_description(self): - # GIVEN — actions[].description is required - schema = get_schema_v1() - manifest = self._manifest_with_agent_view() - manifest["features"]["agent_view"]["actions"] = [{"name": "open_settings"}] - # WHEN - with pytest.raises(ValidationError): - validate(manifest, schema) - # THEN validation exception is raised - - def test_agent_view_description_too_long(self): - # GIVEN — agent_description is capped at 300 chars - schema = get_schema_v1() - manifest = self._manifest_with_agent_view() - manifest["features"]["agent_view"]["agent_description"] = "x" * 301 - # WHEN - with pytest.raises(ValidationError): - validate(manifest, schema) - # THEN validation exception is raised - - def test_agent_view_additional_property_rejected(self): - # GIVEN — additionalProperties is false on agent_view - schema = get_schema_v1() - manifest = self._manifest_with_agent_view() - manifest["features"]["agent_view"]["unexpected_field"] = True - # WHEN - with pytest.raises(ValidationError): - validate(manifest, schema) - # THEN validation exception is raised diff --git a/tests/test_manifest_v2_schema.py b/tests/test_manifest_v2_schema.py index c2acb2a..0f99a2c 100644 --- a/tests/test_manifest_v2_schema.py +++ b/tests/test_manifest_v2_schema.py @@ -1,7 +1,7 @@ import pytest from jsonschema import ValidationError, validate -from .utils import get_json, get_schema, get_schema_v2 +from .utils import get_json, get_schema class TestManifestV2Schema: @@ -34,81 +34,3 @@ def test_invalid(self): with pytest.raises(ValidationError): validate(manifest, schema) # THEN validation exception is raised - - -def _agent_view_fixture(): - return { - "agent_description": "A sample agent that demonstrates agent_view manifest settings.", - "suggested_prompts": [ - { - "title": "Summarize this thread", - "message": "Please summarize the conversation in this thread.", - }, - ], - "actions": [ - {"name": "open_settings", "description": "Open the agent settings panel."}, - ], - } - - -class TestManifestV2AgentView: - """Validates the local v2 schema (not the GitHub-pinned router) so changes - to schemas/manifest.schema.2.0.0.json are exercised before release.""" - - def _manifest_with_agent_view(self): - manifest = get_json("tests/manifests/v2/manifest.valid.json") - manifest["features"]["agent_view"] = _agent_view_fixture() - return manifest - - def test_agent_view_valid(self): - # GIVEN — manifest with a fully populated agent_view block - schema = get_schema_v2() - manifest = self._manifest_with_agent_view() - # WHEN - try: - validate(manifest, schema) - except ValidationError as e: - # THEN - assert False, f"validation failed: {e}" - - def test_agent_view_too_many_prompts(self): - # GIVEN — suggested_prompts is capped at 4 - schema = get_schema_v2() - manifest = self._manifest_with_agent_view() - manifest["features"]["agent_view"]["suggested_prompts"] = [ - {"title": f"prompt {i}", "message": f"message {i}"} for i in range(5) - ] - # WHEN - with pytest.raises(ValidationError): - validate(manifest, schema) - # THEN validation exception is raised - - def test_agent_view_action_missing_description(self): - # GIVEN — actions[].description is required - schema = get_schema_v2() - manifest = self._manifest_with_agent_view() - manifest["features"]["agent_view"]["actions"] = [{"name": "open_settings"}] - # WHEN - with pytest.raises(ValidationError): - validate(manifest, schema) - # THEN validation exception is raised - - def test_agent_view_description_too_long(self): - # GIVEN — agent_description is capped at 300 chars - schema = get_schema_v2() - manifest = self._manifest_with_agent_view() - manifest["features"]["agent_view"]["agent_description"] = "x" * 301 - # WHEN - with pytest.raises(ValidationError): - validate(manifest, schema) - # THEN validation exception is raised - - def test_agent_view_additional_property_rejected(self): - # GIVEN — additionalProperties is false on agent_view - schema = get_schema_v2() - manifest = self._manifest_with_agent_view() - manifest["features"]["agent_view"]["unexpected_field"] = True - # WHEN - with pytest.raises(ValidationError): - validate(manifest, schema) - # THEN validation exception is raised diff --git a/tests/utils.py b/tests/utils.py index 8416e01..8cb9d60 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -2,8 +2,6 @@ from typing import Dict MANIFEST_SCHEMA_PATH = "manifest.schema.json" -MANIFEST_SCHEMA_V1_PATH = "schemas/manifest.schema.1.0.0.json" -MANIFEST_SCHEMA_V2_PATH = "schemas/manifest.schema.2.0.0.json" def get_json(path: str) -> Dict: @@ -13,11 +11,3 @@ def get_json(path: str) -> Dict: def get_schema() -> Dict: return get_json(MANIFEST_SCHEMA_PATH) - - -def get_schema_v1() -> Dict: - return get_json(MANIFEST_SCHEMA_V1_PATH) - - -def get_schema_v2() -> Dict: - return get_json(MANIFEST_SCHEMA_V2_PATH)