@team-agent/installer 0.2.10 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.lock +744 -0
- package/Cargo.toml +34 -0
- package/crates/team-agent/Cargo.toml +33 -0
- package/crates/team-agent/src/cli/adapters.rs +1343 -0
- package/crates/team-agent/src/cli/diagnose.rs +554 -0
- package/crates/team-agent/src/cli/emit.rs +1077 -0
- package/crates/team-agent/src/cli/helpers.rs +88 -0
- package/crates/team-agent/src/cli/leader.rs +216 -0
- package/crates/team-agent/src/cli/mod.rs +1141 -0
- package/crates/team-agent/src/cli/profile.rs +306 -0
- package/crates/team-agent/src/cli/send.rs +215 -0
- package/crates/team-agent/src/cli/status.rs +179 -0
- package/crates/team-agent/src/cli/status_port.rs +502 -0
- package/crates/team-agent/src/cli/tests/base.rs +616 -0
- package/crates/team-agent/src/cli/tests/compile.rs +96 -0
- package/crates/team-agent/src/cli/tests/divergence.rs +509 -0
- package/crates/team-agent/src/cli/tests/lane_c.rs +333 -0
- package/crates/team-agent/src/cli/tests/leader_watch.rs +395 -0
- package/crates/team-agent/src/cli/tests/main_preserved.rs +675 -0
- package/crates/team-agent/src/cli/tests/missing_subcommands.rs +390 -0
- package/crates/team-agent/src/cli/tests/mod.rs +97 -0
- package/crates/team-agent/src/cli/tests/peer_allow.rs +137 -0
- package/crates/team-agent/src/cli/tests/repair_state_byte_lock.rs +302 -0
- package/crates/team-agent/src/cli/tests/run_delegation.rs +305 -0
- package/crates/team-agent/src/cli/tests/status_send.rs +385 -0
- package/crates/team-agent/src/cli/tests/verb_profile.rs +182 -0
- package/crates/team-agent/src/cli/tests/verb_settle.rs +236 -0
- package/crates/team-agent/src/cli/tests/verb_validate.rs +184 -0
- package/crates/team-agent/src/cli/types.rs +605 -0
- package/crates/team-agent/src/compiler/tests.rs +701 -0
- package/crates/team-agent/src/compiler.rs +489 -0
- package/crates/team-agent/src/coordinator/backoff.rs +153 -0
- package/crates/team-agent/src/coordinator/health.rs +436 -0
- package/crates/team-agent/src/coordinator/mod.rs +80 -0
- package/crates/team-agent/src/coordinator/orphan.rs +179 -0
- package/crates/team-agent/src/coordinator/tests/abnormal.rs +255 -0
- package/crates/team-agent/src/coordinator/tests/basics.rs +262 -0
- package/crates/team-agent/src/coordinator/tests/daemon.rs +323 -0
- package/crates/team-agent/src/coordinator/tests/health_sync.rs +263 -0
- package/crates/team-agent/src/coordinator/tests/main_preserved.rs +136 -0
- package/crates/team-agent/src/coordinator/tests/mod.rs +310 -0
- package/crates/team-agent/src/coordinator/tests/spine.rs +261 -0
- package/crates/team-agent/src/coordinator/tests/takeover.rs +227 -0
- package/crates/team-agent/src/coordinator/tests/tick_core.rs +256 -0
- package/crates/team-agent/src/coordinator/tests/watch.rs +167 -0
- package/crates/team-agent/src/coordinator/tick.rs +2032 -0
- package/crates/team-agent/src/coordinator/types.rs +584 -0
- package/crates/team-agent/src/db/migration.rs +716 -0
- package/crates/team-agent/src/db/mod.rs +23 -0
- package/crates/team-agent/src/db/schema.rs +378 -0
- package/crates/team-agent/src/event_log.rs +375 -0
- package/crates/team-agent/src/fake_worker.rs +253 -0
- package/crates/team-agent/src/leader/helpers.rs +190 -0
- package/crates/team-agent/src/leader/inject.rs +33 -0
- package/crates/team-agent/src/leader/lease.rs +1063 -0
- package/crates/team-agent/src/leader/mod.rs +99 -0
- package/crates/team-agent/src/leader/owner_bind.rs +292 -0
- package/crates/team-agent/src/leader/rediscover/tests.rs +525 -0
- package/crates/team-agent/src/leader/rediscover.rs +1099 -0
- package/crates/team-agent/src/leader/start.rs +273 -0
- package/crates/team-agent/src/leader/takeover.rs +235 -0
- package/crates/team-agent/src/leader/tests/basics.rs +183 -0
- package/crates/team-agent/src/leader/tests/byte_findings.rs +234 -0
- package/crates/team-agent/src/leader/tests/identity.rs +206 -0
- package/crates/team-agent/src/leader/tests/idle.rs +271 -0
- package/crates/team-agent/src/leader/tests/lease_api.rs +225 -0
- package/crates/team-agent/src/leader/tests/lease_claim.rs +253 -0
- package/crates/team-agent/src/leader/tests/mod.rs +125 -0
- package/crates/team-agent/src/leader/tests/rediscover.rs +351 -0
- package/crates/team-agent/src/leader/tests/wake_start_owner.rs +204 -0
- package/crates/team-agent/src/leader/types.rs +487 -0
- package/crates/team-agent/src/lib.rs +85 -0
- package/crates/team-agent/src/lifecycle/display.rs +228 -0
- package/crates/team-agent/src/lifecycle/helpers.rs +112 -0
- package/crates/team-agent/src/lifecycle/launch/plan.rs +227 -0
- package/crates/team-agent/src/lifecycle/launch.rs +1833 -0
- package/crates/team-agent/src/lifecycle/mod.rs +62 -0
- package/crates/team-agent/src/lifecycle/restart/agent.rs +533 -0
- package/crates/team-agent/src/lifecycle/restart/common.rs +517 -0
- package/crates/team-agent/src/lifecycle/restart/orchestrator.rs +41 -0
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +268 -0
- package/crates/team-agent/src/lifecycle/restart/remove.rs +780 -0
- package/crates/team-agent/src/lifecycle/restart/selection.rs +208 -0
- package/crates/team-agent/src/lifecycle/restart/team_state.rs +242 -0
- package/crates/team-agent/src/lifecycle/restart.rs +76 -0
- package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +455 -0
- package/crates/team-agent/src/lifecycle/tests/core.rs +989 -0
- package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +583 -0
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +933 -0
- package/crates/team-agent/src/lifecycle/tests/main_preserved.rs +265 -0
- package/crates/team-agent/src/lifecycle/tests.rs +27 -0
- package/crates/team-agent/src/lifecycle/types.rs +685 -0
- package/crates/team-agent/src/main.rs +41 -0
- package/crates/team-agent/src/mcp_server/helpers.rs +228 -0
- package/crates/team-agent/src/mcp_server/mod.rs +183 -0
- package/crates/team-agent/src/mcp_server/normalize.rs +312 -0
- package/crates/team-agent/src/mcp_server/tests/golden.rs +283 -0
- package/crates/team-agent/src/mcp_server/tests/normalize.rs +244 -0
- package/crates/team-agent/src/mcp_server/tests/scoped.rs +189 -0
- package/crates/team-agent/src/mcp_server/tests/send.rs +222 -0
- package/crates/team-agent/src/mcp_server/tests/tools.rs +158 -0
- package/crates/team-agent/src/mcp_server/tests/wire.rs +159 -0
- package/crates/team-agent/src/mcp_server/tests.rs +38 -0
- package/crates/team-agent/src/mcp_server/tools.rs +603 -0
- package/crates/team-agent/src/mcp_server/types.rs +421 -0
- package/crates/team-agent/src/mcp_server/wire.rs +388 -0
- package/crates/team-agent/src/message_store.rs +767 -0
- package/crates/team-agent/src/messaging/activity.rs +433 -0
- package/crates/team-agent/src/messaging/delivery.rs +542 -0
- package/crates/team-agent/src/messaging/helpers.rs +209 -0
- package/crates/team-agent/src/messaging/leader_receiver.rs +340 -0
- package/crates/team-agent/src/messaging/mod.rs +147 -0
- package/crates/team-agent/src/messaging/peers.rs +32 -0
- package/crates/team-agent/src/messaging/results.rs +537 -0
- package/crates/team-agent/src/messaging/scheduler.rs +344 -0
- package/crates/team-agent/src/messaging/selftest.rs +100 -0
- package/crates/team-agent/src/messaging/send.rs +582 -0
- package/crates/team-agent/src/messaging/tests/basic.rs +357 -0
- package/crates/team-agent/src/messaging/tests/main_preserved.rs +122 -0
- package/crates/team-agent/src/messaging/tests/mod.rs +293 -0
- package/crates/team-agent/src/messaging/tests/runtime.rs +1422 -0
- package/crates/team-agent/src/messaging/tests/spine.rs +437 -0
- package/crates/team-agent/src/messaging/trust.rs +192 -0
- package/crates/team-agent/src/messaging/types.rs +355 -0
- package/crates/team-agent/src/messaging/watchers.rs +591 -0
- package/crates/team-agent/src/model/enums.rs +311 -0
- package/crates/team-agent/src/model/errors.rs +17 -0
- package/crates/team-agent/src/model/ids.rs +155 -0
- package/crates/team-agent/src/model/mod.rs +22 -0
- package/crates/team-agent/src/model/paths.rs +228 -0
- package/crates/team-agent/src/model/permissions.rs +567 -0
- package/crates/team-agent/src/model/routing.rs +340 -0
- package/crates/team-agent/src/model/spec.rs +680 -0
- package/crates/team-agent/src/model/task_graph.rs +380 -0
- package/crates/team-agent/src/model/testdata/fuzz.golden.yaml +43 -0
- package/crates/team-agent/src/model/testdata/fuzz.yaml +43 -0
- package/crates/team-agent/src/model/testdata/spec_invalid_a.yaml +207 -0
- package/crates/team-agent/src/model/testdata/team.spec.golden.yaml +206 -0
- package/crates/team-agent/src/model/testdata/team.spec.yaml +206 -0
- package/crates/team-agent/src/model/yaml/tests.rs +288 -0
- package/crates/team-agent/src/model/yaml.rs +800 -0
- package/crates/team-agent/src/packaging/install.rs +305 -0
- package/crates/team-agent/src/packaging/migrate.rs +30 -0
- package/crates/team-agent/src/packaging/mod.rs +82 -0
- package/crates/team-agent/src/packaging/repair.rs +24 -0
- package/crates/team-agent/src/packaging/tests.rs +829 -0
- package/crates/team-agent/src/packaging/types.rs +369 -0
- package/crates/team-agent/src/provider/adapter.rs +801 -0
- package/crates/team-agent/src/provider/approvals/mod.rs +2 -0
- package/crates/team-agent/src/provider/approvals/parsing.rs +452 -0
- package/crates/team-agent/src/provider/approvals/runtime_prompts.rs +163 -0
- package/crates/team-agent/src/provider/classify.rs +456 -0
- package/crates/team-agent/src/provider/faults.rs +136 -0
- package/crates/team-agent/src/provider/helpers.rs +41 -0
- package/crates/team-agent/src/provider/mod.rs +53 -0
- package/crates/team-agent/src/provider/startup_prompt.rs +423 -0
- package/crates/team-agent/src/provider/tests/adapter.rs +239 -0
- package/crates/team-agent/src/provider/tests/classify.rs +240 -0
- package/crates/team-agent/src/provider/tests/faults.rs +120 -0
- package/crates/team-agent/src/provider/tests/idle.rs +208 -0
- package/crates/team-agent/src/provider/tests/wire.rs +213 -0
- package/crates/team-agent/src/provider/tests.rs +31 -0
- package/crates/team-agent/src/provider/types.rs +424 -0
- package/crates/team-agent/src/state/identity.rs +656 -0
- package/crates/team-agent/src/state/mod.rs +58 -0
- package/crates/team-agent/src/state/owner_gate.rs +423 -0
- package/crates/team-agent/src/state/persist.rs +712 -0
- package/crates/team-agent/src/state/projection.rs +657 -0
- package/crates/team-agent/src/state/selector.rs +105 -0
- package/crates/team-agent/src/state/testdata/state-rich.canonical.json +133 -0
- package/crates/team-agent/src/tmux_backend/tests.rs +586 -0
- package/crates/team-agent/src/tmux_backend.rs +758 -0
- package/crates/team-agent/src/transport/test_support.rs +252 -0
- package/crates/team-agent/src/transport/tests/behavior.rs +327 -0
- package/crates/team-agent/src/transport/tests/mod.rs +199 -0
- package/crates/team-agent/src/transport/tests/wire.rs +527 -0
- package/crates/team-agent/src/transport.rs +774 -0
- package/npm/install.mjs +90 -106
- package/package.json +15 -13
- package/crates/team-agent-core/Cargo.toml +0 -12
- package/crates/team-agent-core/src/lib.rs +0 -332
- package/crates/team-agent-core/src/main.rs +0 -152
- package/pyproject.toml +0 -18
- package/scripts/install.py +0 -88
- package/scripts/run_regression_tests.py +0 -83
- package/src/team_agent/__init__.py +0 -3
- package/src/team_agent/__main__.py +0 -5
- package/src/team_agent/_legacy_pane_discovery.py +0 -186
- package/src/team_agent/abnormal_track.py +0 -253
- package/src/team_agent/approvals/__init__.py +0 -65
- package/src/team_agent/approvals/constants.py +0 -6
- package/src/team_agent/approvals/parsing.py +0 -176
- package/src/team_agent/approvals/runtime_prompts.py +0 -171
- package/src/team_agent/approvals/status.py +0 -176
- package/src/team_agent/cli/__init__.py +0 -137
- package/src/team_agent/cli/commands.py +0 -481
- package/src/team_agent/cli/e2e.py +0 -202
- package/src/team_agent/cli/helpers.py +0 -226
- package/src/team_agent/cli/parser.py +0 -540
- package/src/team_agent/compiler.py +0 -334
- package/src/team_agent/coordinator/__init__.py +0 -53
- package/src/team_agent/coordinator/__main__.py +0 -83
- package/src/team_agent/coordinator/lifecycle.py +0 -363
- package/src/team_agent/coordinator/metadata.py +0 -61
- package/src/team_agent/coordinator/paths.py +0 -17
- package/src/team_agent/diagnose/__init__.py +0 -48
- package/src/team_agent/diagnose/checks.py +0 -101
- package/src/team_agent/diagnose/comms.py +0 -213
- package/src/team_agent/diagnose/health.py +0 -241
- package/src/team_agent/diagnose/orphan_cleanup.py +0 -364
- package/src/team_agent/diagnose/preflight.py +0 -194
- package/src/team_agent/diagnose/quick_start.py +0 -324
- package/src/team_agent/display/__init__.py +0 -92
- package/src/team_agent/display/adaptive.py +0 -511
- package/src/team_agent/display/backend.py +0 -46
- package/src/team_agent/display/close.py +0 -154
- package/src/team_agent/display/ghostty.py +0 -77
- package/src/team_agent/display/rebuild.py +0 -102
- package/src/team_agent/display/tiling.py +0 -156
- package/src/team_agent/display/worker_window.py +0 -114
- package/src/team_agent/display/workspace.py +0 -382
- package/src/team_agent/errors.py +0 -10
- package/src/team_agent/events.py +0 -84
- package/src/team_agent/fake_worker.py +0 -80
- package/src/team_agent/idle_predicate.py +0 -200
- package/src/team_agent/idle_takeover.py +0 -59
- package/src/team_agent/idle_takeover_wiring.py +0 -111
- package/src/team_agent/launch/__init__.py +0 -41
- package/src/team_agent/launch/bootstrap.py +0 -85
- package/src/team_agent/launch/config.py +0 -106
- package/src/team_agent/launch/core.py +0 -301
- package/src/team_agent/launch/requirements.py +0 -57
- package/src/team_agent/leader/__init__.py +0 -926
- package/src/team_agent/leader_binding.py +0 -183
- package/src/team_agent/lifecycle/__init__.py +0 -5
- package/src/team_agent/lifecycle/agents.py +0 -278
- package/src/team_agent/lifecycle/operations.py +0 -411
- package/src/team_agent/lifecycle/paste_buffer_hygiene.py +0 -39
- package/src/team_agent/lifecycle/start.py +0 -363
- package/src/team_agent/mcp_server/__init__.py +0 -42
- package/src/team_agent/mcp_server/__main__.py +0 -7
- package/src/team_agent/mcp_server/contracts.py +0 -148
- package/src/team_agent/mcp_server/normalize.py +0 -257
- package/src/team_agent/mcp_server/server.py +0 -150
- package/src/team_agent/mcp_server/tools.py +0 -352
- package/src/team_agent/message_store/__init__.py +0 -23
- package/src/team_agent/message_store/agent_health.py +0 -113
- package/src/team_agent/message_store/core.py +0 -497
- package/src/team_agent/message_store/leader_notification_log.py +0 -198
- package/src/team_agent/message_store/result_watchers.py +0 -251
- package/src/team_agent/message_store/schema.py +0 -308
- package/src/team_agent/message_store/schema_migration.py +0 -448
- package/src/team_agent/messaging/__init__.py +0 -1
- package/src/team_agent/messaging/activity_detector.py +0 -254
- package/src/team_agent/messaging/delivery.py +0 -473
- package/src/team_agent/messaging/deps.py +0 -247
- package/src/team_agent/messaging/idle_alerts.py +0 -423
- package/src/team_agent/messaging/internal_delivery.py +0 -46
- package/src/team_agent/messaging/leader.py +0 -497
- package/src/team_agent/messaging/leader_api_errors.py +0 -216
- package/src/team_agent/messaging/leader_panes.py +0 -673
- package/src/team_agent/messaging/owner_bypass.py +0 -29
- package/src/team_agent/messaging/result_delivery.py +0 -539
- package/src/team_agent/messaging/results.py +0 -447
- package/src/team_agent/messaging/scheduler.py +0 -450
- package/src/team_agent/messaging/send.py +0 -532
- package/src/team_agent/messaging/session_drift.py +0 -94
- package/src/team_agent/messaging/tmux_io.py +0 -506
- package/src/team_agent/messaging/tmux_prompt.py +0 -338
- package/src/team_agent/messaging/trust_auto_answer.py +0 -52
- package/src/team_agent/orchestrator/__init__.py +0 -376
- package/src/team_agent/orchestrator/plan.py +0 -122
- package/src/team_agent/orchestrator/state.py +0 -128
- package/src/team_agent/paths.py +0 -45
- package/src/team_agent/permissions.py +0 -123
- package/src/team_agent/profiles/__init__.py +0 -82
- package/src/team_agent/profiles/constants.py +0 -19
- package/src/team_agent/profiles/core.py +0 -407
- package/src/team_agent/profiles/helpers.py +0 -69
- package/src/team_agent/profiles/provider_env.py +0 -188
- package/src/team_agent/profiles/smoke.py +0 -201
- package/src/team_agent/provider_cli/__init__.py +0 -43
- package/src/team_agent/provider_cli/adapter.py +0 -172
- package/src/team_agent/provider_cli/base.py +0 -48
- package/src/team_agent/provider_cli/claude.py +0 -457
- package/src/team_agent/provider_cli/codex.py +0 -336
- package/src/team_agent/provider_cli/copilot.py +0 -8
- package/src/team_agent/provider_cli/fake.py +0 -39
- package/src/team_agent/provider_cli/gemini.py +0 -95
- package/src/team_agent/provider_cli/opencode.py +0 -8
- package/src/team_agent/provider_cli/prompt.py +0 -62
- package/src/team_agent/provider_cli/registry.py +0 -18
- package/src/team_agent/provider_cli/unsupported.py +0 -32
- package/src/team_agent/provider_state/README.md +0 -78
- package/src/team_agent/provider_state/__init__.py +0 -86
- package/src/team_agent/provider_state/claude.py +0 -86
- package/src/team_agent/provider_state/codex.py +0 -84
- package/src/team_agent/provider_state/common.py +0 -207
- package/src/team_agent/provider_state/registry.py +0 -118
- package/src/team_agent/providers.py +0 -163
- package/src/team_agent/quality_gates.py +0 -104
- package/src/team_agent/restart/__init__.py +0 -34
- package/src/team_agent/restart/orchestration.py +0 -554
- package/src/team_agent/restart/selection.py +0 -89
- package/src/team_agent/restart/snapshot.py +0 -70
- package/src/team_agent/routing.py +0 -84
- package/src/team_agent/runtime.py +0 -1239
- package/src/team_agent/rust_core.py +0 -327
- package/src/team_agent/sessions/__init__.py +0 -25
- package/src/team_agent/sessions/capture.py +0 -143
- package/src/team_agent/sessions/inventory.py +0 -44
- package/src/team_agent/sessions/resume.py +0 -135
- package/src/team_agent/simple_yaml.py +0 -236
- package/src/team_agent/spec.py +0 -370
- package/src/team_agent/state.py +0 -602
- package/src/team_agent/status/__init__.py +0 -63
- package/src/team_agent/status/approvals.py +0 -52
- package/src/team_agent/status/compact.py +0 -158
- package/src/team_agent/status/constants.py +0 -18
- package/src/team_agent/status/inbox.py +0 -58
- package/src/team_agent/status/peek.py +0 -117
- package/src/team_agent/status/queries.py +0 -199
- package/src/team_agent/task_graph.py +0 -80
- package/src/team_agent/terminal.py +0 -57
- package/src/team_agent/wake.py +0 -58
- package/src/team_agent/watch/__init__.py +0 -145
|
@@ -1,198 +0,0 @@
|
|
|
1
|
-
"""Atomic exactly-once dedupe at the leader-pane injection boundary.
|
|
2
|
-
|
|
3
|
-
The current key is (result_id, owner_team_id, owner_epoch). The legacy
|
|
4
|
-
leader_session_uuid argument is retained as nullable audit/compatibility data.
|
|
5
|
-
"""
|
|
6
|
-
from __future__ import annotations
|
|
7
|
-
|
|
8
|
-
from contextlib import closing
|
|
9
|
-
from datetime import datetime, timedelta, timezone
|
|
10
|
-
import sqlite3
|
|
11
|
-
import time
|
|
12
|
-
from typing import Any
|
|
13
|
-
import zlib
|
|
14
|
-
|
|
15
|
-
from team_agent.message_store.schema_migration import MANAGED_TABLE_LAYOUTS
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
LEADER_NOTIFICATION_SELECT = ", ".join(MANAGED_TABLE_LAYOUTS["leader_notification_log"])
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def _sqlite_locked(exc: sqlite3.OperationalError) -> bool:
|
|
22
|
-
message = str(exc).lower()
|
|
23
|
-
return (
|
|
24
|
-
"database is locked" in message
|
|
25
|
-
or "database table is locked" in message
|
|
26
|
-
or "database schema is locked" in message
|
|
27
|
-
)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def claim_leader_notification_delivery(
|
|
31
|
-
store: Any,
|
|
32
|
-
*,
|
|
33
|
-
result_id: str,
|
|
34
|
-
leader_session_uuid: str | None = None,
|
|
35
|
-
owner_epoch: int | None = None,
|
|
36
|
-
proposed_message_id: str,
|
|
37
|
-
envelope_hash: str,
|
|
38
|
-
owner_team_id: str | None,
|
|
39
|
-
pane_id: str | None,
|
|
40
|
-
) -> dict[str, Any]:
|
|
41
|
-
"""Atomic claim. INSERT OR IGNORE rowcount=1 means this caller won."""
|
|
42
|
-
team_key = owner_team_id or ""
|
|
43
|
-
if owner_epoch is None:
|
|
44
|
-
owner_epoch = _legacy_epoch_from_uuid(leader_session_uuid)
|
|
45
|
-
delay = 0.05
|
|
46
|
-
row = None
|
|
47
|
-
for attempt in range(6):
|
|
48
|
-
now = datetime.now(timezone.utc).isoformat()
|
|
49
|
-
try:
|
|
50
|
-
with closing(store.connect()) as conn:
|
|
51
|
-
with conn:
|
|
52
|
-
cur = conn.execute(
|
|
53
|
-
"insert or ignore into leader_notification_log("
|
|
54
|
-
" result_id, owner_team_id, owner_epoch, leader_session_uuid,"
|
|
55
|
-
" notified_message_id, notified_at, leader_pane_id_at_notify, envelope_content_hash"
|
|
56
|
-
") values (?, ?, ?, ?, ?, ?, ?, ?)",
|
|
57
|
-
(
|
|
58
|
-
result_id, team_key, int(owner_epoch), leader_session_uuid,
|
|
59
|
-
proposed_message_id, now, pane_id, envelope_hash,
|
|
60
|
-
),
|
|
61
|
-
)
|
|
62
|
-
if cur.rowcount == 1:
|
|
63
|
-
_remember_row(store, {
|
|
64
|
-
"result_id": result_id,
|
|
65
|
-
"owner_team_id": team_key,
|
|
66
|
-
"owner_epoch": int(owner_epoch),
|
|
67
|
-
"leader_session_uuid": leader_session_uuid,
|
|
68
|
-
"notified_message_id": proposed_message_id,
|
|
69
|
-
"notified_at": now,
|
|
70
|
-
"leader_pane_id_at_notify": pane_id,
|
|
71
|
-
"envelope_content_hash": envelope_hash,
|
|
72
|
-
})
|
|
73
|
-
return {
|
|
74
|
-
"status": "claimed_by_you",
|
|
75
|
-
"notified_message_id": proposed_message_id,
|
|
76
|
-
"notified_at": now,
|
|
77
|
-
"envelope_content_hash": envelope_hash,
|
|
78
|
-
}
|
|
79
|
-
row = conn.execute(
|
|
80
|
-
"select notified_message_id, notified_at, envelope_content_hash, "
|
|
81
|
-
"leader_pane_id_at_notify from leader_notification_log "
|
|
82
|
-
"where result_id = ? and owner_team_id = ? and owner_epoch = ?",
|
|
83
|
-
(result_id, team_key, int(owner_epoch)),
|
|
84
|
-
).fetchone()
|
|
85
|
-
break
|
|
86
|
-
except sqlite3.OperationalError as exc:
|
|
87
|
-
if not _sqlite_locked(exc) or attempt == 5:
|
|
88
|
-
raise
|
|
89
|
-
time.sleep(delay)
|
|
90
|
-
delay *= 2
|
|
91
|
-
if row is None:
|
|
92
|
-
# Should not happen (INSERT OR IGNORE returned 0 → row must exist), but be defensive.
|
|
93
|
-
return {"status": "claimed_by_you", "notified_message_id": proposed_message_id,
|
|
94
|
-
"notified_at": now, "envelope_content_hash": envelope_hash}
|
|
95
|
-
return {
|
|
96
|
-
"status": "already_notified_by",
|
|
97
|
-
"notified_message_id": row["notified_message_id"],
|
|
98
|
-
"notified_at": row["notified_at"],
|
|
99
|
-
"envelope_content_hash": row["envelope_content_hash"],
|
|
100
|
-
"leader_pane_id_at_notify": row["leader_pane_id_at_notify"],
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
def peek_leader_notification(
|
|
105
|
-
store: Any,
|
|
106
|
-
*,
|
|
107
|
-
result_id: str,
|
|
108
|
-
leader_session_uuid: str | None = None,
|
|
109
|
-
owner_team_id: str | None = None,
|
|
110
|
-
owner_epoch: int | None = None,
|
|
111
|
-
) -> dict[str, Any] | None:
|
|
112
|
-
"""Read-only fast-path peek (Stage 12). Returns the existing log row for
|
|
113
|
-
(result_id, leader_session_uuid) or None. Used by notify_result_watchers to short-
|
|
114
|
-
circuit before calling deliver_stored_message; the authoritative atomic claim still
|
|
115
|
-
happens at the _send_to_leader_receiver injection boundary."""
|
|
116
|
-
team_key = owner_team_id or ""
|
|
117
|
-
if owner_epoch is None:
|
|
118
|
-
owner_epoch = _legacy_epoch_from_uuid(leader_session_uuid)
|
|
119
|
-
with closing(store.connect()) as conn:
|
|
120
|
-
if owner_team_id is None and leader_session_uuid:
|
|
121
|
-
row = conn.execute(
|
|
122
|
-
"select notified_message_id, notified_at, envelope_content_hash, "
|
|
123
|
-
"leader_pane_id_at_notify, owner_team_id from leader_notification_log "
|
|
124
|
-
"where result_id = ? and leader_session_uuid = ? order by notified_at limit 1",
|
|
125
|
-
(result_id, leader_session_uuid),
|
|
126
|
-
).fetchone()
|
|
127
|
-
else:
|
|
128
|
-
row = conn.execute(
|
|
129
|
-
"select notified_message_id, notified_at, envelope_content_hash, "
|
|
130
|
-
"leader_pane_id_at_notify, owner_team_id from leader_notification_log "
|
|
131
|
-
"where result_id = ? and owner_team_id = ? and owner_epoch = ?",
|
|
132
|
-
(result_id, team_key, int(owner_epoch)),
|
|
133
|
-
).fetchone()
|
|
134
|
-
if row is None:
|
|
135
|
-
return None
|
|
136
|
-
return {
|
|
137
|
-
"notified_message_id": row["notified_message_id"],
|
|
138
|
-
"notified_at": row["notified_at"],
|
|
139
|
-
"envelope_content_hash": row["envelope_content_hash"],
|
|
140
|
-
"leader_pane_id_at_notify": row["leader_pane_id_at_notify"],
|
|
141
|
-
"owner_team_id": row["owner_team_id"],
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
def _legacy_epoch_from_uuid(leader_session_uuid: str | None) -> int:
|
|
146
|
-
value = str(leader_session_uuid or "")
|
|
147
|
-
return int(zlib.crc32(value.encode("utf-8")) & 0x7FFFFFFF)
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
def prune_leader_notification_log(store: Any, *, max_age_hours: int = 24) -> int:
|
|
151
|
-
"""Coordinator-tick maintenance: drop rows older than max_age_hours. Cheap, bounded."""
|
|
152
|
-
cutoff = (datetime.now(timezone.utc) - timedelta(hours=max_age_hours)).isoformat()
|
|
153
|
-
with closing(store.connect()) as conn:
|
|
154
|
-
with conn:
|
|
155
|
-
cur = conn.execute(
|
|
156
|
-
"delete from leader_notification_log where notified_at < ?",
|
|
157
|
-
(cutoff,),
|
|
158
|
-
)
|
|
159
|
-
return cur.rowcount or 0
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
def leader_notification_log_rows(store: Any, *, owner_team_id: str | None = None) -> list[dict[str, Any]]:
|
|
163
|
-
"""Test/diagnostic accessor. Returns all rows (optionally team-scoped)."""
|
|
164
|
-
try:
|
|
165
|
-
with closing(store.connect()) as conn:
|
|
166
|
-
if owner_team_id is None:
|
|
167
|
-
rows = conn.execute(
|
|
168
|
-
f"select {LEADER_NOTIFICATION_SELECT} from leader_notification_log order by notified_at"
|
|
169
|
-
).fetchall()
|
|
170
|
-
else:
|
|
171
|
-
rows = conn.execute(
|
|
172
|
-
f"select {LEADER_NOTIFICATION_SELECT} from leader_notification_log where owner_team_id = ? "
|
|
173
|
-
"or owner_team_id is null order by notified_at",
|
|
174
|
-
(owner_team_id,),
|
|
175
|
-
).fetchall()
|
|
176
|
-
return [dict(row) for row in rows]
|
|
177
|
-
except sqlite3.OperationalError:
|
|
178
|
-
remembered = list(getattr(store, "_leader_notification_log_rows", []))
|
|
179
|
-
if owner_team_id is not None:
|
|
180
|
-
remembered = [row for row in remembered if row.get("owner_team_id") in {owner_team_id, None}]
|
|
181
|
-
return remembered
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
def _remember_row(store: Any, row: dict[str, Any]) -> None:
|
|
185
|
-
rows = list(getattr(store, "_leader_notification_log_rows", []))
|
|
186
|
-
rows.append(row)
|
|
187
|
-
try:
|
|
188
|
-
setattr(store, "_leader_notification_log_rows", rows)
|
|
189
|
-
except Exception:
|
|
190
|
-
pass
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
__all__ = [
|
|
194
|
-
"claim_leader_notification_delivery",
|
|
195
|
-
"peek_leader_notification",
|
|
196
|
-
"prune_leader_notification_log",
|
|
197
|
-
"leader_notification_log_rows",
|
|
198
|
-
]
|
|
@@ -1,251 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import uuid
|
|
4
|
-
from contextlib import closing
|
|
5
|
-
from typing import Any
|
|
6
|
-
|
|
7
|
-
from team_agent.message_store.schema_migration import MANAGED_TABLE_LAYOUTS
|
|
8
|
-
from team_agent.message_store.schema import utcnow
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
RESULT_WATCHER_SELECT = ", ".join(MANAGED_TABLE_LAYOUTS["result_watchers"])
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def create_result_watcher(
|
|
15
|
-
self,
|
|
16
|
-
task_id: str | None,
|
|
17
|
-
agent_id: str | None,
|
|
18
|
-
message_id: str | None,
|
|
19
|
-
leader_id: str = "leader",
|
|
20
|
-
owner_team_id: str | None = None,
|
|
21
|
-
) -> str:
|
|
22
|
-
watcher_id = f"watch_{uuid.uuid4().hex[:12]}"
|
|
23
|
-
with closing(self.connect()) as conn:
|
|
24
|
-
with conn:
|
|
25
|
-
conn.execute(
|
|
26
|
-
"""
|
|
27
|
-
insert into result_watchers(
|
|
28
|
-
watcher_id, owner_team_id, task_id, agent_id, message_id, leader_id, status, created_at
|
|
29
|
-
)
|
|
30
|
-
values (?, ?, ?, ?, ?, ?, 'pending', ?)
|
|
31
|
-
""",
|
|
32
|
-
(watcher_id, owner_team_id, task_id, agent_id, message_id, leader_id, utcnow()),
|
|
33
|
-
)
|
|
34
|
-
return watcher_id
|
|
35
|
-
|
|
36
|
-
def pending_result_watchers(self, owner_team_id: str | None = None) -> list[dict[str, Any]]:
|
|
37
|
-
with closing(self.connect()) as conn:
|
|
38
|
-
if owner_team_id is None:
|
|
39
|
-
rows = conn.execute(
|
|
40
|
-
f"select {RESULT_WATCHER_SELECT} from result_watchers where status = 'pending' order by created_at"
|
|
41
|
-
).fetchall()
|
|
42
|
-
else:
|
|
43
|
-
rows = conn.execute(
|
|
44
|
-
f"""
|
|
45
|
-
select {RESULT_WATCHER_SELECT} from result_watchers
|
|
46
|
-
where status = 'pending' and (owner_team_id = ? or owner_team_id is null)
|
|
47
|
-
order by created_at
|
|
48
|
-
""",
|
|
49
|
-
(owner_team_id,),
|
|
50
|
-
).fetchall()
|
|
51
|
-
return [dict(row) for row in rows]
|
|
52
|
-
|
|
53
|
-
def retryable_result_watchers(self) -> list[dict[str, Any]]:
|
|
54
|
-
with closing(self.connect()) as conn:
|
|
55
|
-
rows = conn.execute(
|
|
56
|
-
f"select {RESULT_WATCHER_SELECT} from result_watchers where status in ('pending', 'notify_failed') order by created_at"
|
|
57
|
-
).fetchall()
|
|
58
|
-
return [dict(row) for row in rows]
|
|
59
|
-
|
|
60
|
-
def result_watchers(self, owner_team_id: str | None = None) -> list[dict[str, Any]]:
|
|
61
|
-
with closing(self.connect()) as conn:
|
|
62
|
-
if owner_team_id is None:
|
|
63
|
-
rows = conn.execute(f"select {RESULT_WATCHER_SELECT} from result_watchers order by created_at").fetchall()
|
|
64
|
-
else:
|
|
65
|
-
rows = conn.execute(
|
|
66
|
-
f"select {RESULT_WATCHER_SELECT} from result_watchers where owner_team_id = ? or owner_team_id is null order by created_at",
|
|
67
|
-
(owner_team_id,),
|
|
68
|
-
).fetchall()
|
|
69
|
-
return [dict(row) for row in rows]
|
|
70
|
-
|
|
71
|
-
def mark_result_watcher(
|
|
72
|
-
self,
|
|
73
|
-
watcher_id: str,
|
|
74
|
-
status: str,
|
|
75
|
-
result_id: str | None = None,
|
|
76
|
-
notified_message_id: str | None = None,
|
|
77
|
-
error: str | None = None,
|
|
78
|
-
) -> None:
|
|
79
|
-
with closing(self.connect()) as conn:
|
|
80
|
-
with conn:
|
|
81
|
-
conn.execute(
|
|
82
|
-
"""
|
|
83
|
-
update result_watchers
|
|
84
|
-
set status = ?,
|
|
85
|
-
completed_at = ?,
|
|
86
|
-
result_id = coalesce(?, result_id),
|
|
87
|
-
notified_message_id = coalesce(?, notified_message_id),
|
|
88
|
-
error = coalesce(?, error)
|
|
89
|
-
where watcher_id = ?
|
|
90
|
-
""",
|
|
91
|
-
(status, utcnow(), result_id, notified_message_id, error, watcher_id),
|
|
92
|
-
)
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
def requeue_delivery_exhausted_watchers(self) -> list[str]:
|
|
96
|
-
with closing(self.connect()) as conn:
|
|
97
|
-
with conn:
|
|
98
|
-
rows = conn.execute(
|
|
99
|
-
"select watcher_id from result_watchers where status = 'delivery_exhausted'"
|
|
100
|
-
).fetchall()
|
|
101
|
-
watcher_ids = [row["watcher_id"] for row in rows]
|
|
102
|
-
if watcher_ids:
|
|
103
|
-
# Phase D hotfix-3 (78055bc) cleared notified_message_id here; Gap 32 dedupe
|
|
104
|
-
# reverses that — preserve notified_message_id so the retry path can re-confirm
|
|
105
|
-
# (or skip if the same result_id was already injected on a different pane_id).
|
|
106
|
-
conn.execute(
|
|
107
|
-
"update result_watchers "
|
|
108
|
-
"set status = 'notify_failed', error = null, completed_at = null "
|
|
109
|
-
"where status = 'delivery_exhausted'"
|
|
110
|
-
)
|
|
111
|
-
return watcher_ids
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
def claim_leader_notification(
|
|
115
|
-
store: Any,
|
|
116
|
-
owner_team_id: str | None,
|
|
117
|
-
result_id: str | None,
|
|
118
|
-
watcher_id: str,
|
|
119
|
-
proposed_token: str,
|
|
120
|
-
) -> dict[str, Any]:
|
|
121
|
-
"""DEPRECATED (Stage 12 roundtable retirement). The watcher-table UPSERT did not
|
|
122
|
-
actually prevent duplicate leader-pane injections in Mac mini real flow because two
|
|
123
|
-
independent code paths (scheduled_event branch + result_watchers branch) emit
|
|
124
|
-
deliver_attempt without coordinating at the watcher level. Replaced by
|
|
125
|
-
leader_notification_log.claim_leader_notification_delivery consulted inside
|
|
126
|
-
_send_to_leader_receiver. Kept here as a no-op shim so legacy callers / tests that
|
|
127
|
-
still import this symbol don't crash on import — but it does NOT perform a claim and
|
|
128
|
-
should NOT be used in new code."""
|
|
129
|
-
if not result_id:
|
|
130
|
-
return {"status": "deprecated_noop", "canonical_message_id": None}
|
|
131
|
-
return {"status": "deprecated_noop", "canonical_message_id": None}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
def _claim_leader_notification_disabled_impl( # legacy reference for archaeology
|
|
135
|
-
store: Any,
|
|
136
|
-
owner_team_id: str | None,
|
|
137
|
-
result_id: str | None,
|
|
138
|
-
watcher_id: str,
|
|
139
|
-
proposed_token: str,
|
|
140
|
-
) -> dict[str, Any]:
|
|
141
|
-
if not result_id:
|
|
142
|
-
return {"status": "no_result_id", "canonical_message_id": None}
|
|
143
|
-
with closing(store.connect()) as conn:
|
|
144
|
-
conn.isolation_level = None
|
|
145
|
-
try:
|
|
146
|
-
conn.execute("BEGIN IMMEDIATE")
|
|
147
|
-
if owner_team_id is None:
|
|
148
|
-
sibling = conn.execute(
|
|
149
|
-
"select notified_message_id from result_watchers "
|
|
150
|
-
"where result_id = ? and notified_message_id is not null "
|
|
151
|
-
"order by coalesce(completed_at, created_at) limit 1",
|
|
152
|
-
(result_id,),
|
|
153
|
-
).fetchone()
|
|
154
|
-
else:
|
|
155
|
-
sibling = conn.execute(
|
|
156
|
-
"select notified_message_id from result_watchers "
|
|
157
|
-
"where result_id = ? and notified_message_id is not null "
|
|
158
|
-
"and (owner_team_id = ? or owner_team_id is null) "
|
|
159
|
-
"order by coalesce(completed_at, created_at) limit 1",
|
|
160
|
-
(result_id, owner_team_id),
|
|
161
|
-
).fetchone()
|
|
162
|
-
if sibling and sibling["notified_message_id"]:
|
|
163
|
-
conn.execute("COMMIT")
|
|
164
|
-
return {"status": "already_notified_by", "canonical_message_id": sibling["notified_message_id"]}
|
|
165
|
-
cur = conn.execute(
|
|
166
|
-
"update result_watchers "
|
|
167
|
-
"set notified_message_id = ?, result_id = coalesce(result_id, ?) "
|
|
168
|
-
"where watcher_id = ? and notified_message_id is null",
|
|
169
|
-
(proposed_token, result_id, watcher_id),
|
|
170
|
-
)
|
|
171
|
-
if cur.rowcount == 1:
|
|
172
|
-
conn.execute("COMMIT")
|
|
173
|
-
return {"status": "claimed_by_you", "canonical_message_id": proposed_token}
|
|
174
|
-
row = conn.execute(
|
|
175
|
-
"select notified_message_id from result_watchers where watcher_id = ?",
|
|
176
|
-
(watcher_id,),
|
|
177
|
-
).fetchone()
|
|
178
|
-
conn.execute("COMMIT")
|
|
179
|
-
return {
|
|
180
|
-
"status": "already_notified_by",
|
|
181
|
-
"canonical_message_id": (row["notified_message_id"] if row else None) or None,
|
|
182
|
-
}
|
|
183
|
-
except Exception:
|
|
184
|
-
try:
|
|
185
|
-
conn.execute("ROLLBACK")
|
|
186
|
-
except Exception:
|
|
187
|
-
pass
|
|
188
|
-
raise
|
|
189
|
-
finally:
|
|
190
|
-
conn.isolation_level = "" # restore default
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
def release_leader_notification_claim(
|
|
194
|
-
store: Any,
|
|
195
|
-
watcher_id: str,
|
|
196
|
-
expected_token: str,
|
|
197
|
-
) -> bool:
|
|
198
|
-
"""Release a sentinel claim after delivery failure so the next retry can re-claim.
|
|
199
|
-
Returns True iff we released the claim we owned (rowcount == 1)."""
|
|
200
|
-
with closing(store.connect()) as conn:
|
|
201
|
-
with conn:
|
|
202
|
-
cur = conn.execute(
|
|
203
|
-
"update result_watchers set notified_message_id = null "
|
|
204
|
-
"where watcher_id = ? and notified_message_id = ?",
|
|
205
|
-
(watcher_id, expected_token),
|
|
206
|
-
)
|
|
207
|
-
return cur.rowcount == 1
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
def promote_leader_notification_id(
|
|
211
|
-
store: Any,
|
|
212
|
-
watcher_id: str,
|
|
213
|
-
sentinel_token: str,
|
|
214
|
-
real_message_id: str,
|
|
215
|
-
) -> bool:
|
|
216
|
-
"""After successful delivery, replace the sentinel claim with the real message_id.
|
|
217
|
-
Returns True iff the promotion succeeded (rowcount == 1)."""
|
|
218
|
-
with closing(store.connect()) as conn:
|
|
219
|
-
with conn:
|
|
220
|
-
cur = conn.execute(
|
|
221
|
-
"update result_watchers set notified_message_id = ? "
|
|
222
|
-
"where watcher_id = ? and notified_message_id = ?",
|
|
223
|
-
(real_message_id, watcher_id, sentinel_token),
|
|
224
|
-
)
|
|
225
|
-
return cur.rowcount == 1
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
def leader_notified_message_id_for_result(
|
|
229
|
-
store: Any,
|
|
230
|
-
owner_team_id: str | None,
|
|
231
|
-
result_id: str | None,
|
|
232
|
-
) -> str | None:
|
|
233
|
-
if not result_id:
|
|
234
|
-
return None
|
|
235
|
-
with closing(store.connect()) as conn:
|
|
236
|
-
if owner_team_id is None:
|
|
237
|
-
row = conn.execute(
|
|
238
|
-
"select notified_message_id from result_watchers "
|
|
239
|
-
"where result_id = ? and notified_message_id is not null "
|
|
240
|
-
"order by coalesce(completed_at, created_at) limit 1",
|
|
241
|
-
(result_id,),
|
|
242
|
-
).fetchone()
|
|
243
|
-
else:
|
|
244
|
-
row = conn.execute(
|
|
245
|
-
"select notified_message_id from result_watchers "
|
|
246
|
-
"where result_id = ? and notified_message_id is not null "
|
|
247
|
-
"and (owner_team_id = ? or owner_team_id is null) "
|
|
248
|
-
"order by coalesce(completed_at, created_at) limit 1",
|
|
249
|
-
(result_id, owner_team_id),
|
|
250
|
-
).fetchone()
|
|
251
|
-
return row["notified_message_id"] if row else None
|