@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.
- package/Cargo.lock +1 -1
- package/Cargo.toml +1 -1
- package/crates/team-agent/src/cli/adapters.rs +38 -7
- package/crates/team-agent/src/cli/emit.rs +7 -6
- package/crates/team-agent/src/cli/mod.rs +623 -21
- package/crates/team-agent/src/cli/status_port.rs +170 -44
- package/crates/team-agent/src/cli/tests/run_delegation.rs +2 -0
- package/crates/team-agent/src/cli/types.rs +1 -0
- package/crates/team-agent/src/coordinator/health.rs +9 -0
- package/crates/team-agent/src/lifecycle/launch.rs +271 -58
- package/crates/team-agent/src/lifecycle/restart/common.rs +65 -0
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +57 -15
- package/crates/team-agent/src/lifecycle/restart/remove.rs +5 -1
- package/crates/team-agent/src/lifecycle/restart.rs +20 -0
- package/crates/team-agent/src/messaging/delivery.rs +397 -36
- package/crates/team-agent/src/messaging/mod.rs +1 -1
- package/crates/team-agent/src/messaging/results.rs +200 -47
- package/crates/team-agent/src/provider/adapter.rs +95 -10
- package/crates/team-agent/src/provider/helpers.rs +10 -1
- package/crates/team-agent/src/state/persist.rs +113 -1
- package/crates/team-agent/src/state/projection.rs +127 -3
- package/crates/team-agent/src/tmux_backend.rs +66 -6
- package/package.json +4 -4
|
@@ -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
|
|
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(
|
|
247
|
-
|
|
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 =
|
|
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
|
|
261
|
-
|
|
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 =
|
|
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(
|
|
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
|
|
281
|
-
|
|
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
|
|
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
|
-
|
|
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(
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
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
|
-
|
|
425
|
-
|
|
426
|
-
|
|
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
|
|
431
|
-
|
|
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 =
|
|
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,
|