@team-agent/installer 0.5.41 → 0.5.43
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 +129 -54
- package/crates/team-agent/src/cli/diagnose.rs +1 -2
- package/crates/team-agent/src/cli/emit.rs +1 -7
- package/crates/team-agent/src/cli/helpers.rs +3 -1
- package/crates/team-agent/src/cli/leader.rs +2 -1
- package/crates/team-agent/src/cli/mod.rs +27 -7
- package/crates/team-agent/src/cli/named_address.rs +14 -5
- package/crates/team-agent/src/cli/profile.rs +19 -7
- package/crates/team-agent/src/cli/send.rs +9 -3
- package/crates/team-agent/src/cli/status.rs +8 -30
- package/crates/team-agent/src/cli/status_port.rs +1341 -1272
- package/crates/team-agent/src/cli/tests/base.rs +738 -660
- package/crates/team-agent/src/cli/tests/compile.rs +45 -18
- package/crates/team-agent/src/cli/tests/divergence.rs +462 -444
- package/crates/team-agent/src/cli/tests/lane_c.rs +365 -282
- package/crates/team-agent/src/cli/tests/leader_watch.rs +356 -329
- package/crates/team-agent/src/cli/tests/main_preserved.rs +672 -564
- package/crates/team-agent/src/cli/tests/missing_subcommands.rs +284 -224
- package/crates/team-agent/src/cli/tests/mod.rs +17 -7
- package/crates/team-agent/src/cli/tests/named_address.rs +8 -2
- package/crates/team-agent/src/cli/tests/peer_allow.rs +10 -2
- package/crates/team-agent/src/cli/tests/run_delegation.rs +314 -273
- package/crates/team-agent/src/cli/tests/shutdown_kill_plan.rs +26 -15
- package/crates/team-agent/src/cli/tests/status_send.rs +707 -629
- package/crates/team-agent/src/cli/tests/verb_install_skill.rs +20 -4
- package/crates/team-agent/src/cli/tests/verb_profile.rs +46 -17
- package/crates/team-agent/src/cli/tests/verb_validate.rs +15 -3
- package/crates/team-agent/src/codex_app_server.rs +2 -5
- package/crates/team-agent/src/compiler/tests.rs +139 -33
- package/crates/team-agent/src/compiler.rs +55 -22
- package/crates/team-agent/src/conpty/backend.rs +23 -33
- package/crates/team-agent/src/coordinator/backoff.rs +2 -7
- package/crates/team-agent/src/coordinator/conpty_shim.rs +55 -67
- package/crates/team-agent/src/coordinator/health.rs +46 -31
- package/crates/team-agent/src/coordinator/mod.rs +3 -3
- package/crates/team-agent/src/coordinator/orphan.rs +22 -10
- package/crates/team-agent/src/coordinator/steps/abnormal.rs +47 -52
- package/crates/team-agent/src/coordinator/tests/abnormal.rs +55 -19
- package/crates/team-agent/src/coordinator/tests/basics.rs +179 -41
- package/crates/team-agent/src/coordinator/tests/daemon.rs +53 -13
- package/crates/team-agent/src/coordinator/tests/health_sync.rs +78 -19
- package/crates/team-agent/src/coordinator/tests/main_preserved.rs +61 -11
- package/crates/team-agent/src/coordinator/tests/mod.rs +33 -39
- package/crates/team-agent/src/coordinator/tests/spine.rs +52 -12
- package/crates/team-agent/src/coordinator/tests/takeover.rs +73 -15
- package/crates/team-agent/src/coordinator/tests/tick_core.rs +50 -15
- package/crates/team-agent/src/coordinator/tests/watch.rs +74 -20
- package/crates/team-agent/src/coordinator/tick.rs +19 -1
- package/crates/team-agent/src/db/message_store.rs +138 -30
- package/crates/team-agent/src/db/migration.rs +249 -61
- package/crates/team-agent/src/db/schema.rs +303 -82
- package/crates/team-agent/src/diagnose/comms.rs +9 -2
- package/crates/team-agent/src/diagnose/mod.rs +1 -3
- package/crates/team-agent/src/diagnose/orphans.rs +79 -61
- package/crates/team-agent/src/event_log.rs +70 -16
- package/crates/team-agent/src/layout/manager.rs +15 -4
- package/crates/team-agent/src/layout/mod.rs +4 -4
- package/crates/team-agent/src/layout/overlay.rs +10 -3
- package/crates/team-agent/src/layout/placement.rs +5 -1
- package/crates/team-agent/src/layout/recovery.rs +4 -2
- package/crates/team-agent/src/layout/runtime_sessions.rs +7 -7
- package/crates/team-agent/src/layout/sessions.rs +17 -9
- package/crates/team-agent/src/layout/tmux_endpoint.rs +1 -1
- package/crates/team-agent/src/layout/worker_env.rs +52 -19
- package/crates/team-agent/src/leader/helpers.rs +7 -1
- package/crates/team-agent/src/leader/lease.rs +195 -82
- package/crates/team-agent/src/leader/owner_bind.rs +55 -22
- package/crates/team-agent/src/leader/rediscover/tests.rs +88 -24
- package/crates/team-agent/src/leader/rediscover.rs +74 -25
- package/crates/team-agent/src/leader/start.rs +75 -54
- package/crates/team-agent/src/leader/takeover.rs +46 -11
- package/crates/team-agent/src/leader/tests/basics.rs +320 -167
- package/crates/team-agent/src/leader/tests/byte_findings.rs +361 -219
- package/crates/team-agent/src/leader/tests/identity.rs +428 -356
- package/crates/team-agent/src/leader/tests/idle.rs +285 -254
- package/crates/team-agent/src/leader/tests/lease_api.rs +338 -274
- package/crates/team-agent/src/leader/tests/lease_claim.rs +643 -593
- package/crates/team-agent/src/leader/tests/mod.rs +115 -99
- package/crates/team-agent/src/leader/tests/rediscover.rs +74 -22
- package/crates/team-agent/src/leader/tests/wake_start_owner.rs +237 -211
- package/crates/team-agent/src/lib.rs +4 -4
- package/crates/team-agent/src/lifecycle/display.rs +7 -3
- package/crates/team-agent/src/lifecycle/launch.rs +6 -1
- package/crates/team-agent/src/lifecycle/mod.rs +9 -1
- package/crates/team-agent/src/lifecycle/profile_launch.rs +77 -34
- package/crates/team-agent/src/lifecycle/profile_smoke.rs +3 -1
- package/crates/team-agent/src/lifecycle/restart/agent.rs +1 -6
- package/crates/team-agent/src/lifecycle/restart/orchestrator.rs +1 -4
- package/crates/team-agent/src/lifecycle/restart/preflight.rs +6 -5
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +26 -22
- package/crates/team-agent/src/lifecycle/restart/remove.rs +45 -35
- package/crates/team-agent/src/lifecycle/restart/team_state.rs +63 -17
- package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +251 -84
- package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +198 -48
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +2 -1
- package/crates/team-agent/src/lifecycle/tests/main_preserved.rs +152 -32
- package/crates/team-agent/src/lifecycle/tests/phase_b_contracts.rs +1 -5
- package/crates/team-agent/src/lifecycle/tests.rs +2 -2
- package/crates/team-agent/src/main.rs +4 -4
- package/crates/team-agent/src/mcp_server/lifecycle_tools/agent_ops.rs +47 -20
- package/crates/team-agent/src/mcp_server/mod.rs +11 -2
- package/crates/team-agent/src/mcp_server/types.rs +14 -3
- package/crates/team-agent/src/mcp_server/wire.rs +230 -68
- package/crates/team-agent/src/messaging/delivery.rs +20 -18
- package/crates/team-agent/src/messaging/helpers.rs +25 -4
- package/crates/team-agent/src/messaging/leader_receiver.rs +4 -5
- package/crates/team-agent/src/messaging/mod.rs +2 -3
- package/crates/team-agent/src/messaging/selftest.rs +46 -26
- package/crates/team-agent/src/messaging/tests/main_preserved.rs +47 -12
- package/crates/team-agent/src/messaging/tests/runtime.rs +57 -22
- package/crates/team-agent/src/messaging/tests/spine.rs +154 -40
- package/crates/team-agent/src/messaging/tests/wave2.rs +31 -32
- package/crates/team-agent/src/messaging/trust.rs +25 -2
- package/crates/team-agent/src/messaging/watchers.rs +37 -9
- package/crates/team-agent/src/model/enums.rs +65 -16
- package/crates/team-agent/src/model/ids.rs +12 -3
- package/crates/team-agent/src/model/paths.rs +28 -7
- package/crates/team-agent/src/model/permissions.rs +176 -33
- package/crates/team-agent/src/model/routing.rs +66 -20
- package/crates/team-agent/src/model/spec.rs +365 -69
- package/crates/team-agent/src/model/task_graph.rs +36 -9
- package/crates/team-agent/src/model/yaml/tests.rs +24 -6
- package/crates/team-agent/src/model/yaml.rs +7 -6
- package/crates/team-agent/src/packaging/install.rs +23 -9
- package/crates/team-agent/src/packaging/migrate.rs +5 -7
- package/crates/team-agent/src/packaging/mod.rs +9 -1
- package/crates/team-agent/src/packaging/repair.rs +13 -6
- package/crates/team-agent/src/packaging/tests.rs +63 -16
- package/crates/team-agent/src/packaging/types.rs +22 -7
- package/crates/team-agent/src/platform/argv.rs +4 -1
- package/crates/team-agent/src/platform/file_lock.rs +22 -8
- package/crates/team-agent/src/platform/process.rs +21 -22
- package/crates/team-agent/src/provider/adapters/claude.rs +1 -3
- package/crates/team-agent/src/provider/approvals/parsing.rs +134 -26
- package/crates/team-agent/src/provider/approvals/runtime_prompts.rs +13 -3
- package/crates/team-agent/src/provider/classify.rs +127 -42
- package/crates/team-agent/src/provider/faults.rs +17 -5
- package/crates/team-agent/src/provider/helpers.rs +6 -5
- package/crates/team-agent/src/provider/startup_prompt.rs +75 -27
- package/crates/team-agent/src/state/repository.rs +27 -14
- package/crates/team-agent/src/tmux_backend/tests.rs +1637 -1398
- package/crates/team-agent/src/tmux_backend.rs +80 -44
- package/crates/team-agent/src/topology.rs +27 -20
- package/crates/team-agent/src/transport/test_support.rs +20 -23
- package/crates/team-agent/src/transport/tests/behavior.rs +292 -293
- package/crates/team-agent/src/transport/tests/mod.rs +178 -187
- package/crates/team-agent/src/transport/tests/wire.rs +561 -525
- package/crates/team-agent/src/transport.rs +12 -17
- package/crates/team-agent/src/transport_factory.rs +29 -14
- package/package.json +4 -4
|
@@ -95,32 +95,38 @@ pub fn orphan_gate_json(workspace: &Path, fix: bool, confirm: bool) -> Result<Va
|
|
|
95
95
|
let scope = OrphanScanScope::current_workspace(workspace);
|
|
96
96
|
let report = scan_orphans_bounded(&scope, false);
|
|
97
97
|
if report.orphans.is_empty() {
|
|
98
|
-
return Ok(with_ignored_foreign(
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
98
|
+
return Ok(with_ignored_foreign(
|
|
99
|
+
json!({
|
|
100
|
+
"ok": true,
|
|
101
|
+
"gate": "orphans",
|
|
102
|
+
"status": "passed",
|
|
103
|
+
"scanned": report.scanned,
|
|
104
|
+
"dry_run": !fix,
|
|
105
|
+
"scanned_at": chrono::Utc::now().to_rfc3339(),
|
|
106
|
+
"action_required": false,
|
|
107
|
+
"fix": fix,
|
|
108
|
+
"orphans": [],
|
|
109
|
+
}),
|
|
110
|
+
&report,
|
|
111
|
+
));
|
|
109
112
|
}
|
|
110
113
|
if fix {
|
|
111
114
|
return fix_orphans(&scope, report);
|
|
112
115
|
}
|
|
113
|
-
Ok(with_ignored_foreign(
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
116
|
+
Ok(with_ignored_foreign(
|
|
117
|
+
json!({
|
|
118
|
+
"ok": false,
|
|
119
|
+
"gate": "orphans",
|
|
120
|
+
"status": "failed",
|
|
121
|
+
"scanned": report.scanned,
|
|
122
|
+
"dry_run": true,
|
|
123
|
+
"scanned_at": chrono::Utc::now().to_rfc3339(),
|
|
124
|
+
"action_required": true,
|
|
125
|
+
"fix": false,
|
|
126
|
+
"orphans": orphan_values(&report.orphans),
|
|
127
|
+
}),
|
|
128
|
+
&report,
|
|
129
|
+
))
|
|
124
130
|
}
|
|
125
131
|
|
|
126
132
|
pub fn cleanup_orphans_json(workspace: &Path, confirm: bool) -> Result<Value, CliError> {
|
|
@@ -128,26 +134,32 @@ pub fn cleanup_orphans_json(workspace: &Path, confirm: bool) -> Result<Value, Cl
|
|
|
128
134
|
let report = scan_orphans_bounded(&scope, false);
|
|
129
135
|
if confirm {
|
|
130
136
|
if report.orphans.is_empty() {
|
|
131
|
-
return Ok(with_ignored_foreign(
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
137
|
+
return Ok(with_ignored_foreign(
|
|
138
|
+
json!({
|
|
139
|
+
"ok": true,
|
|
140
|
+
"scanned": report.scanned,
|
|
141
|
+
"orphans": [],
|
|
142
|
+
"dry_run": false,
|
|
143
|
+
"scanned_at": chrono::Utc::now().to_rfc3339(),
|
|
144
|
+
"killed": [],
|
|
145
|
+
"failed": [],
|
|
146
|
+
}),
|
|
147
|
+
&report,
|
|
148
|
+
));
|
|
140
149
|
}
|
|
141
150
|
return cleanup_confirmed(&scope, report);
|
|
142
151
|
}
|
|
143
|
-
Ok(with_ignored_foreign(
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
152
|
+
Ok(with_ignored_foreign(
|
|
153
|
+
json!({
|
|
154
|
+
"ok": true,
|
|
155
|
+
"scanned": report.scanned,
|
|
156
|
+
"orphans": orphan_values(&report.orphans),
|
|
157
|
+
"dry_run": true,
|
|
158
|
+
"scanned_at": chrono::Utc::now().to_rfc3339(),
|
|
159
|
+
"action_required": "re-run with --confirm to send SIGTERM",
|
|
160
|
+
}),
|
|
161
|
+
&report,
|
|
162
|
+
))
|
|
151
163
|
}
|
|
152
164
|
|
|
153
165
|
pub fn has_orphan_residue(workspace: &Path) -> bool {
|
|
@@ -188,33 +200,39 @@ pub fn orphan_blocker_detail(workspace: &Path) -> String {
|
|
|
188
200
|
fn fix_orphans(scope: &OrphanScanScope, report: ScanReport) -> Result<Value, CliError> {
|
|
189
201
|
let cleanup = cleanup_report(report, scope);
|
|
190
202
|
let residual = scan_orphans(scope, false);
|
|
191
|
-
Ok(with_ignored_foreign(
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
203
|
+
Ok(with_ignored_foreign(
|
|
204
|
+
json!({
|
|
205
|
+
"ok": residual.orphans.is_empty() && cleanup.failed.is_empty(),
|
|
206
|
+
"gate": "orphans",
|
|
207
|
+
"status": if residual.orphans.is_empty() && cleanup.failed.is_empty() { "fixed" } else { "failed" },
|
|
208
|
+
"scanned": cleanup.scanned,
|
|
209
|
+
"dry_run": false,
|
|
210
|
+
"scanned_at": chrono::Utc::now().to_rfc3339(),
|
|
211
|
+
"action_required": !residual.orphans.is_empty() || !cleanup.failed.is_empty(),
|
|
212
|
+
"fix": true,
|
|
213
|
+
"orphans": orphan_values(&residual.orphans),
|
|
214
|
+
"killed": cleanup.killed,
|
|
215
|
+
"failed": cleanup.failed,
|
|
216
|
+
}),
|
|
217
|
+
&residual,
|
|
218
|
+
))
|
|
204
219
|
}
|
|
205
220
|
|
|
206
221
|
fn cleanup_confirmed(scope: &OrphanScanScope, report: ScanReport) -> Result<Value, CliError> {
|
|
207
222
|
let cleanup = cleanup_report(report, scope);
|
|
208
223
|
let residual = scan_orphans(scope, false);
|
|
209
|
-
Ok(with_ignored_foreign(
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
224
|
+
Ok(with_ignored_foreign(
|
|
225
|
+
json!({
|
|
226
|
+
"ok": residual.orphans.is_empty() && cleanup.failed.is_empty(),
|
|
227
|
+
"scanned": cleanup.scanned,
|
|
228
|
+
"orphans": orphan_values(&residual.orphans),
|
|
229
|
+
"dry_run": false,
|
|
230
|
+
"scanned_at": chrono::Utc::now().to_rfc3339(),
|
|
231
|
+
"killed": cleanup.killed,
|
|
232
|
+
"failed": cleanup.failed,
|
|
233
|
+
}),
|
|
234
|
+
&residual,
|
|
235
|
+
))
|
|
218
236
|
}
|
|
219
237
|
|
|
220
238
|
struct CleanupReport {
|
|
@@ -48,14 +48,22 @@ pub enum EventLogError {
|
|
|
48
48
|
struct PythonFormatter;
|
|
49
49
|
|
|
50
50
|
impl serde_json::ser::Formatter for PythonFormatter {
|
|
51
|
-
fn begin_array_value<W: ?Sized + std::io::Write>(
|
|
51
|
+
fn begin_array_value<W: ?Sized + std::io::Write>(
|
|
52
|
+
&mut self,
|
|
53
|
+
w: &mut W,
|
|
54
|
+
first: bool,
|
|
55
|
+
) -> std::io::Result<()> {
|
|
52
56
|
if first {
|
|
53
57
|
Ok(())
|
|
54
58
|
} else {
|
|
55
59
|
w.write_all(b", ")
|
|
56
60
|
}
|
|
57
61
|
}
|
|
58
|
-
fn begin_object_key<W: ?Sized + std::io::Write>(
|
|
62
|
+
fn begin_object_key<W: ?Sized + std::io::Write>(
|
|
63
|
+
&mut self,
|
|
64
|
+
w: &mut W,
|
|
65
|
+
first: bool,
|
|
66
|
+
) -> std::io::Result<()> {
|
|
59
67
|
if first {
|
|
60
68
|
Ok(())
|
|
61
69
|
} else {
|
|
@@ -114,7 +122,9 @@ pub struct EventLog {
|
|
|
114
122
|
impl EventLog {
|
|
115
123
|
/// `EventLog(workspace)`:路径 = `<workspace>/.team/logs/events.jsonl`。
|
|
116
124
|
pub fn new(workspace: &Path) -> Self {
|
|
117
|
-
Self {
|
|
125
|
+
Self {
|
|
126
|
+
path: logs_dir(workspace).join("events.jsonl"),
|
|
127
|
+
}
|
|
118
128
|
}
|
|
119
129
|
|
|
120
130
|
/// 直接指定 events.jsonl 路径(测试 / 非标准布局)。
|
|
@@ -142,7 +152,10 @@ impl EventLog {
|
|
|
142
152
|
// 单次 write_all(line+"\n"):POSIX O_APPEND 对 <PIPE_BUF 写原子,避免并发写者交错(对抗 P1)。
|
|
143
153
|
let mut bytes = to_python_json(&sort_value(&event)).into_bytes();
|
|
144
154
|
bytes.push(b'\n');
|
|
145
|
-
let mut file = std::fs::OpenOptions::new()
|
|
155
|
+
let mut file = std::fs::OpenOptions::new()
|
|
156
|
+
.create(true)
|
|
157
|
+
.append(true)
|
|
158
|
+
.open(&self.path)?;
|
|
146
159
|
file.write_all(&bytes)?;
|
|
147
160
|
Ok(event)
|
|
148
161
|
}
|
|
@@ -155,7 +168,11 @@ impl EventLog {
|
|
|
155
168
|
let text = std::fs::read_to_string(&self.path)?;
|
|
156
169
|
let lines: Vec<&str> = text.lines().collect();
|
|
157
170
|
// Python lines[-limit:]:limit==0 → lines[0:] = 全部(负零切片怪癖);limit>len → 全部。
|
|
158
|
-
let start = if limit == 0 {
|
|
171
|
+
let start = if limit == 0 {
|
|
172
|
+
0
|
|
173
|
+
} else {
|
|
174
|
+
lines.len().saturating_sub(limit)
|
|
175
|
+
};
|
|
159
176
|
let mut out = Vec::new();
|
|
160
177
|
for line in &lines[start..] {
|
|
161
178
|
match serde_json::from_str::<Value>(line) {
|
|
@@ -194,7 +211,10 @@ impl EventLog {
|
|
|
194
211
|
}
|
|
195
212
|
|
|
196
213
|
fn archive_path(&self, index: u32) -> PathBuf {
|
|
197
|
-
let name = self
|
|
214
|
+
let name = self
|
|
215
|
+
.path
|
|
216
|
+
.file_name()
|
|
217
|
+
.map_or_else(String::new, |n| n.to_string_lossy().into_owned());
|
|
198
218
|
self.path.with_file_name(format!("{name}.{index}"))
|
|
199
219
|
}
|
|
200
220
|
}
|
|
@@ -223,7 +243,10 @@ mod tests {
|
|
|
223
243
|
r#"{"event": "u", "msg": "héllo🦀\n世界", "nested": {"a": [1, 2], "b": 2}}"#
|
|
224
244
|
);
|
|
225
245
|
// 空 fields。
|
|
226
|
-
assert_eq!(
|
|
246
|
+
assert_eq!(
|
|
247
|
+
to_python_json(&sort_value(&json!({"event":"empty"}))),
|
|
248
|
+
r#"{"event": "empty"}"#
|
|
249
|
+
);
|
|
227
250
|
// 类型:bool/null/int 与 Python 一致。
|
|
228
251
|
assert_eq!(
|
|
229
252
|
to_python_json(&sort_value(&json!({"missing":false,"x":null,"n":2}))),
|
|
@@ -235,7 +258,11 @@ mod tests {
|
|
|
235
258
|
fn write_sorts_keys_and_has_ts_event() {
|
|
236
259
|
let ws = temp_ws();
|
|
237
260
|
let log = EventLog::new(&ws);
|
|
238
|
-
log.write(
|
|
261
|
+
log.write(
|
|
262
|
+
"schema.layout_rebuild",
|
|
263
|
+
json!({"table":"messages","row_count_before":2,"row_count_after":2,"missing":false}),
|
|
264
|
+
)
|
|
265
|
+
.unwrap();
|
|
239
266
|
let line = std::fs::read_to_string(ws.join(".team/logs/events.jsonl")).unwrap();
|
|
240
267
|
let line = line.trim_end();
|
|
241
268
|
// 键全排序:event < missing < row_count_after < row_count_before < table < ts。
|
|
@@ -243,7 +270,10 @@ mod tests {
|
|
|
243
270
|
// ts 是合法 rfc3339 UTC。
|
|
244
271
|
let v: Value = serde_json::from_str(line).unwrap();
|
|
245
272
|
let ts = v["ts"].as_str().unwrap();
|
|
246
|
-
assert!(
|
|
273
|
+
assert!(
|
|
274
|
+
chrono::DateTime::parse_from_rfc3339(ts).is_ok(),
|
|
275
|
+
"ts 非合法 rfc3339: {ts}"
|
|
276
|
+
);
|
|
247
277
|
assert!(ts.ends_with("+00:00"));
|
|
248
278
|
}
|
|
249
279
|
|
|
@@ -276,7 +306,11 @@ mod tests {
|
|
|
276
306
|
// 预置 current >= 5MiB + 已有 .1..=.5 archive(各带标记内容)。
|
|
277
307
|
std::fs::write(&p, vec![b'x'; EVENT_LOG_ROTATE_BYTES as usize]).unwrap();
|
|
278
308
|
for i in 1..=EVENT_LOG_ARCHIVE_KEEP {
|
|
279
|
-
std::fs::write(
|
|
309
|
+
std::fs::write(
|
|
310
|
+
p.with_file_name(format!("events.jsonl.{i}")),
|
|
311
|
+
format!("arc{i}"),
|
|
312
|
+
)
|
|
313
|
+
.unwrap();
|
|
280
314
|
}
|
|
281
315
|
// 下一次 write 触发轮转:.5 丢弃,.4→.5 ... .1→.2,current→.1。
|
|
282
316
|
log.write("after.rotate", json!({})).unwrap();
|
|
@@ -285,10 +319,21 @@ mod tests {
|
|
|
285
319
|
assert_eq!(new_current.lines().count(), 1);
|
|
286
320
|
assert!(new_current.contains("after.rotate"));
|
|
287
321
|
// .1 = 旧 current(5MiB 的 x)。
|
|
288
|
-
assert_eq!(
|
|
322
|
+
assert_eq!(
|
|
323
|
+
std::fs::metadata(p.with_file_name("events.jsonl.1"))
|
|
324
|
+
.unwrap()
|
|
325
|
+
.len(),
|
|
326
|
+
EVENT_LOG_ROTATE_BYTES
|
|
327
|
+
);
|
|
289
328
|
// .2 = 旧 .1(内容 "arc1");.5 = 旧 .4("arc4");旧 .5("arc5")被丢弃。
|
|
290
|
-
assert_eq!(
|
|
291
|
-
|
|
329
|
+
assert_eq!(
|
|
330
|
+
std::fs::read_to_string(p.with_file_name("events.jsonl.2")).unwrap(),
|
|
331
|
+
"arc1"
|
|
332
|
+
);
|
|
333
|
+
assert_eq!(
|
|
334
|
+
std::fs::read_to_string(p.with_file_name("events.jsonl.5")).unwrap(),
|
|
335
|
+
"arc4"
|
|
336
|
+
);
|
|
292
337
|
}
|
|
293
338
|
|
|
294
339
|
#[test]
|
|
@@ -319,7 +364,11 @@ mod tests {
|
|
|
319
364
|
for i in 0..3 {
|
|
320
365
|
log.write("e", json!({ "i": i })).unwrap();
|
|
321
366
|
}
|
|
322
|
-
assert_eq!(
|
|
367
|
+
assert_eq!(
|
|
368
|
+
log.tail(0).unwrap().len(),
|
|
369
|
+
3,
|
|
370
|
+
"tail(0) == 全部(Python [-0:])"
|
|
371
|
+
);
|
|
323
372
|
assert_eq!(log.tail(2).unwrap().len(), 2);
|
|
324
373
|
assert_eq!(log.tail(99).unwrap().len(), 3);
|
|
325
374
|
}
|
|
@@ -349,7 +398,8 @@ mod tests {
|
|
|
349
398
|
assert_eq!(lines.len(), 8 * 50, "无半行/丢行");
|
|
350
399
|
for line in &lines {
|
|
351
400
|
// 每行完整合法 JSON 且含 event 键 → 未交错。
|
|
352
|
-
let v: Value =
|
|
401
|
+
let v: Value =
|
|
402
|
+
serde_json::from_str(line).unwrap_or_else(|e| panic!("交错坏行: {line:?} ({e})"));
|
|
353
403
|
assert_eq!(v["event"], json!("concurrent"));
|
|
354
404
|
}
|
|
355
405
|
}
|
|
@@ -367,7 +417,11 @@ mod tests {
|
|
|
367
417
|
continue;
|
|
368
418
|
}
|
|
369
419
|
let v: Value = serde_json::from_str(line).unwrap();
|
|
370
|
-
assert_eq!(
|
|
420
|
+
assert_eq!(
|
|
421
|
+
to_python_json(&sort_value(&v)),
|
|
422
|
+
line,
|
|
423
|
+
"第 {n} 行 round-trip 不字节一致"
|
|
424
|
+
);
|
|
371
425
|
n += 1;
|
|
372
426
|
}
|
|
373
427
|
assert_eq!(n, 60, "fixture 应 60 行");
|
|
@@ -99,7 +99,11 @@ pub fn next_worker_window(
|
|
|
99
99
|
.iter()
|
|
100
100
|
.any(|w| w.as_str() == window.as_str());
|
|
101
101
|
let action = if window_exists {
|
|
102
|
-
if force {
|
|
102
|
+
if force {
|
|
103
|
+
WorkerSpawnAction::ForceReplace
|
|
104
|
+
} else {
|
|
105
|
+
WorkerSpawnAction::Noop
|
|
106
|
+
}
|
|
103
107
|
} else {
|
|
104
108
|
WorkerSpawnAction::NewWindow
|
|
105
109
|
};
|
|
@@ -134,7 +138,11 @@ mod worker_placement_tests {
|
|
|
134
138
|
return WorkerSpawnTarget::new(session, window, WorkerSpawnAction::NewSession);
|
|
135
139
|
}
|
|
136
140
|
let action = if window_exists {
|
|
137
|
-
if force {
|
|
141
|
+
if force {
|
|
142
|
+
WorkerSpawnAction::ForceReplace
|
|
143
|
+
} else {
|
|
144
|
+
WorkerSpawnAction::Noop
|
|
145
|
+
}
|
|
138
146
|
} else {
|
|
139
147
|
WorkerSpawnAction::NewWindow
|
|
140
148
|
};
|
|
@@ -191,8 +199,11 @@ mod tests {
|
|
|
191
199
|
|
|
192
200
|
#[test]
|
|
193
201
|
fn exec_provider_mode_returns_exec_current_pane() {
|
|
194
|
-
let placement =
|
|
195
|
-
|
|
202
|
+
let placement = leader_placement(
|
|
203
|
+
LeaderStartMode::ExecProvider,
|
|
204
|
+
Provider::Claude,
|
|
205
|
+
Path::new("/tmp/x"),
|
|
206
|
+
);
|
|
196
207
|
assert_eq!(placement, LeaderPlacement::ExecCurrentPane);
|
|
197
208
|
}
|
|
198
209
|
|
|
@@ -20,15 +20,15 @@
|
|
|
20
20
|
//! (Step 8)
|
|
21
21
|
//! * `worker_env` — worker_spawn_env whitelist + worker_spawn_cwd (Step 3)
|
|
22
22
|
|
|
23
|
-
pub mod sessions;
|
|
24
23
|
pub mod manager;
|
|
25
|
-
pub mod
|
|
26
|
-
pub mod worker_env;
|
|
24
|
+
pub mod overlay;
|
|
27
25
|
pub mod placement;
|
|
28
26
|
pub mod recovery;
|
|
29
|
-
pub mod overlay;
|
|
30
27
|
pub mod runtime_sessions;
|
|
28
|
+
pub mod sessions;
|
|
31
29
|
pub mod tmux_endpoint;
|
|
30
|
+
pub mod worker_env;
|
|
31
|
+
pub mod worker_window_helpers;
|
|
32
32
|
|
|
33
33
|
pub use runtime_sessions::{
|
|
34
34
|
LeaderLauncherSession, LeaderLauncherSessionError, RuntimeSessionAnomaly, RuntimeSessions,
|
|
@@ -22,7 +22,10 @@ pub fn overlay_window_name(session_tag: &str, group_index: usize) -> WindowName
|
|
|
22
22
|
if group_index == 0 {
|
|
23
23
|
WindowName::new(format!("team-agent:{session_tag}:overview"))
|
|
24
24
|
} else {
|
|
25
|
-
WindowName::new(format!(
|
|
25
|
+
WindowName::new(format!(
|
|
26
|
+
"team-agent:{session_tag}:overview-{}",
|
|
27
|
+
group_index + 1
|
|
28
|
+
))
|
|
26
29
|
}
|
|
27
30
|
}
|
|
28
31
|
|
|
@@ -113,8 +116,12 @@ mod tests {
|
|
|
113
116
|
|
|
114
117
|
#[test]
|
|
115
118
|
fn is_overlay_window_matches_overview_namespace_only() {
|
|
116
|
-
assert!(is_overlay_window(&WindowName::new(
|
|
117
|
-
|
|
119
|
+
assert!(is_overlay_window(&WindowName::new(
|
|
120
|
+
"team-agent:alpha:overview"
|
|
121
|
+
)));
|
|
122
|
+
assert!(is_overlay_window(&WindowName::new(
|
|
123
|
+
"team-agent:alpha:overview-2"
|
|
124
|
+
)));
|
|
118
125
|
assert!(!is_overlay_window(&WindowName::new("developer")));
|
|
119
126
|
assert!(!is_overlay_window(&WindowName::new("team-w1")));
|
|
120
127
|
assert!(!is_overlay_window(&WindowName::new("team-alpha:leader")));
|
|
@@ -92,9 +92,11 @@ mod tests {
|
|
|
92
92
|
match recover_leader_pane(env) {
|
|
93
93
|
RecoveryOutcome::NeedsUserAttach => {} // expected
|
|
94
94
|
RecoveryOutcome::AutobindToPane(_) => {
|
|
95
|
-
panic!(
|
|
95
|
+
panic!(
|
|
96
|
+
"recovery must not autobind without TMUX_PANE — \
|
|
96
97
|
the worker session must NEVER carry a co-located leader \
|
|
97
|
-
window in managed mode (E57-3 root)"
|
|
98
|
+
window in managed mode (E57-3 root)"
|
|
99
|
+
)
|
|
98
100
|
}
|
|
99
101
|
}
|
|
100
102
|
}
|
|
@@ -205,9 +205,12 @@ impl RuntimeSessions {
|
|
|
205
205
|
/// True if a `state.session_name` looked like a leader launcher session
|
|
206
206
|
/// — the exact 0.3.39 shape unit-3 must refuse before any kill.
|
|
207
207
|
pub fn worker_session_name_is_leader_prefixed(&self) -> bool {
|
|
208
|
-
self.anomalies
|
|
209
|
-
|
|
210
|
-
|
|
208
|
+
self.anomalies.iter().any(|a| {
|
|
209
|
+
matches!(
|
|
210
|
+
a,
|
|
211
|
+
RuntimeSessionAnomaly::WorkerSessionNameIsLeaderPrefixed { .. }
|
|
212
|
+
)
|
|
213
|
+
})
|
|
211
214
|
}
|
|
212
215
|
}
|
|
213
216
|
|
|
@@ -261,10 +264,7 @@ mod tests {
|
|
|
261
264
|
});
|
|
262
265
|
let r = RuntimeSessions::from_state(&state);
|
|
263
266
|
assert_eq!(r.worker.unwrap().as_str(), "team-foo");
|
|
264
|
-
assert_eq!(
|
|
265
|
-
r.leader.unwrap().as_str(),
|
|
266
|
-
"team-agent-leader-codex-abc"
|
|
267
|
-
);
|
|
267
|
+
assert_eq!(r.leader.unwrap().as_str(), "team-agent-leader-codex-abc");
|
|
268
268
|
assert!(r.anomalies.is_empty());
|
|
269
269
|
}
|
|
270
270
|
|
|
@@ -102,12 +102,13 @@ pub fn assert_topology_invariants(state: &JsonValue, spec: &YamlValue) -> Vec<To
|
|
|
102
102
|
),
|
|
103
103
|
});
|
|
104
104
|
}
|
|
105
|
-
let agents = state
|
|
106
|
-
.get("agents")
|
|
107
|
-
.and_then(JsonValue::as_object);
|
|
105
|
+
let agents = state.get("agents").and_then(JsonValue::as_object);
|
|
108
106
|
if let Some(agents) = agents {
|
|
109
107
|
for (agent_id, agent) in agents {
|
|
110
|
-
let window = agent
|
|
108
|
+
let window = agent
|
|
109
|
+
.get("window")
|
|
110
|
+
.and_then(JsonValue::as_str)
|
|
111
|
+
.unwrap_or("");
|
|
111
112
|
if window.eq_ignore_ascii_case("leader") {
|
|
112
113
|
out.push(TopologyViolation {
|
|
113
114
|
kind: TopologyViolationKind::WorkerWindowNamedLeader,
|
|
@@ -120,7 +121,8 @@ pub fn assert_topology_invariants(state: &JsonValue, spec: &YamlValue) -> Vec<To
|
|
|
120
121
|
}
|
|
121
122
|
}
|
|
122
123
|
// Pane-id collision among agents.
|
|
123
|
-
let mut by_pane: std::collections::HashMap<String, Vec<String>> =
|
|
124
|
+
let mut by_pane: std::collections::HashMap<String, Vec<String>> =
|
|
125
|
+
std::collections::HashMap::new();
|
|
124
126
|
for (agent_id, agent) in agents {
|
|
125
127
|
if let Some(pane) = agent
|
|
126
128
|
.get("pane_id")
|
|
@@ -229,7 +231,10 @@ mod tests {
|
|
|
229
231
|
"leader_receiver": { "pane_id": "%0" }
|
|
230
232
|
});
|
|
231
233
|
let v = assert_topology_invariants(&state, &spec_for("alpha"));
|
|
232
|
-
assert!(
|
|
234
|
+
assert!(
|
|
235
|
+
v.is_empty(),
|
|
236
|
+
"clean state should produce no violations; got {v:?}"
|
|
237
|
+
);
|
|
233
238
|
}
|
|
234
239
|
|
|
235
240
|
#[test]
|
|
@@ -243,7 +248,8 @@ mod tests {
|
|
|
243
248
|
});
|
|
244
249
|
let v = assert_topology_invariants(&state, &spec_for("alpha"));
|
|
245
250
|
assert!(
|
|
246
|
-
v.iter()
|
|
251
|
+
v.iter()
|
|
252
|
+
.any(|x| matches!(x.kind, TopologyViolationKind::AgentPaneIdCollision)),
|
|
247
253
|
"must flag AgentPaneIdCollision; got {v:?}"
|
|
248
254
|
);
|
|
249
255
|
}
|
|
@@ -258,7 +264,8 @@ mod tests {
|
|
|
258
264
|
});
|
|
259
265
|
let v = assert_topology_invariants(&state, &spec_for("alpha"));
|
|
260
266
|
assert!(
|
|
261
|
-
v.iter()
|
|
267
|
+
v.iter()
|
|
268
|
+
.any(|x| matches!(x.kind, TopologyViolationKind::LeaderPaneIdCollidesWithAgent)),
|
|
262
269
|
"must flag LeaderPaneIdCollidesWithAgent; got {v:?}"
|
|
263
270
|
);
|
|
264
271
|
}
|
|
@@ -272,7 +279,8 @@ mod tests {
|
|
|
272
279
|
});
|
|
273
280
|
let v = assert_topology_invariants(&state, &spec_for("alpha"));
|
|
274
281
|
assert!(
|
|
275
|
-
v.iter()
|
|
282
|
+
v.iter()
|
|
283
|
+
.any(|x| matches!(x.kind, TopologyViolationKind::WorkerWindowNamedLeader)),
|
|
276
284
|
"must flag WorkerWindowNamedLeader; got {v:?}"
|
|
277
285
|
);
|
|
278
286
|
}
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
//! policy here mirrors it byte-for-byte (verified by `endpoint_priority_matches_backend`
|
|
18
18
|
//! contract tests).
|
|
19
19
|
|
|
20
|
-
use std::path::Path;
|
|
21
20
|
use serde_json::Value;
|
|
21
|
+
use std::path::Path;
|
|
22
22
|
|
|
23
23
|
/// Which state field (or fallback) supplied the chosen endpoint.
|
|
24
24
|
///
|