summaryrefslogtreecommitdiff
path: root/makima/src/daemon
diff options
context:
space:
mode:
authorsoryu <soryu@soryu.co>2026-01-18 18:55:04 +0000
committersoryu <soryu@soryu.co>2026-01-18 18:55:04 +0000
commit9dbc2c3199047609a9f8496fec07ecdb10aee73d (patch)
treec5a4e19b2a83cac1901e81f2ff1bc7a465f8a9ff /makima/src/daemon
parent273da072fa0573c935798dc723ed79fd71ab037a (diff)
downloadsoryu-9dbc2c3199047609a9f8496fec07ecdb10aee73d.tar.gz
soryu-9dbc2c3199047609a9f8496fec07ecdb10aee73d.zip
Add pushed heartbeats and multi-question select
Diffstat (limited to 'makima/src/daemon')
-rw-r--r--makima/src/daemon/api/supervisor.rs5
-rw-r--r--makima/src/daemon/cli/supervisor.rs4
-rw-r--r--makima/src/daemon/task/manager.rs34
3 files changed, 38 insertions, 5 deletions
diff --git a/makima/src/daemon/api/supervisor.rs b/makima/src/daemon/api/supervisor.rs
index adeda22..1dc699e 100644
--- a/makima/src/daemon/api/supervisor.rs
+++ b/makima/src/daemon/api/supervisor.rs
@@ -73,6 +73,9 @@ pub struct AskQuestionRequest {
pub timeout_seconds: i32,
/// When true, the request will block indefinitely until user responds (no timeout)
pub phaseguard: bool,
+ /// When true, allow selecting multiple choices (response will be comma-separated)
+ #[serde(default)]
+ pub multi_select: bool,
}
// Generic response type for JSON output
@@ -205,6 +208,7 @@ impl ApiClient {
context: Option<String>,
timeout_seconds: i32,
phaseguard: bool,
+ multi_select: bool,
) -> Result<JsonValue, ApiError> {
let req = AskQuestionRequest {
question: question.to_string(),
@@ -212,6 +216,7 @@ impl ApiClient {
context,
timeout_seconds,
phaseguard,
+ multi_select,
};
self.post("/api/v1/mesh/supervisor/questions", &req).await
}
diff --git a/makima/src/daemon/cli/supervisor.rs b/makima/src/daemon/cli/supervisor.rs
index 0e2da32..ae1a126 100644
--- a/makima/src/daemon/cli/supervisor.rs
+++ b/makima/src/daemon/cli/supervisor.rs
@@ -184,6 +184,10 @@ pub struct AskArgs {
/// Block indefinitely until user responds (no timeout)
#[arg(long, default_value = "false")]
pub phaseguard: bool,
+
+ /// Allow selecting multiple choices (response will be comma-separated)
+ #[arg(long, default_value = "false")]
+ pub multi_select: bool,
}
/// Arguments for status command (get contract status including phase).
diff --git a/makima/src/daemon/task/manager.rs b/makima/src/daemon/task/manager.rs
index c3ccfa4..029d026 100644
--- a/makima/src/daemon/task/manager.rs
+++ b/makima/src/daemon/task/manager.rs
@@ -3665,10 +3665,11 @@ impl TaskManagerInner {
_ = heartbeat_interval.tick(), if heartbeat_enabled => {
// Create periodic heartbeat commit to preserve work-in-progress
match self.create_heartbeat_commit(task_id, &working_dir).await {
- Ok(sha) => {
+ Ok((sha, pushed)) => {
+ let status = if pushed { "pushed" } else { "local only" };
let msg = DaemonMessage::task_output(
task_id,
- format!("[Heartbeat] Created WIP checkpoint: {}\n", &sha[..8]),
+ format!("[Heartbeat] WIP checkpoint {} ({})\n", &sha[..8], status),
false,
);
let _ = ws_tx.send(msg).await;
@@ -4151,12 +4152,12 @@ impl TaskManagerInner {
}
/// Create a heartbeat commit with all uncommitted changes (WIP checkpoint).
- /// Returns the commit SHA on success, or an error message if nothing to commit.
+ /// Returns (commit SHA, push succeeded) on success, or an error message if nothing to commit.
async fn create_heartbeat_commit(
&self,
task_id: Uuid,
worktree_path: &std::path::Path,
- ) -> Result<String, String> {
+ ) -> Result<(String, bool), String> {
// 1. Check for uncommitted changes using git status --porcelain
let status_output = tokio::process::Command::new("git")
.current_dir(worktree_path)
@@ -4220,7 +4221,30 @@ impl TaskManagerInner {
let sha = String::from_utf8_lossy(&sha_output.stdout).trim().to_string();
tracing::info!(task_id = %task_id, sha = %sha, "Created heartbeat commit");
- Ok(sha)
+ // 5. Push to remote (best effort - don't fail if push fails)
+ let push_output = tokio::process::Command::new("git")
+ .current_dir(worktree_path)
+ .args(["push"])
+ .output()
+ .await;
+
+ let pushed = match push_output {
+ Ok(output) if output.status.success() => {
+ tracing::info!(task_id = %task_id, sha = %sha, "Pushed heartbeat commit to remote");
+ true
+ }
+ Ok(output) => {
+ let stderr = String::from_utf8_lossy(&output.stderr);
+ tracing::warn!(task_id = %task_id, sha = %sha, error = %stderr, "Failed to push heartbeat commit (commit saved locally)");
+ false
+ }
+ Err(e) => {
+ tracing::warn!(task_id = %task_id, sha = %sha, error = %e, "Failed to run git push (commit saved locally)");
+ false
+ }
+ };
+
+ Ok((sha, pushed))
}
}