mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-04-18 08:52:11 +08:00
feat: add ecc2 computer use remote dispatch
This commit is contained in:
parent
7809518612
commit
30913b2cc4
@ -50,6 +50,16 @@ pub struct ConflictResolutionConfig {
|
|||||||
pub notify_lead: bool,
|
pub notify_lead: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
#[serde(default)]
|
||||||
|
pub struct ComputerUseDispatchConfig {
|
||||||
|
pub agent: Option<String>,
|
||||||
|
pub profile: Option<String>,
|
||||||
|
pub use_worktree: bool,
|
||||||
|
pub project: Option<String>,
|
||||||
|
pub task_group: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct AgentProfileConfig {
|
pub struct AgentProfileConfig {
|
||||||
@ -223,6 +233,7 @@ pub struct Config {
|
|||||||
pub agent_profiles: BTreeMap<String, AgentProfileConfig>,
|
pub agent_profiles: BTreeMap<String, AgentProfileConfig>,
|
||||||
pub orchestration_templates: BTreeMap<String, OrchestrationTemplateConfig>,
|
pub orchestration_templates: BTreeMap<String, OrchestrationTemplateConfig>,
|
||||||
pub memory_connectors: BTreeMap<String, MemoryConnectorConfig>,
|
pub memory_connectors: BTreeMap<String, MemoryConnectorConfig>,
|
||||||
|
pub computer_use_dispatch: ComputerUseDispatchConfig,
|
||||||
pub auto_dispatch_unread_handoffs: bool,
|
pub auto_dispatch_unread_handoffs: bool,
|
||||||
pub auto_dispatch_limit_per_session: usize,
|
pub auto_dispatch_limit_per_session: usize,
|
||||||
pub auto_create_worktrees: bool,
|
pub auto_create_worktrees: bool,
|
||||||
@ -289,6 +300,7 @@ impl Default for Config {
|
|||||||
agent_profiles: BTreeMap::new(),
|
agent_profiles: BTreeMap::new(),
|
||||||
orchestration_templates: BTreeMap::new(),
|
orchestration_templates: BTreeMap::new(),
|
||||||
memory_connectors: BTreeMap::new(),
|
memory_connectors: BTreeMap::new(),
|
||||||
|
computer_use_dispatch: ComputerUseDispatchConfig::default(),
|
||||||
auto_dispatch_unread_handoffs: false,
|
auto_dispatch_unread_handoffs: false,
|
||||||
auto_dispatch_limit_per_session: 5,
|
auto_dispatch_limit_per_session: 5,
|
||||||
auto_create_worktrees: true,
|
auto_create_worktrees: true,
|
||||||
@ -347,6 +359,26 @@ impl Config {
|
|||||||
self.budget_alert_thresholds.sanitized()
|
self.budget_alert_thresholds.sanitized()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn computer_use_dispatch_defaults(&self) -> ResolvedComputerUseDispatchConfig {
|
||||||
|
let agent = self
|
||||||
|
.computer_use_dispatch
|
||||||
|
.agent
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| self.default_agent.clone());
|
||||||
|
let profile = self
|
||||||
|
.computer_use_dispatch
|
||||||
|
.profile
|
||||||
|
.clone()
|
||||||
|
.or_else(|| self.default_agent_profile.clone());
|
||||||
|
ResolvedComputerUseDispatchConfig {
|
||||||
|
agent,
|
||||||
|
profile,
|
||||||
|
use_worktree: self.computer_use_dispatch.use_worktree,
|
||||||
|
project: self.computer_use_dispatch.project.clone(),
|
||||||
|
task_group: self.computer_use_dispatch.task_group.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn resolve_agent_profile(&self, name: &str) -> Result<ResolvedAgentProfile> {
|
pub fn resolve_agent_profile(&self, name: &str) -> Result<ResolvedAgentProfile> {
|
||||||
let mut chain = Vec::new();
|
let mut chain = Vec::new();
|
||||||
self.resolve_agent_profile_inner(name, &mut chain)
|
self.resolve_agent_profile_inner(name, &mut chain)
|
||||||
@ -771,6 +803,27 @@ impl Default for HarnessRunnerConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for ComputerUseDispatchConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
agent: None,
|
||||||
|
profile: None,
|
||||||
|
use_worktree: false,
|
||||||
|
project: None,
|
||||||
|
task_group: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||||
|
pub struct ResolvedComputerUseDispatchConfig {
|
||||||
|
pub agent: String,
|
||||||
|
pub profile: Option<String>,
|
||||||
|
pub use_worktree: bool,
|
||||||
|
pub project: Option<String>,
|
||||||
|
pub task_group: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
fn merge_unique<T>(base: &mut Vec<T>, additions: &[T])
|
fn merge_unique<T>(base: &mut Vec<T>, additions: &[T])
|
||||||
where
|
where
|
||||||
T: Clone + PartialEq,
|
T: Clone + PartialEq,
|
||||||
@ -851,8 +904,8 @@ impl BudgetAlertThresholds {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{
|
use super::{
|
||||||
BudgetAlertThresholds, Config, ConflictResolutionConfig, ConflictResolutionStrategy,
|
BudgetAlertThresholds, ComputerUseDispatchConfig, Config, ConflictResolutionConfig,
|
||||||
PaneLayout,
|
ConflictResolutionStrategy, PaneLayout, ResolvedComputerUseDispatchConfig,
|
||||||
};
|
};
|
||||||
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
@ -1202,6 +1255,42 @@ notify_lead = false
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn computer_use_dispatch_deserializes_from_toml() {
|
||||||
|
let config: Config = toml::from_str(
|
||||||
|
r#"
|
||||||
|
[computer_use_dispatch]
|
||||||
|
agent = "codex"
|
||||||
|
profile = "browser"
|
||||||
|
use_worktree = true
|
||||||
|
project = "ops"
|
||||||
|
task_group = "remote browser"
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
config.computer_use_dispatch,
|
||||||
|
ComputerUseDispatchConfig {
|
||||||
|
agent: Some("codex".to_string()),
|
||||||
|
profile: Some("browser".to_string()),
|
||||||
|
use_worktree: true,
|
||||||
|
project: Some("ops".to_string()),
|
||||||
|
task_group: Some("remote browser".to_string()),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
config.computer_use_dispatch_defaults(),
|
||||||
|
ResolvedComputerUseDispatchConfig {
|
||||||
|
agent: "codex".to_string(),
|
||||||
|
profile: Some("browser".to_string()),
|
||||||
|
use_worktree: true,
|
||||||
|
project: Some("ops".to_string()),
|
||||||
|
task_group: Some("remote browser".to_string()),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn agent_profiles_resolve_inheritance_and_defaults() {
|
fn agent_profiles_resolve_inheritance_and_defaults() {
|
||||||
let config: Config = toml::from_str(
|
let config: Config = toml::from_str(
|
||||||
|
|||||||
268
ecc2/src/main.rs
268
ecc2/src/main.rs
@ -45,6 +45,28 @@ impl WorktreePolicyArgs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(clap::Args, Debug, Clone, Default)]
|
||||||
|
struct OptionalWorktreePolicyArgs {
|
||||||
|
/// Create a dedicated worktree
|
||||||
|
#[arg(short = 'w', long = "worktree", action = clap::ArgAction::SetTrue, overrides_with = "no_worktree")]
|
||||||
|
worktree: bool,
|
||||||
|
/// Skip dedicated worktree creation
|
||||||
|
#[arg(long = "no-worktree", action = clap::ArgAction::SetTrue, overrides_with = "worktree")]
|
||||||
|
no_worktree: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OptionalWorktreePolicyArgs {
|
||||||
|
fn resolve(&self, default_value: bool) -> bool {
|
||||||
|
if self.worktree {
|
||||||
|
true
|
||||||
|
} else if self.no_worktree {
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
default_value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(clap::Subcommand, Debug)]
|
#[derive(clap::Subcommand, Debug)]
|
||||||
enum Commands {
|
enum Commands {
|
||||||
/// Launch the TUI dashboard
|
/// Launch the TUI dashboard
|
||||||
@ -479,6 +501,41 @@ enum RemoteCommands {
|
|||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
json: bool,
|
json: bool,
|
||||||
},
|
},
|
||||||
|
/// Queue a remote computer-use task request
|
||||||
|
ComputerUse {
|
||||||
|
/// Goal to complete with computer-use/browser tools
|
||||||
|
#[arg(long)]
|
||||||
|
goal: String,
|
||||||
|
/// Optional target URL to open first
|
||||||
|
#[arg(long)]
|
||||||
|
target_url: Option<String>,
|
||||||
|
/// Extra context for the operator
|
||||||
|
#[arg(long)]
|
||||||
|
context: Option<String>,
|
||||||
|
/// Optional lead session ID or alias to route through
|
||||||
|
#[arg(long)]
|
||||||
|
to_session: Option<String>,
|
||||||
|
/// Task priority
|
||||||
|
#[arg(long, value_enum, default_value_t = TaskPriorityArg::Normal)]
|
||||||
|
priority: TaskPriorityArg,
|
||||||
|
/// Agent type override (defaults to [computer_use_dispatch] or ECC default agent)
|
||||||
|
#[arg(short, long)]
|
||||||
|
agent: Option<String>,
|
||||||
|
/// Agent profile override (defaults to [computer_use_dispatch] or ECC default profile)
|
||||||
|
#[arg(long)]
|
||||||
|
profile: Option<String>,
|
||||||
|
#[command(flatten)]
|
||||||
|
worktree: OptionalWorktreePolicyArgs,
|
||||||
|
/// Optional project grouping override
|
||||||
|
#[arg(long)]
|
||||||
|
project: Option<String>,
|
||||||
|
/// Optional task-group grouping override
|
||||||
|
#[arg(long)]
|
||||||
|
task_group: Option<String>,
|
||||||
|
/// Emit machine-readable JSON instead of the human summary
|
||||||
|
#[arg(long)]
|
||||||
|
json: bool,
|
||||||
|
},
|
||||||
/// List queued remote task requests
|
/// List queued remote task requests
|
||||||
List {
|
List {
|
||||||
/// Include already dispatched or failed requests
|
/// Include already dispatched or failed requests
|
||||||
@ -816,6 +873,20 @@ struct RemoteDispatchHttpRequest {
|
|||||||
task_group: Option<String>,
|
task_group: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
|
struct RemoteComputerUseHttpRequest {
|
||||||
|
goal: String,
|
||||||
|
target_url: Option<String>,
|
||||||
|
context: Option<String>,
|
||||||
|
to_session: Option<String>,
|
||||||
|
priority: Option<TaskPriorityArg>,
|
||||||
|
agent: Option<String>,
|
||||||
|
profile: Option<String>,
|
||||||
|
use_worktree: Option<bool>,
|
||||||
|
project: Option<String>,
|
||||||
|
task_group: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, Deserialize)]
|
#[derive(Debug, Clone, Default, Deserialize)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
struct JsonlMemoryConnectorRecord {
|
struct JsonlMemoryConnectorRecord {
|
||||||
@ -1996,6 +2067,57 @@ async fn main() -> Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
RemoteCommands::ComputerUse {
|
||||||
|
goal,
|
||||||
|
target_url,
|
||||||
|
context,
|
||||||
|
to_session,
|
||||||
|
priority,
|
||||||
|
agent,
|
||||||
|
profile,
|
||||||
|
worktree,
|
||||||
|
project,
|
||||||
|
task_group,
|
||||||
|
json,
|
||||||
|
} => {
|
||||||
|
let target_session_id = to_session
|
||||||
|
.as_deref()
|
||||||
|
.map(|value| resolve_session_id(&db, value))
|
||||||
|
.transpose()?;
|
||||||
|
let defaults = cfg.computer_use_dispatch_defaults();
|
||||||
|
let request = session::manager::create_computer_use_remote_dispatch_request(
|
||||||
|
&db,
|
||||||
|
&cfg,
|
||||||
|
&goal,
|
||||||
|
target_url.as_deref(),
|
||||||
|
context.as_deref(),
|
||||||
|
target_session_id.as_deref(),
|
||||||
|
priority.into(),
|
||||||
|
agent.as_deref(),
|
||||||
|
profile.as_deref(),
|
||||||
|
Some(worktree.resolve(defaults.use_worktree)),
|
||||||
|
session::SessionGrouping {
|
||||||
|
project,
|
||||||
|
task_group,
|
||||||
|
},
|
||||||
|
"cli_computer_use",
|
||||||
|
None,
|
||||||
|
)?;
|
||||||
|
if json {
|
||||||
|
println!("{}", serde_json::to_string_pretty(&request)?);
|
||||||
|
} else {
|
||||||
|
println!(
|
||||||
|
"Queued remote {} request #{} [{}] {}",
|
||||||
|
request.request_kind, request.id, request.priority, goal
|
||||||
|
);
|
||||||
|
if let Some(target_url) = request.target_url.as_deref() {
|
||||||
|
println!("- target url {target_url}");
|
||||||
|
}
|
||||||
|
if let Some(target_session_id) = request.target_session_id.as_deref() {
|
||||||
|
println!("- target {}", short_session(target_session_id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
RemoteCommands::List { all, limit, json } => {
|
RemoteCommands::List { all, limit, json } => {
|
||||||
let requests = session::manager::list_remote_dispatch_requests(&db, all, limit)?;
|
let requests = session::manager::list_remote_dispatch_requests(&db, all, limit)?;
|
||||||
if json {
|
if json {
|
||||||
@ -2010,9 +2132,15 @@ async fn main() -> Result<()> {
|
|||||||
.as_deref()
|
.as_deref()
|
||||||
.map(short_session)
|
.map(short_session)
|
||||||
.unwrap_or_else(|| "new-session".to_string());
|
.unwrap_or_else(|| "new-session".to_string());
|
||||||
|
let label = format_remote_dispatch_kind(request.request_kind);
|
||||||
println!(
|
println!(
|
||||||
"#{} [{}] {} -> {} | {}",
|
"#{} [{}] {} {} -> {} | {}",
|
||||||
request.id, request.priority, request.status, target, request.task
|
request.id,
|
||||||
|
request.priority,
|
||||||
|
label,
|
||||||
|
request.status,
|
||||||
|
target,
|
||||||
|
request.task.lines().next().unwrap_or(&request.task)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3096,6 +3224,13 @@ fn format_remote_dispatch_action(action: &session::manager::RemoteDispatchAction
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn format_remote_dispatch_kind(kind: session::RemoteDispatchKind) -> &'static str {
|
||||||
|
match kind {
|
||||||
|
session::RemoteDispatchKind::Standard => "standard",
|
||||||
|
session::RemoteDispatchKind::ComputerUse => "computer_use",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn short_session(session_id: &str) -> String {
|
fn short_session(session_id: &str) -> String {
|
||||||
session_id.chars().take(8).collect()
|
session_id.chars().take(8).collect()
|
||||||
}
|
}
|
||||||
@ -3225,6 +3360,86 @@ fn handle_remote_dispatch_connection(
|
|||||||
&serde_json::to_string(&request)?,
|
&serde_json::to_string(&request)?,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
("POST", "/computer-use") => {
|
||||||
|
let auth = headers
|
||||||
|
.get("authorization")
|
||||||
|
.map(String::as_str)
|
||||||
|
.unwrap_or_default();
|
||||||
|
let expected = format!("Bearer {bearer_token}");
|
||||||
|
if auth != expected {
|
||||||
|
return write_http_response(
|
||||||
|
stream,
|
||||||
|
401,
|
||||||
|
"application/json",
|
||||||
|
&serde_json::json!({"error": "unauthorized"}).to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let payload: RemoteComputerUseHttpRequest =
|
||||||
|
serde_json::from_slice(&body).context("Invalid remote computer-use JSON body")?;
|
||||||
|
if payload.goal.trim().is_empty() {
|
||||||
|
return write_http_response(
|
||||||
|
stream,
|
||||||
|
400,
|
||||||
|
"application/json",
|
||||||
|
&serde_json::json!({"error": "goal is required"}).to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let target_session_id = match payload
|
||||||
|
.to_session
|
||||||
|
.as_deref()
|
||||||
|
.map(|value| resolve_session_id(db, value))
|
||||||
|
.transpose()
|
||||||
|
{
|
||||||
|
Ok(value) => value,
|
||||||
|
Err(error) => {
|
||||||
|
return write_http_response(
|
||||||
|
stream,
|
||||||
|
400,
|
||||||
|
"application/json",
|
||||||
|
&serde_json::json!({"error": error.to_string()}).to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let requester = stream.peer_addr().ok().map(|addr| addr.ip().to_string());
|
||||||
|
let defaults = cfg.computer_use_dispatch_defaults();
|
||||||
|
let request = match session::manager::create_computer_use_remote_dispatch_request(
|
||||||
|
db,
|
||||||
|
cfg,
|
||||||
|
&payload.goal,
|
||||||
|
payload.target_url.as_deref(),
|
||||||
|
payload.context.as_deref(),
|
||||||
|
target_session_id.as_deref(),
|
||||||
|
payload.priority.unwrap_or(TaskPriorityArg::Normal).into(),
|
||||||
|
payload.agent.as_deref(),
|
||||||
|
payload.profile.as_deref(),
|
||||||
|
Some(payload.use_worktree.unwrap_or(defaults.use_worktree)),
|
||||||
|
session::SessionGrouping {
|
||||||
|
project: payload.project,
|
||||||
|
task_group: payload.task_group,
|
||||||
|
},
|
||||||
|
"http_computer_use",
|
||||||
|
requester.as_deref(),
|
||||||
|
) {
|
||||||
|
Ok(request) => request,
|
||||||
|
Err(error) => {
|
||||||
|
return write_http_response(
|
||||||
|
stream,
|
||||||
|
400,
|
||||||
|
"application/json",
|
||||||
|
&serde_json::json!({"error": error.to_string()}).to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
write_http_response(
|
||||||
|
stream,
|
||||||
|
202,
|
||||||
|
"application/json",
|
||||||
|
&serde_json::to_string(&request)?,
|
||||||
|
)
|
||||||
|
}
|
||||||
_ => write_http_response(
|
_ => write_http_response(
|
||||||
stream,
|
stream,
|
||||||
404,
|
404,
|
||||||
@ -4995,6 +5210,55 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cli_parses_remote_computer_use_command() {
|
||||||
|
let cli = Cli::try_parse_from([
|
||||||
|
"ecc",
|
||||||
|
"remote",
|
||||||
|
"computer-use",
|
||||||
|
"--goal",
|
||||||
|
"Confirm the recovery banner",
|
||||||
|
"--target-url",
|
||||||
|
"https://ecc.tools/account",
|
||||||
|
"--context",
|
||||||
|
"Use the production flow",
|
||||||
|
"--priority",
|
||||||
|
"critical",
|
||||||
|
"--agent",
|
||||||
|
"codex",
|
||||||
|
"--profile",
|
||||||
|
"browser",
|
||||||
|
"--no-worktree",
|
||||||
|
])
|
||||||
|
.expect("remote computer-use should parse");
|
||||||
|
|
||||||
|
match cli.command {
|
||||||
|
Some(Commands::Remote {
|
||||||
|
command:
|
||||||
|
RemoteCommands::ComputerUse {
|
||||||
|
goal,
|
||||||
|
target_url,
|
||||||
|
context,
|
||||||
|
priority,
|
||||||
|
agent,
|
||||||
|
profile,
|
||||||
|
worktree,
|
||||||
|
..
|
||||||
|
},
|
||||||
|
}) => {
|
||||||
|
assert_eq!(goal, "Confirm the recovery banner");
|
||||||
|
assert_eq!(target_url.as_deref(), Some("https://ecc.tools/account"));
|
||||||
|
assert_eq!(context.as_deref(), Some("Use the production flow"));
|
||||||
|
assert_eq!(priority, TaskPriorityArg::Critical);
|
||||||
|
assert_eq!(agent.as_deref(), Some("codex"));
|
||||||
|
assert_eq!(profile.as_deref(), Some("browser"));
|
||||||
|
assert!(worktree.no_worktree);
|
||||||
|
assert!(!worktree.worktree);
|
||||||
|
}
|
||||||
|
_ => panic!("expected remote computer-use subcommand"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn cli_parses_start_with_handoff_source() {
|
fn cli_parses_start_with_handoff_source() {
|
||||||
let cli = Cli::try_parse_from([
|
let cli = Cli::try_parse_from([
|
||||||
|
|||||||
@ -14,8 +14,8 @@ use super::runtime::capture_command_output;
|
|||||||
use super::store::StateStore;
|
use super::store::StateStore;
|
||||||
use super::{
|
use super::{
|
||||||
default_project_label, default_task_group_label, normalize_group_label, HarnessKind,
|
default_project_label, default_task_group_label, normalize_group_label, HarnessKind,
|
||||||
ScheduledTask, Session, SessionAgentProfile, SessionGrouping, SessionHarnessInfo,
|
RemoteDispatchKind, ScheduledTask, Session, SessionAgentProfile, SessionGrouping,
|
||||||
SessionMetrics, SessionState,
|
SessionHarnessInfo, SessionMetrics, SessionState,
|
||||||
};
|
};
|
||||||
use crate::comms::{self, MessageType, TaskPriority};
|
use crate::comms::{self, MessageType, TaskPriority};
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
@ -268,6 +268,125 @@ pub fn create_remote_dispatch_request(
|
|||||||
) -> Result<super::RemoteDispatchRequest> {
|
) -> Result<super::RemoteDispatchRequest> {
|
||||||
let working_dir =
|
let working_dir =
|
||||||
std::env::current_dir().context("Failed to resolve current working directory")?;
|
std::env::current_dir().context("Failed to resolve current working directory")?;
|
||||||
|
create_remote_dispatch_request_inner(
|
||||||
|
db,
|
||||||
|
cfg,
|
||||||
|
RemoteDispatchKind::Standard,
|
||||||
|
&working_dir,
|
||||||
|
task,
|
||||||
|
None,
|
||||||
|
target_session_id,
|
||||||
|
priority,
|
||||||
|
agent_type,
|
||||||
|
profile_name,
|
||||||
|
use_worktree,
|
||||||
|
grouping,
|
||||||
|
source,
|
||||||
|
requester,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub fn create_computer_use_remote_dispatch_request(
|
||||||
|
db: &StateStore,
|
||||||
|
cfg: &Config,
|
||||||
|
goal: &str,
|
||||||
|
target_url: Option<&str>,
|
||||||
|
context: Option<&str>,
|
||||||
|
target_session_id: Option<&str>,
|
||||||
|
priority: TaskPriority,
|
||||||
|
agent_type_override: Option<&str>,
|
||||||
|
profile_name_override: Option<&str>,
|
||||||
|
use_worktree_override: Option<bool>,
|
||||||
|
grouping: SessionGrouping,
|
||||||
|
source: &str,
|
||||||
|
requester: Option<&str>,
|
||||||
|
) -> Result<super::RemoteDispatchRequest> {
|
||||||
|
let working_dir =
|
||||||
|
std::env::current_dir().context("Failed to resolve current working directory")?;
|
||||||
|
create_computer_use_remote_dispatch_request_in_dir(
|
||||||
|
db,
|
||||||
|
cfg,
|
||||||
|
&working_dir,
|
||||||
|
goal,
|
||||||
|
target_url,
|
||||||
|
context,
|
||||||
|
target_session_id,
|
||||||
|
priority,
|
||||||
|
agent_type_override,
|
||||||
|
profile_name_override,
|
||||||
|
use_worktree_override,
|
||||||
|
grouping,
|
||||||
|
source,
|
||||||
|
requester,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
fn create_computer_use_remote_dispatch_request_in_dir(
|
||||||
|
db: &StateStore,
|
||||||
|
cfg: &Config,
|
||||||
|
working_dir: &Path,
|
||||||
|
goal: &str,
|
||||||
|
target_url: Option<&str>,
|
||||||
|
context: Option<&str>,
|
||||||
|
target_session_id: Option<&str>,
|
||||||
|
priority: TaskPriority,
|
||||||
|
agent_type_override: Option<&str>,
|
||||||
|
profile_name_override: Option<&str>,
|
||||||
|
use_worktree_override: Option<bool>,
|
||||||
|
grouping: SessionGrouping,
|
||||||
|
source: &str,
|
||||||
|
requester: Option<&str>,
|
||||||
|
) -> Result<super::RemoteDispatchRequest> {
|
||||||
|
let defaults = cfg.computer_use_dispatch_defaults();
|
||||||
|
let task = render_computer_use_task(goal, target_url, context);
|
||||||
|
let agent_type = agent_type_override.unwrap_or(&defaults.agent);
|
||||||
|
let profile_name = profile_name_override.or(defaults.profile.as_deref());
|
||||||
|
let use_worktree = use_worktree_override.unwrap_or(defaults.use_worktree);
|
||||||
|
let grouping = SessionGrouping {
|
||||||
|
project: grouping.project.or(defaults.project),
|
||||||
|
task_group: grouping
|
||||||
|
.task_group
|
||||||
|
.or(defaults.task_group)
|
||||||
|
.or_else(|| Some(default_task_group_label(goal))),
|
||||||
|
};
|
||||||
|
|
||||||
|
create_remote_dispatch_request_inner(
|
||||||
|
db,
|
||||||
|
cfg,
|
||||||
|
RemoteDispatchKind::ComputerUse,
|
||||||
|
working_dir,
|
||||||
|
&task,
|
||||||
|
target_url,
|
||||||
|
target_session_id,
|
||||||
|
priority,
|
||||||
|
agent_type,
|
||||||
|
profile_name,
|
||||||
|
use_worktree,
|
||||||
|
grouping,
|
||||||
|
source,
|
||||||
|
requester,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
fn create_remote_dispatch_request_inner(
|
||||||
|
db: &StateStore,
|
||||||
|
cfg: &Config,
|
||||||
|
request_kind: RemoteDispatchKind,
|
||||||
|
working_dir: &Path,
|
||||||
|
task: &str,
|
||||||
|
target_url: Option<&str>,
|
||||||
|
target_session_id: Option<&str>,
|
||||||
|
priority: TaskPriority,
|
||||||
|
agent_type: &str,
|
||||||
|
profile_name: Option<&str>,
|
||||||
|
use_worktree: bool,
|
||||||
|
grouping: SessionGrouping,
|
||||||
|
source: &str,
|
||||||
|
requester: Option<&str>,
|
||||||
|
) -> Result<super::RemoteDispatchRequest> {
|
||||||
let project = grouping
|
let project = grouping
|
||||||
.project
|
.project
|
||||||
.as_deref()
|
.as_deref()
|
||||||
@ -288,8 +407,10 @@ pub fn create_remote_dispatch_request(
|
|||||||
}
|
}
|
||||||
|
|
||||||
db.insert_remote_dispatch_request(
|
db.insert_remote_dispatch_request(
|
||||||
|
request_kind,
|
||||||
target_session_id,
|
target_session_id,
|
||||||
task,
|
task,
|
||||||
|
target_url,
|
||||||
priority,
|
priority,
|
||||||
&agent_type,
|
&agent_type,
|
||||||
profile_name,
|
profile_name,
|
||||||
@ -302,6 +423,24 @@ pub fn create_remote_dispatch_request(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn render_computer_use_task(goal: &str, target_url: Option<&str>, context: Option<&str>) -> String {
|
||||||
|
let mut lines = vec![
|
||||||
|
"Computer-use task.".to_string(),
|
||||||
|
format!("Goal: {}", goal.trim()),
|
||||||
|
];
|
||||||
|
if let Some(target_url) = target_url.map(str::trim).filter(|value| !value.is_empty()) {
|
||||||
|
lines.push(format!("Target URL: {target_url}"));
|
||||||
|
}
|
||||||
|
if let Some(context) = context.map(str::trim).filter(|value| !value.is_empty()) {
|
||||||
|
lines.push(format!("Context: {context}"));
|
||||||
|
}
|
||||||
|
lines.push(
|
||||||
|
"Use browser or computer-use tools directly when available, and report blockers clearly if auth, approvals, or local-device access prevent completion."
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
|
lines.join("\n")
|
||||||
|
}
|
||||||
|
|
||||||
pub fn list_remote_dispatch_requests(
|
pub fn list_remote_dispatch_requests(
|
||||||
db: &StateStore,
|
db: &StateStore,
|
||||||
include_processed: bool,
|
include_processed: bool,
|
||||||
@ -3840,6 +3979,7 @@ mod tests {
|
|||||||
agent_profiles: Default::default(),
|
agent_profiles: Default::default(),
|
||||||
orchestration_templates: Default::default(),
|
orchestration_templates: Default::default(),
|
||||||
memory_connectors: Default::default(),
|
memory_connectors: Default::default(),
|
||||||
|
computer_use_dispatch: crate::config::ComputerUseDispatchConfig::default(),
|
||||||
auto_dispatch_unread_handoffs: false,
|
auto_dispatch_unread_handoffs: false,
|
||||||
auto_dispatch_limit_per_session: 5,
|
auto_dispatch_limit_per_session: 5,
|
||||||
auto_create_worktrees: true,
|
auto_create_worktrees: true,
|
||||||
@ -4656,8 +4796,10 @@ mod tests {
|
|||||||
let (fake_runner, _log_path) = write_fake_claude(tempdir.path())?;
|
let (fake_runner, _log_path) = write_fake_claude(tempdir.path())?;
|
||||||
|
|
||||||
let request = db.insert_remote_dispatch_request(
|
let request = db.insert_remote_dispatch_request(
|
||||||
|
RemoteDispatchKind::Standard,
|
||||||
None,
|
None,
|
||||||
"Remote phone triage",
|
"Remote phone triage",
|
||||||
|
None,
|
||||||
TaskPriority::High,
|
TaskPriority::High,
|
||||||
"claude",
|
"claude",
|
||||||
None,
|
None,
|
||||||
@ -4703,6 +4845,59 @@ mod tests {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn create_computer_use_remote_dispatch_request_uses_config_defaults() -> Result<()> {
|
||||||
|
let tempdir = TestDir::new("manager-create-computer-use-remote-defaults")?;
|
||||||
|
let repo_root = tempdir.path().join("repo");
|
||||||
|
init_git_repo(&repo_root)?;
|
||||||
|
|
||||||
|
let mut cfg = build_config(tempdir.path());
|
||||||
|
cfg.computer_use_dispatch = crate::config::ComputerUseDispatchConfig {
|
||||||
|
agent: Some("codex".to_string()),
|
||||||
|
profile: None,
|
||||||
|
use_worktree: false,
|
||||||
|
project: Some("ops".to_string()),
|
||||||
|
task_group: Some("remote browser".to_string()),
|
||||||
|
};
|
||||||
|
let db = StateStore::open(&cfg.db_path)?;
|
||||||
|
|
||||||
|
let request = create_computer_use_remote_dispatch_request_in_dir(
|
||||||
|
&db,
|
||||||
|
&cfg,
|
||||||
|
&repo_root,
|
||||||
|
"Open the billing portal and confirm the refund banner",
|
||||||
|
Some("https://ecc.tools/account"),
|
||||||
|
Some("Use the production account flow"),
|
||||||
|
None,
|
||||||
|
TaskPriority::Critical,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
SessionGrouping::default(),
|
||||||
|
"http_computer_use",
|
||||||
|
Some("127.0.0.1"),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
assert_eq!(request.request_kind, RemoteDispatchKind::ComputerUse);
|
||||||
|
assert_eq!(
|
||||||
|
request.target_url.as_deref(),
|
||||||
|
Some("https://ecc.tools/account")
|
||||||
|
);
|
||||||
|
assert_eq!(request.agent_type, "codex");
|
||||||
|
assert_eq!(request.project, "ops");
|
||||||
|
assert_eq!(request.task_group, "remote browser");
|
||||||
|
assert!(!request.use_worktree);
|
||||||
|
assert!(request.task.contains("Computer-use task."));
|
||||||
|
assert!(request.task.contains("Goal: Open the billing portal"));
|
||||||
|
assert!(request
|
||||||
|
.task
|
||||||
|
.contains("Target URL: https://ecc.tools/account"));
|
||||||
|
assert!(request
|
||||||
|
.task
|
||||||
|
.contains("Context: Use the production account flow"));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "current_thread")]
|
#[tokio::test(flavor = "current_thread")]
|
||||||
async fn stop_session_kills_process_and_optionally_cleans_worktree() -> Result<()> {
|
async fn stop_session_kills_process_and_optionally_cleans_worktree() -> Result<()> {
|
||||||
let tempdir = TestDir::new("manager-stop-session")?;
|
let tempdir = TestDir::new("manager-stop-session")?;
|
||||||
|
|||||||
@ -398,8 +398,10 @@ pub struct ScheduledTask {
|
|||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
pub struct RemoteDispatchRequest {
|
pub struct RemoteDispatchRequest {
|
||||||
pub id: i64,
|
pub id: i64,
|
||||||
|
pub request_kind: RemoteDispatchKind,
|
||||||
pub target_session_id: Option<String>,
|
pub target_session_id: Option<String>,
|
||||||
pub task: String,
|
pub task: String,
|
||||||
|
pub target_url: Option<String>,
|
||||||
pub priority: crate::comms::TaskPriority,
|
pub priority: crate::comms::TaskPriority,
|
||||||
pub agent_type: String,
|
pub agent_type: String,
|
||||||
pub profile_name: Option<String>,
|
pub profile_name: Option<String>,
|
||||||
@ -418,6 +420,31 @@ pub struct RemoteDispatchRequest {
|
|||||||
pub dispatched_at: Option<DateTime<Utc>>,
|
pub dispatched_at: Option<DateTime<Utc>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum RemoteDispatchKind {
|
||||||
|
Standard,
|
||||||
|
ComputerUse,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for RemoteDispatchKind {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Standard => write!(f, "standard"),
|
||||||
|
Self::ComputerUse => write!(f, "computer_use"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RemoteDispatchKind {
|
||||||
|
pub fn from_db_value(value: &str) -> Self {
|
||||||
|
match value {
|
||||||
|
"computer_use" => Self::ComputerUse,
|
||||||
|
_ => Self::Standard,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum RemoteDispatchStatus {
|
pub enum RemoteDispatchStatus {
|
||||||
|
|||||||
@ -18,8 +18,8 @@ use super::{
|
|||||||
ContextGraphCompactionStats, ContextGraphEntity, ContextGraphEntityDetail,
|
ContextGraphCompactionStats, ContextGraphEntity, ContextGraphEntityDetail,
|
||||||
ContextGraphObservation, ContextGraphRecallEntry, ContextGraphRelation, ContextGraphSyncStats,
|
ContextGraphObservation, ContextGraphRecallEntry, ContextGraphRelation, ContextGraphSyncStats,
|
||||||
ContextObservationPriority, DecisionLogEntry, FileActivityAction, FileActivityEntry,
|
ContextObservationPriority, DecisionLogEntry, FileActivityAction, FileActivityEntry,
|
||||||
HarnessKind, RemoteDispatchRequest, RemoteDispatchStatus, ScheduledTask, Session,
|
HarnessKind, RemoteDispatchKind, RemoteDispatchRequest, RemoteDispatchStatus, ScheduledTask,
|
||||||
SessionAgentProfile, SessionHarnessInfo, SessionMessage, SessionMetrics, SessionState,
|
Session, SessionAgentProfile, SessionHarnessInfo, SessionMessage, SessionMetrics, SessionState,
|
||||||
WorktreeInfo,
|
WorktreeInfo,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -318,8 +318,10 @@ impl StateStore {
|
|||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS remote_dispatch_requests (
|
CREATE TABLE IF NOT EXISTS remote_dispatch_requests (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
request_kind TEXT NOT NULL DEFAULT 'standard',
|
||||||
target_session_id TEXT REFERENCES sessions(id) ON DELETE SET NULL,
|
target_session_id TEXT REFERENCES sessions(id) ON DELETE SET NULL,
|
||||||
task TEXT NOT NULL,
|
task TEXT NOT NULL,
|
||||||
|
target_url TEXT,
|
||||||
priority INTEGER NOT NULL DEFAULT 1,
|
priority INTEGER NOT NULL DEFAULT 1,
|
||||||
agent_type TEXT NOT NULL,
|
agent_type TEXT NOT NULL,
|
||||||
profile_name TEXT,
|
profile_name TEXT,
|
||||||
@ -681,6 +683,24 @@ impl StateStore {
|
|||||||
.context("Failed to add last_auto_prune_active_skipped column to daemon_activity table")?;
|
.context("Failed to add last_auto_prune_active_skipped column to daemon_activity table")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !self.has_column("remote_dispatch_requests", "request_kind")? {
|
||||||
|
self.conn
|
||||||
|
.execute(
|
||||||
|
"ALTER TABLE remote_dispatch_requests ADD COLUMN request_kind TEXT NOT NULL DEFAULT 'standard'",
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
.context("Failed to add request_kind column to remote_dispatch_requests table")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.has_column("remote_dispatch_requests", "target_url")? {
|
||||||
|
self.conn
|
||||||
|
.execute(
|
||||||
|
"ALTER TABLE remote_dispatch_requests ADD COLUMN target_url TEXT",
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
.context("Failed to add target_url column to remote_dispatch_requests table")?;
|
||||||
|
}
|
||||||
|
|
||||||
self.conn.execute_batch(
|
self.conn.execute_batch(
|
||||||
"CREATE UNIQUE INDEX IF NOT EXISTS idx_tool_log_hook_event
|
"CREATE UNIQUE INDEX IF NOT EXISTS idx_tool_log_hook_event
|
||||||
ON tool_log(hook_event_id)
|
ON tool_log(hook_event_id)
|
||||||
@ -1192,8 +1212,10 @@ impl StateStore {
|
|||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn insert_remote_dispatch_request(
|
pub fn insert_remote_dispatch_request(
|
||||||
&self,
|
&self,
|
||||||
|
request_kind: RemoteDispatchKind,
|
||||||
target_session_id: Option<&str>,
|
target_session_id: Option<&str>,
|
||||||
task: &str,
|
task: &str,
|
||||||
|
target_url: Option<&str>,
|
||||||
priority: crate::comms::TaskPriority,
|
priority: crate::comms::TaskPriority,
|
||||||
agent_type: &str,
|
agent_type: &str,
|
||||||
profile_name: Option<&str>,
|
profile_name: Option<&str>,
|
||||||
@ -1207,8 +1229,10 @@ impl StateStore {
|
|||||||
let now = chrono::Utc::now();
|
let now = chrono::Utc::now();
|
||||||
self.conn.execute(
|
self.conn.execute(
|
||||||
"INSERT INTO remote_dispatch_requests (
|
"INSERT INTO remote_dispatch_requests (
|
||||||
|
request_kind,
|
||||||
target_session_id,
|
target_session_id,
|
||||||
task,
|
task,
|
||||||
|
target_url,
|
||||||
priority,
|
priority,
|
||||||
agent_type,
|
agent_type,
|
||||||
profile_name,
|
profile_name,
|
||||||
@ -1221,10 +1245,12 @@ impl StateStore {
|
|||||||
status,
|
status,
|
||||||
created_at,
|
created_at,
|
||||||
updated_at
|
updated_at
|
||||||
) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, 'pending', ?12, ?13)",
|
) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, 'pending', ?14, ?15)",
|
||||||
rusqlite::params![
|
rusqlite::params![
|
||||||
|
request_kind.to_string(),
|
||||||
target_session_id,
|
target_session_id,
|
||||||
task,
|
task,
|
||||||
|
target_url,
|
||||||
task_priority_db_value(priority),
|
task_priority_db_value(priority),
|
||||||
agent_type,
|
agent_type,
|
||||||
profile_name,
|
profile_name,
|
||||||
@ -1250,7 +1276,7 @@ impl StateStore {
|
|||||||
limit: usize,
|
limit: usize,
|
||||||
) -> Result<Vec<RemoteDispatchRequest>> {
|
) -> Result<Vec<RemoteDispatchRequest>> {
|
||||||
let sql = if include_processed {
|
let sql = if include_processed {
|
||||||
"SELECT id, target_session_id, task, priority, agent_type, profile_name, working_dir,
|
"SELECT id, request_kind, target_session_id, task, target_url, priority, agent_type, profile_name, working_dir,
|
||||||
project, task_group, use_worktree, source, requester, status,
|
project, task_group, use_worktree, source, requester, status,
|
||||||
result_session_id, result_action, error, created_at, updated_at, dispatched_at
|
result_session_id, result_action, error, created_at, updated_at, dispatched_at
|
||||||
FROM remote_dispatch_requests
|
FROM remote_dispatch_requests
|
||||||
@ -1258,7 +1284,7 @@ impl StateStore {
|
|||||||
priority DESC, created_at ASC, id ASC
|
priority DESC, created_at ASC, id ASC
|
||||||
LIMIT ?1"
|
LIMIT ?1"
|
||||||
} else {
|
} else {
|
||||||
"SELECT id, target_session_id, task, priority, agent_type, profile_name, working_dir,
|
"SELECT id, request_kind, target_session_id, task, target_url, priority, agent_type, profile_name, working_dir,
|
||||||
project, task_group, use_worktree, source, requester, status,
|
project, task_group, use_worktree, source, requester, status,
|
||||||
result_session_id, result_action, error, created_at, updated_at, dispatched_at
|
result_session_id, result_action, error, created_at, updated_at, dispatched_at
|
||||||
FROM remote_dispatch_requests
|
FROM remote_dispatch_requests
|
||||||
@ -1285,7 +1311,7 @@ impl StateStore {
|
|||||||
) -> Result<Option<RemoteDispatchRequest>> {
|
) -> Result<Option<RemoteDispatchRequest>> {
|
||||||
self.conn
|
self.conn
|
||||||
.query_row(
|
.query_row(
|
||||||
"SELECT id, target_session_id, task, priority, agent_type, profile_name, working_dir,
|
"SELECT id, request_kind, target_session_id, task, target_url, priority, agent_type, profile_name, working_dir,
|
||||||
project, task_group, use_worktree, source, requester, status,
|
project, task_group, use_worktree, source, requester, status,
|
||||||
result_session_id, result_action, error, created_at, updated_at, dispatched_at
|
result_session_id, result_action, error, created_at, updated_at, dispatched_at
|
||||||
FROM remote_dispatch_requests
|
FROM remote_dispatch_requests
|
||||||
@ -3900,29 +3926,31 @@ fn map_scheduled_task(row: &rusqlite::Row<'_>) -> rusqlite::Result<ScheduledTask
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn map_remote_dispatch_request(row: &rusqlite::Row<'_>) -> rusqlite::Result<RemoteDispatchRequest> {
|
fn map_remote_dispatch_request(row: &rusqlite::Row<'_>) -> rusqlite::Result<RemoteDispatchRequest> {
|
||||||
let created_at = parse_store_timestamp(row.get::<_, String>(16)?, 16)?;
|
let created_at = parse_store_timestamp(row.get::<_, String>(18)?, 18)?;
|
||||||
let updated_at = parse_store_timestamp(row.get::<_, String>(17)?, 17)?;
|
let updated_at = parse_store_timestamp(row.get::<_, String>(19)?, 19)?;
|
||||||
let dispatched_at = row
|
let dispatched_at = row
|
||||||
.get::<_, Option<String>>(18)?
|
.get::<_, Option<String>>(20)?
|
||||||
.map(|value| parse_store_timestamp(value, 18))
|
.map(|value| parse_store_timestamp(value, 20))
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
Ok(RemoteDispatchRequest {
|
Ok(RemoteDispatchRequest {
|
||||||
id: row.get(0)?,
|
id: row.get(0)?,
|
||||||
target_session_id: normalize_optional_string(row.get(1)?),
|
request_kind: RemoteDispatchKind::from_db_value(&row.get::<_, String>(1)?),
|
||||||
task: row.get(2)?,
|
target_session_id: normalize_optional_string(row.get(2)?),
|
||||||
priority: task_priority_from_db_value(row.get::<_, i64>(3)?),
|
task: row.get(3)?,
|
||||||
agent_type: row.get(4)?,
|
target_url: normalize_optional_string(row.get(4)?),
|
||||||
profile_name: normalize_optional_string(row.get(5)?),
|
priority: task_priority_from_db_value(row.get::<_, i64>(5)?),
|
||||||
working_dir: PathBuf::from(row.get::<_, String>(6)?),
|
agent_type: row.get(6)?,
|
||||||
project: row.get(7)?,
|
profile_name: normalize_optional_string(row.get(7)?),
|
||||||
task_group: row.get(8)?,
|
working_dir: PathBuf::from(row.get::<_, String>(8)?),
|
||||||
use_worktree: row.get::<_, i64>(9)? != 0,
|
project: row.get(9)?,
|
||||||
source: row.get(10)?,
|
task_group: row.get(10)?,
|
||||||
requester: normalize_optional_string(row.get(11)?),
|
use_worktree: row.get::<_, i64>(11)? != 0,
|
||||||
status: RemoteDispatchStatus::from_db_value(&row.get::<_, String>(12)?),
|
source: row.get(12)?,
|
||||||
result_session_id: normalize_optional_string(row.get(13)?),
|
requester: normalize_optional_string(row.get(13)?),
|
||||||
result_action: normalize_optional_string(row.get(14)?),
|
status: RemoteDispatchStatus::from_db_value(&row.get::<_, String>(14)?),
|
||||||
error: normalize_optional_string(row.get(15)?),
|
result_session_id: normalize_optional_string(row.get(15)?),
|
||||||
|
result_action: normalize_optional_string(row.get(16)?),
|
||||||
|
error: normalize_optional_string(row.get(17)?),
|
||||||
created_at,
|
created_at,
|
||||||
updated_at,
|
updated_at,
|
||||||
dispatched_at,
|
dispatched_at,
|
||||||
|
|||||||
@ -14612,6 +14612,7 @@ diff --git a/src/lib.rs b/src/lib.rs
|
|||||||
agent_profiles: Default::default(),
|
agent_profiles: Default::default(),
|
||||||
orchestration_templates: Default::default(),
|
orchestration_templates: Default::default(),
|
||||||
memory_connectors: Default::default(),
|
memory_connectors: Default::default(),
|
||||||
|
computer_use_dispatch: crate::config::ComputerUseDispatchConfig::default(),
|
||||||
auto_dispatch_unread_handoffs: false,
|
auto_dispatch_unread_handoffs: false,
|
||||||
auto_dispatch_limit_per_session: 5,
|
auto_dispatch_limit_per_session: 5,
|
||||||
auto_create_worktrees: true,
|
auto_create_worktrees: true,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user