Full Code of agentscope-ai/CoPaw for AI

main 82a49d77890b cached
814 files
16.6 MB
4.2M tokens
2841 symbols
1 requests
Copy disabled (too large) Download .txt
Showing preview only (16,700K chars total). Download the full file to get everything.
Repository: agentscope-ai/CoPaw
Branch: main
Commit: 82a49d77890b
Files: 814
Total size: 16.6 MB

Directory structure:
gitextract_9wv9z_k8/

├── .dockerignore
├── .flake8
├── .gitattributes
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── 1-question.md
│   │   ├── 2-feature_request.md
│   │   ├── 3-documentation.md
│   │   ├── 4-bug_report.md
│   │   ├── 5-support_environment.md
│   │   └── config.yml
│   ├── PULL_REQUEST_TEMPLATE.md
│   ├── condarc
│   └── workflows/
│       ├── deploy-website.yml
│       ├── desktop-release.yml
│       ├── docker-release.yml
│       ├── first-time-contributor-welcome.yml
│       ├── npm-format.yml
│       ├── pr-label.yml
│       ├── pre-commit.yml
│       ├── publish-pypi.yml
│       └── tests.yml
├── .gitignore
├── .pre-commit-config.yaml
├── .python-version
├── CONTRIBUTING.md
├── CONTRIBUTING_zh.md
├── LICENSE
├── README.md
├── README_ja.md
├── README_zh.md
├── SECURITY.md
├── console/
│   ├── eslint.config.js
│   ├── index.html
│   ├── package.json
│   ├── src/
│   │   ├── App.tsx
│   │   ├── api/
│   │   │   ├── config.ts
│   │   │   ├── index.ts
│   │   │   ├── modules/
│   │   │   │   ├── agent.ts
│   │   │   │   ├── agents.ts
│   │   │   │   ├── auth.ts
│   │   │   │   ├── channel.ts
│   │   │   │   ├── chat.ts
│   │   │   │   ├── console.ts
│   │   │   │   ├── cronjob.ts
│   │   │   │   ├── env.ts
│   │   │   │   ├── heartbeat.ts
│   │   │   │   ├── localModel.ts
│   │   │   │   ├── mcp.ts
│   │   │   │   ├── ollamaModel.ts
│   │   │   │   ├── provider.ts
│   │   │   │   ├── root.ts
│   │   │   │   ├── security.ts
│   │   │   │   ├── skill.ts
│   │   │   │   ├── tokenUsage.ts
│   │   │   │   ├── tools.ts
│   │   │   │   ├── userTimezone.ts
│   │   │   │   └── workspace.ts
│   │   │   ├── request.ts
│   │   │   └── types/
│   │   │       ├── agent.ts
│   │   │       ├── agents.ts
│   │   │       ├── channel.ts
│   │   │       ├── chat.ts
│   │   │       ├── cronjob.ts
│   │   │       ├── env.ts
│   │   │       ├── heartbeat.ts
│   │   │       ├── index.ts
│   │   │       ├── mcp.ts
│   │   │       ├── provider.ts
│   │   │       ├── skill.ts
│   │   │       ├── tokenUsage.ts
│   │   │       └── workspace.ts
│   │   ├── components/
│   │   │   ├── AgentSelector/
│   │   │   │   ├── index.module.less
│   │   │   │   └── index.tsx
│   │   │   ├── ConsoleCronBubble/
│   │   │   │   ├── index.module.less
│   │   │   │   └── index.tsx
│   │   │   ├── LanguageSwitcher.tsx
│   │   │   ├── MarkdownCopy/
│   │   │   │   ├── MarkdownCopy.tsx
│   │   │   │   └── index.module.less
│   │   │   ├── PageHeader/
│   │   │   │   └── index.tsx
│   │   │   └── ThemeToggleButton/
│   │   │       ├── index.module.less
│   │   │       └── index.tsx
│   │   ├── constants/
│   │   │   └── timezone.ts
│   │   ├── contexts/
│   │   │   └── ThemeContext.tsx
│   │   ├── i18n.ts
│   │   ├── layouts/
│   │   │   ├── Header.tsx
│   │   │   ├── MainLayout/
│   │   │   │   └── index.tsx
│   │   │   ├── Sidebar.tsx
│   │   │   ├── constants.ts
│   │   │   └── index.module.less
│   │   ├── locales/
│   │   │   ├── en.json
│   │   │   ├── ja.json
│   │   │   ├── ru.json
│   │   │   └── zh.json
│   │   ├── main.tsx
│   │   ├── pages/
│   │   │   ├── Agent/
│   │   │   │   ├── Config/
│   │   │   │   │   ├── components/
│   │   │   │   │   │   ├── ContextManagementCard.tsx
│   │   │   │   │   │   ├── PageHeader.tsx
│   │   │   │   │   │   ├── ReactAgentCard.tsx
│   │   │   │   │   │   ├── SliderWithValue.tsx
│   │   │   │   │   │   └── index.ts
│   │   │   │   │   ├── index.module.less
│   │   │   │   │   ├── index.tsx
│   │   │   │   │   └── useAgentConfig.tsx
│   │   │   │   ├── MCP/
│   │   │   │   │   ├── components/
│   │   │   │   │   │   ├── MCPClientCard.tsx
│   │   │   │   │   │   ├── MCPClientDrawer.tsx
│   │   │   │   │   │   └── index.ts
│   │   │   │   │   ├── index.module.less
│   │   │   │   │   ├── index.tsx
│   │   │   │   │   └── useMCP.ts
│   │   │   │   ├── Skills/
│   │   │   │   │   ├── components/
│   │   │   │   │   │   ├── SkillCard.tsx
│   │   │   │   │   │   ├── SkillDrawer.tsx
│   │   │   │   │   │   └── index.ts
│   │   │   │   │   ├── index.module.less
│   │   │   │   │   ├── index.tsx
│   │   │   │   │   └── useSkills.ts
│   │   │   │   ├── Tools/
│   │   │   │   │   ├── index.module.less
│   │   │   │   │   ├── index.tsx
│   │   │   │   │   └── useTools.ts
│   │   │   │   └── Workspace/
│   │   │   │       ├── components/
│   │   │   │       │   ├── FileEditor.tsx
│   │   │   │       │   ├── FileItem.tsx
│   │   │   │       │   ├── FileListPanel.tsx
│   │   │   │       │   ├── index.ts
│   │   │   │       │   ├── useAgentsData.ts
│   │   │   │       │   └── utils.ts
│   │   │   │       ├── index.module.less
│   │   │   │       └── index.tsx
│   │   │   ├── Chat/
│   │   │   │   ├── ModelSelector/
│   │   │   │   │   ├── index.module.less
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── OptionsPanel/
│   │   │   │   │   ├── FormItem.tsx
│   │   │   │   │   ├── OptionsEditor.tsx
│   │   │   │   │   ├── defaultConfig.ts
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── index.module.less
│   │   │   │   ├── index.tsx
│   │   │   │   └── sessionApi/
│   │   │   │       └── index.ts
│   │   │   ├── Control/
│   │   │   │   ├── Channels/
│   │   │   │   │   ├── components/
│   │   │   │   │   │   ├── ChannelCard.tsx
│   │   │   │   │   │   ├── ChannelDrawer.tsx
│   │   │   │   │   │   ├── constants.ts
│   │   │   │   │   │   └── index.ts
│   │   │   │   │   ├── index.module.less
│   │   │   │   │   ├── index.tsx
│   │   │   │   │   └── useChannels.ts
│   │   │   │   ├── CronJobs/
│   │   │   │   │   ├── components/
│   │   │   │   │   │   ├── JobDrawer.tsx
│   │   │   │   │   │   ├── columns.tsx
│   │   │   │   │   │   ├── constants.ts
│   │   │   │   │   │   ├── index.ts
│   │   │   │   │   │   └── parseCron.ts
│   │   │   │   │   ├── index.module.less
│   │   │   │   │   ├── index.tsx
│   │   │   │   │   └── useCronJobs.ts
│   │   │   │   ├── Heartbeat/
│   │   │   │   │   ├── index.module.less
│   │   │   │   │   ├── index.tsx
│   │   │   │   │   └── parseEvery.ts
│   │   │   │   └── Sessions/
│   │   │   │       ├── components/
│   │   │   │       │   ├── FilterBar.tsx
│   │   │   │       │   ├── SessionDrawer.tsx
│   │   │   │       │   ├── columns.tsx
│   │   │   │       │   ├── constants.ts
│   │   │   │       │   └── index.ts
│   │   │   │       ├── index.module.less
│   │   │   │       ├── index.tsx
│   │   │   │       └── useSessions.ts
│   │   │   ├── Login/
│   │   │   │   └── index.tsx
│   │   │   └── Settings/
│   │   │       ├── Agents/
│   │   │       │   ├── components/
│   │   │       │   │   ├── AgentModal.tsx
│   │   │       │   │   ├── AgentTable.tsx
│   │   │       │   │   ├── PageHeader.tsx
│   │   │       │   │   └── index.ts
│   │   │       │   ├── index.module.less
│   │   │       │   ├── index.tsx
│   │   │       │   └── useAgents.ts
│   │   │       ├── Environments/
│   │   │       │   ├── components/
│   │   │       │   │   ├── AddButton.tsx
│   │   │       │   │   ├── EmptyState.tsx
│   │   │       │   │   ├── EnvRow.tsx
│   │   │       │   │   ├── PageHeader.tsx
│   │   │       │   │   ├── Toolbar.tsx
│   │   │       │   │   └── index.ts
│   │   │       │   ├── index.module.less
│   │   │       │   ├── index.tsx
│   │   │       │   └── useEnvVars.ts
│   │   │       ├── Models/
│   │   │       │   ├── components/
│   │   │       │   │   ├── ModelManageModal.tsx
│   │   │       │   │   ├── cards/
│   │   │       │   │   │   ├── LocalProviderCard.tsx
│   │   │       │   │   │   ├── ProviderCard.tsx
│   │   │       │   │   │   ├── RemoteProviderCard.tsx
│   │   │       │   │   │   └── index.ts
│   │   │       │   │   ├── index.ts
│   │   │       │   │   ├── modals/
│   │   │       │   │   │   ├── CustomProviderModal.tsx
│   │   │       │   │   │   ├── LocalModelManageModal.tsx
│   │   │       │   │   │   ├── ModelManageModal.tsx
│   │   │       │   │   │   ├── OllamaModelManageModal.tsx
│   │   │       │   │   │   ├── ProviderConfigModal.tsx
│   │   │       │   │   │   ├── RemoteModelManageModal.tsx
│   │   │       │   │   │   └── index.ts
│   │   │       │   │   └── sections/
│   │   │       │   │       ├── LoadingState.tsx
│   │   │       │   │       ├── ModelsSection.tsx
│   │   │       │   │       ├── PageHeader.tsx
│   │   │       │   │       └── index.ts
│   │   │       │   ├── index.module.less
│   │   │       │   ├── index.tsx
│   │   │       │   └── useProviders.ts
│   │   │       ├── Security/
│   │   │       │   ├── components/
│   │   │       │   │   ├── PageHeader.tsx
│   │   │       │   │   ├── PreviewModal.tsx
│   │   │       │   │   ├── RuleModal.tsx
│   │   │       │   │   ├── RuleTable.tsx
│   │   │       │   │   ├── SkillScannerSection.tsx
│   │   │       │   │   └── index.ts
│   │   │       │   ├── index.module.less
│   │   │       │   ├── index.tsx
│   │   │       │   ├── useSkillScanner.ts
│   │   │       │   └── useToolGuard.ts
│   │   │       ├── TokenUsage/
│   │   │       │   ├── components/
│   │   │       │   │   ├── EmptyState.tsx
│   │   │       │   │   ├── LoadingState.tsx
│   │   │       │   │   ├── PageHeader.tsx
│   │   │       │   │   └── index.ts
│   │   │       │   ├── index.module.less
│   │   │       │   └── index.tsx
│   │   │       └── VoiceTranscription/
│   │   │           ├── index.module.less
│   │   │           └── index.tsx
│   │   ├── stores/
│   │   │   └── agentStore.ts
│   │   ├── styles/
│   │   │   ├── form-override.css
│   │   │   └── layout.css
│   │   ├── utils/
│   │   │   ├── formatNumber.ts
│   │   │   └── markdown.ts
│   │   └── vite-env.d.ts
│   ├── tsconfig.app.json
│   ├── tsconfig.json
│   ├── tsconfig.node.json
│   └── vite.config.ts
├── deploy/
│   ├── Dockerfile
│   ├── config/
│   │   └── supervisord.conf.template
│   └── entrypoint.sh
├── docker-compose.yml
├── pyproject.toml
├── scripts/
│   ├── README.md
│   ├── docker_build.sh
│   ├── docker_sync_latest.sh
│   ├── install.bat
│   ├── install.ps1
│   ├── install.sh
│   ├── pack/
│   │   ├── README.md
│   │   ├── README_zh.md
│   │   ├── assets/
│   │   │   └── icon.icns
│   │   ├── build_common.py
│   │   ├── build_macos.sh
│   │   ├── build_win.ps1
│   │   └── copaw_desktop.nsi
│   ├── run_tests.py
│   ├── website_build.sh
│   ├── wheel_build.ps1
│   └── wheel_build.sh
├── setup.py
├── src/
│   └── copaw/
│       ├── __init__.py
│       ├── __main__.py
│       ├── __version__.py
│       ├── agents/
│       │   ├── __init__.py
│       │   ├── command_handler.py
│       │   ├── hooks/
│       │   │   ├── __init__.py
│       │   │   ├── bootstrap.py
│       │   │   └── memory_compaction.py
│       │   ├── md_files/
│       │   │   ├── en/
│       │   │   │   ├── AGENTS.md
│       │   │   │   ├── BOOTSTRAP.md
│       │   │   │   ├── HEARTBEAT.md
│       │   │   │   ├── MEMORY.md
│       │   │   │   ├── PROFILE.md
│       │   │   │   └── SOUL.md
│       │   │   ├── ru/
│       │   │   │   ├── AGENTS.md
│       │   │   │   ├── BOOTSTRAP.md
│       │   │   │   ├── HEARTBEAT.md
│       │   │   │   ├── MEMORY.md
│       │   │   │   ├── PROFILE.md
│       │   │   │   └── SOUL.md
│       │   │   └── zh/
│       │   │       ├── AGENTS.md
│       │   │       ├── BOOTSTRAP.md
│       │   │       ├── HEARTBEAT.md
│       │   │       ├── MEMORY.md
│       │   │       ├── PROFILE.md
│       │   │       └── SOUL.md
│       │   ├── memory/
│       │   │   ├── __init__.py
│       │   │   ├── agent_md_manager.py
│       │   │   └── memory_manager.py
│       │   ├── model_factory.py
│       │   ├── prompt.py
│       │   ├── react_agent.py
│       │   ├── routing_chat_model.py
│       │   ├── schema.py
│       │   ├── skills/
│       │   │   ├── __init__.py
│       │   │   ├── browser_visible/
│       │   │   │   └── SKILL.md
│       │   │   ├── cron/
│       │   │   │   └── SKILL.md
│       │   │   ├── dingtalk_channel/
│       │   │   │   └── SKILL.md
│       │   │   ├── docx/
│       │   │   │   ├── LICENSE.txt
│       │   │   │   ├── SKILL.md
│       │   │   │   └── scripts/
│       │   │   │       ├── __init__.py
│       │   │   │       ├── accept_changes.py
│       │   │   │       ├── comment.py
│       │   │   │       ├── office/
│       │   │   │       │   ├── helpers/
│       │   │   │       │   │   ├── __init__.py
│       │   │   │       │   │   ├── merge_runs.py
│       │   │   │       │   │   └── simplify_redlines.py
│       │   │   │       │   ├── pack.py
│       │   │   │       │   ├── schemas/
│       │   │   │       │   │   ├── ISO-IEC29500-4_2016/
│       │   │   │       │   │   │   ├── dml-chart.xsd
│       │   │   │       │   │   │   ├── dml-chartDrawing.xsd
│       │   │   │       │   │   │   ├── dml-diagram.xsd
│       │   │   │       │   │   │   ├── dml-lockedCanvas.xsd
│       │   │   │       │   │   │   ├── dml-main.xsd
│       │   │   │       │   │   │   ├── dml-picture.xsd
│       │   │   │       │   │   │   ├── dml-spreadsheetDrawing.xsd
│       │   │   │       │   │   │   ├── dml-wordprocessingDrawing.xsd
│       │   │   │       │   │   │   ├── pml.xsd
│       │   │   │       │   │   │   ├── shared-additionalCharacteristics.xsd
│       │   │   │       │   │   │   ├── shared-bibliography.xsd
│       │   │   │       │   │   │   ├── shared-commonSimpleTypes.xsd
│       │   │   │       │   │   │   ├── shared-customXmlDataProperties.xsd
│       │   │   │       │   │   │   ├── shared-customXmlSchemaProperties.xsd
│       │   │   │       │   │   │   ├── shared-documentPropertiesCustom.xsd
│       │   │   │       │   │   │   ├── shared-documentPropertiesExtended.xsd
│       │   │   │       │   │   │   ├── shared-documentPropertiesVariantTypes.xsd
│       │   │   │       │   │   │   ├── shared-math.xsd
│       │   │   │       │   │   │   ├── shared-relationshipReference.xsd
│       │   │   │       │   │   │   ├── sml.xsd
│       │   │   │       │   │   │   ├── vml-main.xsd
│       │   │   │       │   │   │   ├── vml-officeDrawing.xsd
│       │   │   │       │   │   │   ├── vml-presentationDrawing.xsd
│       │   │   │       │   │   │   ├── vml-spreadsheetDrawing.xsd
│       │   │   │       │   │   │   ├── vml-wordprocessingDrawing.xsd
│       │   │   │       │   │   │   ├── wml.xsd
│       │   │   │       │   │   │   └── xml.xsd
│       │   │   │       │   │   ├── ecma/
│       │   │   │       │   │   │   └── fouth-edition/
│       │   │   │       │   │   │       ├── opc-contentTypes.xsd
│       │   │   │       │   │   │       ├── opc-coreProperties.xsd
│       │   │   │       │   │   │       ├── opc-digSig.xsd
│       │   │   │       │   │   │       └── opc-relationships.xsd
│       │   │   │       │   │   ├── mce/
│       │   │   │       │   │   │   └── mc.xsd
│       │   │   │       │   │   └── microsoft/
│       │   │   │       │   │       ├── wml-2010.xsd
│       │   │   │       │   │       ├── wml-2012.xsd
│       │   │   │       │   │       ├── wml-2018.xsd
│       │   │   │       │   │       ├── wml-cex-2018.xsd
│       │   │   │       │   │       ├── wml-cid-2016.xsd
│       │   │   │       │   │       ├── wml-sdtdatahash-2020.xsd
│       │   │   │       │   │       └── wml-symex-2015.xsd
│       │   │   │       │   ├── soffice.py
│       │   │   │       │   ├── unpack.py
│       │   │   │       │   ├── validate.py
│       │   │   │       │   └── validators/
│       │   │   │       │       ├── __init__.py
│       │   │   │       │       ├── base.py
│       │   │   │       │       ├── docx.py
│       │   │   │       │       ├── pptx.py
│       │   │   │       │       └── redlining.py
│       │   │   │       └── templates/
│       │   │   │           ├── comments.xml
│       │   │   │           ├── commentsExtended.xml
│       │   │   │           ├── commentsExtensible.xml
│       │   │   │           ├── commentsIds.xml
│       │   │   │           └── people.xml
│       │   │   ├── file_reader/
│       │   │   │   └── SKILL.md
│       │   │   ├── guidance/
│       │   │   │   └── SKILL.md
│       │   │   ├── himalaya/
│       │   │   │   ├── SKILL.md
│       │   │   │   └── references/
│       │   │   │       └── configuration.md
│       │   │   ├── news/
│       │   │   │   └── SKILL.md
│       │   │   ├── pdf/
│       │   │   │   ├── LICENSE.txt
│       │   │   │   ├── SKILL.md
│       │   │   │   ├── forms.md
│       │   │   │   ├── reference.md
│       │   │   │   └── scripts/
│       │   │   │       ├── check_bounding_boxes.py
│       │   │   │       ├── check_fillable_fields.py
│       │   │   │       ├── convert_pdf_to_images.py
│       │   │   │       ├── create_validation_image.py
│       │   │   │       ├── extract_form_field_info.py
│       │   │   │       ├── extract_form_structure.py
│       │   │   │       ├── fill_fillable_fields.py
│       │   │   │       └── fill_pdf_form_with_annotations.py
│       │   │   ├── pptx/
│       │   │   │   ├── LICENSE.txt
│       │   │   │   ├── SKILL.md
│       │   │   │   ├── editing.md
│       │   │   │   ├── pptxgenjs.md
│       │   │   │   └── scripts/
│       │   │   │       ├── __init__.py
│       │   │   │       ├── add_slide.py
│       │   │   │       ├── clean.py
│       │   │   │       ├── office/
│       │   │   │       │   ├── helpers/
│       │   │   │       │   │   ├── __init__.py
│       │   │   │       │   │   ├── merge_runs.py
│       │   │   │       │   │   └── simplify_redlines.py
│       │   │   │       │   ├── pack.py
│       │   │   │       │   ├── schemas/
│       │   │   │       │   │   ├── ISO-IEC29500-4_2016/
│       │   │   │       │   │   │   ├── dml-chart.xsd
│       │   │   │       │   │   │   ├── dml-chartDrawing.xsd
│       │   │   │       │   │   │   ├── dml-diagram.xsd
│       │   │   │       │   │   │   ├── dml-lockedCanvas.xsd
│       │   │   │       │   │   │   ├── dml-main.xsd
│       │   │   │       │   │   │   ├── dml-picture.xsd
│       │   │   │       │   │   │   ├── dml-spreadsheetDrawing.xsd
│       │   │   │       │   │   │   ├── dml-wordprocessingDrawing.xsd
│       │   │   │       │   │   │   ├── pml.xsd
│       │   │   │       │   │   │   ├── shared-additionalCharacteristics.xsd
│       │   │   │       │   │   │   ├── shared-bibliography.xsd
│       │   │   │       │   │   │   ├── shared-commonSimpleTypes.xsd
│       │   │   │       │   │   │   ├── shared-customXmlDataProperties.xsd
│       │   │   │       │   │   │   ├── shared-customXmlSchemaProperties.xsd
│       │   │   │       │   │   │   ├── shared-documentPropertiesCustom.xsd
│       │   │   │       │   │   │   ├── shared-documentPropertiesExtended.xsd
│       │   │   │       │   │   │   ├── shared-documentPropertiesVariantTypes.xsd
│       │   │   │       │   │   │   ├── shared-math.xsd
│       │   │   │       │   │   │   ├── shared-relationshipReference.xsd
│       │   │   │       │   │   │   ├── sml.xsd
│       │   │   │       │   │   │   ├── vml-main.xsd
│       │   │   │       │   │   │   ├── vml-officeDrawing.xsd
│       │   │   │       │   │   │   ├── vml-presentationDrawing.xsd
│       │   │   │       │   │   │   ├── vml-spreadsheetDrawing.xsd
│       │   │   │       │   │   │   ├── vml-wordprocessingDrawing.xsd
│       │   │   │       │   │   │   ├── wml.xsd
│       │   │   │       │   │   │   └── xml.xsd
│       │   │   │       │   │   ├── ecma/
│       │   │   │       │   │   │   └── fouth-edition/
│       │   │   │       │   │   │       ├── opc-contentTypes.xsd
│       │   │   │       │   │   │       ├── opc-coreProperties.xsd
│       │   │   │       │   │   │       ├── opc-digSig.xsd
│       │   │   │       │   │   │       └── opc-relationships.xsd
│       │   │   │       │   │   ├── mce/
│       │   │   │       │   │   │   └── mc.xsd
│       │   │   │       │   │   └── microsoft/
│       │   │   │       │   │       ├── wml-2010.xsd
│       │   │   │       │   │       ├── wml-2012.xsd
│       │   │   │       │   │       ├── wml-2018.xsd
│       │   │   │       │   │       ├── wml-cex-2018.xsd
│       │   │   │       │   │       ├── wml-cid-2016.xsd
│       │   │   │       │   │       ├── wml-sdtdatahash-2020.xsd
│       │   │   │       │   │       └── wml-symex-2015.xsd
│       │   │   │       │   ├── soffice.py
│       │   │   │       │   ├── unpack.py
│       │   │   │       │   ├── validate.py
│       │   │   │       │   └── validators/
│       │   │   │       │       ├── __init__.py
│       │   │   │       │       ├── base.py
│       │   │   │       │       ├── docx.py
│       │   │   │       │       ├── pptx.py
│       │   │   │       │       └── redlining.py
│       │   │   │       └── thumbnail.py
│       │   │   └── xlsx/
│       │   │       ├── LICENSE.txt
│       │   │       ├── SKILL.md
│       │   │       └── scripts/
│       │   │           ├── office/
│       │   │           │   ├── helpers/
│       │   │           │   │   ├── __init__.py
│       │   │           │   │   ├── merge_runs.py
│       │   │           │   │   └── simplify_redlines.py
│       │   │           │   ├── pack.py
│       │   │           │   ├── schemas/
│       │   │           │   │   ├── ISO-IEC29500-4_2016/
│       │   │           │   │   │   ├── dml-chart.xsd
│       │   │           │   │   │   ├── dml-chartDrawing.xsd
│       │   │           │   │   │   ├── dml-diagram.xsd
│       │   │           │   │   │   ├── dml-lockedCanvas.xsd
│       │   │           │   │   │   ├── dml-main.xsd
│       │   │           │   │   │   ├── dml-picture.xsd
│       │   │           │   │   │   ├── dml-spreadsheetDrawing.xsd
│       │   │           │   │   │   ├── dml-wordprocessingDrawing.xsd
│       │   │           │   │   │   ├── pml.xsd
│       │   │           │   │   │   ├── shared-additionalCharacteristics.xsd
│       │   │           │   │   │   ├── shared-bibliography.xsd
│       │   │           │   │   │   ├── shared-commonSimpleTypes.xsd
│       │   │           │   │   │   ├── shared-customXmlDataProperties.xsd
│       │   │           │   │   │   ├── shared-customXmlSchemaProperties.xsd
│       │   │           │   │   │   ├── shared-documentPropertiesCustom.xsd
│       │   │           │   │   │   ├── shared-documentPropertiesExtended.xsd
│       │   │           │   │   │   ├── shared-documentPropertiesVariantTypes.xsd
│       │   │           │   │   │   ├── shared-math.xsd
│       │   │           │   │   │   ├── shared-relationshipReference.xsd
│       │   │           │   │   │   ├── sml.xsd
│       │   │           │   │   │   ├── vml-main.xsd
│       │   │           │   │   │   ├── vml-officeDrawing.xsd
│       │   │           │   │   │   ├── vml-presentationDrawing.xsd
│       │   │           │   │   │   ├── vml-spreadsheetDrawing.xsd
│       │   │           │   │   │   ├── vml-wordprocessingDrawing.xsd
│       │   │           │   │   │   ├── wml.xsd
│       │   │           │   │   │   └── xml.xsd
│       │   │           │   │   ├── ecma/
│       │   │           │   │   │   └── fouth-edition/
│       │   │           │   │   │       ├── opc-contentTypes.xsd
│       │   │           │   │   │       ├── opc-coreProperties.xsd
│       │   │           │   │   │       ├── opc-digSig.xsd
│       │   │           │   │   │       └── opc-relationships.xsd
│       │   │           │   │   ├── mce/
│       │   │           │   │   │   └── mc.xsd
│       │   │           │   │   └── microsoft/
│       │   │           │   │       ├── wml-2010.xsd
│       │   │           │   │       ├── wml-2012.xsd
│       │   │           │   │       ├── wml-2018.xsd
│       │   │           │   │       ├── wml-cex-2018.xsd
│       │   │           │   │       ├── wml-cid-2016.xsd
│       │   │           │   │       ├── wml-sdtdatahash-2020.xsd
│       │   │           │   │       └── wml-symex-2015.xsd
│       │   │           │   ├── soffice.py
│       │   │           │   ├── unpack.py
│       │   │           │   ├── validate.py
│       │   │           │   └── validators/
│       │   │           │       ├── __init__.py
│       │   │           │       ├── base.py
│       │   │           │       ├── docx.py
│       │   │           │       ├── pptx.py
│       │   │           │       └── redlining.py
│       │   │           └── recalc.py
│       │   ├── skills_hub.py
│       │   ├── skills_manager.py
│       │   ├── tool_guard_mixin.py
│       │   ├── tools/
│       │   │   ├── __init__.py
│       │   │   ├── browser_control.py
│       │   │   ├── browser_snapshot.py
│       │   │   ├── desktop_screenshot.py
│       │   │   ├── file_io.py
│       │   │   ├── file_search.py
│       │   │   ├── get_current_time.py
│       │   │   ├── get_token_usage.py
│       │   │   ├── memory_search.py
│       │   │   ├── send_file.py
│       │   │   ├── shell.py
│       │   │   ├── utils.py
│       │   │   └── view_image.py
│       │   └── utils/
│       │       ├── __init__.py
│       │       ├── audio_transcription.py
│       │       ├── copaw_token_counter.py
│       │       ├── file_handling.py
│       │       ├── message_processing.py
│       │       ├── setup_utils.py
│       │       └── tool_message_utils.py
│       ├── app/
│       │   ├── __init__.py
│       │   ├── _app.py
│       │   ├── agent_config_watcher.py
│       │   ├── agent_context.py
│       │   ├── approvals/
│       │   │   ├── __init__.py
│       │   │   └── service.py
│       │   ├── auth.py
│       │   ├── channels/
│       │   │   ├── __init__.py
│       │   │   ├── base.py
│       │   │   ├── console/
│       │   │   │   ├── __init__.py
│       │   │   │   └── channel.py
│       │   │   ├── dingtalk/
│       │   │   │   ├── __init__.py
│       │   │   │   ├── ai_card.py
│       │   │   │   ├── channel.py
│       │   │   │   ├── constants.py
│       │   │   │   ├── content_utils.py
│       │   │   │   ├── handler.py
│       │   │   │   ├── markdown.py
│       │   │   │   └── utils.py
│       │   │   ├── discord_/
│       │   │   │   ├── __init__.py
│       │   │   │   └── channel.py
│       │   │   ├── feishu/
│       │   │   │   ├── __init__.py
│       │   │   │   ├── channel.py
│       │   │   │   ├── constants.py
│       │   │   │   └── utils.py
│       │   │   ├── imessage/
│       │   │   │   ├── __init__.py
│       │   │   │   └── channel.py
│       │   │   ├── manager.py
│       │   │   ├── matrix/
│       │   │   │   ├── __init__.py
│       │   │   │   └── channel.py
│       │   │   ├── mattermost/
│       │   │   │   ├── __init__.py
│       │   │   │   └── channel.py
│       │   │   ├── mqtt/
│       │   │   │   ├── __init__.py
│       │   │   │   └── channel.py
│       │   │   ├── qq/
│       │   │   │   ├── __init__.py
│       │   │   │   └── channel.py
│       │   │   ├── registry.py
│       │   │   ├── renderer.py
│       │   │   ├── schema.py
│       │   │   ├── telegram/
│       │   │   │   ├── __init__.py
│       │   │   │   ├── channel.py
│       │   │   │   └── format_html.py
│       │   │   ├── utils.py
│       │   │   ├── voice/
│       │   │   │   ├── __init__.py
│       │   │   │   ├── channel.py
│       │   │   │   ├── conversation_relay.py
│       │   │   │   ├── session.py
│       │   │   │   ├── twilio_manager.py
│       │   │   │   └── twiml.py
│       │   │   ├── wecom/
│       │   │   │   ├── __init__.py
│       │   │   │   ├── channel.py
│       │   │   │   └── utils.py
│       │   │   └── xiaoyi/
│       │   │       ├── __init__.py
│       │   │       ├── auth.py
│       │   │       ├── channel.py
│       │   │       ├── constants.py
│       │   │       └── utils.py
│       │   ├── console_push_store.py
│       │   ├── crons/
│       │   │   ├── __init__.py
│       │   │   ├── api.py
│       │   │   ├── executor.py
│       │   │   ├── heartbeat.py
│       │   │   ├── manager.py
│       │   │   ├── models.py
│       │   │   └── repo/
│       │   │       ├── __init__.py
│       │   │       ├── base.py
│       │   │       └── json_repo.py
│       │   ├── download_task_store.py
│       │   ├── mcp/
│       │   │   ├── __init__.py
│       │   │   ├── manager.py
│       │   │   └── watcher.py
│       │   ├── migration.py
│       │   ├── multi_agent_manager.py
│       │   ├── routers/
│       │   │   ├── __init__.py
│       │   │   ├── agent.py
│       │   │   ├── agent_scoped.py
│       │   │   ├── agents.py
│       │   │   ├── auth.py
│       │   │   ├── config.py
│       │   │   ├── console.py
│       │   │   ├── envs.py
│       │   │   ├── local_models.py
│       │   │   ├── mcp.py
│       │   │   ├── ollama_models.py
│       │   │   ├── providers.py
│       │   │   ├── schemas_config.py
│       │   │   ├── skills.py
│       │   │   ├── skills_stream.py
│       │   │   ├── token_usage.py
│       │   │   ├── tools.py
│       │   │   ├── voice.py
│       │   │   └── workspace.py
│       │   ├── runner/
│       │   │   ├── __init__.py
│       │   │   ├── api.py
│       │   │   ├── command_dispatch.py
│       │   │   ├── daemon_commands.py
│       │   │   ├── manager.py
│       │   │   ├── models.py
│       │   │   ├── query_error_dump.py
│       │   │   ├── repo/
│       │   │   │   ├── __init__.py
│       │   │   │   ├── base.py
│       │   │   │   └── json_repo.py
│       │   │   ├── runner.py
│       │   │   ├── session.py
│       │   │   ├── task_tracker.py
│       │   │   └── utils.py
│       │   └── workspace/
│       │       ├── __init__.py
│       │       ├── service_factories.py
│       │       ├── service_manager.py
│       │       └── workspace.py
│       ├── cli/
│       │   ├── __init__.py
│       │   ├── app_cmd.py
│       │   ├── auth_cmd.py
│       │   ├── channels_cmd.py
│       │   ├── chats_cmd.py
│       │   ├── clean_cmd.py
│       │   ├── cron_cmd.py
│       │   ├── daemon_cmd.py
│       │   ├── desktop_cmd.py
│       │   ├── env_cmd.py
│       │   ├── http.py
│       │   ├── init_cmd.py
│       │   ├── main.py
│       │   ├── process_utils.py
│       │   ├── providers_cmd.py
│       │   ├── shutdown_cmd.py
│       │   ├── skills_cmd.py
│       │   ├── uninstall_cmd.py
│       │   ├── update_cmd.py
│       │   └── utils.py
│       ├── config/
│       │   ├── __init__.py
│       │   ├── config.py
│       │   ├── context.py
│       │   ├── timezone.py
│       │   └── utils.py
│       ├── constant.py
│       ├── envs/
│       │   ├── __init__.py
│       │   └── store.py
│       ├── local_models/
│       │   ├── __init__.py
│       │   ├── backends/
│       │   │   ├── __init__.py
│       │   │   ├── base.py
│       │   │   ├── llamacpp_backend.py
│       │   │   └── mlx_backend.py
│       │   ├── chat_model.py
│       │   ├── factory.py
│       │   ├── manager.py
│       │   ├── schema.py
│       │   └── tag_parser.py
│       ├── providers/
│       │   ├── __init__.py
│       │   ├── anthropic_provider.py
│       │   ├── gemini_provider.py
│       │   ├── models.py
│       │   ├── ollama_manager.py
│       │   ├── ollama_provider.py
│       │   ├── openai_chat_model_compat.py
│       │   ├── openai_provider.py
│       │   ├── provider.py
│       │   ├── provider_manager.py
│       │   └── retry_chat_model.py
│       ├── security/
│       │   ├── __init__.py
│       │   ├── skill_scanner/
│       │   │   ├── __init__.py
│       │   │   ├── analyzers/
│       │   │   │   ├── __init__.py
│       │   │   │   └── pattern_analyzer.py
│       │   │   ├── data/
│       │   │   │   └── default_policy.yaml
│       │   │   ├── models.py
│       │   │   ├── rules/
│       │   │   │   └── signatures/
│       │   │   │       ├── command_injection.yaml
│       │   │   │       ├── data_exfiltration.yaml
│       │   │   │       ├── hardcoded_secrets.yaml
│       │   │   │       ├── obfuscation.yaml
│       │   │   │       ├── prompt_injection.yaml
│       │   │   │       ├── resource_abuse.yaml
│       │   │   │       ├── social_engineering.yaml
│       │   │   │       ├── supply_chain.yaml
│       │   │   │       └── unauthorized_tool_use.yaml
│       │   │   ├── scan_policy.py
│       │   │   └── scanner.py
│       │   └── tool_guard/
│       │       ├── __init__.py
│       │       ├── approval.py
│       │       ├── engine.py
│       │       ├── guardians/
│       │       │   ├── __init__.py
│       │       │   └── rule_guardian.py
│       │       ├── models.py
│       │       ├── rules/
│       │       │   └── dangerous_shell_commands.yaml
│       │       └── utils.py
│       ├── token_usage/
│       │   ├── __init__.py
│       │   ├── manager.py
│       │   └── model_wrapper.py
│       ├── tokenizer/
│       │   ├── merges.txt
│       │   ├── tokenizer.json
│       │   ├── tokenizer_config.json
│       │   └── vocab.json
│       ├── tunnel/
│       │   ├── __init__.py
│       │   ├── binary_manager.py
│       │   └── cloudflare.py
│       └── utils/
│           ├── __init__.py
│           ├── logging.py
│           └── telemetry.py
├── tests/
│   ├── __init__.py
│   ├── integrated/
│   │   ├── test_app_startup.py
│   │   └── test_version.py
│   └── unit/
│       ├── channels/
│       │   ├── __init__.py
│       │   └── test_qq_channel.py
│       ├── cli/
│       │   ├── test_cli_shutdown.py
│       │   ├── test_cli_update.py
│       │   └── test_cli_version.py
│       ├── memory/
│       │   └── test_copaw_token_counter.py
│       ├── providers/
│       │   ├── test_anthropic_provider.py
│       │   ├── test_default_provider.py
│       │   ├── test_gemini_provider.py
│       │   ├── test_kimi_provider.py
│       │   ├── test_ollama_manager_timeout.py
│       │   ├── test_ollama_provider.py
│       │   ├── test_openai_provider.py
│       │   ├── test_openai_stream_toolcall_compat.py
│       │   └── test_provider_manager.py
│       └── workspace/
│           ├── __init__.py
│           ├── test_agent_creation.py
│           ├── test_agent_id.py
│           ├── test_agent_model.py
│           ├── test_cli_agent_id.py
│           ├── test_prompt.py
│           └── test_workspace.py
└── website/
    ├── README.md
    ├── index.html
    ├── package.json
    ├── public/
    │   ├── docs/
    │   │   ├── channels.en.md
    │   │   ├── channels.zh.md
    │   │   ├── cli.en.md
    │   │   ├── cli.zh.md
    │   │   ├── commands.en.md
    │   │   ├── commands.zh.md
    │   │   ├── community.en.md
    │   │   ├── community.zh.md
    │   │   ├── comparison.en.md
    │   │   ├── comparison.zh.md
    │   │   ├── config.en.md
    │   │   ├── config.zh.md
    │   │   ├── console.en.md
    │   │   ├── console.zh.md
    │   │   ├── context.en.md
    │   │   ├── context.zh.md
    │   │   ├── contributing.en.md
    │   │   ├── contributing.zh.md
    │   │   ├── desktop.en.md
    │   │   ├── desktop.zh.md
    │   │   ├── faq.en.md
    │   │   ├── faq.zh.md
    │   │   ├── heartbeat.en.md
    │   │   ├── heartbeat.zh.md
    │   │   ├── intro.en.md
    │   │   ├── intro.zh.md
    │   │   ├── mcp.en.md
    │   │   ├── mcp.zh.md
    │   │   ├── memory.en.md
    │   │   ├── memory.zh.md
    │   │   ├── models.en.md
    │   │   ├── models.zh.md
    │   │   ├── multi-agent.en.md
    │   │   ├── multi-agent.zh.md
    │   │   ├── quickstart.en.md
    │   │   ├── quickstart.zh.md
    │   │   ├── roadmap.en.md
    │   │   ├── roadmap.zh.md
    │   │   ├── security.en.md
    │   │   ├── security.zh.md
    │   │   ├── skills.en.md
    │   │   └── skills.zh.md
    │   ├── release-notes/
    │   │   ├── v0.0.4.md
    │   │   ├── v0.0.4.zh.md
    │   │   ├── v0.0.5-beta.1.md
    │   │   ├── v0.0.5-beta.1.zh.md
    │   │   ├── v0.0.5-beta.2.md
    │   │   ├── v0.0.5-beta.2.zh.md
    │   │   ├── v0.0.5-beta.3.md
    │   │   ├── v0.0.5-beta.3.zh.md
    │   │   ├── v0.0.5.md
    │   │   ├── v0.0.5.zh.md
    │   │   ├── v0.0.6.md
    │   │   ├── v0.0.6.zh.md
    │   │   ├── v0.0.7.md
    │   │   ├── v0.0.7.zh.md
    │   │   ├── v0.1.0.md
    │   │   └── v0.1.0.zh.md
    │   └── site.config.json
    ├── scripts/
    │   ├── build-search-index.mjs
    │   └── spa-fallback-pages.mjs
    ├── src/
    │   ├── App.tsx
    │   ├── components/
    │   │   ├── BrandStory.tsx
    │   │   ├── CatPawIcon.tsx
    │   │   ├── CopawLogo.tsx
    │   │   ├── CopawMascot.tsx
    │   │   ├── DocSearch.tsx
    │   │   ├── DocSearchResults.tsx
    │   │   ├── Ecosystem.tsx
    │   │   ├── Features.tsx
    │   │   ├── FollowUs.tsx
    │   │   ├── Footer.tsx
    │   │   ├── Hero.tsx
    │   │   ├── MermaidBlock.tsx
    │   │   ├── Nav.tsx
    │   │   ├── QuickStart.tsx
    │   │   ├── Testimonials.tsx
    │   │   └── UseCases.tsx
    │   ├── config.ts
    │   ├── data/
    │   │   └── testimonials.ts
    │   ├── i18n.ts
    │   ├── index.css
    │   ├── lib/
    │   │   └── docsSearch.ts
    │   ├── main.tsx
    │   ├── pages/
    │   │   ├── Docs.tsx
    │   │   ├── Home.tsx
    │   │   └── ReleaseNotes.tsx
    │   └── vite-env.d.ts
    ├── tsconfig.json
    └── vite.config.ts

================================================
FILE CONTENTS
================================================

================================================
FILE: .dockerignore
================================================
# Git and IDE
.git
.gitignore
.idea
.vscode
*.md
!README.md

# Python dev and cache
__pycache__
*.py[cod]
*$py.class
.venv
venv
uv.lock
.pytest_cache
.coverage
htmlcov
.tox
.mypy_cache
.ruff_cache

# Tests (not needed in runtime image)
tests
test.py
*_test.py
pytest.ini
.pre-commit-config.yaml
.flake8
.eslintrc
.stylelintrc

# Frontend: exclude build artifacts; ship pre-built src/console/dist in image
website
console/node_modules
console/dist
console/.vite
node_modules
**/node_modules
**/dist
!src/copaw/console/dist
!src/copaw/console/dist/**
**/.vite

# Example and local config (mount at runtime instead)
example
config.json
jobs.json
sessions_mount_dir

# Misc
.env
.env.*
!.env.example
.DS_Store
*.log
logs
cookbook


================================================
FILE: .flake8
================================================
[flake8]
exclude =
  scripts/*
  src/agentscope/rpc/*
max-line-length = 79
inline-quotes = "
avoid-escape = no
ignore =
    F401
    F403
    W503
    E731

================================================
FILE: .gitattributes
================================================
*.bat text eol=crlf


================================================
FILE: .github/ISSUE_TEMPLATE/1-question.md
================================================
---
name: Question / Discussion
about: Ask a question or start a discussion (consider GitHub Discussions for open-ended topics)
title: "[Question]: "
labels: ["question", "triage"]
assignees: []
---

## Question or topic

[What would you like to ask or discuss?]

## Context

[Relevant setup: CoPaw version, channel, skill, or use case. This helps others answer.]

## Tried so far

[Optional: what you already tried or read (docs, issues, etc.).]

---

**Note:** For general questions or ideas, [GitHub Discussions](https://github.com/agentscope-ai/CoPaw/discussions) may get more visibility. Use this template when the answer might lead to a bug report, feature request, or doc change.


================================================
FILE: .github/ISSUE_TEMPLATE/2-feature_request.md
================================================
---
name: Feature Request
about: Suggest a new feature or enhancement
title: "[Feature]: "
labels: ["enhancement", "triage"]
assignees: []
---

## Summary

[One or two sentences: what do you want and why?]

## Component(s) Affected

- [ ] Core / Backend (app, agents, config, providers, utils, local_models)
- [ ] Console (frontend web UI)
- [ ] Channels (DingTalk, Feishu, QQ, Discord, iMessage, etc.)
- [ ] Skills
- [ ] CLI
- [ ] Documentation (website)
- [ ] Tests
- [ ] CI/CD
- [ ] Scripts / Deploy

## Problem / Motivation

[What problem does this solve? Who benefits?]

## Proposed Solution

[Describe the feature or change you have in mind. Be as specific as possible.]

## Alternatives Considered

[Any other approaches or workarounds you thought about.]

## Additional Context

[Screenshots, examples, links to docs or similar features elsewhere.]

## Willing to Contribute

- [ ] I am willing to open a PR for this feature (after discussion).


================================================
FILE: .github/ISSUE_TEMPLATE/3-documentation.md
================================================
---
name: Documentation
about: Report docs issues or suggest documentation improvements
title: "[Docs]: "
labels: ["documentation", "triage"]
assignees: []
---

## Summary

[What is wrong or missing in the docs? One or two sentences.]

**Docs location:** [e.g. website, README, `website/public/docs/quickstart.en.md`, Console UI copy]

## Type

- [ ] Typo / wording fix
- [ ] Outdated or incorrect content
- [ ] Missing section or topic
- [ ] Broken link or image
- [ ] Translation (zh/en)
- [ ] Other

## Details

[Describe the issue or the change you suggest. If possible, point to the exact file or URL.]

**Current (if applicable):**
[Quote or describe current text.]

**Suggested:**
[Proposed text or structure.]

## Additional Context

[Optional: related issues, screenshots, which audience this affects.]


================================================
FILE: .github/ISSUE_TEMPLATE/4-bug_report.md
================================================
---
name: Bug Report
about: Report a bug or unexpected behavior
title: "[Bug]: "
labels: ["bug", "triage"]
assignees: []
---

## CoPaw Version

[Provide the version of CoPaw you are using, e.g. 0.x.x or git commit hash.]
[Using `copaw --version` in your command line or checking the version in the console UI can help.]

## Description

[Describe the bug clearly and concisely. What happened vs what you expected?]

**Related PR(s):** #(optional)

**Security considerations:** [If applicable, e.g. auth, env/config exposure]

## Component(s) Affected

- [ ] Core / Backend (app, agents, config, providers, utils, local_models)
- [ ] Console (frontend web UI)
- [ ] Channels (DingTalk, Feishu, QQ, Discord, iMessage, etc.)
- [ ] Skills
- [ ] CLI
- [ ] Documentation (website)
- [ ] Tests
- [ ] CI/CD
- [ ] Scripts / Deploy

## Environment

- **CoPaw version:** [e.g. 0.x.x or git commit]
- **OS:** [e.g. macOS 14, Ubuntu 22.04, Windows 11]
- **Install method:** [pip / one-line install / Docker / from source]
- **Python version (if applicable):** [e.g. 3.10]

## Steps to Reproduce

1.
2.
3.

## Actual vs Expected

- **Actual:**
- **Expected:**

## Logs / Screenshots

[Paste relevant log output or attach screenshots. Use code blocks for logs.]

```
(paste logs here)
```

## Additional Notes

[Optional: workarounds, similar issues, etc.]


================================================
FILE: .github/ISSUE_TEMPLATE/5-support_environment.md
================================================
---
name: Support / Environment
description: Issues with install, Docker, platform, or runtime environment
title: "[Support]: "
labels: ["support", "triage"]
assignees: []
---

## Summary

[What went wrong during install, run, or in your environment?]

## Environment

- **OS:** [e.g. macOS 14 (Apple Silicon / Intel), Ubuntu 22.04, Windows 11]
- **Install method:** [pip / one-line install (install.sh or install.ps1) / Docker / from source / ModelScope]
- **CoPaw version:** [e.g. 0.x.x or commit]
- **Python version (if applicable):** [e.g. 3.10, 3.12]

## Steps you took

1.
2.
3.

## What happened

[Error message, log excerpt, or behavior. Paste relevant output in code blocks.]

```
(paste here)
```

## Expected

[What you expected to happen.]

## Additional context

[Optional: conda/venv, WSL, proxy, firewall, or other env details.]


================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
# Issue template chooser. Templates are listed alphanumerically.
# See: https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/configuring-issue-templates-for-your-repository

blank_issues_enabled: true

contact_links:
  - name: GitHub Discussions
    url: https://github.com/agentscope-ai/CoPaw/discussions
    about: Ask questions, share ideas, or discuss CoPaw here.
  - name: View docs
    url: https://copaw.agentscope.io/
    about: CoPaw docs and guides.


================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
## Description

[Describe what this PR does and why]

**Related Issue:** Fixes #(issue_number) or Relates to #(issue_number)

**Security Considerations:** [If applicable, e.g. channel auth, env/config handling]

## Type of Change

- [ ] Bug fix
- [ ] New feature
- [ ] Breaking change
- [ ] Documentation
- [ ] Refactoring

## Component(s) Affected

- [ ] Core / Backend (app, agents, config, providers, utils, local_models)
- [ ] Console (frontend web UI)
- [ ] Channels (DingTalk, Feishu, QQ, Discord, iMessage, etc.)
- [ ] Skills
- [ ] CLI
- [ ] Documentation (website)
- [ ] Tests
- [ ] CI/CD
- [ ] Scripts / Deploy

## Checklist

- [ ] I ran `pre-commit run --all-files` locally and it passes
- [ ] If pre-commit auto-fixed files, I committed those changes and reran checks
- [ ] I ran tests locally (`pytest` or as relevant) and they pass
- [ ] Documentation updated (if needed)
- [ ] Ready for review

## Testing

[How to test these changes]

## Local Verification Evidence

```bash
pre-commit run --all-files
# paste summary result

pytest
# paste summary result
```

## Additional Notes

[Optional: any other context]


================================================
FILE: .github/condarc
================================================
# Explicit channels so conda does not warn about implicit 'defaults'
channels:
  - defaults


================================================
FILE: .github/workflows/deploy-website.yml
================================================
# Deploy website to GitHub Pages (see scripts/website_build.sh).
# Custom domain: copaw.agentscope.io (CNAME).
name: Deploy website to GitHub Pages

on:
  release:
    types: [published]
  workflow_dispatch:

permissions:
  contents: write

concurrency:
  group: deploy-website
  cancel-in-progress: false

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 9

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: "20"
          cache: "pnpm"
          cache-dependency-path: website/pnpm-lock.yaml

      - name: Install dependencies
        working-directory: website
        run: pnpm install --frozen-lockfile

      - name: Build
        working-directory: website
        env:
          VITE_BASE_PATH: "/"
        run: pnpm run build

      # Copy installation scripts so they are served from the website
      # (e.g. copaw.agentscope.io/install.sh) instead of raw.githubusercontent.com,
      # which may be blocked by some firewalls.
      - name: Copy installation scripts to dist
        run: cp scripts/install.sh scripts/install.ps1 scripts/install.bat website/dist/

      # 404.html fallback for unknown paths (real routes from build script).
      - name: 404 fallback for GitHub Pages
        run: cp website/dist/index.html website/dist/404.html

      - name: Deploy to GitHub Pages
        uses: peaceiris/actions-gh-pages@v4
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_dir: website/dist
          cname: copaw.agentscope.io


================================================
FILE: .github/workflows/desktop-release.yml
================================================
# Build CoPaw Desktop: Windows (conda-pack + NSIS), macOS (conda-pack -> .app)
# Runs automatically on release publish; also manually via Actions tab.

name: CoPaw Desktop Build

on:
  release:
    types: [published]
  workflow_dispatch:

permissions:
  contents: read

jobs:
  build-windows:
    runs-on: windows-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          submodules: recursive

      - name: Get version
        id: version
        shell: pwsh
        run: |
          $content = Get-Content src/copaw/__version__.py -Raw
          if ($content -match '__version__\s*=\s*"([^"]+)"') {
            $v = $Matches[1]
          } else {
            throw "Failed to extract version from __version__.py"
          }
          "version<<EOF" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
          $v | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
          "EOF" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append

      - name: Set up Node
        uses: actions/setup-node@v4
        with:
          node-version: "20"
          cache: "npm"
          cache-dependency-path: console/package-lock.json

      - name: Set up Miniconda
        uses: conda-incubator/setup-miniconda@v3
        with:
          auto-update-conda: true
          python-version: "3.10"
          activate-environment: "copaw-build"
          condarc-file: .github/condarc
          conda-remove-defaults: false

      - name: Install NSIS
        uses: negrutiu/nsis-install@v2

      - name: Clean dist directory
        shell: pwsh
        run: |
          if (Test-Path dist) {
            Remove-Item -Recurse -Force dist
          }
          New-Item -ItemType Directory -Force -Path dist | Out-Null

      - name: Build Windows installer
        shell: pwsh
        run: conda run -n copaw-build pwsh -File ./scripts/pack/build_win.ps1

      - name: List dist (on failure)
        if: failure()
        shell: pwsh
        run: |
          Write-Host "=== dist contents ==="
          Get-ChildItem -Path dist -Recurse -Depth 2 -ErrorAction SilentlyContinue | ForEach-Object { $_.FullName }
          if (Test-Path dist\win-unpacked) {
            Write-Host "=== dist\win-unpacked (first 30) ==="
            Get-ChildItem -Path dist\win-unpacked -ErrorAction SilentlyContinue | Select-Object -First 30 | ForEach-Object { $_.Name }
          }

      - name: Upload Windows artifact
        uses: actions/upload-artifact@v4
        with:
          name: CoPaw-Desktop-Windows-${{ steps.version.outputs.version }}
          path: dist/CoPaw-Setup-*.exe

  build-macos:
    runs-on: macos-14
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          submodules: recursive

      - name: Get version
        id: version
        run: echo "version=$(sed -n 's/^__version__[[:space:]]*=[[:space:]]*"\([^"]*\)".*/\1/p' src/copaw/__version__.py)" >> $GITHUB_OUTPUT

      - name: Set up Node
        uses: actions/setup-node@v4
        with:
          node-version: "20"
          cache: "npm"
          cache-dependency-path: console/package-lock.json

      - name: Set up Miniconda
        uses: conda-incubator/setup-miniconda@v3
        with:
          auto-update-conda: true
          python-version: "3.10"
          activate-environment: "copaw-build"
          condarc-file: .github/condarc
          conda-remove-defaults: false

      - name: Clean dist directory
        run: rm -rf dist && mkdir -p dist

      - name: Build macOS .app
        run: |
          chmod +x scripts/pack/build_macos.sh
          conda run -n copaw-build bash -c 'CREATE_ZIP=1 ./scripts/pack/build_macos.sh'

      - name: Upload macOS artifact
        uses: actions/upload-artifact@v4
        with:
          name: CoPaw-Desktop-macOS-${{ steps.version.outputs.version }}
          path: dist/CoPaw-*.zip

  upload-release:
    needs: [build-windows, build-macos]
    if: github.event_name == 'release'
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - name: Download all artifacts
        uses: actions/download-artifact@v4

      - name: Move artifacts to root for upload
        run: |
          mv CoPaw-Desktop-Windows-*/CoPaw-Setup-*.exe . 2>/dev/null || true
          mv CoPaw-Desktop-macOS-*/CoPaw-*.zip . 2>/dev/null || true

      - name: Upload to Release
        uses: softprops/action-gh-release@v2
        with:
          files: |
            CoPaw-Setup-*.exe
            CoPaw-*-macOS.zip
          fail-on-unmatched-files: false
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}


================================================
FILE: .github/workflows/docker-release.yml
================================================
# Build CoPaw multi-arch Docker image and push to DockerHub + Aliyun ACR on release.
# Pre-release: update <version> and pre only.
# Formal release: update <version>, pre and latest.
name: Docker Build and Push on Release

on:
  release:
    types: [published]
  workflow_dispatch:
    inputs:
      version:
        description: "Image tag (e.g. v1.2.3 or v1.2.3-beta.1)"
        required: true
        type: string
      is_prerelease:
        description: "Pre-release (do not update latest tag)"
        required: false
        type: boolean
        default: false

env:
  ACR_REGISTRY: agentscope-registry.ap-southeast-1.cr.aliyuncs.com
  IMAGE: agentscope/copaw

jobs:
  build-and-push:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          submodules: recursive

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Log in to DockerHub
        uses: docker/login-action@v3
        with:
          registry: docker.io
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}

      - name: Log in to Aliyun ACR
        uses: docker/login-action@v3
        with:
          registry: ${{ env.ACR_REGISTRY }}
          username: ${{ secrets.ALIYUN_ACR_USERNAME }}
          password: ${{ secrets.ALIYUN_ACR_PASSWORD }}

      - name: Set version and prerelease flag
        id: vars
        run: |
          if [ -n "${{ github.event.release.tag_name }}" ]; then
            VERSION="${{ github.event.release.tag_name }}"
            echo "version=${VERSION}" >> $GITHUB_OUTPUT

            # Parse version to determine if it's a pre-release
            # Beta/alpha/rc/dev are pre-releases, but .post is NOT
            if [[ "$VERSION" =~ (beta|alpha|rc|dev) ]]; then
              echo "is_prerelease=true" >> $GITHUB_OUTPUT
            elif [[ "$VERSION" =~ post ]]; then
              # .post versions should update latest (post-release patches)
              echo "is_prerelease=false" >> $GITHUB_OUTPUT
            else
              # Use GitHub's prerelease flag as fallback
              echo "is_prerelease=${{ github.event.release.prerelease }}" >> $GITHUB_OUTPUT
            fi
          else
            echo "version=${{ github.event.inputs.version }}" >> $GITHUB_OUTPUT
            echo "is_prerelease=${{ github.event.inputs.is_prerelease }}" >> $GITHUB_OUTPUT
          fi

      - name: Build and push multi-arch (version + pre [+ latest])
        env:
          VERSION: ${{ steps.vars.outputs.version }}
          IS_PRERELEASE: ${{ steps.vars.outputs.is_prerelease }}
          COPAW_DISABLED_CHANNELS: "imessage"
        run: |
          TAGS="-t ${ACR_REGISTRY}/${IMAGE}:${VERSION} -t ${ACR_REGISTRY}/${IMAGE}:pre"
          TAGS="${TAGS} -t docker.io/${IMAGE}:${VERSION} -t docker.io/${IMAGE}:pre"
          if [ "${IS_PRERELEASE}" != "true" ]; then
            TAGS="${TAGS} -t ${ACR_REGISTRY}/${IMAGE}:latest -t docker.io/${IMAGE}:latest"
          fi
          docker buildx build --platform linux/amd64,linux/arm64 \
            -f deploy/Dockerfile \
            --build-arg COPAW_DISABLED_CHANNELS="${COPAW_DISABLED_CHANNELS}" \
            ${TAGS} --push .


================================================
FILE: .github/workflows/first-time-contributor-welcome.yml
================================================
name: First-Time Contributor Welcome

on:
  pull_request_target:
    types: [closed]

jobs:
  welcome-comment:
    if: >-
      github.event.pull_request.merged == true &&
      contains(toJSON(github.event.pull_request.labels.*.name), 'first-time-contributor')
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write
    steps:
      - name: Post welcome comment
        uses: actions/github-script@v7
        with:
          script: |
            const author = context.payload.pull_request.user.login;
            const prNumber = context.payload.pull_request.number;

            const body = [
              `## Welcome to CoPaw! :tada:`,
              ``,
              `Thank you @${author} for your first contribution! Your PR has been merged. :rocket:`,
              ``,
              `We'd love to give you a shout-out in our release notes! If you're comfortable sharing, ` +
              `please reply to this comment with your social media handles using the format below:`,
              ``,
              '```',
              `discord: your_discord_handle`,
              `x: your_x_handle`,
              `xiaohongshu: your_xiaohongshu_id`,
              '```',
              ``,
              `> **Note:** Only share what you're comfortable with — all fields are optional.`,
              ``,
              `Thanks again for helping make CoPaw better!`,
            ].join('\n');

            await github.rest.issues.createComment({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: prNumber,
              body,
            });

            core.info(`Posted welcome comment on PR #${prNumber} for @${author}`);


================================================
FILE: .github/workflows/npm-format.yml
================================================
name: NPM Format (website & console)

on: [push, pull_request]

jobs:
  website:
    name: website format check
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: website
    steps:
      - uses: actions/checkout@v4

      - name: Setup pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 9

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: "20"
          cache: "pnpm"
          cache-dependency-path: website/pnpm-lock.yaml

      - name: Install dependencies
        run: pnpm install --frozen-lockfile

      - name: Run format check
        run: pnpm run format:check

  console:
    name: console format check
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: console
    steps:
      - uses: actions/checkout@v4

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: "20"
          cache: "npm"
          cache-dependency-path: console/package-lock.json

      - name: Install dependencies
        run: npm ci

      - name: Run format check
        run: npm run format:check


================================================
FILE: .github/workflows/pr-label.yml
================================================
name: PR Labeler

on:
  pull_request_target:
    types: [opened]

jobs:
  first-time-contributor:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: write
    steps:
      - name: Check if first-time contributor
        uses: actions/github-script@v7
        with:
          script: |
            const author = context.payload.pull_request.user.login;
            let isFirstTime = false;

            try {
              const { data: searchResult } = await github.rest.search.issuesAndPullRequests({
                q: `repo:${context.repo.owner}/${context.repo.repo} type:pr author:${author} is:closed`,
              });
              isFirstTime = searchResult.total_count === 0;
              if (!isFirstTime) {
                core.info(`${author} has ${searchResult.total_count} closed PRs, skipping label`);
              }
            } catch (err) {
              if (err.status === 422 && err.message && err.message.includes('cannot be searched')) {
                core.warning(`Search API cannot look up author ${author}, skipping first-time label`);
                return;
              }
              throw err;
            }

            if (isFirstTime) {
              await github.rest.issues.addLabels({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: context.payload.pull_request.number,
                labels: ['first-time-contributor'],
              });
              core.info(`Labeled PR #${context.payload.pull_request.number} as first-time-contributor`);
            }


================================================
FILE: .github/workflows/pre-commit.yml
================================================
name: Pre-commit Checks

on: [push, pull_request]

jobs:
  run:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: true
    env:
      OS: ubuntu-latest
      PYTHON: "3.10"
    steps:
      - uses: actions/checkout@v4

      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.10"

      - name: Update setuptools and wheel
        run: |
          pip install setuptools==68.2.2 wheel==0.41.2

      - name: Install CoPaw with dev dependencies
        run: |
          pip install -q -e ".[dev]"

      - name: Install pre-commit hooks
        run: |
          pre-commit install

      - name: Run pre-commit
        run: |
          pre-commit run --all-files > pre-commit.log 2>&1 || true
          cat pre-commit.log
          if grep -q Failed pre-commit.log; then
            echo -e "\e[41m  [**FAIL**] pre-commit checks failed. Run 'pre-commit run --all-files' locally, commit any auto-fixes, then push again. \e[0m"
            exit 1
          fi
          echo -e "\e[46m  ********************************Passed******************************** \e[0m"


================================================
FILE: .github/workflows/publish-pypi.yml
================================================
# Publish Python package to PyPI when a release is created.
# Build includes console frontend (see scripts/wheel_build.sh).

name: Publish Python Package to PyPI

on:
  workflow_dispatch:
  release:
    types: [published]

permissions:
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.10"

      - name: Set up Node (for console build)
        uses: actions/setup-node@v4
        with:
          node-version: "20"
          cache: "npm"
          cache-dependency-path: console/package-lock.json

      - name: Build console frontend
        run: |
          cd console && npm ci && npm run build

      - name: Copy console build into package
        run: |
          rm -rf src/copaw/console/*
          mkdir -p src/copaw/console
          cp -R console/dist/* src/copaw/console/

      - name: Install build dependencies
        run: |
          python -m pip install --upgrade pip
          pip install setuptools wheel build

      - name: Build package
        run: python -m build

      - name: Publish package to PyPI
        uses: pypa/gh-action-pypi-publish@release/v1
        with:
          user: __token__
          password: ${{ secrets.PYPI_API_TOKEN }}


================================================
FILE: .github/workflows/tests.yml
================================================
name: Tests

on:
  push:
    branches: [main, master, dev, develop]
    paths:
      - 'src/**'
      - 'tests/**'
      - 'pyproject.toml'
      - 'setup.py'
      - '.github/workflows/tests.yml'
  pull_request:
    branches: [main, master, dev, develop]
    paths:
      - 'src/**'
      - 'tests/**'
      - 'pyproject.toml'
      - 'setup.py'
      - '.github/workflows/tests.yml'
  workflow_dispatch:

jobs:
  approval-gate:
    name: Maintainer Approval
    runs-on: ubuntu-latest
    environment: maintainer-approved
    steps:
      - name: Approval granted
        run: echo "Approved by maintainer"

  unit-tests:
    name: Unit Tests - py${{ matrix.python-version }} - ${{ matrix.os }}
    needs: approval-gate
    if: |
      always() &&
      needs.approval-gate.result == 'success'
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        python-version: ["3.10", "3.13"]
        os: [ubuntu-latest]
        include:
          - os: macos-latest
            python-version: "3.10"
          - os: windows-latest
            python-version: "3.10"

    steps:
      - uses: actions/checkout@v4

      - name: Set up Node.js (for console build)
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
          cache-dependency-path: console/package-lock.json

      - name: Build console frontend
        shell: bash
        run: |
          cd console && npm ci && npm run build

      - name: Copy console build into package
        shell: bash
        run: |
          rm -rf src/copaw/console/*
          mkdir -p src/copaw/console
          cp -R console/dist/* src/copaw/console/

      - name: Set up Python ${{ matrix.python-version }}
        uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}
          cache: 'pip'

      - name: Install dependencies
        shell: bash
        run: |
          python -m pip install --upgrade pip
          if [[ "${{ matrix.os }}" == "macos-latest" ]]; then
            pip install -e ".[dev,local,ollama]"
            pip install 'mlx-lm>=0.10.0' || echo "mlx-lm install skipped"
            pip install --only-binary=llama-cpp-python 'llama-cpp-python>=0.3.0' || \
              echo "⚠️ llama-cpp-python prebuilt wheel not available, skipping"
          else
            pip install -e ".[dev,full]"
          fi

      - name: Run all unit tests
        shell: bash
        run: |
          pytest tests/unit -v

  integrated-tests:
    name: Integrated Tests - py${{ matrix.python-version }} - ${{ matrix.os }}
    needs: approval-gate
    if: |
      always() &&
      needs.approval-gate.result == 'success'
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        python-version: ["3.10", "3.13"]
        os: [ubuntu-latest]
        include:
          - os: macos-latest
            python-version: "3.10"
          - os: windows-latest
            python-version: "3.10"

    steps:
      - uses: actions/checkout@v4

      - name: Set up Node.js (for console build)
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
          cache-dependency-path: console/package-lock.json

      - name: Build console frontend
        shell: bash
        run: |
          cd console && npm ci && npm run build

      - name: Copy console build into package
        shell: bash
        run: |
          rm -rf src/copaw/console/*
          mkdir -p src/copaw/console
          cp -R console/dist/* src/copaw/console/

      - name: Set up Python ${{ matrix.python-version }}
        uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}
          cache: 'pip'

      - name: Install dependencies
        shell: bash
        run: |
          python -m pip install --upgrade pip
          if [[ "${{ matrix.os }}" == "macos-latest" ]]; then
            pip install -e ".[dev,local,ollama]"
            pip install 'mlx-lm>=0.10.0' || echo "mlx-lm install skipped"
            pip install --only-binary=llama-cpp-python 'llama-cpp-python>=0.3.0' || \
              echo "⚠️ llama-cpp-python prebuilt wheel not available, skipping"
          else
            pip install -e ".[dev,full]"
          fi

      - name: Check if integrated tests exist
        id: check-integrated
        shell: bash
        run: |
          if [ -d "tests/integrated" ] && compgen -G "tests/integrated/*.py" > /dev/null; then
            echo "has_tests=true" >> "$GITHUB_OUTPUT"
          else
            echo "has_tests=false" >> "$GITHUB_OUTPUT"
          fi

      - name: Run integrated tests
        if: steps.check-integrated.outputs.has_tests == 'true'
        shell: bash
        run: |
          pytest tests/integrated -v

  coverage-report:
    name: Coverage Report
    needs: approval-gate
    if: |
      always() &&
      needs.approval-gate.result == 'success'
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: write
    steps:
      - uses: actions/checkout@v4

      - name: Set up Node.js (for console build)
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
          cache-dependency-path: console/package-lock.json

      - name: Build console frontend
        shell: bash
        run: |
          cd console && npm ci && npm run build

      - name: Copy console build into package
        shell: bash
        run: |
          rm -rf src/copaw/console/*
          mkdir -p src/copaw/console
          cp -R console/dist/* src/copaw/console/

      - name: Set up Python 3.12
        uses: actions/setup-python@v5
        with:
          python-version: "3.12"
          cache: 'pip'

      - name: Install dependencies
        shell: bash
        run: |
          python -m pip install --upgrade pip
          pip install -e ".[dev,full]"

      - name: Run all tests with coverage
        shell: bash
        run: |
          pytest tests/ \
            -v \
            --cov=src/copaw \
            --cov-report=xml \
            --cov-report=term-missing \
            --cov-report=html

      - name: Generate coverage comment on PR
        if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository
        uses: orgoro/coverage@v3.2
        with:
          coverageFile: coverage.xml
          token: ${{ github.token }}
          thresholdAll: 0.0
          thresholdNew: 0.0
          thresholdModified: 0.0

  test-summary:
    name: Test Summary
    needs: [approval-gate, unit-tests, integrated-tests, coverage-report]
    if: always()
    runs-on: ubuntu-latest
    steps:
      - name: Check test results
        shell: bash
        run: |
          echo "Approval gate: ${{ needs.approval-gate.result }}"
          echo "Unit tests: ${{ needs.unit-tests.result }}"
          echo "Integrated tests: ${{ needs.integrated-tests.result }}"

          if [ "${{ needs.approval-gate.result }}" != "success" ]; then
            echo "❌ Approval not granted"
            exit 1
          fi

          if [ "${{ needs.unit-tests.result }}" = "failure" ] || \
             [ "${{ needs.integrated-tests.result }}" = "failure" ]; then
            echo "❌ Some tests failed"
            exit 1
          else
            echo "✅ All tests passed"
          fi


================================================
FILE: .gitignore
================================================
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/.pnp
.pnp.js
node_modules/
sessions_mount_dir/

# testing
/coverage

# cookbook
cookbook/_build

# production
/build
dist/
.wheelshim/
# Console frontend build (generated in Docker/CI, do not commit)
src/copaw/console/dist/
src/copaw/console/

# frontend (website)
website/node_modules/
website/.vite/
website/tsconfig.tsbuildinfo
website/dist/
# Search index generated by scripts/build-search-index.mjs (run during build)
website/public/search-index.json

# misc
.env
.env.*
providers.json
envs.json
!.env.example
!.env.template
__pycache__/
*.db
*.rdb
*.egg-info/
*.zip
config.json

# IDEs and editors
.idea/
.vscode/
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

# Logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
openapi-ts*.log

# MacOS
.DS_Store

# Windows
Thumbs.db
ehthumbs.db
Desktop.ini

# Linux
*~

# Python
*.py[cod]
*$py.class
uv.lock
venv/
.venv/
providers.json

# Logs
logs/
*.log

# vibe coding
.claude
openspec
/AGENTS.md
CLAUDE.md
*.tmp

# Build cache
.cache/  # Includes conda_unpack_wheels/ for Windows packaging workaround


================================================
FILE: .pre-commit-config.yaml
================================================
repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.3.0
    hooks:
      - id: check-ast
        exclude: '(?x)(.*/skills/.*|^scripts/pack/)'
      - id: sort-simple-yaml
        exclude: '(?x)(.*/skills/.*|^scripts/pack/)'
      - id: check-yaml
        exclude: |
          (?x)^(
              meta.yaml
          )$
      - id: check-xml
        exclude: '(?x)(.*/skills/.*|^scripts/pack/)'
      - id: check-toml
      - id: check-docstring-first
        exclude: '(?x)(.*/skills/.*|^scripts/pack/)'
      - id: check-json
        exclude: '(?x)(.*/skills/.*|^scripts/pack/)'
      - id: fix-encoding-pragma
        exclude: '(?x)(.*/skills/.*|^scripts/pack/)'
      - id: detect-private-key
      - id: trailing-whitespace
        exclude: '(?x)(.*/skills/.*|^scripts/pack/)'
  - repo: https://github.com/asottile/add-trailing-comma
    rev: v3.1.0
    hooks:
      - id: add-trailing-comma
        exclude: '(?x)(.*/skills/.*|^scripts/pack/)'
  - repo: https://github.com/pre-commit/mirrors-mypy
    rev: v1.7.0
    hooks:
      - id: mypy
        exclude:
          (?x)(
          pb2\.py$
          | grpc\.py$
          | ^docs
          | \.html$
          | .*/skills/.*
          )
        args: [
          --ignore-missing-imports,
          --disable-error-code=var-annotated,
          --disable-error-code=union-attr,
          --disable-error-code=assignment,
          --disable-error-code=attr-defined,
          --disable-error-code=import-untyped,
          --disable-error-code=truthy-function,
          --follow-imports=skip,
          --explicit-package-bases,
        ]
  - repo: https://github.com/psf/black
    rev: 23.3.0
    hooks:
      - id: black
        args: [ --line-length=79 ]
        exclude: '(?x)(.*/skills/.*|^scripts/pack/)'
  - repo: https://github.com/PyCQA/flake8
    rev: 6.1.0
    hooks:
      - id: flake8
        args: [ "--extend-ignore=E203"]
        exclude: '(?x)(.*/skills/.*|^scripts/pack/)'
  - repo: https://github.com/pylint-dev/pylint
    rev: v3.0.2
    hooks:
      - id: pylint
        exclude:
          (?x)(
          ^docs
          | pb2\.py$
          | grpc\.py$
          | \.demo$
          | \.md$
          | \.html$
          | .*/skills/.*
          )
        args: [
          --disable=W0511,
          --disable=W0718,
          --disable=W0122,
          --disable=C0103,
          --disable=R0913,
          --disable=E0401,
          --disable=E1101,
          --disable=C0415,
          --disable=W0603,
          --disable=R1705,
          --disable=R0914,
          --disable=E0601,
          --disable=W0602,
          --disable=W0604,
          --disable=R0801,
          --disable=R0902,
          --disable=R0903,
          --disable=C0123,
          --disable=W0231,
          --disable=W1113,
          --disable=W0221,
          --disable=R0401,
          --disable=W0632,
          --disable=W0123,
          --disable=C3001,
          --disable=W0201,
          --disable=C0302,
          --disable=W1203,
          --disable=C2801,
          --disable=C0114,  # Disable missing module docstring for quick dev
          --disable=C0115,  # Disable missing class docstring for quick dev
          --disable=C0116,  # Disable missing function or method docstring for quick dev
        ]
  - repo: https://github.com/pre-commit/mirrors-prettier
    rev: 'v3.0.0'
    hooks:
      - id: prettier
        additional_dependencies: [ 'prettier@3.0.0' ]
        files: \.(tsx?)$
        exclude: '(?x)(^web/|^console/|/dist/|.*/skills/.*|^scripts/pack/)'

================================================
FILE: .python-version
================================================
3.10


================================================
FILE: CONTRIBUTING.md
================================================
# Contributing to CoPaw

## Welcome! 🐾

Thank you for your interest in contributing to CoPaw! CoPaw is an open-source **personal AI assistant** that runs in your own environment—on your machine or in the cloud. It connects to DingTalk, Feishu, QQ, Discord, iMessage, and other chat apps, supports scheduled tasks and heartbeat, and extends its capabilities through **Skills**. We warmly welcome contributions that help make CoPaw more useful for everyone: whether you add a new channel, a new model provider, a Skill, improve docs, or fix bugs.

**Quick links:** [GitHub](https://github.com/agentscope-ai/CoPaw) · [Docs](https://copaw.agentscope.io/) · [License: Apache 2.0](LICENSE)

---

## How to Contribute

To keep collaboration smooth and maintain quality, please follow these guidelines.

### 1. Check Existing Plans and Issues

Before starting:

- **Check [Open Issues](https://github.com/agentscope-ai/CoPaw/issues)** and any [Projects](https://github.com/agentscope-ai/CoPaw/projects) or roadmap labels.
- **If a related issue exists** and is open or unassigned: comment to say you want to work on it to avoid duplicate effort.
- **If no related issue exists**: open a new issue describing your proposal. The maintainers will respond and can help align with the project direction.

### 2. Commit Message Format

We follow the [Conventional Commits](https://www.conventionalcommits.org/) specification for clear history and tooling.

**Format:**
```
<type>(<scope>): <subject>
```

**Types:**
- `feat:` New feature
- `fix:` Bug fix
- `docs:` Documentation only
- `style:` Code style (whitespace, formatting, etc.)
- `refactor:` Code change that neither fixes a bug nor adds a feature
- `perf:` Performance improvement
- `test:` Adding or updating tests
- `chore:` Build, tooling, or maintenance

**Examples:**
```bash
feat(channels): add Telegram channel stub
fix(skills): correct SKILL.md front matter parsing
docs(readme): update quick start for Docker
refactor(providers): simplify custom provider validation
test(agents): add tests for skill loading
```

### 3. Pull Request Title Format

PR titles should follow the same convention:

**Format:** ` <type>(<scope>): <description> `

- Use one of: `feat`, `fix`, `docs`, `test`, `refactor`, `chore`, `perf`, `style`, `build`, `revert`.
- **Scope must be lowercase** (letters, numbers, hyphens, underscores only).
- Keep the description short and descriptive.

**Examples:**
```
feat(models): add custom provider for Azure OpenAI
fix(channels): handle empty content_parts in Discord
docs(skills): document Skills Hub import
```

### 4. Code and Quality

- **Required local gate (must pass before push/PR):**
  ```bash
  pip install -e ".[dev,full]"
  pre-commit install
  pre-commit run --all-files
  pytest
  ```
- **If pre-commit modifies files:** Commit those changes, then rerun
  `pre-commit run --all-files` until it passes cleanly.
- **CI policy:** Pull requests with failing pre-commit checks are not merge-ready.
- **Frontend formatting:** If your changes involve the `console` or `website` directories, run the formatter before committing:
  ```bash
  cd console && npm run format
  cd website && npm run format
  ```
- **Documentation:** Update docs and README when you add or change user-facing behavior. The docs live under `website/public/docs/`.

---

## Types of Contributions

CoPaw is designed to be **extensible**: you can add models, channels, Skills, and more. Below are the main contribution areas we care about.

---

### Adding New Models / Model Providers

CoPaw supports **multiple model backends**: cloud APIs (e.g. DashScope, ModelScope), **Ollama**, and local backends (**llama.cpp**, **MLX**). You can contribute in two ways:

#### A. Custom provider (user configuration)

Users can add **custom providers** via the Console or `providers.json`: any OpenAI-compatible API (e.g. vLLM, SGLang, private endpoints) can be configured with a unique ID, base URL, API key, and optional model list. No code change is required for standard OpenAI-compatible APIs.

#### B. New built-in provider or new ChatModel (code contribution)

If you want to add a **new built-in provider** or a **new API protocol** that is not OpenAI-compatible:

1. **Provider definition** (in `src/copaw/providers/registry.py` or equivalent):
   - Add a `ProviderDefinition` with `id`, `name`, `default_base_url`, `api_key_prefix`, and optionally `models` and `chat_model`.
   - For local/self-hosted backends, set `is_local` as appropriate.

2. **Chat model class** (if the API is not OpenAI-compatible):
   - Implement a class inheriting from `agentscope.model.ChatModelBase` (or CoPaw’s local/remote wrappers where applicable).
   - Support streaming and non-streaming if the agent uses both; respect `tool_choice` and tools API if used.
   - Register the class in the registry’s chat model map so the runtime can resolve it by name (see `_CHAT_MODEL_MAP` in `src/copaw/providers/registry.py`).

3. **Documentation:** Document the new provider or model in the docs (e.g. under a “Models” or “Providers” section) and mention any env vars or config keys.

Adding a fully new API (new message format, token counting, tools) is a larger change; we recommend opening an issue first to discuss scope and design.

---

### Adding New Channels

Channels are how CoPaw talks to **DingTalk, Feishu, QQ, Discord, iMessage**, etc. You can add a new channel so CoPaw can work with your favorite IM or bot platform.

- **Protocol:** All channels use a unified in-process contract: **native payload → `content_parts`** (e.g. `TextContent`, `ImageContent`, `FileContent`). The agent receives `AgentRequest` with these content parts; replies are sent back via the channel’s send path.
- **Implementation:** Implement a **subclass of `BaseChannel`** (in `src/copaw/app/channels/base.py`):
  - Set the class attribute `channel` to a unique channel key (e.g. `"telegram"`).
  - Implement the lifecycle and message handling (e.g. receive → `content_parts` → `process` → send response).
  - Use the manager’s queue and consumer loop if the channel is long-lived (default).
- **Discovery:** Built-in channels are registered in `src/copaw/app/channels/registry.py`. **Custom channels** are loaded from the working directory: place a module (e.g. `custom_channels/telegram.py` or a package `custom_channels/telegram/`) that defines a `BaseChannel` subclass with a `channel` attribute.
- **CLI:** Users install/add channels with:
  - `copaw channels install <key>` — create a template or copy from `--path` / `--url`
  - `copaw channels add <key>` — install and add to config
  - `copaw channels remove <key>` — remove custom channel from `custom_channels/`
  - `copaw channels config` — interactive config

If you contribute a **new built-in channel**, add it to the registry and, if needed, a configurator so it appears in the Console and CLI. Document the new channel (auth, webhooks, etc.) in `website/public/docs/channels.*.md`.

---

### Adding Base Skills

**Skills** define what CoPaw can do: cron, file reading, PDF/Office, news, browser, etc. We welcome **broadly useful** base skills (productivity, documents, communication, automation) that fit the majority of users.

- **Structure:** Each skill is a **directory** containing:
  - **`SKILL.md`** — Markdown instructions for the agent. Use YAML front matter for at least `name` and `description`; optional `metadata` (e.g. for Console).
  - **`references/`** (optional) — Reference documents the agent can use.
  - **`scripts/`** (optional) — Scripts or tools the skill uses.
- **Location:** Built-in skills live under `src/copaw/agents/skills/<skill_name>/`. The app merges built-in and user **customized_skills** from the working dir into **active_skills**; no extra registration is needed beyond placing a valid `SKILL.md` in a directory.
- **Content:** Write clear, task-oriented instructions. Describe **when** the skill should be used and **how** (steps, commands, file formats). Avoid overly niche or personal workflows if targeting the **base** repository; those are great as custom or community Skills.
- **Skills Hub:** CoPaw supports importing skills from a community hub (e.g. ClawHub). If you want your skill to be installable via hub, follow the same `SKILL.md` + `references/`/`scripts/` layout and the hub’s packaging format.

Examples of in-repo base skills: **cron**, **file_reader**, **news**, **pdf**, **docx**, **pptx**, **xlsx**, **browser_visible**. Contributing a new base skill usually means: add the directory under `agents/skills/`, add a short entry in the docs (e.g. Skills table in `website/public/docs/skills.*.md`), and ensure it syncs correctly to the working directory.

#### Writing Effective Skill Descriptions

To help the model accurately recognize and invoke your skill, the `description` field in your SKILL.md front matter must be **clear, specific, and include trigger keywords**. Follow these best practices:

**✅ Recommended format:**
```yaml
---
name: example_skill
description: "Use this skill whenever user wants to [main functionality]. Trigger especially when user mentions: [trigger keywords]. Also use when [other scenarios]."

# Detailed instructions below
...
```

**✅ Best practices:**
1. **Clearly state when to trigger**: Use phrases like "Use this skill whenever user wants to..." or "Trigger when user asks for..."
2. **List trigger keywords explicitly**: Make it easy for the model to recognize, for example:
   - "Trigger especially when user mentions: \"call\", \"dial\", \"phone\", \"microsip\""
   - "Also trigger for desktop automation tasks like opening apps, controlling windows"
3. **Be specific about the skill's scope**: Say exactly what it does, avoid vague terms
   - ✅ Good: "Make phone calls via MicroSIP or similar desktop apps"
   - ❌ Not ideal: "Control desktop"
4. **Provide usage examples**: If the skill has specific usage patterns, explain them in the body of SKILL.md

**❌ Common pitfalls:**
- Overly abstract descriptions (like "control desktop", "process files")
- Missing trigger keywords, making it hard for the model to identify use cases
- Lack of usage scenario context

**📝 Examples comparison:**

| Skill | Description (Not ideal) | Description (Better) |
|-------|-------------------------|----------------------|
| Desktop Control | "Control desktop applications" | "Use this skill whenever user wants to control desktop applications or make phone calls. Trigger especially when user mentions: \"call\", \"dial\", \"phone\", \"microsip\", or requests to use specific desktop apps." |
| File Reader | "Read files" | "Use this skill when user asks to read or summarize local text-based files. PDFs, Office documents, images are out of scope." |

---

### Platform support (Windows, Linux, macOS, etc.)

CoPaw aims to run on **Windows**, **Linux**, and **macOS**. Contributions that improve support on a specific platform are welcome.

- **Compatibility fixes:** Path handling, line endings, shell commands, or dependencies that behave differently per OS. For example: Windows compatibility for the memory/vector stack, or install scripts that work on both Linux and macOS.
- **Install and run:** One-line install (`install.sh`), `pip` install, and `copaw init` / `copaw app` should work (or be clearly documented) on each supported platform. Fixes to install or startup on a given OS are valuable.
- **Platform-specific features:** Optional integrations (e.g. notifying only when supported) are fine as long as they don’t break other platforms. Use runtime checks or optional dependencies where appropriate.
- **Documentation:** Document any platform-specific steps, known limitations, or recommended setups (e.g. WSL on Windows, Apple Silicon vs x86) in the docs or README.

If you add or change platform support, please test on the affected OS and mention it in the PR description. Opening an issue first is recommended for larger or ambiguous platform work.

---

### Other Contributions

- **MCP (Model Context Protocol):** CoPaw supports runtime **MCP tool** discovery and hot-plug. Contributing new MCP servers or tools (or docs on how to attach them) helps users extend the agent without changing core code.
- **Documentation:** Fixes and improvements to [the docs](https://copaw.agentscope.io/) (under `website/public/docs/`) and README are always welcome.
- **Bug fixes and refactors:** Small fixes, clearer error messages, and refactors that keep behavior the same are valuable. Prefer opening an issue for larger refactors so we can align on approach.
- **Examples and workflows:** Tutorials or example workflows (e.g. “daily digest to DingTalk”, “local model + cron”) can be documented or linked from the repo/docs.
- **Any other useful things!**
---

## Do's and Don'ts

### ✅ DO

- Start with small, focused changes.
- Discuss large or design-sensitive changes in an issue first.
- Write or update tests where applicable.
- Update documentation for user-facing changes.
- Use conventional commit messages and PR titles.
- Be respectful and constructive (we follow a welcoming Code of Conduct).

### ❌ DON'T

- Don’t open very large PRs without prior discussion.
- Don’t ignore CI or pre-commit failures.
- Don’t mix unrelated changes in one PR.
- Don’t break existing APIs without a good reason and clear migration notes.
- Don’t add heavy or optional dependencies to the core install without discussing in an issue.

---

## Getting Help

- **Discussions:** [GitHub Discussions](https://github.com/agentscope-ai/CoPaw/discussions)
- **Bugs and features:** [GitHub Issues](https://github.com/agentscope-ai/CoPaw/issues)
- **Community:** DingTalk group (see [README](README.md)) and [Discord](https://discord.gg/eYMpfnkG8h)

Thank you for contributing to CoPaw. Your work helps make it a better assistant for everyone. 🐾


================================================
FILE: CONTRIBUTING_zh.md
================================================
# 为 CoPaw 贡献代码

## 欢迎!🐾

感谢你对 CoPaw 的关注!CoPaw 是一个开源的**个人 AI 助手**,可以在你自己的环境中运行——无论是你的机器还是云端。它可以连接钉钉、飞书、QQ、Discord、iMessage 等聊天应用,支持定时任务和心跳机制,并通过 **Skills** 扩展其能力。我们热烈欢迎能让 CoPaw 对所有人更有用的贡献:无论是添加新的频道、新的模型提供商、Skill,改进文档,还是修复 bug。

**快速链接:** [GitHub](https://github.com/agentscope-ai/CoPaw) · [文档](https://copaw.agentscope.io/) · [许可证:Apache 2.0](LICENSE)

---

## 如何贡献

为了保持协作顺畅并维护质量,请遵循以下指南。

### 1. 检查现有计划和问题

在开始之前:

- **检查 [Open Issues](https://github.com/agentscope-ai/CoPaw/issues)** 以及任何 [Projects](https://github.com/agentscope-ai/CoPaw/projects) 或路线图标签。
- **如果存在相关 issue** 且处于开放或未分配状态:发表评论表示你想要处理它,以避免重复工作。
- **如果不存在相关 issue**:创建一个新 issue 描述你的提案。维护者会回复并帮助与项目方向对齐。

### 2. 提交信息格式

我们遵循 [Conventional Commits](https://www.conventionalcommits.org/) 规范,以保持清晰的历史记录和工具支持。

**格式:**
```
<type>(<scope>): <subject>
```

**类型:**
- `feat:` 新功能
- `fix:` Bug 修复
- `docs:` 仅文档更改
- `style:` 代码风格(空格、格式等)
- `refactor:` 既不修复 bug 也不添加功能的代码更改
- `perf:` 性能改进
- `test:` 添加或更新测试
- `chore:` 构建、工具或维护

**示例:**
```bash
feat(channels): add Telegram channel stub
fix(skills): correct SKILL.md front matter parsing
docs(readme): update quick start for Docker
refactor(providers): simplify custom provider validation
test(agents): add tests for skill loading
```

### 3. Pull Request 标题格式

PR 标题应遵循相同的约定:

**格式:** ` <type>(<scope>): <description> `

- 使用以下之一:`feat`、`fix`、`docs`、`test`、`refactor`、`chore`、`perf`、`style`、`build`、`revert`。
- **scope 必须小写**(仅字母、数字、连字符、下划线)。
- 保持描述简短且描述性强。

**示例:**
```
feat(models): add custom provider for Azure OpenAI
fix(channels): handle empty content_parts in Discord
docs(skills): document Skills Hub import
```

### 4. 代码和质量

- **本地必跑门禁(push/提 PR 前必须通过):**
  ```bash
  pip install -e ".[dev,full]"
  pre-commit install
  pre-commit run --all-files
  pytest
  ```
- **如果 pre-commit 自动修改了文件:** 先提交这些修改,再重复执行
  `pre-commit run --all-files`,直到无修改且通过。
- **CI 策略:** pre-commit 检查失败的 PR 视为未就绪(not merge-ready)。
- **前端代码格式化:** 如果你的修改涉及到 `console` 或 `website` 目录,请在提交前运行格式化:
  ```bash
  cd console && npm run format
  cd website && npm run format
  ```
- **文档:** 当你添加或更改面向用户的行为时,更新文档和 README。文档位于 `website/public/docs/` 下。

---

## 贡献类型

CoPaw 设计为**可扩展的**:你可以添加模型、频道、Skills 等。以下是我们关心的主要贡献领域。

---

### 添加新模型 / 模型提供商

CoPaw 支持**多种模型后端**:云 API(如 DashScope、ModelScope)、**Ollama** 和本地后端(**llama.cpp**、**MLX**)。你可以通过两种方式贡献:

#### A. 自定义提供商(用户配置)

用户可以通过 Console 或 `providers.json` 添加**自定义提供商**:任何 OpenAI 兼容的 API(如 vLLM、SGLang、私有端点)都可以通过唯一 ID、base URL、API key 和可选的模型列表进行配置。标准 OpenAI 兼容 API 无需代码更改。

#### B. 新的内置提供商或新的 ChatModel(代码贡献)

如果你想添加**新的内置提供商**或**不兼容 OpenAI 的新 API 协议**:

1. **提供商定义**(在 `src/copaw/providers/registry.py` 或等效位置):
   - 添加一个 `ProviderDefinition`,包含 `id`、`name`、`default_base_url`、`api_key_prefix`,以及可选的 `models` 和 `chat_model`。
   - 对于本地/自托管后端,根据需要设置 `is_local`。

2. **聊天模型类**(如果 API 不兼容 OpenAI):
   - 实现一个继承自 `agentscope.model.ChatModelBase` 的类(或适用时使用 CoPaw 的本地/远程包装器)。
   - 如果 agent 同时使用流式和非流式,则都要支持;如果使用了 tools API,则遵守 `tool_choice` 和 tools。
   - 在注册表的聊天模型映射中注册该类,以便运行时可以按名称解析它(参见 `src/copaw/providers/registry.py` 中的 `_CHAT_MODEL_MAP`)。

3. **文档:** 在文档中记录新的提供商或模型(例如在"模型"或"提供商"部分下),并提及任何环境变量或配置键。

添加全新的 API(新消息格式、token 计数、tools)是较大的更改;我们建议先创建 issue 讨论范围和设计。

---

### 添加新频道

频道是 CoPaw 与**钉钉、飞书、QQ、Discord、iMessage** 等通信的方式。你可以添加新频道,以便 CoPaw 可以与你喜欢的 IM 或机器人平台配合使用。

- **协议:** 所有频道使用统一的进程内契约:**原生 payload → `content_parts`**(如 `TextContent`、`ImageContent`、`FileContent`)。agent 接收带有这些内容部分的 `AgentRequest`;回复通过频道的发送路径返回。
- **实现:** 实现 **`BaseChannel` 的子类**(在 `src/copaw/app/channels/base.py` 中):
  - 将类属性 `channel` 设置为唯一的频道键(如 `"telegram"`)。
  - 实现生命周期和消息处理(如 receive → `content_parts` → `process` → send response)。
  - 如果频道是长期运行的(默认),使用 manager 的队列和消费者循环。
- **发现:** 内置频道在 `src/copaw/app/channels/registry.py` 中注册。**自定义频道**从工作目录加载:放置一个模块(如 `custom_channels/telegram.py` 或包 `custom_channels/telegram/`),定义一个带有 `channel` 属性的 `BaseChannel` 子类。
- **CLI:** 用户使用以下命令安装/添加频道:
  - `copaw channels install <key>` — 创建模板或从 `--path` / `--url` 复制
  - `copaw channels add <key>` — 安装并添加到配置
  - `copaw channels remove <key>` — 从 `custom_channels/` 中删除自定义频道
  - `copaw channels config` — 交互式配置

如果你贡献**新的内置频道**,将其添加到注册表,如有需要,添加配置器以使其出现在 Console 和 CLI 中。在 `website/public/docs/channels.*.md` 中记录新频道(身份验证、webhooks 等)。

---

### 添加基础 Skills

**Skills** 定义了 CoPaw 可以做什么:cron、文件读取、PDF/Office、新闻、浏览器等。我们欢迎**广泛有用的**基础 skills(生产力、文档、通信、自动化),适合大多数用户。

- **结构:** 每个 skill 是一个**目录**,包含:
  - **`SKILL.md`** — agent 的 Markdown 指令。使用 YAML front matter 至少包含 `name` 和 `description`;可选的 `metadata`(如用于 Console)。
  - **`references/`**(可选)— agent 可以使用的参考文档。
  - **`scripts/`**(可选)— skill 使用的脚本或工具。
- **位置:** 内置 skills 位于 `src/copaw/agents/skills/<skill_name>/` 下。应用程序将内置和用户的 **customized_skills**(来自工作目录)合并到 **active_skills** 中;除了在目录中放置有效的 `SKILL.md` 外,不需要额外的注册。
- **内容:** 编写清晰的、面向任务的指令。描述**何时**应该使用该 skill 以及**如何**使用(步骤、命令、文件格式)。如果针对**基础**仓库,避免过于小众或个人的工作流程;这些作为自定义或社区 Skills 非常好。

#### 编写有效的 Skill Description

为了让 model 能够准确识别并调用你的 skill,`description` 字段必须**清晰、具体且包含触发词**。请遵循以下最佳实践:

**✅ 推荐格式:**
```yaml
---
name: example_skill
description: "Use this skill whenever user wants to [主要功能]. Trigger especially when user mentions: [触发词列表]. Also use when [其他场景]."

# 详细说明
...
```

**✅ 最佳实践:**
1. **明确触发时机**:使用 "Use this skill whenever user wants to..." 或 "Trigger when user asks for..."
2. **列出触发关键词**:在 description 中明确列出触发词,例如:
   - "Trigger especially when user mentions: \"call\", \"dial\", \"phone\", \"microsip\""
   - "Also trigger for desktop automation tasks like opening apps, controlling windows"
3. **具体描述功能范围**:说明技能做什么,不要含糊
   - ✅ 好的:"Make phone calls via MicroSIP or similar desktop apps"
   - ❌ 不好:"Control desktop"
4. **提供使用示例**:如果技能有特定用法,在 SKILL.md 主体部分说明

**❌ 常见问题:**
- 描述过于抽象(如"控制桌面"、"处理文件")
- 没有列出触发关键词,导致 model 无法识别
- 缺少使用场景说明

**📝 示例对比:**

| 技能 | 描述(不好) | 描述(好) |
|------|---------------|-------------|
| Desktop Control | "控制桌面应用" | "Use this skill whenever user wants to control desktop applications or make phone calls. Trigger especially when user mentions: \"call\" (呼叫), \"dial\" (拨打), \"phone\" (电话), \"microsip\", or requests to use specific desktop apps." |
| File Reader | "读取文件" | "Use this skill when user asks to read or summarize local text-based files. PDFs, Office documents, and images are out of scope." |

- **Skills Hub:** CoPaw 支持从社区 hub(如 ClawHub)导入 skills。如果你希望你的 skill 可以通过 hub 安装,请遵循相同的 `SKILL.md` + `references/`/`scripts/` 布局和 hub 的打包格式。

仓库内基础 skills 的示例:**cron**、**file_reader**、**news**、**pdf**、**docx**、**pptx**、**xlsx**、**browser_visible**。贡献新的基础 skill 通常意味着:在 `agents/skills/` 下添加目录,在文档中添加简短条目(如 `website/public/docs/skills.*.md` 中的 Skills 表),并确保它正确同步到工作目录。

---

### 平台支持(Windows、Linux、macOS 等)

CoPaw 旨在在 **Windows**、**Linux** 和 **macOS** 上运行。欢迎改进特定平台支持的贡献。

- **兼容性修复:** 路径处理、行尾、shell 命令或在不同操作系统上行为不同的依赖项。例如:内存/向量栈的 Windows 兼容性,或在 Linux 和 macOS 上都能工作的安装脚本。
- **安装和运行:** 一行安装(`install.sh`)、`pip` 安装,以及 `copaw init` / `copaw app` 应该在每个支持的平台上工作(或有清晰的文档说明)。对给定操作系统上的安装或启动的修复很有价值。
- **平台特定功能:** 可选集成(如仅在支持时通知)是可以的,只要它们不会破坏其他平台。在适当的地方使用运行时检查或可选依赖项。
- **文档:** 在文档或 README 中记录任何平台特定的步骤、已知限制或推荐设置(如 Windows 上的 WSL、Apple Silicon vs x86)。

如果你添加或更改平台支持,请在受影响的操作系统上进行测试,并在 PR 描述中提及。对于较大或模糊的平台工作,建议先创建 issue。

---

### 其他贡献

- **MCP(模型上下文协议):** CoPaw 支持运行时 **MCP 工具**发现和热插拔。贡献新的 MCP 服务器或工具(或关于如何附加它们的文档)可以帮助用户扩展 agent 而无需更改核心代码。
- **文档:** 对 [文档](https://copaw.agentscope.io/)(位于 `website/public/docs/` 下)和 README 的修复和改进始终受欢迎。
- **Bug 修复和重构:** 小的修复、更清晰的错误消息以及保持行为相同的重构都很有价值。对于较大的重构,最好先创建 issue,以便我们可以就方法达成一致。
- **示例和工作流程:** 教程或示例工作流程(如"每日摘要到钉钉"、"本地模型 + cron")可以记录或从仓库/文档链接。
- **任何其他有用的东西!**

---

## 应该做和不应该做

### ✅ 应该做

- 从小的、集中的更改开始。
- 在 issue 中首先讨论大型或涉及敏感的更改。
- 在适用的地方编写或更新测试。
- 为面向用户的更改更新文档。
- 使用常规提交消息和 PR 标题。
- 保持尊重和建设性(我们遵循友好的行为准则)。

### ❌ 不应该做

- 不要在没有事先讨论的情况下打开非常大的 PR。
- 不要忽略 CI 或 pre-commit 失败。
- 不要在一个 PR 中混合不相关的更改。
- 不要在没有充分理由和清晰迁移说明的情况下破坏现有 API。
- 不要在没有在 issue 中讨论的情况下向核心安装添加重型或可选依赖项。

---

## 获取帮助

- **讨论:** [GitHub Discussions](https://github.com/agentscope-ai/CoPaw/discussions)
- **Bug 和功能:** [GitHub Issues](https://github.com/agentscope-ai/CoPaw/issues)
- **社区:** 钉钉群(见 [README](README_zh.md))和 [Discord](https://discord.gg/eYMpfnkG8h)

感谢你为 CoPaw 贡献代码。你的工作帮助它成为每个人更好的助手。🐾


================================================
FILE: LICENSE
================================================
                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to the Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   Copyright 2025 The CoPaw Authors

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.


================================================
FILE: README.md
================================================
<div align="center">

# CoPaw

[![GitHub Repo](https://img.shields.io/badge/GitHub-Repo-black.svg?logo=github)](https://github.com/agentscope-ai/CoPaw)
[![PyPI](https://img.shields.io/pypi/v/copaw?color=3775A9&label=PyPI&logo=pypi)](https://pypi.org/project/copaw/)
[![Documentation](https://img.shields.io/badge/Docs-Website-green.svg?logo=readthedocs&label=Docs)](https://copaw.agentscope.io/)
[![Python Version](https://img.shields.io/badge/python-3.10%20~%20%3C3.14-blue.svg?logo=python&label=Python)](https://www.python.org/downloads/)
[![Last Commit](https://img.shields.io/github/last-commit/agentscope-ai/CoPaw)](https://github.com/agentscope-ai/CoPaw)
[![License](https://img.shields.io/badge/license-Apache%202.0-red.svg?logo=apache&label=License)](LICENSE)
[![Code Style](https://img.shields.io/badge/code%20style-black-black.svg?logo=python&label=CodeStyle)](https://github.com/psf/black)
[![GitHub Stars](https://img.shields.io/github/stars/agentscope-ai/CoPaw?style=flat&logo=github&color=yellow&label=Stars)](https://github.com/agentscope-ai/CoPaw/stargazers)
[![GitHub Forks](https://img.shields.io/github/forks/agentscope-ai/CoPaw?style=flat&logo=github&color=purple&label=Forks)](https://github.com/agentscope-ai/CoPaw/network)
[![DeepWiki](https://img.shields.io/badge/DeepWiki-Ask_Devin-navy.svg?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAyCAYAAAAnWDnqAAAAAXNSR0IArs4c6QAAA05JREFUaEPtmUtyEzEQhtWTQyQLHNak2AB7ZnyXZMEjXMGeK/AIi+QuHrMnbChYY7MIh8g01fJoopFb0uhhEqqcbWTp06/uv1saEDv4O3n3dV60RfP947Mm9/SQc0ICFQgzfc4CYZoTPAswgSJCCUJUnAAoRHOAUOcATwbmVLWdGoH//PB8mnKqScAhsD0kYP3j/Yt5LPQe2KvcXmGvRHcDnpxfL2zOYJ1mFwrryWTz0advv1Ut4CJgf5uhDuDj5eUcAUoahrdY/56ebRWeraTjMt/00Sh3UDtjgHtQNHwcRGOC98BJEAEymycmYcWwOprTgcB6VZ5JK5TAJ+fXGLBm3FDAmn6oPPjR4rKCAoJCal2eAiQp2x0vxTPB3ALO2CRkwmDy5WohzBDwSEFKRwPbknEggCPB/imwrycgxX2NzoMCHhPkDwqYMr9tRcP5qNrMZHkVnOjRMWwLCcr8ohBVb1OMjxLwGCvjTikrsBOiA6fNyCrm8V1rP93iVPpwaE+gO0SsWmPiXB+jikdf6SizrT5qKasx5j8ABbHpFTx+vFXp9EnYQmLx02h1QTTrl6eDqxLnGjporxl3NL3agEvXdT0WmEost648sQOYAeJS9Q7bfUVoMGnjo4AZdUMQku50McDcMWcBPvr0SzbTAFDfvJqwLzgxwATnCgnp4wDl6Aa+Ax283gghmj+vj7feE2KBBRMW3FzOpLOADl0Isb5587h/U4gGvkt5v60Z1VLG8BhYjbzRwyQZemwAd6cCR5/XFWLYZRIMpX39AR0tjaGGiGzLVyhse5C9RKC6ai42ppWPKiBagOvaYk8lO7DajerabOZP46Lby5wKjw1HCRx7p9sVMOWGzb/vA1hwiWc6jm3MvQDTogQkiqIhJV0nBQBTU+3okKCFDy9WwferkHjtxib7t3xIUQtHxnIwtx4mpg26/HfwVNVDb4oI9RHmx5WGelRVlrtiw43zboCLaxv46AZeB3IlTkwouebTr1y2NjSpHz68WNFjHvupy3q8TFn3Hos2IAk4Ju5dCo8B3wP7VPr/FGaKiG+T+v+TQqIrOqMTL1VdWV1DdmcbO8KXBz6esmYWYKPwDL5b5FA1a0hwapHiom0r/cKaoqr+27/XcrS5UwSMbQAAAABJRU5ErkJggg==)](https://deepwiki.com/agentscope-ai/CoPaw)
[![Discord](https://img.shields.io/badge/Discord-Join_Us-blueviolet.svg?logo=discord)](https://discord.gg/eYMpfnkG8h)
[![X](https://img.shields.io/badge/X-Follow_Us-black.svg?logo=x)](https://x.com/agentscope_ai)
[![DingTalk](https://img.shields.io/badge/DingTalk-Join_Us-orange.svg)](https://qr.dingtalk.com/action/joingroup?code=v1,k1,OmDlBXpjW+I2vWjKDsjvI9dhcXjGZi3bQiojOq3dlDw=&_dt_no_comment=1&origin=11)

[[Documentation](https://copaw.agentscope.io/)] [[中文 README](README_zh.md)] [[日本語](README_ja.md)]

<p align="center">
  <img src="https://img.alicdn.com/imgextra/i2/O1CN014TIqyO1U5wDiSbFfA_!!6000000002467-2-tps-816-192.png" alt="CoPaw Logo" width="120">
</p>

<p align="center"><b>Works for you, grows with you.</b></p>

</div>

Your Personal AI Assistant; easy to install, deploy on your own machine or on the cloud; supports multiple chat apps with easily extensible capabilities.

> **Core capabilities:**
>
> **Every channel** — DingTalk, Feishu, QQ, Discord, iMessage, and more. One assistant, connect as you need.
>
> **Under your control** — Memory and personalization under your control. Deploy locally or in the cloud; scheduled reminders to any channel.
>
> **Skills** — Built-in cron; custom skills in your workspace, auto-loaded. No lock-in.
>
> <details>
> <summary><b>What you can do</b></summary>
>
> <br>
>
> - **Social**: daily digest of hot posts (Xiaohongshu, Zhihu, Reddit), Bilibili/YouTube summaries.
> - **Productivity**: newsletter digests to DingTalk/Feishu/QQ, contacts from email/calendar.
> - **Creative**: describe your goal, run overnight, get a draft next day.
> - **Research**: track tech/AI news, personal knowledge base.
> - **Desktop**: organize files, read/summarize docs, request files in chat.
> - **Explore**: combine Skills and cron into your own agentic app.
>
> </details>

---

## News

[2026-03-18] We released v0.1.0! See the [v0.1.0 Release Notes](https://agentscope-ai.github.io/CoPaw/release-notes) for the full changelog.

- **[v0.1.0] Added:** Multi-workspace architecture with agent selector; skill security scanner and destructive shell command detection; optional web authentication; WeCom and XiaoYi channels; DingTalk AI Card replies; Gemini, DeepSeek, MiniMax, and Kimi providers; console dark mode and multimodal chat; SSE-based chat streaming with reconnect; voice message transcription via Whisper; `view_image` tool for multimodal conversations; LobeHub, ModelScope, and zip archive skill import; `glob_search` and `grep_search` built-in tools; timezone selector; `copaw update` CLI.
- **[v0.1.0] Improved:** Graceful lifecycle management with zero-downtime agent reload; dynamic per-agent token counting; config loading protection; console internationalization with localized chat prompts; Windows desktop startup speed via bytecode pre-compilation; QQ channel reply logic with DM support.
- **[v0.1.0] Fixed:** Telegram thread replies, media handling, and auto-reconnect; Discord cross-channel message merging and debounce generalization; Feishu channel reload; Ollama/LM Studio context length and error messages; cron jobs in correct workspace; Windows cross-disk moves, AutoRun stderr, and GBK encoding.
- **[v0.1.0] Contributors:** Thanks to new contributors: [@dipeshbabu](https://github.com/dipeshbabu), [@sljeff](https://github.com/sljeff), [@octo-patch](https://github.com/octo-patch), [@Alexxigang](https://github.com/Alexxigang), [@howyoungchen](https://github.com/howyoungchen), [@nphenix](https://github.com/nphenix), [@skyfaker](https://github.com/skyfaker), [@hh0592821](https://github.com/hh0592821), [@futuremeng](https://github.com/futuremeng), [@toby1123yjh](https://github.com/toby1123yjh), [@hiyuchang](https://github.com/hiyuchang), [@hanson-hex](https://github.com/hanson-hex), [@JackyMao1999](https://github.com/JackyMao1999), [@mvanhorn](https://github.com/mvanhorn), [@yuanxs21](https://github.com/yuanxs21), [@aissac](https://github.com/aissac), [@lcq225](https://github.com/lcq225), [@Justin-lu](https://github.com/Justin-lu), [@rowanchen-com](https://github.com/rowanchen-com), [@pzlav](https://github.com/pzlav), [@mautops](https://github.com/mautops), [@hikariming](https://github.com/hikariming), [@Vanlee0129](https://github.com/Vanlee0129), [@JiwaniZakir](https://github.com/JiwaniZakir), [@EuanTop](https://github.com/EuanTop).

[2026-03-12] We released v0.0.7! See the [v0.0.7 Release Notes](https://agentscope-ai.github.io/CoPaw/release-notes) for the full changelog.

[2026-03-09] We released v0.0.6! See the [v0.0.6 Release Notes](https://agentscope-ai.github.io/CoPaw/release-notes) for the full changelog.

[2026-03-06] We released v0.0.5! See the [v0.0.5 Release Notes](https://agentscope-ai.github.io/CoPaw/release-notes) for the full changelog.

[2026-03-02] We released v0.0.4! See the [v0.0.4 Release Notes](https://agentscope-ai.github.io/CoPaw/release-notes) for the full changelog.

---

## Table of Contents

> **Recommended reading:**
>
> - **I want to run CoPaw in 3 commands**: [Quick Start](#quick-start) → open Console in browser.
> - **I want to chat in DingTalk / Feishu / QQ**: Configure [channels](https://copaw.agentscope.io/docs/channels) in the Console.
> - **I don’t want to install Python**: [Script install](#script-install) handles Python automatically, or use [ModelScope one-click](https://modelscope.cn/studios/fork?target=AgentScope/CoPaw) for cloud deployment.

- [News](#news)
- [Quick Start](#quick-start)
- [API Key](#api-key)
- [Local Models](#local-models)
- [Documentation](#documentation)
- [FAQ](#faq)
- [Roadmap](#roadmap)
- [Contributing](#get-involved)
- [Install from source](#install-from-source)
- [Why CoPaw?](#why-copaw)
- [Built by](#built-by)
- [License](#license)

---

## Quick Start

### pip install

If you prefer managing Python yourself:

```bash
pip install copaw
copaw init --defaults
copaw app
```

Then open **http://127.0.0.1:8088/** in your browser for the Console (chat with CoPaw, configure the agent). To talk in DingTalk, Feishu, QQ, etc., add a channel in the [docs](https://copaw.agentscope.io/docs/channels).

![Console](https://img.alicdn.com/imgextra/i3/O1CN01VYsFVo23aAvIM3GXB_!!6000000007271-2-tps-3328-1860.png)

### Script install

No Python setup required, one command installs everything. The script will automatically download uv (Python package manager), create a virtual environment, and install CoPaw with all dependencies (including Node.js and frontend assets). Note: May not work in restricted network environments or corporate firewalls.

**macOS / Linux:**

```bash
curl -fsSL https://copaw.agentscope.io/install.sh | bash
```

To install with Ollama support:

```bash
curl -fsSL https://copaw.agentscope.io/install.sh | bash -s -- --extras ollama
```

To install with multiple extras (e.g., Ollama + llama.cpp):

```bash
curl -fsSL https://copaw.agentscope.io/install.sh | bash -s -- --extras ollama,llamacpp
```

**Windows (CMD):**

```CMD
curl -fsSL https://copaw.agentscope.io/install.bat -o install.bat && install.bat
```

**Windows (PowerShell):**

```powershell
irm https://copaw.agentscope.io/install.ps1 | iex
```

> **Note**: The installer will automatically check the status of uv. If it is not installed, it will attempt to download and configure it automatically. If the automatic installation fails, please follow the on-screen prompts or execute `python -m pip install -U uv`, then rerun the installer.

> **⚠️ Special Notice for Windows Enterprise LTSC Users**
>
> If you are using Windows LTSC or an enterprise environment governed by strict security policies, PowerShell may run in **Constrained Language Mode**, potentially causing the following issue:
> 1. **If using CMD (.bat): Script executes successfully but fails to write to `Path`**
>
>    The script completes file installation. Due to **Constrained Language Mode**, it cannot automatically update environment variables. Manually configure as follows:
>    - **Locate the installation directory**:
>      - Check if `uv` is available: Enter `uv --version` in CMD. If a version number appears, **only configure the CoPaw path**. If you receive the prompt `'uv' is not recognized as an internal or external command, operable program or batch file,` configure both paths.
>      - uv path (choose one based on installation location; use if `uv` fails): Typically `%USERPROFILE%\.local\bin`, `%USERPROFILE%\AppData\Local\uv`, or the `Scripts` folder within your Python installation directory
>      - CoPaw path: Typically located at `%USERPROFILE%\.copaw\bin`.
>    - **Manually add to the system's Path environment variable**:
>      - Press `Win + R`, type `sysdm.cpl` and press Enter to open System Properties.
>      - Click “Advanced” -> “Environment Variables”.
>      - Under “System variables”, locate and select `Path`, then click “Edit”.
>      - Click “New”, enter both directory paths sequentially, then click OK to save.
> 2. **If using PowerShell (.ps1): Script execution interrupted**
>
>   Due to **Constrained Language Mode**, the script may fail to automatically download `uv`.
>   - **Manually install uv**: Refer to the [GitHub Release](https://github.com/astral-sh/uv/releases) to download `uv.exe` and place it in `%USERPROFILE%\.local\bin` or `%USERPROFILE%\AppData\Local\uv`; or ensure Python is installed and run `python -m pip install -U uv`.
>   - **Configure `uv` environment variables**: Add the `uv` directory and `%USERPROFILE%\.copaw\bin` to your system's `Path` variable.
>   - **Re-run the installation**: Open a new terminal and execute the installation script again to complete the `CoPaw` installation.
>   - **Configure the `CoPaw` environment variable**: Add `%USERPROFILE%\.copaw\bin` to your system's `Path` variable.

Once installed, open a new terminal and run:

```bash
copaw init --defaults   # or: copaw init (interactive)
copaw app
```

<details>
<summary><b>Install options</b></summary>

**macOS / Linux:**

```bash
# Install a specific version
curl -fsSL ... | bash -s -- --version 0.0.2

# Install from source (dev/testing)
curl -fsSL ... | bash -s -- --from-source

# With local model support
bash install.sh --extras llamacpp    # llama.cpp (cross-platform)
bash install.sh --extras mlx         # MLX (Apple Silicon)
bash install.sh --extras llamacpp,mlx

# Upgrade — just re-run the installer
curl -fsSL ... | bash

# Uninstall
copaw uninstall          # keeps config and data
copaw uninstall --purge  # removes everything
```

**Windows (PowerShell):**

```powershell
# Install a specific version
irm ... | iex; .\install.ps1 -Version 0.0.2

# Install from source (dev/testing)
.\install.ps1 -FromSource

# With local model support
.\install.ps1 -Extras llamacpp      # llama.cpp (cross-platform)
.\install.ps1 -Extras mlx           # MLX
.\install.ps1 -Extras llamacpp,mlx

# Upgrade — just re-run the installer
irm ... | iex

# Uninstall
copaw uninstall          # keeps config and data
copaw uninstall --purge  # removes everything
```

</details>

### Desktop Application (Beta)

> **Beta Notice**: The desktop application is currently in Beta testing phase with the following known limitations:
> - **Incomplete compatibility testing**: Not fully tested across all system versions and hardware configurations
> - **Potential performance issues**: Startup time, memory usage, and other performance aspects may need further optimization
> - **Features under development**: Some features may be unstable or missing

If you're not comfortable with command-line tools, you can download and use CoPaw's desktop application without manually configuring Python environments or running commands.

#### Download

Download the desktop app from [GitHub Releases](https://github.com/agentscope-ai/CoPaw/releases):
- **Windows**: `CoPaw-Setup-<version>.exe`
- **macOS**: `CoPaw-<version>-macOS.zip` (Apple Silicon recommended)

#### Features

- ✅ **Zero configuration**: Download and double-click to run, no need to install Python or configure environment variables
- ✅ **Cross-platform**: Supports Windows 10+ and macOS 14+
- ✅ **Visual interface**: Automatically opens browser interface, no need to manually enter addresses
- ⚠️ **Beta stage**: Features are continuously being improved, feedback welcome

#### First Launch

**Important**: The first launch may take 10-60 seconds (depending on your system configuration). The application needs to initialize the Python environment and load dependencies. Please wait patiently for the browser window to open automatically.

#### macOS: Bypass System Security Restrictions

When you download the CoPaw macOS app from Releases, macOS may show: *"Apple cannot verify that 'CoPaw' contains no malicious software"*. This happens because the app is not notarized. You can still open it as follows:

- **Right-click to open (recommended)**
  Right-click (or Control+click) the CoPaw app → **Open** → in the dialog click **Open** again. This tells Gatekeeper you trust the app; after that you can double-click to launch as usual.

- **Allow in System Settings**
  If it is still blocked, go to **System Settings → Privacy & Security**, scroll to the message like *"CoPaw was blocked because it is from an unidentified developer"*, and click **Open Anyway** or **Allow**.

- **Remove quarantine attribute (not recommended for most users)**
  In Terminal run:
  `xattr -cr /Applications/CoPaw.app`
  (or use the path to the `.app` after unzipping). This clears the "downloaded from the internet" quarantine flag so the warning usually does not appear, but is less safe and controllable than using **Right-click → Open**.

For detailed usage instructions, troubleshooting, and common issues, see the [Desktop Application Guide](https://copaw.agentscope.io/docs/desktop).

### Using Docker

Images are on **Docker Hub** (`agentscope/copaw`). Image tags: `latest` (stable); `pre` (PyPI pre-release).

```bash
docker pull agentscope/copaw:latest
docker run -p 127.0.0.1:8088:8088 \
  -v copaw-data:/app/working \
  -v copaw-secrets:/app/working.secret \
  agentscope/copaw:latest
```

Also available on Alibaba Cloud Container Registry (ACR) for users in China: `agentscope-registry.ap-southeast-1.cr.aliyuncs.com/agentscope/copaw` (same tags).

Then open **http://127.0.0.1:8088/** for the Console. Config, memory, and skills are stored in the `copaw-data` volume; model provider settings and API keys are in the `copaw-secrets` volume. To pass API keys (e.g. `DASHSCOPE_API_KEY`), add `-e VAR=value` or `--env-file .env` to `docker run`.

> **Connecting to Ollama or other services on the host machine**
>
> Inside a Docker container, `localhost` refers to the container itself, not your host machine. If you run Ollama (or other model services) on the host and want CoPaw in Docker to reach them, use one of these approaches:
>
> **Option A** — Explicit host binding (all platforms):
> ```bash
> docker run -p 127.0.0.1:8088:8088 \
>   --add-host=host.docker.internal:host-gateway \
>   -v copaw-data:/app/working \
>   -v copaw-secrets:/app/working.secret \
>   agentscope/copaw:latest
> ```
> Then in CoPaw **Settings → Models**, change the Base URL to `http://host.docker.internal:<port>` — for example, `http://host.docker.internal:11434` for Ollama, or `http://host.docker.internal:1234/v1` for LM Studio.
>
> **Option B** — Host networking (Linux only):
> ```bash
> docker run --network=host \
>   -v copaw-data:/app/working \
>   -v copaw-secrets:/app/working.secret \
>   agentscope/copaw:latest
> ```
> No port mapping (`-p`) is needed; the container shares the host network directly. Note that all container ports are exposed on the host, which may cause conflicts if the port is already in use.
>
> **Note:** If you only mount `/app/working` without a separate volume for `/app/working.secret`, the entrypoint will automatically redirect secrets into `/app/working/.secret` so they persist on the same volume.

The image is built from scratch. To build the image yourself, please refer to the [Build Docker image](scripts/README.md#build-docker-image) section in `scripts/README.md`, and then push to your registry.

### Using ModelScope

**No local install?** [ModelScope Studio](https://modelscope.cn/studios/fork?target=AgentScope/CoPaw) one-click cloud setup. Set your Studio to **non-public** so others cannot control your CoPaw.

### Deploy on Alibaba Cloud ECS

To run CoPaw on Alibaba Cloud (ECS), use the one-click deployment: open the [CoPaw on Alibaba Cloud (ECS) deployment link](https://computenest.console.aliyun.com/service/instance/create/cn-hangzhou?type=user&ServiceId=service-1ed84201799f40879884) and follow the prompts. For step-by-step instructions, see [Alibaba Cloud Developer: Deploy your AI assistant in 3 minutes](https://developer.aliyun.com/article/1713682).

---

## API Key

If you use a **cloud LLM** (e.g. DashScope, ModelScope), you must configure an API key before chatting. CoPaw will not work until a valid key is set. See the [official docs](https://copaw.agentscope.io/docs/models#configure-cloud-providers) for details.

**How to configure:**

1. **Console (recommended)** — After running `copaw app`, open **http://127.0.0.1:8088/** → **Settings** → **Models**. Choose a provider, enter the **API Key**, and enable that provider and model.
2. **`copaw init`** — When you run `copaw init`, it will guide you through configuring the LLM provider and API key. Follow the prompts to choose a provider and enter your key.
3. **Environment variable** — For DashScope you can set `DASHSCOPE_API_KEY` in your shell or in a `.env` file in the working directory.

Tools that need extra keys (e.g. `TAVILY_API_KEY` for web search) can be set in Console **Settings → Environment variables**, or see [Config](https://copaw.agentscope.io/docs/config) for details.

> **Using local models only?** If you use [Local Models](#local-models) (llama.cpp or MLX), you do **not** need any API key.

## Local Models

CoPaw can run LLMs entirely on your machine — no API keys or cloud services required. See the [official docs](https://copaw.agentscope.io/docs/models#local-providers-llamacpp--mlx) for details.

| Backend       | Best for                                 | Install                                                              |
| ------------- | ---------------------------------------- | -------------------------------------------------------------------- |
| **llama.cpp** | Cross-platform (macOS / Linux / Windows) | `pip install 'copaw[llamacpp]'` or `bash install.sh --extras llamacpp` |
| **MLX**       | Apple Silicon Macs (M1/M2/M3/M4)         | `pip install 'copaw[mlx]'` or `bash install.sh --extras mlx`         |
| **Ollama**    | Cross-platform (requires Ollama service) | `pip install 'copaw[ollama]'` or `bash install.sh --extras ollama`   |

After installing, you can download and manage local models in the **Console** UI. You can also use the command line:

```bash
copaw models download Qwen/Qwen3-4B-GGUF
copaw models # select the downloaded model
copaw app # start the server
```

---

## Documentation

| Topic                                                                 | Description                                      |
| --------------------------------------------------------------------- | ------------------------------------------------ |
| [Introduction](https://copaw.agentscope.io/docs/intro)                | What CoPaw is and how to use it                  |
| [Quick start](https://copaw.agentscope.io/docs/quickstart)            | Install and run (local or ModelScope Studio)    |
| [Console](https://copaw.agentscope.io/docs/console)                   | Web UI: chat and agent configuration            |
| [Models](https://copaw.agentscope.io/docs/models)                     | Configure cloud, local, and custom providers    |
| [Channels](https://copaw.agentscope.io/docs/channels)                  | DingTalk, Feishu, QQ, Discord, iMessage, and more |
| [Skills](https://copaw.agentscope.io/docs/skills)                      | Extend and customize capabilities               |
| [MCP](https://copaw.agentscope.io/docs/mcp)                            | Manage MCP clients                               |
| [Memory](https://copaw.agentscope.io/docs/memory)                     | Long-term memory                     |
| [Context](https://copaw.agentscope.io/docs/context)                   | Context management mechanism                     |
| [Magic commands](https://copaw.agentscope.io/docs/commands)           | Control conversation state without waiting for the AI |
| [Heartbeat](https://copaw.agentscope.io/docs/heartbeat)                | Scheduled check-in and digest                    |
| [Config & working dir](https://copaw.agentscope.io/docs/config) | Working directory and config file                |
| [CLI](https://copaw.agentscope.io/docs/cli)                            | Init, cron jobs, skills, clean                   |
| [FAQ](https://copaw.agentscope.io/docs/faq)                           | Common questions and troubleshooting             |

Full docs in this repo: [website/public/docs/](website/public/docs/).

---

## FAQ

For common questions, troubleshooting tips, and known issues, please visit the **[FAQ page](https://copaw.agentscope.io/docs/faq)**.

---

## Roadmap

| Area                                  | Item                                                                                                                                             | Status               |
| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------- |
| **Horizontal Expansion**              | More channels, models, skills, MCPs — **community contributions welcome**                                                                        | Seeking Contributors |
| **Existing Feature Extension**        | Display optimization, download hints, Windows path compatibility, etc. — **community contributions welcome**                                     | Seeking Contributors |
| **Console Web UI**                    | Expose more info/config in the Console                                                                                                           | In Progress          |
| **Self-healing**                      | Magic commands and daemon capabilities (CLI, status, restart, logs)                                                                              | In Progress          |
|                                       | DaemonAgent: autonomous diagnostics, self-healing, and recovery                                                                                  | Planned              |
| **Multi-agent**                       | Background task support                                                                                                                          | In Progress          |
|                                       | Multi-agent isolation                                                                                                                            | Planned              |
|                                       | Inter-agent contention resolution                                                                                                                | Planned              |
|                                       | Multi-agent communication                                                                                                                        | Planned              |
| **Multimodal**                        | Voice/video calls and real-time interaction                                                                                                      | In Progress          |
| **Small + Large Model Collaboration** | Train/fine-tune local small LLMs for CoPaw workflows and sensitive-data use cases                                                                | In Progress          |
|                                       | Multi-model routing. Local LLMs for sensitive data; cloud LLMs for planning and coding; balance of privacy, performance, and capability          | Planned              |
| **Memory System**                     | Experience distillation & skill extraction                                                                                                       | In Progress          |
|                                       | Multimodal memory fusion                                                                                                                         | Planned              |
|                                       | Context-aware proactive delivery                                                                                                                 | Planned              |
| **Security**                          | Shell execution confirmation                                                                                                                     | Planned              |
|                                       | Tool/skills security                                                                                                                             | Planned              |
|                                       | Configurable security levels (user-configurable)                                                                                                 | Planned              |
| **Release & Contributing**            | Contributing guidance for vibe coding agents                                                                                                     | Planned              |
| **Sandbox**                           | Deeper integration with AgentScope Runtime sandboxes                                                                                             | Long-term Planned    |
| **Cloud-native**                      | Deeper integration with AgentScope Runtime; leverage cloud compute, storage, and tooling                                                         | Long-term Planned    |
| **Skills Hub**                        | Enrich the [AgentScope Skills](https://github.com/agentscope-ai/agentscope-skills) repository and improve discoverability of high-quality skills | Long-term Planned    |


*Status:* *In Progress* — actively being worked on; *Planned* — queued or under design, also **welcome contributions**; *Seeking Contributors* — we **strongly encourage community contributions**; *Long-term Planned* — longer-horizon roadmap.

### Get involved

We are building CoPaw in the open and welcome contributions of all kinds! Check the [Roadmap](#roadmap) above (especially items marked **Seeking Contributors**) to find areas that interest you, and read [CONTRIBUTING](https://github.com/agentscope-ai/CoPaw/blob/main/CONTRIBUTING.md) to get started. We particularly welcome:

- **Horizontal expansion** — new channels, model providers, skills, MCPs.
- **Existing feature extension** — display and UX improvements, download hints, Windows path compatibility, and the like.

Join the conversation on [GitHub Discussions](https://github.com/agentscope-ai/CoPaw/discussions) to suggest or pick up work.

---

## Install from source

```bash
git clone https://github.com/agentscope-ai/CoPaw.git
cd CoPaw

# Build console frontend first (required for web UI)
cd console && npm ci && npm run build
cd ..

# Copy console build output to package directory
mkdir -p src/copaw/console
cp -R console/dist/. src/copaw/console/

# Install Python package
pip install -e .
```

- **Dev** (tests, formatting): `pip install -e ".[dev,full]"`
- **Then**: Run `copaw init --defaults`, then `copaw app`.

---

## Why CoPaw?

CoPaw represents both a **Co Personal Agent Workstation** and a "co-paw"—a partner always by your side. More than just a cold tool, CoPaw is a warm "little paw" always ready to lend a hand (or a paw!). It is the ultimate teammate for your digital life.

---

## Built by

[AgentScope team](https://github.com/agentscope-ai) · [AgentScope](https://github.com/agentscope-ai/agentscope) · [AgentScope Runtime](https://github.com/agentscope-ai/agentscope-runtime) · [ReMe](https://github.com/agentscope-ai/ReMe)

---

## Contact us

| [Discord](https://discord.gg/eYMpfnkG8h)                     | [X (Twitter)](https://x.com/agentscope_ai)                   | [DingTalk](https://qr.dingtalk.com/action/joingroup?code=v1,k1,OmDlBXpjW+I2vWjKDsjvI9dhcXjGZi3bQiojOq3dlDw=&_dt_no_comment=1&origin=11) |
| ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |
| [<img src="https://gw.alicdn.com/imgextra/i1/O1CN01hhD1mu1Dd3BWVUvxN_!!6000000000238-2-tps-400-400.png" width="80" height="80" alt="Discord">](https://discord.gg/eYMpfnkG8h) | [<img src="https://img.shields.io/badge/X-black.svg?logo=x&logoColor=white" width="80" height="80" alt="X">](https://x.com/agentscope_ai) | [<img src="https://img.alicdn.com/imgextra/i2/O1CN01vCWI8a1skHtLGXEMQ_!!6000000005804-2-tps-458-460.png" width="80" height="80" alt="DingTalk">](https://qr.dingtalk.com/action/joingroup?code=v1,k1,OmDlBXpjW+I2vWjKDsjvI9dhcXjGZi3bQiojOq3dlDw=&_dt_no_comment=1&origin=11) |

---

## Telemetry

CoPaw collects **anonymous** usage data during `copaw init` to help us understand our user base and prioritize improvements. Data is sent **once per version** — when you upgrade CoPaw, telemetry is re-collected so we can track version adoption.

**What we collect:**

- CoPaw version (e.g., 0.0.7)
- Install method (pip, Docker, or desktop app)
- OS and version (e.g., macOS 14.0, Ubuntu 22.04)
- Python version (e.g., 3.13)
- CPU architecture (e.g., x86_64, arm64)
- GPU availability (yes/no)

**What we do NOT collect:** No personal data, no files, no credentials, no IP addresses, no identifiable information.

When running `copaw init` interactively, you will be asked whether to opt in. If you choose `--defaults`, telemetry is accepted automatically. The prompt appears once per version and never affects CoPaw's functionality.

---

## License

CoPaw is released under the [Apache License 2.0](LICENSE).

---

## Contributors

All thanks to our contributors:

<a href="https://github.com/agentscope-ai/CoPaw/graphs/contributors">
  <img src="https://contrib.rocks/image?repo=agentscope-ai/CoPaw" alt="Contributors" />
</a>


================================================
FILE: README_ja.md
================================================
<div align="center">

# CoPaw

[![GitHub Repo](https://img.shields.io/badge/GitHub-Repo-black.svg?logo=github)](https://github.com/agentscope-ai/CoPaw)
[![PyPI](https://img.shields.io/pypi/v/copaw?color=3775A9&label=PyPI&logo=pypi)](https://pypi.org/project/copaw/)
[![Documentation](https://img.shields.io/badge/Docs-Website-green.svg?logo=readthedocs&label=Docs)](https://copaw.agentscope.io/)
[![Python Version](https://img.shields.io/badge/python-3.10%20~%20%3C3.14-blue.svg?logo=python&label=Python)](https://www.python.org/downloads/)
[![Last Commit](https://img.shields.io/github/last-commit/agentscope-ai/CoPaw)](https://github.com/agentscope-ai/CoPaw)
[![License](https://img.shields.io/badge/license-Apache%202.0-red.svg?logo=apache&label=License)](LICENSE)
[![Code Style](https://img.shields.io/badge/code%20style-black-black.svg?logo=python&label=CodeStyle)](https://github.com/psf/black)
[![GitHub Stars](https://img.shields.io/github/stars/agentscope-ai/CoPaw?style=flat&logo=github&color=yellow&label=Stars)](https://github.com/agentscope-ai/CoPaw/stargazers)
[![GitHub Forks](https://img.shields.io/github/forks/agentscope-ai/CoPaw?style=flat&logo=github&color=purple&label=Forks)](https://github.com/agentscope-ai/CoPaw/network)
[![DeepWiki](https://img.shields.io/badge/DeepWiki-Ask_Devin-navy.svg?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAyCAYAAAAnWDnqAAAAAXNSR0IArs4c6QAAA05JREFUaEPtmUtyEzEQhtWTQyQLHNak2AB7ZnyXZMEjXMGeK/AIi+QuHrMnbChYY7MIh8g01fJoopFb0uhhEqqcbWTp06/uv1saEDv4O3n3dV60RfP947Mm9/SQc0ICFQgzfc4CYZoTPAswgSJCCUJUnAAoRHOAUOcATwbmVLWdGoH//PB8mnKqScAhsD0kYP3j/Yt5LPQe2KvcXmGvRHcDnpxfL2zOYJ1mFwrryWTz0advv1Ut4CJgf5uhDuDj5eUcAUoahrdY/56ebRWeraTjMt/00Sh3UDtjgHtQNHwcRGOC98BJEAEymycmYcWwOprTgcB6VZ5JK5TAJ+fXGLBm3FDAmn6oPPjR4rKCAoJCal2eAiQp2x0vxTPB3ALO2CRkwmDy5WohzBDwSEFKRwPbknEggCPB/imwrycgxX2NzoMCHhPkDwqYMr9tRcP5qNrMZHkVnOjRMWwLCcr8ohBVb1OMjxLwGCvjTikrsBOiA6fNyCrm8V1rP93iVPpwaE+gO0SsWmPiXB+jikdf6SizrT5qKasx5j8ABbHpFTx+vFXp9EnYQmLx02h1QTTrl6eDqxLnGjporxl3NL3agEvXdT0WmEost648sQOYAeJS9Q7bfUVoMGnjo4AZdUMQku50McDcMWcBPvr0SzbTAFDfvJqwLzgxwATnCgnp4wDl6Aa+Ax283gghmj+vj7feE2KBBRMW3FzOpLOADl0Isb5587h/U4gGvkt5v60Z1VLG8BhYjbzRwyQZemwAd6cCR5/XFWLYZRIMpX39AR0tjaGGiGzLVyhse5C9RKC6ai42ppWPKiBagOvaYk8lO7DajerabOZP46Lby5wKjw1HCRx7p9sVMOWGzb/vA1hwiWc6jm3MvQDTogQkiqIhJV0nBQBTU+3okKCFDy9WwferkHjtxib7t3xIUQtHxnIwtx4mpg26/HfwVNVDb4oI9RHmx5WGelRVlrtiw43zboCLaxv46AZeB3IlTkwouebTr1y2NjSpHz68WNFjHvupy3q8TFn3Hos2IAk4Ju5dCo8B3wP7VPr/FGaKiG+T+v+TQqIrOqMTL1VdWV1DdmcbO8KXBz6esmYWYKPwDL5b5FA1a0hwapHiom0r/cKaoqr+27/XcrS5UwSMbQAAAABJRU5ErkJggg==)](https://deepwiki.com/agentscope-ai/CoPaw)
[![Discord](https://img.shields.io/badge/Discord-Join_Us-blueviolet.svg?logo=discord)](https://discord.gg/eYMpfnkG8h)
[![X](https://img.shields.io/badge/X-Follow_Us-black.svg?logo=x)](https://x.com/agentscope_ai)
[![DingTalk](https://img.shields.io/badge/DingTalk-Join_Us-orange.svg)](https://qr.dingtalk.com/action/joingroup?code=v1,k1,OmDlBXpjW+I2vWjKDsjvI9dhcXjGZi3bQiojOq3dlDw=&_dt_no_comment=1&origin=11)

[[ドキュメント](https://copaw.agentscope.io/)] [[English README](README.md)] [[中文 README](README_zh.md)]

<p align="center">
  <img src="https://img.alicdn.com/imgextra/i2/O1CN014TIqyO1U5wDiSbFfA_!!6000000002467-2-tps-816-192.png" alt="CoPaw Logo" width="120">
</p>

<p align="center"><b>あなたのために働き、あなたとともに成長する。</b></p>

</div>

あなた専用のAIアシスタント。簡単にインストールでき、ローカルマシンやクラウドにデプロイ可能。複数のチャットアプリに対応し、機能を簡単に拡張できます。

> **主な機能:**
>
> **あらゆるチャネル** — DingTalk、Feishu、QQ、Discord、iMessageなど。1つのアシスタントで、必要に応じて接続。
>
> **あなたの管理下** — メモリとパーソナライズはあなたの管理下に。ローカルまたはクラウドにデプロイ。任意のチャネルへのスケジュールリマインダー。
>
> **スキル** — 組み込みのcron機能。ワークスペース内のカスタムスキルを自動読み込み。ロックインなし。
>
> <details>
> <summary><b>こんなことができます</b></summary>
>
> <br>
>
> - **ソーシャル**: 人気投稿のデイリーダイジェスト(小紅書、知乎、Reddit)、Bilibili/YouTubeの要約。
> - **生産性**: ニュースレターのダイジェストをDingTalk/Feishu/QQに配信、メール/カレンダーからの連絡先管理。
> - **クリエイティブ**: 目標を伝えて一晩実行、翌日にはドラフトが完成。
> - **リサーチ**: テック/AIニュースの追跡、パーソナルナレッジベース。
> - **デスクトップ**: ファイル整理、ドキュメントの読み取り/要約、チャットでファイルをリクエスト。
> - **探索**: スキルとcronを組み合わせて、独自のエージェントアプリを構築。
>
> </details>

---

## ニュース

[2026-03-12] v0.0.7をリリースしました!詳細は[v0.0.7リリースノート](https://agentscope-ai.github.io/CoPaw/release-notes)でご確認ください。

- **[v0.0.7] 追加:** Tool Guardセキュリティレイヤー(危険なツール呼び出しをユーザー承認まで遮断); MattermostとMatrixチャネル統合; Discord/DingTalk/Feishu/Telegramの@メンションフィルタリング; Telegram Markdownレンダリング; Feishu絵文字リアクションとリッチテキストメディア; QQ画像送信; LLM呼び出し自動リトライ; LM Studioプロバイダー; トークン使用量ダッシュボード; プロバイダー`generate_kwargs`エディタ; ワークスペースファイルのドラッグ&ドロップ; チャット中のモデル切替; エージェント言語セレクター; コンテキスト管理UI; ページ遷移時のチャット状態保持; AIスキル最適化とストリーミング出力; スキルカード説明表示; 中国ユーザー向け自動PyPIミラー選択。
- **[v0.0.7] 改善:** プロバイダー接続テストメッセージ; ワークスペースzip・セッション読取の非同期化; プロバイダーID競合の自動解決; モデル検出のオンデマンド化; トークン記録の集約; 組み込みスキルドキュメントとシェルPATH処理; Himalayaメールスキル; メモリドキュメント再構成; 設定・セキュリティページのリファクタリング。
- **[v0.0.7] 修正:** DingTalk認証失敗時のクリーンアップ; Discord 2000文字超メッセージ分割; Matrix/Mattermost/MQTTチャネル設定の型整合; Windowsシェルエンコーディングとプロセスツリークリーンアップ; デスクトップSSL証明書・IME入力・外部URLナビゲーション; インストールスクリプトの関数定義順序修正; マジックコマンドセッション状態保護; Ollamaモーダル再レンダリング; `get_token_usage`データアクセス; チャットリクエストの重複排除。
- **[v0.0.7] 貢献者:** 新規貢献者の皆さんに感謝します: [@2catycm](https://github.com/2catycm), [@2niuhe](https://github.com/2niuhe), [@yingdachen](https://github.com/yingdachen), [@Atletico1999](https://github.com/Atletico1999), [@buecker](https://github.com/buecker), [@Cirilla-zmh](https://github.com/Cirilla-zmh), [@gnipping](https://github.com/gnipping), [@Nufe-muzi](https://github.com/Nufe-muzi), [@FuKunZ](https://github.com/FuKunZ), [@JasonBuildAI](https://github.com/JasonBuildAI), [@StarMoonCity](https://github.com/StarMoonCity), [@walker83](https://github.com/walker83), [@lllcy](https://github.com/lllcy)。

[2026-03-09] v0.0.6をリリースしました!詳細は[v0.0.6リリースノート](https://agentscope-ai.github.io/CoPaw/release-notes)でご確認ください。

[2026-03-06] v0.0.5をリリースしました!詳細は[v0.0.5リリースノート](https://agentscope-ai.github.io/CoPaw/release-notes)でご確認ください。

[2026-03-02] v0.0.4をリリースしました!詳細は[v0.0.4リリースノート](https://agentscope-ai.github.io/CoPaw/release-notes)でご確認ください。

---

## 目次

> **おすすめの読み方:**
>
> - **3つのコマンドで実行したい**: [クイックスタート](#クイックスタート) → ブラウザでコンソールを開く。
> - **DingTalk / Feishu / QQ でチャットしたい**: コンソールで[チャネル](https://copaw.agentscope.io/docs/channels)を設定。
> - **Pythonをインストールしたくない**: [ワンラインインストール](#ワンラインインストールベータ版継続的に改善中)がPythonを自動処理。または[ModelScopeワンクリック](https://modelscope.cn/studios/fork?target=AgentScope/CoPaw)でクラウドデプロイ。

- [ニュース](#ニュース)
- [クイックスタート](#クイックスタート)
- [APIキー](#apiキー)
- [ローカルモデル](#ローカルモデル)
- [ドキュメント](#ドキュメント)
- [FAQ](#faq)
- [ロードマップ](#ロードマップ)
- [参加方法](#参加方法)
- [ソースからインストール](#ソースからインストール)
- [なぜCoPaw?](#なぜcopaw)
- [開発チーム](#開発チーム)
- [ライセンス](#ライセンス)

---

## クイックスタート

### pip install(推奨)

Pythonを自分で管理する場合:

```bash
pip install copaw
copaw init --defaults
copaw app
```

ブラウザで **http://127.0.0.1:8088/** を開くとコンソール(CoPawとのチャット、エージェントの設定)が利用できます。DingTalk、Feishu、QQなどで会話するには、[チャネルドキュメント](https://copaw.agentscope.io/docs/channels)でチャネルを接続してください。

![Console](https://img.alicdn.com/imgextra/i3/O1CN01VYsFVo23aAvIM3GXB_!!6000000007271-2-tps-3328-1860.png)

### ワンラインインストール(ベータ版、継続的に改善中)

Pythonは不要です — インストーラーがすべて自動で処理します:

**macOS / Linux:**

```bash
curl -fsSL https://copaw.agentscope.io/install.sh | bash
```

Ollamaサポート付きでインストールする場合:

```bash
curl -fsSL https://copaw.agentscope.io/install.sh | bash -s -- --extras ollama
```

複数のエクストラ(例: Ollama + llama.cpp)付きでインストールする場合:

```bash
curl -fsSL https://copaw.agentscope.io/install.sh | bash -s -- --extras ollama,llamacpp
```

**Windows (CMD):**

```CMD
curl -fsSL https://copaw.agentscope.io/install.bat -o install.bat && install.bat
```

**Windows (PowerShell):**

```powershell
irm https://copaw.agentscope.io/install.ps1 | iex
```

> **注意**: インストーラーは uv の状態を自動的に確認します。未インストールの場合はダウンロードと設定を試みます。自動インストールに失敗した場合は、画面の指示に従うか、`python -m pip install -U uv` を実行してからインストーラーを再実行してください。

> **⚠️ Windows Enterprise LTSC ユーザーへの重要なお知らせ**
>
> Windows LTSC または厳格なセキュリティポリシーが適用された企業環境をご利用の場合、PowerShell は **制限付き言語モード** で実行される可能性があり、以下の問題が発生する可能性があります:
> 1. **CMD(.bat)使用時:スクリプトは正常に実行されるが`Path`への書き込みができない**
>
>    スクリプトはファイルインストールを完了しましたが、**制限付き言語モード**のため環境変数への自動書き込みができません。この場合、手動で設定してください:
>    - **インストールディレクトリを確認**:
>      - `uv` の利用可否を確認:CMD で `uv --version` と入力し、バージョン番号が表示された場合は**CoPaw パスのみ設定**。`『uv』 は内部コマンドでも外部コマンドでもなく、実行可能プログラムまたはバッチファイルでもありません。` と表示された場合は両方を設定する必要があります。
>      - uvパス(いずれか一つ、インストール場所に応じて選択。`uv`が利用不可の場合に記入):通常`%USERPROFILE%\.local\bin`、`%USERPROFILE%\AppData\Local\uv`、またはPythonインストールディレクトリの`Scripts`フォルダ
>      - CoPawパス:通常 `%USERPROFILE%\.copaw\bin` にあります。
>    - **システム環境変数Pathへの手動追加**:
>      - `Win + R` を押し、`sysdm.cpl` と入力して Enter キーを押し、「システムのプロパティ」を開く。
>      - 「詳細設定」→「環境変数」をクリック。
>      - 「システム変数」で `Path` を探して選択し、「編集」をクリック。
>      - 「新規」をクリックし、上記の2つのディレクトリパスを順に入力して「OK」をクリックし保存します。
> 2. **PowerShell(.ps1)を使用している場合:スクリプト実行が中断する**
>
>   **制限付き言語モード** のため、スクリプトが自動的に`uv`をダウンロードできない可能性があります。
>   - **uvを手動でインストール**: [GitHub Release](https://github.com/astral-sh/uv/releases) を参照し、`uv.exe` を `%USERPROFILE%\.local\bin` または `%USERPROFILE%\AppData\Local\uv` に配置。または Python がインストールされていることを確認し、`python -m pip install -U uv` を実行。
>   - **`uv`環境変数の設定**:`uv`の配置ディレクトリと `%USERPROFILE%\.copaw\bin` をシステムの `Path` 変数に追加してください。
>   - **再実行**:新しいターミナルを開き、インストールスクリプトを再度実行して `CoPaw` のインストールを完了させてください。
>   - **`CoPaw`環境変数の設定**:`%USERPROFILE%\.copaw\bin` をシステムの `Path` 変数に追加します。

インストール完了後、新しいターミナルを開き、以下を実行してください:

```bash
copaw init --defaults   # または: copaw init(対話式)
copaw app
```

<details>
<summary><b>インストールオプション</b></summary>

**macOS / Linux:**

```bash
# 特定のバージョンをインストール
curl -fsSL ... | bash -s -- --version 0.0.2

# ソースからインストール(開発/テスト用)
curl -fsSL ... | bash -s -- --from-source

# ローカルモデルサポート付き
bash install.sh --extras llamacpp    # llama.cpp(クロスプラットフォーム)
bash install.sh --extras mlx         # MLX(Apple Silicon)
bash install.sh --extras llamacpp,mlx

# アップグレード — インストーラーを再実行するだけ
curl -fsSL ... | bash

# アンインストール
copaw uninstall          # 設定とデータを保持
copaw uninstall --purge  # すべて削除
```

**Windows (PowerShell):**

```powershell
# 特定のバージョンをインストール
irm ... | iex; .\install.ps1 -Version 0.0.2

# ソースからインストール(開発/テスト用)
.\install.ps1 -FromSource

# ローカルモデルサポート付き
.\install.ps1 -Extras llamacpp      # llama.cpp(クロスプラットフォーム)
.\install.ps1 -Extras mlx           # MLX
.\install.ps1 -Extras llamacpp,mlx

# アップグレード — インストーラーを再実行するだけ
irm ... | iex

# アンインストール
copaw uninstall          # 設定とデータを保持
copaw uninstall --purge  # すべて削除
```

</details>

### デスクトップアプリケーション(Beta)

> **Beta版の注意事項**: デスクトップアプリケーションは現在Beta版テスト段階にあり、以下の既知の制限があります:
> - **互換性テストが不完全**: すべてのシステムバージョンとハードウェア構成で十分にテストされていません
> - **パフォーマンスの問題の可能性**: 起動時間、メモリ使用量などのパフォーマンス面でさらなる最適化が必要な場合があります
> - **開発中の機能**: 一部の機能が不安定または欠落している可能性があります

コマンドラインツールに慣れていない場合、CoPawのデスクトップアプリケーションをダウンロードして使用できます。Python環境の手動設定やコマンドの実行は不要です。

#### ダウンロード

[GitHub Releases](https://github.com/agentscope-ai/CoPaw/releases)からデスクトップアプリをダウンロード:
- **Windows**: `CoPaw-Setup-<version>.exe`
- **macOS**: `CoPaw-<version>-macOS.zip` (Apple Silicon推奨)

#### 特徴

- ✅ **ゼロ設定**: ダウンロードしてダブルクリックするだけで実行可能、Pythonのインストールや環境変数の設定は不要
- ✅ **クロスプラットフォーム**: Windows 10+ と macOS 14+ に対応
- ✅ **ビジュアルインターフェース**: ブラウザインターフェースが自動的に開き、手動でアドレスを入力する必要はありません
- ⚠️ **Beta段階**: 機能は継続的に改善中、フィードバックを歓迎します

#### 初回起動

**重要**: 初回起動には10〜60秒かかる場合があります(システム構成によります)。アプリケーションはPython環境の初期化と依存関係の読み込みが必要です。ブラウザウィンドウが自動的に開くまでお待ちください。

#### macOS: システムセキュリティ制限の回避

ReleasesからCoPaw macOSアプリをダウンロードすると、macOSは次のように表示する場合があります: *「Appleは'CoPaw'に悪意のあるソフトウェアが含まれていないことを確認できません」*。これはアプリが公証されていないためです。以下の方法で開くことができます:

- **右クリックして開く(推奨)**
  CoPawアプリを右クリック(またはControl+クリック)→ **「開く」** → ダイアログで再度 **「開く」** をクリック。これによりGatekeeperにアプリを信頼していることを伝えます。その後は通常通りダブルクリックで起動できます。

- **システム設定で許可**
  それでもブロックされる場合、**システム設定 → プライバシーとセキュリティ** に移動し、*「'CoPaw'は未確認の開発元からのものであるためブロックされました」* のようなメッセージまでスクロールし、**「このまま開く」** または **「許可」** をクリックします。

- **検疫属性の削除(ほとんどのユーザーには非推奨)**
  ターミナルで実行:
  `xattr -cr /Applications/CoPaw.app`
  (または解凍後の `.app` へのパスを使用)。これにより「インターネットからダウンロードされた」検疫フラグがクリアされ、通常は警告が表示されなくなりますが、**右クリック → 開く** を使用するよりも安全性と制御性が低くなります。

詳細な使用方法、トラブルシューティング、よくある問題については、[デスクトップアプリケーションガイド](https://copaw.agentscope.io/docs/desktop)を参照してください。

### Dockerを使用

イメージは **Docker Hub**(`agentscope/copaw`)で公開しています。タグ: `latest`(安定版); `pre`(PyPIプレリリース版)。

```bash
docker pull agentscope/copaw:latest
docker run -p 127.0.0.1:8088:8088 \
  -v copaw-data:/app/working \
  -v copaw-secrets:/app/working.secret \
  agentscope/copaw:latest
```

中国のユーザーは阿里雲コンテナレジストリ(ACR)も利用できます: `agentscope-registry.ap-southeast-1.cr.aliyuncs.com/agentscope/copaw`(タグは同じ)。

ブラウザで **http://127.0.0.1:8088/** を開くとコンソールが利用できます。設定、メモリ、スキルは `copaw-data` ボリュームに保存されます。モデル設定とAPIキーは `copaw-secrets` ボリュームに保存されます。APIキー(例: `DASHSCOPE_API_KEY`)を渡すには、`docker run` に `-e VAR=value` または `--env-file .env` を追加してください。

> **ホストマシン上のOllamaや他のモデルサービスに接続する**
>
> Dockerコンテナ内の `localhost` はコンテナ自身を指し、ホストマシンではありません。Ollama(または他のモデルサービス)がホスト上で動作している場合、以下のいずれかの方法でCoPawコンテナからアクセスできます:
>
> **方法A** — ホストアドレスの明示的バインディング(全プラットフォーム対応):
> ```bash
> docker run -p 127.0.0.1:8088:8088 \
>   --add-host=host.docker.internal:host-gateway \
>   -v copaw-data:/app/working \
>   -v copaw-secrets:/app/working.secret \
>   agentscope/copaw:latest
> ```
> その後、CoPawの **Settings → Models → Ollama** で、Base URLを `http://host.docker.internal:11434` または対応するポートに変更してください。
>
> **方法B** — ホストネットワーク(Linuxのみ):
> ```bash
> docker run --network=host \
>   -v copaw-data:/app/working \
>   -v copaw-secrets:/app/working.secret \
>   agentscope/copaw:latest
> ```
> ポートマッピング(`-p`)は不要で、コンテナはホストネットワークを直接共有します。ただし、コンテナの全ポートがホスト上に公開されるため、使用中のポートと競合する可能性があります。
>
> **ヒント:** `/app/working` のみをマウントし `/app/working.secret` を別途マウントしない場合、エントリポイントスクリプトが自動的にsecretsを `/app/working/.secret` にリダイレクトし、同じボリュームに永続化します。

イメージはゼロからビルドされています。自分でイメージをビルドする場合は、`scripts/README.md` の [Build Docker image](scripts/README.md#build-docker-image) セクションを参照し、レジストリにプッシュしてください。

### ModelScopeを使用

**ローカルインストール不要?** [ModelScope Studio](https://modelscope.cn/studios/fork?target=AgentScope/CoPaw) でワンクリッククラウドセットアップ。他の人があなたのCoPawを操作できないよう、Studioを**非公開**に設定してください。

### Alibaba Cloud ECSへのデプロイ

CoPawをAlibaba Cloud(ECS)で実行するには、ワンクリックデプロイを使用します: [CoPaw on Alibaba Cloud (ECS) デプロイリンク](https://computenest.console.aliyun.com/service/instance/create/cn-hangzhou?type=user&ServiceId=service-1ed84201799f40879884)を開き、プロンプトに従ってください。ステップバイステップの手順については、[Alibaba Cloud Developer: 3分でAIアシスタントをデプロイ](https://developer.aliyun.com/article/1713682)を参照してください。

---

## APIキー

**クラウドLLM**(例: DashScope、ModelScope)を使用する場合、チャットの前にAPIキーを設定する必要があります。有効なキーが設定されるまでCoPawは動作しません。詳細は[公式ドキュメント](https://copaw.agentscope.io/docs/models#configure-cloud-providers)をご覧ください。

**設定方法:**

1. **コンソール(推奨)** — `copaw app` 実行後、**http://127.0.0.1:8088/** を開き → **設定** → **モデル**。プロバイダーを選択し、**APIキー**を入力して、そのプロバイダーとモデルを有効にしてください。
2. **`copaw init`** — `copaw init` を実行すると、LLMプロバイダーとAPIキーの設定が案内されます。プロンプトに従ってプロバイダーを選択し、キーを入力してください。
3. **環境変数** — DashScopeの場合、シェルまたはワーキングディレクトリの `.env` ファイルで `DASHSCOPE_API_KEY` を設定できます。

その他のキー(例: Web検索用 `TAVILY_API_KEY`)は、コンソールの **設定 → 環境変数** で設定するか、[Config](https://copaw.agentscope.io/docs/config) で詳細を確認してください。

> **ローカルモデルのみ使用する場合:** [ローカルモデル](#ローカルモデル)(llama.cppまたはMLX)を使用する場合、APIキーは**不要**です。

## ローカルモデル

CoPawはLLMを完全にローカルマシン上で実行できます — APIキーやクラウドサービスは不要です。詳細は[公式ドキュメント](https://copaw.agentscope.io/docs/models#local-providers-llamacpp--mlx)をご覧ください。

| バックエンド       | 最適な用途                                 | インストール                                                              |
| ------------- | ---------------------------------------- | -------------------------------------------------------------------- |
| **llama.cpp** | クロスプラットフォーム(macOS / Linux / Windows) | `pip install 'copaw[llamacpp]'` または `bash install.sh --extras llamacpp` |
| **MLX**       | Apple Silicon(M1/M2/M3/M4)             | `pip install 'copaw[mlx]'` または `bash install.sh --extras mlx`         |
| **Ollama**    | クロスプラットフォーム(Ollamaサービスが必要) | `pip install 'copaw[ollama]'` または `bash install.sh --extras ollama`   |

インストール後、**コンソール**UIでローカルモデルのダウンロードと管理ができます。コマンドラインでも利用できます:

```bash
copaw models download Qwen/Qwen3-4B-GGUF
copaw models # ダウンロードしたモデルを選択
copaw app    # サーバーを起動
```

---

## ドキュメント

| トピック                                                                 | 説明                                                |
| ------------------------------------------------------------------------- | --------------------------------------------------- |
| [はじめに](https://copaw.agentscope.io/docs/intro)                        | CoPawとは何か、使い方                               |
| [クイックスタート](https://copaw.agentscope.io/docs/quickstart)          | インストールと実行(ローカルまたはModelScope Studio) |
| [コンソール](https://copaw.agentscope.io/docs/console)                     | Web UI: チャットとエージェント設定                  |
| [モデル](https://copaw.agentscope.io/docs/models)                         | クラウド・ローカル・カスタムプロバイダーの設定       |
| [チャネル](https://copaw.agentscope.io/docs/channels)                      | DingTalk、Feishu、QQ、Discord、iMessageなど         |
| [スキル](https://copaw.agentscope.io/docs/skills)                          | 機能の拡張とカスタマイズ                             |
| [MCP](https://copaw.agentscope.io/docs/mcp)                                | MCPクライアントの管理                               |
| [メモリ](https://copaw.agentscope.io/docs/memory)                          | 長期記憶                              |
| [コンテキスト](https://copaw.agentscope.io/docs/context)                  | コンテキスト管理メカニズム                          |
| [魔法コマンド](https://copaw.agentscope.io/docs/commands)                 | AIの応答を待たずに会話状態を制御                     |
| [ハートビート](https://copaw.agentscope.io/docs/heartbeat)                 | スケジュールされたチェックインとダイジェスト        |
| [設定とワーキングディレクトリ](https://copaw.agentscope.io/docs/config)   | ワーキングディレクトリと設定ファイル                 |
| [CLI](https://copaw.agentscope.io/docs/cli)                                | Init、cronジョブ、スキル、クリーン                   |
| [FAQ](https://copaw.agentscope.io/docs/faq)                               | よくある質問とトラブルシューティング                 |

リポジトリ内の完全なドキュメント: [website/public/docs/](website/public/docs/)

---

## FAQ

よくある質問、トラブルシューティングのヒント、既知の問題については、**[FAQページ](https://copaw.agentscope.io/docs/faq)** をご覧ください。

---

## ロードマップ

| 方向 | 項目 | 状態 |
| --- | --- | --- |
| **横展開** | より多くのチャネル、モデル、スキル、MCP など — **コミュニティの貢献歓迎** | 貢献者募集中 |
| **既存機能の拡張・改善** | 表示の最適化、ダウンロードヒント、Windowsパス互換など — **コミュニティの貢献歓迎** | 貢献者募集中 |
| **コンソール Web UI** | コンソールでより多くの情報と設定を公開 | 進行中 |
| **自己修復** | マジックコマンドとデーモン機能(CLI、status、restart、logs) | 進行中 |
| | DaemonAgent: 自律診断、自己修復、復旧 | 計画中 |
| **マルチエージェント** | バックグラウンドタスクサポート | 進行中 |
| | マルチエージェントの分離 | 計画中 |
| | エージェント間の競合・衝突の解決 | 計画中 |
| | マルチエージェント通信 | 計画中 |
| **マルチモーダル** | 音声/ビデオ通話とリアルタイム対話 | 進行中 |
| **大小モデル協調** | CoPaw ワークフローと機密データ向けのローカル小モデル学習・ファインチューニング | 進行中 |
| | マルチモデルルーティング。ローカルモデルで機密データ処理、クラウドモデルで計画・コーディング;プライバシー・性能・能力の両立 | 計画中 |
| **メモリシステム** | 経験の蓄積とスキル抽出 | 進行中 |
| | マルチモーダルメモリの融合強化 | 計画中 |
| | シーン認識による能動的プッシュ | 計画中 |
| **セキュリティ** | シェル実行の確認 | 計画中 |
| | ツール/スキルのセキュリティ | 計画中 |
| | 設定可能なセキュリティレベル | 計画中 |
| **バージョンリリース・貢献規範** | Vibe Coding 等のエージェント向け貢献ガイダンス | 計画中 |
| **サンドボックス** | AgentScope Runtime サンドボックスとの深い統合 | 長期計画 |
| **クラウドネイティブ** | AgentScope Runtime との深い統合、クラウド算力・ストレージ・ツールエコシステムの活用 | 長期計画 |
| **スキルエコシステム** | [AgentScope Skills](https://github.com/agentscope-ai/agentscope-skills) リポジトリの充実、高品質スキルの発見・利用向上 | 長期計画 |

*状態説明:進行中 — 推進中;計画中 — 予定または設計中、**貢献も歓迎**;**貢献者募集中** — **コミュニティの参加を歓迎**;長期計画 — 中長期ロードマップ。*

### 参加方法

CoPawはオープンに開発しており、あらゆる形の貢献を歓迎しています!上記の[ロードマップ](#ロードマップ)(特に**貢献者募集中**の項目)から興味のある領域を選び、[CONTRIBUTING](https://github.com/agentscope-ai/CoPaw/blob/main/CONTRIBUTING.md)を読んで始めてください。特に歓迎するのは:

- **横展開** — 新規チャネル、モデルプロバイダー、スキル、MCP。
- **既存機能の拡張・改善** — 表示とインタラクションの最適化、ダウンロードヒント、Windowsパス互換など。

[GitHub Discussions](https://github.com/agentscope-ai/CoPaw/discussions)で議論に参加し、アイデアを提案したりタスクを担当したりしてください。

---

## ソースからインストール

```bash
git clone https://github.com/agentscope-ai/CoPaw.git
cd CoPaw

# まずコンソールフロントエンドをビルド(Web UIに必須)
cd console && npm ci && npm run build
cd ..

# コンソールのビルド出力をパッケージディレクトリにコピー
mkdir -p src/copaw/console
cp -R console/dist/. src/copaw/console/

# Pythonパッケージのインストール
pip install -e .
```

- **開発**(テスト、フォーマット): `pip install -e ".[dev,full]"`
- **その後**: `copaw init --defaults` を実行し、次に `copaw app` を実行。

---

## なぜCoPaw?

CoPawは **Co Personal Agent Workstation**(共同パーソナルエージェントワークステーション)であると同時に、「co-paw」— いつもあなたのそばにいるパートナーを表しています。単なる冷たいツールではなく、CoPawはいつでも手(または肉球!)を貸してくれる温かい「小さな肉球」です。デジタルライフにおける究極のチームメイトです。

---

## 開発チーム

[AgentScope team](https://github.com/agentscope-ai) · [AgentScope](https://github.com/agentscope-ai/agentscope) · [AgentScope Runtime](https://github.com/agentscope-ai/agentscope-runtime) · [ReMe](https://github.com/agentscope-ai/ReMe)

---

## お問い合わせ

| [Discord](https://discord.gg/eYMpfnkG8h)                     | [X (Twitter)](https://x.com/agentscope_ai)                   | [DingTalk](https://qr.dingtalk.com/action/joingroup?code=v1,k1,OmDlBXpjW+I2vWjKDsjvI9dhcXjGZi3bQiojOq3dlDw=&_dt_no_comment=1&origin=11) |
| ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |
| [<img src="https://gw.alicdn.com/imgextra/i1/O1CN01hhD1mu1Dd3BWVUvxN_!!6000000000238-2-tps-400-400.png" width="80" height="80" alt="Discord">](https://discord.gg/eYMpfnkG8h) | [<img src="https://img.shields.io/badge/X-black.svg?logo=x&logoColor=white" width="80" height="80" alt="X">](https://x.com/agentscope_ai) | [<img src="https://img.alicdn.com/imgextra/i2/O1CN01vCWI8a1skHtLGXEMQ_!!6000000005804-2-tps-458-460.png" width="80" height="80" alt="DingTalk">](https://qr.dingtalk.com/action/joingroup?code=v1,k1,OmDlBXpjW+I2vWjKDsjvI9dhcXjGZi3bQiojOq3dlDw=&_dt_no_comment=1&origin=11) |

---

## テレメトリ

CoPawは `copaw init` 実行時に**匿名**の利用データを収集し、ユーザー環境の把握と製品改善に役立てています。データは**バージョンごとに1回**送信されます — CoPawをアップグレードすると、バージョン分布を把握するために再収集されます。

**収集する情報:**

- CoPawバージョン(例: 0.0.7)
- インストール方法(pip、Docker、またはデスクトップアプリ)
- OSとバージョン(例: macOS 14.0、Ubuntu 22.04)
- Pythonバージョン(例: 3.13)
- CPUアーキテクチャ(例: x86_64、arm64)
- GPUの利用可否(はい/いいえ)

**収集しないもの:** 個人データ、ファイル、認証情報、IPアドレス、個人を特定できる情報は一切収集しません。

`copaw init` を対話モードで実行すると、同意するかどうか尋ねられます。`--defaults` モードでは自動的に同意されます。プロンプトはバージョンごとに1回のみ表示され、CoPawの機能には影響しません。

---

## ライセンス

CoPawは[Apache License 2.0](LICENSE)の下でリリースされています。

---

## コントリビューター

CoPawをより良くするために貢献してくださったすべての方々に感謝します:

<a href="https://github.com/agentscope-ai/CoPaw/graphs/contributors">
  <img src="https://contrib.rocks/image?repo=agentscope-ai/CoPaw" alt="コントリビューター" />
</a>


================================================
FILE: README_zh.md
================================================
<div align="center">

# CoPaw

[![GitHub 仓库](https://img.shields.io/badge/GitHub-仓库-black.svg?logo=github)](https://github.com/agentscope-ai/CoPaw)
[![PyPI](https://img.shields.io/pypi/v/copaw?color=3775A9&label=PyPI&logo=pypi)](https://pypi.org/project/copaw/)
[![文档](https://img.shields.io/badge/文档-在线-green.svg?logo=readthedocs&label=Docs)](https://copaw.agentscope.io/)
[![Python 版本](https://img.shields.io/badge/python-3.10%20~%20%3C3.14-blue.svg?logo=python&label=Python)](https://www.python.org/downloads/)
[![最后提交](https://img.shields.io/github/last-commit/agentscope-ai/CoPaw)](https://github.com/agentscope-ai/CoPaw)
[![许可证](https://img.shields.io/badge/license-Apache%202.0-red.svg?logo=apache&label=%E8%AE%B8%E5%8F%AF%E8%AF%81)](LICENSE)
[![代码风格](https://img.shields.io/badge/code%20style-black-black.svg?logo=python&label=%E4%BB%A3%E7%A0%81%E9%A3%8E%E6%A0%BC)](https://github.com/psf/black)
[![GitHub Star](https://img.shields.io/github/stars/agentscope-ai/CoPaw?style=flat&logo=github&color=yellow&label=Star)](https://github.com/agentscope-ai/CoPaw/stargazers)
[![GitHub Fork](https://img.shields.io/github/forks/agentscope-ai/CoPaw?style=flat&logo=github&color=purple&label=Fork)](https://github.com/agentscope-ai/CoPaw/network)
[![DeepWiki](https://img.shields.io/badge/DeepWiki-Ask_Devin-navy.svg?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAyCAYAAAAnWDnqAAAAAXNSR0IArs4c6QAAA05JREFUaEPtmUtyEzEQhtWTQyQLHNak2AB7ZnyXZMEjXMGeK/AIi+QuHrMnbChYY7MIh8g01fJoopFb0uhhEqqcbWTp06/uv1saEDv4O3n3dV60RfP947Mm9/SQc0ICFQgzfc4CYZoTPAswgSJCCUJUnAAoRHOAUOcATwbmVLWdGoH//PB8mnKqScAhsD0kYP3j/Yt5LPQe2KvcXmGvRHcDnpxfL2zOYJ1mFwrryWTz0advv1Ut4CJgf5uhDuDj5eUcAUoahrdY/56ebRWeraTjMt/00Sh3UDtjgHtQNHwcRGOC98BJEAEymycmYcWwOprTgcB6VZ5JK5TAJ+fXGLBm3FDAmn6oPPjR4rKCAoJCal2eAiQp2x0vxTPB3ALO2CRkwmDy5WohzBDwSEFKRwPbknEggCPB/imwrycgxX2NzoMCHhPkDwqYMr9tRcP5qNrMZHkVnOjRMWwLCcr8ohBVb1OMjxLwGCvjTikrsBOiA6fNyCrm8V1rP93iVPpwaE+gO0SsWmPiXB+jikdf6SizrT5qKasx5j8ABbHpFTx+vFXp9EnYQmLx02h1QTTrl6eDqxLnGjporxl3NL3agEvXdT0WmEost648sQOYAeJS9Q7bfUVoMGnjo4AZdUMQku50McDcMWcBPvr0SzbTAFDfvJqwLzgxwATnCgnp4wDl6Aa+Ax283gghmj+vj7feE2KBBRMW3FzOpLOADl0Isb5587h/U4gGvkt5v60Z1VLG8BhYjbzRwyQZemwAd6cCR5/XFWLYZRIMpX39AR0tjaGGiGzLVyhse5C9RKC6ai42ppWPKiBagOvaYk8lO7DajerabOZP46Lby5wKjw1HCRx7p9sVMOWGzb/vA1hwiWc6jm3MvQDTogQkiqIhJV0nBQBTU+3okKCFDy9WwferkHjtxib7t3xIUQtHxnIwtx4mpg26/HfwVNVDb4oI9RHmx5WGelRVlrtiw43zboCLaxv46AZeB3IlTkwouebTr1y2NjSpHz68WNFjHvupy3q8TFn3Hos2IAk4Ju5dCo8B3wP7VPr/FGaKiG+T+v+TQqIrOqMTL1VdWV1DdmcbO8KXBz6esmYWYKPwDL5b5FA1a0hwapHiom0r/cKaoqr+27/XcrS5UwSMbQAAAABJRU5ErkJggg==)](https://deepwiki.com/agentscope-ai/CoPaw)
[![Discord](https://img.shields.io/badge/Discord-Join_Us-blueviolet.svg?logo=discord)](https://discord.gg/eYMpfnkG8h)
[![X](https://img.shields.io/badge/X-Follow_Us-black.svg?logo=x)](https://x.com/agentscope_ai)
[![钉钉群](https://img.shields.io/badge/DingTalk-Join_Us-orange.svg)](https://qr.dingtalk.com/action/joingroup?code=v1,k1,OmDlBXpjW+I2vWjKDsjvI9dhcXjGZi3bQiojOq3dlDw=&_dt_no_comment=1&origin=11)

[[文档](https://copaw.agentscope.io/)] [[English](README.md)] [[日本語](README_ja.md)]

<p align="center">
  <img src="https://img.alicdn.com/imgextra/i2/O1CN014TIqyO1U5wDiSbFfA_!!6000000002467-2-tps-816-192.png" alt="CoPaw Logo" width="120">
</p>

<p align="center"><b>懂你所需,伴你左右。</b></p>

</div>

你的AI个人助理;安装极简、本地与云上均可部署;支持多端接入、能力轻松扩展。

> **核心能力:**
>
> **全域触达** — 钉钉、飞书、QQ、Discord、iMessage 等频道,一个 CoPaw 按需连接。
>
> **由你掌控** — 记忆与个性化由你掌控,本地或云端均可;定时与协作发往指定频道。
>
> **Skills 扩展** — 内置定时任务,自定义技能目录,CoPaw 自动加载,无绑定。
>
> <details>
> <summary><b>你可以用 CoPaw 做什么</b></summary>
>
> <br>
>
> - **社交媒体**:每日热帖摘要(小红书、知乎、Reddit),B 站/YouTube 新视频摘要。
> - **生产力**:邮件与 Newsletter 精华推送到钉钉/飞书/QQ,邮件与日历整理联系人。
> - **创意与构建**:睡前说明目标、自动执行,次日获得雏形;从选题到成片全流程。
> - **研究与学习**:追踪科技与 AI 资讯,个人知识库检索复用。
> - **桌面与文件**:整理与搜索本地文件、阅读与摘要文档,在会话中索要文件。
> - **探索更多**:用 Skills 与定时任务组合成你自己的 agentic app。
>
> </details>

---

## 新闻

[2026-03-18] 我们发布了 v0.1.0!完整更新说明见 [v0.1.0 发布说明](https://agentscope-ai.github.io/CoPaw/release-notes)。

- **[v0.1.0] 新增:** 多工作区架构,支持 Agent 切换器;技能安全扫描和危险 Shell 命令检测;可选 Web 认证;企业微信和小艺通道;钉钉 AI 卡片回复;Gemini、DeepSeek、MiniMax、Kimi 模型供应商;控制台暗色模式和多模态聊天;基于 SSE 的流式对话(支持重连);Whisper 语音转文字;`view_image` 多模态对话工具;LobeHub、魔搭和 Zip 压缩包技能导入;`glob_search` 和 `grep_search` 内置搜索工具;时区选择器;`copaw update` 自升级命令。
- **[v0.1.0] 优化:** 优雅的生命周期管理(Agent 零停机重载);动态 Agent 级 Token 计数;配置加载保护;控制台多语言改进(含聊天提示词本地化);Windows 桌面端字节码预编译加速启动;QQ 频道回复逻辑优化和私聊支持。
- **[v0.1.0] 修复:** Telegram 消息线程、媒体处理和自动重连;Discord 跨频道消息合并和防抖泛化;飞书通道重载;Ollama/LM Studio 上下文长度和错误提示;定时任务工作区修复;聊天会话导航持久化;Windows 跨磁盘文件移动、AutoRun stderr 和 GBK 编码。
- **[v0.1.0] 贡献者:** 感谢新贡献者:[@dipeshbabu](https://github.com/dipeshbabu)、[@sljeff](https://github.com/sljeff)、[@octo-patch](https://github.com/octo-patch)、[@Alexxigang](https://github.com/Alexxigang)、[@howyoungchen](https://github.com/howyoungchen)、[@nphenix](https://github.com/nphenix)、[@skyfaker](https://github.com/skyfaker)、[@hh0592821](https://github.com/hh0592821)、[@futuremeng](https://github.com/futuremeng)、[@toby1123yjh](https://github.com/toby1123yjh)、[@hiyuchang](https://github.com/hiyuchang)、[@hanson-hex](https://github.com/hanson-hex)、[@JackyMao1999](https://github.com/JackyMao1999)、[@mvanhorn](https://github.com/mvanhorn)、[@yuanxs21](https://github.com/yuanxs21)、[@aissac](https://github.com/aissac)、[@lcq225](https://github.com/lcq225)、[@Justin-lu](https://github.com/Justin-lu)、[@rowanchen-com](https://github.com/rowanchen-com)、[@pzlav](https://github.com/pzlav)、[@mautops](https://github.com/mautops)、[@hikariming](https://github.com/hikariming)、[@Vanlee0129](https://github.com/Vanlee0129)、[@JiwaniZakir](https://github.com/JiwaniZakir)、[@EuanTop](https://github.com/EuanTop)。

[2026-03-12] 我们发布了 v0.0.7!完整更新说明见 [v0.0.7 发布说明](https://agentscope-ai.github.io/CoPaw/release-notes)。

[2026-03-09] 我们发布了 v0.0.6!完整更新说明见 [v0.0.6 发布说明](https://agentscope-ai.github.io/CoPaw/release-notes)。

[2026-03-06] 我们发布了 v0.0.5!完整更新说明见 [v0.0.5 发布说明](https://agentscope-ai.github.io/CoPaw/release-notes)。

[2026-03-02] 我们发布了 v0.0.4!完整更新说明见 [v0.0.4 发布说明](https://agentscope-ai.github.io/CoPaw/release-notes)。

---

## 目录

> **推荐阅读:**
>
> - **我想三条命令跑起来**: [快速开始](#快速开始) → 浏览器打开控制台。
> - **我想在钉钉 / 飞书 / QQ 里聊**:在控制台中进行 [频道配置](https://copaw.agentscope.io/docs/channels)。
> - **我不想装 Python**:[脚本安装](#脚本安装) 自动管理 Python,或使用 [魔搭一键配置](https://modelscope.cn/studios/fork?target=AgentScope/CoPaw) 云端部署。

- [新闻](#新闻)
- [快速开始](#快速开始)
- [API Key](#api-key)
- [本地模型](#本地模型)
- [文档](#文档)
- [常见问题](#常见问题)
- [路线图](#路线图)
- [参与贡献](#参与贡献)
- [从源码安装](#从源码安装)
- [为什么叫 CoPaw?](#为什么叫-copaw)
- [由谁构建](#由谁构建)
- [许可证](#许可证)

---

## 快速开始

### pip 安装

如果你习惯自行管理 Python 环境:

```bash
pip install copaw
copaw init --defaults
copaw app
```

在浏览器打开 **http://127.0.0.1:8088/** 即可使用控制台(与 CoPaw 对话、配置 Agent)。若要在钉钉、飞书、QQ 等 app 内对话,请参考 [文档](https://copaw.agentscope.io/docs/channels) 接入频道。

![Console](https://img.alicdn.com/imgextra/i3/O1CN01N6TeJ41Y2y7O4gppz_!!6000000003002-2-tps-3328-1860.png)

### 脚本安装

无需手动配置 Python,一行命令自动完成安装。脚本会自动下载 uv(Python 包管理器)、创建虚拟环境、安装 CoPaw 及其依赖(含 Node.js 和前端资源)。注意:部分网络环境或企业权限管控下可能无法使用。

**macOS / Linux:**

```bash
curl -fsSL https://copaw.agentscope.io/install.sh | bash
```

如需安装 Ollama 支持:

```bash
curl -fsSL https://copaw.agentscope.io/install.sh | bash -s -- --extras ollama
```

如需安装多个扩展(例如 Ollama + llama.cpp):

```bash
curl -fsSL https://copaw.agentscope.io/install.sh | bash -s -- --extras ollama,llamacpp
```

**Windows (CMD):**

```CMD
curl -fsSL https://copaw.agentscope.io/install.bat -o install.bat && install.bat
```

**Windows(PowerShell):**

```powershell
irm https://copaw.agentscope.io/install.ps1 | iex
```

> **注意**:安装程序将自动检查 uv 状态,若未安装则尝试自动下载配置。如遇自动安装失败,请遵循屏幕提示操作,或执行 `python -m pip install -U uv`,然后重新运行安装程序。

> **⚠️ Windows 企业版 LTSC 用户特别提示**
>
> 如果您使用的是 Windows LTSC 或受严格安全策略管控的企业环境,PowerShell 可能运行在 **受限语言模式** 下,可能会遇到以下问题:
> 1. **如果你使用的是 CMD(.bat):脚本执行成功但无法写入`Path`**
>
>    脚本已完成文件安装,由于 **受限语言模式** ,脚本无法自动写入环境变量,此时只需手动配置:
>    - **找到安装目录**:
>      - 检查 `uv` 是否可用:在 CMD 中输入 `uv --version` ,如果显示版本号,则**只需配置 CoPaw 路径**;如果提示 `'uv' 不是内部或外部命令,也不是可运行的程序或批处理文件。`,则需同时配置两者。
>      - uv路径(任选其一,取决于安装位置,若`uv`不可用则填):通常在`%USERPROFILE%\.local\bin`、`%USERPROFILE%\AppData\Local\uv`或 Python 安装目录下的 `Scripts` 文件夹
>      - CoPaw路径:通常在 `%USERPROFILE%\.copaw\bin` 。
>    - **手动添加到系统的 Path 环境变量**:
>      - 按 `Win + R`,输入 `sysdm.cpl` 并回车,打开“系统属性”。
>      - 点击 “高级” -> “环境变量”。
>      - 在 “系统变量” 中找到并选中 `Path`,点击 “编辑”。
>      - 点击 “新建”,依次填入上述两个目录路径,点击确定保存。
> 2. **如果你使用的是 PowerShell(.ps1):脚本运行中断**
>
>   由于 **受限语言模式** ,脚本可能无法自动下载`uv`。
>   - **手动安装uv**:参考 [GitHub Release](https://github.com/astral-sh/uv/releases)下载并将`uv.exe`放至`%USERPROFILE%\.local\bin`或`%USERPROFILE%\AppData\Local\uv`;或者确保已安装 Python ,然后运行`python -m pip install -U uv`
>   - **配置`uv`环境变量**:将`uv`所在目录和 `%USERPROFILE%\.copaw\bin` 添加到系统的 `Path` 变量中。
>   - **重新运行**:打开新终端,再次执行安装脚本以完成 `CoPaw` 安装。
>   - **配置`CoPaw`环境变量**:将 `%USERPROFILE%\.copaw\bin` 添加到系统的 `Path` 变量中。

安装完成后,请打开新终端并运行:

```bash
copaw init --defaults   # 或:copaw init(交互式)
copaw app
```

<details>
<summary><b>安装选项</b></summary>

**macOS / Linux:**

```bash
# 安装指定版本
curl -fsSL ... | bash -s -- --version 0.0.2

# 从源码安装(开发/测试用)
curl -fsSL ... | bash -s -- --from-source

# 安装本地模型支持
bash install.sh --extras llamacpp    # llama.cpp(跨平台)
bash install.sh --extras mlx         # MLX(Apple Silicon)
bash install.sh --extras llamacpp,mlx

# 升级 — 重新运行安装命令即可
curl -fsSL ... | bash

# 卸载
copaw uninstall          # 保留配置和数据
copaw uninstall --purge  # 删除所有内容
```

**Windows(PowerShell):**

```powershell
# 安装指定版本
irm ... | iex; .\install.ps1 -Version 0.0.2

# 从源码安装(开发/测试用)
.\install.ps1 -FromSource

# 安装本地模型支持
.\install.ps1 -Extras llamacpp      # llama.cpp(跨平台)
.\install.ps1 -Extras mlx           # MLX
.\install.ps1 -Extras llamacpp,mlx

# 升级 — 重新运行安装命令即可
irm ... | iex

# 卸载
copaw uninstall          # 保留配置和数据
copaw uninstall --purge  # 删除所有内容
```

</details>

### 桌面应用(Beta)

> **Beta 版本说明**:桌面应用目前处于 Beta 测试阶段,存在以下已知限制:
> - **兼容性测试不完整**:未在所有系统版本和硬件配置上进行充分测试
> - **性能可能存在缺陷**:启动速度、内存占用等方面可能需要进一步优化
> - **功能持续完善中**:部分功能可能不稳定或缺失

如果你不习惯使用命令行,可以下载并使用 CoPaw 的桌面应用版本,无需手动配置 Python 环境或执行命令。

#### 下载

从 [GitHub Releases](https://github.com/agentscope-ai/CoPaw/releases) 下载桌面应用:
- **Windows**: `CoPaw-Setup-<version>.exe`
- **macOS**: `CoPaw-<version>-macOS.zip` (推荐 Apple Silicon)

#### 特点

- ✅ **零配置**:下载后双击即可运行,无需安装 Python 或配置环境变量
- ✅ **跨平台**:支持 Windows 10+ 和 macOS 14+
- ✅ **可视化**:自动打开浏览器界面,无需手动输入地址
- ⚠️ **Beta 阶段**:功能持续完善中,欢迎反馈问题

#### 首次启动

**重要提示**:首次启动可能需要 10-60 秒(取决于您的系统配置)。应用需要初始化 Python 环境和加载依赖,请耐心等待浏览器窗口自动打开。

#### macOS:绕过系统安全限制

当你从 Releases 下载 CoPaw macOS 应用时,macOS 可能显示:*"Apple 无法验证 'CoPaw' 不包含恶意软件"*。这是因为应用未经过公证。你仍然可以通过以下方式打开:

- **右键打开(推荐)**
  右键点击(或 Control + 点击)CoPaw 应用 → **"打开"** → 在对话框中再次点击 **"打开"**。这会告诉 Gatekeeper 你信任该应用;之后可以像往常一样双击启动。

- **在系统设置中允许**
  如果仍被阻止,进入 **系统设置 → 隐私与安全性**,向下滚动找到类似 *"已阻止 'CoPaw',因为无法验证开发者"* 的提示,点击 **"仍要打开"** 或 **"允许"**。

- **移除隔离属性(不推荐大多数用户)**
  在终端运行:
  `xattr -cr /Applications/CoPaw.app`
  (或使用解压后的 `.app` 路径)。这会清除"从互联网下载"的隔离标志,使警告通常不会出现,但不如使用 **右键 → 打开** 安全和可控。

详细使用说明、故障排除和常见问题,请参见 [桌面应用指南](https://copaw.agentscope.io/docs/desktop)。

---

### 使用 Docker

镜像在 **Docker Hub**(`agentscope/copaw`)。镜像 tag:`latest`(稳定版);`pre`(PyPI 预发布版)。

```bash
docker pull agentscope/copaw:latest
docker run -p 127.0.0.1:8088:8088 \
  -v copaw-data:/app/working \
  -v copaw-secrets:/app/working.secret \
  agentscope/copaw:latest
```

国内用户也可选用阿里云容器镜像服务 (ACR):`agentscope-registry.ap-southeast-1.cr.aliyuncs.com/agentscope/copaw`(tag 相同)。

然后在浏览器打开 **http://127.0.0.1:8088/** 进入控制台。配置、记忆与 Skills 保存在 `copaw-data` 卷中;模型配置与 API Key 保存在 `copaw-secrets` 卷中。如需传入 API Key(如 `DASHSCOPE_API_KEY`),在 `docker run` 时添加 `-e VAR=value` 或 `--env-file .env`。

> **从容器内连接宿主机上的 Ollama 或其他模型服务**
>
> Docker 容器内的 `localhost` 指向容器自身,而非宿主机。如果 Ollama(或其他模型服务)运行在宿主机上,可通过以下方式让容器内的 CoPaw 访问:
>
> **方式 A** — 显式绑定宿主机地址(全平台通用):
> ```bash
> docker run -p 127.0.0.1:8088:8088 \
>   --add-host=host.docker.internal:host-gateway \
>   -v copaw-data:/app/working \
>   -v copaw-secrets:/app/working.secret \
>   agentscope/copaw:latest
> ```
> 然后在 CoPaw **设置 → 模型** 中,将 Base URL 改为 `http://host.docker.internal:<端口>` — 例如 Ollama 填 `http://host.docker.internal:11434`,LM Studio 填 `http://host.docker.internal:1234/v1`。
>
> **方式 B** — 使用宿主机网络(仅限 Linux):
> ```bash
> docker run --network=host \
>   -v copaw-data:/app/working \
>   -v copaw-secrets:/app/working.secret \
>   agentscope/copaw:latest
> ```
> 无需端口映射(`-p`),容器直接共享宿主机网络。注意这会将容器的所有端口暴露在宿主机上,可能与已占用的端口产生冲突。
>
> **提示:** 如果你只挂载了 `/app/working` 而没有单独挂载 `/app/working.secret`,入口脚本会自动将 secrets 重定向到 `/app/working/.secret`,使其也保存在同一个 volume 中。

镜像从零构建。若需自行构建镜像,请参阅 [scripts/README.md](scripts/README.md#build-docker-image) 中的「Build Docker image」小节,构建后推送到你的镜像仓库。

### 使用魔搭创空间

**不想本地安装?** 使用 [魔搭创空间](https://modelscope.cn/studios/fork?target=AgentScope/CoPaw) 一键云端配置。请将创空间设为 **非公开**,否则他人可能操纵你的 CoPaw。

### 部署到阿里云 ECS

若希望将 CoPaw 部署在阿里云上,可使用阿里云 ECS 一键部署:打开 [CoPaw 阿里云 ECS 部署链接](https://computenest.console.aliyun.com/service/instance/create/cn-hangzhou?type=user&ServiceId=service-1ed84201799f40879884) 按页面提示操作即可。详细步骤见 [阿里云开发者社区:CoPaw 3 分钟部署你的 AI 助理](https://developer.aliyun.com/article/1713682)。

---

## API Key

若使用**云端大模型**(如 DashScope、ModelScope),在开始对话前必须配置 API Key。未配置有效 Key 前,CoPaw 无法正常工作。详情请参考[官方文档](https://copaw.agentscope.io/docs/models#%E9%85%8D%E7%BD%AE%E4%BA%91%E6%8F%90%E4%BE%9B%E5%95%86)。

**配置方式:**

1. **控制台(推荐)** — 运行 `copaw app` 后,打开 **http://127.0.0.1:8088/** → **设置** → **模型**。选择提供商、填写 **API Key**,并启用该提供商与模型。
2. **`copaw init`** — 运行 `copaw init` 时,会引导你配置 LLM 提供商与 API Key。按提示选择提供商并填写 Key 即可。
3. **环境变量** — 使用 DashScope 时,可在终端或工作目录下的 `.env` 文件中设置 `DASHSCOPE_API_KEY`。

其他工具所需密钥(如网页搜索的 `TAVILY_API_KEY`)可在控制台 **设置 → 环境变量** 中配置,详见 [配置](https://copaw.agentscope.io/docs/config)。

> **仅用本地模型?** 若使用 [本地模型](#本地模型)(llama.cpp 或 MLX),则**无需**任何 API Key。

---

## 本地模型

CoPaw 可在本机完全本地运行大模型,无需 API Key 或云端服务。详情请见[官方文档](https://copaw.agentscope.io/docs/models#%E6%9C%AC%E5%9C%B0%E6%8F%90%E4%BE%9B%E5%95%86llamacpp--MLX)

| 后端          | 适用场景                          | 安装                                                                 |
| ------------- | --------------------------------- | -------------------------------------------------------------------- |
| **llama.cpp** | 跨平台(macOS / Linux / Windows) | `pip install 'copaw[llamacpp]'` 或 `bash install.sh --extras llamacpp` |
| **MLX**       | Apple Silicon(M1/M2/M3/M4)      | `pip install 'copaw[mlx]'` 或 `bash install.sh --extras mlx`         |
| **Ollama**    | 跨平台(需要 Ollama 服务运行)    | `pip install 'copaw[ollama]'` 或 `bash install.sh --extras ollama`   |

安装后可以在 **控制台** 界面中下载与管理本地模型。

也可以用命令行管理模型:

```bash
copaw models download Qwen/Qwen3-4B-GGUF
copaw models # 选择已下载的模型
copaw app # 启动服务
```

---

## 文档

| 主题                                                      | 说明                                 |
| --------------------------------------------------------- | ------------------------------------ |
| [项目介绍](https://copaw.agentscope.io/docs/intro)        | CoPaw 是什么、怎么用                 |
| [快速开始](https://copaw.agentscope.io/docs/quickstart)   | 安装与运行(本地或魔搭创空间)       |
| [控制台](https://copaw.agentscope.io/docs/console)        | Web 界面:对话与 Agent 配置          |
| [模型](https://copaw.agentscope.io/docs/models)        | 配置云/本地/自定义提供商          |
| [频道配置](https://copaw.agentscope.io/docs/channels)     | 钉钉、飞书、QQ、Discord、iMessage 等 |
| [Skills](https://copaw.agentscope.io/docs/skills)         | 扩展与自定义能力                     |
| [MCP](https://copaw.agentscope.io/docs/mcp)               | 管理 MCP 客户端                     |
| [记忆](https://copaw.agentscope.io/docs/memory)           | 长期记忆                 |
| [上下文](https://copaw.agentscope.io/docs/context)       | 上下文管理机制                       |
| [魔法命令](https://copaw.agentscope.io/docs/commands)           | 控制对话状态,无需等待AI理解        |
| [心跳](https://copaw.agentscope.io/docs/heartbeat)        | 定时自检与摘要                       |
| [配置与工作目录](https://copaw.agentscope.io/docs/config) | 工作目录与配置文件                   |
| [CLI](https://copaw.agentscope.io/docs/cli)               | 初始化、定时任务、Skills、清理       |
| [FAQ 常见问题](https://copaw.agentscope.io/docs/faq)               | 常见问题与报错排查                   |

完整文档见本仓库 [website/public/docs/](website/public/docs/)。

---

## 常见问题

常见问题、排错指南与已知问题,请访问 **[FAQ 页面](https://copaw.agentscope.io/docs/faq)**。

---

## 路线图

| 方向                   | 事项                                                                                                        | 状态     |
| ---------------------- | ----------------------------------------------------------------------------------------------------------- | -------- |
| **横向拓展**           | 更多频道、模型、技能、MCP 等 — **欢迎社区贡献**                                                             | 征集中   |
| **已有功能扩展与完善** | 展示优化、下载提示、Windows 路径兼容等 — **欢迎社区贡献**                                                   | 征集中   |
| **控制台 Web UI**      | 在控制台中透出更多信息与配置                                                                                | 进行中   |
| **自愈**               | 魔法命令与 Daemon 能力(CLI、status、restart、logs)                                                        | 进行中   |
|                        | DaemonAgent:自诊断、自愈与恢复                                                                             | 计划中   |
| **多智能体**           | 后台任务支持                                                                                                | 进行中   |
|                        | 多智能体隔离                                                                                                | 计划中   |
|                        | 智能体间竞争与冲突的解决                                                                                    | 计划中   |
|                        | 多智能体通信                                                                                                | 计划中   |
| **多模态**             | 语音/视频通话与实时交互                                                                                     | 进行中   |
| **大小模型协同**       | 针对 CoPaw 工作流与敏感数据场景的本地小模型训练与微调                                                       | 进行中   |
|                        | 多模型路由。本地模型处理敏感数据,云端模型负责规划与编码;兼顾隐私、性能与能力                              | 计划中   |
| **记忆系统**           | 经验沉淀技能提炼                                                                                            | 进行中   |
|                        | 多模态记忆融合增强                                                                                          | 计划中   |
|                        | 场景感知主动推送                                                                                            | 计划中   |
| **安全**               | Shell 执行确认                                                                                              | 计划中   |
|                        | 工具/技能安全性                                                                                             | 计划中   |
|                        | 可配置安全等级                                                                                              | 计划中   |
| **版本发布与贡献规范** | Vibe Coding 等 Agent 的贡献引导                                                                             | 计划中   |
| **沙箱**               | 与 AgentScope Runtime 沙箱深度集成                                                                          | 长期规划 |
| **云原生**             | 与 AgentScope Runtime 深度集成,充分利用云端算力、存储与工具生态                                            | 长期规划 |
| **技能生态**           | 丰富 [AgentScope Skills](https://github.com/agentscope-ai/agentscope-skills) 仓库,提升优质技能的发现与使用 | 长期规划 |

*状态说明:进行中 — 正在推进;计划中 — 已排期或设计中,也**欢迎贡献**;**征集中** — 我们**非常欢迎**社区参与;长期规划 — 中长期路线。*

### 参与贡献

CoPaw 在开放协作中持续演进,欢迎各种形式的参与!请参考上方 [路线图](#路线图)(尤其是标记为 **征集中** 的项)选择你感兴趣的方向,并阅读 [CONTRIBUTING](https://github.com/agentscope-ai/CoPaw/blob/main/CONTRIBUTING_zh.md) 了解如何开始。我们特别欢迎:

- **横向拓展** — 新频道、模型提供商、Skills、MCP。
- **已有功能扩展与完善** — 展示与交互优化、下载提示、Windows 路径兼容等。

欢迎在 [GitHub Discussions](https://github.com/agentscope-ai/CoPaw/discussions) 参与讨论、提出想法或认领任务。

---

## 从源码安装

```bash
git clone https://github.com/agentscope-ai/CoPaw.git
cd CoPaw

# 先构建前端控制台(Web 界面必需)
cd console && npm ci && npm run build
cd ..

# 将控制台构建产物复制到包目录
mkdir -p src/copaw/console
cp -R console/dist/. src/copaw/console/

# 安装 Python 包
pip install -e .
```

- **开发**(测试、格式化):`pip install -e ".[dev,full]"`
- **然后**:运行 `copaw init --defaults`,再运行 `copaw app`。

---

## 为什么叫 CoPaw?

CoPaw 既是「你的搭档小爪子」(co-paw),也寓意 **Co Personal Agent Workstation**(协同个人智能体工作台)。我们希望它不是冰冷的工具,而是一只随时准备帮忙的温暖「小爪子」,是你数字生活中最默契的伙伴。

---

## 由谁构建

[AgentScope 团队](https://github.com/agentscope-ai) · [AgentScope](https://github.com/agentscope-ai/agentscope) · [AgentScope Runtime](https://github.com/agentscope-ai/agentscope-runtime) · [ReMe](https://github.com/agentscope-ai/ReMe)

---

## 联系我们

| [Discord](https://discord.gg/eYMpfnkG8h)                     | [X (Twitter)](https://x.com/agentscope_ai)                   | [钉钉](https://qr.dingtalk.com/action/joingroup?code=v1,k1,OmDlBXpjW+I2vWjKDsjvI9dhcXjGZi3bQiojOq3dlDw=&_dt_no_comment=1&origin=11) |
| ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |
| [<img src="https://gw.alicdn.com/imgextra/i1/O1CN01hhD1mu1Dd3BWVUvxN_!!6000000000238-2-tps-400-400.png" width="80" height="80" alt="Discord">](https://discord.gg/eYMpfnkG8h) | [<img src="https://img.shields.io/badge/X-black.svg?logo=x&logoColor=white" width="80" height="80" alt="X">](https://x.com/agentscope_ai) | [<img src="https://img.alicdn.com/imgextra/i2/O1CN01vCWI8a1skHtLGXEMQ_!!6000000005804-2-tps-458-460.png" width="80" height="80" alt="钉钉">](https://qr.dingtalk.com/action/joingroup?code=v1,k1,OmDlBXpjW+I2vWjKDsjvI9dhcXjGZi3bQiojOq3dlDw=&_dt_no_comment=1&origin=11) |

---

## 遥测数据

CoPaw 在执行 `copaw init` 时会收集**匿名**使用数据,帮助我们了解用户环境并优化产品。数据**每个版本收集一次** — 当你升级 CoPaw 后,会重新收集以便我们了解版本分布。

**收集的信息:**

- CoPaw 版本(如 0.0.7)
- 安装方式(pip、Docker 或桌面应用)
- 操作系统及版本(如 macOS 14.0、Ubuntu 22.04)
- Python 版本(如 3.13)
- CPU 架构(如 x86_64、arm64)
- GPU 是否可用(是/否)

**不收集:** 不涉及任何个人数据、文件、密钥、IP 地址或可识别信息。

交互式运行 `copaw init` 时,会询问你是否同意。使用 `--defaults` 模式则自动同意。提示每个版本仅出现一次,且不影响 CoPaw 的任何功能。

---

## 许可证

CoPaw 采用 [Apache License 2.0](LICENSE) 开源协议。

---

## 贡献者

感谢所有为 CoPaw 做出贡献的朋友们:

<a href="https://github.com/agentscope-ai/CoPaw/graphs/contributors">
  <img src="https://contrib.rocks/image?repo=agentscope-ai/CoPaw" alt="贡献者" />
</a>


================================================
FILE: SECURITY.md
================================================
# Security Policy

If you believe you've found a security issue in CoPaw, please report it privately.

## Reporting

Report vulnerabilities of the CoPaw repository:

If you discover a security issue in CoPaw, please report it to us through the [Alibaba Security Response Center (ASRC)](https://security.alibaba.com/).

### Required in Reports

1. **Title**
2. **Severity assessment**
3. **Impact**
4. **Affected component** (e.g. channel adapter, skill, config loading)
5. **Technical reproduction steps**
6. **Demonstrated impact** (how it crosses a trust boundary, not just theoretical)
7. **Environment** (Python version, OS, how CoPaw is run)
8. **Remediation advice** (if you have suggestions)

Reports without reproduction steps, demonstrated impact, and remediation advice will be deprioritized. Given the volume of scanner or AI-generated findings, we need vetted reports from researchers who understand the issues.

### Report Acceptance Gate

For fastest triage, include all of the following:

- Exact vulnerable path (file, function, and line range) on a current revision.
- Tested version details (CoPaw version and/or commit SHA).
- Reproducible PoC against latest `main` or latest released version.
- Demonstrated impact tied to CoPaw's documented trust boundaries (see below).
- For exposed-secret reports: proof the credential is CoPaw-owned or grants access to CoPaw-operated infrastructure/services.
- Scope check explaining why the report is **not** covered by the Out of Scope section below.

Reports that miss these requirements may be closed as `invalid` or `no-action`.

### Common False-Positive Patterns

- Prompt-injection-only chains without a boundary bypass (prompt injection is out of scope).
- Operator-intended local features (e.g. skills or commands the operator explicitly enabled) presented as remote injection.
- Authorized user–triggered actions presented as privilege escalation (e.g. an allowed sender triggering a skill that writes to an allowed path). In this trust model, authorized user actions are trusted unless you demonstrate an auth/sandbox/boundary bypass.
- Reports that only show a malicious skill executing privileged actions after a trusted operator installs/enables it.
- Reports that assume per-user multi-tenant authorization on a shared CoPaw instance/config.
- ReDoS/DoS claims that require trusted operator configuration input without a trust-boundary bypass.
- Scanner-only claims against stale or nonexistent paths, or claims without a working repro.

### Duplicate Report Handling

- Search existing advisories and issues before filing.
- Include likely duplicate advisory IDs in your report when applicable.
- Maintainers may close lower-quality or later duplicates in favor of the earliest high-quality canonical report.

## Alignment with `copaw init`

The security model and recommended baseline in this document are **aligned with** what users see and accept during **`copaw init`**. The init flow shows a security notice that covers: single-operator boundary; shared delegated authority when multiple people message the same instance; restricting channels and users (allowlists); using separate config/credentials and OS users or hosts per trust boundary; least privilege and sandboxing; keeping secrets out of the working directory and skill-accessible paths; and reviewing config and skills regularly. When updating either this document or the `SECURITY_WARNING` in `src/copaw/cli/init_cmd.py`, keep the **concepts and recommendations** consistent so operators get the same message in both places.

## Security & Trust

Security handling is owned by the CoPaw maintainers. For sensitive reports, use the private advisory or a private channel as above.

## Bug Bounties

CoPaw is a community open-source project. There is no bug bounty program and no budget for paid reports. Please still disclose responsibly so we can fix issues quickly. The best way to help the project right now is by sending PRs or clear, reproducible reports.

## Operator Trust Model

CoPaw does **not** model one instance as a multi-tenant, adversarial user boundary.

- Authenticated callers to the same CoPaw instance (same config, same channel workspace) are treated as **trusted operators** for that instance.
- Session identifiers and labels are routing/context controls, not per-user authorization boundaries.
- If one operator can see or trigger what another operator can on the same instance, that is expected in this trust model.
- **Recommended mode**: one user per machine/host (or per OS user), one CoPaw config for that user, and one or more agents/skills inside that instance.
- If multiple users need CoPaw, use one host/OS user (or VPS) per user, or strict isolation; sharing one instance by mutually untrusted users is not the recommended default.
- Skills run with the same privileges as the CoPaw process; only install and enable skills you trust.


## Trusted Skills Concept

Skills/extensions are part of CoPaw's **trusted computing base** for an instance.

- Installing or enabling a skill grants it the same trust level as local code running for that instance.
- Skill behavior such as reading env/files or running host commands is expected inside this trust boundary.
- Security reports must show a **boundary bypass** (e.g. unauthenticated skill load, allowlist/policy bypass, or path-safety bypass), not only malicious behavior from a trusted-installed skill.

## Out of Scope

- Public internet exposure of CoPaw when the docs recommend against it.
- Using CoPaw in ways that the docs recommend not to.
- Deployments where mutually untrusted/adversarial operators share one CoPaw instance and config (e.g. reports expecting per-operator isolation for sessions, history, or similar).
- **Prompt-injection-only** attacks (without a policy/auth/sandbox boundary bypass).
- Reports that require write access to trusted local state (working directory, config, memory files) to achieve impact.
- Reports where the only demonstrated impact is an already-authorized user intentionally invoking a skill or command that writes to an allowed path, without bypassing auth, sandbox, or another documented boundary.
- Reports where the only claim is that a trusted-installed/enabled skill can execute with process/host privileges (documented trust model behavior).
- Any report whose only claim is that an operator-enabled “dangerous” or break-glass option weakens defaults (these are explicit tradeoffs by design).
- Reports that depend on trusted operator–supplied configuration to trigger availability impact (e.g. custom regex or patterns). These may still be fixed as defense-in-depth but are not security-boundary bypasses.
- Exposed secrets that are third-party or user-controlled credentials (not CoPaw-owned and not granting access to CoPaw-operated infrastructure) without demonstrated CoPaw impact.
- Scanner-only claims against stale or nonexistent paths, or without a working repro.

## Deployment Assumptions

CoPaw security guidance assumes:

- The host where CoPaw runs is within a trusted OS/admin boundary.
- Anyone who can modify the CoPaw working directory and config (including `config.json`) is effectively a trusted operator.
- A single instance shared by mutually untrusted people is **not** a recommended setup. Use separate configs and, at minimum, separate OS users or hosts per trust boundary.
- Authenticated callers to the same instance are treated as trusted operators; session or context identifiers are routing controls, not per-user authorization boundaries.
- Multiple instances can run on one machine, but the recommended model is clean per-user isolation (prefer one host/OS user per user).

## One-User Trust Model

CoPaw's security model is **“personal assistant”** (one trusted operator, potentially many agents/skills), not “shared multi-tenant bus.”

- If multiple people can message the same tool-enabled CoPaw instance (e.g. a shared channel workspace), they can all steer that agent within its granted permissions.
- Session or memory scoping reduces context bleed but does **not** create per-user host authorization boundaries.
- For mixed-trust or adversarial users, isolate by OS user/host and use separate config and credentials per boundary.
- A company-shared agent can be valid when users are in the same trust boundary and the agent is strictly business-only.
- For company-shared setups, use a dedicated machine/VM/container and dedicated accounts; avoid mixing personal data on that runtime.
- If that host or environment is logged into personal accounts (e.g. personal password manager, personal cloud), you have collapsed the boundary and increased personal-data exposure risk.

## Agent and Model Assumptions

- The model/agent is **not** a trusted principal. Assume prompt/content injection can manipulate behavior.
- Security boundaries come from host/config trust, channel/user allowlists, tool policy, and what skills are enabled.
- Prompt injection by itself is not a vulnerability report unless it crosses one of those boundaries.

## Working Directory and Config Trust Boundary

The CoPaw working directory is treated as **trusted local operator state**.

- If someone can edit working directory files or config, they have already crossed the trusted operator boundary.
- Memory or context over those files is expected behavior, not a separate security boundary.
- Example out-of-scope pattern: “attacker writes malicious content into workspace files, then the agent uses it.” If you need isolation between mutually untrusted users, split by OS user or host and run separate instances.
- Keep secrets out of the working directory and skill-accessible paths; this is part of the baseline recommended in `copaw init`.

## Skills Trust Boundary

Skills are loaded and run **in-process** (or under the same trust boundary) as the CoPaw runtime and are treated as trusted code.

- Skills can execute with the same OS privileges as the CoPaw process.
- Runtime helpers used by skills are convenience APIs, not a sandbox boundary.
- Only install skills you trust, and restrict which skills are enabled in config where possible.

## Operational Guidance

- **Channels and users**: Restrict which channels and users can trigger the agent; use allowlists where possible.
- **Multi-user or shared inbox**: Use separate config/credentials and ideally separate OS users or hosts per trust boundary.
- **Skills**: Run with least privilege; sandbox where you can; limit tool scope to what you need.
- **Secrets**: Keep them out of the agent's working directory and skill-accessible paths.
- **Model**: Use a capable model when the agent has tools or handles untrusted input.
- **Review**: Review your config and skills regularly.

For more operational and hardening guidance, see the [documentation](https://copaw.agentscope.io/) and any security-related docs linked from the repo.

## Runtime Requirements

- **Python**: CoPaw requires a supported Python version (see [README](README.md)). Use a version with current security updates.
- **Docker or restricted environments**: When running in containers, run as a non-root user when possible. Use read-only mounts where feasible and limit capabilities to what is needed.


================================================
FILE: console/eslint.config.js
================================================
import js from "@eslint/js";
import globals from "globals";
import reactHooks from "eslint-plugin-react-hooks";
import reactRefresh from "eslint-plugin-react-refresh";
import tseslint from "typescript-eslint";

export default tseslint.config(
  { ignores: ["dist"] },
  {
    extends: [js.configs.recommended, ...tseslint.configs.recommended],
    files: ["**/*.{ts,tsx}"],
    languageOptions: {
      ecmaVersion: 2020,
      globals: globals.browser,
    },
    plugins: {
      "react-hooks": reactHooks,
      "react-refresh": reactRefresh,
    },
    rules: {
      ...reactHooks.configs.recommended.rules,
      "react-refresh/only-export-components": [
        "warn",
        { allowConstantExport: true },
      ],
    },
  },
);


================================================
FILE: console/index.html
================================================
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/copaw-symbol.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>CoPaw Console</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>


================================================
FILE: console/package.json
================================================
{
  "name": "copaw-console",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite --host",
    "build": "tsc -b && vite build",
    "build:prod": "tsc -b && vite build --mode production",
    "build:test": "tsc -b && vite build --mode test",
    "format": "prettier --write .",
    "format:check": "prettier --check .",
    "lint": "eslint .",
    "preview": "vite preview",
    "preview:prod": "vite preview --mode production",
    "preview:test": "vite preview --mode test"
  },
  "dependencies": {
    "@agentscope-ai/chat": "^1.1.51",
    "@agentscope-ai/design": "^1.0.14",
    "@agentscope-ai/icons": "^1.0.46",
    "@ant-design/x-markdown": "^2.2.2",
    "@dnd-kit/core": "^6.3.1",
    "@dnd-kit/sortable": "^10.0.0",
    "@dnd-kit/utilities": "^3.2.2",
    "ahooks": "^3.9.6",
    "antd": "^5.29.1",
    "antd-style": "^3.7.1",
    "i18next": "^25.8.4",
    "lucide-react": "^0.562.0",
    "react": "^18",
    "react-dom": "^18",
    "react-i18next": "^16.5.4",
    "react-markdown": "^10.1.0",
    "react-router-dom": "^7.13.0",
    "remark-gfm": "^4.0.1"
  },
  "devDependencies": {
    "@eslint/js": "^9.25.0",
    "@types/i18next": "^12.1.0",
    "@types/node": "^25.0.3",
    "@types/react": "^18",
    "@types/react-dom": "^18",
    "@vitejs/plugin-react": "^4.4.1",
    "eslint": "^9.25.0",
    "eslint-plugin-react-hooks": "^5.2.0",
    "eslint-plugin-react-refresh": "^0.4.19",
    "globals": "^16.0.0",
    "less": "^4.5.1",
    "prettier": "3.0.0",
    "typescript": "~5.8.3",
    "typescript-eslint": "^8.30.1",
    "vite": "^6.3.5"
  },
  "repository": "https://github.com/agentscope-ai/agentscope-runtime.git"
}


================================================
FILE: console/src/App.tsx
================================================
import { createGlobalStyle } from "antd-style";
import { ConfigProvider, bailianTheme } from "@agentscope-ai/design";
import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import zhCN from "antd/locale/zh_CN";
import enUS from "antd/locale/en_US";
import jaJP from "antd/locale/ja_JP";
import ruRU from "antd/locale/ru_RU";
import type { Locale } from "antd/es/locale";
import { theme as antdTheme } from "antd";
import dayjs from "dayjs";
import "dayjs/locale/zh-cn";
import "dayjs/locale/ja";
import "dayjs/locale/ru";
import MainLayout from "./layouts/MainLayout";
import { ThemeProvider, useTheme } from "./contexts/ThemeContext";
import LoginPage from "./pages/Login";
import { authApi } from "./api/modules/auth";
import { getApiUrl, getApiToken, clearAuthToken } from "./api/config";
import "./styles/layout.css";
import "./styles/form-override.css";

const antdLocaleMap: Record<string, Locale> = {
  zh: zhCN,
  en: enUS,
  ja: jaJP,
  ru: ruRU,
};

const dayjsLocaleMap: Record<string, string> = {
  zh: "zh-cn",
  en: "en",
  ja: "ja",
  ru: "ru",
};

const GlobalStyle = createGlobalStyle`
* {
  margin: 0;
  box-sizing: border-box;
}
`;

function AuthGuard({ children }: { children: React.ReactNode }) {
  const [status, setStatus] = useState<"loading" | "auth-required" | "ok">(
    "loading",
  );

  useEffect(() => {
    let cancelled = false;
    (async () => {
      try {
        const res = await authApi.getStatus();
        if (cancelled) return;
        if (!res.enabled) {
          setStatus("ok");
          return;
        }
        const token = getApiToken();
        if (!token) {
          setStatus("auth-required");
          return;
        }
        try {
          const r = await fetch(getApiUrl("/auth/verify"), {
            headers: { Authorization: `Bearer ${token}` },
          });
          if (cancelled) return;
          if (r.ok) {
            setStatus("ok");
          } else {
            clearAuthToken();
            setStatus("auth-required");
          }
        } catch {
          if (!cancelled) {
            clearAuthToken();
            setStatus("auth-required");
          }
        }
      } catch {
        if (!cancelled) setStatus("ok");
      }
    })();
    return () => {
      cancelled = true;
    };
  }, []);

  if (status === "loading") return null;
  if (status === "auth-required")
    return (
      <Navigate
        to={`/login?redirect=${encodeURIComponent(window.location.pathname)}`}
        replace
      />
    );
  return <>{children}</>;
}

function getRouterBasename(pathname: string): string | undefined {
  return /^\/console(?:\/|$)/.test(pathname) ? "/console" : undefined;
}

function AppInner() {
  const basename = getRouterBasename(window.location.pathname);
  const { i18n } = useTranslation();
  const { isDark } = useTheme();
  const lang = i18n.resolvedLanguage || i18n.language || "en";
  const [antdLocale, setAntdLocale] = useState<Locale>(
    antdLocaleMap[lang] ?? enUS,
  );

  useEffect(() => {
    const handleLanguageChanged = (lng: string) => {
      const shortLng = lng.split("-")[0];
      setAntdLocale(antdLocaleMap[shortLng] ?? enUS);
      dayjs.locale(dayjsLocaleMap[shortLng] ?? "en");
    };

    // Set initial dayjs locale
    dayjs.locale(dayjsLocaleMap[lang.split("-")[0]] ?? "en");

    i18n.on("languageChanged", handleLanguageChanged);
    return () => {
      i18n.off("languageChanged", handleLanguageChanged);
    };
  }, [i18n]);

  return (
    <BrowserRouter basename={basename}>
      <GlobalStyle />
      <ConfigProvider
        {...bailianTheme}
        prefix="copaw"
        prefixCls="copaw"
        locale={antdLocale}
        theme={{
          ...(bailianTheme as any)?.theme,
          algorithm: isDark
            ? antdTheme.darkAlgorithm
            : antdTheme.defaultAlgorithm,
        }}
      >
        <Routes>
          <Route path="/login" element={<LoginPage />} />
          <Route
            path="/*"
            element={
              <AuthGuard>
                <MainLayout />
              </AuthGuard>
            }
          />
        </Routes>
      </ConfigProvider>
    </BrowserRouter>
  );
}

function App() {
  return (
    <ThemeProvider>
      <AppInner />
    </ThemeProvider>
  );
}

export default App;


================================================
FILE: console/src/api/config.ts
================================================
declare const BASE_URL: string;
declare const TOKEN: string;

const AUTH_TOKEN_KEY = "copaw_auth_token";

/**
 * Get the full API URL with /api prefix
 * @param path - API path (e.g., "/models", "/skills")
 * @returns Full API URL (e.g., "http://localhost:8088/api/models" or "/api/models")
 */
export function getApiUrl(path: string): string {
  const base = BASE_URL || "";
  const apiPrefix = "/api";
  const normalizedPath = path.startsWith("/") ? path : `/${path}`;
  return `${base}${apiPrefix}${normalizedPath}`;
}

/**
 * Get the API token - checks localStorage first (auth login),
 * then falls back to the build-time TOKEN constant.
 * @returns API token string or empty string
 */
export function getApiToken(): string {
  const stored = localStorage.getItem(AUTH_TOKEN_KEY);
  if (stored) return stored;
  return typeof TOKEN !== "undefined" ? TOKEN : "";
}

/**
 * Store the auth token in localStorage after login.
 */
export function setAuthToken(token: string): void {
  localStorage.setItem(AUTH_TOKEN_KEY, token);
}

/**
 * Remove the auth token from localStorage (logout / 401).
 */
export function clearAuthToken(): void {
  localStorage.removeItem(AUTH_TOKEN_KEY);
}


================================================
FILE: console/src/api/index.ts
================================================
export * from "./types";

export { request } from "./request";

export { getApiUrl, getApiToken } from "./config";

import { rootApi } from "./modules/root";
import { channelApi } from "./modules/channel";
import { heartbeatApi } from "./modules/heartbeat";
import { cronJobApi } from "./modules/cronjob";
import { chatApi, sessionApi } from "./modules/chat";
import { envApi } from "./modules/env";
import { providerApi } from "./modules/provider";
import { skillApi } from "./modules/skill";
import { agentApi } from "./modules/agent";
import { agentsApi } from "./modules/agents";
import { workspaceApi } from "./modules/workspace";
import { localModelApi } from "./modules/localModel";
import { ollamaModelApi } from "./modules/ollamaModel";
import { mcpApi } from "./modules/mcp";
import { tokenUsageApi } from "./modules/tokenUsage";
import { toolsApi } from "./modules/tools";
import { securityApi } from "./modules/security";
import { userTimezoneApi } from "./modules/userTimezone";

export const api = {
  // Root
  ...rootApi,

  // Channels
  ...channelApi,

  // Heartbeat
  ...heartbeatApi,

  // Cron Jobs
  ...cronJobApi,

  // Chats
  ...chatApi,

  // Sessions(Legacy aliases)
  ...sessionApi,

  // Environment Variables
  ...envApi,

  // Providers
  ...providerApi,

  // Agent
  ...agentApi,

  // Skills
  ...skillApi,

  // Workspace
  ...workspaceApi,

  // Local Models
  ...localModelApi,

  // Ollama Models
  ...ollamaModelApi,

  // MCP Clients
  ...mcpApi,

  // Token Usage
  ...tokenUsageApi,
  // Tools
  ...toolsApi,

  // Security
  ...securityApi,

  // User Timezone
  ...userTimezoneApi,
};

export default api;

// Export individual APIs for direct access
export { agentsApi };


================================================
FILE: console/src/api/modules/agent.ts
================================================
import { request } from "../request";
import type { AgentRequest, AgentsRunningConfig } from "../types";

// Agent API
export const agentApi = {
  agentRoot: () => request<unknown>("/agent/"),

  healthCheck: () => request<unknown>("/agent/health"),

  agentApi: (body: AgentRequest) =>
    request<unknown>("/agent/process", {
      method: "POST",
      body: JSON.stringify(body),
    }),

  getProcessStatus: () => request<unknown>("/agent/admin/status"),

  shutdownSimple: () =>
    request<void>("/agent/shutdown", {
      method: "POST",
    }),

  shutdown: () =>
    request<void>("/agent/admin/shutdown", {
      method: "POST",
    }),

  getAgentRunningConfig: () =>
    request<AgentsRunningConfig>("/agent/running-config"),

  updateAgentRunningConfig: (config: AgentsRunningConfig) =>
    request<AgentsRunningConfig>("/agent/running-config", {
      method: "PUT",
      body: JSON.stringify(config),
    }),

  getAgentLanguage: () => request<{ language: string }>("/agent/language"),

  updateAgentLanguage: (language: string) =>
    request<{ language: string; copied_files: string[] }>("/agent/language", {
      method: "PUT",
      body: JSON.stringify({ language }),
    }),

  getAudioMode: () => request<{ audio_mode: string }>("/agent/audio-mode"),

  updateAudioMode: (audio_mode: string) =>
    request<{ audio_mode: string }>("/agent/audio-mode", {
      method: "PUT",
      body: JSON.stringify({ audio_mode }),
    }),

  getTranscriptionProviders: () =>
    request<{
      providers: { id: string; name: string; available: boolean }[];
      configured_provider_id: string;
    }>("/agent/transcription-providers"),

  updateTranscriptionProvider: (provider_id: string) =>
    request<{ provider_id: string }>("/agent/transcription-provider", {
      method: "PUT",
      body: JSON.stringify({ provider_id }),
    }),

  getTranscriptionProviderType: () =>
    request<{ transcription_provider_type: string }>(
      "/agent/transcription-provider-type",
    ),

  updateTranscriptionProviderType: (transcription_provider_type: string) =>
    request<{ transcription_provider_type: string }>(
      "/agent/transcription-provider-type",
      {
        method: "PUT",
        body: JSON.stringify({ transcription_provider_type }),
      },
    ),

  getLocalWhisperStatus: () =>
    request<{
      available: boolean;
      ffmpeg_installed: boolean;
      whisper_installed: boolean;
    }>("/agent/local-whisper-status"),
};


================================================
FILE: console/src/api/modules/agents.ts
================================================
import { request } from "../request";
import type {
  AgentListResponse,
  AgentProfileConfig,
  CreateAgentRequest,
  AgentProfileRef,
} from "../types/agents";
import type { MdFileInfo, MdFileContent } from "../types/workspace";

// Multi-agent management API
export const agentsApi = {
  // List all agents
  listAgents: () => request<AgentListResponse>("/agents"),

  // Get agent details
  getAgent: (agentId: string) =>
    request<AgentProfileConfig>(`/agents/${agentId}`),

  // Create new agent
  createAgent: (agent: CreateAgentRequest) =>
    request<AgentProfileRef>("/agents", {
      method: "POST",
      body: JSON.stringify(agent),
    }),

  // Update agent configuration
  updateAgent: (agentId: string, agent: AgentProfileConfig) =>
    request<AgentProfileConfig>(`/agents/${agentId}`, {
      method: "PUT",
      body: JSON.stringify(agent),
    }),

  // Delete agent
  deleteAgent: (agentId: string) =>
    request<{ success: boolean; agent_id: string }>(`/agents/${agentId}`, {
      method: "DELETE",
    }),

  // Agent workspace files
  listAgentFiles: (agentId: string) =>
    request<MdFileInfo[]>(`/agents/${agentId}/files`),

  readAgentFile: (agentId: string, filename: string) =>
    request<MdFileContent>(
      `/agents/${agentId}/files/${encodeURIComponent(filename)}`,
    ),

  writeAgentFile: (agentId: string, filename: string, content: string) =>
    request<{ written: boolean; filename: string }>(
      `/agents/${agentId}/files/${encodeURIComponent(filename)}`,
      {
        method: "PUT",
        body: JSON.stringify({ content }),
      },
    ),

  // Agent memory files
  listAgentMemory: (agentId: string) =>
    request<MdFileInfo[]>(`/agents/${agentId}/memory`),
};


================================================
FILE: console/src/api/modules/auth.ts
================================================
import { getApiUrl } from "../config";

export interface LoginResponse {
  token: string;
  username: string;
  message?: string;
}

export interface AuthStatusResponse {
  enabled: boolean;
  has_users: boolean;
}

export const authApi = {
  login: async (username: string, password: string): Promise<LoginResponse> => {
    const res = await fetch(getApiUrl("/auth/login"), {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ username, password }),
    });
    if (!res.ok) {
      const err = await res.json().catch(() => ({}));
      throw new Error(err.detail || "Login failed");
    }
    return res.json();
  },

  register: async (
    username: string,
    password: string,
  ): Promise<LoginResponse> => {
    const res = await fetch(getApiUrl("/auth/register"), {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ username, password }),
    });
    if (!res.ok) {
      const err = await res.json().catch(() => ({}));
      throw new Error(err.detail || "Registration failed");
    }
    return res.json();
  },

  getStatus: async (): Promise<AuthStatusResponse> => {
    const res = await fetch(getApiUrl("/auth/status"));
    if (!res.ok) throw new Error("Failed to check auth status");
    return res.json();
  },
};


================================================
FILE: console/src/api/modules/channel.ts
================================================
import { request } from "../request";
import type { ChannelConfig, SingleChannelConfig } from "../types";

export const channelApi = {
  listChannelTypes: () => request<string[]>("/config/channels/types"),

  listChannels: () => request<ChannelConfig>("/config/channels"),

  updateChannels: (body: ChannelConfig) =>
    request<ChannelConfig>("/config/channels", {
      method: "PUT",
      body: JSON.stringify(body),
    }),

  getChannelConfig: (channelName: string) =>
    request<SingleChannelConfig>(
      `/config/channels/${encodeURIComponent(channelName)}`,
    ),

  updateChannelConfig: (channelName: string, body: SingleChannelConfig) =>
    request<SingleChannelConfig>(
      `/config/channels/${encodeURIComponent(channelName)}`,
      {
        method: "PUT",
        body: JSON.stringify(body),
      },
    ),
};


================================================
FILE: console/src/api/modules/chat.ts
================================================
import { request } from "../request";
import { getApiUrl } from "../config";
import type {
  ChatSpec,
  ChatHistory,
  ChatDeleteResponse,
  Session,
} from "../types";

/** Response from POST /console/upload. url = filename only; agent_id from header. */
export interface ChatUploadResponse {
  url: string;
  file_name: string;
  stored_name?: string;
}

const CONSOLE_FILES_PREFIX = "/console/files";

function buildChatUploadHeaders(): HeadersInit {
  const headers: Record<string, string> = {};
  const token = localStorage.getItem("copaw_auth_token");
  if (token) headers.Authorization = `Bearer ${token}`;
  try {
    const agentStorage = localStorage.getItem("copaw-agent-storage");
    if (agentStorage) {
      const parsed = JSON.parse(agentStorage);
      const selectedAgent = parsed?.state?.selectedAgent;
      if (selectedAgent) headers["X-Agent-Id"] = selectedAgent;
    }
  } catch {
    // ignore
  }
  return headers;
}

function getSelectedAgentId(): string {
  try {
    const agentStorage = localStorage.getItem("copaw-agent-storage");
    if (agentStorage) {
      const parsed = JSON.parse(agentStorage);
      const id = parsed?.state?.selectedAgent;
      if (id) return id;
    }
  } catch {
    // ignore
  }
  return "";
}

export const chatApi = {
  /** Upload a file for chat attachment. Returns URL path for content. */
  uploadFile: async (file: File): Promise<ChatUploadResponse> => {
    const formData = new FormData();
    formData.append("file", file);
    const response = await fetch(getApiUrl("/console/upload"), {
      method: "POST",
      headers: buildChatUploadHeaders(),
      body: formData,
    });
    if (!response.ok) {
      const text = await response.text().catch(() => "");
      throw new Error(
        `Upload failed: ${response.status} ${response.statusText}${
          text ? ` - ${text}` : ""
        }`,
      );
    }
    return response.json();
  },

  /** Build full API URL for a console file. Backend returns filename only; agent_id from header/context (selectedAgent). */
  fileUrl: (filename: string): string => {
    if (!filename) return "";
    if (filename.startsWith("http://") || filename.startsWith("https://"))
      return filename;
    const agentId = getSelectedAgentId() || "default";
    const path = `${CONSOLE_FILES_PREFIX}/${agentId}/${filename.replace(
      /^\/+/,
      "",
    )}`;
    return getApiUrl(path);
  },
  listChats: (params?: { user_id?: string; channel?: string }) => {
    const searchParams = new URLSearchParams();
    if (params?.user_id) searchParams.append("user_id", params.user_id);
    if (params?.channel) searchParams.append("channel", params.channel);
    const query = searchParams.toString();
    return request<ChatSpec[]>(`/chats${query ? `?${query}` : ""}`);
  },

  createChat: (chat: Partial<ChatSpec>) =>
    request<ChatSpec>("/chats", {
      method: "POST",
      body: JSON.stringify(chat),
    }),

  getChat: (chatId: string) =>
    request<ChatHistory>(`/chats/${encodeURIComponent(chatId)}`),

  updateChat: (chatId: string, chat: Partial<ChatSpec>) =>
    request<ChatSpec>(`/chats/${encodeURIComponent(chatId)}`, {
      method: "PUT",
      body: JSON.stringify(chat),
    }),

  deleteChat: (chatId: string) =>
    request<ChatDeleteResponse>(`/chats/${encodeURIComponent(chatId)}`, {
      method: "DELETE",
    }),

  batchDeleteChats: (chatIds: string[]) =>
    request<{ success: boolean; deleted_count: number }>(
      "/chats/batch-delete",
      {
        method: "POST",
        body: JSON.stringify(chatIds),
      },
    ),

  /** Stop a running console chat (only stop when user clicks stop). chat_id = ChatSpec.id */
  stopConsoleChat: (chatId: string) =>
    request<{ stopped: boolean }>(
      `/console/chat/stop?chat_id=${encodeURIComponent(chatId)}`,
      { method: "POST" },
    ),
};

export const sessionApi = {
  listSessions: (params?: { user_id?: string; channel?: string }) => {
    const searchParams = new URLSearchParams();
    if (params?.user_id) searchParams.append("user_id", params.user_id);
    if (params?.channel) searchParams.append("channel", params.channel);
    const query = searchParams.toString();
    return request<Session[]>(`/chats${query ? `?${query}` : ""}`);
  },

  getSession: (sessionId: string) =>
    request<ChatHistory>(`/chats/${encodeURIComponent(sessionId)}`),

  deleteSession: (sessionId: string) =>
    request<ChatDeleteResponse>(`/chats/${encodeURIComponent(sessionId)}`, {
      method: "DELETE",
    }),

  createSession: (session: Partial<Session>) =>
    request<Session>("/chats", {
      method: "POST",
      body: JSON.stringify(session),
    }),

  updateSession: (sessionId: string, session: Partial<Session>) =>
    request<Session>(`/chats/${encodeURIComponent(sessionId)}`, {
      method: "PUT",
      body: JSON.stringify(session),
    }),

  batchDeleteSessions: (sessionIds: string[]) =>
    request<{ success: boolean; deleted_count: number }>(
      "/chats/batch-delete",
      {
        method: "POST",
        body: JSON.stringify(sessionIds),
      },
    ),
};


================================================
FILE: console/src/api/modules/console.ts
================================================
import { request } from "../request";

export interface PushMessage {
  id: string;
  text: string;
}

export const consoleApi = {
  getPushMessages: () =>
    request<{ messages: PushMessage[] }>("/console/push-messages"),
};


================================================
FILE: console/src/api/modules/cronjob.ts
================================================
import { request } from "../request";
import type {
  CronJobSpecInput,
  CronJobSpecOutput,
  CronJobView,
} from "../types";

export const cronJobApi = {
  listCronJobs: () => request<CronJobSpecOutput[]>("/cron/jobs"),

  createCronJob: (spec: CronJobSpecInput) =>
    request<CronJobSpecOutput>("/cron/jobs", {
      method: "POST",
      body: JSON.stringify(spec),
    }),

  getCronJob: (jobId: string) =>
    request<CronJobView>(`/cron/jobs/${encodeURIComponent(jobId)}`),

  replaceCronJob: (jobId: string, spec: CronJobSpecInput) =>
    request<CronJobSpecOutput>(`/cron/jobs/${encodeURIComponent(jobId)}`, {
      method: "PUT",
      body: JSON.stringify(spec),
    }),

  deleteCronJob: (jobId: string) =>
    request<void>(`/cron/jobs/${encodeURIComponent(jobId)}`, {
      method: "DELETE",
    }),

  pauseCronJob: (jobId: string) =>
    request<void>(`/cron/jobs/${encodeURIComponent(jobId)}/pause`, {
      method: "POST",
    }),

  resumeCronJob: (jobId: string) =>
    request<void>(`/cron/jobs/${encodeURIComponent(jobId)}/resume`, {
      method: "POST",
    }),

  runCronJob: (jobId: string) =>
    request<void>(`/cron/jobs/${encodeURIComponent(jobId)}/run`, {
      method: "POST",
    }),

  triggerCronJob: (jobId: string) =>
    request<void>(`/cron/jobs/${encodeURIComponent(jobId)}/run`, {
      method: "POST",
    }),

  getCronJobState: (jobId: string) =>
    request<unknown>(`/cron/jobs/${encodeURIComponent(jobId)}/state`),
};


================================================
FILE: console/src/api/modules/env.ts
================================================
import { request } from "../request";
import type { EnvVar } from "../types";

export const envApi = {
  listEnvs: () => request<EnvVar[]>("/envs"),

  /** Batch save – full replacement of all env vars. */
  saveEnvs: (envs: Record<string, string>) =>
    request<EnvVar[]>("/envs", {
      method: "PUT",
      body: JSON.stringify(envs),
    }),

  deleteEnv: (key: string) =>
    request<EnvVar[]>(`/envs/${encodeURIComponent(key)}`, {
      method: "DELETE",
    }),
};


================================================
FILE: console/src/api/modules/heartbeat.ts
================================================
import { request } from "../request";
import type { HeartbeatConfig } from "../types/heartbeat";

export const heartbeatApi = {
  getHeartbeatConfig: () => request<HeartbeatConfig>("/config/heartbeat"),

  updateHeartbeatConfig: (body: HeartbeatConfig) =>
    request<HeartbeatConfig>("/config/heartbeat", {
      method: "PUT",
      body: JSON.stringify(body),
    }),
};


================================================
FILE: console/src/api/modules/localModel.ts
================================================
import { request } from "../request";
import type {
  LocalModelResponse,
  DownloadModelRequest,
  DownloadTaskResponse,
} from "../types";

export const localModelApi = {
  listLocalModels: (backend?: string) => {
    const params = backend ? `?backend=${encodeURIComponent(backend)}` : "";
    return request<LocalModelResponse[]>(`/local-models${params}`);
  },

  downloadModel: (body: DownloadModelRequest) =>
    request<DownloadTaskResponse>("/local-models/download", {
      method: "POST",
      body: JSON.stringify(body),
    }),

  getDownloadStatus: (backend?: string) => {
    const params = backend ? `?backend=${encodeURIComponent(backend)}` : "";
    return request<DownloadTaskResponse[]>(
      `/local-models/download-status${params}`,
    );
  },

  cancelDownload: (taskId: string) =>
    request<{ status: string; task_id: string }>(
      `/local-models/cancel-download/${encodeURIComponent(taskId)}`,
      { method: "POST" },
    ),

  deleteLocalModel: (modelId: string) =>
    request<{ status: string; model_id: string }>(
      `/local-models/${encodeURIComponent(modelId)}`,
      { method: "DELETE" },
    ),
};


================================================
FILE: console/src/api/modules/mcp.ts
================================================
import { request } from "../request";
import type {
  MCPClientInfo,
  MCPClientCreateRequest,
  MCPClientUpdateRequest,
} from "../types";

export const mcpApi = {
  /**
   * List all MCP clients
   */
  listMCPClients: () => request<MCPClientInfo[]>("/mcp"),

  /**
   * Get details of a specific MCP client
   */
  getMCPClient: (clientKey: string) =>
    request<MCPClientInfo>(`/mcp/${encodeURIComponent(clientKey)}`),

  /**
   * Create a new MCP client
   */
  createMCPClient: (body: MCPClientCreateRequest) =>
    request<MCPClientInfo>("/mcp", {
      method: "POST",
      body: JSON.stringify(body),
    }),

  /**
   * Update an existing MCP client
   */
  updateMCPClient: (clientKey: string, body: MCPClientUpdateRequest) =>
    request<MCPClientInfo>(`/mcp/${encodeURIComponent(clientKey)}`, {
      method: "PUT",
      body: JSON.stringify(body),
    }),

  /**
   * Toggle MCP client enabled status
   */
  toggleMCPClient: (clientKey: string) =>
    request<MCPClientInfo>(`/mcp/${encodeURIComponent(clientKey)}/toggle`, {
      method: "PATCH",
    }),

  /**
   * Delete an MCP client
   */
  deleteMCPClient: (clientKey: string) =>
    request<{ message: string }>(`/mcp/${encodeURIComponent(clientKey)}`, {
      method: "DELETE",
    }),
};


================================================
FILE: console/src/api/modules/ollamaModel.ts
================================================
import { request } from "../request";
import type {
  OllamaModelResponse,
  OllamaDownloadRequest,
  OllamaDownloadTaskResponse,
} from "../types";

export const ollamaModelApi = {
  listOllamaModels: () => request<OllamaModelResponse[]>("/ollama-models"),

  downloadOllamaModel: (body: OllamaDownloadRequest) =>
    request<OllamaDownloadTaskResponse>("/ollama-models/download", {
      method: "POST",
      body: JSON.stringify(body),
    }),

  getOllamaDownloadStatus: () =>
    request<OllamaDownloadTaskResponse[]>("/ollama-models/download-status"),

  cancelOllamaDownload: (taskId: string) =>
    request<{ status: string; task_id: string }>(
      `/ollama-models/download/${encodeURIComponent(taskId)}`,
      { method: "DELETE" },
    ),

  deleteOllamaModel: (name: string) =>
    request<{ status: string; name: string }>(
      `/ollama-models/${encodeURIComponent(name)}`,
      { method: "DELETE" },
    ),
};


================================================
FILE: console/src/api/modules/provider.ts
================================================
import { request } from "../request";
import type {
  ProviderInfo,
  ProviderConfigRequest,
  ActiveModelsInfo,
  ModelSlotRequest,
  CreateCustomProviderRequest,
  AddModelRequest,
  TestConnectionResponse,
  TestProviderRequest,
  TestModelRequest,
  DiscoverModelsResponse,
} from "../types";

export const providerApi = {
  listProviders: () => request<ProviderInfo[]>("/models"),

  configureProvider: (providerId: string, body: ProviderConfigRequest) =>
    request<ProviderInfo>(`/models/${encodeURIComponent(providerId)}/config`, {
      method: "PUT",
      body: JSON.stringify(body),
    }),

  getActiveModels: () => request<ActiveModelsInfo>("/models/active"),

  setActiveLlm: (body: ModelSlotRequest) =>
    request<ActiveModelsInfo>("/models/active", {
      method: "PUT",
      body: JSON.stringify(body),
    }),

  /* ---- Custom provider CRUD ---- */

  createCustomProvider: (body: CreateCustomProviderRequest) =>
    request<ProviderInfo>("/models/custom-providers", {
      method: "POST",
      body: JSON.stringify(body),
    }),

  deleteCustomProvider: (providerId: string) =>
    request<ProviderInfo[]>(
      `/models/custom-providers/${encodeURIComponent(providerId)}`,
      { method: "DELETE" },
    ),

  /* ---- Model CRUD (works for both built-in and custom providers) ---- */

  addModel: (providerId: string, body: AddModelRequest) =>
    request<ProviderInfo>(`/models/${encodeURIComponent(providerId)}/models`, {
      method: "POST",
      body: JSON.stringify(body),
    }),

  removeModel: (providerId: string, modelId: string) =>
    request<ProviderInfo>(
      `/models/${encodeURIComponent(providerId)}/models/${encodeURIComponent(
        modelId,
      )}`,
      { method: "DELETE" },
    ),

  /* ---- Test Connection ---- */

  testProviderConnection: (providerId: string, body?: TestProviderRequest) =>
    request<TestConnectionResponse>(
      `/models/${encodeURIComponent(providerId)}/test`,
      {
        method: "POST",
        body: body ? JSON.stringify(body) : undefined,
      },
    ),

  testModelConnection: (providerId: string, body: TestModelRequest) =>
    request<TestConnectionResponse>(
      `/models/${encodeURIComponent(providerId)}/models/test`,
      {
        method: "POST",
        body: JSON.stringify(body),
      },
    ),

  discoverModels: (providerId: string, body?: TestProviderRequest) =>
    request<DiscoverModelsResponse>(
      `/models/${encodeURIComponent(providerId)}/discover`,
      {
        method: "POST",
        body: body ? JSON.stringify(body) : undefined,
      },
    ),
};


================================================
FILE: console/src/api/modules/root.ts
================================================
import { request } from "../request";

// Root API
export const rootApi = {
  readRoot: () => request<unknown>("/"),
  getVersion: () => request<{ version: string }>("/version"),
};


================================================
FILE: console/src/api/modules/security.ts
================================================
import { request } from "../request";

export interface ToolGuardRule {
  id: string;
  tools: string[];
  params: string[];
  category: string;
  severity: string;
  patterns: string[];
  exclude_patterns: string[];
  description: string;
  remediation: string;
}

export interface ToolGuardConfig {
  enabled: boolean;
  guarded_tools: string[] | null;
  denied_tools: string[];
  custom_rules: ToolGuardRule[];
  disabled_rules: string[];
}

// ── Skill Scanner types ────────────────────────────────────────────

export interface SkillScannerWhitelistEntry {
  skill_name: string;
  content_hash: string;
  added_at: string;
}

export type SkillScannerMode = "block" | "warn" | "off";

export interface SkillScannerConfig {
  mode: SkillScannerMode;
  timeout: number;
  whitelist: SkillScannerWhitelistEntry[];
}

export interface BlockedSkillFinding {
  severity: string;
  title: string;
  description: string;
  file_path: string;
  line_number: number | null;
  rule_id: string;
}

export interface BlockedSkillRecord {
  skill_name: string;
  blocked_at: string;
  max_severity: string;
  findings: BlockedSkillFinding[];
  content_hash: string;
  action: "blocked" | "warned";
}

export interface SecurityScanErrorResponse {
  type: "security_scan_failed";
  detail: string;
  skill_name: string;
  max_severity: string;
  findings: BlockedSkillFinding[];
}

export const securityApi = {
  // ── Tool Guard ──────────────────────────────────────────────────

  getToolGuard: () => request<ToolGuardConfig>("/config/security/tool-guard"),

  updateToolGuard: (body: ToolGuardConfig) =>
    request<ToolGuardConfig>("/config/security/tool-guard", {
      method: "PUT",
      body: JSON.stringify(body),
    }),

  getBuiltinRules: () =>
    request<ToolGuardRule[]>("/config/security/tool-guard/builtin-rules"),

  // ── Skill Scanner ───────────────────────────────────────────────

  getSkillScanner: () =>
    request<SkillScannerConfig>("/config/security/skill-scanner"),

  updateSkillScanner: (body: SkillScannerConfig) =>
    request<SkillScannerConfig>("/config/security/skill-scanner", {
      method: "PUT",
      body: JSON.stringify(body),
    }),

  getBlockedHistory: () =>
    request<BlockedSkillRecord[]>(
      "/config/security/skill-scanner/blocked-history",
    ),

  clearBlockedHistory: () =>
    request<{ cleared: boolean }>(
      "/config/security/skill-scanner/blocked-history",
      { method: "DELETE" },
    ),

  removeBlockedEntry: (index: number) =>
    request<{ removed: boolean }>(
      `/config/security/skill-scanner/blocked-history/${index}`,
      { method: "DELETE" },
    ),

  addToWhitelist: (skillName: string, contentHash: string = "") =>
    request<{ whitelisted: boolean; skill_name: string }>(
      "/config/security/skill-scanner/whitelist",
      {
        method: "POST",
        body: JSON.stringify({
          skill_name: skillName,
          content_hash: contentHash,
        }),
      },
    ),

  removeFromWhitelist: (skillName: string) =>
    request<{ removed: boolean; skill_name: string }>(
      `/config/security/skill-scanner/whitelist/${encodeURIComponent(
        skillName,
      )}`,
      { method: "DELETE" },
    ),
};


================================================
FILE: console/src/api/modules/skill.ts
================================================
import { request } from "../request";
import { getApiUrl, getApiToken } from "../config";
import type { HubSkillSpec, SkillSpec } from "../types";

// Declare BASE_URL as global (injected by Vite)
declare const BASE_URL: string;

// Get the API base URL for streaming requests
function getStreamApiUrl(): string {
  const base = typeof BASE_URL === "string" ? BASE_URL : "";
  return `${base}/api`;
}

function buildHeaders(): HeadersInit {
  const headers: Record<string, string> = {};

  const token = getApiToken();
  if (token) {
    headers["Authorization"] = `Bearer ${token}`;
  }

  try {
    const agentStorage = localStorage.getItem("copaw-agent-storage");
    if (agentStorage) {
      const parsed = JSON.parse(agentStorage);
      const selectedAgent = parsed?.state?.selectedAgent;
      if (selectedAgent) {
        headers["X-Agent-Id"] = selectedAgent;
      }
    }
  } catch (error) {
    console.warn("Failed to get selected agent from storage:", error);
  }

  return headers;
}

export const skillApi = {
  listSkills: () => request<SkillSpec[]>("/skills"),

  createSkill: (skillName: string, content: string) =>
    request<Record<string, unknown>>("/skills", {
      method: "POST",
      body: JSON.stringify({
        name: skillName,
        content: content,
      }),
    }),

  enableSkill: (skillName: string) =>
    request<void>(`/skills/${encodeURIComponent(skillName)}/enable`, {
      method: "POST",
    }),

  disableSkill: (skillName: string) =>
    request<void>(`/skills/${encodeURIComponent(skillName)}/disable`, {
      method: "POST",
    }),

  batchEnableSkills: (skillNames: string[]) =>
    request<void>("/skills/batch-enable", {
      method: "POST",
      body: JSON.stringify(skillNames),
    }),

  deleteSkill: (skillName: string) =>
    request<{ deleted: boolean }>(`/skills/${encodeURIComponent(
Download .txt
gitextract_9wv9z_k8/

├── .dockerignore
├── .flake8
├── .gitattributes
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── 1-question.md
│   │   ├── 2-feature_request.md
│   │   ├── 3-documentation.md
│   │   ├── 4-bug_report.md
│   │   ├── 5-support_environment.md
│   │   └── config.yml
│   ├── PULL_REQUEST_TEMPLATE.md
│   ├── condarc
│   └── workflows/
│       ├── deploy-website.yml
│       ├── desktop-release.yml
│       ├── docker-release.yml
│       ├── first-time-contributor-welcome.yml
│       ├── npm-format.yml
│       ├── pr-label.yml
│       ├── pre-commit.yml
│       ├── publish-pypi.yml
│       └── tests.yml
├── .gitignore
├── .pre-commit-config.yaml
├── .python-version
├── CONTRIBUTING.md
├── CONTRIBUTING_zh.md
├── LICENSE
├── README.md
├── README_ja.md
├── README_zh.md
├── SECURITY.md
├── console/
│   ├── eslint.config.js
│   ├── index.html
│   ├── package.json
│   ├── src/
│   │   ├── App.tsx
│   │   ├── api/
│   │   │   ├── config.ts
│   │   │   ├── index.ts
│   │   │   ├── modules/
│   │   │   │   ├── agent.ts
│   │   │   │   ├── agents.ts
│   │   │   │   ├── auth.ts
│   │   │   │   ├── channel.ts
│   │   │   │   ├── chat.ts
│   │   │   │   ├── console.ts
│   │   │   │   ├── cronjob.ts
│   │   │   │   ├── env.ts
│   │   │   │   ├── heartbeat.ts
│   │   │   │   ├── localModel.ts
│   │   │   │   ├── mcp.ts
│   │   │   │   ├── ollamaModel.ts
│   │   │   │   ├── provider.ts
│   │   │   │   ├── root.ts
│   │   │   │   ├── security.ts
│   │   │   │   ├── skill.ts
│   │   │   │   ├── tokenUsage.ts
│   │   │   │   ├── tools.ts
│   │   │   │   ├── userTimezone.ts
│   │   │   │   └── workspace.ts
│   │   │   ├── request.ts
│   │   │   └── types/
│   │   │       ├── agent.ts
│   │   │       ├── agents.ts
│   │   │       ├── channel.ts
│   │   │       ├── chat.ts
│   │   │       ├── cronjob.ts
│   │   │       ├── env.ts
│   │   │       ├── heartbeat.ts
│   │   │       ├── index.ts
│   │   │       ├── mcp.ts
│   │   │       ├── provider.ts
│   │   │       ├── skill.ts
│   │   │       ├── tokenUsage.ts
│   │   │       └── workspace.ts
│   │   ├── components/
│   │   │   ├── AgentSelector/
│   │   │   │   ├── index.module.less
│   │   │   │   └── index.tsx
│   │   │   ├── ConsoleCronBubble/
│   │   │   │   ├── index.module.less
│   │   │   │   └── index.tsx
│   │   │   ├── LanguageSwitcher.tsx
│   │   │   ├── MarkdownCopy/
│   │   │   │   ├── MarkdownCopy.tsx
│   │   │   │   └── index.module.less
│   │   │   ├── PageHeader/
│   │   │   │   └── index.tsx
│   │   │   └── ThemeToggleButton/
│   │   │       ├── index.module.less
│   │   │       └── index.tsx
│   │   ├── constants/
│   │   │   └── timezone.ts
│   │   ├── contexts/
│   │   │   └── ThemeContext.tsx
│   │   ├── i18n.ts
│   │   ├── layouts/
│   │   │   ├── Header.tsx
│   │   │   ├── MainLayout/
│   │   │   │   └── index.tsx
│   │   │   ├── Sidebar.tsx
│   │   │   ├── constants.ts
│   │   │   └── index.module.less
│   │   ├── locales/
│   │   │   ├── en.json
│   │   │   ├── ja.json
│   │   │   ├── ru.json
│   │   │   └── zh.json
│   │   ├── main.tsx
│   │   ├── pages/
│   │   │   ├── Agent/
│   │   │   │   ├── Config/
│   │   │   │   │   ├── components/
│   │   │   │   │   │   ├── ContextManagementCard.tsx
│   │   │   │   │   │   ├── PageHeader.tsx
│   │   │   │   │   │   ├── ReactAgentCard.tsx
│   │   │   │   │   │   ├── SliderWithValue.tsx
│   │   │   │   │   │   └── index.ts
│   │   │   │   │   ├── index.module.less
│   │   │   │   │   ├── index.tsx
│   │   │   │   │   └── useAgentConfig.tsx
│   │   │   │   ├── MCP/
│   │   │   │   │   ├── components/
│   │   │   │   │   │   ├── MCPClientCard.tsx
│   │   │   │   │   │   ├── MCPClientDrawer.tsx
│   │   │   │   │   │   └── index.ts
│   │   │   │   │   ├── index.module.less
│   │   │   │   │   ├── index.tsx
│   │   │   │   │   └── useMCP.ts
│   │   │   │   ├── Skills/
│   │   │   │   │   ├── components/
│   │   │   │   │   │   ├── SkillCard.tsx
│   │   │   │   │   │   ├── SkillDrawer.tsx
│   │   │   │   │   │   └── index.ts
│   │   │   │   │   ├── index.module.less
│   │   │   │   │   ├── index.tsx
│   │   │   │   │   └── useSkills.ts
│   │   │   │   ├── Tools/
│   │   │   │   │   ├── index.module.less
│   │   │   │   │   ├── index.tsx
│   │   │   │   │   └── useTools.ts
│   │   │   │   └── Workspace/
│   │   │   │       ├── components/
│   │   │   │       │   ├── FileEditor.tsx
│   │   │   │       │   ├── FileItem.tsx
│   │   │   │       │   ├── FileListPanel.tsx
│   │   │   │       │   ├── index.ts
│   │   │   │       │   ├── useAgentsData.ts
│   │   │   │       │   └── utils.ts
│   │   │   │       ├── index.module.less
│   │   │   │       └── index.tsx
│   │   │   ├── Chat/
│   │   │   │   ├── ModelSelector/
│   │   │   │   │   ├── index.module.less
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── OptionsPanel/
│   │   │   │   │   ├── FormItem.tsx
│   │   │   │   │   ├── OptionsEditor.tsx
│   │   │   │   │   ├── defaultConfig.ts
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── index.module.less
│   │   │   │   ├── index.tsx
│   │   │   │   └── sessionApi/
│   │   │   │       └── index.ts
│   │   │   ├── Control/
│   │   │   │   ├── Channels/
│   │   │   │   │   ├── components/
│   │   │   │   │   │   ├── ChannelCard.tsx
│   │   │   │   │   │   ├── ChannelDrawer.tsx
│   │   │   │   │   │   ├── constants.ts
│   │   │   │   │   │   └── index.ts
│   │   │   │   │   ├── index.module.less
│   │   │   │   │   ├── index.tsx
│   │   │   │   │   └── useChannels.ts
│   │   │   │   ├── CronJobs/
│   │   │   │   │   ├── components/
│   │   │   │   │   │   ├── JobDrawer.tsx
│   │   │   │   │   │   ├── columns.tsx
│   │   │   │   │   │   ├── constants.ts
│   │   │   │   │   │   ├── index.ts
│   │   │   │   │   │   └── parseCron.ts
│   │   │   │   │   ├── index.module.less
│   │   │   │   │   ├── index.tsx
│   │   │   │   │   └── useCronJobs.ts
│   │   │   │   ├── Heartbeat/
│   │   │   │   │   ├── index.module.less
│   │   │   │   │   ├── index.tsx
│   │   │   │   │   └── parseEvery.ts
│   │   │   │   └── Sessions/
│   │   │   │       ├── components/
│   │   │   │       │   ├── FilterBar.tsx
│   │   │   │       │   ├── SessionDrawer.tsx
│   │   │   │       │   ├── columns.tsx
│   │   │   │       │   ├── constants.ts
│   │   │   │       │   └── index.ts
│   │   │   │       ├── index.module.less
│   │   │   │       ├── index.tsx
│   │   │   │       └── useSessions.ts
│   │   │   ├── Login/
│   │   │   │   └── index.tsx
│   │   │   └── Settings/
│   │   │       ├── Agents/
│   │   │       │   ├── components/
│   │   │       │   │   ├── AgentModal.tsx
│   │   │       │   │   ├── AgentTable.tsx
│   │   │       │   │   ├── PageHeader.tsx
│   │   │       │   │   └── index.ts
│   │   │       │   ├── index.module.less
│   │   │       │   ├── index.tsx
│   │   │       │   └── useAgents.ts
│   │   │       ├── Environments/
│   │   │       │   ├── components/
│   │   │       │   │   ├── AddButton.tsx
│   │   │       │   │   ├── EmptyState.tsx
│   │   │       │   │   ├── EnvRow.tsx
│   │   │       │   │   ├── PageHeader.tsx
│   │   │       │   │   ├── Toolbar.tsx
│   │   │       │   │   └── index.ts
│   │   │       │   ├── index.module.less
│   │   │       │   ├── index.tsx
│   │   │       │   └── useEnvVars.ts
│   │   │       ├── Models/
│   │   │       │   ├── components/
│   │   │       │   │   ├── ModelManageModal.tsx
│   │   │       │   │   ├── cards/
│   │   │       │   │   │   ├── LocalProviderCard.tsx
│   │   │       │   │   │   ├── ProviderCard.tsx
│   │   │       │   │   │   ├── RemoteProviderCard.tsx
│   │   │       │   │   │   └── index.ts
│   │   │       │   │   ├── index.ts
│   │   │       │   │   ├── modals/
│   │   │       │   │   │   ├── CustomProviderModal.tsx
│   │   │       │   │   │   ├── LocalModelManageModal.tsx
│   │   │       │   │   │   ├── ModelManageModal.tsx
│   │   │       │   │   │   ├── OllamaModelManageModal.tsx
│   │   │       │   │   │   ├── ProviderConfigModal.tsx
│   │   │       │   │   │   ├── RemoteModelManageModal.tsx
│   │   │       │   │   │   └── index.ts
│   │   │       │   │   └── sections/
│   │   │       │   │       ├── LoadingState.tsx
│   │   │       │   │       ├── ModelsSection.tsx
│   │   │       │   │       ├── PageHeader.tsx
│   │   │       │   │       └── index.ts
│   │   │       │   ├── index.module.less
│   │   │       │   ├── index.tsx
│   │   │       │   └── useProviders.ts
│   │   │       ├── Security/
│   │   │       │   ├── components/
│   │   │       │   │   ├── PageHeader.tsx
│   │   │       │   │   ├── PreviewModal.tsx
│   │   │       │   │   ├── RuleModal.tsx
│   │   │       │   │   ├── RuleTable.tsx
│   │   │       │   │   ├── SkillScannerSection.tsx
│   │   │       │   │   └── index.ts
│   │   │       │   ├── index.module.less
│   │   │       │   ├── index.tsx
│   │   │       │   ├── useSkillScanner.ts
│   │   │       │   └── useToolGuard.ts
│   │   │       ├── TokenUsage/
│   │   │       │   ├── components/
│   │   │       │   │   ├── EmptyState.tsx
│   │   │       │   │   ├── LoadingState.tsx
│   │   │       │   │   ├── PageHeader.tsx
│   │   │       │   │   └── index.ts
│   │   │       │   ├── index.module.less
│   │   │       │   └── index.tsx
│   │   │       └── VoiceTranscription/
│   │   │           ├── index.module.less
│   │   │           └── index.tsx
│   │   ├── stores/
│   │   │   └── agentStore.ts
│   │   ├── styles/
│   │   │   ├── form-override.css
│   │   │   └── layout.css
│   │   ├── utils/
│   │   │   ├── formatNumber.ts
│   │   │   └── markdown.ts
│   │   └── vite-env.d.ts
│   ├── tsconfig.app.json
│   ├── tsconfig.json
│   ├── tsconfig.node.json
│   └── vite.config.ts
├── deploy/
│   ├── Dockerfile
│   ├── config/
│   │   └── supervisord.conf.template
│   └── entrypoint.sh
├── docker-compose.yml
├── pyproject.toml
├── scripts/
│   ├── README.md
│   ├── docker_build.sh
│   ├── docker_sync_latest.sh
│   ├── install.bat
│   ├── install.ps1
│   ├── install.sh
│   ├── pack/
│   │   ├── README.md
│   │   ├── README_zh.md
│   │   ├── assets/
│   │   │   └── icon.icns
│   │   ├── build_common.py
│   │   ├── build_macos.sh
│   │   ├── build_win.ps1
│   │   └── copaw_desktop.nsi
│   ├── run_tests.py
│   ├── website_build.sh
│   ├── wheel_build.ps1
│   └── wheel_build.sh
├── setup.py
├── src/
│   └── copaw/
│       ├── __init__.py
│       ├── __main__.py
│       ├── __version__.py
│       ├── agents/
│       │   ├── __init__.py
│       │   ├── command_handler.py
│       │   ├── hooks/
│       │   │   ├── __init__.py
│       │   │   ├── bootstrap.py
│       │   │   └── memory_compaction.py
│       │   ├── md_files/
│       │   │   ├── en/
│       │   │   │   ├── AGENTS.md
│       │   │   │   ├── BOOTSTRAP.md
│       │   │   │   ├── HEARTBEAT.md
│       │   │   │   ├── MEMORY.md
│       │   │   │   ├── PROFILE.md
│       │   │   │   └── SOUL.md
│       │   │   ├── ru/
│       │   │   │   ├── AGENTS.md
│       │   │   │   ├── BOOTSTRAP.md
│       │   │   │   ├── HEARTBEAT.md
│       │   │   │   ├── MEMORY.md
│       │   │   │   ├── PROFILE.md
│       │   │   │   └── SOUL.md
│       │   │   └── zh/
│       │   │       ├── AGENTS.md
│       │   │       ├── BOOTSTRAP.md
│       │   │       ├── HEARTBEAT.md
│       │   │       ├── MEMORY.md
│       │   │       ├── PROFILE.md
│       │   │       └── SOUL.md
│       │   ├── memory/
│       │   │   ├── __init__.py
│       │   │   ├── agent_md_manager.py
│       │   │   └── memory_manager.py
│       │   ├── model_factory.py
│       │   ├── prompt.py
│       │   ├── react_agent.py
│       │   ├── routing_chat_model.py
│       │   ├── schema.py
│       │   ├── skills/
│       │   │   ├── __init__.py
│       │   │   ├── browser_visible/
│       │   │   │   └── SKILL.md
│       │   │   ├── cron/
│       │   │   │   └── SKILL.md
│       │   │   ├── dingtalk_channel/
│       │   │   │   └── SKILL.md
│       │   │   ├── docx/
│       │   │   │   ├── LICENSE.txt
│       │   │   │   ├── SKILL.md
│       │   │   │   └── scripts/
│       │   │   │       ├── __init__.py
│       │   │   │       ├── accept_changes.py
│       │   │   │       ├── comment.py
│       │   │   │       ├── office/
│       │   │   │       │   ├── helpers/
│       │   │   │       │   │   ├── __init__.py
│       │   │   │       │   │   ├── merge_runs.py
│       │   │   │       │   │   └── simplify_redlines.py
│       │   │   │       │   ├── pack.py
│       │   │   │       │   ├── schemas/
│       │   │   │       │   │   ├── ISO-IEC29500-4_2016/
│       │   │   │       │   │   │   ├── dml-chart.xsd
│       │   │   │       │   │   │   ├── dml-chartDrawing.xsd
│       │   │   │       │   │   │   ├── dml-diagram.xsd
│       │   │   │       │   │   │   ├── dml-lockedCanvas.xsd
│       │   │   │       │   │   │   ├── dml-main.xsd
│       │   │   │       │   │   │   ├── dml-picture.xsd
│       │   │   │       │   │   │   ├── dml-spreadsheetDrawing.xsd
│       │   │   │       │   │   │   ├── dml-wordprocessingDrawing.xsd
│       │   │   │       │   │   │   ├── pml.xsd
│       │   │   │       │   │   │   ├── shared-additionalCharacteristics.xsd
│       │   │   │       │   │   │   ├── shared-bibliography.xsd
│       │   │   │       │   │   │   ├── shared-commonSimpleTypes.xsd
│       │   │   │       │   │   │   ├── shared-customXmlDataProperties.xsd
│       │   │   │       │   │   │   ├── shared-customXmlSchemaProperties.xsd
│       │   │   │       │   │   │   ├── shared-documentPropertiesCustom.xsd
│       │   │   │       │   │   │   ├── shared-documentPropertiesExtended.xsd
│       │   │   │       │   │   │   ├── shared-documentPropertiesVariantTypes.xsd
│       │   │   │       │   │   │   ├── shared-math.xsd
│       │   │   │       │   │   │   ├── shared-relationshipReference.xsd
│       │   │   │       │   │   │   ├── sml.xsd
│       │   │   │       │   │   │   ├── vml-main.xsd
│       │   │   │       │   │   │   ├── vml-officeDrawing.xsd
│       │   │   │       │   │   │   ├── vml-presentationDrawing.xsd
│       │   │   │       │   │   │   ├── vml-spreadsheetDrawing.xsd
│       │   │   │       │   │   │   ├── vml-wordprocessingDrawing.xsd
│       │   │   │       │   │   │   ├── wml.xsd
│       │   │   │       │   │   │   └── xml.xsd
│       │   │   │       │   │   ├── ecma/
│       │   │   │       │   │   │   └── fouth-edition/
│       │   │   │       │   │   │       ├── opc-contentTypes.xsd
│       │   │   │       │   │   │       ├── opc-coreProperties.xsd
│       │   │   │       │   │   │       ├── opc-digSig.xsd
│       │   │   │       │   │   │       └── opc-relationships.xsd
│       │   │   │       │   │   ├── mce/
│       │   │   │       │   │   │   └── mc.xsd
│       │   │   │       │   │   └── microsoft/
│       │   │   │       │   │       ├── wml-2010.xsd
│       │   │   │       │   │       ├── wml-2012.xsd
│       │   │   │       │   │       ├── wml-2018.xsd
│       │   │   │       │   │       ├── wml-cex-2018.xsd
│       │   │   │       │   │       ├── wml-cid-2016.xsd
│       │   │   │       │   │       ├── wml-sdtdatahash-2020.xsd
│       │   │   │       │   │       └── wml-symex-2015.xsd
│       │   │   │       │   ├── soffice.py
│       │   │   │       │   ├── unpack.py
│       │   │   │       │   ├── validate.py
│       │   │   │       │   └── validators/
│       │   │   │       │       ├── __init__.py
│       │   │   │       │       ├── base.py
│       │   │   │       │       ├── docx.py
│       │   │   │       │       ├── pptx.py
│       │   │   │       │       └── redlining.py
│       │   │   │       └── templates/
│       │   │   │           ├── comments.xml
│       │   │   │           ├── commentsExtended.xml
│       │   │   │           ├── commentsExtensible.xml
│       │   │   │           ├── commentsIds.xml
│       │   │   │           └── people.xml
│       │   │   ├── file_reader/
│       │   │   │   └── SKILL.md
│       │   │   ├── guidance/
│       │   │   │   └── SKILL.md
│       │   │   ├── himalaya/
│       │   │   │   ├── SKILL.md
│       │   │   │   └── references/
│       │   │   │       └── configuration.md
│       │   │   ├── news/
│       │   │   │   └── SKILL.md
│       │   │   ├── pdf/
│       │   │   │   ├── LICENSE.txt
│       │   │   │   ├── SKILL.md
│       │   │   │   ├── forms.md
│       │   │   │   ├── reference.md
│       │   │   │   └── scripts/
│       │   │   │       ├── check_bounding_boxes.py
│       │   │   │       ├── check_fillable_fields.py
│       │   │   │       ├── convert_pdf_to_images.py
│       │   │   │       ├── create_validation_image.py
│       │   │   │       ├── extract_form_field_info.py
│       │   │   │       ├── extract_form_structure.py
│       │   │   │       ├── fill_fillable_fields.py
│       │   │   │       └── fill_pdf_form_with_annotations.py
│       │   │   ├── pptx/
│       │   │   │   ├── LICENSE.txt
│       │   │   │   ├── SKILL.md
│       │   │   │   ├── editing.md
│       │   │   │   ├── pptxgenjs.md
│       │   │   │   └── scripts/
│       │   │   │       ├── __init__.py
│       │   │   │       ├── add_slide.py
│       │   │   │       ├── clean.py
│       │   │   │       ├── office/
│       │   │   │       │   ├── helpers/
│       │   │   │       │   │   ├── __init__.py
│       │   │   │       │   │   ├── merge_runs.py
│       │   │   │       │   │   └── simplify_redlines.py
│       │   │   │       │   ├── pack.py
│       │   │   │       │   ├── schemas/
│       │   │   │       │   │   ├── ISO-IEC29500-4_2016/
│       │   │   │       │   │   │   ├── dml-chart.xsd
│       │   │   │       │   │   │   ├── dml-chartDrawing.xsd
│       │   │   │       │   │   │   ├── dml-diagram.xsd
│       │   │   │       │   │   │   ├── dml-lockedCanvas.xsd
│       │   │   │       │   │   │   ├── dml-main.xsd
│       │   │   │       │   │   │   ├── dml-picture.xsd
│       │   │   │       │   │   │   ├── dml-spreadsheetDrawing.xsd
│       │   │   │       │   │   │   ├── dml-wordprocessingDrawing.xsd
│       │   │   │       │   │   │   ├── pml.xsd
│       │   │   │       │   │   │   ├── shared-additionalCharacteristics.xsd
│       │   │   │       │   │   │   ├── shared-bibliography.xsd
│       │   │   │       │   │   │   ├── shared-commonSimpleTypes.xsd
│       │   │   │       │   │   │   ├── shared-customXmlDataProperties.xsd
│       │   │   │       │   │   │   ├── shared-customXmlSchemaProperties.xsd
│       │   │   │       │   │   │   ├── shared-documentPropertiesCustom.xsd
│       │   │   │       │   │   │   ├── shared-documentPropertiesExtended.xsd
│       │   │   │       │   │   │   ├── shared-documentPropertiesVariantTypes.xsd
│       │   │   │       │   │   │   ├── shared-math.xsd
│       │   │   │       │   │   │   ├── shared-relationshipReference.xsd
│       │   │   │       │   │   │   ├── sml.xsd
│       │   │   │       │   │   │   ├── vml-main.xsd
│       │   │   │       │   │   │   ├── vml-officeDrawing.xsd
│       │   │   │       │   │   │   ├── vml-presentationDrawing.xsd
│       │   │   │       │   │   │   ├── vml-spreadsheetDrawing.xsd
│       │   │   │       │   │   │   ├── vml-wordprocessingDrawing.xsd
│       │   │   │       │   │   │   ├── wml.xsd
│       │   │   │       │   │   │   └── xml.xsd
│       │   │   │       │   │   ├── ecma/
│       │   │   │       │   │   │   └── fouth-edition/
│       │   │   │       │   │   │       ├── opc-contentTypes.xsd
│       │   │   │       │   │   │       ├── opc-coreProperties.xsd
│       │   │   │       │   │   │       ├── opc-digSig.xsd
│       │   │   │       │   │   │       └── opc-relationships.xsd
│       │   │   │       │   │   ├── mce/
│       │   │   │       │   │   │   └── mc.xsd
│       │   │   │       │   │   └── microsoft/
│       │   │   │       │   │       ├── wml-2010.xsd
│       │   │   │       │   │       ├── wml-2012.xsd
│       │   │   │       │   │       ├── wml-2018.xsd
│       │   │   │       │   │       ├── wml-cex-2018.xsd
│       │   │   │       │   │       ├── wml-cid-2016.xsd
│       │   │   │       │   │       ├── wml-sdtdatahash-2020.xsd
│       │   │   │       │   │       └── wml-symex-2015.xsd
│       │   │   │       │   ├── soffice.py
│       │   │   │       │   ├── unpack.py
│       │   │   │       │   ├── validate.py
│       │   │   │       │   └── validators/
│       │   │   │       │       ├── __init__.py
│       │   │   │       │       ├── base.py
│       │   │   │       │       ├── docx.py
│       │   │   │       │       ├── pptx.py
│       │   │   │       │       └── redlining.py
│       │   │   │       └── thumbnail.py
│       │   │   └── xlsx/
│       │   │       ├── LICENSE.txt
│       │   │       ├── SKILL.md
│       │   │       └── scripts/
│       │   │           ├── office/
│       │   │           │   ├── helpers/
│       │   │           │   │   ├── __init__.py
│       │   │           │   │   ├── merge_runs.py
│       │   │           │   │   └── simplify_redlines.py
│       │   │           │   ├── pack.py
│       │   │           │   ├── schemas/
│       │   │           │   │   ├── ISO-IEC29500-4_2016/
│       │   │           │   │   │   ├── dml-chart.xsd
│       │   │           │   │   │   ├── dml-chartDrawing.xsd
│       │   │           │   │   │   ├── dml-diagram.xsd
│       │   │           │   │   │   ├── dml-lockedCanvas.xsd
│       │   │           │   │   │   ├── dml-main.xsd
│       │   │           │   │   │   ├── dml-picture.xsd
│       │   │           │   │   │   ├── dml-spreadsheetDrawing.xsd
│       │   │           │   │   │   ├── dml-wordprocessingDrawing.xsd
│       │   │           │   │   │   ├── pml.xsd
│       │   │           │   │   │   ├── shared-additionalCharacteristics.xsd
│       │   │           │   │   │   ├── shared-bibliography.xsd
│       │   │           │   │   │   ├── shared-commonSimpleTypes.xsd
│       │   │           │   │   │   ├── shared-customXmlDataProperties.xsd
│       │   │           │   │   │   ├── shared-customXmlSchemaProperties.xsd
│       │   │           │   │   │   ├── shared-documentPropertiesCustom.xsd
│       │   │           │   │   │   ├── shared-documentPropertiesExtended.xsd
│       │   │           │   │   │   ├── shared-documentPropertiesVariantTypes.xsd
│       │   │           │   │   │   ├── shared-math.xsd
│       │   │           │   │   │   ├── shared-relationshipReference.xsd
│       │   │           │   │   │   ├── sml.xsd
│       │   │           │   │   │   ├── vml-main.xsd
│       │   │           │   │   │   ├── vml-officeDrawing.xsd
│       │   │           │   │   │   ├── vml-presentationDrawing.xsd
│       │   │           │   │   │   ├── vml-spreadsheetDrawing.xsd
│       │   │           │   │   │   ├── vml-wordprocessingDrawing.xsd
│       │   │           │   │   │   ├── wml.xsd
│       │   │           │   │   │   └── xml.xsd
│       │   │           │   │   ├── ecma/
│       │   │           │   │   │   └── fouth-edition/
│       │   │           │   │   │       ├── opc-contentTypes.xsd
│       │   │           │   │   │       ├── opc-coreProperties.xsd
│       │   │           │   │   │       ├── opc-digSig.xsd
│       │   │           │   │   │       └── opc-relationships.xsd
│       │   │           │   │   ├── mce/
│       │   │           │   │   │   └── mc.xsd
│       │   │           │   │   └── microsoft/
│       │   │           │   │       ├── wml-2010.xsd
│       │   │           │   │       ├── wml-2012.xsd
│       │   │           │   │       ├── wml-2018.xsd
│       │   │           │   │       ├── wml-cex-2018.xsd
│       │   │           │   │       ├── wml-cid-2016.xsd
│       │   │           │   │       ├── wml-sdtdatahash-2020.xsd
│       │   │           │   │       └── wml-symex-2015.xsd
│       │   │           │   ├── soffice.py
│       │   │           │   ├── unpack.py
│       │   │           │   ├── validate.py
│       │   │           │   └── validators/
│       │   │           │       ├── __init__.py
│       │   │           │       ├── base.py
│       │   │           │       ├── docx.py
│       │   │           │       ├── pptx.py
│       │   │           │       └── redlining.py
│       │   │           └── recalc.py
│       │   ├── skills_hub.py
│       │   ├── skills_manager.py
│       │   ├── tool_guard_mixin.py
│       │   ├── tools/
│       │   │   ├── __init__.py
│       │   │   ├── browser_control.py
│       │   │   ├── browser_snapshot.py
│       │   │   ├── desktop_screenshot.py
│       │   │   ├── file_io.py
│       │   │   ├── file_search.py
│       │   │   ├── get_current_time.py
│       │   │   ├── get_token_usage.py
│       │   │   ├── memory_search.py
│       │   │   ├── send_file.py
│       │   │   ├── shell.py
│       │   │   ├── utils.py
│       │   │   └── view_image.py
│       │   └── utils/
│       │       ├── __init__.py
│       │       ├── audio_transcription.py
│       │       ├── copaw_token_counter.py
│       │       ├── file_handling.py
│       │       ├── message_processing.py
│       │       ├── setup_utils.py
│       │       └── tool_message_utils.py
│       ├── app/
│       │   ├── __init__.py
│       │   ├── _app.py
│       │   ├── agent_config_watcher.py
│       │   ├── agent_context.py
│       │   ├── approvals/
│       │   │   ├── __init__.py
│       │   │   └── service.py
│       │   ├── auth.py
│       │   ├── channels/
│       │   │   ├── __init__.py
│       │   │   ├── base.py
│       │   │   ├── console/
│       │   │   │   ├── __init__.py
│       │   │   │   └── channel.py
│       │   │   ├── dingtalk/
│       │   │   │   ├── __init__.py
│       │   │   │   ├── ai_card.py
│       │   │   │   ├── channel.py
│       │   │   │   ├── constants.py
│       │   │   │   ├── content_utils.py
│       │   │   │   ├── handler.py
│       │   │   │   ├── markdown.py
│       │   │   │   └── utils.py
│       │   │   ├── discord_/
│       │   │   │   ├── __init__.py
│       │   │   │   └── channel.py
│       │   │   ├── feishu/
│       │   │   │   ├── __init__.py
│       │   │   │   ├── channel.py
│       │   │   │   ├── constants.py
│       │   │   │   └── utils.py
│       │   │   ├── imessage/
│       │   │   │   ├── __init__.py
│       │   │   │   └── channel.py
│       │   │   ├── manager.py
│       │   │   ├── matrix/
│       │   │   │   ├── __init__.py
│       │   │   │   └── channel.py
│       │   │   ├── mattermost/
│       │   │   │   ├── __init__.py
│       │   │   │   └── channel.py
│       │   │   ├── mqtt/
│       │   │   │   ├── __init__.py
│       │   │   │   └── channel.py
│       │   │   ├── qq/
│       │   │   │   ├── __init__.py
│       │   │   │   └── channel.py
│       │   │   ├── registry.py
│       │   │   ├── renderer.py
│       │   │   ├── schema.py
│       │   │   ├── telegram/
│       │   │   │   ├── __init__.py
│       │   │   │   ├── channel.py
│       │   │   │   └── format_html.py
│       │   │   ├── utils.py
│       │   │   ├── voice/
│       │   │   │   ├── __init__.py
│       │   │   │   ├── channel.py
│       │   │   │   ├── conversation_relay.py
│       │   │   │   ├── session.py
│       │   │   │   ├── twilio_manager.py
│       │   │   │   └── twiml.py
│       │   │   ├── wecom/
│       │   │   │   ├── __init__.py
│       │   │   │   ├── channel.py
│       │   │   │   └── utils.py
│       │   │   └── xiaoyi/
│       │   │       ├── __init__.py
│       │   │       ├── auth.py
│       │   │       ├── channel.py
│       │   │       ├── constants.py
│       │   │       └── utils.py
│       │   ├── console_push_store.py
│       │   ├── crons/
│       │   │   ├── __init__.py
│       │   │   ├── api.py
│       │   │   ├── executor.py
│       │   │   ├── heartbeat.py
│       │   │   ├── manager.py
│       │   │   ├── models.py
│       │   │   └── repo/
│       │   │       ├── __init__.py
│       │   │       ├── base.py
│       │   │       └── json_repo.py
│       │   ├── download_task_store.py
│       │   ├── mcp/
│       │   │   ├── __init__.py
│       │   │   ├── manager.py
│       │   │   └── watcher.py
│       │   ├── migration.py
│       │   ├── multi_agent_manager.py
│       │   ├── routers/
│       │   │   ├── __init__.py
│       │   │   ├── agent.py
│       │   │   ├── agent_scoped.py
│       │   │   ├── agents.py
│       │   │   ├── auth.py
│       │   │   ├── config.py
│       │   │   ├── console.py
│       │   │   ├── envs.py
│       │   │   ├── local_models.py
│       │   │   ├── mcp.py
│       │   │   ├── ollama_models.py
│       │   │   ├── providers.py
│       │   │   ├── schemas_config.py
│       │   │   ├── skills.py
│       │   │   ├── skills_stream.py
│       │   │   ├── token_usage.py
│       │   │   ├── tools.py
│       │   │   ├── voice.py
│       │   │   └── workspace.py
│       │   ├── runner/
│       │   │   ├── __init__.py
│       │   │   ├── api.py
│       │   │   ├── command_dispatch.py
│       │   │   ├── daemon_commands.py
│       │   │   ├── manager.py
│       │   │   ├── models.py
│       │   │   ├── query_error_dump.py
│       │   │   ├── repo/
│       │   │   │   ├── __init__.py
│       │   │   │   ├── base.py
│       │   │   │   └── json_repo.py
│       │   │   ├── runner.py
│       │   │   ├── session.py
│       │   │   ├── task_tracker.py
│       │   │   └── utils.py
│       │   └── workspace/
│       │       ├── __init__.py
│       │       ├── service_factories.py
│       │       ├── service_manager.py
│       │       └── workspace.py
│       ├── cli/
│       │   ├── __init__.py
│       │   ├── app_cmd.py
│       │   ├── auth_cmd.py
│       │   ├── channels_cmd.py
│       │   ├── chats_cmd.py
│       │   ├── clean_cmd.py
│       │   ├── cron_cmd.py
│       │   ├── daemon_cmd.py
│       │   ├── desktop_cmd.py
│       │   ├── env_cmd.py
│       │   ├── http.py
│       │   ├── init_cmd.py
│       │   ├── main.py
│       │   ├── process_utils.py
│       │   ├── providers_cmd.py
│       │   ├── shutdown_cmd.py
│       │   ├── skills_cmd.py
│       │   ├── uninstall_cmd.py
│       │   ├── update_cmd.py
│       │   └── utils.py
│       ├── config/
│       │   ├── __init__.py
│       │   ├── config.py
│       │   ├── context.py
│       │   ├── timezone.py
│       │   └── utils.py
│       ├── constant.py
│       ├── envs/
│       │   ├── __init__.py
│       │   └── store.py
│       ├── local_models/
│       │   ├── __init__.py
│       │   ├── backends/
│       │   │   ├── __init__.py
│       │   │   ├── base.py
│       │   │   ├── llamacpp_backend.py
│       │   │   └── mlx_backend.py
│       │   ├── chat_model.py
│       │   ├── factory.py
│       │   ├── manager.py
│       │   ├── schema.py
│       │   └── tag_parser.py
│       ├── providers/
│       │   ├── __init__.py
│       │   ├── anthropic_provider.py
│       │   ├── gemini_provider.py
│       │   ├── models.py
│       │   ├── ollama_manager.py
│       │   ├── ollama_provider.py
│       │   ├── openai_chat_model_compat.py
│       │   ├── openai_provider.py
│       │   ├── provider.py
│       │   ├── provider_manager.py
│       │   └── retry_chat_model.py
│       ├── security/
│       │   ├── __init__.py
│       │   ├── skill_scanner/
│       │   │   ├── __init__.py
│       │   │   ├── analyzers/
│       │   │   │   ├── __init__.py
│       │   │   │   └── pattern_analyzer.py
│       │   │   ├── data/
│       │   │   │   └── default_policy.yaml
│       │   │   ├── models.py
│       │   │   ├── rules/
│       │   │   │   └── signatures/
│       │   │   │       ├── command_injection.yaml
│       │   │   │       ├── data_exfiltration.yaml
│       │   │   │       ├── hardcoded_secrets.yaml
│       │   │   │       ├── obfuscation.yaml
│       │   │   │       ├── prompt_injection.yaml
│       │   │   │       ├── resource_abuse.yaml
│       │   │   │       ├── social_engineering.yaml
│       │   │   │       ├── supply_chain.yaml
│       │   │   │       └── unauthorized_tool_use.yaml
│       │   │   ├── scan_policy.py
│       │   │   └── scanner.py
│       │   └── tool_guard/
│       │       ├── __init__.py
│       │       ├── approval.py
│       │       ├── engine.py
│       │       ├── guardians/
│       │       │   ├── __init__.py
│       │       │   └── rule_guardian.py
│       │       ├── models.py
│       │       ├── rules/
│       │       │   └── dangerous_shell_commands.yaml
│       │       └── utils.py
│       ├── token_usage/
│       │   ├── __init__.py
│       │   ├── manager.py
│       │   └── model_wrapper.py
│       ├── tokenizer/
│       │   ├── merges.txt
│       │   ├── tokenizer.json
│       │   ├── tokenizer_config.json
│       │   └── vocab.json
│       ├── tunnel/
│       │   ├── __init__.py
│       │   ├── binary_manager.py
│       │   └── cloudflare.py
│       └── utils/
│           ├── __init__.py
│           ├── logging.py
│           └── telemetry.py
├── tests/
│   ├── __init__.py
│   ├── integrated/
│   │   ├── test_app_startup.py
│   │   └── test_version.py
│   └── unit/
│       ├── channels/
│       │   ├── __init__.py
│       │   └── test_qq_channel.py
│       ├── cli/
│       │   ├── test_cli_shutdown.py
│       │   ├── test_cli_update.py
│       │   └── test_cli_version.py
│       ├── memory/
│       │   └── test_copaw_token_counter.py
│       ├── providers/
│       │   ├── test_anthropic_provider.py
│       │   ├── test_default_provider.py
│       │   ├── test_gemini_provider.py
│       │   ├── test_kimi_provider.py
│       │   ├── test_ollama_manager_timeout.py
│       │   ├── test_ollama_provider.py
│       │   ├── test_openai_provider.py
│       │   ├── test_openai_stream_toolcall_compat.py
│       │   └── test_provider_manager.py
│       └── workspace/
│           ├── __init__.py
│           ├── test_agent_creation.py
│           ├── test_agent_id.py
│           ├── test_agent_model.py
│           ├── test_cli_agent_id.py
│           ├── test_prompt.py
│           └── test_workspace.py
└── website/
    ├── README.md
    ├── index.html
    ├── package.json
    ├── public/
    │   ├── docs/
    │   │   ├── channels.en.md
    │   │   ├── channels.zh.md
    │   │   ├── cli.en.md
    │   │   ├── cli.zh.md
    │   │   ├── commands.en.md
    │   │   ├── commands.zh.md
    │   │   ├── community.en.md
    │   │   ├── community.zh.md
    │   │   ├── comparison.en.md
    │   │   ├── comparison.zh.md
    │   │   ├── config.en.md
    │   │   ├── config.zh.md
    │   │   ├── console.en.md
    │   │   ├── console.zh.md
    │   │   ├── context.en.md
    │   │   ├── context.zh.md
    │   │   ├── contributing.en.md
    │   │   ├── contributing.zh.md
    │   │   ├── desktop.en.md
    │   │   ├── desktop.zh.md
    │   │   ├── faq.en.md
    │   │   ├── faq.zh.md
    │   │   ├── heartbeat.en.md
    │   │   ├── heartbeat.zh.md
    │   │   ├── intro.en.md
    │   │   ├── intro.zh.md
    │   │   ├── mcp.en.md
    │   │   ├── mcp.zh.md
    │   │   ├── memory.en.md
    │   │   ├── memory.zh.md
    │   │   ├── models.en.md
    │   │   ├── models.zh.md
    │   │   ├── multi-agent.en.md
    │   │   ├── multi-agent.zh.md
    │   │   ├── quickstart.en.md
    │   │   ├── quickstart.zh.md
    │   │   ├── roadmap.en.md
    │   │   ├── roadmap.zh.md
    │   │   ├── security.en.md
    │   │   ├── security.zh.md
    │   │   ├── skills.en.md
    │   │   └── skills.zh.md
    │   ├── release-notes/
    │   │   ├── v0.0.4.md
    │   │   ├── v0.0.4.zh.md
    │   │   ├── v0.0.5-beta.1.md
    │   │   ├── v0.0.5-beta.1.zh.md
    │   │   ├── v0.0.5-beta.2.md
    │   │   ├── v0.0.5-beta.2.zh.md
    │   │   ├── v0.0.5-beta.3.md
    │   │   ├── v0.0.5-beta.3.zh.md
    │   │   ├── v0.0.5.md
    │   │   ├── v0.0.5.zh.md
    │   │   ├── v0.0.6.md
    │   │   ├── v0.0.6.zh.md
    │   │   ├── v0.0.7.md
    │   │   ├── v0.0.7.zh.md
    │   │   ├── v0.1.0.md
    │   │   └── v0.1.0.zh.md
    │   └── site.config.json
    ├── scripts/
    │   ├── build-search-index.mjs
    │   └── spa-fallback-pages.mjs
    ├── src/
    │   ├── App.tsx
    │   ├── components/
    │   │   ├── BrandStory.tsx
    │   │   ├── CatPawIcon.tsx
    │   │   ├── CopawLogo.tsx
    │   │   ├── CopawMascot.tsx
    │   │   ├── DocSearch.tsx
    │   │   ├── DocSearchResults.tsx
    │   │   ├── Ecosystem.tsx
    │   │   ├── Features.tsx
    │   │   ├── FollowUs.tsx
    │   │   ├── Footer.tsx
    │   │   ├── Hero.tsx
    │   │   ├── MermaidBlock.tsx
    │   │   ├── Nav.tsx
    │   │   ├── QuickStart.tsx
    │   │   ├── Testimonials.tsx
    │   │   └── UseCases.tsx
    │   ├── config.ts
    │   ├── data/
    │   │   └── testimonials.ts
    │   ├── i18n.ts
    │   ├── index.css
    │   ├── lib/
    │   │   └── docsSearch.ts
    │   ├── main.tsx
    │   ├── pages/
    │   │   ├── Docs.tsx
    │   │   ├── Home.tsx
    │   │   └── ReleaseNotes.tsx
    │   └── vite-env.d.ts
    ├── tsconfig.json
    └── vite.config.ts
Download .txt
Showing preview only (246K chars total). Download the full file or copy to clipboard to get everything.
SYMBOL INDEX (2841 symbols across 352 files)

FILE: console/src/App.tsx
  function AuthGuard (line 45) | function AuthGuard({ children }: { children: React.ReactNode }) {
  function getRouterBasename (line 102) | function getRouterBasename(pathname: string): string | undefined {
  function AppInner (line 106) | function AppInner() {
  function App (line 162) | function App() {

FILE: console/src/api/config.ts
  constant AUTH_TOKEN_KEY (line 4) | const AUTH_TOKEN_KEY = "copaw_auth_token";
  function getApiUrl (line 11) | function getApiUrl(path: string): string {
  function getApiToken (line 23) | function getApiToken(): string {
  function setAuthToken (line 32) | function setAuthToken(token: string): void {
  function clearAuthToken (line 39) | function clearAuthToken(): void {

FILE: console/src/api/modules/auth.ts
  type LoginResponse (line 3) | interface LoginResponse {
  type AuthStatusResponse (line 9) | interface AuthStatusResponse {

FILE: console/src/api/modules/chat.ts
  type ChatUploadResponse (line 11) | interface ChatUploadResponse {
  constant CONSOLE_FILES_PREFIX (line 17) | const CONSOLE_FILES_PREFIX = "/console/files";
  function buildChatUploadHeaders (line 19) | function buildChatUploadHeaders(): HeadersInit {
  function getSelectedAgentId (line 36) | function getSelectedAgentId(): string {

FILE: console/src/api/modules/console.ts
  type PushMessage (line 3) | interface PushMessage {

FILE: console/src/api/modules/security.ts
  type ToolGuardRule (line 3) | interface ToolGuardRule {
  type ToolGuardConfig (line 15) | interface ToolGuardConfig {
  type SkillScannerWhitelistEntry (line 25) | interface SkillScannerWhitelistEntry {
  type SkillScannerMode (line 31) | type SkillScannerMode = "block" | "warn" | "off";
  type SkillScannerConfig (line 33) | interface SkillScannerConfig {
  type BlockedSkillFinding (line 39) | interface BlockedSkillFinding {
  type BlockedSkillRecord (line 48) | interface BlockedSkillRecord {
  type SecurityScanErrorResponse (line 57) | interface SecurityScanErrorResponse {

FILE: console/src/api/modules/skill.ts
  function getStreamApiUrl (line 9) | function getStreamApiUrl(): string {
  function buildHeaders (line 14) | function buildHeaders(): HeadersInit {

FILE: console/src/api/modules/tokenUsage.ts
  type GetTokenUsageParams (line 4) | interface GetTokenUsageParams {
  function buildQuery (line 9) | function buildQuery(params: GetTokenUsageParams): string {

FILE: console/src/api/modules/tools.ts
  type ToolInfo (line 3) | interface ToolInfo {

FILE: console/src/api/modules/userTimezone.ts
  type UserTimezoneConfig (line 3) | interface UserTimezoneConfig {

FILE: console/src/api/modules/workspace.ts
  function buildHeaders (line 5) | function buildHeaders(): HeadersInit {
  function getSelectedAgentId (line 29) | function getSelectedAgentId(): string {
  function generateFallbackFilename (line 45) | function generateFallbackFilename(): string {
  type WorkspaceDownloadResult (line 57) | interface WorkspaceDownloadResult {

FILE: console/src/api/request.ts
  function buildHeaders (line 3) | function buildHeaders(method?: string, extra?: HeadersInit): Headers {
  function request (line 39) | async function request<T = unknown>(

FILE: console/src/api/types/agent.ts
  type AgentRequest (line 1) | interface AgentRequest {
  type AgentsRunningConfig (line 9) | interface AgentsRunningConfig {

FILE: console/src/api/types/agents.ts
  type AgentSummary (line 3) | interface AgentSummary {
  type AgentListResponse (line 10) | interface AgentListResponse {
  type AgentProfileConfig (line 14) | interface AgentProfileConfig {
  type CreateAgentRequest (line 29) | interface CreateAgentRequest {
  type AgentProfileRef (line 36) | interface AgentProfileRef {

FILE: console/src/api/types/channel.ts
  type BaseChannelConfig (line 1) | interface BaseChannelConfig {
  type IMessageChannelConfig (line 12) | interface IMessageChannelConfig extends BaseChannelConfig {
  type DiscordConfig (line 17) | interface DiscordConfig extends BaseChannelConfig {
  type DingTalkConfig (line 23) | interface DingTalkConfig extends BaseChannelConfig {
  type FeishuConfig (line 32) | interface FeishuConfig extends BaseChannelConfig {
  type QQConfig (line 40) | interface QQConfig extends BaseChannelConfig {
  type TelegramConfig (line 45) | interface TelegramConfig extends BaseChannelConfig {
  type MQTTConfig (line 52) | interface MQTTConfig extends BaseChannelConfig {
  type MatrixConfig (line 68) | interface MatrixConfig extends BaseChannelConfig {
  type ConsoleConfig (line 74) | type ConsoleConfig = BaseChannelConfig;
  type VoiceChannelConfig (line 76) | interface VoiceChannelConfig extends BaseChannelConfig {
  type XiaoYiConfig (line 88) | interface XiaoYiConfig extends BaseChannelConfig {
  type ChannelConfig (line 96) | interface ChannelConfig {
  type SingleChannelConfig (line 110) | type SingleChannelConfig =

FILE: console/src/api/types/chat.ts
  type ChatStatus (line 1) | type ChatStatus = "idle" | "running";
  type ChatSpec (line 3) | interface ChatSpec {
  type Message (line 14) | interface Message {
  type ChatHistory (line 20) | interface ChatHistory {
  type ChatDeleteResponse (line 25) | interface ChatDeleteResponse {
  type Session (line 31) | type Session = ChatSpec;

FILE: console/src/api/types/cronjob.ts
  type CronJobSchedule (line 1) | interface CronJobSchedule {
  type CronJobTarget (line 7) | interface CronJobTarget {
  type CronJobDispatch (line 12) | interface CronJobDispatch {
  type CronJobRuntime (line 20) | interface CronJobRuntime {
  type CronJobRequest (line 26) | interface CronJobRequest {
  type CronJobSpecInput (line 33) | interface CronJobSpecInput {
  type CronJobSpecOutput (line 46) | type CronJobSpecOutput = CronJobSpecInput;
  type CronJobView (line 48) | interface CronJobView extends CronJobSpecOutput {
  type CronJobSpecInputLegacy (line 55) | type CronJobSpecInputLegacy = Record<string, unknown>;
  type CronJobSpecOutputLegacy (line 56) | type CronJobSpecOutputLegacy = Record<string, unknown>;
  type CronJobViewLegacy (line 57) | type CronJobViewLegacy = Record<string, unknown>;

FILE: console/src/api/types/env.ts
  type EnvVar (line 1) | interface EnvVar {

FILE: console/src/api/types/heartbeat.ts
  type ActiveHoursConfig (line 1) | interface ActiveHoursConfig {
  type HeartbeatConfig (line 6) | interface HeartbeatConfig {

FILE: console/src/api/types/mcp.ts
  type MCPClientInfo (line 5) | interface MCPClientInfo {
  type MCPClientCreateRequest (line 30) | interface MCPClientCreateRequest {
  type MCPClientUpdateRequest (line 58) | interface MCPClientUpdateRequest {

FILE: console/src/api/types/provider.ts
  type ModelInfo (line 1) | interface ModelInfo {
  type ProviderInfo (line 6) | interface ProviderInfo {
  type ProviderConfigRequest (line 30) | interface ProviderConfigRequest {
  type ModelSlotConfig (line 37) | interface ModelSlotConfig {
  type ActiveModelsInfo (line 42) | interface ActiveModelsInfo {
  type ModelSlotRequest (line 46) | interface ModelSlotRequest {
  type CreateCustomProviderRequest (line 53) | interface CreateCustomProviderRequest {
  type AddModelRequest (line 62) | interface AddModelRequest {
  type LocalModelResponse (line 69) | interface LocalModelResponse {
  type DownloadModelRequest (line 80) | interface DownloadModelRequest {
  type DownloadTaskResponse (line 87) | interface DownloadTaskResponse {
  type OllamaModelResponse (line 100) | interface OllamaModelResponse {
  type OllamaDownloadRequest (line 107) | interface OllamaDownloadRequest {
  type OllamaDownloadTaskResponse (line 111) | interface OllamaDownloadTaskResponse {
  type TestConnectionResponse (line 121) | interface TestConnectionResponse {
  type TestProviderRequest (line 126) | interface TestProviderRequest {
  type TestModelRequest (line 133) | interface TestModelRequest {
  type DiscoverModelsResponse (line 137) | interface DiscoverModelsResponse {

FILE: console/src/api/types/skill.ts
  type SkillSpec (line 1) | interface SkillSpec {
  type HubSkillSpec (line 10) | interface HubSkillSpec {
  type Skill (line 19) | interface Skill {

FILE: console/src/api/types/tokenUsage.ts
  type TokenUsageStats (line 2) | interface TokenUsageStats {
  type TokenUsageSummary (line 10) | interface TokenUsageSummary {

FILE: console/src/api/types/workspace.ts
  type MdFileInfo (line 1) | interface MdFileInfo {
  type MdFileContent (line 9) | interface MdFileContent {
  type MarkdownFile (line 13) | interface MarkdownFile extends MdFileInfo {
  type DailyMemoryFile (line 18) | interface DailyMemoryFile extends MdFileInfo {

FILE: console/src/components/AgentSelector/index.tsx
  function AgentSelector (line 9) | function AgentSelector() {

FILE: console/src/components/ConsoleCronBubble/index.tsx
  constant POLL_INTERVAL_MS (line 6) | const POLL_INTERVAL_MS = 2500;
  constant AUTO_DISMISS_MS (line 7) | const AUTO_DISMISS_MS = 8000;
  constant MAX_SEEN_IDS (line 8) | const MAX_SEEN_IDS = 500;
  constant MAX_VISIBLE_BUBBLES (line 9) | const MAX_VISIBLE_BUBBLES = 4;
  constant MAX_NEW_PER_POLL (line 10) | const MAX_NEW_PER_POLL = 2;
  constant TITLE_BLINK_PREFIX (line 11) | const TITLE_BLINK_PREFIX = "\u2022 ";
  type BubbleItem (line 13) | interface BubbleItem extends PushMessage {
  function ConsoleCronBubble (line 17) | function ConsoleCronBubble() {

FILE: console/src/components/LanguageSwitcher.tsx
  function LanguageSwitcher (line 6) | function LanguageSwitcher() {

FILE: console/src/components/MarkdownCopy/MarkdownCopy.tsx
  type MarkdownCopyProps (line 10) | interface MarkdownCopyProps {
  function MarkdownCopy (line 43) | function MarkdownCopy({

FILE: console/src/components/ThemeToggleButton/index.tsx
  function ThemeToggleButton (line 11) | function ThemeToggleButton() {

FILE: console/src/constants/timezone.ts
  constant TIMEZONE_OPTIONS (line 1) | const TIMEZONE_OPTIONS = [

FILE: console/src/contexts/ThemeContext.tsx
  type ThemeMode (line 10) | type ThemeMode = "light" | "dark" | "system";
  type ResolvedTheme (line 11) | type ResolvedTheme = "light" | "dark";
  constant STORAGE_KEY (line 13) | const STORAGE_KEY = "copaw-theme";
  type ThemeContextValue (line 15) | interface ThemeContextValue {
  function getInitialMode (line 32) | function getInitialMode(): ThemeMode {
  function resolveIsDark (line 44) | function resolveIsDark(mode: ThemeMode): boolean {
  function ThemeProvider (line 51) | function ThemeProvider({ children }: { children: ReactNode }) {
  function useTheme (line 102) | function useTheme(): ThemeContextValue {

FILE: console/src/layouts/Header.tsx
  type HeaderProps (line 24) | interface HeaderProps {
  function Header (line 28) | function Header({ selectedKey }: HeaderProps) {

FILE: console/src/layouts/MainLayout/index.tsx
  function MainLayout (line 45) | function MainLayout() {

FILE: console/src/layouts/Sidebar.tsx
  type SidebarProps (line 64) | interface SidebarProps {
  function CopyButton (line 70) | function CopyButton({ text }: { text: string }) {
  function Sidebar (line 100) | function Sidebar({ selectedKey }: SidebarProps) {

FILE: console/src/layouts/constants.ts
  constant PYPI_URL (line 3) | const PYPI_URL = "https://pypi.org/pypi/copaw/json";
  constant GITHUB_URL (line 5) | const GITHUB_URL = "https://github.com/agentscope-ai/CoPaw" as const;
  constant ONE_HOUR_MS (line 9) | const ONE_HOUR_MS = 60 * 60 * 1000;
  constant DEFAULT_OPEN_KEYS (line 13) | const DEFAULT_OPEN_KEYS = [
  constant KEY_TO_PATH (line 20) | const KEY_TO_PATH: Record<string, string> = {
  constant KEY_TO_LABEL (line 39) | const KEY_TO_LABEL: Record<string, string> = {
  constant UPDATE_MD (line 98) | const UPDATE_MD: Record<string, string> = {

FILE: console/src/pages/Agent/Config/components/ContextManagementCard.tsx
  type ContextManagementCardProps (line 6) | interface ContextManagementCardProps {
  function ContextManagementCard (line 11) | function ContextManagementCard({

FILE: console/src/pages/Agent/Config/components/PageHeader.tsx
  function PageHeader (line 4) | function PageHeader() {

FILE: console/src/pages/Agent/Config/components/ReactAgentCard.tsx
  constant LANGUAGE_OPTIONS (line 6) | const LANGUAGE_OPTIONS = [
  type ReactAgentCardProps (line 12) | interface ReactAgentCardProps {
  function ReactAgentCard (line 21) | function ReactAgentCard({

FILE: console/src/pages/Agent/Config/components/SliderWithValue.tsx
  type SliderWithValueProps (line 3) | interface SliderWithValueProps {
  function SliderWithValue (line 12) | function SliderWithValue({

FILE: console/src/pages/Agent/Config/index.tsx
  function AgentConfigPage (line 12) | function AgentConfigPage() {

FILE: console/src/pages/Agent/Config/useAgentConfig.tsx
  function useAgentConfig (line 7) | function useAgentConfig() {

FILE: console/src/pages/Agent/MCP/components/MCPClientCard.tsx
  type MCPClientCardProps (line 10) | interface MCPClientCardProps {
  function MCPClientCard (line 20) | function MCPClientCard({

FILE: console/src/pages/Agent/MCP/components/MCPClientDrawer.tsx
  type MCPClientDrawerProps (line 6) | interface MCPClientDrawerProps {
  function MCPClientDrawer (line 27) | function MCPClientDrawer({

FILE: console/src/pages/Agent/MCP/index.tsx
  type MCPTransport (line 8) | type MCPTransport = "stdio" | "streamable_http" | "sse";
  function normalizeTransport (line 10) | function normalizeTransport(raw?: unknown): MCPTransport | undefined {
  function normalizeClientData (line 28) | function normalizeClientData(key: string, rawData: any) {
  function MCPPage (line 52) | function MCPPage() {

FILE: console/src/pages/Agent/MCP/useMCP.ts
  function useMCP (line 8) | function useMCP() {

FILE: console/src/pages/Agent/Skills/components/SkillCard.tsx
  type SkillCardProps (line 17) | interface SkillCardProps {
  function SkillCard (line 77) | function SkillCard({

FILE: console/src/pages/Agent/Skills/components/SkillDrawer.tsx
  function parseFrontmatter (line 14) | function parseFrontmatter(content: string): Record<string, string> | null {
  type SkillDrawerProps (line 36) | interface SkillDrawerProps {
  function SkillDrawer (line 45) | function SkillDrawer({

FILE: console/src/pages/Agent/Skills/index.tsx
  function SkillsPage (line 14) | function SkillsPage() {

FILE: console/src/pages/Agent/Skills/useSkills.ts
  function tryParseScanError (line 10) | function tryParseScanError(error: unknown): SecurityScanErrorResponse | ...
  function useSkills (line 26) | function useSkills() {

FILE: console/src/pages/Agent/Tools/index.tsx
  function ToolsPage (line 8) | function ToolsPage() {

FILE: console/src/pages/Agent/Tools/useTools.ts
  function useTools (line 8) | function useTools() {

FILE: console/src/pages/Agent/Workspace/components/FileEditor.tsx
  type FileEditorProps (line 10) | interface FileEditorProps {

FILE: console/src/pages/Agent/Workspace/components/FileItem.tsx
  type FileItemProps (line 15) | interface FileItemProps {

FILE: console/src/pages/Agent/Workspace/components/FileListPanel.tsx
  type FileListPanelProps (line 22) | interface FileListPanelProps {

FILE: console/src/pages/Agent/Workspace/index.tsx
  function WorkspacePage (line 9) | function WorkspacePage() {

FILE: console/src/pages/Chat/ModelSelector/index.tsx
  type EligibleProvider (line 15) | interface EligibleProvider {
  function ModelSelector (line 21) | function ModelSelector() {

FILE: console/src/pages/Chat/OptionsPanel/FormItem.tsx
  type FormItemProps (line 4) | interface FormItemProps {
  function FormItem (line 20) | function FormItem(props: FormItemProps) {

FILE: console/src/pages/Chat/OptionsPanel/OptionsEditor.tsx
  type OptionsEditorProps (line 31) | interface OptionsEditorProps {

FILE: console/src/pages/Chat/OptionsPanel/defaultConfig.ts
  function getDefaultConfig (line 38) | function getDefaultConfig(t: TFunction) {
  type DefaultConfig (line 56) | type DefaultConfig = typeof defaultConfig;

FILE: console/src/pages/Chat/OptionsPanel/index.tsx
  type OptionsPanelProps (line 6) | interface OptionsPanelProps {
  function OptionsPanel (line 11) | function OptionsPanel(props: OptionsPanelProps) {

FILE: console/src/pages/Chat/index.tsx
  type CopyableContent (line 31) | type CopyableContent = {
  type CopyableMessage (line 37) | type CopyableMessage = {
  type CopyableResponse (line 42) | type CopyableResponse = {
  type RuntimeUiMessage (line 46) | type RuntimeUiMessage = IAgentScopeRuntimeWebUIMessage & {
  type StreamResponseData (line 56) | type StreamResponseData = {
  type RuntimeLoadingBridgeApi (line 63) | type RuntimeLoadingBridgeApi = {
  type CustomWindow (line 68) | interface CustomWindow extends Window {
  function extractCopyableText (line 76) | function extractCopyableText(response: CopyableResponse): string {
  function copyText (line 108) | async function copyText(text: string) {
  function buildModelError (line 135) | function buildModelError(): Response {
  function cloneRuntimeMessages (line 145) | function cloneRuntimeMessages(
  function cloneValue (line 151) | function cloneValue<T>(value: T): T {
  function isFinalResponseStatus (line 155) | function isFinalResponseStatus(status?: string): boolean {
  function hasRenderableOutput (line 163) | function hasRenderableOutput(response: StreamResponseData): boolean {
  function getResponseCardData (line 174) | function getResponseCardData(
  function getStreamingAssistantMessageId (line 188) | function getStreamingAssistantMessageId(
  function RuntimeLoadingBridge (line 206) | function RuntimeLoadingBridge({
  function ChatPage (line 240) | function ChatPage() {

FILE: console/src/pages/Chat/sessionApi/index.ts
  constant DEFAULT_USER_ID (line 16) | const DEFAULT_USER_ID = "default";
  constant DEFAULT_CHANNEL (line 17) | const DEFAULT_CHANNEL = "console";
  constant DEFAULT_SESSION_NAME (line 18) | const DEFAULT_SESSION_NAME = "New Chat";
  constant ROLE_TOOL (line 19) | const ROLE_TOOL = "tool";
  constant ROLE_USER (line 20) | const ROLE_USER = "user";
  constant ROLE_ASSISTANT (line 21) | const ROLE_ASSISTANT = "assistant";
  constant TYPE_PLUGIN_CALL_OUTPUT (line 22) | const TYPE_PLUGIN_CALL_OUTPUT = "plugin_call_output";
  constant CARD_RESPONSE (line 24) | const CARD_RESPONSE = "AgentScopeRuntimeResponseCard";
  type CustomWindow (line 30) | interface CustomWindow extends Window {
  type ContentItem (line 43) | interface ContentItem {
  type OutputMessage (line 50) | interface OutputMessage extends Omit<Message, "role"> {
  type ExtendedSession (line 60) | interface ExtendedSession extends IAgentScopeRuntimeWebUISession {
  type RuntimeResponseCard (line 71) | interface RuntimeResponseCard {
  constant LIVE_MESSAGE_STATUSES (line 77) | const LIVE_MESSAGE_STATUSES = new Set(["generating", "created", "in_prog...
  function generateId (line 126) | function generateId(): string {
  function toDisplayUrl (line 131) | function toDisplayUrl(url: string | undefined): string {
  function contentToRequestParts (line 138) | function contentToRequestParts(
  function buildUserCard (line 186) | function buildUserCard(msg: Message): IAgentScopeRuntimeWebUIMessage {
  class SessionApi (line 317) | class SessionApi implements IAgentScopeRuntimeWebUISessionAPI {
    method findSessionIndexByAnyId (line 321) | private findSessionIndexByAnyId(sessionId?: string | null): number {
    method findSessionByAnyId (line 336) | private findSessionByAnyId(
    method cacheSession (line 343) | private cacheSession(session: ExtendedSession): void {
    method setChatRef (line 394) | setChatRef(ref: RefObject<IAgentScopeRuntimeWebUIRef> | null): void {
    method triggerReconnectSubmit (line 403) | triggerReconnectSubmit(): void {
    method resolveRealIdInBackground (line 417) | private resolveRealIdInBackground(tempId: string): void {
    method createEmptySession (line 436) | private createEmptySession(sessionId: string): ExtendedSession {
    method updateWindowVariables (line 451) | private updateWindowVariables(session: ExtendedSession): void {
    method getLocalSession (line 457) | private getLocalSession(sessionId: string): IAgentScopeRuntimeWebUISes...
    method getRealIdForSession (line 470) | getRealIdForSession(sessionId: string): string | null {
    method hasLiveMessagesForSession (line 475) | hasLiveMessagesForSession(sessionId?: string | null): boolean {
    method getSessionList (line 479) | async getSessionList() {
    method getSession (line 545) | async getSession(sessionId: string) {
    method _doGetSession (line 565) | private async _doGetSession(
    method updateSession (line 653) | async updateSession(session: Partial<IAgentScopeRuntimeWebUISession>) {
    method createSession (line 698) | async createSession(session: Partial<IAgentScopeRuntimeWebUISession>) {
    method removeSession (line 714) | async removeSession(session: Partial<IAgentScopeRuntimeWebUISession>) {

FILE: console/src/pages/Control/Channels/components/ChannelCard.tsx
  type ChannelCardProps (line 6) | interface ChannelCardProps {
  function ChannelCard (line 15) | function ChannelCard({

FILE: console/src/pages/Control/Channels/components/ChannelDrawer.tsx
  constant WECOM_SDK_URL (line 20) | const WECOM_SDK_URL =
  constant WECOM_SOURCE (line 23) | const WECOM_SOURCE = "copaw";
  type WecomBotInfo (line 25) | interface WecomBotInfo {
  type WecomAuthError (line 30) | interface WecomAuthError {
  type Window (line 37) | interface Window {
  constant CHANNELS_WITH_ACCESS_CONTROL (line 48) | const CHANNELS_WITH_ACCESS_CONTROL: ChannelKey[] = [
  constant CHANNEL_DOC_EN_URLS (line 59) | const CHANNEL_DOC_EN_URLS: Partial<Record<ChannelKey, string>> = {
  constant CHANNEL_DOC_ZH_URLS (line 77) | const CHANNEL_DOC_ZH_URLS: Partial<Record<ChannelKey, string>> = {
  constant TWILIO_CONSOLE_URL (line 93) | const TWILIO_CONSOLE_URL = "https://console.twilio.com";
  constant BASE_FIELDS (line 95) | const BASE_FIELDS = [
  type ChannelDrawerProps (line 103) | interface ChannelDrawerProps {
  function ChannelDrawer (line 115) | function ChannelDrawer({

FILE: console/src/pages/Control/Channels/components/constants.ts
  type ChannelKey (line 2) | type ChannelKey = string;
  constant CHANNEL_LABELS (line 5) | const CHANNEL_LABELS: Record<string, string> = {
  function getChannelLabel (line 22) | function getChannelLabel(key: string): string {

FILE: console/src/pages/Control/Channels/index.tsx
  type FilterType (line 15) | type FilterType = "all" | "builtin" | "custom";
  function ChannelsPage (line 17) | function ChannelsPage() {

FILE: console/src/pages/Control/Channels/useChannels.ts
  function useChannels (line 5) | function useChannels() {

FILE: console/src/pages/Control/CronJobs/components/JobDrawer.tsx
  type CronJob (line 18) | type CronJob = CronJobSpecOutput;
  type JobDrawerProps (line 20) | interface JobDrawerProps {
  function JobDrawer (line 29) | function JobDrawer({

FILE: console/src/pages/Control/CronJobs/components/columns.tsx
  type CronJob (line 10) | type CronJob = CronJobSpecOutput;
  type ColumnHandlers (line 12) | interface ColumnHandlers {

FILE: console/src/pages/Control/CronJobs/components/constants.ts
  constant DEFAULT_FORM_VALUES (line 5) | const DEFAULT_FORM_VALUES = {

FILE: console/src/pages/Control/CronJobs/components/parseCron.ts
  type CronType (line 10) | type CronType = "hourly" | "daily" | "weekly" | "custom";
  type CronParts (line 12) | interface CronParts {
  constant CRON_RE (line 20) | const CRON_RE = /^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)$/;
  constant INTEGER_RE (line 21) | const INTEGER_RE = /^\d+$/;
  constant ORDERED_DAYS (line 27) | const ORDERED_DAYS = ["mon", "tue", "wed", "thu", "fri", "sat", "sun"] a...
  type DayName (line 28) | type DayName = (typeof ORDERED_DAYS)[number];
  constant NUM_TO_NAME (line 30) | const NUM_TO_NAME: Record<string, DayName> = {
  constant VALID_NAMES (line 41) | const VALID_NAMES = new Set<DayName>(ORDERED_DAYS);
  function isDayName (line 43) | function isDayName(value: string): value is DayName {
  function parseCron (line 55) | function parseCron(cron: string): CronParts {
  function parsePlainCronNumber (line 104) | function parsePlainCronNumber(
  function serializeCron (line 124) | function serializeCron(parts: CronParts): string {
  function parseDaysOfWeek (line 158) | function parseDaysOfWeek(dayOfWeek: string): string[] {
  function serializeDaysOfWeek (line 221) | function serializeDaysOfWeek(daysOfWeek?: string[]): string {

FILE: console/src/pages/Control/CronJobs/index.tsx
  type CronJob (line 16) | type CronJob = CronJobSpecOutput;
  function CronJobsPage (line 18) | function CronJobsPage() {

FILE: console/src/pages/Control/CronJobs/useCronJobs.ts
  type CronJob (line 7) | type CronJob = CronJobSpecOutput;
  function useCronJobs (line 9) | function useCronJobs() {

FILE: console/src/pages/Control/Heartbeat/index.tsx
  constant TIME_FORMAT (line 23) | const TIME_FORMAT = "HH:mm";
  function TimePickerHHmm (line 26) | function TimePickerHHmm({
  type HeartbeatFormValues (line 51) | type HeartbeatFormValues = Omit<HeartbeatConfig, "every"> & {
  constant TARGET_OPTIONS (line 60) | const TARGET_OPTIONS = [
  constant EVERY_UNIT_OPTIONS (line 65) | const EVERY_UNIT_OPTIONS: { value: EveryUnit; labelKey: string }[] = [
  function HeartbeatPage (line 70) | function HeartbeatPage() {

FILE: console/src/pages/Control/Heartbeat/parseEvery.ts
  constant EVERY_RE (line 6) | const EVERY_RE =
  type EveryUnit (line 9) | type EveryUnit = "m" | "h";
  type EveryParts (line 11) | interface EveryParts {
  function parseEvery (line 16) | function parseEvery(every: string): EveryParts {
  function serializeEvery (line 38) | function serializeEvery(parts: EveryParts): string {

FILE: console/src/pages/Control/Sessions/components/FilterBar.tsx
  type FilterBarProps (line 5) | interface FilterBarProps {
  function FilterBar (line 13) | function FilterBar({

FILE: console/src/pages/Control/Sessions/components/SessionDrawer.tsx
  type SessionDrawerProps (line 7) | interface SessionDrawerProps {
  function SessionDrawer (line 16) | function SessionDrawer({

FILE: console/src/pages/Control/Sessions/components/columns.tsx
  type ColumnHandlers (line 7) | interface ColumnHandlers {

FILE: console/src/pages/Control/Sessions/components/constants.ts
  type Session (line 3) | interface Session extends ChatSpec {
  constant CHANNEL_COLORS (line 7) | const CHANNEL_COLORS: Record<string, string> = {

FILE: console/src/pages/Control/Sessions/index.tsx
  function SessionsPage (line 21) | function SessionsPage() {

FILE: console/src/pages/Control/Sessions/useSessions.ts
  function useSessions (line 7) | function useSessions() {

FILE: console/src/pages/Login/index.tsx
  function LoginPage (line 9) | function LoginPage() {

FILE: console/src/pages/Settings/Agents/components/AgentModal.tsx
  type AgentModalProps (line 5) | interface AgentModalProps {
  function AgentModal (line 13) | function AgentModal({

FILE: console/src/pages/Settings/Agents/components/AgentTable.tsx
  type AgentTableProps (line 9) | interface AgentTableProps {
  function AgentTable (line 16) | function AgentTable({

FILE: console/src/pages/Settings/Agents/components/PageHeader.tsx
  type PageHeaderProps (line 3) | interface PageHeaderProps {
  function PageHeader (line 10) | function PageHeader({

FILE: console/src/pages/Settings/Agents/index.tsx
  function AgentsPage (line 11) | function AgentsPage() {

FILE: console/src/pages/Settings/Agents/useAgents.ts
  type UseAgentsReturn (line 8) | interface UseAgentsReturn {
  function useAgents (line 16) | function useAgents(): UseAgentsReturn {

FILE: console/src/pages/Settings/Environments/components/AddButton.tsx
  type AddButtonProps (line 5) | interface AddButtonProps {
  function AddButton (line 10) | function AddButton({ onClick, className }: AddButtonProps) {

FILE: console/src/pages/Settings/Environments/components/EmptyState.tsx
  type EmptyStateProps (line 4) | interface EmptyStateProps {
  function EmptyState (line 8) | function EmptyState({ className }: EmptyStateProps) {

FILE: console/src/pages/Settings/Environments/components/EnvRow.tsx
  type Row (line 8) | interface Row {
  type EnvRowProps (line 14) | interface EnvRowProps {
  function EnvRow (line 25) | function EnvRow({

FILE: console/src/pages/Settings/Environments/components/PageHeader.tsx
  type PageHeaderProps (line 4) | interface PageHeaderProps {
  function PageHeader (line 8) | function PageHeader({ className }: PageHeaderProps) {

FILE: console/src/pages/Settings/Environments/components/Toolbar.tsx
  type ToolbarProps (line 6) | interface ToolbarProps {
  function Toolbar (line 21) | function Toolbar({

FILE: console/src/pages/Settings/Environments/index.tsx
  function shiftIndices (line 22) | function shiftIndices(prev: Set<number>, removedIdx: number): Set<number> {
  function EnvironmentsPage (line 35) | function EnvironmentsPage() {

FILE: console/src/pages/Settings/Environments/useEnvVars.ts
  function useEnvVars (line 5) | function useEnvVars() {

FILE: console/src/pages/Settings/Models/components/ModelManageModal.tsx
  constant POLL_INTERVAL_MS (line 29) | const POLL_INTERVAL_MS = 3000;
  type ModelManageModalProps (line 31) | interface ModelManageModalProps {
  function formatFileSize (line 38) | function formatFileSize(bytes: number): string {
  function ModelManageModal (line 45) | function ModelManageModal({

FILE: console/src/pages/Settings/Models/components/cards/LocalProviderCard.tsx
  type LocalProviderCardProps (line 9) | interface LocalProviderCardProps {
  function LocalProviderCard (line 17) | function LocalProviderCard({

FILE: console/src/pages/Settings/Models/components/cards/ProviderCard.tsx
  type ProviderCardProps (line 5) | interface ProviderCardProps {
  function ProviderCard (line 14) | function ProviderCard({

FILE: console/src/pages/Settings/Models/components/cards/RemoteProviderCard.tsx
  type RemoteProviderCardProps (line 15) | interface RemoteProviderCardProps {
  function RemoteProviderCard (line 24) | function RemoteProviderCard({

FILE: console/src/pages/Settings/Models/components/modals/CustomProviderModal.tsx
  type CustomProviderModalProps (line 6) | interface CustomProviderModalProps {
  function CustomProviderModal (line 12) | function CustomProviderModal({

FILE: console/src/pages/Settings/Models/components/modals/LocalModelManageModal.tsx
  constant POLL_INTERVAL_MS (line 27) | const POLL_INTERVAL_MS = 3000;
  type LocalModelManageModalProps (line 29) | interface LocalModelManageModalProps {
  function formatFileSize (line 36) | function formatFileSize(bytes: number): string {
  function LocalModelManageModal (line 43) | function LocalModelManageModal({

FILE: console/src/pages/Settings/Models/components/modals/ModelManageModal.tsx
  type ModelManageModalProps (line 6) | interface ModelManageModalProps {
  function ModelManageModal (line 13) | function ModelManageModal({

FILE: console/src/pages/Settings/Models/components/modals/OllamaModelManageModal.tsx
  constant POLL_INTERVAL_MS (line 20) | const POLL_INTERVAL_MS = 3000;
  type OllamaModelManageModalProps (line 22) | interface OllamaModelManageModalProps {
  function formatFileSize (line 29) | function formatFileSize(bytes: number): string {
  function OllamaModelManageModal (line 36) | function OllamaModelManageModal({

FILE: console/src/pages/Settings/Models/components/modals/ProviderConfigModal.tsx
  type ProviderConfigFormValues (line 17) | interface ProviderConfigFormValues
  type JsonCodeEditorProps (line 22) | interface JsonCodeEditorProps {
  function highlightJson (line 29) | function highlightJson(text: string): ReactNode[] {
  function JsonCodeEditor (line 103) | function JsonCodeEditor({
  type ProviderConfigModalProps (line 244) | interface ProviderConfigModalProps {
  function ProviderConfigModal (line 263) | function ProviderConfigModal({

FILE: console/src/pages/Settings/Models/components/modals/RemoteModelManageModal.tsx
  type RemoteModelManageModalProps (line 22) | interface RemoteModelManageModalProps {
  function RemoteModelManageModal (line 29) | function RemoteModelManageModal({

FILE: console/src/pages/Settings/Models/components/sections/LoadingState.tsx
  type LoadingStateProps (line 5) | interface LoadingStateProps {
  function LoadingState (line 12) | function LoadingState({

FILE: console/src/pages/Settings/Models/components/sections/ModelsSection.tsx
  type ModelsSectionProps (line 9) | interface ModelsSectionProps {
  function ModelsSection (line 30) | function ModelsSection({

FILE: console/src/pages/Settings/Models/components/sections/PageHeader.tsx
  type PageHeaderProps (line 3) | interface PageHeaderProps {
  function PageHeader (line 9) | function PageHeader({ title, description, className }: PageHeaderProps) {

FILE: console/src/pages/Settings/Models/index.tsx
  function ModelsPage (line 19) | function ModelsPage() {

FILE: console/src/pages/Settings/Models/useProviders.ts
  function useProviders (line 6) | function useProviders() {

FILE: console/src/pages/Settings/Security/components/PageHeader.tsx
  type PageHeaderProps (line 4) | interface PageHeaderProps {
  function PageHeader (line 8) | function PageHeader({ className }: PageHeaderProps) {

FILE: console/src/pages/Settings/Security/components/PreviewModal.tsx
  constant SEVERITY_COLORS (line 5) | const SEVERITY_COLORS: Record<string, string> = {
  type PreviewModalProps (line 13) | interface PreviewModalProps {
  function PreviewModal (line 18) | function PreviewModal({ rule, onClose }: PreviewModalProps) {

FILE: console/src/pages/Settings/Security/components/RuleModal.tsx
  constant SEVERITY_OPTIONS (line 6) | const SEVERITY_OPTIONS = ["CRITICAL", "HIGH", "MEDIUM", "LOW", "INFO"];
  constant CATEGORY_OPTIONS (line 7) | const CATEGORY_OPTIONS = [
  constant BUILTIN_TOOLS (line 17) | const BUILTIN_TOOLS = [
  type RuleModalProps (line 32) | interface RuleModalProps {
  function RuleModal (line 41) | function RuleModal({

FILE: console/src/pages/Settings/Security/components/RuleTable.tsx
  constant SEVERITY_COLORS (line 8) | const SEVERITY_COLORS: Record<string, string> = {
  type RuleTableProps (line 16) | interface RuleTableProps {
  function RuleTable (line 25) | function RuleTable({

FILE: console/src/pages/Settings/Security/components/SkillScannerSection.tsx
  function FindingsModal (line 28) | function FindingsModal({
  function SkillScannerSection (line 84) | function SkillScannerSection() {

FILE: console/src/pages/Settings/Security/index.tsx
  constant BUILTIN_TOOLS (line 28) | const BUILTIN_TOOLS = [
  function SecurityPage (line 43) | function SecurityPage() {

FILE: console/src/pages/Settings/Security/useSkillScanner.ts
  function useSkillScanner (line 9) | function useSkillScanner() {

FILE: console/src/pages/Settings/Security/useToolGuard.ts
  type MergedRule (line 8) | interface MergedRule extends ToolGuardRule {
  function useToolGuard (line 13) | function useToolGuard() {

FILE: console/src/pages/Settings/TokenUsage/components/EmptyState.tsx
  type EmptyStateProps (line 3) | interface EmptyStateProps {
  function EmptyState (line 8) | function EmptyState({ message, className }: EmptyStateProps) {

FILE: console/src/pages/Settings/TokenUsage/components/LoadingState.tsx
  type LoadingStateProps (line 5) | interface LoadingStateProps {
  function LoadingState (line 12) | function LoadingState({

FILE: console/src/pages/Settings/TokenUsage/components/PageHeader.tsx
  type PageHeaderProps (line 3) | interface PageHeaderProps {
  function PageHeader (line 9) | function PageHeader({ title, description, className }: PageHeaderProps) {

FILE: console/src/pages/Settings/TokenUsage/index.tsx
  type ByModelRow (line 16) | type ByModelRow = TokenUsageStats & { key: string };
  type ByDateRow (line 17) | type ByDateRow = TokenUsageStats & { key: string; date: string };
  function TokenUsagePage (line 19) | function TokenUsagePage() {

FILE: console/src/pages/Settings/VoiceTranscription/index.tsx
  type TranscriptionProvider (line 8) | interface TranscriptionProvider {
  type LocalWhisperStatus (line 14) | interface LocalWhisperStatus {
  function VoiceTranscriptionPage (line 20) | function VoiceTranscriptionPage() {

FILE: console/src/stores/agentStore.ts
  type AgentStore (line 5) | interface AgentStore {

FILE: console/src/utils/formatNumber.ts
  function formatCompact (line 5) | function formatCompact(n: number): string {

FILE: console/src/vite-env.d.ts
  type PyWebViewAPI (line 8) | interface PyWebViewAPI {
  type Window (line 13) | interface Window {

FILE: scripts/pack/build_common.py
  function _conda_exe (line 35) | def _conda_exe() -> str:
  function _run (line 43) | def _run(
  function _pick_wheel (line 55) | def _pick_wheel(wheel_arg: str | None) -> Path:
  function main (line 76) | def main() -> int:

FILE: scripts/run_tests.py
  class Colors (line 33) | class Colors:
  function print_info (line 43) | def print_info(message: str) -> None:
  function print_success (line 48) | def print_success(message: str) -> None:
  function print_error (line 53) | def print_error(message: str) -> None:
  function print_warning (line 58) | def print_warning(message: str) -> None:
  function check_pytest (line 63) | def check_pytest() -> bool:
  function run_unit_tests (line 76) | def run_unit_tests(
  function run_integrated_tests (line 123) | def run_integrated_tests(
  function run_pytest (line 148) | def run_pytest(
  function main (line 175) | def main() -> int:

FILE: src/copaw/agents/__init__.py
  function __getattr__ (line 24) | def __getattr__(name: str):

FILE: src/copaw/agents/command_handler.py
  class ConversationCommandHandlerMixin (line 23) | class ConversationCommandHandlerMixin:
    method is_conversation_command (line 45) | def is_conversation_command(self, query: str | None) -> bool:
  class CommandHandler (line 59) | class CommandHandler(ConversationCommandHandlerMixin):
    method __init__ (line 62) | def __init__(
    method _get_agent_config (line 82) | def _get_agent_config(self):
    method is_command (line 90) | def is_command(self, query: str | None) -> bool:
    method _make_system_msg (line 94) | async def _make_system_msg(self, text: str) -> Msg:
    method _has_memory_manager (line 109) | def _has_memory_manager(self) -> bool:
    method _process_compact (line 113) | async def _process_compact(
    method _process_new (line 147) | async def _process_new(self, messages: list[Msg], _args: str = "") -> ...
    method _process_clear (line 174) | async def _process_clear(
    method _process_compact_str (line 188) | async def _process_compact_str(
    method _process_history (line 205) | async def _process_history(
    method _process_await_summary (line 232) | async def _process_await_summary(
    method _process_message (line 260) | async def _process_message(
    method _process_dump_history (line 328) | async def _process_dump_history(
    method _process_load_history (line 384) | async def _process_load_history(
    method handle_conversation_command (line 460) | async def handle_conversation_command(self, query: str) -> Msg:
    method handle_command (line 486) | async def handle_command(self, query: str) -> Msg:

FILE: src/copaw/agents/hooks/bootstrap.py
  class BootstrapHook (line 20) | class BootstrapHook:
    method __init__ (line 28) | def __init__(
    method __call__ (line 42) | async def __call__(

FILE: src/copaw/agents/hooks/memory_compaction.py
  class MemoryCompactionHook (line 28) | class MemoryCompactionHook:
    method __init__ (line 36) | def __init__(self, memory_manager: "MemoryManager"):
    method _print_status_message (line 45) | async def _print_status_message(
    method __call__ (line 62) | async def __call__(

FILE: src/copaw/agents/memory/agent_md_manager.py
  class AgentMdManager (line 8) | class AgentMdManager:
    method __init__ (line 12) | def __init__(self, working_dir: str | Path):
    method list_working_mds (line 19) | def list_working_mds(self) -> list[dict]:
    method read_working_md (line 49) | def read_working_md(self, md_name: str) -> str:
    method write_working_md (line 64) | def write_working_md(self, md_name: str, content: str):
    method list_memory_mds (line 72) | def list_memory_mds(self) -> list[dict]:
    method read_memory_md (line 102) | def read_memory_md(self, md_name: str) -> str:
    method write_memory_md (line 117) | def write_memory_md(self, md_name: str, content: str):

FILE: src/copaw/agents/memory/memory_manager.py
  class ReMeLight (line 36) | class ReMeLight:  # type: ignore
    method start (line 39) | async def start(self) -> None:
  class MemoryManager (line 43) | class MemoryManager(ReMeLight):
    method __init__ (line 53) | def __init__(
    method mask_key (line 141) | def mask_key(key: str) -> str:
    method get_embedding_config (line 149) | def get_embedding_config(self) -> dict:
    method prepare_model_formatter (line 169) | def prepare_model_formatter(self) -> None:
    method restart_embedding_model (line 188) | async def restart_embedding_model(self):
    method compact_memory (line 198) | async def compact_memory(
    method summary_memory (line 230) | async def summary_memory(self, messages: list[Msg], **_kwargs) -> str:
    method memory_search (line 259) | async def memory_search(
    method get_in_memory_memory (line 295) | def get_in_memory_memory(self, **_kwargs):

FILE: src/copaw/agents/model_factory.py
  function _file_url_to_path (line 40) | def _file_url_to_path(url: str) -> str:
  function _get_formatter_for_chat_model (line 64) | def _get_formatter_for_chat_model(
  function _create_file_block_support_formatter (line 82) | def _create_file_block_support_formatter(
  function _strip_top_level_message_name (line 266) | def _strip_top_level_message_name(
  function create_model_and_formatter (line 280) | def create_model_and_formatter(
  function _create_formatter_instance (line 352) | def _create_formatter_instance(

FILE: src/copaw/agents/prompt.py
  class PromptConfig (line 23) | class PromptConfig:
  class PromptBuilder (line 35) | class PromptBuilder:
    method __init__ (line 44) | def __init__(
    method _load_file (line 63) | def _load_file(self, filename: str) -> None:
    method _process_heartbeat_section (line 115) | def _process_heartbeat_section(self, content: str) -> str:
    method build (line 142) | def build(self) -> str:
  function build_system_prompt_from_working_dir (line 177) | def build_system_prompt_from_working_dir(
  function build_bootstrap_guidance (line 260) | def build_bootstrap_guidance(

FILE: src/copaw/agents/react_agent.py
  class CoPawAgent (line 63) | class CoPawAgent(ToolGuardMixin, ReActAgent):
    method __init__ (line 83) | def __init__(
    method _create_toolkit (line 166) | def _create_toolkit(
    method _register_skills (line 230) | def _register_skills(self, toolkit: Toolkit) -> None:
    method _build_sys_prompt (line 257) | def _build_sys_prompt(self) -> str:
    method _setup_memory_manager (line 288) | def _setup_memory_manager(
    method _register_hooks (line 323) | def _register_hooks(self) -> None:
    method rebuild_sys_prompt (line 353) | def rebuild_sys_prompt(self) -> None:
    method register_mcp_clients (line 369) | async def register_mcp_clients(
    method _recover_mcp_client (line 434) | async def _recover_mcp_client(self, client: Any) -> Any | None:
    method _reuse_shared_client_reference (line 452) | def _reuse_shared_client_reference(
    method _should_propagate_cancelled_error (line 465) | def _should_propagate_cancelled_error(error: BaseException) -> bool:
    method _reconnect_mcp_client (line 484) | async def _reconnect_mcp_client(
    method _rebuild_mcp_client (line 513) | def _rebuild_mcp_client(client: Any) -> Any | None:
    method _reasoning (line 558) | async def _reasoning(
    method _summarizing (line 587) | async def _summarizing(self) -> Msg:
    method print (line 623) | async def print(
    method _strip_tool_use_from_msg (line 670) | def _strip_tool_use_from_msg(msg: Msg) -> Msg:
    method _is_bad_request_or_media_error (line 703) | def _is_bad_request_or_media_error(exc: Exception) -> bool:
    method _strip_media_blocks_from_memory (line 730) | def _strip_media_blocks_from_memory(self) -> int:
    method reply (line 786) | async def reply(
    method interrupt (line 825) | async def interrupt(self, msg: Msg | list[Msg] | None = None) -> None:

FILE: src/copaw/agents/routing_chat_model.py
  class RoutingDecision (line 24) | class RoutingDecision:
  class RoutingPolicy (line 29) | class RoutingPolicy:
    method __init__ (line 32) | def __init__(self, cfg: AgentsLLMRoutingConfig):
    method decide (line 35) | def decide(
  class RoutingEndpoint (line 57) | class RoutingEndpoint:
  class RoutingChatModel (line 65) | class RoutingChatModel(ChatModelBase):
    method __init__ (line 68) | def __init__(
    method __call__ (line 84) | async def __call__(

FILE: src/copaw/agents/schema.py
  class FileBlock (line 11) | class FileBlock(TypedDict, total=False):

FILE: src/copaw/agents/skills/docx/scripts/accept_changes.py
  function accept_changes (line 37) | def accept_changes(
  function _setup_libreoffice_macro (line 93) | def _setup_libreoffice_macro() -> bool:

FILE: src/copaw/agents/skills/docx/scripts/comment.py
  function _generate_hex_id (line 68) | def _generate_hex_id() -> str:
  function _encode_smart_quotes (line 80) | def _encode_smart_quotes(text: str) -> str:
  function _append_xml (line 86) | def _append_xml(xml_path: Path, root_tag: str, content: str) -> None:
  function _find_para_id (line 98) | def _find_para_id(comments_path: Path, comment_id: int) -> str | None:
  function _get_next_rid (line 108) | def _get_next_rid(rels_path: Path) -> int:
  function _has_relationship (line 121) | def _has_relationship(rels_path: Path, target: str) -> bool:
  function _has_content_type (line 129) | def _has_content_type(ct_path: Path, part_name: str) -> bool:
  function _ensure_comment_relationships (line 137) | def _ensure_comment_relationships(unpacked_dir: Path) -> None:
  function _ensure_comment_content_types (line 179) | def _ensure_comment_content_types(unpacked_dir: Path) -> None:
  function add_comment (line 218) | def add_comment(

FILE: src/copaw/agents/skills/docx/scripts/office/helpers/merge_runs.py
  function merge_runs (line 16) | def merge_runs(input_dir: str) -> tuple[int, str]:
  function _find_elements (line 44) | def _find_elements(root, tag: str) -> list:
  function _get_child (line 59) | def _get_child(parent, tag: str):
  function _get_children (line 68) | def _get_children(parent, tag: str) -> list:
  function _is_adjacent (line 78) | def _is_adjacent(elem1, elem2) -> bool:
  function _remove_elements (line 93) | def _remove_elements(root, tag: str):
  function _strip_run_rsid_attrs (line 99) | def _strip_run_rsid_attrs(root):
  function _merge_runs_in (line 108) | def _merge_runs_in(container) -> int:
  function _first_child_run (line 128) | def _first_child_run(container):
  function _next_element_sibling (line 135) | def _next_element_sibling(node):
  function _next_sibling_run (line 144) | def _next_sibling_run(node):
  function _is_run (line 154) | def _is_run(node) -> bool:
  function _can_merge (line 159) | def _can_merge(run1, run2) -> bool:
  function _merge_run_content (line 170) | def _merge_run_content(target, source):
  function _consolidate_text (line 178) | def _consolidate_text(run):

FILE: src/copaw/agents/skills/docx/scripts/office/helpers/simplify_redlines.py
  function simplify_redlines (line 22) | def simplify_redlines(input_dir: str) -> tuple[int, str]:
  function _merge_tracked_changes_in (line 47) | def _merge_tracked_changes_in(container, tag: str) -> int:
  function _is_element (line 75) | def _is_element(node, tag: str) -> bool:
  function _get_author (line 80) | def _get_author(elem) -> str:
  function _can_merge_tracked (line 89) | def _can_merge_tracked(elem1, elem2) -> bool:
  function _merge_tracked_content (line 104) | def _merge_tracked_content(target, source):
  function _find_elements (line 111) | def _find_elements(root, tag: str) -> list:
  function get_tracked_change_authors (line 126) | def get_tracked_change_authors(doc_xml_path: Path) -> dict[str, int]:
  function _get_authors_from_docx (line 149) | def _get_authors_from_docx(docx_path: Path) -> dict[str, int]:
  function infer_author (line 172) | def infer_author(modified_dir: Path, original_docx: Path, default: str =...

FILE: src/copaw/agents/skills/docx/scripts/office/pack.py
  function pack (line 24) | def pack(
  function _run_validation (line 69) | def _run_validation(
  function _condense_xml (line 108) | def _condense_xml(xml_file: Path) -> None:

FILE: src/copaw/agents/skills/docx/scripts/office/soffice.py
  function get_soffice_cmd (line 26) | def get_soffice_cmd() -> str:
  function get_soffice_env (line 56) | def get_soffice_env() -> dict:
  function run_soffice (line 69) | def run_soffice(args: list[str], **kwargs) -> subprocess.CompletedProcess:
  function _needs_shim (line 78) | def _needs_shim() -> bool:
  function _ensure_shim (line 90) | def _ensure_shim() -> Path:

FILE: src/copaw/agents/skills/docx/scripts/office/unpack.py
  function unpack (line 34) | def unpack(
  function _pretty_print_xml (line 82) | def _pretty_print_xml(xml_file: Path) -> None:
  function _escape_smart_quotes (line 91) | def _escape_smart_quotes(xml_file: Path) -> None:

FILE: src/copaw/agents/skills/docx/scripts/office/validate.py
  function main (line 25) | def main():

FILE: src/copaw/agents/skills/docx/scripts/office/validators/base.py
  class BaseSchemaValidator (line 12) | class BaseSchemaValidator:
    method __init__ (line 94) | def __init__(self, unpacked_dir, original_file=None, verbose=False):
    method validate (line 109) | def validate(self):
    method repair (line 112) | def repair(self) -> int:
    method repair_whitespace_preservation (line 115) | def repair_whitespace_preservation(self) -> int:
    method validate_xml (line 143) | def validate_xml(self):
    method validate_namespaces (line 170) | def validate_namespaces(self):
    method validate_unique_ids (line 199) | def validate_unique_ids(self):
    method validate_file_references (line 289) | def validate_file_references(self):
    method validate_all_relationship_ids (line 385) | def validate_all_relationship_ids(self):
    method _get_expected_relationship_type (line 469) | def _get_expected_relationship_type(self, element_name):
    method validate_content_types (line 492) | def validate_content_types(self):
    method validate_file_against_xsd (line 598) | def validate_file_against_xsd(self, xml_file, verbose=False):
    method validate_against_xsd (line 636) | def validate_against_xsd(self):
    method _get_schema_path (line 685) | def _get_schema_path(self, xml_file):
    method _clean_ignorable_namespaces (line 703) | def _clean_ignorable_namespaces(self, xml_doc):
    method _remove_ignorable_elements (line 723) | def _remove_ignorable_elements(self, root):
    method _preprocess_for_mc_ignorable (line 742) | def _preprocess_for_mc_ignorable(self, xml_doc):
    method _validate_single_file_xsd (line 750) | def _validate_single_file_xsd(self, xml_file, base_path):
    method _get_original_file_errors (line 787) | def _get_original_file_errors(self, xml_file):
    method _remove_template_tags_from_text_nodes (line 814) | def _remove_template_tags_from_text_nodes(self, xml_doc):

FILE: src/copaw/agents/skills/docx/scripts/office/validators/docx.py
  class DOCXSchemaValidator (line 17) | class DOCXSchemaValidator(BaseSchemaValidator):
    method validate (line 25) | def validate(self):
    method validate_whitespace_preservation (line 67) | def validate_whitespace_preservation(self):
    method validate_deletions (line 113) | def validate_deletions(self):
    method count_paragraphs_in_unpacked (line 164) | def count_paragraphs_in_unpacked(self):
    method count_paragraphs_in_original (line 180) | def count_paragraphs_in_original(self):
    method validate_insertions (line 203) | def validate_insertions(self):
    method compare_paragraph_counts (line 244) | def compare_paragraph_counts(self):
    method _parse_id_value (line 252) | def _parse_id_value(self, val: str, base: int = 16) -> int:
    method validate_id_constraints (line 255) | def validate_id_constraints(self):
    method validate_comment_markers (line 299) | def validate_comment_markers(self):
    method repair (line 387) | def repair(self) -> int:
    method repair_durableId (line 392) | def repair_durableId(self) -> int:

FILE: src/copaw/agents/skills/docx/scripts/office/validators/pptx.py
  class PPTXSchemaValidator (line 10) | class PPTXSchemaValidator(BaseSchemaValidator):
    method validate (line 25) | def validate(self):
    method validate_uuid_ids (line 62) | def validate_uuid_ids(self):
    method _looks_like_uuid (line 100) | def _looks_like_uuid(self, value):
    method validate_slide_layout_ids (line 104) | def validate_slide_layout_ids(self):
    method validate_no_duplicate_slide_layouts (line 172) | def validate_no_duplicate_slide_layouts(self):
    method validate_notes_slide_references (line 210) | def validate_notes_slide_references(self):

FILE: src/copaw/agents/skills/docx/scripts/office/validators/redlining.py
  class RedliningValidator (line 11) | class RedliningValidator:
    method __init__ (line 13) | def __init__(self, unpacked_dir, original_docx, verbose=False, author=...
    method repair (line 22) | def repair(self) -> int:
    method validate (line 25) | def validate(self):
    method _generate_detailed_diff (line 104) | def _generate_detailed_diff(self, original_text, modified_text):
    method _get_git_word_diff (line 127) | def _get_git_word_diff(self, original_text, modified_text):
    method _remove_author_tracked_changes (line 198) | def _remove_author_tracked_changes(self, root):
    method _extract_text_content (line 229) | def _extract_text_content(self, root):

FILE: src/copaw/agents/skills/pdf/scripts/check_bounding_boxes.py
  class RectAndField (line 9) | class RectAndField:
  function get_bounding_box_messages (line 15) | def get_bounding_box_messages(fields_json_stream) -> list[str]:

FILE: src/copaw/agents/skills/pdf/scripts/convert_pdf_to_images.py
  function convert (line 9) | def convert(pdf_path, output_dir, max_dim=1000):

FILE: src/copaw/agents/skills/pdf/scripts/create_validation_image.py
  function create_validation_image (line 9) | def create_validation_image(page_number, fields_json_path, input_path, o...

FILE: src/copaw/agents/skills/pdf/scripts/extract_form_field_info.py
  function get_full_annotation_field_id (line 9) | def get_full_annotation_field_id(annotation):
  function make_field_dict (line 19) | def make_field_dict(field, field_id):
  function get_field_info (line 47) | def get_field_info(reader: PdfReader):
  function write_field_info (line 110) | def write_field_info(pdf_path: str, json_output_path: str):

FILE: src/copaw/agents/skills/pdf/scripts/extract_form_structure.py
  function extract_form_structure (line 20) | def extract_form_structure(pdf_path):
  function main (line 91) | def main():

FILE: src/copaw/agents/skills/pdf/scripts/fill_fillable_fields.py
  function fill_pdf_fields (line 11) | def fill_pdf_fields(input_pdf_path: str, fields_json_path: str, output_p...
  function validation_error_for_field_value (line 55) | def validation_error_for_field_value(field_info, field_value):
  function monkeypatch_pydpf_method (line 74) | def monkeypatch_pydpf_method():

FILE: src/copaw/agents/skills/pdf/scripts/fill_pdf_form_with_annotations.py
  function transform_from_image_coords (line 10) | def transform_from_image_coords(bbox, image_width, image_height, pdf_wid...
  function transform_from_pdf_coords (line 23) | def transform_from_pdf_coords(bbox, pdf_height):
  function fill_pdf_form (line 33) | def fill_pdf_form(input_pdf_path, fields_json_path, output_pdf_path):

FILE: src/copaw/agents/skills/pptx/scripts/add_slide.py
  function get_next_slide_number (line 27) | def get_next_slide_number(slides_dir: Path) -> int:
  function create_slide_from_layout (line 33) | def create_slide_from_layout(unpacked_dir: Path, layout_file: str) -> None:
  function duplicate_slide (line 90) | def duplicate_slide(unpacked_dir: Path, source: str) -> None:
  function _add_to_content_types (line 130) | def _add_to_content_types(unpacked_dir: Path, dest: str) -> None:
  function _add_to_presentation_rels (line 141) | def _add_to_presentation_rels(unpacked_dir: Path, dest: str) -> str:
  function _get_next_slide_id (line 158) | def _get_next_slide_id(unpacked_dir: Path) -> int:
  function parse_source (line 165) | def parse_source(source: str) -> tuple[str, str | None]:

FILE: src/copaw/agents/skills/pptx/scripts/clean.py
  function get_slides_in_sldidlst (line 27) | def get_slides_in_sldidlst(unpacked_dir: Path) -> set[str]:
  function remove_orphaned_slides (line 49) | def remove_orphaned_slides(unpacked_dir: Path) -> list[str]:
  function remove_trash_directory (line 91) | def remove_trash_directory(unpacked_dir: Path) -> list[str]:
  function get_slide_referenced_files (line 106) | def get_slide_referenced_files(unpacked_dir: Path) -> set:
  function remove_orphaned_rels_files (line 128) | def remove_orphaned_rels_files(unpacked_dir: Path) -> list[str]:
  function get_referenced_files (line 153) | def get_referenced_files(unpacked_dir: Path) -> set:
  function remove_orphaned_files (line 171) | def remove_orphaned_files(unpacked_dir: Path, referenced: set) -> list[s...
  function update_content_types (line 221) | def update_content_types(unpacked_dir: Path, removed_files: list[str]) -...
  function clean_unused_files (line 241) | def clean_unused_files(unpacked_dir: Path) -> list[str]:

FILE: src/copaw/agents/skills/pptx/scripts/office/helpers/merge_runs.py
  function merge_runs (line 16) | def merge_runs(input_dir: str) -> tuple[int, str]:
  function _find_elements (line 44) | def _find_elements(root, tag: str) -> list:
  function _get_child (line 59) | def _get_child(parent, tag: str):
  function _get_children (line 68) | def _get_children(parent, tag: str) -> list:
  function _is_adjacent (line 78) | def _is_adjacent(elem1, elem2) -> bool:
  function _remove_elements (line 93) | def _remove_elements(root, tag: str):
  function _strip_run_rsid_attrs (line 99) | def _strip_run_rsid_attrs(root):
  function _merge_runs_in (line 108) | def _merge_runs_in(container) -> int:
  function _first_child_run (line 128) | def _first_child_run(container):
  function _next_element_sibling (line 135) | def _next_element_sibling(node):
  function _next_sibling_run (line 144) | def _next_sibling_run(node):
  function _is_run (line 154) | def _is_run(node) -> bool:
  function _can_merge (line 159) | def _can_merge(run1, run2) -> bool:
  function _merge_run_content (line 170) | def _merge_run_content(target, source):
  function _consolidate_text (line 178) | def _consolidate_text(run):

FILE: src/copaw/agents/skills/pptx/scripts/office/helpers/simplify_redlines.py
  function simplify_redlines (line 22) | def simplify_redlines(input_dir: str) -> tuple[int, str]:
  function _merge_tracked_changes_in (line 47) | def _merge_tracked_changes_in(container, tag: str) -> int:
  function _is_element (line 75) | def _is_element(node, tag: str) -> bool:
  function _get_author (line 80) | def _get_author(elem) -> str:
  function _can_merge_tracked (line 89) | def _can_merge_tracked(elem1, elem2) -> bool:
  function _merge_tracked_content (line 104) | def _merge_tracked_content(target, source):
  function _find_elements (line 111) | def _find_elements(root, tag: str) -> list:
  function get_tracked_change_authors (line 126) | def get_tracked_change_authors(doc_xml_path: Path) -> dict[str, int]:
  function _get_authors_from_docx (line 149) | def _get_authors_from_docx(docx_path: Path) -> dict[str, int]:
  function infer_author (line 172) | def infer_author(modified_dir: Path, original_docx: Path, default: str =...

FILE: src/copaw/agents/skills/pptx/scripts/office/pack.py
  function pack (line 24) | def pack(
  function _run_validation (line 69) | def _run_validation(
  function _condense_xml (line 108) | def _condense_xml(xml_file: Path) -> None:

FILE: src/copaw/agents/skills/pptx/scripts/office/soffice.py
  function get_soffice_cmd (line 26) | def get_soffice_cmd() -> str:
  function get_soffice_env (line 55) | def get_soffice_env() -> dict:
  function run_soffice (line 67) | def run_soffice(args: list[str], **kwargs) -> subprocess.CompletedProcess:
  function _needs_shim (line 76) | def _needs_shim() -> bool:
  function _ensure_shim (line 88) | def _ensure_shim() -> Path:

FILE: src/copaw/agents/skills/pptx/scripts/office/unpack.py
  function unpack (line 34) | def unpack(
  function _pretty_print_xml (line 82) | def _pretty_print_xml(xml_file: Path) -> None:
  function _escape_smart_quotes (line 91) | def _escape_smart_quotes(xml_file: Path) -> None:

FILE: src/copaw/agents/skills/pptx/scripts/office/validate.py
  function main (line 25) | def main():

FILE: src/copaw/agents/skills/pptx/scripts/office/validators/base.py
  class BaseSchemaValidator (line 12) | class BaseSchemaValidator:
    method __init__ (line 94) | def __init__(self, unpacked_dir, original_file=None, verbose=False):
    method validate (line 109) | def validate(self):
    method repair (line 112) | def repair(self) -> int:
    method repair_whitespace_preservation (line 115) | def repair_whitespace_preservation(self) -> int:
    method validate_xml (line 143) | def validate_xml(self):
    method validate_namespaces (line 170) | def validate_namespaces(self):
    method validate_unique_ids (line 199) | def validate_unique_ids(self):
    method validate_file_references (line 289) | def validate_file_references(self):
    method validate_all_relationship_ids (line 385) | def validate_all_relationship_ids(self):
    method _get_expected_relationship_type (line 469) | def _get_expected_relationship_type(self, element_name):
    method validate_content_types (line 492) | def validate_content_types(self):
    method validate_file_against_xsd (line 598) | def validate_file_against_xsd(self, xml_file, verbose=False):
    method validate_against_xsd (line 636) | def validate_against_xsd(self):
    method _get_schema_path (line 685) | def _get_schema_path(self, xml_file):
    method _clean_ignorable_namespaces (line 703) | def _clean_ignorable_namespaces(self, xml_doc):
    method _remove_ignorable_elements (line 723) | def _remove_ignorable_elements(self, root):
    method _preprocess_for_mc_ignorable (line 742) | def _preprocess_for_mc_ignorable(self, xml_doc):
    method _validate_single_file_xsd (line 750) | def _validate_single_file_xsd(self, xml_file, base_path):
    method _get_original_file_errors (line 787) | def _get_original_file_errors(self, xml_file):
    method _remove_template_tags_from_text_nodes (line 814) | def _remove_template_tags_from_text_nodes(self, xml_doc):

FILE: src/copaw/agents/skills/pptx/scripts/office/validators/docx.py
  class DOCXSchemaValidator (line 17) | class DOCXSchemaValidator(BaseSchemaValidator):
    method validate (line 25) | def validate(self):
    method validate_whitespace_preservation (line 67) | def validate_whitespace_preservation(self):
    method validate_deletions (line 113) | def validate_deletions(self):
    method count_paragraphs_in_unpacked (line 164) | def count_paragraphs_in_unpacked(self):
    method count_paragraphs_in_original (line 180) | def count_paragraphs_in_original(self):
    method validate_insertions (line 203) | def validate_insertions(self):
    method compare_paragraph_counts (line 244) | def compare_paragraph_counts(self):
    method _parse_id_value (line 252) | def _parse_id_value(self, val: str, base: int = 16) -> int:
    method validate_id_constraints (line 255) | def validate_id_constraints(self):
    method validate_comment_markers (line 299) | def validate_comment_markers(self):
    method repair (line 387) | def repair(self) -> int:
    method repair_durableId (line 392) | def repair_durableId(self) -> int:

FILE: src/copaw/agents/skills/pptx/scripts/office/validators/pptx.py
  class PPTXSchemaValidator (line 10) | class PPTXSchemaValidator(BaseSchemaValidator):
    method validate (line 25) | def validate(self):
    method validate_uuid_ids (line 62) | def validate_uuid_ids(self):
    method _looks_like_uuid (line 100) | def _looks_like_uuid(self, value):
    method validate_slide_layout_ids (line 104) | def validate_slide_layout_ids(self):
    method validate_no_duplicate_slide_layouts (line 172) | def validate_no_duplicate_slide_layouts(self):
    method validate_notes_slide_references (line 210) | def validate_notes_slide_references(self):

FILE: src/copaw/agents/skills/pptx/scripts/office/validators/redlining.py
  class RedliningValidator (line 11) | class RedliningValidator:
    method __init__ (line 13) | def __init__(self, unpacked_dir, original_docx, verbose=False, author=...
    method repair (line 22) | def repair(self) -> int:
    method validate (line 25) | def validate(self):
    method _generate_detailed_diff (line 104) | def _generate_detailed_diff(self, original_text, modified_text):
    method _get_git_word_diff (line 127) | def _get_git_word_diff(self, original_text, modified_text):
    method _remove_author_tracked_changes (line 198) | def _remove_author_tracked_changes(self, root):
    method _extract_text_content (line 229) | def _extract_text_content(self, root):

FILE: src/copaw/agents/skills/pptx/scripts/thumbnail.py
  function main (line 40) | def main():
  function get_slide_info (line 95) | def get_slide_info(pptx_path: Path) -> list[dict]:
  function build_slide_list (line 121) | def build_slide_list(
  function create_hidden_placeholder (line 149) | def create_hidden_placeholder(size: tuple[int, int]) -> Image.Image:
  function convert_to_images (line 158) | def convert_to_images(pptx_path: Path, temp_dir: Path) -> list[Path]:
  function create_grids (line 213) | def create_grids(
  function create_grid (line 242) | def create_grid(

FILE: src/copaw/agents/skills/xlsx/scripts/office/helpers/merge_runs.py
  function merge_runs (line 16) | def merge_runs(input_dir: str) -> tuple[int, str]:
  function _find_elements (line 44) | def _find_elements(root, tag: str) -> list:
  function _get_child (line 59) | def _get_child(parent, tag: str):
  function _get_children (line 68) | def _get_children(parent, tag: str) -> list:
  function _is_adjacent (line 78) | def _is_adjacent(elem1, elem2) -> bool:
  function _remove_elements (line 93) | def _remove_elements(root, tag: str):
  function _strip_run_rsid_attrs (line 99) | def _strip_run_rsid_attrs(root):
  function _merge_runs_in (line 108) | def _merge_runs_in(container) -> int:
  function _first_child_run (line 128) | def _first_child_run(container):
  function _next_element_sibling (line 135) | def _next_element_sibling(node):
  function _next_sibling_run (line 144) | def _next_sibling_run(node):
  function _is_run (line 154) | def _is_run(node) -> bool:
  function _can_merge (line 159) | def _can_merge(run1, run2) -> bool:
  function _merge_run_content (line 170) | def _merge_run_content(target, source):
  function _consolidate_text (line 178) | def _consolidate_text(run):

FILE: src/copaw/agents/skills/xlsx/scripts/office/helpers/simplify_redlines.py
  function simplify_redlines (line 22) | def simplify_redlines(input_dir: str) -> tuple[int, str]:
  function _merge_tracked_changes_in (line 47) | def _merge_tracked_changes_in(container, tag: str) -> int:
  function _is_element (line 75) | def _is_element(node, tag: str) -> bool:
  function _get_author (line 80) | def _get_author(elem) -> str:
  function _can_merge_tracked (line 89) | def _can_merge_tracked(elem1, elem2) -> bool:
  function _merge_tracked_content (line 104) | def _merge_tracked_content(target, source):
  function _find_elements (line 111) | def _find_elements(root, tag: str) -> list:
  function get_tracked_change_authors (line 126) | def get_tracked_change_authors(doc_xml_path: Path) -> dict[str, int]:
  function _get_authors_from_docx (line 149) | def _get_authors_from_docx(docx_path: Path) -> dict[str, int]:
  function infer_author (line 172) | def infer_author(modified_dir: Path, original_docx: Path, default: str =...

FILE: src/copaw/agents/skills/xlsx/scripts/office/pack.py
  function pack (line 24) | def pack(
  function _run_validation (line 69) | def _run_validation(
  function _condense_xml (line 108) | def _condense_xml(xml_file: Path) -> None:

FILE: src/copaw/agents/skills/xlsx/scripts/office/soffice.py
  function get_soffice_cmd (line 26) | def get_soffice_cmd() -> str:
  function get_soffice_env (line 55) | def get_soffice_env() -> dict:
  function run_soffice (line 67) | def run_soffice(args: list[str], **kwargs) -> subprocess.CompletedProcess:
  function _needs_shim (line 76) | def _needs_shim() -> bool:
  function _ensure_shim (line 88) | def _ensure_shim() -> Path:

FILE: src/copaw/agents/skills/xlsx/scripts/office/unpack.py
  function unpack (line 34) | def unpack(
  function _pretty_print_xml (line 82) | def _pretty_print_xml(xml_file: Path) -> None:
  function _escape_smart_quotes (line 91) | def _escape_smart_quotes(xml_file: Path) -> None:

FILE: src/copaw/agents/skills/xlsx/scripts/office/validate.py
  function main (line 25) | def main():

FILE: src/copaw/agents/skills/xlsx/scripts/office/validators/base.py
  class BaseSchemaValidator (line 12) | class BaseSchemaValidator:
    method __init__ (line 94) | def __init__(self, unpacked_dir, original_file=None, verbose=False):
    method validate (line 109) | def validate(self):
    method repair (line 112) | def repair(self) -> int:
    method repair_whitespace_preservation (line 115) | def repair_whitespace_preservation(self) -> int:
    method validate_xml (line 143) | def validate_xml(self):
    method validate_namespaces (line 170) | def validate_namespaces(self):
    method validate_unique_ids (line 199) | def validate_unique_ids(self):
    method validate_file_references (line 289) | def validate_file_references(self):
    method validate_all_relationship_ids (line 385) | def validate_all_relationship_ids(self):
    method _get_expected_relationship_type (line 469) | def _get_expected_relationship_type(self, element_name):
    method validate_content_types (line 492) | def validate_content_types(self):
    method validate_file_against_xsd (line 598) | def validate_file_against_xsd(self, xml_file, verbose=False):
    method validate_against_xsd (line 636) | def validate_against_xsd(self):
    method _get_schema_path (line 685) | def _get_schema_path(self, xml_file):
    method _clean_ignorable_namespaces (line 703) | def _clean_ignorable_namespaces(self, xml_doc):
    method _remove_ignorable_elements (line 723) | def _remove_ignorable_elements(self, root):
    method _preprocess_for_mc_ignorable (line 742) | def _preprocess_for_mc_ignorable(self, xml_doc):
    method _validate_single_file_xsd (line 750) | def _validate_single_file_xsd(self, xml_file, base_path):
    method _get_original_file_errors (line 787) | def _get_original_file_errors(self, xml_file):
    method _remove_template_tags_from_text_nodes (line 814) | def _remove_template_tags_from_text_nodes(self, xml_doc):

FILE: src/copaw/agents/skills/xlsx/scripts/office/validators/docx.py
  class DOCXSchemaValidator (line 17) | class DOCXSchemaValidator(BaseSchemaValidator):
    method validate (line 25) | def validate(self):
    method validate_whitespace_preservation (line 67) | def validate_whitespace_preservation(self):
    method validate_deletions (line 113) | def validate_deletions(self):
    method count_paragraphs_in_unpacked (line 164) | def count_paragraphs_in_unpacked(self):
    method count_paragraphs_in_original (line 180) | def count_paragraphs_in_original(self):
    method validate_insertions (line 203) | def validate_insertions(self):
    method compare_paragraph_counts (line 244) | def compare_paragraph_counts(self):
    method _parse_id_value (line 252) | def _parse_id_value(self, val: str, base: int = 16) -> int:
    method validate_id_constraints (line 255) | def validate_id_constraints(self):
    method validate_comment_markers (line 299) | def validate_comment_markers(self):
    method repair (line 387) | def repair(self) -> int:
    method repair_durableId (line 392) | def repair_durableId(self) -> int:

FILE: src/copaw/agents/skills/xlsx/scripts/office/validators/pptx.py
  class PPTXSchemaValidator (line 10) | class PPTXSchemaValidator(BaseSchemaValidator):
    method validate (line 25) | def validate(self):
    method validate_uuid_ids (line 62) | def validate_uuid_ids(self):
    method _looks_like_uuid (line 100) | def _looks_like_uuid(self, value):
    method validate_slide_layout_ids (line 104) | def validate_slide_layout_ids(self):
    method validate_no_duplicate_slide_layouts (line 172) | def validate_no_duplicate_slide_layouts(self):
    method validate_notes_slide_references (line 210) | def validate_notes_slide_references(self):

FILE: src/copaw/agents/skills/xlsx/scripts/office/validators/redlining.py
  class RedliningValidator (line 11) | class RedliningValidator:
    method __init__ (line 13) | def __init__(self, unpacked_dir, original_docx, verbose=False, author=...
    method repair (line 22) | def repair(self) -> int:
    method validate (line 25) | def validate(self):
    method _generate_detailed_diff (line 104) | def _generate_detailed_diff(self, original_text, modified_text):
    method _get_git_word_diff (line 127) | def _get_git_word_diff(self, original_text, modified_text):
    method _remove_author_tracked_changes (line 198) | def _remove_author_tracked_changes(self, root):
    method _extract_text_content (line 229) | def _extract_text_content(self, root):

FILE: src/copaw/agents/skills/xlsx/scripts/recalc.py
  function has_gtimeout (line 33) | def has_gtimeout():
  function _get_macro_dir (line 43) | def _get_macro_dir() -> str:
  function setup_libreoffice_macro (line 54) | def setup_libreoffice_macro():
  function recalc (line 80) | def recalc(filename, timeout=30):
  function main (line 189) | def main():

FILE: src/copaw/agents/skills_hub.py
  class HubSkillResult (line 35) | class HubSkillResult:
  class HubInstallResult (line 44) | class HubInstallResult:
  class SkillImportCancelled (line 50) | class SkillImportCancelled(RuntimeError):
  function _hub_http_timeout (line 70) | def _hub_http_timeout() -> float:
  function _hub_http_retries (line 78) | def _hub_http_retries() -> int:
  function _hub_http_backoff_base (line 86) | def _hub_http_backoff_base() -> float:
  function _hub_http_backoff_cap (line 94) | def _hub_http_backoff_cap() -> float:
  function _compute_backoff_seconds (line 102) | def _compute_backoff_seconds(attempt: int) -> float:
  function _ensure_not_cancelled (line 108) | def _ensure_not_cancelled() -> None:
  function _with_cancel_checker (line 123) | def _with_cancel_checker(checker: Any | None):
  function _hub_base_url (line 131) | def _hub_base_url() -> str:
  function _hub_search_path (line 135) | def _hub_search_path() -> str:
  function _hub_version_path (line 142) | def _hub_version_path() -> str:
  function _hub_detail_path (line 149) | def _hub_detail_path() -> str:
  function _hub_file_path (line 156) | def _hub_file_path() -> str:
  function _join_url (line 163) | def _join_url(base: str, path: str) -> str:
  function _build_request (line 167) | def _build_request(full_url: str, accept: str) -> Request:
  function _read_response_bytes (line 183) | def _read_response_bytes(
  function _http_fetch (line 226) | def _http_fetch(
  function _http_get (line 338) | def _http_get(
  function _http_bytes_get (line 350) | def _http_bytes_get(
  function _http_json_get (line 364) | def _http_json_get(url: str, params: dict[str, Any] | None = None) -> Any:
  function _http_text_get (line 369) | def _http_text_get(url: str, params: dict[str, Any] | None = None) -> str:
  function _norm_search_items (line 377) | def _norm_search_items(data: Any) -> list[dict[str, Any]]:
  function _safe_path_parts (line 390) | def _safe_path_parts(path: str) -> list[str] | None:
  function _tree_insert (line 402) | def _tree_insert(
  function _files_to_tree (line 417) | def _files_to_tree(
  function _sanitize_tree (line 435) | def _sanitize_tree(tree: Any) -> dict[str, Any]:
  function _bundle_has_content (line 451) | def _bundle_has_content(payload: Any) -> bool:
  function _extract_version_hint (line 467) | def _extract_version_hint(
  function _hydrate_clawhub_payload (line 489) | def _hydrate_clawhub_payload(
  function _normalize_bundle (line 574) | def _normalize_bundle(
  function _safe_fallback_name (line 636) | def _safe_fallback_name(raw: str) -> str:
  function _extract_error_message_from_payload (line 641) | def _extract_error_message_from_payload(payload: bytes) -> str:
  function _lobehub_http_error_message (line 657) | def _lobehub_http_error_message(error: HTTPError) -> str:
  function _is_probably_text_blob (line 670) | def _is_probably_text_blob(payload: bytes) -> bool:
  function _should_keep_lobehub_file (line 682) | def _should_keep_lobehub_file(parts: list[str]) -> bool:
  function _sanitize_skill_dir_name (line 692) | def _sanitize_skill_dir_name(name: str) -> str:
  function _is_http_url (line 706) | def _is_http_url(text: str) -> bool:
  function _extract_clawhub_slug_from_url (line 711) | def _extract_clawhub_slug_from_url(url: str) -> str:
  function _extract_skills_sh_spec (line 723) | def _extract_skills_sh_spec(url: str) -> tuple[str, str, str] | None:
  function _extract_skillsmp_slug (line 737) | def _extract_skillsmp_slug(url: str) -> str:
  function _extract_lobehub_identifier (line 752) | def _extract_lobehub_identifier(url: str) -> str:
  function _extract_modelscope_skill_spec (line 772) | def _extract_modelscope_skill_spec(
  function _extract_github_spec (line 804) | def _extract_github_spec(
  function _github_repo_exists (line 831) | def _github_repo_exists(owner: str, repo: str) -> bool:
  function _extract_skillsmp_spec (line 842) | def _extract_skillsmp_spec(
  function _resolve_clawhub_slug (line 882) | def _resolve_clawhub_slug(bundle_url: str) -> str:
  function _github_api_url (line 889) | def _github_api_url(owner: str, repo: str, suffix: str) -> str:
  function _github_encode_path (line 895) | def _github_encode_path(path: str) -> str:
  function _github_get_default_branch (line 902) | def _github_get_default_branch(owner: str, repo: str) -> str:
  function _normalize_skill_key (line 911) | def _normalize_skill_key(text: str) -> str:
  function _github_list_skill_md_roots (line 915) | def _github_list_skill_md_roots(
  function _github_get_content_entry (line 955) | def _github_get_content_entry(
  function _github_get_dir_entries (line 969) | def _github_get_dir_entries(
  function _github_read_file (line 984) | def _github_read_file(entry: dict[str, Any]) -> str:
  function _join_repo_path (line 1003) | def _join_repo_path(root: str, leaf: str) -> str:
  function _relative_from_root (line 1009) | def _relative_from_root(full_path: str, root: str) -> str:
  function _github_collect_tree_files (line 1018) | def _github_collect_tree_files(
  function _fetch_bundle_from_skills_sh_url (line 1057) | def _fetch_bundle_from_skills_sh_url(
  function _fetch_bundle_from_repo_and_skill_hint (line 1159) | def _fetch_bundle_from_repo_and_skill_hint(
  function _fetch_bundle_from_github_url (line 1262) | def _fetch_bundle_from_github_url(
  function _fetch_bundle_from_skillsmp_url (line 1295) | def _fetch_bundle_from_skillsmp_url(
  function _lobehub_download_url (line 1311) | def _lobehub_download_url(identifier: str) -> str:
  function _lobehub_zip_to_bundle (line 1315) | def _lobehub_zip_to_bundle(identifier: str, payload: bytes) -> dict[str,...
  function _fetch_bundle_from_modelscope_url (line 1370) | def _fetch_bundle_from_modelscope_url(
  function _fetch_bundle_from_lobehub_url (line 1450) | def _fetch_bundle_from_lobehub_url(
  function _fetch_bundle_from_clawhub_slug (line 1479) | def _fetch_bundle_from_clawhub_slug(
  function search_hub_skills (line 1513) | def search_hub_skills(query: str, limit: int = 20) -> list[HubSkillResult]:
  function _resolve_bundle_from_url (line 1539) | def _resolve_bundle_from_url(
  function install_skill_from_hub (line 1567) | def install_skill_from_hub(

FILE: src/copaw/agents/skills_manager.py
  function _dedupe_skills_by_name (line 20) | def _dedupe_skills_by_name(skills: list["SkillInfo"]) -> list["SkillInfo"]:
  class SkillInfo (line 28) | class SkillInfo(BaseModel):
  function get_builtin_skills_dir (line 63) | def get_builtin_skills_dir() -> Path:
  function get_customized_skills_dir (line 68) | def get_customized_skills_dir(workspace_dir: Path) -> Path:
  function get_active_skills_dir (line 73) | def get_active_skills_dir(workspace_dir: Path) -> Path:
  function get_working_skills_dir (line 78) | def get_working_skills_dir(workspace_dir: Path) -> Path:
  function _build_directory_tree (line 87) | def _build_directory_tree(directory: Path) -> dict[str, Any]:
  function _collect_skills_from_dir (line 124) | def _collect_skills_from_dir(directory: Path) -> dict[str, Path]:
  function _get_builtin_skill_version (line 142) | def _get_builtin_skill_version(skill_dir: Path) -> Version | None:
  function _replace_skill_dir (line 164) | def _replace_skill_dir(source: Path, target: Path) -> None:
  function _skill_md_differs (line 171) | def _skill_md_differs(dir_a: Path, dir_b: Path) -> bool:
  function sync_skills_to_working_dir (line 183) | def sync_skills_to_working_dir(
  function sync_skills_from_active_to_customized (line 263) | def sync_skills_from_active_to_customized(
  function list_available_skills (line 344) | def list_available_skills(workspace_dir: Path) -> list[str]:
  function ensure_skills_initialized (line 366) | def ensure_skills_initialized(workspace_dir: Path) -> None:
  function _read_skills_from_dir (line 394) | def _read_skills_from_dir(
  function _create_files_from_tree (line 473) | def _create_files_from_tree(
  function _is_hidden (line 524) | def _is_hidden(name: str) -> bool:
  function _extract_and_validate_zip (line 529) | def _extract_and_validate_zip(data: bytes, tmp_dir: Path) -> None:
  function _resolve_skill_name (line 552) | def _resolve_skill_name(skill_dir: Path) -> str:
  function _find_skill_dirs (line 569) | def _find_skill_dirs(root: Path) -> list[tuple[Path, str]]:
  function _import_skill_dir (line 580) | def _import_skill_dir(
  class SkillService (line 627) | class SkillService:
    method __init__ (line 635) | def __init__(self, workspace_dir: Path):
    method get_customized_skill_dir (line 644) | def get_customized_skill_dir(self, name: str) -> Path | None:
    method list_all_skills (line 649) | def list_all_skills(self) -> list[SkillInfo]:
    method list_available_skills (line 687) | def list_available_skills(self) -> list[SkillInfo]:
    method create_skill (line 699) | def create_skill(
    method disable_skill (line 861) | def disable_skill(self, name: str) -> bool:
    method enable_skill (line 893) | def enable_skill(self, name: str, force: bool = False) -> bool:
    method delete_skill (line 942) | def delete_skill(self, name: str) -> bool:
    method sync_from_active_to_customized (line 982) | def sync_from_active_to_customized(
    method import_from_zip (line 1000) | def import_from_zip(
    method load_skill_file (line 1087) | def load_skill_file(  # pylint: disable=too-many-return-statements

FILE: src/copaw/agents/tool_guard_mixin.py
  class ToolGuardMixin (line 24) | class ToolGuardMixin:
    method _init_tool_guard (line 36) | def _init_tool_guard(self) -> None:
    method _ensure_tool_guard (line 45) | def _ensure_tool_guard(self) -> None:
    method _should_require_approval (line 53) | def _should_require_approval(self) -> bool:
    method _last_tool_response_is_denied (line 57) | def _last_tool_response_is_denied(self) -> bool:
    method _cleanup_tool_guard_denied_messages (line 64) | async def _cleanup_tool_guard_denied_messages(
    method _acting (line 103) | async def _acting(self, tool_call) -> dict | None:  # noqa: C901
    method _acting_auto_denied (line 187) | async def _acting_auto_denied(
    method _acting_with_approval (line 237) | async def _acting_with_approval(
    method _reasoning (line 315) | async def _reasoning(

FILE: src/copaw/agents/tools/browser_control.py
  function _get_executor (line 46) | def _get_executor() -> futures.ThreadPoolExecutor:
  function _run_sync (line 55) | async def _run_sync(func, *args, **kwargs):
  function _run_sync (line 65) | async def _run_sync(func, *args, **kwargs):
  function _touch_activity (line 97) | def _touch_activity() -> None:
  function _is_browser_running (line 102) | def _is_browser_running() -> bool:
  function _reset_browser_state (line 109) | def _reset_browser_state() -> None:
  function _idle_watchdog (line 132) | async def _idle_watchdog(idle_seconds: float = _BROWSER_IDLE_TIMEOUT) ->...
  function _atexit_cleanup (line 156) | def _atexit_cleanup() -> None:
  function _tool_response (line 177) | def _tool_response(text: str) -> ToolResponse:
  function _chromium_launch_args (line 184) | def _chromium_launch_args() -> list[str]:
  function _chromium_executable_path (line 191) | def _chromium_executable_path() -> str | None:
  function _use_webkit_fallback (line 196) | def _use_webkit_fallback() -> bool:
  function _ensure_playwright_async (line 204) | def _ensure_playwright_async():
  function _ensure_playwright_sync (line 219) | def _ensure_playwright_sync():
  function _sync_browser_launch (line 234) | def _sync_browser_launch(headless: bool):
  function _sync_browser_close (line 272) | def _sync_browser_close():
  function _parse_json_param (line 286) | def _parse_json_param(value: str, default: Any = None):
  function browser_use (line 301) | async def browser_use(  # pylint: disable=R0911,R0912
  function _get_page (line 610) | def _get_page(page_id: str):
  function _get_refs (line 615) | def _get_refs(page_id: str) -> dict[str, dict]:
  function _get_root (line 620) | def _get_root(page, _page_id: str, frame_selector: str = ""):
  function _get_locator_by_ref (line 627) | def _get_locator_by_ref(
  function _attach_page_listeners (line 648) | def _attach_page_listeners(page, page_id: str) -> None:
  function _next_page_id (line 689) | def _next_page_id() -> str:
  function _attach_context_listeners (line 696) | def _attach_context_listeners(context) -> None:
  function _ensure_browser (line 718) | async def _ensure_browser() -> bool:  # pylint: disable=too-many-branches
  function _start_idle_watchdog (line 797) | def _start_idle_watchdog() -> None:
  function _cancel_idle_watchdog (line 805) | def _cancel_idle_watchdog() -> None:
  function _action_start (line 814) | async def _action_start(
  function _action_stop (line 917) | async def _action_stop() -> ToolResponse:
  function _action_open (line 974) | async def _action_open(url: str, page_id: str) -> ToolResponse:
  function _action_navigate (line 1046) | async def _action_navigate(url: str, page_id: str) -> ToolResponse:
  function _action_screenshot (line 1096) | async def _action_screenshot(
  function _action_click (line 1207) | async def _action_click(  # pylint: disable=too-many-branches
  function _action_type (line 1341) | async def _action_type(
  function _action_eval (line 1449) | async def _action_eval(page_id: str, code: str) -> ToolResponse:
  function _action_pdf (line 1505) | async def _action_pdf(page_id: str, path: str) -> ToolResponse:
  function _action_close (line 1538) | async def _action_close(page_id: str) -> ToolResponse:
  function _action_snapshot (line 1583) | async def _action_snapshot(
  function _action_navigate_back (line 1645) | async def _action_navigate_back(page_id: str) -> ToolResponse:
  function _action_evaluate (line 1677) | async def _action_evaluate(
  function _action_resize (line 1763) | async def _action_resize(
  function _action_console_messages (line 1810) | async def _action_console_messages(
  function _action_handle_dialog (line 1858) | async def _action_handle_dialog(
  function _action_file_upload (line 1916) | async def _action_file_upload(page_id: str, paths_json: str) -> ToolResp...
  function _action_fill_form (line 1976) | async def _action_fill_form(page_id: str, fields_json: str) -> ToolRespo...
  function _run_playwright_install (line 2062) | def _run_playwright_install() -> None:
  function _action_install (line 2073) | async def _action_install() -> ToolResponse:
  function _action_press_key (line 2137) | async def _action_press_key(page_id: str, key: str) -> ToolResponse:
  function _action_network_requests (line 2178) | async def _action_network_requests(
  function _action_run_code (line 2224) | async def _action_run_code(page_id: str, code: str) -> ToolResponse:
  function _action_drag (line 2281) | async def _action_drag(
  function _action_hover (line 2366) | async def _action_hover(
  function _action_select_option (line 2427) | async def _action_select_option(
  function _action_tabs (line 2497) | async def _action_tabs(  # pylint: disable=too-many-return-statements
  function _action_wait_for (line 2613) | async def _action_wait_for(

FILE: src/copaw/agents/tools/browser_snapshot.py
  function _get_indent_level (line 68) | def _get_indent_level(line: str) -> int:
  function _create_tracker (line 73) | def _create_tracker() -> dict[str, Any]:
  function _remove_nth_from_non_duplicates (line 101) | def _remove_nth_from_non_duplicates(
  function _compact_tree (line 112) | def _compact_tree(tree: str) -> str:
  function _process_line (line 135) | def _process_line(  # pylint: disable=too-many-return-statements
  function build_role_snapshot_from_aria (line 185) | def build_role_snapshot_from_aria(

FILE: src/copaw/agents/tools/desktop_screenshot.py
  function _tool_error (line 15) | def _tool_error(msg: str) -> ToolResponse:
  function _tool_ok (line 30) | def _tool_ok(path: str, message: str) -> ToolResponse:
  function _capture_mss (line 49) | def _capture_mss(path: str) -> ToolResponse:
  function _capture_macos_screencapture (line 69) | def _capture_macos_screencapture(
  function desktop_screenshot (line 101) | async def desktop_screenshot(

FILE: src/copaw/agents/tools/file_io.py
  function _resolve_file_path (line 16) | def _resolve_file_path(file_path: str) -> str:
  function read_file (line 35) | async def read_file(  # pylint: disable=too-many-return-statements
  function write_file (line 165) | async def write_file(
  function edit_file (line 213) | async def edit_file(
  function append_file (line 305) | async def append_file(

FILE: src/copaw/agents/tools/file_search.py
  function _is_text_file (line 71) | def _is_text_file(path: Path) -> bool:
  function grep_search (line 83) | async def grep_search(  # pylint: disable=too-many-branches
  function glob_search (line 221) | async def glob_search(
  function _relative_display (line 314) | def _relative_display(target: Path, root: Path) -> str:

FILE: src/copaw/agents/tools/get_current_time.py
  function get_current_time (line 16) | async def get_current_time() -> ToolResponse:
  function set_user_timezone (line 48) | async def set_user_timezone(timezone_name: str) -> ToolResponse:

FILE: src/copaw/agents/tools/get_token_usage.py
  function get_token_usage (line 12) | async def get_token_usage(

FILE: src/copaw/agents/tools/memory_search.py
  function create_memory_search_tool (line 7) | def create_memory_search_tool(memory_manager):

FILE: src/copaw/agents/tools/send_file.py
  function _auto_as_type (line 19) | def _auto_as_type(mt: str) -> str:
  function send_file_to_user (line 29) | async def send_file_to_user(

FILE: src/copaw/agents/tools/shell.py
  function _kill_process_tree_win32 (line 21) | def _kill_process_tree_win32(pid: int) -> None:
  function _execute_subprocess_sync (line 38) | def _execute_subprocess_sync(
  function execute_shell_command (line 126) | async def execute_shell_command(
  function smart_decode (line 280) | def smart_decode(data: bytes) -> str:

FILE: src/copaw/agents/tools/utils.py
  function truncate_output (line 10) | def truncate_output(
  function _truncate_line_by_bytes (line 79) | def _truncate_line_by_bytes(line: str, max_bytes: int) -> str:
  function _truncate_line_by_bytes_tail (line 106) | def _truncate_line_by_bytes_tail(line: str, max_bytes: int) -> str:
  function truncate_file_output (line 133) | def truncate_file_output(
  function truncate_shell_output (line 184) | def truncate_shell_output(text: str) -> str:
  function read_file_safe (line 226) | def read_file_safe(file_path: str) -> str:

FILE: src/copaw/agents/tools/view_image.py
  function view_image (line 24) | async def view_image(image_path: str) -> ToolResponse:

FILE: src/copaw/agents/utils/audio_transcription.py
  function _get_local_whisper_model (line 27) | def _get_local_whisper_model():
  function _url_for_provider (line 46) | def _url_for_provider(provider) -> Optional[Tuple[str, str]]:
  function _get_manager (line 71) | def _get_manager():
  function list_transcription_providers (line 87) | def list_transcription_providers() -> List[dict]:
  function get_configured_transcription_provider_id (line 115) | def get_configured_transcription_provider_id() -> str:
  function check_local_whisper_available (line 122) | def check_local_whisper_available() -> dict:
  function _transcribe_local_whisper (line 155) | async def _transcribe_local_whisper(file_path: str) -> Optional[str]:
  function _get_configured_provider_creds (line 203) | def _get_configured_provider_creds() -> Optional[Tuple[str, str]]:
  function _transcribe_whisper_api (line 236) | async def _transcribe_whisper_api(file_path: str) -> Optional[str]:
  function transcribe_audio (line 295) | async def transcribe_audio(file_path: str) -> Optional[str]:

FILE: src/copaw/agents/utils/copaw_token_counter.py
  class CopawTokenCounter (line 20) | class CopawTokenCounter(HuggingFaceTokenCounter):
    method __init__ (line 35) | def __init__(
    method count (line 99) | async def count(
    method estimate_tokens (line 138) | def estimate_tokens(self, text: str) -> int:
  function get_copaw_token_counter (line 160) | def get_copaw_token_counter(

FILE: src/copaw/agents/utils/file_handling.py
  function _resolve_local_path (line 23) | def _resolve_local_path(
  function _download_remote_to_path (line 56) | def _download_remote_to_path(url: str, local_file_path: Path) -> None:
  function _guess_suffix_from_url_headers (line 96) | def _guess_suffix_from_url_headers(url: str) -> Optional[str]:
  function _guess_suffix_from_file_content (line 130) | def _guess_suffix_from_file_content(path: Path) -> Optional[str]:
  function download_file_from_base64 (line 146) | async def download_file_from_base64(
  function download_file_from_url (line 184) | async def download_file_from_url(

FILE: src/copaw/agents/utils/message_processing.py
  function _process_single_file_block (line 25) | async def _process_single_file_block(
  function _extract_source_and_filename (line 76) | def _extract_source_and_filename(block: dict, block_type: str):
  function _media_type_from_path (line 95) | def _media_type_from_path(path: str) -> str:
  function _convert_audio_to_wav (line 114) | def _convert_audio_to_wav(src_path: str) -> Optional[str]:
  function _update_block_with_local_path (line 181) | def _update_block_with_local_path(
  function _handle_download_failure (line 206) | def _handle_download_failure(block_type: str) -> Optional[dict]:
  function _process_audio_block (line 217) | async def _process_audio_block(
  function _process_single_block (line 292) | async def _process_single_block(
  function process_file_and_media_blocks_in_message (line 374) | async def process_file_and_media_blocks_in_message(msg) -> None:
  function is_first_user_interaction (line 419) | def is_first_user_interaction(messages: list) -> bool:
  function prepend_to_message_content (line 442) | def prepend_to_message_content(msg, guidance: str) -> None:

FILE: src/copaw/agents/utils/setup_utils.py
  function copy_md_files (line 14) | def copy_md_files(

FILE: src/copaw/agents/utils/tool_message_utils.py
  function extract_tool_ids (line 13) | def extract_tool_ids(msg) -> tuple[set[str], set[str]]:
  function check_valid_messages (line 35) | def check_valid_messages(messages: list) -> bool:
  function _reorder_tool_results (line 56) | def _reorder_tool_results(msgs: list) -> list:
  function _remove_unpaired_tool_messages (line 104) | def _remove_unpaired_tool_messages(msgs: list) -> list:
  function _dedup_tool_blocks (line 150) | def _dedup_tool_blocks(msgs: list) -> list:
  function _remove_invalid_tool_blocks (line 179) | def _remove_invalid_tool_blocks(msgs: list) -> list:
  function _repair_empty_tool_inputs (line 250) | def _repair_empty_tool_inputs(
  function _sanitize_tool_messages (line 322) | def _sanitize_tool_messages(msgs: list) -> list:
  function _truncate_text (line 359) | def _truncate_text(text: str, max_length: int) -> str:

FILE: src/copaw/app/_app.py
  class DynamicMultiAgentRunner (line 49) | class DynamicMultiAgentRunner:
    method __init__ (line 56) | def __init__(self):
    method set_multi_agent_manager (line 60) | def set_multi_agent_manager(self, manager):
    method _get_workspace_runner (line 64) | async def _get_workspace_runner(self, request):
    method stream_query (line 95) | async def stream_query(self, request, *args, **kwargs):
    method query_handler (line 119) | async def query_handler(self, request, *args, **kwargs):
    method __aenter__ (line 127) | async def __aenter__(self):
    method __aexit__ (line 133) | async def __aexit__(self, exc_type, exc_val, exc_tb):
  function lifespan (line 149) | async def lifespan(
  function _resolve_console_static_dir (line 270) | def _resolve_console_static_dir() -> str:
  function read_root (line 307) | def read_root():
  function get_version (line 322) | def get_version():
  function _serve_console_index (line 349) | def _serve_console_index():
  function _console_logo (line 356) | def _console_logo():
  function _console_dark_logo (line 363) | def _console_dark_logo():
  function _console_icon (line 370) | def _console_icon():
  function _console_dark_icon (line 377) | def _console_dark_icon():
  function _console_spa_alias (line 394) | def _console_spa_alias(full_path: str = ""):
  function _console_spa (line 401) | def _console_spa(full_path: str):

FILE: src/copaw/app/agent_config_watcher.py
  function _heartbeat_hash (line 28) | def _heartbeat_hash(hb: Optional[HeartbeatConfig]) -> int:
  class AgentConfigWatcher (line 35) | class AgentConfigWatcher:
    method __init__ (line 42) | def __init__(
    method start (line 74) | async def start(self) -> None:
    method stop (line 86) | async def stop(self) -> None:
    method _snapshot (line 101) | def _snapshot(self) -> None:
    method _channels_hash (line 134) | def _channels_hash(channels: ChannelConfig) -> int:
    method _channel_dump (line 139) | def _channel_dump(ch: Any) -> Any:
    method _reload_one_channel (line 149) | async def _reload_one_channel(
    method _apply_channel_changes (line 178) | async def _apply_channel_changes(self, agent_config: Any) -> None:
    method _apply_heartbeat_change (line 218) | async def _apply_heartbeat_change(self, agent_config: Any) -> None:
    method _poll_loop (line 240) | async def _poll_loop(self) -> None:
    method _check (line 252) | async def _check(self) -> None:

FILE: src/copaw/app/agent_context.py
  function get_agent_for_request (line 22) | async def get_agent_for_request(
  function get_active_agent_id (line 86) | def get_active_agent_id() -> str:
  function set_current_agent_id (line 99) | def set_current_agent_id(agent_id: str) -> None:
  function get_current_agent_id (line 108) | def get_current_agent_id() -> str:

FILE: src/copaw/app/approvals/service.py
  class PendingApproval (line 36) | class PendingApproval:
  class ApprovalService (line 58) | class ApprovalService:
    method __init__ (line 66) | def __init__(self) -> None:
    method set_channel_manager (line 72) | def set_channel_manager(self, channel_manager: Any) -> None:
    method create_pending (line 80) | async def create_pending(
    method resolve_request (line 116) | async def resolve_request(
    method get_request (line 137) | async def get_request(self, request_id: str) -> PendingApproval | None:
    method get_pending_by_session (line 144) | async def get_pending_by_session(
    method consume_approval (line 158) | async def consume_approval(
    method _gc_pending_locked (line 209) | def _gc_pending_locked(self) -> None:
    method _gc_completed_locked (line 243) | def _gc_completed_locked(self) -> None:
  function get_approval_service (line 276) | def get_approval_service() -> ApprovalService:

FILE: src/copaw/app/auth.py
  function _chmod_best_effort (line 64) | def _chmod_best_effort(path, mode: int) -> None:
  function _prepare_secret_parent (line 71) | def _prepare_secret_parent(path) -> None:
  function _hash_password (line 81) | def _hash_password(
  function verify_password (line 92) | def verify_password(password: str, stored_hash: str, salt: str) -> bool:
  function _get_jwt_secret (line 103) | def _get_jwt_secret() -> str:
  function create_token (line 114) | def create_token(username: str) -> str:
  function verify_token (line 135) | def verify_token(token: str) -> Optional[str]:
  function _load_auth_data (line 166) | def _load_auth_data() -> dict:
  function _save_auth_data (line 183) | def _save_auth_data(data: dict) -> None:
  function is_auth_enabled (line 191) | def is_auth_enabled() -> bool:
  function has_registered_users (line 203) | def has_registered_users() -> bool:
  function register_user (line 214) | def register_user(username: str, password: str) -> Optional[str]:
  function auto_register_from_env (line 241) | def auto_register_from_env() -> None:
  function authenticate (line 278) | def authenticate(username: str, password: str) -> Optional[str]:
  class AuthMiddleware (line 302) | class AuthMiddleware(BaseHTTPMiddleware):
    method dispatch (line 305) | async def dispatch(
    method _should_skip_auth (line 336) | def _should_skip_auth(request: Request) -> bool:
    method _extract_token (line 360) | def _extract_token(request: Request) -> Optional[str]:

FILE: src/copaw/app/channels/__init__.py
  function __getattr__ (line 7) | def __getattr__(name: str):

FILE: src/copaw/app/channels/base.py
  class BaseChannel (line 69) | class BaseChannel(ABC):
    method __init__ (line 79) | def __init__(
    method _is_native_payload (line 126) | def _is_native_payload(self, payload: Any) -> bool:
    method get_debounce_key (line 130) | def get_debounce_key(self, payload: Any) -> str:
    method merge_native_items (line 145) | def merge_native_items(self, items: List[Any]) -> Any:
    method merge_requests (line 176) | def merge_requests(self, requests: List[Any]) -> Any:
    method _on_debounce_buffer_append (line 208) | def _on_debounce_buffer_append(
    method _content_has_text (line 222) | def _content_has_text(self, contents: List[Any]) -> bool:
    method _content_has_audio (line 240) | def _content_has_audio(self, contents: List[Any]) -> bool:
    method _apply_no_text_debounce (line 247) | def _apply_no_text_debounce(
    method _check_allowlist (line 281) | def _check_allowlist(
    method _check_group_mention (line 305) | def _check_group_mention(
    method set_enqueue (line 317) | def set_enqueue(self, cb: EnqueueCallback) -> None:
    method from_env (line 322) | def from_env(
    method from_config (line 330) | def from_config(
    method resolve_session_id (line 341) | def resolve_session_id(
    method build_agent_request_from_user_content (line 353) | def build_agent_request_from_user_content(
    method build_agent_request_from_native (line 388) | def build_agent_request_from_native(
    method _payload_to_request (line 404) | def _payload_to_request(self, payload: Any) -> "AgentRequest":
    method get_to_handle_from_request (line 416) | def get_to_handle_from_request(self, request: "AgentRequest") -> str:
    method get_on_reply_sent_args (line 423) | def get_on_reply_sent_args(
    method refresh_webhook_or_token (line 437) | async def refresh_webhook_or_token(self) -> None:
    method consume_one (line 443) | async def consume_one(self, payload: Any) -> None:
    method _consume_one_request (line 481) | async def _consume_one_request(self, payload: Any) -> None:
    method _run_process_loop (line 541) | async def _run_process_loop(
    method _get_response_error_message (line 584) | def _get_response_error_message(self, last_response: Any) -> Optional[...
    method _before_consume_process (line 605) | async def _before_consume_process(self, request: "AgentRequest") -> None:
    method on_event_message_completed (line 611) | async def on_event_message_completed(
    method on_event_response (line 624) | async def on_event_response(
    method _on_consume_error (line 631) | async def _on_consume_error(
    method send_response (line 648) | async def send_response(
    method _message_to_content_parts (line 663) | def _message_to_content_parts(
    method send_message_content (line 674) | async def send_message_content(
    method send_content_parts (line 700) | async def send_content_parts(
    method send_media (line 753) | async def send_media(
    method _response_to_text (line 766) | def _response_to_text(self, response: "AgentResponse") -> str:
    method clone (line 789) | def clone(self, config) -> "BaseChannel":
    method start (line 816) | async def start(self) -> None:
    method stop (line 819) | async def stop(self) -> None:
    method send (line 822) | async def send(
    method to_handle_from_target (line 833) | def to_handle_from_target(self, *, user_id: str, session_id: str) -> str:
    method send_event (line 842) | async def send_event(

FILE: src/copaw/app/channels/console/channel.py
  function _ts (line 52) | def _ts() -> str:
  class ConsoleChannel (line 56) | class ConsoleChannel(BaseChannel):
    method __init__ (line 70) | def __init__(
    method media_dir (line 119) | def media_dir(self) -> Path:
    method from_env (line 124) | def from_env(
    method from_config (line 138) | def from_config(
    method resolve_session_id (line 174) | def resolve_session_id(
    method _resolve_console_upload_refs (line 187) | def _resolve_console_upload_refs(
    method build_agent_request_from_native (line 243) | def build_agent_request_from_native(self, native_payload: Any) -> Any:
    method stream_one (line 266) | async def stream_one(self, payload: Any) -> AsyncGenerator[str, None]:
    method consume_one (line 355) | async def consume_one(self, payload: Any) -> None:
    method _print_parts (line 362) | def _print_parts(
    method _print_error (line 394) | def _print_error(self, err: str) -> None:
    method _parts_to_text (line 401) | def _parts_to_text(
    method send (line 424) | async def send(
    method send_content_parts (line 443) | async def send_content_parts(
    method start (line 461) | async def start(self) -> None:
    method stop (line 467) | async def stop(self) -> None:

FILE: src/copaw/app/channels/dingtalk/ai_card.py
  class ActiveAICard (line 21) | class ActiveAICard:
  class AICardPendingStore (line 33) | class AICardPendingStore:
    method __init__ (line 36) | def __init__(self, path: Path):
    method path (line 40) | def path(self) -> Path:
    method load (line 43) | def load(self) -> List[dict]:
    method save (line 55) | def save(self, cards: Dict[str, ActiveAICard]) -> None:
  function is_group_conversation (line 82) | def is_group_conversation(conversation_id: str) -> bool:
  function thinking_or_tool_to_card_text (line 86) | def thinking_or_tool_to_card_text(text: str, title: str) -> str:
  function to_pending_record (line 99) | def to_pending_record(card: ActiveAICard) -> dict:

FILE: src/copaw/app/channels/dingtalk/channel.py
  class DingTalkChannel (line 81) | class DingTalkChannel(BaseChannel):
    method __init__ (line 97) | def __init__(
    method from_env (line 183) | def from_env(
    method from_config (line 218) | def from_config(
    method resolve_session_id (line 257) | def resolve_session_id(
    method build_agent_request_from_native (line 269) | def build_agent_request_from_native(
    method to_handle_from_target (line 293) | def to_handle_from_target(self, *, user_id: str, session_id: str) -> str:
    method _route_from_handle (line 298) | def _route_from_handle(self, to_handle: str) -> dict:
    method _session_webhook_store_path (line 316) | def _session_webhook_store_path(self) -> Path:
    method _load_session_webhook_store_from_disk (line 326) | def _load_session_webhook_store_from_disk(self) -> None:
    method _save_session_webhook_store_to_disk (line 345) | def _save_session_webhook_store_to_disk(self) -> None:
    method _save_session_webhook (line 364) | async def _save_session_webhook(
    method _load_session_webhook (line 387) | async def _load_session_webhook(self, webhook_key: str) -> Optional[str]:
    method _try_accept_message (line 421) | def _try_accept_message(self, msg_id: str) -> bool:
    method _release_message_ids (line 442) | def _release_message_ids(self, msg_ids: List[str]) -> None:
    method _safe_set_future_result (line 457) | def _safe_set_future_result(
    method _reply_sync (line 469) | def _reply_sync(self, meta: Dict[str, Any], text: str) -> None:
    method _reply_sync_batch (line 488) | def _reply_sync_batch(self, meta: Dict[str, Any], text: str) -> None:
    method _ack_early (line 506) | def _ack_early(self, meta: Dict[str, Any], text: str) -> None:
    method _get_session_webhook (line 542) | def _get_session_webhook(
    method _parts_to_single_text (line 561) | def _parts_to_single_text(
    method _send_payload_via_session_webhook (line 596) | async def _send_payload_via_session_webhook(
    method _send_via_session_webhook (line 669) | async def _send_via_session_webhook(
    method _upload_media (line 694) | async def _upload_media(
    method _fetch_bytes_from_url (line 778) | async def _fetch_bytes_from_url(self, url: str) -> Optional[bytes]:
    method _get_session_webhook_for_send (line 822) | async def _get_session_webhook_for_send(
    method _map_upload_type (line 879) | def _map_upload_type(self, part: OutgoingContentPart) -> Optional[str]:
    method _send_media_part_via_webhook (line 897) | async def _send_media_part_via_webhook(
    method send_content_parts (line 1204) | async def send_content_parts(
    method merge_native_items (line 1312) | def merge_native_items(self, items: List[Any]) -> Any:
    method _on_debounce_buffer_append (line 1316) | def _on_debounce_buffer_append(
    method _run_process_loop (line 1335) | async def _run_process_loop(
    method _process_one_request (line 1412) | async def _process_one_request(
    method _merge_native (line 1646) | def _merge_native(self, items: list) -> dict:
    method _run_stream_forever (line 1701) | def _run_stream_forever(self) -> None:
    method _stream_loop (line 1716) | async def _stream_loop(self) -> None:
    method start (line 1769) | async def start(self) -> None:
    method stop (line 1810) | async def stop(self) -> None:
    method _ai_card_enabled (line 1851) | def _ai_card_enabled(self) -> bool:
    method _build_ai_card_initial_text (line 1858) | def _build_ai_card_initial_text(self) -> str:
    method _merge_ai_card_text (line 1861) | def _merge_ai_card_text(self, current: str, incoming: str) -> str:
    method _save_active_cards (line 1872) | async def _save_active_cards(self) -> None:
    method _mark_card_failed (line 1876) | async def _mark_card_failed(self, conversation_id: str) -> None:
    method _create_ai_card (line 1885) | async def _create_ai_card(
    method _stream_ai_card (line 2058) | async def _stream_ai_card(
    method _finish_ai_card (line 2157) | async def _finish_ai_card(
    method _recover_active_cards (line 2168) | async def _recover_active_cards(self) -> None:
    method send (line 2210) | async def send(
    method _get_access_token (line 2268) | async def _get_access_token(self) -> str:
    method _get_message_file_download_url (line 2308) | async def _get_message_file_download_url(
    method _download_media_to_local (line 2350) | async def _download_media_to_local(
    method _fetch_and_download_media (line 2413) | async def _fetch_and_download_media(
    method _guess_filename_and_ext (line 2436) | def _guess_filename_and_ext(
    method _is_public_http_url (line 2494) | def _is_public_http_url(self, s: Optional[str]) -> bool:

FILE: src/copaw/app/channels/dingtalk/content_utils.py
  function dingtalk_content_from_type (line 33) | def dingtalk_content_from_type(mapped: str, url: str) -> Any:
  function parse_data_url (line 48) | def parse_data_url(data_url: str) -> tuple[bytes, Optional[str]]:
  function sender_from_chatbot_message (line 63) | def sender_from_chatbot_message(incoming_message: Any) -> tuple[str, bool]:
  function conversation_id_from_chatbot_message (line 89) | def conversation_id_from_chatbot_message(incoming_message: Any) -> str:
  function conversation_type_from_chatbot_message (line 99) | def conversation_type_from_chatbot_message(incoming_message: Any) -> str:
  function short_session_id_from_conversation_id (line 117) | def short_session_id_from_conversation_id(conversation_id: str) -> str:
  function session_param_from_webhook_url (line 125) | def session_param_from_webhook_url(url: str) -> Optional[str]:
  function get_type_mapping (line 139) | def get_type_mapping() -> dict:

FILE: src/copaw/app/channels/dingtalk/handler.py
  class DingTalkChannelHandler (line 39) | class DingTalkChannelHandler(dingtalk_stream.ChatbotHandler):
    method __init__ (line 43) | def __init__(
    method _emit_native_threadsafe (line 58) | def _emit_native_threadsafe(self, native: dict) -> None:
    method _fetch_download_url_and_content (line 65) | def _fetch_download_url_and_content(
    method _extract_filename_hint (line 92) | def _extract_filename_hint(payload: Dict[str, Any]) -> Optional[str]:
    method _parse_rich_content (line 100) | def _parse_rich_content(
    method process (line 189) | async def process(self, callback: CallbackMessage) -> tuple[int, str]:

FILE: src/copaw/app/channels/dingtalk/markdown.py
  function ensure_list_spacing (line 7) | def ensure_list_spacing(text: str) -> str:
  function dedent_code_blocks (line 44) | def dedent_code_blocks(text: str) -> str:
  function format_code_blocks (line 73) | def format_code_blocks(text: str, prefix: str = "·") -> str:
  function normalize_dingtalk_markdown (line 96) | def normalize_dingtalk_markdown(

FILE: src/copaw/app/channels/dingtalk/utils.py
  function guess_suffix_from_file_content (line 23) | def guess_suffix_from_file_content(path: Path) -> Optional[str]:

FILE: src/copaw/app/channels/discord_/channel.py
  class DiscordChannel (line 39) | class DiscordChannel(BaseChannel):
    method __init__ (line 44) | def __init__(
    method from_env (line 229) | def from_env(
    method from_config (line 259) | def from_config(
    method _resolve_target (line 286) | async def _resolve_target(self, to_handle, _meta):
    method _chunk_text (line 306) | def _chunk_text(text: str, max_len: int = 2000) -> list[str]:
    method send (line 369) | async def send(
    method send_content_parts (line 403) | async def send_content_parts(
    method send_media (line 432) | async def send_media(
    method _run (line 496) | async def _run(self) -> None:
    method start (line 501) | async def start(self) -> None:
    method stop (line 506) | async def stop(self) -> None:
    method resolve_session_id (line 518) | def resolve_session_id(
    method get_to_handle_from_request (line 534) | def get_to_handle_from_request(self, request: Any) -> str:
    method build_agent_request_from_native (line 540) | def build_agent_request_from_native(self, native_payload) -> Any:
    method to_handle_from_target (line 560) | def to_handle_from_target(self, *, user_id: str, session_id: str) -> str:
    method _route_from_handle (line 563) | def _route_from_handle(self, to_handle: str) -> dict:

FILE: src/copaw/app/channels/feishu/channel.py
  function _declare_namespace_shim (line 71) | def _declare_namespace_shim(_name: str) -> None:
  class FeishuChannel (line 145) | class FeishuChannel(BaseChannel):
    method __init__ (line 156) | def __init__(
    method from_env (line 231) | def from_env(
    method from_config (line 262) | def from_config(
    method resolve_session_id (line 293) | def resolve_session_id(
    method build_agent_request_from_native (line 310) | def build_agent_request_from_native(
    method merge_native_items (line 348) | def merge_native_items(self, items: List[Any]) -> Any:
    method to_handle_from_target (line 369) | def to_handle_from_target(self, *, user_id: str, session_id: str) -> str:
    method _route_from_handle (line 375) | def _route_from_handle(self, to_handle: str) -> Dict[str, str]:
    method _get_tenant_access_token (line 398) | async def _get_tenant_access_token(self) -> str:
    method _fetch_bot_open_id (line 447) | async def _fetch_bot_open_id(self) -> Optional[str]:
    method _get_user_name_by_open_id (line 464) | async def _get_user_name_by_open_id(self, open_id: str) -> Optional[str]:
    method _emit_request_threadsafe (line 585) | def _emit_request_threadsafe(self, request: Any) -> None:
    method _on_message_sync (line 590) | def _on_message_sync(self, data: "P2ImMessageReceiveV1") -> None:
    method _on_message (line 605) | async def _on_message(self, data: "P2ImMessageReceiveV1") -> None:
    method _add_reaction (line 856) | async def _add_reaction(
    method _add_reaction_sync (line 878) | def _add_reaction_sync(self, message_id: str, emoji_type: str) -> None:
    method _download_image_resource (line 904) | async def _download_image_resource(
    method _download_file_resource (line 947) | async def _download_file_resource(
    method _receive_id_store_path (line 1015) | def _receive_id_store_path(self) -> Path:
    method _load_receive_id_store_from_disk (line 1026) | def _load_receive_id_store_from_disk(self) -> None:
    method _save_receive_id_store_to_disk (line 1055) | def _save_receive_id_store_to_disk(self) -> None:
    method _save_receive_id (line 1078) | async def _save_receive_id(
    method _load_receive_id (line 1102) | async def _load_receive_id(
    method _build_post_content (line 1115) | def _build_post_content(
    method _upload_image_sync (line 1135) | def _upload_image_sync(self, data: bytes, filename: str) -> Optional[s...
    method _upload_file (line 1175) | async def _upload_file(self, path_or_url: str) -> Optional[str]:
    method _fetch_bytes_from_url (line 1250) | async def _fetch_bytes_from_url(self, url: str) -> Optional[bytes]:
    method _send_message_sync (line 1266) | def _send_message_sync(
    method _send_text (line 1322) | async def _send_text(
    method _part_to_image_bytes (line 1365) | async def _part_to_image_bytes(
    method _send_image (line 1412) | async def _send_image(
    method _part_to_file_path_or_url (line 1457) | async def _part_to_file_path_or_url(
    method _send_file (line 1515) | async def _send_file(
    method _get_receive_for_send (line 1557) | async def _get_receive_for_send(
    method send_content_parts (line 1632) | async def send_content_parts(  # type: ignore[override]
    method _run_process_loop (line 1736) | async def _run_process_loop(
    method send (line 1784) | async def send(
    method get_to_handle_from_request (line 1806) | def get_to_handle_from_request(self, request: Any) -> str:
    method get_on_reply_sent_args (line 1818) | def get_on_reply_sent_args(
    method _before_consume_process (line 1829) | async def _before_consume_process(self, request: Any) -> None:
    method _run_ws_forever (line 1841) | def _run_ws_forever(self) -> None:
    method start (line 1930) | async def start(self) -> None:
    method stop (line 1990) | async def stop(self) -> None:

FILE: src/copaw/app/channels/feishu/utils.py
  function short_session_id_from_full_id (line 11) | def short_session_id_from_full_id(full_id: str) -> str:
  function sender_display_string (line 17) | def sender_display_string(
  function extract_json_key (line 28) | def extract_json_key(content: Optional[str], *keys: str) -> Optional[str]:
  function extract_post_text (line 43) | def extract_post_text(content: Optional[str]) -> Optional[str]:
  function _extract_post_keys (line 95) | def _extract_post_keys(
  function extract_post_image_keys (line 125) | def extract_post_image_keys(content: Optional[str]) -> list[str]:
  function extract_post_media_file_keys (line 130) | def extract_post_media_file_keys(content: Optional[str]) -> list[str]:
  function normalize_feishu_md (line 135) | def normalize_feishu_md(text: str) -> str:
  function _parse_md_table (line 146) | def _parse_md_table(table_lines: List[str]) -> Optional[Dict[str, Any]]:
  function _convert_md_headings_to_bold (line 229) | def _convert_md_headings_to_bold(text: str) -> str:
  function _build_elements (line 237) | def _build_elements(text: str) -> List[Dict[str, Any]]:
  function _split_elements (line 286) | def _split_elements(
  function build_interactive_content (line 316) | def build_interactive_content(text: str) -> str:
  function build_interactive_content_chunks (line 323) | def build_interactive_content_chunks(text: str) -> List[str]:

FILE: src/copaw/app/channels/imessage/channel.py
  class IMessageChannel (line 38) | class IMessageChannel(BaseChannel):
    method __init__ (line 41) | def __init__(
    method from_env (line 81) | def from_env(
    method from_config (line 103) | def from_config(
    method _ensure_imsg (line 126) | def _ensure_imsg(self) -> str:
    method _send_sync (line 137) | def _send_sync(
    method _emit_request_threadsafe (line 169) | def _emit_request_threadsafe(self, request: Any) -> None:
    method _watcher_loop (line 174) | def _watcher_loop(self) -> None:
    method build_agent_request_from_native (line 248) | def build_agent_request_from_native(self, native_payload: Any) -> Any:
    method _on_consume_error (line 265) | async def _on_consume_error(
    method start (line 274) | async def start(self) -> None:
    method stop (line 286) | async def stop(self) -> None:
    method send (line 294) | async def send(
    method send_content_parts (line 305) | async def send_content_parts(
    method _extract_url_and_filename (line 384) | def _extract_url_and_filename(self, part: OutgoingContentPart):
    method _get_file_extension (line 414) | def _get_file_extension(
    method _sanitize_filename (line 434) | def _sanitize_filename(self, filename: str) -> str:
    method _handle_local_file (line 465) | async def _handle_local_file(self, url: str) -> Optional[str]:
    method _handle_remote_url (line 483) | async def _handle_remote_url(
    method _handle_data_url (line 508) | async def _handle_data_url(
    method send_media (line 587) | async def send_media(

FILE: src/copaw/app/channels/manager.py
  function _drain_same_key (line 42) | def _drain_same_key(
  function _process_batch (line 65) | async def _process_batch(ch: BaseChannel, batch: List[Any]) -> None:
  function _put_pending_merged (line 94) | def _put_pending_merged(
  class ChannelManager (line 114) | class ChannelManager:
    method __init__ (line 119) | def __init__(self, channels: List[BaseChannel]):
    method from_env (line 136) | def from_env(
    method from_config (line 158) | def from_config(
    method _make_enqueue_cb (line 249) | def _make_enqueue_cb(self, channel_id: str) -> Callable[[Any], None]:
    method _enqueue_one (line 257) | def _enqueue_one(self, channel_id: str, payload: Any) -> None:
    method enqueue (line 289) | def enqueue(self, channel_id: str, payload: Any) -> None:
    method _consume_channel_loop (line 307) | async def _consume_channel_loop(
    method start_all (line 350) | async def start_all(self) -> None:
    method stop_all (line 379) | async def stop_all(self) -> None:
    method get_channel (line 412) | async def get_channel(self, channel: str) -> Optional[BaseChannel]:
    method replace_channel (line 419) | async def replace_channel(
    method send_event (line 484) | async def send_event(
    method send_text (line 513) | async def send_text(

FILE: src/copaw/app/channels/matrix/channel.py
  class MatrixChannel (line 45) | class MatrixChannel(BaseChannel):
    method __init__ (line 49) | def __init__(
    method _mxc_to_http (line 88) | def _mxc_to_http(self, mxc_url: str) -> str:
    method _check_allowlist (line 103) | def _check_allowlist(
    method from_env (line 116) | def from_env(
    method from_config (line 126) | def from_config(
    method build_agent_request_from_native (line 153) | def build_agent_request_from_native(
    method get_to_handle_from_request (line 175) | def get_to_handle_from_request(self, request: AgentRequest) -> str:
    method _handle_event (line 182) | async def _handle_event(
    method _message_callback (line 215) | async def _message_callback(
    method _media_callback (line 242) | async def _media_callback(
    method send_content_parts (line 281) | async def send_content_parts(
    method send_media (line 306) | async def send_media(  # pylint: disable=too-many-branches
    method start (line 419) | async def start(self) -> None:
    method stop (line 478) | async def stop(self) -> None:
    method send (line 485) | async def send(

FILE: src/copaw/app/channels/mattermost/channel.py
  function _download_mattermost_file (line 43) | async def _download_mattermost_file(
  class MattermostChannel (line 74) | class MattermostChannel(BaseChannel):
    method __init__ (line 100) | def __init__(
    method from_config (line 171) | def from_config(
    method from_env (line 210) | def from_env(
    method start (line 245) | async def start(self) -> None:
    method stop (line 255) | async def stop(self) -> None:
    method _init_bot_info (line 276) | async def _init_bot_info(self) -> bool:
    method _run (line 298) | async def _run(self) -> None:
    method _websocket_loop (line 305) | async def _websocket_loop(self) -> None:
    method _is_triggered (line 362) | def _is_triggered(self, post: dict, channel_type: str) -> bool:
    method _get_context_prefix (line 384) | async def _get_context_prefix(
    method _process_attachments (line 422) | async def _process_attachments(self, post: dict) -> list[Any]:
    method _on_posted_event (line 461) | async def _on_posted_event(self, event_data: dict) -> None:
    method _start_typing (line 576) | def _start_typing(self, mm_channel_id: str, root_id: str = "") -> None:
    method _stop_typing (line 583) | def _stop_typing(self, mm_channel_id: str) -> None:
    method _typing_loop (line 589) | async def _typing_loop(
    method _get_thread_target_order (line 621) | def _get_thread_target_order(
    method _fetch_thread_history (line 666) | async def _fetch_thread_history(
    method _fetch_channel_history (line 733) | async def _fetch_channel_history(
    method _download_file (line 771) | async def _download_file(
    method _upload_file (line 785) | async def _upload_file(
    method _post_message (line 828) | async def _post_message(
    method _chunk_text (line 863) | def _chunk_text(self, text: str) -> list[str]:
    method send (line 885) | async def send(
    method send_media (line 910) | async def send_media(
    method resolve_session_id (line 958) | def resolve_session_id(
    method build_agent_request_from_native (line 979) | def build_agent_request_from_native(self, native_payload: Any) -> Any:
    method get_to_handle_from_request (line 998) | def get_to_handle_from_request(self, request: Any) -> str:
    method to_handle_from_target (line 1010) | def to_handle_from_target(self, *, user_id: str, session_id: str) -> str:

FILE: src/copaw/app/channels/mqtt/channel.py
  class MQTTChannel (line 29) | class MQTTChannel(BaseChannel):
    method __init__ (line 35) | def __init__(
    method from_env (line 86) | def from_env(
    method from_config (line 121) | def from_config(
    method _validate_config (line 196) | def _validate_config(self):
    method _on_connect (line 205) | def _on_connect(
    method _on_disconnect (line 223) | def _on_disconnect(
    method _on_message (line 235) | def _on_message(self, _client, _userdata, msg):
    method start (line 286) | async def start(self) -> None:
    method stop (line 345) | async def stop(self) -> None:
    method send (line 354) | async def send(
    method send_media (line 379) | async def send_media(
    method resolve_session_id (line 431) | def resolve_session_id(
    method get_to_handle_from_request (line 438) | def get_to_handle_from_request(self, request: Any) -> str:
    method build_agent_request_from_native (line 448) | def build_agent_request_from_native(self, native_payload: Any) -> Any:
    method to_handle_from_target (line 467) | def to_handle_from_target(self, *, user_id: str, session_id: str) -> str:

FILE: src/copaw/app/channels/qq/channel.py
  class _MessageEventSpec (line 94) | class _MessageEventSpec:
  class _WSState (line 131) | class _WSState:
  class _HeartbeatController (line 143) | class _HeartbeatController:
    method __init__ (line 146) | def __init__(
    method start (line 158) | def start(self, interval_ms: float) -> None:
    method stop (line 162) | def stop(self) -> None:
    method _schedule (line 167) | def _schedule(self) -> None:
    method _send_ping (line 177) | def _send_ping(self) -> None:
  class QQApiError (line 193) | class QQApiError(RuntimeError):
    method __init__ (line 196) | def __init__(self, path: str, status: int, data: Any):
  function _sanitize_qq_text (line 203) | def _sanitize_qq_text(text: str) -> tuple[str, bool]:
  function _aggressive_sanitize_qq_text (line 214) | def _aggressive_sanitize_qq_text(text: str) -> tuple[str, bool]:
  function _is_url_content_error (line 226) | def _is_url_content_error(exc: Exception) -> bool:
  function _as_bool (line 241) | def _as_bool(value: Any) -> bool:
  function _should_plaintext_fallback_from_markdown (line 249) | def _should_plaintext_fallback_from_markdown(exc: Exception) -> bool:
  function _get_api_base (line 272) | def _get_api_base() -> str:
  function _get_channel_url_sync (line 277) | def _get_channel_url_sync(access_token: str) -> str:
  function _get_next_msg_seq (line 315) | def _get_next_msg_seq(msg_id: str) -> int:
  function _api_request_async (line 325) | async def _api_request_async(
  function _send_message_async (line 348) | async def _send_message_async(
  function _media_path (line 397) | def _media_path(
  function _upload_media_async (line 409) | async def _upload_media_async(
  function _send_media_message_async (line 439) | async def _send_media_message_async(
  function _download_qq_file (line 468) | async def _download_qq_file(
  class QQChannel (line 503) | class QQChannel(BaseChannel):
    method __init__ (line 510) | def __init__(
    method _get_access_token_sync (line 549) | def _get_access_token_sync(self) -> str:
    method _get_access_token_async (line 585) | async def _get_access_token_async(self) -> str:
    method _clear_token_cache (line 617) | def _clear_token_cache(self) -> None:
    method from_env (line 622) | def from_env(
    method from_config (line 638) | def from_config(
    method _resolve_send_path (line 661) | def _resolve_send_path(
    method _dispatch_text (line 695) | async def _dispatch_text(
    method _send_text_with_fallback (line 726) | async def _send_text_with_fallback(
    method _try_aggressive_url_fallback (line 816) | async def _try_aggressive_url_fallback(
    method _send_images (line 858) | async def _send_images(
    method send (line 899) | async def send(
    method _resolve_attachment_type (line 990) | def _resolve_attachment_type(
    method _download_attachment_sync (line 1009) | def _download_attachment_sync(
    method _make_content_part (line 1034) | def _make_content_part(
    method _parse_qq_attachments (line 1063) | def _parse_qq_attachments(
    method build_agent_request_from_native (line 1099) | def build_agent_request_from_native(self, native_payload: Any) -> Any:
    method _handle_msg_event (line 1127) | def _handle_msg_event(
    method _handle_ws_payload (line 1188) | def _handle_ws_payload(
    method _compute_reconnect_delay (line 1279) | def _compute_reconnect_delay(self, state: _WSState) -> float:
    method _ws_connect_once (line 1308) | def _ws_connect_once(
    method _run_ws_forever (line 1378) | def _run_ws_forever(self) -> None:
    method start (line 1392) | async def start(self) -> None:
    method stop (line 1411) | async def stop(self) -> None:

FILE: src/copaw/app/channels/registry.py
  function _load_builtin_channels (line 42) | def _load_builtin_channels() -> dict[str, type[BaseChannel]]:
  function _get_cached_builtin_channels (line 78) | def _get_cached_builtin_channels() -> dict[str, type[BaseChannel]]:
  function clear_builtin_channel_cache (line 87) | def clear_builtin_channel_cache() -> None:
  function _discover_custom_channels (line 94) | def _discover_custom_channels() -> dict[str, type[BaseChannel]]:
  function get_channel_registry (line 132) | def get_channel_registry() -> dict[str, type[BaseChannel]]:

FILE: src/copaw/app/channels/renderer.py
  class RenderStyle (line 38) | class RenderStyle:
  function _fmt_tool_call (line 50) | def _fmt_tool_call(
  function _fmt_tool_output_label (line 64) | def _fmt_tool_output_label(name: str, style: RenderStyle) -> str:
  function _fmt_code_block (line 72) | def _fmt_code_block(preview: str, style: RenderStyle) -> str:
  class MessageRenderer (line 78) | class MessageRenderer:
    method __init__ (line 84) | def __init__(self, style: RenderStyle | None = None):
    method message_to_parts (line 87) | def message_to_parts(self, message: Any) -> List[_OutgoingPart]:
    method parts_to_text (line 352) | def parts_to_text(

FILE: src/copaw/app/channels/schema.py
  class ChannelAddress (line 13) | class ChannelAddress:
    method to_handle (line 23) | def to_handle(self) -> str:
  class ChannelMessageConverter (line 52) | class ChannelMessageConverter(Protocol):
    method build_agent_request_from_native (line 58) | def build_agent_request_from_native(self, native_payload: Any) -> Any:
    method send_response (line 64) | async def send_response(

FILE: src/copaw/app/channels/telegram/channel.py
  class _FileTooLargeError (line 70) | class _FileTooLargeError(Exception):
  class _MediaFileUnavailableError (line 74) | class _MediaFileUnavailableError(Exception):
  function _download_telegram_file (line 78) | async def _download_telegram_file(
  function _resolve_telegram_file_url (line 114) | async def _resolve_telegram_file_url(
  function _build_content_parts_from_message (line 140) | async def _build_content_parts_from_message(
  function _message_meta (line 240) | def _message_meta(update: Any) -> dict:
  class TelegramChannel (line 264) | class TelegramChannel(BaseChannel):
    method __init__ (line 270) | def __init__(
    method _build_application (line 335) | def _build_application(self):
    method _apply_no_text_debounce (line 437) | def _apply_no_text_debounce(
    method from_env (line 454) | def from_env(
    method from_config (line 484) | def from_config(
    method _chunk_text (line 526) | def _chunk_text(self, text: str) -> list[str]:
    method _send_chat_action (line 548) | async def _send_chat_action(
    method _start_typing (line 567) | def _start_typing(self, chat_id: str) -> None:
    method _stop_typing (line 576) | def _stop_typing(self, chat_id: str) -> None:
    method _typing_loop (line 582) | async def _typing_loop(self, chat_id: str) -> None:
    method send (line 597) | async def send(
    method send_media (line 652) | async def send_media(  # pylint: disable=too-many-statements
    method _send_media_value (line 769) | async def _send_media_value(
    method _send_media_payload (line 835) | async def _send_media_payload(
    method _polling_cycle (line 856) | async def _polling_cycle(self, app) -> None:
    method _teardown_application (line 913) | async def _teardown_application(app) -> None:
    method _run_polling (line 925) | async def _run_polling(self) -> None:
    method start (line 967) | async def start(self) -> None:
    method stop (line 981) | async def stop(self) -> None:
    method resolve_session_id (line 996) | def resolve_session_id(
    method get_to_handle_from_request (line 1008) | def get_to_handle_from_request(self, request: Any) -> str:
    method build_agent_request_from_native (line 1019) | def build_agent_request_from_native(self, native_payload: Any) -> Any:
    method to_handle_from_target (line 1039) | def to_handle_from_target(self, *, user_id: str, session_id: str) -> str:

FILE: src/copaw/app/channels/telegram/format_html.py
  function _escape_html (line 17) | def _escape_html(text: str) -> str:
  function markdown_to_telegram_html (line 22) | def markdown_to_telegram_html(text: str) -> str:
  function strip_markdown (line 165) | def strip_markdown(text: str) -> str:

FILE: src/copaw/app/channels/utils.py
  function file_url_to_local_path (line 15) | def file_url_to_local_path(url: str) -> Optional[str]:
  function make_process_from_runner (line 58) | def make_process_from_runner(runner: Any):

FILE: src/copaw/app/channels/voice/channel.py
  class VoiceChannel (line 17) | class VoiceChannel(BaseChannel):
    method __init__ (line 28) | def __init__(
    method from_config (line 54) | def from_config(
    method start (line 81) | async def start(self) -> None:
    method stop (line 138) | async def stop(self) -> None:
    method send (line 159) | async def send(
    method build_agent_request_from_native (line 171) | def build_agent_request_from_native(
    method config (line 202) | def config(self) -> Any:
    method process (line 207) | def process(self) -> ProcessHandler:
    method create_ws_token (line 213) | def create_ws_token(self) -> str:
    method validate_ws_token (line 223) | def validate_ws_token(self, token: str) -> bool:
    method get_tunnel_url (line 227) | def get_tunnel_url(self) -> str | None:
    method get_tunnel_wss_url (line 233) | def get_tunnel_wss_url(self) -> str | None:

FILE: src/copaw/app/channels/voice/conversation_relay.py
  class ConversationRelayHandler (line 29) | class ConversationRelayHandler:
    method __init__ (line 44) | def __init__(
    method handle (line 60) | async def handle(self) -> None:
    method _handle_setup (line 103) | async def _handle_setup(self, msg: dict) -> None:
    method _handle_prompt (line 127) | async def _handle_prompt(self, msg: dict) -> None:
    method _handle_interrupt (line 144) | async def _handle_interrupt(self, msg: dict) -> None:
    method _handle_dtmf (line 155) | async def _handle_dtmf(self, msg: dict) -> None:
    method _build_agent_request (line 164) | def _build_agent_request(self, text: str) -> Any:
    method _process_and_stream (line 185) | async def _process_and_stream(self, request: Any) -> None:
    method _extract_text_from_event (line 228) | def _extract_text_from_event(event: Any) -> str:
    method _send_token (line 247) | async def _send_token(
    method send_text (line 269) | async def send_text(self, text: str) -> None:
    method close (line 273) | async def close(self) -> None:

FILE: src/copaw/app/channels/voice/session.py
  class CallSession (line 17) | class CallSession:
  class CallSessionManager (line 28) | class CallSessionManager:
    method __init__ (line 31) | def __init__(self) -> None:
    method create_session (line 34) | def create_session(
    method get_session (line 56) | def get_session(self, call_sid: str) -> Optional[CallSession]:
    method end_session (line 59) | def end_session(self, call_sid: str) -> None:
    method active_sessions (line 65) | def active_sessions(self) -> list[CallSession]:
    method active_count (line 68) | def active_count(self) -> int:
    method all_sessions (line 71) | def all_sessions(self) -> list[CallSession]:

FILE: src/copaw/app/channels/voice/twilio_manager.py
  class TwilioManager (line 12) | class TwilioManager:
    method __init__ (line 15) | def __init__(self, account_sid: str, auth_token: str) -> None:
    method _get_client (line 20) | def _get_client(self):
    method _run_sync (line 27) | async def _run_sync(self, fn, *args, **kwargs):
    method configure_voice_webhook (line 31) | async def configure_voice_webhook(

FILE: src/copaw/app/channels/voice/twiml.py
  function build_conversation_relay_twiml (line 8) | def build_conversation_relay_twiml(
  function build_busy_twiml (line 40) | def build_busy_twiml(
  function build_error_twiml (line 54) | def build_error_twiml(

FILE: src/copaw/app/channels/wecom/channel.py
  class WecomChannel (line 48) | class WecomChannel(BaseChannel):
    method __init__ (line 58) | def __init__(
    method from_env (line 108) | def from_env(
    method from_config (line 137) | def from_config(
    method resolve_session_id (line 173) | def resolve_session_id(
    method _parse_chatid_from_handle (line 189) | def _parse_chatid_from_handle(to_handle: str) -> str:
    method to_handle_from_target (line 202) | def to_handle_from_target(self, *, user_id: str, session_id: str) -> str:
    method get_to_handle_from_request (line 206) | def get_to_handle_from_request(self, request: Any) -> str:
    method get_on_reply_sent_args (line 211) | def get_on_reply_sent_args(
    method build_agent_request_from_native (line 221) | def build_agent_request_from_native(
    method merge_native_items (line 246) | def merge_native_items(self, items: List[Any]) -> Any:
    method _is_duplicate (line 275) | def _is_duplicate(self, msg_id: str) -> bool:
    method _on_message_sync (line 289) | def _on_message_sync(self, frame: Any) -> None:
    method _on_message (line 299) | async def _on_message(self, frame: Any) -> None:
    method _on_enter_chat_sync (line 478) | def _on_enter_chat_sync(self, frame: Any) -> None:
    method _on_enter_chat (line 488) | async def _on_enter_chat(self, frame: Any) -> None:
    method _download_media (line 502) | async def _download_media(
    method _send_text_via_frame (line 537) | async def _send_text_via_frame(
    method _send_image_via_send_message (line 564) | async def _send_image_via_send_message(
    method send_content_parts (line 587) | async def send_content_parts(
    method send (line 687) | async def send(
    method _run_ws_forever (line 735) | def _run_ws_forever(self) -> None:
    method start (line 770) | async def start(self) -> None:
    method stop (line 804) | async def stop(self) -> None:

FILE: src/copaw/app/channels/wecom/utils.py
  function format_markdown_tables (line 9) | def format_markdown_tables(text: str) -> str:
  function _format_table (line 58) | def _format_table(lines: List[str]) -> List[str]:

FILE: src/copaw/app/channels/xiaoyi/auth.py
  function generate_signature (line 15) | def generate_signature(sk: str, timestamp: str) -> str:
  function generate_auth_headers (line 31) | def generate_auth_headers(ak: str, sk: str, agent_id: str) -> Dict[str, ...

FILE: src/copaw/app/channels/xiaoyi/channel.py
  class XiaoYiChannel (line 55) | class XiaoYiChannel(BaseChannel):
    method __init__ (line 65) | def __init__(
    method from_env (line 129) | def from_env(
    method from_config (line 152) | def from_config(
    method _validate_config (line 203) | def _validate_config(self) -> None:
    method start (line 212) | async def start(self) -> None:
    method _wait_and_register_connection (line 288) | async def _wait_and_register_connection(self) -> None:
    method _unregister_connection (line 339) | async def _unregister_connection(self) -> None:
    method _connect (line 350) | async def _connect(self) -> None:
    method _send_init_message (line 385) | async def _send_init_message(self) -> None:
    method _heartbeat_loop (line 400) | async def _heartbeat_loop(self) -> None:
    method _receive_loop (line 422) | async def _receive_loop(self) -> None:
    method _handle_message (line 449) | async def _handle_message(self, data: str) -> None:
    method _handle_a2a_request (line 491) | async def _handle_a2a_request(self, message: Dict[str, Any]) -> None:
    method _process_file_part (line 557) | async def _process_file_part(
    method _handle_clear_context (line 598) | async def _handle_clear_context(self, message: Dict[str, Any]) -> None:
    method _handle_tasks_cancel (line 612) | async def _handle_tasks_cancel(self, message: Dict[str, Any]) -> None:
    method _send_clear_context_response (line 623) | async def _send_clear_context_response(
    method _send_tasks_cancel_response (line 654) | async def _send_tasks_cancel_response(
    method _schedule_reconnect (line 686) | def _schedule_reconnect(self) -> None:
    method _cleanup_session (line 721) | async def _cleanup_session(self) -> None:
    method stop (line 737) | async def stop(self) -> None:
    method send (line 778) | async def send(
    method _chunk_text (line 815) | def _chunk_text(self, text: str) -> List[str]:
    method _build_artifact_msg (line 853) | def _build_artifact_msg(
    method _send_chunk (line 887) | async def _send_chunk(
    method _send_reasoning_chunk (line 908) | async def _send_reasoning_chunk(
    method send_final_message (line 929) | async def send_final_message(
    method send_media (line 951) | async def send_media(
    method _extract_xiaoyi_parts (line 1013) | def _extract_xiaoyi_parts(
    method send_xiaoyi_parts (line 1195) | async def send_xiaoyi_parts(
    method on_event_message_completed (line 1302) | async def on_event_message_completed(
    method resolve_session_id (line 1323) | def resolve_session_id(
    method get_to_handle_from_request (line 1333) | def get_to_handle_from_request(self, request: "AgentRequest") -> str:
    method build_agent_request_from_native (line 1340) | def build_agent_request_from_native(
    method to_handle_from_target (line 1365) | def to_handle_from_target(self, *, user_id: str, session_id: str) -> str:
    method _run_process_loop (line 1371) | async def _run_process_loop(

FILE: src/copaw/app/channels/xiaoyi/utils.py
  function download_file (line 11) | async def download_file(

FILE: src/copaw/app/console_push_store.py
  function append (line 22) | async def append(session_id: str, text: str, *, sticky: bool = False) ->...
  function take (line 41) | async def take(session_id: str) -> List[Dict[str, Any]]:
  function take_all (line 51) | async def take_all() -> List[Dict[str, Any]]:
  function _strip_ts (line 59) | def _strip_ts(msgs: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
  function get_recent (line 70) | async def get_recent(

FILE: src/copaw/app/crons/api.py
  function get_cron_manager (line 13) | async def get_cron_manager(
  function list_jobs (line 29) | async def list_jobs(mgr: CronManager = Depends(get_cron_manager)):
  function get_job (line 34) | async def get_job(job_id: str, mgr: CronManager = Depends(get_cron_manag...
  function create_job (line 42) | async def create_job(
  function replace_job (line 54) | async def replace_job(
  function delete_job (line 66) | async def delete_job(
  function pause_job (line 77) | async def pause_job(job_id: str, mgr: CronManager = Depends(get_cron_man...
  function resume_job (line 86) | async def resume_job(
  function run_job (line 98) | async def run_job(job_id: str, mgr: CronManager = Depends(get_cron_manag...
  function get_job_state (line 109) | async def get_job_state(

FILE: src/copaw/app/crons/executor.py
  class CronExecutor (line 13) | class CronExecutor:
    method __init__ (line 14) | def __init__(self, *, runner: Any, channel_manager: Any):
    method execute (line 18) | async def execute(self, job: CronJobSpec) -> None:

FILE: src/copaw/app/crons/heartbeat.py
  function parse_heartbeat_every (line 33) | def parse_heartbeat_every(every: str) -> int:
  function _in_active_hours (line 51) | def _in_active_hours(active_hours: Any) -> bool:
  function run_heartbeat_once (line 89) | async def run_heartbeat_once(

FILE: src/copaw/app/crons/manager.py
  class _Runtime (line 28) | class _Runtime:
  class CronManager (line 32) | class CronManager:
    method __init__ (line 33) | def __init__(
    method start (line 57) | async def start(self) -> None:
    method stop (line 105) | async def stop(self) -> None:
    method list_jobs (line 114) | async def list_jobs(self) -> list[CronJobSpec]:
    method get_job (line 117) | async def get_job(self, job_id: str) -> Optional[CronJobSpec]:
    method get_state (line 120) | def get_state(self, job_id: str) -> CronJobState:
    method create_or_replace_job (line 125) | async def create_or_replace_job(self, spec: CronJobSpec) -> None:
    method delete_job (line 131) | async def delete_job(self, job_id: str) -> bool:
    method pause_job (line 139) | async def pause_job(self, job_id: str) -> None:
    method resume_job (line 143) | async def resume_job(self, job_id: str) -> None:
    method reschedule_heartbeat (line 147) | async def reschedule_heartbeat(self) -> None:
    method run_job (line 184) | async def run_job(self, job_id: str) -> None:
    method _task_done_cb (line 211) | def _task_done_cb(self, task: asyncio.Task, job: CronJobSpec) -> None:
    method _register_or_update (line 236) | async def _register_or_update(self, spec: CronJobSpec) -> None:
    method _build_trigger (line 268) | def _build_trigger(self, spec: CronJobSpec) -> CronTrigger:
    method _scheduled_callback (line 287) | async def _scheduled_callback(self, job_id: str) -> None:
    method _heartbeat_callback (line 300) | async def _heartbeat_callback(self) -> None:
    method _execute_once (line 320) | async def _execute_once(self, job: CronJobSpec) -> None:

FILE: src/copaw/app/crons/models.py
  function _crontab_dow_to_name (line 37) | def _crontab_dow_to_name(field: str) -> str:
  class ScheduleSpec (line 58) | class ScheduleSpec(BaseModel):
    method normalize_cron_5_fields (line 65) | def normalize_cron_5_fields(cls, v: str) -> str:
  class DispatchTarget (line 88) | class DispatchTarget(BaseModel):
  class DispatchSpec (line 93) | class DispatchSpec(BaseModel):
  class JobRuntimeSpec (line 101) | class JobRuntimeSpec(BaseModel):
  class CronJobRequest (line 107) | class CronJobRequest(BaseModel):
  class CronJobSpec (line 123) | class CronJobSpec(BaseModel):
    method _validate_task_type_fields (line 138) | def _validate_task_type_fields(self) -> "CronJobSpec":
  class JobsFile (line 156) | class JobsFile(BaseModel):
  class CronJobState (line 161) | class CronJobState(BaseModel):
  class CronJobView (line 170) | class CronJobView(BaseModel):

FILE: src/copaw/app/crons/repo/base.py
  class BaseJobRepository (line 10) | class BaseJobRepository(ABC):
    method load (line 14) | async def load(self) -> JobsFile:
    method save (line 19) | async def save(self, jobs_file: JobsFile) -> None:
    method list_jobs (line 25) | async def list_jobs(self) -> list[CronJobSpec]:
    method get_job (line 29) | async def get_job(self, job_id: str) -> Optional[CronJobSpec]:
    method upsert_job (line 36) | async def upsert_job(self, spec: CronJobSpec) -> None:
    method delete_job (line 46) | async def delete_job(self, job_id: str) -> bool:

FILE: src/copaw/app/crons/repo/json_repo.py
  class JsonJobRepository (line 12) | class JsonJobRepository(BaseJobRepository):
    method __init__ (line 20) | def __init__(self, path: Path | str):
    method path (line 26) | def path(self) -> Path:
    method load (line 29) | async def load(self) -> JobsFile:
    method save (line 36) | async def save(self, jobs_file: JobsFile) -> None:

FILE: src/copaw/app/download_task_store.py
  class DownloadTaskStatus (line 18) | class DownloadTaskStatus(str, Enum):
  class DownloadTask (line 26) | class DownloadTask(BaseModel):
  function create_task (line 43) | async def create_task(
  function get_tasks (line 61) | async def get_tasks(backend: Optional[str] = None) -> List[DownloadTask]:
  function get_task (line 70) | async def get_task(task_id: str) -> Optional[DownloadTask]:
  function update_status (line 76) | async def update_status(
  function cancel_task (line 96) | async def cancel_task(task_id: str) -> bool:
  function clear_completed (line 115) | async def clear_completed(backend: Optional[str] = None) -> None:

FILE: src/copaw/app/mcp/manager.py
  class MCPClientManager (line 23) | class MCPClientManager:
    method __init__ (line 34) | def __init__(self) -> None:
    method init_from_config (line 39) | async def init_from_config(self, config: "MCPConfig") -> None:
    method get_clients (line 62) | async def get_clients(self) -> List[Any]:
    method replace_client (line 78) | async def replace_client(
    method remove_client (line 134) | async def remove_client(self, key: str) -> None:
    method close_all (line 150) | async def close_all(self) -> None:
    method _add_client (line 167) | async def _add_client(
    method _build_client (line 189) | def _build_client(client_config: "MCPClientConfig") -> Any:

FILE: src/copaw/app/mcp/watcher.py
  class MCPConfigWatcher (line 26) | class MCPConfigWatcher:
    method __init__ (line 33) | def __init__(
    method start (line 69) | async def start(self) -> None:
    method stop (line 81) | async def stop(self) -> None:
    method _snapshot (line 112) | def _snapshot(self) -> None:
    method _load_mcp_config (line 129) | def _load_mcp_config(self) -> "MCPConfig":
    method _mcp_hash (line 138) | def _mcp_hash(mcp_config: "MCPConfig") -> int:
    method _poll_loop (line 142) | async def _poll_loop(self) -> None:
    method _check (line 151) | async def _check(self) -> None:
    method _reload_changed_clients_wrapper (line 192) | async def _reload_changed_clients_wrapper(
    method _reload_changed_clients (line 215) | async def _reload_changed_clients(self, new_mcp: "MCPConfig") -> None:
    method _handle_client_update (line 234) | async def _handle_client_update(
    method _reload_single_client (line 262) | async def _reload_single_client(self, key: str, new_cfg) -> None:
    method _should_skip_client (line 284) | def _should_skip_client(self, key: str, client_hash: int) -> bool:
    method _track_client_failure (line 297) | def _track_client_failure(self, key: str, client_hash: int) -> None:
    method _handle_client_removal (line 320) | async def _handle_client_removal(self, key: str) -> None:

FILE: src/copaw/app/migration.py
  function migrate_legacy_workspace_to_default_agent (line 45) | def migrate_legacy_workspace_to_default_agent() -> bool:
  function _migrate_workspace_item (line 192) | def _migrate_workspace_item(
  function _migrate_workspace_items_from_source (line 226) | def _migrate_workspace_items_from_source(
  function ensure_default_agent_exists (line 247) | def ensure_default_agent_exists() -> None:

FILE: src/copaw/app/multi_agent_manager.py
  class MultiAgentManager (line 17) | class MultiAgentManager:
    method __init__ (line 27) | def __init__(self):
    method get_agent (line 34) | async def get_agent(self, agent_id: str) -> Workspace:
    method _graceful_stop_old_instance (line 83) | async def _graceful_stop_old_instance(
    method stop_agent (line 180) | async def stop_agent(self, agent_id: str) -> bool:
    method reload_agent (line 200) | async def reload_agent(self, agent_id: str) -> bool:
    method cancel_all_cleanup_tasks (line 313) | async def cancel_all_cleanup_tasks(self) -> None:
    method stop_all (line 338) | async def stop_all(self):
    method list_loaded_agents (line 363) | def list_loaded_agents(self) -> list[str]:
    method is_agent_loaded (line 371) | def is_agent_loaded(self, agent_id: str) -> bool:
    method preload_agent (line 382) | async def preload_agent(self, agent_id: str) -> bool:
    method start_all_configured_agents (line 399) | async def start_all_configured_agents(self) -> dict[str, bool]:
    method __repr__ (line 447) | def __repr__(self) -> str:

FILE: src/copaw/app/routers/__init__.py
  function create_agent_scoped_router (line 44) | def create_agent_scoped_router() -> APIRouter:

FILE: src/copaw/app/routers/agent.py
  class MdFileInfo (line 23) | class MdFileInfo(BaseModel):
  class MdFileContent (line 33) | class MdFileContent(BaseModel):
  function list_working_files (line 45) | async def list_working_files(
  function read_working_file (line 69) | async def read_working_file(
  function write_working_file (line 93) | async def write_working_file(
  function list_memory_files (line 116) | async def list_memory_files(
  function read_memory_file (line 140) | async def read_memory_file(
  function write_memory_file (line 164) | async def write_memory_file(
  function get_agent_language (line 186) | async def get_agent_language(request: Request) -> dict:
  function put_agent_language (line 204) | async def put_agent_language(
  function get_audio_mode (line 263) | async def get_audio_mode() -> dict:
  function put_audio_mode (line 278) | async def put_audio_mode(
  function get_transcription_provider_type (line 310) | async def get_transcription_provider_type() -> dict:
  function put_transcription_provider_type (line 330) | async def put_transcription_provider_type(
  function get_local_whisper_status (line 365) | async def get_local_whisper_status() -> dict:
  function get_transcription_providers (line 382) | async def get_transcription_providers() -> dict:
  function put_transcription_provider (line 403) | async def put_transcription_provider(
  function get_agents_running_config (line 426) | async def get_agents_running_config(
  function put_agents_running_config (line 441) | async def put_agents_running_config(
  function get_system_prompt_files (line 479) | async def get_system_prompt_files(
  function put_system_prompt_files (line 494) | async def put_system_prompt_files(

FILE: src/copaw/app/routers/agent_scoped.py
  class AgentContextMiddleware (line 15) | class AgentContextMiddleware(BaseHTTPMiddleware):
    method dispatch (line 18) | async def dispatch(
  function create_agent_scoped_router (line 53) | def create_agent_scoped_router() -> APIRouter:

FILE: src/copaw/app/routers/agents.py
  class AgentSummary (line 31) | class AgentSummary(BaseModel):
  class AgentListResponse (line 40) | class AgentListResponse(BaseModel):
  class CreateAgentRequest (line 46) | class CreateAgentRequest(BaseModel):
  class MdFileInfo (line 55) | class MdFileInfo(BaseModel):
  class MdFileContent (line 65) | class MdFileContent(BaseModel):
  function _get_multi_agent_manager (line 71) | def _get_multi_agent_manager(request: Request) -> MultiAgentManager:
  function list_agents (line 87) | async def list_agents() -> AgentListResponse:
  function get_agent (line 126) | async def get_agent(agentId: str = PathParam(...)) -> AgentProfileConfig:
  function create_agent (line 144) | async def create_agent(
  function update_agent (line 218) | async def update_agent(
  function delete_agent (line 268) | async def delete_agent(
  function list_agent_files (line 307) | async def list_agent_files(
  function read_agent_file (line 337) | async def read_agent_file(
  function write_agent_file (line 370) | async def write_agent_file(
  function list_agent_memory (line 399) | async def list_agent_memory(
  function _initialize_agent_workspace (line 423) | def _initialize_agent_workspace(  # pylint: disable=too-many-branches

FILE: src/copaw/app/routers/auth.py
  class LoginRequest (line 21) | class LoginRequest(BaseModel):
  class LoginResponse (line 26) | class LoginResponse(BaseModel):
  class RegisterRequest (line 31) | class RegisterRequest(BaseModel):
  class AuthStatusResponse (line 36) | class AuthStatusResponse(BaseModel):
  function login (line 42) | async def login(req: LoginRequest):
  function register (line 55) | async def register(req: RegisterRequest):
  function auth_status (line 87) | async def auth_status():
  function verify (line 96) | async def verify(request: Request):

FILE: src/copaw/app/routers/config.py
  function list_channels (line 64) | async def list_channels(request: Request) -> dict:
  function list_channel_types (line 106) | async def list_channel_types() -> List[str]:
  function put_channels (line 117) | async def put_channels(
  function get_channel (line 161) | async def get_channel(
  function put_channel (line 205) | async def put_channel(
  function get_heartbeat (line 273) | async def get_heartbeat(request: Request) -> Any:
  function put_heartbeat (line 291) | async def put_heartbeat(
  function get_agents_llm_routing (line 333) | async def get_agents_llm_routing() -> AgentsLLMRoutingConfig:
  function put_agents_llm_routing (line 343) | async def put_agents_llm_routing(
  function get_user_timezone (line 360) | async def get_user_timezone() -> dict:
  function put_user_timezone (line 370) | async def put_user_timezone(
  function get_tool_guard (line 390) | async def get_tool_guard() -> ToolGuardConfig:
  function put_tool_guard (line 400) | async def put_tool_guard(
  function get_builtin_rules (line 421) | async def get_builtin_rules() -> List[ToolGuardRuleConfig]:
  function get_skill_scanner (line 451) | async def get_skill_scanner() -> SkillScannerConfig:
  function put_skill_scanner (line 461) | async def put_skill_scanner(
  function get_blocked_history (line 474) | async def get_blocked_history() -> list:
  function delete_blocked_history (line 485) | async def delete_blocked_history() -> dict:
  function delete_blocked_entry (line 496) | async def delete_blocked_entry(
  class WhitelistAddRequest (line 507) | class WhitelistAddRequest(BaseModel):
  function add_to_whitelist (line 516) | async def add_to_whitelist(
  function remove_from_whitelist (line 549) | async def remove_from_whitelist(

FILE: src/copaw/app/routers/console.py
  function _safe_filename (line 26) | def _safe_filename(name: str) -> str:
  function _extract_session_and_payload (line 32) | def _extract_session_and_payload(request_data: Union[AgentRequest, dict]):
  function post_console_chat (line 75) | async def post_console_chat(
  function post_console_chat_stop (line 153) | async def post_console_chat_stop(
  function post_console_upload (line 164) | async def post_console_upload(
  function get_console_file (line 197) | async def get_console_file(
  function get_push_messages (line 227) | async def get_push_messages(

FILE: src/copaw/app/routers/envs.py
  class EnvVar (line 20) | class EnvVar(BaseModel):
  function list_envs (line 37) | async def list_envs() -> List[EnvVar]:
  function batch_save_envs (line 50) | async def batch_save_envs(
  function delete_env (line 71) | async def delete_env(key: str) -> List[EnvVar]:

FILE: src/copaw/app/routers/local_models.py
  class DownloadRequest (line 33) | class DownloadRequest(BaseModel):
  class LocalModelResponse (line 46) | class LocalModelResponse(BaseModel):
  class DownloadTaskResponse (line 57) | class DownloadTaskResponse(BaseModel):
  function _task_to_response (line 68) | def _task_to_response(task: DownloadTask) -> DownloadTaskResponse:
  function _register_background_task (line 84) | async def _register_background_task(task_id: str, task: asyncio.Task) ->...
  function _pop_background_task (line 89) | async def _pop_background_task(task_id: str) -> Optional[asyncio.Task]:
  function list_local (line 99) | async def list_local(
  function download_model (line 128) | async def download_model(
  function _run_download_in_background (line 170) | async def _run_download_in_background(
  function get_download_status (line 263) | async def get_download_status(
  function delete_local (line 274) | async def delete_local(model_id: str) -> dict:
  function _cancel_download_task (line 293) | async def _cancel_download_task(task_id: str) -> dict:
  function cancel_download (line 317) | async def cancel_download(task_id: str) -> dict:

FILE: src/copaw/app/routers/mcp.py
  class MCPClientInfo (line 16) | class MCPClientInfo(BaseModel):
  class MCPClientCreateRequest (line 53) | class MCPClientCreateRequest(BaseModel):
  class MCPClientUpdateRequest (line 92) | class MCPClientUpdateRequest(BaseModel):
  function _mask_env_value (line 131) | def _mask_env_value(value: str) -> str:
  function _build_client_info (line 162) | def _build_client_info(key: str, client: MCPClientConfig) -> MCPClientInfo:
  function list_mcp_clients (line 196) | async def list_mcp_clients(request: Request) -> List[MCPClientInfo]:
  function get_mcp_client (line 216) | async def get_mcp_client(
  function create_mcp_client (line 240) | async def create_mcp_client(
  function update_mcp_client (line 309) | async def update_mcp_client(
  function toggle_mcp_client (line 370) | async def toggle_mcp_client(
  function delete_mcp_client (line 417) | async def delete_mcp_client(

FILE: src/copaw/app/routers/ollama_models.py
  class OllamaDownloadRequest (line 40) | class OllamaDownloadRequest(BaseModel):
  class OllamaModelResponse (line 44) | class OllamaModelResponse(BaseModel):
  class OllamaDownloadTaskResponse (line 51) | class OllamaDownloadTaskResponse(BaseModel):
  function _is_ollama_connection_error (line 59) | def _is_ollama_connection_error(exc: Exception) -> bool:
  function _task_to_response (line 71) | def _task_to_response(task: DownloadTask) -> OllamaDownloadTaskResponse:
  function _register_background_task (line 84) | async def _register_background_task(task_id: str, task: asyncio.Task) ->...
  function _pop_background_task (line 89) | async def _pop_background_task(task_id: str) -> Optional[asyncio.Task]:
  function _run_ollama_download_in_background (line 94) | async def _run_ollama_download_in_background(
  function list_ollama_models (line 157) | async def list_ollama_models(
  function download_ollama_model (line 198) | async def download_ollama_model(
  function get_ollama_download_status (line 237) | async def get_ollama_download_status() -> List[OllamaDownloadTaskResponse]:
  function cancel_ollama_download (line 247) | async def cancel_ollama_download(task_id: str) -> dict:
  function delete_ollama_model (line 270) | async def delete_ollama_model(

FILE: src/copaw/app/routers/providers.py
  function get_provider_manager (line 31) | def get_provider_manager(request: Request) -> ProviderManager:
  class ProviderConfigRequest (line 43) | class ProviderConfigRequest(BaseModel):
  class ModelSlotRequest (line 60) | class ModelSlotRequest(BaseModel):
  class CreateCustomProviderRequest (line 65) | class CreateCustomProviderRequest(BaseModel):
  class AddModelRequest (line 74) | class AddModelRequest(BaseModel):
  function list_all_providers (line 84) | async def list_all_providers(
  function configure_provider (line 95) | async def configure_provider(
  function create_custom_provider_endpoint (line 130) | async def create_custom_provider_endpoint(
  class TestConnectionResponse (line 151) | class TestConnectionResponse(BaseModel):
  class TestProviderRequest (line 156) | class TestProviderRequest(BaseModel):
  class TestModelRequest (line 171) | class TestModelRequest(BaseModel):
  class DiscoverModelsRequest (line 175) | class DiscoverModelsRequest(BaseModel):
  class DiscoverModelsResponse (line 190) | class DiscoverModelsResponse(BaseModel):
  function test_provider (line 211) | async def test_provider(
  function discover_models (line 243) | async def discover_models(
  function test_model (line 279) | async def test_model(
  function delete_custom_provider_endpoint (line 307) | async def delete_custom_provider_endpoint(
  function add_model_endpoint (line 326) | async def add_model_endpoint(
  function remove_model_endpoint (line 346) | async def remove_model_endpoint(
  function get_active_models (line 366) | async def get_active_models(
  function set_active_model (line 405) | async def set_active_model(

FILE: src/copaw/app/routers/schemas_config.py
  class HeartbeatBody (line 11) | class HeartbeatBody(BaseModel):

FILE: src/copaw/app/routers/skills.py
  function _scan_error_response (line 28) | def _scan_error_response(exc: SkillScanError) -> JSONResponse:
  class SkillSpec (line 53) | class SkillSpec(SkillInfo):
  class CreateSkillRequest (line 57) | class CreateSkillRequest(BaseModel):
  class HubSkillSpec (line 74) | class HubSkillSpec(BaseModel):
  class HubInstallRequest (line 82) | class HubInstallRequest(BaseModel):
  class HubInstallTaskStatus (line 92) | class HubInstallTaskStatus(str, Enum):
  class HubInstallTask (line 100) | class HubInstallTask(BaseModel):
  function list_skills (line 123) | async def list_skills(
  function get_available_skills (line 165) | async def get_available_skills(
  function search_hub (line 197) | async def search_hub(
  function _hub_task_set_status (line 214) | async def _hub_task_set_status(
  function _hub_task_get (line 233) | async def _hub_task_get(task_id: str) -> HubInstallTask | None:
  function _hub_task_register_runtime (line 238) | async def _hub_task_register_runtime(task_id: str, task: asyncio.Task) -...
  function _hub_task_pop_runtime (line 243) | async def _hub_task_pop_runtime(task_id: str) -> asyncio.Task | None:
  function _cleanup_imported_skill (line 248) | def _cleanup_imported_skill(workspace_dir: Path, skill_name: str) -> None:
  function _run_hub_install_task (line 264) | async def _run_hub_install_task(
  function install_from_hub (line 345) | async def install_from_hub(
  function start_install_from_hub (line 392) | async def start_install_from_hub(
  function get_hub_install_status (line 425) | async def get_hub_install_status(task_id: str) -> HubInstallTask:
  function cancel_hub_install (line 433) | async def cancel_hub_install(task_id: str) -> dict[str, Any]:
  function upload_skill_zip (line 464) | async def upload_skill_zip(
  function batch_disable_skills (line 518) | async def batch_disable_skills(
  function batch_enable_skills (line 533) | async def batch_enable_skills(
  function create_skill (line 569) | async def create_skill(
  function disable_skill (line 592) | async def disable_skill(
  function enable_skill (line 627) | async def enable_skill(
  function delete_skill (line 697) | async def delete_skill(
  function load_skill_file (line 717) | async def load_skill_file(

FILE: src/copaw/app/routers/skills_stream.py
  function get_model (line 19) | def get_model():
  class AIOptimizeSkillRequest (line 116) | class AIOptimizeSkillRequest(BaseModel):
  function _extract_text_from_chunk (line 127) | def _extract_text_from_chunk(chunk) -> str:
  function _extract_text_from_response (line 150) | def _extract_text_from_response(response) -> str:
  function ai_optimize_skill_stream (line 167) | async def ai_optimize_skill_stream(request: AIOptimizeSkillRequest):

FILE: src/copaw/app/routers/token_usage.py
  function _parse_date (line 13) | def _parse_date(s: str | None) -> date | None:
  function get_token_usage (line 28) | async def get_token_usage(

FILE: src/copaw/app/routers/tools.py
  class ToolInfo (line 21) | class ToolInfo(BaseModel):
  function list_tools (line 30) | async def list_tools(
  function toggle_tool (line 69) | async def toggle_tool(

FILE: src/copaw/app/routers/voice.py
  function _get_voice_channel (line 28) | def _get_voice_channel(request_or_ws):
  function _validate_twilio_signature (line 42) | async def _validate_twilio_signature(request: Request) -> None:
  function voice_incoming (line 88) | async def voice_incoming(request: Request) -> Response:
  function voice_ws (line 126) | async def voice_ws(websocket: WebSocket) -> None:
  function voice_status_callback (line 167) | async def voice_status_callback(request: Request) -> Response:

FILE: src/copaw/app/routers/workspace.py
  function _dir_stats (line 21) | def _dir_stats(root: Path) -> tuple[int, int]:
  function _zip_directory (line 33) | def _zip_directory(root: Path) -> io.BytesIO:
  function _validate_zip_data (line 56) | def _validate_zip_data(data: bytes, workspace_dir: Path) -> None:
  function _extract_and_merge_zip (line 73) | def _extract_and_merge_zip(data: bytes, workspace_dir: Path) -> None:
  function _validate_and_extract_zip (line 101) | def _validate_and_extract_zip(data: bytes, workspace_dir: Path) -> None:
  function download_workspace (line 126) | async def download_workspace(request: Request):
  function upload_workspace (line 165) | async def upload_workspace(

FILE: src/copaw/app/runner/api.py
  function get_workspace (line 21) | async def get_workspace(request: Request):
  function get_chat_manager (line 28) | async def get_chat_manager(
  function get_session (line 46) | async def get_session(
  function list_chats (line 65) | async def list_chats(
  function create_chat (line 88) | async def create_chat(
  function batch_delete_chats (line 116) | async def batch_delete_chats(
  function get_chat (line 134) | async def get_chat(
  function update_chat (line 178) | async def update_chat(
  function delete_chat (line 215) | async def delete_chat(

FILE: src/copaw/app/runner/command_dispatch.py
  function _get_last_user_text (line 28) | def _get_last_user_text(msgs) -> str | None:
  function _is_conversation_command (line 46) | def _is_conversation_command(query: str | None) -> bool:
  function _is_command (line 54) | def _is_command(query: str | None) -> bool:
  function run_command_path (line 63) | async def run_command_path(

FILE: src/copaw/app/runner/daemon_commands.py
  class RestartInProgressError (line 28) | class RestartInProgressError(Exception):
  class DaemonContext (line 49) | class DaemonContext:
  function _get_last_lines (line 62) | def _get_last_lines(
  function run_daemon_status (line 97) | def run_daemon_status(context: DaemonContext) -> str:
  function run_daemon_restart (line 126) | async def run_daemon_restart(context: DaemonContext) -> str:
  function run_daemon_reload_config (line 153) | def run_daemon_reload_config(context: DaemonContext) -> str:
  function run_daemon_version (line 164) | def run_daemon_version(context: DaemonContext) -> str:
  function run_daemon_logs (line 178) | def run_daemon_logs(context: DaemonContext, lines: int = 100) -> str:
  function run_daemon_approve (line 185) | async def run_daemon_approve(
  function parse_daemon_query (line 225) | def parse_daemon_query(query: str) -> Optional[tuple[str, list[str]]]:
  class DaemonCommandHandlerMixin (line 254) | class DaemonCommandHandlerMixin:
    method is_daemon_command (line 257) | def is_daemon_command(self, query: str | None) -> bool:
    method handle_daemon_command (line 261) | async def handle_daemon_command(

FILE: src/copaw/app/runner/manager.py
  class ChatManager (line 17) | class ChatManager:
    method __init__ (line 26) | def __init__(
    method list_chats (line 44) | async def list_chats(
    method get_chat (line 68) | async def get_chat(self, chat_id: str) -> Optional[ChatSpec]:
    method get_or_create_chat (line 80) | async def get_or_create_chat(
    method create_chat (line 137) | async def create_chat(self, spec: ChatSpec) -> ChatSpec:
    method update_chat (line 150) | async def update_chat(self, spec: ChatSpec) -> ChatSpec:
    method delete_chats (line 164) | async def delete_chats(self, chat_ids: list[str]) -> bool:
    method count_chats (line 183) | async def count_chats(

FILE: src/copaw/app/runner/models.py
  class ChatSpec (line 15) | class ChatSpec(BaseModel):
  class ChatHistory (line 51) | class ChatHistory(BaseModel):
  class ChatsFile (line 61) | class ChatsFile(BaseModel):

FILE: src/copaw/app/runner/query_error_dump.py
  function _safe_json_serialize (line 18) | def _safe_json_serialize(obj: object) -> object:
  function _request_to_dict (line 29) | def _request_to_dict(request: Any) -> Any:
  function write_query_error_dump (line 48) | def write_query_error_dump(

FILE: src/copaw/app/runner/repo/base.py
  class BaseChatRepository (line 12) | class BaseChatRepository(ABC):
    method load (line 16) | async def load(self) -> ChatsFile:
    method save (line 21) | async def save(self, chats_file: ChatsFile) -> None:
    method list_chats (line 27) | async def list_chats(self) -> list[ChatSpec]:
    method get_chat (line 32) | async def get_chat(self, chat_id: str) -> Optional[ChatSpec]:
    method get_chat_by_id (line 47) | async def get_chat_by_id(
    method upsert_chat (line 87) | async def upsert_chat(self, spec: ChatSpec) -> None:
    method delete_chats (line 102) | async def delete_chats(self, chat_ids: list[str]) -> bool:
    method filter_chats (line 122) | async def filter_chats(

FILE: src/copaw/app/runner/repo/json_repo.py
  class JsonChatRepository (line 13) | class JsonChatRepository(BaseChatRepository):
    method __init__ (line 24) | def __init__(self, path: Path | str):
    method path (line 35) | def path(self) -> Path:
    method load (line 39) | async def load(self) -> ChatsFile:
    method save (line 51) | async def save(self, chats_file: ChatsFile) -> None:

FILE: src/copaw/app/runner/runner.py
  function _is_approval (line 52) | def _is_approval(text: str) -> bool:
  class AgentRunner (line 63) | class AgentRunner(Runner):
    method __init__ (line 64) | def __init__(
    method set_chat_manager (line 79) | def set_chat_manager(self, chat_manager):
    method set_mcp_manager (line 87) | def set_mcp_manager(self, mcp_manager):
    method _resolve_pending_approval (line 97) | async def _resolve_pending_approval(
    method query_handler (line 175) | async def query_handler(
    method _cleanup_denied_session_memory (line 379) | async def _cleanup_denied_session_memory(
    method init_handler (line 489) | async def init_handler(self, *args, **kwargs):
    method shutdown_handler (line 511) | async def shutdown_handler(self, *args, **kwargs):

FILE: src/copaw/app/runner/session.py
  function sanitize_filename (line 26) | def sanitize_filename(name: str) -> str:
  class SafeJSONSession (line 37) | class SafeJSONSession(SessionBase):
    method __init__ (line 44) | def __init__(
    method _get_save_path (line 56) | def _get_save_path(self, session_id: str, user_id: str) -> str:
    method save_session_state (line 71) | async def save_session_state(
    method load_session_state (line 95) | async def load_session_state(
    method update_session_state (line 134) | async def update_session_state(
    method get_session_state_dict (line 186) | async def get_session_state_dict(

FILE: src/copaw/app/runner/task_tracker.py
  class _RunState (line 23) | class _RunState:
  class TaskTracker (line 31) | class TaskTracker:
    method __init__ (line 38) | def __init__(self) -> None:
    method lock (line 43) | def lock(self) -> asyncio.Lock:
    method get_status (line 46) | async def get_status(self, run_key: str) -> str:
    method has_active_tasks (line 54) | async def has_active_tasks(self) -> bool:
    method list_active_tasks (line 66) | async def list_active_tasks(self) -> list[str]:
    method wait_all_done (line 79) | async def wait_all_done(self, timeout: float = 300.0) -> bool:
    method attach (line 99) | async def attach(self, run_key: str) -> asyncio.Queue | None:
    method request_stop (line 115) | async def request_stop(self, run_key: str) -> bool:
    method attach_or_start (line 124) | async def attach_or_start(
    method stream_from_queue (line 210) | async def stream_from_queue(

FILE: src/copaw/app/runner/utils.py
  function build_env_context (line 30) | def build_env_context(
  function _is_local_file_url (line 93) | def _is_local_file_url(url: str) -> bool:
  function _basename_from_url (line 114) | def _basename_from_url(url: str) -> str:
  function _resolve_content_url (line 122) | def _resolve_content_url(url: str) -> str:
  function agentscope_msg_to_message (line 132) | def agentscope_msg_to_message(

FILE: src/copaw/app/workspace/service_factories.py
  function create_mcp_service (line 18) | async def create_mcp_service(ws: "Workspace", mcp):
  function create_chat_service (line 36) | async def create_chat_service(ws: "Workspace", service):
  function create_channel_service (line 64) | async def create_channel_service(ws: "Workspace", _):
  function create_agent_config_watcher (line 104) | async def create_agent_config_watcher(ws: "Workspace", _):
  function create_mcp_config_watcher (line 134) | async def create_mcp_config_watcher(ws: "Workspace", _):

FILE: src/copaw/app/workspace/service_manager.py
  class ServiceDescriptor (line 31) | class ServiceDescriptor:
  class ServiceManager (line 74) | class ServiceManager:
    method __init__ (line 81) | def __init__(self, workspace: Workspace):
    method register (line 92) | def register(self, descriptor: ServiceDescriptor) -> None:
    method set_reusable (line 106) | async def set_reusable(self, name: str, instance: Any) -> None:
    method get_reusable_services (line 146) | def get_reusable_services(self) -> Dict[str, Any]:
    method _group_by_priority (line 158) | def _group_by_priority(self) -> Dict[int, List[ServiceDescriptor]]:
    method start_all (line 171) | async def start_all(self) -> None:
    method _start_service (line 201) | async def _start_service(self, descriptor: ServiceDescriptor) -> None:
    method _get_or_create_service (line 230) | async def _get_or_create_service(
    method _run_post_init (line 262) | async def _run_post_init(
    method _run_start_method (line 297) | async def _run_start_method(
    method stop_all (line 324) | async def stop_all(self, final: bool = False) -> None:
    method _stop_service (line 361) | async def _stop_service(

FILE: src/copaw/app/workspace/workspace.py
  class Workspace (line 39) | class Workspace:
    method __init__ (line 52) | def __init__(self, agent_id: str, workspace_dir: str):
    method runner (line 81) | def runner(self) -> Optional[AgentRunner]:
    method memory_manager (line 86) | def memory_manager(self):
    method mcp_manager (line 91) | def mcp_manager(self):
    method chat_manager (line 96) | def chat_manager(self):
    method channel_manager (line 101) | def channel_manager(self):
    method cron_manager (line 106) | def cron_manager(self):
    method task_tracker (line 112) | def task_tracker(self) -> TaskTracker:
    method config (line 117) | def config(self):
    method set_manager (line 123) | def set_manager(self, manager) -> None:
    method _register_services (line 134) | def _register_services(  # pylint: disable=too-many-statements
    method set_reusable_components (line 279) | async def set_reusable_components(self, components: dict) -> None:
    method start (line 311) | async def start(self):
    method stop (line 338) | async def stop(self, final: bool = True):
    method __repr__ (line 359) | def __repr__(self) -> str:

FILE: src/copaw/cli/app_cmd.py
  function app_cmd (line 54) | def app_cmd(

FILE: src/copaw/cli/auth_cmd.py
  function auth_group (line 17) | def auth_group() -> None:
  function reset_password_cmd (line 22) | def reset_password_cmd() -> None:

FILE: src/copaw/cli/channels_cmd.py
  function _get_channel_names (line 159) | def _get_channel_names() -> dict[str, str]:
  function _mask (line 177) | def _mask(value: str) -> str:
  function configure_imessage (line 189) | def configure_imessage(
  function configure_discord (line 229) | def configure_discord(current_config: DiscordConfig) -> DiscordConfig:
  function configure_dingtalk (line 294) | def configure_dingtalk(current_config: DingTalkConfig) -> DingTalkConfig:
  function configure_feishu (line 334) | def configure_feishu(current_config: FeishuConfig) -> FeishuConfig:
  function configure_qq (line 374) | def configure_qq(current_config: QQConfig) -> QQConfig:
  function configure_telegram (line 420) | def configure_telegram(current_config: TelegramConfig) -> TelegramConfig:
  function configure_voice (line 497) | def configure_voice(
  function configure_console (line 596) | def configure_console(current_config: ConsoleConfig) -> ConsoleConfig:
  function _plugin_configure (line 636) | def _plugin_configure(
  function get_channel_configurators (line 656) | def get_channel_configurators() -> dict:
  function _get_channel_config (line 727) | def _get_channel_config(config: Config, key: str):
  function configure_channels_interactive (line 736) | def configure_channels_interactive(config: Config) -> None:
  function channels_group (line 800) | def channels_group() -> None:
  function _channel_config_fields (line 805) | def _channel_config_fields(ch):
  function _channel_enabled (line 824) | def _channel_enabled(ch) -> bool:
  function list_cmd (line 841) | def list_cmd(agent_id: str) -> None:
  function _install_channel_to_dir (line 883) | def _install_channel_to_dir(
  function install_cmd (line 965) | def install_cmd(key: str, from_path: str | None, from_url: str | None) -...
  function add_cmd (line 992) | def add_cmd(
  function remove_cmd (line 1036) | def remove_cmd(key: str, keep_config: bool) -> None:
  function configure_cmd (line 1084) | def configure_cmd(agent_id: str) -> None:

FILE: src/copaw/cli/chats_cmd.py
  function _base_url (line 15) | def _base_url(ctx: click.Context, base_url: Optional[str]) -> str:
  function chats_group (line 29) | def chats_group() -> None:
  function list_chats (line 64) | def list_chats(
  function get_chat (line 102) | def get_chat(
  function create_chat (line 166) | def create_chat(
  function update_chat (line 228) | def update_chat(
  function delete_chat (line 275) | def delete_chat(

FILE: src/copaw/cli/clean_cmd.py
  function _iter_children (line 13) | def _iter_children(p: Path) -> list[Path]:
  function clean_cmd (line 27) | def clean_cmd(yes: bool, dry_run: bool) -> None:

FILE: src/copaw/cli/cron_cmd.py
  function _base_url (line 14) | def _base_url(ctx: click.Context, base_url: Optional[str]) -> str:
  function cron_group (line 28) | def cron_group() -> None:
  function list_jobs (line 51) | def list_jobs(
  function get_job (line 78) | def get_job(
  function job_state (line 108) | def job_state(
  function _build_spec_from_cli (line 125) | def _build_spec_from_cli(
  function create_job (line 299) | def create_job(
  function delete_job (line 373) | def delete_job(
  function pause_job (line 403) | def pause_job(
  function resume_job (line 435) | def resume_job(
  function run_job (line 465) | def run_job(

FILE: src/copaw/cli/daemon_cmd.py
  function _get_agent_workspace (line 25) | def _get_agent_workspace(agent_id: str) -> Path:
  function _context (line 38) | def _context(agent_id: str) -> DaemonContext:
  function daemon_group (line 49) | def daemon_group() -> None:
  function status_cmd (line 59) | def status_cmd(agent_id: str) -> None:
  function restart_cmd (line 72) | def restart_cmd(agent_id: str) -> None:
  function reload_config_cmd (line 85) | def reload_config_cmd(agent_id: str) -> None:
  function version_cmd (line 98) | def version_cmd(agent_id: str) -> None:
  function logs_cmd (line 118) | def logs_cmd(lines: int, agent_id: str) -> None:

FILE: src/copaw/cli/desktop_cmd.py
  class WebViewAPI (line 29) | class WebViewAPI:
    method open_external_link (line 32) | def open_external_link(self, url: str) -> None:
  function _find_free_port (line 39) | def _find_free_port(host: str = "127.0.0.1") -> int:
  function _wait_for_http (line 47) | def _wait_for_http(host: str, port: int, timeout_sec: float = 300.0) -> ...
  function _stream_reader (line 61) | def _stream_reader(in_stream, out_stream) -> None:
  function desktop_cmd (line 99) | def desktop_cmd(

FILE: src/copaw/cli/env_cmd.py
  function env_group (line 11) | def env_group() -> None:
  function list_cmd (line 21) | def list_cmd() -> None:
  function set_cmd (line 42) | def set_cmd(key: str, value: str) -> None:
  function delete_cmd (line 55) | def delete_cmd(key: str) -> None:
  function configure_env_interactive (line 75) | def configure_env_interactive() -> None:

FILE: src/copaw/cli/http.py
  function client (line 14) | def client(base_url: str) -> httpx.Client:
  function print_json (line 23) | def print_json(data: Any) -> None:

FILE: src/copaw/cli/init_cmd.py
  function _echo_security_warning_box (line 73) | def _echo_security_warning_box() -> None:
  function _echo_telemetry_info_box (line 85) | def _echo_telemetry_info_box() -> None:
  function init_cmd (line 138) | def init_cmd(

FILE: src/copaw/cli/main.py
  function _record (line 28) | def _record(label: str, elapsed: float) -> None:
  function log_init_timings (line 124) | def log_init_timings() -> None:
  function cli (line 140) | def cli(ctx: click.Context, host: str | None, port: int | None) -> None:

FILE: src/copaw/cli/process_utils.py
  function _coerce_optional_int (line 16) | def _coerce_optional_int(value: object) -> Optional[int]:
  function _parse_windows_process_snapshot_json (line 30) | def _parse_windows_process_snapshot_json(
  function _parse_windows_process_snapshot_csv (line 62) | def _parse_windows_process_snapshot_csv(
  function _windows_process_snapshot (line 85) | def _windows_process_snapshot() -> dict[int, tuple[Optional[int], str, s...
  function _process_table (line 131) | def _process_table() -> list[tuple[int, str]]:
  function _matches_copaw_cli_command (line 167) | def _matches_copaw_cli_command(command: str, *subcommands: str) -> bool:
  function _is_copaw_service_command (line 183) | def _is_copaw_service_command(command: str) -> bool:
  function _is_copaw_wrapper_process (line 188) | def _is_copaw_wrapper_process(name: str, command: str) -> bool:
  function _extract_port_from_command (line 198) | def _extract_port_from_command(command: str, default: int = 8088) -> int:
  function _base_url (line 204) | def _base_url(host: str, port: int) -> str:
  function _candidate_hosts (line 212) | def _candidate_hosts(host: str | None) -> list[str]:

FILE: src/copaw/cli/providers_cmd.py
  function _manager (line 16) | def _manager() -> ProviderManager:
  function _mask_api_key (line 20) | def _mask_api_key(api_key: str) -> str:
  function _is_configured (line 28) | def _is_configured(provider: Provider) -> bool:
  function _save_provider (line 40) | def _save_provider(manager: ProviderManager, provider_id: str) -> None:
  function _all_provider_objects (line 50) | def _all_provider_objects(manager: ProviderManager) -> list[Provider]:
  function _get_ollama_host (line 59) | def _get_ollama_host() -> str:
  function _select_provider_interactive (line 67) | def _select_provider_interactive(
  function configure_provider_api_key_interactive (line 95) | def configure_provider_api_key_interactive(
  function _add_models_interactive (line 179) | def _add_models_interactive(provider_id: str) -> None:
  function _pick_model_from_list (line 230) | def _pick_model_from_list(
  function _pick_model_free_text (line 246) | def _pick_model_free_text(prompt_text: str, current_model: str = "") -> ...
  function _filter_eligible (line 254) | def _filter_eligible(all_providers: list[Provider]) -> list[Provider]:
  function _select_llm_model (line 258) | def _select_llm_model(defn, pid, current_slot, *, use_defaults):
  function configure_llm_slot_interactive (line 284) | def configure_llm_slot_interactive(*, use_defaults: bool = False) -> None:
  function configure_providers_interactive (line 371) | def configure_providers_interactive(*, use_defaults: bool = False) -> None:
  function models_group (line 408) | def models_group() -> None:
  function list_cmd (line 413) | def list_cmd() -> None:
  function config_cmd (line 474) | def config_cmd() -> None:
  function config_key_cmd (line 481) | def config_key_cmd(provider_id: str | None) -> None:
  function set_llm_cmd (line 487) | def set_llm_cmd() -> None:
  function add_provider_cmd (line 497) | def add_provider_cmd(
  function remove_provider_cmd (line 538) | def remove_provider_cmd(provider_id: str, yes: bool) -> None:
  function add_model_cmd (line 569) | def add_model_cmd(provider_id: str, model_id: str, model_name: str) -> N...
  function remove_model_cmd (line 602) | def remove_model_cmd(provider_id: str, model_id: str) -> None:
  function download_cmd (line 659) | def download_cmd(
  function list_local_cmd (line 729) | def list_local_cmd(backend: str | None) -> None:
  function remove_local_cmd (line 764) | def remove_local_cmd(model_id: str, yes: bool) -> None:
  function ollama_pull_cmd (line 796) | def ollama_pull_cmd(model_name: str) -> None:
  function ollama_list_cmd (line 825) | def ollama_list_cmd() -> None:
  function ollama_remove_cmd (line 862) | def ollama_remove_cmd(model_name: str, yes: bool) -> None:

FILE: src/copaw/cli/shutdown_cmd.py
  function _backend_port (line 27) | def _backend_port(ctx: click.Context, port: Optional[int]) -> int:
  function _listening_pids_for_port (line 34) | def _listening_pids_for_port(port: int) -> set[int]:
  function _find_frontend_dev_pids (line 90) | def _find_frontend_dev_pids() -> set[int]:
  function _find_desktop_wrapper_pids (line 109) | def _find_desktop_wrapper_pids() -> set[int]:
  function _find_windows_wrapper_ancestor_pids (line 124) | def _find_windows_wrapper_ancestor_pids(pids: set[int]) -> set[int]:
  function _child_pids_unix (line 155) | def _child_pids_unix(pid: int) -> set[int]:
  function _pid_exists (line 182) | def _pid_exists(pid: int) -> bool:
  function _wait_for_pid_exit (line 195) | def _wait_for_pid_exit(
  function _signal_process_tree_unix (line 209) | def _signal_process_tree_unix(pid: int, sig: signal.Signals) -> None:
  function _terminate_process_tree_windows (line 223) | def _terminate_process_tree_windows(pid: int, force: bool = False) -> None:
  function _force_terminate_windows_process (line 240) | def _force_terminate_windows_process(pid: int) -> None:
  function _terminate_pid (line 267) | def _terminate_pid(pid: int, timeout_sec: float = 5.0) -> bool:
  function _stop_pid_set (line 291) | def _stop_pid_set(pids: set[int]) -> tuple[list[int], list[int]]:
  function shutdown_cmd (line 311) | def shutdown_cmd(ctx: click.Context, port: Optional[int]) -> None:

FILE: src/copaw/cli/skills_cmd.py
  function _get_agent_workspace (line 15) | def _get_agent_workspace(agent_id: str) -> Path:
  function configure_skills_interactive (line 29) | def configure_skills_interactive(
  function skills_group (line 128) | def skills_group() -> None:
  function list_cmd (line 138) | def list_cmd(agent_id: str) -> None:
  function configure_cmd (line 179) | def configure_cmd(agent_id: str) -> None:

FILE: src/copaw/cli/uninstall_cmd.py
  function _remove_path_entry (line 25) | def _remove_path_entry(profile: Path) -> bool:
  function uninstall_cmd (line 53) | def uninstall_cmd(purge: bool, yes: bool) -> None:

FILE: src/copaw/cli/update_cmd.py
  function _subprocess_text_kwargs (line 35) | def _subprocess_text_kwargs() -> dict[str, Any]:
  class InstallInfo (line 50) | class InstallInfo:
  class RunningServiceInfo (line 63) | class RunningServiceInfo:
  function _version_obj (line 71) | def _version_obj(version: str) -> Any:
  function _is_newer_version (line 79) | def _is_newer_version(latest: str, current: str) -> bool | None:
  function _fetch_latest_version (line 93) | def _fetch_latest_version() -> str:
  function _detect_source_type (line 120) | def _detect_source_type(
  function _detect_installation (line 138) | def _detect_installation() -> InstallInfo:
  function _probe_service (line 171) | def _probe_service(base_url: str) -> RunningServiceInfo:
  function _process_candidate_ports (line 193) | def _process_candidate_ports() -> list[int]:
  function _detect_running_service_from_processes (line 206) | def _detect_running_service_from_processes(
  function _detect_running_service (line 226) | def _detect_running_service(
  function _running_service_display (line 268) | def _running_service_display(running: RunningServiceInfo) -> str:
  function _confirm_force_shutdown (line 276) | def _confirm_force_shutdown(running: RunningServiceInfo) -> bool:
  function _run_shutdown_for_update (line 310) | def _run_shutdown_for_update(
  function _build_upgrade_command (line 348) | def _build_upgrade_command(
  function _plan_dir (line 383) | def _plan_dir() -> Path:
  function _write_worker_plan (line 388) | def _write_worker_plan(plan: dict[str, Any]) -> Path:
  function _spawn_update_worker (line 400) | def _spawn_update_worker(
  function _terminate_update_worker (line 436) | def _terminate_update_worker(proc: subprocess.Popen[str]) -> None:
  function _wait_for_process_exit (line 466) | def _wait_for_process_exit(pid: int | None, timeout: float = 15.0) -> None:
  function _run_update_worker_foreground (line 503) | def _run_update_worker_foreground(plan_path: Path) -> int:
  function _run_update_worker_detached (line 525) | def _run_update_worker_detached(plan_path: Path) -> None:
  function _load_worker_plan (line 535) | def _load_worker_plan(plan_path: str | Path) -> dict[str, Any]:
  function run_update_worker (line 540) | def run_update_worker(plan_path: str | Path) -> int:
  function _echo_install_summary (line 592) | def _echo_install_summary(info: InstallInfo, latest_version: str) -> None:
  function _confirm_source_override (line 605) | def _confirm_source_override(info: InstallInfo, yes: bool) -> bool:
  function update_cmd (line 638) | def update_cmd(ctx: click.Context, yes: bool) -> None:

FILE: src/copaw/cli/utils.py
  function prompt_confirm (line 15) | def prompt_confirm(question: str, *, default: bool = False) -> bool:
  function prompt_path (line 44) | def prompt_path(label: str, *, default: str = "") -> str:
  function prompt_choice (line 70) | def prompt_choice(
  function prompt_select (line 110) | def prompt_select(
  function prompt_checkbox (line 151) | def prompt_checkbox(

FILE: src/copaw/config/config.py
  function generate_short_agent_id (line 19) | def generate_short_agent_id() -> str:
  class BaseChannelConfig (line 28) | class BaseChannelConfig(BaseModel):
  class IMessageChannelConfig (line 42) | class IMessageChannelConfig(BaseChannelConfig):
  class DiscordConfig (line 51) | class DiscordConfig(BaseChannelConfig):
  class DingTalkConfig (line 57) | class DingTalkConfig(BaseChannelConfig):
  class FeishuConfig (line 67) | class FeishuConfig(BaseChannelConfig):
  class QQConfig (line 79) | class QQConfig(BaseChannelConfig):
  class TelegramConfig (line 85) | class TelegramConfig(BaseChannelConfig):
  class MQTTConfig (line 92) | class MQTTConfig(BaseChannelConfig):
  class MattermostConfig (line 108) | class MattermostConfig(BaseChannelConfig):
  class ConsoleConfig (line 118) | class ConsoleConfig(BaseChannelConfig):
  class WecomConfig (line 125) | class WecomConfig(BaseChannelConfig):
  class MatrixConfig (line 135) | class MatrixConfig(BaseChannelConfig):
  class VoiceChannelConfig (line 143) | class VoiceChannelConfig(BaseChannelConfig):
  class XiaoYiConfig (line 157) | class XiaoYiConfig(BaseChannelConfig):
  class ChannelConfig (line 167) | class ChannelConfig(BaseModel):
  class LastApiConfig (line 187) | class LastApiConfig(BaseModel):
  class ActiveHoursConfig (line 192) | class ActiveHoursConfig(BaseModel):
  class HeartbeatConfig (line 199) | class HeartbeatConfig(BaseModel):
  class AgentsDefaultsConfig (line 213) | class AgentsDefaultsConfig(BaseModel):
  class EmbeddingConfig (line 217) | class EmbeddingConfig(BaseModel):
  class AgentsRunningConfig (line 252) | class AgentsRunningConfig(BaseModel):
    method memory_compact_reserve (line 345) | def memory_compact_reserve(self) -> int:
    method memory_compact_threshold (line 350) | def memory_compact_threshold(self) -> int:
  class AgentsLLMRoutingConfig (line 355) | class AgentsLLMRoutingConfig(Base
Copy disabled (too large) Download .json
Condensed preview — 814 files, each showing path, character count, and a content snippet. Download the .json file for the full structured content (18,403K chars).
[
  {
    "path": ".dockerignore",
    "chars": 726,
    "preview": "# Git and IDE\n.git\n.gitignore\n.idea\n.vscode\n*.md\n!README.md\n\n# Python dev and cache\n__pycache__\n*.py[cod]\n*$py.class\n.ve"
  },
  {
    "path": ".flake8",
    "chars": 155,
    "preview": "[flake8]\nexclude =\n  scripts/*\n  src/agentscope/rpc/*\nmax-line-length = 79\ninline-quotes = \"\navoid-escape = no\nignore =\n"
  },
  {
    "path": ".gitattributes",
    "chars": 20,
    "preview": "*.bat text eol=crlf\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/1-question.md",
    "chars": 687,
    "preview": "---\nname: Question / Discussion\nabout: Ask a question or start a discussion (consider GitHub Discussions for open-ended "
  },
  {
    "path": ".github/ISSUE_TEMPLATE/2-feature_request.md",
    "chars": 953,
    "preview": "---\nname: Feature Request\nabout: Suggest a new feature or enhancement\ntitle: \"[Feature]: \"\nlabels: [\"enhancement\", \"tria"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/3-documentation.md",
    "chars": 812,
    "preview": "---\nname: Documentation\nabout: Report docs issues or suggest documentation improvements\ntitle: \"[Docs]: \"\nlabels: [\"docu"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/4-bug_report.md",
    "chars": 1342,
    "preview": "---\nname: Bug Report\nabout: Report a bug or unexpected behavior\ntitle: \"[Bug]: \"\nlabels: [\"bug\", \"triage\"]\nassignees: []"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/5-support_environment.md",
    "chars": 844,
    "preview": "---\nname: Support / Environment\ndescription: Issues with install, Docker, platform, or runtime environment\ntitle: \"[Supp"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "chars": 507,
    "preview": "# Issue template chooser. Templates are listed alphanumerically.\n# See: https://docs.github.com/en/communities/using-tem"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "chars": 1127,
    "preview": "## Description\n\n[Describe what this PR does and why]\n\n**Related Issue:** Fixes #(issue_number) or Relates to #(issue_num"
  },
  {
    "path": ".github/condarc",
    "chars": 92,
    "preview": "# Explicit channels so conda does not warn about implicit 'defaults'\nchannels:\n  - defaults\n"
  },
  {
    "path": ".github/workflows/deploy-website.yml",
    "chars": 1686,
    "preview": "# Deploy website to GitHub Pages (see scripts/website_build.sh).\n# Custom domain: copaw.agentscope.io (CNAME).\nname: Dep"
  },
  {
    "path": ".github/workflows/desktop-release.yml",
    "chars": 4652,
    "preview": "# Build CoPaw Desktop: Windows (conda-pack + NSIS), macOS (conda-pack -> .app)\n# Runs automatically on release publish; "
  },
  {
    "path": ".github/workflows/docker-release.yml",
    "chars": 3243,
    "preview": "# Build CoPaw multi-arch Docker image and push to DockerHub + Aliyun ACR on release.\n# Pre-release: update <version> and"
  },
  {
    "path": ".github/workflows/first-time-contributor-welcome.yml",
    "chars": 1704,
    "preview": "name: First-Time Contributor Welcome\n\non:\n  pull_request_target:\n    types: [closed]\n\njobs:\n  welcome-comment:\n    if: >"
  },
  {
    "path": ".github/workflows/npm-format.yml",
    "chars": 1156,
    "preview": "name: NPM Format (website & console)\n\non: [push, pull_request]\n\njobs:\n  website:\n    name: website format check\n    runs"
  },
  {
    "path": ".github/workflows/pr-label.yml",
    "chars": 1604,
    "preview": "name: PR Labeler\n\non:\n  pull_request_target:\n    types: [opened]\n\njobs:\n  first-time-contributor:\n    runs-on: ubuntu-la"
  },
  {
    "path": ".github/workflows/pre-commit.yml",
    "chars": 1121,
    "preview": "name: Pre-commit Checks\n\non: [push, pull_request]\n\njobs:\n  run:\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast"
  },
  {
    "path": ".github/workflows/publish-pypi.yml",
    "chars": 1339,
    "preview": "# Publish Python package to PyPI when a release is created.\n# Build includes console frontend (see scripts/wheel_build.s"
  },
  {
    "path": ".github/workflows/tests.yml",
    "chars": 7416,
    "preview": "name: Tests\n\non:\n  push:\n    branches: [main, master, dev, develop]\n    paths:\n      - 'src/**'\n      - 'tests/**'\n     "
  },
  {
    "path": ".gitignore",
    "chars": 1141,
    "preview": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/.pnp\n.pnp.js\nnode"
  },
  {
    "path": ".pre-commit-config.yaml",
    "chars": 3567,
    "preview": "repos:\n  - repo: https://github.com/pre-commit/pre-commit-hooks\n    rev: v4.3.0\n    hooks:\n      - id: check-ast\n       "
  },
  {
    "path": ".python-version",
    "chars": 5,
    "preview": "3.10\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 13820,
    "preview": "# Contributing to CoPaw\n\n## Welcome! 🐾\n\nThank you for your interest in contributing to CoPaw! CoPaw is an open-source **"
  },
  {
    "path": "CONTRIBUTING_zh.md",
    "chars": 8121,
    "preview": "# 为 CoPaw 贡献代码\n\n## 欢迎!🐾\n\n感谢你对 CoPaw 的关注!CoPaw 是一个开源的**个人 AI 助手**,可以在你自己的环境中运行——无论是你的机器还是云端。它可以连接钉钉、飞书、QQ、Discord、iMessag"
  },
  {
    "path": "LICENSE",
    "chars": 10766,
    "preview": "                                 Apache License\n                           Version 2.0, January 2004\n                   "
  },
  {
    "path": "README.md",
    "chars": 33095,
    "preview": "<div align=\"center\">\n\n# CoPaw\n\n[![GitHub Repo](https://img.shields.io/badge/GitHub-Repo-black.svg?logo=github)](https://"
  },
  {
    "path": "README_ja.md",
    "chars": 22437,
    "preview": "<div align=\"center\">\n\n# CoPaw\n\n[![GitHub Repo](https://img.shields.io/badge/GitHub-Repo-black.svg?logo=github)](https://"
  },
  {
    "path": "README_zh.md",
    "chars": 22389,
    "preview": "<div align=\"center\">\n\n# CoPaw\n\n[![GitHub 仓库](https://img.shields.io/badge/GitHub-仓库-black.svg?logo=github)](https://gith"
  },
  {
    "path": "SECURITY.md",
    "chars": 11228,
    "preview": "# Security Policy\n\nIf you believe you've found a security issue in CoPaw, please report it privately.\n\n## Reporting\n\nRep"
  },
  {
    "path": "console/eslint.config.js",
    "chars": 740,
    "preview": "import js from \"@eslint/js\";\nimport globals from \"globals\";\nimport reactHooks from \"eslint-plugin-react-hooks\";\nimport r"
  },
  {
    "path": "console/index.html",
    "chars": 370,
    "preview": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <link rel=\"icon\" type=\"image/svg+xml\" href=\"/"
  },
  {
    "path": "console/package.json",
    "chars": 1674,
    "preview": "{\n  \"name\": \"copaw-console\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vit"
  },
  {
    "path": "console/src/App.tsx",
    "chars": 4397,
    "preview": "import { createGlobalStyle } from \"antd-style\";\nimport { ConfigProvider, bailianTheme } from \"@agentscope-ai/design\";\nim"
  },
  {
    "path": "console/src/api/config.ts",
    "chars": 1187,
    "preview": "declare const BASE_URL: string;\ndeclare const TOKEN: string;\n\nconst AUTH_TOKEN_KEY = \"copaw_auth_token\";\n\n/**\n * Get the"
  },
  {
    "path": "console/src/api/index.ts",
    "chars": 1718,
    "preview": "export * from \"./types\";\n\nexport { request } from \"./request\";\n\nexport { getApiUrl, getApiToken } from \"./config\";\n\nimpo"
  },
  {
    "path": "console/src/api/modules/agent.ts",
    "chars": 2464,
    "preview": "import { request } from \"../request\";\nimport type { AgentRequest, AgentsRunningConfig } from \"../types\";\n\n// Agent API\ne"
  },
  {
    "path": "console/src/api/modules/agents.ts",
    "chars": 1724,
    "preview": "import { request } from \"../request\";\nimport type {\n  AgentListResponse,\n  AgentProfileConfig,\n  CreateAgentRequest,\n  A"
  },
  {
    "path": "console/src/api/modules/auth.ts",
    "chars": 1345,
    "preview": "import { getApiUrl } from \"../config\";\n\nexport interface LoginResponse {\n  token: string;\n  username: string;\n  message?"
  },
  {
    "path": "console/src/api/modules/channel.ts",
    "chars": 834,
    "preview": "import { request } from \"../request\";\nimport type { ChannelConfig, SingleChannelConfig } from \"../types\";\n\nexport const "
  },
  {
    "path": "console/src/api/modules/chat.ts",
    "chars": 5093,
    "preview": "import { request } from \"../request\";\nimport { getApiUrl } from \"../config\";\nimport type {\n  ChatSpec,\n  ChatHistory,\n  "
  },
  {
    "path": "console/src/api/modules/console.ts",
    "chars": 227,
    "preview": "import { request } from \"../request\";\n\nexport interface PushMessage {\n  id: string;\n  text: string;\n}\n\nexport const cons"
  },
  {
    "path": "console/src/api/modules/cronjob.ts",
    "chars": 1466,
    "preview": "import { request } from \"../request\";\nimport type {\n  CronJobSpecInput,\n  CronJobSpecOutput,\n  CronJobView,\n} from \"../t"
  },
  {
    "path": "console/src/api/modules/env.ts",
    "chars": 474,
    "preview": "import { request } from \"../request\";\nimport type { EnvVar } from \"../types\";\n\nexport const envApi = {\n  listEnvs: () =>"
  },
  {
    "path": "console/src/api/modules/heartbeat.ts",
    "chars": 374,
    "preview": "import { request } from \"../request\";\nimport type { HeartbeatConfig } from \"../types/heartbeat\";\n\nexport const heartbeat"
  },
  {
    "path": "console/src/api/modules/localModel.ts",
    "chars": 1145,
    "preview": "import { request } from \"../request\";\nimport type {\n  LocalModelResponse,\n  DownloadModelRequest,\n  DownloadTaskResponse"
  },
  {
    "path": "console/src/api/modules/mcp.ts",
    "chars": 1266,
    "preview": "import { request } from \"../request\";\nimport type {\n  MCPClientInfo,\n  MCPClientCreateRequest,\n  MCPClientUpdateRequest,"
  },
  {
    "path": "console/src/api/modules/ollamaModel.ts",
    "chars": 929,
    "preview": "import { request } from \"../request\";\nimport type {\n  OllamaModelResponse,\n  OllamaDownloadRequest,\n  OllamaDownloadTask"
  },
  {
    "path": "console/src/api/modules/provider.ts",
    "chars": 2584,
    "preview": "import { request } from \"../request\";\nimport type {\n  ProviderInfo,\n  ProviderConfigRequest,\n  ActiveModelsInfo,\n  Model"
  },
  {
    "path": "console/src/api/modules/root.ts",
    "chars": 182,
    "preview": "import { request } from \"../request\";\n\n// Root API\nexport const rootApi = {\n  readRoot: () => request<unknown>(\"/\"),\n  g"
  },
  {
    "path": "console/src/api/modules/security.ts",
    "chars": 3207,
    "preview": "import { request } from \"../request\";\n\nexport interface ToolGuardRule {\n  id: string;\n  tools: string[];\n  params: strin"
  },
  {
    "path": "console/src/api/modules/skill.ts",
    "chars": 6581,
    "preview": "import { request } from \"../request\";\nimport { getApiUrl, getApiToken } from \"../config\";\nimport type { HubSkillSpec, Sk"
  },
  {
    "path": "console/src/api/modules/tokenUsage.ts",
    "chars": 545,
    "preview": "import { request } from \"../request\";\nimport type { TokenUsageSummary } from \"../types/tokenUsage\";\n\nexport interface Ge"
  },
  {
    "path": "console/src/api/modules/tools.ts",
    "chars": 434,
    "preview": "import { request } from \"../request\";\n\nexport interface ToolInfo {\n  name: string;\n  enabled: boolean;\n  description: st"
  },
  {
    "path": "console/src/api/modules/userTimezone.ts",
    "chars": 390,
    "preview": "import { request } from \"../request\";\n\nexport interface UserTimezoneConfig {\n  timezone: string;\n}\n\nexport const userTim"
  },
  {
    "path": "console/src/api/modules/workspace.ts",
    "chars": 4643,
    "preview": "import { request } from \"../request\";\nimport { getApiUrl, getApiToken } from \"../config\";\nimport type { MdFileInfo, MdFi"
  },
  {
    "path": "console/src/api/request.ts",
    "chars": 2325,
    "preview": "import { getApiUrl, getApiToken, clearAuthToken } from \"./config\";\n\nfunction buildHeaders(method?: string, extra?: Heade"
  },
  {
    "path": "console/src/api/types/agent.ts",
    "chars": 496,
    "preview": "export interface AgentRequest {\n  input: unknown;\n  session_id?: string | null;\n  user_id?: string | null;\n  channel?: s"
  },
  {
    "path": "console/src/api/types/agents.ts",
    "chars": 720,
    "preview": "// Multi-agent management types\n\nexport interface AgentSummary {\n  id: string;\n  name: string;\n  description: string;\n  "
  },
  {
    "path": "console/src/api/types/channel.ts",
    "chars": 2681,
    "preview": "export interface BaseChannelConfig {\n  enabled: boolean;\n  bot_prefix: string;\n  filter_tool_messages?: boolean;\n  filte"
  },
  {
    "path": "console/src/api/types/chat.ts",
    "chars": 914,
    "preview": "export type ChatStatus = \"idle\" | \"running\";\n\nexport interface ChatSpec {\n  id: string; // Chat UUID identifier\n  sessio"
  },
  {
    "path": "console/src/api/types/cronjob.ts",
    "chars": 1287,
    "preview": "export interface CronJobSchedule {\n  type: \"cron\";\n  cron: string;\n  timezone?: string;\n}\n\nexport interface CronJobTarge"
  },
  {
    "path": "console/src/api/types/env.ts",
    "chars": 60,
    "preview": "export interface EnvVar {\n  key: string;\n  value: string;\n}\n"
  },
  {
    "path": "console/src/api/types/heartbeat.ts",
    "chars": 206,
    "preview": "export interface ActiveHoursConfig {\n  start: string;\n  end: string;\n}\n\nexport interface HeartbeatConfig {\n  enabled: bo"
  },
  {
    "path": "console/src/api/types/index.ts",
    "chars": 316,
    "preview": "export * from \"./agent\";\nexport * from \"./agents\";\nexport * from \"./channel\";\nexport * from \"./heartbeat\";\nexport * from"
  },
  {
    "path": "console/src/api/types/mcp.ts",
    "chars": 2241,
    "preview": "/**\n * MCP (Model Context Protocol) client types\n */\n\nexport interface MCPClientInfo {\n  /** Unique client key identifie"
  },
  {
    "path": "console/src/api/types/provider.ts",
    "chars": 3151,
    "preview": "export interface ModelInfo {\n  id: string;\n  name: string;\n}\n\nexport interface ProviderInfo {\n  id: string;\n  name: stri"
  },
  {
    "path": "console/src/api/types/skill.ts",
    "chars": 536,
    "preview": "export interface SkillSpec {\n  name: string;\n  description?: string;\n  content: string;\n  source: string;\n  path: string"
  },
  {
    "path": "console/src/api/types/tokenUsage.ts",
    "chars": 458,
    "preview": "/** Per-model (has provider_id, model) or per-date (no provider_id, model) stats. */\nexport interface TokenUsageStats {\n"
  },
  {
    "path": "console/src/api/types/workspace.ts",
    "chars": 380,
    "preview": "export interface MdFileInfo {\n  filename: string;\n  path: string;\n  size: number;\n  created_time: string;\n  modified_tim"
  },
  {
    "path": "console/src/components/AgentSelector/index.module.less",
    "chars": 8800,
    "preview": "// ─── Wrapper ──────────────────────────────────────────────────────────────────\n.agentSelectorWrapper {\n  display: fle"
  },
  {
    "path": "console/src/components/AgentSelector/index.tsx",
    "chars": 3218,
    "preview": "import { Select, message, Badge } from \"antd\";\nimport { useEffect, useState } from \"react\";\nimport { Bot, Layers, CheckC"
  },
  {
    "path": "console/src/components/ConsoleCronBubble/index.module.less",
    "chars": 1227,
    "preview": ".wrap {\n  position: fixed;\n  top: 24px;\n  right: 24px;\n  z-index: 1000;\n  display: flex;\n  flex-direction: column;\n  gap"
  },
  {
    "path": "console/src/components/ConsoleCronBubble/index.tsx",
    "chars": 3976,
    "preview": "import { useEffect, useRef, useState } from \"react\";\nimport { MessageCircle, X } from \"lucide-react\";\nimport { consoleAp"
  },
  {
    "path": "console/src/components/LanguageSwitcher.tsx",
    "chars": 1341,
    "preview": "import { Dropdown, Button } from \"@agentscope-ai/design\";\nimport { GlobalOutlined } from \"@ant-design/icons\";\nimport { u"
  },
  {
    "path": "console/src/components/MarkdownCopy/MarkdownCopy.tsx",
    "chars": 5288,
    "preview": "import { useState, useEffect, useMemo } from \"react\";\nimport { Button, message, Switch, Input } from \"@agentscope-ai/des"
  },
  {
    "path": "console/src/components/MarkdownCopy/index.module.less",
    "chars": 957,
    "preview": ".markdownCopy {\n  display: flex;\n  flex-direction: column;\n  height: 100%;\n}\n\n.controls {\n  display: flex;\n  justify-con"
  },
  {
    "path": "console/src/components/PageHeader/index.tsx",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "console/src/components/ThemeToggleButton/index.module.less",
    "chars": 596,
    "preview": "/* ---- Theme toggle button ---- */\n.toggleBtn {\n  display: inline-flex;\n  align-items: center;\n  justify-content: cente"
  },
  {
    "path": "console/src/components/ThemeToggleButton/index.tsx",
    "chars": 930,
    "preview": "import { Tooltip, Button } from \"antd\";\nimport { SunOutlined, MoonOutlined } from \"@ant-design/icons\";\nimport { useTheme"
  },
  {
    "path": "console/src/constants/timezone.ts",
    "chars": 1226,
    "preview": "export const TIMEZONE_OPTIONS = [\n  { value: \"America/Los_Angeles\", label: \"America/Los_Angeles (UTC-8)\" },\n  { value: \""
  },
  {
    "path": "console/src/contexts/ThemeContext.tsx",
    "chars": 2722,
    "preview": "import {\n  createContext,\n  useContext,\n  useEffect,\n  useState,\n  useCallback,\n  type ReactNode,\n} from \"react\";\n\nexpor"
  },
  {
    "path": "console/src/i18n.ts",
    "chars": 573,
    "preview": "import i18n from \"i18next\";\nimport { initReactI18next } from \"react-i18next\";\nimport en from \"./locales/en.json\";\nimport"
  },
  {
    "path": "console/src/layouts/Header.tsx",
    "chars": 2475,
    "preview": "import { Layout, Space } from \"antd\";\nimport LanguageSwitcher from \"../components/LanguageSwitcher\";\nimport ThemeToggleB"
  },
  {
    "path": "console/src/layouts/MainLayout/index.tsx",
    "chars": 3482,
    "preview": "import { Layout } from \"antd\";\nimport { Routes, Route, useLocation, Navigate } from \"react-router-dom\";\nimport Sidebar f"
  },
  {
    "path": "console/src/layouts/Sidebar.tsx",
    "chars": 13340,
    "preview": "import {\n  Layout,\n  Menu,\n  Button,\n  Badge,\n  Modal,\n  Spin,\n  Tooltip,\n  type MenuProps,\n} from \"antd\";\nimport { useS"
  },
  {
    "path": "console/src/layouts/constants.ts",
    "chars": 4924,
    "preview": "// ── URLs ──────────────────────────────────────────────────────────────────\n\nexport const PYPI_URL = \"https://pypi.org"
  },
  {
    "path": "console/src/layouts/index.module.less",
    "chars": 4538,
    "preview": "// ─── MainLayout ───────────────────────────────────────────────────────────────\n.mainLayout {\n  height: 100vh;\n}\n\n.pag"
  },
  {
    "path": "console/src/locales/en.json",
    "chars": 45094,
    "preview": "{\n  \"common\": {\n    \"save\": \"Save\",\n    \"reset\": \"Reset\",\n    \"cancel\": \"Cancel\",\n    \"confirm\": \"Confirm\",\n    \"delete\""
  },
  {
    "path": "console/src/locales/ja.json",
    "chars": 34579,
    "preview": "{\n  \"common\": {\n    \"save\": \"保存\",\n    \"reset\": \"リセット\",\n    \"cancel\": \"キャンセル\",\n    \"confirm\": \"確認\",\n    \"delete\": \"削除\",\n "
  },
  {
    "path": "console/src/locales/ru.json",
    "chars": 47616,
    "preview": "{\n  \"common\": {\n    \"save\": \"Сохранить\",\n    \"reset\": \"Сбросить\",\n    \"cancel\": \"Отмена\",\n    \"confirm\": \"Подтвердить\",\n"
  },
  {
    "path": "console/src/locales/zh.json",
    "chars": 30840,
    "preview": "{\n  \"common\": {\n    \"save\": \"保存\",\n    \"reset\": \"重置\",\n    \"cancel\": \"取消\",\n    \"confirm\": \"确认\",\n    \"delete\": \"删除\",\n    \"e"
  },
  {
    "path": "console/src/main.tsx",
    "chars": 786,
    "preview": "import { createRoot } from \"react-dom/client\";\nimport App from \"./App.tsx\";\nimport \"./i18n\";\n\nif (typeof window !== \"und"
  },
  {
    "path": "console/src/pages/Agent/Config/components/ContextManagementCard.tsx",
    "chars": 5528,
    "preview": "import { Form, InputNumber, Input, Card } from \"@agentscope-ai/design\";\nimport { useTranslation } from \"react-i18next\";\n"
  },
  {
    "path": "console/src/pages/Agent/Config/components/PageHeader.tsx",
    "chars": 391,
    "preview": "import { useTranslation } from \"react-i18next\";\nimport styles from \"../index.module.less\";\n\nexport function PageHeader()"
  },
  {
    "path": "console/src/pages/Agent/Config/components/ReactAgentCard.tsx",
    "chars": 2410,
    "preview": "import { Form, InputNumber, Select, Card } from \"@agentscope-ai/design\";\nimport { useTranslation } from \"react-i18next\";"
  },
  {
    "path": "console/src/pages/Agent/Config/components/SliderWithValue.tsx",
    "chars": 980,
    "preview": "import { Slider } from \"@agentscope-ai/design\";\n\ninterface SliderWithValueProps {\n  value?: number;\n  min?: number;\n  ma"
  },
  {
    "path": "console/src/pages/Agent/Config/components/index.ts",
    "chars": 212,
    "preview": "export { SliderWithValue } from \"./SliderWithValue\";\nexport { PageHeader } from \"./PageHeader\";\nexport { ReactAgentCard "
  },
  {
    "path": "console/src/pages/Agent/Config/index.module.less",
    "chars": 1129,
    "preview": ".configPage {\n  padding: 24px;\n  height: 100%;\n  overflow-y: auto;\n  width: 100%;\n}\n\n.header {\n  display: flex;\n  justif"
  },
  {
    "path": "console/src/pages/Agent/Config/index.tsx",
    "chars": 3122,
    "preview": "import { useState } from \"react\";\nimport { Button, Form } from \"@agentscope-ai/design\";\nimport { useTranslation } from \""
  },
  {
    "path": "console/src/pages/Agent/Config/useAgentConfig.tsx",
    "chars": 4017,
    "preview": "import { useState, useEffect, useCallback } from \"react\";\nimport { Form, Modal, message } from \"@agentscope-ai/design\";\n"
  },
  {
    "path": "console/src/pages/Agent/MCP/components/MCPClientCard.tsx",
    "chars": 6372,
    "preview": "import { Card, Button, Modal, Tooltip, Input } from \"@agentscope-ai/design\";\nimport { DeleteOutlined } from \"@ant-design"
  },
  {
    "path": "console/src/pages/Agent/MCP/components/MCPClientDrawer.tsx",
    "chars": 3689,
    "preview": "import { Drawer, Form, Input, Switch, Button } from \"@agentscope-ai/design\";\nimport type { MCPClientInfo } from \"../../."
  },
  {
    "path": "console/src/pages/Agent/MCP/components/index.ts",
    "chars": 102,
    "preview": "export { MCPClientCard } from \"./MCPClientCard\";\nexport { MCPClientDrawer } from \"./MCPClientDrawer\";\n"
  },
  {
    "path": "console/src/pages/Agent/MCP/index.module.less",
    "chars": 2641,
    "preview": ".mcpCard {\n  border-radius: 12px;\n  transition: all 0.2s ease-in-out;\n  cursor: pointer;\n  padding: 16px;\n\n  &.enabledCa"
  },
  {
    "path": "console/src/pages/Agent/MCP/index.tsx",
    "chars": 7591,
    "preview": "import { useState } from \"react\";\nimport { Button, Empty, Modal, Input } from \"@agentscope-ai/design\";\nimport type { MCP"
  },
  {
    "path": "console/src/pages/Agent/MCP/useMCP.ts",
    "chars": 3355,
    "preview": "import { useCallback, useEffect, useState } from \"react\";\nimport { message } from \"@agentscope-ai/design\";\nimport api fr"
  },
  {
    "path": "console/src/pages/Agent/Skills/components/SkillCard.tsx",
    "chars": 5245,
    "preview": "import { Card, Button, Tooltip } from \"@agentscope-ai/design\";\nimport {\n  DeleteOutlined,\n  FileTextFilled,\n  FileZipFil"
  },
  {
    "path": "console/src/pages/Agent/Skills/components/SkillDrawer.tsx",
    "chars": 8301,
    "preview": "import { useState, useEffect, useCallback, useRef } from \"react\";\nimport { Drawer, Form, Input, Button, message } from \""
  },
  {
    "path": "console/src/pages/Agent/Skills/components/index.ts",
    "chars": 86,
    "preview": "export { SkillCard } from \"./SkillCard\";\nexport { SkillDrawer } from \"./SkillDrawer\";\n"
  },
  {
    "path": "console/src/pages/Agent/Skills/index.module.less",
    "chars": 6471,
    "preview": ".skillsPage {\n  padding: 24px;\n}\n\n.header {\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n  "
  },
  {
    "path": "console/src/pages/Agent/Skills/index.tsx",
    "chars": 8957,
    "preview": "import { useState, useRef } from \"react\";\nimport { Button, Form, Modal, message } from \"@agentscope-ai/design\";\nimport {"
  },
  {
    "path": "console/src/pages/Agent/Skills/useSkills.ts",
    "chars": 11892,
    "preview": "import { useState, useEffect, useCallback, useRef } from \"react\";\nimport { message, Modal } from \"@agentscope-ai/design\""
  },
  {
    "path": "console/src/pages/Agent/Tools/index.module.less",
    "chars": 2562,
    "preview": ".toolsPage {\n  padding: 24px;\n}\n\n.header {\n  display: flex;\n  justify-content: space-between;\n  align-items: flex-start;"
  },
  {
    "path": "console/src/pages/Agent/Tools/index.tsx",
    "chars": 3355,
    "preview": "import { useState, useMemo } from \"react\";\nimport { Card, Switch, Empty, Button } from \"@agentscope-ai/design\";\nimport {"
  },
  {
    "path": "console/src/pages/Agent/Tools/useTools.ts",
    "chars": 3820,
    "preview": "import { useCallback, useEffect, useState } from \"react\";\nimport { message } from \"@agentscope-ai/design\";\nimport api fr"
  },
  {
    "path": "console/src/pages/Agent/Workspace/components/FileEditor.tsx",
    "chars": 4592,
    "preview": "import React, { useState, useMemo } from \"react\";\nimport { Button, Card, Input, Switch, message } from \"@agentscope-ai/d"
  },
  {
    "path": "console/src/pages/Agent/Workspace/components/FileItem.tsx",
    "chars": 4223,
    "preview": "import React from \"react\";\nimport { Switch, Tooltip } from \"@agentscope-ai/design\";\nimport {\n  CaretDownOutlined,\n  Care"
  },
  {
    "path": "console/src/pages/Agent/Workspace/components/FileListPanel.tsx",
    "chars": 3748,
    "preview": "import React from \"react\";\nimport { Button, Card } from \"@agentscope-ai/design\";\nimport { ReloadOutlined } from \"@ant-de"
  },
  {
    "path": "console/src/pages/Agent/Workspace/components/index.ts",
    "chars": 205,
    "preview": "export { FileListPanel } from \"./FileListPanel\";\nexport { FileEditor } from \"./FileEditor\";\nexport { FileItem } from \"./"
  },
  {
    "path": "console/src/pages/Agent/Workspace/components/useAgentsData.ts",
    "chars": 9374,
    "preview": "import { useState, useEffect } from \"react\";\nimport { message } from \"@agentscope-ai/design\";\nimport { useTranslation } "
  },
  {
    "path": "console/src/pages/Agent/Workspace/components/utils.ts",
    "chars": 726,
    "preview": "export const formatFileSize = (bytes: number): string => {\n  if (bytes === 0) return \"0 B\";\n  if (bytes < 1024) return `"
  },
  {
    "path": "console/src/pages/Agent/Workspace/index.module.less",
    "chars": 8304,
    "preview": ".agentsPage {\n  padding: 24px;\n  height: calc(100vh - 96px);\n  display: flex;\n  flex-direction: column;\n  overflow: hidd"
  },
  {
    "path": "console/src/pages/Agent/Workspace/index.tsx",
    "chars": 5196,
    "preview": "import { useAgentsData, FileListPanel, FileEditor } from \"./components\";\nimport styles from \"./index.module.less\";\nimpor"
  },
  {
    "path": "console/src/pages/Chat/ModelSelector/index.module.less",
    "chars": 4106,
    "preview": "/* ---- Trigger button ---- */\n.trigger {\n  display: inline-flex;\n  align-items: center;\n  gap: 5px;\n  padding: 4px 10px"
  },
  {
    "path": "console/src/pages/Chat/ModelSelector/index.tsx",
    "chars": 7003,
    "preview": "import { useState, useEffect, useCallback, useRef } from \"react\";\nimport { Dropdown, message, Spin } from \"antd\";\nimport"
  },
  {
    "path": "console/src/pages/Chat/OptionsPanel/FormItem.tsx",
    "chars": 792,
    "preview": "import { Form } from \"antd\";\nimport { createStyles } from \"antd-style\";\n\ninterface FormItemProps {\n  name: string | stri"
  },
  {
    "path": "console/src/pages/Chat/OptionsPanel/OptionsEditor.tsx",
    "chars": 4971,
    "preview": "import React from \"react\";\nimport { Form, Input, ColorPicker, Flex, Divider, InputNumber } from \"antd\";\nimport { createS"
  },
  {
    "path": "console/src/pages/Chat/OptionsPanel/defaultConfig.ts",
    "chars": 1243,
    "preview": "import type { TFunction } from \"i18next\";\n\nconst defaultConfig = {\n  theme: {\n    colorPrimary: \"#615CED\",\n    darkMode:"
  },
  {
    "path": "console/src/pages/Chat/OptionsPanel/index.tsx",
    "chars": 942,
    "preview": "import { SparkSettingLine } from \"@agentscope-ai/icons\";\nimport { IconButton, Drawer } from \"@agentscope-ai/design\";\nimp"
  },
  {
    "path": "console/src/pages/Chat/index.module.less",
    "chars": 660,
    "preview": "/* Disabled input overlay */\n.chatDisabledOverlay {\n  position: relative;\n  height: 100%;\n  width: 100%;\n}\n\n.chatDisable"
  },
  {
    "path": "console/src/pages/Chat/index.tsx",
    "chars": 27803,
    "preview": "import {\n  AgentScopeRuntimeWebUI,\n  IAgentScopeRuntimeWebUIOptions,\n  type IAgentScopeRuntimeWebUIMessage,\n  type IAgen"
  },
  {
    "path": "console/src/pages/Chat/sessionApi/index.ts",
    "chars": 23561,
    "preview": "import type { RefObject } from \"react\";\nimport {\n  IAgentScopeRuntimeWebUISession,\n  IAgentScopeRuntimeWebUISessionAPI,\n"
  },
  {
    "path": "console/src/pages/Control/Channels/components/ChannelCard.tsx",
    "chars": 2585,
    "preview": "import { Card, Tooltip } from \"@agentscope-ai/design\";\nimport { useTranslation } from \"react-i18next\";\nimport { getChann"
  },
  {
    "path": "console/src/pages/Control/Channels/components/ChannelDrawer.tsx",
    "chars": 28315,
    "preview": "import {\n  Drawer,\n  Form,\n  Input,\n  InputNumber,\n  Switch,\n  Button,\n  Select,\n  message,\n} from \"@agentscope-ai/desig"
  },
  {
    "path": "console/src/pages/Control/Channels/components/constants.ts",
    "chars": 819,
    "preview": "// Channel key type - now accepts any string for custom channels\nexport type ChannelKey = string;\n\n// Built-in channel l"
  },
  {
    "path": "console/src/pages/Control/Channels/components/index.ts",
    "chars": 220,
    "preview": "export { ChannelCard } from \"./ChannelCard\";\nexport { ChannelDrawer } from \"./ChannelDrawer\";\nexport { useChannels } fro"
  },
  {
    "path": "console/src/pages/Control/Channels/index.module.less",
    "chars": 4777,
    "preview": ".channelsPage {\n  padding: 24px;\n}\n\n.pageHeader {\n  display: flex;\n  justify-content: space-between;\n  align-items: flex"
  },
  {
    "path": "console/src/pages/Control/Channels/index.tsx",
    "chars": 5155,
    "preview": "import { useMemo, useState } from \"react\";\nimport { Form, message } from \"@agentscope-ai/design\";\nimport { useTranslatio"
  },
  {
    "path": "console/src/pages/Control/Channels/useChannels.ts",
    "chars": 1773,
    "preview": "import { useState, useEffect, useCallback, useMemo } from \"react\";\nimport api from \"../../../api\";\nimport { useAgentStor"
  },
  {
    "path": "console/src/pages/Control/CronJobs/components/JobDrawer.tsx",
    "chars": 11958,
    "preview": "import {\n  Drawer,\n  Form,\n  Input,\n  InputNumber,\n  Select,\n  Switch,\n  Button,\n  Checkbox,\n} from \"@agentscope-ai/desi"
  },
  {
    "path": "console/src/pages/Control/CronJobs/components/columns.tsx",
    "chars": 10488,
    "preview": "import { Button, Tooltip, Dropdown } from \"@agentscope-ai/design\";\nimport type { ColumnsType } from \"antd/es/table\";\nimp"
  },
  {
    "path": "console/src/pages/Control/CronJobs/components/constants.ts",
    "chars": 597,
    "preview": "import dayjs from \"dayjs\";\n\nexport { TIMEZONE_OPTIONS } from \"../../../../constants/timezone\";\n\nexport const DEFAULT_FOR"
  },
  {
    "path": "console/src/pages/Control/CronJobs/components/index.ts",
    "chars": 199,
    "preview": "export { createColumns } from \"./columns\";\nexport { JobDrawer } from \"./JobDrawer\";\nexport { useCronJobs } from \"../useC"
  },
  {
    "path": "console/src/pages/Control/CronJobs/components/parseCron.ts",
    "chars": 6200,
    "preview": "/**\n * Parse cron expression to form-friendly format and vice versa.\n * Supports: hourly, daily, weekly, custom\n *\n * Da"
  },
  {
    "path": "console/src/pages/Control/CronJobs/index.module.less",
    "chars": 705,
    "preview": ".cronJobsPage {\n  padding: 24px;\n}\n\n.header {\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n"
  },
  {
    "path": "console/src/pages/Control/CronJobs/index.tsx",
    "chars": 6260,
    "preview": "import { useState, useEffect, useRef } from \"react\";\nimport { Button, Card, Form, Modal, Table } from \"@agentscope-ai/de"
  },
  {
    "path": "console/src/pages/Control/CronJobs/useCronJobs.ts",
    "chars": 3810,
    "preview": "import { useState, useEffect } from \"react\";\nimport { message } from \"@agentscope-ai/design\";\nimport api from \"../../../"
  },
  {
    "path": "console/src/pages/Control/Heartbeat/index.module.less",
    "chars": 913,
    "preview": ".heartbeatPage {\n  padding: 24px;\n}\n\n.title {\n  margin-bottom: 4px;\n  font-size: 24px;\n  font-weight: 600;\n}\n\n.descripti"
  },
  {
    "path": "console/src/pages/Control/Heartbeat/index.tsx",
    "chars": 7728,
    "preview": "import { useEffect, useState } from \"react\";\nimport {\n  Button,\n  Card,\n  Form,\n  InputNumber,\n  message,\n  Select,\n  Sw"
  },
  {
    "path": "console/src/pages/Control/Heartbeat/parseEvery.ts",
    "chars": 1197,
    "preview": "/**\n * Parse backend \"every\" string (e.g. \"6h\", \"30m\", \"2h30m\") to number + unit\n * for form display. Serialize back to "
  },
  {
    "path": "console/src/pages/Control/Sessions/components/FilterBar.tsx",
    "chars": 1273,
    "preview": "import { Input, Select } from \"@agentscope-ai/design\";\nimport { useTranslation } from \"react-i18next\";\nimport styles fro"
  },
  {
    "path": "console/src/pages/Control/Sessions/components/SessionDrawer.tsx",
    "chars": 1963,
    "preview": "import { Drawer, Form, Input, Button } from \"@agentscope-ai/design\";\nimport { useTranslation } from \"react-i18next\";\nimp"
  },
  {
    "path": "console/src/pages/Control/Sessions/components/columns.tsx",
    "chars": 2697,
    "preview": "import { Button, Tag } from \"@agentscope-ai/design\";\nimport { useTranslation } from \"react-i18next\";\nimport type { TFunc"
  },
  {
    "path": "console/src/pages/Control/Sessions/components/constants.ts",
    "chars": 1276,
    "preview": "import type { ChatSpec } from \"../../../../api/types\";\n\nexport interface Session extends ChatSpec {\n  name?: string;\n}\n\n"
  },
  {
    "path": "console/src/pages/Control/Sessions/components/index.ts",
    "chars": 205,
    "preview": "export { createColumns } from \"./columns\";\nexport { FilterBar } from \"./FilterBar\";\nexport { SessionDrawer } from \"./Ses"
  },
  {
    "path": "console/src/pages/Control/Sessions/index.module.less",
    "chars": 2844,
    "preview": ".sessionsPage {\n  padding: 24px;\n  height: calc(100vh - 128px);\n}\n\n.selectedRow > td {\n  background-color: #e6f7ff !impo"
  },
  {
    "path": "console/src/pages/Control/Sessions/index.tsx",
    "chars": 5545,
    "preview": "import { useEffect, useState } from \"react\";\nimport {\n  Card,\n  Form,\n  Modal,\n  Table,\n  message,\n  Button,\n} from \"@ag"
  },
  {
    "path": "console/src/pages/Control/Sessions/useSessions.ts",
    "chars": 2382,
    "preview": "import { useState, useEffect } from \"react\";\nimport { message } from \"@agentscope-ai/design\";\nimport api from \"../../../"
  },
  {
    "path": "console/src/pages/Login/index.tsx",
    "chars": 4398,
    "preview": "import { useState, useEffect } from \"react\";\nimport { useTranslation } from \"react-i18next\";\nimport { useNavigate, useSe"
  },
  {
    "path": "console/src/pages/Settings/Agents/components/AgentModal.tsx",
    "chars": 1798,
    "preview": "import { Modal, Form, Input } from \"antd\";\nimport { useTranslation } from \"react-i18next\";\nimport type { AgentSummary } "
  },
  {
    "path": "console/src/pages/Settings/Agents/components/AgentTable.tsx",
    "chars": 3342,
    "preview": "import { Table, Button, Space, Popconfirm } from \"antd\";\nimport type { ColumnsType } from \"antd/es/table\";\nimport { useT"
  },
  {
    "path": "console/src/pages/Settings/Agents/components/PageHeader.tsx",
    "chars": 691,
    "preview": "import styles from \"../index.module.less\";\n\ninterface PageHeaderProps {\n  title: string;\n  description?: string;\n  class"
  },
  {
    "path": "console/src/pages/Settings/Agents/components/index.ts",
    "chars": 129,
    "preview": "export { PageHeader } from \"./PageHeader\";\nexport { AgentTable } from \"./AgentTable\";\nexport { AgentModal } from \"./Agen"
  },
  {
    "path": "console/src/pages/Settings/Agents/index.module.less",
    "chars": 1279,
    "preview": ".agentsPage {\n  padding: 24px;\n}\n\n/* ---- Section (same as Models / Environments) ---- */\n\n.section {\n  margin-bottom: 2"
  },
  {
    "path": "console/src/pages/Settings/Agents/index.tsx",
    "chars": 2814,
    "preview": "import { useState } from \"react\";\nimport { Card, Button, Form, message } from \"antd\";\nimport { PlusOutlined } from \"@ant"
  },
  {
    "path": "console/src/pages/Settings/Agents/useAgents.ts",
    "chars": 1704,
    "preview": "import { useState, useEffect } from \"react\";\nimport { message } from \"antd\";\nimport { useTranslation } from \"react-i18ne"
  },
  {
    "path": "console/src/pages/Settings/Environments/components/AddButton.tsx",
    "chars": 636,
    "preview": "import { SparkPlusLine } from \"@agentscope-ai/icons\";\nimport { useTranslation } from \"react-i18next\";\nimport styles from"
  },
  {
    "path": "console/src/pages/Settings/Environments/components/EmptyState.tsx",
    "chars": 435,
    "preview": "import { useTranslation } from \"react-i18next\";\nimport styles from \"../index.module.less\";\n\ninterface EmptyStateProps {\n"
  },
  {
    "path": "console/src/pages/Settings/Environments/components/EnvRow.tsx",
    "chars": 3119,
    "preview": "import { Checkbox, Input } from \"@agentscope-ai/design\";\nimport { SparkDeleteLine, SparkPlusLine } from \"@agentscope-ai/"
  },
  {
    "path": "console/src/pages/Settings/Environments/components/PageHeader.tsx",
    "chars": 488,
    "preview": "import { useTranslation } from \"react-i18next\";\nimport styles from \"../index.module.less\";\n\ninterface PageHeaderProps {\n"
  },
  {
    "path": "console/src/pages/Settings/Environments/components/Toolbar.tsx",
    "chars": 2291,
    "preview": "import { Checkbox, Button } from \"@agentscope-ai/design\";\nimport { SparkDeleteLine } from \"@agentscope-ai/icons\";\nimport"
  },
  {
    "path": "console/src/pages/Settings/Environments/components/index.ts",
    "chars": 142,
    "preview": "export * from \"./PageHeader\";\nexport * from \"./EmptyState\";\nexport * from \"./AddButton\";\nexport * from \"./Toolbar\";\nexpo"
  },
  {
    "path": "console/src/pages/Settings/Environments/index.module.less",
    "chars": 9042,
    "preview": ".environmentsPage {\n  padding: 24px;\n}\n\n/* ---- Section (matches Models page) ---- */\n\n.section {\n  margin-bottom: 24px;"
  },
  {
    "path": "console/src/pages/Settings/Environments/index.tsx",
    "chars": 10046,
    "preview": "import { useState, useCallback, useMemo } from \"react\";\nimport { Button, Modal, message } from \"@agentscope-ai/design\";\n"
  },
  {
    "path": "console/src/pages/Settings/Environments/useEnvVars.ts",
    "chars": 889,
    "preview": "import { useState, useEffect, useCallback } from \"react\";\nimport api from \"../../../api\";\nimport type { EnvVar } from \"."
  },
  {
    "path": "console/src/pages/Settings/Models/components/ModelManageModal.tsx",
    "chars": 27293,
    "preview": "import { useState, useEffect, useCallback, useRef } from \"react\";\nimport {\n  Button,\n  Form,\n  Input,\n  Modal,\n  Select,"
  },
  {
    "path": "console/src/pages/Settings/Models/components/cards/LocalProviderCard.tsx",
    "chars": 3401,
    "preview": "import { useState } from \"react\";\nimport { Card, Button, Tag } from \"@agentscope-ai/design\";\nimport { AppstoreOutlined }"
  },
  {
    "path": "console/src/pages/Settings/Models/components/cards/ProviderCard.tsx",
    "chars": 996,
    "preview": "import type { ProviderInfo, ActiveModelsInfo } from \"../../../../../api/types\";\nimport { LocalProviderCard } from \"./Loc"
  },
  {
    "path": "console/src/pages/Settings/Models/components/cards/RemoteProviderCard.tsx",
    "chars": 6747,
    "preview": "import { useState } from \"react\";\nimport { Card, Button, Tag, Modal, message } from \"@agentscope-ai/design\";\nimport {\n  "
  },
  {
    "path": "console/src/pages/Settings/Models/components/cards/index.ts",
    "chars": 107,
    "preview": "export * from \"./ProviderCard\";\nexport * from \"./LocalProviderCard\";\nexport * from \"./RemoteProviderCard\";\n"
  },
  {
    "path": "console/src/pages/Settings/Models/components/index.ts",
    "chars": 127,
    "preview": "// Re-export all components from subdirectories\nexport * from \"./sections\";\nexport * from \"./cards\";\nexport * from \"./mo"
  },
  {
    "path": "console/src/pages/Settings/Models/components/modals/CustomProviderModal.tsx",
    "chars": 3498,
    "preview": "import { useState, useEffect } from \"react\";\nimport { Form, Input, Modal, Select, message } from \"@agentscope-ai/design\""
  },
  {
    "path": "console/src/pages/Settings/Models/components/modals/LocalModelManageModal.tsx",
    "chars": 12713,
    "preview": "import { useState, useEffect, useCallback, useRef } from \"react\";\nimport {\n  Button,\n  Form,\n  Input,\n  Modal,\n  Select,"
  },
  {
    "path": "console/src/pages/Settings/Models/components/modals/ModelManageModal.tsx",
    "chars": 1097,
    "preview": "import type { ProviderInfo } from \"../../../../../api/types\";\nimport { LocalModelManageModal } from \"./LocalModelManageM"
  },
  {
    "path": "console/src/pages/Settings/Models/components/modals/OllamaModelManageModal.tsx",
    "chars": 12281,
    "preview": "import { useState, useEffect, useCallback, useRef } from \"react\";\nimport { Button, Form, Input, Modal, message } from \"@"
  },
  {
    "path": "console/src/pages/Settings/Models/components/modals/ProviderConfigModal.tsx",
    "chars": 20516,
    "preview": "import { useState, useEffect, useMemo, useRef } from \"react\";\nimport type { KeyboardEvent, ReactNode, UIEvent } from \"re"
  },
  {
    "path": "console/src/pages/Settings/Models/components/modals/RemoteModelManageModal.tsx",
    "chars": 11264,
    "preview": "import { useState, useEffect } from \"react\";\nimport {\n  Button,\n  Form,\n  Input,\n  Modal,\n  Tag,\n  message,\n} from \"@age"
  },
  {
    "path": "console/src/pages/Settings/Models/components/modals/index.ts",
    "chars": 239,
    "preview": "export * from \"./CustomProviderModal\";\nexport * from \"./ProviderConfigModal\";\nexport * from \"./ModelManageModal\";\nexport"
  },
  {
    "path": "console/src/pages/Settings/Models/components/sections/LoadingState.tsx",
    "chars": 779,
    "preview": "import { Button } from \"@agentscope-ai/design\";\nimport { useTranslation } from \"react-i18next\";\nimport styles from \"../."
  },
  {
    "path": "console/src/pages/Settings/Models/components/sections/ModelsSection.tsx",
    "chars": 5419,
    "preview": "import { useState, useEffect, useMemo } from \"react\";\nimport { SaveOutlined } from \"@ant-design/icons\";\nimport { Select,"
  },
  {
    "path": "console/src/pages/Settings/Models/components/sections/PageHeader.tsx",
    "chars": 432,
    "preview": "import styles from \"../../index.module.less\";\n\ninterface PageHeaderProps {\n  title: string;\n  description: string;\n  cla"
  },
  {
    "path": "console/src/pages/Settings/Models/components/sections/index.ts",
    "chars": 95,
    "preview": "export * from \"./PageHeader\";\nexport * from \"./LoadingState\";\nexport * from \"./ModelsSection\";\n"
  },
  {
    "path": "console/src/pages/Settings/Models/index.module.less",
    "chars": 10915,
    "preview": ".settingsPage {\n  padding: 24px;\n}\n\n.loading {\n  text-align: center;\n  padding: 60px;\n}\n\n.loadingText {\n  color: #999;\n}"
  },
  {
    "path": "console/src/pages/Settings/Models/index.tsx",
    "chars": 3670,
    "preview": "import { useMemo, useState } from \"react\";\nimport { Button } from \"@agentscope-ai/design\";\nimport { PlusOutlined } from "
  },
  {
    "path": "console/src/pages/Settings/Models/useProviders.ts",
    "chars": 1520,
    "preview": "import { useState, useEffect, useCallback } from \"react\";\nimport api from \"../../../api\";\nimport type { ProviderInfo, Ac"
  },
  {
    "path": "console/src/pages/Settings/Security/components/PageHeader.tsx",
    "chars": 464,
    "preview": "import { useTranslation } from \"react-i18next\";\nimport styles from \"../index.module.less\";\n\ninterface PageHeaderProps {\n"
  },
  {
    "path": "console/src/pages/Settings/Security/components/PreviewModal.tsx",
    "chars": 2727,
    "preview": "import { Modal, Button, Tag } from \"@agentscope-ai/design\";\nimport { useTranslation } from \"react-i18next\";\nimport type "
  },
  {
    "path": "console/src/pages/Settings/Security/components/RuleModal.tsx",
    "chars": 4983,
    "preview": "import { useEffect } from \"react\";\nimport { Modal, Form, Input, Select } from \"@agentscope-ai/design\";\nimport { useTrans"
  },
  {
    "path": "console/src/pages/Settings/Security/components/RuleTable.tsx",
    "chars": 4729,
    "preview": "import { Table, Tag, Switch, Button, Tooltip } from \"@agentscope-ai/design\";\nimport { Space } from \"antd\";\nimport { Eye,"
  }
]

// ... and 614 more files (download for full content)

About this extraction

This page contains the full source code of the agentscope-ai/CoPaw GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 814 files (16.6 MB), approximately 4.2M tokens, and a symbol index with 2841 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!