summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsoryu <soryu@soryu.co>2026-02-14 21:27:50 +0000
committersoryu <soryu@soryu.co>2026-02-14 21:27:50 +0000
commitafd820f99056caeada04c19d7c33fef615a809b4 (patch)
tree48eb67af94209f647cedc4facaf1823fbc42c926
parent1867caa31a79f6cf4fc163a73a8f18b4d8e285be (diff)
parent41a24585e8c771132b47b28ab1b5869040744af0 (diff)
downloadsoryu-afd820f99056caeada04c19d7c33fef615a809b4.tar.gz
soryu-afd820f99056caeada04c19d7c33fef615a809b4.zip
Merge remote-tracking branch 'origin/makima/soryu-co-soryu---makima--add-question-support-for--525448aa' into makima/directive-soryu-co-soryu-makima-c29f9112
-rw-r--r--makima/frontend/src/components/directives/DirectiveDetail.tsx131
-rw-r--r--makima/frontend/src/lib/api.ts10
-rw-r--r--makima/frontend/src/routes/directives.tsx3
-rw-r--r--makima/migrations/20260214100000_directive_reconcile_mode.sql4
-rw-r--r--makima/src/db/models.rs8
-rw-r--r--makima/src/db/repository.rs9
-rw-r--r--makima/src/orchestration/directive.rs10
-rw-r--r--makima/src/server/handlers/mesh_supervisor.rs90
-rw-r--r--makima/src/server/state.rs30
9 files changed, 268 insertions, 27 deletions
diff --git a/makima/frontend/src/components/directives/DirectiveDetail.tsx b/makima/frontend/src/components/directives/DirectiveDetail.tsx
index 79cc97c..e278939 100644
--- a/makima/frontend/src/components/directives/DirectiveDetail.tsx
+++ b/makima/frontend/src/components/directives/DirectiveDetail.tsx
@@ -1,8 +1,9 @@
import { useState, useMemo, useEffect, useRef } from "react";
-import type { DirectiveWithSteps, DirectiveStatus } from "../../lib/api";
+import type { DirectiveWithSteps, DirectiveStatus, UpdateDirectiveRequest } from "../../lib/api";
import { DirectiveDAG } from "./DirectiveDAG";
import { DirectiveLogStream } from "./DirectiveLogStream";
import { useMultiTaskSubscription } from "../../hooks/useMultiTaskSubscription";
+import { useSupervisorQuestions } from "../../contexts/SupervisorQuestionsContext";
const STATUS_BADGE: Record<DirectiveStatus, { color: string; label: string }> = {
draft: { color: "text-[#7788aa] border-[#2a3a5a]", label: "DRAFT" },
@@ -21,6 +22,7 @@ interface DirectiveDetailProps {
onFailStep: (stepId: string) => void;
onSkipStep: (stepId: string) => void;
onUpdateGoal: (goal: string) => void;
+ onUpdate: (req: UpdateDirectiveRequest) => void;
onDelete: () => void;
onRefresh: () => void;
onCleanupTasks: () => void;
@@ -35,6 +37,7 @@ export function DirectiveDetail({
onFailStep,
onSkipStep,
onUpdateGoal,
+ onUpdate,
onDelete,
onRefresh,
onCleanupTasks,
@@ -59,6 +62,24 @@ export function DirectiveDetail({
const terminalStatuses = new Set(["completed", "failed", "skipped"]);
const hasTerminalTasks = directive.steps.some((s) => s.taskId && terminalStatuses.has(s.status));
+ // Get pending questions for this directive's tasks
+ const { pendingQuestions, submitAnswer } = useSupervisorQuestions();
+ const directiveTaskIds = useMemo(() => {
+ const ids = new Set<string>();
+ if (directive.orchestratorTaskId) ids.add(directive.orchestratorTaskId);
+ for (const step of directive.steps) {
+ if (step.taskId) ids.add(step.taskId);
+ }
+ return ids;
+ }, [directive.orchestratorTaskId, directive.steps]);
+
+ const directiveQuestions = useMemo(
+ () => pendingQuestions.filter((q) =>
+ q.directiveId === directive.id || directiveTaskIds.has(q.taskId)
+ ),
+ [pendingQuestions, directive.id, directiveTaskIds]
+ );
+
// Build task map from directive steps and orchestrator
// Derive a stable key from the actual task IDs to avoid recreating the map on every poll
const taskMapKey = useMemo(() => {
@@ -155,6 +176,26 @@ export function DirectiveDetail({
</div>
)}
+ {/* Reconcile mode toggle */}
+ <div className="flex items-center gap-2 mb-2">
+ <button
+ type="button"
+ onClick={() => onUpdate({ reconcileMode: !directive.reconcileMode })}
+ className={`text-[10px] font-mono border rounded px-2 py-0.5 transition-colors ${
+ directive.reconcileMode
+ ? "text-amber-400 border-amber-800 bg-amber-900/20"
+ : "text-[#556677] border-[#2a3a5a] hover:text-[#7788aa]"
+ }`}
+ >
+ {directive.reconcileMode ? "Reconcile: ON" : "Reconcile: OFF"}
+ </button>
+ <span className="text-[9px] font-mono text-[#445566]">
+ {directive.reconcileMode
+ ? "Questions pause execution"
+ : "Questions timeout after 30s"}
+ </span>
+ </div>
+
{/* Orchestrator planning indicator */}
{directive.orchestratorTaskId && (
<div className="flex items-center gap-2 mb-2 px-2 py-1.5 bg-[#1a1a30] border border-[rgba(117,170,252,0.2)] rounded">
@@ -205,6 +246,20 @@ export function DirectiveDetail({
</div>
)}
+ {/* Pending Questions */}
+ {directiveQuestions.length > 0 && (
+ <div className="mb-2 space-y-2">
+ {directiveQuestions.map((q) => (
+ <DirectiveQuestionCard
+ key={q.questionId}
+ question={q}
+ taskName={taskMap.get(q.taskId) || "Task"}
+ onAnswer={(response) => submitAnswer(q.questionId, response)}
+ />
+ ))}
+ </div>
+ )}
+
{/* Controls */}
<div className="flex flex-wrap gap-2">
{(directive.status === "draft" || directive.status === "paused") && (
@@ -348,3 +403,77 @@ export function DirectiveDetail({
</div>
);
}
+
+/** Inline question card for directive pending questions */
+function DirectiveQuestionCard({
+ question,
+ taskName,
+ onAnswer,
+}: {
+ question: { questionId: string; question: string; choices: string[]; context: string | null };
+ taskName: string;
+ onAnswer: (response: string) => void;
+}) {
+ const [customResponse, setCustomResponse] = useState("");
+ const [submitting, setSubmitting] = useState(false);
+
+ const handleSubmit = async (response: string) => {
+ setSubmitting(true);
+ await onAnswer(response);
+ setSubmitting(false);
+ };
+
+ return (
+ <div className="px-2 py-2 bg-[#1a1020] border border-purple-900/50 rounded">
+ <div className="flex items-center gap-1.5 mb-1">
+ <span className="inline-block w-2 h-2 rounded-full bg-purple-400 animate-pulse" />
+ <span className="text-[9px] font-mono text-purple-400 uppercase">
+ Question from {taskName}
+ </span>
+ </div>
+ <p className="text-[11px] font-mono text-white mb-1.5">{question.question}</p>
+ {question.context && (
+ <p className="text-[9px] font-mono text-[#556677] mb-1.5">{question.context}</p>
+ )}
+ {question.choices.length > 0 ? (
+ <div className="flex flex-wrap gap-1">
+ {question.choices.map((choice) => (
+ <button
+ key={choice}
+ type="button"
+ disabled={submitting}
+ onClick={() => handleSubmit(choice)}
+ className="text-[10px] font-mono text-purple-300 hover:text-white border border-purple-800 hover:border-purple-600 rounded px-2 py-0.5 disabled:opacity-50"
+ >
+ {choice}
+ </button>
+ ))}
+ </div>
+ ) : (
+ <div className="flex gap-1">
+ <input
+ type="text"
+ value={customResponse}
+ onChange={(e) => setCustomResponse(e.target.value)}
+ onKeyDown={(e) => {
+ if (e.key === "Enter" && customResponse.trim()) {
+ handleSubmit(customResponse.trim());
+ }
+ }}
+ placeholder="Type your answer..."
+ className="flex-1 bg-[#0a0618] border border-purple-900/50 rounded px-2 py-0.5 text-[10px] font-mono text-white placeholder:text-[#445566]"
+ disabled={submitting}
+ />
+ <button
+ type="button"
+ disabled={submitting || !customResponse.trim()}
+ onClick={() => handleSubmit(customResponse.trim())}
+ className="text-[10px] font-mono text-purple-300 hover:text-white border border-purple-800 rounded px-2 py-0.5 disabled:opacity-50"
+ >
+ Send
+ </button>
+ </div>
+ )}
+ </div>
+ );
+}
diff --git a/makima/frontend/src/lib/api.ts b/makima/frontend/src/lib/api.ts
index 6adc4d4..f88176b 100644
--- a/makima/frontend/src/lib/api.ts
+++ b/makima/frontend/src/lib/api.ts
@@ -2241,6 +2241,8 @@ export interface PendingQuestion {
questionId: string;
taskId: string;
contractId: string;
+ /** Directive this question relates to (if from a directive task) */
+ directiveId?: string | null;
question: string;
choices: string[];
context: string | null;
@@ -3025,6 +3027,8 @@ export interface Directive {
completionTaskId: string | null;
/** Whether the memory system is enabled for this directive */
memoryEnabled: boolean;
+ /** Whether questions pause execution indefinitely until answered */
+ reconcileMode: boolean;
goalUpdatedAt: string;
startedAt: string | null;
version: number;
@@ -3064,6 +3068,8 @@ export interface DirectiveSummary {
completionTaskId: string | null;
/** Whether the memory system is enabled for this directive */
memoryEnabled: boolean;
+ /** Whether questions pause execution indefinitely until answered */
+ reconcileMode: boolean;
version: number;
createdAt: string;
updatedAt: string;
@@ -3086,6 +3092,8 @@ export interface CreateDirectiveRequest {
baseBranch?: string;
/** Enable the memory system for this directive (default: false) */
memoryEnabled?: boolean;
+ /** Whether questions pause execution indefinitely until answered (default: false) */
+ reconcileMode?: boolean;
}
export interface UpdateDirectiveRequest {
@@ -3098,6 +3106,8 @@ export interface UpdateDirectiveRequest {
orchestratorTaskId?: string;
/** Enable or disable the memory system for this directive */
memoryEnabled?: boolean;
+ /** Whether questions pause execution indefinitely until answered */
+ reconcileMode?: boolean;
version?: number;
}
diff --git a/makima/frontend/src/routes/directives.tsx b/makima/frontend/src/routes/directives.tsx
index ca4437c..643cfee 100644
--- a/makima/frontend/src/routes/directives.tsx
+++ b/makima/frontend/src/routes/directives.tsx
@@ -12,7 +12,7 @@ export default function DirectivesPage() {
const navigate = useNavigate();
const { id: selectedId } = useParams<{ id: string }>();
const { directives, loading: listLoading, create, remove } = useDirectives();
- const { directive, refresh: refreshDetail, start, pause, advance, completeStep, failStep, skipStep, updateGoal, cleanupTasks } = useDirective(selectedId);
+ const { directive, refresh: refreshDetail, update, start, pause, advance, completeStep, failStep, skipStep, updateGoal, cleanupTasks } = useDirective(selectedId);
const [showCreate, setShowCreate] = useState(false);
const [newTitle, setNewTitle] = useState("");
@@ -207,6 +207,7 @@ export default function DirectivesPage() {
onFailStep={failStep}
onSkipStep={skipStep}
onUpdateGoal={updateGoal}
+ onUpdate={update}
onDelete={handleDelete}
onRefresh={refreshDetail}
onCleanupTasks={cleanupTasks}
diff --git a/makima/migrations/20260214100000_directive_reconcile_mode.sql b/makima/migrations/20260214100000_directive_reconcile_mode.sql
new file mode 100644
index 0000000..a06e8f2
--- /dev/null
+++ b/makima/migrations/20260214100000_directive_reconcile_mode.sql
@@ -0,0 +1,4 @@
+-- Add reconcile_mode flag to directives table.
+-- When true, directive task questions pause execution indefinitely until answered.
+-- When false (default), questions timeout after 30 seconds.
+ALTER TABLE directives ADD COLUMN IF NOT EXISTS reconcile_mode BOOLEAN NOT NULL DEFAULT false;
diff --git a/makima/src/db/models.rs b/makima/src/db/models.rs
index e36da0d..6ec6cf4 100644
--- a/makima/src/db/models.rs
+++ b/makima/src/db/models.rs
@@ -2714,6 +2714,8 @@ pub struct Directive {
pub pr_url: Option<String>,
pub pr_branch: Option<String>,
pub completion_task_id: Option<Uuid>,
+ /// Whether questions pause execution indefinitely until answered
+ pub reconcile_mode: bool,
pub goal_updated_at: DateTime<Utc>,
pub started_at: Option<DateTime<Utc>>,
pub version: i32,
@@ -2763,6 +2765,8 @@ pub struct DirectiveSummary {
pub orchestrator_task_id: Option<Uuid>,
pub pr_url: Option<String>,
pub completion_task_id: Option<Uuid>,
+ /// Whether questions pause execution indefinitely until answered
+ pub reconcile_mode: bool,
pub version: i32,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
@@ -2789,6 +2793,8 @@ pub struct CreateDirectiveRequest {
pub repository_url: Option<String>,
pub local_path: Option<String>,
pub base_branch: Option<String>,
+ /// Whether questions pause execution indefinitely until answered
+ pub reconcile_mode: Option<bool>,
}
/// Request to update a directive.
@@ -2804,6 +2810,8 @@ pub struct UpdateDirectiveRequest {
pub orchestrator_task_id: Option<Uuid>,
pub pr_url: Option<String>,
pub pr_branch: Option<String>,
+ /// Whether questions pause execution indefinitely until answered
+ pub reconcile_mode: Option<bool>,
pub version: Option<i32>,
}
diff --git a/makima/src/db/repository.rs b/makima/src/db/repository.rs
index bfc485b..a2489aa 100644
--- a/makima/src/db/repository.rs
+++ b/makima/src/db/repository.rs
@@ -4930,8 +4930,8 @@ pub async fn create_directive_for_owner(
) -> Result<Directive, sqlx::Error> {
sqlx::query_as::<_, Directive>(
r#"
- INSERT INTO directives (owner_id, title, goal, repository_url, local_path, base_branch)
- VALUES ($1, $2, $3, $4, $5, $6)
+ INSERT INTO directives (owner_id, title, goal, repository_url, local_path, base_branch, reconcile_mode)
+ VALUES ($1, $2, $3, $4, $5, $6, $7)
RETURNING *
"#,
)
@@ -4941,6 +4941,7 @@ pub async fn create_directive_for_owner(
.bind(&req.repository_url)
.bind(&req.local_path)
.bind(&req.base_branch)
+ .bind(req.reconcile_mode.unwrap_or(false))
.fetch_one(pool)
.await
}
@@ -4993,6 +4994,7 @@ pub async fn list_directives_for_owner(
SELECT
d.id, d.owner_id, d.title, d.goal, d.status, d.repository_url,
d.orchestrator_task_id, d.pr_url, d.completion_task_id,
+ d.reconcile_mode,
d.version, d.created_at, d.updated_at,
COALESCE(s.total_steps, 0) as total_steps,
COALESCE(s.completed_steps, 0) as completed_steps,
@@ -5056,12 +5058,14 @@ pub async fn update_directive_for_owner(
let orchestrator_task_id = req.orchestrator_task_id.or(current.orchestrator_task_id);
let pr_url = req.pr_url.as_deref().or(current.pr_url.as_deref());
let pr_branch = req.pr_branch.as_deref().or(current.pr_branch.as_deref());
+ let reconcile_mode = req.reconcile_mode.unwrap_or(current.reconcile_mode);
let result = sqlx::query_as::<_, Directive>(
r#"
UPDATE directives
SET title = $3, goal = $4, status = $5, repository_url = $6, local_path = $7,
base_branch = $8, orchestrator_task_id = $9, pr_url = $10, pr_branch = $11,
+ reconcile_mode = $12,
version = version + 1, updated_at = NOW()
WHERE id = $1 AND owner_id = $2
RETURNING *
@@ -5078,6 +5082,7 @@ pub async fn update_directive_for_owner(
.bind(orchestrator_task_id)
.bind(pr_url)
.bind(pr_branch)
+ .bind(reconcile_mode)
.fetch_optional(pool)
.await
.map_err(RepositoryError::Database)?;
diff --git a/makima/src/orchestration/directive.rs b/makima/src/orchestration/directive.rs
index ea8009d..6d5d63d 100644
--- a/makima/src/orchestration/directive.rs
+++ b/makima/src/orchestration/directive.rs
@@ -248,6 +248,16 @@ impl DirectiveOrchestrator {
.await?;
repository::check_directive_idle(&self.pool, step.directive_id).await?;
}
+ "paused" => {
+ // Task is paused (e.g., waiting for user answer in reconcile mode)
+ // Keep step in running status — task will auto-resume when answered
+ tracing::debug!(
+ step_id = %step.step_id,
+ directive_id = %step.directive_id,
+ task_id = %step.task_id,
+ "Step task paused (waiting for user response) — keeping step running"
+ );
+ }
_ => {
// Still running — do nothing
}
diff --git a/makima/src/server/handlers/mesh_supervisor.rs b/makima/src/server/handlers/mesh_supervisor.rs
index c9cb849..90c6dc7 100644
--- a/makima/src/server/handlers/mesh_supervisor.rs
+++ b/makima/src/server/handlers/mesh_supervisor.rs
@@ -129,6 +129,9 @@ pub struct PendingQuestionSummary {
pub question_id: Uuid,
pub task_id: Uuid,
pub contract_id: Uuid,
+ /// Directive this question relates to (if from a directive task)
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub directive_id: Option<Uuid>,
pub question: String,
pub choices: Vec<String>,
pub context: Option<String>,
@@ -257,11 +260,11 @@ async fn verify_supervisor_auth(
)
})?;
- // Verify task is a supervisor
- if !task.is_supervisor {
+ // Verify task is a supervisor or a directive task
+ if !task.is_supervisor && task.directive_id.is_none() {
return Err((
StatusCode::FORBIDDEN,
- Json(ApiError::new("NOT_SUPERVISOR", "Only supervisor tasks can use these endpoints")),
+ Json(ApiError::new("NOT_SUPERVISOR", "Only supervisor or directive tasks can use these endpoints")),
));
}
@@ -1694,17 +1697,43 @@ pub async fn ask_question(
}
};
- let Some(contract_id) = supervisor.contract_id else {
+ // Determine context: contract or directive
+ let contract_id = supervisor.contract_id;
+ let directive_id = supervisor.directive_id;
+
+ if contract_id.is_none() && directive_id.is_none() {
return (
StatusCode::BAD_REQUEST,
- Json(ApiError::new("NO_CONTRACT", "Supervisor has no associated contract")),
+ Json(ApiError::new("NO_CONTEXT", "Supervisor has no associated contract or directive")),
).into_response();
+ }
+
+ let is_directive_context = directive_id.is_some() && contract_id.is_none();
+
+ // For directive context, check reconcile_mode to determine behavior
+ let directive_reconcile_mode = if let Some(did) = directive_id {
+ if is_directive_context {
+ match repository::get_directive_for_owner(pool, owner_id, did).await {
+ Ok(Some(d)) => d.reconcile_mode,
+ Ok(None) => false,
+ Err(e) => {
+ tracing::warn!(error = %e, "Failed to get directive for reconcile_mode check");
+ false
+ }
+ }
+ } else {
+ false
+ }
+ } else {
+ false
};
- // Add the question
- let question_id = state.add_supervisor_question(
+ // Add the question (use Uuid::nil() for contract_id in directive-only context)
+ let effective_contract_id = contract_id.unwrap_or(Uuid::nil());
+ let question_id = state.add_supervisor_question_with_directive(
supervisor_id,
- contract_id,
+ effective_contract_id,
+ directive_id,
owner_id,
request.question.clone(),
request.choices.clone(),
@@ -1714,15 +1743,18 @@ pub async fn ask_question(
);
// Save state: question asked is a key save point (Task 3.3)
- let pending_question = PendingQuestion {
- id: question_id,
- question: request.question.clone(),
- choices: request.choices.clone(),
- context: request.context.clone(),
- question_type: request.question_type.clone(),
- asked_at: chrono::Utc::now(),
- };
- save_state_on_question_asked(pool, contract_id, pending_question).await;
+ // Only for contract context — directive tasks don't use supervisor_states table
+ if let Some(cid) = contract_id {
+ let pending_question = PendingQuestion {
+ id: question_id,
+ question: request.question.clone(),
+ choices: request.choices.clone(),
+ context: request.context.clone(),
+ question_type: request.question_type.clone(),
+ asked_at: chrono::Utc::now(),
+ };
+ save_state_on_question_asked(pool, cid, pending_question).await;
+ }
// Broadcast question as task output entry for the task's chat
let question_data = serde_json::json!({
@@ -1775,9 +1807,10 @@ pub async fn ask_question(
).into_response();
}
- // If phaseguard is enabled, pause the supervisor task and return
+ // If phaseguard is enabled (or directive reconcile mode), pause the supervisor task and return
// The task will be auto-resumed when a message is sent to it (e.g., when user answers)
- if request.phaseguard {
+ let use_phaseguard = request.phaseguard || (is_directive_context && directive_reconcile_mode);
+ if use_phaseguard {
// Pause the supervisor task
if let Some(daemon_id) = supervisor.daemon_id {
let cmd = DaemonCommand::PauseTask { task_id: supervisor_id };
@@ -1808,7 +1841,13 @@ pub async fn ask_question(
}
// Poll for response with timeout
- let timeout_duration = std::time::Duration::from_secs(request.timeout_seconds.max(1) as u64);
+ // For directive tasks without reconcile mode, use 30s default timeout
+ let timeout_secs = if is_directive_context && !directive_reconcile_mode {
+ 30
+ } else {
+ request.timeout_seconds.max(1) as u64
+ };
+ let timeout_duration = std::time::Duration::from_secs(timeout_secs);
let start = std::time::Instant::now();
let poll_interval = std::time::Duration::from_millis(500);
@@ -1819,7 +1858,10 @@ pub async fn ask_question(
state.cleanup_question_response(question_id);
// Clear pending question from supervisor state (Task 3.3)
- clear_pending_question(pool, contract_id, question_id).await;
+ // Skip for directive context — no supervisor_states for directives
+ if let Some(cid) = contract_id {
+ clear_pending_question(pool, cid, question_id).await;
+ }
return (
StatusCode::OK,
@@ -1837,7 +1879,10 @@ pub async fn ask_question(
state.remove_pending_question(question_id);
// Clear pending question from supervisor state on timeout (Task 3.3)
- clear_pending_question(pool, contract_id, question_id).await;
+ // Skip for directive context — no supervisor_states for directives
+ if let Some(cid) = contract_id {
+ clear_pending_question(pool, cid, question_id).await;
+ }
return (
StatusCode::REQUEST_TIMEOUT,
@@ -1880,6 +1925,7 @@ pub async fn list_pending_questions(
question_id: q.question_id,
task_id: q.task_id,
contract_id: q.contract_id,
+ directive_id: q.directive_id,
question: q.question,
choices: q.choices,
context: q.context,
diff --git a/makima/src/server/state.rs b/makima/src/server/state.rs
index 58e8545..41c336e 100644
--- a/makima/src/server/state.rs
+++ b/makima/src/server/state.rs
@@ -142,8 +142,11 @@ pub struct SupervisorQuestionNotification {
pub question_id: Uuid,
/// Supervisor task that asked the question
pub task_id: Uuid,
- /// Contract this question relates to
+ /// Contract this question relates to (Uuid::nil() for directive context)
pub contract_id: Uuid,
+ /// Directive this question relates to (if from a directive task)
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub directive_id: Option<Uuid>,
/// Owner ID for data isolation
#[serde(skip)]
pub owner_id: Option<Uuid>,
@@ -170,6 +173,8 @@ pub struct PendingSupervisorQuestion {
pub question_id: Uuid,
pub task_id: Uuid,
pub contract_id: Uuid,
+ /// Directive this question relates to (if from a directive task)
+ pub directive_id: Option<Uuid>,
pub owner_id: Uuid,
pub question: String,
pub choices: Vec<String>,
@@ -819,6 +824,25 @@ impl AppState {
multi_select: bool,
question_type: String,
) -> Uuid {
+ self.add_supervisor_question_with_directive(
+ task_id, contract_id, None, owner_id,
+ question, choices, context, multi_select, question_type,
+ )
+ }
+
+ /// Add a pending supervisor question with optional directive context and broadcast it.
+ pub fn add_supervisor_question_with_directive(
+ &self,
+ task_id: Uuid,
+ contract_id: Uuid,
+ directive_id: Option<Uuid>,
+ owner_id: Uuid,
+ question: String,
+ choices: Vec<String>,
+ context: Option<String>,
+ multi_select: bool,
+ question_type: String,
+ ) -> Uuid {
let question_id = Uuid::new_v4();
let now = chrono::Utc::now();
@@ -829,6 +853,7 @@ impl AppState {
question_id,
task_id,
contract_id,
+ directive_id,
owner_id,
question: question.clone(),
choices: choices.clone(),
@@ -844,6 +869,7 @@ impl AppState {
question_id,
task_id,
contract_id,
+ directive_id,
owner_id: Some(owner_id),
question,
choices,
@@ -857,6 +883,7 @@ impl AppState {
question_id = %question_id,
task_id = %task_id,
contract_id = %contract_id,
+ directive_id = ?directive_id,
question_type = %question_type,
"Supervisor question added"
);
@@ -904,6 +931,7 @@ impl AppState {
question_id,
task_id: question.1.task_id,
contract_id: question.1.contract_id,
+ directive_id: question.1.directive_id,
owner_id: Some(question.1.owner_id),
question: question.1.question,
choices: question.1.choices,