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
2 changes: 1 addition & 1 deletion application/single_app/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@
EXECUTOR_TYPE = 'thread'
EXECUTOR_MAX_WORKERS = 30
SESSION_TYPE = 'filesystem'
VERSION = "0.261.303"
VERSION = "0.261.304"
IS_DEVELOPMENT = is_development_env_enabled()

# Opt-out for deployments where App Service Easy Auth is active but the platform
Expand Down
62 changes: 62 additions & 0 deletions application/single_app/functions_orchestration_attempts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# functions_orchestration_attempts.py
"""Which saved orchestration attempts a later retry replaced.

Retrying a failed plan saves the retry's answer as a new assistant message and keeps the
earlier attempt's message, so the conversation still records what happened. Once the

Check warning on line 5 in application/single_app/functions_orchestration_attempts.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.
retry's message exists, the earlier attempt no longer describes the request: its "could
not be completed" text is history, not an answer. Readers that send stored messages to a
model, or export them for the user, drop it with these helpers. The V2 thread hides it the
same way (``lib/orchestration.ts`` ``supersededOrchestrationRunIds``).

Only the standard library is imported, so any reader can use this module without pulling
orchestration storage into its import graph.

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

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.

Version: 0.261.304
Implemented in: 0.261.304
"""

from collections.abc import Mapping


def _attempt_lineage(message):
"""The run id and the retried run id a saved message records, either of which may be None."""
metadata = message.get('metadata') if isinstance(message, Mapping) else None
orchestration = metadata.get('orchestration') if isinstance(metadata, Mapping) else None
if not isinstance(orchestration, Mapping):
return None, None
run_id = orchestration.get('run_id')
retry_of = orchestration.get('retry_of_run_id')
return (
run_id if isinstance(run_id, str) and run_id else None,
retry_of if isinstance(retry_of, str) and retry_of else None,
)


def superseded_orchestration_run_ids(messages):
"""The run ids of attempts that a later attempt among ``messages`` retried."""
superseded = set()
for message in messages or ():
run_id, retry_of = _attempt_lineage(message)
if retry_of and retry_of != run_id:
superseded.add(retry_of)
return superseded


def is_superseded_orchestration_attempt(message, superseded):
"""Whether ``message`` is the assistant answer of an attempt in ``superseded``."""
if not superseded or not isinstance(message, Mapping) or message.get('role') != 'assistant':

Check warning on line 47 in application/single_app/functions_orchestration_attempts.py

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.
return False
run_id, _retry_of = _attempt_lineage(message)
return bool(run_id and run_id in superseded)


