summaryrefslogtreecommitdiff
path: root/makima/frontend/src/components/mesh/TaskOutput.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'makima/frontend/src/components/mesh/TaskOutput.tsx')
-rw-r--r--makima/frontend/src/components/mesh/TaskOutput.tsx96
1 files changed, 96 insertions, 0 deletions
diff --git a/makima/frontend/src/components/mesh/TaskOutput.tsx b/makima/frontend/src/components/mesh/TaskOutput.tsx
index 10de225..cb0eba3 100644
--- a/makima/frontend/src/components/mesh/TaskOutput.tsx
+++ b/makima/frontend/src/components/mesh/TaskOutput.tsx
@@ -275,7 +275,103 @@ function OutputEntryRenderer({ entry }: { entry: TaskOutputEvent }) {
</div>
);
+ case "auth_required":
+ return <AuthRequiredEntry entry={entry} />;
+
default:
return null;
}
}
+
+function AuthRequiredEntry({ entry }: { entry: TaskOutputEvent }) {
+ const [authCode, setAuthCode] = useState("");
+ const [submitting, setSubmitting] = useState(false);
+ const [submitted, setSubmitted] = useState(false);
+ const [error, setError] = useState<string | null>(null);
+
+ const loginUrl = entry.toolInput?.loginUrl as string | undefined;
+ const hostname = entry.toolInput?.hostname as string | undefined;
+ // Get taskId from entry or fallback to toolInput (for robustness)
+ const taskId = entry.taskId || (entry.toolInput?.taskId as string | undefined);
+
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault();
+ if (!authCode.trim() || !taskId) return;
+
+ setSubmitting(true);
+ setError(null);
+
+ try {
+ // Send the auth code to the task via the message endpoint
+ await sendTaskMessage(taskId, `AUTH_CODE:${authCode.trim()}`);
+ setSubmitted(true);
+ } catch (err) {
+ setError(err instanceof Error ? err.message : "Failed to submit code");
+ } finally {
+ setSubmitting(false);
+ }
+ };
+
+ if (submitted) {
+ return (
+ <div className="bg-green-900/30 border border-green-500/50 rounded p-3 my-2">
+ <div className="flex items-center gap-2 text-green-400 font-semibold">
+ <span>✓</span>
+ <span>Authentication code submitted</span>
+ </div>
+ <p className="text-green-200/80 text-sm mt-1">
+ Waiting for authentication to complete...
+ </p>
+ </div>
+ );
+ }
+
+ return (
+ <div className="bg-amber-900/30 border border-amber-500/50 rounded p-3 my-2">
+ <div className="flex items-center gap-2 text-amber-400 font-semibold mb-2">
+ <span>🔐</span>
+ <span>Authentication Required{hostname ? ` (${hostname})` : ""}</span>
+ </div>
+ <p className="text-amber-200/80 text-sm mb-3">
+ The daemon's OAuth token has expired. Click the button to login, then paste the code below:
+ </p>
+
+ <div className="flex flex-col gap-3">
+ {loginUrl ? (
+ <a
+ href={loginUrl}
+ target="_blank"
+ rel="noopener noreferrer"
+ className="inline-block bg-amber-500 hover:bg-amber-400 text-black font-medium px-4 py-2 rounded transition-colors text-center"
+ >
+ 1. Login to Claude
+ </a>
+ ) : (
+ <p className="text-red-400 text-sm">Login URL not available</p>
+ )}
+
+ <form onSubmit={handleSubmit} className="flex gap-2">
+ <input
+ type="text"
+ value={authCode}
+ onChange={(e) => setAuthCode(e.target.value)}
+ placeholder="2. Paste authentication code here"
+ className="flex-1 bg-[#0a1525] border border-amber-500/30 rounded px-3 py-2 text-amber-100 placeholder-amber-500/50 focus:outline-none focus:border-amber-400"
+ disabled={submitting}
+ />
+ <button
+ type="submit"
+ disabled={submitting || !authCode.trim()}
+ className="bg-amber-500 hover:bg-amber-400 disabled:bg-amber-700 disabled:cursor-not-allowed text-black font-medium px-4 py-2 rounded transition-colors"
+ >
+ {submitting ? "..." : "Submit"}
+ </button>
+ </form>
+
+ {error && (
+ <p className="text-red-400 text-sm">{error}</p>
+ )}
+ </div>
+ </div>
+ );
+}