@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.
Files changed (113) hide show
  1. package/crates/team-agent-core/src/lib.rs +50 -5
  2. package/package.json +1 -1
  3. package/schemas/team.schema.json +1 -0
  4. package/src/team_agent/approvals/__init__.py +65 -0
  5. package/src/team_agent/approvals/constants.py +6 -0
  6. package/src/team_agent/approvals/parsing.py +176 -0
  7. package/src/team_agent/approvals/runtime_prompts.py +171 -0
  8. package/src/team_agent/approvals/status.py +165 -0
  9. package/src/team_agent/cli/__init__.py +137 -0
  10. package/src/team_agent/cli/commands.py +339 -0
  11. package/src/team_agent/cli/e2e.py +202 -0
  12. package/src/team_agent/cli/helpers.py +137 -0
  13. package/src/team_agent/cli/parser.py +477 -0
  14. package/src/team_agent/compiler.py +98 -33
  15. package/src/team_agent/coordinator/__init__.py +53 -0
  16. package/src/team_agent/{coordinator.py → coordinator/__main__.py} +3 -1
  17. package/src/team_agent/coordinator/lifecycle.py +334 -0
  18. package/src/team_agent/coordinator/metadata.py +61 -0
  19. package/src/team_agent/coordinator/paths.py +17 -0
  20. package/src/team_agent/diagnose/__init__.py +48 -0
  21. package/src/team_agent/diagnose/checks.py +101 -0
  22. package/src/team_agent/diagnose/health.py +241 -0
  23. package/src/team_agent/diagnose/preflight.py +194 -0
  24. package/src/team_agent/diagnose/quick_start.py +233 -0
  25. package/src/team_agent/display/__init__.py +61 -0
  26. package/src/team_agent/display/close.py +147 -0
  27. package/src/team_agent/display/ghostty.py +77 -0
  28. package/src/team_agent/display/worker_window.py +110 -0
  29. package/src/team_agent/display/workspace.py +473 -0
  30. package/src/team_agent/launch/__init__.py +41 -0
  31. package/src/team_agent/launch/bootstrap.py +85 -0
  32. package/src/team_agent/launch/config.py +106 -0
  33. package/src/team_agent/launch/core.py +291 -0
  34. package/src/team_agent/launch/requirements.py +57 -0
  35. package/src/team_agent/leader/__init__.py +320 -0
  36. package/src/team_agent/lifecycle/__init__.py +5 -0
  37. package/src/team_agent/lifecycle/agents.py +226 -0
  38. package/src/team_agent/lifecycle/operations.py +321 -0
  39. package/src/team_agent/lifecycle/paste_buffer_hygiene.py +39 -0
  40. package/src/team_agent/lifecycle/start.py +363 -0
  41. package/src/team_agent/mcp_server/__init__.py +42 -0
  42. package/src/team_agent/mcp_server/__main__.py +7 -0
  43. package/src/team_agent/mcp_server/contracts.py +148 -0
  44. package/src/team_agent/mcp_server/normalize.py +257 -0
  45. package/src/team_agent/mcp_server/server.py +150 -0
  46. package/src/team_agent/mcp_server/tools.py +205 -0
  47. package/src/team_agent/message_store/__init__.py +23 -0
  48. package/src/team_agent/message_store/agent_health.py +109 -0
  49. package/src/team_agent/{message_store.py → message_store/core.py} +188 -245
  50. package/src/team_agent/message_store/result_watchers.py +102 -0
  51. package/src/team_agent/message_store/schema.py +266 -0
  52. package/src/team_agent/messaging/__init__.py +1 -0
  53. package/src/team_agent/messaging/activity_detector.py +190 -0
  54. package/src/team_agent/messaging/delivery.py +138 -0
  55. package/src/team_agent/messaging/deps.py +263 -0
  56. package/src/team_agent/messaging/idle_alerts.py +323 -0
  57. package/src/team_agent/messaging/internal_delivery.py +46 -0
  58. package/src/team_agent/messaging/leader.py +317 -0
  59. package/src/team_agent/messaging/leader_panes.py +343 -0
  60. package/src/team_agent/messaging/owner_bypass.py +29 -0
  61. package/src/team_agent/messaging/result_delivery.py +300 -0
  62. package/src/team_agent/messaging/results.py +456 -0
  63. package/src/team_agent/messaging/scheduler.py +428 -0
  64. package/src/team_agent/messaging/send.py +500 -0
  65. package/src/team_agent/messaging/session_drift.py +94 -0
  66. package/src/team_agent/messaging/tmux_io.py +337 -0
  67. package/src/team_agent/messaging/tmux_prompt.py +229 -0
  68. package/src/team_agent/orchestrator/__init__.py +376 -0
  69. package/src/team_agent/orchestrator/plan.py +122 -0
  70. package/src/team_agent/orchestrator/state.py +128 -0
  71. package/src/team_agent/profiles/__init__.py +82 -0
  72. package/src/team_agent/profiles/constants.py +19 -0
  73. package/src/team_agent/profiles/core.py +407 -0
  74. package/src/team_agent/profiles/helpers.py +69 -0
  75. package/src/team_agent/profiles/provider_env.py +188 -0
  76. package/src/team_agent/profiles/smoke.py +201 -0
  77. package/src/team_agent/provider_cli/__init__.py +43 -0
  78. package/src/team_agent/provider_cli/adapter.py +167 -0
  79. package/src/team_agent/provider_cli/base.py +48 -0
  80. package/src/team_agent/provider_cli/claude.py +457 -0
  81. package/src/team_agent/provider_cli/codex.py +319 -0
  82. package/src/team_agent/provider_cli/copilot.py +8 -0
  83. package/src/team_agent/provider_cli/fake.py +39 -0
  84. package/src/team_agent/provider_cli/gemini.py +95 -0
  85. package/src/team_agent/provider_cli/opencode.py +8 -0
  86. package/src/team_agent/provider_cli/prompt.py +62 -0
  87. package/src/team_agent/provider_cli/registry.py +18 -0
  88. package/src/team_agent/provider_cli/unsupported.py +32 -0
  89. package/src/team_agent/providers.py +67 -949
  90. package/src/team_agent/quality_gates.py +104 -0
  91. package/src/team_agent/restart/__init__.py +34 -0
  92. package/src/team_agent/restart/orchestration.py +328 -0
  93. package/src/team_agent/restart/selection.py +89 -0
  94. package/src/team_agent/restart/snapshot.py +70 -0
  95. package/src/team_agent/runtime.py +809 -5892
  96. package/src/team_agent/rust_core.py +22 -5
  97. package/src/team_agent/sessions/__init__.py +25 -0
  98. package/src/team_agent/sessions/capture.py +93 -0
  99. package/src/team_agent/sessions/inventory.py +44 -0
  100. package/src/team_agent/sessions/resume.py +135 -0
  101. package/src/team_agent/spec.py +3 -1
  102. package/src/team_agent/state.py +218 -4
  103. package/src/team_agent/status/__init__.py +63 -0
  104. package/src/team_agent/status/approvals.py +52 -0
  105. package/src/team_agent/status/compact.py +158 -0
  106. package/src/team_agent/status/constants.py +18 -0
  107. package/src/team_agent/status/inbox.py +28 -0
  108. package/src/team_agent/status/peek.py +117 -0
  109. package/src/team_agent/status/queries.py +168 -0
  110. package/src/team_agent/terminal.py +57 -0
  111. package/src/team_agent/cli.py +0 -858
  112. package/src/team_agent/mcp_server.py +0 -579
  113. 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