def exclude_superseded_orchestration_attempts(messages):
"""Return the messages without replaced attempts' answers, preserving their order."""
messages = list(messages or ())
superseded = superseded_orchestration_run_ids(messages)
if not superseded:
return messages
return [
message for message in messages
if not is_superseded_orchestration_attempt(message, superseded)
]
12 changes: 11 additions & 1 deletion application/single_app/functions_orchestration_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@
from functions_message_block_revisions import resolve_block_sources_in_content
from functions_message_deletion import is_soft_deleted_message
from functions_message_masking import remove_masked_content
from functions_orchestration_attempts import (
is_superseded_orchestration_attempt, superseded_orchestration_run_ids,
)
from functions_orchestration_registry import (
CAPABILITY_ACTION_INVOKE,
CAPABILITY_AGENT_INVOKE,
Expand Down Expand Up @@ -1838,15 +1841,22 @@


def build_conversation_snapshot(messages, settings=None, *, turn_id=None, truncated=False):
"""Keep the recent eligible conversation, bounded independently of the current request."""
"""Keep the recent eligible conversation, bounded independently of the current request.

Check warning on line 1844 in application/single_app/functions_orchestration_context.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.

An attempt a later retry replaced is left out: its failure text no longer describes the
request, and the retry's own answer is what the conversation now says.

Check warning on line 1847 in application/single_app/functions_orchestration_context.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.
"""
eligible = []
superseded = superseded_orchestration_run_ids(messages)
for message in messages or ():
if not isinstance(message, dict):
continue
metadata = message.get('metadata') or {}
orchestration = (metadata.get('orchestration') or {}) if isinstance(metadata, dict) else {}
if turn_id and isinstance(orchestration, dict) and orchestration.get('turn_id') == turn_id:
continue
if is_superseded_orchestration_attempt(message, superseded):
continue
normalized = normalize_history_message(message)
if normalized is not None:
if not normalized['id']:
Expand Down
38 changes: 30 additions & 8 deletions application/single_app/functions_orchestration_recovery.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# functions_orchestration_recovery.py
"""Execution leases and explicitly requested, checkpoint-only retry attempts.

Version: 0.261.303
Version: 0.261.304
Retry publication is one transactional parent CAS + child create. It never
replans, invokes an adapter, or changes plan-revision lineage.
Terminal publication preserves an administrator's reply retraction; its probe
Expand All @@ -13,6 +13,8 @@
and asks for confirmation when that step can have external effects (0.261.293).
A wait left unfinished by an attempt that failed or was stopped no longer blocks a retry;
the retry runs the waiting step again (0.261.303).
An action step that stopped for Microsoft 365 sign-in, approval or policy is retried without

Check warning on line 16 in application/single_app/functions_orchestration_recovery.py

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.
that confirmation: a plan step only reads Microsoft 365 data (0.261.304).
A retry that could only resend requests a service declined is not offered.
A run from the removed legacy contract is never retried, resumed or continued; only
conversation deletion still reads it, to remove its saved data.
Expand All @@ -39,11 +41,12 @@
from functions_orchestration_plan_revisions import PlanRevisionError, read_revision_run
from functions_orchestration_output_store import build_output_cleanup_intent
from functions_orchestration_registry import (
CAPABILITY_WORKFLOW_RESULTS, admitted_export_pairs, external_effect_capability_ids, get_capability,
CAPABILITY_ACTION_INVOKE, CAPABILITY_WORKFLOW_RESULTS, admitted_export_pairs, external_effect_capability_ids,
get_capability,
)
from functions_orchestration_schema import (
LEGACY_PLAN_CODE, LEGACY_PLAN_MESSAGE, PlanValidationError, build_failure, build_step_result,
failure_repeats_on_retry, is_legacy_plan, safe_failure, step_input_specs, summarize_plan,
LEGACY_PLAN_CODE, LEGACY_PLAN_MESSAGE, M365_STEP_FAILURE_CODES, PlanValidationError, build_failure,
build_step_result, failure_repeats_on_retry, is_legacy_plan, safe_failure, step_input_specs, summarize_plan,
)
from functions_orchestration_result_contracts import ProducerIdentity, ResultContractError, ResultRef, TaskResult
from functions_orchestration_result_runtime import (
Expand Down Expand Up @@ -73,6 +76,28 @@
return {'completed', 'partial'}


def _stopped_without_effects(step):
"""Whether a failed step is known to have changed nothing outside the plan.

An action step that stopped for Microsoft 365 sign-in, approval or policy ran only its

Check warning on line 82 in application/single_app/functions_orchestration_recovery.py

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.
one Microsoft 365 action, and a plan step can only read Microsoft 365 data:
``functions_m365_runtime.step_m365_context`` removes the send, invite and read-state
functions. Retrying it repeats no change, so it needs no confirmation.
"""
failure = step.get('failure')
return (
step.get('capability_id') == CAPABILITY_ACTION_INVOKE
and isinstance(failure, dict) and failure.get('code') in M365_STEP_FAILURE_CODES
)


def _effects_uncertain(step):

Check warning on line 94 in application/single_app/functions_orchestration_recovery.py

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.
return (
step.get('capability_id') in EFFECT_CAPABILITIES and bool(step.get('effects_uncertain'))

Check warning on line 96 in application/single_app/functions_orchestration_recovery.py

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.
and not _stopped_without_effects(step)
)


def _redraw_step_ids(record):
"""Steps that finished without the chart or diagram the plan asked of them.

Expand Down Expand Up @@ -463,10 +488,7 @@
if step.get('enabled', True) and step['step_id'] not in reused
]
repeats_refusal = _retry_repeats_refusal(record, steps, retry)
uncertain = any(
step.get('capability_id') in EFFECT_CAPABILITIES and step.get('effects_uncertain')
for step in steps
) or any(
uncertain = any(_effects_uncertain(step) for step in steps) or any(

Check warning on line 491 in application/single_app/functions_orchestration_recovery.py

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.
# A step run again to draw a missing chart repeats what it did the first time.
step.get('capability_id') in EFFECT_CAPABILITIES and step['step_id'] in retry
and step['step_id'] in _redraw_step_ids(record)
Expand Down
30 changes: 28 additions & 2 deletions application/single_app/functions_orchestration_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,10 @@
render through the very same card. Our own paging lives in a sibling ``ui_hints``
field rather than inside the schema, which keeps the schema itself MCP-clean.

Version: 0.261.238
Version: 0.261.304
Missing signed-in session reported as its own step failure in: 0.261.209
Microsoft 365 step failures, with their sources, added in: 0.261.238
Microsoft 365 approval stops carry their pending approval id in: 0.261.304
"""

import hashlib
Expand Down Expand Up @@ -1886,8 +1887,23 @@ def summarize_plan(plan):
INTEGRATION_CAPABILITIES = frozenset({'action_invoke', 'agent_invoke'})


_M365_APPROVAL_ID_PREFIX = 'm365-'
_M365_APPROVAL_ID_DIGITS = frozenset('0123456789abcdef')


def _m365_approval_id(value):
"""An application-minted Microsoft 365 approval id (``m365-`` and a SHA-256 hex digest), or None."""
if type(value) is not str or len(value) != len(_M365_APPROVAL_ID_PREFIX) + 64:
return None
if not value.startswith(_M365_APPROVAL_ID_PREFIX):
return None
digest = value[len(_M365_APPROVAL_ID_PREFIX):]
return value if set(digest) <= _M365_APPROVAL_ID_DIGITS else None


def build_failure(
code='step_failed', *, step_id=None, capability_id=None, provider_status=None, m365_sources=None,
approval_id=None,
):
"""Only application-owned text may cross a failure boundary."""
code = code if code in FAILURE_MESSAGES else 'step_failed'
Expand All @@ -1905,6 +1921,12 @@ def build_failure(
sources = sorted({source for source in m365_sources if source in M365_SOURCES})
if sources:
result['m365_sources'] = sources
if code == 'm365_approval_required':
# The chat offers this pending approval inline. The approvals API still checks that
# the signed-in user is its subject before showing or deciding it.
approval = _m365_approval_id(approval_id)
if approval:
result['approval_id'] = approval
return result


Expand All @@ -1915,14 +1937,18 @@ def safe_failure(value, *, step_id=None, capability_id=None):
capability_id=capability_id or value.get('capability_id'),
provider_status=value.get('provider_status'),
m365_sources=value.get('m365_sources'),
approval_id=value.get('approval_id'),
)


def failure_from_exception(exc, *, answering=False, _depth=0):
"""Use types and structured status, never diagnostic prose or model content."""
code = getattr(exc, 'orchestration_failure_code', None)
if type(code) is str and code in EXCEPTION_FAILURE_CODES:
return build_failure(code, m365_sources=getattr(exc, 'm365_sources', None))
return build_failure(
code, m365_sources=getattr(exc, 'm365_sources', None),
approval_id=getattr(exc, 'approval_id', None),
)
if isinstance(exc, ModelCatalogError):
return build_failure('model_routing_changed')
status = getattr(exc, 'status_code', None)
Expand Down
4 changes: 4 additions & 0 deletions application/single_app/route_backend_chats.py
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,7 @@
exclude_soft_deleted_messages,
is_soft_deleted_message,
)
from functions_orchestration_attempts import exclude_superseded_orchestration_attempts
from functions_message_visual_styles import (
UNSET as VISUAL_STYLE_HEIGHT_UNSET,
VisualStyleError,
Expand Down Expand Up @@ -28462,6 +28463,9 @@ def build_conversation_history_segments(
# them. A deleted message must not reach the summary or the recent window, nor take a slot
# in the history limit; its mask is only a fail-safe, not what keeps it out.
ordered_messages = exclude_soft_deleted_messages(ordered_messages)
# An orchestration attempt a later retry replaced is history, not the conversation: its
# failure text would otherwise reach the model on every later turn.
ordered_messages = exclude_superseded_orchestration_attempts(ordered_messages)

total_messages = len(ordered_messages)
num_recent_messages = min(total_messages, conversation_history_limit)
Expand Down
3 changes: 3 additions & 0 deletions application/single_app/route_backend_conversation_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
)
from functions_mermaid_export import extract_mermaid_sources
from functions_message_block_revisions import resolve_block_sources_in_content
from functions_orchestration_attempts import exclude_superseded_orchestration_attempts
from functions_mermaid_server_render import (
is_mermaid_server_rendering_available,
render_mermaid_visual_assets,
Expand Down Expand Up @@ -674,6 +675,8 @@ def _build_export_entry(
filtered_messages = sanitize_saved_analysis_messages(filtered_messages, user_id)
filtered_messages = hydrate_agent_citations_from_artifacts(filtered_messages, artifact_payload_map)
filtered_messages = public_history_messages(filtered_messages, user_id)
# The thread shows only the latest attempt of a retried plan; the export matches it.
filtered_messages = exclude_superseded_orchestration_attempts(filtered_messages)
ordered_messages = sort_messages_by_thread(filtered_messages)

raw_thoughts = [] if is_collaboration_conversation(conversation) else get_thoughts_for_conversation(conversation.get('id'), user_id)
Expand Down
Loading
Loading