android-midscene-automation 0.2.3 → 1.1.0
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.
- package/CHANGELOG.md +11 -1
- package/README.md +63 -1
- package/artemis-main/.dockerignore +15 -0
- package/artemis-main/.env.example +34 -0
- package/artemis-main/CONTRIBUTING.md +35 -0
- package/artemis-main/Dockerfile +68 -0
- package/artemis-main/LICENSE +202 -0
- package/artemis-main/Makefile +115 -0
- package/artemis-main/README.md +322 -0
- package/artemis-main/README_CN.md +320 -0
- package/artemis-main/apps/__init__.py +15 -0
- package/artemis-main/apps/admin_console/__init__.py +19 -0
- package/artemis-main/apps/admin_console/core/__init__.py +13 -0
- package/artemis-main/apps/admin_console/core/config.py +43 -0
- package/artemis-main/apps/admin_console/core/security.py +178 -0
- package/artemis-main/apps/admin_console/core/state.py +243 -0
- package/artemis-main/apps/admin_console/database/__init__.py +13 -0
- package/artemis-main/apps/admin_console/database/connection.py +60 -0
- package/artemis-main/apps/admin_console/database/repositories/__init__.py +13 -0
- package/artemis-main/apps/admin_console/database/repositories/session_repository.py +564 -0
- package/artemis-main/apps/admin_console/database/repositories/step_repository.py +512 -0
- package/artemis-main/apps/admin_console/database/repositories/trace_repository.py +133 -0
- package/artemis-main/apps/admin_console/index.html +3787 -0
- package/artemis-main/apps/admin_console/replay_manager.py +2585 -0
- package/artemis-main/apps/admin_console/routers/__init__.py +13 -0
- package/artemis-main/apps/admin_console/routers/media.py +198 -0
- package/artemis-main/apps/admin_console/routers/replay.py +91 -0
- package/artemis-main/apps/admin_console/routers/sessions.py +239 -0
- package/artemis-main/apps/admin_console/routers/steps.py +109 -0
- package/artemis-main/apps/admin_console/routers/stream.py +56 -0
- package/artemis-main/apps/admin_console/routers/system.py +761 -0
- package/artemis-main/apps/admin_console/routers/tasks.py +517 -0
- package/artemis-main/apps/admin_console/schemas/__init__.py +13 -0
- package/artemis-main/apps/admin_console/schemas/task_schema.py +47 -0
- package/artemis-main/apps/admin_console/server.py +508 -0
- package/artemis-main/apps/admin_console/services/__init__.py +13 -0
- package/artemis-main/apps/admin_console/services/device_stream_service.py +137 -0
- package/artemis-main/apps/admin_console/services/ipc_service.py +262 -0
- package/artemis-main/apps/admin_console/services/media_service.py +623 -0
- package/artemis-main/apps/admin_console/services/model_service.py +148 -0
- package/artemis-main/apps/admin_console/services/task_preset_catalog.py +514 -0
- package/artemis-main/apps/admin_console/services/task_queue_service.py +1719 -0
- package/artemis-main/apps/admin_console/services/worker_process_io.py +144 -0
- package/artemis-main/apps/showcase_ui/README.md +31 -0
- package/artemis-main/apps/showcase_ui/angular.json +132 -0
- package/artemis-main/apps/showcase_ui/package-lock.json +9472 -0
- package/artemis-main/apps/showcase_ui/package.json +39 -0
- package/artemis-main/apps/showcase_ui/proxy.conf.json +23 -0
- package/artemis-main/apps/showcase_ui/public/favicon.ico +0 -0
- package/artemis-main/apps/showcase_ui/public/logo.png +0 -0
- package/artemis-main/apps/showcase_ui/public/logo.svg +56 -0
- package/artemis-main/apps/showcase_ui/src/app/app.component.html +24 -0
- package/artemis-main/apps/showcase_ui/src/app/app.component.scss +49 -0
- package/artemis-main/apps/showcase_ui/src/app/app.component.spec.ts +42 -0
- package/artemis-main/apps/showcase_ui/src/app/app.component.ts +32 -0
- package/artemis-main/apps/showcase_ui/src/app/app.config.ts +29 -0
- package/artemis-main/apps/showcase_ui/src/app/app.routes.ts +27 -0
- package/artemis-main/apps/showcase_ui/src/app/components/agent-stream/agent-stream-startup.spec.ts +108 -0
- package/artemis-main/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.html +1436 -0
- package/artemis-main/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.scss +3973 -0
- package/artemis-main/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.ts +2235 -0
- package/artemis-main/apps/showcase_ui/src/app/components/chat-interface/chat-interface.component.html +380 -0
- package/artemis-main/apps/showcase_ui/src/app/components/chat-interface/chat-interface.component.scss +1171 -0
- package/artemis-main/apps/showcase_ui/src/app/components/chat-interface/chat-interface.component.ts +270 -0
- package/artemis-main/apps/showcase_ui/src/app/components/floating-video-player/floating-video-player.component.html +433 -0
- package/artemis-main/apps/showcase_ui/src/app/components/floating-video-player/floating-video-player.component.scss +987 -0
- package/artemis-main/apps/showcase_ui/src/app/components/floating-video-player/floating-video-player.component.ts +632 -0
- package/artemis-main/apps/showcase_ui/src/app/components/legacy-agent-stream/legacy-agent-stream.component.html +130 -0
- package/artemis-main/apps/showcase_ui/src/app/components/legacy-agent-stream/legacy-agent-stream.component.scss +406 -0
- package/artemis-main/apps/showcase_ui/src/app/components/legacy-agent-stream/legacy-agent-stream.component.ts +74 -0
- package/artemis-main/apps/showcase_ui/src/app/components/nav-switcher/nav-switcher.component.scss +115 -0
- package/artemis-main/apps/showcase_ui/src/app/components/nav-switcher/nav-switcher.component.ts +52 -0
- package/artemis-main/apps/showcase_ui/src/app/core/data/smart-tasks.data.ts +334 -0
- package/artemis-main/apps/showcase_ui/src/app/core/i18n/language.service.spec.ts +55 -0
- package/artemis-main/apps/showcase_ui/src/app/core/i18n/language.service.ts +61 -0
- package/artemis-main/apps/showcase_ui/src/app/core/i18n/translate.pipe.ts +11 -0
- package/artemis-main/apps/showcase_ui/src/app/core/i18n/zh-cn.ts +651 -0
- package/artemis-main/apps/showcase_ui/src/app/core/models/markdown.model.ts +43 -0
- package/artemis-main/apps/showcase_ui/src/app/core/models/pro-tuning.model.spec.ts +47 -0
- package/artemis-main/apps/showcase_ui/src/app/core/models/pro-tuning.model.ts +165 -0
- package/artemis-main/apps/showcase_ui/src/app/core/models/session.model.ts +85 -0
- package/artemis-main/apps/showcase_ui/src/app/core/models/stream.model.ts +246 -0
- package/artemis-main/apps/showcase_ui/src/app/core/models/system.model.ts +118 -0
- package/artemis-main/apps/showcase_ui/src/app/core/services/task-recommendation.service.ts +111 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/home/home.component.html +2130 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/home/home.component.scss +5204 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/home/home.component.ts +1189 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/legacy-workspace/legacy-workspace.component.html +39 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/legacy-workspace/legacy-workspace.component.scss +151 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/legacy-workspace/legacy-workspace.component.ts +58 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/workspace/workspace.component.html +201 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/workspace/workspace.component.scss +886 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/workspace/workspace.component.ts +324 -0
- package/artemis-main/apps/showcase_ui/src/app/services/agent.service.spec.ts +418 -0
- package/artemis-main/apps/showcase_ui/src/app/services/agent.service.ts +2012 -0
- package/artemis-main/apps/showcase_ui/src/app/services/system.service.spec.ts +173 -0
- package/artemis-main/apps/showcase_ui/src/app/services/system.service.ts +620 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/action-formatter.util.spec.ts +513 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/action-formatter.util.ts +1015 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/image-overlay.util.spec.ts +85 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/image-overlay.util.ts +662 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/markdown-parser.util.spec.ts +70 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/markdown-parser.util.ts +383 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/recording-timeline.util.spec.ts +107 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/recording-timeline.util.ts +123 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/run-info.util.spec.ts +92 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/run-info.util.ts +116 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/stream-aggregator.util.spec.ts +640 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/stream-aggregator.util.ts +1021 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/tool-formatter.util.spec.ts +375 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/tool-formatter.util.ts +1419 -0
- package/artemis-main/apps/showcase_ui/src/index.html +37 -0
- package/artemis-main/apps/showcase_ui/src/main.ts +22 -0
- package/artemis-main/apps/showcase_ui/src/styles.scss +60 -0
- package/artemis-main/apps/showcase_ui/tsconfig.app.json +23 -0
- package/artemis-main/apps/showcase_ui/tsconfig.json +27 -0
- package/artemis-main/apps/showcase_ui/tsconfig.spec.json +23 -0
- package/artemis-main/artemis/__init__.py +44 -0
- package/artemis-main/artemis/__main__.py +18 -0
- package/artemis-main/artemis/_legacy_imports.py +94 -0
- package/artemis-main/artemis/_version.py +30 -0
- package/artemis-main/artemis/agents/__init__.py +17 -0
- package/artemis-main/artemis/agents/checker/checker.json +9 -0
- package/artemis-main/artemis/agents/checker/checker.py +859 -0
- package/artemis-main/artemis/agents/diagnoser/diagnoser.json +23 -0
- package/artemis-main/artemis/agents/diagnoser/diagnoser.py +608 -0
- package/artemis-main/artemis/agents/explorer/constants.py +52 -0
- package/artemis-main/artemis/agents/explorer/explorer.json +57 -0
- package/artemis-main/artemis/agents/explorer/explorer.py +473 -0
- package/artemis-main/artemis/agents/explorer/geometry.py +73 -0
- package/artemis-main/artemis/agents/explorer/native_runner.py +878 -0
- package/artemis-main/artemis/agents/explorer/perception_tools.py +632 -0
- package/artemis-main/artemis/agents/explorer/run_setup.py +403 -0
- package/artemis-main/artemis/agents/explorer/screen_index.py +266 -0
- package/artemis-main/artemis/agents/explorer/tiers.py +110 -0
- package/artemis-main/artemis/agents/explorer/tool_declarations.py +394 -0
- package/artemis-main/artemis/agents/explorer/universal_runner.py +317 -0
- package/artemis-main/artemis/agents/flash/context_compressor.py +452 -0
- package/artemis-main/artemis/agents/flash/flash_runner.md +51 -0
- package/artemis-main/artemis/agents/flash/flash_summarizer.md +53 -0
- package/artemis-main/artemis/agents/flash/flash_summarizer_single.md +54 -0
- package/artemis-main/artemis/agents/flash/runner.py +1167 -0
- package/artemis-main/artemis/agents/flash/summarizer.py +511 -0
- package/artemis-main/artemis/agents/history_analyzer/history_analyzer.md +8 -0
- package/artemis-main/artemis/agents/history_analyzer/history_analyzer.py +182 -0
- package/artemis-main/artemis/agents/image_processor/image_processor.md +74 -0
- package/artemis-main/artemis/agents/image_processor/image_processor.py +322 -0
- package/artemis-main/artemis/agents/log_analyzer/log_analyzer.md +15 -0
- package/artemis-main/artemis/agents/log_analyzer/log_analyzer.py +403 -0
- package/artemis-main/artemis/agents/log_analyzer/log_reader_sub_agent.md +8 -0
- package/artemis-main/artemis/agents/log_analyzer/output_analyzer.md +11 -0
- package/artemis-main/artemis/agents/log_analyzer/output_analyzer.py +163 -0
- package/artemis-main/artemis/agents/object_detector/object_detector.json +9 -0
- package/artemis-main/artemis/agents/object_detector/object_detector.py +211 -0
- package/artemis-main/artemis/agents/operator/operator.json +3 -0
- package/artemis-main/artemis/agents/operator/operator.py +1394 -0
- package/artemis-main/artemis/agents/operator/prompts.py +1064 -0
- package/artemis-main/artemis/agents/outputter/outputter.md +36 -0
- package/artemis-main/artemis/agents/outputter/outputter.py +552 -0
- package/artemis-main/artemis/agents/planner/planner.json +32 -0
- package/artemis-main/artemis/agents/planner/planner.py +436 -0
- package/artemis-main/artemis/agents/prompt_assembly.py +79 -0
- package/artemis-main/artemis/agents/summarizer/summarizer.py +190 -0
- package/artemis-main/artemis/agents/validator/action_execution.py +98 -0
- package/artemis-main/artemis/agents/validator/categories.py +33 -0
- package/artemis-main/artemis/agents/validator/execution_loop.py +488 -0
- package/artemis-main/artemis/agents/validator/incidents.py +120 -0
- package/artemis-main/artemis/agents/validator/pixel_safety_net.md +15 -0
- package/artemis-main/artemis/agents/validator/precondition_pixel.py +320 -0
- package/artemis-main/artemis/agents/validator/precondition_xml.py +672 -0
- package/artemis-main/artemis/agents/validator/tool_declarations.py +332 -0
- package/artemis-main/artemis/agents/validator/validator.py +188 -0
- package/artemis-main/artemis/agents/video_analyzer/__init__.py +15 -0
- package/artemis-main/artemis/agents/video_analyzer/audio_native.py +543 -0
- package/artemis-main/artemis/agents/video_analyzer/blackboard.py +821 -0
- package/artemis-main/artemis/agents/video_analyzer/chunk_agentic.py +455 -0
- package/artemis-main/artemis/agents/video_analyzer/chunk_conversation.py +491 -0
- package/artemis-main/artemis/agents/video_analyzer/chunk_native.py +437 -0
- package/artemis-main/artemis/agents/video_analyzer/conflict_resolution.py +137 -0
- package/artemis-main/artemis/agents/video_analyzer/gemini_files.py +162 -0
- package/artemis-main/artemis/agents/video_analyzer/native_coordinator.py +541 -0
- package/artemis-main/artemis/agents/video_analyzer/reliability.py +128 -0
- package/artemis-main/artemis/agents/video_analyzer/universal_engine.py +487 -0
- package/artemis-main/artemis/agents/video_analyzer/universal_tools.py +323 -0
- package/artemis-main/artemis/agents/video_analyzer/video_analyzer.md +51 -0
- package/artemis-main/artemis/agents/video_analyzer/video_analyzer.py +875 -0
- package/artemis-main/artemis/agents/video_analyzer/video_sub_agent.md +15 -0
- package/artemis-main/artemis/agents/video_analyzer/video_sub_agent_audio.md +12 -0
- package/artemis-main/artemis/clients/accessibility_client.py +438 -0
- package/artemis-main/artemis/clients/screen_client_factory.py +403 -0
- package/artemis-main/artemis/config/__init__.py +309 -0
- package/artemis-main/artemis/config/agent.py +1098 -0
- package/artemis-main/artemis/config/constants.py +151 -0
- package/artemis-main/artemis/config/core/__init__.py +24 -0
- package/artemis-main/artemis/config/core/engine.py +118 -0
- package/artemis-main/artemis/config/core/interpolator.py +78 -0
- package/artemis-main/artemis/config/llm.py +267 -0
- package/artemis-main/artemis/config/paths.py +244 -0
- package/artemis-main/artemis/config/runtime.py +197 -0
- package/artemis-main/artemis/config/settings.py +315 -0
- package/artemis-main/artemis/constants.py +47 -0
- package/artemis-main/artemis/context.py +289 -0
- package/artemis-main/artemis/controllers/__init__.py +41 -0
- package/artemis-main/artemis/controllers/controller_factory.py +41 -0
- package/artemis-main/artemis/controllers/device_controller.py +29 -0
- package/artemis-main/artemis/controllers/types.py +37 -0
- package/artemis-main/artemis/controllers/unified_controller.py +649 -0
- package/artemis-main/artemis/core/__init__.py +21 -0
- package/artemis-main/artemis/core/diagnostics/__init__.py +90 -0
- package/artemis-main/artemis/core/diagnostics/adb_keys.py +270 -0
- package/artemis-main/artemis/core/diagnostics/adb_server_connection.py +330 -0
- package/artemis-main/artemis/core/diagnostics/device_smoke.py +364 -0
- package/artemis-main/artemis/core/diagnostics/emulator_manager.py +501 -0
- package/artemis-main/artemis/core/diagnostics/engine.py +438 -0
- package/artemis-main/artemis/core/diagnostics/hierarchy_parity.py +216 -0
- package/artemis-main/artemis/core/diagnostics/probes/adb_probe.py +887 -0
- package/artemis-main/artemis/core/diagnostics/probes/base.py +45 -0
- package/artemis-main/artemis/core/diagnostics/probes/credentials_probe.py +296 -0
- package/artemis-main/artemis/core/diagnostics/probes/host_probe.py +427 -0
- package/artemis-main/artemis/core/diagnostics/probes/runtime_probe.py +169 -0
- package/artemis-main/artemis/core/diagnostics/probes/toolchain_probe.py +139 -0
- package/artemis-main/artemis/core/diagnostics/readiness.py +83 -0
- package/artemis-main/artemis/core/diagnostics/schema.py +118 -0
- package/artemis-main/artemis/core/tool_declaration.py +43 -0
- package/artemis-main/artemis/core/tool_failure.py +56 -0
- package/artemis-main/artemis/data_engine/context_vars.py +24 -0
- package/artemis-main/artemis/data_engine/engine.py +1615 -0
- package/artemis-main/artemis/data_engine/history_reader.py +142 -0
- package/artemis-main/artemis/data_engine/models.py +131 -0
- package/artemis-main/artemis/data_engine/storage.py +1245 -0
- package/artemis-main/artemis/data_engine/trace.py +727 -0
- package/artemis-main/artemis/drivers/__init__.py +48 -0
- package/artemis-main/artemis/drivers/android/adb_driver.py +507 -0
- package/artemis-main/artemis/drivers/android/hierarchy.py +64 -0
- package/artemis-main/artemis/drivers/android/input_ime.py +120 -0
- package/artemis-main/artemis/drivers/android/recorder.py +57 -0
- package/artemis-main/artemis/drivers/base.py +290 -0
- package/artemis-main/artemis/drivers/cloud/cloud_driver.py +117 -0
- package/artemis-main/artemis/drivers/factory.py +80 -0
- package/artemis-main/artemis/drivers/mock/mock_driver.py +198 -0
- package/artemis-main/artemis/drivers/types.py +67 -0
- package/artemis-main/artemis/graph/checkpoints.py +1243 -0
- package/artemis-main/artemis/graph/graph.py +1055 -0
- package/artemis-main/artemis/graph/perception.py +234 -0
- package/artemis-main/artemis/graph/state.py +151 -0
- package/artemis-main/artemis/graph/visibility.py +238 -0
- package/artemis-main/artemis/interfaces/cli/__init__.py +19 -0
- package/artemis-main/artemis/interfaces/cli/commands/__init__.py +15 -0
- package/artemis-main/artemis/interfaces/cli/commands/batch.py +278 -0
- package/artemis-main/artemis/interfaces/cli/commands/doctor.py +468 -0
- package/artemis-main/artemis/interfaces/cli/commands/helper.py +226 -0
- package/artemis-main/artemis/interfaces/cli/commands/init.py +193 -0
- package/artemis-main/artemis/interfaces/cli/commands/mcp.py +862 -0
- package/artemis-main/artemis/interfaces/cli/commands/run.py +463 -0
- package/artemis-main/artemis/interfaces/cli/commands/server.py +151 -0
- package/artemis-main/artemis/interfaces/cli/commands/server_lifecycle.py +328 -0
- package/artemis-main/artemis/interfaces/cli/commands/trace.py +111 -0
- package/artemis-main/artemis/interfaces/cli/commands/ui.py +404 -0
- package/artemis-main/artemis/interfaces/cli/main.py +109 -0
- package/artemis-main/artemis/interfaces/sdk/__init__.py +31 -0
- package/artemis-main/artemis/interfaces/sdk/client.py +19 -0
- package/artemis-main/artemis/interfaces/sdk/task.py +72 -0
- package/artemis-main/artemis/llm/__init__.py +25 -0
- package/artemis-main/artemis/llm/fake_model.py +179 -0
- package/artemis-main/artemis/llm/google/__init__.py +57 -0
- package/artemis-main/artemis/llm/google/provider.py +185 -0
- package/artemis-main/artemis/llm/google/usage.py +148 -0
- package/artemis-main/artemis/llm/parser.py +32 -0
- package/artemis-main/artemis/llm/reliability.py +267 -0
- package/artemis-main/artemis/llm/router.py +389 -0
- package/artemis-main/artemis/llm/schemas.py +36 -0
- package/artemis-main/artemis/llm/structured.py +196 -0
- package/artemis-main/artemis/main.py +58 -0
- package/artemis-main/artemis/mcp/__init__.py +20 -0
- package/artemis-main/artemis/mcp/action_executor.py +760 -0
- package/artemis-main/artemis/mcp/action_manifest.py +320 -0
- package/artemis-main/artemis/mcp/action_names.py +172 -0
- package/artemis-main/artemis/mcp/action_server.py +202 -0
- package/artemis-main/artemis/mcp/action_session.py +245 -0
- package/artemis-main/artemis/mcp/action_specs.py +868 -0
- package/artemis-main/artemis/mcp/action_types.py +86 -0
- package/artemis-main/artemis/mcp/actuators/__init__.py +21 -0
- package/artemis-main/artemis/mcp/actuators/adb.py +414 -0
- package/artemis-main/artemis/mcp/actuators/base.py +113 -0
- package/artemis-main/artemis/mcp/actuators/mock.py +70 -0
- package/artemis-main/artemis/mcp/adb_server.py +490 -0
- package/artemis-main/artemis/mcp/observation.py +133 -0
- package/artemis-main/artemis/mcp/xml_search_server.py +297 -0
- package/artemis-main/artemis/memory/__init__.py +90 -0
- package/artemis-main/artemis/memory/chunking.py +1843 -0
- package/artemis-main/artemis/memory/context_policy.py +226 -0
- package/artemis-main/artemis/memory/step_capsule.md +73 -0
- package/artemis-main/artemis/memory/step_memory.py +281 -0
- package/artemis-main/artemis/memory/transcript.py +798 -0
- package/artemis-main/artemis/platform/__init__.py +61 -0
- package/artemis-main/artemis/platform/base.py +104 -0
- package/artemis-main/artemis/platform/darwin.py +184 -0
- package/artemis-main/artemis/platform/linux.py +207 -0
- package/artemis-main/artemis/platform/windows.py +193 -0
- package/artemis-main/artemis/resources/__init__.py +46 -0
- package/artemis-main/artemis/runtime/__init__.py +108 -0
- package/artemis-main/artemis/runtime/adb_endpoint.py +173 -0
- package/artemis-main/artemis/runtime/awake_lease.py +283 -0
- package/artemis-main/artemis/runtime/awake_service.py +343 -0
- package/artemis-main/artemis/runtime/cancel_requests.py +254 -0
- package/artemis-main/artemis/runtime/daemon_client.py +422 -0
- package/artemis-main/artemis/runtime/device_lock.py +1013 -0
- package/artemis-main/artemis/runtime/device_pool.py +478 -0
- package/artemis-main/artemis/runtime/helper_manager.py +1030 -0
- package/artemis-main/artemis/runtime/process_probe.py +98 -0
- package/artemis-main/artemis/runtime/server_lifecycle.py +435 -0
- package/artemis-main/artemis/runtime/supervisor.py +121 -0
- package/artemis-main/artemis/runtime/trace_store.py +375 -0
- package/artemis-main/artemis/sdk/__init__.py +26 -0
- package/artemis-main/artemis/sdk/agent.py +485 -0
- package/artemis-main/artemis/sdk/builders/__init__.py +29 -0
- package/artemis-main/artemis/sdk/builders/agent_config_builder.py +474 -0
- package/artemis-main/artemis/sdk/constants.py +15 -0
- package/artemis-main/artemis/sdk/run_outcome.py +58 -0
- package/artemis-main/artemis/sdk/types/__init__.py +61 -0
- package/artemis-main/artemis/sdk/types/agent.py +89 -0
- package/artemis-main/artemis/sdk/utils.py +28 -0
- package/artemis-main/artemis/services/llm.py +1040 -0
- package/artemis-main/artemis/services/token_meter.py +267 -0
- package/artemis-main/artemis/telemetry/__init__.py +35 -0
- package/artemis-main/artemis/telemetry/models.py +64 -0
- package/artemis-main/artemis/telemetry/replay.py +34 -0
- package/artemis-main/artemis/telemetry/storage/json_storage.py +47 -0
- package/artemis-main/artemis/telemetry/tracer.py +85 -0
- package/artemis-main/artemis/toolchain/__init__.py +52 -0
- package/artemis-main/artemis/toolchain/descriptors.py +92 -0
- package/artemis-main/artemis/toolchain/resolver.py +185 -0
- package/artemis-main/artemis/tools/base.py +232 -0
- package/artemis-main/artemis/tools/command_tool.py +1240 -0
- package/artemis-main/artemis/tools/committee_prompts.json +5 -0
- package/artemis-main/artemis/tools/committee_tool.py +422 -0
- package/artemis-main/artemis/tools/diagnoser_submit_answer_tool.py +87 -0
- package/artemis-main/artemis/tools/diagnostic_tool.py +120 -0
- package/artemis-main/artemis/tools/explorer_tool.py +581 -0
- package/artemis-main/artemis/tools/history/__init__.py +398 -0
- package/artemis-main/artemis/tools/history/replay.py +120 -0
- package/artemis-main/artemis/tools/history/screenshot.py +139 -0
- package/artemis-main/artemis/tools/history/search.py +563 -0
- package/artemis-main/artemis/tools/image_processor_tool.py +124 -0
- package/artemis-main/artemis/tools/index.py +47 -0
- package/artemis-main/artemis/tools/log_tool.py +108 -0
- package/artemis-main/artemis/tools/mobile/launch_app.py +132 -0
- package/artemis-main/artemis/tools/mobile/log_utils.py +138 -0
- package/artemis-main/artemis/tools/mobile/ocr.py +202 -0
- package/artemis-main/artemis/tools/mobile/read_hierarchy.py +108 -0
- package/artemis-main/artemis/tools/mobile/read_logs.py +116 -0
- package/artemis-main/artemis/tools/mobile/search_logs.py +260 -0
- package/artemis-main/artemis/tools/object_detection_tool.py +244 -0
- package/artemis-main/artemis/tools/scratchpad.py +819 -0
- package/artemis-main/artemis/tools/system/adb_shell.py +31 -0
- package/artemis-main/artemis/tools/tool_wrapper.py +270 -0
- package/artemis-main/artemis/tools/video_tool.py +261 -0
- package/artemis-main/artemis/tools/wait_tool.py +97 -0
- package/artemis-main/artemis/utils/app_launch_utils.py +207 -0
- package/artemis-main/artemis/utils/coordinates.py +606 -0
- package/artemis-main/artemis/utils/credentials_validator.py +201 -0
- package/artemis-main/artemis/utils/cv_canvas.py +189 -0
- package/artemis-main/artemis/utils/cython_compat.py +45 -0
- package/artemis-main/artemis/utils/element_hit_test.py +80 -0
- package/artemis-main/artemis/utils/image_diff.py +335 -0
- package/artemis-main/artemis/utils/image_hash.py +80 -0
- package/artemis-main/artemis/utils/notes.py +470 -0
- package/artemis-main/artemis/utils/ocr_api.py +105 -0
- package/artemis-main/artemis/utils/ocr_xml_fusion.py +318 -0
- package/artemis-main/artemis/utils/plan_grammar.py +513 -0
- package/artemis-main/artemis/utils/python_executor.py +213 -0
- package/artemis-main/artemis/utils/startup_progress.py +60 -0
- package/artemis-main/artemis/utils/task_tree.py +1174 -0
- package/artemis-main/artemis/utils/text.py +37 -0
- package/artemis-main/artemis/utils/ui_filter.py +651 -0
- package/artemis-main/artemis/utils/video.py +685 -0
- package/artemis-main/artemis/utils/visualization.py +802 -0
- package/artemis-main/config/artemis.example.jsonc +12 -0
- package/artemis-main/mcp_server/README.md +111 -0
- package/artemis-main/mcp_server/__init__.py +21 -0
- package/artemis-main/mcp_server/__main__.py +20 -0
- package/artemis-main/mcp_server/background/__init__.py +15 -0
- package/artemis-main/mcp_server/background/task_runner.py +453 -0
- package/artemis-main/mcp_server/base.py +49 -0
- package/artemis-main/mcp_server/notifiers/__init__.py +74 -0
- package/artemis-main/mcp_server/notifiers/agentapi.py +289 -0
- package/artemis-main/mcp_server/notifiers/base.py +45 -0
- package/artemis-main/mcp_server/notifiers/composite.py +87 -0
- package/artemis-main/mcp_server/notifiers/desktop.py +108 -0
- package/artemis-main/mcp_server/notifiers/file.py +69 -0
- package/artemis-main/mcp_server/notifiers/script.py +90 -0
- package/artemis-main/mcp_server/notifiers/webhook.py +87 -0
- package/artemis-main/mcp_server/rules.md +68 -0
- package/artemis-main/mcp_server/server.py +106 -0
- package/artemis-main/mcp_server/tools/__init__.py +29 -0
- package/artemis-main/mcp_server/tools/device_state.py +107 -0
- package/artemis-main/mcp_server/tools/diagnose.py +1249 -0
- package/artemis-main/mcp_server/tools/inspect_trace.py +394 -0
- package/artemis-main/mcp_server/tools/task_manager.py +627 -0
- package/artemis-main/mcp_server/tools/task_runner.py +548 -0
- package/artemis-main/mcp_server/utils/__init__.py +15 -0
- package/artemis-main/mcp_server/utils/device_utils.py +180 -0
- package/artemis-main/mcp_server/utils/env_utils.py +65 -0
- package/artemis-main/packages/artemis-accessibility-helper/ArtemisAccessibilityHelper.apk +0 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/build.gradle.kts +35 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/AndroidManifest.xml +49 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/A11yNode.java +274 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/ArtemisAccessibilityService.java +227 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/CommandServer.java +463 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/DisplayUtils.java +111 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/GestureController.java +232 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/HierarchyDumper.java +832 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/TokenReceiver.java +36 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/TokenStore.java +38 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/XmlUtils.java +102 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/res/values/strings.xml +4 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/res/xml/accessibility_service_config.xml +10 -0
- package/artemis-main/packages/artemis-accessibility-helper/build.gradle.kts +3 -0
- package/artemis-main/packages/artemis-accessibility-helper/build_apk.sh +215 -0
- package/artemis-main/packages/artemis-accessibility-helper/debug.keystore +0 -0
- package/artemis-main/packages/artemis-accessibility-helper/gradle.properties +2 -0
- package/artemis-main/packages/artemis-accessibility-helper/helper_manifest.json +7 -0
- package/artemis-main/packages/artemis-accessibility-helper/settings.gradle.kts +17 -0
- package/artemis-main/packages/artemis-client/DESIGN.md +89 -0
- package/artemis-main/packages/artemis-client/README.md +112 -0
- package/artemis-main/packages/artemis-client/examples/basic.py +30 -0
- package/artemis-main/packages/artemis-client/pyproject.toml +36 -0
- package/artemis-main/packages/artemis-client/src/artemis_client/__init__.py +45 -0
- package/artemis-main/packages/artemis-client/src/artemis_client/client.py +401 -0
- package/artemis-main/packages/artemis-client/src/artemis_client/errors.py +66 -0
- package/artemis-main/packages/artemis-client/src/artemis_client/models.py +212 -0
- package/artemis-main/packages/artemis-client/src/artemis_client/py.typed +1 -0
- package/artemis-main/packages/artemis-client/src/artemis_client/transport.py +134 -0
- package/artemis-main/packages/artemis-client/tests/test_client.py +289 -0
- package/artemis-main/packages/artemis-client/tests/test_transport.py +85 -0
- package/artemis-main/pyproject.toml +259 -0
- package/artemis-main/pyright-core.json +17 -0
- package/artemis-main/scripts/check_dependency_sources.py +68 -0
- package/artemis-main/scripts/install_deps.ps1 +520 -0
- package/artemis-main/scripts/install_deps.sh +533 -0
- package/artemis-main/scripts/quality_ratchet.py +97 -0
- package/artemis-main/scripts/real_device_video_acceptance.py +483 -0
- package/artemis-main/scripts/run_edge_case_part.py +327 -0
- package/artemis-main/scripts/run_ui.py +242 -0
- package/artemis-main/scripts/start.ps1 +500 -0
- package/artemis-main/scripts/test_all_edge_cases.py +269 -0
- package/artemis-main/scripts/verify_concurrency_and_devices.py +385 -0
- package/artemis-main/setup.py +164 -0
- package/artemis-main/start.bat +35 -0
- package/artemis-main/start.sh +423 -0
- package/artemis-main/third_party/mobile_use/LICENSE +190 -0
- package/artemis-main/third_party/mobile_use/METADATA +27 -0
- package/artemis-main/third_party/mobile_use/NOTICE +12 -0
- package/artemis-main/third_party/mobile_use/__init__.py +19 -0
- package/artemis-main/third_party/mobile_use/agents/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/agents/hopper/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/agents/hopper/hopper.md +22 -0
- package/artemis-main/third_party/mobile_use/agents/hopper/hopper.py +80 -0
- package/artemis-main/third_party/mobile_use/clients/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/clients/ui_automator_client.py +482 -0
- package/artemis-main/third_party/mobile_use/config/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/config/llm.py +183 -0
- package/artemis-main/third_party/mobile_use/config/output.py +184 -0
- package/artemis-main/third_party/mobile_use/context.py +119 -0
- package/artemis-main/third_party/mobile_use/controllers/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/controllers/platform_specific_commands_controller.py +151 -0
- package/artemis-main/third_party/mobile_use/controllers/types.py +92 -0
- package/artemis-main/third_party/mobile_use/controllers/unified_controller.py +183 -0
- package/artemis-main/third_party/mobile_use/graph.py +54 -0
- package/artemis-main/third_party/mobile_use/llm_service.py +193 -0
- package/artemis-main/third_party/mobile_use/main.py +130 -0
- package/artemis-main/third_party/mobile_use/sdk/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/sdk/agent.py +1060 -0
- package/artemis-main/third_party/mobile_use/sdk/builders/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/sdk/builders/agent_config_builder.py +225 -0
- package/artemis-main/third_party/mobile_use/sdk/builders/index.py +33 -0
- package/artemis-main/third_party/mobile_use/sdk/builders/task_request_builder.py +243 -0
- package/artemis-main/third_party/mobile_use/sdk/types/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/sdk/types/agent.py +98 -0
- package/artemis-main/third_party/mobile_use/sdk/types/exceptions.py +109 -0
- package/artemis-main/third_party/mobile_use/sdk/types/task.py +234 -0
- package/artemis-main/third_party/mobile_use/testing.py +59 -0
- package/artemis-main/third_party/mobile_use/tools/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/tools/index.py +44 -0
- package/artemis-main/third_party/mobile_use/tools/mobile/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/tools/mobile/launch_app.py +89 -0
- package/artemis-main/third_party/mobile_use/tools/tool_wrapper.py +52 -0
- package/artemis-main/third_party/mobile_use/utils/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/utils/app_launch_utils.py +217 -0
- package/artemis-main/third_party/mobile_use/utils/cli_helpers.py +60 -0
- package/artemis-main/third_party/mobile_use/utils/decorators.py +158 -0
- package/artemis-main/third_party/mobile_use/utils/file.py +31 -0
- package/artemis-main/third_party/mobile_use/utils/logger.py +216 -0
- package/artemis-main/third_party/mobile_use/utils/media.py +213 -0
- package/artemis-main/third_party/mobile_use/utils/shell_utils.py +39 -0
- package/artemis-main/third_party/mobile_use/utils/video.py +398 -0
- package/artemis-main/uv.lock +4908 -0
- package/bin/android-midscene-automation.js +1 -5
- package/bin/artemis-config.mjs +15 -0
- package/bin/artemis-embedded.py +56 -0
- package/bin/start-artemis.mjs +30 -0
- package/bin/start-dev.mjs +112 -0
- package/bin/test_artemis_embedded.py +44 -0
- package/package.json +10 -8
- package/server/artemis.ts +34 -0
- package/server/http-api.ts +13 -1
- package/src/App.vue +72 -23
- package/src/api.ts +5 -0
- package/src/appium-recorder/AppiumPage.vue +1 -1
- package/src/appium-recorder/components/ComponentInspector.vue +1 -1
- package/src/appium-recorder/components/FlowNodeSearch.vue +3 -3
- package/src/appium-recorder/components/ImageCheckDialog.vue +3 -3
- package/src/appium-recorder/components/RecorderWorkspace.vue +4 -4
- package/src/appium-recorder/flow-appearance.ts +4 -4
- package/src/components/AppSidebar.vue +8 -7
- package/src/components/MidsceneMaintenanceNotice.vue +17 -0
- package/src/components/analytics/AnalysisCharts.vue +4 -4
- package/src/components/analytics/ReportAnalysis.vue +1 -1
- package/src/components/generator/TestCaseFileUpload.vue +1 -1
- package/src/components/help/HelpCenter.vue +56 -37
- package/src/pages/AiGeneratorPage.vue +1 -1
- package/src/pages/ArtemisPage.vue +72 -0
- package/src/pages/ConfigPage.vue +18 -19
- package/src/style.css +369 -186
- package/src/types.ts +1 -1
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
# Copyright 2026 Google LLC
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""Single source of truth for the task-plan grammar (the "machine channel").
|
|
16
|
+
|
|
17
|
+
``task_plan.md`` is a dual-channel document:
|
|
18
|
+
|
|
19
|
+
- **Machine channel**: the checkbox micro-grammar parsed here (status chars,
|
|
20
|
+
indentation, ``[Loop]`` / ``[Loop:continuous]`` tags). Harness code may only
|
|
21
|
+
ever read this channel.
|
|
22
|
+
- **Semantic channel**: all remaining free text. Only LLM agents interpret it;
|
|
23
|
+
harness code must never branch on wording.
|
|
24
|
+
|
|
25
|
+
Every deterministic decision the harness makes about the plan (termination,
|
|
26
|
+
loop protection, validation/checker triggers, hand-slip detection) derives
|
|
27
|
+
from the :class:`PlanSnapshot` produced by :func:`parse_plan`. The same
|
|
28
|
+
constants render :data:`PLAN_GRAMMAR_SPEC`, the grammar description injected
|
|
29
|
+
into agent prompts via the ``{{ plan_grammar }}`` template variable — so the
|
|
30
|
+
contract the agents are taught and the contract the code enforces cannot
|
|
31
|
+
drift apart.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
from __future__ import annotations
|
|
35
|
+
|
|
36
|
+
import hashlib
|
|
37
|
+
import re
|
|
38
|
+
from collections import Counter
|
|
39
|
+
from dataclasses import dataclass, field
|
|
40
|
+
from typing import Literal
|
|
41
|
+
|
|
42
|
+
STATUS_PENDING = " "
|
|
43
|
+
STATUS_ACTIVE = "/"
|
|
44
|
+
STATUS_DONE = "x"
|
|
45
|
+
STATUS_BLOCKED = "!"
|
|
46
|
+
STATUS_CHARS = STATUS_PENDING + STATUS_ACTIVE + STATUS_DONE + STATUS_BLOCKED
|
|
47
|
+
|
|
48
|
+
LOOP_TAG = "[Loop]"
|
|
49
|
+
CONTINUOUS_LOOP_TAG = "[Loop:continuous]"
|
|
50
|
+
|
|
51
|
+
CHECKBOX_LINE_RE = re.compile(
|
|
52
|
+
r"^(?P<indent>\s*)-\s*\[(?P<status>[" + re.escape(STATUS_CHARS) + r"])\]\s*(?P<text>.*)$"
|
|
53
|
+
)
|
|
54
|
+
_LOOP_TAG_RE = re.compile(r"\[Loop(?::continuous)?\]")
|
|
55
|
+
_CONTINUOUS_TAG_RE = re.compile(re.escape(CONTINUOUS_LOOP_TAG))
|
|
56
|
+
|
|
57
|
+
#: Check lines are indented attachments under a top-level subgoal
|
|
58
|
+
#: (``- verify: ...`` / ``- assert: ...``) or task-level free items
|
|
59
|
+
#: (``- assert@end: ...`` at zero indent). The optional ``@end`` suffix moves
|
|
60
|
+
#: the judgment moment from the anchored subgoal's completion to task exit.
|
|
61
|
+
CHECK_LINE_RE = re.compile(
|
|
62
|
+
r"^(?P<indent>\s*)-\s*(?P<kind>verify|assert)(?P<at_end>@end)?\s*:\s*(?P<text>\S.*?)\s*$"
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
#: Finding lines are SYSTEM-authored standing headlines for unresolved verify
|
|
66
|
+
#: failures, indented under the affected subgoal. They are a single-direction
|
|
67
|
+
#: projection of the checkpoint repair state: harness code re-renders them on
|
|
68
|
+
#: every plan write and NEVER reads their text back. They match neither
|
|
69
|
+
#: :data:`CHECKBOX_LINE_RE` nor :data:`CHECK_LINE_RE`, so they can never
|
|
70
|
+
#: contribute to milestone hashing, ratchet drift detection, check-item
|
|
71
|
+
#: multisets, or completion triggers.
|
|
72
|
+
FINDING_LINE_RE = re.compile(r"^(?P<indent>\s*)-\s*finding\s*:\s*(?P<text>.*)$")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def subgoal_hash(text: str) -> str:
|
|
76
|
+
"""Stable identity hash for a subgoal text (used for per-subgoal artifacts)."""
|
|
77
|
+
return hashlib.md5(text.strip().encode("utf-8")).hexdigest()
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@dataclass(frozen=True)
|
|
81
|
+
class PlanItem:
|
|
82
|
+
"""One checkbox line of the plan's machine channel."""
|
|
83
|
+
|
|
84
|
+
indent: int
|
|
85
|
+
status: str
|
|
86
|
+
text: str
|
|
87
|
+
line_no: int
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def is_top_level(self) -> bool:
|
|
91
|
+
return self.indent == 0
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def is_pending(self) -> bool:
|
|
95
|
+
return self.status == STATUS_PENDING
|
|
96
|
+
|
|
97
|
+
@property
|
|
98
|
+
def is_active(self) -> bool:
|
|
99
|
+
return self.status == STATUS_ACTIVE
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def is_done(self) -> bool:
|
|
103
|
+
return self.status == STATUS_DONE
|
|
104
|
+
|
|
105
|
+
@property
|
|
106
|
+
def is_loop(self) -> bool:
|
|
107
|
+
return bool(_LOOP_TAG_RE.search(self.text))
|
|
108
|
+
|
|
109
|
+
@property
|
|
110
|
+
def is_continuous(self) -> bool:
|
|
111
|
+
return bool(_CONTINUOUS_TAG_RE.search(self.text))
|
|
112
|
+
|
|
113
|
+
@property
|
|
114
|
+
def key(self) -> str:
|
|
115
|
+
return subgoal_hash(self.text)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@dataclass(frozen=True)
|
|
119
|
+
class CheckItem:
|
|
120
|
+
"""One declared check line of the plan's machine channel.
|
|
121
|
+
|
|
122
|
+
``verify`` marks a completion acceptance criterion (a failure triggers
|
|
123
|
+
repair); ``assert`` marks a test assertion (a failure is recorded as a
|
|
124
|
+
legitimate test result, never repaired).
|
|
125
|
+
"""
|
|
126
|
+
|
|
127
|
+
kind: Literal["verify", "assert"]
|
|
128
|
+
when: Literal["on_complete", "at_end"]
|
|
129
|
+
text: str
|
|
130
|
+
parent_key: str | None
|
|
131
|
+
|
|
132
|
+
@property
|
|
133
|
+
def key(self) -> str:
|
|
134
|
+
"""Stable identity of the check line (kind + when + text + anchor)."""
|
|
135
|
+
return subgoal_hash(f"{self.kind}|{self.when}|{self.parent_key}|{self.text}")
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
@dataclass(frozen=True)
|
|
139
|
+
class PlanSnapshot:
|
|
140
|
+
"""Read-only derived view of a plan. Never persisted, never written back."""
|
|
141
|
+
|
|
142
|
+
items: tuple[PlanItem, ...]
|
|
143
|
+
check_items: tuple[CheckItem, ...] = field(default=())
|
|
144
|
+
|
|
145
|
+
@property
|
|
146
|
+
def top_level(self) -> tuple[PlanItem, ...]:
|
|
147
|
+
return tuple(i for i in self.items if i.is_top_level)
|
|
148
|
+
|
|
149
|
+
@property
|
|
150
|
+
def has_top_level(self) -> bool:
|
|
151
|
+
return bool(self.top_level)
|
|
152
|
+
|
|
153
|
+
@property
|
|
154
|
+
def all_top_level_done(self) -> bool:
|
|
155
|
+
top = self.top_level
|
|
156
|
+
return bool(top) and all(i.is_done for i in top)
|
|
157
|
+
|
|
158
|
+
@property
|
|
159
|
+
def continuous_top_level(self) -> tuple[PlanItem, ...]:
|
|
160
|
+
return tuple(i for i in self.top_level if i.is_continuous)
|
|
161
|
+
|
|
162
|
+
@property
|
|
163
|
+
def milestone_texts(self) -> tuple[str, ...]:
|
|
164
|
+
return tuple(i.text.strip() for i in self.top_level)
|
|
165
|
+
|
|
166
|
+
def first_active(self) -> PlanItem | None:
|
|
167
|
+
for item in self.items:
|
|
168
|
+
if item.is_active:
|
|
169
|
+
return item
|
|
170
|
+
return None
|
|
171
|
+
|
|
172
|
+
def last_active(self) -> PlanItem | None:
|
|
173
|
+
for item in reversed(self.items):
|
|
174
|
+
if item.is_active:
|
|
175
|
+
return item
|
|
176
|
+
return None
|
|
177
|
+
|
|
178
|
+
def parent_of(self, item: PlanItem) -> PlanItem | None:
|
|
179
|
+
"""Nearest top-level item above an indented item."""
|
|
180
|
+
parent = None
|
|
181
|
+
for candidate in self.items:
|
|
182
|
+
if candidate.line_no >= item.line_no:
|
|
183
|
+
break
|
|
184
|
+
if candidate.is_top_level:
|
|
185
|
+
parent = candidate
|
|
186
|
+
return parent
|
|
187
|
+
|
|
188
|
+
def children_of(self, item: PlanItem) -> tuple[PlanItem, ...]:
|
|
189
|
+
"""Nested items under a top-level item (up to the next top-level line)."""
|
|
190
|
+
if not item.is_top_level:
|
|
191
|
+
return ()
|
|
192
|
+
children: list[PlanItem] = []
|
|
193
|
+
for candidate in self.items:
|
|
194
|
+
if candidate.line_no <= item.line_no:
|
|
195
|
+
continue
|
|
196
|
+
if candidate.is_top_level:
|
|
197
|
+
break
|
|
198
|
+
children.append(candidate)
|
|
199
|
+
return tuple(children)
|
|
200
|
+
|
|
201
|
+
def active_milestone(self) -> PlanItem | None:
|
|
202
|
+
"""The milestone currently being executed.
|
|
203
|
+
|
|
204
|
+
An explicitly active top-level item wins; otherwise the parent of the
|
|
205
|
+
bottom-most active nested item; otherwise, when every item is still
|
|
206
|
+
untouched, the first pending milestone (the same fallback the step
|
|
207
|
+
stamping uses). ``None`` when nothing is in progress.
|
|
208
|
+
"""
|
|
209
|
+
for item in self.top_level:
|
|
210
|
+
if item.is_active:
|
|
211
|
+
return item
|
|
212
|
+
nested_active = self.last_active()
|
|
213
|
+
if nested_active is not None:
|
|
214
|
+
return self.parent_of(nested_active)
|
|
215
|
+
if self.top_level and all(item.is_pending for item in self.top_level):
|
|
216
|
+
return self.top_level[0]
|
|
217
|
+
return None
|
|
218
|
+
|
|
219
|
+
def active_leaf(self, milestone: PlanItem) -> PlanItem | None:
|
|
220
|
+
"""Bottom-most ``[/]`` nested item under ``milestone`` (the current sub-goal)."""
|
|
221
|
+
leaf = None
|
|
222
|
+
for child in self.children_of(milestone):
|
|
223
|
+
if child.is_active:
|
|
224
|
+
leaf = child
|
|
225
|
+
return leaf
|
|
226
|
+
|
|
227
|
+
def check_items_of(self, item: PlanItem) -> tuple[CheckItem, ...]:
|
|
228
|
+
"""Check lines anchored to the given top-level subgoal."""
|
|
229
|
+
return tuple(ci for ci in self.check_items if ci.parent_key == item.key)
|
|
230
|
+
|
|
231
|
+
@property
|
|
232
|
+
def task_level_check_items(self) -> tuple[CheckItem, ...]:
|
|
233
|
+
return tuple(ci for ci in self.check_items if ci.parent_key is None)
|
|
234
|
+
|
|
235
|
+
@property
|
|
236
|
+
def all_check_items(self) -> tuple[CheckItem, ...]:
|
|
237
|
+
return self.check_items
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def parse_plan(content: str | None) -> PlanSnapshot:
|
|
241
|
+
"""Parses plan markdown into its machine channel. Non-checkbox lines are ignored.
|
|
242
|
+
|
|
243
|
+
Check lines (``- verify:`` / ``- assert:``) are parsed into
|
|
244
|
+
:class:`CheckItem` entries; they never contribute to the checkbox item list
|
|
245
|
+
and therefore never influence milestone hashing, drift detection, or
|
|
246
|
+
completion triggers.
|
|
247
|
+
"""
|
|
248
|
+
items: list[PlanItem] = []
|
|
249
|
+
checks: list[CheckItem] = []
|
|
250
|
+
current_top: PlanItem | None = None
|
|
251
|
+
if content:
|
|
252
|
+
for line_no, line in enumerate(content.splitlines()):
|
|
253
|
+
match = CHECKBOX_LINE_RE.match(line)
|
|
254
|
+
if match:
|
|
255
|
+
item = PlanItem(
|
|
256
|
+
indent=len(match.group("indent")),
|
|
257
|
+
status=match.group("status"),
|
|
258
|
+
text=match.group("text").strip(),
|
|
259
|
+
line_no=line_no,
|
|
260
|
+
)
|
|
261
|
+
items.append(item)
|
|
262
|
+
if item.is_top_level:
|
|
263
|
+
current_top = item
|
|
264
|
+
continue
|
|
265
|
+
check_match = CHECK_LINE_RE.match(line)
|
|
266
|
+
if check_match:
|
|
267
|
+
indented = bool(check_match.group("indent"))
|
|
268
|
+
at_end = bool(check_match.group("at_end"))
|
|
269
|
+
parent_key = current_top.key if (indented and current_top) else None
|
|
270
|
+
checks.append(
|
|
271
|
+
CheckItem(
|
|
272
|
+
kind=check_match.group("kind"),
|
|
273
|
+
when="at_end" if at_end else "on_complete",
|
|
274
|
+
text=check_match.group("text").strip(),
|
|
275
|
+
parent_key=parent_key,
|
|
276
|
+
)
|
|
277
|
+
)
|
|
278
|
+
return PlanSnapshot(items=tuple(items), check_items=tuple(checks))
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def milestones_changed(before: PlanSnapshot, after: PlanSnapshot) -> bool:
|
|
282
|
+
"""True if the top-level milestone texts differ (status flips don't count)."""
|
|
283
|
+
return before.milestone_texts != after.milestone_texts
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def _check_multiset(snapshot: PlanSnapshot) -> Counter:
|
|
287
|
+
return Counter((ci.kind, ci.when, ci.text, ci.parent_key) for ci in snapshot.check_items)
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def check_items_changed(before: PlanSnapshot, after: PlanSnapshot) -> bool:
|
|
291
|
+
"""True if the multiset of check lines differs between the two snapshots."""
|
|
292
|
+
return _check_multiset(before) != _check_multiset(after)
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def missing_check_items(before: PlanSnapshot, after: PlanSnapshot) -> list[CheckItem]:
|
|
296
|
+
"""Check items present in ``before`` but absent from ``after`` (multiset diff).
|
|
297
|
+
|
|
298
|
+
Additions in ``after`` are ignored — adding check lines is allowed; only
|
|
299
|
+
deletions/rewrites of existing ones are guarded.
|
|
300
|
+
"""
|
|
301
|
+
deficit = _check_multiset(before) - _check_multiset(after)
|
|
302
|
+
missing: list[CheckItem] = []
|
|
303
|
+
for ci in before.check_items:
|
|
304
|
+
sig = (ci.kind, ci.when, ci.text, ci.parent_key)
|
|
305
|
+
if deficit.get(sig, 0) > 0:
|
|
306
|
+
deficit[sig] -= 1
|
|
307
|
+
missing.append(ci)
|
|
308
|
+
return missing
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def render_check_line(item: CheckItem, indent: int = 2) -> str:
|
|
312
|
+
suffix = "@end" if item.when == "at_end" else ""
|
|
313
|
+
prefix = " " * indent if item.parent_key is not None else ""
|
|
314
|
+
return f"{prefix}- {item.kind}{suffix}: {item.text}"
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def restore_missing_check_items(
|
|
318
|
+
content_before: str,
|
|
319
|
+
content_after: str,
|
|
320
|
+
items: list[CheckItem] | None = None,
|
|
321
|
+
) -> str | None:
|
|
322
|
+
"""Deterministic guard: merge check lines deleted/rewritten by a plan write
|
|
323
|
+
back into the new content.
|
|
324
|
+
|
|
325
|
+
Returns the merged plan text, or ``None`` when nothing is missing. Lines
|
|
326
|
+
whose parent subgoal still exists are re-anchored directly under it; lines
|
|
327
|
+
whose parent subgoal was removed are converted to task-level ``@end`` items
|
|
328
|
+
appended at the end of the plan. ``items`` restricts the merge to the given
|
|
329
|
+
missing items (the caller may waive some of them).
|
|
330
|
+
"""
|
|
331
|
+
before = parse_plan(content_before)
|
|
332
|
+
after = parse_plan(content_after)
|
|
333
|
+
missing = missing_check_items(before, after) if items is None else list(items)
|
|
334
|
+
if not missing:
|
|
335
|
+
return None
|
|
336
|
+
|
|
337
|
+
lines = (content_after or "").splitlines()
|
|
338
|
+
top_line_by_key: dict[str, int] = {}
|
|
339
|
+
for item in after.items:
|
|
340
|
+
if item.is_top_level:
|
|
341
|
+
top_line_by_key.setdefault(item.key, item.line_no)
|
|
342
|
+
|
|
343
|
+
inserts: dict[int, list[str]] = {}
|
|
344
|
+
tail: list[str] = []
|
|
345
|
+
for ci in missing:
|
|
346
|
+
if ci.parent_key is not None and ci.parent_key in top_line_by_key:
|
|
347
|
+
inserts.setdefault(top_line_by_key[ci.parent_key], []).append(render_check_line(ci))
|
|
348
|
+
else:
|
|
349
|
+
orphan = CheckItem(kind=ci.kind, when="at_end", text=ci.text, parent_key=None)
|
|
350
|
+
tail.append(render_check_line(orphan))
|
|
351
|
+
|
|
352
|
+
merged: list[str] = []
|
|
353
|
+
for line_no, line in enumerate(lines):
|
|
354
|
+
merged.append(line)
|
|
355
|
+
if line_no in inserts:
|
|
356
|
+
merged.extend(inserts[line_no])
|
|
357
|
+
merged.extend(tail)
|
|
358
|
+
return "\n".join(merged) + ("\n" if (content_after or "").endswith("\n") else "")
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def render_finding_line(text: str, indent: int = 2) -> str:
|
|
362
|
+
return f"{' ' * indent}- finding: {text}"
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def strip_finding_lines(content: str) -> str:
|
|
366
|
+
"""Removes every finding line (system channel hygiene before re-projection)."""
|
|
367
|
+
if not content:
|
|
368
|
+
return content
|
|
369
|
+
lines = [line for line in content.splitlines() if not FINDING_LINE_RE.match(line)]
|
|
370
|
+
return "\n".join(lines) + ("\n" if content.endswith("\n") else "")
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def apply_finding_lines(content: str, findings_by_parent_key: dict[str, str]) -> str:
|
|
374
|
+
"""Deterministic single-direction projection of unresolved verify findings.
|
|
375
|
+
|
|
376
|
+
Removes ALL existing ``- finding:`` lines, then re-inserts one rendered
|
|
377
|
+
line per entry directly under its parent top-level subgoal (matched by
|
|
378
|
+
content hash). Entries whose parent subgoal no longer exists in the plan
|
|
379
|
+
are dropped silently — a rewritten subgoal supersedes its stale finding.
|
|
380
|
+
The projection is content-driven and idempotent: the model deleting a
|
|
381
|
+
finding line simply makes it grow back on the next write, and harness code
|
|
382
|
+
never reads the rendered text back.
|
|
383
|
+
"""
|
|
384
|
+
stripped = strip_finding_lines(content or "")
|
|
385
|
+
if not findings_by_parent_key:
|
|
386
|
+
return stripped
|
|
387
|
+
|
|
388
|
+
snapshot = parse_plan(stripped)
|
|
389
|
+
line_by_key: dict[str, int] = {}
|
|
390
|
+
for item in snapshot.items:
|
|
391
|
+
if item.is_top_level:
|
|
392
|
+
line_by_key.setdefault(item.key, item.line_no)
|
|
393
|
+
|
|
394
|
+
inserts: dict[int, list[str]] = {}
|
|
395
|
+
for parent_key, text in findings_by_parent_key.items():
|
|
396
|
+
line_no = line_by_key.get(parent_key)
|
|
397
|
+
if line_no is None:
|
|
398
|
+
continue
|
|
399
|
+
inserts.setdefault(line_no, []).append(render_finding_line(text))
|
|
400
|
+
|
|
401
|
+
if not inserts:
|
|
402
|
+
return stripped
|
|
403
|
+
|
|
404
|
+
lines = stripped.splitlines()
|
|
405
|
+
merged: list[str] = []
|
|
406
|
+
for line_no, line in enumerate(lines):
|
|
407
|
+
merged.append(line)
|
|
408
|
+
if line_no in inserts:
|
|
409
|
+
merged.extend(inserts[line_no])
|
|
410
|
+
return "\n".join(merged) + ("\n" if stripped.endswith("\n") else "")
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def new_top_level_completions(before: PlanSnapshot, after: PlanSnapshot) -> list[str]:
|
|
414
|
+
"""Top-level texts newly marked done, in plan order."""
|
|
415
|
+
before_done = {i.text for i in before.top_level if i.is_done}
|
|
416
|
+
return [i.text for i in after.top_level if i.is_done and i.text not in before_done]
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def unintended_milestone_edits(before: PlanSnapshot, after: PlanSnapshot) -> list[tuple[str, str]]:
|
|
420
|
+
"""Hand-slip signature of a full-file rewrite: positional top-level pairs
|
|
421
|
+
whose status char is unchanged but whose text was reworded.
|
|
422
|
+
|
|
423
|
+
Only defined when the milestone count is unchanged; structural changes
|
|
424
|
+
(add/remove) are a declared replan and are judged by planner validation
|
|
425
|
+
instead.
|
|
426
|
+
"""
|
|
427
|
+
top_before = before.top_level
|
|
428
|
+
top_after = after.top_level
|
|
429
|
+
if len(top_before) != len(top_after):
|
|
430
|
+
return []
|
|
431
|
+
return [
|
|
432
|
+
(b.text, a.text)
|
|
433
|
+
for b, a in zip(top_before, top_after)
|
|
434
|
+
if b.status == a.status and b.text != a.text
|
|
435
|
+
]
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
_PLAN_GRAMMAR_BASE = f"""### Task Plan Grammar (machine-enforced contract)
|
|
439
|
+
Every checklist line MUST match `<indent>- [<status>] <text>`:
|
|
440
|
+
- Status characters: `[{STATUS_PENDING}]` pending, `[{STATUS_ACTIVE}]` in progress, `[{STATUS_DONE}]` completed, `[{STATUS_BLOCKED}]` blocked.
|
|
441
|
+
- Top-level lines (no indentation) are strategic milestones. Nested lines are their sub-goals: 2 spaces for a sub-goal, 4 spaces for a sub-sub-goal beneath it (deeper nesting is allowed). Only zero-indent lines count as milestones for termination and validation; nested lines are the executor's live ledger. All other text in the note is free-form context.
|
|
442
|
+
- `{LOOP_TAG}` tags a BOUNDED iterative milestone: declare its exit boundary (e.g., `(Exit: <condition>; Interval: <cadence>)`) and mark it `[{STATUS_DONE}]` only once that exit condition is verifiably met.
|
|
443
|
+
- `{CONTINUOUS_LOOP_TAG}` tags an UNBOUNDED continuous-monitoring milestone: it must stay `[{STATUS_ACTIVE}]` and can never be marked `[{STATUS_DONE}]`, deleted, or untagged by you — the system mechanically rejects such edits. Only an explicit external stop signal injected by the user unlocks its completion; that signal is an external interruption delivered by the system, never something inferred from the screen or the plan.
|
|
444
|
+
- The task terminates only when every top-level milestone is `[{STATUS_DONE}]`."""
|
|
445
|
+
|
|
446
|
+
_CHECK_GRAMMAR_HEADER = """### Check Line Grammar (declared verification standards)
|
|
447
|
+
A top-level milestone may carry indented check lines, and the plan may end with task-level check lines at zero indentation:"""
|
|
448
|
+
|
|
449
|
+
#: Judgment-moment wording of the check lines, keyed by the midway gate.
|
|
450
|
+
_VERIFY_JUDGED_MIDWAY = (
|
|
451
|
+
"It is judged by an independent Checker at the moment the milestone is marked"
|
|
452
|
+
" completed; a failed verify reopens the milestone for repair."
|
|
453
|
+
)
|
|
454
|
+
_VERIFY_JUDGED_AT_EXIT = (
|
|
455
|
+
"It is judged once at task exit from the recorded evidence around the"
|
|
456
|
+
" milestone's completion; there is no midway repair loop."
|
|
457
|
+
)
|
|
458
|
+
_ASSERT_JUDGED_MIDWAY = (
|
|
459
|
+
"It is judged by the independent Checker when its milestone completes (at task exit for `@end`)"
|
|
460
|
+
)
|
|
461
|
+
_ASSERT_JUDGED_AT_EXIT = "It is judged once at task exit from the recorded evidence"
|
|
462
|
+
|
|
463
|
+
_CHECK_GRAMMAR_COMMON = """- `- verify@end: ...` / `- assert@end: ...` — the `@end` suffix defers judgment to task exit, using the final device state. Items without `@end` are judged from the evidence recorded around their anchored milestone's completion.
|
|
464
|
+
- Capability boundary (declare honestly, never promise more): all checks are POST-HOC audits over recorded evidence. They detect and record violations after the fact; they CANNOT block an action from happening, so "B must not run before A is confirmed" ordering rules are verified retroactively, not enforced. Transient prompts that were never captured in the execution history cannot be recovered — prefer expressing expectations as persistently probeable state."""
|
|
465
|
+
|
|
466
|
+
_FINDING_GRAMMAR = """- ` - finding: ...` — a SYSTEM-authored standing headline pinned under a milestone whose verify criterion failed. You never author these lines: the system re-renders them on every plan write (deleting one only makes it reappear) and removes them automatically once the verify passes again or its repair budget is exhausted. Each finding names a `checker-...` note holding the full details and repair log — read it with `read_note`; note keys with the `checker-` prefix are reserved for the system and are read-only for you."""
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
def _render_check_grammar(*, midway: bool, final: bool) -> str:
|
|
470
|
+
"""The check-line grammar worded for the active gates.
|
|
471
|
+
|
|
472
|
+
Midway on: verify lines are judged at milestone completion and can reopen
|
|
473
|
+
the milestone; finding lines exist. Midway off (final only): every line is
|
|
474
|
+
judged once at task exit, and no repair loop or finding line exists, so
|
|
475
|
+
neither is taught.
|
|
476
|
+
"""
|
|
477
|
+
verify_when = _VERIFY_JUDGED_MIDWAY if midway else _VERIFY_JUDGED_AT_EXIT
|
|
478
|
+
assert_when = _ASSERT_JUDGED_MIDWAY if midway else _ASSERT_JUDGED_AT_EXIT
|
|
479
|
+
lines = [
|
|
480
|
+
_CHECK_GRAMMAR_HEADER,
|
|
481
|
+
"- ` - verify: <expected state>` — an acceptance criterion for its parent"
|
|
482
|
+
f" milestone. {verify_when}",
|
|
483
|
+
f"- ` - assert: <expected observation>` — a test assertion. {assert_when} and a"
|
|
484
|
+
" failure is recorded verbatim as a legitimate test result; assertions are"
|
|
485
|
+
" NEVER repaired, worked around, or satisfied by constructing state.",
|
|
486
|
+
_CHECK_GRAMMAR_COMMON,
|
|
487
|
+
]
|
|
488
|
+
if midway:
|
|
489
|
+
lines.append(_FINDING_GRAMMAR)
|
|
490
|
+
return "\n".join(lines)
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
def render_plan_grammar_spec(
|
|
494
|
+
include_checks: bool | None = None, *, midway: bool = False, final: bool = False
|
|
495
|
+
) -> str:
|
|
496
|
+
"""Renders the grammar spec taught to agents.
|
|
497
|
+
|
|
498
|
+
``midway`` / ``final`` are the two check gates
|
|
499
|
+
(``ExecutionSetup.midway_checks_enabled`` / ``final_check_enabled``); the
|
|
500
|
+
check-line grammar is worded for whichever is active. Both off returns
|
|
501
|
+
exactly the base grammar — zero context pollution from the checking
|
|
502
|
+
feature. ``include_checks`` is a compatibility shim meaning "both gates".
|
|
503
|
+
"""
|
|
504
|
+
if include_checks is not None:
|
|
505
|
+
midway = final = bool(include_checks)
|
|
506
|
+
if midway or final:
|
|
507
|
+
return _PLAN_GRAMMAR_BASE + "\n\n" + _render_check_grammar(midway=midway, final=final)
|
|
508
|
+
return _PLAN_GRAMMAR_BASE
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
#: Backward-compatible module constant: the base grammar without the check
|
|
512
|
+
#: extension. Prompt injection sites use :func:`render_plan_grammar_spec`.
|
|
513
|
+
PLAN_GRAMMAR_SPEC = _PLAN_GRAMMAR_BASE
|