Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
40a61b3
fix: update Claude memory sidecar model (fixes #798)
1jehuang Aug 6, 2026
93139fe
fix: wait for daemon registration during SDK close (fixes #818)
1jehuang Aug 6, 2026
742973c
style: satisfy current clippy guardrail
1jehuang Aug 6, 2026
6523b9b
style: remove obsolete auth token helper
1jehuang Aug 6, 2026
38721c5
test: make strict schema fixture fully typed
1jehuang Aug 6, 2026
951c962
style: satisfy Rust 1.97 app-core lints
1jehuang Aug 6, 2026
b0f4c2e
Improve todo quality gate rendering
1jehuang Aug 6, 2026
8d5a4ea
feat(todo): distinguish synthetic validation
1jehuang Aug 6, 2026
9abb32d
fix(memory): distinguish permanent sidecar failures
1jehuang Aug 6, 2026
2a884f2
feat(providers): support Meta Muse and DeepSeek passback
1jehuang Aug 6, 2026
c31dfd4
docs(providers): add Meta Model API setup
1jehuang Aug 6, 2026
25463c3
feat(todo): require requirement traceability
1jehuang Aug 6, 2026
a4972b3
fix: prevent empty transcript checkpoints (fixes #814)
1jehuang Aug 6, 2026
6dad509
log cargo action durations
1jehuang Aug 6, 2026
907e9a2
fix(discovery): restore explicit select guidance
1jehuang Aug 6, 2026
537d90f
test(todo): cover traceability in TUI fixtures
1jehuang Aug 6, 2026
8c0eec3
route bash cargo commands through timing logger
1jehuang Aug 6, 2026
fe83c27
fix: fall back when spawn hooks reject launch (fixes #792)
1jehuang Aug 6, 2026
a4c0027
fix(ci): satisfy TUI quality guardrails
1jehuang Aug 6, 2026
fb97e18
fix(schedule): keep scheduled turns out of user prompt history
1jehuang Aug 6, 2026
3355906
test(todo): align TUI gate fixtures
1jehuang Aug 6, 2026
25927cc
test(todo): expect generic completion follow-up wording
1jehuang Aug 6, 2026
df686c7
test(todo): assert compact batched card contract
1jehuang Aug 6, 2026
ab19a81
test(todo): cover compact narrow card rendering
1jehuang Aug 6, 2026
247886a
chore(ci): refresh quality ratchets
1jehuang Aug 6, 2026
e675cbd
test(todo): assert hidden passing card gates
1jehuang Aug 6, 2026
145b8ba
Offer Jcode subscription during onboarding
1jehuang Aug 6, 2026
fd3941e
fix(discovery): validate selection receipts and benchmark identity
1jehuang Aug 6, 2026
6ea3db3
fix(ci): compile macOS notification broker
1jehuang Aug 6, 2026
858071a
Open pricing from onboarding subscription choice
1jehuang Aug 6, 2026
bd64f8f
fix(discovery): enforce provenance report contract
1jehuang Aug 6, 2026
03a957d
style: apply current rustfmt
1jehuang Aug 6, 2026
5af3451
fix(benchmark): identify dirty self-dev binaries
1jehuang Aug 6, 2026
0a61a35
Expose full GPT-5.6 OpenAI model family
1jehuang Aug 6, 2026
b82fe93
chore(ci): advance quality ratchets
1jehuang Aug 6, 2026
acdf685
Advertise hosted model discount in onboarding
1jehuang Aug 6, 2026
19e26f3
test(discovery): verify off-catalog select receipt
1jehuang Aug 6, 2026
e7ce403
Include GPT-5.6 Terra in OpenAI fallback catalog
1jehuang Aug 6, 2026
c8048f6
test(tui): align fixtures with semantic gates
1jehuang Aug 6, 2026
a7e6f81
chore(ci): sync TUI fixture ratchets
1jehuang Aug 6, 2026
893f22a
Default onboarding to Jcode subscription
1jehuang Aug 6, 2026
b3286d9
test(security): avoid secret-shaped source literal
1jehuang Aug 6, 2026
8db5c4e
chore(ci): sync onboarding test ratchet
1jehuang Aug 6, 2026
edb8f64
test(tui): align onboarding fixtures with subscription default
1jehuang Aug 6, 2026
09d328b
chore(release): prepare v0.71.0
1jehuang Aug 6, 2026
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
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "jcode"
version = "0.70.1"
version = "0.71.0"
description = "Possibly the greatest coding agent ever built — blazing-fast TUI, multi-model, swarm coordination, 30+ tools"
edition = "2024"
autobins = false
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,7 @@ jcode works with subscription-backed OAuth flows and many provider integrations,
- **Alibaba Cloud Coding Plan** (`jcode login --provider alibaba-coding-plan`)
- **Fireworks** (`jcode login --provider fireworks`)
- **MiniMax** (`jcode login --provider minimax`)
- **Meta Model API / Muse** (`jcode login --provider meta-muse`)
- **LM Studio** (`jcode login --provider lmstudio`)
- **Ollama** (`jcode login --provider ollama`)
- **Custom OpenAI-compatible endpoint** (`jcode login --provider openai-compatible`)
Expand All @@ -363,9 +364,10 @@ There are two ways to set one up:
jcode login --provider deepseek
jcode login --provider opencode # OpenCode Zen
jcode login --provider moonshotai
jcode login --provider meta-muse # Meta Model API / Muse Spark
```

Built-in OpenAI-compatible profile ids include: `openrouter`, `deepseek`, `zai`, `kimi`, `moonshotai`, `opencode` (OpenCode Zen), `opencode-go`, `302ai`, `baseten`, `cortecs`, `huggingface`, `nebius`, `scaleway`, `stackit`, and `firmware`. Each profile only sets the endpoint and key variable; you still pick the model with `/model` (or `--model`). Run `jcode login` with no provider to see the interactive list.
Built-in OpenAI-compatible profile ids include: `openrouter`, `deepseek`, `zai`, `kimi`, `moonshotai`, `meta-muse` (Meta Model API / Muse Spark), `opencode` (OpenCode Zen), `opencode-go`, `302ai`, `baseten`, `cortecs`, `huggingface`, `nebius`, `scaleway`, `stackit`, and `firmware`. Each profile only sets the endpoint and key variable; you still pick the model with `/model` (or `--model`). Run `jcode login` with no provider to see the interactive list.

- **Any other endpoint** — point jcode at an arbitrary OpenAI-compatible API (hosted or local) with `jcode login --provider openai-compatible` or the scriptable `jcode provider add` command described below.

Expand Down
4 changes: 4 additions & 0 deletions changelog/index.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
{
"entries": [
{
"version": "0.71.0",
"date": "2026-08-06"
},
{
"version": "0.70.1",
"date": "2026-08-06"
Expand Down
20 changes: 20 additions & 0 deletions changelog/v0.71.0.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"version": "0.71.0",
"date": "2026-08-06",
"title": "Subscription onboarding and broader model support",
"highlights": [
"Onboarding now defaults to the Jcode subscription, with clearer hosted-model pricing and a direct path to subscription details",
"OpenAI users can choose the full GPT-5.6 family, including Terra, and Meta Model API users can configure Muse and DeepSeek passback models"
],
"improvements": [
"Todo quality feedback now distinguishes synthetic validation and tracks requirement-to-check traceability",
"Cargo commands report action durations to make slow build and test steps easier to identify"
],
"fixes": [
"Scheduled turns no longer leak into user prompt history",
"Empty transcript checkpoints are prevented so session history remains usable",
"Terminal launches fall back cleanly when spawn hooks reject a launch",
"The TypeScript SDK waits for daemon registration before closing launched sessions",
"Permanent memory sidecar failures are distinguished from transient failures and use the current Claude model"
]
}
43 changes: 40 additions & 3 deletions crates/jcode-app-core/src/agent/turn_execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,22 @@ impl Agent {
}

pub async fn run_once_capture(&mut self, user_message: &str) -> Result<String> {
self.add_message(
self.run_once_capture_with_display_role(user_message, None)
.await
}

pub(crate) async fn run_once_capture_with_display_role(
&mut self,
user_message: &str,
display_role: Option<crate::session::StoredDisplayRole>,
) -> Result<String> {
self.add_message_with_display_role(
Role::User,
vec![ContentBlock::Text {
text: user_message.to_string(),
cache_control: None,
}],
display_role,
);
self.session.save()?;
if trace_enabled() {
Expand All @@ -41,6 +51,24 @@ impl Agent {
images: Vec<(String, String)>,
system_reminder: Option<String>,
event_tx: mpsc::UnboundedSender<ServerEvent>,
) -> Result<()> {
self.run_once_streaming_mpsc_with_display_role(
user_message,
images,
system_reminder,
event_tx,
None,
)
.await
}

pub(crate) async fn run_once_streaming_mpsc_with_display_role(
&mut self,
user_message: &str,
images: Vec<(String, String)>,
system_reminder: Option<String>,
event_tx: mpsc::UnboundedSender<ServerEvent>,
display_role: Option<crate::session::StoredDisplayRole>,
) -> Result<()> {
// Inject any pending notifications before the user message
let alerts = self.take_alerts();
Expand All @@ -62,7 +90,7 @@ impl Agent {
self.current_turn_system_reminder =
system_reminder.filter(|value| !value.trim().is_empty());

self.append_user_context_message(user_message, images)?;
self.append_user_context_message_with_display_role(user_message, images, display_role)?;
crate::telemetry::record_turn();
let turn_started_at = Instant::now();
let start_message_index = self.message_count();
Expand All @@ -78,6 +106,15 @@ impl Agent {
&mut self,
user_message: &str,
images: Vec<(String, String)>,
) -> Result<()> {
self.append_user_context_message_with_display_role(user_message, images, None)
}

fn append_user_context_message_with_display_role(
&mut self,
user_message: &str,
images: Vec<(String, String)>,
display_role: Option<crate::session::StoredDisplayRole>,
) -> Result<()> {
let mut blocks: Vec<ContentBlock> = images
.into_iter()
Expand All @@ -95,7 +132,7 @@ impl Agent {
));
}

self.add_message(Role::User, blocks);
self.add_message_with_display_role(Role::User, blocks, display_role);
self.session.save()
}

Expand Down
14 changes: 12 additions & 2 deletions crates/jcode-app-core/src/ambient/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,12 @@ impl AmbientRunnerHandle {
agent.restore_session(session_id)?;

let reminder = ambient::format_scheduled_session_message(item);
let _ = agent.run_once_capture(&reminder).await?;
let _ = agent
.run_once_capture_with_display_role(
&reminder,
Some(crate::session::StoredDisplayRole::System),
)
.await?;
agent.mark_closed();
Ok(())
}
Expand Down Expand Up @@ -470,7 +475,12 @@ impl AmbientRunnerHandle {
}

let reminder = ambient::format_scheduled_session_message(item);
let _ = agent.run_once_capture(&reminder).await?;
let _ = agent
.run_once_capture_with_display_role(
&reminder,
Some(crate::session::StoredDisplayRole::System),
)
.await?;
agent.mark_closed();
Ok(child_session_id)
}
Expand Down
2 changes: 1 addition & 1 deletion crates/jcode-app-core/src/notifications.rs
Original file line number Diff line number Diff line change
Expand Up @@ -452,7 +452,7 @@ pub fn send_macos_turn_notification(
#[cfg(not(target_os = "macos"))]
{
let _ = (title, subtitle, body, sound);
return false;
false
}

#[cfg(target_os = "macos")]
Expand Down
4 changes: 2 additions & 2 deletions crates/jcode-app-core/src/server/client_actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,10 +105,9 @@ pub(super) async fn handle_notify_session(
};

let ran_immediately = if target_has_client {
super::live_turn::run_live_turn_if_idle(
super::live_turn::run_live_system_turn_if_idle(
&session_id,
&message,
None,
ctx.sessions,
super::live_turn::LiveTurnSwarmContext::new(
ctx.swarm_members,
Expand Down Expand Up @@ -992,6 +991,7 @@ pub(super) async fn handle_resume_all_sessions(
Arc::clone(&agent),
String::new(),
Some(reminder),
None,
Some("resuming interrupted session".to_string()),
super::live_turn::LiveTurnSwarmContext::new(
swarm_members,
Expand Down
1 change: 1 addition & 0 deletions crates/jcode-app-core/src/server/client_actions_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,7 @@ async fn notify_session_runs_scheduled_task_immediately_for_idle_live_session()
let guard = agent.lock().await;
assert!(guard.messages().iter().any(|message| {
message.role == Role::User
&& message.display_role == Some(crate::session::StoredDisplayRole::System)
&& message
.content_preview()
.contains("[Scheduled task] Task: Follow up")
Expand Down
1 change: 1 addition & 0 deletions crates/jcode-app-core/src/server/client_lifecycle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2834,6 +2834,7 @@ async fn append_context_message(
let _ = client_event_tx.send(event);
}

#[allow(clippy::too_many_arguments)]
async fn start_processing_message(
message: ProcessingMessage,
client_session_id: &str,
Expand Down
54 changes: 46 additions & 8 deletions crates/jcode-app-core/src/server/live_turn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ pub(super) async fn spawn_tracked_live_turn(
agent: Arc<Mutex<Agent>>,
message: String,
system_reminder: Option<String>,
display_role: Option<crate::session::StoredDisplayRole>,
status_detail: Option<String>,
swarm: LiveTurnSwarmContext,
) {
Expand All @@ -117,14 +118,27 @@ pub(super) async fn spawn_tracked_live_turn(
let agent_guard = agent.lock().await;
agent_guard.message_count()
};
let result = process_message_streaming_mpsc(
Arc::clone(&agent),
&message,
vec![],
system_reminder,
event_tx.clone(),
)
.await;
let result = if let Some(display_role) = display_role {
let mut agent = agent.lock().await;
agent
.run_once_streaming_mpsc_with_display_role(
&message,
vec![],
system_reminder,
event_tx.clone(),
Some(display_role),
)
.await
} else {
process_message_streaming_mpsc(
Arc::clone(&agent),
&message,
vec![],
system_reminder,
event_tx.clone(),
)
.await
};
match result {
Ok(()) => {
let completion_report = {
Expand Down Expand Up @@ -189,6 +203,30 @@ pub(super) async fn run_live_turn_if_idle(
agent,
message.to_string(),
system_reminder,
None,
detail,
swarm,
)
.await;
true
}

pub(super) async fn run_live_system_turn_if_idle(
session_id: &str,
message: &str,
sessions: &SessionAgents,
swarm: LiveTurnSwarmContext,
) -> bool {
let Some(agent) = idle_live_agent(session_id, sessions, &swarm.members).await else {
return false;
};
let detail = Some(truncate_detail(message, 120)).filter(|detail| !detail.is_empty());
spawn_tracked_live_turn(
session_id,
agent,
message.to_string(),
None,
Some(crate::session::StoredDisplayRole::System),
detail,
swarm,
)
Expand Down
38 changes: 38 additions & 0 deletions crates/jcode-app-core/src/tool/bash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,38 @@ const BASH_TOOL_DESCRIPTION: &str = "Run a bash command.";
const WINDOWS_SHELL_TOOL_DESCRIPTION: &str =
"Run a Windows cmd.exe command (compatibility name `bash`). Use cmd.exe syntax, not Bash.";

#[cfg(unix)]
fn shell_single_quote(value: &str) -> String {
format!("'{}'", value.replace('\'', "'\"'\"'"))
}

/// Route ordinary `cargo` invocations (including those inside child scripts)
/// through the repository wrapper. Besides applying the project's build policy,
/// that wrapper appends real action timings to rust-actions.jsonl.
#[cfg(unix)]
fn wrap_repo_cargo_commands(command: &str, working_dir: Option<&Path>) -> Option<String> {
let working_dir = working_dir?;
let repo = crate::build::find_repo_in_ancestors(working_dir)?;
let wrapper = repo.join("scripts").join("dev_cargo.sh");
if !wrapper.is_file() {
return None;
}

Some(format!(
r#"export JCODE_DEV_CARGO_SCRIPT={wrapper}
cargo() {{
if [[ "${{JCODE_IN_DEV_CARGO:-0}}" == "1" ]]; then
command cargo "$@"
else
JCODE_IN_DEV_CARGO=1 "$JCODE_DEV_CARGO_SCRIPT" "$@"
fi
}}
export -f cargo
{command}"#,
wrapper = shell_single_quote(&wrapper.to_string_lossy()),
))
}

/// Build a clear timeout message. The `timeout` param is in milliseconds, which
/// agents frequently mistake for seconds (e.g. passing 1000 thinking it means
/// 1000s when it is 1s). Spell out the seconds equivalent and, for suspiciously
Expand Down Expand Up @@ -700,6 +732,12 @@ impl Tool for BashTool {
return Err(anyhow::anyhow!(refusal));
}

#[cfg(unix)]
if let Some(wrapped) = wrap_repo_cargo_commands(&params.command, ctx.working_dir.as_deref())
{
params.command = wrapped;
}

if run_in_background {
return self.execute_background(params, ctx).await;
}
Expand Down
25 changes: 25 additions & 0 deletions crates/jcode-app-core/src/tool/bash_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,31 @@ use crate::tool::bash::{BashTool, parse_heuristic_progress};
use serde_json::json;
use tokio::sync::mpsc;

#[test]
fn repository_commands_export_a_logged_cargo_function() {
let repo =
crate::build::find_repo_in_ancestors(std::path::Path::new(env!("CARGO_MANIFEST_DIR")))
.expect("test runs inside the jcode repository");
let wrapped = wrap_repo_cargo_commands("cargo test -p demo && echo done", Some(&repo))
.expect("jcode repository has dev_cargo.sh");

assert!(wrapped.contains("export JCODE_DEV_CARGO_SCRIPT="));
assert!(wrapped.contains("JCODE_IN_DEV_CARGO=1 \"$JCODE_DEV_CARGO_SCRIPT\" \"$@\""));
assert!(wrapped.contains("export -f cargo"));
assert!(wrapped.ends_with("cargo test -p demo && echo done"));
}

#[test]
fn cargo_routing_is_limited_to_the_jcode_repository() {
assert!(wrap_repo_cargo_commands("cargo test", Some(std::path::Path::new("/"))).is_none());
assert!(wrap_repo_cargo_commands("cargo test", None).is_none());
}

#[test]
fn cargo_wrapper_path_is_shell_quoted() {
assert_eq!(shell_single_quote("a'b"), "'a'\"'\"'b'");
}

fn make_ctx(stdin_tx: Option<mpsc::UnboundedSender<StdinInputRequest>>) -> ToolContext {
ToolContext {
session_id: "test-session".to_string(),
Expand Down
Loading
Loading