Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
30 changes: 15 additions & 15 deletions Cargo.lock

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

8 changes: 8 additions & 0 deletions slate.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"$schema": "https://randomlabs.ai/config.json",
"permission": {
"*": "allow",
"bash": "ask",
"edit": "ask"
}
}
9 changes: 8 additions & 1 deletion src/agent/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -690,8 +690,11 @@ pub async fn run_interactive(
Some(&current_input),
session.plan_mode,
);
let is_generation = prompts::is_generation_query(&current_input);
let is_planning = session.plan_mode.is_planning();
// Inherit generation mode for short follow-up messages ("sure", "yes", "go ahead",
// etc.) so the write/shell tool set is not lost between turns.
let is_generation = prompts::is_generation_query(&current_input)
|| (!is_planning && session.last_was_generation && current_input.trim().len() < 60);

// Note: using raw_chat_history directly which preserves Reasoning blocks
// This is needed for extended thinking to work with multi-turn conversations
Expand Down Expand Up @@ -1138,6 +1141,10 @@ pub async fn run_interactive(
// Add to conversation history with tool call records
conversation_history.add_turn(input.clone(), text.clone(), tool_calls.clone());

// Remember whether this turn had generation tools active so short follow-up
// messages ("sure", "go ahead", etc.) don't lose write/shell access.
session.last_was_generation = is_generation;

// Check if this heavy turn requires immediate compaction
// This helps prevent context overflow in subsequent requests
if conversation_history.needs_compaction() {
Expand Down
12 changes: 12 additions & 0 deletions src/agent/prompts/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -705,6 +705,18 @@ pub fn is_generation_query(query: &str) -> bool {
"new feature",
"develop",
"code",
// Common modification verbs (previously missing)
"fix",
"update",
"add",
"change",
"modify",
"edit",
"configure",
"setup",
"set up",
"patch",
"install",
// Plan execution keywords - needed for plan continuation
"plan",
"continue",
Expand Down
5 changes: 5 additions & 0 deletions src/agent/session/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ pub struct ChatSession {
pub token_usage: TokenUsage,
/// Current planning mode state
pub plan_mode: PlanMode,
/// Whether the previous turn used generation mode (write/shell tools active).
/// Used so short follow-up messages ("sure", "go ahead", "yes") inherit the
/// tool set from the previous turn instead of losing write/shell access.
pub last_was_generation: bool,
/// Session loaded via /resume command, to be processed by main loop
pub pending_resume: Option<crate::agent::persistence::ConversationRecord>,
/// Platform session state (selected project/org context)
Expand All @@ -58,6 +62,7 @@ impl ChatSession {
history: Vec::new(),
token_usage: TokenUsage::new(),
plan_mode: PlanMode::default(),
last_was_generation: false,
pending_resume: None,
platform_session,
}
Expand Down
39 changes: 22 additions & 17 deletions src/agent/tools/platform/create_deployment_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,12 @@ use crate::platform::api::types::{
build_cloud_runner_config_v2,
};
use crate::platform::api::{PlatformApiClient, PlatformApiError};
use crate::platform::session::PlatformSession;
use std::str::FromStr;

/// Arguments for the create deployment config tool
#[derive(Debug, Deserialize)]
pub struct CreateDeploymentConfigArgs {
/// The project UUID
pub project_id: String,
/// Service name for the deployment
pub service_name: String,
/// Repository ID from GitHub integration
Expand Down Expand Up @@ -102,7 +101,6 @@ A deployment config defines how to build and deploy a service, including:
- Auto-deploy settings

**Required Parameters:**
- project_id: The project UUID
- service_name: Name for the service (lowercase, hyphens allowed)
- repository_id: GitHub repository ID (from platform GitHub integration)
- repository_full_name: Full repo name like "owner/repo"
Expand Down Expand Up @@ -138,10 +136,6 @@ A deployment config defines how to build and deploy a service, including:
parameters: json!({
"type": "object",
"properties": {
"project_id": {
"type": "string",
"description": "The UUID of the project"
},
"service_name": {
"type": "string",
"description": "Name for the service (lowercase, hyphens allowed)"
Expand Down Expand Up @@ -218,27 +212,38 @@ A deployment config defines how to build and deploy a service, including:
}
},
"required": [
"project_id", "service_name", "repository_id", "repository_full_name",
"service_name", "repository_id", "repository_full_name",
"port", "branch", "target_type", "provider", "environment_id"
]
}),
}
}

async fn call(&self, args: Self::Args) -> Result<Self::Output, Self::Error> {
// Validate required fields
if args.project_id.trim().is_empty() {
// Load project_id from session (authoritative source — prevents stale IDs from LLM context)
let session = match PlatformSession::load() {
Ok(s) => s,
Err(_) => {
return Ok(format_error_for_llm(
"create_deployment_config",
ErrorCategory::InternalError,
"Failed to load platform session",
Some(vec!["Try authenticating with `sync-ctl auth login`"]),
));
}
};

if !session.is_project_selected() {
return Ok(format_error_for_llm(
"create_deployment_config",
ErrorCategory::ValidationFailed,
"project_id cannot be empty",
Some(vec![
"Use list_projects to find valid project IDs",
"Use current_context to get the selected project",
]),
"No project selected",
Some(vec!["Use select_project to choose a project first"]),
));
}

let project_id = session.project_id.clone().unwrap_or_default();

if args.service_name.trim().is_empty() {
return Ok(format_error_for_llm(
"create_deployment_config",
Expand Down Expand Up @@ -316,7 +321,7 @@ A deployment config defines how to build and deploy a service, including:
if let Some(ref provider) = provider_enum {
if matches!(provider, CloudProvider::Gcp | CloudProvider::Azure) {
if let Ok(credential) = client
.check_provider_connection(provider, &args.project_id)
.check_provider_connection(provider, &project_id)
.await
{
if let Some(cred) = credential {
Expand Down Expand Up @@ -351,7 +356,7 @@ A deployment config defines how to build and deploy a service, including:
// Note: Send both field name variants (dockerfile/dockerfilePath, context/buildContext)
// for backend compatibility - different endpoints may expect different field names
let request = CreateDeploymentConfigRequest {
project_id: args.project_id.clone(),
project_id,
service_name: args.service_name.clone(),
repository_id: args.repository_id,
repository_full_name: args.repository_full_name.clone(),
Expand Down
Loading
Loading