Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions COMMIT_MESSAGE_ISSUE_5965.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
feat(core): include current date in environment context (#5965)

- add optional `current_date` field to `EnvironmentContext` so turn context carries ISO dates
- serialize `<current_date>` tags and extend comparisons/tests to tolerate deterministic values
- cover default date formatting with new unit tests
12 changes: 12 additions & 0 deletions PR_BODY_ISSUE_5965.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
## Summary
- inject the current local date into `EnvironmentContext` so every turn shares an ISO8601 `<current_date>` tag with the model
- extend the serializer/equality helpers to account for the new field while keeping comparisons deterministic in tests
- add lightweight unit coverage to lock the XML output and default date format

## Testing
- ./build-fast.sh

## Acceptance Criteria
- environment context payloads now surface a `<current_date>` element in YYYY-MM-DD form
- existing comparisons that ignore shell differences remain stable once the date is normalized
- unit tests document the new field and its formatting so regressions are caught automatically
54 changes: 54 additions & 0 deletions code-rs/core/src/environment_context.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use chrono::Local;
use os_info::Type as OsType;
use os_info::Version;
use serde::Deserialize;
Expand Down Expand Up @@ -33,6 +34,7 @@ pub(crate) struct EnvironmentContext {
pub operating_system: Option<OperatingSystemInfo>,
pub common_tools: Option<Vec<String>>,
pub shell: Option<Shell>,
pub current_date: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
Expand Down Expand Up @@ -145,6 +147,7 @@ impl EnvironmentContext {
operating_system: detect_operating_system_info(),
common_tools: detect_common_tools(),
shell,
current_date: Some(Local::now().format("%Y-%m-%d").to_string()),
}
}

Expand All @@ -161,6 +164,7 @@ impl EnvironmentContext {
writable_roots,
operating_system,
common_tools,
current_date,
// should compare all fields except shell
shell: _,
} = other;
Expand All @@ -172,6 +176,7 @@ impl EnvironmentContext {
&& self.writable_roots == *writable_roots
&& self.operating_system == *operating_system
&& self.common_tools == *common_tools
&& self.current_date == *current_date
}
}

Expand Down Expand Up @@ -249,6 +254,9 @@ impl EnvironmentContext {
lines.push(" </common_tools>".to_string());
}
}
if let Some(current_date) = self.current_date {
lines.push(format!(" <current_date>{current_date}</current_date>"));
}
if let Some(shell) = self.shell
&& let Some(shell_name) = shell.name()
{
Expand Down Expand Up @@ -360,6 +368,7 @@ mod tests {
);
context.operating_system = None;
context.common_tools = None;
context.current_date = Some("2025-01-02".to_string());

let expected = r#"<environment_context>
<cwd>/repo</cwd>
Expand All @@ -370,6 +379,7 @@ mod tests {
<root>/repo</root>
<root>/tmp</root>
</writable_roots>
<current_date>2025-01-02</current_date>
</environment_context>"#;

assert_eq!(context.serialize_to_xml(), expected);
Expand All @@ -385,11 +395,13 @@ mod tests {
);
context.operating_system = None;
context.common_tools = None;
context.current_date = Some("2025-01-02".to_string());

let expected = r#"<environment_context>
<approval_policy>never</approval_policy>
<sandbox_mode>read-only</sandbox_mode>
<network_access>restricted</network_access>
<current_date>2025-01-02</current_date>
</environment_context>"#;

assert_eq!(context.serialize_to_xml(), expected);
Expand All @@ -405,11 +417,13 @@ mod tests {
);
context.operating_system = None;
context.common_tools = None;
context.current_date = Some("2025-01-02".to_string());

let expected = r#"<environment_context>
<approval_policy>on-failure</approval_policy>
<sandbox_mode>danger-full-access</sandbox_mode>
<network_access>enabled</network_access>
<current_date>2025-01-02</current_date>
</environment_context>"#;

assert_eq!(context.serialize_to_xml(), expected);
Expand All @@ -429,6 +443,7 @@ mod tests {
architecture: Some("aarch64".to_string()),
});
context.common_tools = Some(vec!["rg".to_string(), "git".to_string()]);
context.current_date = Some("2025-01-02".to_string());

let xml = context.serialize_to_xml();
assert!(xml.contains("<operating_system>"));
Expand All @@ -438,6 +453,7 @@ mod tests {
assert!(xml.contains("<common_tools>"));
assert!(xml.contains("<tool>rg</tool>"));
assert!(xml.contains("<tool>git</tool>"));
assert!(xml.contains("<current_date>2025-01-02</current_date>"));
}

#[test]
Expand All @@ -455,6 +471,12 @@ mod tests {
Some(workspace_write_policy(vec!["/repo"], true)),
None,
);
// ensure current_date doesn't influence this comparison
let fixed_date = Some("2025-01-02".to_string());
let mut context1 = context1;
context1.current_date = fixed_date.clone();
let mut context2 = context2;
context2.current_date = fixed_date;
assert!(!context1.equals_except_shell(&context2));
}

