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,226 @@
|
|
|
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
|
+
"""Per-agent policies for rendering compiled history.
|
|
16
|
+
|
|
17
|
+
Each policy sets the visibility options for
|
|
18
|
+
:func:`artemis.utils.task_tree.build_plan_and_history`. When transcript mode
|
|
19
|
+
is enabled, compressed steps are rendered as chunk blocks.
|
|
20
|
+
|
|
21
|
+
Chunk views:
|
|
22
|
+
|
|
23
|
+
- ``"full"`` (outputter / history_analyzer): the chunk's stored three-band
|
|
24
|
+
rendering including the ③ full-width per-step ledger;
|
|
25
|
+
- ``"digest"`` (everything else): bands ① + ② with the ③ ledger replaced by
|
|
26
|
+
a ``ledger via search_history`` marker line.
|
|
27
|
+
|
|
28
|
+
Because chunks only exist when ``agent.memory.transcript.enabled`` is on, the
|
|
29
|
+
flag-off output of every agent stays byte-for-byte unchanged.
|
|
30
|
+
|
|
31
|
+
``agent.memory.policies`` allows per-agent field overrides, e.g.::
|
|
32
|
+
|
|
33
|
+
"memory": {"policies": {"planner": {"last_n_detailed": 3}}}
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
from dataclasses import dataclass, fields, replace
|
|
37
|
+
from typing import Any
|
|
38
|
+
|
|
39
|
+
from third_party.mobile_use.utils.logger import get_logger
|
|
40
|
+
|
|
41
|
+
logger = get_logger(__name__)
|
|
42
|
+
|
|
43
|
+
#: Marker appended to a digest-view chunk block in place of the ③ ledger.
|
|
44
|
+
DIGEST_LEDGER_MARKER_TEMPLATE = (
|
|
45
|
+
"③ Step action ledger: available via search_history (Steps {start}–{end})"
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
#: The band-③ heading inside a chunk's stored ``rendered_text``.
|
|
49
|
+
_BAND3_HEADING = "③ Step action ledger"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass(frozen=True)
|
|
53
|
+
class ContextPolicy:
|
|
54
|
+
"""One agent's compiled-history rendering declaration.
|
|
55
|
+
|
|
56
|
+
Field semantics map 1:1 onto ``build_plan_and_history`` kwargs, with two
|
|
57
|
+
conveniences: ``uncompressed=True`` renders every step (the historical
|
|
58
|
+
``min_summaries=len(steps)`` idiom) and ``whitelist=True`` marks policies
|
|
59
|
+
whose caller supplies ``keep_subgoal_hashes`` at runtime.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
strategy: str = "sliding_window" # documentation only
|
|
63
|
+
strict_milestone_pruning: bool = False
|
|
64
|
+
recent_window_size: int = 3
|
|
65
|
+
min_summaries: int = 5
|
|
66
|
+
uncompressed: bool = False
|
|
67
|
+
last_n_detailed: int = 1
|
|
68
|
+
all_detailed: bool = False
|
|
69
|
+
whitelist: bool = False
|
|
70
|
+
chunk_view: str | None = "digest" # "full" | "digest" | None (never chunks)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
#: Default compiled-history settings for each agent.
|
|
74
|
+
CONTEXT_POLICIES: dict[str, ContextPolicy] = {
|
|
75
|
+
# operator.py:996 — last_n_detailed comes from the node constructor.
|
|
76
|
+
"operator": ContextPolicy(
|
|
77
|
+
strategy="strict_milestone",
|
|
78
|
+
strict_milestone_pruning=True,
|
|
79
|
+
recent_window_size=3,
|
|
80
|
+
),
|
|
81
|
+
# operator.py:341 — transcript F-region cold-start restore.
|
|
82
|
+
"operator_cold_start": ContextPolicy(
|
|
83
|
+
strategy="full",
|
|
84
|
+
uncompressed=True,
|
|
85
|
+
last_n_detailed=1,
|
|
86
|
+
chunk_view=None,
|
|
87
|
+
),
|
|
88
|
+
# planner.py:133 — caller passes an empty plan and hash "default".
|
|
89
|
+
"planner": ContextPolicy(
|
|
90
|
+
strategy="strict_milestone",
|
|
91
|
+
strict_milestone_pruning=True,
|
|
92
|
+
recent_window_size=5,
|
|
93
|
+
last_n_detailed=2,
|
|
94
|
+
),
|
|
95
|
+
# outputter.py:81
|
|
96
|
+
"outputter": ContextPolicy(
|
|
97
|
+
strategy="full",
|
|
98
|
+
uncompressed=True,
|
|
99
|
+
last_n_detailed=0,
|
|
100
|
+
chunk_view="full",
|
|
101
|
+
),
|
|
102
|
+
# history_analyzer.py:113
|
|
103
|
+
"history_analyzer": ContextPolicy(
|
|
104
|
+
strategy="full",
|
|
105
|
+
uncompressed=True,
|
|
106
|
+
last_n_detailed=1,
|
|
107
|
+
chunk_view="full",
|
|
108
|
+
),
|
|
109
|
+
# diagnoser.py:166 — every non-whitelist kwarg is the historical default.
|
|
110
|
+
"diagnoser": ContextPolicy(strategy="milestone_whitelist", whitelist=True),
|
|
111
|
+
# committee_tool.py:189
|
|
112
|
+
"committee": ContextPolicy(
|
|
113
|
+
strategy="milestone_whitelist",
|
|
114
|
+
whitelist=True,
|
|
115
|
+
last_n_detailed=3,
|
|
116
|
+
),
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
_POLICY_FIELDS = {f.name for f in fields(ContextPolicy)}
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def resolve_policy(agent: str) -> ContextPolicy:
|
|
123
|
+
"""The agent's policy with any ``agent.memory.policies`` overrides applied."""
|
|
124
|
+
try:
|
|
125
|
+
policy = CONTEXT_POLICIES[agent]
|
|
126
|
+
except KeyError:
|
|
127
|
+
raise KeyError(
|
|
128
|
+
f"No ContextPolicy declared for agent '{agent}';"
|
|
129
|
+
f" known agents: {sorted(CONTEXT_POLICIES)}"
|
|
130
|
+
) from None
|
|
131
|
+
|
|
132
|
+
overrides: dict[str, Any] = {}
|
|
133
|
+
try:
|
|
134
|
+
from artemis.config import load_agent_config
|
|
135
|
+
|
|
136
|
+
configured = (load_agent_config().memory.policies or {}).get(agent) or {}
|
|
137
|
+
overrides = {k: v for k, v in configured.items() if k in _POLICY_FIELDS}
|
|
138
|
+
unknown = set(configured) - _POLICY_FIELDS
|
|
139
|
+
if unknown:
|
|
140
|
+
logger.warning(
|
|
141
|
+
f"agent.memory.policies['{agent}'] ignores unknown fields: {sorted(unknown)}"
|
|
142
|
+
)
|
|
143
|
+
except Exception:
|
|
144
|
+
overrides = {}
|
|
145
|
+
return replace(policy, **overrides) if overrides else policy
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _chunk_digest_text(rendered_text: str, start: int, end: int) -> str:
|
|
149
|
+
"""Bands ① + ② of a stored chunk rendering, ③ replaced by the recall marker."""
|
|
150
|
+
marker = DIGEST_LEDGER_MARKER_TEMPLATE.format(start=start, end=end)
|
|
151
|
+
head, sep, _ = rendered_text.partition(f"{_BAND3_HEADING}\n")
|
|
152
|
+
if not sep:
|
|
153
|
+
return f"{rendered_text.rstrip()}\n{marker}"
|
|
154
|
+
return f"{head.rstrip()}\n\n{marker}"
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def load_chunk_blocks(engine: Any, view: str | None) -> list[dict] | None:
|
|
158
|
+
"""The persisted history chunks rendered for the compiled view, or None.
|
|
159
|
+
|
|
160
|
+
Returns None (→ byte-identical legacy rendering) unless the transcript
|
|
161
|
+
flag is on, the engine exposes chunks, and at least one chunk exists.
|
|
162
|
+
"""
|
|
163
|
+
if engine is None or view not in ("full", "digest"):
|
|
164
|
+
return None
|
|
165
|
+
try:
|
|
166
|
+
from artemis.config import load_agent_config
|
|
167
|
+
|
|
168
|
+
if not load_agent_config().memory.transcript.enabled:
|
|
169
|
+
return None
|
|
170
|
+
except Exception:
|
|
171
|
+
return None
|
|
172
|
+
try:
|
|
173
|
+
rows = engine.get_history_chunks() or []
|
|
174
|
+
except Exception as e:
|
|
175
|
+
logger.debug(f"load_chunk_blocks: no chunks available: {e}")
|
|
176
|
+
return None
|
|
177
|
+
|
|
178
|
+
blocks: list[dict] = []
|
|
179
|
+
for row in rows:
|
|
180
|
+
start = getattr(row, "start_step_number", None)
|
|
181
|
+
end = getattr(row, "end_step_number", None)
|
|
182
|
+
rendered = getattr(row, "rendered_text", None)
|
|
183
|
+
if not isinstance(start, int) or not isinstance(end, int) or not rendered:
|
|
184
|
+
continue
|
|
185
|
+
text = rendered if view == "full" else _chunk_digest_text(rendered, start, end)
|
|
186
|
+
blocks.append({"start_step_number": start, "end_step_number": end, "text": text})
|
|
187
|
+
return blocks or None
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def build_history_for(
|
|
191
|
+
agent: str,
|
|
192
|
+
task_plan: str,
|
|
193
|
+
steps: list,
|
|
194
|
+
current_subgoal_hash: str,
|
|
195
|
+
*,
|
|
196
|
+
keep_subgoal_hashes: set | None = None,
|
|
197
|
+
last_n_detailed: int | None = None,
|
|
198
|
+
engine: Any = None,
|
|
199
|
+
) -> str:
|
|
200
|
+
"""Render the agent's compiled plan-and-history view per its policy.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
agent: Key into :data:`CONTEXT_POLICIES`.
|
|
204
|
+
keep_subgoal_hashes: Runtime whitelist (whitelist policies only).
|
|
205
|
+
last_n_detailed: Runtime override (the operator's constructor knob).
|
|
206
|
+
engine: DataEngine handing chunks to the compiled view; omit at call
|
|
207
|
+
sites that must never receive chunk blocks.
|
|
208
|
+
"""
|
|
209
|
+
from artemis.utils.task_tree import build_plan_and_history
|
|
210
|
+
|
|
211
|
+
policy = resolve_policy(agent)
|
|
212
|
+
kwargs: dict[str, Any] = {
|
|
213
|
+
"min_summaries": len(steps) if policy.uncompressed else policy.min_summaries,
|
|
214
|
+
"last_n_detailed": (policy.last_n_detailed if last_n_detailed is None else last_n_detailed),
|
|
215
|
+
"all_detailed": policy.all_detailed,
|
|
216
|
+
"strict_milestone_pruning": policy.strict_milestone_pruning,
|
|
217
|
+
"recent_window_size": policy.recent_window_size,
|
|
218
|
+
}
|
|
219
|
+
if policy.whitelist:
|
|
220
|
+
kwargs["keep_subgoal_hashes"] = keep_subgoal_hashes
|
|
221
|
+
|
|
222
|
+
chunks = load_chunk_blocks(engine, policy.chunk_view)
|
|
223
|
+
if chunks:
|
|
224
|
+
kwargs["chunks"] = chunks
|
|
225
|
+
|
|
226
|
+
return build_plan_and_history(task_plan, steps, current_subgoal_hash, **kwargs)
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# ROLE
|
|
2
|
+
You are the Segment Capsule writer for a mobile automation agent's history compression. You receive one contiguous segment of executed steps and produce the two LLM-authored bands of the segment's history chunk:
|
|
3
|
+
|
|
4
|
+
- Band ① — Synopsis & effects: what this segment was doing, what was actually done, and what effects/artifacts it left behind, plus structured fields.
|
|
5
|
+
- Band ② — Compressed step summary: an interval narrative ("Steps xx–xx did ..., Step xx did ...") covering every step of the segment.
|
|
6
|
+
|
|
7
|
+
The mechanical per-step action ledger (band ③) is assembled outside of you; do NOT reproduce it.
|
|
8
|
+
|
|
9
|
+
# INPUT SHAPE
|
|
10
|
+
The segment arrives turn by turn, in order. Each turn block has:
|
|
11
|
+
- `Recorded steps:` — the mechanical facts of the step(s) executed in that turn: step number, session offset, the exact action, the controller/validator result, the visual transition summary, the notes written (tool → note key: gist), and any user-injected instruction.
|
|
12
|
+
- `Transcript as seen by the operator during this turn:` — the turn exactly as it stood in the operator's own context: the observation text, the operator's reasoning (including `(thinking)` blocks), every `[tool call]` with its arguments, every `[tool result]` returned to the operator (explorer answers, recalled history, note reads, ADB output, screenshot placeholders), and the `--- Action Execution Result ---` message that closes the turn (every turn on the Pro path; only when an action failed on the Flash path). Your capsule REPLACES this transcript in the operator's context — everything the operator learned from it that still matters must survive in your output.
|
|
13
|
+
|
|
14
|
+
A turn block without a transcript falls back to a reasoning excerpt; treat it the same way with less evidence.
|
|
15
|
+
|
|
16
|
+
# NEUTRAL-WORDING CONTRACT (STRICT)
|
|
17
|
+
1. **Zero subjective validation**: never declare semantic success, completion, or final failure. Subjective verdicts in compressed history cause future turns to falsely believe a goal was definitively met or permanently broken.
|
|
18
|
+
- **BANNED WORDS**: "successfully", "completed", "achieved", "entered", "navigated to", "arrived at", "failed", "unsuccessful", "could not", "impossible".
|
|
19
|
+
- Describe observable transitions and recorded results instead (e.g. "the settings page was displayed", "the controller reported an execution error").
|
|
20
|
+
- Tool results in the transcript may themselves use verdict words; restate them as recorded results ("the explorer reported ...", "the tool returned ...").
|
|
21
|
+
2. **First-person perspective**: write from the agent's perspective using "I". Never "the agent" / "the operator".
|
|
22
|
+
3. **Planned iterations are not anomalies**: systematic processing of different candidate items, or periodic polling cycles under a `[Loop]` / `[Loop:continuous]` milestone, is planned progress — record it objectively (e.g. "polling round #2"). Only flag a loop when the exact same target is repeated without progress or states oscillate blindly; then record the pattern and count factually.
|
|
23
|
+
4. **Preserve, don't abstract**: be deliberately detailed and stay close to the source wording. Prefer longer output over abstracting facts beyond recognition. Faithfully carry visible text, codes, prices, account names, file names, error banners — and the concrete content of tool results the operator relied on (an explorer's answer, a recalled step, a note's contents, a command's output).
|
|
24
|
+
5. **Fact priority**: user instructions > validator/controller recorded results > tool results returned to the operator (explorer / recall / notes / ADB) > UI/OCR deltas > visual transition summaries > the operator's reasoning.
|
|
25
|
+
|
|
26
|
+
# BAND ① REQUIREMENTS
|
|
27
|
+
Band ① is the segment-level account; band ② below it is the interval-level account. The two bands must not say the same thing twice: ① never re-lists what the intervals of ② already narrate, and it should come out shorter than ② (the `doing` + `did` + `effect` prose together — not counting the structured fields — shorter than the ② intervals together).
|
|
28
|
+
|
|
29
|
+
Answer three questions in prose (field `doing` / `did` / `effect`):
|
|
30
|
+
- `doing`: what this segment was trying to do and why (strategic context within the plan). One or two sentences.
|
|
31
|
+
- `did`: the route the segment actually took, at segment level: the plan-relevant outcome, plus every detour, retry, recovery, pivot, and tool consultation (what I asked, what it returned) when that shaped the next action. Do NOT walk through the steps one by one — ② does that. A segment that went straight through gets one sentence.
|
|
32
|
+
- `effect`: what the segment left behind beyond the screen state already in `exit_state`: persistent state changes, artifacts, and notes. **This MUST include every note left during the segment**: for each notes write in the input, name the target note file and the gist of what was recorded. Every note's target key is machine-checked against your effect field — an omitted note key forces regeneration. If nothing was left behind besides the screen state, say so in a few words.
|
|
33
|
+
|
|
34
|
+
Structured fields (arrays of short strings, may be empty; each entry one short clause, not a sentence):
|
|
35
|
+
- `verified_facts`: states/values actually observed on screen or returned by a tool that later steps may need (name the source: "on screen", "explorer", "recall", "adb"). Values, names, versions, counts, codes, prices — not a description of every screen visited.
|
|
36
|
+
- `unresolved`: open questions, unverified assumptions, missing prerequisites.
|
|
37
|
+
- `failed_paths`: approaches attempted in this segment whose recorded results were errors (state the recorded error factually).
|
|
38
|
+
- `important_entities`: accounts, files, order/tracking numbers, package names, key UI entry points.
|
|
39
|
+
- `entry_state` / `exit_state` (single strings): the observable screen/app state at segment start and end, a short clause each.
|
|
40
|
+
|
|
41
|
+
# BAND ② REQUIREMENTS
|
|
42
|
+
Field `intervals`: an ordered array of `{"start_step": N, "end_step": M, "text": "..."}` objects.
|
|
43
|
+
- Band ② answers only "what were these steps doing, and what did I see as a result". The action itself (tap / swipe / input, its target and its coordinates) is ALREADY carried step by step by the mechanical ledger (band ③) that sits right below your output — do not write it twice.
|
|
44
|
+
- **NEVER include coordinates** — no `[x, y]` pairs, no pixel or normalized positions, no bounds. This is machine-checked; any `[number, number]` in an interval text forces regeneration.
|
|
45
|
+
- **NEVER narrate step by step** ("Step 5: I tapped X. Step 6: I tapped Y."). One interval is a behavior plus the observed outcome, e.g. "Opened the 'Network & internet' section and returned to the main list; the section showed Wi‑Fi, mobile network and VPN rows."
|
|
46
|
+
- **Merge homogeneous consecutive actions into one interval** (repeated scrolls, a sweep through several sections, a polling loop, retries of the same target) — describe the run and its count/result once. A step that does something of a different nature gets its own single-step interval (`start_step == end_step`).
|
|
47
|
+
- Every line's text must reference concrete behavior for those steps (the step numbers come from the `Recorded steps:` lines): the target's visible text, the screen or state observed afterwards, values read, errors shown.
|
|
48
|
+
- **The union of intervals MUST cover the segment's full step range** — no gaps, no overlaps, in ascending order. This is machine-checked; a gap forces regeneration.
|
|
49
|
+
- Keep the neutral-wording contract in every line.
|
|
50
|
+
|
|
51
|
+
# LENGTH TARGET (SOFT)
|
|
52
|
+
Bands ① and ② together should stay within roughly one third of the source text you were given. Reach that by not repeating what band ③ already records (actions, targets, coordinates) and by merging homogeneous runs — NEVER by abstracting away concrete facts (visible text, values read, tool answers, error banners, note contents must all survive verbatim).
|
|
53
|
+
|
|
54
|
+
# OUTPUT FORMAT
|
|
55
|
+
Return ONLY one JSON object (no markdown fences, no commentary):
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
{
|
|
59
|
+
"doing": "...",
|
|
60
|
+
"did": "...",
|
|
61
|
+
"effect": "...",
|
|
62
|
+
"entry_state": "...",
|
|
63
|
+
"exit_state": "...",
|
|
64
|
+
"verified_facts": ["..."],
|
|
65
|
+
"unresolved": ["..."],
|
|
66
|
+
"failed_paths": ["..."],
|
|
67
|
+
"important_entities": ["..."],
|
|
68
|
+
"intervals": [
|
|
69
|
+
{"start_step": 18, "end_step": 20, "text": "..."},
|
|
70
|
+
{"start_step": 21, "end_step": 21, "text": "..."}
|
|
71
|
+
]
|
|
72
|
+
}
|
|
73
|
+
```
|
|
@@ -0,0 +1,281 @@
|
|
|
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
|
+
"""Shared background step-summary runtime (StepMemoryService).
|
|
16
|
+
|
|
17
|
+
Hosts the scheduling skeleton lifted from the Flash VisualStepSummarizer:
|
|
18
|
+
zero-blocking dispatch, bounded retry, bounded flush, canonical step_id keying
|
|
19
|
+
with alias resolution (tool_call_id / legacy ordinals), and status queries.
|
|
20
|
+
|
|
21
|
+
What a summary *contains* (the lens semantics) lives in subclasses — the
|
|
22
|
+
service only guarantees how jobs are scheduled, retried, keyed, and drained.
|
|
23
|
+
Flash and Pro profiles share this runtime.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
import asyncio
|
|
27
|
+
from typing import Any
|
|
28
|
+
|
|
29
|
+
from artemis.data_engine.trace import detached_trace
|
|
30
|
+
from third_party.mobile_use.utils.logger import get_logger
|
|
31
|
+
|
|
32
|
+
logger = get_logger(__name__)
|
|
33
|
+
|
|
34
|
+
JobKey = int | str
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class StepLens:
|
|
38
|
+
"""Formal lens interface: what one summary attempt *produces*.
|
|
39
|
+
|
|
40
|
+
A lens turns one job payload into summary text; the surrounding service
|
|
41
|
+
owns scheduling, retries, keying, and draining. Implementations must be
|
|
42
|
+
side-effect-tolerant under retries (an attempt may run again after a
|
|
43
|
+
failure) and return ``None``/raise to signal a failed attempt.
|
|
44
|
+
|
|
45
|
+
Two lens altitudes exist in the redesign (§5): the step-level visual
|
|
46
|
+
transition lens (:class:`~artemis.agents.flash.summarizer.VisualStepSummarizer`
|
|
47
|
+
keeps it inlined as its ``_attempt`` for compatibility) and the chunk-level
|
|
48
|
+
:class:`~artemis.memory.chunking.StepCapsuleLens`.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
name = "lens"
|
|
52
|
+
|
|
53
|
+
async def render(self, key: JobKey, payload: dict[str, Any]) -> str | None:
|
|
54
|
+
"""Produce the summary text for one payload, or None on failure."""
|
|
55
|
+
raise NotImplementedError
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class StepMemoryService:
|
|
59
|
+
"""Non-blocking background runtime for per-step summary jobs.
|
|
60
|
+
|
|
61
|
+
Key design properties:
|
|
62
|
+
1. Zero-blocking dispatch: callers submit and proceed immediately.
|
|
63
|
+
2. Bounded retry: each job attempts at most ``1 + retry_limit`` times and
|
|
64
|
+
then enters an explicit failed state — never an unbounded loop.
|
|
65
|
+
3. Bounded flush: ``flush()`` waits up to the configured timeout, then
|
|
66
|
+
cancels whatever is still in flight.
|
|
67
|
+
4. Canonical keying with aliases: jobs are keyed by the DataEngine step id
|
|
68
|
+
where available; tool_call_ids (and legacy ordinals) remain queryable
|
|
69
|
+
through an alias map so message-level consumers need no migration.
|
|
70
|
+
5. Bounded concurrency: at most ``max_concurrency`` attempts run at once.
|
|
71
|
+
6. Detached tracing: every job runs inside
|
|
72
|
+
:func:`~artemis.data_engine.trace.detached_trace`, so a lens's model
|
|
73
|
+
calls never attach to the trace span (the operator's step) that was
|
|
74
|
+
current when the job was submitted — no lens needs to know.
|
|
75
|
+
|
|
76
|
+
Subclasses implement :meth:`_attempt` (one summarization attempt for a
|
|
77
|
+
payload) and may override :meth:`_on_status` to persist pending/failed
|
|
78
|
+
status transitions.
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
#: Trace node label of a lens-less subclass's jobs (``lens:<name>``); a
|
|
82
|
+
#: configured :class:`StepLens` contributes its own ``name`` instead.
|
|
83
|
+
lens_name: str | None = None
|
|
84
|
+
|
|
85
|
+
def __init__(
|
|
86
|
+
self,
|
|
87
|
+
ctx: Any,
|
|
88
|
+
*,
|
|
89
|
+
lens: StepLens | None = None,
|
|
90
|
+
max_concurrency: int = 1,
|
|
91
|
+
retry_limit: int = 3,
|
|
92
|
+
flush_timeout_s: float = 30.0,
|
|
93
|
+
):
|
|
94
|
+
self.ctx = ctx
|
|
95
|
+
self._lens = lens
|
|
96
|
+
self._summaries: dict[JobKey, str] = {}
|
|
97
|
+
self._failed: set[JobKey] = set()
|
|
98
|
+
self._pending_tasks: dict[JobKey, asyncio.Task] = {}
|
|
99
|
+
self._step_inputs: dict[JobKey, dict[str, Any]] = {}
|
|
100
|
+
self._retry_counts: dict[JobKey, int] = {}
|
|
101
|
+
self._retry_delays = (0.0, 0.5, 1.0, 2.0, 3.0)
|
|
102
|
+
self._retry_limit = max(0, retry_limit)
|
|
103
|
+
self._flush_timeout_s = max(0.0, flush_timeout_s)
|
|
104
|
+
self._aliases: dict[JobKey, JobKey] = {}
|
|
105
|
+
self._semaphore = asyncio.Semaphore(max(1, max_concurrency))
|
|
106
|
+
|
|
107
|
+
# ------------------------------------------------------------------
|
|
108
|
+
# Keying
|
|
109
|
+
# ------------------------------------------------------------------
|
|
110
|
+
|
|
111
|
+
def resolve_key(self, key: JobKey) -> JobKey:
|
|
112
|
+
"""Resolve an alias (tool_call_id / legacy ordinal) to its canonical key."""
|
|
113
|
+
return self._aliases.get(key, key)
|
|
114
|
+
|
|
115
|
+
def submit(
|
|
116
|
+
self,
|
|
117
|
+
key: JobKey,
|
|
118
|
+
payload: dict[str, Any],
|
|
119
|
+
aliases: tuple[JobKey, ...] = (),
|
|
120
|
+
) -> None:
|
|
121
|
+
"""Submit one summary job without blocking the caller.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
key: Canonical job key (DataEngine step id where available).
|
|
125
|
+
payload: Lens-specific input data; stored until the job resolves.
|
|
126
|
+
aliases: Additional keys (e.g. tool_call_id) that resolve to ``key``.
|
|
127
|
+
"""
|
|
128
|
+
for alias in aliases:
|
|
129
|
+
if alias is not None and alias != key:
|
|
130
|
+
self._aliases[alias] = key
|
|
131
|
+
|
|
132
|
+
self._step_inputs[key] = payload
|
|
133
|
+
self._failed.discard(key)
|
|
134
|
+
self._on_status(key, "pending")
|
|
135
|
+
|
|
136
|
+
task = asyncio.create_task(self._run_until_ready(key))
|
|
137
|
+
self._pending_tasks[key] = task
|
|
138
|
+
|
|
139
|
+
# ------------------------------------------------------------------
|
|
140
|
+
# Scheduling core
|
|
141
|
+
# ------------------------------------------------------------------
|
|
142
|
+
|
|
143
|
+
@property
|
|
144
|
+
def trace_node_name(self) -> str:
|
|
145
|
+
"""Node label the job's traces and usage records carry (``lens:<name>``)."""
|
|
146
|
+
name = getattr(self._lens, "name", None) or self.lens_name or type(self).__name__.lower()
|
|
147
|
+
return f"lens:{name}"
|
|
148
|
+
|
|
149
|
+
async def _run_until_ready(self, key: JobKey) -> None:
|
|
150
|
+
"""Run one job to completion, detached from the submitter's trace span.
|
|
151
|
+
|
|
152
|
+
The task was created while the submitting agent's step span was
|
|
153
|
+
current and inherits that context; the whole job — every attempt,
|
|
154
|
+
retry sleeps included — runs under :func:`detached_trace` so nothing
|
|
155
|
+
a lens records (model calls, their thinking/raw_thinking children,
|
|
156
|
+
stream deltas, usage) can be attributed to the submitter's step.
|
|
157
|
+
"""
|
|
158
|
+
with detached_trace(self.trace_node_name):
|
|
159
|
+
await self._run_attempts(key)
|
|
160
|
+
|
|
161
|
+
async def _run_attempts(self, key: JobKey) -> None:
|
|
162
|
+
"""Retry one job independently, bounded by the configured retry limit.
|
|
163
|
+
|
|
164
|
+
Attempts at most ``1 + retry_limit`` times; on exhaustion the job
|
|
165
|
+
enters an explicit failed state (surfaced via :meth:`has_failed` and
|
|
166
|
+
the ``_on_status`` hook). Retry backoff sleeps happen outside the
|
|
167
|
+
concurrency semaphore so a waiting job never starves its peers.
|
|
168
|
+
"""
|
|
169
|
+
while key not in self._summaries:
|
|
170
|
+
async with self._semaphore:
|
|
171
|
+
succeeded = await self._attempt(key)
|
|
172
|
+
if succeeded:
|
|
173
|
+
return
|
|
174
|
+
|
|
175
|
+
retry_count = self._retry_counts.get(key, 0) + 1
|
|
176
|
+
self._retry_counts[key] = retry_count
|
|
177
|
+
payload = self._step_inputs.get(key)
|
|
178
|
+
display_step = payload.get("step_number") if payload else key
|
|
179
|
+
|
|
180
|
+
if retry_count > self._retry_limit:
|
|
181
|
+
self._failed.add(key)
|
|
182
|
+
logger.warning(
|
|
183
|
+
f"StepMemoryService: Giving up on Step {display_step} after"
|
|
184
|
+
f" {retry_count} failed attempts (retry_limit={self._retry_limit})."
|
|
185
|
+
)
|
|
186
|
+
self._on_status(key, "failed")
|
|
187
|
+
return
|
|
188
|
+
|
|
189
|
+
delay = self._retry_delays[min(retry_count - 1, len(self._retry_delays) - 1)]
|
|
190
|
+
logger.info(
|
|
191
|
+
f"StepMemoryService: Retrying Step {display_step} "
|
|
192
|
+
f"(attempt {retry_count + 1}) after {delay:.1f}s"
|
|
193
|
+
)
|
|
194
|
+
if delay:
|
|
195
|
+
await asyncio.sleep(delay)
|
|
196
|
+
|
|
197
|
+
async def _attempt(self, key: JobKey) -> bool:
|
|
198
|
+
"""Run one summarization attempt for ``key``; True when a summary landed.
|
|
199
|
+
|
|
200
|
+
The default implementation delegates to the configured :class:`StepLens`;
|
|
201
|
+
subclasses without a lens override this method directly (the Flash
|
|
202
|
+
visual summarizer keeps its inlined attempt for compatibility).
|
|
203
|
+
"""
|
|
204
|
+
if self._lens is None:
|
|
205
|
+
raise NotImplementedError
|
|
206
|
+
payload = self._step_inputs.get(key)
|
|
207
|
+
if payload is None:
|
|
208
|
+
return False
|
|
209
|
+
try:
|
|
210
|
+
summary = await self._lens.render(key, payload)
|
|
211
|
+
except Exception as e:
|
|
212
|
+
logger.warning(f"StepMemoryService: lens '{self._lens.name}' attempt failed: {e}")
|
|
213
|
+
return False
|
|
214
|
+
if not summary:
|
|
215
|
+
return False
|
|
216
|
+
self._summaries[key] = summary
|
|
217
|
+
self._on_ready(key, summary)
|
|
218
|
+
return True
|
|
219
|
+
|
|
220
|
+
def _on_ready(self, key: JobKey, summary: str) -> None:
|
|
221
|
+
"""Hook invoked when a lens-produced summary lands; default is a no-op."""
|
|
222
|
+
|
|
223
|
+
def _on_status(self, key: JobKey, status: str) -> None:
|
|
224
|
+
"""Hook invoked on pending/failed transitions; default is a no-op."""
|
|
225
|
+
|
|
226
|
+
# ------------------------------------------------------------------
|
|
227
|
+
# Status queries (alias-resolving)
|
|
228
|
+
# ------------------------------------------------------------------
|
|
229
|
+
|
|
230
|
+
def get_summary(self, key: JobKey, fallback_text: str | None = None) -> str | None:
|
|
231
|
+
"""Retrieve the summary for a job key (or alias), or fallback text."""
|
|
232
|
+
return self._summaries.get(self.resolve_key(key), fallback_text)
|
|
233
|
+
|
|
234
|
+
def has_summary(self, key: JobKey) -> bool:
|
|
235
|
+
"""Check whether a job's summary has completed."""
|
|
236
|
+
return self.resolve_key(key) in self._summaries
|
|
237
|
+
|
|
238
|
+
def has_job(self, key: JobKey) -> bool:
|
|
239
|
+
"""Check whether a job has been submitted for summarization."""
|
|
240
|
+
return self.resolve_key(key) in self._step_inputs
|
|
241
|
+
|
|
242
|
+
def has_failed(self, key: JobKey) -> bool:
|
|
243
|
+
"""Check whether a job permanently failed (retries exhausted)."""
|
|
244
|
+
return self.resolve_key(key) in self._failed
|
|
245
|
+
|
|
246
|
+
def is_pending(self, key: JobKey) -> bool:
|
|
247
|
+
"""Check whether a job still needs a summary."""
|
|
248
|
+
return self.has_job(key) and not self.has_summary(key)
|
|
249
|
+
|
|
250
|
+
def get_job_payload(self, key: JobKey) -> dict[str, Any] | None:
|
|
251
|
+
"""Return the stored payload for a job key (or alias), if any."""
|
|
252
|
+
return self._step_inputs.get(self.resolve_key(key))
|
|
253
|
+
|
|
254
|
+
def get_step_number(self, key: JobKey) -> int | None:
|
|
255
|
+
"""Return the human-facing step ordinal recorded for a job, if known."""
|
|
256
|
+
payload = self.get_job_payload(key)
|
|
257
|
+
if payload is None:
|
|
258
|
+
return None
|
|
259
|
+
step_number = payload.get("step_number")
|
|
260
|
+
return step_number if isinstance(step_number, int) else None
|
|
261
|
+
|
|
262
|
+
# ------------------------------------------------------------------
|
|
263
|
+
# Draining
|
|
264
|
+
# ------------------------------------------------------------------
|
|
265
|
+
|
|
266
|
+
async def flush(self, timeout_seconds: float | None = None) -> None:
|
|
267
|
+
"""Wait for active jobs up to a bound, then cancel remaining retry loops."""
|
|
268
|
+
if timeout_seconds is None:
|
|
269
|
+
timeout_seconds = self._flush_timeout_s
|
|
270
|
+
tasks = [t for t in self._pending_tasks.values() if not t.done()]
|
|
271
|
+
if tasks:
|
|
272
|
+
logger.info(f"StepMemoryService: Flushing {len(tasks)} pending summary tasks...")
|
|
273
|
+
_, pending = await asyncio.wait(tasks, timeout=timeout_seconds)
|
|
274
|
+
if pending:
|
|
275
|
+
logger.warning(
|
|
276
|
+
f"StepMemoryService: Cancelling {len(pending)} unfinished summary tasks "
|
|
277
|
+
f"after {timeout_seconds:.1f}s flush timeout."
|
|
278
|
+
)
|
|
279
|
+
for task in pending:
|
|
280
|
+
task.cancel()
|
|
281
|
+
await asyncio.gather(*pending, return_exceptions=True)
|