colonymind 1.0.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.
- colonymind/__init__.py +40 -0
- colonymind/__main__.py +6 -0
- colonymind/agent_mailbox.py +411 -0
- colonymind/api/__init__.py +1 -0
- colonymind/api/archive_queue.py +30 -0
- colonymind/api/auth.py +466 -0
- colonymind/api/credentials.py +788 -0
- colonymind/api/errors.py +108 -0
- colonymind/api/legacy_scopes.py +324 -0
- colonymind/api/mcp.py +536 -0
- colonymind/api/middleware.py +391 -0
- colonymind/api/owner_location.py +70 -0
- colonymind/api/public_spec.py +308 -0
- colonymind/api/retired_routes.py +6 -0
- colonymind/api/routers/__init__.py +1 -0
- colonymind/api/routers/activity.py +54 -0
- colonymind/api/routers/agent_mailbox.py +94 -0
- colonymind/api/routers/branches.py +191 -0
- colonymind/api/routers/claims.py +120 -0
- colonymind/api/routers/context_card.py +34 -0
- colonymind/api/routers/curated_memory.py +186 -0
- colonymind/api/routers/devices.py +105 -0
- colonymind/api/routers/executions.py +80 -0
- colonymind/api/routers/hands.py +307 -0
- colonymind/api/routers/host.py +6807 -0
- colonymind/api/routers/instinct.py +154 -0
- colonymind/api/routers/inventory.py +113 -0
- colonymind/api/routers/mind.py +1599 -0
- colonymind/api/routers/opinions.py +139 -0
- colonymind/api/routers/owner_goals.py +208 -0
- colonymind/api/routers/people.py +689 -0
- colonymind/api/routers/secrets.py +414 -0
- colonymind/api/routers/social_state.py +256 -0
- colonymind/api/routers/temporal_followups.py +150 -0
- colonymind/api/routers/transport.py +110 -0
- colonymind/api/routers/transport_ingress_api.py +333 -0
- colonymind/api/routers/watch.py +24 -0
- colonymind/api/routers/work.py +267 -0
- colonymind/api/schemas/__init__.py +1 -0
- colonymind/api/schemas/approval_history.py +92 -0
- colonymind/api/schemas/context_card.py +16 -0
- colonymind/api/schemas/host.py +1233 -0
- colonymind/api/viewer.py +15 -0
- colonymind/atlas/__init__.py +10 -0
- colonymind/atlas/cli.py +78 -0
- colonymind/atlas/render.py +527 -0
- colonymind/atlas/site.py +444 -0
- colonymind/atlas/snapshot.py +271 -0
- colonymind/backup.py +1053 -0
- colonymind/beliefs/__init__.py +20 -0
- colonymind/beliefs/promotion.py +45 -0
- colonymind/beliefs/reextract.py +1334 -0
- colonymind/beliefs/reextract_cli.py +223 -0
- colonymind/beliefs/source_claims.py +1068 -0
- colonymind/beliefs/source_projection.py +2536 -0
- colonymind/beliefs/source_time.py +652 -0
- colonymind/beliefs/value_revision.py +124 -0
- colonymind/build_stamp.json +5 -0
- colonymind/build_stamp.py +41 -0
- colonymind/cli.py +1389 -0
- colonymind/commitments/__init__.py +18 -0
- colonymind/commitments/extract.py +1692 -0
- colonymind/commitments/parties.py +65 -0
- colonymind/commitments/store.py +1304 -0
- colonymind/commitments/work.py +96 -0
- colonymind/config.py +1245 -0
- colonymind/console/__init__.py +10 -0
- colonymind/console/app.py +269 -0
- colonymind/console/cli.py +53 -0
- colonymind/console/pages.py +487 -0
- colonymind/console/server.py +92 -0
- colonymind/console/source.py +203 -0
- colonymind/contacts/__init__.py +30 -0
- colonymind/contacts/affect.py +513 -0
- colonymind/contacts/affect_writer.py +95 -0
- colonymind/contacts/changes.py +164 -0
- colonymind/contacts/cli.py +105 -0
- colonymind/contacts/comms.py +756 -0
- colonymind/contacts/config.py +43 -0
- colonymind/contacts/continuity.py +70 -0
- colonymind/contacts/digest.py +238 -0
- colonymind/contacts/identity_links.py +313 -0
- colonymind/contacts/migrations/001_contacts_schema.sql +101 -0
- colonymind/contacts/migrations/002_trust_scopes.sql +36 -0
- colonymind/contacts/migrations/003_open_gateway_enum.sql +32 -0
- colonymind/contacts/migrations/004_contact_provision_operations.sql +4 -0
- colonymind/contacts/migrations/005_identity_links.sql +27 -0
- colonymind/contacts/migrations/006_may_contact.sql +14 -0
- colonymind/contacts/migrations/007_digest_lineage.sql +16 -0
- colonymind/contacts/migrations/008_location_report.sql +5 -0
- colonymind/contacts/models.py +337 -0
- colonymind/contacts/optout.py +78 -0
- colonymind/contacts/refresh.py +130 -0
- colonymind/contacts/source_lineage.py +112 -0
- colonymind/contacts/store.py +1873 -0
- colonymind/contacts/transport_ingress.py +475 -0
- colonymind/context_card/__init__.py +1 -0
- colonymind/context_card/builder.py +139 -0
- colonymind/context_card/inputs.py +209 -0
- colonymind/context_card/lifecycle.py +37 -0
- colonymind/context_card/measure.py +130 -0
- colonymind/context_card/refresh.py +207 -0
- colonymind/context_card/store.py +144 -0
- colonymind/credentials_cli.py +257 -0
- colonymind/demo.py +579 -0
- colonymind/devices.py +330 -0
- colonymind/devtools/__init__.py +7 -0
- colonymind/devtools/fake_model.py +313 -0
- colonymind/devtools/mind_loop_probe.py +637 -0
- colonymind/doctor.py +1248 -0
- colonymind/events/__init__.py +5 -0
- colonymind/events/activity.py +219 -0
- colonymind/events/broadcaster.py +105 -0
- colonymind/events/journal.py +848 -0
- colonymind/events/stream.py +131 -0
- colonymind/feedback/__init__.py +5 -0
- colonymind/feedback/store.py +145 -0
- colonymind/feeds/__init__.py +19 -0
- colonymind/feeds/cli.py +84 -0
- colonymind/feeds/engine.py +438 -0
- colonymind/feeds/example-feed.yaml +77 -0
- colonymind/feeds/hermes_cron.py +126 -0
- colonymind/feeds/manager.py +235 -0
- colonymind/feeds/spec.py +250 -0
- colonymind/feeds/template.py +202 -0
- colonymind/hands/__init__.py +5 -0
- colonymind/hands/admission.py +68 -0
- colonymind/hands/approvals.py +335 -0
- colonymind/hands/brief.py +134 -0
- colonymind/hands/budgets.py +66 -0
- colonymind/hands/cascade.py +34 -0
- colonymind/hands/cli.py +80 -0
- colonymind/hands/contracts.py +56 -0
- colonymind/hands/controller.py +1111 -0
- colonymind/hands/definition_queue.py +110 -0
- colonymind/hands/definitions.py +409 -0
- colonymind/hands/definitions_schema.py +129 -0
- colonymind/hands/digest.py +80 -0
- colonymind/hands/effects.py +63 -0
- colonymind/hands/evidence.py +208 -0
- colonymind/hands/health.py +91 -0
- colonymind/hands/locks.py +38 -0
- colonymind/hands/matrix.py +17 -0
- colonymind/hands/notices.py +110 -0
- colonymind/hands/obligations.py +114 -0
- colonymind/hands/provenance.py +495 -0
- colonymind/hands/retention.py +110 -0
- colonymind/hands/runtime.py +118 -0
- colonymind/hands/sessions.py +136 -0
- colonymind/hands/settings.py +28 -0
- colonymind/hands/store.py +415 -0
- colonymind/hands/types.py +405 -0
- colonymind/hands/wire.py +456 -0
- colonymind/health_counters.py +26 -0
- colonymind/identity/__init__.py +15 -0
- colonymind/identity/participants.py +237 -0
- colonymind/identity/policy.py +45 -0
- colonymind/identity/resolver.py +64 -0
- colonymind/inference_pool/__init__.py +5 -0
- colonymind/inference_pool/config.py +300 -0
- colonymind/inference_pool/scheduler.py +323 -0
- colonymind/inference_pool/server.py +603 -0
- colonymind/init.py +3454 -0
- colonymind/initiatives/__init__.py +16 -0
- colonymind/initiatives/context_freshness.py +42 -0
- colonymind/initiatives/models.py +372 -0
- colonymind/initiatives/store.py +1455 -0
- colonymind/initiatives/temporal_followup.py +362 -0
- colonymind/instance.py +102 -0
- colonymind/instinct/__init__.py +119 -0
- colonymind/instinct/calibration.py +236 -0
- colonymind/instinct/cli.py +319 -0
- colonymind/instinct/deliberate.py +184 -0
- colonymind/instinct/external.py +425 -0
- colonymind/instinct/fit.py +868 -0
- colonymind/instinct/log.py +690 -0
- colonymind/instinct/outcomes.py +1517 -0
- colonymind/instinct/points.py +347 -0
- colonymind/instinct/providers/__init__.py +78 -0
- colonymind/instinct/providers/local_decision.py +180 -0
- colonymind/instinct/providers/logits.py +342 -0
- colonymind/instinct/records/__init__.py +120 -0
- colonymind/instinct/records/local-decision-opt-out.json +58 -0
- colonymind/instinct/registry.py +232 -0
- colonymind/instinct/settings.py +254 -0
- colonymind/instinct/state.py +588 -0
- colonymind/instinct/tiers.py +658 -0
- colonymind/intelligence/__init__.py +1 -0
- colonymind/intelligence/components/__init__.py +9 -0
- colonymind/intelligence/components/preference_learner.py +496 -0
- colonymind/jobwatch/__init__.py +1 -0
- colonymind/jobwatch/activity.py +192 -0
- colonymind/jobwatch/cli.py +37 -0
- colonymind/jobwatch/network.py +22 -0
- colonymind/jobwatch/runner.py +480 -0
- colonymind/jobwatch/settings.py +69 -0
- colonymind/jobwatch/store.py +74 -0
- colonymind/loop_lag.py +78 -0
- colonymind/memory/__init__.py +1 -0
- colonymind/memory/blocks.py +423 -0
- colonymind/memory/branches.py +640 -0
- colonymind/memory/compass.py +840 -0
- colonymind/memory/curated.py +1463 -0
- colonymind/memory/curated_health.py +118 -0
- colonymind/memory/inventory.py +647 -0
- colonymind/memory/learning.py +661 -0
- colonymind/memory/live_recall.py +60 -0
- colonymind/memory/notice.py +206 -0
- colonymind/memory/producers.py +285 -0
- colonymind/memory/recall.py +661 -0
- colonymind/memory/recall_exit.py +188 -0
- colonymind/memory/recall_stats.py +98 -0
- colonymind/memory/recent.py +235 -0
- colonymind/memory/rerank_gate.py +155 -0
- colonymind/memory/search.py +412 -0
- colonymind/memory/selection.py +559 -0
- colonymind/memory/time_lane.py +462 -0
- colonymind/memory/trace.py +509 -0
- colonymind/memory/turn_budget.py +253 -0
- colonymind/memory/voice_affect.py +86 -0
- colonymind/memory/voice_recall.py +619 -0
- colonymind/memory_cli.py +154 -0
- colonymind/migrations.py +369 -0
- colonymind/mind/__init__.py +29 -0
- colonymind/mind/affect.py +979 -0
- colonymind/mind/affect_rules.py +44 -0
- colonymind/mind/allows.py +449 -0
- colonymind/mind/answer_turns.py +188 -0
- colonymind/mind/answers.py +976 -0
- colonymind/mind/approval_history.py +250 -0
- colonymind/mind/ask_origin.py +100 -0
- colonymind/mind/asks_report.py +362 -0
- colonymind/mind/audit.py +311 -0
- colonymind/mind/authority.py +1054 -0
- colonymind/mind/cli.py +603 -0
- colonymind/mind/compose.py +152 -0
- colonymind/mind/concerns.py +418 -0
- colonymind/mind/consolidate.py +1861 -0
- colonymind/mind/contradiction_keys.py +333 -0
- colonymind/mind/contradiction_keys_bounds.json +341 -0
- colonymind/mind/contradiction_keys_dev.json +66 -0
- colonymind/mind/deliberate.py +429 -0
- colonymind/mind/digest.py +102 -0
- colonymind/mind/drift.py +1201 -0
- colonymind/mind/drives.py +874 -0
- colonymind/mind/factory.py +79 -0
- colonymind/mind/gate.py +858 -0
- colonymind/mind/goals.py +271 -0
- colonymind/mind/idea_assets/calendar-planning-v1.svg +1 -0
- colonymind/mind/idea_assets/document-paperwork-v1.svg +1 -0
- colonymind/mind/idea_assets/packet-money-v1.svg +1 -0
- colonymind/mind/idea_assets/renewal-subscriptions-v1.svg +1 -0
- colonymind/mind/ideas.py +724 -0
- colonymind/mind/ideas_association.py +81 -0
- colonymind/mind/ideas_build.py +188 -0
- colonymind/mind/ideas_copy.py +205 -0
- colonymind/mind/ideas_presentation.py +31 -0
- colonymind/mind/learned.py +251 -0
- colonymind/mind/lesson_match.py +500 -0
- colonymind/mind/lesson_match_bounds.json +919 -0
- colonymind/mind/lesson_match_dev.json +87 -0
- colonymind/mind/lessons.py +1869 -0
- colonymind/mind/observations.py +415 -0
- colonymind/mind/opinions.py +808 -0
- colonymind/mind/outbox.py +978 -0
- colonymind/mind/outcomes.py +615 -0
- colonymind/mind/outreach.py +941 -0
- colonymind/mind/owner_actions.py +100 -0
- colonymind/mind/permit_asks.py +326 -0
- colonymind/mind/plain.py +419 -0
- colonymind/mind/questions.py +656 -0
- colonymind/mind/rank.py +229 -0
- colonymind/mind/reactions.py +703 -0
- colonymind/mind/skills.py +181 -0
- colonymind/mind/status.py +324 -0
- colonymind/mind/tick.py +6699 -0
- colonymind/mind/tool_origins.py +76 -0
- colonymind/mind/wishes.py +1219 -0
- colonymind/owner_goals/__init__.py +1 -0
- colonymind/owner_goals/artifacts.py +133 -0
- colonymind/owner_goals/authority.py +85 -0
- colonymind/owner_goals/hub.py +75 -0
- colonymind/owner_goals/reads.py +398 -0
- colonymind/owner_goals/schema.py +165 -0
- colonymind/owner_goals/sealing.py +197 -0
- colonymind/owner_goals/store.py +649 -0
- colonymind/owner_goals/validation.py +239 -0
- colonymind/plasticity/__init__.py +24 -0
- colonymind/plasticity/_io.py +139 -0
- colonymind/plasticity/admission.py +358 -0
- colonymind/plasticity/audience.py +111 -0
- colonymind/plasticity/budgets.py +122 -0
- colonymind/plasticity/builds.py +211 -0
- colonymind/plasticity/certificates.py +56 -0
- colonymind/plasticity/cli.py +84 -0
- colonymind/plasticity/compaction.py +62 -0
- colonymind/plasticity/comparator.py +433 -0
- colonymind/plasticity/erasure.py +488 -0
- colonymind/plasticity/forced.py +49 -0
- colonymind/plasticity/gates.py +407 -0
- colonymind/plasticity/learners/__init__.py +11 -0
- colonymind/plasticity/learners/action_success.py +586 -0
- colonymind/plasticity/learners/behaviour.py +617 -0
- colonymind/plasticity/learners/contact_reply_timing.py +539 -0
- colonymind/plasticity/learners/query_adapter.py +642 -0
- colonymind/plasticity/learners/reply_timing.py +599 -0
- colonymind/plasticity/learners/salience.py +899 -0
- colonymind/plasticity/learners/service_health.py +564 -0
- colonymind/plasticity/lineage.py +159 -0
- colonymind/plasticity/locking.py +94 -0
- colonymind/plasticity/manifests.py +196 -0
- colonymind/plasticity/nightly.py +1170 -0
- colonymind/plasticity/numeric.py +490 -0
- colonymind/plasticity/numeric_comparator.py +254 -0
- colonymind/plasticity/objectives.py +220 -0
- colonymind/plasticity/periods.py +123 -0
- colonymind/plasticity/query_adapter.py +421 -0
- colonymind/plasticity/query_log.py +235 -0
- colonymind/plasticity/recall_qualify.py +408 -0
- colonymind/plasticity/registration.py +144 -0
- colonymind/plasticity/revisions.py +443 -0
- colonymind/plasticity/rows.py +32 -0
- colonymind/plasticity/states.py +267 -0
- colonymind/plasticity/store.py +242 -0
- colonymind/plasticity/synthetic.py +383 -0
- colonymind/plasticity/teachers.py +144 -0
- colonymind/qualification/__init__.py +1 -0
- colonymind/qualification/benchmark.py +274 -0
- colonymind/qualification/benchmark_cases.py +143 -0
- colonymind/qualification/cases.py +90 -0
- colonymind/qualification/cli.py +93 -0
- colonymind/qualification/coding.py +207 -0
- colonymind/qualification/coding_sandbox.py +202 -0
- colonymind/qualification/coding_verify_worker.py +36 -0
- colonymind/qualification/coding_worker.py +98 -0
- colonymind/qualification/evidence_cases.py +185 -0
- colonymind/qualification/fixtures/coding-development.json +542 -0
- colonymind/qualification/fixtures/decision-role-screen-1/manifest.json +65 -0
- colonymind/qualification/fixtures/decision-role-screen-1/scenarios.json +2134 -0
- colonymind/qualification/fixtures/interaction-frame-a.png +0 -0
- colonymind/qualification/fixtures/interaction-frame-b.png +0 -0
- colonymind/qualification/fixtures/paired-agent-reviewed-2/manifest.json +44 -0
- colonymind/qualification/fixtures/paired-agent-reviewed-2/scenarios.json +3419 -0
- colonymind/qualification/fixtures/paired-agent-workflows-1/manifest.json +61 -0
- colonymind/qualification/fixtures/paired-agent-workflows-1/scenarios.json +4852 -0
- colonymind/qualification/fixtures/vision-arrangement.png +0 -0
- colonymind/qualification/fixtures/vision-covered-label.png +0 -0
- colonymind/qualification/fixtures/vision-labels.png +0 -0
- colonymind/qualification/formation_extended.py +213 -0
- colonymind/qualification/interaction_evidence.py +83 -0
- colonymind/qualification/memory_cases.py +222 -0
- colonymind/qualification/native.py +222 -0
- colonymind/qualification/native_authority.py +113 -0
- colonymind/qualification/native_authority_cases.py +62 -0
- colonymind/qualification/native_authority_worker.py +115 -0
- colonymind/qualification/native_coding.py +54 -0
- colonymind/qualification/native_contact_identity.py +113 -0
- colonymind/qualification/native_identity.py +51 -0
- colonymind/qualification/native_identity_cases.py +60 -0
- colonymind/qualification/native_identity_worker.py +127 -0
- colonymind/qualification/native_interactive.py +102 -0
- colonymind/qualification/native_interactive_cases.py +96 -0
- colonymind/qualification/native_interactive_worker.py +282 -0
- colonymind/qualification/native_memory.py +155 -0
- colonymind/qualification/native_memory_batch.py +136 -0
- colonymind/qualification/native_memory_cases.py +79 -0
- colonymind/qualification/native_memory_identity.py +31 -0
- colonymind/qualification/native_memory_worker.py +671 -0
- colonymind/qualification/native_perspective.py +155 -0
- colonymind/qualification/native_perspective_cases.py +49 -0
- colonymind/qualification/native_perspective_worker.py +69 -0
- colonymind/qualification/native_reasoning.py +36 -0
- colonymind/qualification/native_recovery.py +69 -0
- colonymind/qualification/native_recovery_worker.py +87 -0
- colonymind/qualification/native_semantic_recall.py +84 -0
- colonymind/qualification/native_semantic_worker.py +30 -0
- colonymind/qualification/native_unified.py +128 -0
- colonymind/qualification/native_unified_base_worker.py +75 -0
- colonymind/qualification/native_unified_cases.py +57 -0
- colonymind/qualification/native_unified_host.py +73 -0
- colonymind/qualification/native_unified_worker.py +157 -0
- colonymind/qualification/native_worker.py +136 -0
- colonymind/qualification/pack_batch.py +314 -0
- colonymind/qualification/pack_registry.py +61 -0
- colonymind/qualification/paired.py +787 -0
- colonymind/qualification/paired_arms.py +198 -0
- colonymind/qualification/paired_body.py +349 -0
- colonymind/qualification/paired_body_grading.py +488 -0
- colonymind/qualification/paired_cases.py +730 -0
- colonymind/qualification/paired_container.py +252 -0
- colonymind/qualification/paired_history.py +262 -0
- colonymind/qualification/paired_public.py +270 -0
- colonymind/qualification/paired_report.py +1350 -0
- colonymind/qualification/paired_statistics.py +195 -0
- colonymind/qualification/paired_suite.py +361 -0
- colonymind/qualification/paired_trace.py +70 -0
- colonymind/qualification/paired_transport.py +256 -0
- colonymind/qualification/paired_worker.py +1489 -0
- colonymind/qualification/paired_workflow_grading.py +93 -0
- colonymind/qualification/paired_workflow_runtime.py +454 -0
- colonymind/qualification/planning_effects.py +48 -0
- colonymind/qualification/public_export.py +392 -0
- colonymind/qualification/recall_capture.py +142 -0
- colonymind/qualification/records.py +111 -0
- colonymind/qualification/recovery_cases.py +51 -0
- colonymind/qualification/recovery_fixture.py +87 -0
- colonymind/qualification/report.py +153 -0
- colonymind/qualification/router_recovery.py +174 -0
- colonymind/qualification/router_recovery_proxy.py +81 -0
- colonymind/qualification/runner.py +394 -0
- colonymind/qualification/schema_cases.py +105 -0
- colonymind/qualification/screen_contracts.py +151 -0
- colonymind/qualification/semantic_recall_cases.py +100 -0
- colonymind/qualification/semantic_recall_host.py +186 -0
- colonymind/qualification/structured_cases.py +131 -0
- colonymind/qualification/vision_cases.py +70 -0
- colonymind/recall_cli.py +321 -0
- colonymind/redact/__init__.py +179 -0
- colonymind/removed_settings.py +112 -0
- colonymind/reports/__init__.py +7 -0
- colonymind/reports/arms.py +229 -0
- colonymind/reports/bench.py +454 -0
- colonymind/reports/claims.py +175 -0
- colonymind/reports/cli.py +187 -0
- colonymind/reports/faculties.py +1070 -0
- colonymind/reports/hands.py +51 -0
- colonymind/reports/instance.py +331 -0
- colonymind/reports/instinct.py +356 -0
- colonymind/reports/plasticity.py +319 -0
- colonymind/reports/registry.py +122 -0
- colonymind/reports/senses.py +68 -0
- colonymind/reports/text.py +152 -0
- colonymind/reports/usage.py +170 -0
- colonymind/reports/work.py +38 -0
- colonymind/reports/working_memory.py +114 -0
- colonymind/resources.py +44 -0
- colonymind/router/__init__.py +17 -0
- colonymind/router/complexity_scorer.py +137 -0
- colonymind/router/endpoints.py +156 -0
- colonymind/router/fallback.py +58 -0
- colonymind/router/functions.py +320 -0
- colonymind/router/router.py +840 -0
- colonymind/router/tiers.py +677 -0
- colonymind/router/tokens.py +45 -0
- colonymind/runtime_logging.py +195 -0
- colonymind/scope_bounds.py +7 -0
- colonymind/secrets/__init__.py +17 -0
- colonymind/secrets/claims.py +139 -0
- colonymind/secrets/cli.py +185 -0
- colonymind/secrets/config.py +133 -0
- colonymind/secrets/detect.py +21 -0
- colonymind/secrets/migrate.py +635 -0
- colonymind/secrets/refs.py +59 -0
- colonymind/secrets/saved_login.py +80 -0
- colonymind/secrets/seal.py +448 -0
- colonymind/secrets/vault.py +482 -0
- colonymind/self_model/__init__.py +10 -0
- colonymind/self_model/appraisals.py +1019 -0
- colonymind/self_model/brief.py +36 -0
- colonymind/self_model/expectations.py +1457 -0
- colonymind/self_model/journal.py +119 -0
- colonymind/self_model/judgments.py +1253 -0
- colonymind/self_model/perspective.py +200 -0
- colonymind/self_model/reply_forecasts.py +449 -0
- colonymind/self_model/runtime_forecasts.py +35 -0
- colonymind/self_model/store.py +618 -0
- colonymind/self_model/task_assessments.py +14 -0
- colonymind/server.py +744 -0
- colonymind/services/__init__.py +1 -0
- colonymind/services/instance.py +487 -0
- colonymind/session_archive/__init__.py +1 -0
- colonymind/session_archive/reparent.py +424 -0
- colonymind/session_archive/store.py +547 -0
- colonymind/setup.py +181 -0
- colonymind/telemetry.py +150 -0
- colonymind/turns/__init__.py +17 -0
- colonymind/turns/audio.py +156 -0
- colonymind/turns/board_observations.py +73 -0
- colonymind/turns/claim_keys.py +170 -0
- colonymind/turns/documents.py +239 -0
- colonymind/turns/erasure_hooks.py +28 -0
- colonymind/turns/executions.py +265 -0
- colonymind/turns/hermes_history.py +248 -0
- colonymind/turns/hermes_work.py +95 -0
- colonymind/turns/history_references.py +51 -0
- colonymind/turns/idempotency.py +1387 -0
- colonymind/turns/keywords.py +216 -0
- colonymind/turns/media.py +671 -0
- colonymind/turns/media_names.py +30 -0
- colonymind/turns/observed.py +415 -0
- colonymind/turns/projection_backlog.py +117 -0
- colonymind/turns/reported_workers.py +196 -0
- colonymind/turns/source_annotations.py +365 -0
- colonymind/turns/source_attribution.py +170 -0
- colonymind/turns/source_channels.py +88 -0
- colonymind/turns/source_read.py +406 -0
- colonymind/turns/source_replies.py +132 -0
- colonymind/turns/source_vectors.py +506 -0
- colonymind/turns/video.py +210 -0
- colonymind/twin/__init__.py +7 -0
- colonymind/twin/cli.py +231 -0
- colonymind/twin/config.py +164 -0
- colonymind/twin/primary.py +553 -0
- colonymind/twin/segment.py +127 -0
- colonymind/twin/source.py +361 -0
- colonymind/twin/standby.py +587 -0
- colonymind/twin/transport.py +152 -0
- colonymind/twin/trees.py +258 -0
- colonymind/twin/wal.py +136 -0
- colonymind/usage.py +647 -0
- colonymind/util/instance.py +39 -0
- colonymind/util/model_output.py +25 -0
- colonymind/util/session_safety.py +38 -0
- colonymind/util/state_access.py +41 -0
- colonymind/util/temporal.py +446 -0
- colonymind/vector/__init__.py +64 -0
- colonymind/vector/backfill.py +171 -0
- colonymind/vector/caption.py +114 -0
- colonymind/vector/collections.py +51 -0
- colonymind/vector/compaction.py +276 -0
- colonymind/vector/config.py +88 -0
- colonymind/vector/embedder.py +662 -0
- colonymind/vector/http_client.py +152 -0
- colonymind/vector/image_preprocess.py +459 -0
- colonymind/vector/image_store.py +271 -0
- colonymind/vector/indexes.py +219 -0
- colonymind/vector/local_embedder.py +331 -0
- colonymind/vector/migrate.py +308 -0
- colonymind/vector/multimodal_provider.py +393 -0
- colonymind/vector/multimodal_types.py +59 -0
- colonymind/vector/openai_provider.py +159 -0
- colonymind/vector/query.py +26 -0
- colonymind/vector/reranker.py +443 -0
- colonymind/vector/safety_image.py +159 -0
- colonymind/vector/scanner.py +197 -0
- colonymind/vector/store.py +796 -0
- colonymind/vector/tiers.py +263 -0
- colonymind/work/__init__.py +72 -0
- colonymind/work/__main__.py +5 -0
- colonymind/work/digest.py +13 -0
- colonymind/work/hands.py +49 -0
- colonymind/work/hermes_jobs.py +237 -0
- colonymind/work/ledger.py +229 -0
- colonymind/work/models.py +134 -0
- colonymind/work/night.py +107 -0
- colonymind/work/schema/work-envelope.json +560 -0
- colonymind/work/schema/work-event.json +602 -0
- colonymind/work/schema/work-status.json +35 -0
- colonymind/work/schema/work-titles.json +44 -0
- colonymind/work/store.py +714 -0
- colonymind/work/verdicts.py +209 -0
- colonymind/work/verification.py +87 -0
- colonymind-1.0.0.dist-info/METADATA +110 -0
- colonymind-1.0.0.dist-info/RECORD +566 -0
- colonymind-1.0.0.dist-info/WHEEL +5 -0
- colonymind-1.0.0.dist-info/entry_points.txt +3 -0
- colonymind-1.0.0.dist-info/licenses/LICENSE +21 -0
- colonymind-1.0.0.dist-info/top_level.txt +3 -0
- colonymind_hand_context/__init__.py +20 -0
- colonymind_shared/__init__.py +1 -0
- colonymind_shared/effects.py +490 -0
- colonymind_shared/host_owner.py +102 -0
- colonymind_shared/midturn.py +193 -0
- colonymind_shared/secret_detection.py +285 -0
- colonymind_shared/turn_bindings.py +84 -0
- colonymind_shared/write_origin.py +18 -0
colonymind/__init__.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""ColonyMind cognition server for agent runtimes.
|
|
2
|
+
|
|
3
|
+
A standalone FastAPI server that agent hosts (Hermes plugin, MCP harnesses,
|
|
4
|
+
REST integrations) mount via the ``/v1/host`` API surface.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
import os
|
|
9
|
+
|
|
10
|
+
# Local first: LiteLLM, which routes model requests, downloads its model price list from the internet when it is
|
|
11
|
+
# imported unless told to use the copy it ships. ColonyMind never reads prices from it, so the flag is set here,
|
|
12
|
+
# before anything of ColonyMind can import the library, and starting the sidecar makes no network request. An
|
|
13
|
+
# operator who sets it explicitly (to false, for the downloaded list) keeps that choice.
|
|
14
|
+
os.environ.setdefault("LITELLM_LOCAL_MODEL_COST_MAP", "True")
|
|
15
|
+
|
|
16
|
+
try: # single source of truth: the installed package metadata (pyproject)
|
|
17
|
+
from importlib.metadata import version as _pkg_version
|
|
18
|
+
__version__ = _pkg_version("colonymind")
|
|
19
|
+
except Exception: # editable/unbuilt checkouts without installed metadata
|
|
20
|
+
__version__ = "0.0.0+unknown"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def get_state_dir() -> Path:
|
|
24
|
+
"""Return the selected state directory without moving existing state.
|
|
25
|
+
|
|
26
|
+
The instance directory (``$COLONYMIND_HOME``, default ``~/.colonymind``)
|
|
27
|
+
holds the state once ``colonymind init`` has written ``colonymind.yaml``.
|
|
28
|
+
Unconfigured library use defaults to ``~/.colonymind/data``.
|
|
29
|
+
|
|
30
|
+
Creates the directory if it does not exist.
|
|
31
|
+
"""
|
|
32
|
+
explicit = os.environ.get("COLONYMIND_STATE_DIR") or os.environ.get("COLONYMIND_HOME")
|
|
33
|
+
if explicit:
|
|
34
|
+
path = Path(explicit).expanduser()
|
|
35
|
+
elif (Path.home() / ".colonymind" / "colonymind.yaml").is_file():
|
|
36
|
+
path = Path.home() / ".colonymind"
|
|
37
|
+
else:
|
|
38
|
+
path = Path.home() / ".colonymind" / "data"
|
|
39
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
40
|
+
return path
|
colonymind/__main__.py
ADDED
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
"""Provider-neutral retained mail. Content is data; nothing here invokes a model.
|
|
2
|
+
|
|
3
|
+
SQLite WAL transactions retain identity, content and replay tombstones together.
|
|
4
|
+
Each operation owns a connection; SQLite serializes writers across processes.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import base64
|
|
9
|
+
from contextlib import closing, contextmanager, nullcontext
|
|
10
|
+
from datetime import datetime, timezone
|
|
11
|
+
import hashlib
|
|
12
|
+
import json
|
|
13
|
+
import os
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
import sqlite3
|
|
16
|
+
import threading
|
|
17
|
+
from typing import Literal
|
|
18
|
+
from uuid import uuid4
|
|
19
|
+
|
|
20
|
+
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
|
21
|
+
|
|
22
|
+
from colonymind.util.temporal import now_utc
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class MailboxError(ValueError):
|
|
26
|
+
def __init__(self, code: str, status: int = 422):
|
|
27
|
+
super().__init__(code)
|
|
28
|
+
self.code = code
|
|
29
|
+
self.status = status
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class LocalIdentity(BaseModel):
|
|
33
|
+
model_config = ConfigDict(extra="forbid", strict=True)
|
|
34
|
+
local_part: str = Field(pattern=r"^[a-z0-9][a-z0-9-]{1,30}[a-z0-9]$", max_length=32)
|
|
35
|
+
display_name: str = Field(min_length=1, max_length=128)
|
|
36
|
+
|
|
37
|
+
@field_validator("display_name")
|
|
38
|
+
@classmethod
|
|
39
|
+
def name_present(cls, value):
|
|
40
|
+
if not value.strip() or any(ord(c) < 32 for c in value):
|
|
41
|
+
raise ValueError("invalid_display_name")
|
|
42
|
+
return value
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class IdentityUpdate(BaseModel):
|
|
46
|
+
model_config = ConfigDict(extra="forbid", strict=True)
|
|
47
|
+
display_name: str | None = Field(default=None, min_length=1, max_length=128)
|
|
48
|
+
status: Literal["unprovisioned", "active", "disabled"] | None = None
|
|
49
|
+
|
|
50
|
+
@field_validator("display_name")
|
|
51
|
+
@classmethod
|
|
52
|
+
def name_present(cls, value):
|
|
53
|
+
return LocalIdentity.name_present(value) if value is not None else value
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class InboundMessage(BaseModel):
|
|
57
|
+
"""Trusted adapter's envelope. Sender and all text remain untrusted."""
|
|
58
|
+
model_config = ConfigDict(extra="forbid")
|
|
59
|
+
delivery_id: str = Field(min_length=1, max_length=256)
|
|
60
|
+
thread_id: str = Field(pattern=r"^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$", max_length=128)
|
|
61
|
+
sender: str = Field(min_length=1, max_length=320)
|
|
62
|
+
recipients: list[str] = Field(min_length=1, max_length=100)
|
|
63
|
+
subject: str = Field(default="", max_length=998)
|
|
64
|
+
text: str = Field(default="", max_length=262144)
|
|
65
|
+
occurred_at: datetime
|
|
66
|
+
in_reply_to: str | None = Field(default=None, max_length=256)
|
|
67
|
+
|
|
68
|
+
@field_validator("occurred_at")
|
|
69
|
+
@classmethod
|
|
70
|
+
def aware_time(cls, value):
|
|
71
|
+
if value.utcoffset() is None:
|
|
72
|
+
raise ValueError("timezone_required")
|
|
73
|
+
return value.astimezone(timezone.utc)
|
|
74
|
+
|
|
75
|
+
@field_validator("recipients")
|
|
76
|
+
@classmethod
|
|
77
|
+
def bounded_addresses(cls, value):
|
|
78
|
+
if any(not address or len(address) > 320 for address in value):
|
|
79
|
+
raise ValueError("invalid_recipients")
|
|
80
|
+
return value
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _json(value):
|
|
84
|
+
return json.dumps(value, ensure_ascii=True, sort_keys=True, separators=(",", ":"))
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class MailboxStore:
|
|
88
|
+
def __init__(self, path: Path):
|
|
89
|
+
self.path = Path(path)
|
|
90
|
+
self._init_lock = threading.Lock()
|
|
91
|
+
self._write_lock = threading.Lock()
|
|
92
|
+
self._initialized = False
|
|
93
|
+
|
|
94
|
+
def _connect(self):
|
|
95
|
+
self.path.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
|
|
96
|
+
# Create privately before SQLite also creates its WAL/shared-memory files.
|
|
97
|
+
fd = os.open(self.path, os.O_CREAT | os.O_RDWR, 0o600)
|
|
98
|
+
try:
|
|
99
|
+
os.fchmod(fd, 0o600)
|
|
100
|
+
finally:
|
|
101
|
+
os.close(fd)
|
|
102
|
+
conn = sqlite3.connect(self.path, check_same_thread=False)
|
|
103
|
+
try:
|
|
104
|
+
conn.row_factory = sqlite3.Row
|
|
105
|
+
conn.execute("PRAGMA busy_timeout=5000")
|
|
106
|
+
conn.execute("PRAGMA synchronous=NORMAL")
|
|
107
|
+
conn.execute("PRAGMA foreign_keys=ON")
|
|
108
|
+
conn.execute("PRAGMA secure_delete=ON")
|
|
109
|
+
except BaseException:
|
|
110
|
+
conn.close()
|
|
111
|
+
raise
|
|
112
|
+
return conn
|
|
113
|
+
|
|
114
|
+
@staticmethod
|
|
115
|
+
def _create_tables(conn):
|
|
116
|
+
"""Initialize/migrate once, serialized with other store openers."""
|
|
117
|
+
version = conn.execute("PRAGMA user_version").fetchone()[0]
|
|
118
|
+
if version > 1:
|
|
119
|
+
raise sqlite3.DatabaseError("unsupported mailbox schema version")
|
|
120
|
+
if version == 1:
|
|
121
|
+
return
|
|
122
|
+
for statement in (
|
|
123
|
+
"""CREATE TABLE IF NOT EXISTS mailboxes (
|
|
124
|
+
mailbox_id TEXT PRIMARY KEY,
|
|
125
|
+
address TEXT NOT NULL UNIQUE,
|
|
126
|
+
display_name TEXT NOT NULL,
|
|
127
|
+
status TEXT NOT NULL CHECK(status IN ('unprovisioned', 'active', 'disabled'))
|
|
128
|
+
)""",
|
|
129
|
+
"""CREATE TABLE IF NOT EXISTS messages (
|
|
130
|
+
sequence INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
131
|
+
mailbox_id TEXT NOT NULL REFERENCES mailboxes(mailbox_id),
|
|
132
|
+
message_id TEXT NOT NULL UNIQUE,
|
|
133
|
+
delivery_id TEXT NOT NULL,
|
|
134
|
+
thread_id TEXT NOT NULL,
|
|
135
|
+
sender TEXT NOT NULL,
|
|
136
|
+
recipients TEXT NOT NULL,
|
|
137
|
+
subject TEXT NOT NULL,
|
|
138
|
+
text TEXT NOT NULL,
|
|
139
|
+
occurred_at TEXT NOT NULL,
|
|
140
|
+
in_reply_to TEXT,
|
|
141
|
+
received_at TEXT NOT NULL,
|
|
142
|
+
read INTEGER NOT NULL DEFAULT 0 CHECK(read IN (0, 1)),
|
|
143
|
+
UNIQUE(mailbox_id, delivery_id)
|
|
144
|
+
)""",
|
|
145
|
+
"""CREATE TABLE IF NOT EXISTS deliveries (
|
|
146
|
+
mailbox_id TEXT NOT NULL REFERENCES mailboxes(mailbox_id),
|
|
147
|
+
delivery_id TEXT NOT NULL,
|
|
148
|
+
message_id TEXT NOT NULL UNIQUE,
|
|
149
|
+
digest TEXT NOT NULL,
|
|
150
|
+
deleted INTEGER NOT NULL DEFAULT 0 CHECK(deleted IN (0, 1)),
|
|
151
|
+
PRIMARY KEY(mailbox_id, delivery_id)
|
|
152
|
+
)""",
|
|
153
|
+
"""CREATE TABLE IF NOT EXISTS deleted_threads (
|
|
154
|
+
mailbox_id TEXT NOT NULL REFERENCES mailboxes(mailbox_id),
|
|
155
|
+
thread_id TEXT NOT NULL,
|
|
156
|
+
PRIMARY KEY(mailbox_id, thread_id)
|
|
157
|
+
)""",
|
|
158
|
+
"CREATE INDEX IF NOT EXISTS idx_messages_thread ON messages(mailbox_id, thread_id, sequence)",
|
|
159
|
+
"CREATE INDEX IF NOT EXISTS idx_messages_unread ON messages(mailbox_id, read, thread_id)",
|
|
160
|
+
):
|
|
161
|
+
conn.execute(statement)
|
|
162
|
+
conn.execute("PRAGMA user_version=1")
|
|
163
|
+
|
|
164
|
+
def _initialize(self, conn):
|
|
165
|
+
if self._initialized:
|
|
166
|
+
return
|
|
167
|
+
with self._init_lock:
|
|
168
|
+
if self._initialized:
|
|
169
|
+
return
|
|
170
|
+
version = conn.execute("PRAGMA user_version").fetchone()[0]
|
|
171
|
+
if version > 1:
|
|
172
|
+
raise sqlite3.DatabaseError("unsupported mailbox schema version")
|
|
173
|
+
if version < 1:
|
|
174
|
+
conn.execute("PRAGMA journal_mode=WAL")
|
|
175
|
+
with conn:
|
|
176
|
+
conn.execute("BEGIN IMMEDIATE")
|
|
177
|
+
self._create_tables(conn)
|
|
178
|
+
self._initialized = True
|
|
179
|
+
|
|
180
|
+
@contextmanager
|
|
181
|
+
def _transaction(self, *, write=False, purge=False):
|
|
182
|
+
# Readers use their own WAL snapshots, without reserving a writer slot
|
|
183
|
+
# or waiting behind an in-process writer. Only initialization is shared.
|
|
184
|
+
with closing(self._connect()) as conn:
|
|
185
|
+
self._initialize(conn)
|
|
186
|
+
with self._write_lock if write else nullcontext():
|
|
187
|
+
with conn:
|
|
188
|
+
conn.execute("BEGIN IMMEDIATE" if write else "BEGIN")
|
|
189
|
+
yield conn
|
|
190
|
+
if purge:
|
|
191
|
+
# secure_delete overwrites deleted pages, but old WAL frames
|
|
192
|
+
# retain plaintext until a successful truncating checkpoint.
|
|
193
|
+
if conn.execute("PRAGMA wal_checkpoint(TRUNCATE)").fetchone()[0]:
|
|
194
|
+
raise MailboxError("mailbox_busy", 503)
|
|
195
|
+
|
|
196
|
+
@staticmethod
|
|
197
|
+
def _mailbox(conn, mailbox_id):
|
|
198
|
+
mailbox = conn.execute("SELECT * FROM mailboxes WHERE mailbox_id=?", (mailbox_id,)).fetchone()
|
|
199
|
+
if mailbox is None:
|
|
200
|
+
raise MailboxError("mailbox_not_found", 404)
|
|
201
|
+
return {key: mailbox[key] for key in sorted(mailbox.keys())}
|
|
202
|
+
|
|
203
|
+
@staticmethod
|
|
204
|
+
def _messages(conn, mailbox_id, thread_id, snapshot, position, limit):
|
|
205
|
+
rows = conn.execute(
|
|
206
|
+
"SELECT sequence, mailbox_id, message_id, thread_id, sender, recipients, subject, text, "
|
|
207
|
+
"occurred_at, in_reply_to, received_at, read FROM messages "
|
|
208
|
+
"WHERE mailbox_id=? AND thread_id=? AND sequence<=? AND sequence>? "
|
|
209
|
+
"ORDER BY sequence LIMIT ?",
|
|
210
|
+
(mailbox_id, thread_id, snapshot, position or 0, limit)).fetchall()
|
|
211
|
+
messages = []
|
|
212
|
+
for row in rows:
|
|
213
|
+
# Preserve the existing JSON projection's field order and value types.
|
|
214
|
+
message = {key: row[key] for key in sorted(row.keys())}
|
|
215
|
+
message["recipients"] = json.loads(message["recipients"])
|
|
216
|
+
message["read"] = bool(message["read"])
|
|
217
|
+
messages.append(message)
|
|
218
|
+
return messages
|
|
219
|
+
|
|
220
|
+
@staticmethod
|
|
221
|
+
def _counts(conn, mailbox_id):
|
|
222
|
+
return dict(conn.execute(
|
|
223
|
+
"SELECT COUNT(*) AS unread_count, COUNT(DISTINCT thread_id) AS unread_thread_count "
|
|
224
|
+
"FROM messages WHERE mailbox_id=? AND read=0", (mailbox_id,)).fetchone())
|
|
225
|
+
|
|
226
|
+
def identities(self):
|
|
227
|
+
with self._transaction() as conn:
|
|
228
|
+
return {"mailboxes": [{key: row[key] for key in sorted(row.keys())}
|
|
229
|
+
for row in conn.execute("SELECT * FROM mailboxes ORDER BY mailbox_id")]}
|
|
230
|
+
|
|
231
|
+
def create(self, body: LocalIdentity):
|
|
232
|
+
# The mail domain is the deployment's own value: no default, and no identity without it.
|
|
233
|
+
domain = os.environ.get("COLONYMIND_AGENT_MAIL_DOMAIN", "").strip().lower()
|
|
234
|
+
if not domain:
|
|
235
|
+
raise MailboxError("mail_domain_not_configured", 501)
|
|
236
|
+
address = body.local_part + "@" + domain
|
|
237
|
+
with self._transaction(write=True) as conn:
|
|
238
|
+
if conn.execute("SELECT 1 FROM mailboxes WHERE address=?", (address,)).fetchone():
|
|
239
|
+
raise MailboxError("address_already_registered", 409)
|
|
240
|
+
mailbox = {"mailbox_id": str(uuid4()), "address": address,
|
|
241
|
+
"display_name": body.display_name, "status": "unprovisioned"}
|
|
242
|
+
conn.execute("INSERT INTO mailboxes (mailbox_id, address, display_name, status) VALUES (?, ?, ?, ?)",
|
|
243
|
+
tuple(mailbox.values()))
|
|
244
|
+
return mailbox
|
|
245
|
+
|
|
246
|
+
def identity(self, mailbox_id):
|
|
247
|
+
with self._transaction() as conn:
|
|
248
|
+
return {**self._mailbox(conn, mailbox_id), **self._counts(conn, mailbox_id)}
|
|
249
|
+
|
|
250
|
+
def update(self, mailbox_id, body: IdentityUpdate):
|
|
251
|
+
with self._transaction(write=True) as conn:
|
|
252
|
+
mailbox = self._mailbox(conn, mailbox_id)
|
|
253
|
+
mailbox.update(body.model_dump(exclude_none=True))
|
|
254
|
+
conn.execute("UPDATE mailboxes SET display_name=?, status=? WHERE mailbox_id=?",
|
|
255
|
+
(mailbox["display_name"], mailbox["status"], mailbox_id))
|
|
256
|
+
return mailbox
|
|
257
|
+
|
|
258
|
+
def receive(self, mailbox_id: str, message: InboundMessage):
|
|
259
|
+
"""Persist an adapter-authenticated delivery; never execute or trust its text."""
|
|
260
|
+
payload = message.model_dump(mode="json")
|
|
261
|
+
digest = hashlib.sha256(_json(payload).encode()).hexdigest()
|
|
262
|
+
with self._transaction(write=True) as conn:
|
|
263
|
+
mailbox = self._mailbox(conn, mailbox_id)
|
|
264
|
+
previous = conn.execute("SELECT * FROM deliveries WHERE mailbox_id=? AND delivery_id=?",
|
|
265
|
+
(mailbox_id, message.delivery_id)).fetchone()
|
|
266
|
+
if previous:
|
|
267
|
+
if previous["digest"] != digest:
|
|
268
|
+
raise MailboxError("delivery_conflict", 409)
|
|
269
|
+
return {"message_id": previous["message_id"], "thread_id": message.thread_id,
|
|
270
|
+
"duplicate": True, "deleted": bool(previous["deleted"])}
|
|
271
|
+
if mailbox["status"] != "active":
|
|
272
|
+
raise MailboxError("mailbox_inactive", 409)
|
|
273
|
+
if mailbox["address"] not in {a.lower() for a in message.recipients}:
|
|
274
|
+
raise MailboxError("recipient_mismatch")
|
|
275
|
+
conn.execute("DELETE FROM deleted_threads WHERE mailbox_id=? AND thread_id=?",
|
|
276
|
+
(mailbox_id, message.thread_id))
|
|
277
|
+
ident = str(uuid4())
|
|
278
|
+
conn.execute(
|
|
279
|
+
"INSERT INTO messages (mailbox_id, message_id, delivery_id, thread_id, sender, recipients, "
|
|
280
|
+
"subject, text, occurred_at, in_reply_to, received_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
|
281
|
+
(mailbox_id, ident, message.delivery_id, message.thread_id, message.sender,
|
|
282
|
+
_json(message.recipients), message.subject, message.text, payload["occurred_at"],
|
|
283
|
+
message.in_reply_to, now_utc().isoformat()))
|
|
284
|
+
conn.execute("INSERT INTO deliveries (mailbox_id, delivery_id, message_id, digest) VALUES (?, ?, ?, ?)",
|
|
285
|
+
(mailbox_id, message.delivery_id, ident, digest))
|
|
286
|
+
return {"message_id": ident, "thread_id": message.thread_id,
|
|
287
|
+
"duplicate": False, "deleted": False}
|
|
288
|
+
|
|
289
|
+
@staticmethod
|
|
290
|
+
def _page(conn, mailbox_id, thread_id, limit, cursor):
|
|
291
|
+
if not 1 <= limit <= 100:
|
|
292
|
+
raise MailboxError("invalid_limit")
|
|
293
|
+
row = conn.execute("SELECT seq FROM sqlite_sequence WHERE name='messages'").fetchone()
|
|
294
|
+
sequence = row[0] if row else 0
|
|
295
|
+
snapshot, position = sequence, None
|
|
296
|
+
if cursor is not None:
|
|
297
|
+
try:
|
|
298
|
+
if not cursor or len(cursor) > 1024:
|
|
299
|
+
raise ValueError
|
|
300
|
+
raw = base64.b64decode(cursor + "=" * (-len(cursor) % 4), altchars=b"-_", validate=True)
|
|
301
|
+
resource, snapshot, position = json.loads(raw)
|
|
302
|
+
if (resource != [mailbox_id, thread_id] or type(snapshot) is not int
|
|
303
|
+
or type(position) is not int or not 0 < position <= snapshot <= sequence):
|
|
304
|
+
raise ValueError
|
|
305
|
+
except (ValueError, TypeError, UnicodeError):
|
|
306
|
+
raise MailboxError("invalid_cursor") from None
|
|
307
|
+
return snapshot, position
|
|
308
|
+
|
|
309
|
+
@staticmethod
|
|
310
|
+
def _continuation(mailbox_id, thread_id, snapshot, position, has_more):
|
|
311
|
+
cursor = None
|
|
312
|
+
if has_more:
|
|
313
|
+
cursor = base64.urlsafe_b64encode(_json([[mailbox_id, thread_id], snapshot, position]).encode()).decode().rstrip("=")
|
|
314
|
+
return {"has_more": has_more, "next_cursor": cursor}
|
|
315
|
+
|
|
316
|
+
@staticmethod
|
|
317
|
+
def _thread(row):
|
|
318
|
+
unread = row["unread_count"]
|
|
319
|
+
return {"thread_id": row["thread_id"], "subject": row["subject"], "sender": row["sender"],
|
|
320
|
+
"summary": (row["summary"] or b"").decode("utf-8", errors="ignore")[:240],
|
|
321
|
+
"message_count": row["message_count"],
|
|
322
|
+
"received_at": row["received_at"], "unread_count": unread,
|
|
323
|
+
"unread": bool(unread), "content_trust": "untrusted"}
|
|
324
|
+
|
|
325
|
+
@staticmethod
|
|
326
|
+
def _thread_rows(conn, mailbox_id, snapshot, *, thread_id=None, position=None, limit=1):
|
|
327
|
+
parameters = [mailbox_id, snapshot]
|
|
328
|
+
predicate = "mailbox_id=? AND sequence<=?"
|
|
329
|
+
if thread_id is not None:
|
|
330
|
+
predicate += " AND thread_id=?"
|
|
331
|
+
parameters.append(thread_id)
|
|
332
|
+
continuation = ""
|
|
333
|
+
if position is not None:
|
|
334
|
+
continuation = "HAVING MAX(sequence)<?"
|
|
335
|
+
parameters.append(position)
|
|
336
|
+
parameters.append(limit)
|
|
337
|
+
# Aggregate metadata before joining the bounded page to its latest mail.
|
|
338
|
+
# A UTF-8 excerpt needs at most 4 bytes per character; BLOB substr also
|
|
339
|
+
# preserves embedded NULs that SQLite's TEXT substr would cut short.
|
|
340
|
+
return conn.execute(
|
|
341
|
+
f"""WITH page AS (
|
|
342
|
+
SELECT thread_id, MAX(sequence) AS sequence, COUNT(*) AS message_count
|
|
343
|
+
FROM messages WHERE {predicate} GROUP BY thread_id {continuation}
|
|
344
|
+
ORDER BY sequence DESC LIMIT ?
|
|
345
|
+
)
|
|
346
|
+
SELECT page.sequence, latest.thread_id, latest.subject, latest.sender,
|
|
347
|
+
substr(CAST(latest.text AS BLOB), 1, 960) AS summary,
|
|
348
|
+
page.message_count, latest.received_at,
|
|
349
|
+
(SELECT COUNT(*) FROM messages AS unread
|
|
350
|
+
WHERE unread.mailbox_id=latest.mailbox_id AND unread.thread_id=latest.thread_id
|
|
351
|
+
AND unread.read=0) AS unread_count
|
|
352
|
+
FROM page JOIN messages AS latest ON latest.sequence=page.sequence
|
|
353
|
+
ORDER BY page.sequence DESC""", parameters).fetchall()
|
|
354
|
+
|
|
355
|
+
def threads(self, mailbox_id, *, limit=50, cursor=None):
|
|
356
|
+
with self._transaction() as conn:
|
|
357
|
+
self._mailbox(conn, mailbox_id)
|
|
358
|
+
snapshot, position = self._page(conn, mailbox_id, None, limit, cursor)
|
|
359
|
+
rows = self._thread_rows(conn, mailbox_id, snapshot, position=position, limit=limit + 1)
|
|
360
|
+
page = rows[:limit]
|
|
361
|
+
return {"threads": [self._thread(row) for row in page], **self._counts(conn, mailbox_id),
|
|
362
|
+
**self._continuation(mailbox_id, None, snapshot,
|
|
363
|
+
page[-1]["sequence"] if page else 0, len(rows) > limit)}
|
|
364
|
+
|
|
365
|
+
def thread(self, mailbox_id, thread_id, *, limit=50, cursor=None):
|
|
366
|
+
with self._transaction() as conn:
|
|
367
|
+
self._mailbox(conn, mailbox_id)
|
|
368
|
+
snapshot, position = self._page(conn, mailbox_id, thread_id, limit, cursor)
|
|
369
|
+
rows = self._thread_rows(conn, mailbox_id, snapshot, thread_id=thread_id)
|
|
370
|
+
if not rows:
|
|
371
|
+
raise MailboxError("thread_not_found", 404)
|
|
372
|
+
page = self._messages(conn, mailbox_id, thread_id, snapshot, position, limit)
|
|
373
|
+
last = page[-1]["sequence"] if page else position or 0
|
|
374
|
+
has_more = bool(conn.execute(
|
|
375
|
+
"SELECT EXISTS(SELECT 1 FROM messages WHERE mailbox_id=? AND thread_id=? "
|
|
376
|
+
"AND sequence>? AND sequence<=?)", (mailbox_id, thread_id, last, snapshot)).fetchone()[0])
|
|
377
|
+
return {"thread": self._thread(rows[0]), "messages": [
|
|
378
|
+
{**{key: value for key, value in row.items() if key not in ("sequence", "delivery_id")},
|
|
379
|
+
"content_trust": "untrusted"} for row in page],
|
|
380
|
+
**self._continuation(mailbox_id, thread_id, snapshot, last, has_more)}
|
|
381
|
+
|
|
382
|
+
def read(self, mailbox_id, thread_id, through_message_id):
|
|
383
|
+
with self._transaction(write=True) as conn:
|
|
384
|
+
self._mailbox(conn, mailbox_id)
|
|
385
|
+
if not conn.execute("SELECT 1 FROM messages WHERE mailbox_id=? AND thread_id=?",
|
|
386
|
+
(mailbox_id, thread_id)).fetchone():
|
|
387
|
+
raise MailboxError("thread_not_found", 404)
|
|
388
|
+
marker = conn.execute(
|
|
389
|
+
"SELECT sequence FROM messages WHERE mailbox_id=? AND thread_id=? AND message_id=?",
|
|
390
|
+
(mailbox_id, thread_id, through_message_id)).fetchone()
|
|
391
|
+
if marker is None:
|
|
392
|
+
raise MailboxError("invalid_read_marker")
|
|
393
|
+
changed = conn.execute(
|
|
394
|
+
"UPDATE messages SET read=1 WHERE mailbox_id=? AND thread_id=? AND sequence<=? AND read=0",
|
|
395
|
+
(mailbox_id, thread_id, marker["sequence"])).rowcount
|
|
396
|
+
return {"thread_id": thread_id, "read_count": changed, **self._counts(conn, mailbox_id)}
|
|
397
|
+
|
|
398
|
+
def delete(self, mailbox_id, thread_id):
|
|
399
|
+
with self._transaction(write=True, purge=True) as conn:
|
|
400
|
+
self._mailbox(conn, mailbox_id)
|
|
401
|
+
parameters = (mailbox_id, thread_id)
|
|
402
|
+
present = conn.execute("SELECT 1 FROM messages WHERE mailbox_id=? AND thread_id=?", parameters).fetchone()
|
|
403
|
+
deleted = conn.execute("SELECT 1 FROM deleted_threads WHERE mailbox_id=? AND thread_id=?", parameters).fetchone()
|
|
404
|
+
if not present and not deleted:
|
|
405
|
+
raise MailboxError("thread_not_found", 404)
|
|
406
|
+
conn.execute("UPDATE deliveries SET deleted=1 WHERE mailbox_id=? AND message_id IN "
|
|
407
|
+
"(SELECT message_id FROM messages WHERE mailbox_id=? AND thread_id=?)",
|
|
408
|
+
(mailbox_id, mailbox_id, thread_id))
|
|
409
|
+
conn.execute("DELETE FROM messages WHERE mailbox_id=? AND thread_id=?", parameters)
|
|
410
|
+
conn.execute("INSERT OR IGNORE INTO deleted_threads (mailbox_id, thread_id) VALUES (?, ?)", parameters)
|
|
411
|
+
return {"thread_id": thread_id, "deleted": True, **self._counts(conn, mailbox_id)}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# api
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Deployment engine: durable pull queue only; all job logic runs in Hermes."""
|
|
2
|
+
import asyncio
|
|
3
|
+
|
|
4
|
+
from colonymind.session_archive.store import ArchiveError
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ArchiveQueueClient:
|
|
8
|
+
def __init__(self, store, *, timeout=15):
|
|
9
|
+
self.store, self.timeout = store, timeout
|
|
10
|
+
|
|
11
|
+
def available(self):
|
|
12
|
+
return self.store.worker_available()
|
|
13
|
+
|
|
14
|
+
def __call__(self, *, phase, **kwargs):
|
|
15
|
+
# Receipt reads consult only the overlay; no gateway code is imported.
|
|
16
|
+
if phase != 'validate':
|
|
17
|
+
raise ArchiveError('source_unavailable')
|
|
18
|
+
|
|
19
|
+
async def archive(self, session_id, body, authority):
|
|
20
|
+
await asyncio.to_thread(self.store.enqueue, session_id, body, authority)
|
|
21
|
+
deadline = asyncio.get_running_loop().time() + self.timeout
|
|
22
|
+
while True:
|
|
23
|
+
result = await asyncio.to_thread(self.store.plan_result, body['operation_id'])
|
|
24
|
+
if result is not None:
|
|
25
|
+
if 'error' in result:
|
|
26
|
+
raise ArchiveError(result['error'], **{k: v for k, v in result.items() if k != 'error'})
|
|
27
|
+
return result
|
|
28
|
+
if asyncio.get_running_loop().time() >= deadline:
|
|
29
|
+
raise ArchiveError('source_unavailable', pending=True, operation_id=body['operation_id'])
|
|
30
|
+
await asyncio.sleep(.1)
|