@vibevibes/mcp 0.2.0 → 0.3.1
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/LICENSE +21 -21
- package/README.md +69 -201
- package/bin/cli.js +11 -11
- package/bin/postinstall.js +39 -0
- package/bin/serve.js +41 -0
- package/dist/bundler.d.ts +36 -0
- package/dist/bundler.js +254 -0
- package/dist/index.d.ts +4 -9
- package/dist/index.js +687 -468
- package/dist/protocol.d.ts +85 -0
- package/dist/protocol.js +240 -0
- package/dist/server.d.ts +17 -0
- package/dist/server.js +1947 -0
- package/dist/tick-engine.d.ts +81 -0
- package/dist/tick-engine.js +151 -0
- package/dist/viewer/index.html +689 -0
- package/hooks/logic.js +258 -0
- package/hooks/stop-hook.js +341 -0
- package/package.json +59 -33
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tick Engine — server-side tick loop for experiences with netcode: "tick".
|
|
3
|
+
*
|
|
4
|
+
* Runs at a fixed tick rate, executing tool calls from the tick context.
|
|
5
|
+
* The tick engine provides the timing infrastructure; experiences define
|
|
6
|
+
* what happens each tick via their tool handlers.
|
|
7
|
+
*/
|
|
8
|
+
import type { ToolCtx, ToolEvent } from "@vibevibes/sdk";
|
|
9
|
+
import type { EventEmitter } from "events";
|
|
10
|
+
/** ToolEvent extended with observation field (computed server-side after tool execution). */
|
|
11
|
+
interface TickToolEvent extends ToolEvent {
|
|
12
|
+
observation?: Record<string, unknown>;
|
|
13
|
+
}
|
|
14
|
+
/** Minimal Room interface — what the tick engine needs from a room. */
|
|
15
|
+
export interface TickRoom {
|
|
16
|
+
readonly id: string;
|
|
17
|
+
readonly experienceId: string;
|
|
18
|
+
readonly config: Record<string, unknown>;
|
|
19
|
+
sharedState: Record<string, unknown>;
|
|
20
|
+
participantList(): string[];
|
|
21
|
+
broadcastToAll(message: Record<string, unknown>): void;
|
|
22
|
+
broadcastStateUpdate?(extra: {
|
|
23
|
+
changedBy: string;
|
|
24
|
+
tick?: unknown;
|
|
25
|
+
}, forceFullState?: boolean): void;
|
|
26
|
+
appendEvent(event: TickToolEvent): void;
|
|
27
|
+
enqueueExecution<T>(fn: () => Promise<T>): Promise<T>;
|
|
28
|
+
}
|
|
29
|
+
/** Minimal experience interface — what the tick engine needs. */
|
|
30
|
+
export interface TickExperience {
|
|
31
|
+
module: {
|
|
32
|
+
manifest: {
|
|
33
|
+
id: string;
|
|
34
|
+
};
|
|
35
|
+
tools: Array<{
|
|
36
|
+
name: string;
|
|
37
|
+
input_schema?: {
|
|
38
|
+
parse?: (input: unknown) => unknown;
|
|
39
|
+
};
|
|
40
|
+
handler: (ctx: ToolCtx, input: unknown) => Promise<unknown>;
|
|
41
|
+
}>;
|
|
42
|
+
observe?: (state: Record<string, unknown>, event: TickToolEvent, actorId: string) => Record<string, unknown>;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
export declare class TickEngine {
|
|
46
|
+
private room;
|
|
47
|
+
private experience;
|
|
48
|
+
private roomEvents;
|
|
49
|
+
private interval;
|
|
50
|
+
private tickCount;
|
|
51
|
+
private tickRateMs;
|
|
52
|
+
private startedAt;
|
|
53
|
+
private _running;
|
|
54
|
+
private _stopped;
|
|
55
|
+
private _stateSetThisTick;
|
|
56
|
+
constructor(room: TickRoom, experience: TickExperience, roomEvents: EventEmitter, tickRateMs?: number);
|
|
57
|
+
/** Start the tick loop. Uses self-correcting setTimeout to prevent timing drift. */
|
|
58
|
+
start(): void;
|
|
59
|
+
/** Schedule the next tick with drift correction. */
|
|
60
|
+
private scheduleNext;
|
|
61
|
+
/** Stop the tick loop. */
|
|
62
|
+
stop(): void;
|
|
63
|
+
/** Mark dirty — no-op in simplified engine, kept for API compat. */
|
|
64
|
+
markDirty(): void;
|
|
65
|
+
/** Get current tick status. */
|
|
66
|
+
getStatus(): {
|
|
67
|
+
enabled: boolean;
|
|
68
|
+
tickRateMs: number;
|
|
69
|
+
tickCount: number;
|
|
70
|
+
};
|
|
71
|
+
/** Execute one tick cycle. */
|
|
72
|
+
private tick;
|
|
73
|
+
/** Execute a tool call internally (no HTTP round-trip). */
|
|
74
|
+
executeTool(toolName: string, input: Record<string, unknown>, callerActorId?: string, expiredFlag?: {
|
|
75
|
+
value: boolean;
|
|
76
|
+
}): Promise<{
|
|
77
|
+
output?: unknown;
|
|
78
|
+
error?: string;
|
|
79
|
+
}>;
|
|
80
|
+
}
|
|
81
|
+
export {};
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tick Engine — server-side tick loop for experiences with netcode: "tick".
|
|
3
|
+
*
|
|
4
|
+
* Runs at a fixed tick rate, executing tool calls from the tick context.
|
|
5
|
+
* The tick engine provides the timing infrastructure; experiences define
|
|
6
|
+
* what happens each tick via their tool handlers.
|
|
7
|
+
*/
|
|
8
|
+
// ── Tick Engine ───────────────────────────────────────────────────────────────
|
|
9
|
+
const TICK_ACTOR_ID = "_tick-engine";
|
|
10
|
+
const TICK_OWNER = "_system";
|
|
11
|
+
export class TickEngine {
|
|
12
|
+
room;
|
|
13
|
+
experience;
|
|
14
|
+
roomEvents;
|
|
15
|
+
interval = null;
|
|
16
|
+
tickCount = 0;
|
|
17
|
+
tickRateMs;
|
|
18
|
+
startedAt = 0;
|
|
19
|
+
_running = false;
|
|
20
|
+
_stopped = false;
|
|
21
|
+
_stateSetThisTick = false;
|
|
22
|
+
constructor(room, experience, roomEvents, tickRateMs = 50) {
|
|
23
|
+
this.room = room;
|
|
24
|
+
this.experience = experience;
|
|
25
|
+
this.roomEvents = roomEvents;
|
|
26
|
+
this.tickRateMs = tickRateMs;
|
|
27
|
+
}
|
|
28
|
+
/** Start the tick loop. Uses self-correcting setTimeout to prevent timing drift. */
|
|
29
|
+
start() {
|
|
30
|
+
if (this.interval)
|
|
31
|
+
return;
|
|
32
|
+
this._stopped = false;
|
|
33
|
+
this.tickCount = 0;
|
|
34
|
+
this.startedAt = Date.now();
|
|
35
|
+
this.scheduleNext();
|
|
36
|
+
}
|
|
37
|
+
/** Schedule the next tick with drift correction. */
|
|
38
|
+
scheduleNext() {
|
|
39
|
+
if (this._stopped)
|
|
40
|
+
return;
|
|
41
|
+
const expected = this.startedAt + (this.tickCount + 1) * this.tickRateMs;
|
|
42
|
+
const delay = Math.max(0, expected - Date.now());
|
|
43
|
+
this.interval = setTimeout(() => {
|
|
44
|
+
this.tick().then(() => this.scheduleNext(), () => this.scheduleNext());
|
|
45
|
+
}, delay);
|
|
46
|
+
}
|
|
47
|
+
/** Stop the tick loop. */
|
|
48
|
+
stop() {
|
|
49
|
+
this._stopped = true;
|
|
50
|
+
this._running = false;
|
|
51
|
+
if (this.interval) {
|
|
52
|
+
clearTimeout(this.interval);
|
|
53
|
+
this.interval = null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/** Mark dirty — no-op in simplified engine, kept for API compat. */
|
|
57
|
+
markDirty() { }
|
|
58
|
+
/** Get current tick status. */
|
|
59
|
+
getStatus() {
|
|
60
|
+
return {
|
|
61
|
+
enabled: this.interval !== null,
|
|
62
|
+
tickRateMs: this.tickRateMs,
|
|
63
|
+
tickCount: this.tickCount,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/** Execute one tick cycle. */
|
|
67
|
+
async tick() {
|
|
68
|
+
if (this._running)
|
|
69
|
+
return;
|
|
70
|
+
this._running = true;
|
|
71
|
+
try {
|
|
72
|
+
this.tickCount++;
|
|
73
|
+
}
|
|
74
|
+
finally {
|
|
75
|
+
this._running = false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/** Execute a tool call internally (no HTTP round-trip). */
|
|
79
|
+
async executeTool(toolName, input, callerActorId, expiredFlag) {
|
|
80
|
+
const tool = this.experience.module.tools.find((t) => t.name === toolName);
|
|
81
|
+
if (!tool) {
|
|
82
|
+
return { error: `Tool '${toolName}' not found` };
|
|
83
|
+
}
|
|
84
|
+
const actorId = callerActorId || TICK_ACTOR_ID;
|
|
85
|
+
const owner = callerActorId || TICK_OWNER;
|
|
86
|
+
try {
|
|
87
|
+
let validatedInput = input;
|
|
88
|
+
if (tool.input_schema?.parse) {
|
|
89
|
+
validatedInput = tool.input_schema.parse(input);
|
|
90
|
+
}
|
|
91
|
+
const self = this;
|
|
92
|
+
const ctxBase = {
|
|
93
|
+
roomId: this.room.id,
|
|
94
|
+
actorId,
|
|
95
|
+
owner,
|
|
96
|
+
state: null,
|
|
97
|
+
setState: (newState) => {
|
|
98
|
+
if (expiredFlag?.value)
|
|
99
|
+
return;
|
|
100
|
+
self.room.sharedState = newState;
|
|
101
|
+
self._stateSetThisTick = true;
|
|
102
|
+
},
|
|
103
|
+
timestamp: Date.now(),
|
|
104
|
+
memory: {},
|
|
105
|
+
setMemory: () => { },
|
|
106
|
+
};
|
|
107
|
+
Object.defineProperty(ctxBase, 'state', {
|
|
108
|
+
get() { return self.room.sharedState; },
|
|
109
|
+
enumerable: true,
|
|
110
|
+
configurable: true,
|
|
111
|
+
});
|
|
112
|
+
const ctx = ctxBase;
|
|
113
|
+
const output = await tool.handler(ctx, validatedInput);
|
|
114
|
+
const event = {
|
|
115
|
+
id: `tick-${this.tickCount}-${toolName}-${Math.random().toString(36).slice(2, 6)}`,
|
|
116
|
+
ts: Date.now(),
|
|
117
|
+
actorId: TICK_ACTOR_ID,
|
|
118
|
+
owner: TICK_OWNER,
|
|
119
|
+
tool: toolName,
|
|
120
|
+
input: validatedInput,
|
|
121
|
+
output,
|
|
122
|
+
};
|
|
123
|
+
if (this.experience.module.observe) {
|
|
124
|
+
try {
|
|
125
|
+
event.observation = this.experience.module.observe(this.room.sharedState, event, actorId);
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
// Don't fail tick if observe throws
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
this.room.appendEvent(event);
|
|
132
|
+
this.roomEvents.emit(`room:${this.room.id}`);
|
|
133
|
+
return { output };
|
|
134
|
+
}
|
|
135
|
+
catch (err) {
|
|
136
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
137
|
+
const event = {
|
|
138
|
+
id: `tick-${this.tickCount}-${toolName}-${Math.random().toString(36).slice(2, 6)}`,
|
|
139
|
+
ts: Date.now(),
|
|
140
|
+
actorId: TICK_ACTOR_ID,
|
|
141
|
+
owner: TICK_OWNER,
|
|
142
|
+
tool: toolName,
|
|
143
|
+
input,
|
|
144
|
+
error: errorMsg,
|
|
145
|
+
};
|
|
146
|
+
this.room.appendEvent(event);
|
|
147
|
+
this.roomEvents.emit(`room:${this.room.id}`);
|
|
148
|
+
return { error: errorMsg };
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|