This is page 91 of 145. Use http://codebase.md/microsoft/semanticworkbench?lines=true&page={x} to view the full context.
# Directory Structure
```
├── .devcontainer
│ ├── .vscode
│ │ └── settings.json
│ ├── devcontainer.json
│ ├── OPTIMIZING_FOR_CODESPACES.md
│ ├── POST_SETUP_README.md
│ └── README.md
├── .dockerignore
├── .gitattributes
├── .github
│ ├── policheck.yml
│ └── workflows
│ ├── assistants-codespace-assistant.yml
│ ├── assistants-document-assistant.yml
│ ├── assistants-explorer-assistant.yml
│ ├── assistants-guided-conversation-assistant.yml
│ ├── assistants-knowledge-transfer-assistant.yml
│ ├── assistants-navigator-assistant.yml
│ ├── assistants-project-assistant.yml
│ ├── assistants-prospector-assistant.yml
│ ├── assistants-skill-assistant.yml
│ ├── libraries.yml
│ ├── mcp-server-giphy.yml
│ ├── mcp-server-memory-filesystem-edit.yml
│ ├── mcp-server-memory-user-bio.yml
│ ├── mcp-server-memory-whiteboard.yml
│ ├── mcp-server-open-deep-research-clone.yml
│ ├── mcp-server-web-research.yml
│ ├── workbench-app.yml
│ └── workbench-service.yml
├── .gitignore
├── .multi-root-tools
│ ├── Makefile
│ └── README.md
├── .vscode
│ ├── extensions.json
│ ├── launch.json
│ └── settings.json
├── ai_context
│ └── generated
│ ├── ASPIRE_ORCHESTRATOR.md
│ ├── ASSISTANT_CODESPACE.md
│ ├── ASSISTANT_DOCUMENT.md
│ ├── ASSISTANT_NAVIGATOR.md
│ ├── ASSISTANT_PROJECT.md
│ ├── ASSISTANT_PROSPECTOR.md
│ ├── ASSISTANTS_OTHER.md
│ ├── ASSISTANTS_OVERVIEW.md
│ ├── CONFIGURATION.md
│ ├── DOTNET_LIBRARIES.md
│ ├── EXAMPLES.md
│ ├── MCP_SERVERS.md
│ ├── PYTHON_LIBRARIES_AI_CLIENTS.md
│ ├── PYTHON_LIBRARIES_CORE.md
│ ├── PYTHON_LIBRARIES_EXTENSIONS.md
│ ├── PYTHON_LIBRARIES_SKILLS.md
│ ├── PYTHON_LIBRARIES_SPECIALIZED.md
│ ├── TOOLS.md
│ ├── WORKBENCH_FRONTEND.md
│ └── WORKBENCH_SERVICE.md
├── aspire-orchestrator
│ ├── .editorconfig
│ ├── Aspire.AppHost
│ │ ├── .gitignore
│ │ ├── appsettings.json
│ │ ├── Aspire.AppHost.csproj
│ │ ├── Program.cs
│ │ └── Properties
│ │ └── launchSettings.json
│ ├── Aspire.Extensions
│ │ ├── Aspire.Extensions.csproj
│ │ ├── Dashboard.cs
│ │ ├── DockerFileExtensions.cs
│ │ ├── PathNormalizer.cs
│ │ ├── UvAppHostingExtensions.cs
│ │ ├── UvAppResource.cs
│ │ ├── VirtualEnvironment.cs
│ │ └── WorkbenchServiceHostingExtensions.cs
│ ├── Aspire.ServiceDefaults
│ │ ├── Aspire.ServiceDefaults.csproj
│ │ └── Extensions.cs
│ ├── README.md
│ ├── run.sh
│ ├── SemanticWorkbench.Aspire.sln
│ └── SemanticWorkbench.Aspire.sln.DotSettings
├── assistants
│ ├── codespace-assistant
│ │ ├── .claude
│ │ │ └── settings.local.json
│ │ ├── .env.example
│ │ ├── .vscode
│ │ │ ├── extensions.json
│ │ │ ├── launch.json
│ │ │ └── settings.json
│ │ ├── assistant
│ │ │ ├── __init__.py
│ │ │ ├── assets
│ │ │ │ ├── icon_context_transfer.svg
│ │ │ │ └── icon.svg
│ │ │ ├── chat.py
│ │ │ ├── config.py
│ │ │ ├── helpers.py
│ │ │ ├── response
│ │ │ │ ├── __init__.py
│ │ │ │ ├── completion_handler.py
│ │ │ │ ├── models.py
│ │ │ │ ├── request_builder.py
│ │ │ │ ├── response.py
│ │ │ │ ├── step_handler.py
│ │ │ │ └── utils
│ │ │ │ ├── __init__.py
│ │ │ │ ├── abbreviations.py
│ │ │ │ ├── formatting_utils.py
│ │ │ │ ├── message_utils.py
│ │ │ │ └── openai_utils.py
│ │ │ ├── text_includes
│ │ │ │ ├── card_content_context_transfer.md
│ │ │ │ ├── card_content.md
│ │ │ │ ├── codespace_assistant_info.md
│ │ │ │ ├── context_transfer_assistant_info.md
│ │ │ │ ├── guardrails_prompt.txt
│ │ │ │ ├── guidance_prompt_context_transfer.txt
│ │ │ │ ├── guidance_prompt.txt
│ │ │ │ ├── instruction_prompt_context_transfer.txt
│ │ │ │ └── instruction_prompt.txt
│ │ │ └── whiteboard
│ │ │ ├── __init__.py
│ │ │ ├── _inspector.py
│ │ │ └── _whiteboard.py
│ │ ├── assistant.code-workspace
│ │ ├── Makefile
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ └── uv.lock
│ ├── document-assistant
│ │ ├── .env.example
│ │ ├── .vscode
│ │ │ ├── launch.json
│ │ │ └── settings.json
│ │ ├── assistant
│ │ │ ├── __init__.py
│ │ │ ├── assets
│ │ │ │ └── icon.svg
│ │ │ ├── chat.py
│ │ │ ├── config.py
│ │ │ ├── context_management
│ │ │ │ ├── __init__.py
│ │ │ │ └── inspector.py
│ │ │ ├── filesystem
│ │ │ │ ├── __init__.py
│ │ │ │ ├── _convert.py
│ │ │ │ ├── _file_sources.py
│ │ │ │ ├── _filesystem.py
│ │ │ │ ├── _inspector.py
│ │ │ │ ├── _model.py
│ │ │ │ ├── _prompts.py
│ │ │ │ └── _tasks.py
│ │ │ ├── guidance
│ │ │ │ ├── __init__.py
│ │ │ │ ├── dynamic_ui_inspector.py
│ │ │ │ ├── guidance_config.py
│ │ │ │ ├── guidance_prompts.py
│ │ │ │ └── README.md
│ │ │ ├── response
│ │ │ │ ├── __init__.py
│ │ │ │ ├── completion_handler.py
│ │ │ │ ├── models.py
│ │ │ │ ├── prompts.py
│ │ │ │ ├── responder.py
│ │ │ │ └── utils
│ │ │ │ ├── __init__.py
│ │ │ │ ├── formatting_utils.py
│ │ │ │ ├── message_utils.py
│ │ │ │ ├── openai_utils.py
│ │ │ │ ├── tokens_tiktoken.py
│ │ │ │ └── workbench_messages.py
│ │ │ ├── text_includes
│ │ │ │ └── document_assistant_info.md
│ │ │ ├── types.py
│ │ │ └── whiteboard
│ │ │ ├── __init__.py
│ │ │ ├── _inspector.py
│ │ │ └── _whiteboard.py
│ │ ├── assistant.code-workspace
│ │ ├── CLAUDE.md
│ │ ├── Makefile
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── tests
│ │ │ ├── __init__.py
│ │ │ ├── test_convert.py
│ │ │ └── test_data
│ │ │ ├── blank_image.png
│ │ │ ├── Formatting Test.docx
│ │ │ ├── sample_data.csv
│ │ │ ├── sample_data.xlsx
│ │ │ ├── sample_page.html
│ │ │ ├── sample_presentation.pptx
│ │ │ └── simple_pdf.pdf
│ │ └── uv.lock
│ ├── explorer-assistant
│ │ ├── .env.example
│ │ ├── .vscode
│ │ │ ├── launch.json
│ │ │ └── settings.json
│ │ ├── assistant
│ │ │ ├── __init__.py
│ │ │ ├── chat.py
│ │ │ ├── config.py
│ │ │ ├── helpers.py
│ │ │ ├── response
│ │ │ │ ├── __init__.py
│ │ │ │ ├── model.py
│ │ │ │ ├── response_anthropic.py
│ │ │ │ ├── response_openai.py
│ │ │ │ └── response.py
│ │ │ └── text_includes
│ │ │ └── guardrails_prompt.txt
│ │ ├── assistant.code-workspace
│ │ ├── Makefile
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ └── uv.lock
│ ├── guided-conversation-assistant
│ │ ├── .env.example
│ │ ├── .vscode
│ │ │ ├── launch.json
│ │ │ └── settings.json
│ │ ├── assistant
│ │ │ ├── __init__.py
│ │ │ ├── agents
│ │ │ │ ├── guided_conversation
│ │ │ │ │ ├── config.py
│ │ │ │ │ ├── definition.py
│ │ │ │ │ └── definitions
│ │ │ │ │ ├── er_triage.py
│ │ │ │ │ ├── interview.py
│ │ │ │ │ ├── patient_intake.py
│ │ │ │ │ └── poem_feedback.py
│ │ │ │ └── guided_conversation_agent.py
│ │ │ ├── chat.py
│ │ │ ├── config.py
│ │ │ └── text_includes
│ │ │ └── guardrails_prompt.txt
│ │ ├── assistant.code-workspace
│ │ ├── Makefile
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ └── uv.lock
│ ├── knowledge-transfer-assistant
│ │ ├── .claude
│ │ │ └── settings.local.json
│ │ ├── .env.example
│ │ ├── .vscode
│ │ │ ├── launch.json
│ │ │ └── settings.json
│ │ ├── assistant
│ │ │ ├── __init__.py
│ │ │ ├── agentic
│ │ │ │ ├── __init__.py
│ │ │ │ ├── analysis.py
│ │ │ │ ├── coordinator_support.py
│ │ │ │ └── team_welcome.py
│ │ │ ├── assets
│ │ │ │ ├── icon-knowledge-transfer.svg
│ │ │ │ └── icon.svg
│ │ │ ├── assistant.py
│ │ │ ├── common.py
│ │ │ ├── config.py
│ │ │ ├── conversation_clients.py
│ │ │ ├── conversation_share_link.py
│ │ │ ├── data.py
│ │ │ ├── domain
│ │ │ │ ├── __init__.py
│ │ │ │ ├── audience_manager.py
│ │ │ │ ├── information_request_manager.py
│ │ │ │ ├── knowledge_brief_manager.py
│ │ │ │ ├── knowledge_digest_manager.py
│ │ │ │ ├── learning_objectives_manager.py
│ │ │ │ └── share_manager.py
│ │ │ ├── files.py
│ │ │ ├── logging.py
│ │ │ ├── notifications.py
│ │ │ ├── respond.py
│ │ │ ├── storage_models.py
│ │ │ ├── storage.py
│ │ │ ├── string_utils.py
│ │ │ ├── text_includes
│ │ │ │ ├── assistant_info.md
│ │ │ │ ├── card_content.md
│ │ │ │ ├── coordinator_instructions.txt
│ │ │ │ ├── coordinator_role.txt
│ │ │ │ ├── knowledge_digest_instructions.txt
│ │ │ │ ├── knowledge_digest_prompt.txt
│ │ │ │ ├── share_information_request_detection.txt
│ │ │ │ ├── team_instructions.txt
│ │ │ │ ├── team_role.txt
│ │ │ │ └── welcome_message_generation.txt
│ │ │ ├── tools
│ │ │ │ ├── __init__.py
│ │ │ │ ├── base.py
│ │ │ │ ├── information_requests.py
│ │ │ │ ├── learning_objectives.py
│ │ │ │ ├── learning_outcomes.py
│ │ │ │ ├── progress_tracking.py
│ │ │ │ └── share_setup.py
│ │ │ ├── ui_tabs
│ │ │ │ ├── __init__.py
│ │ │ │ ├── brief.py
│ │ │ │ ├── common.py
│ │ │ │ ├── debug.py
│ │ │ │ ├── learning.py
│ │ │ │ └── sharing.py
│ │ │ └── utils.py
│ │ ├── CLAUDE.md
│ │ ├── docs
│ │ │ ├── design
│ │ │ │ ├── actions.md
│ │ │ │ └── inference.md
│ │ │ ├── DEV_GUIDE.md
│ │ │ ├── how-kta-works.md
│ │ │ ├── JTBD.md
│ │ │ ├── knowledge-transfer-goals.md
│ │ │ ├── learning_assistance.md
│ │ │ ├── notable_claude_conversations
│ │ │ │ ├── clarifying_quad_modal_design.md
│ │ │ │ ├── CLAUDE_PROMPTS.md
│ │ │ │ ├── transfer_state.md
│ │ │ │ └── trying_the_context_agent.md
│ │ │ └── opportunities-of-knowledge-transfer.md
│ │ ├── knowledge-transfer-assistant.code-workspace
│ │ ├── Makefile
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── tests
│ │ │ ├── __init__.py
│ │ │ ├── test_artifact_loading.py
│ │ │ ├── test_inspector.py
│ │ │ ├── test_share_manager.py
│ │ │ ├── test_share_storage.py
│ │ │ ├── test_share_tools.py
│ │ │ └── test_team_mode.py
│ │ └── uv.lock
│ ├── Makefile
│ ├── navigator-assistant
│ │ ├── .env.example
│ │ ├── .vscode
│ │ │ ├── launch.json
│ │ │ └── settings.json
│ │ ├── assistant
│ │ │ ├── __init__.py
│ │ │ ├── assets
│ │ │ │ ├── card_content.md
│ │ │ │ └── icon.svg
│ │ │ ├── chat.py
│ │ │ ├── config.py
│ │ │ ├── helpers.py
│ │ │ ├── response
│ │ │ │ ├── __init__.py
│ │ │ │ ├── completion_handler.py
│ │ │ │ ├── completion_requestor.py
│ │ │ │ ├── local_tool
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── add_assistant_to_conversation.py
│ │ │ │ │ ├── list_assistant_services.py
│ │ │ │ │ └── model.py
│ │ │ │ ├── models.py
│ │ │ │ ├── prompt.py
│ │ │ │ ├── request_builder.py
│ │ │ │ ├── response.py
│ │ │ │ ├── step_handler.py
│ │ │ │ └── utils
│ │ │ │ ├── __init__.py
│ │ │ │ ├── formatting_utils.py
│ │ │ │ ├── message_utils.py
│ │ │ │ ├── openai_utils.py
│ │ │ │ └── tools.py
│ │ │ ├── text_includes
│ │ │ │ ├── guardrails_prompt.md
│ │ │ │ ├── guidance_prompt.md
│ │ │ │ ├── instruction_prompt.md
│ │ │ │ ├── navigator_assistant_info.md
│ │ │ │ └── semantic_workbench_features.md
│ │ │ └── whiteboard
│ │ │ ├── __init__.py
│ │ │ ├── _inspector.py
│ │ │ └── _whiteboard.py
│ │ ├── Makefile
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ └── uv.lock
│ ├── project-assistant
│ │ ├── .cspell
│ │ │ └── custom-dictionary-workspace.txt
│ │ ├── .env.example
│ │ ├── .vscode
│ │ │ ├── launch.json
│ │ │ └── settings.json
│ │ ├── assistant
│ │ │ ├── __init__.py
│ │ │ ├── agentic
│ │ │ │ ├── __init__.py
│ │ │ │ ├── act.py
│ │ │ │ ├── coordinator_next_action.py
│ │ │ │ ├── create_invitation.py
│ │ │ │ ├── detect_audience_and_takeaways.py
│ │ │ │ ├── detect_coordinator_actions.py
│ │ │ │ ├── detect_information_request_needs.py
│ │ │ │ ├── detect_knowledge_package_gaps.py
│ │ │ │ ├── focus.py
│ │ │ │ ├── respond.py
│ │ │ │ ├── team_welcome.py
│ │ │ │ └── update_digest.py
│ │ │ ├── assets
│ │ │ │ ├── icon-knowledge-transfer.svg
│ │ │ │ └── icon.svg
│ │ │ ├── assistant.py
│ │ │ ├── common.py
│ │ │ ├── config.py
│ │ │ ├── conversation_clients.py
│ │ │ ├── data.py
│ │ │ ├── domain
│ │ │ │ ├── __init__.py
│ │ │ │ ├── audience_manager.py
│ │ │ │ ├── conversation_preferences_manager.py
│ │ │ │ ├── information_request_manager.py
│ │ │ │ ├── knowledge_brief_manager.py
│ │ │ │ ├── knowledge_digest_manager.py
│ │ │ │ ├── learning_objectives_manager.py
│ │ │ │ ├── share_manager.py
│ │ │ │ ├── tasks_manager.py
│ │ │ │ └── transfer_manager.py
│ │ │ ├── errors.py
│ │ │ ├── files.py
│ │ │ ├── logging.py
│ │ │ ├── notifications.py
│ │ │ ├── prompt_utils.py
│ │ │ ├── storage.py
│ │ │ ├── string_utils.py
│ │ │ ├── text_includes
│ │ │ │ ├── actor_instructions.md
│ │ │ │ ├── assistant_info.md
│ │ │ │ ├── card_content.md
│ │ │ │ ├── coordinator_instructions copy.md
│ │ │ │ ├── coordinator_instructions.md
│ │ │ │ ├── create_invitation.md
│ │ │ │ ├── detect_audience.md
│ │ │ │ ├── detect_coordinator_actions.md
│ │ │ │ ├── detect_information_request_needs.md
│ │ │ │ ├── detect_knowledge_package_gaps.md
│ │ │ │ ├── focus.md
│ │ │ │ ├── knowledge_digest_instructions.txt
│ │ │ │ ├── team_instructions.txt
│ │ │ │ ├── to_do.md
│ │ │ │ ├── update_knowledge_brief.md
│ │ │ │ ├── update_knowledge_digest.md
│ │ │ │ └── welcome_message_generation.txt
│ │ │ ├── tools
│ │ │ │ ├── __init__.py
│ │ │ │ ├── base.py
│ │ │ │ ├── conversation_preferences.py
│ │ │ │ ├── information_requests.py
│ │ │ │ ├── learning_objectives.py
│ │ │ │ ├── learning_outcomes.py
│ │ │ │ ├── progress_tracking.py
│ │ │ │ ├── share_setup.py
│ │ │ │ ├── system_reminders.py
│ │ │ │ ├── tasks.py
│ │ │ │ └── todo.py
│ │ │ ├── ui_tabs
│ │ │ │ ├── __init__.py
│ │ │ │ ├── brief.py
│ │ │ │ ├── common.py
│ │ │ │ ├── debug.py
│ │ │ │ ├── learning.py
│ │ │ │ └── sharing.py
│ │ │ └── utils.py
│ │ ├── CLAUDE.md
│ │ ├── docs
│ │ │ ├── design
│ │ │ │ ├── actions.md
│ │ │ │ ├── control_options.md
│ │ │ │ ├── design.md
│ │ │ │ ├── inference.md
│ │ │ │ └── PXL_20250814_190140267.jpg
│ │ │ ├── DEV_GUIDE.md
│ │ │ ├── how-kta-works.md
│ │ │ ├── JTBD.md
│ │ │ ├── knowledge-transfer-goals.md
│ │ │ ├── learning_assistance.md
│ │ │ ├── notable_claude_conversations
│ │ │ │ ├── clarifying_quad_modal_design.md
│ │ │ │ ├── CLAUDE_PROMPTS.md
│ │ │ │ ├── transfer_state.md
│ │ │ │ └── trying_the_context_agent.md
│ │ │ └── opportunities-of-knowledge-transfer.md
│ │ ├── knowledge-transfer-assistant.code-workspace
│ │ ├── Makefile
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── tests
│ │ │ ├── __init__.py
│ │ │ ├── test_artifact_loading.py
│ │ │ ├── test_inspector.py
│ │ │ ├── test_share_manager.py
│ │ │ ├── test_share_storage.py
│ │ │ └── test_team_mode.py
│ │ └── uv.lock
│ ├── prospector-assistant
│ │ ├── .env.example
│ │ ├── .vscode
│ │ │ ├── launch.json
│ │ │ └── settings.json
│ │ ├── assistant
│ │ │ ├── __init__.py
│ │ │ ├── agents
│ │ │ │ ├── artifact_agent.py
│ │ │ │ ├── document
│ │ │ │ │ ├── config.py
│ │ │ │ │ ├── gc_draft_content_feedback_config.py
│ │ │ │ │ ├── gc_draft_outline_feedback_config.py
│ │ │ │ │ ├── guided_conversation.py
│ │ │ │ │ └── state.py
│ │ │ │ └── document_agent.py
│ │ │ ├── artifact_creation_extension
│ │ │ │ ├── __init__.py
│ │ │ │ ├── _llm.py
│ │ │ │ ├── config.py
│ │ │ │ ├── document.py
│ │ │ │ ├── extension.py
│ │ │ │ ├── store.py
│ │ │ │ ├── test
│ │ │ │ │ ├── conftest.py
│ │ │ │ │ ├── evaluation.py
│ │ │ │ │ ├── test_completion_with_tools.py
│ │ │ │ │ └── test_extension.py
│ │ │ │ └── tools.py
│ │ │ ├── chat.py
│ │ │ ├── config.py
│ │ │ ├── form_fill_extension
│ │ │ │ ├── __init__.py
│ │ │ │ ├── config.py
│ │ │ │ ├── extension.py
│ │ │ │ ├── inspector.py
│ │ │ │ ├── state.py
│ │ │ │ └── steps
│ │ │ │ ├── __init__.py
│ │ │ │ ├── _guided_conversation.py
│ │ │ │ ├── _llm.py
│ │ │ │ ├── acquire_form_step.py
│ │ │ │ ├── extract_form_fields_step.py
│ │ │ │ ├── fill_form_step.py
│ │ │ │ └── types.py
│ │ │ ├── helpers.py
│ │ │ ├── legacy.py
│ │ │ └── text_includes
│ │ │ ├── artifact_agent_enabled.md
│ │ │ ├── guardrails_prompt.txt
│ │ │ ├── guided_conversation_agent_enabled.md
│ │ │ └── skills_agent_enabled.md
│ │ ├── assistant.code-workspace
│ │ ├── gc_learnings
│ │ │ ├── gc_learnings.md
│ │ │ └── images
│ │ │ ├── gc_conversation_plan_fcn.png
│ │ │ ├── gc_conversation_plan_template.png
│ │ │ ├── gc_execute_plan_callstack.png
│ │ │ ├── gc_functions.png
│ │ │ ├── gc_generate_plan_callstack.png
│ │ │ ├── gc_get_resource_instructions.png
│ │ │ ├── gc_get_termination_instructions.png
│ │ │ ├── gc_kernel_arguments.png
│ │ │ ├── gc_plan_calls.png
│ │ │ ├── gc_termination_instructions.png
│ │ │ ├── sk_get_chat_message_contents.png
│ │ │ ├── sk_inner_get_chat_message_contents.png
│ │ │ ├── sk_send_request_prep.png
│ │ │ └── sk_send_request.png
│ │ ├── Makefile
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ └── uv.lock
│ └── skill-assistant
│ ├── .env.example
│ ├── .vscode
│ │ ├── launch.json
│ │ └── settings.json
│ ├── assistant
│ │ ├── __init__.py
│ │ ├── config.py
│ │ ├── logging.py
│ │ ├── skill_assistant.py
│ │ ├── skill_engine_registry.py
│ │ ├── skill_event_mapper.py
│ │ ├── text_includes
│ │ │ └── guardrails_prompt.txt
│ │ └── workbench_helpers.py
│ ├── assistant.code-workspace
│ ├── Makefile
│ ├── pyproject.toml
│ ├── README.md
│ ├── tests
│ │ └── test_setup.py
│ └── uv.lock
├── CLAUDE.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── docs
│ ├── .vscode
│ │ └── settings.json
│ ├── ASSISTANT_CONFIG.md
│ ├── ASSISTANT_DEVELOPMENT_GUIDE.md
│ ├── CUSTOM_APP_REGISTRATION.md
│ ├── HOSTED_ASSISTANT_WITH_LOCAL_MCP_SERVERS.md
│ ├── images
│ │ ├── architecture-animation.gif
│ │ ├── configure_assistant.png
│ │ ├── conversation_canvas_open.png
│ │ ├── conversation_duplicate.png
│ │ ├── conversation_export.png
│ │ ├── conversation_share_dialog.png
│ │ ├── conversation_share_link.png
│ │ ├── dashboard_configured_view.png
│ │ ├── dashboard_view.png
│ │ ├── license_agreement.png
│ │ ├── message_bar.png
│ │ ├── message_inspection.png
│ │ ├── message_link.png
│ │ ├── new_prospector_assistant_dialog.png
│ │ ├── open_conversation_canvas.png
│ │ ├── prospector_example.png
│ │ ├── readme1.png
│ │ ├── readme2.png
│ │ ├── readme3.png
│ │ ├── rewind.png
│ │ ├── signin_page.png
│ │ └── splash_screen.png
│ ├── LOCAL_ASSISTANT_WITH_REMOTE_WORKBENCH.md
│ ├── SETUP_DEV_ENVIRONMENT.md
│ └── WORKBENCH_APP.md
├── examples
│ ├── dotnet
│ │ ├── .editorconfig
│ │ ├── dotnet-01-echo-bot
│ │ │ ├── appsettings.json
│ │ │ ├── dotnet-01-echo-bot.csproj
│ │ │ ├── MyAgent.cs
│ │ │ ├── MyAgentConfig.cs
│ │ │ ├── MyWorkbenchConnector.cs
│ │ │ ├── Program.cs
│ │ │ └── README.md
│ │ ├── dotnet-02-message-types-demo
│ │ │ ├── appsettings.json
│ │ │ ├── ConnectorExtensions.cs
│ │ │ ├── docs
│ │ │ │ ├── abc.png
│ │ │ │ ├── code.png
│ │ │ │ ├── config.png
│ │ │ │ ├── echo.png
│ │ │ │ ├── markdown.png
│ │ │ │ ├── mermaid.png
│ │ │ │ ├── reverse.png
│ │ │ │ └── safety-check.png
│ │ │ ├── dotnet-02-message-types-demo.csproj
│ │ │ ├── MyAgent.cs
│ │ │ ├── MyAgentConfig.cs
│ │ │ ├── MyWorkbenchConnector.cs
│ │ │ ├── Program.cs
│ │ │ └── README.md
│ │ └── dotnet-03-simple-chatbot
│ │ ├── appsettings.json
│ │ ├── ConnectorExtensions.cs
│ │ ├── dotnet-03-simple-chatbot.csproj
│ │ ├── MyAgent.cs
│ │ ├── MyAgentConfig.cs
│ │ ├── MyWorkbenchConnector.cs
│ │ ├── Program.cs
│ │ └── README.md
│ ├── Makefile
│ └── python
│ ├── python-01-echo-bot
│ │ ├── .env.example
│ │ ├── .vscode
│ │ │ ├── launch.json
│ │ │ └── settings.json
│ │ ├── assistant
│ │ │ ├── __init__.py
│ │ │ ├── chat.py
│ │ │ └── config.py
│ │ ├── assistant.code-workspace
│ │ ├── Makefile
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ └── uv.lock
│ ├── python-02-simple-chatbot
│ │ ├── .env.example
│ │ ├── .vscode
│ │ │ ├── launch.json
│ │ │ └── settings.json
│ │ ├── assistant
│ │ │ ├── __init__.py
│ │ │ ├── chat.py
│ │ │ ├── config.py
│ │ │ └── text_includes
│ │ │ └── guardrails_prompt.txt
│ │ ├── assistant.code-workspace
│ │ ├── Makefile
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ └── uv.lock
│ └── python-03-multimodel-chatbot
│ ├── .env.example
│ ├── .vscode
│ │ ├── launch.json
│ │ └── settings.json
│ ├── assistant
│ │ ├── __init__.py
│ │ ├── chat.py
│ │ ├── config.py
│ │ ├── model_adapters.py
│ │ └── text_includes
│ │ └── guardrails_prompt.txt
│ ├── assistant.code-workspace
│ ├── Makefile
│ ├── pyproject.toml
│ ├── README.md
│ └── uv.lock
├── KNOWN_ISSUES.md
├── libraries
│ ├── dotnet
│ │ ├── .editorconfig
│ │ ├── pack.sh
│ │ ├── README.md
│ │ ├── SemanticWorkbench.sln
│ │ ├── SemanticWorkbench.sln.DotSettings
│ │ └── WorkbenchConnector
│ │ ├── AgentBase.cs
│ │ ├── AgentConfig
│ │ │ ├── AgentConfigBase.cs
│ │ │ ├── AgentConfigPropertyAttribute.cs
│ │ │ └── ConfigUtils.cs
│ │ ├── Constants.cs
│ │ ├── IAgentBase.cs
│ │ ├── icon.png
│ │ ├── Models
│ │ │ ├── Command.cs
│ │ │ ├── Conversation.cs
│ │ │ ├── ConversationEvent.cs
│ │ │ ├── DebugInfo.cs
│ │ │ ├── Insight.cs
│ │ │ ├── Message.cs
│ │ │ ├── MessageMetadata.cs
│ │ │ ├── Participant.cs
│ │ │ ├── Sender.cs
│ │ │ └── ServiceInfo.cs
│ │ ├── Storage
│ │ │ ├── AgentInfo.cs
│ │ │ ├── AgentServiceStorage.cs
│ │ │ └── IAgentServiceStorage.cs
│ │ ├── StringLoggingExtensions.cs
│ │ ├── Webservice.cs
│ │ ├── WorkbenchConfig.cs
│ │ ├── WorkbenchConnector.cs
│ │ └── WorkbenchConnector.csproj
│ ├── Makefile
│ └── python
│ ├── anthropic-client
│ │ ├── .vscode
│ │ │ └── settings.json
│ │ ├── anthropic_client
│ │ │ ├── __init__.py
│ │ │ ├── client.py
│ │ │ ├── config.py
│ │ │ └── messages.py
│ │ ├── Makefile
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ └── uv.lock
│ ├── assistant-data-gen
│ │ ├── .vscode
│ │ │ ├── launch.json
│ │ │ └── settings.json
│ │ ├── assistant_data_gen
│ │ │ ├── __init__.py
│ │ │ ├── assistant_api.py
│ │ │ ├── config.py
│ │ │ ├── gce
│ │ │ │ ├── __init__.py
│ │ │ │ ├── gce_agent.py
│ │ │ │ └── prompts.py
│ │ │ └── pydantic_ai_utils.py
│ │ ├── configs
│ │ │ └── document_assistant_example_config.yaml
│ │ ├── Makefile
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── scripts
│ │ │ ├── gce_simulation.py
│ │ │ └── generate_scenario.py
│ │ └── uv.lock
│ ├── assistant-drive
│ │ ├── .gitignore
│ │ ├── .vscode
│ │ │ ├── extensions.json
│ │ │ └── settings.json
│ │ ├── assistant_drive
│ │ │ ├── __init__.py
│ │ │ ├── drive.py
│ │ │ └── tests
│ │ │ └── test_basic.py
│ │ ├── Makefile
│ │ ├── pyproject.toml
│ │ ├── pytest.ini
│ │ ├── README.md
│ │ ├── usage.ipynb
│ │ └── uv.lock
│ ├── assistant-extensions
│ │ ├── .vscode
│ │ │ └── settings.json
│ │ ├── assistant_extensions
│ │ │ ├── __init__.py
│ │ │ ├── ai_clients
│ │ │ │ └── config.py
│ │ │ ├── artifacts
│ │ │ │ ├── __init__.py
│ │ │ │ ├── _artifacts.py
│ │ │ │ ├── _inspector.py
│ │ │ │ └── _model.py
│ │ │ ├── attachments
│ │ │ │ ├── __init__.py
│ │ │ │ ├── _attachments.py
│ │ │ │ ├── _convert.py
│ │ │ │ ├── _model.py
│ │ │ │ ├── _shared.py
│ │ │ │ └── _summarizer.py
│ │ │ ├── chat_context_toolkit
│ │ │ │ ├── __init__.py
│ │ │ │ ├── _config.py
│ │ │ │ ├── archive
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── _archive.py
│ │ │ │ │ └── _summarizer.py
│ │ │ │ ├── message_history
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── _history.py
│ │ │ │ │ └── _message.py
│ │ │ │ └── virtual_filesystem
│ │ │ │ ├── __init__.py
│ │ │ │ ├── _archive_file_source.py
│ │ │ │ └── _attachments_file_source.py
│ │ │ ├── dashboard_card
│ │ │ │ ├── __init__.py
│ │ │ │ └── _dashboard_card.py
│ │ │ ├── document_editor
│ │ │ │ ├── __init__.py
│ │ │ │ ├── _extension.py
│ │ │ │ ├── _inspector.py
│ │ │ │ └── _model.py
│ │ │ ├── mcp
│ │ │ │ ├── __init__.py
│ │ │ │ ├── _assistant_file_resource_handler.py
│ │ │ │ ├── _client_utils.py
│ │ │ │ ├── _devtunnel.py
│ │ │ │ ├── _model.py
│ │ │ │ ├── _openai_utils.py
│ │ │ │ ├── _sampling_handler.py
│ │ │ │ ├── _tool_utils.py
│ │ │ │ └── _workbench_file_resource_handler.py
│ │ │ ├── navigator
│ │ │ │ ├── __init__.py
│ │ │ │ └── _navigator.py
│ │ │ └── workflows
│ │ │ ├── __init__.py
│ │ │ ├── _model.py
│ │ │ ├── _workflows.py
│ │ │ └── runners
│ │ │ └── _user_proxy.py
│ │ ├── Makefile
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── test
│ │ │ └── attachments
│ │ │ └── test_attachments.py
│ │ └── uv.lock
│ ├── chat-context-toolkit
│ │ ├── .claude
│ │ │ └── settings.local.json
│ │ ├── .env.sample
│ │ ├── .vscode
│ │ │ └── settings.json
│ │ ├── assets
│ │ │ ├── archive_v1.png
│ │ │ ├── history_v1.png
│ │ │ └── vfs_v1.png
│ │ ├── chat_context_toolkit
│ │ │ ├── __init__.py
│ │ │ ├── archive
│ │ │ │ ├── __init__.py
│ │ │ │ ├── _archive_reader.py
│ │ │ │ ├── _archive_task_queue.py
│ │ │ │ ├── _state.py
│ │ │ │ ├── _types.py
│ │ │ │ └── summarization
│ │ │ │ ├── __init__.py
│ │ │ │ └── _summarizer.py
│ │ │ ├── history
│ │ │ │ ├── __init__.py
│ │ │ │ ├── _budget.py
│ │ │ │ ├── _decorators.py
│ │ │ │ ├── _history.py
│ │ │ │ ├── _prioritize.py
│ │ │ │ ├── _types.py
│ │ │ │ └── tool_abbreviations
│ │ │ │ ├── __init__.py
│ │ │ │ └── _tool_abbreviations.py
│ │ │ └── virtual_filesystem
│ │ │ ├── __init__.py
│ │ │ ├── _types.py
│ │ │ ├── _virtual_filesystem.py
│ │ │ ├── README.md
│ │ │ └── tools
│ │ │ ├── __init__.py
│ │ │ ├── _ls_tool.py
│ │ │ ├── _tools.py
│ │ │ └── _view_tool.py
│ │ ├── CLAUDE.md
│ │ ├── Makefile
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── test
│ │ │ ├── archive
│ │ │ │ └── test_archive_reader.py
│ │ │ ├── history
│ │ │ │ ├── test_abbreviate_messages.py
│ │ │ │ ├── test_history.py
│ │ │ │ ├── test_pair_and_order_tool_messages.py
│ │ │ │ ├── test_prioritize.py
│ │ │ │ └── test_truncate_messages.py
│ │ │ └── virtual_filesystem
│ │ │ ├── test_virtual_filesystem.py
│ │ │ └── tools
│ │ │ ├── test_ls_tool.py
│ │ │ ├── test_tools.py
│ │ │ └── test_view_tool.py
│ │ └── uv.lock
│ ├── content-safety
│ │ ├── .vscode
│ │ │ └── settings.json
│ │ ├── content_safety
│ │ │ ├── __init__.py
│ │ │ ├── evaluators
│ │ │ │ ├── __init__.py
│ │ │ │ ├── azure_content_safety
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── config.py
│ │ │ │ │ └── evaluator.py
│ │ │ │ ├── config.py
│ │ │ │ ├── evaluator.py
│ │ │ │ └── openai_moderations
│ │ │ │ ├── __init__.py
│ │ │ │ ├── config.py
│ │ │ │ └── evaluator.py
│ │ │ └── README.md
│ │ ├── Makefile
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ └── uv.lock
│ ├── events
│ │ ├── .vscode
│ │ │ └── settings.json
│ │ ├── events
│ │ │ ├── __init__.py
│ │ │ └── events.py
│ │ ├── Makefile
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ └── uv.lock
│ ├── guided-conversation
│ │ ├── .vscode
│ │ │ └── settings.json
│ │ ├── guided_conversation
│ │ │ ├── __init__.py
│ │ │ ├── functions
│ │ │ │ ├── __init__.py
│ │ │ │ ├── conversation_plan.py
│ │ │ │ ├── execution.py
│ │ │ │ └── final_update_plan.py
│ │ │ ├── guided_conversation_agent.py
│ │ │ ├── plugins
│ │ │ │ ├── __init__.py
│ │ │ │ ├── agenda.py
│ │ │ │ └── artifact.py
│ │ │ └── utils
│ │ │ ├── __init__.py
│ │ │ ├── base_model_llm.py
│ │ │ ├── conversation_helpers.py
│ │ │ ├── openai_tool_calling.py
│ │ │ ├── plugin_helpers.py
│ │ │ └── resources.py
│ │ ├── Makefile
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ └── uv.lock
│ ├── llm-client
│ │ ├── .vscode
│ │ │ └── settings.json
│ │ ├── llm_client
│ │ │ ├── __init__.py
│ │ │ └── model.py
│ │ ├── Makefile
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ └── uv.lock
│ ├── Makefile
│ ├── mcp-extensions
│ │ ├── .vscode
│ │ │ └── settings.json
│ │ ├── Makefile
│ │ ├── mcp_extensions
│ │ │ ├── __init__.py
│ │ │ ├── _client_session.py
│ │ │ ├── _model.py
│ │ │ ├── _sampling.py
│ │ │ ├── _server_extensions.py
│ │ │ ├── _tool_utils.py
│ │ │ ├── llm
│ │ │ │ ├── __init__.py
│ │ │ │ ├── chat_completion.py
│ │ │ │ ├── helpers.py
│ │ │ │ ├── llm_types.py
│ │ │ │ ├── mcp_chat_completion.py
│ │ │ │ └── openai_chat_completion.py
│ │ │ └── server
│ │ │ ├── __init__.py
│ │ │ └── storage.py
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── tests
│ │ │ └── test_tool_utils.py
│ │ └── uv.lock
│ ├── mcp-tunnel
│ │ ├── .vscode
│ │ │ └── settings.json
│ │ ├── Makefile
│ │ ├── mcp_tunnel
│ │ │ ├── __init__.py
│ │ │ ├── _devtunnel.py
│ │ │ ├── _dir.py
│ │ │ └── _main.py
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ └── uv.lock
│ ├── openai-client
│ │ ├── .vscode
│ │ │ └── settings.json
│ │ ├── Makefile
│ │ ├── openai_client
│ │ │ ├── __init__.py
│ │ │ ├── chat_driver
│ │ │ │ ├── __init__.py
│ │ │ │ ├── chat_driver.ipynb
│ │ │ │ ├── chat_driver.py
│ │ │ │ ├── message_history_providers
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── in_memory_message_history_provider.py
│ │ │ │ │ ├── local_message_history_provider.py
│ │ │ │ │ ├── message_history_provider.py
│ │ │ │ │ └── tests
│ │ │ │ │ └── formatted_instructions_test.py
│ │ │ │ └── README.md
│ │ │ ├── client.py
│ │ │ ├── completion.py
│ │ │ ├── config.py
│ │ │ ├── errors.py
│ │ │ ├── logging.py
│ │ │ ├── messages.py
│ │ │ ├── tokens.py
│ │ │ └── tools.py
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── tests
│ │ │ ├── test_command_parsing.py
│ │ │ ├── test_formatted_messages.py
│ │ │ ├── test_messages.py
│ │ │ └── test_tokens.py
│ │ └── uv.lock
│ ├── semantic-workbench-api-model
│ │ ├── .vscode
│ │ │ └── settings.json
│ │ ├── Makefile
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── semantic_workbench_api_model
│ │ │ ├── __init__.py
│ │ │ ├── assistant_model.py
│ │ │ ├── assistant_service_client.py
│ │ │ ├── workbench_model.py
│ │ │ └── workbench_service_client.py
│ │ └── uv.lock
│ ├── semantic-workbench-assistant
│ │ ├── .vscode
│ │ │ ├── launch.json
│ │ │ └── settings.json
│ │ ├── Makefile
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── semantic_workbench_assistant
│ │ │ ├── __init__.py
│ │ │ ├── assistant_app
│ │ │ │ ├── __init__.py
│ │ │ │ ├── assistant.py
│ │ │ │ ├── config.py
│ │ │ │ ├── content_safety.py
│ │ │ │ ├── context.py
│ │ │ │ ├── error.py
│ │ │ │ ├── export_import.py
│ │ │ │ ├── protocol.py
│ │ │ │ └── service.py
│ │ │ ├── assistant_service.py
│ │ │ ├── auth.py
│ │ │ ├── canonical.py
│ │ │ ├── command.py
│ │ │ ├── config.py
│ │ │ ├── logging_config.py
│ │ │ ├── settings.py
│ │ │ ├── start.py
│ │ │ └── storage.py
│ │ ├── tests
│ │ │ ├── conftest.py
│ │ │ ├── test_assistant_app.py
│ │ │ ├── test_canonical.py
│ │ │ ├── test_config.py
│ │ │ └── test_storage.py
│ │ └── uv.lock
│ └── skills
│ ├── .vscode
│ │ └── settings.json
│ ├── Makefile
│ ├── README.md
│ └── skill-library
│ ├── .vscode
│ │ └── settings.json
│ ├── docs
│ │ └── vs-recipe-tool.md
│ ├── Makefile
│ ├── pyproject.toml
│ ├── README.md
│ ├── skill_library
│ │ ├── __init__.py
│ │ ├── chat_driver_helpers.py
│ │ ├── cli
│ │ │ ├── azure_openai.py
│ │ │ ├── conversation_history.py
│ │ │ ├── README.md
│ │ │ ├── run_routine.py
│ │ │ ├── settings.py
│ │ │ └── skill_logger.py
│ │ ├── engine.py
│ │ ├── llm_info.txt
│ │ ├── logging.py
│ │ ├── README.md
│ │ ├── routine_stack.py
│ │ ├── skill.py
│ │ ├── skills
│ │ │ ├── common
│ │ │ │ ├── __init__.py
│ │ │ │ ├── common_skill.py
│ │ │ │ └── routines
│ │ │ │ ├── bing_search.py
│ │ │ │ ├── consolidate.py
│ │ │ │ ├── echo.py
│ │ │ │ ├── gather_context.py
│ │ │ │ ├── get_content_from_url.py
│ │ │ │ ├── gpt_complete.py
│ │ │ │ ├── select_user_intent.py
│ │ │ │ └── summarize.py
│ │ │ ├── eval
│ │ │ │ ├── __init__.py
│ │ │ │ ├── eval_skill.py
│ │ │ │ └── routines
│ │ │ │ └── eval.py
│ │ │ ├── fabric
│ │ │ │ ├── __init__.py
│ │ │ │ ├── fabric_skill.py
│ │ │ │ ├── patterns
│ │ │ │ │ ├── agility_story
│ │ │ │ │ │ ├── system.md
│ │ │ │ │ │ └── user.md
│ │ │ │ │ ├── ai
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── analyze_answers
│ │ │ │ │ │ ├── README.md
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── analyze_candidates
│ │ │ │ │ │ ├── system.md
│ │ │ │ │ │ └── user.md
│ │ │ │ │ ├── analyze_cfp_submission
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── analyze_claims
│ │ │ │ │ │ ├── system.md
│ │ │ │ │ │ └── user.md
│ │ │ │ │ ├── analyze_comments
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── analyze_debate
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── analyze_email_headers
│ │ │ │ │ │ ├── system.md
│ │ │ │ │ │ └── user.md
│ │ │ │ │ ├── analyze_incident
│ │ │ │ │ │ ├── system.md
│ │ │ │ │ │ └── user.md
│ │ │ │ │ ├── analyze_interviewer_techniques
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── analyze_logs
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── analyze_malware
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── analyze_military_strategy
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── analyze_mistakes
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── analyze_paper
│ │ │ │ │ │ ├── system.md
│ │ │ │ │ │ └── user.md
│ │ │ │ │ ├── analyze_patent
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── analyze_personality
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── analyze_presentation
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── analyze_product_feedback
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── analyze_proposition
│ │ │ │ │ │ ├── system.md
│ │ │ │ │ │ └── user.md
│ │ │ │ │ ├── analyze_prose
│ │ │ │ │ │ ├── system.md
│ │ │ │ │ │ └── user.md
│ │ │ │ │ ├── analyze_prose_json
│ │ │ │ │ │ ├── system.md
│ │ │ │ │ │ └── user.md
│ │ │ │ │ ├── analyze_prose_pinker
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── analyze_risk
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── analyze_sales_call
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── analyze_spiritual_text
│ │ │ │ │ │ ├── system.md
│ │ │ │ │ │ └── user.md
│ │ │ │ │ ├── analyze_tech_impact
│ │ │ │ │ │ ├── system.md
│ │ │ │ │ │ └── user.md
│ │ │ │ │ ├── analyze_threat_report
│ │ │ │ │ │ ├── system.md
│ │ │ │ │ │ └── user.md
│ │ │ │ │ ├── analyze_threat_report_cmds
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── analyze_threat_report_trends
│ │ │ │ │ │ ├── system.md
│ │ │ │ │ │ └── user.md
│ │ │ │ │ ├── answer_interview_question
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── ask_secure_by_design_questions
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── ask_uncle_duke
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── capture_thinkers_work
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── check_agreement
│ │ │ │ │ │ ├── system.md
│ │ │ │ │ │ └── user.md
│ │ │ │ │ ├── clean_text
│ │ │ │ │ │ ├── system.md
│ │ │ │ │ │ └── user.md
│ │ │ │ │ ├── coding_master
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── compare_and_contrast
│ │ │ │ │ │ ├── system.md
│ │ │ │ │ │ └── user.md
│ │ │ │ │ ├── convert_to_markdown
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── create_5_sentence_summary
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── create_academic_paper
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── create_ai_jobs_analysis
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── create_aphorisms
│ │ │ │ │ │ ├── system.md
│ │ │ │ │ │ └── user.md
│ │ │ │ │ ├── create_art_prompt
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── create_better_frame
│ │ │ │ │ │ ├── system.md
│ │ │ │ │ │ └── user.md
│ │ │ │ │ ├── create_coding_project
│ │ │ │ │ │ ├── README.md
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── create_command
│ │ │ │ │ │ ├── README.md
│ │ │ │ │ │ ├── system.md
│ │ │ │ │ │ └── user.md
│ │ │ │ │ ├── create_cyber_summary
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── create_design_document
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── create_diy
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── create_formal_email
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── create_git_diff_commit
│ │ │ │ │ │ ├── README.md
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── create_graph_from_input
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── create_hormozi_offer
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── create_idea_compass
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── create_investigation_visualization
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── create_keynote
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── create_logo
│ │ │ │ │ │ ├── system.md
│ │ │ │ │ │ └── user.md
│ │ │ │ │ ├── create_markmap_visualization
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── create_mermaid_visualization
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── create_mermaid_visualization_for_github
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── create_micro_summary
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── create_network_threat_landscape
│ │ │ │ │ │ ├── system.md
│ │ │ │ │ │ └── user.md
│ │ │ │ │ ├── create_newsletter_entry
│ │ │ │ │ │ ├── system.md
│ │ │ │ │ │ └── user.md
│ │ │ │ │ ├── create_npc
│ │ │ │ │ │ ├── system.md
│ │ │ │ │ │ └── user.md
│ │ │ │ │ ├── create_pattern
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── create_prd
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── create_prediction_block
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── create_quiz
│ │ │ │ │ │ ├── README.md
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── create_reading_plan
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── create_recursive_outline
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── create_report_finding
│ │ │ │ │ │ ├── system.md
│ │ │ │ │ │ └── user.md
│ │ │ │ │ ├── create_rpg_summary
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── create_security_update
│ │ │ │ │ │ ├── system.md
│ │ │ │ │ │ └── user.md
│ │ │ │ │ ├── create_show_intro
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── create_sigma_rules
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── create_story_explanation
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── create_stride_threat_model
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── create_summary
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── create_tags
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── create_threat_scenarios
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── create_ttrc_graph
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── create_ttrc_narrative
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── create_upgrade_pack
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── create_user_story
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── create_video_chapters
│ │ │ │ │ │ ├── system.md
│ │ │ │ │ │ └── user.md
│ │ │ │ │ ├── create_visualization
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── dialog_with_socrates
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── enrich_blog_post
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── explain_code
│ │ │ │ │ │ ├── system.md
│ │ │ │ │ │ └── user.md
│ │ │ │ │ ├── explain_docs
│ │ │ │ │ │ ├── system.md
│ │ │ │ │ │ └── user.md
│ │ │ │ │ ├── explain_math
│ │ │ │ │ │ ├── README.md
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── explain_project
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── explain_terms
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── export_data_as_csv
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── extract_algorithm_update_recommendations
│ │ │ │ │ │ ├── system.md
│ │ │ │ │ │ └── user.md
│ │ │ │ │ ├── extract_article_wisdom
│ │ │ │ │ │ ├── dmiessler
│ │ │ │ │ │ │ └── extract_wisdom-1.0.0
│ │ │ │ │ │ │ ├── system.md
│ │ │ │ │ │ │ └── user.md
│ │ │ │ │ │ ├── README.md
│ │ │ │ │ │ ├── system.md
│ │ │ │ │ │ └── user.md
│ │ │ │ │ ├── extract_book_ideas
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── extract_book_recommendations
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── extract_business_ideas
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── extract_controversial_ideas
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── extract_core_message
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── extract_ctf_writeup
│ │ │ │ │ │ ├── README.md
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── extract_domains
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── extract_extraordinary_claims
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── extract_ideas
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── extract_insights
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── extract_insights_dm
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── extract_instructions
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── extract_jokes
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── extract_latest_video
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── extract_main_idea
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── extract_most_redeeming_thing
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── extract_patterns
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── extract_poc
│ │ │ │ │ │ ├── system.md
│ │ │ │ │ │ └── user.md
│ │ │ │ │ ├── extract_predictions
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── extract_primary_problem
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── extract_primary_solution
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── extract_product_features
│ │ │ │ │ │ ├── dmiessler
│ │ │ │ │ │ │ └── extract_wisdom-1.0.0
│ │ │ │ │ │ │ ├── system.md
│ │ │ │ │ │ │ └── user.md
│ │ │ │ │ │ ├── README.md
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── extract_questions
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── extract_recipe
│ │ │ │ │ │ ├── README.md
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── extract_recommendations
│ │ │ │ │ │ ├── system.md
│ │ │ │ │ │ └── user.md
│ │ │ │ │ ├── extract_references
│ │ │ │ │ │ ├── system.md
│ │ │ │ │ │ └── user.md
│ │ │ │ │ ├── extract_skills
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── extract_song_meaning
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── extract_sponsors
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── extract_videoid
│ │ │ │ │ │ ├── system.md
│ │ │ │ │ │ └── user.md
│ │ │ │ │ ├── extract_wisdom
│ │ │ │ │ │ ├── dmiessler
│ │ │ │ │ │ │ └── extract_wisdom-1.0.0
│ │ │ │ │ │ │ ├── system.md
│ │ │ │ │ │ │ └── user.md
│ │ │ │ │ │ ├── README.md
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── extract_wisdom_agents
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── extract_wisdom_dm
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── extract_wisdom_nometa
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── find_hidden_message
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── find_logical_fallacies
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── get_wow_per_minute
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── get_youtube_rss
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── humanize
│ │ │ │ │ │ ├── README.md
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── identify_dsrp_distinctions
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── identify_dsrp_perspectives
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── identify_dsrp_relationships
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── identify_dsrp_systems
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── identify_job_stories
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── improve_academic_writing
│ │ │ │ │ │ ├── system.md
│ │ │ │ │ │ └── user.md
│ │ │ │ │ ├── improve_prompt
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── improve_report_finding
│ │ │ │ │ │ ├── system.md
│ │ │ │ │ │ └── user.md
│ │ │ │ │ ├── improve_writing
│ │ │ │ │ │ ├── system.md
│ │ │ │ │ │ └── user.md
│ │ │ │ │ ├── judge_output
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── label_and_rate
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── loaded
│ │ │ │ │ ├── md_callout
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── official_pattern_template
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── pattern_explanations.md
│ │ │ │ │ ├── prepare_7s_strategy
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── provide_guidance
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── rate_ai_response
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── rate_ai_result
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── rate_content
│ │ │ │ │ │ ├── system.md
│ │ │ │ │ │ └── user.md
│ │ │ │ │ ├── rate_value
│ │ │ │ │ │ ├── README.md
│ │ │ │ │ │ ├── system.md
│ │ │ │ │ │ └── user.md
│ │ │ │ │ ├── raw_query
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── raycast
│ │ │ │ │ │ ├── capture_thinkers_work
│ │ │ │ │ │ ├── create_story_explanation
│ │ │ │ │ │ ├── extract_primary_problem
│ │ │ │ │ │ ├── extract_wisdom
│ │ │ │ │ │ └── yt
│ │ │ │ │ ├── recommend_artists
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── recommend_pipeline_upgrades
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── recommend_talkpanel_topics
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── refine_design_document
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── review_design
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── sanitize_broken_html_to_markdown
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── show_fabric_options_markmap
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── solve_with_cot
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── stringify
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── suggest_pattern
│ │ │ │ │ │ ├── system.md
│ │ │ │ │ │ └── user.md
│ │ │ │ │ ├── summarize
│ │ │ │ │ │ ├── dmiessler
│ │ │ │ │ │ │ └── summarize
│ │ │ │ │ │ │ ├── system.md
│ │ │ │ │ │ │ └── user.md
│ │ │ │ │ │ ├── system.md
│ │ │ │ │ │ └── user.md
│ │ │ │ │ ├── summarize_debate
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── summarize_git_changes
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── summarize_git_diff
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── summarize_lecture
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── summarize_legislation
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── summarize_meeting
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── summarize_micro
│ │ │ │ │ │ ├── system.md
│ │ │ │ │ │ └── user.md
│ │ │ │ │ ├── summarize_newsletter
│ │ │ │ │ │ ├── system.md
│ │ │ │ │ │ └── user.md
│ │ │ │ │ ├── summarize_paper
│ │ │ │ │ │ ├── README.md
│ │ │ │ │ │ ├── system.md
│ │ │ │ │ │ └── user.md
│ │ │ │ │ ├── summarize_prompt
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── summarize_pull-requests
│ │ │ │ │ │ ├── system.md
│ │ │ │ │ │ └── user.md
│ │ │ │ │ ├── summarize_rpg_session
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── t_analyze_challenge_handling
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── t_check_metrics
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── t_create_h3_career
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── t_create_opening_sentences
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── t_describe_life_outlook
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── t_extract_intro_sentences
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── t_extract_panel_topics
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── t_find_blindspots
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── t_find_negative_thinking
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── t_find_neglected_goals
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── t_give_encouragement
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── t_red_team_thinking
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── t_threat_model_plans
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── t_visualize_mission_goals_projects
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── t_year_in_review
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── to_flashcards
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── transcribe_minutes
│ │ │ │ │ │ ├── README.md
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── translate
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── tweet
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── write_essay
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── write_hackerone_report
│ │ │ │ │ │ ├── README.md
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── write_latex
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── write_micro_essay
│ │ │ │ │ │ └── system.md
│ │ │ │ │ ├── write_nuclei_template_rule
│ │ │ │ │ │ ├── system.md
│ │ │ │ │ │ └── user.md
│ │ │ │ │ ├── write_pull-request
│ │ │ │ │ │ └── system.md
│ │ │ │ │ └── write_semgrep_rule
│ │ │ │ │ ├── system.md
│ │ │ │ │ └── user.md
│ │ │ │ └── routines
│ │ │ │ ├── list.py
│ │ │ │ ├── run.py
│ │ │ │ └── show.py
│ │ │ ├── guided_conversation
│ │ │ │ ├── __init__.py
│ │ │ │ ├── agenda.py
│ │ │ │ ├── artifact_helpers.py
│ │ │ │ ├── chat_completions
│ │ │ │ │ ├── fix_agenda_error.py
│ │ │ │ │ ├── fix_artifact_error.py
│ │ │ │ │ ├── generate_agenda.py
│ │ │ │ │ ├── generate_artifact_updates.py
│ │ │ │ │ ├── generate_final_artifact.py
│ │ │ │ │ └── generate_message.py
│ │ │ │ ├── conversation_guides
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── acrostic_poem.py
│ │ │ │ │ ├── er_triage.py
│ │ │ │ │ ├── interview.py
│ │ │ │ │ └── patient_intake.py
│ │ │ │ ├── guide.py
│ │ │ │ ├── guided_conversation_skill.py
│ │ │ │ ├── logging.py
│ │ │ │ ├── message.py
│ │ │ │ ├── resources.py
│ │ │ │ ├── routines
│ │ │ │ │ └── guided_conversation.py
│ │ │ │ └── tests
│ │ │ │ ├── conftest.py
│ │ │ │ ├── test_artifact_helpers.py
│ │ │ │ ├── test_generate_agenda.py
│ │ │ │ ├── test_generate_artifact_updates.py
│ │ │ │ ├── test_generate_final_artifact.py
│ │ │ │ └── test_resource.py
│ │ │ ├── meta
│ │ │ │ ├── __init__.py
│ │ │ │ ├── meta_skill.py
│ │ │ │ ├── README.md
│ │ │ │ └── routines
│ │ │ │ └── generate_routine.py
│ │ │ ├── posix
│ │ │ │ ├── __init__.py
│ │ │ │ ├── posix_skill.py
│ │ │ │ ├── routines
│ │ │ │ │ ├── append_file.py
│ │ │ │ │ ├── cd.py
│ │ │ │ │ ├── ls.py
│ │ │ │ │ ├── make_home_dir.py
│ │ │ │ │ ├── mkdir.py
│ │ │ │ │ ├── mv.py
│ │ │ │ │ ├── pwd.py
│ │ │ │ │ ├── read_file.py
│ │ │ │ │ ├── rm.py
│ │ │ │ │ ├── touch.py
│ │ │ │ │ └── write_file.py
│ │ │ │ └── sandbox_shell.py
│ │ │ ├── README.md
│ │ │ ├── research
│ │ │ │ ├── __init__.py
│ │ │ │ ├── README.md
│ │ │ │ ├── research_skill.py
│ │ │ │ └── routines
│ │ │ │ ├── answer_question_about_content.py
│ │ │ │ ├── evaluate_answer.py
│ │ │ │ ├── generate_research_plan.py
│ │ │ │ ├── generate_search_query.py
│ │ │ │ ├── update_research_plan.py
│ │ │ │ ├── web_research.py
│ │ │ │ └── web_search.py
│ │ │ ├── research2
│ │ │ │ ├── __init__.py
│ │ │ │ ├── README.md
│ │ │ │ ├── research_skill.py
│ │ │ │ └── routines
│ │ │ │ ├── facts.py
│ │ │ │ ├── make_final_report.py
│ │ │ │ ├── research.py
│ │ │ │ ├── search_plan.py
│ │ │ │ ├── search.py
│ │ │ │ └── visit_pages.py
│ │ │ └── web_research
│ │ │ ├── __init__.py
│ │ │ ├── README.md
│ │ │ ├── research_skill.py
│ │ │ └── routines
│ │ │ ├── facts.py
│ │ │ ├── make_final_report.py
│ │ │ ├── research.py
│ │ │ ├── search_plan.py
│ │ │ ├── search.py
│ │ │ └── visit_pages.py
│ │ ├── tests
│ │ │ ├── test_common_skill.py
│ │ │ ├── test_integration.py
│ │ │ ├── test_routine_stack.py
│ │ │ ├── tst_skill
│ │ │ │ ├── __init__.py
│ │ │ │ └── routines
│ │ │ │ ├── __init__.py
│ │ │ │ └── a_routine.py
│ │ │ └── utilities
│ │ │ ├── test_find_template_vars.py
│ │ │ ├── test_make_arg_set.py
│ │ │ ├── test_paramspec.py
│ │ │ ├── test_parse_command_string.py
│ │ │ └── test_to_string.py
│ │ ├── types.py
│ │ ├── usage.py
│ │ └── utilities.py
│ └── uv.lock
├── LICENSE
├── Makefile
├── mcp-servers
│ ├── ai-assist-content
│ │ ├── .vscode
│ │ │ └── settings.json
│ │ ├── mcp-example-brave-search.md
│ │ ├── mcp-fastmcp-typescript-README.md
│ │ ├── mcp-llms-full.txt
│ │ ├── mcp-metadata-tips.md
│ │ ├── mcp-python-sdk-README.md
│ │ ├── mcp-typescript-sdk-README.md
│ │ ├── pydanticai-documentation.md
│ │ ├── pydanticai-example-question-graph.md
│ │ ├── pydanticai-example-weather.md
│ │ ├── pydanticai-tutorial.md
│ │ └── README.md
│ ├── Makefile
│ ├── mcp-server-bing-search
│ │ ├── .env.example
│ │ ├── .gitignore
│ │ ├── .vscode
│ │ │ ├── launch.json
│ │ │ └── settings.json
│ │ ├── Makefile
│ │ ├── mcp_server_bing_search
│ │ │ ├── __init__.py
│ │ │ ├── config.py
│ │ │ ├── prompts
│ │ │ │ ├── __init__.py
│ │ │ │ ├── clean_website.py
│ │ │ │ └── filter_links.py
│ │ │ ├── server.py
│ │ │ ├── start.py
│ │ │ ├── tools.py
│ │ │ ├── types.py
│ │ │ ├── utils.py
│ │ │ └── web
│ │ │ ├── __init__.py
│ │ │ ├── get_content.py
│ │ │ ├── llm_processing.py
│ │ │ ├── process_website.py
│ │ │ └── search_bing.py
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── tests
│ │ │ └── test_tools.py
│ │ └── uv.lock
│ ├── mcp-server-bundle
│ │ ├── .vscode
│ │ │ ├── launch.json
│ │ │ └── settings.json
│ │ ├── Makefile
│ │ ├── mcp_server_bundle
│ │ │ ├── __init__.py
│ │ │ └── main.py
│ │ ├── pyinstaller.spec
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ └── uv.lock
│ ├── mcp-server-filesystem
│ │ ├── .env.example
│ │ ├── .github
│ │ │ └── workflows
│ │ │ └── ci.yml
│ │ ├── .gitignore
│ │ ├── .vscode
│ │ │ ├── launch.json
│ │ │ └── settings.json
│ │ ├── Makefile
│ │ ├── mcp_server_filesystem
│ │ │ ├── __init__.py
│ │ │ ├── config.py
│ │ │ ├── server.py
│ │ │ └── start.py
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── tests
│ │ │ └── test_filesystem.py
│ │ └── uv.lock
│ ├── mcp-server-filesystem-edit
│ │ ├── .env.example
│ │ ├── .gitignore
│ │ ├── .vscode
│ │ │ ├── launch.json
│ │ │ └── settings.json
│ │ ├── data
│ │ │ ├── attachments
│ │ │ │ ├── Daily Game Ideas.txt
│ │ │ │ ├── Frontend Framework Proposal.txt
│ │ │ │ ├── ReDoodle.txt
│ │ │ │ └── Research Template.tex
│ │ │ ├── test_cases.yaml
│ │ │ └── transcripts
│ │ │ ├── transcript_research_simple.md
│ │ │ ├── transcript_Startup_Idea_1_202503031513.md
│ │ │ ├── transcript_Startup_Idea_2_202503031659.md
│ │ │ └── transcript_Web_Frontends_202502281551.md
│ │ ├── Makefile
│ │ ├── mcp_server_filesystem_edit
│ │ │ ├── __init__.py
│ │ │ ├── app_handling
│ │ │ │ ├── __init__.py
│ │ │ │ ├── excel.py
│ │ │ │ ├── miktex.py
│ │ │ │ ├── office_common.py
│ │ │ │ ├── powerpoint.py
│ │ │ │ └── word.py
│ │ │ ├── config.py
│ │ │ ├── evals
│ │ │ │ ├── __init__.py
│ │ │ │ ├── common.py
│ │ │ │ ├── run_comments.py
│ │ │ │ ├── run_edit.py
│ │ │ │ └── run_ppt_edit.py
│ │ │ ├── prompts
│ │ │ │ ├── __init__.py
│ │ │ │ ├── add_comments.py
│ │ │ │ ├── analyze_comments.py
│ │ │ │ ├── latex_edit.py
│ │ │ │ ├── markdown_draft.py
│ │ │ │ ├── markdown_edit.py
│ │ │ │ └── powerpoint_edit.py
│ │ │ ├── server.py
│ │ │ ├── start.py
│ │ │ ├── tools
│ │ │ │ ├── __init__.py
│ │ │ │ ├── add_comments.py
│ │ │ │ ├── edit_adapters
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── common.py
│ │ │ │ │ ├── latex.py
│ │ │ │ │ └── markdown.py
│ │ │ │ ├── edit.py
│ │ │ │ └── helpers.py
│ │ │ └── types.py
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── tests
│ │ │ ├── app_handling
│ │ │ │ ├── test_excel.py
│ │ │ │ ├── test_miktext.py
│ │ │ │ ├── test_office_common.py
│ │ │ │ ├── test_powerpoint.py
│ │ │ │ └── test_word.py
│ │ │ ├── conftest.py
│ │ │ └── tools
│ │ │ └── edit_adapters
│ │ │ ├── test_latex.py
│ │ │ └── test_markdown.py
│ │ └── uv.lock
│ ├── mcp-server-fusion
│ │ ├── .gitignore
│ │ ├── .vscode
│ │ │ ├── launch.json
│ │ │ └── settings.json
│ │ ├── AddInIcon.svg
│ │ ├── config.py
│ │ ├── FusionMCPServerAddIn.manifest
│ │ ├── FusionMCPServerAddIn.py
│ │ ├── mcp_server_fusion
│ │ │ ├── __init__.py
│ │ │ ├── fusion_mcp_server.py
│ │ │ ├── fusion_utils
│ │ │ │ ├── __init__.py
│ │ │ │ ├── event_utils.py
│ │ │ │ ├── general_utils.py
│ │ │ │ └── tool_utils.py
│ │ │ ├── mcp_tools
│ │ │ │ ├── __init__.py
│ │ │ │ ├── fusion_3d_operation.py
│ │ │ │ ├── fusion_geometry.py
│ │ │ │ ├── fusion_pattern.py
│ │ │ │ └── fusion_sketch.py
│ │ │ └── vendor
│ │ │ └── README.md
│ │ ├── README.md
│ │ └── requirements.txt
│ ├── mcp-server-giphy
│ │ ├── .env.example
│ │ ├── .gitignore
│ │ ├── .vscode
│ │ │ ├── launch.json
│ │ │ └── settings.json
│ │ ├── Makefile
│ │ ├── mcp_server
│ │ │ ├── __init__.py
│ │ │ ├── config.py
│ │ │ ├── giphy_search.py
│ │ │ ├── sampling.py
│ │ │ ├── server.py
│ │ │ ├── start.py
│ │ │ └── utils.py
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ └── uv.lock
│ ├── mcp-server-memory-user-bio
│ │ ├── .env.example
│ │ ├── .gitignore
│ │ ├── .vscode
│ │ │ ├── launch.json
│ │ │ └── settings.json
│ │ ├── Makefile
│ │ ├── mcp_server_memory_user_bio
│ │ │ ├── __init__.py
│ │ │ ├── config.py
│ │ │ ├── server.py
│ │ │ └── start.py
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ └── uv.lock
│ ├── mcp-server-memory-whiteboard
│ │ ├── .env.example
│ │ ├── .gitignore
│ │ ├── .vscode
│ │ │ ├── launch.json
│ │ │ └── settings.json
│ │ ├── Makefile
│ │ ├── mcp_server_memory_whiteboard
│ │ │ ├── __init__.py
│ │ │ ├── config.py
│ │ │ ├── server.py
│ │ │ └── start.py
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ └── uv.lock
│ ├── mcp-server-office
│ │ ├── .env.example
│ │ ├── .vscode
│ │ │ ├── launch.json
│ │ │ └── settings.json
│ │ ├── build.sh
│ │ ├── data
│ │ │ ├── attachments
│ │ │ │ ├── Daily Game Ideas.txt
│ │ │ │ ├── Frontend Framework Proposal.txt
│ │ │ │ └── ReDoodle.txt
│ │ │ └── word
│ │ │ ├── test_cases.yaml
│ │ │ └── transcripts
│ │ │ ├── transcript_Startup_Idea_1_202503031513.md
│ │ │ ├── transcript_Startup_Idea_2_202503031659.md
│ │ │ └── transcript_Web_Frontends_202502281551.md
│ │ ├── Makefile
│ │ ├── mcp_server
│ │ │ ├── __init__.py
│ │ │ ├── app_interaction
│ │ │ │ ├── __init__.py
│ │ │ │ ├── excel_editor.py
│ │ │ │ ├── powerpoint_editor.py
│ │ │ │ └── word_editor.py
│ │ │ ├── config.py
│ │ │ ├── constants.py
│ │ │ ├── evals
│ │ │ │ ├── __init__.py
│ │ │ │ ├── common.py
│ │ │ │ ├── run_comment_analysis.py
│ │ │ │ ├── run_feedback.py
│ │ │ │ └── run_markdown_edit.py
│ │ │ ├── helpers.py
│ │ │ ├── markdown_edit
│ │ │ │ ├── __init__.py
│ │ │ │ ├── comment_analysis.py
│ │ │ │ ├── feedback_step.py
│ │ │ │ ├── markdown_edit.py
│ │ │ │ └── utils.py
│ │ │ ├── prompts
│ │ │ │ ├── __init__.py
│ │ │ │ ├── comment_analysis.py
│ │ │ │ ├── feedback.py
│ │ │ │ ├── markdown_draft.py
│ │ │ │ └── markdown_edit.py
│ │ │ ├── server.py
│ │ │ ├── start.py
│ │ │ └── types.py
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── tests
│ │ │ └── test_word_editor.py
│ │ └── uv.lock
│ ├── mcp-server-open-deep-research
│ │ ├── .env.example
│ │ ├── .gitignore
│ │ ├── .vscode
│ │ │ ├── launch.json
│ │ │ └── settings.json
│ │ ├── Makefile
│ │ ├── mcp_server
│ │ │ ├── __init__.py
│ │ │ ├── config.py
│ │ │ ├── libs
│ │ │ │ └── open_deep_research
│ │ │ │ ├── cookies.py
│ │ │ │ ├── mdconvert.py
│ │ │ │ ├── run_agents.py
│ │ │ │ ├── text_inspector_tool.py
│ │ │ │ ├── text_web_browser.py
│ │ │ │ └── visual_qa.py
│ │ │ ├── open_deep_research.py
│ │ │ ├── server.py
│ │ │ └── start.py
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ └── uv.lock
│ ├── mcp-server-open-deep-research-clone
│ │ ├── .env.example
│ │ ├── .gitignore
│ │ ├── .vscode
│ │ │ ├── launch.json
│ │ │ └── settings.json
│ │ ├── Makefile
│ │ ├── mcp_server_open_deep_research_clone
│ │ │ ├── __init__.py
│ │ │ ├── azure_openai.py
│ │ │ ├── config.py
│ │ │ ├── logging.py
│ │ │ ├── sampling.py
│ │ │ ├── server.py
│ │ │ ├── start.py
│ │ │ ├── utils.py
│ │ │ └── web_research.py
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── test
│ │ │ └── test_open_deep_research_clone.py
│ │ └── uv.lock
│ ├── mcp-server-template
│ │ ├── .taplo.toml
│ │ ├── .vscode
│ │ │ └── settings.json
│ │ ├── copier.yml
│ │ ├── README.md
│ │ └── template
│ │ └── {{ project_slug }}
│ │ ├── .env.example.jinja
│ │ ├── .gitignore
│ │ ├── .vscode
│ │ │ ├── launch.json.jinja
│ │ │ └── settings.json
│ │ ├── {{ module_name }}
│ │ │ ├── __init__.py
│ │ │ ├── config.py.jinja
│ │ │ ├── server.py.jinja
│ │ │ └── start.py.jinja
│ │ ├── Makefile.jinja
│ │ ├── pyproject.toml.jinja
│ │ └── README.md.jinja
│ ├── mcp-server-vscode
│ │ ├── .eslintrc.cjs
│ │ ├── .gitignore
│ │ ├── .npmrc
│ │ ├── .vscode
│ │ │ ├── extensions.json
│ │ │ ├── launch.json
│ │ │ ├── settings.json
│ │ │ └── tasks.json
│ │ ├── .vscode-test.mjs
│ │ ├── .vscodeignore
│ │ ├── ASSISTANT_BOOTSTRAP.md
│ │ ├── eslint.config.mjs
│ │ ├── images
│ │ │ └── icon.png
│ │ ├── LICENSE
│ │ ├── Makefile
│ │ ├── out
│ │ │ ├── extension.d.ts
│ │ │ ├── extension.js
│ │ │ ├── test
│ │ │ │ ├── extension.test.d.ts
│ │ │ │ └── extension.test.js
│ │ │ ├── tools
│ │ │ │ ├── code_checker.d.ts
│ │ │ │ ├── code_checker.js
│ │ │ │ ├── debug_tools.d.ts
│ │ │ │ ├── debug_tools.js
│ │ │ │ ├── focus_editor.d.ts
│ │ │ │ ├── focus_editor.js
│ │ │ │ ├── search_symbol.d.ts
│ │ │ │ └── search_symbol.js
│ │ │ └── utils
│ │ │ ├── port.d.ts
│ │ │ └── port.js
│ │ ├── package.json
│ │ ├── pnpm-lock.yaml
│ │ ├── prettier.config.cjs
│ │ ├── README.md
│ │ ├── src
│ │ │ ├── extension.d.ts
│ │ │ ├── extension.ts
│ │ │ ├── test
│ │ │ │ ├── extension.test.d.ts
│ │ │ │ └── extension.test.ts
│ │ │ ├── tools
│ │ │ │ ├── code_checker.d.ts
│ │ │ │ ├── code_checker.ts
│ │ │ │ ├── debug_tools.d.ts
│ │ │ │ ├── debug_tools.ts
│ │ │ │ ├── focus_editor.d.ts
│ │ │ │ ├── focus_editor.ts
│ │ │ │ ├── search_symbol.d.ts
│ │ │ │ └── search_symbol.ts
│ │ │ └── utils
│ │ │ ├── port.d.ts
│ │ │ └── port.ts
│ │ ├── tsconfig.json
│ │ ├── tsconfig.tsbuildinfo
│ │ ├── vsc-extension-quickstart.md
│ │ └── webpack.config.js
│ └── mcp-server-web-research
│ ├── .env.example
│ ├── .gitignore
│ ├── .vscode
│ │ ├── launch.json
│ │ └── settings.json
│ ├── Makefile
│ ├── mcp_server_web_research
│ │ ├── __init__.py
│ │ ├── azure_openai.py
│ │ ├── config.py
│ │ ├── logging.py
│ │ ├── sampling.py
│ │ ├── server.py
│ │ ├── start.py
│ │ ├── utils.py
│ │ └── web_research.py
│ ├── pyproject.toml
│ ├── README.md
│ ├── test
│ │ └── test_web_research.py
│ └── uv.lock
├── README.md
├── RESPONSIBLE_AI_FAQ.md
├── ruff.toml
├── SECURITY.md
├── semantic-workbench.code-workspace
├── SUPPORT.md
├── tools
│ ├── build_ai_context_files.py
│ ├── collect_files.py
│ ├── docker
│ │ ├── azure_website_sshd.conf
│ │ ├── docker-entrypoint.sh
│ │ ├── Dockerfile.assistant
│ │ └── Dockerfile.mcp-server
│ ├── makefiles
│ │ ├── docker-assistant.mk
│ │ ├── docker-mcp-server.mk
│ │ ├── docker.mk
│ │ ├── python.mk
│ │ ├── recursive.mk
│ │ └── shell.mk
│ ├── reset-service-data.ps1
│ ├── reset-service-data.sh
│ ├── run-app.ps1
│ ├── run-app.sh
│ ├── run-canonical-agent.ps1
│ ├── run-canonical-agent.sh
│ ├── run-dotnet-examples-with-aspire.sh
│ ├── run-python-example1.sh
│ ├── run-python-example2.ps1
│ ├── run-python-example2.sh
│ ├── run-service.ps1
│ ├── run-service.sh
│ ├── run-workbench-chatbot.ps1
│ └── run-workbench-chatbot.sh
├── workbench-app
│ ├── .dockerignore
│ ├── .env.example
│ ├── .eslintrc.cjs
│ ├── .gitignore
│ ├── .vscode
│ │ ├── launch.json
│ │ └── settings.json
│ ├── docker-entrypoint.sh
│ ├── Dockerfile
│ ├── docs
│ │ ├── APP_DEV_GUIDE.md
│ │ ├── MESSAGE_METADATA.md
│ │ ├── MESSAGE_TYPES.md
│ │ ├── README.md
│ │ └── STATE_INSPECTORS.md
│ ├── index.html
│ ├── Makefile
│ ├── nginx.conf
│ ├── package.json
│ ├── pnpm-lock.yaml
│ ├── prettier.config.cjs
│ ├── public
│ │ └── assets
│ │ ├── background-1-upscaled.jpg
│ │ ├── background-1-upscaled.png
│ │ ├── background-1.jpg
│ │ ├── background-1.png
│ │ ├── background-2.jpg
│ │ ├── background-2.png
│ │ ├── experimental-feature.jpg
│ │ ├── favicon.svg
│ │ ├── workflow-designer-1.jpg
│ │ ├── workflow-designer-outlets.jpg
│ │ ├── workflow-designer-states.jpg
│ │ └── workflow-designer-transitions.jpg
│ ├── README.md
│ ├── run.sh
│ ├── src
│ │ ├── components
│ │ │ ├── App
│ │ │ │ ├── AppFooter.tsx
│ │ │ │ ├── AppHeader.tsx
│ │ │ │ ├── AppMenu.tsx
│ │ │ │ ├── AppView.tsx
│ │ │ │ ├── CodeLabel.tsx
│ │ │ │ ├── CommandButton.tsx
│ │ │ │ ├── ConfirmLeave.tsx
│ │ │ │ ├── ContentExport.tsx
│ │ │ │ ├── ContentImport.tsx
│ │ │ │ ├── CopyButton.tsx
│ │ │ │ ├── DialogControl.tsx
│ │ │ │ ├── DynamicIframe.tsx
│ │ │ │ ├── ErrorListFromAppState.tsx
│ │ │ │ ├── ErrorMessageBar.tsx
│ │ │ │ ├── ExperimentalNotice.tsx
│ │ │ │ ├── FormWidgets
│ │ │ │ │ ├── BaseModelEditorWidget.tsx
│ │ │ │ │ ├── CustomizedArrayFieldTemplate.tsx
│ │ │ │ │ ├── CustomizedFieldTemplate.tsx
│ │ │ │ │ ├── CustomizedObjectFieldTemplate.tsx
│ │ │ │ │ └── InspectableWidget.tsx
│ │ │ │ ├── LabelWithDescription.tsx
│ │ │ │ ├── Loading.tsx
│ │ │ │ ├── MenuItemControl.tsx
│ │ │ │ ├── MiniControl.tsx
│ │ │ │ ├── MyAssistantServiceRegistrations.tsx
│ │ │ │ ├── MyItemsManager.tsx
│ │ │ │ ├── OverflowMenu.tsx
│ │ │ │ ├── PresenceMotionList.tsx
│ │ │ │ ├── ProfileSettings.tsx
│ │ │ │ └── TooltipWrapper.tsx
│ │ │ ├── Assistants
│ │ │ │ ├── ApplyConfigButton.tsx
│ │ │ │ ├── AssistantAdd.tsx
│ │ │ │ ├── AssistantConfigExportButton.tsx
│ │ │ │ ├── AssistantConfigImportButton.tsx
│ │ │ │ ├── AssistantConfiguration.tsx
│ │ │ │ ├── AssistantConfigure.tsx
│ │ │ │ ├── AssistantCreate.tsx
│ │ │ │ ├── AssistantDelete.tsx
│ │ │ │ ├── AssistantDuplicate.tsx
│ │ │ │ ├── AssistantExport.tsx
│ │ │ │ ├── AssistantImport.tsx
│ │ │ │ ├── AssistantRemove.tsx
│ │ │ │ ├── AssistantRename.tsx
│ │ │ │ ├── AssistantServiceInfo.tsx
│ │ │ │ ├── AssistantServiceMetadata.tsx
│ │ │ │ └── MyAssistants.tsx
│ │ │ ├── AssistantServiceRegistrations
│ │ │ │ ├── AssistantServiceRegistrationApiKey.tsx
│ │ │ │ ├── AssistantServiceRegistrationApiKeyReset.tsx
│ │ │ │ ├── AssistantServiceRegistrationCreate.tsx
│ │ │ │ └── AssistantServiceRegistrationRemove.tsx
│ │ │ ├── Conversations
│ │ │ │ ├── Canvas
│ │ │ │ │ ├── AssistantCanvas.tsx
│ │ │ │ │ ├── AssistantCanvasList.tsx
│ │ │ │ │ ├── AssistantInspector.tsx
│ │ │ │ │ ├── AssistantInspectorList.tsx
│ │ │ │ │ └── ConversationCanvas.tsx
│ │ │ │ ├── ChatInputPlugins
│ │ │ │ │ ├── ClearEditorPlugin.tsx
│ │ │ │ │ ├── LexicalMenu.ts
│ │ │ │ │ ├── ParticipantMentionsPlugin.tsx
│ │ │ │ │ ├── TypeaheadMenuPlugin.css
│ │ │ │ │ └── TypeaheadMenuPlugin.tsx
│ │ │ │ ├── ContentRenderers
│ │ │ │ │ ├── CodeContentRenderer.tsx
│ │ │ │ │ ├── ContentListRenderer.tsx
│ │ │ │ │ ├── ContentRenderer.tsx
│ │ │ │ │ ├── DiffRenderer.tsx
│ │ │ │ │ ├── HtmlContentRenderer.tsx
│ │ │ │ │ ├── JsonSchemaContentRenderer.tsx
│ │ │ │ │ ├── MarkdownContentRenderer.tsx
│ │ │ │ │ ├── MarkdownEditorRenderer.tsx
│ │ │ │ │ ├── MermaidContentRenderer.tsx
│ │ │ │ │ ├── MusicABCContentRenderer.css
│ │ │ │ │ └── MusicABCContentRenderer.tsx
│ │ │ │ ├── ContextWindow.tsx
│ │ │ │ ├── ConversationCreate.tsx
│ │ │ │ ├── ConversationDuplicate.tsx
│ │ │ │ ├── ConversationExport.tsx
│ │ │ │ ├── ConversationFileIcon.tsx
│ │ │ │ ├── ConversationRemove.tsx
│ │ │ │ ├── ConversationRename.tsx
│ │ │ │ ├── ConversationShare.tsx
│ │ │ │ ├── ConversationShareCreate.tsx
│ │ │ │ ├── ConversationShareList.tsx
│ │ │ │ ├── ConversationShareView.tsx
│ │ │ │ ├── ConversationsImport.tsx
│ │ │ │ ├── ConversationTranscript.tsx
│ │ │ │ ├── DebugInspector.tsx
│ │ │ │ ├── FileItem.tsx
│ │ │ │ ├── FileList.tsx
│ │ │ │ ├── InputAttachmentList.tsx
│ │ │ │ ├── InputOptionsControl.tsx
│ │ │ │ ├── InteractHistory.tsx
│ │ │ │ ├── InteractInput.tsx
│ │ │ │ ├── Message
│ │ │ │ │ ├── AttachmentSection.tsx
│ │ │ │ │ ├── ContentRenderer.tsx
│ │ │ │ │ ├── ContentSafetyNotice.tsx
│ │ │ │ │ ├── InteractMessage.tsx
│ │ │ │ │ ├── MessageActions.tsx
│ │ │ │ │ ├── MessageBase.tsx
│ │ │ │ │ ├── MessageBody.tsx
│ │ │ │ │ ├── MessageContent.tsx
│ │ │ │ │ ├── MessageFooter.tsx
│ │ │ │ │ ├── MessageHeader.tsx
│ │ │ │ │ ├── NotificationAccordion.tsx
│ │ │ │ │ └── ToolResultMessage.tsx
│ │ │ │ ├── MessageDelete.tsx
│ │ │ │ ├── MessageLink.tsx
│ │ │ │ ├── MyConversations.tsx
│ │ │ │ ├── MyShares.tsx
│ │ │ │ ├── ParticipantAvatar.tsx
│ │ │ │ ├── ParticipantAvatarGroup.tsx
│ │ │ │ ├── ParticipantItem.tsx
│ │ │ │ ├── ParticipantList.tsx
│ │ │ │ ├── ParticipantStatus.tsx
│ │ │ │ ├── RewindConversation.tsx
│ │ │ │ ├── ShareRemove.tsx
│ │ │ │ ├── SpeechButton.tsx
│ │ │ │ └── ToolCalls.tsx
│ │ │ └── FrontDoor
│ │ │ ├── Chat
│ │ │ │ ├── AssistantDrawer.tsx
│ │ │ │ ├── CanvasDrawer.tsx
│ │ │ │ ├── Chat.tsx
│ │ │ │ ├── ChatCanvas.tsx
│ │ │ │ ├── ChatControls.tsx
│ │ │ │ └── ConversationDrawer.tsx
│ │ │ ├── Controls
│ │ │ │ ├── AssistantCard.tsx
│ │ │ │ ├── AssistantSelector.tsx
│ │ │ │ ├── AssistantServiceSelector.tsx
│ │ │ │ ├── ConversationItem.tsx
│ │ │ │ ├── ConversationList.tsx
│ │ │ │ ├── ConversationListOptions.tsx
│ │ │ │ ├── NewConversationButton.tsx
│ │ │ │ ├── NewConversationForm.tsx
│ │ │ │ └── SiteMenuButton.tsx
│ │ │ ├── GlobalContent.tsx
│ │ │ └── MainContent.tsx
│ │ ├── Constants.ts
│ │ ├── global.d.ts
│ │ ├── index.css
│ │ ├── libs
│ │ │ ├── AppStorage.ts
│ │ │ ├── AuthHelper.ts
│ │ │ ├── EventSubscriptionManager.ts
│ │ │ ├── Theme.ts
│ │ │ ├── useAssistantCapabilities.ts
│ │ │ ├── useChatCanvasController.ts
│ │ │ ├── useConversationEvents.ts
│ │ │ ├── useConversationUtility.ts
│ │ │ ├── useCreateConversation.ts
│ │ │ ├── useDebugComponentLifecycle.ts
│ │ │ ├── useDragAndDrop.ts
│ │ │ ├── useEnvironment.ts
│ │ │ ├── useExportUtility.ts
│ │ │ ├── useHistoryUtility.ts
│ │ │ ├── useKeySequence.ts
│ │ │ ├── useMediaQuery.ts
│ │ │ ├── useMicrosoftGraph.ts
│ │ │ ├── useNotify.tsx
│ │ │ ├── useParticipantUtility.tsx
│ │ │ ├── useSiteUtility.ts
│ │ │ ├── useWorkbenchEventSource.ts
│ │ │ ├── useWorkbenchService.ts
│ │ │ └── Utility.ts
│ │ ├── main.tsx
│ │ ├── models
│ │ │ ├── Assistant.ts
│ │ │ ├── AssistantCapability.ts
│ │ │ ├── AssistantServiceInfo.ts
│ │ │ ├── AssistantServiceRegistration.ts
│ │ │ ├── Config.ts
│ │ │ ├── Conversation.ts
│ │ │ ├── ConversationFile.ts
│ │ │ ├── ConversationMessage.ts
│ │ │ ├── ConversationMessageDebug.ts
│ │ │ ├── ConversationParticipant.ts
│ │ │ ├── ConversationShare.ts
│ │ │ ├── ConversationShareRedemption.ts
│ │ │ ├── ConversationState.ts
│ │ │ ├── ConversationStateDescription.ts
│ │ │ ├── ServiceEnvironment.ts
│ │ │ └── User.ts
│ │ ├── redux
│ │ │ ├── app
│ │ │ │ ├── hooks.ts
│ │ │ │ ├── rtkQueryErrorLogger.ts
│ │ │ │ └── store.ts
│ │ │ └── features
│ │ │ ├── app
│ │ │ │ ├── appSlice.ts
│ │ │ │ └── AppState.ts
│ │ │ ├── chatCanvas
│ │ │ │ ├── chatCanvasSlice.ts
│ │ │ │ └── ChatCanvasState.ts
│ │ │ ├── localUser
│ │ │ │ ├── localUserSlice.ts
│ │ │ │ └── LocalUserState.ts
│ │ │ └── settings
│ │ │ ├── settingsSlice.ts
│ │ │ └── SettingsState.ts
│ │ ├── Root.tsx
│ │ ├── routes
│ │ │ ├── AcceptTerms.tsx
│ │ │ ├── AssistantEditor.tsx
│ │ │ ├── AssistantServiceRegistrationEditor.tsx
│ │ │ ├── Dashboard.tsx
│ │ │ ├── ErrorPage.tsx
│ │ │ ├── FrontDoor.tsx
│ │ │ ├── Login.tsx
│ │ │ ├── Settings.tsx
│ │ │ ├── ShareRedeem.tsx
│ │ │ └── Shares.tsx
│ │ ├── services
│ │ │ └── workbench
│ │ │ ├── assistant.ts
│ │ │ ├── assistantService.ts
│ │ │ ├── conversation.ts
│ │ │ ├── file.ts
│ │ │ ├── index.ts
│ │ │ ├── participant.ts
│ │ │ ├── share.ts
│ │ │ ├── state.ts
│ │ │ └── workbench.ts
│ │ └── vite-env.d.ts
│ ├── tools
│ │ └── filtered-ts-prune.cjs
│ ├── tsconfig.json
│ └── vite.config.ts
└── workbench-service
├── .env.example
├── .vscode
│ ├── extensions.json
│ ├── launch.json
│ └── settings.json
├── alembic.ini
├── devdb
│ ├── docker-compose.yaml
│ └── postgresql-init.sh
├── Dockerfile
├── Makefile
├── migrations
│ ├── env.py
│ ├── README
│ ├── script.py.mako
│ └── versions
│ ├── 2024_09_19_000000_69dcda481c14_init.py
│ ├── 2024_09_19_190029_dffb1d7e219a_file_version_filename.py
│ ├── 2024_09_20_204130_b29524775484_share.py
│ ├── 2024_10_30_231536_039bec8edc33_index_message_type.py
│ ├── 2024_11_04_204029_5149c7fb5a32_conversationmessagedebug.py
│ ├── 2024_11_05_015124_245baf258e11_double_check_debugs.py
│ ├── 2024_11_25_191056_a106de176394_drop_workflow.py
│ ├── 2025_03_19_140136_aaaf792d4d72_set_user_title_set.py
│ ├── 2025_03_21_153250_3763629295ad_add_assistant_template_id.py
│ ├── 2025_05_19_163613_b2f86e981885_delete_context_transfer_assistants.py
│ └── 2025_06_18_174328_503c739152f3_delete_knowlege_transfer_assistants.py
├── pyproject.toml
├── README.md
├── semantic_workbench_service
│ ├── __init__.py
│ ├── api.py
│ ├── assistant_api_key.py
│ ├── auth.py
│ ├── azure_speech.py
│ ├── config.py
│ ├── controller
│ │ ├── __init__.py
│ │ ├── assistant_service_client_pool.py
│ │ ├── assistant_service_registration.py
│ │ ├── assistant.py
│ │ ├── conversation_share.py
│ │ ├── conversation.py
│ │ ├── convert.py
│ │ ├── exceptions.py
│ │ ├── export_import.py
│ │ ├── file.py
│ │ ├── participant.py
│ │ └── user.py
│ ├── db.py
│ ├── event.py
│ ├── files.py
│ ├── logging_config.py
│ ├── middleware.py
│ ├── query.py
│ ├── service_user_principals.py
│ ├── service.py
│ └── start.py
├── tests
│ ├── __init__.py
│ ├── conftest.py
│ ├── docker-compose.yaml
│ ├── test_assistant_api_key.py
│ ├── test_files.py
│ ├── test_integration.py
│ ├── test_middleware.py
│ ├── test_migrations.py
│ ├── test_workbench_service.py
│ └── types.py
└── uv.lock
```
# Files
--------------------------------------------------------------------------------
/mcp-servers/mcp-server-office/mcp_server/app_interaction/word_editor.py:
--------------------------------------------------------------------------------
```python
1 | # Copyright (c) Microsoft. All rights reserved.
2 |
3 |
4 | import sys
5 |
6 | from mcp_server.types import WordCommentData
7 |
8 |
9 | def get_word_app():
10 | if sys.platform != "win32":
11 | raise EnvironmentError("This script only works on Windows.")
12 |
13 | import win32com.client as win32
14 |
15 | """Connect to Word if it is running, or start a new instance."""
16 | try:
17 | # Try connecting to an existing instance of Word
18 | word = win32.GetActiveObject("Word.Application")
19 | except Exception:
20 | # If not running, create a new instance
21 | word = win32.Dispatch("Word.Application")
22 | # Make sure Word is visible so you can see your edits
23 | word.Visible = True
24 | return word
25 |
26 |
27 | def get_active_document(word):
28 | """Return an active Word document, or create one if none is open."""
29 | if word.Documents.Count == 0:
30 | # If there are no documents, add a new one
31 | return word.Documents.Add()
32 | else:
33 | return word.ActiveDocument
34 |
35 |
36 | def get_document_content(doc):
37 | """Return the content of the document."""
38 | return doc.Content.Text
39 |
40 |
41 | def replace_document_content(doc, content):
42 | """Replace the content of the document with the given content."""
43 | doc.Content.Text = content
44 |
45 |
46 | def get_markdown_representation(doc, include_comments: bool = False) -> str:
47 | """
48 | Get the markdown representation of the document.
49 | Supports Headings, plaintext, bulleted/numbered lists, bold, italic, and code blocks.
50 | """
51 | markdown_text = []
52 | in_code_block = False
53 | for i in range(1, doc.Paragraphs.Count + 1):
54 | paragraph = doc.Paragraphs(i)
55 | style_name = paragraph.Style.NameLocal
56 |
57 | # Handle Code style for code blocks
58 | if style_name == "Code":
59 | if not in_code_block:
60 | markdown_text.append("```")
61 | in_code_block = True
62 | markdown_text.append(paragraph.Range.Text.rstrip())
63 | continue
64 | elif in_code_block:
65 | # Close code block when style changes
66 | markdown_text.append("```")
67 | in_code_block = False
68 |
69 | # Process paragraph style first
70 | prefix = ""
71 | if "Heading" in style_name:
72 | try:
73 | level = int(style_name.split("Heading")[1].strip())
74 | prefix = "#" * level + " "
75 | except (ValueError, IndexError):
76 | pass
77 |
78 | para_text = ""
79 | para_range = paragraph.Range
80 | # For performance, check if there's any formatting at all
81 | if para_range.Text.strip():
82 | if para_range.Font.Bold or para_range.Font.Italic:
83 | # Process words instead of characters for better performance
84 | current_run = {"text": "", "bold": False, "italic": False}
85 |
86 | # Get all words in this paragraph
87 | for w in range(1, para_range.Words.Count + 1):
88 | word_range = para_range.Words(w)
89 | word_text = word_range.Text # Keep original with potential spaces
90 |
91 | # Skip if empty
92 | if not word_text.strip():
93 | continue
94 |
95 | # Get formatting for this word
96 | is_bold = word_range.Font.Bold
97 | is_italic = word_range.Font.Italic
98 |
99 | # If formatting changed, start a new run
100 | if is_bold != current_run["bold"] or is_italic != current_run["italic"]:
101 | # Finish the previous run if it exists
102 | if current_run["text"]:
103 | if current_run["bold"] and current_run["italic"]:
104 | para_text += f"***{current_run['text'].rstrip()}***"
105 | elif current_run["bold"]:
106 | para_text += f"**{current_run['text'].rstrip()}**"
107 | elif current_run["italic"]:
108 | para_text += f"*{current_run['text'].rstrip()}*"
109 | else:
110 | para_text += current_run["text"].rstrip()
111 |
112 | # Add a space if the previous run ended with a space
113 | if current_run["text"].endswith(" "):
114 | para_text += " "
115 |
116 | # Start a new run with the current word
117 | current_run = {"text": word_text, "bold": is_bold, "italic": is_italic}
118 | else:
119 | # Continue the current run - but be careful with spaces
120 | if current_run["text"]:
121 | current_run["text"] += word_text
122 | else:
123 | current_run["text"] = word_text
124 |
125 | # Process the final run
126 | if current_run["text"]:
127 | if current_run["bold"] and current_run["italic"]:
128 | para_text += f"***{current_run['text'].rstrip()}***"
129 | elif current_run["bold"]:
130 | para_text += f"**{current_run['text'].rstrip()}**"
131 | elif current_run["italic"]:
132 | para_text += f"*{current_run['text'].rstrip()}*"
133 | else:
134 | para_text += current_run["text"].rstrip()
135 | else:
136 | # No special formatting, just get the text
137 | para_text = para_range.Text.strip()
138 | else:
139 | para_text = para_range.Text.strip()
140 |
141 | if not para_text:
142 | continue
143 |
144 | # Handle list formatting
145 | if paragraph.Range.ListFormat.ListType == 2:
146 | markdown_text.append(f"- {para_text}")
147 | elif paragraph.Range.ListFormat.ListType == 3:
148 | markdown_text.append(f"1. {para_text}")
149 | else:
150 | markdown_text.append(f"{prefix}{para_text}")
151 |
152 | # Close any open code block at the end of document
153 | if in_code_block:
154 | markdown_text.append("```")
155 |
156 | if include_comments:
157 | comment_section = get_comments_markdown_representation(doc)
158 | if comment_section:
159 | markdown_text.append(comment_section)
160 |
161 | return "\n".join(markdown_text)
162 |
163 |
164 | def _write_formatted_text(selection, text):
165 | """
166 | Helper function to write text with markdown formatting (bold, italic) to the document.
167 | Processes text in chunks for better performance.
168 |
169 | Args:
170 | selection: Word selection object where text will be inserted
171 | text: Markdown-formatted text string to process
172 | """
173 |
174 | segments = []
175 | i = 0
176 | while i < len(text):
177 | # Bold+Italic (***text***)
178 | if i + 2 < len(text) and text[i : i + 3] == "***" and "***" in text[i + 3 :]:
179 | end_pos = text.find("***", i + 3)
180 | if end_pos != -1:
181 | segments.append(("bold_italic", text[i + 3 : end_pos]))
182 | i = end_pos + 3
183 | continue
184 |
185 | # Bold (**text**)
186 | elif i + 1 < len(text) and text[i : i + 2] == "**" and "**" in text[i + 2 :]:
187 | end_pos = text.find("**", i + 2)
188 | if end_pos != -1:
189 | segments.append(("bold", text[i + 2 : end_pos]))
190 | i = end_pos + 2
191 | continue
192 |
193 | # Italic (*text*)
194 | elif text[i] == "*" and i + 1 < len(text) and text[i + 1] != "*" and "*" in text[i + 1 :]:
195 | end_pos = text.find("*", i + 1)
196 | if end_pos != -1:
197 | segments.append(("italic", text[i + 1 : end_pos]))
198 | i = end_pos + 1
199 | continue
200 |
201 | # Find the next special marker or end of string
202 | next_marker = float("inf")
203 | for marker in ["***", "**", "*"]:
204 | pos = text.find(marker, i)
205 | if pos != -1 and pos < next_marker:
206 | next_marker = pos
207 |
208 | # Add plain text segment
209 | if next_marker == float("inf"):
210 | segments.append(("plain", text[i:]))
211 | break
212 | else:
213 | segments.append(("plain", text[i:next_marker]))
214 | i = next_marker
215 |
216 | # Now write all segments with minimal formatting changes
217 | current_format = None
218 | for format_type, content in segments:
219 | if format_type != current_format:
220 | selection.Font.Bold = False
221 | selection.Font.Italic = False
222 |
223 | if format_type == "bold" or format_type == "bold_italic":
224 | selection.Font.Bold = True
225 | if format_type == "italic" or format_type == "bold_italic":
226 | selection.Font.Italic = True
227 |
228 | current_format = format_type
229 | selection.TypeText(content)
230 |
231 | selection.Font.Bold = False
232 | selection.Font.Italic = False
233 |
234 |
235 | def write_markdown_to_document(doc, markdown_text: str) -> None:
236 | """Writes markdown text to a Word document with appropriate formatting.
237 |
238 | Converts markdown syntax to Word formatting, including:
239 | - Headings (# to Heading styles)
240 | - Lists (bulleted and numbered)
241 | - Text formatting (bold, italic)
242 | - Code blocks (``` to Code style)
243 | """
244 | comments = get_document_comments(doc)
245 |
246 | doc.Content.Delete()
247 |
248 | word_app = doc.Application
249 | selection = word_app.Selection
250 |
251 | # Create "Code" style if it doesn't exist
252 | try:
253 | # Check if Code style exists
254 | code_style = word_app.ActiveDocument.Styles("Code")
255 | except Exception:
256 | # Create the Code style
257 | code_style = word_app.ActiveDocument.Styles.Add("Code", 1)
258 | code_style.Font.Name = "Cascadia Code"
259 | code_style.Font.Size = 10
260 | code_style.ParagraphFormat.SpaceAfter = 0
261 | code_style.QuickStyle = True
262 | code_style.LinkStyle = True
263 |
264 | # This fixes an issue where if there are comments on a doc, there is no selection
265 | # which causes insertion to fail
266 | doc.Range(0, 0).Select()
267 |
268 | # Ensure we start with normal style
269 | selection.Style = word_app.ActiveDocument.Styles("Normal")
270 |
271 | lines = markdown_text.split("\n")
272 | i = 0
273 | while i < len(lines):
274 | line = lines[i].strip()
275 | i += 1
276 |
277 | if not line:
278 | continue
279 |
280 | if line.startswith("```"):
281 | i_start = i
282 |
283 | # Find the end of the code block
284 | while i < len(lines) and not lines[i].strip().startswith("```"):
285 | i += 1
286 |
287 | # Process all lines in the code block
288 | for j in range(i_start, i):
289 | code_line = lines[j]
290 | selection.TypeText(code_line)
291 | selection.Style = word_app.ActiveDocument.Styles("Code")
292 | selection.TypeParagraph()
293 |
294 | # Skip the closing code fence
295 | if i < len(lines):
296 | i += 1
297 |
298 | # Restore normal style for next paragraph
299 | selection.Style = word_app.ActiveDocument.Styles("Normal")
300 | continue
301 |
302 | # Check if the line is a heading
303 | if line.startswith("#"):
304 | heading_level = 0
305 | for char in line:
306 | if char == "#":
307 | heading_level += 1
308 | else:
309 | break
310 |
311 | # Remove the # characters and any leading space
312 | text = line[heading_level:].strip()
313 |
314 | _write_formatted_text(selection, text)
315 |
316 | # Get the current paragraph and set its style
317 | current_paragraph = selection.Paragraphs.Last
318 | if 1 <= heading_level <= 9: # Word supports Heading 1-9
319 | current_paragraph.Style = f"Heading {heading_level}"
320 |
321 | selection.TypeParagraph()
322 |
323 | # Check if line is a bulleted list item
324 | elif line.startswith(("- ", "* ")):
325 | # Extract the text after the bullet marker
326 | text = line[2:].strip()
327 |
328 | _write_formatted_text(selection, text)
329 |
330 | # Apply bullet formatting
331 | selection.Range.ListFormat.ApplyBulletDefault()
332 |
333 | selection.TypeParagraph()
334 | selection.Style = word_app.ActiveDocument.Styles("Normal")
335 |
336 | # Check if line is a numbered list item
337 | elif line.strip().startswith(tuple(f"{i}. " for i in range(1, 100)) + tuple(f"{i})" for i in range(1, 100))):
338 | # Extract the text after the number and period/parenthesis
339 | text = ""
340 | if ". " in line:
341 | parts = line.strip().split(". ", 1)
342 | if len(parts) > 1:
343 | text = parts[1]
344 | elif ") " in line:
345 | parts = line.strip().split(") ", 1)
346 | if len(parts) > 1:
347 | text = parts[1]
348 |
349 | if text:
350 | _write_formatted_text(selection, text)
351 |
352 | # Apply numbered list formatting
353 | selection.Range.ListFormat.ApplyNumberDefault()
354 | selection.TypeParagraph()
355 | selection.Style = word_app.ActiveDocument.Styles("Normal")
356 | else:
357 | # If parsing failed, just add the line as normal text
358 | _write_formatted_text(selection, line)
359 | selection.TypeParagraph()
360 |
361 | else:
362 | # Regular paragraph text with formatting support
363 | _write_formatted_text(selection, line)
364 | selection.TypeParagraph()
365 |
366 | # Move cursor to the beginning of the document
367 | doc.Range(0, 0).Select()
368 |
369 | # Reapply comments. Note this implicitly will remove any comments that have locations not in the new text.
370 | for comment in comments:
371 | add_document_comment(doc, comment)
372 |
373 |
374 | def add_document_comment(
375 | doc,
376 | comment_data: WordCommentData,
377 | ) -> bool:
378 | """
379 | Add a comment to specific text within a Word document.
380 |
381 | Returns:
382 | bool: True if comment was added successfully, False otherwise
383 | """
384 | try:
385 | content_range = doc.Content
386 | found_range = None
387 |
388 | # Find the specified occurrence of the text
389 | found_count = 0
390 |
391 | # Use FindText to locate the text
392 | content_range.Find.ClearFormatting()
393 | found = content_range.Find.Execute(FindText=comment_data.location_text, MatchCase=True, MatchWholeWord=False)
394 |
395 | while found:
396 | found_count += 1
397 | if found_count == comment_data.occurrence:
398 | found_range = content_range.Duplicate
399 | break
400 |
401 | # Continue searching from the end of the current match
402 | content_range.Collapse(Direction=0) # Collapse to end
403 | found = content_range.Find.Execute(
404 | FindText=comment_data.location_text, MatchCase=True, MatchWholeWord=False
405 | )
406 |
407 | if not found_range:
408 | return False
409 |
410 | # Add a comment to the found range
411 | comment = doc.Comments.Add(Range=found_range, Text=comment_data.comment_text)
412 | if comment_data.author:
413 | comment.Author = comment_data.author
414 | return True
415 | except Exception:
416 | return False
417 |
418 |
419 | def get_document_comments(doc) -> list[WordCommentData]:
420 | """
421 | Retrieve all comments from a Word document.
422 | """
423 | comments: list[WordCommentData] = []
424 |
425 | try:
426 | if doc.Comments.Count == 0:
427 | return comments
428 |
429 | for i in range(1, doc.Comments.Count + 1):
430 | try:
431 | comment = doc.Comments(i)
432 |
433 | comment_text = ""
434 | try:
435 | comment_text = comment.Range.Text
436 | except Exception:
437 | pass
438 |
439 | author = "Unknown"
440 | try:
441 | author = comment.Author
442 | except Exception:
443 | pass
444 |
445 | date = ""
446 | try:
447 | date = str(comment.Date)
448 | except Exception:
449 | pass
450 |
451 | reference_text = ""
452 | try:
453 | if hasattr(comment, "Scope"):
454 | reference_text = comment.Scope.Text
455 | except Exception:
456 | pass
457 |
458 | comment_info = WordCommentData(
459 | id=str(i),
460 | comment_text=comment_text,
461 | location_text=reference_text,
462 | date=date,
463 | author=author,
464 | )
465 | comments.append(comment_info)
466 | except Exception:
467 | continue
468 |
469 | return comments
470 | except Exception as e:
471 | print(f"Error retrieving comments: {e}")
472 | return comments
473 |
474 |
475 | def get_comments_markdown_representation(doc) -> str:
476 | comments = get_document_comments(doc)
477 |
478 | if not comments:
479 | return ""
480 |
481 | comment_section = "\n\n<comments>\n"
482 | for i, comment in enumerate(comments, 1):
483 | comment_section += f'<comment id={i} author="{comment.author}">\n'
484 | comment_section += f" <location_text>{comment.location_text}</location_text>\n"
485 | comment_section += f" <comment_text>{comment.comment_text}</comment_text>\n"
486 | comment_section += "</comment>\n"
487 | comment_section.rstrip()
488 | comment_section += "</comments>"
489 | return comment_section
490 |
491 |
492 | def delete_comments_containing_text(doc, search_text: str, case_sensitive: bool = False) -> int:
493 | """
494 | Delete comments containing specific text.
495 |
496 | Args:
497 | doc: Word document object
498 | search_text: Text to search for in comments
499 | case_sensitive: Whether the search should be case-sensitive
500 |
501 | Returns:
502 | int: Number of comments deleted
503 | """
504 | deleted_count = 0
505 | try:
506 | if not case_sensitive:
507 | search_text = search_text.lower()
508 |
509 | # Work backwards to avoid index shifting issues when deleting
510 | for i in range(doc.Comments.Count, 0, -1):
511 | comment = doc.Comments(i)
512 | comment_text = comment.Range.Text
513 |
514 | if not case_sensitive:
515 | comment_text = comment_text.lower()
516 |
517 | if search_text in comment_text:
518 | comment.Delete()
519 | deleted_count += 1
520 | return deleted_count
521 | except Exception:
522 | return deleted_count
523 |
```
--------------------------------------------------------------------------------
/workbench-app/src/components/Conversations/ChatInputPlugins/LexicalMenu.ts:
--------------------------------------------------------------------------------
```typescript
1 | // Copyright (c) Microsoft. All rights reserved.
2 | // Based on code from: https://github.com/facebook/lexical/blob/main/packages/lexical-react/src/shared/LexicalMenu.ts
3 |
4 | import { useLexicalComposerContext } from '@fluentui-copilot/react-copilot';
5 | import { mergeRegister } from '@lexical/utils';
6 | import {
7 | $getSelection,
8 | $isRangeSelection,
9 | COMMAND_PRIORITY_LOW,
10 | CommandListenerPriority,
11 | KEY_ARROW_DOWN_COMMAND,
12 | KEY_ARROW_UP_COMMAND,
13 | KEY_ENTER_COMMAND,
14 | KEY_ESCAPE_COMMAND,
15 | KEY_TAB_COMMAND,
16 | LexicalEditor,
17 | TextNode,
18 | } from 'lexical';
19 | import React from 'react';
20 | import { SCROLL_TYPEAHEAD_OPTION_INTO_VIEW_COMMAND } from './TypeaheadMenuPlugin';
21 |
22 | export type MenuTextMatch = {
23 | leadOffset: number;
24 | matchingString: string;
25 | replaceableString: string;
26 | };
27 |
28 | export type MenuResolution = {
29 | match?: MenuTextMatch;
30 | getRect: () => DOMRect;
31 | };
32 |
33 | export class MenuOption {
34 | key: string;
35 | ref?: React.MutableRefObject<HTMLElement | null>;
36 |
37 | constructor(key: string) {
38 | this.key = key;
39 | this.ref = { current: null };
40 | this.setRefElement = this.setRefElement.bind(this);
41 | }
42 |
43 | setRefElement(element: HTMLElement | null) {
44 | this.ref = { current: element };
45 | }
46 | }
47 |
48 | export type MenuRenderFn<TOption extends MenuOption> = (
49 | anchorElementRef: React.MutableRefObject<HTMLElement | null>,
50 | itemProps: {
51 | selectedIndex: number | null;
52 | selectOptionAndCleanUp: (option: TOption) => void;
53 | setHighlightedIndex: (index: number) => void;
54 | options: Array<TOption>;
55 | },
56 | matchingString: string | null,
57 | ) => React.ReactPortal | JSX.Element | null;
58 |
59 | const scrollIntoViewIfNeeded = (target: HTMLElement) => {
60 | const typeaheadContainerNode = document.getElementById('typeahead-menu');
61 | if (!typeaheadContainerNode) {
62 | return;
63 | }
64 |
65 | const typeaheadRect = typeaheadContainerNode.getBoundingClientRect();
66 |
67 | if (typeaheadRect.top + typeaheadRect.height > window.innerHeight) {
68 | typeaheadContainerNode.scrollIntoView({
69 | block: 'center',
70 | });
71 | }
72 |
73 | if (typeaheadRect.top < 0) {
74 | typeaheadContainerNode.scrollIntoView({
75 | block: 'center',
76 | });
77 | }
78 |
79 | target.scrollIntoView({ block: 'nearest' });
80 | };
81 |
82 | /**
83 | * Walk backwards along user input and forward through entity title to try
84 | * and replace more of the user's text with entity.
85 | */
86 | const getFullMatchOffset = (documentText: string, entryText: string, offset: number): number => {
87 | let triggerOffset = offset;
88 | for (let i = triggerOffset; i <= entryText.length; i++) {
89 | if (documentText.slice(-i) === entryText.slice(0, i)) {
90 | triggerOffset = i;
91 | }
92 | }
93 | return triggerOffset;
94 | };
95 |
96 | /**
97 | * Split Lexical TextNode and return a new TextNode only containing matched text.
98 | * Common use cases include: removing the node, replacing with a new node.
99 | */
100 | const $splitNodeContainingQuery = (match: MenuTextMatch): TextNode | null => {
101 | const selection = $getSelection();
102 | if (!$isRangeSelection(selection) || !selection.isCollapsed()) {
103 | return null;
104 | }
105 | const anchor = selection.anchor;
106 | if (anchor.type !== 'text') {
107 | return null;
108 | }
109 | const anchorNode = anchor.getNode();
110 | if (!anchorNode.isSimpleText()) {
111 | return null;
112 | }
113 | const selectionOffset = anchor.offset;
114 | const textContent = anchorNode.getTextContent().slice(0, selectionOffset);
115 | const characterOffset = match.replaceableString.length;
116 | const queryOffset = getFullMatchOffset(textContent, match.matchingString, characterOffset);
117 | const startOffset = selectionOffset - queryOffset;
118 | if (startOffset < 0) {
119 | return null;
120 | }
121 | let newNode;
122 | if (startOffset === 0) {
123 | [newNode] = anchorNode.splitText(selectionOffset);
124 | } else {
125 | [, newNode] = anchorNode.splitText(startOffset, selectionOffset);
126 | }
127 |
128 | return newNode;
129 | };
130 |
131 | // Got from https://stackoverflow.com/a/42543908/2013580
132 | export const getScrollParent = (element: HTMLElement, includeHidden: boolean): HTMLElement | HTMLBodyElement => {
133 | let style = getComputedStyle(element);
134 | const excludeStaticParent = style.position === 'absolute';
135 | const overflowRegex = includeHidden ? /(auto|scroll|hidden)/ : /(auto|scroll)/;
136 | if (style.position === 'fixed') {
137 | return document.body;
138 | }
139 | for (let parent: HTMLElement | null = element; (parent = parent.parentElement); ) {
140 | style = getComputedStyle(parent);
141 | if (excludeStaticParent && style.position === 'static') {
142 | continue;
143 | }
144 | if (overflowRegex.test(style.overflow + style.overflowY + style.overflowX)) {
145 | return parent;
146 | }
147 | }
148 | return document.body;
149 | };
150 |
151 | const isTriggerVisibleInNearestScrollContainer = (
152 | targetElement: HTMLElement,
153 | containerElement: HTMLElement,
154 | ): boolean => {
155 | const tRect = targetElement.getBoundingClientRect();
156 | const cRect = containerElement.getBoundingClientRect();
157 | return tRect.top > cRect.top && tRect.top < cRect.bottom;
158 | };
159 |
160 | // Reposition the menu on scroll, window resize, and element resize.
161 | export const useDynamicPositioning = (
162 | resolution: MenuResolution | null,
163 | targetElement: HTMLElement | null,
164 | onReposition: () => void,
165 | onVisibilityChange?: (isInView: boolean) => void,
166 | ) => {
167 | const [editor] = useLexicalComposerContext();
168 | React.useEffect(() => {
169 | if (targetElement != null && resolution != null) {
170 | const rootElement = editor.getRootElement();
171 | const rootScrollParent = rootElement != null ? getScrollParent(rootElement, false) : document.body;
172 | let ticking = false;
173 | let previousIsInView = isTriggerVisibleInNearestScrollContainer(targetElement, rootScrollParent);
174 | const handleScroll = () => {
175 | if (!ticking) {
176 | window.requestAnimationFrame(() => {
177 | onReposition();
178 | ticking = false;
179 | });
180 | ticking = true;
181 | }
182 | const isInView = isTriggerVisibleInNearestScrollContainer(targetElement, rootScrollParent);
183 | if (isInView !== previousIsInView) {
184 | previousIsInView = isInView;
185 | if (onVisibilityChange != null) {
186 | onVisibilityChange(isInView);
187 | }
188 | }
189 | };
190 | const resizeObserver = new ResizeObserver(onReposition);
191 | window.addEventListener('resize', onReposition);
192 | document.addEventListener('scroll', handleScroll, {
193 | capture: true,
194 | passive: true,
195 | });
196 | resizeObserver.observe(targetElement);
197 | return () => {
198 | resizeObserver.unobserve(targetElement);
199 | window.removeEventListener('resize', onReposition);
200 | document.removeEventListener('scroll', handleScroll, true);
201 | };
202 | }
203 |
204 | return;
205 | }, [targetElement, editor, onVisibilityChange, onReposition, resolution]);
206 | };
207 |
208 | export const LexicalMenu = <TOption extends MenuOption>({
209 | close,
210 | editor,
211 | anchorElementRef,
212 | resolution,
213 | options,
214 | menuRenderFn,
215 | onSelectOption,
216 | shouldSplitNodeWithQuery = false,
217 | commandPriority = COMMAND_PRIORITY_LOW,
218 | }: {
219 | close: () => void;
220 | editor: LexicalEditor;
221 | anchorElementRef: React.MutableRefObject<HTMLElement>;
222 | resolution: MenuResolution;
223 | options: Array<TOption>;
224 | shouldSplitNodeWithQuery?: boolean;
225 | menuRenderFn: MenuRenderFn<TOption>;
226 | onSelectOption: (
227 | option: TOption,
228 | textNodeContainingQuery: TextNode | null,
229 | closeMenu: () => void,
230 | matchingString: string,
231 | ) => void;
232 | commandPriority?: CommandListenerPriority;
233 | }): JSX.Element | null => {
234 | const [selectedIndex, setHighlightedIndex] = React.useState<null | number>(null);
235 |
236 | const matchingString = resolution.match && resolution.match.matchingString;
237 |
238 | React.useEffect(() => {
239 | setHighlightedIndex(0);
240 | }, [matchingString]);
241 |
242 | const selectOptionAndCleanUp = React.useCallback(
243 | (selectedEntry: TOption) => {
244 | editor.update(() => {
245 | const textNodeContainingQuery =
246 | resolution.match != null && shouldSplitNodeWithQuery
247 | ? $splitNodeContainingQuery(resolution.match)
248 | : null;
249 |
250 | onSelectOption(
251 | selectedEntry,
252 | textNodeContainingQuery,
253 | close,
254 | resolution.match ? resolution.match.matchingString : '',
255 | );
256 | });
257 | },
258 | [editor, shouldSplitNodeWithQuery, resolution.match, onSelectOption, close],
259 | );
260 |
261 | const updateSelectedIndex = React.useCallback(
262 | (index: number) => {
263 | const rootElem = editor.getRootElement();
264 | if (rootElem !== null) {
265 | rootElem.setAttribute('aria-activedescendant', 'typeahead-item-' + index);
266 | setHighlightedIndex(index);
267 | }
268 | },
269 | [editor],
270 | );
271 |
272 | React.useEffect(() => {
273 | return () => {
274 | const rootElem = editor.getRootElement();
275 | if (rootElem !== null) {
276 | rootElem.removeAttribute('aria-activedescendant');
277 | }
278 | };
279 | }, [editor]);
280 |
281 | React.useLayoutEffect(() => {
282 | if (options === null) {
283 | setHighlightedIndex(null);
284 | } else if (selectedIndex === null) {
285 | updateSelectedIndex(0);
286 | }
287 | }, [options, selectedIndex, updateSelectedIndex]);
288 |
289 | React.useEffect(() => {
290 | return mergeRegister(
291 | editor.registerCommand(
292 | SCROLL_TYPEAHEAD_OPTION_INTO_VIEW_COMMAND,
293 | ({ option }) => {
294 | if (option.ref && option.ref.current != null) {
295 | scrollIntoViewIfNeeded(option.ref.current);
296 | return true;
297 | }
298 |
299 | return false;
300 | },
301 | commandPriority,
302 | ),
303 | );
304 | }, [editor, updateSelectedIndex, commandPriority]);
305 |
306 | React.useEffect(() => {
307 | return mergeRegister(
308 | editor.registerCommand<KeyboardEvent>(
309 | KEY_ARROW_DOWN_COMMAND,
310 | (payload) => {
311 | const event = payload;
312 | if (options !== null && options.length && selectedIndex !== null) {
313 | const newSelectedIndex = selectedIndex !== options.length - 1 ? selectedIndex + 1 : 0;
314 | updateSelectedIndex(newSelectedIndex);
315 | const option = options[newSelectedIndex];
316 | if (option.ref != null && option.ref.current) {
317 | editor.dispatchCommand(SCROLL_TYPEAHEAD_OPTION_INTO_VIEW_COMMAND, {
318 | index: newSelectedIndex,
319 | option,
320 | });
321 | }
322 | event.preventDefault();
323 | event.stopImmediatePropagation();
324 | }
325 | return true;
326 | },
327 | commandPriority,
328 | ),
329 | editor.registerCommand<KeyboardEvent>(
330 | KEY_ARROW_UP_COMMAND,
331 | (payload) => {
332 | const event = payload;
333 | if (options !== null && options.length && selectedIndex !== null) {
334 | const newSelectedIndex = selectedIndex !== 0 ? selectedIndex - 1 : options.length - 1;
335 | updateSelectedIndex(newSelectedIndex);
336 | const option = options[newSelectedIndex];
337 | if (option.ref != null && option.ref.current) {
338 | scrollIntoViewIfNeeded(option.ref.current);
339 | }
340 | event.preventDefault();
341 | event.stopImmediatePropagation();
342 | }
343 | return true;
344 | },
345 | commandPriority,
346 | ),
347 | editor.registerCommand<KeyboardEvent>(
348 | KEY_ESCAPE_COMMAND,
349 | (payload) => {
350 | const event = payload;
351 | event.preventDefault();
352 | event.stopImmediatePropagation();
353 | close();
354 | return true;
355 | },
356 | commandPriority,
357 | ),
358 | editor.registerCommand<KeyboardEvent>(
359 | KEY_TAB_COMMAND,
360 | (payload) => {
361 | const event = payload;
362 | if (options === null || selectedIndex === null || options[selectedIndex] == null) {
363 | return false;
364 | }
365 | event.preventDefault();
366 | event.stopImmediatePropagation();
367 | selectOptionAndCleanUp(options[selectedIndex]);
368 | return true;
369 | },
370 | commandPriority,
371 | ),
372 | editor.registerCommand(
373 | KEY_ENTER_COMMAND,
374 | (event: KeyboardEvent | null) => {
375 | if (options === null || selectedIndex === null || options[selectedIndex] == null) {
376 | return false;
377 | }
378 | if (event !== null) {
379 | event.preventDefault();
380 | event.stopImmediatePropagation();
381 | }
382 | selectOptionAndCleanUp(options[selectedIndex]);
383 | return true;
384 | },
385 | commandPriority,
386 | ),
387 | );
388 | }, [selectOptionAndCleanUp, close, editor, options, selectedIndex, updateSelectedIndex, commandPriority]);
389 |
390 | const listItemProps = React.useMemo(
391 | () => ({
392 | options,
393 | selectOptionAndCleanUp,
394 | selectedIndex,
395 | setHighlightedIndex,
396 | }),
397 | [selectOptionAndCleanUp, selectedIndex, options],
398 | );
399 |
400 | return menuRenderFn(anchorElementRef, listItemProps, resolution.match ? resolution.match.matchingString : '');
401 | };
402 |
403 | export const useMenuAnchorRef = (
404 | resolution: MenuResolution | null,
405 | setResolution: (r: MenuResolution | null) => void,
406 | className?: string,
407 | parent: HTMLElement = document.body,
408 | ): React.MutableRefObject<HTMLElement> => {
409 | const [editor] = useLexicalComposerContext();
410 | const anchorElementRef = React.useRef<HTMLElement>(document.createElement('div'));
411 | const positionMenu = React.useCallback(() => {
412 | anchorElementRef.current.style.top = anchorElementRef.current.style.bottom;
413 | const rootElement = editor.getRootElement();
414 | const containerDiv = anchorElementRef.current;
415 |
416 | const menuEle = containerDiv.firstChild as HTMLElement;
417 | if (rootElement !== null && resolution !== null) {
418 | const { left, top, width, height } = resolution.getRect();
419 | containerDiv.style.bottom = `${top + window.scrollY}px`;
420 | containerDiv.style.left = `${left + window.scrollX}px`;
421 | containerDiv.style.height = `${height}px`;
422 | containerDiv.style.width = `${width}px`;
423 | if (menuEle !== null) {
424 | menuEle.style.top = `${top}`;
425 | const menuRect = menuEle.getBoundingClientRect();
426 | const menuHeight = menuRect.height;
427 | const menuWidth = menuRect.width;
428 |
429 | const rootElementRect = rootElement.getBoundingClientRect();
430 |
431 | if (left + menuWidth > rootElementRect.right) {
432 | containerDiv.style.left = `${rootElementRect.right - menuWidth + window.scrollX}px`;
433 | }
434 | if (
435 | (top + menuHeight > window.innerHeight || top + menuHeight > rootElementRect.bottom) &&
436 | top - rootElementRect.top > menuHeight + height
437 | ) {
438 | containerDiv.style.top = `${top - menuHeight + window.scrollY - height}px`;
439 | }
440 | }
441 |
442 | if (!containerDiv.isConnected) {
443 | if (className != null) {
444 | containerDiv.className = className;
445 | }
446 | containerDiv.setAttribute('aria-label', 'Typeahead menu');
447 | containerDiv.setAttribute('id', 'typeahead-menu');
448 | containerDiv.setAttribute('role', 'listbox');
449 | containerDiv.style.display = 'block';
450 | containerDiv.style.position = 'absolute';
451 | parent.append(containerDiv);
452 | }
453 | anchorElementRef.current = containerDiv;
454 | rootElement.setAttribute('aria-controls', 'typeahead-menu');
455 | }
456 | }, [editor, resolution, className, parent]);
457 |
458 | React.useEffect(() => {
459 | const rootElement = editor.getRootElement();
460 | if (resolution !== null) {
461 | positionMenu();
462 | return () => {
463 | if (rootElement !== null) {
464 | rootElement.removeAttribute('aria-controls');
465 | }
466 |
467 | const containerDiv = anchorElementRef.current;
468 | if (containerDiv !== null && containerDiv.isConnected) {
469 | containerDiv.remove();
470 | }
471 | };
472 | }
473 |
474 | return;
475 | }, [editor, positionMenu, resolution]);
476 |
477 | const onVisibilityChange = React.useCallback(
478 | (isInView: boolean) => {
479 | if (resolution !== null) {
480 | if (!isInView) {
481 | setResolution(null);
482 | }
483 | }
484 | },
485 | [resolution, setResolution],
486 | );
487 |
488 | useDynamicPositioning(resolution, anchorElementRef.current, positionMenu, onVisibilityChange);
489 |
490 | return anchorElementRef;
491 | };
492 |
493 | export type TriggerFn = (text: string, editor: LexicalEditor) => MenuTextMatch | null;
494 |
```
--------------------------------------------------------------------------------
/assistants/knowledge-transfer-assistant/assistant/domain/learning_objectives_manager.py:
--------------------------------------------------------------------------------
```python
1 | """
2 | Learning objectives and outcomes management for Knowledge Transfer Assistant.
3 |
4 | Handles learning objectives, outcomes creation, updates, and deletion.
5 | """
6 |
7 | from typing import List, Optional, Tuple
8 |
9 | from semantic_workbench_assistant.assistant_app import ConversationContext
10 |
11 | from ..data import InspectorTab, KnowledgePackage, LearningObjective, LearningOutcome, LogEntryType
12 | from ..logging import logger
13 | from ..notifications import Notifications
14 | from ..storage import ShareStorage
15 | from ..utils import require_current_user
16 | from .share_manager import ShareManager
17 |
18 |
19 | class LearningObjectivesManager:
20 | """Manages learning objectives and outcomes operations."""
21 |
22 | @staticmethod
23 | async def add_learning_objective(
24 | context: ConversationContext,
25 | objective_name: str,
26 | description: str,
27 | outcomes: Optional[List[str]] = None,
28 | priority: int = 1,
29 | ) -> Optional[LearningObjective]:
30 | share_id = await ShareManager.get_share_id(context)
31 | if not share_id:
32 | logger.error("Cannot add learning objective: no share associated with this conversation")
33 | return None
34 |
35 | current_user_id = await require_current_user(context, "add learning objective")
36 | if not current_user_id:
37 | return None
38 |
39 | criterion_objects = []
40 | if outcomes:
41 | for criterion in outcomes:
42 | criterion_objects.append(LearningOutcome(description=criterion))
43 |
44 | new_learning_objective = LearningObjective(
45 | name=objective_name,
46 | description=description,
47 | priority=priority,
48 | learning_outcomes=criterion_objects,
49 | )
50 |
51 | share = ShareStorage.read_share(share_id)
52 | if not share:
53 | # Create a new share if it doesn't exist
54 | share = KnowledgePackage(
55 | share_id=share_id,
56 | brief=None,
57 | learning_objectives=[new_learning_objective],
58 | digest=None,
59 | requests=[],
60 | log=None,
61 | )
62 | else:
63 | share.learning_objectives.append(new_learning_objective)
64 |
65 | ShareStorage.write_share(share_id, share)
66 |
67 | await ShareStorage.log_share_event(
68 | context=context,
69 | share_id=share_id,
70 | entry_type=LogEntryType.LEARNING_OBJECTIVE_ADDED.value,
71 | message=f"Added learning objective: {objective_name}",
72 | )
73 |
74 | await Notifications.notify_all(context, share_id, f"Learning objective '{objective_name}' was added")
75 | await Notifications.notify_all_state_update(context, share_id, [InspectorTab.LEARNING, InspectorTab.BRIEF])
76 |
77 | return new_learning_objective
78 |
79 | @staticmethod
80 | async def update_learning_objective(
81 | context: ConversationContext,
82 | objective_id: str,
83 | objective_name: Optional[str] = None,
84 | description: Optional[str] = None,
85 | ) -> Tuple[bool, Optional[str]]:
86 | """Update an existing learning objective's name or description."""
87 | share_id = await ShareManager.get_share_id(context)
88 | if not share_id:
89 | logger.error("Cannot update learning objective: no share associated with this conversation")
90 | return False, "No share associated with this conversation."
91 |
92 | current_user_id = await require_current_user(context, "update learning objective")
93 | if not current_user_id:
94 | return False, "Could not identify current user."
95 |
96 | share = ShareStorage.read_share(share_id)
97 | if not share or not share.learning_objectives:
98 | return False, "No learning objectives found."
99 |
100 | # Find objective by ID
101 | objective = None
102 | for obj in share.learning_objectives:
103 | if obj.id == objective_id:
104 | objective = obj
105 | break
106 |
107 | if not objective:
108 | available_ids = [obj.id for obj in share.learning_objectives]
109 | return (
110 | False,
111 | f"Learning objective with ID '{objective_id}' not found. Available objective IDs: {', '.join(available_ids[:3]) + ('...' if len(available_ids) > 3 else '')}",
112 | )
113 |
114 | original_name = objective.name
115 | changes_made = []
116 |
117 | # Update fields if provided
118 | if objective_name and objective_name.strip():
119 | objective.name = objective_name.strip()
120 | changes_made.append(f"name: '{original_name}' → '{objective_name.strip()}'")
121 |
122 | if description and description.strip():
123 | objective.description = description.strip()
124 | changes_made.append("description updated")
125 |
126 | if not changes_made:
127 | return True, "No changes specified"
128 |
129 | ShareStorage.write_share(share_id, share)
130 |
131 | changes_text = ", ".join(changes_made)
132 | await ShareStorage.log_share_event(
133 | context=context,
134 | share_id=share_id,
135 | entry_type=LogEntryType.LEARNING_OBJECTIVE_UPDATED.value,
136 | message=f"Updated learning objective '{objective.name}': {changes_text}",
137 | metadata={
138 | "objective_id": objective_id,
139 | "objective_name": objective.name,
140 | "changes": changes_text,
141 | },
142 | )
143 |
144 | await Notifications.notify_all(context, share_id, f"Learning objective '{objective.name}' has been updated")
145 | await Notifications.notify_all_state_update(context, share_id, [InspectorTab.LEARNING, InspectorTab.BRIEF])
146 |
147 | return True, f"Learning objective '{objective.name}' has been successfully updated: {changes_text}."
148 |
149 | @staticmethod
150 | async def delete_learning_objective(
151 | context: ConversationContext,
152 | objective_id: str,
153 | ) -> Tuple[bool, Optional[str]]:
154 | """Delete a learning objective by ID."""
155 | share_id = await ShareManager.get_share_id(context)
156 | if not share_id:
157 | logger.error("Cannot delete learning objective: no share associated with this conversation")
158 | return False, "No share associated with this conversation."
159 |
160 | current_user_id = await require_current_user(context, "delete learning objective")
161 | if not current_user_id:
162 | return False, "Could not identify current user."
163 |
164 | share = ShareStorage.read_share(share_id)
165 | if not share or not share.learning_objectives:
166 | return False, "No learning objectives found."
167 |
168 | # Find objective by ID
169 | objective = None
170 | objective_index = -1
171 | for idx, obj in enumerate(share.learning_objectives):
172 | if obj.id == objective_id:
173 | objective = obj
174 | objective_index = idx
175 | break
176 |
177 | if not objective:
178 | available_ids = [obj.id for obj in share.learning_objectives]
179 | return (
180 | False,
181 | f"Learning objective with ID '{objective_id}' not found. Available objective IDs: {', '.join(available_ids[:3]) + ('...' if len(available_ids) > 3 else '')}",
182 | )
183 |
184 | objective_name = objective.name
185 |
186 | # Clean up any achievement records for all outcomes in this objective across all team conversations
187 | for outcome in objective.learning_outcomes:
188 | for team_info in share.team_conversations.values():
189 | team_info.outcome_achievements = [
190 | achievement
191 | for achievement in team_info.outcome_achievements
192 | if achievement.outcome_id != outcome.id
193 | ]
194 |
195 | # Remove the objective from the share
196 | share.learning_objectives.pop(objective_index)
197 |
198 | ShareStorage.write_share(share_id, share)
199 |
200 | await ShareStorage.log_share_event(
201 | context=context,
202 | share_id=share_id,
203 | entry_type=LogEntryType.LEARNING_OBJECTIVE_UPDATED.value,
204 | message=f"Deleted learning objective '{objective_name}' and all its outcomes",
205 | metadata={
206 | "objective_id": objective_id,
207 | "objective_name": objective_name,
208 | "outcomes_count": len(objective.learning_outcomes),
209 | },
210 | )
211 |
212 | await Notifications.notify_all(context, share_id, f"Learning objective '{objective_name}' has been deleted")
213 | await Notifications.notify_all_state_update(context, share_id, [InspectorTab.LEARNING, InspectorTab.BRIEF])
214 |
215 | return True, f"Learning objective '{objective_name}' has been successfully deleted from the knowledge package."
216 |
217 | @staticmethod
218 | async def get_learning_outcomes(context: ConversationContext) -> List[LearningOutcome]:
219 | share_id = await ShareManager.get_share_id(context)
220 | if not share_id:
221 | return []
222 |
223 | share = ShareStorage.read_share(share_id)
224 | if not share:
225 | return []
226 |
227 | objectives = share.learning_objectives
228 | outcomes = []
229 | for objective in objectives:
230 | outcomes.extend(objective.learning_outcomes)
231 |
232 | return outcomes
233 |
234 | @staticmethod
235 | async def add_learning_outcome(
236 | context: ConversationContext,
237 | objective_id: str,
238 | outcome_description: str,
239 | ) -> Tuple[bool, Optional[str]]:
240 | """Add a new learning outcome to an existing learning objective."""
241 | share_id = await ShareManager.get_share_id(context)
242 | if not share_id:
243 | logger.error("Cannot add learning outcome: no share associated with this conversation")
244 | return False, "No knowledge package associated with this conversation."
245 |
246 | current_user_id = await require_current_user(context, "add learning outcome")
247 | if not current_user_id:
248 | return False, "Could not identify current user."
249 |
250 | share = ShareStorage.read_share(share_id)
251 | if not share or not share.learning_objectives:
252 | return False, "No learning objectives found. Please add objectives before adding outcomes."
253 |
254 | # Find the objective by ID
255 | objective = None
256 | for obj in share.learning_objectives:
257 | if obj.id == objective_id:
258 | objective = obj
259 | break
260 |
261 | if objective is None:
262 | available_ids = [obj.id for obj in share.learning_objectives]
263 | return (
264 | False,
265 | f"Learning objective with ID '{objective_id}' not found. Available objective IDs: {', '.join(available_ids[:3]) + ('...' if len(available_ids) > 3 else '')}",
266 | )
267 |
268 | # Create the new outcome
269 | new_outcome = LearningOutcome(description=outcome_description.strip())
270 |
271 | # Add the outcome to the objective
272 | objective.learning_outcomes.append(new_outcome)
273 |
274 | # Save the updated knowledge package
275 | ShareStorage.write_share(share_id, share)
276 |
277 | # Log the outcome addition
278 | await ShareStorage.log_share_event(
279 | context=context,
280 | share_id=share_id,
281 | entry_type=LogEntryType.LEARNING_OBJECTIVE_UPDATED.value,
282 | message=f"Added learning outcome to objective '{objective.name}': {outcome_description}",
283 | metadata={
284 | "objective_id": objective_id,
285 | "objective_name": objective.name,
286 | "outcome_added": outcome_description,
287 | "outcome_id": new_outcome.id,
288 | },
289 | )
290 |
291 | # Notify linked conversations
292 | await Notifications.notify_all(context, share_id, f"Learning outcome '{outcome_description}' has been added")
293 | await Notifications.notify_all_state_update(context, share_id, [InspectorTab.LEARNING, InspectorTab.BRIEF])
294 |
295 | return True, f"Learning outcome added successfully to objective '{objective.name}': {outcome_description}"
296 |
297 | @staticmethod
298 | async def update_learning_outcome(
299 | context: ConversationContext,
300 | outcome_id: str,
301 | new_description: str,
302 | ) -> Tuple[bool, Optional[str]]:
303 | """Update the description of an existing learning outcome."""
304 | share_id = await ShareManager.get_share_id(context)
305 | if not share_id:
306 | logger.error("Cannot update learning outcome: no share associated with this conversation")
307 | return False, "No knowledge package associated with this conversation."
308 |
309 | current_user_id = await require_current_user(context, "update learning outcome")
310 | if not current_user_id:
311 | return False, "Could not identify current user."
312 |
313 | share = ShareStorage.read_share(share_id)
314 | if not share or not share.learning_objectives:
315 | return False, "No learning objectives found. Please add objectives before updating outcomes."
316 |
317 | # Find the outcome by ID across all objectives
318 | objective = None
319 | outcome = None
320 | for obj in share.learning_objectives:
321 | for out in obj.learning_outcomes:
322 | if out.id == outcome_id:
323 | objective = obj
324 | outcome = out
325 | break
326 | if outcome:
327 | break
328 |
329 | if outcome is None or objective is None:
330 | # Collect available outcome IDs for error message
331 | available_outcome_ids = []
332 | for obj in share.learning_objectives:
333 | for out in obj.learning_outcomes:
334 | available_outcome_ids.append(out.id)
335 | return (
336 | False,
337 | f"Learning outcome with ID '{outcome_id}' not found. Available outcome IDs: {', '.join(available_outcome_ids[:3]) + ('...' if len(available_outcome_ids) > 3 else '')}",
338 | )
339 |
340 | old_description = outcome.description
341 |
342 | # Update the outcome description
343 | outcome.description = new_description.strip()
344 |
345 | # Save the updated knowledge package
346 | ShareStorage.write_share(share_id, share)
347 |
348 | # Log the outcome update
349 | await ShareStorage.log_share_event(
350 | context=context,
351 | share_id=share_id,
352 | entry_type=LogEntryType.LEARNING_OBJECTIVE_UPDATED.value,
353 | message=f"Updated learning outcome in objective '{objective.name}': '{old_description}' → '{new_description}'",
354 | metadata={
355 | "objective_id": objective.id,
356 | "objective_name": objective.name,
357 | "outcome_id": outcome_id,
358 | "old_description": old_description,
359 | "new_description": new_description,
360 | },
361 | )
362 |
363 | # Notify linked conversations
364 | await Notifications.notify_all(context, share_id, f"Learning outcome '{new_description}' has been updated")
365 | await Notifications.notify_all_state_update(context, share_id, [InspectorTab.LEARNING, InspectorTab.BRIEF])
366 |
367 | return True, f"Learning outcome updated successfully in objective '{objective.name}': {new_description}"
368 |
369 | @staticmethod
370 | async def delete_learning_outcome(
371 | context: ConversationContext,
372 | outcome_id: str,
373 | ) -> Tuple[bool, Optional[str]]:
374 | """Delete a learning outcome from a learning objective."""
375 | share_id = await ShareManager.get_share_id(context)
376 | if not share_id:
377 | logger.error("Cannot delete learning outcome: no share associated with this conversation")
378 | return False, "No knowledge package associated with this conversation."
379 |
380 | current_user_id = await require_current_user(context, "delete learning outcome")
381 | if not current_user_id:
382 | return False, "Could not identify current user."
383 |
384 | share = ShareStorage.read_share(share_id)
385 | if not share or not share.learning_objectives:
386 | return False, "No learning objectives found. Please add objectives before deleting outcomes."
387 |
388 | # Find the outcome by ID across all objectives
389 | objective = None
390 | outcome_to_delete = None
391 | outcome_index = -1
392 | for obj in share.learning_objectives:
393 | for idx, out in enumerate(obj.learning_outcomes):
394 | if out.id == outcome_id:
395 | objective = obj
396 | outcome_to_delete = out
397 | outcome_index = idx
398 | break
399 | if outcome_to_delete:
400 | break
401 |
402 | if outcome_to_delete is None or objective is None:
403 | # Collect available outcome IDs for error message
404 | available_outcome_ids = []
405 | for obj in share.learning_objectives:
406 | for out in obj.learning_outcomes:
407 | available_outcome_ids.append(out.id)
408 | return (
409 | False,
410 | f"Learning outcome with ID '{outcome_id}' not found. Available outcome IDs: {', '.join(available_outcome_ids[:3]) + ('...' if len(available_outcome_ids) > 3 else '')}",
411 | )
412 |
413 | deleted_description = outcome_to_delete.description
414 |
415 | # Remove the outcome from the objective
416 | objective.learning_outcomes.pop(outcome_index)
417 |
418 | # Clean up any achievement records for this outcome across all team conversations
419 | for team_info in share.team_conversations.values():
420 | team_info.outcome_achievements = [
421 | achievement for achievement in team_info.outcome_achievements if achievement.outcome_id != outcome_id
422 | ]
423 |
424 | # Save the updated knowledge package
425 | ShareStorage.write_share(share_id, share)
426 |
427 | # Log the outcome deletion
428 | await ShareStorage.log_share_event(
429 | context=context,
430 | share_id=share_id,
431 | entry_type=LogEntryType.LEARNING_OBJECTIVE_UPDATED.value,
432 | message=f"Deleted learning outcome from objective '{objective.name}': {deleted_description}",
433 | metadata={
434 | "objective_id": objective.id,
435 | "objective_name": objective.name,
436 | "outcome_index": outcome_index,
437 | "outcome_id": outcome_id,
438 | "deleted_description": deleted_description,
439 | },
440 | )
441 |
442 | # Notify linked conversations
443 | await Notifications.notify_all(context, share_id, f"Learning outcome '{deleted_description}' has been removed")
444 | await Notifications.notify_all_state_update(context, share_id, [InspectorTab.LEARNING, InspectorTab.BRIEF])
445 |
446 | return True, f"Learning outcome deleted successfully from objective '{objective.name}': {deleted_description}"
447 |
```