Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/workflows/cosmos-response-contract-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,12 @@
run: python -c "import tiktoken; tiktoken.get_encoding('cl100k_base'); tiktoken.get_encoding('o200k_base')"
- name: Require reviewed storage owners and doubles
run: python scripts/check_cosmos_response_boundaries.py
- name: Verify real plugin discovery and document-action runtime

Check warning on line 43 in .github/workflows/cosmos-response-contract-check.yml

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains AI, plugin, agent, or workspace boundary marker. Recommendation%3A Check whether prompts, chat history, uploaded documents, embeddings, citations, settings, or identity can cross a new boundary.
run: >-
python -m pytest -q -p no:cacheprovider -p test_support.cosmos_network_guard

Check warning on line 45 in .github/workflows/cosmos-response-contract-check.yml

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains secret or sensitive data source marker. Recommendation%3A Pair this source with any nearby network, logging, serialization, or process execution sink before approving.
--disable-warnings --tb=short

Check warning on line 46 in .github/workflows/cosmos-response-contract-check.yml

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains security control, sanitization, or audit marker. Recommendation%3A Confirm the change does not weaken auth, CSRF, CSP, XSS defenses, settings sanitization, redaction, audit logging, or tests.
functional_tests/test_plugin_discovery_identity.py

Check warning on line 47 in .github/workflows/cosmos-response-contract-check.yml

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains AI, plugin, agent, or workspace boundary marker. Recommendation%3A Check whether prompts, chat history, uploaded documents, embeddings, citations, settings, or identity can cross a new boundary.
functional_tests/test_orchestration_document_action_end_to_end.py
functional_tests/test_orchestration_document_loader_reliability.py
- name: Run contracts with external network blocked before collection
run: python scripts/check_cosmos_response_boundaries.py --run-contracts
2 changes: 1 addition & 1 deletion application/single_app/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@
EXECUTOR_MAX_WORKERS = 30
CHAT_STREAM_EXECUTOR_MAX_WORKERS = max(8, int(os.getenv('SIMPLECHAT_CHAT_STREAM_WORKERS', '32') or '32'))
SESSION_TYPE = 'filesystem'
VERSION = "0.261.420"
VERSION = "0.261.421"
IS_DEVELOPMENT = is_development_env_enabled()

# Opt-out for deployments where App Service Easy Auth is active but the platform
Expand Down
37 changes: 30 additions & 7 deletions application/single_app/functions_orchestration_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@
kernel holds only the built-in chart tools, never the action's own functions, so saved
visual preferences can be applied there without reaching calls to the integration.

Version: 0.261.321
Version: 0.261.421
Microsoft 365 actions authorized for their own step in: 0.261.238
SharePoint and OneDrive steps bind their model's token budget and analysis model in: 0.261.300
Explicit operation intent and named input consumption added in: 0.261.321
Canonical plugin identity and safe action-stage failures added in: 0.261.421

