mirror of
https://github.com/instructkr/claude-code.git
synced 2026-05-16 19:06:44 +00:00
Make G006 task policy state machine executable
Typed task packets, policy decisions, lane board status, and session liveness now have concrete runtime contracts and focused regressions for Stream 4. Constraint: G006 requires task/lane operation without pane scraping while preserving legacy task packet callers. Rejected: waiting on stale worker worktrees | all G006 worker worktrees remained at main with no commits, so leader integrated the verified slice directly. Confidence: high Scope-risk: moderate Directive: Keep task packet serde defaults when adding fields so older packets continue to deserialize. Tested: git diff --check; cargo fmt --manifest-path rust/Cargo.toml --all -- --check; cargo check --manifest-path rust/Cargo.toml -p runtime -p tools -p rusty-claude-cli; cargo test --manifest-path rust/Cargo.toml -p runtime task_packet -- --nocapture; cargo test --manifest-path rust/Cargo.toml -p runtime policy_engine -- --nocapture; cargo test --manifest-path rust/Cargo.toml -p runtime task_registry -- --nocapture; cargo test --manifest-path rust/Cargo.toml -p runtime session_heartbeat -- --nocapture; cargo test --manifest-path rust/Cargo.toml -p tools run_task_packet_creates_packet_backed_task -- --nocapture; cargo test --manifest-path rust/Cargo.toml -p tools lane_completion -- --nocapture; cargo test --manifest-path rust/Cargo.toml -p rusty-claude-cli status_json_surfaces -- --nocapture Not-tested: full workspace test suite; PR/issue reconciliation deferred to G011/G012 Co-authored-by: OmX <omx@oh-my-codex.dev>
This commit is contained in:
@@ -8,6 +8,7 @@ use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use crate::json::{JsonError, JsonValue};
|
||||
use crate::usage::TokenUsage;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
const SESSION_VERSION: u32 = 1;
|
||||
const ROTATE_AFTER_BYTES: u64 = 256 * 1024;
|
||||
@@ -82,6 +83,25 @@ struct SessionPersistence {
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
/// Running-state liveness classification for a session heartbeat.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum SessionLiveness {
|
||||
Healthy,
|
||||
Stalled,
|
||||
TransportDead,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
/// Heartbeat emitted from canonical session state, independent of terminal rendering.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct SessionHeartbeat {
|
||||
pub session_id: String,
|
||||
pub observed_at_ms: u64,
|
||||
pub transport_alive: bool,
|
||||
pub liveness: SessionLiveness,
|
||||
}
|
||||
|
||||
/// Persisted conversational state for the runtime and CLI session manager.
|
||||
///
|
||||
/// `workspace_root` binds the session to the worktree it was created in. The
|
||||
@@ -250,6 +270,35 @@ impl Session {
|
||||
self.push_message(ConversationMessage::user_text(text))
|
||||
}
|
||||
|
||||
pub fn record_health_check(&mut self, timestamp_ms: u64) {
|
||||
self.last_health_check_ms = Some(timestamp_ms);
|
||||
self.touch();
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn heartbeat_at(
|
||||
&self,
|
||||
now_ms: u64,
|
||||
stalled_after_ms: u64,
|
||||
transport_alive: bool,
|
||||
) -> SessionHeartbeat {
|
||||
let liveness = match (transport_alive, self.last_health_check_ms) {
|
||||
(false, _) => SessionLiveness::TransportDead,
|
||||
(true, Some(last)) if now_ms.saturating_sub(last) <= stalled_after_ms => {
|
||||
SessionLiveness::Healthy
|
||||
}
|
||||
(true, Some(_)) => SessionLiveness::Stalled,
|
||||
(true, None) => SessionLiveness::Unknown,
|
||||
};
|
||||
|
||||
SessionHeartbeat {
|
||||
session_id: self.session_id.clone(),
|
||||
observed_at_ms: now_ms,
|
||||
transport_alive,
|
||||
liveness,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn record_compaction(&mut self, summary: impl Into<String>, removed_message_count: usize) {
|
||||
self.touch();
|
||||
let count = self.compaction.as_ref().map_or(1, |value| value.count + 1);
|
||||
@@ -1599,4 +1648,26 @@ mod workspace_sessions_dir_tests {
|
||||
fs::remove_dir_all(&tmp_a).ok();
|
||||
fs::remove_dir_all(&tmp_b).ok();
|
||||
}
|
||||
#[test]
|
||||
fn session_heartbeat_classifies_healthy_stalled_transport_dead_and_unknown() {
|
||||
let mut session = Session::new();
|
||||
assert_eq!(
|
||||
session.heartbeat_at(1_000, 500, true).liveness,
|
||||
SessionLiveness::Unknown
|
||||
);
|
||||
|
||||
session.record_health_check(800);
|
||||
assert_eq!(
|
||||
session.heartbeat_at(1_000, 500, true).liveness,
|
||||
SessionLiveness::Healthy
|
||||
);
|
||||
assert_eq!(
|
||||
session.heartbeat_at(2_000, 500, true).liveness,
|
||||
SessionLiveness::Stalled
|
||||
);
|
||||
assert_eq!(
|
||||
session.heartbeat_at(1_000, 500, false).liveness,
|
||||
SessionLiveness::TransportDead
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user