apsimo 1.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- apsimo/__init__.py +38 -0
- apsimo/__main__.py +6 -0
- apsimo/agent/__init__.py +6 -0
- apsimo/agent/client.py +276 -0
- apsimo/agent/models.py +46 -0
- apsimo/agents/__init__.py +20 -0
- apsimo/agents/models.py +264 -0
- apsimo/agents/store.py +861 -0
- apsimo/agents/websocket.py +522 -0
- apsimo/api/__init__.py +1 -0
- apsimo/api/auth_telemetry.py +287 -0
- apsimo/api/authority.py +1203 -0
- apsimo/api/contact_grants.py +347 -0
- apsimo/api/middleware.py +483 -0
- apsimo/api/routers/__init__.py +1 -0
- apsimo/api/routers/commitment_work.py +265 -0
- apsimo/api/routers/context_gate.py +123 -0
- apsimo/api/routers/executions.py +140 -0
- apsimo/api/routers/followup_plans.py +147 -0
- apsimo/api/routers/governed_actions.py +162 -0
- apsimo/api/routers/host.py +14473 -0
- apsimo/api/routers/initiative_work.py +115 -0
- apsimo/api/routers/mining.py +104 -0
- apsimo/api/routers/observations.py +110 -0
- apsimo/api/routers/social_state.py +225 -0
- apsimo/api/routers/task_queue.py +2715 -0
- apsimo/api/routers/temporal_followups.py +251 -0
- apsimo/api/routers/transport.py +110 -0
- apsimo/api/routers/transport_ingress_api.py +240 -0
- apsimo/api/schemas/__init__.py +1 -0
- apsimo/api/schemas/host.py +1949 -0
- apsimo/autonomy/cli.py +110 -0
- apsimo/autonomy/condition_worker.py +437 -0
- apsimo/autonomy/config.py +424 -0
- apsimo/autonomy/loop.py +4316 -0
- apsimo/autonomy/registry.py +339 -0
- apsimo/autonomy/scheduler.py +1822 -0
- apsimo/autonomy/synthesis.py +449 -0
- apsimo/backup.py +962 -0
- apsimo/beliefs/__init__.py +23 -0
- apsimo/beliefs/contradictions.py +109 -0
- apsimo/beliefs/decay.py +61 -0
- apsimo/beliefs/engine.py +479 -0
- apsimo/beliefs/models.py +67 -0
- apsimo/beliefs/promotion.py +41 -0
- apsimo/beliefs/resolve.py +58 -0
- apsimo/beliefs/source_claims.py +690 -0
- apsimo/beliefs/source_projection.py +883 -0
- apsimo/beliefs/source_time.py +208 -0
- apsimo/beliefs/store.py +133 -0
- apsimo/briefings/aggregators.py +824 -0
- apsimo/briefings/composer.py +420 -0
- apsimo/briefings/config.py +55 -0
- apsimo/briefings/delivery.py +439 -0
- apsimo/briefings/engagement.py +97 -0
- apsimo/briefings/engine.py +274 -0
- apsimo/briefings/enhancer.py +99 -0
- apsimo/briefings/models.py +183 -0
- apsimo/briefings/scheduler.py +382 -0
- apsimo/briefings/store.py +435 -0
- apsimo/chain/__init__.py +48 -0
- apsimo/chain/block.py +100 -0
- apsimo/chain/cli.py +704 -0
- apsimo/chain/genesis.py +443 -0
- apsimo/chain/identity.py +416 -0
- apsimo/chain/keys.py +1025 -0
- apsimo/chain/local_keys.py +187 -0
- apsimo/chain/manager.py +290 -0
- apsimo/chain/node.py +163 -0
- apsimo/chain/plugin_transactions.py +371 -0
- apsimo/chain/protocol.py +220 -0
- apsimo/chain/state_machine.py +676 -0
- apsimo/chain/storage.py +503 -0
- apsimo/chain/transactions.py +250 -0
- apsimo/chain/validation.py +397 -0
- apsimo/channels/__init__.py +1 -0
- apsimo/channels/manifest.py +31 -0
- apsimo/channels/migrations/001_channels_schema.sql +12 -0
- apsimo/channels/phone_gateways.py +42 -0
- apsimo/channels/presence.py +188 -0
- apsimo/channels/router.py +235 -0
- apsimo/channels/store.py +231 -0
- apsimo/cli.py +2688 -0
- apsimo/cognition/__init__.py +11 -0
- apsimo/cognition/charter.py +398 -0
- apsimo/cognition/drive_governance.py +3530 -0
- apsimo/cognition/evidence_pipeline.py +1627 -0
- apsimo/cognition/external_events.py +932 -0
- apsimo/cognition/goal_spine.py +3488 -0
- apsimo/cognition/introspection.py +214 -0
- apsimo/cognition/prompt.py +150 -0
- apsimo/cognition/runtime.py +108 -0
- apsimo/cognition/trigger.py +154 -0
- apsimo/commitments/__init__.py +18 -0
- apsimo/commitments/local_work.py +355 -0
- apsimo/commitments/store.py +1052 -0
- apsimo/commitments/work.py +91 -0
- apsimo/compat.py +53 -0
- apsimo/compression/__init__.py +467 -0
- apsimo/connectors/__init__.py +21 -0
- apsimo/connectors/base.py +152 -0
- apsimo/connectors/caldav_calendar.py +125 -0
- apsimo/connectors/fs_documents.py +85 -0
- apsimo/connectors/imap_email.py +138 -0
- apsimo/connectors/manager.py +218 -0
- apsimo/connectors/webhook_pull.py +88 -0
- apsimo/contacts/__init__.py +33 -0
- apsimo/contacts/comms.py +357 -0
- apsimo/contacts/config.py +79 -0
- apsimo/contacts/exporters/__init__.py +1 -0
- apsimo/contacts/exporters/vcard.py +71 -0
- apsimo/contacts/identity_links.py +251 -0
- apsimo/contacts/importer.py +280 -0
- apsimo/contacts/importers/__init__.py +1 -0
- apsimo/contacts/importers/batch.py +43 -0
- apsimo/contacts/importers/macos_contacts.py +101 -0
- apsimo/contacts/migrations/001_contacts_schema.sql +141 -0
- apsimo/contacts/migrations/002_trust_scopes.sql +36 -0
- apsimo/contacts/migrations/003_open_gateway_enum.sql +32 -0
- apsimo/contacts/migrations/004_contact_provision_operations.sql +18 -0
- apsimo/contacts/migrations/005_identity_links.sql +27 -0
- apsimo/contacts/models.py +308 -0
- apsimo/contacts/scoring.py +16 -0
- apsimo/contacts/store.py +1623 -0
- apsimo/contacts/transport_ingress.py +252 -0
- apsimo/contacts/world_bridge.py +314 -0
- apsimo/contextgate/__init__.py +69 -0
- apsimo/contextgate/chunker.py +169 -0
- apsimo/contextgate/estimate.py +54 -0
- apsimo/contextgate/gate.py +313 -0
- apsimo/contextgate/retrieve.py +115 -0
- apsimo/delivery/__init__.py +16 -0
- apsimo/delivery/bridge.py +1260 -0
- apsimo/delivery/channels.py +526 -0
- apsimo/delivery/classification.py +50 -0
- apsimo/delivery/rate_limiter.py +268 -0
- apsimo/delivery/reachout_policy.py +206 -0
- apsimo/directed/__init__.py +22 -0
- apsimo/directed/audit.py +167 -0
- apsimo/directed/intake.py +95 -0
- apsimo/directed/models.py +191 -0
- apsimo/directed/service.py +509 -0
- apsimo/directives/__init__.py +25 -0
- apsimo/directives/evidence.py +87 -0
- apsimo/directives/extractor.py +188 -0
- apsimo/directives/guard.py +364 -0
- apsimo/directives/models.py +206 -0
- apsimo/directives/service.py +372 -0
- apsimo/directives/store.py +167 -0
- apsimo/doctor.py +2173 -0
- apsimo/environment.py +43 -0
- apsimo/events/__init__.py +33 -0
- apsimo/events/broadcaster.py +98 -0
- apsimo/events/bus.py +217 -0
- apsimo/events/journal.py +863 -0
- apsimo/events/stream.py +131 -0
- apsimo/events/types.py +150 -0
- apsimo/execution_results.py +357 -0
- apsimo/feedback/__init__.py +5 -0
- apsimo/feedback/store.py +76 -0
- apsimo/feeds/__init__.py +19 -0
- apsimo/feeds/cli.py +84 -0
- apsimo/feeds/engine.py +437 -0
- apsimo/feeds/example-feed.yaml +77 -0
- apsimo/feeds/hermes_cron.py +126 -0
- apsimo/feeds/manager.py +235 -0
- apsimo/feeds/spec.py +250 -0
- apsimo/feeds/template.py +202 -0
- apsimo/gate/__init__.py +18 -0
- apsimo/gate/audit.py +61 -0
- apsimo/gate/communication_policy.py +166 -0
- apsimo/gate/config.py +72 -0
- apsimo/gate/context_provenance.py +170 -0
- apsimo/gate/env_risk.py +226 -0
- apsimo/gate/guard_audit.py +353 -0
- apsimo/gate/layers/__init__.py +1 -0
- apsimo/gate/layers/base.py +15 -0
- apsimo/gate/layers/l1_recipient.py +66 -0
- apsimo/gate/layers/l2_pii.py +134 -0
- apsimo/gate/layers/l3_cross_context.py +50 -0
- apsimo/gate/layers/l4_trust_tier.py +78 -0
- apsimo/gate/layers/l5_injection.py +199 -0
- apsimo/gate/layers/l6_review.py +86 -0
- apsimo/gate/layers/l7_delay.py +100 -0
- apsimo/gate/layers/tom2_epistemic.py +185 -0
- apsimo/gate/models.py +64 -0
- apsimo/gate/pending_dispatch.py +5 -0
- apsimo/gate/pipeline.py +206 -0
- apsimo/gate/rejection.py +259 -0
- apsimo/gate/response_guard.py +700 -0
- apsimo/gate/rulesets/injection_v1.yaml +51 -0
- apsimo/gate/surface_policy.py +189 -0
- apsimo/gate/taint.py +226 -0
- apsimo/genesis.json +9 -0
- apsimo/goals/__init__.py +100 -0
- apsimo/goals/config.py +38 -0
- apsimo/goals/decomposer.py +421 -0
- apsimo/goals/engine.py +617 -0
- apsimo/goals/inference.py +354 -0
- apsimo/goals/models.py +302 -0
- apsimo/goals/priority.py +270 -0
- apsimo/goals/queue_bridge.py +149 -0
- apsimo/goals/replan.py +450 -0
- apsimo/goals/schema.sql +89 -0
- apsimo/goals/store.py +692 -0
- apsimo/governed_actions.py +1708 -0
- apsimo/harness_integration/__init__.py +45 -0
- apsimo/harness_integration/context.py +41 -0
- apsimo/harness_integration/skills.py +231 -0
- apsimo/identity/__init__.py +26 -0
- apsimo/identity/participants.py +181 -0
- apsimo/identity/resolver.py +329 -0
- apsimo/identity_bootstrap/__init__.py +5 -0
- apsimo/identity_bootstrap/builder.py +208 -0
- apsimo/identity_bootstrap/corpus.py +443 -0
- apsimo/identity_bootstrap/models.py +54 -0
- apsimo/identity_bootstrap/runner.py +353 -0
- apsimo/identity_bootstrap/seeders/__init__.py +25 -0
- apsimo/identity_bootstrap/seeders/briefings.py +109 -0
- apsimo/identity_bootstrap/seeders/chain.py +57 -0
- apsimo/identity_bootstrap/seeders/goals.py +128 -0
- apsimo/identity_bootstrap/seeders/memory.py +191 -0
- apsimo/identity_bootstrap/seeders/neo4j_cognition.py +79 -0
- apsimo/identity_bootstrap/seeders/relationship.py +152 -0
- apsimo/identity_bootstrap/seeders/sessions.py +67 -0
- apsimo/identity_bootstrap/seeders/skills.py +92 -0
- apsimo/identity_bootstrap/seeders/task_queue.py +72 -0
- apsimo/identity_bootstrap/seeders/world_model.py +143 -0
- apsimo/identity_bootstrap/self_query.py +92 -0
- apsimo/identity_bootstrap/self_reflection.py +155 -0
- apsimo/identity_bootstrap/skill.py +37 -0
- apsimo/identity_bootstrap/verifier.py +436 -0
- apsimo/initiatives/__init__.py +20 -0
- apsimo/initiatives/action_registry.py +454 -0
- apsimo/initiatives/approval_authority.py +2105 -0
- apsimo/initiatives/approval_policy.py +123 -0
- apsimo/initiatives/assignment.py +263 -0
- apsimo/initiatives/backup_evidence.py +100 -0
- apsimo/initiatives/context_freshness.py +103 -0
- apsimo/initiatives/models.py +318 -0
- apsimo/initiatives/native_work.py +270 -0
- apsimo/initiatives/standing_approvals.py +232 -0
- apsimo/initiatives/store.py +1081 -0
- apsimo/initiatives/temporal_followup.py +410 -0
- apsimo/intelligence/__init__.py +1 -0
- apsimo/intelligence/cognition/__init__.py +24 -0
- apsimo/intelligence/cognition/gap_detector.py +148 -0
- apsimo/intelligence/cognition/metalearner.py +547 -0
- apsimo/intelligence/cognition/metrics_collector.py +217 -0
- apsimo/intelligence/cognition/performance_index.py +299 -0
- apsimo/intelligence/cognition/registry.py +192 -0
- apsimo/intelligence/cognition/strategy_adjuster.py +222 -0
- apsimo/intelligence/cognition/types.py +16 -0
- apsimo/intelligence/components/__init__.py +66 -0
- apsimo/intelligence/components/anomaly_detector.py +413 -0
- apsimo/intelligence/components/initiative_engine.py +2643 -0
- apsimo/intelligence/components/preference_learner.py +521 -0
- apsimo/intelligence/components/research_orchestrator.py +358 -0
- apsimo/intelligence/components/self_directed_thinker.py +221 -0
- apsimo/intelligence/components/self_reflector.py +252 -0
- apsimo/intelligence/components/session_continuity.py +154 -0
- apsimo/intelligence/components/task_planner.py +320 -0
- apsimo/intelligence/components/tool_learner.py +217 -0
- apsimo/intelligence/graph/__init__.py +79 -0
- apsimo/intelligence/graph/client.py +2483 -0
- apsimo/intelligence/graph/consolidator.py +405 -0
- apsimo/intelligence/graph/distiller.py +312 -0
- apsimo/intelligence/graph/migrations.py +129 -0
- apsimo/intelligence/graph/queries.py +248 -0
- apsimo/intelligence/graph/recall.py +281 -0
- apsimo/intelligence/graph/reconciler.py +144 -0
- apsimo/intelligence/graph/schema.py +337 -0
- apsimo/intelligence/graph/selection.py +252 -0
- apsimo/intelligence/learning/__init__.py +17 -0
- apsimo/intelligence/learning/continuous_learner.py +245 -0
- apsimo/intelligence/learning/feedback_store.py +321 -0
- apsimo/intelligence/mind_model/__init__.py +1 -0
- apsimo/intelligence/mind_model/graph_baseline.py +136 -0
- apsimo/intelligence/mind_model/signal_collector.py +361 -0
- apsimo/intelligence/relationships/__init__.py +11 -0
- apsimo/intelligence/relationships/profiler.py +389 -0
- apsimo/intelligence/relationships/scorer.py +560 -0
- apsimo/intelligence/relationships/signal_floor.py +66 -0
- apsimo/intelligence/relationships/trust_tiers.py +300 -0
- apsimo/intelligence/synthesis/__init__.py +40 -0
- apsimo/intelligence/synthesis/connection_discoverer.py +379 -0
- apsimo/intelligence/synthesis/cross_domain_analyzer.py +287 -0
- apsimo/intelligence/synthesis/insight_deliverer.py +171 -0
- apsimo/intelligence/synthesis/insight_store.py +79 -0
- apsimo/intelligence/synthesis/insight_validator.py +183 -0
- apsimo/intelligence/synthesis/novelty_scorer.py +267 -0
- apsimo/intelligence/turn_middleware/__init__.py +15 -0
- apsimo/intelligence/turn_middleware/memory_sync.py +119 -0
- apsimo/mcp/__init__.py +41 -0
- apsimo/mcp/__main__.py +6 -0
- apsimo/mcp/config.py +287 -0
- apsimo/mcp/server.py +501 -0
- apsimo/migrations.py +187 -0
- apsimo/mining/__init__.py +27 -0
- apsimo/mining/corpus.py +239 -0
- apsimo/mining/escalations.py +289 -0
- apsimo/mining/models.py +169 -0
- apsimo/mining/store.py +210 -0
- apsimo/models/__init__.py +30 -0
- apsimo/models/memory.py +80 -0
- apsimo/models/mesh.py +72 -0
- apsimo/models/person.py +104 -0
- apsimo/models/signal.py +108 -0
- apsimo/observations/__init__.py +15 -0
- apsimo/observations/store.py +277 -0
- apsimo/patterns/__init__.py +6 -0
- apsimo/patterns/extract.py +187 -0
- apsimo/patterns/store.py +227 -0
- apsimo/persona/__init__.py +1 -0
- apsimo/persona/engine.py +611 -0
- apsimo/persona/manifest.py +140 -0
- apsimo/projects/__init__.py +28 -0
- apsimo/projects/engine.py +1681 -0
- apsimo/projects/event_outbox.py +188 -0
- apsimo/projects/models.py +216 -0
- apsimo/projects/planner.py +181 -0
- apsimo/projects/store.py +1446 -0
- apsimo/proposals/__init__.py +12 -0
- apsimo/proposals/engine.py +114 -0
- apsimo/proposals/models.py +207 -0
- apsimo/qualification/__init__.py +1 -0
- apsimo/qualification/cases.py +75 -0
- apsimo/qualification/cli.py +51 -0
- apsimo/qualification/memory_cases.py +209 -0
- apsimo/qualification/records.py +92 -0
- apsimo/qualification/report.py +87 -0
- apsimo/qualification/runner.py +311 -0
- apsimo/qualification/structured_cases.py +131 -0
- apsimo/reasoning/__init__.py +13 -0
- apsimo/reasoning/executor.py +506 -0
- apsimo/reasoning/loop.py +373 -0
- apsimo/reasoning/native_tools/__init__.py +16 -0
- apsimo/reasoning/native_tools/calculate.py +141 -0
- apsimo/reasoning/native_tools/file_ops.py +150 -0
- apsimo/reasoning/native_tools/web_search.py +49 -0
- apsimo/reasoning/tool_policy.py +182 -0
- apsimo/redact/__init__.py +176 -0
- apsimo/repos/__init__.py +5 -0
- apsimo/repos/mirrors.py +204 -0
- apsimo/research/__init__.py +41 -0
- apsimo/research/artifact.py +482 -0
- apsimo/research/gatherer.py +387 -0
- apsimo/research/pipeline.py +513 -0
- apsimo/research/search/__init__.py +7 -0
- apsimo/research/search/base.py +41 -0
- apsimo/research/search/brave.py +59 -0
- apsimo/research/search/cache.py +51 -0
- apsimo/research/search/duckduckgo.py +103 -0
- apsimo/research/search/orchestrator.py +119 -0
- apsimo/research/search/serpapi.py +59 -0
- apsimo/research/search/tavily.py +59 -0
- apsimo/research/synthesizer.py +309 -0
- apsimo/router/__init__.py +30 -0
- apsimo/router/complexity_scorer.py +148 -0
- apsimo/router/endpoints.py +153 -0
- apsimo/router/fallback.py +58 -0
- apsimo/router/functions.py +243 -0
- apsimo/router/native_policy.py +52 -0
- apsimo/router/router.py +762 -0
- apsimo/router/self_learning.py +174 -0
- apsimo/router/tiers.py +677 -0
- apsimo/sandbox/__init__.py +21 -0
- apsimo/sandbox/backend.py +195 -0
- apsimo/sandbox/manager.py +173 -0
- apsimo/scope_bounds.py +7 -0
- apsimo/secrets/__init__.py +6 -0
- apsimo/secrets/backends/__init__.py +8 -0
- apsimo/secrets/backends/base.py +42 -0
- apsimo/secrets/backends/env.py +110 -0
- apsimo/secrets/backends/keyring.py +72 -0
- apsimo/secrets/backends/onepassword.py +232 -0
- apsimo/secrets/cli.py +191 -0
- apsimo/secrets/manager.py +160 -0
- apsimo/secrets/migration.py +101 -0
- apsimo/secrets/types.py +98 -0
- apsimo/seed.py +41 -0
- apsimo/self_model/__init__.py +37 -0
- apsimo/self_model/appraisals.py +673 -0
- apsimo/self_model/benchmark.py +1314 -0
- apsimo/self_model/brief.py +40 -0
- apsimo/self_model/event_concerns.py +1128 -0
- apsimo/self_model/execution_forecasts.py +353 -0
- apsimo/self_model/expectations.py +1595 -0
- apsimo/self_model/experiments.py +1150 -0
- apsimo/self_model/journal.py +148 -0
- apsimo/self_model/judgments.py +705 -0
- apsimo/self_model/native_outcomes.py +55 -0
- apsimo/self_model/params.py +220 -0
- apsimo/self_model/perspective.py +246 -0
- apsimo/self_model/reconcile.py +183 -0
- apsimo/self_model/reply_forecasts.py +381 -0
- apsimo/self_model/runtime_forecasts.py +296 -0
- apsimo/self_model/runtime_models.py +67 -0
- apsimo/self_model/settlement.py +207 -0
- apsimo/self_model/situation.py +1731 -0
- apsimo/self_model/store.py +883 -0
- apsimo/self_model/supervised.py +137 -0
- apsimo/self_model/thinker.py +99 -0
- apsimo/self_model/trust.py +388 -0
- apsimo/self_model/workspace.py +2388 -0
- apsimo/server.py +4197 -0
- apsimo/services/__init__.py +1 -0
- apsimo/services/agent_bridge.py +474 -0
- apsimo/services/initiative_executor.py +914 -0
- apsimo/services/instance.py +297 -0
- apsimo/sessions/__init__.py +22 -0
- apsimo/sessions/config.py +13 -0
- apsimo/sessions/context_loader.py +88 -0
- apsimo/sessions/federation_session.py +75 -0
- apsimo/sessions/isolated_session.py +98 -0
- apsimo/sessions/reports.py +84 -0
- apsimo/sessions/store.py +148 -0
- apsimo/setup.py +2818 -0
- apsimo/setup_hermes.py +879 -0
- apsimo/setup_local_work.py +218 -0
- apsimo/setup_native_goals.py +134 -0
- apsimo/setup_native_reviews.py +115 -0
- apsimo/skills/__init__.py +10 -0
- apsimo/skills/base.py +108 -0
- apsimo/skills/budget.py +28 -0
- apsimo/skills/executor.py +493 -0
- apsimo/skills/executors/__init__.py +1 -0
- apsimo/skills/executors/behavioral_correction.py +75 -0
- apsimo/skills/executors/capability_gap.py +38 -0
- apsimo/skills/executors/data_quality.py +163 -0
- apsimo/skills/executors/knowledge_acquisition.py +41 -0
- apsimo/skills/executors/operational_hygiene.py +185 -0
- apsimo/skills/executors/subsystem_health.py +169 -0
- apsimo/skills/hermes_export.py +431 -0
- apsimo/skills/index.py +123 -0
- apsimo/skills/learning/__init__.py +21 -0
- apsimo/skills/learning/novelty_detector.py +206 -0
- apsimo/skills/learning/pattern_extractor.py +199 -0
- apsimo/skills/learning/triggers.py +159 -0
- apsimo/skills/loader.py +246 -0
- apsimo/skills/migrations/002_progressive_loading.sql +6 -0
- apsimo/skills/migrations/backfill_triggers.py +20 -0
- apsimo/skills/models.py +202 -0
- apsimo/skills/packager.py +128 -0
- apsimo/skills/protocols.py +70 -0
- apsimo/skills/registry.py +191 -0
- apsimo/skills/runtime.py +58 -0
- apsimo/skills/sandbox_runner.py +229 -0
- apsimo/skills/scheduler.py +129 -0
- apsimo/skills/schema.py +79 -0
- apsimo/skills/security/__init__.py +12 -0
- apsimo/skills/security/guards.py +53 -0
- apsimo/skills/security/scanner.py +223 -0
- apsimo/skills_memory/__init__.py +26 -0
- apsimo/skills_memory/distill.py +159 -0
- apsimo/skills_memory/models.py +85 -0
- apsimo/skills_memory/retrieve.py +62 -0
- apsimo/skills_memory/store.py +172 -0
- apsimo/surprise/__init__.py +6 -0
- apsimo/surprise/accumulation.py +57 -0
- apsimo/surprise/scorer.py +102 -0
- apsimo/surprise/store.py +203 -0
- apsimo/task_queue/__init__.py +69 -0
- apsimo/task_queue/action_receipts.py +148 -0
- apsimo/task_queue/approval_relay_canary.py +108 -0
- apsimo/task_queue/config.py +85 -0
- apsimo/task_queue/contract.py +361 -0
- apsimo/task_queue/events.py +130 -0
- apsimo/task_queue/governor.py +1031 -0
- apsimo/task_queue/handlers/__init__.py +16 -0
- apsimo/task_queue/handlers/base.py +37 -0
- apsimo/task_queue/handlers/inference.py +640 -0
- apsimo/task_queue/handlers/monitoring.py +116 -0
- apsimo/task_queue/handlers/registry.py +75 -0
- apsimo/task_queue/handlers/subtask_handler.py +173 -0
- apsimo/task_queue/handlers/system_maintenance.py +147 -0
- apsimo/task_queue/mesh_integration.py +111 -0
- apsimo/task_queue/models.py +317 -0
- apsimo/task_queue/queue_manager.py +8286 -0
- apsimo/task_queue/routing.py +287 -0
- apsimo/task_queue/scheduler.py +252 -0
- apsimo/task_queue/schema.sql +197 -0
- apsimo/task_queue/work_control.py +342 -0
- apsimo/task_queue/worker.py +993 -0
- apsimo/telemetry.py +145 -0
- apsimo/tom/__init__.py +6 -0
- apsimo/tom/affect.py +387 -0
- apsimo/tom/approvals.py +171 -0
- apsimo/tom/arcs.py +896 -0
- apsimo/tom/asymmetry.py +131 -0
- apsimo/tom/eligibility.py +248 -0
- apsimo/tom/engagement.py +214 -0
- apsimo/tom/exposure.py +214 -0
- apsimo/tom/extractor.py +306 -0
- apsimo/tom/fact_adapters.py +144 -0
- apsimo/tom/facts.py +326 -0
- apsimo/tom/integration.py +592 -0
- apsimo/tom/leveled.py +118 -0
- apsimo/tom/levels.py +247 -0
- apsimo/tom/recipient_audit.py +995 -0
- apsimo/tom/recipient_simulator.py +593 -0
- apsimo/tom/source_lineage.py +93 -0
- apsimo/tom/tom2.py +277 -0
- apsimo/tom/visibility.py +559 -0
- apsimo/tom/visibility_store.py +414 -0
- apsimo/tools/__init__.py +0 -0
- apsimo/tools/definitions.py +740 -0
- apsimo/tools/handlers.py +943 -0
- apsimo/toolsmith/__init__.py +26 -0
- apsimo/toolsmith/authority.py +166 -0
- apsimo/toolsmith/engine.py +559 -0
- apsimo/toolsmith/integrity.py +100 -0
- apsimo/toolsmith/miner.py +145 -0
- apsimo/toolsmith/policy.py +110 -0
- apsimo/toolsmith/registry.py +635 -0
- apsimo/turns/__init__.py +17 -0
- apsimo/turns/audio.py +134 -0
- apsimo/turns/documents.py +235 -0
- apsimo/turns/executions.py +486 -0
- apsimo/turns/hermes_history.py +245 -0
- apsimo/turns/hermes_kanban.py +268 -0
- apsimo/turns/hermes_work.py +96 -0
- apsimo/turns/idempotency.py +752 -0
- apsimo/turns/local_work.py +115 -0
- apsimo/turns/media.py +581 -0
- apsimo/turns/reported_workers.py +196 -0
- apsimo/turns/source_annotations.py +283 -0
- apsimo/turns/source_attribution.py +154 -0
- apsimo/turns/source_read.py +351 -0
- apsimo/turns/source_vectors.py +263 -0
- apsimo/turns/video.py +210 -0
- apsimo/util/autonomy_preset.py +220 -0
- apsimo/util/instance.py +92 -0
- apsimo/util/model_output.py +25 -0
- apsimo/util/quiet_hours.py +27 -0
- apsimo/util/session_safety.py +37 -0
- apsimo/util/temporal.py +343 -0
- apsimo/vector/__init__.py +75 -0
- apsimo/vector/backfill.py +171 -0
- apsimo/vector/caption.py +114 -0
- apsimo/vector/collections.py +51 -0
- apsimo/vector/config.py +102 -0
- apsimo/vector/embedder.py +670 -0
- apsimo/vector/image_preprocess.py +406 -0
- apsimo/vector/image_store.py +296 -0
- apsimo/vector/indexes.py +162 -0
- apsimo/vector/migrate.py +334 -0
- apsimo/vector/multimodal_provider.py +417 -0
- apsimo/vector/multimodal_types.py +87 -0
- apsimo/vector/openai_provider.py +119 -0
- apsimo/vector/query.py +49 -0
- apsimo/vector/reranker.py +565 -0
- apsimo/vector/safety_image.py +159 -0
- apsimo/vector/scanner.py +197 -0
- apsimo/vector/setup.py +289 -0
- apsimo/vector/store.py +533 -0
- apsimo/vector/tiers.py +263 -0
- apsimo/work_orders.py +925 -0
- apsimo/workers/__init__.py +21 -0
- apsimo/workers/agent_bridge.py +640 -0
- apsimo/workers/colony_worker.py +382 -0
- apsimo/workers/queue_worker.py +441 -0
- apsimo/workers/skills_sync.py +152 -0
- apsimo/world_model/__init__.py +71 -0
- apsimo/world_model/causal_maintenance.py +131 -0
- apsimo/world_model/causal_policy.py +43 -0
- apsimo/world_model/causal_query.py +125 -0
- apsimo/world_model/confidence.py +54 -0
- apsimo/world_model/config.py +64 -0
- apsimo/world_model/constants.py +97 -0
- apsimo/world_model/entities.py +145 -0
- apsimo/world_model/expectation_resolvers.py +177 -0
- apsimo/world_model/extraction/__init__.py +7 -0
- apsimo/world_model/extraction/base.py +62 -0
- apsimo/world_model/extraction/conversation_extractor.py +262 -0
- apsimo/world_model/extraction/detector.py +74 -0
- apsimo/world_model/extraction/document_extractor.py +78 -0
- apsimo/world_model/extraction/formats/__init__.py +24 -0
- apsimo/world_model/extraction/formats/csv_fmt.py +68 -0
- apsimo/world_model/extraction/formats/html_fmt.py +72 -0
- apsimo/world_model/extraction/formats/json_fmt.py +68 -0
- apsimo/world_model/extraction/formats/pdf.py +43 -0
- apsimo/world_model/extraction/formats/text.py +27 -0
- apsimo/world_model/extraction/llm_extractor.py +164 -0
- apsimo/world_model/extraction/pipeline.py +73 -0
- apsimo/world_model/integrations/__init__.py +5 -0
- apsimo/world_model/integrations/mind_model_bridge.py +115 -0
- apsimo/world_model/integrations/social_intel_bridge.py +120 -0
- apsimo/world_model/jobs/__init__.py +4 -0
- apsimo/world_model/jobs/extraction_job.py +168 -0
- apsimo/world_model/llm_extract.py +572 -0
- apsimo/world_model/neo4j/__init__.py +5 -0
- apsimo/world_model/neo4j/backend.py +654 -0
- apsimo/world_model/observations.py +155 -0
- apsimo/world_model/populator.py +307 -0
- apsimo/world_model/postgres/__init__.py +1 -0
- apsimo/world_model/postgres/backend.py +683 -0
- apsimo/world_model/relationships.py +25 -0
- apsimo/world_model/resolution/__init__.py +13 -0
- apsimo/world_model/resolution/entity_resolver.py +232 -0
- apsimo/world_model/resolution/merge_audit.py +16 -0
- apsimo/world_model/resolution/merge_workflow.py +117 -0
- apsimo/world_model/source_reports.py +121 -0
- apsimo/world_model/sqlite/__init__.py +4 -0
- apsimo/world_model/sqlite/backend.py +855 -0
- apsimo/world_model/sqlite/schema.sql +132 -0
- apsimo/world_model/store.py +545 -0
- apsimo-1.3.0.dist-info/METADATA +78 -0
- apsimo-1.3.0.dist-info/RECORD +614 -0
- apsimo-1.3.0.dist-info/WHEEL +5 -0
- apsimo-1.3.0.dist-info/entry_points.txt +11 -0
- apsimo-1.3.0.dist-info/licenses/LICENSE +21 -0
- apsimo-1.3.0.dist-info/top_level.txt +2 -0
- colony_sidecar/__init__.py +4 -0
apsimo/autonomy/cli.py
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"""CLI entry point for Colony autonomy loop management.
|
|
2
|
+
|
|
3
|
+
Usage:
|
|
4
|
+
colony autonomy status # live loop stats from the running sidecar
|
|
5
|
+
colony autonomy cycle # wake the loop / run one cycle now
|
|
6
|
+
|
|
7
|
+
The loop itself lives inside the sidecar process and starts with it — there
|
|
8
|
+
is no standalone "start" mode (running a second loop against the same state
|
|
9
|
+
would double-fire every phase). `status` and `cycle` talk to the running
|
|
10
|
+
sidecar over HTTP, resolved the same way the doctor does (COLONY_URL /
|
|
11
|
+
COLONY_SIDECAR_HOST/PORT, COLONY_API_KEY).
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import json
|
|
17
|
+
import os
|
|
18
|
+
import sys
|
|
19
|
+
import urllib.error
|
|
20
|
+
import urllib.request
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _request(path: str, method: str = "GET") -> dict:
|
|
24
|
+
from apsimo.doctor import default_colony_url
|
|
25
|
+
|
|
26
|
+
url = default_colony_url().rstrip("/") + path
|
|
27
|
+
key = os.environ.get("COLONY_API_KEY", "")
|
|
28
|
+
req = urllib.request.Request(
|
|
29
|
+
url,
|
|
30
|
+
method=method,
|
|
31
|
+
data=b"{}" if method == "POST" else None,
|
|
32
|
+
headers={
|
|
33
|
+
"Content-Type": "application/json",
|
|
34
|
+
**({"Authorization": f"Bearer {key}"} if key else {}),
|
|
35
|
+
},
|
|
36
|
+
)
|
|
37
|
+
with urllib.request.urlopen(req, timeout=10) as r:
|
|
38
|
+
return json.loads(r.read().decode())
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _print_config_defaults() -> None:
|
|
42
|
+
from apsimo.autonomy.config import AutonomyConfig
|
|
43
|
+
|
|
44
|
+
cfg = AutonomyConfig.from_env()
|
|
45
|
+
print(f" tick_interval_secs: {cfg.tick_interval_secs}")
|
|
46
|
+
print(f" initiative_confidence_threshold: {cfg.initiative_confidence_threshold}")
|
|
47
|
+
print(f" max_actions_per_hour: {cfg.max_actions_per_hour}")
|
|
48
|
+
print(f" quiet_hours: {cfg.quiet_hours_start} – {cfg.quiet_hours_end}")
|
|
49
|
+
print(f" anomaly_severity_threshold: {cfg.anomaly_severity_threshold}")
|
|
50
|
+
print(f" prediction_confidence_threshold: {cfg.prediction_confidence_threshold}")
|
|
51
|
+
print(f" goal_stale_threshold_hours: {cfg.goal_stale_threshold_hours}")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def run_autonomy_status(loop_instance=None) -> int:
|
|
55
|
+
"""Print live autonomy status.
|
|
56
|
+
|
|
57
|
+
A provided loop_instance (in-process embedding) is used directly;
|
|
58
|
+
otherwise the running sidecar is queried over HTTP. Only when no sidecar
|
|
59
|
+
is reachable do we fall back to printing config defaults, clearly
|
|
60
|
+
labelled as such.
|
|
61
|
+
"""
|
|
62
|
+
if loop_instance is not None:
|
|
63
|
+
print(json.dumps(loop_instance.status(), indent=2))
|
|
64
|
+
return 0
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
status = _request("/v1/host/autonomy/status")
|
|
68
|
+
print(json.dumps(status, indent=2))
|
|
69
|
+
return 0
|
|
70
|
+
except (urllib.error.URLError, OSError, ValueError) as exc:
|
|
71
|
+
print(f"Autonomy Loop — sidecar unreachable ({exc}); env config defaults:")
|
|
72
|
+
_print_config_defaults()
|
|
73
|
+
return 1
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def run_autonomy_cycle() -> int:
|
|
77
|
+
"""Wake the running loop (proactive mode) or run one cycle (reactive)."""
|
|
78
|
+
try:
|
|
79
|
+
out = _request("/v1/host/autonomy/cycle", method="POST")
|
|
80
|
+
print(json.dumps(out, indent=2))
|
|
81
|
+
return 0
|
|
82
|
+
except (urllib.error.URLError, OSError, ValueError) as exc:
|
|
83
|
+
print(f"cycle failed: sidecar unreachable ({exc})", file=sys.stderr)
|
|
84
|
+
return 1
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def run_autonomy_command(args: list) -> int:
|
|
88
|
+
"""Dispatch 'colony autonomy <subcommand>'."""
|
|
89
|
+
if not args:
|
|
90
|
+
print("Usage: colony autonomy <status|cycle>", file=sys.stderr)
|
|
91
|
+
return 1
|
|
92
|
+
|
|
93
|
+
sub = args[0]
|
|
94
|
+
|
|
95
|
+
if sub == "status":
|
|
96
|
+
return run_autonomy_status()
|
|
97
|
+
|
|
98
|
+
if sub == "cycle":
|
|
99
|
+
return run_autonomy_cycle()
|
|
100
|
+
|
|
101
|
+
if sub == "start":
|
|
102
|
+
# The loop starts with the sidecar; a second standalone loop against
|
|
103
|
+
# the same state would double-fire every phase.
|
|
104
|
+
print("The autonomy loop runs inside the sidecar and starts with it "
|
|
105
|
+
"(colony serve). Use 'colony autonomy status' to inspect it or "
|
|
106
|
+
"'colony autonomy cycle' to wake it.", file=sys.stderr)
|
|
107
|
+
return 1
|
|
108
|
+
|
|
109
|
+
print(f"Unknown autonomy subcommand: {sub!r}", file=sys.stderr)
|
|
110
|
+
return 1
|
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
"""ConditionWorker — poll external conditions on behalf of blocked goals.
|
|
2
|
+
|
|
3
|
+
Two consumption paths:
|
|
4
|
+
- The system-level checkers (commitment overdue flip, affect decline,
|
|
5
|
+
surprise accumulation) run hourly from the autonomy loop's
|
|
6
|
+
_phase_condition_checks — that is the LIVE path.
|
|
7
|
+
- handle_check_condition remains available as a task-queue handler for
|
|
8
|
+
per-goal external conditions (email reply, deployment health, ...), but no
|
|
9
|
+
producer schedules those jobs yet (see docs/KNOWN-GAPS.md); a goal blocked
|
|
10
|
+
on such a condition is only unblocked when something enqueues the check.
|
|
11
|
+
|
|
12
|
+
When a per-goal condition is met, the handler calls
|
|
13
|
+
goal_engine.unblock_goal(); when not yet met it returns condition_met=False
|
|
14
|
+
and the caller may reschedule.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import logging
|
|
20
|
+
from datetime import datetime, timezone, timedelta
|
|
21
|
+
from typing import Any, Dict, Optional
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
# Default polling intervals (seconds) by condition type
|
|
26
|
+
DEFAULT_INTERVALS: Dict[str, int] = {
|
|
27
|
+
"email_reply": 5 * 60, # 5 minutes
|
|
28
|
+
"deployment_health": 30, # 30 seconds
|
|
29
|
+
"delivery_status": 60 * 60, # 1 hour
|
|
30
|
+
"api_response": 60, # 1 minute
|
|
31
|
+
"custom": 5 * 60, # 5 minutes (fallback)
|
|
32
|
+
"commitment_overdue": 30 * 60, # 30 minutes
|
|
33
|
+
"affect_decline": 30 * 60, # 30 minutes
|
|
34
|
+
"surprise_accumulation": 30 * 60, # 30 minutes
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
async def handle_check_condition(
|
|
39
|
+
payload: Dict[str, Any],
|
|
40
|
+
goal_engine: Any,
|
|
41
|
+
) -> Dict[str, Any]:
|
|
42
|
+
"""Execute a single condition check and report to the goal engine.
|
|
43
|
+
|
|
44
|
+
Returns a result dict with:
|
|
45
|
+
- condition_met: bool
|
|
46
|
+
- goal_id: str
|
|
47
|
+
- details: dict (condition-specific)
|
|
48
|
+
|
|
49
|
+
If condition is not yet met, returns condition_met=False.
|
|
50
|
+
The loop's _phase_goals() picks up any newly unblocked goals on the
|
|
51
|
+
next tick.
|
|
52
|
+
"""
|
|
53
|
+
goal_id = payload["goal_id"]
|
|
54
|
+
condition_type = payload["condition_type"]
|
|
55
|
+
condition_params = payload.get("condition_params", {})
|
|
56
|
+
|
|
57
|
+
condition_checkers = {
|
|
58
|
+
"email_reply": _check_email_reply,
|
|
59
|
+
"deployment_health": _check_deployment_health,
|
|
60
|
+
"delivery_status": _check_delivery_status,
|
|
61
|
+
"api_response": _check_api_response,
|
|
62
|
+
"commitment_overdue": _check_commitment_overdue,
|
|
63
|
+
"affect_decline": _check_affect_decline,
|
|
64
|
+
"surprise_accumulation": _check_surprise_accumulation,
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
checker = condition_checkers.get(condition_type)
|
|
68
|
+
if not checker:
|
|
69
|
+
logger.warning("Unknown condition type %r for goal %s", condition_type, goal_id)
|
|
70
|
+
return {"condition_met": False, "goal_id": goal_id, "details": {}}
|
|
71
|
+
|
|
72
|
+
try:
|
|
73
|
+
result = await checker(condition_params)
|
|
74
|
+
except NotImplementedError:
|
|
75
|
+
logger.debug("Condition checker %r not yet implemented", condition_type)
|
|
76
|
+
return {"condition_met": False, "goal_id": goal_id, "details": {}}
|
|
77
|
+
except Exception as exc:
|
|
78
|
+
logger.error(
|
|
79
|
+
"Condition check %r failed for goal %s: %s",
|
|
80
|
+
condition_type,
|
|
81
|
+
goal_id,
|
|
82
|
+
exc,
|
|
83
|
+
)
|
|
84
|
+
return {"condition_met": False, "goal_id": goal_id, "details": {"error": str(exc)}}
|
|
85
|
+
|
|
86
|
+
if result.get("condition_met"):
|
|
87
|
+
try:
|
|
88
|
+
goal_engine.unblock_goal(goal_id)
|
|
89
|
+
logger.info("Condition met for goal %s — unblocked", goal_id)
|
|
90
|
+
except Exception as exc:
|
|
91
|
+
logger.warning("Could not unblock goal %s: %s", goal_id, exc)
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
"condition_met": result.get("condition_met", False),
|
|
95
|
+
"goal_id": goal_id,
|
|
96
|
+
"details": result,
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def get_check_interval(condition_type: str, deadline: Optional[datetime] = None) -> int:
|
|
101
|
+
"""Return appropriate polling interval in seconds.
|
|
102
|
+
|
|
103
|
+
If deadline is within 1 hour, returns 60s regardless of type.
|
|
104
|
+
"""
|
|
105
|
+
if deadline is not None:
|
|
106
|
+
remaining = (deadline - datetime.now(timezone.utc)).total_seconds()
|
|
107
|
+
if remaining < 3600:
|
|
108
|
+
return 60
|
|
109
|
+
|
|
110
|
+
return DEFAULT_INTERVALS.get(condition_type, DEFAULT_INTERVALS["custom"])
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
# ---------------------------------------------------------------------------
|
|
114
|
+
# Condition checker implementations
|
|
115
|
+
# ---------------------------------------------------------------------------
|
|
116
|
+
|
|
117
|
+
async def _check_deployment_health(params: dict) -> dict:
|
|
118
|
+
"""Check if a deployment endpoint returns the expected HTTP status.
|
|
119
|
+
|
|
120
|
+
params:
|
|
121
|
+
endpoint (str): HTTP URL to GET.
|
|
122
|
+
expected_status (int, default 200): Expected HTTP status code.
|
|
123
|
+
timeout_secs (float, default 10): Per-request timeout.
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
{"condition_met": bool, "status_code": int, "latency_ms": float}
|
|
127
|
+
"""
|
|
128
|
+
try:
|
|
129
|
+
import aiohttp
|
|
130
|
+
except ImportError as exc:
|
|
131
|
+
raise RuntimeError(
|
|
132
|
+
"_check_deployment_health requires 'aiohttp'. "
|
|
133
|
+
"Install with: pip install aiohttp"
|
|
134
|
+
) from exc
|
|
135
|
+
|
|
136
|
+
import time
|
|
137
|
+
|
|
138
|
+
endpoint = params["endpoint"]
|
|
139
|
+
expected = params.get("expected_status", 200)
|
|
140
|
+
timeout = params.get("timeout_secs", 10.0)
|
|
141
|
+
|
|
142
|
+
t0 = time.monotonic()
|
|
143
|
+
async with aiohttp.ClientSession() as session:
|
|
144
|
+
async with session.get(
|
|
145
|
+
endpoint, timeout=aiohttp.ClientTimeout(total=timeout)
|
|
146
|
+
) as resp:
|
|
147
|
+
latency_ms = (time.monotonic() - t0) * 1000
|
|
148
|
+
return {
|
|
149
|
+
"condition_met": resp.status == expected,
|
|
150
|
+
"status_code": resp.status,
|
|
151
|
+
"latency_ms": round(latency_ms, 2),
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
async def _check_api_response(params: dict) -> dict:
|
|
156
|
+
"""Check if an API endpoint returns an expected field value.
|
|
157
|
+
|
|
158
|
+
params:
|
|
159
|
+
url (str): URL to request.
|
|
160
|
+
method (str, default "GET"): HTTP method.
|
|
161
|
+
headers (dict, optional): Request headers.
|
|
162
|
+
body (dict, optional): JSON body (for POST/PUT).
|
|
163
|
+
expected_field (str, optional): Dot-separated JSON path, e.g. "data.status".
|
|
164
|
+
expected_value (Any, optional): Value to compare against.
|
|
165
|
+
timeout_secs (float, default 30): Per-request timeout.
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
{"condition_met": bool, "field_value": Any}
|
|
169
|
+
"""
|
|
170
|
+
try:
|
|
171
|
+
import aiohttp
|
|
172
|
+
except ImportError as exc:
|
|
173
|
+
raise RuntimeError(
|
|
174
|
+
"_check_api_response requires 'aiohttp'. "
|
|
175
|
+
"Install with: pip install aiohttp"
|
|
176
|
+
) from exc
|
|
177
|
+
|
|
178
|
+
method = params.get("method", "GET").upper()
|
|
179
|
+
url = params["url"]
|
|
180
|
+
headers = params.get("headers", {})
|
|
181
|
+
body = params.get("body")
|
|
182
|
+
expected_field = params.get("expected_field")
|
|
183
|
+
expected_value = params.get("expected_value")
|
|
184
|
+
timeout = params.get("timeout_secs", 30.0)
|
|
185
|
+
|
|
186
|
+
async with aiohttp.ClientSession() as session:
|
|
187
|
+
async with session.request(
|
|
188
|
+
method,
|
|
189
|
+
url,
|
|
190
|
+
headers=headers,
|
|
191
|
+
json=body,
|
|
192
|
+
timeout=aiohttp.ClientTimeout(total=timeout),
|
|
193
|
+
) as resp:
|
|
194
|
+
data = await resp.json()
|
|
195
|
+
|
|
196
|
+
# Traverse dot-separated field path
|
|
197
|
+
field_value = data
|
|
198
|
+
if expected_field:
|
|
199
|
+
for key in expected_field.split("."):
|
|
200
|
+
if isinstance(field_value, dict):
|
|
201
|
+
field_value = field_value.get(key)
|
|
202
|
+
else:
|
|
203
|
+
field_value = None
|
|
204
|
+
break
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
"condition_met": field_value == expected_value,
|
|
208
|
+
"field_value": field_value,
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
async def _check_email_reply(params: dict) -> dict:
|
|
213
|
+
"""Check if a reply has arrived to a specific email thread via IMAP.
|
|
214
|
+
|
|
215
|
+
params:
|
|
216
|
+
thread_id (str, optional): Message-ID to match in In-Reply-To/References.
|
|
217
|
+
expected_sender (str, optional): Match on from address (substring).
|
|
218
|
+
subject_pattern (str, optional): Regex to match subject line.
|
|
219
|
+
since_iso (str, optional): ISO timestamp — only consider messages after this.
|
|
220
|
+
|
|
221
|
+
Credentials are loaded from SecretsManager:
|
|
222
|
+
COLONY_IMAP_HOST, COLONY_IMAP_PORT, COLONY_IMAP_USERNAME, COLONY_IMAP_PASSWORD
|
|
223
|
+
|
|
224
|
+
Returns:
|
|
225
|
+
{"condition_met": bool, "message_id": str|None, "from": str|None}
|
|
226
|
+
"""
|
|
227
|
+
import re
|
|
228
|
+
from datetime import datetime
|
|
229
|
+
|
|
230
|
+
try:
|
|
231
|
+
from apsimo.secrets.manager import SecretsManager
|
|
232
|
+
from apsimo.email.providers import IMAPProvider
|
|
233
|
+
except ImportError:
|
|
234
|
+
logger.debug("email_reply check skipped — IMAP provider not installed")
|
|
235
|
+
return {
|
|
236
|
+
"condition_met": False,
|
|
237
|
+
"message_id": None,
|
|
238
|
+
"from": None,
|
|
239
|
+
"details": {"unavailable": "imap_provider_not_installed"},
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
sm = SecretsManager()
|
|
243
|
+
host = sm.get_required("COLONY_IMAP_HOST")
|
|
244
|
+
port = int(sm.get("COLONY_IMAP_PORT") or "993")
|
|
245
|
+
username = sm.get_required("COLONY_IMAP_USERNAME")
|
|
246
|
+
password = sm.get_required("COLONY_IMAP_PASSWORD")
|
|
247
|
+
|
|
248
|
+
since_str = params.get("since_iso")
|
|
249
|
+
since = datetime.fromisoformat(since_str) if since_str else None
|
|
250
|
+
|
|
251
|
+
provider = IMAPProvider(
|
|
252
|
+
host=host, port=port, username=username, password=password
|
|
253
|
+
)
|
|
254
|
+
entries = provider.fetch_recent(limit=50, since=since)
|
|
255
|
+
|
|
256
|
+
thread_id = params.get("thread_id", "")
|
|
257
|
+
expected_sender = params.get("expected_sender", "")
|
|
258
|
+
subject_pattern = params.get("subject_pattern")
|
|
259
|
+
|
|
260
|
+
for entry in entries:
|
|
261
|
+
if expected_sender and expected_sender.lower() not in entry.from_address.lower():
|
|
262
|
+
continue
|
|
263
|
+
if subject_pattern and not re.search(
|
|
264
|
+
subject_pattern, entry.subject or "", re.IGNORECASE
|
|
265
|
+
):
|
|
266
|
+
continue
|
|
267
|
+
# thread_id match: check external_id (Message-ID) as a best-effort proxy
|
|
268
|
+
if thread_id and thread_id not in (entry.external_id or ""):
|
|
269
|
+
continue
|
|
270
|
+
return {
|
|
271
|
+
"condition_met": True,
|
|
272
|
+
"message_id": entry.external_id,
|
|
273
|
+
"from": entry.from_address,
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return {"condition_met": False, "message_id": None, "from": None}
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
async def _check_delivery_status(params: dict) -> dict:
|
|
280
|
+
"""Check carrier delivery status for a tracking number.
|
|
281
|
+
|
|
282
|
+
params:
|
|
283
|
+
tracking_number (str): Carrier tracking number.
|
|
284
|
+
carrier (str): "ups" | "fedex"
|
|
285
|
+
|
|
286
|
+
Carrier API keys are loaded from SecretsManager:
|
|
287
|
+
COLONY_UPS_API_KEY, COLONY_FEDEX_API_KEY
|
|
288
|
+
|
|
289
|
+
Returns:
|
|
290
|
+
{"condition_met": bool, "status": str, "last_update": str|None}
|
|
291
|
+
"""
|
|
292
|
+
try:
|
|
293
|
+
from apsimo.secrets.manager import SecretsManager
|
|
294
|
+
except ImportError as exc:
|
|
295
|
+
raise RuntimeError(
|
|
296
|
+
"_check_delivery_status requires colony.secrets"
|
|
297
|
+
) from exc
|
|
298
|
+
|
|
299
|
+
sm = SecretsManager()
|
|
300
|
+
carrier = params.get("carrier", "").lower()
|
|
301
|
+
tracking_number = params["tracking_number"]
|
|
302
|
+
|
|
303
|
+
CARRIER_CONFIGS: dict = {
|
|
304
|
+
"ups": {
|
|
305
|
+
"url": f"https://onlinetools.ups.com/track/v1/details/{tracking_number}",
|
|
306
|
+
"key_secret": "COLONY_UPS_API_KEY",
|
|
307
|
+
"delivered_statuses": {"D"},
|
|
308
|
+
"status_path": "trackResponse.shipment.0.activity.0.status.type",
|
|
309
|
+
},
|
|
310
|
+
"fedex": {
|
|
311
|
+
"url": "https://apis.fedex.com/track/v1/trackingnumbers",
|
|
312
|
+
"key_secret": "COLONY_FEDEX_API_KEY",
|
|
313
|
+
"delivered_statuses": {"DL"},
|
|
314
|
+
"status_path": (
|
|
315
|
+
"output.completeTrackResults.0.trackResults"
|
|
316
|
+
".0.latestStatusDetail.code"
|
|
317
|
+
),
|
|
318
|
+
},
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
config = CARRIER_CONFIGS.get(carrier)
|
|
322
|
+
if not config:
|
|
323
|
+
raise ValueError(
|
|
324
|
+
f"Unsupported carrier: {carrier!r}. "
|
|
325
|
+
f"Supported: {sorted(CARRIER_CONFIGS)}"
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
api_key = sm.get(config["key_secret"])
|
|
329
|
+
if not api_key:
|
|
330
|
+
raise KeyError(
|
|
331
|
+
f"Missing carrier API key secret: {config['key_secret']}"
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
result = await _check_api_response({
|
|
335
|
+
"url": config["url"],
|
|
336
|
+
"method": "GET",
|
|
337
|
+
"headers": {"Authorization": f"Bearer {api_key}"},
|
|
338
|
+
"expected_field": config["status_path"],
|
|
339
|
+
"expected_value": None,
|
|
340
|
+
"timeout_secs": 15.0,
|
|
341
|
+
})
|
|
342
|
+
|
|
343
|
+
status_code = result.get("field_value")
|
|
344
|
+
delivered = status_code in config["delivered_statuses"]
|
|
345
|
+
return {
|
|
346
|
+
"condition_met": delivered,
|
|
347
|
+
"status": str(status_code),
|
|
348
|
+
"last_update": None,
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
async def _check_commitment_overdue(params: dict) -> dict:
|
|
353
|
+
"""Check for commitments that are past their due_at and still pending."""
|
|
354
|
+
from apsimo.api.routers.host import _commitment_store, emit
|
|
355
|
+
|
|
356
|
+
if _commitment_store is None:
|
|
357
|
+
return {"condition_met": False, "overdue_count": 0}
|
|
358
|
+
|
|
359
|
+
overdue = _commitment_store.get_overdue()
|
|
360
|
+
if not overdue:
|
|
361
|
+
return {"condition_met": False, "overdue_count": 0}
|
|
362
|
+
|
|
363
|
+
# Mark pending → overdue. get_overdue() also returns items already
|
|
364
|
+
# flipped, so guard on status or the overdue event re-fires every check.
|
|
365
|
+
for c in overdue:
|
|
366
|
+
if c.get("status") != "pending":
|
|
367
|
+
continue
|
|
368
|
+
try:
|
|
369
|
+
_commitment_store.update(c["id"], status="overdue")
|
|
370
|
+
emit("commitment.overdue", {
|
|
371
|
+
"commitment_id": c["id"],
|
|
372
|
+
"person_id": c["person_id"],
|
|
373
|
+
"description": c["description"],
|
|
374
|
+
})
|
|
375
|
+
except Exception:
|
|
376
|
+
logger.debug("commitment overdue update failed for %s", c["id"], exc_info=True)
|
|
377
|
+
|
|
378
|
+
return {
|
|
379
|
+
"condition_met": True,
|
|
380
|
+
"overdue_count": len(overdue),
|
|
381
|
+
"overdue_commitments": [
|
|
382
|
+
{"id": c["id"], "description": c["description"], "person_id": c["person_id"]}
|
|
383
|
+
for c in overdue[:5]
|
|
384
|
+
],
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
async def _check_affect_decline(params: dict) -> dict:
|
|
389
|
+
"""Check for sustained affect decline across contacts."""
|
|
390
|
+
from apsimo.api.routers.host import _affect_store
|
|
391
|
+
|
|
392
|
+
if _affect_store is None:
|
|
393
|
+
return {"condition_met": False, "declining_contacts": 0}
|
|
394
|
+
|
|
395
|
+
states = _affect_store.get_all_states()
|
|
396
|
+
declining = [s for s in states if _affect_store.detect_sustained_decline(s["contact_id"])]
|
|
397
|
+
if not declining:
|
|
398
|
+
return {"condition_met": False, "declining_contacts": 0}
|
|
399
|
+
|
|
400
|
+
try:
|
|
401
|
+
from apsimo.events.broadcaster import emit as _emit
|
|
402
|
+
for s in declining:
|
|
403
|
+
_emit("affect.sustained_decline", {
|
|
404
|
+
"contact_id": s["contact_id"],
|
|
405
|
+
"current_valence": s["current_valence"],
|
|
406
|
+
})
|
|
407
|
+
except Exception:
|
|
408
|
+
logger.debug("affect decline emit failed", exc_info=True)
|
|
409
|
+
|
|
410
|
+
return {
|
|
411
|
+
"condition_met": True,
|
|
412
|
+
"declining_contacts": len(declining),
|
|
413
|
+
"contacts": [{"contact_id": s["contact_id"], "valence": s["current_valence"]} for s in declining],
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
async def _check_surprise_accumulation(params: dict) -> dict:
|
|
418
|
+
"""Check for accumulated unresolved surprises."""
|
|
419
|
+
from apsimo.api.routers.host import _surprise_store
|
|
420
|
+
|
|
421
|
+
if _surprise_store is None:
|
|
422
|
+
return {"condition_met": False, "unresolved_count": 0}
|
|
423
|
+
|
|
424
|
+
count = _surprise_store.count_unresolved(since_hours=1)
|
|
425
|
+
if count < 5:
|
|
426
|
+
return {"condition_met": False, "unresolved_count": count}
|
|
427
|
+
|
|
428
|
+
try:
|
|
429
|
+
from apsimo.events.broadcaster import emit as _emit
|
|
430
|
+
_emit("surprise.accumulation", {"unresolved_count": count})
|
|
431
|
+
except Exception:
|
|
432
|
+
logger.debug("surprise accumulation emit failed", exc_info=True)
|
|
433
|
+
|
|
434
|
+
return {
|
|
435
|
+
"condition_met": True,
|
|
436
|
+
"unresolved_count": count,
|
|
437
|
+
}
|