summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsoryu <soryu@soryu.co>2026-02-13 14:08:26 +0000
committersoryu <soryu@soryu.co>2026-02-13 14:08:26 +0000
commit13ef4416e5c35c7f03296765943c298d10d3aaf9 (patch)
treec8d01936f0749253db78f6fc2facfc18e619370d
parent0fdcbea27b3b736dd21a460339b39b975ca91b99 (diff)
downloadsoryu-makima/makima-jp--add-question-system-for-directive-orche-47f3c023.tar.gz
soryu-makima/makima-jp--add-question-system-for-directive-orche-47f3c023.zip
-rw-r--r--makima/frontend/src/components/SupervisorQuestionNotification.tsx93
-rw-r--r--makima/frontend/src/components/directives/DirectiveDetail.tsx23
-rw-r--r--makima/frontend/src/hooks/useDirectives.ts9
-rw-r--r--makima/frontend/src/lib/api.ts27
-rw-r--r--makima/frontend/src/routes/directives.tsx3
5 files changed, 120 insertions, 35 deletions
diff --git a/makima/frontend/src/components/SupervisorQuestionNotification.tsx b/makima/frontend/src/components/SupervisorQuestionNotification.tsx
index b1cbacc..2bded08 100644
--- a/makima/frontend/src/components/SupervisorQuestionNotification.tsx
+++ b/makima/frontend/src/components/SupervisorQuestionNotification.tsx
@@ -14,47 +14,74 @@ export function SupervisorQuestionNotification() {
return null;
}
- const handleGoToTask = (questionId: string, taskId: string) => {
+ const handleGoToTask = (questionId: string, taskId: string, directiveId?: string | null) => {
dismissNotification(questionId);
- navigate(`/mesh/${taskId}`);
+ if (directiveId) {
+ navigate(`/directives/${directiveId}`);
+ } else {
+ navigate(`/mesh/${taskId}`);
+ }
};
return (
<div className="fixed bottom-4 right-4 z-50 max-w-md space-y-2">
- {filteredQuestions.map((question) => (
- <div
- key={question.questionId}
- className="bg-[#0d1b2d] border border-amber-500/50 rounded-lg shadow-lg overflow-hidden"
- >
- {/* Header */}
- <div className="flex items-center justify-between px-4 py-3 bg-amber-900/30">
- <div className="flex items-center gap-2">
- <span className="text-amber-400 text-lg">?</span>
- <span className="font-mono text-sm text-amber-300 uppercase">
- Task needs input
- </span>
- </div>
- <button
- onClick={() => handleGoToTask(question.questionId, question.taskId)}
- className="px-3 py-1 font-mono text-xs text-amber-400 border border-amber-500/30 hover:border-amber-400/50 hover:bg-amber-900/20 transition-colors uppercase"
+ {filteredQuestions.map((question) => {
+ const isDirective = !!question.directiveId;
+ return (
+ <div
+ key={question.questionId}
+ className={`border rounded-lg shadow-lg overflow-hidden ${
+ isDirective
+ ? "bg-[#0d1b2d] border-violet-500/50"
+ : "bg-[#0d1b2d] border-amber-500/50"
+ }`}
+ >
+ {/* Header */}
+ <div
+ className={`flex items-center justify-between px-4 py-3 ${
+ isDirective ? "bg-violet-900/30" : "bg-amber-900/30"
+ }`}
>
- View Task
- </button>
- </div>
-
- {/* Question preview */}
- <div className="px-4 py-3">
- {question.context && (
- <div className="text-xs text-[#8b949e] font-mono mb-1 uppercase">
- {question.context}
+ <div className="flex items-center gap-2">
+ <span className={isDirective ? "text-violet-400 text-lg" : "text-amber-400 text-lg"}>
+ {isDirective ? "⚡" : "?"}
+ </span>
+ <span
+ className={`font-mono text-sm uppercase ${
+ isDirective ? "text-violet-300" : "text-amber-300"
+ }`}
+ >
+ {isDirective ? "Directive needs input" : "Task needs input"}
+ </span>
</div>
- )}
- <p className="text-sm text-[#dbe7ff] font-mono line-clamp-2">
- {question.question}
- </p>
+ <button
+ onClick={() =>
+ handleGoToTask(question.questionId, question.taskId, question.directiveId)
+ }
+ className={`px-3 py-1 font-mono text-xs border transition-colors uppercase ${
+ isDirective
+ ? "text-violet-400 border-violet-500/30 hover:border-violet-400/50 hover:bg-violet-900/20"
+ : "text-amber-400 border-amber-500/30 hover:border-amber-400/50 hover:bg-amber-900/20"
+ }`}
+ >
+ {isDirective ? "View Directive" : "View Task"}
+ </button>
+ </div>
+
+ {/* Question preview */}
+ <div className="px-4 py-3">
+ {question.context && (
+ <div className="text-xs text-[#8b949e] font-mono mb-1 uppercase">
+ {question.context}
+ </div>
+ )}
+ <p className="text-sm text-[#dbe7ff] font-mono line-clamp-2">
+ {question.question}
+ </p>
+ </div>
</div>
- </div>
- ))}
+ );
+ })}
</div>
);
}
diff --git a/makima/frontend/src/components/directives/DirectiveDetail.tsx b/makima/frontend/src/components/directives/DirectiveDetail.tsx
index 369cdaa..c8d026f 100644
--- a/makima/frontend/src/components/directives/DirectiveDetail.tsx
+++ b/makima/frontend/src/components/directives/DirectiveDetail.tsx
@@ -37,6 +37,7 @@ interface DirectiveDetailProps {
onDelete: () => void;
onRefresh: () => void;
onCleanupTasks: () => void;
+ onToggleReconcileMode: (enabled: boolean) => void;
}
export function DirectiveDetail({
@@ -51,6 +52,7 @@ export function DirectiveDetail({
onDelete,
onRefresh,
onCleanupTasks,
+ onToggleReconcileMode,
}: DirectiveDetailProps) {
const [editingGoal, setEditingGoal] = useState(false);
const [goalText, setGoalText] = useState(directive.goal);
@@ -296,6 +298,27 @@ export function DirectiveDetail({
Delete
</button>
</div>
+
+ {/* Settings row */}
+ <div className="flex items-center gap-4 mt-2 pt-2 border-t border-[rgba(117,170,252,0.05)]">
+ <label className="flex items-center gap-1.5 cursor-pointer group">
+ <input
+ type="checkbox"
+ checked={directive.reconcileMode}
+ onChange={(e) => onToggleReconcileMode(e.target.checked)}
+ className="w-3 h-3 rounded border-[#2a3a5a] bg-transparent accent-violet-500"
+ />
+ <span className="text-[10px] font-mono text-[#556677] group-hover:text-[#9bc3ff] transition-colors">
+ Reconcile mode
+ </span>
+ <span
+ className="text-[9px] font-mono text-[#3a4a60] cursor-help"
+ title="When enabled, directive tasks pause indefinitely when asking questions and wait for your response. When disabled (default), questions timeout after 30 seconds and the directive continues anyway."
+ >
+ [?]
+ </span>
+ </label>
+ </div>
</div>
{/* Goal */}
diff --git a/makima/frontend/src/hooks/useDirectives.ts b/makima/frontend/src/hooks/useDirectives.ts
index e67733c..0a40338 100644
--- a/makima/frontend/src/hooks/useDirectives.ts
+++ b/makima/frontend/src/hooks/useDirectives.ts
@@ -20,6 +20,7 @@ import {
skipDirectiveStep,
updateDirectiveGoal,
cleanupDirectiveTasks,
+ setDirectiveReconcileMode,
} from "../lib/api";
export function useDirectives() {
@@ -160,11 +161,17 @@ export function useDirective(id: string | undefined) {
await refresh();
}, [id, refresh]);
+ const toggleReconcileMode = useCallback(async (enabled: boolean) => {
+ if (!id) return;
+ await setDirectiveReconcileMode(id, enabled);
+ await refresh();
+ }, [id, refresh]);
+
return {
directive, loading, error, refresh,
update, addStep, removeStep,
start, pause, advance,
completeStep, failStep, skipStep,
- updateGoal, cleanupTasks,
+ updateGoal, cleanupTasks, toggleReconcileMode,
};
}
diff --git a/makima/frontend/src/lib/api.ts b/makima/frontend/src/lib/api.ts
index 9d9cb1c..52bbf73 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 ID if this question is 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 reconcile mode is enabled (directive tasks pause indefinitely when asking questions) */
+ 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 reconcile mode is enabled */
+ reconcileMode: boolean;
version: number;
createdAt: string;
updatedAt: string;
@@ -3246,6 +3252,27 @@ export async function cleanupDirectiveTasks(id: string): Promise<{ deleted: numb
return res.json();
}
+/**
+ * Set reconcile mode for a directive.
+ * When enabled, directive tasks that ask questions pause indefinitely until answered.
+ * When disabled, questions have a 30s timeout and the directive continues regardless.
+ */
+export async function setDirectiveReconcileMode(
+ id: string,
+ reconcileMode: boolean
+): Promise<Directive> {
+ const res = await authFetch(
+ `${API_BASE}/api/v1/directives/${id}/reconcile-mode`,
+ {
+ method: "PUT",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ reconcile_mode: reconcileMode }),
+ }
+ );
+ if (!res.ok) throw new Error(`Failed to set reconcile mode: ${res.statusText}`);
+ return res.json();
+}
+
// =============================================================================
// Directive Memory Types & API
// =============================================================================
diff --git a/makima/frontend/src/routes/directives.tsx b/makima/frontend/src/routes/directives.tsx
index ca4437c..5f9b3a8 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, start, pause, advance, completeStep, failStep, skipStep, updateGoal, cleanupTasks, toggleReconcileMode } = useDirective(selectedId);
const [showCreate, setShowCreate] = useState(false);
const [newTitle, setNewTitle] = useState("");
@@ -210,6 +210,7 @@ export default function DirectivesPage() {
onDelete={handleDelete}
onRefresh={refreshDetail}
onCleanupTasks={cleanupTasks}
+ onToggleReconcileMode={toggleReconcileMode}
/>
) : (
<div className="flex-1 flex items-center justify-center h-full">