Skip to content

Commit 3a3e61b

Browse files
authored
Merge pull request #299 from syncable-dev/bug/stale-project-id-parsing
Bug/stale project id parsing
2 parents 749f7a2 + e80b452 commit 3a3e61b

10 files changed

Lines changed: 101 additions & 63 deletions

File tree

Cargo.lock

Lines changed: 15 additions & 15 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

slate.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"$schema": "https://randomlabs.ai/config.json",
3+
"permission": {
4+
"*": "allow",
5+
"bash": "ask",
6+
"edit": "ask"
7+
}
8+
}

src/agent/mod.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -690,8 +690,11 @@ pub async fn run_interactive(
690690
Some(&current_input),
691691
session.plan_mode,
692692
);
693-
let is_generation = prompts::is_generation_query(&current_input);
694693
let is_planning = session.plan_mode.is_planning();
694+
// Inherit generation mode for short follow-up messages ("sure", "yes", "go ahead",
695+
// etc.) so the write/shell tool set is not lost between turns.
696+
let is_generation = prompts::is_generation_query(&current_input)
697+
|| (!is_planning && session.last_was_generation && current_input.trim().len() < 60);
695698

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

1144+
// Remember whether this turn had generation tools active so short follow-up
1145+
// messages ("sure", "go ahead", etc.) don't lose write/shell access.
1146+
session.last_was_generation = is_generation;
1147+
11411148
// Check if this heavy turn requires immediate compaction
11421149
// This helps prevent context overflow in subsequent requests
11431150
if conversation_history.needs_compaction() {

src/agent/prompts/mod.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -705,6 +705,18 @@ pub fn is_generation_query(query: &str) -> bool {
705705
"new feature",
706706
"develop",
707707
"code",
708+
// Common modification verbs (previously missing)
709+
"fix",
710+
"update",
711+
"add",
712+
"change",
713+
"modify",
714+
"edit",
715+
"configure",
716+
"setup",
717+
"set up",
718+
"patch",
719+
"install",
708720
// Plan execution keywords - needed for plan continuation
709721
"plan",
710722
"continue",

src/agent/session/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ pub struct ChatSession {
3434
pub token_usage: TokenUsage,
3535
/// Current planning mode state
3636
pub plan_mode: PlanMode,
37+
/// Whether the previous turn used generation mode (write/shell tools active).
38+
/// Used so short follow-up messages ("sure", "go ahead", "yes") inherit the
39+
/// tool set from the previous turn instead of losing write/shell access.
40+
pub last_was_generation: bool,
3741
/// Session loaded via /resume command, to be processed by main loop
3842
pub pending_resume: Option<crate::agent::persistence::ConversationRecord>,
3943
/// Platform session state (selected project/org context)
@@ -58,6 +62,7 @@ impl ChatSession {
5862
history: Vec::new(),
5963
token_usage: TokenUsage::new(),
6064
plan_mode: PlanMode::default(),
65+
last_was_generation: false,
6166
pending_resume: None,
6267
platform_session,
6368
}

src/agent/tools/platform/create_deployment_config.rs

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,12 @@ use crate::platform::api::types::{
1313
build_cloud_runner_config_v2,
1414
};
1515
use crate::platform::api::{PlatformApiClient, PlatformApiError};
16+
use crate::platform::session::PlatformSession;
1617
use std::str::FromStr;
1718

1819
/// Arguments for the create deployment config tool
1920
#[derive(Debug, Deserialize)]
2021
pub struct CreateDeploymentConfigArgs {
21-
/// The project UUID
22-
pub project_id: String,
2322
/// Service name for the deployment
2423
pub service_name: String,
2524
/// Repository ID from GitHub integration
@@ -102,7 +101,6 @@ A deployment config defines how to build and deploy a service, including:
102101
- Auto-deploy settings
103102
104103
**Required Parameters:**
105-
- project_id: The project UUID
106104
- service_name: Name for the service (lowercase, hyphens allowed)
107105
- repository_id: GitHub repository ID (from platform GitHub integration)
108106
- repository_full_name: Full repo name like "owner/repo"
@@ -138,10 +136,6 @@ A deployment config defines how to build and deploy a service, including:
138136
parameters: json!({
139137
"type": "object",
140138
"properties": {
141-
"project_id": {
142-
"type": "string",
143-
"description": "The UUID of the project"
144-
},
145139
"service_name": {
146140
"type": "string",
147141
"description": "Name for the service (lowercase, hyphens allowed)"
@@ -218,27 +212,38 @@ A deployment config defines how to build and deploy a service, including:
218212
}
219213
},
220214
"required": [
221-
"project_id", "service_name", "repository_id", "repository_full_name",
215+
"service_name", "repository_id", "repository_full_name",
222216
"port", "branch", "target_type", "provider", "environment_id"
223217
]
224218
}),
225219
}
226220
}
227221

228222
async fn call(&self, args: Self::Args) -> Result<Self::Output, Self::Error> {
229-
// Validate required fields
230-
if args.project_id.trim().is_empty() {
223+
// Load project_id from session (authoritative source — prevents stale IDs from LLM context)
224+
let session = match PlatformSession::load() {
225+
Ok(s) => s,
226+
Err(_) => {
227+
return Ok(format_error_for_llm(
228+
"create_deployment_config",
229+
ErrorCategory::InternalError,
230+
"Failed to load platform session",
231+
Some(vec!["Try authenticating with `sync-ctl auth login`"]),
232+
));
233+
}
234+
};
235+
236+
if !session.is_project_selected() {
231237
return Ok(format_error_for_llm(
232238
"create_deployment_config",
233239
ErrorCategory::ValidationFailed,
234-
"project_id cannot be empty",
235-
Some(vec![
236-
"Use list_projects to find valid project IDs",
237-
"Use current_context to get the selected project",
238-
]),
240+
"No project selected",
241+
Some(vec!["Use select_project to choose a project first"]),
239242
));
240243
}
241244

245+
let project_id = session.project_id.clone().unwrap_or_default();
246+
242247
if args.service_name.trim().is_empty() {
243248
return Ok(format_error_for_llm(
244249
"create_deployment_config",
@@ -316,7 +321,7 @@ A deployment config defines how to build and deploy a service, including:
316321
if let Some(ref provider) = provider_enum {
317322
if matches!(provider, CloudProvider::Gcp | CloudProvider::Azure) {
318323
if let Ok(credential) = client
319-
.check_provider_connection(provider, &args.project_id)
324+
.check_provider_connection(provider, &project_id)
320325
.await
321326
{
322327
if let Some(cred) = credential {
@@ -351,7 +356,7 @@ A deployment config defines how to build and deploy a service, including:
351356
// Note: Send both field name variants (dockerfile/dockerfilePath, context/buildContext)
352357
// for backend compatibility - different endpoints may expect different field names
353358
let request = CreateDeploymentConfigRequest {
354-
project_id: args.project_id.clone(),
359+
project_id,
355360
service_name: args.service_name.clone(),
356361
repository_id: args.repository_id,
357362
repository_full_name: args.repository_full_name.clone(),

0 commit comments

Comments
 (0)