Check warning on line 12 in application/single_app/functions_orchestration_actions.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains AI, plugin, agent, or workspace boundary marker. Recommendation%3A Check whether prompts, chat history, uploaded documents, embeddings, citations, settings, or identity can cross a new boundary.
"""

import asyncio
Expand Down Expand Up @@ -87,6 +88,10 @@
class ActionExecutionError(RuntimeError):
"""A user-safe failure of the selected action."""

def __init__(self, message="", *, failure_code=None):
super().__init__(message)
self.orchestration_failure_code = failure_code


def _check_access(settings, catalog, action_ref):
# The invocation's own source preflight owns the runtime services; this checks the
Expand Down Expand Up @@ -791,7 +796,10 @@
payload=invocation.result.value if invocation.result is not None else None,
)
if document_error:
failure = ActionExecutionError('The selected document could not be read by this action.')
failure = ActionExecutionError(
'The selected document could not be read by this action.',
failure_code='document_action_read_failed',
)
raise failure
if invocation.result is not None:
outputs.append(invocation.result.value)
Expand Down Expand Up @@ -829,16 +837,25 @@
}
capture_manifest(manifest, current_settings)
if not loader.load_plugin_from_manifest(prepared, user_id):
raise ActionExecutionError('The selected action could not be loaded.')
raise ActionExecutionError(
'The selected action could not be loaded.',
failure_code='action_initialization_failed',
)
if bound_ids:
# The actual native reader, not a configured name or description, owns these reads.
from semantic_kernel_plugins.document_search_plugin import DocumentSearchPlugin

if not any(isinstance(plugin, DocumentSearchPlugin) for plugin in loader.plugin_instances):
raise ActionExecutionError('The action does not provide the native document reader.')
raise ActionExecutionError(
'The action does not provide the native document reader.',
failure_code='document_reader_identity_mismatch',
)
functions = kernel.get_list_of_function_metadata({})
if not functions:
raise ActionExecutionError('The selected action has no enabled functions.')
raise ActionExecutionError(
'The selected action has no enabled functions.',
failure_code='action_initialization_failed',
)
if cancel_requested():
raise AgentExecutionCancelled('Action execution was cancelled.')
kernel.add_filter(FilterTypes.FUNCTION_INVOCATION, guard_function)
Expand All @@ -853,7 +870,10 @@
service = _build_action_model(current_settings, context, user_id)
kernel.add_service(service)
if not getattr(service, 'SUPPORTS_FUNCTION_CALLING', False):
raise ActionExecutionError('The selected model does not support action functions.')
raise ActionExecutionError(
'The selected model does not support action functions.',
failure_code='action_initialization_failed',
)
if file_source:
# Chat binds these through the selected agent; this step's own model stands in.
kernel.add_filter(FilterTypes.AUTO_FUNCTION_INVOCATION, file_step_filter(
Expand Down Expand Up @@ -938,7 +958,10 @@
if invocation_capture is not None:
invocation_capture.require_valid(captured=True)
if not calls or not outputs:
raise ActionExecutionError('The action did not return any function results.')
raise ActionExecutionError(
'The action did not return any function results.',
failure_code='action_results_missing',
)
findings = '\n\n'.join(str(reply) for reply in replies or [] if str(reply).strip())
if not findings:
findings = '\n\n'.join(
Expand Down
23 changes: 20 additions & 3 deletions application/single_app/functions_orchestration_adapters.py
Original file line number Diff line number Diff line change
Expand Up @@ -2357,13 +2357,30 @@
raise
except Exception as exc:
# This is the boundary for arbitrary plugin/provider code; never expose its exceptions.
failed_result = _failed_result('The action could not complete.', exc)
failure_code = failed_result['failure']['code']
failure_stage = {
'action_initialization_failed': 'action_initialization',
'document_reader_identity_mismatch': 'action_initialization',
'document_action_read_failed': 'document_action_read',
'action_results_missing': 'action_results',
}.get(failure_code, 'action_invocation')
log_event(
f'{_LOG_PREFIX} Direct action execution failed.',
level=logging.ERROR,
extra={'action_ref': action_ref, 'step_id': (step or {}).get('step_id'),
'error_type': type(exc).__name__},
extra={
**workflow_log_context(
conversation_id=_ctx(context, 'conversation_id'), turn_id=_ctx(context, 'turn_id'),

Check warning on line 2373 in application/single_app/functions_orchestration_adapters.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains AI, plugin, agent, or workspace boundary marker. Recommendation%3A Check whether prompts, chat history, uploaded documents, embeddings, citations, settings, or identity can cross a new boundary.
run_id=_ctx(context, 'run_id'), step_id=step['step_id'],
),
'capability_id': CAPABILITY_ACTION_INVOKE,
'stage': failure_stage, 'failure_code': failure_code,
'error_type': type(exc).__name__,
**({'function_calls_attempted': failure_stage == 'document_action_read'}
if failure_stage in ('action_initialization', 'document_action_read') else {}),
},
)
return _failed_result('The action could not complete.', exc)
return failed_result
citations = _agent_citations(
None, user_id, _ctx(context, 'conversation_id'), set(),
root_id=result['root_id'], scoped_invocations=result['invocations'],
Expand Down
18 changes: 18 additions & 0 deletions application/single_app/functions_orchestration_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -2037,6 +2037,22 @@
"This step's agent or action isn't available to you right now, so it didn't run. Check "
'that it still exists, is enabled and is shared with you, then retry.'
),
'action_initialization_failed': (
"The action's tools or model could not be initialized. Ask an admin to check the "

Check warning on line 2041 in application/single_app/functions_orchestration_schema.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains AI, plugin, agent, or workspace boundary marker. Recommendation%3A Check whether prompts, chat history, uploaded documents, embeddings, citations, settings, or identity can cross a new boundary.
'action and model configuration, then retry.'
),
'document_reader_identity_mismatch': (
"The document-reading tool did not match the application's expected tool. No "

Check warning on line 2045 in application/single_app/functions_orchestration_schema.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains AI, plugin, agent, or workspace boundary marker. Recommendation%3A Check whether prompts, chat history, uploaded documents, embeddings, citations, settings, or identity can cross a new boundary.
'document-reading function ran. Ask an admin to repair or update the application, then retry.'
),
'document_action_read_failed': (
'The action could not complete the selected document read. Check the file availability '
'and requested inspection, then retry.'
),
'action_results_missing': (
'The action returned no usable function results. No findings from this step were used. '
'Ask an admin to check the action and model tool support if it keeps failing.'

Check warning on line 2054 in application/single_app/functions_orchestration_schema.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains AI, plugin, agent, or workspace boundary marker. Recommendation%3A Check whether prompts, chat history, uploaded documents, embeddings, citations, settings, or identity can cross a new boundary.
),
'operation_recovery_required': (
'An operation may already have run, or its material changed. Check its saved outgoing '
'action and the external service before creating a new plan. It was not repeated.'
Expand Down Expand Up @@ -2213,6 +2229,8 @@
# Failures an exception may carry by code, through its ``orchestration_failure_code``.
EXCEPTION_FAILURE_CODES = M365_STEP_FAILURE_CODES | {
'external_session_required', 'operation_recovery_required', 'integration_input_unavailable',
'action_initialization_failed', 'document_reader_identity_mismatch',
'document_action_read_failed', 'action_results_missing',
STORAGE_FAILURE_CODE,
}
# Steps that hand work to an integration; input-reading failures have their own code.
Expand Down
2 changes: 2 additions & 0 deletions application/single_app/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
python-dotenv==1.2.2
azure-ai-formrecognizer==3.3.3
azure-ai-projects==1.0.0
azure-ai-agents==1.2.0b6

Check warning on line 32 in application/single_app/requirements.txt

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Python requirement appears to use a pre-release version. Recommendation%3A Confirm the pre-release package was intentionally selected and reviewed.
pyjwt==2.15.0
markdown2==2.5.5
azure-mgmt-cognitiveservices==13.6.0
Expand All @@ -53,6 +53,8 @@
ffmpeg-binaries-compat==1.0.1
ffmpeg-python==0.2.0
semantic-kernel==1.39.4
# Required by Semantic Kernel's Handlebars template parser.
PyMeta3==0.5.1
snowflake-connector-python[pandas]==4.7.3
tableauserverclient==0.40
yamcs-client==2.1.0
Expand Down
36 changes: 22 additions & 14 deletions application/single_app/semantic_kernel_plugins/plugin_loader.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
import os
import importlib.util
# plugin_loader.py
"""Discover installed plugins without duplicating their runtime class identities."""

import importlib
import inspect
import logging
from functions_appinsights import log_event
import os
from typing import Dict, Type, List

from functions_appinsights import log_event
from semantic_kernel_plugins.base_plugin import BasePlugin


PLUGIN_DIR = os.path.dirname(__file__)


def discover_plugins() -> Dict[str, Type[BasePlugin]]:
"""
Dynamically discover all BasePlugin subclasses in the semantic_kernel_plugins directory.
Expand All @@ -18,24 +24,26 @@ def discover_plugins() -> Dict[str, Type[BasePlugin]]:
for filename in os.listdir(PLUGIN_DIR):
if filename.endswith('.py') and not filename.startswith('__') and filename != 'base_plugin.py' and filename != 'plugin_loader.py':
module_name = filename[:-3]
module_path = os.path.join(PLUGIN_DIR, filename)


