@vibevibes/mcp 0.3.1 → 0.4.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 +2 -2
- package/dist/server.js +2 -22
- package/hooks/logic.js +5 -95
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -480,10 +480,10 @@ Call this first, then use act to interact. The stop hook keeps you present.`, {
|
|
|
480
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
481
|
}
|
|
482
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.`, `
|
|
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
484
|
}
|
|
485
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.`, `
|
|
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
487
|
}
|
|
488
488
|
// Register the stop hook so Claude Code wakes us on events
|
|
489
489
|
ensureStopHook();
|
package/dist/server.js
CHANGED
|
@@ -11,13 +11,12 @@ import http from "http";
|
|
|
11
11
|
import path from "path";
|
|
12
12
|
import fs from "fs";
|
|
13
13
|
import { fileURLToPath } from "url";
|
|
14
|
-
import {
|
|
14
|
+
import { ZodError } from "zod";
|
|
15
15
|
import { EventEmitter } from "events";
|
|
16
16
|
import { bundleForServer, bundleForClient, evalServerBundle, validateClientBundle } from "./bundler.js";
|
|
17
17
|
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
18
18
|
import { TickEngine } from "./tick-engine.js";
|
|
19
19
|
import { isProtocolExperience, loadProtocolManifest, createProtocolModule, SubprocessExecutor } from "./protocol.js";
|
|
20
|
-
import { createChatTools } from "@vibevibes/sdk";
|
|
21
20
|
function formatZodError(err, toolName, tool) {
|
|
22
21
|
const issues = err.issues.map((issue) => {
|
|
23
22
|
const path = issue.path.length > 0 ? `'${issue.path.join(".")}'` : "input";
|
|
@@ -375,9 +374,6 @@ async function loadExperience() {
|
|
|
375
374
|
if (!mod?.manifest || !mod?.tools) {
|
|
376
375
|
throw new Error(`Experience at ${entryPath} missing manifest or tools`);
|
|
377
376
|
}
|
|
378
|
-
if (!mod.tools.some((t) => t.name === '_chat.send')) {
|
|
379
|
-
mod.tools.push(...createChatTools(z));
|
|
380
|
-
}
|
|
381
377
|
const clientError = validateClientBundle(cCode);
|
|
382
378
|
if (clientError) {
|
|
383
379
|
throw new Error(`Client bundle validation failed for ${entryPath}: ${clientError}`);
|
|
@@ -405,9 +401,6 @@ async function loadProtocolExperience(manifestPath) {
|
|
|
405
401
|
}
|
|
406
402
|
catch { }
|
|
407
403
|
const mod = createProtocolModule(manifest, executor, manifestDir);
|
|
408
|
-
if (!mod.tools.some((t) => t.name === '_chat.send')) {
|
|
409
|
-
mod.tools.push(...createChatTools(z));
|
|
410
|
-
}
|
|
411
404
|
loadedExperience = {
|
|
412
405
|
module: mod,
|
|
413
406
|
clientBundle: "",
|
|
@@ -461,20 +454,7 @@ export function defineExperience(c) { return c; }
|
|
|
461
454
|
export function defineTool(c) { return { risk: "low", capabilities_required: [], ...c }; }
|
|
462
455
|
export function defineTest(c) { return c; }
|
|
463
456
|
export function defineStream(c) { return c; }
|
|
464
|
-
export function createChatTools(
|
|
465
|
-
return [
|
|
466
|
-
{ name: "_chat.send", description: "Send a chat message", risk: "low", capabilities_required: ["state.write"],
|
|
467
|
-
input_schema: z.object({ message: z.string() }),
|
|
468
|
-
handler: async (ctx, input) => {
|
|
469
|
-
const chat = Array.isArray(ctx.state._chat) ? ctx.state._chat : [];
|
|
470
|
-
ctx.setState({ ...ctx.state, _chat: [...chat.slice(-99), { actorId: ctx.actorId, message: input.message, ts: Date.now() }] });
|
|
471
|
-
return { sent: true };
|
|
472
|
-
}},
|
|
473
|
-
{ name: "_chat.clear", description: "Clear chat", risk: "low", capabilities_required: ["state.write"],
|
|
474
|
-
input_schema: z.object({}),
|
|
475
|
-
handler: async (ctx) => { ctx.setState({ ...ctx.state, _chat: [] }); return { cleared: true }; }},
|
|
476
|
-
];
|
|
477
|
-
}
|
|
457
|
+
export function createChatTools() { return []; }
|
|
478
458
|
`);
|
|
479
459
|
});
|
|
480
460
|
// ── State endpoint ─────────────────────────────────────────
|
package/hooks/logic.js
CHANGED
|
@@ -37,13 +37,7 @@ function smartTruncate(input, maxLen = 150) {
|
|
|
37
37
|
* Format an agent context into a readable prompt string.
|
|
38
38
|
* This is what Claude sees as the "reason" when the Stop hook blocks exit.
|
|
39
39
|
*
|
|
40
|
-
* Layout
|
|
41
|
-
* 1. Errors (browser, tool, observe)
|
|
42
|
-
* 2. Observation (curated state — the "what matters")
|
|
43
|
-
* 3. Chat messages (human communication)
|
|
44
|
-
* 4. Other events (raw details)
|
|
45
|
-
* 5. Room/tick/participant context
|
|
46
|
-
* 6. Instructions
|
|
40
|
+
* Layout: errors > observation > events > participants
|
|
47
41
|
*/
|
|
48
42
|
export function formatPrompt(ctx) {
|
|
49
43
|
const parts = [];
|
|
@@ -102,60 +96,12 @@ export function formatPrompt(ctx) {
|
|
|
102
96
|
const visibleEvents = ctx.events.filter((e) => !(e.tool || "").startsWith("_behavior.") &&
|
|
103
97
|
e.actorId !== "_tick-engine" &&
|
|
104
98
|
e.owner !== "_system");
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
if (chatEvents.length > 0) {
|
|
109
|
-
parts.push(`CHAT MESSAGE${chatEvents.length > 1 ? "S" : ""}:`);
|
|
110
|
-
for (const e of chatEvents) {
|
|
111
|
-
const actor = e.role || e.owner || (e.actorId ? e.actorId.split("-")[0] : "unknown");
|
|
112
|
-
const roomTag = e.roomId ? `[${e.roomId}] ` : "";
|
|
113
|
-
const time = fmtTime(e.ts);
|
|
114
|
-
const input = e.input;
|
|
115
|
-
// Sanitize chat messages to prevent prompt injection via newlines, ANSI escapes, control chars
|
|
116
|
-
const safeMsg = (input?.message || "").replace(/[\x00-\x1f\x7f]/g, " ").slice(0, 500);
|
|
117
|
-
parts.push(` ${time} ${roomTag}${actor}: "${safeMsg}"`);
|
|
118
|
-
}
|
|
119
|
-
parts.push("");
|
|
120
|
-
}
|
|
121
|
-
// Team chat messages (from _chat.team tool calls)
|
|
122
|
-
const teamChatEvents = visibleEvents.filter((e) => e.tool === "_chat.team");
|
|
123
|
-
if (teamChatEvents.length > 0) {
|
|
124
|
-
// Group by team
|
|
125
|
-
const byTeam = new Map();
|
|
126
|
-
for (const e of teamChatEvents) {
|
|
127
|
-
const input = e.input;
|
|
128
|
-
const team = (input?.team || "unknown").replace(/[\x00-\x1f\x7f]/g, " ").slice(0, 50);
|
|
129
|
-
if (!byTeam.has(team))
|
|
130
|
-
byTeam.set(team, []);
|
|
131
|
-
byTeam.get(team).push(e);
|
|
132
|
-
}
|
|
133
|
-
for (const [team, events] of byTeam) {
|
|
134
|
-
parts.push(`TEAM CHAT (${team}):`);
|
|
135
|
-
for (const e of events) {
|
|
136
|
-
const actor = e.role || e.owner || (e.actorId ? e.actorId.split("-")[0] : "unknown");
|
|
137
|
-
const time = fmtTime(e.ts);
|
|
138
|
-
const input = e.input;
|
|
139
|
-
const safeMsg = (input?.message || "").replace(/[\x00-\x1f\x7f]/g, " ").slice(0, 500);
|
|
140
|
-
parts.push(` ${time} ${actor}: "${safeMsg}"`);
|
|
141
|
-
}
|
|
142
|
-
parts.push("");
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
if (otherEvents.length > 0) {
|
|
146
|
-
// Detect multi-room activity
|
|
147
|
-
const roomIds = new Set(otherEvents.map((e) => e.roomId).filter(Boolean));
|
|
148
|
-
const multiRoom = roomIds.size > 1;
|
|
149
|
-
const header = multiRoom
|
|
150
|
-
? `${otherEvents.length} event(s) across ${roomIds.size} rooms:`
|
|
151
|
-
: `${otherEvents.length} event(s) since your last action:`;
|
|
152
|
-
parts.push(header);
|
|
153
|
-
for (const e of otherEvents) {
|
|
154
|
-
const actor = e.role || e.owner || (e.actorId ? e.actorId.split("-")[0] : "unknown");
|
|
99
|
+
if (visibleEvents.length > 0) {
|
|
100
|
+
for (const e of visibleEvents) {
|
|
101
|
+
const actor = e.role || e.owner || (e.actorId ? e.actorId.split("-")[0] : "?");
|
|
155
102
|
const inputStr = smartTruncate(e.input);
|
|
156
|
-
const roomTag = e.roomId ? `[${e.roomId}] ` : "";
|
|
157
103
|
const time = fmtTime(e.ts);
|
|
158
|
-
parts.push(
|
|
104
|
+
parts.push(`${time} [${actor}] ${e.tool}(${inputStr})`);
|
|
159
105
|
}
|
|
160
106
|
}
|
|
161
107
|
}
|
|
@@ -185,25 +131,6 @@ export function formatPrompt(ctx) {
|
|
|
185
131
|
parts.push("");
|
|
186
132
|
parts.push(`Participants: ${unique.join(", ")}`);
|
|
187
133
|
}
|
|
188
|
-
// ── 5. Critical event detection ───────────────────────
|
|
189
|
-
if (ctx.events && ctx.events.length > 0) {
|
|
190
|
-
const criticalPatterns = /kill|destroy|tower|ace|gold|death|victory|defeat|eliminated|reset/i;
|
|
191
|
-
const criticalEvents = ctx.events.filter((e) => {
|
|
192
|
-
const toolStr = e.tool || "";
|
|
193
|
-
const outputStr = typeof e.output === "string" ? e.output : "";
|
|
194
|
-
return criticalPatterns.test(toolStr) || criticalPatterns.test(outputStr);
|
|
195
|
-
});
|
|
196
|
-
if (criticalEvents.length > 0) {
|
|
197
|
-
parts.push("");
|
|
198
|
-
const eventTypes = [...new Set(criticalEvents.map(e => e.tool || "event"))].slice(0, 5).join(", ");
|
|
199
|
-
parts.push(`CRITICAL EVENT DETECTED: ${eventTypes}. Consider adjusting your behavior preset via _behavior.activate to adapt to the new situation.`);
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
// ── 6. Instructions ───────────────────────────────────
|
|
203
|
-
parts.push("");
|
|
204
|
-
parts.push("New activity in the experience. Use the act MCP tool with roomId to respond in the right room. If nothing requires a response, you may observe without acting.");
|
|
205
|
-
parts.push("COMMUNICATE: Use _chat.send to share strategy with all players. Use _chat.team for team-only coordination. Good agents communicate intentions before acting.");
|
|
206
|
-
parts.push("IMPORTANT: You are a game participant. ONLY use MCP tools (act, look, stream, memory). NEVER use Bash, rm, or any terminal/file commands.");
|
|
207
134
|
return parts.join("\n");
|
|
208
135
|
}
|
|
209
136
|
/**
|
|
@@ -234,25 +161,8 @@ export function makeDecision(ctx, iteration, agentState) {
|
|
|
234
161
|
if (!hasEvents && !hasError && !hasBrowserErrors && !hasObserveError)
|
|
235
162
|
return null;
|
|
236
163
|
const reason = formatPrompt(ctx);
|
|
237
|
-
const iter = iteration || 0;
|
|
238
|
-
// Build richer systemMessage with experience + role context
|
|
239
|
-
const mode = agentState?.agentMode || "hybrid";
|
|
240
|
-
const parts = [`vibevibes iteration ${iter}`];
|
|
241
|
-
if (agentState?.roomId)
|
|
242
|
-
parts.push(`Room: ${agentState.roomId}`);
|
|
243
|
-
if (agentState?.role)
|
|
244
|
-
parts.push(`Role: ${agentState.role}`);
|
|
245
|
-
parts.push(`Mode: ${mode}`);
|
|
246
|
-
if (mode === "manual") {
|
|
247
|
-
parts.push(`You are a manual agent. Use act() for all decisions. No behaviors. Communicate strategy via chat.`);
|
|
248
|
-
}
|
|
249
|
-
else {
|
|
250
|
-
parts.push(`You are a live participant. Fast brain: _behavior.set for reactive per-tick actions. Slow brain: act() for strategic decisions. Communicate strategy via chat. Adapt behaviors on critical events.`);
|
|
251
|
-
}
|
|
252
|
-
parts.push(`ONLY use MCP tools. NEVER use Bash, rm, or any terminal/file commands.`);
|
|
253
164
|
return {
|
|
254
165
|
decision: "block",
|
|
255
166
|
reason,
|
|
256
|
-
systemMessage: parts.join(" | "),
|
|
257
167
|
};
|
|
258
168
|
}
|