@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 +4 -4
- package/dist/index.js +4 -23
- package/dist/server.js +1 -15
- package/hooks/logic.js +3 -21
- package/hooks/stop-hook.js +2 -72
- package/package.json +1 -1
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,
|
|
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
|
|
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", "
|
|
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,
|
|
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
|
|
320
|
-
|
|
321
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
|
143
|
-
// No state file — allow normal exit
|
|
130
|
+
export function makeDecision(ctx, iteration) {
|
|
144
131
|
if (ctx === null)
|
|
145
132
|
return null;
|
|
146
|
-
|
|
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;
|
package/hooks/stop-hook.js
CHANGED
|
@@ -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
|
-
//
|
|
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
|
|
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
|
}
|