Expand All @@ -472,6 +494,10 @@ mod tests {
Some(SandboxPolicy::new_workspace_write_policy()),
None,
);
let mut context1 = context1;
context1.current_date = Some("2025-01-02".to_string());
let mut context2 = context2;
context2.current_date = Some("2025-01-02".to_string());

assert!(!context1.equals_except_shell(&context2));
}
Expand All @@ -490,6 +516,10 @@ mod tests {
Some(workspace_write_policy(vec!["/repo", "/tmp"], true)),
None,
);
let mut context1 = context1;
context1.current_date = Some("2025-01-02".to_string());
let mut context2 = context2;
context2.current_date = Some("2025-01-02".to_string());

assert!(!context1.equals_except_shell(&context2));
}
Expand All @@ -514,7 +544,31 @@ mod tests {
zshrc_path: "/home/user/.zshrc".into(),
})),
);
let mut context1 = context1;
context1.current_date = Some("2025-01-02".to_string());
let mut context2 = context2;
context2.current_date = Some("2025-01-02".to_string());

assert!(context1.equals_except_shell(&context2));
}

#[test]
fn serialize_environment_context_includes_current_date() {
let mut context = EnvironmentContext::new(None, None, None, None);
context.current_date = Some("2025-01-02".to_string());

let xml = context.serialize_to_xml();
assert!(xml.contains("<current_date>2025-01-02</current_date>"));
}

#[test]
fn current_date_format_is_iso8601() {
let context = EnvironmentContext::new(None, None, None, None);
let date = context
.current_date
.expect("current_date should be populated");
assert_eq!(date.len(), 10);
assert_eq!(date.chars().nth(4), Some('-'));
assert_eq!(date.chars().nth(7), Some('-'));
}
}
56 changes: 55 additions & 1 deletion code-rs/tui/src/updates.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ struct VersionInfo {
latest_version: String,
// ISO-8601 timestamp (RFC3339)
last_checked_at: DateTime<Utc>,
#[serde(default)]
release_repo: Option<String>,
}

#[derive(Deserialize, Debug, Clone)]
Expand All @@ -118,6 +120,8 @@ struct ReleaseInfo {

const VERSION_FILENAME: &str = "version.json";
const LATEST_RELEASE_URL: &str = "https://api.github.com/repos/just-every/code/releases/latest";
const CURRENT_RELEASE_REPO: &str = "just-every/code";
const LEGACY_RELEASE_REPO: &str = "openai/codex";
pub const CODE_RELEASE_URL: &str = "https://github.com/just-every/code/releases/latest";

const AUTO_UPGRADE_LOCK_FILE: &str = "auto-upgrade.lock";
Expand Down Expand Up @@ -460,7 +464,17 @@ fn truncate_for_log(text: &str) -> String {

fn read_version_info(version_file: &Path) -> anyhow::Result<VersionInfo> {
let contents = std::fs::read_to_string(version_file)?;
Ok(serde_json::from_str(&contents)?)
let info: VersionInfo = serde_json::from_str(&contents)?;
let repo = info
.release_repo
.as_deref()
.unwrap_or(LEGACY_RELEASE_REPO);
if repo != CURRENT_RELEASE_REPO {
anyhow::bail!(
"stale version info from {repo}; discarding cached update metadata"
);
}
Ok(info)
}

async fn check_for_update(version_file: &Path, originator: &str) -> anyhow::Result<VersionInfo> {
Expand Down Expand Up @@ -496,6 +510,7 @@ async fn check_for_update(version_file: &Path, originator: &str) -> anyhow::Resu
let info = VersionInfo {
latest_version,
last_checked_at: Utc::now(),
release_repo: Some(CURRENT_RELEASE_REPO.to_string()),
};

let json_line = format!("{}\n", serde_json::to_string(&info)?);
Expand All @@ -520,3 +535,42 @@ fn parse_version(v: &str) -> Option<(u64, u64, u64)> {
let pat = iter.next()?.parse::<u64>().ok()?;
Some((maj, min, pat))
}

#[cfg(test)]
mod tests {
use super::*;
use chrono::TimeZone;
use tempfile::tempdir;

#[test]
fn read_version_info_rejects_legacy_repo_cache() {
let dir = tempdir().unwrap();
let path = dir.path().join("version.json");
let legacy = serde_json::json!({
"latest_version": "0.50.0",
"last_checked_at": Utc.timestamp_opt(1_696_000_000, 0).unwrap().to_rfc3339(),
});
std::fs::write(&path, format!("{}\n", legacy)).unwrap();

let err = read_version_info(&path).expect_err("legacy cache should be rejected");
assert!(err
.to_string()
.contains("stale version info"));
}

#[test]
fn read_version_info_accepts_current_repo_cache() {
let dir = tempdir().unwrap();
let path = dir.path().join("version.json");
let info = serde_json::json!({
"latest_version": "0.4.7",
"last_checked_at": Utc::now().to_rfc3339(),
"release_repo": CURRENT_RELEASE_REPO,
});
std::fs::write(&path, format!("{}\n", info)).unwrap();

let parsed = read_version_info(&path).expect("current repo cache should load");
assert_eq!(parsed.latest_version, "0.4.7");
assert_eq!(parsed.release_repo.as_deref(), Some(CURRENT_RELEASE_REPO));
}
}
Loading