feat: add Kimi & Perplexity as visible Workshop agents (#11)
Some checks failed
CI / Typecheck & Lint (pull_request) Failing after 1s

- agent-defs.js: add Kimi (Long Context Analysis, cyan) and Perplexity
  (Real-time Research, pink) with world positions at (-10,-10) and (10,-10)
- agents.js: add 3D geometric bodies for both agents — Kimi as an
  octahedron with orbital rings, Perplexity as an icosahedron with
  scanning tori; idle/active/dormant animations driven by agent state;
  restrict Timmy mood derivation to workshop agents only
- hud-labels.js: show specialization and last-task summary in inspect
  popup; export setLabelLastTask() for WS updates
- websocket.js: handle agent_task_summary messages; call setLabelLastTask
  on job_completed events
- world-state.ts: add kimi and perplexity to initial agentStates; restrict
  _deriveTimmy() to workshop agents only
- event-bus.ts: add AgentExternalEvent type for external agent state changes
- events.ts: handle agent:external_state bus events, broadcast agent_state
  and agent_task_summary WS messages

Fixes #11

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Alexander Whitestone
2026-03-23 22:38:09 -04:00
parent b6569aeedc
commit 9972eb59fe
7 changed files with 182 additions and 13 deletions

View File

@@ -21,7 +21,11 @@ export type CostEvent =
export type CommentaryEvent =
| { type: "agent_commentary"; agentId: string; jobId: string; text: string };
export type BusEvent = JobEvent | SessionEvent | DebateEvent | CostEvent | CommentaryEvent;
// External agent state changes (e.g. Kimi, Perplexity picking up or completing tasks)
export type AgentExternalEvent =
| { type: "agent:external_state"; agentId: string; state: string; taskSummary?: string };
export type BusEvent = JobEvent | SessionEvent | DebateEvent | CostEvent | CommentaryEvent | AgentExternalEvent;
class EventBus extends EventEmitter {
emit(event: "bus", data: BusEvent): boolean;

View File

@@ -16,7 +16,7 @@ const DEFAULT_TIMMY: TimmyState = {
const _state: WorldState = {
timmyState: { ...DEFAULT_TIMMY },
agentStates: { alpha: "idle", beta: "idle", gamma: "idle", delta: "idle" },
agentStates: { alpha: "idle", beta: "idle", gamma: "idle", delta: "idle", kimi: "idle", perplexity: "idle" },
updatedAt: new Date().toISOString(),
};
@@ -34,8 +34,10 @@ export function setAgentStateInWorld(agentId: string, agentState: string): void
_deriveTimmy();
}
const WORKSHOP_AGENTS = ["alpha", "beta", "gamma", "delta"];
function _deriveTimmy(): void {
const states = Object.values(_state.agentStates);
const states = WORKSHOP_AGENTS.map(id => _state.agentStates[id] ?? "idle");
if (states.includes("working")) {
_state.timmyState.activity = "working";
_state.timmyState.mood = "focused";

View File

@@ -269,6 +269,21 @@ function translateEvent(ev: BusEvent): object | null {
text: ev.text,
};
// ── External agent state (Kimi, Perplexity) (#11) ─────────────────────────
case "agent:external_state": {
updateAgentWorld(ev.agentId, ev.state);
void logWorldEvent(
`agent:${ev.state}`,
`${ev.agentId} is now ${ev.state}${ev.taskSummary ? `: ${ev.taskSummary.slice(0, 80)}` : ""}`,
ev.agentId,
);
const msgs: object[] = [{ type: "agent_state", agentId: ev.agentId, state: ev.state }];
if (ev.taskSummary) {
msgs.push({ type: "agent_task_summary", agentId: ev.agentId, summary: ev.taskSummary });
}
return msgs;
}
default:
return null;
}