Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
4e4f105
Add custom subdomain support for OpenAI and Speech Service in Terraform
Jan 2, 2026
be56540
Merge branch 'Development' of https://github.com/vivche/simplechat-de…
Jan 7, 2026
e13ba0c
Merge remote-tracking branch 'upstream/Development' into Development
Jan 23, 2026
087fb3d
feat: Add ServiceNow integration documentation and bug fixes
Jan 23, 2026
502355f
Removed the readme files for bug fix details
Jan 24, 2026
33bee68
Updated servicenow integration readme
Jan 24, 2026
cd8c520
chore: Revert custom logo changes to upstream version
Jan 24, 2026
fb8181b
chore: Revert terraform main.tf to upstream version
Jan 24, 2026
660d76c
Removed the two openai sample spec downloaed from servicennow site
Jan 24, 2026
de866eb
Update docs/how-to/agents/ServiceNow/servicenow_agent_instructions.txt
vivche Jan 24, 2026
20f994a
Update docs/how-to/agents/ServiceNow/open_api_specs/sample_servicenow…
vivche Jan 24, 2026
351f143
Update docs/how-to/azure_speech_managed_identity_manul_setup.md
vivche Jan 24, 2026
5fe5b14
Update application/single_app/semantic_kernel_plugins/openapi_plugin_…
vivche Jan 24, 2026
9626219
Checked in the bug fix detail readme to docs/explanation/fixes/v0.236…
Jan 24, 2026
6364827
Merge branch 'servicenow-integration' of https://github.com/vivche/si…
Jan 24, 2026
dce54a1
Added version number to the feature readme files
Jan 24, 2026
8353d77
Added version number to document, and removed redudant import statement
Jan 24, 2026
5aa7007
refactor: use _ for intentionally unused variable in AI Search test
Jan 24, 2026
548d8d8
Removed azure_speech_managed_indeity_manual readme file since it is u…
Jan 24, 2026
62b0b5b
update version numbers to 0.236.012 in bug fix documentation
Jan 24, 2026
b0be501
Update application/single_app/semantic_kernel_loader.py
vivche Jan 24, 2026
e264a13
Update docs/how-to/agents/ServiceNow/open_api_specs/sample_now_knowle…
vivche Jan 24, 2026
b04bd67
Update docs/explanation/fixes/v0.236.012/AZURE_AI_SEARCH_TEST_CONNECT…
vivche Jan 24, 2026
84c01e9
Update docs/how-to/agents/ServiceNow/open_api_specs/sample_now_knowle…
vivche Jan 24, 2026
c8e383e
Update docs/how-to/agents/ServiceNow/open_api_specs/sample_servicenow…
vivche Jan 24, 2026
39dc7a4
Update docs/explanation/fixes/v0.236.012/GROUP_AGENT_LOADING_FIX.md
vivche Jan 24, 2026
2e8c737
Remvoed debug statements that might include senstive info
Jan 24, 2026
d0581d5
Merge branch 'servicenow-integration' of https://github.com/vivche/si…
Jan 24, 2026
0c23a78
Rollback Azure AI Search test connection fix for separate PR
Jan 24, 2026
7f8248a
Update application/single_app/semantic_kernel_plugins/openapi_plugin_…
vivche Jan 24, 2026
a0fbffd
Update docs/explanation/fixes/v0.236.012/GROUP_AGENT_LOADING_FIX.md
vivche Jan 24, 2026
4bac07a
Update docs/explanation/fixes/v0.236.012/GROUP_ACTION_OAUTH_SCHEMA_ME…
vivche Jan 24, 2026
1c31db4
Update docs/how-to/agents/ServiceNow/SERVICENOW_OAUTH_SETUP.md
vivche Jan 24, 2026
31e8341
Added ServiceNow support for create and publish article. Including r…
Jan 27, 2026
c70db6b
Replace actual servicenow instance name with generic name in the read…
Jan 27, 2026
fa72a65
Merge remote-tracking branch 'upstream/Development' into servicenow-i…
Jan 27, 2026
0ed07b1
Changed version number in ServiceNow readme files to 0.237.005 since …
Jan 27, 2026
61d8a8b
Enhance ServiceNow agent for managing new KB article creation
Jan 28, 2026
715bb6b
Added readme and open ai specs and agent instructions to support Serv…
Jan 28, 2026
b5804ad
Remove any references to actual ServiceNow instances
Jan 28, 2026
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 @@ -88,7 +88,7 @@
EXECUTOR_TYPE = 'thread'
EXECUTOR_MAX_WORKERS = 30
SESSION_TYPE = 'filesystem'
VERSION = "0.237.004"
VERSION = "0.237.005"


