Loading...
Loading...
Compare original and translation side by side
Skill by ara.so — Hermes Skills collection.
由ara.so开发的Skill——Hermes Skills集合。
~/.hermes/kanban.db~/.hermes/profiles/hermes~/.hermes/kanban.db~/.hermes/profiles/hermesundefinedundefined
Open `http://localhost:3000` in your browser.
在浏览器中打开`http://localhost:3000`。git clone https://github.com/Naroh091/hermes-war-room.git
cd hermes-war-room
pnpm install
pnpm devhttp://localhost:3000git clone https://github.com/Naroh091/hermes-war-room.git
cd hermes-war-room
pnpm install
pnpm devhttp://localhost:3000undefinedundefinedundefinedundefined<!-- Components displaying operatives -->
<OperativeWorkstation
:operative="operative"
:current-task="currentTask"
@click="openDossier"
/>~/.hermes/profiles/<slug>/<!-- 展示执行体的组件 -->
<OperativeWorkstation
:operative="operative"
:current-task="currentTask"
@click="openDossier"
/>~/.hermes/profiles/<slug>/<!-- profiles/lider/SOUL.md -->
You are the mission orchestrator. Your job is to:
1. Decompose user goals into discrete tasks
2. Delegate each task via kanban to the right specialist
3. Never do the work yourself
4. Summarize results when workers complete tasks
Always use the terminal tool to create kanban tasks:
hermes kanban create --assignee <worker> --title "..." --body "..." --json<!-- profiles/investigador/SOUL.md -->
You are a research specialist. When assigned a kanban task:
1. Execute the research using available tools
2. Complete the task with: hermes kanban complete <id> --summary "..."<!-- profiles/lider/SOUL.md -->
你是任务编排者。你的职责是:
1. 将用户目标分解为独立任务
2. 通过看板将每个任务委派给合适的专家
3. 绝不亲自执行任务
4. 当执行者完成任务后汇总结果
必须使用终端工具创建看板任务:
hermes kanban create --assignee <worker> --title "..." --body "..." --json<!-- profiles/investigador/SOUL.md -->
你是研究专家。当收到看板任务时:
1. 使用可用工具执行研究
2. 通过以下命令完成任务:hermes kanban complete <id> --summary "..."// server/api/missions/create.post.ts
export default defineEventHandler(async (event) => {
const { orchestratorId, title } = await readBody(event)
const mission = await db.insert(missions).values({
orchestrator_id: orchestratorId,
title: title,
status: 'open',
created_at: new Date()
}).returning()
return mission[0]
})// server/api/missions/create.post.ts
export default defineEventHandler(async (event) => {
const { orchestratorId, title } = await readBody(event)
const mission = await db.insert(missions).values({
orchestrator_id: orchestratorId,
title: title,
status: 'open',
created_at: new Date()
}).returning()
return mission[0]
})// Create a new mission
POST /api/missions/create
{
"orchestratorId": 1,
"title": "Research competitor pricing"
}
// Send a message (returns SSE stream)
POST /api/missions/:id/message
{
"content": "Find pricing for Acme Corp products"
}
// Get mission history
GET /api/missions/:id// 创建新任务协作
POST /api/missions/create
{
"orchestratorId": 1,
"title": "研究竞争对手定价"
}
// 发送消息(返回SSE流)
POST /api/missions/:id/message
{
"content": "查找Acme Corp产品的定价"
}
// 获取任务协作历史
GET /api/missions/:id// List all operatives
GET /api/operatives
// Get operative details with current task
GET /api/operatives/:id
// Update operative (callsign, avatar, color, active)
PATCH /api/operatives/:id
{
"callsign": "RESEARCHER",
"color": "#3b82f6",
"active": true
}
// Retrain (update SOUL.md, skills, tools)
POST /api/operatives/:id/retrain
{
"soul": "You are a specialized legal analyst...",
"skills": ["research", "analysis"],
"mcpServers": ["filesystem", "brave-search"]
}// 列出所有执行体
GET /api/operatives
// 获取执行体详情及当前任务
GET /api/operatives/:id
// 更新执行体(呼号、头像、颜色、激活状态)
PATCH /api/operatives/:id
{
"callsign": "RESEARCHER",
"color": "#3b82f6",
"active": true
}
// 重新训练(更新SOUL.md、skills、tools)
POST /api/operatives/:id/retrain
{
"soul": "你是专业的法律分析师...",
"skills": ["research", "analysis"],
"mcpServers": ["filesystem", "brave-search"]
}// Get all tasks for the board view
GET /api/kanban/tasks
// Get tasks for a specific operative
GET /api/kanban/tasks?assignee=investigador
// Watch a task (auto-nudge when complete)
POST /api/kanban/watch
{
"taskId": "task_abc123",
"missionId": 5
}// 获取看板视图的所有任务
GET /api/kanban/tasks
// 获取指定执行体的任务
GET /api/kanban/tasks?assignee=investigador
// 监控任务(完成时自动提醒)
POST /api/kanban/watch
{
"taskId": "task_abc123",
"missionId": 5
}undefinedundefinedundefinedundefined~/.hermes/profiles/lider/SOUL.mdundefined~/.hermes/profiles/lider/SOUL.mdundefinedhermes kanban create \
--assignee <specialist> \
--title "<concise title>" \
--body "<detailed instructions>" \
--json
Edit `~/.hermes/profiles/investigador/SOUL.md`:
```markdownhermes kanban create \
--assignee <specialist> \
--title "<简洁标题>" \
--body "<详细说明>" \
--json
编辑`~/.hermes/profiles/investigador/SOUL.md`:
```markdownhermes kanban complete <task_id> --summary "<findings>"undefinedhermes kanban complete <task_id> --summary "<研究结果>"undefined/teamterminalreasoningterminalfilesystemsearchreasoning/teamterminalreasoningterminalfilesystemsearchreasoningreadyundefinedundefined
Without the dispatcher, tasks will stay in "ready" state.
如果没有调度器,任务将一直处于“就绪”状态。// 1. Frontend: Create mission
const mission = await $fetch('/api/missions/create', {
method: 'POST',
body: {
orchestratorId: 1, // lider
title: 'Market research for Product X'
}
})
// 2. Frontend: Send initial brief
const eventSource = new EventSource(
`/api/missions/${mission.id}/message`,
{
method: 'POST',
body: JSON.stringify({
content: 'Research our top 3 competitors: pricing, features, and market position'
})
}
)
eventSource.addEventListener('tool_call', (event) => {
const data = JSON.parse(event.data)
if (data.name === 'terminal' && data.input.includes('kanban create')) {
console.log('Orchestrator delegating task...')
}
})
// 3. Backend: Orchestrator creates tasks
// (Automatically intercepted by War Room)
// hermes kanban create --assignee investigador --title "Research Competitor A" --json
// hermes kanban create --assignee investigador --title "Research Competitor B" --json
// 4. Backend: Workers process tasks
// investigador picks up tasks via dispatcher, completes them
// 5. Backend: Auto-nudge when tasks complete
// War Room injects system message:
// "Tasks task_abc123, task_def456 completed. Summarize for the user."
// 6. Frontend: Orchestrator synthesizes response
eventSource.addEventListener('message', (event) => {
const data = JSON.parse(event.data)
console.log('Orchestrator:', data.content)
// "Based on the research, here's what we found about your competitors..."
})// 1. 前端:创建任务协作
const mission = await $fetch('/api/missions/create', {
method: 'POST',
body: {
orchestratorId: 1, // lider
title: '产品X的市场调研'
}
})
// 2. 前端:发送初始简报
const eventSource = new EventSource(
`/api/missions/${mission.id}/message`,
{
method: 'POST',
body: JSON.stringify({
content: '研究我们的三大竞争对手:定价、功能和市场地位'
})
}
)
eventSource.addEventListener('tool_call', (event) => {
const data = JSON.parse(event.data)
if (data.name === 'terminal' && data.input.includes('kanban create')) {
console.log('编排者正在委派任务...')
}
})
// 3. 后端:编排者创建任务
// (由War Room自动拦截)
// hermes kanban create --assignee investigador --title "研究竞争对手A" --json
// hermes kanban create --assignee investigador --title "研究竞争对手B" --json
// 4. 后端:执行者处理任务
// investigador通过调度器领取任务并完成
// 5. 后端:任务完成时自动提醒
// War Room注入系统消息:
// "任务task_abc123、task_def456已完成。请为用户汇总结果。"
// 6. 前端:编排者整合响应
eventSource.addEventListener('message', (event) => {
const data = JSON.parse(event.data)
console.log('编排者回复:', data.content)
// "根据调研结果,以下是我们对竞争对手的分析..."
})// server/utils/taskWatcher.ts
import { db } from './db'
import { watchedTasks, missions } from './schema'
export async function pollWatchedTasks() {
const watched = await db.select().from(watchedTasks).where(
eq(watchedTasks.notified, false)
)
for (const watch of watched) {
const task = await getKanbanTask(watch.task_id)
if (task.status === 'done' || task.status === 'blocked') {
// Inject system message into orchestrator session
const mission = await db.select().from(missions).where(
eq(missions.id, watch.mission_id)
).get()
await sendSystemMessage(mission.acp_session_id, {
role: 'system',
content: `Task ${watch.task_id} is now ${task.status}. Summary: ${task.summary}. Please integrate this into your response to the user.`
})
// Mark as notified
await db.update(watchedTasks).set({ notified: true }).where(
eq(watchedTasks.id, watch.id)
)
}
}
}
// Run every 5 seconds
setInterval(pollWatchedTasks, 5000)// server/utils/taskWatcher.ts
import { db } from './db'
import { watchedTasks, missions } from './schema'
export async function pollWatchedTasks() {
const watched = await db.select().from(watchedTasks).where(
eq(watchedTasks.notified, false)
)
for (const watch of watched) {
const task = await getKanbanTask(watch.task_id)
if (task.status === 'done' || task.status === 'blocked') {
// 向编排者会话注入系统消息
const mission = await db.select().from(missions).where(
eq(missions.id, watch.mission_id)
).get()
await sendSystemMessage(mission.acp_session_id, {
role: 'system',
content: `任务${watch.task_id}当前状态为${task.status}。摘要:${task.summary}。请将此内容整合到给用户的回复中。`
})
// 标记为已通知
await db.update(watchedTasks).set({ notified: true }).where(
eq(watchedTasks.id, watch.id)
)
}
}
}
// 每5秒运行一次
setInterval(pollWatchedTasks, 5000)<script setup>
const { mission, messages, sendMessage, loading } = useMission(missionId)
async function briefOrchestrator() {
await sendMessage('Analyze legal compliance for EU markets')
}
</script>
<template>
<div>
<ChatMessage
v-for="msg in messages"
:key="msg.id"
:message="msg"
/>
<ChatInput @send="sendMessage" :disabled="loading" />
</div>
</template><script setup>
const { mission, messages, sendMessage, loading } = useMission(missionId)
async function briefOrchestrator() {
await sendMessage('分析欧盟市场的合规性')
}
</script>
<template>
<div>
<ChatMessage
v-for="msg in messages"
:key="msg.id"
:message="msg"
/>
<ChatInput @send="sendMessage" :disabled="loading" />
</div>
</template><script setup>
const { operatives, activeOperatives, refresh } = useOperatives()
const workers = computed(() =>
activeOperatives.value.filter(op => op.profile_slug !== 'lider')
)
</script>
<template>
<OperativeWorkstation
v-for="op in workers"
:key="op.id"
:operative="op"
/>
</template><script setup>
const { operatives, activeOperatives, refresh } = useOperatives()
const workers = computed(() =>
activeOperatives.value.filter(op => op.profile_slug !== 'lider')
)
</script>
<template>
<OperativeWorkstation
v-for="op in workers"
:key="op.id"
:operative="op"
/>
</template><script setup>
const { tasks, tasksByStatus, refresh } = useKanban()
const columns = {
todo: computed(() => tasksByStatus.value.todo),
ready: computed(() => tasksByStatus.value.ready),
running: computed(() => tasksByStatus.value.running),
blocked: computed(() => tasksByStatus.value.blocked)
}
</script>
<template>
<div class="kanban-board">
<KanbanColumn
v-for="(tasks, status) in columns"
:key="status"
:title="status"
:tasks="tasks"
/>
</div>
</template><script setup>
const { tasks, tasksByStatus, refresh } = useKanban()
const columns = {
todo: computed(() => tasksByStatus.value.todo),
ready: computed(() => tasksByStatus.value.ready),
running: computed(() => tasksByStatus.value.running),
blocked: computed(() => tasksByStatus.value.blocked)
}
</script>
<template>
<div class="kanban-board">
<KanbanColumn
v-for="(tasks, status) in columns"
:key="status"
:title="status"
:tasks="tasks"
/>
</div>
</template>// POST /api/operatives/create
export default defineEventHandler(async (event) => {
const { slug, callsign, cloneFrom } = await readBody(event)
// Shell out to Hermes CLI
const { stdout } = await execAsync(
`hermes profile create ${slug} ${cloneFrom ? `--clone ${cloneFrom}` : ''}`
)
// Register in War Room DB
const operative = await db.insert(operatives).values({
profile_slug: slug,
callsign: callsign || slug.toUpperCase(),
avatar: randomAvatar(),
color: randomColor(),
active: true
}).returning()
return operative[0]
})// POST /api/operatives/create
export default defineEventHandler(async (event) => {
const { slug, callsign, cloneFrom } = await readBody(event)
// 调用Hermes CLI
const { stdout } = await execAsync(
`hermes profile create ${slug} ${cloneFrom ? `--clone ${cloneFrom}` : ''}`
)
// 在War Room数据库中注册
const operative = await db.insert(operatives).values({
profile_slug: slug,
callsign: callsign || slug.toUpperCase(),
avatar: randomAvatar(),
color: randomColor(),
active: true
}).returning()
return operative[0]
})<!-- components/OperativeWorkstation.vue -->
<script setup>
const props = defineProps(['operative'])
const { data: currentTask } = useFetch(`/api/operatives/${props.operative.id}/current-task`, {
watch: true,
refreshInterval: 2000 // Poll every 2s
})
const status = computed(() => {
if (!currentTask.value) return 'Standing by'
if (currentTask.value.status === 'running') return `Working on: ${currentTask.value.title}`
if (currentTask.value.status === 'blocked') return 'Blocked'
return 'Standing by'
})
</script>
<template>
<div class="workstation" :style="{ borderColor: operative.color }">
<img :src="operative.avatar" />
<div class="status-pill">{{ status }}</div>
</div>
</template><!-- components/OperativeWorkstation.vue -->
<script setup>
const props = defineProps(['operative'])
const { data: currentTask } = useFetch(`/api/operatives/${props.operative.id}/current-task`, {
watch: true,
refreshInterval: 2000 // 每2秒轮询一次
})
const status = computed(() => {
if (!currentTask.value) return '待命'
if (currentTask.value.status === 'running') return `正在处理:${currentTask.value.title}`
if (currentTask.value.status === 'blocked') return '阻塞'
return '待命'
})
</script>
<template>
<div class="workstation" :style="{ borderColor: operative.color }">
<img :src="operative.avatar" />
<div class="status-pill">{{ status }}</div>
</div>
</template><script setup>
const props = defineProps(['taskId'])
const { data: chain } = useFetch(`/api/kanban/tasks/${props.taskId}/chain`)
</script>
<template>
<div class="delegation-chain">
<div v-for="task in chain" :key="task.id" class="chain-node">
<OperativeBadge :operative="task.assignee" mini />
<div class="task-info">
<strong>{{ task.title }}</strong>
<span class="status">{{ task.status }}</span>
</div>
<div v-if="task.children?.length" class="children">
<DelegationChain
v-for="child in task.children"
:key="child.id"
:task-id="child.id"
/>
</div>
</div>
</div>
</template><script setup>
const props = defineProps(['taskId'])
const { data: chain } = useFetch(`/api/kanban/tasks/${props.taskId}/chain`)
</script>
<template>
<div class="delegation-chain">
<div v-for="task in chain" :key="task.id" class="chain-node">
<OperativeBadge :operative="task.assignee" mini />
<div class="task-info">
<strong>{{ task.title }}</strong>
<span class="status">{{ task.status }}</span>
</div>
<div v-if="task.children?.length" class="children">
<DelegationChain
v-for="child in task.children"
:key="child.id"
:task-id="child.id"
/>
</div>
</div>
</div>
</template>hermes gatewayhermes gatewayCRITICAL RULES:
1. You MUST delegate via kanban. NEVER answer directly.
2. Every user request becomes one or more kanban tasks.
3. Use the terminal tool: hermes kanban create --assignee <worker> --title "..." --body "..." --json
4. After delegating, say "Tasks assigned, waiting for results."terminal核心规则:
1. 必须通过看板委派任务,绝不直接回复。
2. 用户的每个请求都要转化为一个或多个看板任务。
3. 使用终端工具:hermes kanban create --assignee <worker> --title "..." --body "..." --json
4. 委派完成后,回复“任务已分配,等待结果。”terminal[taskWatcher] Detected task task_abc123 completed, nudging orchestrator[taskWatcher] 检测到任务task_abc123已完成,正在提醒编排者pkill -f "hermes gateway" && hermes gatewaypkill -f "hermes gateway" && hermes gatewaydatabase is lockedHERMES_HOMEsqlite3database is lockedHERMES_HOMEsqlite3undefinedundefined
```bash
sudo systemctl enable hermes-war-room
sudo systemctl start hermes-war-room
```bash
sudo systemctl enable hermes-war-room
sudo systemctl start hermes-war-roomserver {
listen 80;
server_name war-room.example.com;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
# SSE requires no buffering
proxy_buffering off;
proxy_cache off;
}
}server {
listen 80;
server_name war-room.example.com;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
# SSE需要禁用缓冲
proxy_buffering off;
proxy_cache off;
}
}undefinedundefinedundefinedundefinedhermes-war-room/
├── server/
│ ├── api/
│ │ ├── missions/ # Mission CRUD + messaging
│ │ ├── operatives/ # Operative management + retrain
│ │ └── kanban/ # Kanban task queries + watch
│ ├── utils/
│ │ ├── db.ts # War Room SQLite connection
│ │ ├── hermes.ts # Hermes CLI wrappers
│ │ └── taskWatcher.ts # Auto-nudge poller
│ └── middleware/
├── components/
│ ├── OperativeWorkstation.vue
│ ├── KanbanBoard.vue
│ ├── MissionChat.vue
│ └── OperativeDossier.vue
├── composables/
│ ├── useMission.ts
│ ├── useOperatives.ts
│ └── useKanban.ts
├── pages/
│ ├── index.vue # War Room floor
│ ├── team.vue # Roster/badges
│ └── missions.vue # Archive
└── data/
└── war-room.db # War Room state (not Hermes kanban.db)~/.hermes/kanban.db~/.hermes/profiles/hermeshermes-war-room/
├── server/
│ ├── api/
│ │ ├── missions/ # 任务协作的增删改查及消息功能
│ │ ├── operatives/ # 执行体管理及重新训练
│ │ └── kanban/ # 看板任务查询及监控
│ ├── utils/
│ │ ├── db.ts # War Room SQLite连接
│ │ ├── hermes.ts # Hermes CLI封装
│ │ └── taskWatcher.ts # 自动提醒轮询器
│ └── middleware/
├── components/
│ ├── OperativeWorkstation.vue
│ ├── KanbanBoard.vue
│ ├── MissionChat.vue
│ └── OperativeDossier.vue
├── composables/
│ ├── useMission.ts
│ ├── useOperatives.ts
│ └── useKanban.ts
├── pages/
│ ├── index.vue # 指挥中心主界面
│ ├── team.vue # 执行体列表
│ └── missions.vue # 任务协作归档
└── data/
└── war-room.db # War Room状态数据库(非Hermes的kanban.db)~/.hermes/kanban.db~/.hermes/profiles/hermes