@team-agent/installer 0.3.1 → 0.3.2

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.
@@ -1,11 +1,25 @@
1
1
  //! status_port extracted from cli::mod inline placeholder.
2
2
  use super::*;
3
+ use crate::state::projection::OwnerTeamResolution;
3
4
  use crate::transport::Transport;
5
+ use rusqlite::params;
4
6
 
5
7
  /// `status.status(workspace, as_json, compact)`(`queries.py:33`,**有副作用**:capture→refresh→save)。
6
8
  pub fn status(workspace: &Path, compact: bool, detail: bool) -> Result<Value, CliError> {
7
- let _ = detail;
8
9
  let state = read_runtime_state(workspace);
10
+ status_scoped(workspace, &state, None, compact, detail)
11
+ }
12
+
13
+ pub fn status_scoped(
14
+ workspace: &Path,
15
+ state: &Value,
16
+ owner_team_id: Option<&str>,
17
+ compact: bool,
18
+ detail: bool,
19
+ ) -> Result<Value, CliError> {
20
+ let _ = detail;
21
+ let resolved_owner_team_id = resolve_status_owner_team(workspace, owner_team_id)?;
22
+ let owner_team_id = resolved_owner_team_id.as_deref().or(owner_team_id);
9
23
  let health = crate::coordinator::coordinator_health(
10
24
  &crate::coordinator::WorkspacePath::new(workspace.to_path_buf()),
11
25
  );
@@ -30,11 +44,11 @@ use crate::transport::Transport;
30
44
  "leader_receiver": leader_receiver,
31
45
  "teams": state.get("teams").cloned().unwrap_or_else(|| json!({})),
32
46
  "agents": agents,
33
- "agent_health": agent_health(&conn)?,
47
+ "agent_health": agent_health(&conn, owner_team_id)?,
34
48
  "tasks": tasks,
35
- "messages": message_counts(&conn)?,
36
- "queued_messages": queued_messages(&conn, 8)?,
37
- "results": result_counts(&conn)?,
49
+ "messages": message_counts(&conn, owner_team_id)?,
50
+ "queued_messages": queued_messages(&conn, owner_team_id, 8)?,
51
+ "results": result_counts(&conn, owner_team_id)?,
38
52
  "latest_results": json!([]),
39
53
  "coordinator": coordinator_health_value(health),
40
54
  "last_events": Value::Array(
@@ -51,7 +65,17 @@ use crate::transport::Transport;
51
65
  }
52
66
  /// `status.format_status(workspace, agent)`(人读)。
53
67
  pub fn format_status(workspace: &Path, agent: Option<&str>) -> Result<String, CliError> {
54
- let status = status(workspace, true, false)?;
68
+ let state = read_runtime_state(workspace);
69
+ format_status_scoped(workspace, &state, None, agent)
70
+ }
71
+
72
+ pub fn format_status_scoped(
73
+ workspace: &Path,
74
+ state: &Value,
75
+ owner_team_id: Option<&str>,
76
+ agent: Option<&str>,
77
+ ) -> Result<String, CliError> {
78
+ let status = status_scoped(workspace, state, owner_team_id, true, false)?;
55
79
  Ok(match agent {
56
80
  Some(agent) => format!("agent {agent}: {}", status.pointer("/agents").is_some()),
57
81
  None => crate::cli::format_status_summary(&status),
@@ -165,6 +189,32 @@ use crate::transport::Transport;
165
189
  .unwrap_or_else(|| json!({}))
166
190
  }
167
191
 
192
+ fn resolve_status_owner_team(
193
+ workspace: &Path,
194
+ owner_team_id: Option<&str>,
195
+ ) -> Result<Option<String>, CliError> {
196
+ let Some(requested) = owner_team_id.filter(|team| !team.is_empty()) else {
197
+ return Ok(None);
198
+ };
199
+ let state = read_runtime_state(workspace);
200
+ match crate::state::projection::resolve_owner_team_id(&state, requested) {
201
+ OwnerTeamResolution::Canonical(canonical) => Ok(Some(canonical)),
202
+ OwnerTeamResolution::LegacyAlias { requested, canonical } => {
203
+ let log = crate::event_log::EventLog::new(workspace);
204
+ crate::messaging::delivery::normalize_owner_team_id_rows(
205
+ workspace,
206
+ &requested,
207
+ &canonical,
208
+ None,
209
+ Some(&log),
210
+ )
211
+ .map_err(CliError::from)?;
212
+ Ok(Some(canonical))
213
+ }
214
+ OwnerTeamResolution::Unresolved { .. } | OwnerTeamResolution::Ambiguous { .. } => Ok(None),
215
+ }
216
+ }
217
+
168
218
  fn agent_window(agent_id: &str, agent_state: &Value) -> String {
169
219
  ["window", "window_name"]
170
220
  .iter()
@@ -224,15 +274,15 @@ use crate::transport::Transport;
224
274
  .unwrap_or(false)
225
275
  }
226
276
 
227
- fn message_counts(conn: &rusqlite::Connection) -> Result<Value, CliError> {
228
- status_counts(conn, "messages")
277
+ fn message_counts(conn: &rusqlite::Connection, owner_team_id: Option<&str>) -> Result<Value, CliError> {
278
+ status_counts(conn, "messages", owner_team_id)
229
279
  }
230
280
 
231
- fn result_counts(conn: &rusqlite::Connection) -> Result<Value, CliError> {
232
- let by_status = result_status_counts(conn)?;
233
- let total = count_rows(conn, "results")?;
234
- let invalid = count_where_status(conn, "results", "invalid")?;
235
- let collected = count_where_status(conn, "results", "collected")?;
281
+ fn result_counts(conn: &rusqlite::Connection, owner_team_id: Option<&str>) -> Result<Value, CliError> {
282
+ let by_status = result_status_counts(conn, owner_team_id)?;
283
+ let total = count_rows(conn, "results", owner_team_id)?;
284
+ let invalid = count_where_status(conn, "results", owner_team_id, "invalid")?;
285
+ let collected = count_where_status(conn, "results", owner_team_id, "collected")?;
236
286
  let uncollected = total.saturating_sub(collected).saturating_sub(invalid);
237
287
  Ok(json!({
238
288
  "total": total,
@@ -243,10 +293,24 @@ use crate::transport::Transport;
243
293
  }))
244
294
  }
245
295
 
246
- fn status_counts(conn: &rusqlite::Connection, table: &str) -> Result<Value, CliError> {
247
- let sql = format!("select status, count(*) from {table} group by status order by status");
296
+ fn status_counts(
297
+ conn: &rusqlite::Connection,
298
+ table: &str,
299
+ owner_team_id: Option<&str>,
300
+ ) -> Result<Value, CliError> {
301
+ let sql = match owner_team_id {
302
+ Some(_) => format!(
303
+ "select status, count(*) from {table}
304
+ where owner_team_id = ?1
305
+ group by status order by status"
306
+ ),
307
+ None => format!("select status, count(*) from {table} group by status order by status"),
308
+ };
248
309
  let mut stmt = conn.prepare(&sql).map_err(|e| CliError::Runtime(e.to_string()))?;
249
- let mut rows = stmt.query([]).map_err(|e| CliError::Runtime(e.to_string()))?;
310
+ let mut rows = match owner_team_id {
311
+ Some(team) => stmt.query(params![team]).map_err(|e| CliError::Runtime(e.to_string()))?,
312
+ None => stmt.query([]).map_err(|e| CliError::Runtime(e.to_string()))?,
313
+ };
250
314
  let mut out = Map::new();
251
315
  while let Some(row) = rows.next().map_err(|e| CliError::Runtime(e.to_string()))? {
252
316
  let status: String = row.get(0).map_err(|e| CliError::Runtime(e.to_string()))?;
@@ -256,16 +320,28 @@ use crate::transport::Transport;
256
320
  Ok(Value::Object(out))
257
321
  }
258
322
 
259
- fn result_status_counts(conn: &rusqlite::Connection) -> Result<Value, CliError> {
260
- let mut stmt = conn
261
- .prepare(
323
+ fn result_status_counts(conn: &rusqlite::Connection, owner_team_id: Option<&str>) -> Result<Value, CliError> {
324
+ let sql = match owner_team_id {
325
+ Some(_) => {
326
+ "select status, count(*) from results
327
+ where status not in ('collected', 'invalid') and owner_team_id = ?1
328
+ group by status
329
+ order by status"
330
+ }
331
+ None => {
262
332
  "select status, count(*) from results
263
333
  where status not in ('collected', 'invalid')
264
334
  group by status
265
- order by status",
266
- )
335
+ order by status"
336
+ }
337
+ };
338
+ let mut stmt = conn
339
+ .prepare(sql)
267
340
  .map_err(|e| CliError::Runtime(e.to_string()))?;
268
- let mut rows = stmt.query([]).map_err(|e| CliError::Runtime(e.to_string()))?;
341
+ let mut rows = match owner_team_id {
342
+ Some(team) => stmt.query(params![team]).map_err(|e| CliError::Runtime(e.to_string()))?,
343
+ None => stmt.query([]).map_err(|e| CliError::Runtime(e.to_string()))?,
344
+ };
269
345
  let mut out = Map::new();
270
346
  while let Some(row) = rows.next().map_err(|e| CliError::Runtime(e.to_string()))? {
271
347
  let status: String = row.get(0).map_err(|e| CliError::Runtime(e.to_string()))?;
@@ -275,19 +351,32 @@ use crate::transport::Transport;
275
351
  Ok(Value::Object(out))
276
352
  }
277
353
 
278
- fn queued_messages(conn: &rusqlite::Connection, limit: usize) -> Result<Value, CliError> {
354
+ fn queued_messages(
355
+ conn: &rusqlite::Connection,
356
+ owner_team_id: Option<&str>,
357
+ limit: usize,
358
+ ) -> Result<Value, CliError> {
279
359
  let limit = i64::try_from(limit).unwrap_or(i64::MAX);
280
- let mut stmt = conn
281
- .prepare(
360
+ let sql = match owner_team_id {
361
+ Some(_) => {
362
+ "select message_id, recipient, status, created_at, delivery_attempts
363
+ from messages
364
+ where status like 'queued%' and owner_team_id = ?1
365
+ order by created_at desc
366
+ limit ?2"
367
+ }
368
+ None => {
282
369
  "select message_id, recipient, status, created_at, delivery_attempts
283
370
  from messages
284
371
  where status like 'queued%'
285
372
  order by created_at desc
286
- limit ?1",
287
- )
373
+ limit ?1"
374
+ }
375
+ };
376
+ let mut stmt = conn
377
+ .prepare(sql)
288
378
  .map_err(|e| CliError::Runtime(e.to_string()))?;
289
- let rows = stmt
290
- .query_map([limit], |row| {
379
+ let map_row = |row: &rusqlite::Row<'_>| {
291
380
  Ok(json!({
292
381
  "message_id": row.get::<_, String>(0)?,
293
382
  "recipient": row.get::<_, Option<String>>(1)?,
@@ -295,8 +384,12 @@ use crate::transport::Transport;
295
384
  "created_at": row.get::<_, Option<String>>(3)?,
296
385
  "delivery_attempts": row.get::<_, i64>(4)?,
297
386
  }))
298
- })
299
- .map_err(|e| CliError::Runtime(e.to_string()))?;
387
+ };
388
+ let rows = match owner_team_id {
389
+ Some(team) => stmt.query_map(params![team, limit], map_row),
390
+ None => stmt.query_map(params![limit], map_row),
391
+ }
392
+ .map_err(|e| CliError::Runtime(e.to_string()))?;
300
393
  let values = rows
301
394
  .collect::<Result<Vec<_>, _>>()
302
395
  .map_err(|e| CliError::Runtime(e.to_string()))?;
@@ -410,30 +503,63 @@ use crate::transport::Transport;
410
503
  Value::Array(items.iter().skip(start).cloned().collect())
411
504
  }
412
505
 
413
- fn count_rows(conn: &rusqlite::Connection, table: &str) -> Result<i64, CliError> {
414
- let sql = format!("select count(*) from {table}");
415
- conn.query_row(&sql, [], |row| row.get::<_, i64>(0))
416
- .map_err(|e| CliError::Runtime(e.to_string()))
506
+ fn count_rows(
507
+ conn: &rusqlite::Connection,
508
+ table: &str,
509
+ owner_team_id: Option<&str>,
510
+ ) -> Result<i64, CliError> {
511
+ match owner_team_id {
512
+ Some(team) => {
513
+ let sql = format!("select count(*) from {table} where owner_team_id = ?1");
514
+ conn.query_row(&sql, [team], |row| row.get::<_, i64>(0))
515
+ .map_err(|e| CliError::Runtime(e.to_string()))
516
+ }
517
+ None => {
518
+ let sql = format!("select count(*) from {table}");
519
+ conn.query_row(&sql, [], |row| row.get::<_, i64>(0))
520
+ .map_err(|e| CliError::Runtime(e.to_string()))
521
+ }
522
+ }
417
523
  }
418
524
 
419
525
  fn count_where_status(
420
526
  conn: &rusqlite::Connection,
421
527
  table: &str,
528
+ owner_team_id: Option<&str>,
422
529
  status: &str,
423
530
  ) -> Result<i64, CliError> {
424
- let sql = format!("select count(*) from {table} where status = ?1");
425
- conn.query_row(&sql, [status], |row| row.get::<_, i64>(0))
426
- .map_err(|e| CliError::Runtime(e.to_string()))
531
+ match owner_team_id {
532
+ Some(team) => {
533
+ let sql = format!("select count(*) from {table} where status = ?1 and owner_team_id = ?2");
534
+ conn.query_row(&sql, params![status, team], |row| row.get::<_, i64>(0))
535
+ .map_err(|e| CliError::Runtime(e.to_string()))
536
+ }
537
+ None => {
538
+ let sql = format!("select count(*) from {table} where status = ?1");
539
+ conn.query_row(&sql, [status], |row| row.get::<_, i64>(0))
540
+ .map_err(|e| CliError::Runtime(e.to_string()))
541
+ }
542
+ }
427
543
  }
428
544
 
429
- fn agent_health(conn: &rusqlite::Connection) -> Result<Value, CliError> {
430
- let mut stmt = conn
431
- .prepare(
545
+ fn agent_health(conn: &rusqlite::Connection, owner_team_id: Option<&str>) -> Result<Value, CliError> {
546
+ let sql = match owner_team_id {
547
+ Some(_) => {
548
+ "select agent_id, status, last_output_at, context_usage_pct, current_task_id, updated_at, owner_team_id
549
+ from agent_health where owner_team_id = ?1 order by agent_id"
550
+ }
551
+ None => {
432
552
  "select agent_id, status, last_output_at, context_usage_pct, current_task_id, updated_at, owner_team_id
433
- from agent_health order by agent_id",
434
- )
553
+ from agent_health order by agent_id"
554
+ }
555
+ };
556
+ let mut stmt = conn
557
+ .prepare(sql)
435
558
  .map_err(|e| CliError::Runtime(e.to_string()))?;
436
- let mut rows = stmt.query([]).map_err(|e| CliError::Runtime(e.to_string()))?;
559
+ let mut rows = match owner_team_id {
560
+ Some(team) => stmt.query(params![team]).map_err(|e| CliError::Runtime(e.to_string()))?,
561
+ None => stmt.query([]).map_err(|e| CliError::Runtime(e.to_string()))?,
562
+ };
437
563
  let mut out = Map::new();
438
564
  while let Some(row) = rows.next().map_err(|e| CliError::Runtime(e.to_string()))? {
439
565
  let agent_id: String = row.get(0).map_err(|e| CliError::Runtime(e.to_string()))?;
@@ -152,6 +152,7 @@ fn current_uid() -> Option<String> {
152
152
  let ws = crate::model::paths::team_workspace(&team).unwrap();
153
153
  let _guard = TeamSocketGuard { ws };
154
154
  let args = QuickStartArgs {
155
+ workspace: crate::model::paths::team_workspace(&team).unwrap(),
155
156
  agents_dir: team.clone(),
156
157
  name: None,
157
158
  team_id: None,
@@ -177,6 +178,7 @@ fn current_uid() -> Option<String> {
177
178
  std::fs::write(team.join("TEAM.md"), DELEG_TEAM_MD).unwrap();
178
179
  std::fs::write(team.join("agents").join("broken.md"), DELEG_INVALID_ROLE).unwrap();
179
180
  let args = QuickStartArgs {
181
+ workspace: crate::model::paths::team_workspace(&team).unwrap(),
180
182
  agents_dir: team,
181
183
  name: None,
182
184
  team_id: None,
@@ -253,6 +253,7 @@ pub struct InteractionCounts {
253
253
  /// `quick-start`(`parser.py:105`)。
254
254
  #[derive(Debug, Clone, PartialEq, Eq)]
255
255
  pub struct QuickStartArgs {
256
+ pub workspace: PathBuf,
256
257
  pub agents_dir: PathBuf,
257
258
  pub name: Option<String>,
258
259
  pub team_id: Option<String>,
@@ -138,6 +138,15 @@ pub fn stop_coordinator(workspace: &WorkspacePath) -> Result<StopReport, StopErr
138
138
  pid: None,
139
139
  });
140
140
  };
141
+ if pid_is_running(pid).ok() == Some(false) {
142
+ remove_file_if_exists(&pid_path)?;
143
+ remove_file_if_exists(&coordinator_meta_path(workspace))?;
144
+ return Ok(StopReport {
145
+ ok: true,
146
+ status: StopOutcome::Missing,
147
+ pid: Some(pid),
148
+ });
149
+ }
141
150
  let Ok(pid_t) = libc::pid_t::try_from(pid.get()) else {
142
151
  return Ok(StopReport {
143
152
  ok: false,