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.304"
VERSION = "0.261.305"
IS_DEVELOPMENT = is_development_env_enabled()

# Opt-out for deployments where App Service Easy Auth is active but the platform
Expand Down
17 changes: 14 additions & 3 deletions application/single_app/functions_m365_citations.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
# functions_m365_citations.py
"""Microsoft 365 emails, calendar events and files as first-class answer citations.

Version: 0.261.303
Version: 0.261.305
Implemented in: 0.261.303
Direct-answer guidance refined in: 0.261.305

An item an answer draws on is normalized into a small citation record with a deterministic id.
Tool results carry a model-facing citation value in SimpleChat's inline marker grammar,
``(Source: <title>, Location: <where>) [#<id>]``, so the model cites an email, event or file the
same way it cites a workspace document. The records travel with the assistant message and the
conversation, so the browser draws citation chips, source cards and "Open online" links without
conversation, so the browser draws citation chips, source cards and online links without

Check warning on line 12 in application/single_app/functions_m365_citations.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.
calling Microsoft 365 again.

Rules every record follows:
Expand Down Expand Up @@ -60,7 +61,16 @@
MAX_MARKER_TITLE_CHARS = 120
MAX_SOURCES_NOTE_ITEMS = 50

M365_ANSWER_STYLE_INSTRUCTIONS = (

Check warning on line 64 in application/single_app/functions_m365_citations.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.
"For Microsoft 365 items, answer the user's question directly without routine source-provenance "
'introductions such as "Confirmed from the SharePoint document". Citations already identify the '

Check warning on line 66 in application/single_app/functions_m365_citations.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.
'sources. Name a source or platform when the user asks about provenance or when needed to '
'distinguish conflicting evidence. Preserve uncertainty, missing evidence and partial-coverage '

Check warning on line 68 in application/single_app/functions_m365_citations.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.
'disclosures.'
)

M365_CITATION_INSTRUCTIONS = (
f"{M365_ANSWER_STYLE_INSTRUCTIONS} "

Check warning on line 73 in application/single_app/functions_m365_citations.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.
'Cite each email, event or file you mention by copying its "citation" value verbatim right '
'after the line or sentence about it. Do not invent, shorten, reformat or renumber citation '
'values, and do not cite an item you did not use.'
Expand Down Expand Up @@ -972,7 +982,8 @@
return ""
return (
"Microsoft 365 sources (copy each citation value verbatim right after the claim or list line it "
"supports; never invent or alter one):\n" + "\n".join(lines)
"supports; never invent or alter one):\n"
f"{M365_ANSWER_STYLE_INSTRUCTIONS}\n\n" + "\n".join(lines)

Check warning on line 986 in application/single_app/functions_m365_citations.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.
)


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
strict_source_authority_enabled,
)
from content_screening.contracts import SCREENING_FIELD, ScreeningError, SourceAuthorityUnverifiedError
from functions_m365_citations import M365_ANSWER_STYLE_INSTRUCTIONS

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

def log_event(*args, **kwargs):
"""Lazily resolve telemetry logging to avoid module-level import cycles."""
Expand Down Expand Up @@ -2108,8 +2109,9 @@
"action to obtain it and reason over the handoff and the action results together before answering. "
"Never derive numeric conclusions from an indexed preview of a tabular source whose evidence status "
"is not completed; obtain those values from a computed tabular result instead. "
f"{M365_ANSWER_STYLE_INSTRUCTIONS} "
f"{partial_coverage_instruction}\n\n{serialized_payload}"
),

Check warning on line 2114 in application/single_app/functions_mixed_source_orchestration.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.
"mixed_source_coverage": coverage,
"evidence_envelopes": list(payload["evidence_envelopes"]),
}
7 changes: 5 additions & 2 deletions application/single_app/functions_orchestration_composition.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# functions_orchestration_composition.py
"""Explicit one-call content preparation from named authorized result readers.