SECRET_KEY = os.getenv('SECRET_KEY', 'dev-secret-key-change-in-production')
Expand Down
12 changes: 12 additions & 0 deletions application/single_app/route_backend_plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,12 @@ def create_group_action_route():
for key in ('group_id', 'last_updated', 'user_id', 'is_global', 'is_group', 'scope'):
payload.pop(key, None)

# Merge with schema to ensure all required fields are present (same as global actions)
schema_dir = os.path.join(current_app.root_path, 'static', 'json', 'schemas')
merged = get_merged_plugin_settings(payload.get('type'), payload, schema_dir)
payload['metadata'] = merged.get('metadata', payload.get('metadata', {}))
payload['additionalFields'] = merged.get('additionalFields', payload.get('additionalFields', {}))

try:
saved = save_group_action(active_group, payload)
except Exception as exc:
Expand Down Expand Up @@ -511,6 +517,12 @@ def update_group_action_route(action_id):
except ValueError as exc:
return jsonify({'error': str(exc)}), 400

# Merge with schema to ensure all required fields are present (same as global actions)
schema_dir = os.path.join(current_app.root_path, 'static', 'json', 'schemas')
schema_merged = get_merged_plugin_settings(merged.get('type'), merged, schema_dir)
merged['metadata'] = schema_merged.get('metadata', merged.get('metadata', {}))
merged['additionalFields'] = schema_merged.get('additionalFields', merged.get('additionalFields', {}))

try:
saved = save_group_action(active_group, merged)
except Exception as exc:
Expand Down
108 changes: 51 additions & 57 deletions application/single_app/semantic_kernel_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
from functions_global_agents import get_global_agents
from functions_group_agents import get_group_agent, get_group_agents
from functions_group_actions import get_group_actions
from functions_group import require_active_group
from functions_group import get_user_groups
from functions_personal_actions import get_personal_actions, ensure_migration_complete as ensure_actions_migration_complete
from functions_personal_agents import get_personal_agents, ensure_migration_complete as ensure_agents_migration_complete
from semantic_kernel_plugins.plugin_loader import discover_plugins
Expand Down Expand Up @@ -1180,65 +1180,41 @@ def load_user_semantic_kernel(kernel: Kernel, settings, user_id: str, redis_clie
ensure_agents_migration_complete(user_id)
agents_cfg = get_personal_agents(user_id)

print(f"[SK Loader] User settings found {len(agents_cfg)} agents for user '{user_id}'")
print(f"[SK Loader] User settings found {len(agents_cfg)} personal agents for user '{user_id}'")

# Always mark user agents as is_global: False
# Always mark personal agents as is_global: False, is_group: False
for agent in agents_cfg:
agent['is_global'] = False

# Append selected group agent (if any) to the candidate list so downstream selection logic can resolve it
selected_agent_data = selected_agent if isinstance(selected_agent, dict) else {}
selected_agent_is_group = selected_agent_data.get('is_group', False)
if selected_agent_is_group:
resolved_group_id = selected_agent_data.get('group_id')
try:
active_group_id = require_active_group(user_id)
if not resolved_group_id:
resolved_group_id = active_group_id
elif resolved_group_id != active_group_id:
debug_print(
f"[SK Loader] Selected group agent references group {resolved_group_id}, active group is {active_group_id}."
)
except ValueError as err:
debug_print(f"[SK Loader] No active group available while loading group agent: {err}")
if not resolved_group_id:
log_event(
"[SK Loader] Group agent selected but no active group in settings.",
level=logging.WARNING
)

if resolved_group_id:
agent_identifier = selected_agent_data.get('id') or selected_agent_data.get('name')
group_agent_cfg = None
if agent_identifier:
group_agent_cfg = get_group_agent(resolved_group_id, agent_identifier)
if not group_agent_cfg:
# Fallback: search by name across group agents if ID lookup failed
for candidate in get_group_agents(resolved_group_id):
if candidate.get('name') == selected_agent_data.get('name'):
group_agent_cfg = candidate
break

if group_agent_cfg:
group_agent_cfg['is_global'] = False
group_agent_cfg['is_group'] = True
group_agent_cfg.setdefault('group_id', resolved_group_id)
group_agent_cfg['group_name'] = selected_agent_data.get('group_name')
agents_cfg.append(group_agent_cfg)
log_event(
f"[SK Loader] Added group agent '{group_agent_cfg.get('name')}' from group {resolved_group_id} to candidate list.",
level=logging.INFO
)
else:
log_event(
f"[SK Loader] Selected group agent '{selected_agent_data.get('name')}' not found for group {resolved_group_id}.",
level=logging.WARNING
)
else:
log_event(
"[SK Loader] Unable to resolve group ID for selected group agent; skipping group agent load.",
level=logging.WARNING
)
agent['is_group'] = False

# Load group agents from all groups the user is a member of
user_groups = [] # Initialize to empty list
try:
user_groups = get_user_groups(user_id)
print(f"[SK Loader] User '{user_id}' is a member of {len(user_groups)} groups")

group_agent_count = 0
for group in user_groups:
group_id = group.get('id')
group_name = group.get('name', 'Unknown')
if group_id:
group_agents = get_group_agents(group_id)
for group_agent in group_agents:
group_agent['is_global'] = False
group_agent['is_group'] = True
group_agent['group_id'] = group_id
group_agent['group_name'] = group_name
agents_cfg.append(group_agent)
group_agent_count += 1
print(f"[SK Loader] Loaded {len(group_agents)} agents from group '{group_name}' (id: {group_id})")

if group_agent_count > 0:
log_event(f"[SK Loader] Loaded {group_agent_count} group agents from {len(user_groups)} groups for user '{user_id}'", level=logging.INFO)
except Exception as e:
log_event(f"[SK Loader] Error loading group agents for user '{user_id}': {e}", {"error": str(e)}, level=logging.ERROR, exceptionTraceback=True)
user_groups = [] # Reset to empty on error

print(f"[SK Loader] Total agents loaded: {len(agents_cfg)} (personal + group) for user '{user_id}'")

# PATCH: Merge global agents if enabled
merge_global = settings.get('merge_global_semantic_kernel_with_workspace', False)
Expand Down Expand Up @@ -1278,9 +1254,27 @@ def load_user_semantic_kernel(kernel: Kernel, settings, user_id: str, redis_clie
"agents": agents_cfg
},
level=logging.INFO)

