@team-agent/installer 0.3.39 → 0.4.0
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/Cargo.toml +7 -0
- package/crates/team-agent/src/cli/adapters.rs +8 -3
- package/crates/team-agent/src/cli/diagnose.rs +12 -3
- package/crates/team-agent/src/cli/emit.rs +1 -0
- package/crates/team-agent/src/cli/mod.rs +250 -9
- package/crates/team-agent/src/cli/send.rs +11 -2
- package/crates/team-agent/src/cli/status_port.rs +39 -4
- package/crates/team-agent/src/cli/tests/shutdown_kill_plan.rs +189 -6
- package/crates/team-agent/src/cli/tests/status_send.rs +189 -0
- package/crates/team-agent/src/cli/tests/verb_settle.rs +2 -0
- package/crates/team-agent/src/cli/types.rs +5 -0
- package/crates/team-agent/src/coordinator/mod.rs +1 -0
- package/crates/team-agent/src/coordinator/steps/abnormal.rs +4 -0
- package/crates/team-agent/src/coordinator/steps/delivery.rs +4 -0
- package/crates/team-agent/src/coordinator/steps/health_sync.rs +4 -0
- package/crates/team-agent/src/coordinator/steps/mod.rs +83 -0
- package/crates/team-agent/src/coordinator/steps/persist.rs +4 -0
- package/crates/team-agent/src/coordinator/steps/runtime_prompts.rs +4 -0
- package/crates/team-agent/src/coordinator/steps/session_gate.rs +5 -0
- package/crates/team-agent/src/coordinator/tests/health_sync.rs +156 -0
- package/crates/team-agent/src/coordinator/tick.rs +126 -30
- package/crates/team-agent/src/{message_store.rs → db/message_store.rs} +6 -0
- package/crates/team-agent/src/db/mod.rs +1 -0
- package/crates/team-agent/src/layout/mod.rs +7 -0
- package/crates/team-agent/src/layout/runtime_sessions.rs +312 -0
- package/crates/team-agent/src/layout/tmux_endpoint.rs +162 -0
- package/crates/team-agent/src/leader/start.rs +27 -1
- package/crates/team-agent/src/leader/tests/identity.rs +50 -0
- package/crates/team-agent/src/lib.rs +6 -2
- package/crates/team-agent/src/lifecycle/launch/readiness.rs +32 -0
- package/crates/team-agent/src/lifecycle/launch/spawn.rs +32 -0
- package/crates/team-agent/src/lifecycle/launch/spec_state.rs +51 -0
- package/crates/team-agent/src/lifecycle/launch.rs +34 -0
- package/crates/team-agent/src/lifecycle/restart/agent.rs +17 -0
- package/crates/team-agent/src/lifecycle/restart/common.rs +143 -23
- package/crates/team-agent/src/lifecycle/restart/preflight.rs +87 -0
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +308 -0
- package/crates/team-agent/src/lifecycle/restart/selection.rs +87 -22
- package/crates/team-agent/src/lifecycle/restart.rs +6 -0
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +6 -1
- package/crates/team-agent/src/lifecycle/tests/restart.rs +187 -0
- package/crates/team-agent/src/lifecycle/tests.rs +1 -0
- package/crates/team-agent/src/lifecycle/types.rs +20 -1
- package/crates/team-agent/src/mcp_server/helpers.rs +72 -1
- package/crates/team-agent/src/mcp_server/tools.rs +29 -4
- package/crates/team-agent/src/provider/adapter.rs +31 -1
- package/crates/team-agent/src/provider/approvals/runtime_prompts.rs +34 -0
- package/crates/team-agent/src/provider/classify.rs +138 -0
- package/crates/team-agent/src/provider/command.rs +71 -0
- package/crates/team-agent/src/provider/mod.rs +3 -0
- package/crates/team-agent/src/{session_capture.rs → provider/session/capture.rs} +139 -6
- package/crates/team-agent/src/provider/session/mod.rs +13 -0
- package/crates/team-agent/src/provider/session/resume.rs +417 -0
- package/crates/team-agent/src/provider/session_scan.rs +63 -0
- package/crates/team-agent/src/provider/types.rs +5 -0
- package/crates/team-agent/src/state/persist.rs +238 -0
- package/crates/team-agent/src/tmux_backend.rs +25 -0
- package/npm/install.mjs +27 -1
- package/package.json +4 -4
|
@@ -262,3 +262,159 @@ fn contract_working_worker_stays_alive_and_deliverable_in_real_tick() {
|
|
|
262
262
|
deferred_busy (golden delivers; fake workers are permanently WORKING). got {events:?}"
|
|
263
263
|
);
|
|
264
264
|
}
|
|
265
|
+
|
|
266
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
267
|
+
// RM-039-STAT-001 third-round RED contracts (architect verdict 2026-06-22).
|
|
268
|
+
//
|
|
269
|
+
// The provider JSONL classifier already recognizes Codex
|
|
270
|
+
// `event_msg/task_started` as an open turn (verified at b6807ba via
|
|
271
|
+
// `p2_codex_event_msg_task_started_is_open_turn`). The third-round bug is
|
|
272
|
+
// in the COORDINATOR ACTIVITY REFRESH BOUNDARY: JSONL classification was
|
|
273
|
+
// gated behind pane-target/window checks and warm-idle suppression, so a
|
|
274
|
+
// stale pane-fallback `idle_prompt` could become the persisted activity
|
|
275
|
+
// truth and then survive across ticks because warm-idle suppression
|
|
276
|
+
// blocked re-classification — even when the rollout grew and the worker
|
|
277
|
+
// process was alive.
|
|
278
|
+
//
|
|
279
|
+
// Fix: JSONL has the floor. If it returns a definite activity, write
|
|
280
|
+
// activity + agent_health BEFORE any pane work and BYPASS warm-idle
|
|
281
|
+
// suppression for that agent on that tick.
|
|
282
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
283
|
+
|
|
284
|
+
/// Write a real-shape Codex rollout prefix to `path`. The prefix ends
|
|
285
|
+
/// after `event_msg/task_started` (no `task_complete` follows), matching
|
|
286
|
+
/// the architect's specification.
|
|
287
|
+
fn write_codex_rollout_prefix_task_started(path: &std::path::Path) {
|
|
288
|
+
if let Some(parent) = path.parent() {
|
|
289
|
+
std::fs::create_dir_all(parent).unwrap();
|
|
290
|
+
}
|
|
291
|
+
// session_meta + task_started — minimal but realistic shape per
|
|
292
|
+
// architect §2 ("real rollout uses modern Codex records: session_meta,
|
|
293
|
+
// event_msg, response_item, turn_context"). The classifier needs only
|
|
294
|
+
// task_started to declare an open turn (provider/classify.rs:364-398).
|
|
295
|
+
let body = "{\"type\":\"session_meta\",\"session_id\":\"019eeda2-7e2f-77a1-bc66-f8d485c8d2c8\",\"created\":\"2026-06-22T12:41:49Z\"}\n\
|
|
296
|
+
{\"type\":\"event_msg\",\"payload\":{\"type\":\"task_started\"},\"ts\":\"2026-06-22T12:42:54Z\"}\n";
|
|
297
|
+
std::fs::write(path, body).unwrap();
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/// Write a real-shape Codex rollout ending with `event_msg/task_complete`
|
|
301
|
+
/// (full turn close). The classifier MUST return Idle with rationale
|
|
302
|
+
/// `provider_jsonl:task_complete`.
|
|
303
|
+
fn write_codex_rollout_task_complete(path: &std::path::Path) {
|
|
304
|
+
if let Some(parent) = path.parent() {
|
|
305
|
+
std::fs::create_dir_all(parent).unwrap();
|
|
306
|
+
}
|
|
307
|
+
let body = "{\"type\":\"session_meta\",\"session_id\":\"019eeda2-7e2f-77a1-bc66-f8d485c8d2c8\",\"created\":\"2026-06-22T12:41:49Z\"}\n\
|
|
308
|
+
{\"type\":\"event_msg\",\"payload\":{\"type\":\"task_started\"},\"ts\":\"2026-06-22T12:42:54Z\"}\n\
|
|
309
|
+
{\"type\":\"event_msg\",\"payload\":{\"type\":\"task_complete\"},\"ts\":\"2026-06-22T12:46:08Z\"}\n";
|
|
310
|
+
std::fs::write(path, body).unwrap();
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
fn agent_activity_rationale(dir: &std::path::Path, agent: &str) -> Option<String> {
|
|
314
|
+
let state = crate::state::persist::load_runtime_state(dir).ok()?;
|
|
315
|
+
state
|
|
316
|
+
.get("agents")?
|
|
317
|
+
.get(agent)?
|
|
318
|
+
.get("activity")?
|
|
319
|
+
.get("rationale")?
|
|
320
|
+
.as_str()
|
|
321
|
+
.map(str::to_string)
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// RED #2 (architect §6.2): an agent currently marked idle with
|
|
325
|
+
// `coordinator_idle_capture_next_at` in the future MUST still get
|
|
326
|
+
// re-classified as `working` once its rollout JSONL records
|
|
327
|
+
// `event_msg/task_started`. The JSONL classifier must beat warm-idle
|
|
328
|
+
// suppression.
|
|
329
|
+
#[test]
|
|
330
|
+
fn rm039_stat001_jsonl_overrides_warm_idle_suppression_for_open_turn() {
|
|
331
|
+
use std::sync::atomic::{AtomicU64, Ordering};
|
|
332
|
+
static N: AtomicU64 = AtomicU64::new(0);
|
|
333
|
+
let n = N.fetch_add(1, Ordering::Relaxed);
|
|
334
|
+
let rollout = std::env::temp_dir().join(format!(
|
|
335
|
+
"ta-rs-rm039-r3-rollout-open-{}-{}.jsonl",
|
|
336
|
+
std::process::id(),
|
|
337
|
+
n
|
|
338
|
+
));
|
|
339
|
+
write_codex_rollout_prefix_task_started(&rollout);
|
|
340
|
+
// future timestamp — would have suppressed pane re-capture
|
|
341
|
+
let next_at = (chrono::Utc::now() + chrono::Duration::seconds(120)).to_rfc3339();
|
|
342
|
+
let agents = serde_json::json!({
|
|
343
|
+
"stat-worker": {
|
|
344
|
+
"provider": "codex",
|
|
345
|
+
"window": "w1",
|
|
346
|
+
"pane_id": "%1",
|
|
347
|
+
"status": "running",
|
|
348
|
+
"session_id": "019eeda2-7e2f-77a1-bc66-f8d485c8d2c8",
|
|
349
|
+
"rollout_path": rollout.to_string_lossy(),
|
|
350
|
+
"activity": {"status": "idle", "confidence": 0.9, "rationale": "idle_prompt"},
|
|
351
|
+
"coordinator_idle_capture_next_at": next_at,
|
|
352
|
+
"pane_pid_status": "pid_running:99999",
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
// Stale pane scrollback that pane-fallback would classify as idle.
|
|
356
|
+
let (coord, dir) = seeded_health_coord(agents, "previous output\n❯\n");
|
|
357
|
+
coord.tick().expect("tick");
|
|
358
|
+
let status = agent_activity_status(&dir, "stat-worker");
|
|
359
|
+
assert_eq!(
|
|
360
|
+
status.as_deref(),
|
|
361
|
+
Some("working"),
|
|
362
|
+
"RM-039-STAT-001 third-round: JSONL `task_started` must override warm-idle \
|
|
363
|
+
suppression even when the pane scrollback still shows the idle prompt; got {status:?}"
|
|
364
|
+
);
|
|
365
|
+
let rationale = agent_activity_rationale(&dir, "stat-worker");
|
|
366
|
+
assert!(
|
|
367
|
+
rationale
|
|
368
|
+
.as_deref()
|
|
369
|
+
.is_some_and(|r| r.starts_with("provider_jsonl:")),
|
|
370
|
+
"rationale must come from the provider JSONL path (provider_jsonl:*), not pane fallback; \
|
|
371
|
+
got {rationale:?}"
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// RED #4 (architect §6.4): a full rollout ending in `event_msg/task_complete`
|
|
376
|
+
// MUST return/persist idle with rationale `provider_jsonl:task_complete`.
|
|
377
|
+
// This preserves the E47 stale-working fix and proves the JSONL-first
|
|
378
|
+
// reorder did not regress the close-turn classification.
|
|
379
|
+
#[test]
|
|
380
|
+
fn rm039_stat001_jsonl_close_turn_preserves_e47_idle_classification() {
|
|
381
|
+
use std::sync::atomic::{AtomicU64, Ordering};
|
|
382
|
+
static N: AtomicU64 = AtomicU64::new(0);
|
|
383
|
+
let n = N.fetch_add(1, Ordering::Relaxed);
|
|
384
|
+
let rollout = std::env::temp_dir().join(format!(
|
|
385
|
+
"ta-rs-rm039-r3-rollout-close-{}-{}.jsonl",
|
|
386
|
+
std::process::id(),
|
|
387
|
+
n
|
|
388
|
+
));
|
|
389
|
+
write_codex_rollout_task_complete(&rollout);
|
|
390
|
+
let agents = serde_json::json!({
|
|
391
|
+
"stat-worker": {
|
|
392
|
+
"provider": "codex",
|
|
393
|
+
"window": "w1",
|
|
394
|
+
"pane_id": "%1",
|
|
395
|
+
"status": "running",
|
|
396
|
+
"session_id": "019eeda2-7e2f-77a1-bc66-f8d485c8d2c8",
|
|
397
|
+
"rollout_path": rollout.to_string_lossy(),
|
|
398
|
+
"pane_pid_status": "pid_running:99999",
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
// The pane Tail(40) still shows the live working spinner — the E47
|
|
402
|
+
// stale-working scenario. The JSONL `task_complete` MUST close the
|
|
403
|
+
// turn anyway.
|
|
404
|
+
let (coord, dir) =
|
|
405
|
+
seeded_health_coord(agents, "Working (5s · esc to interrupt)\n• Working\n");
|
|
406
|
+
coord.tick().expect("tick");
|
|
407
|
+
let status = agent_activity_status(&dir, "stat-worker");
|
|
408
|
+
assert_eq!(
|
|
409
|
+
status.as_deref(),
|
|
410
|
+
Some("idle"),
|
|
411
|
+
"RM-039-STAT-001 third-round must preserve E47: `task_complete` closes the turn \
|
|
412
|
+
even when pane scrollback still shows a working spinner; got {status:?}"
|
|
413
|
+
);
|
|
414
|
+
let rationale = agent_activity_rationale(&dir, "stat-worker");
|
|
415
|
+
assert_eq!(
|
|
416
|
+
rationale.as_deref(),
|
|
417
|
+
Some("provider_jsonl:task_complete"),
|
|
418
|
+
"rationale must be `provider_jsonl:task_complete`, not stale pane working; got {rationale:?}"
|
|
419
|
+
);
|
|
420
|
+
}
|
|
@@ -99,6 +99,33 @@ pub enum TickError {
|
|
|
99
99
|
Panic(String),
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
/// Issue 2 (Round 3b gate review §6): when the runtime state has
|
|
103
|
+
/// `active_team_key` AND `teams.<key>` is a populated object, return the
|
|
104
|
+
/// team-scoped projection so the coordinator's tick reads `session_name` /
|
|
105
|
+
/// `agents` / `leader_receiver` from the team's nested entry rather than
|
|
106
|
+
/// the top-level (often stale) view. When the projection cannot be derived,
|
|
107
|
+
/// returns `None` and the tick falls back to the raw state — preserving
|
|
108
|
+
/// behavior for legacy single-team workspaces and tests that don't seed
|
|
109
|
+
/// `teams.<key>`. Sibling teams under `state.teams.*` are NOT touched.
|
|
110
|
+
fn coordinator_team_scoped_state(
|
|
111
|
+
workspace: &std::path::Path,
|
|
112
|
+
raw_state: &Value,
|
|
113
|
+
) -> Option<Value> {
|
|
114
|
+
let active = raw_state
|
|
115
|
+
.get("active_team_key")
|
|
116
|
+
.and_then(Value::as_str)
|
|
117
|
+
.filter(|s| !s.is_empty())?;
|
|
118
|
+
let has_team_entry = raw_state
|
|
119
|
+
.get("teams")
|
|
120
|
+
.and_then(Value::as_object)
|
|
121
|
+
.map(|teams| teams.contains_key(active))
|
|
122
|
+
.unwrap_or(false);
|
|
123
|
+
if !has_team_entry {
|
|
124
|
+
return None;
|
|
125
|
+
}
|
|
126
|
+
crate::state::projection::select_runtime_state(workspace, Some(active)).ok()
|
|
127
|
+
}
|
|
128
|
+
|
|
102
129
|
// ===========================================================================
|
|
103
130
|
// Coordinator struct(daemon lifecycle + tick orchestration)
|
|
104
131
|
// ===========================================================================
|
|
@@ -190,7 +217,18 @@ impl Coordinator {
|
|
|
190
217
|
/// 副作用 ORDER 测试断言固定序列)。生产两者均 `None`,零开销。
|
|
191
218
|
pub fn tick(&self) -> Result<TickReport, TickError> {
|
|
192
219
|
self.record_step("load_state");
|
|
193
|
-
let
|
|
220
|
+
let raw_state = crate::state::persist::load_runtime_state(self.workspace.as_path())?;
|
|
221
|
+
// Issue 2 (Round 3b gate review §6): when the runtime carries
|
|
222
|
+
// `active_team_key` AND `teams.<key>` exists, project the team-scoped
|
|
223
|
+
// view (session_name / agents / leader_receiver come from the team's
|
|
224
|
+
// nested object) instead of the raw top-level state. Otherwise the
|
|
225
|
+
// coordinator would probe the wrong tmux session (e.g. stale
|
|
226
|
+
// `session_name=team-tmp` while the real team session is
|
|
227
|
+
// `team-prerelease-040-round3b`) and emit `coordinator.session_missing`
|
|
228
|
+
// even though the right session is alive. Fall back to raw state when
|
|
229
|
+
// no team scope can be derived (legacy single-team workspaces).
|
|
230
|
+
let mut state = coordinator_team_scoped_state(self.workspace.as_path(), &raw_state)
|
|
231
|
+
.unwrap_or(raw_state);
|
|
194
232
|
let store = crate::message_store::MessageStore::open(self.workspace.as_path())?;
|
|
195
233
|
let event_log = EventLog::new(self.workspace.as_path());
|
|
196
234
|
increment_coordinator_tick_iteration_count(&self.workspace);
|
|
@@ -406,6 +444,29 @@ impl Coordinator {
|
|
|
406
444
|
true,
|
|
407
445
|
0,
|
|
408
446
|
)?;
|
|
447
|
+
// RM-039-STAT-001 third-round fix (architect verdict 2026-06-22):
|
|
448
|
+
// when capture_missing_sessions assigns a new rollout_path to an
|
|
449
|
+
// agent, clear that agent's `coordinator_idle_capture_next_at` so
|
|
450
|
+
// the JSONL classifier is not gated by stale warm-idle suppression
|
|
451
|
+
// on the SAME tick that the rollout becomes readable. Without this,
|
|
452
|
+
// an agent that was IDLE before attribution would carry the old
|
|
453
|
+
// suppression timestamp into sync_agent_health and only the
|
|
454
|
+
// expensive pane Tail(40) path would observe its working state.
|
|
455
|
+
if !report.assigned.is_empty() {
|
|
456
|
+
if let Some(agents) = state
|
|
457
|
+
.get_mut("agents")
|
|
458
|
+
.and_then(serde_json::Value::as_object_mut)
|
|
459
|
+
{
|
|
460
|
+
for assigned_agent_id in &report.assigned {
|
|
461
|
+
if let Some(agent_obj) = agents
|
|
462
|
+
.get_mut(assigned_agent_id)
|
|
463
|
+
.and_then(serde_json::Value::as_object_mut)
|
|
464
|
+
{
|
|
465
|
+
agent_obj.remove("coordinator_idle_capture_next_at");
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
409
470
|
for failure in report.capture_failures {
|
|
410
471
|
event_log.write(
|
|
411
472
|
"coordinator.tick.capture_missing_failed",
|
|
@@ -466,6 +527,38 @@ impl Coordinator {
|
|
|
466
527
|
if !agent_probe_base_eligible(agent) {
|
|
467
528
|
continue;
|
|
468
529
|
}
|
|
530
|
+
// RM-039-STAT-001 third-round fix (architect verdict 2026-06-22):
|
|
531
|
+
// try the provider JSONL classifier FIRST, BEFORE any pane-target
|
|
532
|
+
// / window checks and BEFORE warm_idle_capture_suppressed. The
|
|
533
|
+
// historical chain ran JSONL only inside the pane-capture branch,
|
|
534
|
+
// so a stale pane-fallback `idle_prompt` could land first
|
|
535
|
+
// (before session attribution completed), then
|
|
536
|
+
// `warm_idle_capture_suppressed` would gate subsequent ticks for
|
|
537
|
+
// IDLE_HEALTH_CAPTURE_INTERVAL_SECS even after JSONL gained a
|
|
538
|
+
// `task_started` record + the worker process was alive. Now: if
|
|
539
|
+
// the JSONL classifier returns a definite activity (not Unknown),
|
|
540
|
+
// we write activity + agent_health here and bypass the warm-idle
|
|
541
|
+
// suppression for the rest of this iteration. Pane capture still
|
|
542
|
+
// runs so the CapturedRuntimeFact map stays populated for
|
|
543
|
+
// downstream runtime detectors; only the activity classify
|
|
544
|
+
// happens once.
|
|
545
|
+
let jsonl_first = jsonl_activity_for_agent(agent);
|
|
546
|
+
if let Some(activity) = jsonl_first.as_ref() {
|
|
547
|
+
remember_idle_capture_schedule(agent, activity);
|
|
548
|
+
write_activity(agent, activity, false);
|
|
549
|
+
let last_output_at_now = agent
|
|
550
|
+
.get("last_output_at")
|
|
551
|
+
.and_then(Value::as_str)
|
|
552
|
+
.map(str::to_string);
|
|
553
|
+
write_agent_health(
|
|
554
|
+
store,
|
|
555
|
+
&team,
|
|
556
|
+
agent_id,
|
|
557
|
+
agent,
|
|
558
|
+
activity,
|
|
559
|
+
last_output_at_now.as_deref(),
|
|
560
|
+
)?;
|
|
561
|
+
}
|
|
469
562
|
let Some((session, window, target)) =
|
|
470
563
|
capture_window_target(agent, session_name.as_deref())
|
|
471
564
|
else {
|
|
@@ -495,7 +588,13 @@ impl Coordinator {
|
|
|
495
588
|
if !windows.iter().any(|known| known == &window) {
|
|
496
589
|
continue;
|
|
497
590
|
}
|
|
498
|
-
|
|
591
|
+
// Warm-idle suppression still gates pane fallback ONLY. When
|
|
592
|
+
// JSONL above produced a definite activity, we already wrote it,
|
|
593
|
+
// so warm-idle gating no longer matters for the activity path —
|
|
594
|
+
// but we still want CapturedRuntimeFact populated for downstream
|
|
595
|
+
// detectors, so fall through into the pane capture block below
|
|
596
|
+
// (skipping the pane-classify activity write at the bottom).
|
|
597
|
+
if jsonl_first.is_none() && warm_idle_capture_suppressed(agent, has_work_obligation) {
|
|
499
598
|
continue;
|
|
500
599
|
}
|
|
501
600
|
let captured = match self
|
|
@@ -544,38 +643,35 @@ impl Coordinator {
|
|
|
544
643
|
.get("last_output_at")
|
|
545
644
|
.and_then(Value::as_str)
|
|
546
645
|
.map(str::to_string);
|
|
547
|
-
// E47 (0.3.24 P0, idle/busy 假阳):
|
|
548
|
-
//
|
|
549
|
-
//
|
|
550
|
-
//
|
|
551
|
-
//
|
|
552
|
-
//
|
|
553
|
-
//
|
|
554
|
-
//
|
|
555
|
-
//
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
let activity = activity_from_jsonl.unwrap_or_else(|| {
|
|
560
|
-
crate::messaging::classify_agent_activity(
|
|
646
|
+
// E47 (0.3.24 P0, idle/busy 假阳): the provider JSONL classifier
|
|
647
|
+
// already ran at the top of this iteration. If it returned a
|
|
648
|
+
// definite fact (Some), activity + agent_health were written
|
|
649
|
+
// there; we MUST NOT classify-and-overwrite here, otherwise the
|
|
650
|
+
// pane fallback would flip the JSONL truth back to a stale
|
|
651
|
+
// idle_prompt. Only run the pane fallback when JSONL returned
|
|
652
|
+
// None (no readable rollout, TurnState::Unknown, or unparseable
|
|
653
|
+
// log). RM-039-STAT-001 third-round fix (architect verdict
|
|
654
|
+
// 2026-06-22).
|
|
655
|
+
let last_output_at = last_output_at_now;
|
|
656
|
+
if jsonl_first.is_none() {
|
|
657
|
+
let activity = crate::messaging::classify_agent_activity(
|
|
561
658
|
&snapshot,
|
|
562
659
|
&captured.text,
|
|
563
660
|
pane_in_mode,
|
|
564
661
|
current_command.as_deref(),
|
|
565
|
-
|
|
566
|
-
)
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
)?;
|
|
662
|
+
last_output_at.as_deref(),
|
|
663
|
+
);
|
|
664
|
+
remember_idle_capture_schedule(agent, &activity);
|
|
665
|
+
write_activity(agent, &activity, false);
|
|
666
|
+
write_agent_health(
|
|
667
|
+
store,
|
|
668
|
+
&team,
|
|
669
|
+
agent_id,
|
|
670
|
+
agent,
|
|
671
|
+
&activity,
|
|
672
|
+
last_output_at.as_deref(),
|
|
673
|
+
)?;
|
|
674
|
+
}
|
|
579
675
|
let pane_info = matching_capture_pane_info(agent, &session, &window, pane_infos);
|
|
580
676
|
let pane_id = pane_info
|
|
581
677
|
.as_ref()
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
// unit-10 (Stage 4): moved from crate::message_store to crate::db::message_store.
|
|
2
|
+
// The historical body uses unwrap/expect/panic for SQL-row failures; the parent
|
|
3
|
+
// db/ module denies these lints, so re-allow them here so the move is a pure
|
|
4
|
+
// physical relocation (zero behavior change).
|
|
5
|
+
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
|
6
|
+
|
|
1
7
|
//! step 7 · message_store — core message lifecycle over `team.db`.
|
|
2
8
|
//!
|
|
3
9
|
//! Truth source (READ-ONLY): `team-agent-public` @ v0.2.11,
|
|
@@ -27,3 +27,10 @@ pub mod worker_env;
|
|
|
27
27
|
pub mod placement;
|
|
28
28
|
pub mod recovery;
|
|
29
29
|
pub mod overlay;
|
|
30
|
+
pub mod runtime_sessions;
|
|
31
|
+
pub mod tmux_endpoint;
|
|
32
|
+
|
|
33
|
+
pub use runtime_sessions::{
|
|
34
|
+
LeaderLauncherSession, LeaderLauncherSessionError, RuntimeSessionAnomaly, RuntimeSessions,
|
|
35
|
+
WorkerSession, WorkerSessionError,
|
|
36
|
+
};
|