@team-agent/installer 0.5.84 → 0.5.85
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/profile.rs +4 -12
- package/crates/team-agent/src/cli/tests/verb_profile.rs +66 -0
- package/crates/team-agent/src/lifecycle/launch/mcp_config.rs +1 -31
- package/crates/team-agent/src/lifecycle/profile_launch.rs +1 -1
- package/crates/team-agent/src/lifecycle/restart/common.rs +2 -1
- package/crates/team-agent/src/lifecycle/tests/copilot_provider_red.rs +179 -8
- package/crates/team-agent/src/messaging/results/collection_tests.rs +719 -0
- package/crates/team-agent/src/messaging/results.rs +104 -27
- package/crates/team-agent/src/provider/adapters/copilot.rs +1 -1
- package/crates/team-agent/src/state/persist.rs +15 -1
- package/package.json +4 -4
|
@@ -130,6 +130,12 @@ fn collect_scoped(
|
|
|
130
130
|
owner_team_id: Option<&str>,
|
|
131
131
|
) -> Result<serde_json::Value, MessagingError> {
|
|
132
132
|
let paths = collect_paths(workspace)?;
|
|
133
|
+
#[cfg(test)]
|
|
134
|
+
collection_tests::boundary("before_lock")?;
|
|
135
|
+
// Serialize collectors, but do not hold a SQLite write transaction while
|
|
136
|
+
// committing state. A process exit releases this existing OS file lock.
|
|
137
|
+
let collection_lock =
|
|
138
|
+
crate::state::persist::acquire_result_collection_lock(&paths.run_workspace)?;
|
|
133
139
|
let log = EventLog::new(&paths.run_workspace);
|
|
134
140
|
let resolved_owner_team_id = match owner_team_id.filter(|team| !team.is_empty()) {
|
|
135
141
|
Some(team) => Some(resolve_owner_team_for_read(
|
|
@@ -159,7 +165,11 @@ fn collect_scoped(
|
|
|
159
165
|
let store = MessageStore::open(&paths.run_workspace)?;
|
|
160
166
|
let conn = crate::db::schema::open_db(store.db_path())?;
|
|
161
167
|
if let Some(path) = result_file {
|
|
168
|
+
#[cfg(test)]
|
|
169
|
+
collection_tests::boundary("before_ingest")?;
|
|
162
170
|
ingest_result_file(&conn, path, owner_team_id)?;
|
|
171
|
+
#[cfg(test)]
|
|
172
|
+
collection_tests::boundary("after_ingest")?;
|
|
163
173
|
}
|
|
164
174
|
let sql = match owner_team_id {
|
|
165
175
|
Some(_) => {
|
|
@@ -197,7 +207,7 @@ fn collect_scoped(
|
|
|
197
207
|
let mut collected_results = Vec::new();
|
|
198
208
|
let mut invalid_results = Vec::new();
|
|
199
209
|
let mut fatal_invalid_results = 0usize;
|
|
200
|
-
let mut
|
|
210
|
+
let mut ready = Vec::new();
|
|
201
211
|
let mut task_updates = Vec::new();
|
|
202
212
|
for row in rows {
|
|
203
213
|
let envelope: serde_json::Value = match serde_json::from_str(&row.envelope) {
|
|
@@ -242,25 +252,75 @@ fn collect_scoped(
|
|
|
242
252
|
)?;
|
|
243
253
|
continue;
|
|
244
254
|
};
|
|
245
|
-
match owner_team_id {
|
|
246
|
-
Some(team) => {
|
|
247
|
-
conn.execute(
|
|
248
|
-
"update results set status = 'collected' where result_id = ?1 and owner_team_id = ?2",
|
|
249
|
-
params![row.result_id.as_str(), team],
|
|
250
|
-
)?;
|
|
251
|
-
}
|
|
252
|
-
None => {
|
|
253
|
-
conn.execute(
|
|
254
|
-
"update results set status = 'collected' where result_id = ?1",
|
|
255
|
-
params![row.result_id.as_str()],
|
|
256
|
-
)?;
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
255
|
if scope == "task" {
|
|
260
|
-
mark_task_done(&mut state, &row.task_id, &row.result_id);
|
|
261
256
|
task_updates.push((row.task_id.clone(), row.result_id.clone()));
|
|
262
|
-
state_dirty = true;
|
|
263
257
|
}
|
|
258
|
+
ready.push((row, envelope, scope));
|
|
259
|
+
}
|
|
260
|
+
// Pending DB rows remain eligible until the complete task delta is durable.
|
|
261
|
+
// Preserve the original created_at/result_id order: its last row per task wins.
|
|
262
|
+
if !task_updates.is_empty() {
|
|
263
|
+
let mut task_winners = Vec::new();
|
|
264
|
+
let mut updated = std::collections::BTreeSet::new();
|
|
265
|
+
for (task_id, result_id) in task_updates.iter().rev() {
|
|
266
|
+
if updated.insert(task_id) {
|
|
267
|
+
task_winners.push((task_id.clone(), result_id.clone()));
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
#[cfg(test)]
|
|
271
|
+
collection_tests::boundary("before_state")?;
|
|
272
|
+
state = crate::state::repository::StateRepository::new(&paths.run_workspace).commit(
|
|
273
|
+
crate::state::repository::StateWriteIntent::ResultCollection { owner_team_id },
|
|
274
|
+
|latest| {
|
|
275
|
+
for (task_id, result_id) in &task_winners {
|
|
276
|
+
let already_applied = latest
|
|
277
|
+
.get("tasks")
|
|
278
|
+
.and_then(serde_json::Value::as_array)
|
|
279
|
+
.is_some_and(|tasks| {
|
|
280
|
+
tasks.iter().any(|task| {
|
|
281
|
+
task.get("id").and_then(serde_json::Value::as_str)
|
|
282
|
+
== Some(task_id.as_str())
|
|
283
|
+
&& task.get("status").and_then(serde_json::Value::as_str)
|
|
284
|
+
== Some("done")
|
|
285
|
+
&& task
|
|
286
|
+
.get("accepted_result_id")
|
|
287
|
+
.and_then(serde_json::Value::as_str)
|
|
288
|
+
== Some(result_id.as_str())
|
|
289
|
+
})
|
|
290
|
+
});
|
|
291
|
+
if !already_applied {
|
|
292
|
+
mark_task_done(latest, task_id, result_id);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
},
|
|
296
|
+
)?;
|
|
297
|
+
for (task_id, result_id) in &task_winners {
|
|
298
|
+
let applied = state
|
|
299
|
+
.get("tasks")
|
|
300
|
+
.and_then(serde_json::Value::as_array)
|
|
301
|
+
.is_some_and(|tasks| {
|
|
302
|
+
tasks.iter().any(|task| {
|
|
303
|
+
task.get("id").and_then(serde_json::Value::as_str) == Some(task_id.as_str())
|
|
304
|
+
&& task.get("status").and_then(serde_json::Value::as_str)
|
|
305
|
+
== Some("done")
|
|
306
|
+
&& task
|
|
307
|
+
.get("accepted_result_id")
|
|
308
|
+
.and_then(serde_json::Value::as_str)
|
|
309
|
+
== Some(result_id.as_str())
|
|
310
|
+
})
|
|
311
|
+
});
|
|
312
|
+
if !applied {
|
|
313
|
+
return Err(MessagingError::Validation(format!(
|
|
314
|
+
"task result projection missing after commit: task_id={task_id} result_id={result_id}"
|
|
315
|
+
)));
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
#[cfg(test)]
|
|
319
|
+
collection_tests::boundary("after_state")?;
|
|
320
|
+
}
|
|
321
|
+
for (row, envelope, scope) in ready {
|
|
322
|
+
#[cfg(test)]
|
|
323
|
+
collection_tests::boundary("before_event")?;
|
|
264
324
|
log.write(
|
|
265
325
|
"collect.result",
|
|
266
326
|
serde_json::json!({
|
|
@@ -270,6 +330,29 @@ fn collect_scoped(
|
|
|
270
330
|
"scope": scope,
|
|
271
331
|
}),
|
|
272
332
|
)?;
|
|
333
|
+
#[cfg(test)]
|
|
334
|
+
collection_tests::boundary("after_event")?;
|
|
335
|
+
#[cfg(test)]
|
|
336
|
+
collection_tests::boundary("before_finalize")?;
|
|
337
|
+
let finalized = match owner_team_id {
|
|
338
|
+
Some(team) => {
|
|
339
|
+
conn.execute(
|
|
340
|
+
"update results set status = 'collected' where result_id = ?1 and owner_team_id = ?2 and status not in ('collected', 'invalid')",
|
|
341
|
+
params![row.result_id.as_str(), team],
|
|
342
|
+
)?
|
|
343
|
+
}
|
|
344
|
+
None => {
|
|
345
|
+
conn.execute(
|
|
346
|
+
"update results set status = 'collected' where result_id = ?1 and status not in ('collected', 'invalid')",
|
|
347
|
+
params![row.result_id.as_str()],
|
|
348
|
+
)?
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
#[cfg(test)]
|
|
352
|
+
collection_tests::boundary("after_finalize")?;
|
|
353
|
+
if finalized == 0 {
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
273
356
|
collected.push(envelope.clone());
|
|
274
357
|
let summary = serde_json::json!({
|
|
275
358
|
"result_id": row.result_id,
|
|
@@ -286,17 +369,8 @@ fn collect_scoped(
|
|
|
286
369
|
});
|
|
287
370
|
collected_results.push(summary);
|
|
288
371
|
}
|
|
289
|
-
if state_dirty {
|
|
290
|
-
state = crate::state::repository::StateRepository::new(&paths.run_workspace).commit(
|
|
291
|
-
crate::state::repository::StateWriteIntent::ResultCollection { owner_team_id },
|
|
292
|
-
|latest| {
|
|
293
|
-
for (task_id, result_id) in &task_updates {
|
|
294
|
-
mark_task_done(latest, task_id, result_id);
|
|
295
|
-
}
|
|
296
|
-
},
|
|
297
|
-
)?;
|
|
298
|
-
}
|
|
299
372
|
let counts = result_counts(&conn, owner_team_id)?;
|
|
373
|
+
drop(collection_lock);
|
|
300
374
|
// results.py:157 — ensure_coordinator=true runs the REAL ensure step; the
|
|
301
375
|
// `{ok:false,status:"not_required"}` literal is ONLY the ensure=false branch.
|
|
302
376
|
let coordinator = if ensure_coordinator {
|
|
@@ -1483,6 +1557,9 @@ pub fn collect_results_and_notify_watchers(
|
|
|
1483
1557
|
}))
|
|
1484
1558
|
}
|
|
1485
1559
|
|
|
1560
|
+
#[cfg(test)]
|
|
1561
|
+
mod collection_tests;
|
|
1562
|
+
|
|
1486
1563
|
#[cfg(test)]
|
|
1487
1564
|
mod tests {
|
|
1488
1565
|
use super::{format_report_result_notification, report_result};
|
|
@@ -57,7 +57,7 @@ pub(crate) fn copilot_base_command(
|
|
|
57
57
|
|
|
58
58
|
/// Translate `McpConfig.raw` canonical schema (`type`) into the copilot
|
|
59
59
|
/// `mcp add`/`--additional-mcp-config` expected `transport` field
|
|
60
|
-
/// (stdio|http|sse).
|
|
60
|
+
/// (stdio|http|sse). Shared by inline plans and lifecycle config files;
|
|
61
61
|
/// claude/codex paths leave canonical schema untouched.
|
|
62
62
|
pub(crate) fn copilot_translate_mcp_config(raw: &serde_json::Value) -> serde_json::Value {
|
|
63
63
|
let Some(servers) = raw.as_object() else {
|
|
@@ -157,7 +157,7 @@ fn errno_name(errno: Option<i32>) -> Option<&'static str> {
|
|
|
157
157
|
/// semantic mapping. The Batch 0 non-Unix `not_yet_implemented`
|
|
158
158
|
/// fallback is now removed (CR C-2 fallback burn-down; grep guard
|
|
159
159
|
/// `platform_fallback_burndown_batch0.rs` flipped in this batch).
|
|
160
|
-
struct RuntimeLock {
|
|
160
|
+
pub(crate) struct RuntimeLock {
|
|
161
161
|
#[allow(dead_code)]
|
|
162
162
|
file: std::fs::File,
|
|
163
163
|
}
|
|
@@ -204,6 +204,20 @@ impl Drop for RuntimeLock {
|
|
|
204
204
|
}
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
+
pub(crate) fn acquire_result_collection_lock(
|
|
208
|
+
run_workspace: &Path,
|
|
209
|
+
) -> Result<RuntimeLock, StateError> {
|
|
210
|
+
RuntimeLock::acquire(run_workspace, "result-collection", 2.0)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
#[cfg(test)]
|
|
214
|
+
pub(crate) fn acquire_state_save_lock(
|
|
215
|
+
workspace: &Path,
|
|
216
|
+
timeout: f64,
|
|
217
|
+
) -> Result<RuntimeLock, StateError> {
|
|
218
|
+
RuntimeLock::acquire(workspace, "state-save", timeout)
|
|
219
|
+
}
|
|
220
|
+
|
|
207
221
|
///
|
|
208
222
|
/// `save_runtime_state`(bug-084)。`state` 是 state.json 的内存 Value(插入序保留)。
|
|
209
223
|
/// 注:Python 在此还调 `_migrate_state_identity`(identity slice 落地后接入;本 slice 不改 state 内容)。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@team-agent/installer",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.85",
|
|
4
4
|
"description": "npx installer for Team Agent",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"codex",
|
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
"team-agent-installer": "npm/install.mjs"
|
|
21
21
|
},
|
|
22
22
|
"optionalDependencies": {
|
|
23
|
-
"@team-agent/cli-darwin-arm64": "0.5.
|
|
24
|
-
"@team-agent/cli-darwin-x64": "0.5.
|
|
25
|
-
"@team-agent/cli-linux-x64": "0.5.
|
|
23
|
+
"@team-agent/cli-darwin-arm64": "0.5.85",
|
|
24
|
+
"@team-agent/cli-darwin-x64": "0.5.85",
|
|
25
|
+
"@team-agent/cli-linux-x64": "0.5.85"
|
|
26
26
|
},
|
|
27
27
|
"scripts": {
|
|
28
28
|
"postinstall": "node npm/bincheck.mjs",
|