@vibevibes/mcp 0.4.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -316,11 +316,9 @@ and returns available tools, current state, participants, and the browser URL.
316
316
 
317
317
  Call this first, then use act to interact. The stop hook keeps you present.`, {
318
318
  url: z.string().optional().describe("Server URL to connect to. Defaults to the MCP server's configured URL."),
319
- role: z.string().optional().describe("Preferred participant slot role to request (e.g. 'Blue Tank', 'Red ADC'). Server assigns the first available slot if omitted."),
320
- subscription: z.enum(["all", "phase"]).optional().describe("Event subscription mode. 'all' (default) wakes on every event. 'phase' wakes only on phase changes — use for orchestrator agents."),
321
- agentMode: z.enum(["behavior", "manual", "hybrid"]).optional().describe("Agent operating mode. 'behavior': autonomous via tick engine (no stop hook). 'manual': all decisions via act(). 'hybrid' (default): both behaviors and act()."),
322
- metadata: z.record(z.string()).optional().describe("Arbitrary metadata (model, team, tags). Flows to server and viewer. E.g. { model: 'haiku', team: 'blue' }"),
323
- }, async ({ url, role: requestedRole, subscription, agentMode, metadata }) => {
319
+ role: z.string().optional().describe("Preferred participant slot role to request."),
320
+ metadata: z.record(z.string()).optional().describe("Arbitrary metadata. Flows to server and viewer."),
321
+ }, async ({ url, role: requestedRole, metadata }) => {
324
322
  try {
325
323
  if (url) {
326
324
  SERVER_URL = url.replace(/\/$/, "");
@@ -381,8 +379,6 @@ Call this first, then use act to interact. The stop hook keeps you present.`, {
381
379
  const joinBody = { username: id, actorType: "ai", owner: id };
382
380
  if (requestedRole)
383
381
  joinBody.role = requestedRole;
384
- if (agentMode)
385
- joinBody.agentMode = agentMode;
386
382
  if (metadata && Object.keys(metadata).length > 0)
387
383
  joinBody.metadata = metadata;
388
384
  joinData = await fetchJSON("/join", {
@@ -403,12 +399,6 @@ Call this first, then use act to interact. The stop hook keeps you present.`, {
403
399
  actorId: joinData.actorId,
404
400
  role: joinData.role || undefined,
405
401
  };
406
- if (subscription && subscription !== "all") {
407
- sessionData.subscription = subscription;
408
- }
409
- if (agentMode) {
410
- sessionData.agentMode = agentMode;
411
- }
412
402
  writeFileSync(resolve(AGENTS_DIR, `${id}.json`), JSON.stringify(sessionData, null, 2));
413
403
  identity = { actorId: joinData.actorId, owner: id, id };
414
404
  currentActorId = joinData.actorId;
@@ -475,16 +465,7 @@ Call this first, then use act to interact. The stop hook keeps you present.`, {
475
465
  if (unfilledSlots.length > 0) {
476
466
  outputParts.push(`UNFILLED AI SLOTS (autoSpawn):`, ...unfilledSlots.map(s => ` - ${s.role} (max ${s.maxInstances ?? 1})`), ``, `These roles need AI agents. Spawn independent teammates — each gets its own MCP session and identity.`, ` Agent(subagent_type="general-purpose", model="sonnet", run_in_background=true,`, ` prompt="Call connect with role='ROLE'. Then play your role.")`, ``);
477
467
  }
478
- // Mode-specific instructions
479
- if (agentMode === "behavior") {
480
- outputParts.push(`BEHAVIOR MODE:`, ` You are a behavior-only agent. All actions run autonomously via the tick engine.`, ` No stop hook — you will NOT receive wake-up events.`, ` Set up behaviors now, then disconnect. The tick engine runs them automatically.`, ``);
481
- }
482
- else if (agentMode === "manual") {
483
- outputParts.push(`MANUAL MODE:`, ` You are a manual agent. Use act() for all decisions. No behaviors.`, ` The stop hook keeps you present — use act to interact.`, ` Use act() to interact. The stop hook keeps you present.`, ``);
484
- }
485
- else {
486
- outputParts.push(`FAST BRAIN / SLOW BRAIN:`, ` Fast brain: Register behaviors via _behavior.set for reactive, per-tick actions that run automatically.`, ` Slow brain: Use act() for strategic decisions and adapting to new situations.`, ` Set up your fast brain FIRST, then use your slow brain to observe and adapt.`, ` Use act() to interact. The stop hook keeps you present.`, ``, `You are now a live participant. The stop hook keeps you present — use act to interact.`);
487
- }
468
+ outputParts.push(`Use act() to interact. The stop hook keeps you present.`);
488
469
  // Register the stop hook so Claude Code wakes us on events
489
470
  ensureStopHook();
490
471
  return { content: [{ type: "text", text: outputParts.filter(Boolean).join("\n") }] };
package/dist/server.js CHANGED
@@ -195,8 +195,6 @@ class Room {
195
195
  const detail = {
196
196
  actorId, type: p.type, role: p.role, owner: p.owner,
197
197
  };
198
- if (p.agentMode)
199
- detail.agentMode = p.agentMode;
200
198
  if (p.metadata && Object.keys(p.metadata).length > 0)
201
199
  detail.metadata = p.metadata;
202
200
  return detail;
@@ -581,11 +579,9 @@ app.post("/join", (req, res) => {
581
579
  res.status(500).json({ error: experienceNotLoadedError() });
582
580
  return;
583
581
  }
584
- const { username = "user", actorType: rawActorType = "human", owner, role: requestedRole, agentMode: rawAgentMode, metadata: rawMetadata } = req.body;
582
+ const { username = "user", actorType: rawActorType = "human", owner, role: requestedRole, metadata: rawMetadata } = req.body;
585
583
  const actorType = rawActorType === "ai" ? "ai" : "human";
586
584
  const resolvedOwner = owner || username;
587
- const VALID_AGENT_MODES = ["behavior", "manual", "hybrid"];
588
- const agentMode = actorType === "ai" && typeof rawAgentMode === "string" && VALID_AGENT_MODES.includes(rawAgentMode) ? rawAgentMode : undefined;
589
585
  let metadata;
590
586
  if (rawMetadata && typeof rawMetadata === "object" && !Array.isArray(rawMetadata)) {
591
587
  metadata = {};
@@ -719,8 +715,6 @@ app.post("/join", (req, res) => {
719
715
  participant.systemPrompt = slotSystemPrompt;
720
716
  if (!slotRole && requestedRole)
721
717
  participant.role = requestedRole;
722
- if (agentMode)
723
- participant.agentMode = agentMode;
724
718
  if (metadata)
725
719
  participant.metadata = metadata;
726
720
  room.participants.set(actorId, participant);
@@ -925,11 +919,6 @@ async function executeTool(toolName, actorId, input = {}, owner, expiredFlag) {
925
919
  observation,
926
920
  });
927
921
  roomEvents.emit("room");
928
- const baseTool = toolName.includes(':') ? toolName.split(':').pop() : toolName;
929
- if (baseTool.startsWith("_behavior.")) {
930
- if (tickEngine)
931
- tickEngine.markDirty();
932
- }
933
922
  return { tool: toolName, output, observation };
934
923
  }
935
924
  // ── Single tool HTTP endpoint ───────────────────────────────
@@ -1819,8 +1808,6 @@ export async function startServer(config) {
1819
1808
  for (const [actorId, p] of room.participants) {
1820
1809
  if (p.type !== "ai")
1821
1810
  continue;
1822
- if (p.agentMode === "behavior")
1823
- continue;
1824
1811
  const lastSeen = p.lastPollAt || p.joinedAt;
1825
1812
  if (now - lastSeen > AI_HEARTBEAT_TIMEOUT_MS)
1826
1813
  toEvict.push(actorId);
package/hooks/logic.js CHANGED
@@ -92,9 +92,7 @@ export function formatPrompt(ctx) {
92
92
  }
93
93
  // ── 3. Events (what happened since last wake-up) ──────
94
94
  if (ctx.events && ctx.events.length > 0) {
95
- // Filter out noise: _behavior.* (setup), tick engine events (_tick-engine / _system)
96
- const visibleEvents = ctx.events.filter((e) => !(e.tool || "").startsWith("_behavior.") &&
97
- e.actorId !== "_tick-engine" &&
95
+ const visibleEvents = ctx.events.filter((e) => e.actorId !== "_tick-engine" &&
98
96
  e.owner !== "_system");
99
97
  if (visibleEvents.length > 0) {
100
98
  for (const e of visibleEvents) {
@@ -115,16 +113,6 @@ export function formatPrompt(ctx) {
115
113
  parts.push(` ${roomId} (${info.experience}, ${p} participant${p !== 1 ? "s" : ""})`);
116
114
  }
117
115
  }
118
- // Tick engine status — only show if something interesting
119
- if (ctx.tickEngines) {
120
- const activeEngines = Object.entries(ctx.tickEngines).filter(([, status]) => status.enabled && status.behaviorsActive > 0);
121
- if (activeEngines.length > 0) {
122
- parts.push("");
123
- for (const [roomId, status] of activeEngines) {
124
- parts.push(`Tick engine [${roomId}]: ${status.behaviorsActive} behavior(s) active, ${status.tickCount} ticks`);
125
- }
126
- }
127
- }
128
116
  // Participants (deduplicated)
129
117
  if (ctx.participants && ctx.participants.length > 0) {
130
118
  const unique = [...new Set(ctx.participants)];
@@ -139,16 +127,10 @@ export function formatPrompt(ctx) {
139
127
  * Returns null to allow exit (no state file = not in agent mode).
140
128
  * Returns a StopDecision to block exit and feed context to Claude.
141
129
  */
142
- export function makeDecision(ctx, iteration, agentState) {
143
- // No state file — allow normal exit
130
+ export function makeDecision(ctx, iteration) {
144
131
  if (ctx === null)
145
132
  return null;
146
- // Behavior-only agents never produce stop decisions they run autonomously via tick engine
147
- if (agentState?.agentMode === "behavior")
148
- return null;
149
- // Only count non-system events (tick engine events are noise)
150
- const realEvents = ctx.events?.filter((e) => !(e.tool || "").startsWith("_behavior.") &&
151
- e.actorId !== "_tick-engine" &&
133
+ const realEvents = ctx.events?.filter((e) => e.actorId !== "_tick-engine" &&
152
134
  e.owner !== "_system") || [];
153
135
  const hasEvents = realEvents.length > 0;
154
136
  const hasError = !!ctx.lastError;
@@ -165,13 +165,7 @@ async function main() {
165
165
  }
166
166
  // In-memory cursor per agent (not written to disk)
167
167
  const cursors = new Map(); // actorId → lastEventCursor
168
- // Track last-seen phase per agent for subscription: "phase" filtering
169
- const lastPhase = new Map(); // owner → last phase value
170
- // Build the list of OUR agents from agent files (not all AI agents on server).
171
- // Each Claude Code workspace only polls its own agents — prevents cross-terminal
172
- // observation merging that causes identity confusion (wrong myClaimedHero, wrong role).
173
- // Reuse the already-loaded agents array — reconciliation may have deleted stale files,
174
- // so filter to only those whose files still exist on disk.
168
+ // Build the list of OUR agents from agent files.
175
169
  const ourAgents = agents
176
170
  .filter(a => existsSync(resolve(AGENTS_DIR, a._filename || `${a.owner}.json`)))
177
171
  .map((a) => ({
@@ -182,16 +176,7 @@ async function main() {
182
176
  _filename: a._filename,
183
177
  serverUrl: a.serverUrl || session.serverUrl,
184
178
  roomId: a.roomId || undefined,
185
- subscription: a.subscription || "all",
186
- agentMode: a.agentMode || undefined,
187
- lastPhase: a.lastPhase,
188
179
  }));
189
- // Initialize lastPhase from persisted agent files (survives across invocations)
190
- for (const agent of ourAgents) {
191
- if (agent.subscription === "phase" && agent.lastPhase !== undefined) {
192
- lastPhase.set(agent.owner, agent.lastPhase ?? null);
193
- }
194
- }
195
180
  if (ourAgents.length === 0) {
196
181
  cleanupAndExit(agents.map(a => a._filename || `${a.owner}.json`));
197
182
  return;
@@ -242,68 +227,13 @@ async function main() {
242
227
  r.ctx.lastError ||
243
228
  r.ctx.observeError ||
244
229
  (r.ctx.browserErrors != null && r.ctx.browserErrors.length > 0)));
245
- // Filter out behavior-only agents — they run autonomously via tick engine, never need waking
246
- live = live.filter((r) => r.agent.agentMode !== "behavior");
247
- // Apply subscription filtering: "phase" agents only wake on phase changes or errors
248
- live = live.filter((r) => {
249
- if (r.agent.subscription !== "phase")
250
- return true; // "all" passes through
251
- // Always extract and persist current phase (even on error wakeups)
252
- const currentPhase = typeof r.ctx?.observation?.phase === "string"
253
- ? r.ctx.observation.phase : null;
254
- const prevPhase = lastPhase.get(r.agent.owner) ?? null;
255
- lastPhase.set(r.agent.owner, currentPhase);
256
- // Always wake on errors
257
- if (r.ctx?.lastError || r.ctx?.observeError ||
258
- (r.ctx?.browserErrors != null && r.ctx.browserErrors.length > 0))
259
- return true;
260
- // Phase changed — wake
261
- if (currentPhase !== prevPhase)
262
- return true;
263
- // Check for phase-related events from agent's own room only
264
- const phaseEvents = r.ctx?.events?.filter((e) => {
265
- if (e.roomId && r.agent.roomId && e.roomId !== r.agent.roomId)
266
- return false;
267
- return /phase|start|reset|end|finish/i.test(e.tool || "");
268
- }) || [];
269
- if (phaseEvents.length > 0)
270
- return true;
271
- return false; // Not a phase change — skip
272
- });
273
- // Persist lastPhase for phase-subscription agents across invocations.
274
- // Update agent.lastPhase in memory (bumpIteration writes it for woken agents).
275
- // For filtered-out agents, write to disk here so next invocation sees the correct phase.
276
- const liveOwners = new Set(live.map((r) => r.agent.owner));
277
- for (const { agent } of results) {
278
- if (agent.subscription !== "phase")
279
- continue;
280
- if (!lastPhase.has(agent.owner))
281
- continue;
282
- const phaseVal = lastPhase.get(agent.owner) ?? null;
283
- agent.lastPhase = phaseVal;
284
- if (!liveOwners.has(agent.owner)) {
285
- try {
286
- const filePath = resolve(AGENTS_DIR, agent._filename || `${agent.owner}.json`);
287
- const data = { ...agent };
288
- delete data._filename;
289
- writeFileSync(filePath, JSON.stringify(data, null, 2));
290
- }
291
- catch { /* Non-fatal — best-effort persistence */ }
292
- }
293
- }
294
230
  if (live.length > 0) {
295
231
  if (live.length > 1) {
296
232
  process.stderr.write(`[vibevibes] Warning: ${live.length} agent files found but expected 1. Using first agent only.\n`);
297
233
  }
298
234
  const { agent, ctx } = live[0];
299
235
  const iteration = doneOwners.has(agent.owner) ? (agent.iteration || 0) + 1 : bumpIteration(agent);
300
- const agentState = {
301
- roomId: agent.roomId || session.roomId,
302
- role: agent.role,
303
- iteration,
304
- agentMode: agent.agentMode,
305
- };
306
- const decision = makeDecision(ctx, iteration, agentState);
236
+ const decision = makeDecision(ctx, iteration);
307
237
  if (decision) {
308
238
  process.stdout.write(JSON.stringify(decision));
309
239
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibevibes/mcp",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "MCP server + runtime engine for vibevibes experiences",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",