- with conn:
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 values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
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 {"injected", "visible", "submitted", "submitted_unverified", "injected_unverified", "failed"}:
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
- rows = conn.execute("select * from messages order by created_at").fetchall()
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
- rows = conn.execute(
261
- """
262
- select * from messages
263
- where sender = ? or recipient = ?
264
- order by created_at desc
265
- limit ?
266
- """,
267
- (agent_id, agent_id, limit),
268
- ).fetchall()
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 upsert_agent_health(
239
+ def add_scheduled_event(
277
240
  self,
278
- agent_id: str,
279
- status: str,
280
- last_output_at: str | None = None,
281
- context_usage_pct: int | None = None,
282
- current_task_id: str | None = None,
283
- ) -> None:
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
- rows = conn.execute(
321
- """
322
- select * from scheduled_events
323
- where status = 'pending' and due_at <= ?
324
- order by due_at, id
325
- """,
326
- (now or utcnow(),),
327
- ).fetchall()
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 create_result_watcher(
383
- self,
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
- "insert into results values (?, ?, ?, ?, ?, ?)",
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 = ? and status in ('pending', 'accepted', 'target_resolved', 'injected', 'visible', 'submitted', 'delivered')
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 = ? and status in ('pending', 'accepted', 'target_resolved', 'injected', 'visible', 'submitted', 'delivered')
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 = ? and status in ('pending', 'accepted', 'target_resolved', 'injected', 'visible', 'submitted', 'delivered')
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 = ? and status in ('pending', 'accepted', 'target_resolved', 'injected', 'visible', 'submitted', 'delivered')
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
- query = "select * from results order by created_at"
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
- query = "select * from results where status not in ('collected', 'invalid') order by created_at"
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