try:
spec = importlib.util.spec_from_file_location(f"semantic_kernel_plugins.{module_name}", module_path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)

# Runtime guards and other loaders must see the same imported classes.
module = importlib.import_module(f"semantic_kernel_plugins.{module_name}")

for name, obj in inspect.getmembers(module, inspect.isclass):
if issubclass(obj, BasePlugin) and obj is not BasePlugin:
plugins[name] = obj

except Exception as e:
# Log the error but continue with other plugins
log_event(f"Failed to load plugin module {module_name}: {str(e)}")

except Exception as exc:
log_event(
"[PLUGIN_LOADER] Plugin module could not be loaded.",
level=logging.WARNING,
extra={"plugin_name": module_name, "error_type": type(exc).__name__},
)
continue

return plugins


def get_all_plugin_metadata() -> List[dict]:
"""
Instantiate each discovered plugin and return a list of their metadata dicts.
Expand Down
14 changes: 13 additions & 1 deletion docs/explanation/features/CHAT_ORCHESTRATION_ACTIONS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Chat Orchestration Action Access

Version: **0.261.420**
Version: **0.261.421**

Implemented in version: **0.261.098**

Expand All @@ -10,6 +10,8 @@ Operation intent and named inputs implemented in version: **0.261.321**

Selected-document action binding implemented in version: **0.261.419**

Canonical plugin discovery and action-stage diagnostics implemented in version: **0.261.421**

Application version tracking remains in `application/single_app/config.py`.

## Purpose
Expand Down Expand Up @@ -102,6 +104,16 @@ and binds their arguments. Avoiding agent setup does **not** mean avoiding model
Conversational follow-ups retain their resolved request and bounded, authorized
conversation reference when handed to an action, including accepted clarification answers.

Shared discovery imports each installed plugin through its registered package module.
The native-reader guard therefore sees the same class as the logged and classic loaders,
without sharing plugin instances or their user-specific configuration.
Loading/identity failures, failed document reads, and missing function results retain
distinct safe failure codes through saved step details and the final failure explanation.
They are not successful findings or automatic transient-retry conditions.

See the [plugin discovery identity fix](../fixes/ORCHESTRATION_PLUGIN_DISCOVERY_IDENTITY_FIX.md)
for the real-loader regression and recovery boundaries.

### Selected documents and native inspection

Since **0.261.419**, a registered Document Search action can bind exact workspace
Expand Down
Loading
Loading