@team-agent/installer 0.1.11 → 0.2.1
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/crates/team-agent-core/src/lib.rs +50 -5
- package/package.json +1 -1
- package/schemas/team.schema.json +1 -0
- package/src/team_agent/approvals/__init__.py +65 -0
- package/src/team_agent/approvals/constants.py +6 -0
- package/src/team_agent/approvals/parsing.py +176 -0
- package/src/team_agent/approvals/runtime_prompts.py +171 -0
- package/src/team_agent/approvals/status.py +165 -0
- package/src/team_agent/cli/__init__.py +137 -0
- package/src/team_agent/cli/commands.py +339 -0
- package/src/team_agent/cli/e2e.py +202 -0
- package/src/team_agent/cli/helpers.py +137 -0
- package/src/team_agent/cli/parser.py +477 -0
- package/src/team_agent/compiler.py +98 -33
- package/src/team_agent/coordinator/__init__.py +53 -0
- package/src/team_agent/{coordinator.py → coordinator/__main__.py} +3 -1
- package/src/team_agent/coordinator/lifecycle.py +334 -0
- package/src/team_agent/coordinator/metadata.py +61 -0
- package/src/team_agent/coordinator/paths.py +17 -0
- package/src/team_agent/diagnose/__init__.py +48 -0
- package/src/team_agent/diagnose/checks.py +101 -0
- package/src/team_agent/diagnose/health.py +241 -0
- package/src/team_agent/diagnose/preflight.py +194 -0
- package/src/team_agent/diagnose/quick_start.py +233 -0
- package/src/team_agent/display/__init__.py +61 -0
- package/src/team_agent/display/close.py +147 -0
- package/src/team_agent/display/ghostty.py +77 -0
- package/src/team_agent/display/worker_window.py +110 -0
- package/src/team_agent/display/workspace.py +473 -0
- package/src/team_agent/launch/__init__.py +41 -0
- package/src/team_agent/launch/bootstrap.py +85 -0
- package/src/team_agent/launch/config.py +106 -0
- package/src/team_agent/launch/core.py +291 -0
- package/src/team_agent/launch/requirements.py +57 -0
- package/src/team_agent/leader/__init__.py +320 -0
- package/src/team_agent/lifecycle/__init__.py +5 -0
- package/src/team_agent/lifecycle/agents.py +226 -0
- package/src/team_agent/lifecycle/operations.py +321 -0
- package/src/team_agent/lifecycle/paste_buffer_hygiene.py +39 -0
- package/src/team_agent/lifecycle/start.py +363 -0
- package/src/team_agent/mcp_server/__init__.py +42 -0
- package/src/team_agent/mcp_server/__main__.py +7 -0
- package/src/team_agent/mcp_server/contracts.py +148 -0
- package/src/team_agent/mcp_server/normalize.py +257 -0
- package/src/team_agent/mcp_server/server.py +150 -0
- package/src/team_agent/mcp_server/tools.py +205 -0
- package/src/team_agent/message_store/__init__.py +23 -0
- package/src/team_agent/message_store/agent_health.py +109 -0
- package/src/team_agent/{message_store.py → message_store/core.py} +188 -245
- package/src/team_agent/message_store/result_watchers.py +102 -0
- package/src/team_agent/message_store/schema.py +266 -0
- package/src/team_agent/messaging/__init__.py +1 -0
- package/src/team_agent/messaging/activity_detector.py +190 -0
- package/src/team_agent/messaging/delivery.py +138 -0
- package/src/team_agent/messaging/deps.py +263 -0
- package/src/team_agent/messaging/idle_alerts.py +323 -0
- package/src/team_agent/messaging/internal_delivery.py +46 -0
- package/src/team_agent/messaging/leader.py +317 -0
- package/src/team_agent/messaging/leader_panes.py +343 -0
- package/src/team_agent/messaging/owner_bypass.py +29 -0
- package/src/team_agent/messaging/result_delivery.py +300 -0
- package/src/team_agent/messaging/results.py +456 -0
- package/src/team_agent/messaging/scheduler.py +428 -0
- package/src/team_agent/messaging/send.py +500 -0
- package/src/team_agent/messaging/session_drift.py +94 -0
- package/src/team_agent/messaging/tmux_io.py +337 -0
- package/src/team_agent/messaging/tmux_prompt.py +229 -0
- package/src/team_agent/orchestrator/__init__.py +376 -0
- package/src/team_agent/orchestrator/plan.py +122 -0
- package/src/team_agent/orchestrator/state.py +128 -0
- package/src/team_agent/profiles/__init__.py +82 -0
- package/src/team_agent/profiles/constants.py +19 -0
- package/src/team_agent/profiles/core.py +407 -0
- package/src/team_agent/profiles/helpers.py +69 -0
- package/src/team_agent/profiles/provider_env.py +188 -0
- package/src/team_agent/profiles/smoke.py +201 -0
- package/src/team_agent/provider_cli/__init__.py +43 -0
- package/src/team_agent/provider_cli/adapter.py +167 -0
- package/src/team_agent/provider_cli/base.py +48 -0
- package/src/team_agent/provider_cli/claude.py +457 -0
- package/src/team_agent/provider_cli/codex.py +319 -0
- package/src/team_agent/provider_cli/copilot.py +8 -0
- package/src/team_agent/provider_cli/fake.py +39 -0
- package/src/team_agent/provider_cli/gemini.py +95 -0
- package/src/team_agent/provider_cli/opencode.py +8 -0
- package/src/team_agent/provider_cli/prompt.py +62 -0
- package/src/team_agent/provider_cli/registry.py +18 -0
- package/src/team_agent/provider_cli/unsupported.py +32 -0
- package/src/team_agent/providers.py +67 -949
- package/src/team_agent/quality_gates.py +104 -0
- package/src/team_agent/restart/__init__.py +34 -0
- package/src/team_agent/restart/orchestration.py +328 -0
- package/src/team_agent/restart/selection.py +89 -0
- package/src/team_agent/restart/snapshot.py +70 -0
- package/src/team_agent/runtime.py +809 -5892
- package/src/team_agent/rust_core.py +22 -5
- package/src/team_agent/sessions/__init__.py +25 -0
- package/src/team_agent/sessions/capture.py +93 -0
- package/src/team_agent/sessions/inventory.py +44 -0
- package/src/team_agent/sessions/resume.py +135 -0
- package/src/team_agent/spec.py +3 -1
- package/src/team_agent/state.py +218 -4
- package/src/team_agent/status/__init__.py +63 -0
- package/src/team_agent/status/approvals.py +52 -0
- package/src/team_agent/status/compact.py +158 -0
- package/src/team_agent/status/constants.py +18 -0
- package/src/team_agent/status/inbox.py +28 -0
- package/src/team_agent/status/peek.py +117 -0
- package/src/team_agent/status/queries.py +168 -0
- package/src/team_agent/terminal.py +57 -0
- package/src/team_agent/cli.py +0 -858
- package/src/team_agent/mcp_server.py +0 -579
- package/src/team_agent/profiles.py +0 -882
|
@@ -8,15 +8,16 @@ from datetime import datetime, timedelta, timezone
|
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
from typing import Any
|
|
10
10
|
|
|
11
|
+
from . import agent_health as _agent_health
|
|
12
|
+
from . import result_watchers as _result_watchers
|
|
13
|
+
from .schema import SCHEMA_VERSION, initialize_schema, utcnow
|
|
11
14
|
from team_agent.paths import runtime_dir
|
|
12
15
|
from team_agent.spec import validate_result_envelope
|
|
13
16
|
|
|
14
17
|
|
|
15
|
-
def utcnow() -> str:
|
|
16
|
-
return datetime.now(timezone.utc).isoformat()
|
|
17
|
-
|
|
18
|
-
|
|
19
18
|
class MessageStore:
|
|
19
|
+
SCHEMA_VERSION = SCHEMA_VERSION
|
|
20
|
+
|
|
20
21
|
def __init__(self, workspace: Path):
|
|
21
22
|
self.workspace = workspace
|
|
22
23
|
self.path = runtime_dir(workspace) / "team.db"
|
|
@@ -24,112 +25,15 @@ class MessageStore:
|
|
|
24
25
|
self._init()
|
|
25
26
|
|
|
26
27
|
def connect(self) -> sqlite3.Connection:
|
|
27
|
-
conn = sqlite3.connect(self.path)
|
|
28
|
+
conn = sqlite3.connect(self.path, timeout=30.0, isolation_level=None)
|
|
28
29
|
conn.row_factory = sqlite3.Row
|
|
30
|
+
conn.execute("PRAGMA journal_mode=WAL")
|
|
31
|
+
conn.execute("PRAGMA busy_timeout=30000")
|
|
29
32
|
return conn
|
|
30
33
|
|
|
31
34
|
def _init(self) -> None:
|
|
32
35
|
with closing(self.connect()) as conn:
|
|
33
|
-
|
|
34
|
-
conn.execute(
|
|
35
|
-
"""
|
|
36
|
-
create table if not exists messages (
|
|
37
|
-
message_id text primary key,
|
|
38
|
-
task_id text,
|
|
39
|
-
sender text,
|
|
40
|
-
recipient text,
|
|
41
|
-
reply_to text,
|
|
42
|
-
requires_ack integer,
|
|
43
|
-
status text,
|
|
44
|
-
content text,
|
|
45
|
-
artifact_refs text,
|
|
46
|
-
created_at text,
|
|
47
|
-
updated_at text,
|
|
48
|
-
delivered_at text,
|
|
49
|
-
acknowledged_at text,
|
|
50
|
-
error text
|
|
51
|
-
)
|
|
52
|
-
"""
|
|
53
|
-
)
|
|
54
|
-
conn.execute(
|
|
55
|
-
"""
|
|
56
|
-
create table if not exists results (
|
|
57
|
-
result_id text primary key,
|
|
58
|
-
task_id text not null,
|
|
59
|
-
agent_id text not null,
|
|
60
|
-
envelope text not null,
|
|
61
|
-
status text not null,
|
|
62
|
-
created_at text not null
|
|
63
|
-
)
|
|
64
|
-
"""
|
|
65
|
-
)
|
|
66
|
-
conn.execute(
|
|
67
|
-
"""
|
|
68
|
-
create table if not exists scheduled_events (
|
|
69
|
-
id integer primary key,
|
|
70
|
-
due_at text not null,
|
|
71
|
-
target text not null,
|
|
72
|
-
kind text not null,
|
|
73
|
-
payload_json text not null,
|
|
74
|
-
status text not null,
|
|
75
|
-
created_at text not null,
|
|
76
|
-
fired_at text,
|
|
77
|
-
result_json text
|
|
78
|
-
)
|
|
79
|
-
"""
|
|
80
|
-
)
|
|
81
|
-
conn.execute(
|
|
82
|
-
"""
|
|
83
|
-
create table if not exists delivery_tokens (
|
|
84
|
-
message_id text primary key,
|
|
85
|
-
unique_token text not null,
|
|
86
|
-
injected_at text not null,
|
|
87
|
-
visible_at text,
|
|
88
|
-
consumed_at text,
|
|
89
|
-
failed_at text,
|
|
90
|
-
failure_reason text
|
|
91
|
-
)
|
|
92
|
-
"""
|
|
93
|
-
)
|
|
94
|
-
conn.execute(
|
|
95
|
-
"""
|
|
96
|
-
create table if not exists agent_health (
|
|
97
|
-
agent_id text primary key,
|
|
98
|
-
status text not null,
|
|
99
|
-
last_output_at text,
|
|
100
|
-
context_usage_pct integer,
|
|
101
|
-
current_task_id text,
|
|
102
|
-
updated_at text not null
|
|
103
|
-
)
|
|
104
|
-
"""
|
|
105
|
-
)
|
|
106
|
-
conn.execute(
|
|
107
|
-
"""
|
|
108
|
-
create table if not exists peer_allowlist (
|
|
109
|
-
a text not null,
|
|
110
|
-
b text not null,
|
|
111
|
-
created_at text not null,
|
|
112
|
-
primary key (a, b)
|
|
113
|
-
)
|
|
114
|
-
"""
|
|
115
|
-
)
|
|
116
|
-
conn.execute(
|
|
117
|
-
"""
|
|
118
|
-
create table if not exists result_watchers (
|
|
119
|
-
watcher_id text primary key,
|
|
120
|
-
task_id text,
|
|
121
|
-
agent_id text,
|
|
122
|
-
message_id text,
|
|
123
|
-
leader_id text not null,
|
|
124
|
-
status text not null,
|
|
125
|
-
created_at text not null,
|
|
126
|
-
completed_at text,
|
|
127
|
-
result_id text,
|
|
128
|
-
notified_message_id text,
|
|
129
|
-
error text
|
|
130
|
-
)
|
|
131
|
-
"""
|
|
132
|
-
)
|
|
36
|
+
initialize_schema(conn)
|
|
133
37
|
|
|
134
38
|
def create_message(
|
|
135
39
|
self,
|
|
@@ -140,6 +44,7 @@ class MessageStore:
|
|
|
140
44
|
reply_to: str | None = None,
|
|
141
45
|
requires_ack: bool = True,
|
|
142
46
|
artifact_refs: list[dict[str, Any]] | None = None,
|
|
47
|
+
owner_team_id: str | None = None,
|
|
143
48
|
) -> str:
|
|
144
49
|
message_id = f"msg_{uuid.uuid4().hex[:12]}"
|
|
145
50
|
now = utcnow()
|
|
@@ -147,10 +52,16 @@ class MessageStore:
|
|
|
147
52
|
with conn:
|
|
148
53
|
conn.execute(
|
|
149
54
|
"""
|
|
150
|
-
insert into messages
|
|
55
|
+
insert into messages(
|
|
56
|
+
message_id, owner_team_id, task_id, sender, recipient, reply_to, requires_ack,
|
|
57
|
+
status, content, artifact_refs, created_at, updated_at,
|
|
58
|
+
delivered_at, acknowledged_at, error, delivery_attempts
|
|
59
|
+
)
|
|
60
|
+
values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
151
61
|
""",
|
|
152
62
|
(
|
|
153
63
|
message_id,
|
|
64
|
+
owner_team_id,
|
|
154
65
|
task_id,
|
|
155
66
|
sender,
|
|
156
67
|
recipient,
|
|
@@ -164,6 +75,7 @@ class MessageStore:
|
|
|
164
75
|
None,
|
|
165
76
|
None,
|
|
166
77
|
None,
|
|
78
|
+
0,
|
|
167
79
|
),
|
|
168
80
|
)
|
|
169
81
|
return message_id
|
|
@@ -177,16 +89,29 @@ class MessageStore:
|
|
|
177
89
|
conn.execute(
|
|
178
90
|
"""
|
|
179
91
|
update messages
|
|
180
|
-
set status =
|
|
92
|
+
set status = case
|
|
93
|
+
when status = 'acknowledged'
|
|
94
|
+
and ? in ('injected', 'visible', 'submitted', 'submitted_unverified', 'delivered')
|
|
95
|
+
then status
|
|
96
|
+
else ?
|
|
97
|
+
end,
|
|
181
98
|
updated_at = ?,
|
|
182
99
|
delivered_at = coalesce(?, delivered_at),
|
|
183
100
|
acknowledged_at = coalesce(?, acknowledged_at),
|
|
184
101
|
error = coalesce(?, error)
|
|
185
102
|
where message_id = ?
|
|
186
103
|
""",
|
|
187
|
-
(status, now, delivered_at, acknowledged_at, error, message_id),
|
|
104
|
+
(status, status, now, delivered_at, acknowledged_at, error, message_id),
|
|
188
105
|
)
|
|
189
|
-
if status in {
|
|
106
|
+
if status in {
|
|
107
|
+
"injected",
|
|
108
|
+
"visible",
|
|
109
|
+
"submitted",
|
|
110
|
+
"submitted_unverified",
|
|
111
|
+
"injected_unverified",
|
|
112
|
+
"delivery_blocked",
|
|
113
|
+
"failed",
|
|
114
|
+
}:
|
|
190
115
|
conn.execute(
|
|
191
116
|
"""
|
|
192
117
|
insert into delivery_tokens(
|
|
@@ -203,8 +128,8 @@ class MessageStore:
|
|
|
203
128
|
message_id,
|
|
204
129
|
now,
|
|
205
130
|
now if status in {"visible", "submitted"} else None,
|
|
206
|
-
now if status in {"failed", "injected_unverified"} else None,
|
|
207
|
-
error if status in {"failed", "injected_unverified"} else None,
|
|
131
|
+
now if status in {"failed", "injected_unverified", "delivery_blocked"} else None,
|
|
132
|
+
error if status in {"failed", "injected_unverified", "delivery_blocked"} else None,
|
|
208
133
|
),
|
|
209
134
|
)
|
|
210
135
|
if status == "acknowledged":
|
|
@@ -213,6 +138,22 @@ class MessageStore:
|
|
|
213
138
|
(now, message_id),
|
|
214
139
|
)
|
|
215
140
|
|
|
141
|
+
def defer_delivery(self, message_id: str, status: str, reason: str) -> None:
|
|
142
|
+
now = utcnow()
|
|
143
|
+
with closing(self.connect()) as conn:
|
|
144
|
+
with conn:
|
|
145
|
+
conn.execute(
|
|
146
|
+
"""
|
|
147
|
+
update messages
|
|
148
|
+
set status = ?,
|
|
149
|
+
updated_at = ?,
|
|
150
|
+
error = ?,
|
|
151
|
+
delivery_attempts = delivery_attempts + 1
|
|
152
|
+
where message_id = ?
|
|
153
|
+
""",
|
|
154
|
+
(status, now, reason, message_id),
|
|
155
|
+
)
|
|
156
|
+
|
|
216
157
|
def claim_for_delivery(self, message_id: str) -> bool:
|
|
217
158
|
now = utcnow()
|
|
218
159
|
with closing(self.connect()) as conn:
|
|
@@ -221,23 +162,27 @@ class MessageStore:
|
|
|
221
162
|
"""
|
|
222
163
|
update messages
|
|
223
164
|
set status = 'target_resolved',
|
|
224
|
-
updated_at =
|
|
165
|
+
updated_at = ?,
|
|
166
|
+
delivery_attempts = delivery_attempts + 1
|
|
225
167
|
where message_id = ?
|
|
226
|
-
and status in ('pending', 'accepted')
|
|
168
|
+
and status in ('pending', 'accepted', 'queued_until_idle', 'queued_until_start', 'queued_stopped', 'queued_pane_missing')
|
|
227
169
|
""",
|
|
228
170
|
(now, message_id),
|
|
229
171
|
)
|
|
230
172
|
return result.rowcount == 1
|
|
231
173
|
|
|
232
|
-
def fail_timeouts(self, timeout_sec: int) -> list[str]:
|
|
174
|
+
def fail_timeouts(self, timeout_sec: int, exclude_recipients: set[str] | None = None) -> list[str]:
|
|
233
175
|
cutoff = datetime.now(timezone.utc) - timedelta(seconds=timeout_sec)
|
|
234
176
|
failed: list[str] = []
|
|
177
|
+
exclude_recipients = exclude_recipients or set()
|
|
235
178
|
with closing(self.connect()) as conn:
|
|
236
179
|
with conn:
|
|
237
180
|
rows = conn.execute(
|
|
238
|
-
"select message_id, updated_at from messages where requires_ack = 1 and status in ('pending','accepted','target_resolved','injected','visible','submitted','delivered')"
|
|
181
|
+
"select message_id, recipient, updated_at from messages where requires_ack = 1 and status in ('pending','accepted','target_resolved','injected','visible','submitted','delivered')"
|
|
239
182
|
).fetchall()
|
|
240
183
|
for row in rows:
|
|
184
|
+
if row["recipient"] in exclude_recipients:
|
|
185
|
+
continue
|
|
241
186
|
try:
|
|
242
187
|
updated = datetime.fromisoformat(row["updated_at"])
|
|
243
188
|
except ValueError:
|
|
@@ -250,22 +195,40 @@ class MessageStore:
|
|
|
250
195
|
)
|
|
251
196
|
return failed
|
|
252
197
|
|
|
253
|
-
def messages(self) -> list[dict[str, Any]]:
|
|
198
|
+
def messages(self, owner_team_id: str | None = None) -> list[dict[str, Any]]:
|
|
254
199
|
with closing(self.connect()) as conn:
|
|
255
|
-
|
|
200
|
+
if owner_team_id is None:
|
|
201
|
+
rows = conn.execute("select * from messages order by created_at").fetchall()
|
|
202
|
+
else:
|
|
203
|
+
rows = conn.execute(
|
|
204
|
+
"select * from messages where owner_team_id = ? or owner_team_id is null order by created_at",
|
|
205
|
+
(owner_team_id,),
|
|
206
|
+
).fetchall()
|
|
256
207
|
return [dict(row) for row in rows]
|
|
257
208
|
|
|
258
|
-
def inbox(self, agent_id: str, limit: int = 20) -> list[dict[str, Any]]:
|
|
209
|
+
def inbox(self, agent_id: str, limit: int = 20, owner_team_id: str | None = None) -> list[dict[str, Any]]:
|
|
259
210
|
with closing(self.connect()) as conn:
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
211
|
+
if owner_team_id is None:
|
|
212
|
+
rows = conn.execute(
|
|
213
|
+
"""
|
|
214
|
+
select * from messages
|
|
215
|
+
where sender = ? or recipient = ?
|
|
216
|
+
order by created_at desc
|
|
217
|
+
limit ?
|
|
218
|
+
""",
|
|
219
|
+
(agent_id, agent_id, limit),
|
|
220
|
+
).fetchall()
|
|
221
|
+
else:
|
|
222
|
+
rows = conn.execute(
|
|
223
|
+
"""
|
|
224
|
+
select * from messages
|
|
225
|
+
where (sender = ? or recipient = ?)
|
|
226
|
+
and (owner_team_id = ? or owner_team_id is null)
|
|
227
|
+
order by created_at desc
|
|
228
|
+
limit ?
|
|
229
|
+
""",
|
|
230
|
+
(agent_id, agent_id, owner_team_id, limit),
|
|
231
|
+
).fetchall()
|
|
269
232
|
return [dict(row) for row in reversed(rows)]
|
|
270
233
|
|
|
271
234
|
def delivery_tokens(self) -> list[dict[str, Any]]:
|
|
@@ -273,58 +236,46 @@ class MessageStore:
|
|
|
273
236
|
rows = conn.execute("select * from delivery_tokens order by injected_at").fetchall()
|
|
274
237
|
return [dict(row) for row in rows]
|
|
275
238
|
|
|
276
|
-
def
|
|
239
|
+
def add_scheduled_event(
|
|
277
240
|
self,
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
) ->
|
|
284
|
-
now = utcnow()
|
|
285
|
-
with closing(self.connect()) as conn:
|
|
286
|
-
with conn:
|
|
287
|
-
conn.execute(
|
|
288
|
-
"""
|
|
289
|
-
insert into agent_health(agent_id, status, last_output_at, context_usage_pct, current_task_id, updated_at)
|
|
290
|
-
values (?, ?, ?, ?, ?, ?)
|
|
291
|
-
on conflict(agent_id) do update set
|
|
292
|
-
status = excluded.status,
|
|
293
|
-
last_output_at = coalesce(excluded.last_output_at, agent_health.last_output_at),
|
|
294
|
-
context_usage_pct = excluded.context_usage_pct,
|
|
295
|
-
current_task_id = excluded.current_task_id,
|
|
296
|
-
updated_at = excluded.updated_at
|
|
297
|
-
""",
|
|
298
|
-
(agent_id, status, last_output_at, context_usage_pct, current_task_id, now),
|
|
299
|
-
)
|
|
300
|
-
|
|
301
|
-
def agent_health(self) -> dict[str, dict[str, Any]]:
|
|
302
|
-
with closing(self.connect()) as conn:
|
|
303
|
-
rows = conn.execute("select * from agent_health order by agent_id").fetchall()
|
|
304
|
-
return {row["agent_id"]: dict(row) for row in rows}
|
|
305
|
-
|
|
306
|
-
def add_scheduled_event(self, due_at: str, target: str, kind: str, payload: dict[str, Any]) -> int:
|
|
241
|
+
due_at: str,
|
|
242
|
+
target: str,
|
|
243
|
+
kind: str,
|
|
244
|
+
payload: dict[str, Any],
|
|
245
|
+
owner_team_id: str | None = None,
|
|
246
|
+
) -> int:
|
|
307
247
|
with closing(self.connect()) as conn:
|
|
308
248
|
with conn:
|
|
309
249
|
cur = conn.execute(
|
|
310
250
|
"""
|
|
311
|
-
insert into scheduled_events(due_at, target, kind, payload_json, status, created_at)
|
|
312
|
-
values (?, ?, ?, ?, 'pending', ?)
|
|
251
|
+
insert into scheduled_events(owner_team_id, due_at, target, kind, payload_json, status, created_at)
|
|
252
|
+
values (?, ?, ?, ?, ?, 'pending', ?)
|
|
313
253
|
""",
|
|
314
|
-
(due_at, target, kind, json.dumps(payload, ensure_ascii=False), utcnow()),
|
|
254
|
+
(owner_team_id, due_at, target, kind, json.dumps(payload, ensure_ascii=False), utcnow()),
|
|
315
255
|
)
|
|
316
256
|
return int(cur.lastrowid)
|
|
317
257
|
|
|
318
|
-
def due_scheduled_events(self, now: str | None = None) -> list[dict[str, Any]]:
|
|
258
|
+
def due_scheduled_events(self, now: str | None = None, owner_team_id: str | None = None) -> list[dict[str, Any]]:
|
|
319
259
|
with closing(self.connect()) as conn:
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
260
|
+
if owner_team_id is None:
|
|
261
|
+
rows = conn.execute(
|
|
262
|
+
"""
|
|
263
|
+
select * from scheduled_events
|
|
264
|
+
where status = 'pending' and due_at <= ?
|
|
265
|
+
order by due_at, id
|
|
266
|
+
""",
|
|
267
|
+
(now or utcnow(),),
|
|
268
|
+
).fetchall()
|
|
269
|
+
else:
|
|
270
|
+
rows = conn.execute(
|
|
271
|
+
"""
|
|
272
|
+
select * from scheduled_events
|
|
273
|
+
where status = 'pending' and due_at <= ?
|
|
274
|
+
and (owner_team_id = ? or owner_team_id is null)
|
|
275
|
+
order by due_at, id
|
|
276
|
+
""",
|
|
277
|
+
(now or utcnow(), owner_team_id),
|
|
278
|
+
).fetchall()
|
|
328
279
|
return [dict(row) for row in rows]
|
|
329
280
|
|
|
330
281
|
def mark_scheduled_event(self, event_id: int, status: str, result: dict[str, Any]) -> None:
|
|
@@ -379,69 +330,17 @@ class MessageStore:
|
|
|
379
330
|
counts["by_status"][status] = n
|
|
380
331
|
return counts
|
|
381
332
|
|
|
382
|
-
def
|
|
383
|
-
|
|
384
|
-
task_id: str | None,
|
|
385
|
-
agent_id: str | None,
|
|
386
|
-
message_id: str | None,
|
|
387
|
-
leader_id: str = "leader",
|
|
388
|
-
) -> str:
|
|
389
|
-
watcher_id = f"watch_{uuid.uuid4().hex[:12]}"
|
|
390
|
-
with closing(self.connect()) as conn:
|
|
391
|
-
with conn:
|
|
392
|
-
conn.execute(
|
|
393
|
-
"""
|
|
394
|
-
insert into result_watchers(
|
|
395
|
-
watcher_id, task_id, agent_id, message_id, leader_id, status, created_at
|
|
396
|
-
)
|
|
397
|
-
values (?, ?, ?, ?, ?, 'pending', ?)
|
|
398
|
-
""",
|
|
399
|
-
(watcher_id, task_id, agent_id, message_id, leader_id, utcnow()),
|
|
400
|
-
)
|
|
401
|
-
return watcher_id
|
|
402
|
-
|
|
403
|
-
def pending_result_watchers(self) -> list[dict[str, Any]]:
|
|
404
|
-
with closing(self.connect()) as conn:
|
|
405
|
-
rows = conn.execute(
|
|
406
|
-
"select * from result_watchers where status = 'pending' order by created_at"
|
|
407
|
-
).fetchall()
|
|
408
|
-
return [dict(row) for row in rows]
|
|
409
|
-
|
|
410
|
-
def result_watchers(self) -> list[dict[str, Any]]:
|
|
411
|
-
with closing(self.connect()) as conn:
|
|
412
|
-
rows = conn.execute("select * from result_watchers order by created_at").fetchall()
|
|
413
|
-
return [dict(row) for row in rows]
|
|
414
|
-
|
|
415
|
-
def mark_result_watcher(
|
|
416
|
-
self,
|
|
417
|
-
watcher_id: str,
|
|
418
|
-
status: str,
|
|
419
|
-
result_id: str | None = None,
|
|
420
|
-
notified_message_id: str | None = None,
|
|
421
|
-
error: str | None = None,
|
|
422
|
-
) -> None:
|
|
423
|
-
with closing(self.connect()) as conn:
|
|
424
|
-
with conn:
|
|
425
|
-
conn.execute(
|
|
426
|
-
"""
|
|
427
|
-
update result_watchers
|
|
428
|
-
set status = ?,
|
|
429
|
-
completed_at = ?,
|
|
430
|
-
result_id = coalesce(?, result_id),
|
|
431
|
-
notified_message_id = coalesce(?, notified_message_id),
|
|
432
|
-
error = coalesce(?, error)
|
|
433
|
-
where watcher_id = ?
|
|
434
|
-
""",
|
|
435
|
-
(status, utcnow(), result_id, notified_message_id, error, watcher_id),
|
|
436
|
-
)
|
|
437
|
-
|
|
438
|
-
def add_result(self, envelope: dict[str, Any]) -> str:
|
|
333
|
+
def add_result(self, envelope: dict[str, Any], owner_team_id: str | None = None) -> str:
|
|
334
|
+
_ = owner_team_id
|
|
439
335
|
validate_result_envelope(envelope)
|
|
440
336
|
result_id = f"res_{uuid.uuid4().hex[:12]}"
|
|
441
337
|
with closing(self.connect()) as conn:
|
|
442
338
|
with conn:
|
|
443
339
|
conn.execute(
|
|
444
|
-
"
|
|
340
|
+
"""
|
|
341
|
+
insert into results(result_id, task_id, agent_id, envelope, status, created_at)
|
|
342
|
+
values (?, ?, ?, ?, ?, ?)
|
|
343
|
+
""",
|
|
445
344
|
(
|
|
446
345
|
result_id,
|
|
447
346
|
envelope["task_id"],
|
|
@@ -453,59 +352,90 @@ class MessageStore:
|
|
|
453
352
|
)
|
|
454
353
|
return result_id
|
|
455
354
|
|
|
456
|
-
def acknowledge_task_messages(self, task_id: str, agent_id: str) -> list[str]:
|
|
355
|
+
def acknowledge_task_messages(self, task_id: str, agent_id: str, owner_team_id: str | None = None) -> list[str]:
|
|
457
356
|
now = utcnow()
|
|
357
|
+
owner_clause = "and (owner_team_id = ? or owner_team_id is null)" if owner_team_id else ""
|
|
358
|
+
owner_args = (owner_team_id,) if owner_team_id else ()
|
|
458
359
|
with closing(self.connect()) as conn:
|
|
459
360
|
with conn:
|
|
460
361
|
rows = conn.execute(
|
|
461
|
-
"""
|
|
362
|
+
f"""
|
|
462
363
|
select message_id from messages
|
|
463
|
-
where task_id = ? and recipient = ?
|
|
364
|
+
where task_id = ? and recipient = ? {owner_clause}
|
|
365
|
+
and status in ('pending', 'accepted', 'target_resolved', 'injected', 'visible', 'submitted', 'delivered')
|
|
464
366
|
""",
|
|
465
|
-
(task_id, agent_id),
|
|
367
|
+
(task_id, agent_id, *owner_args),
|
|
466
368
|
).fetchall()
|
|
467
369
|
ids = [row["message_id"] for row in rows]
|
|
468
370
|
conn.execute(
|
|
469
|
-
"""
|
|
371
|
+
f"""
|
|
470
372
|
update messages
|
|
471
373
|
set status = 'acknowledged', acknowledged_at = ?, updated_at = ?
|
|
472
|
-
where task_id = ? and recipient = ?
|
|
374
|
+
where task_id = ? and recipient = ? {owner_clause}
|
|
375
|
+
and status in ('pending', 'accepted', 'target_resolved', 'injected', 'visible', 'submitted', 'delivered')
|
|
473
376
|
""",
|
|
474
|
-
(now, now, task_id, agent_id),
|
|
377
|
+
(now, now, task_id, agent_id, *owner_args),
|
|
475
378
|
)
|
|
476
379
|
return ids
|
|
477
380
|
|
|
478
|
-
def acknowledge_message(self, message_id: str, agent_id: str) -> list[str]:
|
|
381
|
+
def acknowledge_message(self, message_id: str, agent_id: str, owner_team_id: str | None = None) -> list[str]:
|
|
479
382
|
now = utcnow()
|
|
383
|
+
owner_clause = "and (owner_team_id = ? or owner_team_id is null)" if owner_team_id else ""
|
|
384
|
+
owner_args = (owner_team_id,) if owner_team_id else ()
|
|
480
385
|
with closing(self.connect()) as conn:
|
|
481
386
|
with conn:
|
|
482
387
|
row = conn.execute(
|
|
483
|
-
"""
|
|
388
|
+
f"""
|
|
484
389
|
select message_id from messages
|
|
485
|
-
where message_id = ? and recipient = ?
|
|
390
|
+
where message_id = ? and recipient = ? {owner_clause}
|
|
391
|
+
and status in ('pending', 'accepted', 'target_resolved', 'injected', 'visible', 'submitted', 'delivered')
|
|
486
392
|
""",
|
|
487
|
-
(message_id, agent_id),
|
|
393
|
+
(message_id, agent_id, *owner_args),
|
|
488
394
|
).fetchone()
|
|
489
395
|
if not row:
|
|
490
396
|
return []
|
|
491
397
|
conn.execute(
|
|
492
|
-
"""
|
|
398
|
+
f"""
|
|
493
399
|
update messages
|
|
494
400
|
set status = 'acknowledged', acknowledged_at = ?, updated_at = ?
|
|
495
|
-
where message_id = ? and recipient = ?
|
|
401
|
+
where message_id = ? and recipient = ? {owner_clause}
|
|
402
|
+
and status in ('pending', 'accepted', 'target_resolved', 'injected', 'visible', 'submitted', 'delivered')
|
|
496
403
|
""",
|
|
497
|
-
(now, now, message_id, agent_id),
|
|
404
|
+
(now, now, message_id, agent_id, *owner_args),
|
|
498
405
|
)
|
|
499
406
|
return [message_id]
|
|
500
407
|
|
|
501
|
-
def results(self, uncollected_only: bool = False) -> list[dict[str, Any]]:
|
|
502
|
-
|
|
408
|
+
def results(self, uncollected_only: bool = False, owner_team_id: str | None = None) -> list[dict[str, Any]]:
|
|
409
|
+
_ = owner_team_id
|
|
410
|
+
clauses: list[str] = []
|
|
411
|
+
args: list[Any] = []
|
|
503
412
|
if uncollected_only:
|
|
504
|
-
|
|
413
|
+
clauses.append("status not in ('collected', 'invalid')")
|
|
414
|
+
where = " where " + " and ".join(clauses) if clauses else ""
|
|
415
|
+
query = f"select * from results{where} order by created_at"
|
|
505
416
|
with closing(self.connect()) as conn:
|
|
506
|
-
rows = conn.execute(query).fetchall()
|
|
417
|
+
rows = conn.execute(query, args).fetchall()
|
|
507
418
|
return [dict(row) for row in rows]
|
|
508
419
|
|
|
420
|
+
def result_by_id(self, result_id: str) -> dict[str, Any] | None:
|
|
421
|
+
with closing(self.connect()) as conn:
|
|
422
|
+
row = conn.execute("select * from results where result_id = ?", (result_id,)).fetchone()
|
|
423
|
+
return dict(row) if row else None
|
|
424
|
+
|
|
425
|
+
def latest_results(self, limit: int = 5, owner_team_id: str | None = None) -> list[dict[str, Any]]:
|
|
426
|
+
_ = owner_team_id
|
|
427
|
+
with closing(self.connect()) as conn:
|
|
428
|
+
rows = conn.execute(
|
|
429
|
+
"""
|
|
430
|
+
select * from results
|
|
431
|
+
where status != 'invalid'
|
|
432
|
+
order by created_at desc
|
|
433
|
+
limit ?
|
|
434
|
+
""",
|
|
435
|
+
(limit,),
|
|
436
|
+
).fetchall()
|
|
437
|
+
return [dict(row) for row in reversed(rows)]
|
|
438
|
+
|
|
509
439
|
def mark_result_collected(self, result_id: str) -> None:
|
|
510
440
|
with closing(self.connect()) as conn:
|
|
511
441
|
with conn:
|
|
@@ -518,3 +448,16 @@ class MessageStore:
|
|
|
518
448
|
"update results set status = 'invalid' where result_id = ?",
|
|
519
449
|
(result_id,),
|
|
520
450
|
)
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
MessageStore.upsert_agent_health = _agent_health.upsert_agent_health
|
|
455
|
+
MessageStore.agent_health = _agent_health.agent_health
|
|
456
|
+
MessageStore.delete_agent_health = _agent_health.delete_agent_health
|
|
457
|
+
MessageStore.gc_agent_health = _agent_health.gc_agent_health
|
|
458
|
+
MessageStore.create_result_watcher = _result_watchers.create_result_watcher
|
|
459
|
+
MessageStore.pending_result_watchers = _result_watchers.pending_result_watchers
|
|
460
|
+
MessageStore.retryable_result_watchers = _result_watchers.retryable_result_watchers
|
|
461
|
+
MessageStore.result_watchers = _result_watchers.result_watchers
|
|
462
|
+
MessageStore.mark_result_watcher = _result_watchers.mark_result_watcher
|
|
463
|
+
MessageStore.requeue_delivery_exhausted_watchers = _result_watchers.requeue_delivery_exhausted_watchers
|