@vibevibes/mcp 0.4.0 → 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/bundler.js CHANGED
@@ -74,7 +74,7 @@ function stripExternalImports(code, externals = EXTERNALS) {
74
74
  * CJS shim definitions for server-side eval (new Function()).
75
75
  * Maps esbuild-generated variable names to runtime-provided globals.
76
76
  */
77
- const SDK_CORE_SHIM = "{ defineExperience: defineExperience, defineTool: defineTool, defineTest: defineTest, defineStream: defineStream, createChatTools: createChatTools, default: { defineExperience: defineExperience, defineTool: defineTool, defineTest: defineTest, defineStream: defineStream, createChatTools: createChatTools } }";
77
+ const SDK_CORE_SHIM = "{ defineExperience: defineExperience, defineTool: defineTool, defineTest: defineTest, defineStream: defineStream, default: { defineExperience: defineExperience, defineTool: defineTool, defineTest: defineTest, defineStream: defineStream } }";
78
78
  const CJS_BASE_SHIMS = {
79
79
  import_react: "{ default: React, __esModule: true, createElement: React.createElement, Fragment: React.Fragment, useState: React.useState, useEffect: React.useEffect, useCallback: React.useCallback, useMemo: React.useMemo, useRef: React.useRef, useContext: React.useContext, useReducer: React.useReducer, createContext: React.createContext, forwardRef: React.forwardRef, memo: React.memo }",
80
80
  import_zod: "{ z: z, default: z }",
@@ -182,7 +182,7 @@ export async function bundleForServer(entryPath) {
182
182
  */
183
183
  export async function evalServerBundle(serverCode) {
184
184
  const sdk = await import("@vibevibes/sdk");
185
- const { defineExperience, defineTool, defineTest, defineStream, createChatTools } = sdk;
185
+ const { defineExperience, defineTool, defineTest, defineStream } = sdk;
186
186
  const noop = () => null;
187
187
  const stubReact = {
188
188
  createElement: noop, Fragment: "Fragment",
@@ -194,11 +194,11 @@ export async function evalServerBundle(serverCode) {
194
194
  };
195
195
  const zodModule = await import("zod");
196
196
  const z = zodModule.z ?? zodModule.default ?? zodModule;
197
- const fn = new Function("globalThis", "process", "global", "React", "Y", "z", "defineExperience", "defineTool", "defineTest", "defineStream", "createChatTools", "require", "exports", "module", "console", `"use strict";\n${serverCode}\nreturn typeof __experience_export__ !== 'undefined' ? __experience_export__ : (typeof module !== 'undefined' ? module.exports : undefined);`);
197
+ const fn = new Function("globalThis", "process", "global", "React", "Y", "z", "defineExperience", "defineTool", "defineTest", "defineStream", "require", "exports", "module", "console", `"use strict";\n${serverCode}\nreturn typeof __experience_export__ !== 'undefined' ? __experience_export__ : (typeof module !== 'undefined' ? module.exports : undefined);`);
198
198
  const fakeModule = { exports: {} };
199
199
  const sandboxGlobal = Object.create(null);
200
200
  const sandboxProcess = { env: { NODE_ENV: "production" } };
201
- const result = fn(sandboxGlobal, sandboxProcess, sandboxGlobal, stubReact, {}, z, defineExperience, defineTool, defineTest, defineStream, createChatTools, (id) => { throw new Error(`require('${id}') is not supported in the vibevibes server sandbox. Add '${id}' to EXTERNALS in bundler.ts.`); }, fakeModule.exports, fakeModule, console);
201
+ const result = fn(sandboxGlobal, sandboxProcess, sandboxGlobal, stubReact, {}, z, defineExperience, defineTool, defineTest, defineStream, (id) => { throw new Error(`require('${id}') is not supported in the vibevibes server sandbox. Add '${id}' to EXTERNALS in bundler.ts.`); }, fakeModule.exports, fakeModule, console);
202
202
  const exports = fakeModule.exports;
203
203
  return result?.default ?? result ?? exports?.default ?? exports;
204
204
  }
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;
@@ -454,7 +452,6 @@ export function defineExperience(c) { return c; }
454
452
  export function defineTool(c) { return { risk: "low", capabilities_required: [], ...c }; }
455
453
  export function defineTest(c) { return c; }
456
454
  export function defineStream(c) { return c; }
457
- export function createChatTools() { return []; }
458
455
  `);
459
456
  });
460
457
  // ── State endpoint ─────────────────────────────────────────
@@ -582,11 +579,9 @@ app.post("/join", (req, res) => {
582
579
  res.status(500).json({ error: experienceNotLoadedError() });
583
580
  return;
584
581
  }
585
- 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;
586
583
  const actorType = rawActorType === "ai" ? "ai" : "human";
587
584
  const resolvedOwner = owner || username;
588
- const VALID_AGENT_MODES = ["behavior", "manual", "hybrid"];
589
- const agentMode = actorType === "ai" && typeof rawAgentMode === "string" && VALID_AGENT_MODES.includes(rawAgentMode) ? rawAgentMode : undefined;
590
585
  let metadata;
591
586
  if (rawMetadata && typeof rawMetadata === "object" && !Array.isArray(rawMetadata)) {
592
587
  metadata = {};
@@ -720,8 +715,6 @@ app.post("/join", (req, res) => {
720
715
  participant.systemPrompt = slotSystemPrompt;
721
716
  if (!slotRole && requestedRole)
722
717
  participant.role = requestedRole;
723
- if (agentMode)
724
- participant.agentMode = agentMode;
725
718
  if (metadata)
726
719
  participant.metadata = metadata;
727
720
  room.participants.set(actorId, participant);
@@ -926,11 +919,6 @@ async function executeTool(toolName, actorId, input = {}, owner, expiredFlag) {
926
919
  observation,
927
920
  });
928
921
  roomEvents.emit("room");
929
- const baseTool = toolName.includes(':') ? toolName.split(':').pop() : toolName;
930
- if (baseTool.startsWith("_behavior.")) {
931
- if (tickEngine)
932
- tickEngine.markDirty();
933
- }
934
922
  return { tool: toolName, output, observation };
935
923
  }
936
924
  // ── Single tool HTTP endpoint ───────────────────────────────
@@ -1820,8 +1808,6 @@ export async function startServer(config) {
1820
1808
  for (const [actorId, p] of room.participants) {
1821
1809
  if (p.type !== "ai")
1822
1810
  continue;
1823
- if (p.agentMode === "behavior")
1824
- continue;
1825
1811
  const lastSeen = p.lastPollAt || p.joinedAt;
1826
1812
  if (now - lastSeen > AI_HEARTBEAT_TIMEOUT_MS)
1827
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.0",
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",