summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--frontend/src/components/ContractCreateModal.tsx185
-rw-r--r--frontend/src/components/ContractDetail.tsx139
-rw-r--r--frontend/src/components/ContractList.tsx61
-rw-r--r--frontend/src/styles/pc98.css644
-rw-r--r--frontend/src/types.ts95
-rw-r--r--frontend/tsconfig.tsbuildinfo2
-rw-r--r--makima/src/bin/makima.rs13
-rw-r--r--makima/src/daemon/api/mod.rs2
-rw-r--r--makima/src/daemon/api/red_team.rs39
-rw-r--r--makima/src/daemon/cli/mod.rs57
-rw-r--r--makima/src/daemon/cli/red_team.rs26
11 files changed, 1238 insertions, 25 deletions
diff --git a/frontend/src/components/ContractCreateModal.tsx b/frontend/src/components/ContractCreateModal.tsx
new file mode 100644
index 0000000..e1d9732
--- /dev/null
+++ b/frontend/src/components/ContractCreateModal.tsx
@@ -0,0 +1,185 @@
+import React, { useState } from 'react'
+
+interface ContractCreateModalProps {
+ isOpen: boolean
+ onClose: () => void
+ onCreated: () => void
+}
+
+interface CreateContractForm {
+ name: string
+ description: string
+ contractType: string
+ redTeamEnabled: boolean
+ redTeamPrompt: string
+}
+
+export function ContractCreateModal({ isOpen, onClose, onCreated }: ContractCreateModalProps) {
+ const [form, setForm] = useState<CreateContractForm>({
+ name: '',
+ description: '',
+ contractType: 'simple',
+ redTeamEnabled: false,
+ redTeamPrompt: '',
+ })
+ const [loading, setLoading] = useState(false)
+ const [error, setError] = useState<string | null>(null)
+
+ if (!isOpen) return null
+
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault()
+ setLoading(true)
+ setError(null)
+
+ try {
+ const response = await fetch('/api/v1/contracts', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ name: form.name,
+ description: form.description || undefined,
+ contract_type: form.contractType,
+ red_team_enabled: form.redTeamEnabled,
+ red_team_prompt: form.redTeamEnabled && form.redTeamPrompt ? form.redTeamPrompt : undefined,
+ }),
+ })
+
+ if (!response.ok) {
+ const errorData = await response.json().catch(() => ({}))
+ throw new Error(errorData.message || `Failed to create contract: ${response.statusText}`)
+ }
+
+ // Reset form and close modal
+ setForm({
+ name: '',
+ description: '',
+ contractType: 'simple',
+ redTeamEnabled: false,
+ redTeamPrompt: '',
+ })
+ onCreated()
+ onClose()
+ } catch (err) {
+ setError(err instanceof Error ? err.message : 'Unknown error')
+ } finally {
+ setLoading(false)
+ }
+ }
+
+ return (
+ <div className="modal-overlay" onClick={onClose}>
+ <div className="settings-modal contract-create-modal" onClick={(e) => e.stopPropagation()}>
+ <div className="modal-header">
+ <h2 className="modal-title">Create Contract</h2>
+ <button className="modal-close-btn" onClick={onClose}>
+ ×
+ </button>
+ </div>
+
+ <form onSubmit={handleSubmit}>
+ <div className="modal-content">
+ {error && (
+ <div className="form-error">
+ {error}
+ </div>
+ )}
+
+ <div className="settings-section">
+ <h3>Contract Details</h3>
+
+ <div className="setting-item">
+ <label>
+ Name
+ <input
+ type="text"
+ value={form.name}
+ onChange={(e) => setForm({ ...form, name: e.target.value })}
+ placeholder="Enter contract name"
+ required
+ className="form-input"
+ />
+ </label>
+ </div>
+
+ <div className="setting-item">
+ <label>
+ Description
+ <textarea
+ value={form.description}
+ onChange={(e) => setForm({ ...form, description: e.target.value })}
+ placeholder="Enter contract description (optional)"
+ className="form-textarea"
+ rows={3}
+ />
+ </label>
+ </div>
+
+ <div className="setting-item">
+ <label>
+ Contract Type
+ <select
+ value={form.contractType}
+ onChange={(e) => setForm({ ...form, contractType: e.target.value })}
+ className="form-select"
+ >
+ <option value="simple">Simple</option>
+ <option value="specification">Specification</option>
+ <option value="execute">Execute</option>
+ </select>
+ </label>
+ </div>
+ </div>
+
+ <div className="settings-section">
+ <h3>Red Team Monitoring</h3>
+
+ <div className="setting-item">
+ <label className="checkbox-label">
+ <input
+ type="checkbox"
+ checked={form.redTeamEnabled}
+ onChange={(e) => setForm({ ...form, redTeamEnabled: e.target.checked })}
+ />
+ <span>Enable Red Team monitoring</span>
+ </label>
+ <div className="setting-description">
+ Spawns a parallel task that monitors work output for quality and compliance
+ </div>
+ </div>
+
+ {form.redTeamEnabled && (
+ <div className="setting-item red-team-prompt-container">
+ <label>
+ Custom review criteria (optional)
+ <textarea
+ value={form.redTeamPrompt}
+ onChange={(e) => setForm({ ...form, redTeamPrompt: e.target.value })}
+ placeholder="Enter custom criteria for the red team to evaluate against... (e.g., 'Ensure all functions have proper error handling', 'Verify security best practices are followed')"
+ className="form-textarea"
+ rows={4}
+ />
+ </label>
+ <div className="setting-description">
+ Provide specific criteria for the red team to focus on during reviews
+ </div>
+ </div>
+ )}
+ </div>
+ </div>
+
+ <div className="modal-footer">
+ <button type="button" className="modal-btn secondary" onClick={onClose} disabled={loading}>
+ Cancel
+ </button>
+ <button type="submit" className="modal-btn primary" disabled={loading || !form.name.trim()}>
+ {loading ? 'Creating...' : 'Create Contract'}
+ </button>
+ </div>
+ </form>
+ </div>
+ </div>
+ )
+}
diff --git a/frontend/src/components/ContractDetail.tsx b/frontend/src/components/ContractDetail.tsx
index 72527ce..a9dd550 100644
--- a/frontend/src/components/ContractDetail.tsx
+++ b/frontend/src/components/ContractDetail.tsx
@@ -12,6 +12,8 @@ interface TaskSummary {
id: string
name: string
status: string
+ is_supervisor?: boolean
+ is_red_team?: boolean
}
interface ContractRepository {
@@ -30,6 +32,24 @@ interface Contract {
status: string
version: number
created_at: string
+ red_team_enabled?: boolean
+ red_team_prompt?: string
+}
+
+interface RedTeamNotification {
+ id: string
+ contract_id: string
+ red_team_task_id: string
+ related_task_id?: string
+ message: string
+ severity: 'info' | 'warning' | 'critical'
+ file_path?: string
+ context?: string
+ delivered: boolean
+ delivered_at?: string
+ acknowledged: boolean
+ acknowledged_at?: string
+ created_at: string
}
interface ContractWithRelations {
@@ -39,7 +59,7 @@ interface ContractWithRelations {
tasks: TaskSummary[]
}
-type Tab = 'overview' | 'files' | 'tasks' | 'repositories'
+type Tab = 'overview' | 'files' | 'tasks' | 'repositories' | 'red-team'
export function ContractDetail() {
const { id } = useParams<{ id: string }>()
@@ -47,6 +67,8 @@ export function ContractDetail() {
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const [activeTab, setActiveTab] = useState<Tab>('overview')
+ const [notifications, setNotifications] = useState<RedTeamNotification[]>([])
+ const [notificationsLoading, setNotificationsLoading] = useState(false)
useEffect(() => {
async function fetchContract() {
@@ -70,6 +92,28 @@ export function ContractDetail() {
fetchContract()
}, [id])
+ // Fetch red team notifications when viewing red-team tab
+ useEffect(() => {
+ async function fetchNotifications() {
+ if (!id || activeTab !== 'red-team' || !data?.contract.red_team_enabled) return
+
+ try {
+ setNotificationsLoading(true)
+ const response = await fetch(`/api/v1/contracts/${id}/red-team/notifications`)
+ if (response.ok) {
+ const notificationData = await response.json()
+ setNotifications(notificationData.notifications || [])
+ }
+ } catch (err) {
+ console.error('Failed to fetch red team notifications:', err)
+ } finally {
+ setNotificationsLoading(false)
+ }
+ }
+
+ fetchNotifications()
+ }, [id, activeTab, data?.contract.red_team_enabled])
+
if (loading) {
return (
<div className="contract-detail-container">
@@ -108,7 +152,14 @@ export function ContractDetail() {
<Link to="/contracts" className="back-link">
Back to Contracts
</Link>
- <h1 className="contract-title">{contract.name}</h1>
+ <h1 className="contract-title">
+ {contract.name}
+ {contract.red_team_enabled && (
+ <span className="red-team-badge" title="Red Team monitoring enabled">
+ đŸ›Ąī¸ Red Team
+ </span>
+ )}
+ </h1>
{contract.description && (
<p className="contract-description">{contract.description}</p>
)}
@@ -144,6 +195,14 @@ export function ContractDetail() {
>
Repositories ({repositories.length})
</button>
+ {contract.red_team_enabled && (
+ <button
+ className={`tab-button red-team-tab ${activeTab === 'red-team' ? 'active' : ''}`}
+ onClick={() => setActiveTab('red-team')}
+ >
+ đŸ›Ąī¸ Red Team
+ </button>
+ )}
</div>
<div className="contract-tab-content">
@@ -194,8 +253,12 @@ export function ContractDetail() {
) : (
<ul className="task-list">
{tasks.map((task) => (
- <li key={task.id} className="task-item">
- <h3>{task.name}</h3>
+ <li key={task.id} className={`task-item ${task.is_red_team ? 'red-team-task' : ''}`}>
+ <h3>
+ {task.is_red_team && <span className="task-badge red-team">đŸ›Ąī¸</span>}
+ {task.is_supervisor && <span className="task-badge supervisor">👔</span>}
+ {task.name}
+ </h3>
<span className={`task-status status-${task.status}`}>
{task.status}
</span>
@@ -226,6 +289,74 @@ export function ContractDetail() {
)}
</div>
)}
+
+ {activeTab === 'red-team' && contract.red_team_enabled && (
+ <div className="tab-panel red-team-panel">
+ <h2>đŸ›Ąī¸ Red Team Monitoring</h2>
+
+ {contract.red_team_prompt && (
+ <div className="red-team-prompt">
+ <h3>Review Criteria</h3>
+ <p>{contract.red_team_prompt}</p>
+ </div>
+ )}
+
+ <div className="red-team-status">
+ <h3>Red Team Task</h3>
+ {(() => {
+ const redTeamTask = tasks.find(t => t.is_red_team)
+ if (redTeamTask) {
+ return (
+ <div className="red-team-task-info">
+ <span className="task-name">{redTeamTask.name}</span>
+ <span className={`task-status status-${redTeamTask.status}`}>
+ {redTeamTask.status}
+ </span>
+ </div>
+ )
+ }
+ return <p className="no-task">Red team task not yet spawned</p>
+ })()}
+ </div>
+
+ <div className="red-team-notifications">
+ <h3>Alerts ({notifications.length})</h3>
+ {notificationsLoading ? (
+ <p>Loading notifications...</p>
+ ) : notifications.length === 0 ? (
+ <p className="no-alerts">No alerts from red team</p>
+ ) : (
+ <ul className="notification-list">
+ {notifications.map((notification) => (
+ <li
+ key={notification.id}
+ className={`notification-item severity-${notification.severity}`}
+ >
+ <div className="notification-header">
+ <span className={`severity-badge ${notification.severity}`}>
+ {notification.severity === 'critical' && '🚨'}
+ {notification.severity === 'warning' && 'âš ī¸'}
+ {notification.severity === 'info' && 'â„šī¸'}
+ {notification.severity.toUpperCase()}
+ </span>
+ <span className="notification-time">
+ {new Date(notification.created_at).toLocaleString()}
+ </span>
+ </div>
+ <p className="notification-message">{notification.message}</p>
+ {notification.file_path && (
+ <span className="notification-file">File: {notification.file_path}</span>
+ )}
+ {notification.context && (
+ <pre className="notification-context">{notification.context}</pre>
+ )}
+ </li>
+ ))}
+ </ul>
+ )}
+ </div>
+ </div>
+ )}
</div>
</div>
)
diff --git a/frontend/src/components/ContractList.tsx b/frontend/src/components/ContractList.tsx
index 77012db..253b44f 100644
--- a/frontend/src/components/ContractList.tsx
+++ b/frontend/src/components/ContractList.tsx
@@ -1,5 +1,6 @@
-import React, { useEffect, useState } from 'react'
+import React, { useEffect, useState, useCallback } from 'react'
import { Link } from 'react-router-dom'
+import { ContractCreateModal } from './ContractCreateModal'
interface ContractSummary {
id: string
@@ -12,32 +13,35 @@ interface ContractSummary {
task_count: number
repository_count: number
created_at: string
+ // Red team fields
+ red_team_enabled?: boolean
}
export function ContractList() {
const [contracts, setContracts] = useState<ContractSummary[]>([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
+ const [showCreateModal, setShowCreateModal] = useState(false)
- useEffect(() => {
- async function fetchContracts() {
- try {
- setLoading(true)
- const response = await fetch('/api/v1/contracts')
- if (!response.ok) {
- throw new Error(`Failed to fetch contracts: ${response.statusText}`)
- }
- const data = await response.json()
- setContracts(data.contracts || [])
- } catch (err) {
- setError(err instanceof Error ? err.message : 'Unknown error')
- } finally {
- setLoading(false)
+ const fetchContracts = useCallback(async () => {
+ try {
+ setLoading(true)
+ const response = await fetch('/api/v1/contracts')
+ if (!response.ok) {
+ throw new Error(`Failed to fetch contracts: ${response.statusText}`)
}
+ const data = await response.json()
+ setContracts(data.contracts || [])
+ } catch (err) {
+ setError(err instanceof Error ? err.message : 'Unknown error')
+ } finally {
+ setLoading(false)
}
+ }, [])
+ useEffect(() => {
fetchContracts()
- }, [])
+ }, [fetchContracts])
if (loading) {
return (
@@ -57,7 +61,15 @@ export function ContractList() {
return (
<div className="contract-list-container">
- <h1>Contracts</h1>
+ <div className="contract-list-header">
+ <h1>Contracts</h1>
+ <button
+ className="create-contract-btn"
+ onClick={() => setShowCreateModal(true)}
+ >
+ + New Contract
+ </button>
+ </div>
{contracts.length === 0 ? (
<p>No contracts found</p>
) : (
@@ -65,7 +77,14 @@ export function ContractList() {
{contracts.map((contract) => (
<li key={contract.id} className="contract-item">
<Link to={`/contracts/${contract.id}`}>
- <h2>{contract.name}</h2>
+ <h2>
+ {contract.name}
+ {contract.red_team_enabled && (
+ <span className="red-team-badge" title="Red Team monitoring enabled">
+ 🔍
+ </span>
+ )}
+ </h2>
{contract.description && <p>{contract.description}</p>}
<div className="contract-meta">
<span>Phase: {contract.phase}</span>
@@ -78,6 +97,12 @@ export function ContractList() {
))}
</ul>
)}
+
+ <ContractCreateModal
+ isOpen={showCreateModal}
+ onClose={() => setShowCreateModal(false)}
+ onCreated={fetchContracts}
+ />
</div>
)
}
diff --git a/frontend/src/styles/pc98.css b/frontend/src/styles/pc98.css
index 4dcf15e..a420115 100644
--- a/frontend/src/styles/pc98.css
+++ b/frontend/src/styles/pc98.css
@@ -4621,3 +4621,647 @@ button:focus-visible {
display: none;
}
}
+
+/* =============================================================================
+ Contract List & Detail Styles
+ ============================================================================= */
+
+.contract-list-container,
+.contract-detail-container {
+ padding: 20px;
+ max-width: 1200px;
+ margin: 0 auto;
+ font-family: 'MS Gothic', monospace;
+ color: #ffffff;
+}
+
+.contract-list-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 20px;
+}
+
+.contract-list-header h1 {
+ font-size: 24px;
+ color: #66ccff;
+ margin: 0;
+ text-transform: uppercase;
+ letter-spacing: 1px;
+}
+
+.create-contract-btn {
+ appearance: none;
+ background: linear-gradient(180deg, #004466, #002233);
+ border: 2px solid #66ccff;
+ color: #ffffff;
+ font-family: 'MS Gothic', monospace;
+ font-size: 13px;
+ font-weight: bold;
+ padding: 8px 16px;
+ cursor: pointer;
+ border-radius: 4px;
+ transition: all 0.2s ease;
+}
+
+.create-contract-btn:hover {
+ background: linear-gradient(180deg, #005577, #003344);
+ box-shadow: 0 0 10px rgba(102, 204, 255, 0.4);
+}
+
+.contract-list {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+}
+
+.contract-item {
+ margin-bottom: 15px;
+ border: 1px solid rgba(102, 204, 255, 0.3);
+ background: rgba(0, 0, 0, 0.5);
+ transition: all 0.3s ease;
+}
+
+.contract-item:hover {
+ border-color: #66ccff;
+ background: rgba(102, 204, 255, 0.05);
+}
+
+.contract-item a {
+ display: block;
+ padding: 15px;
+ text-decoration: none;
+ color: inherit;
+}
+
+.contract-item h2 {
+ font-size: 16px;
+ color: #ffffff;
+ margin: 0 0 8px 0;
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+
+.contract-item p {
+ font-size: 13px;
+ color: rgba(255, 255, 255, 0.7);
+ margin: 0 0 10px 0;
+}
+
+.contract-meta {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 15px;
+ font-size: 12px;
+ color: rgba(102, 204, 255, 0.8);
+}
+
+/* =============================================================================
+ Red Team Styling
+ ============================================================================= */
+
+/* Red Team Badge - Contract List and Detail */
+.red-team-badge {
+ display: inline-flex;
+ align-items: center;
+ gap: 4px;
+ margin-left: 8px;
+ padding: 2px 8px;
+ font-size: 12px;
+ font-weight: normal;
+ background: linear-gradient(180deg, #4a0000, #2d0000);
+ border: 1px solid #ff4444;
+ border-radius: 4px;
+ color: #ff6666;
+ vertical-align: middle;
+ box-shadow: 0 0 6px rgba(255, 68, 68, 0.3);
+}
+
+/* Contract Detail Header */
+.contract-detail-header {
+ margin-bottom: 20px;
+}
+
+.back-link {
+ display: inline-block;
+ font-size: 12px;
+ color: #66ccff;
+ text-decoration: none;
+ margin-bottom: 15px;
+ transition: opacity 0.3s ease;
+}
+
+.back-link:hover {
+ opacity: 0.8;
+}
+
+.contract-title {
+ display: flex;
+ align-items: center;
+ gap: 15px;
+ flex-wrap: wrap;
+ font-size: 24px;
+ color: #66ccff;
+ margin: 0 0 10px 0;
+}
+
+.contract-description {
+ font-size: 14px;
+ color: rgba(255, 255, 255, 0.7);
+ margin: 10px 0;
+}
+
+/* Contract Tabs */
+.contract-tabs {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 5px;
+ margin-bottom: 20px;
+ border-bottom: 1px solid rgba(102, 204, 255, 0.3);
+ padding-bottom: 10px;
+}
+
+.tab-button {
+ font-family: 'MS Gothic', monospace;
+ font-size: 12px;
+ padding: 8px 16px;
+ border: 1px solid rgba(102, 204, 255, 0.3);
+ background: rgba(0, 0, 0, 0.3);
+ color: rgba(255, 255, 255, 0.7);
+ cursor: pointer;
+ transition: all 0.3s ease;
+}
+
+.tab-button:hover {
+ border-color: #66ccff;
+ color: #ffffff;
+}
+
+.tab-button.active {
+ background: rgba(102, 204, 255, 0.15);
+ border-color: #66ccff;
+ color: #66ccff;
+}
+
+/* Red Team Tab Button */
+.tab-button.red-team-tab {
+ background: linear-gradient(180deg, #3d0000, #1a0000);
+ border-color: #ff4444;
+ color: #ff6666;
+}
+
+.tab-button.red-team-tab:hover {
+ background: linear-gradient(180deg, #4a0000, #2d0000);
+ box-shadow: 0 0 8px rgba(255, 68, 68, 0.4);
+}
+
+.tab-button.red-team-tab.active {
+ background: linear-gradient(180deg, #5c0000, #3d0000);
+ border-color: #ff6666;
+ color: #ffffff;
+ box-shadow: 0 0 12px rgba(255, 68, 68, 0.5);
+}
+
+/* Tab Content */
+.tab-panel {
+ padding: 20px;
+ background: rgba(0, 0, 0, 0.3);
+ border: 1px solid rgba(102, 204, 255, 0.2);
+}
+
+.tab-panel h2 {
+ font-size: 18px;
+ color: #66ccff;
+ margin: 0 0 15px 0;
+ text-transform: uppercase;
+ letter-spacing: 1px;
+}
+
+.overview-list {
+ display: grid;
+ grid-template-columns: 120px 1fr;
+ gap: 10px;
+}
+
+.overview-list dt {
+ color: rgba(255, 255, 255, 0.6);
+ font-size: 13px;
+}
+
+.overview-list dd {
+ color: #ffffff;
+ margin: 0;
+ font-size: 13px;
+}
+
+/* File/Task/Repository Lists */
+.file-list,
+.task-list,
+.repository-list {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+}
+
+.file-item,
+.task-item,
+.repository-item {
+ padding: 12px;
+ margin-bottom: 10px;
+ border: 1px solid rgba(102, 204, 255, 0.2);
+ background: rgba(0, 0, 0, 0.3);
+ transition: all 0.3s ease;
+}
+
+.file-item:hover,
+.task-item:hover,
+.repository-item:hover {
+ border-color: rgba(102, 204, 255, 0.4);
+}
+
+.file-item a {
+ text-decoration: none;
+ color: inherit;
+}
+
+.file-item h3,
+.task-item h3,
+.repository-item h3 {
+ font-size: 14px;
+ color: #ffffff;
+ margin: 0 0 5px 0;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.file-phase {
+ font-size: 11px;
+ color: rgba(102, 204, 255, 0.7);
+}
+
+.task-status {
+ font-size: 11px;
+ padding: 2px 8px;
+ border-radius: 2px;
+}
+
+.task-status.status-pending {
+ background: rgba(255, 255, 0, 0.15);
+ color: #ffff00;
+ border: 1px solid rgba(255, 255, 0, 0.3);
+}
+
+.task-status.status-active,
+.task-status.status-running {
+ background: rgba(0, 255, 0, 0.15);
+ color: #00ff00;
+ border: 1px solid rgba(0, 255, 0, 0.3);
+}
+
+.task-status.status-completed {
+ background: rgba(102, 204, 255, 0.15);
+ color: #66ccff;
+ border: 1px solid rgba(102, 204, 255, 0.3);
+}
+
+.task-status.status-failed {
+ background: rgba(255, 100, 100, 0.15);
+ color: #ff6464;
+ border: 1px solid rgba(255, 100, 100, 0.3);
+}
+
+/* Task Badges */
+.task-badge {
+ margin-right: 6px;
+ font-size: 14px;
+}
+
+.task-badge.red-team {
+ color: #ff6666;
+}
+
+.task-badge.supervisor {
+ color: #66ccff;
+}
+
+.task-item.red-team-task {
+ border-left: 3px solid #ff4444;
+}
+
+.primary-badge {
+ font-size: 10px;
+ padding: 2px 6px;
+ background: rgba(102, 204, 255, 0.15);
+ color: #66ccff;
+ border: 1px solid rgba(102, 204, 255, 0.3);
+ margin-left: 8px;
+}
+
+.repo-type {
+ font-size: 11px;
+ color: rgba(255, 255, 255, 0.6);
+}
+
+/* Red Team Panel */
+.red-team-panel {
+ background: linear-gradient(180deg, rgba(60, 0, 0, 0.3), rgba(30, 0, 0, 0.3));
+ border-left: 3px solid #ff4444;
+}
+
+.red-team-panel h2 {
+ color: #ff6666;
+ border-bottom: 2px solid #ff4444;
+ padding-bottom: 8px;
+}
+
+.red-team-panel h3 {
+ color: #ffaaaa;
+ margin-top: 20px;
+ margin-bottom: 12px;
+}
+
+/* Red Team Prompt Section */
+.red-team-prompt {
+ background: rgba(0, 0, 0, 0.4);
+ border: 1px solid #ff4444;
+ border-radius: 6px;
+ padding: 12px 16px;
+ margin-bottom: 16px;
+}
+
+.red-team-prompt h3 {
+ margin-top: 0;
+ font-size: 14px;
+}
+
+.red-team-prompt p {
+ margin: 0;
+ color: #cccccc;
+ font-style: italic;
+}
+
+/* Red Team Status */
+.red-team-status {
+ margin-bottom: 20px;
+}
+
+.red-team-task-info {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 12px;
+ background: rgba(0, 0, 0, 0.3);
+ border: 1px solid #444;
+ border-radius: 4px;
+}
+
+.red-team-task-info .task-name {
+ font-weight: bold;
+ color: #ffffff;
+}
+
+.no-task, .no-alerts {
+ color: #888888;
+ font-style: italic;
+}
+
+/* Red Team Notifications */
+.red-team-notifications {
+ margin-top: 20px;
+}
+
+.notification-list {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+}
+
+.notification-item {
+ padding: 12px 16px;
+ margin-bottom: 12px;
+ background: rgba(0, 0, 0, 0.4);
+ border-radius: 6px;
+ border-left: 4px solid;
+}
+
+.notification-item.severity-info {
+ border-left-color: #66ccff;
+ background: linear-gradient(90deg, rgba(0, 80, 150, 0.2), transparent);
+}
+
+.notification-item.severity-warning {
+ border-left-color: #ffcc00;
+ background: linear-gradient(90deg, rgba(150, 120, 0, 0.2), transparent);
+}
+
+.notification-item.severity-critical {
+ border-left-color: #ff4444;
+ background: linear-gradient(90deg, rgba(150, 0, 0, 0.2), transparent);
+}
+
+.notification-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 8px;
+}
+
+.severity-badge {
+ padding: 2px 8px;
+ border-radius: 4px;
+ font-size: 11px;
+ font-weight: bold;
+ letter-spacing: 0.5px;
+}
+
+.severity-badge.info {
+ background: #003366;
+ color: #66ccff;
+ border: 1px solid #66ccff;
+}
+
+.severity-badge.warning {
+ background: #333300;
+ color: #ffcc00;
+ border: 1px solid #ffcc00;
+}
+
+.severity-badge.critical {
+ background: #660000;
+ color: #ff4444;
+ border: 1px solid #ff4444;
+ animation: pulse-critical 2s infinite;
+}
+
+@keyframes pulse-critical {
+ 0%, 100% {
+ box-shadow: 0 0 4px rgba(255, 68, 68, 0.5);
+ }
+ 50% {
+ box-shadow: 0 0 12px rgba(255, 68, 68, 0.8);
+ }
+}
+
+.notification-time {
+ font-size: 11px;
+ color: #888888;
+}
+
+.notification-message {
+ margin: 0 0 8px 0;
+ color: #ffffff;
+ line-height: 1.5;
+}
+
+.notification-file {
+ display: block;
+ font-size: 12px;
+ color: #66ccff;
+ margin-bottom: 8px;
+}
+
+.notification-context {
+ margin: 8px 0 0 0;
+ padding: 8px 12px;
+ background: rgba(0, 0, 0, 0.5);
+ border-radius: 4px;
+ font-size: 12px;
+ font-family: 'MS Gothic', monospace;
+ color: #aaaaaa;
+ white-space: pre-wrap;
+ overflow-x: auto;
+}
+
+/* =============================================================================
+ Contract Create Modal Styles
+ ============================================================================= */
+
+.contract-create-modal {
+ max-width: 600px;
+}
+
+.contract-create-modal .settings-section:last-child {
+ border-top: 1px solid #444;
+ padding-top: 16px;
+ margin-top: 8px;
+}
+
+.form-input,
+.form-textarea,
+.form-select {
+ width: 100%;
+ padding: 8px 12px;
+ margin-top: 6px;
+ background: rgba(0, 0, 0, 0.4);
+ border: 1px solid #444;
+ border-radius: 4px;
+ color: #ffffff;
+ font-family: 'MS Gothic', monospace;
+ font-size: 13px;
+}
+
+.form-input:focus,
+.form-textarea:focus,
+.form-select:focus {
+ outline: none;
+ border-color: #66ccff;
+ box-shadow: 0 0 6px rgba(102, 204, 255, 0.3);
+}
+
+.form-textarea {
+ resize: vertical;
+ min-height: 80px;
+}
+
+.form-select {
+ cursor: pointer;
+}
+
+.form-select option {
+ background: #000000;
+ color: #ffffff;
+}
+
+.form-error {
+ padding: 10px 14px;
+ margin-bottom: 16px;
+ background: rgba(255, 0, 0, 0.15);
+ border: 1px solid #ff4444;
+ border-radius: 4px;
+ color: #ff6666;
+ font-size: 13px;
+}
+
+.checkbox-label {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ cursor: pointer;
+}
+
+.checkbox-label span {
+ color: #ffffff;
+}
+
+.setting-description {
+ margin-top: 4px;
+ font-size: 11px;
+ color: #888888;
+ padding-left: 24px;
+}
+
+.red-team-prompt-container {
+ margin-top: 12px;
+ padding-left: 20px;
+}
+
+.red-team-prompt-container textarea {
+ background: rgba(60, 0, 0, 0.2);
+ border-color: #ff4444;
+}
+
+.red-team-prompt-container textarea:focus {
+ border-color: #ff6666;
+ box-shadow: 0 0 8px rgba(255, 68, 68, 0.3);
+}
+
+.modal-btn.primary {
+ background: linear-gradient(180deg, #004466, #002233);
+ border-color: #66ccff;
+ color: #ffffff;
+}
+
+.modal-btn.primary:hover {
+ background: linear-gradient(180deg, #005577, #003344);
+ box-shadow: 0 0 8px rgba(102, 204, 255, 0.4);
+}
+
+.modal-btn.primary:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+}
+
+.modal-btn.secondary {
+ background: linear-gradient(180deg, #333333, #1a1a1a);
+ border-color: #666666;
+ color: #cccccc;
+}
+
+.modal-btn.secondary:hover {
+ background: linear-gradient(180deg, #444444, #2a2a2a);
+}
+
+.loading,
+.error,
+.not-found {
+ padding: 40px;
+ text-align: center;
+ font-size: 14px;
+ color: rgba(255, 255, 255, 0.7);
+}
+
+.error {
+ color: #ff6464;
+}
diff --git a/frontend/src/types.ts b/frontend/src/types.ts
index c6d1263..ac8d417 100644
--- a/frontend/src/types.ts
+++ b/frontend/src/types.ts
@@ -9,3 +9,98 @@ export type Choice = {
id: string
label: string
}
+
+// Contract types
+export type ContractType = 'simple' | 'specification' | 'execute'
+export type ContractPhase = 'research' | 'specify' | 'plan' | 'execute' | 'review'
+export type ContractStatus = 'active' | 'completed' | 'archived'
+
+export interface ContractSummary {
+ id: string
+ name: string
+ description?: string
+ contractType: string
+ phase: string
+ status: string
+ supervisorTaskId?: string
+ localOnly: boolean
+ fileCount: number
+ taskCount: number
+ repositoryCount: number
+ version: number
+ createdAt: string
+ // Red team fields
+ redTeamEnabled?: boolean
+}
+
+export interface Contract {
+ id: string
+ ownerId: string
+ name: string
+ description?: string
+ contractType: string
+ phase: string
+ status: string
+ supervisorTaskId?: string
+ autonomousLoop: boolean
+ phaseGuard: boolean
+ completedDeliverables: Record<string, string[]>
+ localOnly: boolean
+ redTeamEnabled: boolean
+ redTeamPrompt?: string
+ version: number
+ createdAt: string
+ updatedAt: string
+}
+
+export interface CreateContractRequest {
+ name: string
+ description?: string
+ contractType?: string
+ initialPhase?: string
+ autonomousLoop?: boolean
+ phaseGuard?: boolean
+ localOnly?: boolean
+ redTeamEnabled?: boolean
+ redTeamPrompt?: string
+}
+
+export interface TaskSummary {
+ id: string
+ contractId?: string
+ contractName?: string
+ contractPhase?: string
+ contractStatus?: string
+ parentTaskId?: string
+ depth: number
+ name: string
+ status: string
+ priority: number
+ progressSummary?: string
+ subtaskCount: number
+ version: number
+ isSupervisor: boolean
+ isRedTeam: boolean
+ hidden: boolean
+ createdAt: string
+ updatedAt: string
+}
+
+// Red team notification types
+export type NotificationSeverity = 'info' | 'warning' | 'critical'
+
+export interface RedTeamNotification {
+ id: string
+ contractId: string
+ redTeamTaskId: string
+ relatedTaskId?: string
+ message: string
+ severity: NotificationSeverity
+ filePath?: string
+ context?: string
+ delivered: boolean
+ deliveredAt?: string
+ acknowledged: boolean
+ acknowledgedAt?: string
+ createdAt: string
+}
diff --git a/frontend/tsconfig.tsbuildinfo b/frontend/tsconfig.tsbuildinfo
index 79408dc..0fd5af9 100644
--- a/frontend/tsconfig.tsbuildinfo
+++ b/frontend/tsconfig.tsbuildinfo
@@ -1 +1 @@
-{"root":["./src/app.tsx","./src/main.tsx","./src/types.ts","./src/components/bottombar.tsx","./src/components/choicemenu.tsx","./src/components/cityscapebackground.tsx","./src/components/configmodal.tsx","./src/components/contractdetail.tsx","./src/components/contractlist.tsx","./src/components/dialoguebox.tsx","./src/components/filedetail.tsx","./src/components/heartlogo.tsx","./src/components/landingpage.tsx","./src/components/loadingscreen.tsx","./src/components/origamidragonlogo.tsx","./src/components/topbar.tsx","./src/components/vnapp.tsx","./src/components/vninterface.tsx","./src/components/vnviewport.tsx","./src/services/ws.ts","./src/stores/index.ts"],"version":"5.9.2"} \ No newline at end of file
+{"root":["./src/app.tsx","./src/main.tsx","./src/types.ts","./src/components/bottombar.tsx","./src/components/choicemenu.tsx","./src/components/cityscapebackground.tsx","./src/components/configmodal.tsx","./src/components/contractcreatemodal.tsx","./src/components/contractdetail.tsx","./src/components/contractlist.tsx","./src/components/dialoguebox.tsx","./src/components/filedetail.tsx","./src/components/heartlogo.tsx","./src/components/landingpage.tsx","./src/components/loadingscreen.tsx","./src/components/origamidragonlogo.tsx","./src/components/topbar.tsx","./src/components/vnapp.tsx","./src/components/vninterface.tsx","./src/components/vnviewport.tsx","./src/services/ws.ts","./src/stores/index.ts"],"version":"5.9.2"} \ No newline at end of file
diff --git a/makima/src/bin/makima.rs b/makima/src/bin/makima.rs
index ffb9364..44fa590 100644
--- a/makima/src/bin/makima.rs
+++ b/makima/src/bin/makima.rs
@@ -6,7 +6,7 @@ use std::sync::Arc;
use makima::daemon::api::{ApiClient, CreateContractRequest};
use makima::daemon::cli::{
- Cli, CliConfig, Commands, ConfigCommand, ContractCommand, SupervisorCommand, ViewArgs,
+ Cli, CliConfig, Commands, ConfigCommand, ContractCommand, RedTeamCommand, SupervisorCommand, ViewArgs,
};
use makima::daemon::tui::{self, Action, App, ListItem, ViewType, TuiWsClient, WsEvent, OutputLine, OutputMessageType, WsConnectionState, RepositorySuggestion};
use makima::daemon::config::{DaemonConfig, RepoEntry};
@@ -30,6 +30,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
Commands::Contract(cmd) => run_contract(cmd).await,
Commands::View(args) => run_view(args).await,
Commands::Config(cmd) => run_config(cmd).await,
+ Commands::RedTeam(cmd) => run_red_team(cmd).await,
}
}
@@ -789,6 +790,16 @@ async fn run_config(cmd: ConfigCommand) -> Result<(), Box<dyn std::error::Error
}
}
+/// Run red team commands.
+async fn run_red_team(cmd: RedTeamCommand) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
+ match cmd {
+ RedTeamCommand::Notify(args) => {
+ makima::daemon::cli::handle_notify(args).await?;
+ Ok(())
+ }
+ }
+}
+
/// Load contracts from API
async fn load_contracts(client: &ApiClient) -> Result<Vec<ListItem>, Box<dyn std::error::Error + Send + Sync>> {
let result = client.list_contracts().await?;
diff --git a/makima/src/daemon/api/mod.rs b/makima/src/daemon/api/mod.rs
index 49d80e0..92e34e9 100644
--- a/makima/src/daemon/api/mod.rs
+++ b/makima/src/daemon/api/mod.rs
@@ -2,7 +2,9 @@
pub mod client;
pub mod contract;
+pub mod red_team;
pub mod supervisor;
pub use client::ApiClient;
pub use contract::CreateContractRequest;
+pub use red_team::RedTeamNotifyRequest;
diff --git a/makima/src/daemon/api/red_team.rs b/makima/src/daemon/api/red_team.rs
new file mode 100644
index 0000000..6d3c969
--- /dev/null
+++ b/makima/src/daemon/api/red_team.rs
@@ -0,0 +1,39 @@
+//! Red team API methods.
+
+use serde::Serialize;
+use uuid::Uuid;
+
+use super::client::{ApiClient, ApiError};
+use super::supervisor::JsonValue;
+
+/// Request body for red team notify endpoint.
+#[derive(Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct RedTeamNotifyRequest {
+ /// The issue message
+ pub message: String,
+
+ /// Severity level: low, medium, high, critical
+ pub severity: String,
+
+ /// The specific task this relates to (optional)
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub related_task_id: Option<Uuid>,
+
+ /// The file path where the issue was detected (optional)
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub file_path: Option<String>,
+
+ /// Additional context about the issue (optional)
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub context: Option<String>,
+}
+
+impl ApiClient {
+ /// Send a red team notification about an issue found during adversarial review.
+ ///
+ /// POST /api/v1/mesh/red-team/notify
+ pub async fn red_team_notify(&self, req: RedTeamNotifyRequest) -> Result<JsonValue, ApiError> {
+ self.post("/api/v1/mesh/red-team/notify", &req).await
+ }
+}
diff --git a/makima/src/daemon/cli/mod.rs b/makima/src/daemon/cli/mod.rs
index 0805edd..c848e8e 100644
--- a/makima/src/daemon/cli/mod.rs
+++ b/makima/src/daemon/cli/mod.rs
@@ -3,15 +3,18 @@
pub mod config;
pub mod contract;
pub mod daemon;
+pub mod red_team;
pub mod server;
pub mod supervisor;
pub mod view;
-use clap::{Parser, Subcommand};
+use clap::{Args, Parser, Subcommand};
+use uuid::Uuid;
pub use config::CliConfig;
pub use contract::ContractArgs;
pub use daemon::DaemonArgs;
+pub use red_team::handle_notify;
pub use server::ServerArgs;
pub use supervisor::SupervisorArgs;
pub use view::ViewArgs;
@@ -58,6 +61,10 @@ pub enum Commands {
/// Saves configuration to ~/.makima/config.toml for use by CLI commands.
#[command(subcommand)]
Config(ConfigCommand),
+
+ /// Red team commands for adversarial monitoring
+ #[command(name = "red-team", subcommand)]
+ RedTeam(RedTeamCommand),
}
/// Config subcommands for CLI configuration.
@@ -196,6 +203,54 @@ pub enum ContractCommand {
CreateFile(contract::CreateFileArgs),
}
+/// Red team subcommands for adversarial monitoring.
+#[derive(Subcommand, Debug)]
+pub enum RedTeamCommand {
+ /// Send a notification to the supervisor about a detected issue.
+ /// Only available to red team tasks.
+ Notify(RedTeamNotifyArgs),
+}
+
+/// Arguments for red-team notify command.
+#[derive(Args, Debug)]
+pub struct RedTeamNotifyArgs {
+ /// API URL
+ #[arg(long, env = "MAKIMA_API_URL", default_value = "https://api.makima.jp")]
+ pub api_url: String,
+
+ /// API key for authentication
+ #[arg(long, env = "MAKIMA_API_KEY")]
+ pub api_key: String,
+
+ /// Current task ID (must be a red team task)
+ #[arg(long, env = "MAKIMA_TASK_ID")]
+ pub task_id: Uuid,
+
+ /// Contract ID
+ #[arg(long, env = "MAKIMA_CONTRACT_ID")]
+ pub contract_id: Uuid,
+
+ /// The notification message
+ #[arg(index = 1)]
+ pub message: String,
+
+ /// Severity level: low, medium, high, critical
+ #[arg(long, default_value = "medium")]
+ pub severity: String,
+
+ /// Related task ID (optional)
+ #[arg(long)]
+ pub task: Option<Uuid>,
+
+ /// Related file path (optional)
+ #[arg(long)]
+ pub file: Option<String>,
+
+ /// Additional context (optional)
+ #[arg(long)]
+ pub context: Option<String>,
+}
+
impl Cli {
/// Parse command-line arguments
pub fn parse_args() -> Self {
diff --git a/makima/src/daemon/cli/red_team.rs b/makima/src/daemon/cli/red_team.rs
new file mode 100644
index 0000000..771aae4
--- /dev/null
+++ b/makima/src/daemon/cli/red_team.rs
@@ -0,0 +1,26 @@
+//! Red Team subcommand - adversarial review notification commands.
+
+use crate::daemon::api::{ApiClient, RedTeamNotifyRequest};
+use super::RedTeamNotifyArgs;
+
+/// Handle the red-team notify command.
+pub async fn handle_notify(args: RedTeamNotifyArgs) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
+ let client = ApiClient::new(args.api_url, args.api_key)?;
+
+ // Use --task if provided, otherwise fall back to MAKIMA_TASK_ID
+ let related_task_id = args.task;
+
+ let req = RedTeamNotifyRequest {
+ message: args.message,
+ severity: args.severity,
+ related_task_id,
+ file_path: args.file,
+ context: args.context,
+ };
+
+ eprintln!("Sending red team notification...");
+ let result = client.red_team_notify(req).await?;
+ println!("{}", serde_json::to_string(&result.0)?);
+
+ Ok(())
+}