# Ensure migration is complete (will migrate any remaining legacy data)
ensure_actions_migration_complete(user_id)
plugin_manifests = get_personal_actions(user_id, return_type=SecretReturnType.NAME)

# Load group actions from all groups the user is a member of
try:
group_action_count = 0
for group in user_groups:
group_id = group.get('id')
group_name = group.get('name', 'Unknown')
if group_id:
group_actions = get_group_actions(group_id, return_type=SecretReturnType.NAME)
plugin_manifests.extend(group_actions)
group_action_count += len(group_actions)
print(f"[SK Loader] Loaded {len(group_actions)} actions from group '{group_name}' (id: {group_id})")

if group_action_count > 0:
log_event(f"[SK Loader] Loaded {group_action_count} group actions from {len(user_groups)} groups for user '{user_id}'", level=logging.INFO)
except Exception as e:
log_event(f"[SK Loader] Error loading group actions for user '{user_id}': {e}", {"error": str(e)}, level=logging.ERROR, exceptionTraceback=True)

# PATCH: Merge global plugins if enabled
if merge_global:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,15 +125,55 @@ def _get_local_file_path(cls, config: Dict[str, Any]) -> str:
@classmethod
def _extract_auth_config(cls, config: Dict[str, Any]) -> Dict[str, Any]:
"""Extract authentication configuration from plugin config."""
from functions_debug import debug_print

auth_config = config.get('auth', {})
if not auth_config:
return {}

auth_type = auth_config.get('type', 'none')
debug_print(f"[Factory] auth_type: {auth_type}")

if auth_type == 'none':
return {}

# Return the auth config as-is since the OpenApiPlugin already handles
# the different auth types
# Check if this is basic auth stored in the 'key' field format
# Simple Chat stores basic auth as: auth.type='key', auth.key='username:password', additionalFields.auth_method='basic'
additional_fields = config.get('additionalFields', {})
auth_method = additional_fields.get('auth_method', '')
debug_print(f"[Factory] additionalFields.auth_method: {auth_method}")

if auth_type == 'key' and auth_method == 'basic':
# Extract username and password from the combined key
key = auth_config.get('key', '')
debug_print(f"[Factory] Applying basic auth transformation")
if ':' in key:
username, password = key.split(':', 1)
return {
'type': 'basic',
'username': username,
'password': password
}
else:
# Malformed basic auth key
return {}

# For bearer tokens stored as 'key' type
if auth_type == 'key' and auth_method == 'bearer':
token = auth_config.get('key', '')
debug_print(f"[Factory] Applying bearer auth transformation")
return {
'type': 'bearer',
'token': token
}

# For OAuth2 stored as 'key' type
if auth_type == 'key' and auth_method == 'oauth2':
debug_print(f"[Factory] Applying OAuth2 auth transformation")
return {
'type': 'bearer', # OAuth2 tokens are typically bearer tokens
'token': auth_config.get('key', '')
}

# Return the auth config as-is for other auth types
return auth_config
Loading