建立 Codex Office 虛擬辦公室(模擬 + 實況雙模式)
摘要: 以 claude-office 為基底改造的 Codex CLI 版虛擬辦公室:終端機綠 + 冷灰主題、 >_ 游標標誌,實況模式改為監看 ~/.codex/sessions/ 的 rollout transcripts, 依 cwd 將 session 分組成專案顯示。 根本原因: 既有 claude-office 只能監看 Claude Code 活動,Codex CLI 的 session 格式 (rollout JSONL:session_meta / turn_context / event_msg / response_item) 與存放結構(YYYY/MM/DD 日期目錄)完全不同,需要獨立的轉譯器。 影響: 新專案,與 claude-office 並存:web 5182(0.0.0.0)、watch 5181(僅 127.0.0.1), 與 5179/5180 不衝突,可同時常駐 PM2。 修法: - server/watch.mjs 重寫:遞迴掃描日期目錄、tail 新增位元組、 由 session_meta/turn_context 的 cwd 分組專案, custom_tool_call/function_call/MCP → 工具事件,agent_message/task_complete → 回覆 - 主題改造:constants 配色(石墨綠)、Sora/Manrope/JetBrains Mono 字型、 >_ 閃爍游標標誌、agents 改為 Codex/Sol/Nova/Mini、工具改為 exec/apply_patch 等 - live.ts 新增 thinking 事件(對應 reasoning),port 5181 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
import { STATUS_COLORS, STATUS_LABELS } from "@/lib/constants";
|
||||
import { generateAppearance } from "@/lib/appearance";
|
||||
import { useOfficeStore } from "@/store/office-store";
|
||||
import { getDirector } from "@/sim/runtime";
|
||||
import { Pawn } from "./Pawn";
|
||||
|
||||
const ROLE_LABELS = { lead: "Lead Agent", agent: "Agent", subagent: "Subagent" } as const;
|
||||
|
||||
export function SidePanel() {
|
||||
const selectedAgentId = useOfficeStore((s) => s.selectedAgentId);
|
||||
const agent = useOfficeStore((s) => (s.selectedAgentId ? s.agents.get(s.selectedAgentId) : undefined));
|
||||
const agents = useOfficeStore((s) => s.agents);
|
||||
const events = useOfficeStore((s) => s.events);
|
||||
const selectAgent = useOfficeStore((s) => s.selectAgent);
|
||||
const mode = useOfficeStore((s) => s.mode);
|
||||
|
||||
return (
|
||||
<aside className="side-panel">
|
||||
{selectedAgentId && agent ? (
|
||||
<div className="agent-card">
|
||||
<div className="agent-card-head">
|
||||
<svg viewBox="-30 -42 60 68" width="64" height="72">
|
||||
<Pawn appearance={generateAppearance(agent.id)} pose="stand" motion="none" />
|
||||
</svg>
|
||||
<div>
|
||||
<h2>
|
||||
{agent.role === "lead" && <span className="mark">▸ </span>}
|
||||
{agent.name}
|
||||
</h2>
|
||||
<span className="role-badge">{ROLE_LABELS[agent.role]}</span>
|
||||
<code className="model-tag">{agent.model}</code>
|
||||
</div>
|
||||
<button className="close-btn" onClick={() => selectAgent(null)}>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="agent-stat-row">
|
||||
<span className="status-chip" style={{ backgroundColor: `${STATUS_COLORS[agent.status]}22`, color: STATUS_COLORS[agent.status] }}>
|
||||
● {STATUS_LABELS[agent.status]}
|
||||
</span>
|
||||
{agent.currentTool && <code className="tool-now">{agent.currentTool.name}()</code>}
|
||||
</div>
|
||||
|
||||
<dl className="agent-facts">
|
||||
<div>
|
||||
<dt>工具調用</dt>
|
||||
<dd>{agent.toolCallCount} 次</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>所在區域</dt>
|
||||
<dd>{agent.zone}</dd>
|
||||
</div>
|
||||
{agent.parentId && (
|
||||
<div>
|
||||
<dt>上級</dt>
|
||||
<dd>
|
||||
<button className="link-btn" onClick={() => selectAgent(agent.parentId)}>
|
||||
{agents.get(agent.parentId)?.name ?? agent.parentId}
|
||||
</button>
|
||||
</dd>
|
||||
</div>
|
||||
)}
|
||||
{agent.childIds.length > 0 && (
|
||||
<div>
|
||||
<dt>Subagents</dt>
|
||||
<dd>
|
||||
{agent.childIds.map((cid) => (
|
||||
<button key={cid} className="link-btn" onClick={() => selectAgent(cid)}>
|
||||
{agents.get(cid)?.name ?? cid}
|
||||
</button>
|
||||
))}
|
||||
</dd>
|
||||
</div>
|
||||
)}
|
||||
</dl>
|
||||
|
||||
{agent.toolHistory.length > 0 && (
|
||||
<div className="tool-history">
|
||||
<h3>最近工具</h3>
|
||||
<div className="tool-chips">
|
||||
{agent.toolHistory.map((t, i) => (
|
||||
<code key={i}>{t}</code>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{agent.role !== "subagent" && mode === "sim" && (
|
||||
<button
|
||||
className="btn assign-btn"
|
||||
onClick={() => {
|
||||
if (!getDirector().assignTask(agent.id)) {
|
||||
useOfficeStore.getState().addEvent("💬", `${agent.name} 正忙,稍後再指派`);
|
||||
}
|
||||
}}
|
||||
>
|
||||
📋 指派任務
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="panel-hint">
|
||||
<p>點擊辦公室裡的 agent 查看詳情</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="event-feed">
|
||||
<h3>事件紀錄</h3>
|
||||
<ul>
|
||||
{events.map((e) => (
|
||||
<li key={e.at}>
|
||||
<span className="event-icon">{e.icon}</span>
|
||||
{e.text}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user