manigent 1.3.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- manigent/_manigent_plugin/LICENSE +21 -0
- manigent/_manigent_plugin/README.md +89 -0
- manigent/_manigent_plugin/references/cli-reference.md +349 -0
- manigent/_manigent_plugin/skills/bootstrap/SKILL.md +156 -0
- manigent/_manigent_plugin/skills/install-agent/SKILL.md +71 -0
- manigent/_manigent_plugin/skills/login/SKILL.md +68 -0
- manigent/_manigent_plugin/skills/logout/SKILL.md +46 -0
- manigent/_manigent_plugin/skills/register-agent/SKILL.md +212 -0
- manigent/_manigent_plugin/skills/register-assistant/SKILL.md +99 -0
- manigent/_manigent_plugin/skills/signup/SKILL.md +78 -0
- manigent/_manigent_plugin/skills/start/SKILL.md +56 -0
- manigent/_manigent_plugin/skills/stop/SKILL.md +37 -0
- manigent/api/action_executor.py +404 -0
- manigent/api/action_validator.py +531 -0
- manigent/api/actions.py +221 -0
- manigent/api/client.py +558 -0
- manigent/api/control.py +806 -0
- manigent/api/edge_protocol.py +123 -0
- manigent/api/fs_protocol.py +450 -0
- manigent/api/host_protocol.py +298 -0
- manigent/api/managed_job_client.py +124 -0
- manigent/api/responses.py +342 -0
- manigent/api/retry_transport.py +178 -0
- manigent/api/turn_protocol.py +78 -0
- manigent/cli.py +161 -0
- manigent/cli_brief.py +197 -0
- manigent/cli_browser.py +441 -0
- manigent/cli_caldav.py +125 -0
- manigent/cli_client.py +700 -0
- manigent/cli_daemon.py +229 -0
- manigent/cli_doctor.py +123 -0
- manigent/cli_env.py +270 -0
- manigent/cli_fs.py +469 -0
- manigent/cli_gcal.py +152 -0
- manigent/cli_gdrive.py +85 -0
- manigent/cli_gdrive_write.py +134 -0
- manigent/cli_gmail.py +226 -0
- manigent/cli_homekit.py +57 -0
- manigent/cli_http_api.py +181 -0
- manigent/cli_hub.py +506 -0
- manigent/cli_ib.py +155 -0
- manigent/cli_job.py +180 -0
- manigent/cli_knowledge_dir.py +48 -0
- manigent/cli_lifecycle.py +467 -0
- manigent/cli_mailbox.py +113 -0
- manigent/cli_media.py +144 -0
- manigent/cli_model_config.py +545 -0
- manigent/cli_oauth.py +218 -0
- manigent/cli_osxphotos.py +78 -0
- manigent/cli_plan.py +1639 -0
- manigent/cli_runner.py +5678 -0
- manigent/cli_skill.py +309 -0
- manigent/cli_sop.py +482 -0
- manigent/cli_todo.py +1165 -0
- manigent/doctor.py +1167 -0
- manigent/integrations/_config_resolve.py +98 -0
- manigent/integrations/auth/google_oauth.py +438 -0
- manigent/integrations/browser/_daemon.py +293 -0
- manigent/integrations/browser/_lib.py +1147 -0
- manigent/integrations/caldav/_lib.py +354 -0
- manigent/integrations/gcal/_lib.py +87 -0
- manigent/integrations/gdrive/_lib.py +104 -0
- manigent/integrations/gdrive_write/_lib.py +244 -0
- manigent/integrations/gmail/_lib.py +220 -0
- manigent/integrations/homekit/_lib.py +90 -0
- manigent/integrations/http_api/_client.py +447 -0
- manigent/integrations/ib/_lib.py +953 -0
- manigent/integrations/mailbox/_lib.py +508 -0
- manigent/integrations/media/_lib.py +725 -0
- manigent/integrations/osxphotos/_lib.py +152 -0
- manigent/llm/_harness_tools.py +408 -0
- manigent/llm/_openai_wire.py +257 -0
- manigent/llm/_structured_text.py +134 -0
- manigent/llm/antigravity_cli.py +473 -0
- manigent/llm/api_provider.py +1559 -0
- manigent/llm/base.py +1072 -0
- manigent/llm/claude_cli.py +550 -0
- manigent/llm/codex_cli.py +505 -0
- manigent/llm/gemini_cli.py +458 -0
- manigent/llm/native_harness.py +460 -0
- manigent/llm/opencode_cli.py +425 -0
- manigent/llm/pricing.py +91 -0
- manigent/llm/prompt_builder.py +2450 -0
- manigent/llm/smart_router.py +194 -0
- manigent/llm/triggers.py +68 -0
- manigent/mcp/config_resolve.py +52 -0
- manigent/mcp/servers/chess_server.py +389 -0
- manigent/mcp/servers/chess_view.html +272 -0
- manigent/models/agent.py +2905 -0
- manigent/models/agent_bundle.py +298 -0
- manigent/models/brief_tab.py +311 -0
- manigent/models/chat_message.py +231 -0
- manigent/models/chatroom.py +1108 -0
- manigent/models/context.py +1159 -0
- manigent/models/desk_label.py +836 -0
- manigent/models/desk_read_state.py +220 -0
- manigent/models/desk_sign_off.py +267 -0
- manigent/models/desk_sop.py +965 -0
- manigent/models/desk_todo.py +1603 -0
- manigent/models/desk_todo_link.py +404 -0
- manigent/models/file_cache.py +75 -0
- manigent/models/host.py +732 -0
- manigent/models/install_token.py +401 -0
- manigent/models/invitation_code.py +162 -0
- manigent/models/knowledge_pack.py +552 -0
- manigent/models/managed_job.py +295 -0
- manigent/models/model_config.py +564 -0
- manigent/models/model_defaults.py +238 -0
- manigent/models/onboarding_plan.py +161 -0
- manigent/models/participant.py +449 -0
- manigent/models/persistable.py +892 -0
- manigent/models/plan_markdown.py +1774 -0
- manigent/models/project.py +1343 -0
- manigent/models/project_plan.py +8104 -0
- manigent/models/project_report.py +145 -0
- manigent/models/provenance.py +261 -0
- manigent/models/provider_agents.py +101 -0
- manigent/models/public_request.py +108 -0
- manigent/models/reflection_schedule.py +191 -0
- manigent/models/scheduled_message.py +285 -0
- manigent/models/share_token.py +190 -0
- manigent/models/skill_version.py +31 -0
- manigent/models/sop_template.py +187 -0
- manigent/models/thinking_partner_plan.py +187 -0
- manigent/models/tunnel_binding.py +129 -0
- manigent/models/user.py +1276 -0
- manigent/models/work_tracker.py +510 -0
- manigent/runner/agent_dir_readmes.py +262 -0
- manigent/runner/cost_recorder.py +95 -0
- manigent/runner/egress_proxy.py +409 -0
- manigent/runner/home_fs.py +984 -0
- manigent/runner/home_fs_handler.py +257 -0
- manigent/runner/home_fs_win.py +410 -0
- manigent/runner/host_report.py +172 -0
- manigent/runner/invocation_registry.py +187 -0
- manigent/runner/job_supervisor.py +318 -0
- manigent/runner/knowledge_dir_migration.py +113 -0
- manigent/runner/knowledge_pack_manager.py +388 -0
- manigent/runner/mcp_manager.py +324 -0
- manigent/runner/model_auth_proxy.py +426 -0
- manigent/runner/netshim.py +109 -0
- manigent/runner/participant_notifier.py +404 -0
- manigent/runner/personal_skill_manager.py +38 -0
- manigent/runner/reactive_loop.py +1633 -0
- manigent/runner/references_index.py +150 -0
- manigent/runner/repo_push.py +189 -0
- manigent/runner/resync.py +113 -0
- manigent/runner/skill_manager.py +202 -0
- manigent/runner/system_skill_manager.py +140 -0
- manigent/runner/turn_capability.py +492 -0
- manigent/runner/turn_home.py +208 -0
- manigent/runner/turn_sandbox.py +645 -0
- manigent/runner/turn_scope.py +320 -0
- manigent/runner/turn_session.py +401 -0
- manigent/sync/changelog.py +885 -0
- manigent/sync/console_subscriber.py +222 -0
- manigent/sync/runloop.py +619 -0
- manigent/sync/runloop_manager.py +197 -0
- manigent/sync/subscriber.py +98 -0
- manigent/system_skills/author-brief-tab/SKILL.md +227 -0
- manigent/system_skills/author-brief-tab/manifest.json +6 -0
- manigent/system_skills/bootstrap/SKILL.md +156 -0
- manigent/system_skills/bootstrap/manifest.json +6 -0
- manigent/system_skills/canvas-design/LICENSE.txt +202 -0
- manigent/system_skills/canvas-design/SKILL.md +130 -0
- manigent/system_skills/canvas-design/canvas-fonts/ArsenalSC-OFL.txt +93 -0
- manigent/system_skills/canvas-design/canvas-fonts/ArsenalSC-Regular.ttf +0 -0
- manigent/system_skills/canvas-design/canvas-fonts/BigShoulders-Bold.ttf +0 -0
- manigent/system_skills/canvas-design/canvas-fonts/BigShoulders-OFL.txt +93 -0
- manigent/system_skills/canvas-design/canvas-fonts/BigShoulders-Regular.ttf +0 -0
- manigent/system_skills/canvas-design/canvas-fonts/Boldonse-OFL.txt +93 -0
- manigent/system_skills/canvas-design/canvas-fonts/Boldonse-Regular.ttf +0 -0
- manigent/system_skills/canvas-design/canvas-fonts/BricolageGrotesque-Bold.ttf +0 -0
- manigent/system_skills/canvas-design/canvas-fonts/BricolageGrotesque-OFL.txt +93 -0
- manigent/system_skills/canvas-design/canvas-fonts/BricolageGrotesque-Regular.ttf +0 -0
- manigent/system_skills/canvas-design/canvas-fonts/CrimsonPro-Bold.ttf +0 -0
- manigent/system_skills/canvas-design/canvas-fonts/CrimsonPro-Italic.ttf +0 -0
- manigent/system_skills/canvas-design/canvas-fonts/CrimsonPro-OFL.txt +93 -0
- manigent/system_skills/canvas-design/canvas-fonts/CrimsonPro-Regular.ttf +0 -0
- manigent/system_skills/canvas-design/canvas-fonts/DMMono-OFL.txt +93 -0
- manigent/system_skills/canvas-design/canvas-fonts/DMMono-Regular.ttf +0 -0
- manigent/system_skills/canvas-design/canvas-fonts/EricaOne-OFL.txt +94 -0
- manigent/system_skills/canvas-design/canvas-fonts/EricaOne-Regular.ttf +0 -0
- manigent/system_skills/canvas-design/canvas-fonts/GeistMono-Bold.ttf +0 -0
- manigent/system_skills/canvas-design/canvas-fonts/GeistMono-OFL.txt +93 -0
- manigent/system_skills/canvas-design/canvas-fonts/GeistMono-Regular.ttf +0 -0
- manigent/system_skills/canvas-design/canvas-fonts/Gloock-OFL.txt +93 -0
- manigent/system_skills/canvas-design/canvas-fonts/Gloock-Regular.ttf +0 -0
- manigent/system_skills/canvas-design/canvas-fonts/IBMPlexMono-Bold.ttf +0 -0
- manigent/system_skills/canvas-design/canvas-fonts/IBMPlexMono-OFL.txt +93 -0
- manigent/system_skills/canvas-design/canvas-fonts/IBMPlexMono-Regular.ttf +0 -0
- manigent/system_skills/canvas-design/canvas-fonts/IBMPlexSerif-Bold.ttf +0 -0
- manigent/system_skills/canvas-design/canvas-fonts/IBMPlexSerif-BoldItalic.ttf +0 -0
- manigent/system_skills/canvas-design/canvas-fonts/IBMPlexSerif-Italic.ttf +0 -0
- manigent/system_skills/canvas-design/canvas-fonts/IBMPlexSerif-Regular.ttf +0 -0
- manigent/system_skills/canvas-design/canvas-fonts/InstrumentSans-Bold.ttf +0 -0
- manigent/system_skills/canvas-design/canvas-fonts/InstrumentSans-BoldItalic.ttf +0 -0
- manigent/system_skills/canvas-design/canvas-fonts/InstrumentSans-Italic.ttf +0 -0
- manigent/system_skills/canvas-design/canvas-fonts/InstrumentSans-OFL.txt +93 -0
- manigent/system_skills/canvas-design/canvas-fonts/InstrumentSans-Regular.ttf +0 -0
- manigent/system_skills/canvas-design/canvas-fonts/InstrumentSerif-Italic.ttf +0 -0
- manigent/system_skills/canvas-design/canvas-fonts/InstrumentSerif-Regular.ttf +0 -0
- manigent/system_skills/canvas-design/canvas-fonts/Italiana-OFL.txt +93 -0
- manigent/system_skills/canvas-design/canvas-fonts/Italiana-Regular.ttf +0 -0
- manigent/system_skills/canvas-design/canvas-fonts/JetBrainsMono-Bold.ttf +0 -0
- manigent/system_skills/canvas-design/canvas-fonts/JetBrainsMono-OFL.txt +93 -0
- manigent/system_skills/canvas-design/canvas-fonts/JetBrainsMono-Regular.ttf +0 -0
- manigent/system_skills/canvas-design/canvas-fonts/Jura-Light.ttf +0 -0
- manigent/system_skills/canvas-design/canvas-fonts/Jura-Medium.ttf +0 -0
- manigent/system_skills/canvas-design/canvas-fonts/Jura-OFL.txt +93 -0
- manigent/system_skills/canvas-design/canvas-fonts/LibreBaskerville-OFL.txt +93 -0
- manigent/system_skills/canvas-design/canvas-fonts/LibreBaskerville-Regular.ttf +0 -0
- manigent/system_skills/canvas-design/canvas-fonts/Lora-Bold.ttf +0 -0
- manigent/system_skills/canvas-design/canvas-fonts/Lora-BoldItalic.ttf +0 -0
- manigent/system_skills/canvas-design/canvas-fonts/Lora-Italic.ttf +0 -0
- manigent/system_skills/canvas-design/canvas-fonts/Lora-OFL.txt +93 -0
- manigent/system_skills/canvas-design/canvas-fonts/Lora-Regular.ttf +0 -0
- manigent/system_skills/canvas-design/canvas-fonts/NationalPark-Bold.ttf +0 -0
- manigent/system_skills/canvas-design/canvas-fonts/NationalPark-OFL.txt +93 -0
- manigent/system_skills/canvas-design/canvas-fonts/NationalPark-Regular.ttf +0 -0
- manigent/system_skills/canvas-design/canvas-fonts/NothingYouCouldDo-OFL.txt +93 -0
- manigent/system_skills/canvas-design/canvas-fonts/NothingYouCouldDo-Regular.ttf +0 -0
- manigent/system_skills/canvas-design/canvas-fonts/Outfit-Bold.ttf +0 -0
- manigent/system_skills/canvas-design/canvas-fonts/Outfit-OFL.txt +93 -0
- manigent/system_skills/canvas-design/canvas-fonts/Outfit-Regular.ttf +0 -0
- manigent/system_skills/canvas-design/canvas-fonts/PixelifySans-Medium.ttf +0 -0
- manigent/system_skills/canvas-design/canvas-fonts/PixelifySans-OFL.txt +93 -0
- manigent/system_skills/canvas-design/canvas-fonts/PoiretOne-OFL.txt +93 -0
- manigent/system_skills/canvas-design/canvas-fonts/PoiretOne-Regular.ttf +0 -0
- manigent/system_skills/canvas-design/canvas-fonts/RedHatMono-Bold.ttf +0 -0
- manigent/system_skills/canvas-design/canvas-fonts/RedHatMono-OFL.txt +93 -0
- manigent/system_skills/canvas-design/canvas-fonts/RedHatMono-Regular.ttf +0 -0
- manigent/system_skills/canvas-design/canvas-fonts/Silkscreen-OFL.txt +93 -0
- manigent/system_skills/canvas-design/canvas-fonts/Silkscreen-Regular.ttf +0 -0
- manigent/system_skills/canvas-design/canvas-fonts/SmoochSans-Medium.ttf +0 -0
- manigent/system_skills/canvas-design/canvas-fonts/SmoochSans-OFL.txt +93 -0
- manigent/system_skills/canvas-design/canvas-fonts/Tektur-Medium.ttf +0 -0
- manigent/system_skills/canvas-design/canvas-fonts/Tektur-OFL.txt +93 -0
- manigent/system_skills/canvas-design/canvas-fonts/Tektur-Regular.ttf +0 -0
- manigent/system_skills/canvas-design/canvas-fonts/WorkSans-Bold.ttf +0 -0
- manigent/system_skills/canvas-design/canvas-fonts/WorkSans-BoldItalic.ttf +0 -0
- manigent/system_skills/canvas-design/canvas-fonts/WorkSans-Italic.ttf +0 -0
- manigent/system_skills/canvas-design/canvas-fonts/WorkSans-OFL.txt +93 -0
- manigent/system_skills/canvas-design/canvas-fonts/WorkSans-Regular.ttf +0 -0
- manigent/system_skills/canvas-design/canvas-fonts/YoungSerif-OFL.txt +93 -0
- manigent/system_skills/canvas-design/canvas-fonts/YoungSerif-Regular.ttf +0 -0
- manigent/system_skills/canvas-design/manifest.json +6 -0
- manigent/system_skills/coding-philosophy/SKILL.md +114 -0
- manigent/system_skills/coding-philosophy/manifest.json +6 -0
- manigent/system_skills/coding-plan/SKILL.md +248 -0
- manigent/system_skills/coding-plan/manifest.json +6 -0
- manigent/system_skills/coding-project-completion-report/SKILL.md +594 -0
- manigent/system_skills/coding-project-completion-report/manifest.json +6 -0
- manigent/system_skills/control-agent/SKILL.md +175 -0
- manigent/system_skills/control-agent/manifest.json +6 -0
- manigent/system_skills/control-computer/SKILL.md +182 -0
- manigent/system_skills/control-computer/manifest.json +6 -0
- manigent/system_skills/create-project/SKILL.md +282 -0
- manigent/system_skills/create-project/manifest.json +6 -0
- manigent/system_skills/decide-plan-as-owner/SKILL.md +165 -0
- manigent/system_skills/decide-plan-as-owner/manifest.json +6 -0
- manigent/system_skills/desk-sop/SKILL.md +296 -0
- manigent/system_skills/desk-sop/manifest.json +6 -0
- manigent/system_skills/desk-sop-publish/SKILL.md +70 -0
- manigent/system_skills/desk-sop-publish/manifest.json +6 -0
- manigent/system_skills/desk-todo/SKILL.md +272 -0
- manigent/system_skills/desk-todo/manifest.json +6 -0
- manigent/system_skills/desk-todo-publish/SKILL.md +150 -0
- manigent/system_skills/desk-todo-publish/manifest.json +6 -0
- manigent/system_skills/direct-message/SKILL.md +147 -0
- manigent/system_skills/direct-message/manifest.json +6 -0
- manigent/system_skills/env-var/SKILL.md +112 -0
- manigent/system_skills/env-var/manifest.json +6 -0
- manigent/system_skills/explain-as-page/SKILL.md +206 -0
- manigent/system_skills/explain-as-page/manifest.json +6 -0
- manigent/system_skills/fs/SKILL.md +101 -0
- manigent/system_skills/fs/manifest.json +6 -0
- manigent/system_skills/git-workflow/SKILL.md +211 -0
- manigent/system_skills/git-workflow/manifest.json +6 -0
- manigent/system_skills/install-agent/SKILL.md +71 -0
- manigent/system_skills/install-agent/manifest.json +6 -0
- manigent/system_skills/install-mcp/SKILL.md +59 -0
- manigent/system_skills/install-mcp/manifest.json +6 -0
- manigent/system_skills/install-skill/SKILL.md +110 -0
- manigent/system_skills/install-skill/manifest.json +6 -0
- manigent/system_skills/invoke-timeout/SKILL.md +95 -0
- manigent/system_skills/invoke-timeout/manifest.json +6 -0
- manigent/system_skills/isolation-check/SKILL.md +73 -0
- manigent/system_skills/isolation-check/manifest.json +6 -0
- manigent/system_skills/login/SKILL.md +68 -0
- manigent/system_skills/login/manifest.json +6 -0
- manigent/system_skills/logout/SKILL.md +46 -0
- manigent/system_skills/logout/manifest.json +6 -0
- manigent/system_skills/manage-project-roster/SKILL.md +134 -0
- manigent/system_skills/manage-project-roster/manifest.json +6 -0
- manigent/system_skills/manage-team/SKILL.md +165 -0
- manigent/system_skills/manage-team/manifest.json +6 -0
- manigent/system_skills/managed-background-job/SKILL.md +84 -0
- manigent/system_skills/managed-background-job/manifest.json +6 -0
- manigent/system_skills/onboard-agent/SKILL.md +320 -0
- manigent/system_skills/onboard-agent/manifest.json +6 -0
- manigent/system_skills/personalize-agent/SKILL.md +191 -0
- manigent/system_skills/personalize-agent/manifest.json +6 -0
- manigent/system_skills/personalize-assistant/SKILL.md +197 -0
- manigent/system_skills/personalize-assistant/manifest.json +6 -0
- manigent/system_skills/plan/SKILL.md +1032 -0
- manigent/system_skills/plan/manifest.json +6 -0
- manigent/system_skills/post-chat-message/SKILL.md +74 -0
- manigent/system_skills/post-chat-message/manifest.json +6 -0
- manigent/system_skills/project-completion-report/SKILL.md +381 -0
- manigent/system_skills/project-completion-report/manifest.json +6 -0
- manigent/system_skills/propose-project/SKILL.md +463 -0
- manigent/system_skills/propose-project/manifest.json +6 -0
- manigent/system_skills/reconfigure-agent/SKILL.md +159 -0
- manigent/system_skills/reconfigure-agent/manifest.json +6 -0
- manigent/system_skills/reflect/SKILL.md +296 -0
- manigent/system_skills/reflect/manifest.json +6 -0
- manigent/system_skills/register-agent/SKILL.md +212 -0
- manigent/system_skills/register-agent/manifest.json +6 -0
- manigent/system_skills/register-assistant/SKILL.md +99 -0
- manigent/system_skills/register-assistant/manifest.json +6 -0
- manigent/system_skills/relay-to-agent/SKILL.md +54 -0
- manigent/system_skills/relay-to-agent/manifest.json +6 -0
- manigent/system_skills/schedule-message/SKILL.md +120 -0
- manigent/system_skills/schedule-message/manifest.json +6 -0
- manigent/system_skills/signup/SKILL.md +78 -0
- manigent/system_skills/signup/manifest.json +6 -0
- manigent/system_skills/start/SKILL.md +56 -0
- manigent/system_skills/start/manifest.json +6 -0
- manigent/system_skills/stop/SKILL.md +37 -0
- manigent/system_skills/stop/manifest.json +6 -0
- manigent/system_skills/uninstall-mcp/SKILL.md +48 -0
- manigent/system_skills/uninstall-mcp/manifest.json +6 -0
- manigent/system_skills/uninstall-skill/SKILL.md +48 -0
- manigent/system_skills/uninstall-skill/manifest.json +6 -0
- manigent/system_skills/update-mcp-config/SKILL.md +86 -0
- manigent/system_skills/update-mcp-config/manifest.json +6 -0
- manigent/system_skills/update-skill-config/SKILL.md +87 -0
- manigent/system_skills/update-skill-config/manifest.json +6 -0
- manigent/utils/agent_processes.py +276 -0
- manigent/utils/agent_storage.py +125 -0
- manigent/utils/email.py +563 -0
- manigent/utils/env_store.py +174 -0
- manigent/utils/extra_dirs.py +85 -0
- manigent/utils/file_io.py +485 -0
- manigent/utils/invoke_timeout.py +87 -0
- manigent/utils/jsonc.py +78 -0
- manigent/utils/knowledge_index.py +148 -0
- manigent/utils/nofollow_fs.py +279 -0
- manigent/utils/notification_center.py +67 -0
- manigent/utils/version.py +57 -0
- manigent-1.3.1.dist-info/METADATA +150 -0
- manigent-1.3.1.dist-info/RECORD +357 -0
- manigent-1.3.1.dist-info/WHEEL +5 -0
- manigent-1.3.1.dist-info/entry_points.txt +2 -0
- manigent-1.3.1.dist-info/licenses/LICENSE +21 -0
- manigent-1.3.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: update-skill-config
|
|
3
|
+
description: >
|
|
4
|
+
Add, modify, or remove an entry in a per-agent skill config file
|
|
5
|
+
(the JSON files under `{agent_dir}/skill-hub/configs/<name>.json`,
|
|
6
|
+
carrying API keys, sync windows, source/rule definitions, etc.).
|
|
7
|
+
Use whenever you (an agent) need to mutate your own skill config,
|
|
8
|
+
OR — when you are the user's assistant — when you need to do so on
|
|
9
|
+
behalf of another agent the user owns. **Do not edit those files
|
|
10
|
+
directly with Edit/Write/Bash** — go through this skill so the
|
|
11
|
+
server's per-agent config mirror stays in sync with the runner's
|
|
12
|
+
file; a direct edit silently drifts and can be clobbered by the
|
|
13
|
+
next AGENT_SETTINGS_CHANGE broadcast.
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# Update Skill Config
|
|
17
|
+
|
|
18
|
+
Update the runtime config for a configurable skill. Common cases: API
|
|
19
|
+
keys, sync windows, ETL source/rule definitions.
|
|
20
|
+
|
|
21
|
+
There are two callsites for this skill:
|
|
22
|
+
|
|
23
|
+
- **Agent-self** — you (a worker/coordinator) were asked to modify
|
|
24
|
+
your own `skill-hub/configs/<name>.json`. Use `self` as the agent
|
|
25
|
+
argument and rely on the runner-injected env (`MANIGENT_AGENT_ID`
|
|
26
|
+
/ `MANIGENT_AGENT_TOKEN` / `MANIGENT_SERVER_URL`).
|
|
27
|
+
- **Assistant-on-another-agent** — you are `{username}-assistant` and
|
|
28
|
+
the user asked you to configure a skill on one of their other
|
|
29
|
+
agents. Use the agent's name and `$MANIGENT_ASSISTANT_TOKEN`.
|
|
30
|
+
|
|
31
|
+
## Steps (agent-self)
|
|
32
|
+
|
|
33
|
+
1. **Fetch the current config and starter template** so you know the
|
|
34
|
+
shape:
|
|
35
|
+
```bash
|
|
36
|
+
curl -s -H "Authorization: Bearer $MANIGENT_AGENT_TOKEN" \
|
|
37
|
+
"$MANIGENT_SERVER_URL/agents/$MANIGENT_AGENT_ID/skills/<skill_name>/config" \
|
|
38
|
+
| jq .
|
|
39
|
+
```
|
|
40
|
+
The response carries `config` (current), `starter_config` (template
|
|
41
|
+
with `${VAR}` placeholders for secrets), and `starter_config_text`
|
|
42
|
+
(the JSONC source with comments).
|
|
43
|
+
|
|
44
|
+
2. **Build the full new config**. The PUT replaces the entry whole —
|
|
45
|
+
carry over the existing keys and apply your delta on top. Do not
|
|
46
|
+
invent values for missing secrets; ask the user.
|
|
47
|
+
|
|
48
|
+
3. **Write the config JSON** to a scratch file in cwd:
|
|
49
|
+
```bash
|
|
50
|
+
tmpfile=$(mktemp).json
|
|
51
|
+
cat > "$tmpfile" <<'JSON'
|
|
52
|
+
{ "...": "..." }
|
|
53
|
+
JSON
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
4. **Apply via the CLI**:
|
|
57
|
+
```bash
|
|
58
|
+
manigent skill set-config self <skill_name> "$tmpfile"
|
|
59
|
+
```
|
|
60
|
+
The CLI picks up your agent token from the env, PUTs to the server,
|
|
61
|
+
which persists the entry to `card.local_settings.skill_configs` and
|
|
62
|
+
broadcasts `AGENT_SETTINGS_CHANGE` — the runner's
|
|
63
|
+
`_apply_local_settings` writes through to
|
|
64
|
+
`{agent_dir}/skill-hub/configs/<skill_name>.json` immediately.
|
|
65
|
+
|
|
66
|
+
5. **Clean up the scratch file** and confirm.
|
|
67
|
+
|
|
68
|
+
## Steps (assistant-on-another-agent)
|
|
69
|
+
|
|
70
|
+
Same flow, but resolve `<agent>` to a name/id and use
|
|
71
|
+
`$MANIGENT_ASSISTANT_TOKEN` instead of `$MANIGENT_AGENT_TOKEN`:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
curl -s -H "Authorization: Bearer $MANIGENT_ASSISTANT_TOKEN" \
|
|
75
|
+
"$MANIGENT_SERVER_URL/agents/<agent_id>/skills/<skill_name>/config" \
|
|
76
|
+
| jq .
|
|
77
|
+
# ... build $tmpfile ...
|
|
78
|
+
manigent skill set-config <agent> <skill_name> "$tmpfile"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Error handling
|
|
82
|
+
|
|
83
|
+
- "skill not installed on agent" → run `/manigent:install-skill` first.
|
|
84
|
+
- "invalid JSON" → show the user what you wrote and fix.
|
|
85
|
+
- "'self' requires MANIGENT_AGENT_ID..." → the env vars aren't set;
|
|
86
|
+
you're probably not in an agent subprocess. Pass an explicit agent
|
|
87
|
+
name instead.
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""
|
|
3
|
+
manigent/utils/agent_processes.py
|
|
4
|
+
|
|
5
|
+
Agent discovery and process liveness on ONE machine — the single copy of the
|
|
6
|
+
logic that answers "which agents are set up here, and which of them are
|
|
7
|
+
actually running right now".
|
|
8
|
+
|
|
9
|
+
Extracted from ``cli_lifecycle.py`` because it now has two consumers that must
|
|
10
|
+
never disagree:
|
|
11
|
+
|
|
12
|
+
- ``manigent/cli_lifecycle.py`` — the ``manigent start / stop / status``
|
|
13
|
+
commands a human runs in a terminal.
|
|
14
|
+
- ``manigent_daemon/`` — the connection daemon shipped as the separate
|
|
15
|
+
``manigent-daemon`` distribution, which reports the same facts to the
|
|
16
|
+
server so the web UI can show them.
|
|
17
|
+
|
|
18
|
+
The daemon is a DIFFERENT distribution and cannot import ``manigent`` (that
|
|
19
|
+
is the whole point — it must install in seconds and start even when the
|
|
20
|
+
runner's heavy dependency stack is broken). ``scripts/build-daemon-package.sh``
|
|
21
|
+
therefore copies this file verbatim into the daemon wheel as
|
|
22
|
+
``manigent_daemon/agent_processes.py``, and ``manigent_daemon/discovery.py``
|
|
23
|
+
imports whichever copy exists. Two consequences, both load-bearing:
|
|
24
|
+
|
|
25
|
+
1. **Stdlib only.** No ``manigent.*`` import, no third-party import, not even
|
|
26
|
+
``typer``. Anything added here that is not in the standard library breaks
|
|
27
|
+
the daemon's "tiny dependencies" guarantee. Reporting is the caller's job:
|
|
28
|
+
the functions here return values and never print.
|
|
29
|
+
2. **Public names only.** ``discovery.py`` re-exports with ``import *``, which
|
|
30
|
+
skips underscore-prefixed names. A helper that needs to be visible to the
|
|
31
|
+
daemon must not start with ``_``.
|
|
32
|
+
|
|
33
|
+
``cli_lifecycle`` keeps its historical ``_``-prefixed aliases (``_pid_is_alive``
|
|
34
|
+
and friends) so existing imports and tests continue to resolve.
|
|
35
|
+
"""
|
|
36
|
+
from __future__ import annotations
|
|
37
|
+
|
|
38
|
+
import os
|
|
39
|
+
import signal
|
|
40
|
+
import subprocess
|
|
41
|
+
import sys
|
|
42
|
+
import time
|
|
43
|
+
from pathlib import Path
|
|
44
|
+
|
|
45
|
+
IS_WINDOWS = sys.platform == "win32"
|
|
46
|
+
|
|
47
|
+
# How long a graceful stop is given before escalating to a force kill. Kept
|
|
48
|
+
# here rather than at the call sites so a terminal `manigent stop` and a
|
|
49
|
+
# remote stop driven from the web behave identically.
|
|
50
|
+
STOP_GRACE_SECONDS = 5.0
|
|
51
|
+
_STOP_POLL_SECONDS = 0.25
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def popen_detached_kwargs() -> dict:
|
|
55
|
+
"""Popen kwargs that detach the child so it outlives the parent shell.
|
|
56
|
+
|
|
57
|
+
Windows needs DETACHED_PROCESS (no inherited console) plus
|
|
58
|
+
CREATE_NEW_PROCESS_GROUP (so we can later deliver CTRL_BREAK_EVENT).
|
|
59
|
+
POSIX just needs start_new_session=True.
|
|
60
|
+
"""
|
|
61
|
+
if IS_WINDOWS:
|
|
62
|
+
flags = (
|
|
63
|
+
getattr(subprocess, "DETACHED_PROCESS", 0)
|
|
64
|
+
| getattr(subprocess, "CREATE_NEW_PROCESS_GROUP", 0)
|
|
65
|
+
)
|
|
66
|
+
return {"creationflags": flags}
|
|
67
|
+
return {"start_new_session": True}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def pid_is_alive(pid: int) -> bool:
|
|
71
|
+
"""Check whether a PID refers to a live process, without signaling it.
|
|
72
|
+
|
|
73
|
+
On Windows, ``os.kill(pid, 0)`` actually terminates the target — so we
|
|
74
|
+
must use a non-signaling query (tasklist) instead.
|
|
75
|
+
"""
|
|
76
|
+
if IS_WINDOWS:
|
|
77
|
+
result = subprocess.run(
|
|
78
|
+
["tasklist", "/FI", f"PID eq {pid}", "/NH", "/FO", "CSV"],
|
|
79
|
+
stdout=subprocess.PIPE, stderr=subprocess.DEVNULL,
|
|
80
|
+
text=True, check=False,
|
|
81
|
+
)
|
|
82
|
+
return f'"{pid}"' in (result.stdout or "")
|
|
83
|
+
try:
|
|
84
|
+
os.kill(pid, 0)
|
|
85
|
+
return True
|
|
86
|
+
except (OSError, ProcessLookupError):
|
|
87
|
+
return False
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def signal_terminate(pid: int) -> None:
|
|
91
|
+
"""Send a graceful termination request. Silently no-ops if the target is gone.
|
|
92
|
+
|
|
93
|
+
POSIX: SIGTERM. Windows: CTRL_BREAK_EVENT to the process group (works
|
|
94
|
+
because agents are spawned with CREATE_NEW_PROCESS_GROUP).
|
|
95
|
+
"""
|
|
96
|
+
try:
|
|
97
|
+
if IS_WINDOWS:
|
|
98
|
+
os.kill(pid, getattr(signal, "CTRL_BREAK_EVENT", 15))
|
|
99
|
+
else:
|
|
100
|
+
os.kill(pid, signal.SIGTERM)
|
|
101
|
+
except (OSError, ProcessLookupError):
|
|
102
|
+
pass
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def signal_kill(pid: int) -> None:
|
|
106
|
+
"""Force-kill a process. Silently no-ops on failure.
|
|
107
|
+
|
|
108
|
+
POSIX: SIGKILL. Windows: ``taskkill /F`` — reliable even when graceful
|
|
109
|
+
signaling didn't land.
|
|
110
|
+
"""
|
|
111
|
+
try:
|
|
112
|
+
if IS_WINDOWS:
|
|
113
|
+
subprocess.run(
|
|
114
|
+
["taskkill", "/F", "/PID", str(pid)],
|
|
115
|
+
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
|
|
116
|
+
check=False,
|
|
117
|
+
)
|
|
118
|
+
else:
|
|
119
|
+
os.kill(pid, signal.SIGKILL)
|
|
120
|
+
except OSError:
|
|
121
|
+
pass
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def read_pid(pid_file: Path) -> int | None:
|
|
125
|
+
"""The live PID recorded in ``pid_file``, or None.
|
|
126
|
+
|
|
127
|
+
None covers all three "not running" shapes with one answer: no pidfile, an
|
|
128
|
+
unreadable/garbage pidfile, and a pidfile naming a process that has since
|
|
129
|
+
died (the stale-pidfile case). Callers that need to tell the last one apart
|
|
130
|
+
check ``pid_file.exists()`` themselves.
|
|
131
|
+
"""
|
|
132
|
+
if not pid_file.exists():
|
|
133
|
+
return None
|
|
134
|
+
try:
|
|
135
|
+
pid = int(pid_file.read_text().strip())
|
|
136
|
+
return pid if pid_is_alive(pid) else None
|
|
137
|
+
except (ValueError, OSError):
|
|
138
|
+
return None
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def stop_pid(pid_file: Path) -> int | None:
|
|
142
|
+
"""Stop the process named by ``pid_file``. Returns the PID it stopped, else None.
|
|
143
|
+
|
|
144
|
+
Graceful first (SIGTERM / CTRL_BREAK_EVENT), then a force kill after a
|
|
145
|
+
:data:`STOP_GRACE_SECONDS` grace period (SIGKILL / taskkill /F). The
|
|
146
|
+
pidfile is removed either way, including when it was already stale — that
|
|
147
|
+
cleanup is why a caller should prefer this over signalling by hand.
|
|
148
|
+
|
|
149
|
+
Returns None when there was nothing to stop, so a caller can distinguish
|
|
150
|
+
"stopped it" from "it wasn't running" without a second probe. Never prints:
|
|
151
|
+
the terminal CLI and the remote daemon word the outcome differently.
|
|
152
|
+
"""
|
|
153
|
+
if not pid_file.exists():
|
|
154
|
+
return None
|
|
155
|
+
try:
|
|
156
|
+
pid = int(pid_file.read_text().strip())
|
|
157
|
+
except (ValueError, OSError):
|
|
158
|
+
pid_file.unlink(missing_ok=True)
|
|
159
|
+
return None
|
|
160
|
+
|
|
161
|
+
if not pid_is_alive(pid):
|
|
162
|
+
pid_file.unlink(missing_ok=True)
|
|
163
|
+
return None
|
|
164
|
+
|
|
165
|
+
signal_terminate(pid)
|
|
166
|
+
for _ in range(int(STOP_GRACE_SECONDS / _STOP_POLL_SECONDS)):
|
|
167
|
+
time.sleep(_STOP_POLL_SECONDS)
|
|
168
|
+
if not pid_is_alive(pid):
|
|
169
|
+
break
|
|
170
|
+
else:
|
|
171
|
+
signal_kill(pid)
|
|
172
|
+
|
|
173
|
+
pid_file.unlink(missing_ok=True)
|
|
174
|
+
return pid
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def agents_dir(data_dir: Path) -> Path:
|
|
178
|
+
"""``{data_dir}/agents`` — where every locally registered agent lives."""
|
|
179
|
+
return Path(data_dir).expanduser() / "agents"
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def prefixed_name(username: str, agent_name: str) -> str:
|
|
183
|
+
"""``budget-analyst`` -> ``alice-budget-analyst`` (idempotent)."""
|
|
184
|
+
prefix = f"{username}-"
|
|
185
|
+
return agent_name if agent_name.startswith(prefix) else f"{prefix}{agent_name}"
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def find_agent_dir(agents_root: Path, prefixed: str) -> Path | None:
|
|
189
|
+
"""Find an agent's directory matching ``{prefixed}-{id}/``.
|
|
190
|
+
|
|
191
|
+
Requires ``credential.json`` so a half-registered directory is invisible,
|
|
192
|
+
the same rule :func:`list_owned_agent_short_names` applies.
|
|
193
|
+
"""
|
|
194
|
+
if not agents_root.exists():
|
|
195
|
+
return None
|
|
196
|
+
for d in agents_root.iterdir():
|
|
197
|
+
if d.is_dir() and d.name.startswith(f"{prefixed}-"):
|
|
198
|
+
if (d / "credential.json").exists():
|
|
199
|
+
return d
|
|
200
|
+
return None
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def list_owned_agent_short_names(agents_root: Path, username: str) -> list[str]:
|
|
204
|
+
"""Return owned agents' short names by globbing the filesystem.
|
|
205
|
+
|
|
206
|
+
Pattern: ``{agents_root}/{username}-{short}-{id}/`` with ``credential.json``
|
|
207
|
+
present. Skips ``DELETED-*`` (renamed by self-destruct) and any dir
|
|
208
|
+
without a ``credential.json`` (half-registered). The trailing ``-{id}``
|
|
209
|
+
is stripped off the right.
|
|
210
|
+
|
|
211
|
+
Deliberately a filesystem glob rather than a read of ``settings.json``:
|
|
212
|
+
the directory is what a process can actually be started from, so an agent
|
|
213
|
+
that exists on disk is reported even if some config file forgot it.
|
|
214
|
+
"""
|
|
215
|
+
if not agents_root.exists():
|
|
216
|
+
return []
|
|
217
|
+
prefix = f"{username}-"
|
|
218
|
+
names: list[str] = []
|
|
219
|
+
for entry in sorted(agents_root.iterdir()):
|
|
220
|
+
if not entry.is_dir() or entry.name.startswith("DELETED-"):
|
|
221
|
+
continue
|
|
222
|
+
if not entry.name.startswith(prefix):
|
|
223
|
+
continue
|
|
224
|
+
if not (entry / "credential.json").exists():
|
|
225
|
+
continue
|
|
226
|
+
rest = entry.name[len(prefix):]
|
|
227
|
+
short = rest.rsplit("-", 1)[0] if "-" in rest else rest
|
|
228
|
+
if short:
|
|
229
|
+
names.append(short)
|
|
230
|
+
return names
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def agent_pid_file(agent_dir: Path) -> Path:
|
|
234
|
+
"""The pidfile ``manigent start`` writes for one agent."""
|
|
235
|
+
return Path(agent_dir) / "agent.pid"
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def scan_agents(agents_root: Path, username: str) -> list[dict]:
|
|
239
|
+
"""One dict per locally registered agent, with its OBSERVED run state.
|
|
240
|
+
|
|
241
|
+
``[{"short_name", "name", "dir", "pid", "state"}, …]`` sorted by
|
|
242
|
+
``short_name``, where ``state`` is one of:
|
|
243
|
+
|
|
244
|
+
- ``"running"`` — the pidfile names a live process.
|
|
245
|
+
- ``"crashed"`` — a pidfile exists but the process is gone. The machine
|
|
246
|
+
started this agent and it exited without being asked to; the web UI
|
|
247
|
+
shows it as "Stopped on its own" so the user can tell it apart from a
|
|
248
|
+
stop they performed.
|
|
249
|
+
- ``"stopped"`` — no pidfile. Never started, or stopped cleanly.
|
|
250
|
+
|
|
251
|
+
Observed, never remembered: the state is read off the filesystem on every
|
|
252
|
+
call, so an agent that died without saying goodbye reads as ``crashed``
|
|
253
|
+
within one scan rather than lingering as "running" until something notices.
|
|
254
|
+
"""
|
|
255
|
+
rows: list[dict] = []
|
|
256
|
+
for short in list_owned_agent_short_names(agents_root, username):
|
|
257
|
+
full = prefixed_name(username, short)
|
|
258
|
+
agent_dir = find_agent_dir(agents_root, full)
|
|
259
|
+
if agent_dir is None:
|
|
260
|
+
continue
|
|
261
|
+
pid_file = agent_pid_file(agent_dir)
|
|
262
|
+
pid = read_pid(pid_file)
|
|
263
|
+
if pid is not None:
|
|
264
|
+
state = "running"
|
|
265
|
+
elif pid_file.exists():
|
|
266
|
+
state = "crashed"
|
|
267
|
+
else:
|
|
268
|
+
state = "stopped"
|
|
269
|
+
rows.append({
|
|
270
|
+
"short_name": short,
|
|
271
|
+
"name": full,
|
|
272
|
+
"dir": str(agent_dir),
|
|
273
|
+
"pid": pid,
|
|
274
|
+
"state": state,
|
|
275
|
+
})
|
|
276
|
+
return rows
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""
|
|
3
|
+
manigent/utils/agent_storage.py
|
|
4
|
+
The agent's two persistent storage folders — one place that decides where
|
|
5
|
+
they live, creates them, and names the env vars that carry them.
|
|
6
|
+
|
|
7
|
+
- **Local storage** (``local_settings.local_storage_dir``, default
|
|
8
|
+
``{AGENT_DIR}/storage``): this agent's own files that outlive any one
|
|
9
|
+
project — caches, downloads, datasets, working state. Unlike the per-project
|
|
10
|
+
sandbox, nothing here is scoped to a chat thread.
|
|
11
|
+
- **Shared storage** (``local_settings.shared_storage_dir``, default
|
|
12
|
+
``{MANIGENT_DIR}/shared_storage``): one folder every agent on this computer
|
|
13
|
+
can read and write, for handing files between agents without going through
|
|
14
|
+
a chatroom. ``{AGENT_DIR}/shared_storage`` is a symlink to it so the folder
|
|
15
|
+
shows up in the agent's home.
|
|
16
|
+
|
|
17
|
+
Both are exposed to every LLM invocation as ``$AGENT_LOCAL_STORAGE_DIR`` /
|
|
18
|
+
``$AGENT_SHARED_STORAGE_DIR`` and listed in the prompt's FILES & STATE block.
|
|
19
|
+
Paths in local_settings resolve exactly like ``knowledge_dir``
|
|
20
|
+
(``FileUtil.resolve_local_dir``): absolute and ``~`` verbatim, relative
|
|
21
|
+
against the agent's own home (AGENT_DIR).
|
|
22
|
+
|
|
23
|
+
Called at agent registration (so a new agent's home already has them), at
|
|
24
|
+
every runner start (so agents registered before this existed — or created
|
|
25
|
+
from the web app — get them too), and on a settings change.
|
|
26
|
+
"""
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
import logging
|
|
30
|
+
import os
|
|
31
|
+
from dataclasses import dataclass
|
|
32
|
+
from pathlib import Path
|
|
33
|
+
|
|
34
|
+
from .file_io import FileUtil
|
|
35
|
+
|
|
36
|
+
logger = logging.getLogger(__name__)
|
|
37
|
+
|
|
38
|
+
LOCAL_STORAGE_ENV = "AGENT_LOCAL_STORAGE_DIR"
|
|
39
|
+
SHARED_STORAGE_ENV = "AGENT_SHARED_STORAGE_DIR"
|
|
40
|
+
|
|
41
|
+
LOCAL_STORAGE_KEY = "local_storage_dir"
|
|
42
|
+
SHARED_STORAGE_KEY = "shared_storage_dir"
|
|
43
|
+
|
|
44
|
+
LOCAL_STORAGE_DIRNAME = "storage"
|
|
45
|
+
SHARED_STORAGE_DIRNAME = "shared_storage"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass(frozen=True)
|
|
49
|
+
class AgentStorage:
|
|
50
|
+
"""Resolved absolute storage folders for one agent."""
|
|
51
|
+
|
|
52
|
+
local: Path
|
|
53
|
+
shared: Path
|
|
54
|
+
|
|
55
|
+
def env(self) -> dict[str, str]:
|
|
56
|
+
"""The env vars handed to every LLM subprocess."""
|
|
57
|
+
return {
|
|
58
|
+
LOCAL_STORAGE_ENV: str(self.local),
|
|
59
|
+
SHARED_STORAGE_ENV: str(self.shared),
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def manigent_dir_for(agent_dir: Path) -> Path:
|
|
64
|
+
"""The Manigent data dir (``~/.manigent`` by default) an agent lives in.
|
|
65
|
+
|
|
66
|
+
Agents live at ``{MANIGENT_DIR}/agents/<name>-<id>/``, so the layout
|
|
67
|
+
answers it directly; the env/default fallback only covers an agent dir
|
|
68
|
+
placed somewhere else by hand (``--save``).
|
|
69
|
+
"""
|
|
70
|
+
if agent_dir.parent.name == "agents":
|
|
71
|
+
return agent_dir.parent.parent
|
|
72
|
+
return Path(os.environ.get("MANIGENT_DATA_DIR", str(Path.home() / ".manigent")))
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def default_shared_storage_dir(manigent_dir: Path) -> Path:
|
|
76
|
+
return manigent_dir / SHARED_STORAGE_DIRNAME
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def resolve_storage(agent_dir: Path, local_settings: dict) -> AgentStorage:
|
|
80
|
+
"""Settings → absolute folders. An empty setting means the default."""
|
|
81
|
+
local = FileUtil.resolve_local_dir(
|
|
82
|
+
local_settings.get(LOCAL_STORAGE_KEY) or "", agent_dir
|
|
83
|
+
)
|
|
84
|
+
shared = FileUtil.resolve_local_dir(
|
|
85
|
+
local_settings.get(SHARED_STORAGE_KEY) or "", agent_dir
|
|
86
|
+
)
|
|
87
|
+
return AgentStorage(
|
|
88
|
+
local=(local or agent_dir / LOCAL_STORAGE_DIRNAME).absolute(),
|
|
89
|
+
shared=(shared or default_shared_storage_dir(manigent_dir_for(agent_dir))).absolute(),
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def ensure_storage(agent_dir: Path, storage: AgentStorage) -> None:
|
|
94
|
+
"""Create both folders and point ``{agent_dir}/shared_storage`` at the
|
|
95
|
+
shared one. Idempotent; never raises — a storage folder that can't be
|
|
96
|
+
created must not stop the runner, the agent just finds it missing.
|
|
97
|
+
|
|
98
|
+
The link is only ever created or re-pointed when it is a symlink (or
|
|
99
|
+
absent). A real folder already sitting at that name is left alone.
|
|
100
|
+
"""
|
|
101
|
+
for d in (storage.local, storage.shared):
|
|
102
|
+
try:
|
|
103
|
+
d.mkdir(parents=True, exist_ok=True)
|
|
104
|
+
except OSError as e:
|
|
105
|
+
logger.warning(f"Could not create storage folder {d}: {e}")
|
|
106
|
+
|
|
107
|
+
link = agent_dir / SHARED_STORAGE_DIRNAME
|
|
108
|
+
if link.absolute() == storage.shared:
|
|
109
|
+
return # the shared folder IS the link location; nothing to link
|
|
110
|
+
try:
|
|
111
|
+
if link.is_symlink():
|
|
112
|
+
if Path(os.readlink(link)) == storage.shared:
|
|
113
|
+
return
|
|
114
|
+
link.unlink()
|
|
115
|
+
elif link.exists():
|
|
116
|
+
logger.warning(
|
|
117
|
+
f"{link} exists and is not a symlink; leaving it alone "
|
|
118
|
+
f"(shared storage is {storage.shared})"
|
|
119
|
+
)
|
|
120
|
+
return
|
|
121
|
+
link.symlink_to(storage.shared, target_is_directory=True)
|
|
122
|
+
except OSError as e:
|
|
123
|
+
# Windows without symlink privilege lands here. The env var and the
|
|
124
|
+
# prompt still carry the real path, so the agent can reach it.
|
|
125
|
+
logger.warning(f"Could not link {link} -> {storage.shared}: {e}")
|