Version: 0.261.293
Version: 0.261.305
Refusals for a missing signed-in session reported as their own failure in: 0.261.209
Charts drawn upstream reach the model as their placement tokens in: 0.261.293
No retrieval, file-format inference, upload, publication, or implicit sibling inputs.
Expand All @@ -18,6 +18,7 @@

When an input carries citation values, such as the Microsoft 365 sources an action step lists,
the step is told to copy them verbatim after the claims they support (0.261.303).
Microsoft 365 answers avoid routine provenance introductions without losing disclosures (0.261.305).
"""

import json
Expand All @@ -29,6 +30,7 @@
from content_screening.contracts import ScreeningError
from functions_appinsights import log_event, workflow_log_context
from functions_chart_operations import normalize_inline_chart_markdown
from functions_m365_citations import M365_ANSWER_STYLE_INSTRUCTIONS

Check warning on line 33 in application/single_app/functions_orchestration_composition.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.
from functions_mixed_source_orchestration import MixedSourceCancellationError
from functions_orchestration_context import conversation_reference_messages
from functions_orchestration_deliverables import (
Expand Down Expand Up @@ -112,7 +114,8 @@
'copy its citation value verbatim right after the sentence or list line it supports. Never '
'invent, shorten, renumber or alter a citation value, and never cite an item you did not use. '
"When you list Microsoft 365 emails, events or files, follow the layout in that input's "
'presentation field: one numbered line per item, each ending with its citation value.'
'presentation field: one numbered line per item, each ending with its citation value. '

Check warning on line 117 in application/single_app/functions_orchestration_composition.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.
f'{M365_ANSWER_STYLE_INSTRUCTIONS}'
)
# A citation value as it appears inside the serialized inputs.
_CITATION_VALUE_RE = re.compile(r'\(Source:.{1,400}?\)\s*\[#')
Expand Down
20 changes: 14 additions & 6 deletions application/single_app/semantic_kernel_plugins/msgraph_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ def metadata(self) -> Dict[str, Any]:
{
"name": "select_fields",
"type": "str",
"description": "Optional comma-separated Graph fields to include.",
"description": "Optional comma-separated Graph fields to include. Source-card metadata and webLink are always requested.",
"required": False,
},
{
Expand Down Expand Up @@ -360,7 +360,7 @@ def metadata(self) -> Dict[str, Any]:
{
"name": "select_fields",
"type": "str",
"description": "Optional comma-separated Graph fields to include.",
"description": "Optional comma-separated Graph fields to include. Source-card metadata and webLink are always requested.",
"required": False,
},
{
Expand Down Expand Up @@ -1530,7 +1530,9 @@ def get_my_events(
str,
"End of the time range as an ISO 8601 date or date and time. Requires start_datetime; a date alone includes that whole UTC day.",
] = "",
select_fields: Annotated[str, "Optional comma-separated Graph event fields to include."] = "",
select_fields: Annotated[
str, "Optional comma-separated Graph event fields. Source-card metadata and webLink are always requested.",
] = "",
query: Annotated[
str,
"Optional plain words that must all appear in the subject, location, organizer, attendees, categories, or description.",
Expand Down Expand Up @@ -1576,10 +1578,14 @@ def get_my_events(
return self._invalid_parameter_error(operation_name, "start_datetime must be earlier than end_datetime.")

normalized_top = self._normalize_top(top)
citation_select, _ = self._merge_select_fields(
select_fields or self.DEFAULT_EVENT_SELECT, self.DEFAULT_EVENT_SELECT.split(","),
)
scan_fields = ["start"]
if terms:
scan_fields += ["subject", "location", "organizer", "attendees", "categories", "bodyPreview"]
select, added_fields = self._merge_select_fields(select_fields or self.DEFAULT_EVENT_SELECT, scan_fields)
# Only query-only fields may be stripped before the source card is captured.
select, added_fields = self._merge_select_fields(citation_select, scan_fields)
params, headers = self._build_odata_params(
top=normalized_top,
select_fields=select,
Expand Down Expand Up @@ -1875,7 +1881,9 @@ def get_my_messages(
"Folder to read: inbox, sentitems, drafts, deleteditems, archive, junkemail, a folder id, or all for every folder.",
] = "inbox",
unread_only: Annotated[bool, "If true, only unread messages are returned."] = False,
select_fields: Annotated[str, "Optional comma-separated Graph message fields to include."] = "",
select_fields: Annotated[
str, "Optional comma-separated Graph message fields. Source-card metadata and webLink are always requested.",
] = "",
search: Annotated[
str,
"Optional plain words to find in the sender, subject, or body. Every word must match; operators and field prefixes are ignored.",
Expand Down Expand Up @@ -1914,7 +1922,7 @@ def get_my_messages(
path = f"/v1.0/me/mailFolders/{quote(normalized_folder, safe='')}/messages"

default_select = self.DEFAULT_MESSAGE_SELECT + (",bodyPreview" if terms else "")
required_fields = ["receivedDateTime"] + (["isRead"] if normalized_unread else [])
required_fields = self.DEFAULT_MESSAGE_SELECT.split(",")
select, _ = self._merge_select_fields(select_fields or default_select, required_fields)
params, headers = self._build_odata_params(top=normalized_top, select_fields=select)
scan: Dict[str, Any] = {"last": None}
Expand Down
4 changes: 2 additions & 2 deletions application/v2_ui/src/components/chat/ConversationDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -200,8 +200,8 @@ function M365ItemRow({ item }: { item: UsedM365Item }) {
</div>
<M365OpenLink
record={item}
label="Open online"
className="inline-flex shrink-0 items-center gap-1 rounded-lg px-2 py-1 text-xs font-medium text-accent transition-colors hover:bg-surface-2"
label={item.kind === 'file' ? 'Open online' : undefined}
className="inline-flex shrink-0 items-center gap-1 rounded-lg px-2 py-1 text-xs font-medium text-accent-hover transition-colors hover:bg-surface-2 dark:text-accent"
/>
</div>
</li>
Expand Down
104 changes: 45 additions & 59 deletions application/v2_ui/src/components/chat/M365CitationChip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import { clsx } from 'clsx';
import { CalendarDays, ExternalLink, FileText, Mail, TriangleAlert, X } from 'lucide-react';
import { GlassPanel } from '../ui/primitives';
import { Modal } from '../ui/Modal';
import {
m365ChipLabel,
m365DetailRows,
Expand Down Expand Up @@ -81,38 +81,23 @@ function M365SourceCard({

useEffect(() => {
closeRef.current?.focus();
const onKeyDown = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
onClose();
}
};
document.addEventListener('keydown', onKeyDown);
return () => document.removeEventListener('keydown', onKeyDown);
}, [onClose]);
}, []);

const rows = record ? m365DetailRows(record) : [];
const title = record ? record.title : fallbackTitle || 'Microsoft 365 source';

return (
<div
className="fixed inset-0 z-50 flex items-center justify-center p-4"
role="dialog"
aria-modal="true"
aria-label="Microsoft 365 source"
>
<div className="absolute inset-0 bg-black/40" aria-hidden="true" onClick={onClose} />

<GlassPanel
elevation="modal"
edge
className="relative flex max-h-[70vh] w-full max-w-lg flex-col"
>
<Modal
title="Microsoft 365 source"
onClose={onClose}
bodyClassName="overflow-y-auto px-5 py-4"
banner={(
<div className="flex shrink-0 items-start gap-3 border-b border-edge px-5 py-3.5">
<M365KindIcon kind={record?.kind} size={17} className="mt-0.5 shrink-0 text-text-3" />
<div className="min-w-0 flex-1">
<h2 className="break-words text-sm font-semibold text-text-1">{title}</h2>
{record?.location_label && (
<p className="text-xs text-text-3">{record.location_label}</p>
<p className="text-xs text-text-2">{record.location_label}</p>
)}
</div>
<button
Expand All @@ -125,58 +110,59 @@ function M365SourceCard({
<X size={17} />
</button>
</div>

<div className="min-h-0 flex-1 overflow-y-auto px-5 py-4">
{record ? (
rows.length > 0 ? (
<dl className="space-y-2">
{rows.map((row) => (
<div key={row.label} className="flex gap-3 text-sm">
<dt className="w-24 shrink-0 text-text-3">{row.label}</dt>
<dd className="min-w-0 flex-1 break-words text-text-1">{row.value}</dd>
</div>
))}
</dl>
) : (
<p className="text-sm text-text-3">No further details were recorded for this item.</p>
)
) : (
<p className="flex items-start gap-2 text-sm text-text-2">
<TriangleAlert size={15} className="mt-0.5 shrink-0 text-warn" aria-hidden="true" />
This Microsoft 365 source is no longer available in this conversation.
</p>
)}
</div>

{record && safeM365Url(record.web_url) && (
<div className="flex shrink-0 justify-end border-t border-edge px-5 py-3">
<M365OpenLink
record={record}
className="inline-flex h-8 items-center gap-1.5 rounded-xl bg-accent px-3 text-sm font-medium text-on-accent shadow-sm transition-colors hover:bg-accent-hover"
/>
</div>
)}
</GlassPanel>
</div>
)}
footer={record ? (
safeM365Url(record.web_url) ? (
<M365OpenLink
record={record}
className={clsx(
'inline-flex h-8 items-center gap-1.5 rounded-xl px-3 text-sm font-medium text-on-accent shadow-sm transition-colors',
'bg-accent-hover hover:underline dark:bg-accent dark:hover:bg-accent-hover',
)}
/>
) : (
<p className="text-sm text-text-2">
No online link is available for this source. Recall the item again to refresh its details.
</p>
)
) : undefined}
>
{record ? (
rows.length > 0 ? (
<dl className="space-y-2">
{rows.map((row) => (
<div key={row.label} className="flex gap-3 text-sm">
<dt className="w-24 shrink-0 text-text-2">{row.label}</dt>
<dd className="min-w-0 flex-1 break-words text-text-1">{row.value}</dd>
</div>
))}
</dl>
) : (
<p className="text-sm text-text-2">No further details were recorded for this item.</p>
)
) : (
<p className="flex items-start gap-2 text-sm text-text-2">
<TriangleAlert size={15} className="mt-0.5 shrink-0 text-warn" aria-hidden="true" />
This Microsoft 365 source is no longer available in this conversation.
</p>
)}
</Modal>
);
}

function M365Chip({ citation, fallbackLabel }: { citation: ParsedCitation; fallbackLabel: string }) {
const record = useM365Citation(citation.citationId);
const [open, setOpen] = useState(false);
const chipRef = useRef<HTMLButtonElement>(null);
const label = record ? m365ChipLabel(record) : fallbackLabel || 'Microsoft 365 source';
const where = record?.location_label;

const close = useCallback(() => {
setOpen(false);
chipRef.current?.focus();
}, []);

return (
<>
<button
ref={chipRef}
type="button"
onClick={() => setOpen(true)}
title={where ? `${label} \u2014 ${where}` : label}
Expand Down
4 changes: 2 additions & 2 deletions application/v2_ui/src/components/chat/MessageInspector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -275,8 +275,8 @@ function SourcesSection({ sources }: { sources: MessageSources }) {
</span>
<M365OpenLink
record={record}
label="Open online"
className="inline-flex shrink-0 items-center gap-1 text-accent hover:underline"
label={record.kind === 'file' ? 'Open online' : undefined}
className="inline-flex shrink-0 items-center gap-1 text-accent-hover hover:underline dark:text-accent"
/>
</li>
);
Expand Down
Loading
Loading