@putervision/behavior-mcp 0.1.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.
@@ -0,0 +1,1176 @@
1
+ import {
2
+ ExecutionEngine,
3
+ TriggerRegistry,
4
+ getVersion
5
+ } from "./chunk-FXYS43D6.js";
6
+ import {
7
+ BehaviorRegistry,
8
+ generateId,
9
+ getCurrentIsoString,
10
+ safeJsonParse,
11
+ safeJsonStringify,
12
+ verifyEventChain
13
+ } from "./chunk-QU5PNQP4.js";
14
+ import {
15
+ NotFoundError,
16
+ ValidationError,
17
+ getDb,
18
+ getProjectSlug,
19
+ getReadOnlyDb
20
+ } from "./chunk-L2LJRSDU.js";
21
+
22
+ // src/engine/metrics.ts
23
+ var MetricsEngine = class {
24
+ static recordMetrics(db, params) {
25
+ const id = generateId();
26
+ const now = getCurrentIsoString();
27
+ db.prepare(
28
+ `
29
+ INSERT INTO execution_metrics (
30
+ id, project, execution_id, session_id, intention_id, behavior_name,
31
+ status, tick_count, duration_ms, avg_tick_ms, max_tick_ms, stuck_count,
32
+ interrupt_count, category_metrics_json, created_at
33
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
34
+ `
35
+ ).run(
36
+ id,
37
+ params.project,
38
+ params.execution_id,
39
+ params.session_id ?? null,
40
+ params.intention_id ?? null,
41
+ params.behavior_name,
42
+ params.status,
43
+ params.tick_count,
44
+ params.duration_ms,
45
+ params.avg_tick_ms ?? 16.6,
46
+ params.max_tick_ms ?? 25,
47
+ params.stuck_count ?? 0,
48
+ params.interrupt_count ?? 0,
49
+ safeJsonStringify(params.category_metrics || {}),
50
+ now
51
+ );
52
+ return {
53
+ id,
54
+ project: params.project,
55
+ execution_id: params.execution_id,
56
+ session_id: params.session_id,
57
+ intention_id: params.intention_id,
58
+ behavior_name: params.behavior_name,
59
+ status: params.status,
60
+ tick_count: params.tick_count,
61
+ duration_ms: params.duration_ms,
62
+ avg_tick_ms: params.avg_tick_ms ?? 16.6,
63
+ max_tick_ms: params.max_tick_ms ?? 25,
64
+ stuck_count: params.stuck_count ?? 0,
65
+ interrupt_count: params.interrupt_count ?? 0,
66
+ category_metrics: params.category_metrics,
67
+ created_at: now
68
+ };
69
+ }
70
+ static getMetrics(db, params) {
71
+ let sql = "SELECT * FROM execution_metrics WHERE project = ?";
72
+ const sqlParams = [params.project];
73
+ if (params.execution_id) {
74
+ sql += " AND execution_id = ?";
75
+ sqlParams.push(params.execution_id);
76
+ }
77
+ if (params.behavior_name) {
78
+ sql += " AND behavior_name = ?";
79
+ sqlParams.push(params.behavior_name);
80
+ }
81
+ sql += " ORDER BY created_at DESC LIMIT ?";
82
+ sqlParams.push(params.limit || 50);
83
+ const rows = db.prepare(sql).all(...sqlParams);
84
+ return rows.map((r) => ({
85
+ id: r.id,
86
+ project: r.project,
87
+ execution_id: r.execution_id,
88
+ session_id: r.session_id,
89
+ intention_id: r.intention_id,
90
+ behavior_name: r.behavior_name,
91
+ status: r.status,
92
+ tick_count: r.tick_count,
93
+ duration_ms: r.duration_ms,
94
+ avg_tick_ms: r.avg_tick_ms,
95
+ max_tick_ms: r.max_tick_ms,
96
+ stuck_count: r.stuck_count,
97
+ interrupt_count: r.interrupt_count,
98
+ category_metrics: safeJsonParse(r.category_metrics_json, void 0),
99
+ created_at: r.created_at
100
+ }));
101
+ }
102
+ };
103
+
104
+ // src/engine/snapshots.ts
105
+ var SnapshotEngine = class {
106
+ static saveSnapshot(db, params) {
107
+ if (!params.name || typeof params.name !== "string") {
108
+ throw new ValidationError("Snapshot name is required.");
109
+ }
110
+ const behaviors = db.prepare("SELECT * FROM behavior_definitions WHERE project = ?").all(params.project);
111
+ const triggers = db.prepare("SELECT * FROM triggers WHERE project = ?").all(params.project);
112
+ const executions = db.prepare("SELECT * FROM execution_state WHERE project = ?").all(params.project);
113
+ const data = { behaviors, triggers, executions };
114
+ const id = generateId();
115
+ const now = getCurrentIsoString();
116
+ db.prepare(
117
+ `
118
+ INSERT INTO snapshots (id, project, name, description, data_json, created_at)
119
+ VALUES (?, ?, ?, ?, ?, ?)
120
+ ON CONFLICT(project, name) DO UPDATE SET
121
+ description = excluded.description,
122
+ data_json = excluded.data_json,
123
+ created_at = excluded.created_at
124
+ `
125
+ ).run(id, params.project, params.name, params.description ?? null, JSON.stringify(data), now);
126
+ return { snapshot_id: id, name: params.name, timestamp: now };
127
+ }
128
+ static restoreSnapshot(db, params) {
129
+ const row = db.prepare("SELECT * FROM snapshots WHERE project = ? AND name = ?").get(params.project, params.name);
130
+ if (!row) {
131
+ throw new ValidationError(
132
+ `Snapshot "${params.name}" not found for project "${params.project}".`
133
+ );
134
+ }
135
+ const data = safeJsonParse(row.data_json, { behaviors: [], triggers: [] });
136
+ db.transaction(() => {
137
+ db.prepare("DELETE FROM triggers WHERE project = ?").run(params.project);
138
+ db.prepare("DELETE FROM behavior_definitions WHERE project = ?").run(params.project);
139
+ if (Array.isArray(data.behaviors)) {
140
+ const stmt = db.prepare(`
141
+ INSERT INTO behavior_definitions (id, project, name, version, description, tree_json, tree_hash, is_active, metadata_json, client_request_id, created_at)
142
+ VALUES (@id, @project, @name, @version, @description, @tree_json, @tree_hash, @is_active, @metadata_json, @client_request_id, @created_at)
143
+ `);
144
+ for (const b of data.behaviors) stmt.run(b);
145
+ }
146
+ if (Array.isArray(data.triggers)) {
147
+ const stmt = db.prepare(`
148
+ INSERT INTO triggers (id, project, name, behavior_name, condition_type, condition_params_json, priority, cooldown_ms, last_fired_at, is_enabled, metadata_json, created_at, updated_at)
149
+ VALUES (@id, @project, @name, @behavior_name, @condition_type, @condition_params_json, @priority, @cooldown_ms, @last_fired_at, @is_enabled, @metadata_json, @created_at, @updated_at)
150
+ `);
151
+ for (const t of data.triggers) stmt.run(t);
152
+ }
153
+ })();
154
+ return {
155
+ restored_behaviors: data.behaviors?.length || 0,
156
+ restored_triggers: data.triggers?.length || 0
157
+ };
158
+ }
159
+ static listSnapshots(db, params) {
160
+ return db.prepare(
161
+ "SELECT id, name, description, created_at FROM snapshots WHERE project = ? ORDER BY created_at DESC LIMIT ?"
162
+ ).all(params.project, params.limit || 50);
163
+ }
164
+ };
165
+
166
+ // src/engine/watchdog.ts
167
+ var WatchdogTimer = class {
168
+ lastTickTime = Date.now();
169
+ maxAllowedDeltaMs;
170
+ constructor(maxAllowedDeltaMs = 5e3) {
171
+ this.maxAllowedDeltaMs = maxAllowedDeltaMs;
172
+ }
173
+ feed() {
174
+ this.lastTickTime = Date.now();
175
+ }
176
+ isExpired() {
177
+ return Date.now() - this.lastTickTime > this.maxAllowedDeltaMs;
178
+ }
179
+ };
180
+
181
+ // src/engine/rate-limiter.ts
182
+ var ActionRateLimiter = class {
183
+ actionTimestamps = [];
184
+ maxPerSecond;
185
+ constructor(maxPerSecond = 60) {
186
+ this.maxPerSecond = maxPerSecond;
187
+ }
188
+ allowAction() {
189
+ const now = Date.now();
190
+ this.actionTimestamps = this.actionTimestamps.filter((t) => now - t < 1e3);
191
+ if (this.actionTimestamps.length >= this.maxPerSecond) {
192
+ return false;
193
+ }
194
+ this.actionTimestamps.push(now);
195
+ return true;
196
+ }
197
+ };
198
+
199
+ // src/engine/policy-gate.ts
200
+ var PolicyGate = class {
201
+ deniedActions = /* @__PURE__ */ new Set(["delete_item", "spend_currency", "irreversible_trade"]);
202
+ isAllowed(actionName) {
203
+ return !this.deniedActions.has(actionName);
204
+ }
205
+ };
206
+
207
+ // src/engine/safety.ts
208
+ var EmergencySafety = class {
209
+ static killSwitchEngaged = false;
210
+ static engageKillSwitch() {
211
+ this.killSwitchEngaged = true;
212
+ }
213
+ static isSafe() {
214
+ return !this.killSwitchEngaged;
215
+ }
216
+ static resetSafety() {
217
+ this.killSwitchEngaged = false;
218
+ }
219
+ };
220
+
221
+ // src/engine/stuck-detector.ts
222
+ var StuckDetector = class {
223
+ lastNodes = [];
224
+ maxHistory = 50;
225
+ checkStuck(currentNodePath) {
226
+ this.lastNodes.push(currentNodePath);
227
+ if (this.lastNodes.length > this.maxHistory) {
228
+ this.lastNodes.shift();
229
+ }
230
+ if (this.lastNodes.length >= 30) {
231
+ const allSame = this.lastNodes.every((p) => p === currentNodePath);
232
+ if (allSame) {
233
+ return { isStuck: true, score: 1 };
234
+ }
235
+ }
236
+ return { isStuck: false, score: 0 };
237
+ }
238
+ };
239
+
240
+ // src/server.ts
241
+ import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
242
+
243
+ // src/tools/handlers.ts
244
+ import { z } from "zod";
245
+
246
+ // src/tools/definitions.ts
247
+ var READ_ONLY_TOOLS = /* @__PURE__ */ new Set(["get_status", "get_metrics"]);
248
+ var toolDefinitions = [
249
+ {
250
+ name: "load_behavior",
251
+ description: "Inject, initialize, or hot-swap a behavior tree instance in the browser runtime.",
252
+ inputSchema: {
253
+ type: "object",
254
+ properties: {
255
+ action: {
256
+ type: "string",
257
+ enum: ["load", "unload", "swap"],
258
+ description: "Behavior loading operation"
259
+ },
260
+ behavior_name: { type: "string", description: "Name of the behavior tree to load" },
261
+ behavior_version: {
262
+ type: "number",
263
+ description: "Version of behavior tree (defaults to latest)"
264
+ },
265
+ parameters: { type: "object", description: "Initial execution parameters" },
266
+ session_id: { type: "string", description: "Linked state-memory session ID" },
267
+ intention_id: { type: "string", description: "Linked agent-reasoning intention ID" },
268
+ trace_id: {
269
+ type: "string",
270
+ description: "Distributed trace ID for cross-server correlation"
271
+ },
272
+ client_request_id: { type: "string", description: "Idempotency key" },
273
+ project: { type: "string", description: "Target project slug" }
274
+ },
275
+ required: ["action", "behavior_name"]
276
+ }
277
+ },
278
+ {
279
+ name: "set_parameters",
280
+ description: "Dynamically update or query execution parameters for the active behavior runtime.",
281
+ inputSchema: {
282
+ type: "object",
283
+ properties: {
284
+ action: {
285
+ type: "string",
286
+ enum: ["set", "get", "reset"],
287
+ description: "Parameter operation"
288
+ },
289
+ execution_id: { type: "string", description: "Target execution instance ID" },
290
+ parameters: { type: "object", description: "Key-value parameters map to apply" },
291
+ project: { type: "string", description: "Target project slug" }
292
+ },
293
+ required: ["action"]
294
+ }
295
+ },
296
+ {
297
+ name: "get_status",
298
+ description: "Query active behavior execution status, current node path, tick count, duration, and error state.",
299
+ inputSchema: {
300
+ type: "object",
301
+ properties: {
302
+ action: {
303
+ type: "string",
304
+ enum: ["current", "history", "tree_state"],
305
+ description: "Status query mode"
306
+ },
307
+ execution_id: { type: "string", description: "Target execution ID (or latest if omitted)" },
308
+ limit: { type: "number", description: "Max history entries" },
309
+ project: { type: "string", description: "Target project slug" }
310
+ },
311
+ required: ["action"]
312
+ }
313
+ },
314
+ {
315
+ name: "abort_behavior",
316
+ description: "Immediately halt, pause, or resume behavior execution and disengage active inputs.",
317
+ inputSchema: {
318
+ type: "object",
319
+ properties: {
320
+ action: {
321
+ type: "string",
322
+ enum: ["abort", "pause", "resume"],
323
+ description: "Execution control operation"
324
+ },
325
+ execution_id: { type: "string", description: "Target execution ID" },
326
+ reason: { type: "string", description: "Reason for abort or pause" },
327
+ trace_id: { type: "string", description: "Distributed trace ID" },
328
+ project: { type: "string", description: "Target project slug" }
329
+ },
330
+ required: ["action"]
331
+ }
332
+ },
333
+ {
334
+ name: "register_trigger",
335
+ description: "Configure and manage reactive interrupt triggers with priority preemption and cooldown guards.",
336
+ inputSchema: {
337
+ type: "object",
338
+ properties: {
339
+ action: {
340
+ type: "string",
341
+ enum: ["register", "list"],
342
+ description: "Trigger operation"
343
+ },
344
+ name: { type: "string", description: "Trigger name" },
345
+ behavior_name: {
346
+ type: "string",
347
+ description: "Behavior tree to activate when condition fires"
348
+ },
349
+ condition_type: {
350
+ type: "string",
351
+ description: "Condition type (e.g. hp_threshold, enemy_proximity)"
352
+ },
353
+ condition_params: { type: "object", description: "Condition evaluation parameters" },
354
+ priority: { type: "number", description: "Preemption priority" },
355
+ cooldown_ms: { type: "number", description: "Minimum cooldown interval between fires" },
356
+ trigger_id: { type: "string", description: "Trigger ID" },
357
+ project: { type: "string", description: "Target project slug" }
358
+ },
359
+ required: ["action"]
360
+ }
361
+ },
362
+ {
363
+ name: "replay_recording",
364
+ description: "Capture or replay deterministic browser action sequences with adaptive timing.",
365
+ inputSchema: {
366
+ type: "object",
367
+ properties: {
368
+ action: {
369
+ type: "string",
370
+ enum: ["capture", "list"],
371
+ description: "Recording operation"
372
+ },
373
+ name: { type: "string", description: "Recording name" },
374
+ recording_id: { type: "string", description: "Recording ID" },
375
+ execution_id: { type: "string", description: "Target execution ID" },
376
+ frames: {
377
+ type: "array",
378
+ items: { type: "object" },
379
+ description: "Captured frame sequence"
380
+ },
381
+ project: { type: "string", description: "Target project slug" }
382
+ },
383
+ required: ["action"]
384
+ }
385
+ },
386
+ {
387
+ name: "get_metrics",
388
+ description: "Retrieve runtime execution telemetry, tick durations, stuck events, and category statistics.",
389
+ inputSchema: {
390
+ type: "object",
391
+ properties: {
392
+ action: {
393
+ type: "string",
394
+ enum: ["current", "history", "aggregate", "compare"],
395
+ description: "Metrics query mode"
396
+ },
397
+ execution_id: { type: "string", description: "Filter metrics by execution ID" },
398
+ behavior_name: { type: "string", description: "Filter metrics by behavior tree name" },
399
+ limit: { type: "number", description: "Max records" },
400
+ trace_id: { type: "string", description: "Distributed trace ID" },
401
+ project: { type: "string", description: "Target project slug" }
402
+ },
403
+ required: ["action"]
404
+ }
405
+ },
406
+ {
407
+ name: "manage_behaviors",
408
+ description: "CRUD operations for immutable JSON behavior tree definitions with SHA-256 tree hash verification.",
409
+ inputSchema: {
410
+ type: "object",
411
+ properties: {
412
+ action: {
413
+ type: "string",
414
+ enum: ["register", "list", "get"],
415
+ description: "Behavior definition management operation"
416
+ },
417
+ name: { type: "string", description: "Behavior tree name" },
418
+ version: { type: "number", description: "Tree version" },
419
+ description: { type: "string", description: "Tree description" },
420
+ tree: { type: "object", description: "Behavior tree JSON object" },
421
+ tree_json: { type: "string", description: "Raw behavior tree JSON string" },
422
+ client_request_id: { type: "string", description: "Idempotency key" },
423
+ project: { type: "string", description: "Target project slug" }
424
+ },
425
+ required: ["action"]
426
+ }
427
+ },
428
+ {
429
+ name: "manage_blackboard",
430
+ description: "Read, write, or query shared behavior tree blackboard state variables.",
431
+ inputSchema: {
432
+ type: "object",
433
+ properties: {
434
+ action: {
435
+ type: "string",
436
+ enum: ["get", "set"],
437
+ description: "Blackboard operation"
438
+ },
439
+ execution_id: { type: "string", description: "Target execution ID" },
440
+ key: { type: "string", description: "Blackboard variable key" },
441
+ value: { description: "Blackboard variable value" },
442
+ project: { type: "string", description: "Target project slug" }
443
+ },
444
+ required: ["action"]
445
+ }
446
+ },
447
+ {
448
+ name: "manage_runtime_db",
449
+ description: "Database maintenance, diagnostics, SHA-256 Merkle audit verification, checkpoints save/restore, and diffs.",
450
+ inputSchema: {
451
+ type: "object",
452
+ properties: {
453
+ action: {
454
+ type: "string",
455
+ enum: ["stats", "audit", "snapshot", "diff", "restore"],
456
+ description: "Database maintenance operation"
457
+ },
458
+ name: { type: "string", description: "Snapshot name" },
459
+ description: { type: "string", description: "Snapshot description" },
460
+ project: { type: "string", description: "Target project slug" }
461
+ },
462
+ required: ["action"]
463
+ }
464
+ }
465
+ ];
466
+
467
+ // src/engine/recordings.ts
468
+ var RecordingEngine = class {
469
+ static saveRecording(db, params) {
470
+ const id = generateId();
471
+ const now = getCurrentIsoString();
472
+ const durationMs = params.duration_ms || Math.round(params.frames.length * 16.6);
473
+ db.prepare(
474
+ `
475
+ INSERT INTO recordings (id, project, execution_id, behavior_name, total_frames, frames_json, duration_ms, created_at)
476
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
477
+ `
478
+ ).run(
479
+ id,
480
+ params.project,
481
+ params.execution_id,
482
+ params.behavior_name,
483
+ params.frames.length,
484
+ safeJsonStringify(params.frames),
485
+ durationMs,
486
+ now
487
+ );
488
+ return {
489
+ id,
490
+ project: params.project,
491
+ execution_id: params.execution_id,
492
+ behavior_name: params.behavior_name,
493
+ total_frames: params.frames.length,
494
+ frames_json: safeJsonStringify(params.frames),
495
+ duration_ms: durationMs,
496
+ created_at: now
497
+ };
498
+ }
499
+ static listRecordings(db, project) {
500
+ const rows = db.prepare("SELECT * FROM recordings WHERE project = ? ORDER BY created_at DESC LIMIT 50").all(project);
501
+ return rows.map((r) => ({
502
+ id: r.id,
503
+ project: r.project,
504
+ execution_id: r.execution_id,
505
+ behavior_name: r.behavior_name,
506
+ total_frames: r.total_frames,
507
+ frames_json: r.frames_json,
508
+ duration_ms: r.duration_ms,
509
+ created_at: r.created_at
510
+ }));
511
+ }
512
+ };
513
+
514
+ // src/engine/blackboard.ts
515
+ var BlackboardEngine = class {
516
+ static getBlackboard(db, params) {
517
+ const row = db.prepare("SELECT blackboard_json FROM execution_state WHERE project = ? AND id = ?").get(params.project, params.execution_id);
518
+ if (!row) throw new NotFoundError(`Execution state "${params.execution_id}" not found.`);
519
+ return safeJsonParse(row.blackboard_json, {});
520
+ }
521
+ static setBlackboardKey(db, params) {
522
+ const bb = this.getBlackboard(db, params);
523
+ bb[params.key] = params.value;
524
+ db.prepare("UPDATE execution_state SET blackboard_json = ?, updated_at = ? WHERE id = ?").run(
525
+ safeJsonStringify(bb),
526
+ getCurrentIsoString(),
527
+ params.execution_id
528
+ );
529
+ return bb;
530
+ }
531
+ };
532
+
533
+ // src/engine/advisor.ts
534
+ var TOOL_ALIASES = {
535
+ load: "load_behavior",
536
+ load_tree: "load_behavior",
537
+ run_tree: "load_behavior",
538
+ step: "step_behavior",
539
+ tick: "step_behavior",
540
+ pause: "pause_behavior",
541
+ resume: "pause_behavior",
542
+ abort: "abort_behavior",
543
+ stop: "abort_behavior",
544
+ cancel: "abort_behavior",
545
+ trigger: "register_trigger",
546
+ add_trigger: "register_trigger",
547
+ record: "manage_recordings",
548
+ replay: "manage_recordings",
549
+ metrics: "query_execution_metrics",
550
+ telemetry: "query_execution_metrics",
551
+ status: "get_runtime_status",
552
+ blackboard: "manage_blackboard",
553
+ safety: "emergency_kill_switch",
554
+ kill: "emergency_kill_switch"
555
+ };
556
+ function levenshtein(a, b) {
557
+ const matrix = [];
558
+ for (let i = 0; i <= b.length; i++) matrix[i] = [i];
559
+ for (let j = 0; j <= a.length; j++) matrix[0][j] = j;
560
+ for (let i = 1; i <= b.length; i++) {
561
+ for (let j = 1; j <= a.length; j++) {
562
+ if (b.charAt(i - 1) === a.charAt(j - 1)) {
563
+ matrix[i][j] = matrix[i - 1][j - 1];
564
+ } else {
565
+ matrix[i][j] = Math.min(
566
+ matrix[i - 1][j - 1] + 1,
567
+ matrix[i][j - 1] + 1,
568
+ matrix[i - 1][j] + 1
569
+ );
570
+ }
571
+ }
572
+ }
573
+ return matrix[b.length][a.length];
574
+ }
575
+ var SchemaAdvisor = class {
576
+ static resolveAlias(toolName) {
577
+ return TOOL_ALIASES[toolName.toLowerCase()];
578
+ }
579
+ static getAdvice(toolName, error, availableTools) {
580
+ const alias = this.resolveAlias(toolName);
581
+ if (alias) {
582
+ return `Tool "${toolName}" is an alias. Did you mean to call "${alias}"?`;
583
+ }
584
+ let closest = "";
585
+ let minDistance = Infinity;
586
+ for (const tool of availableTools) {
587
+ const dist = levenshtein(toolName.toLowerCase(), tool.toLowerCase());
588
+ if (dist < minDistance && dist <= 3) {
589
+ minDistance = dist;
590
+ closest = tool;
591
+ }
592
+ }
593
+ if (closest) {
594
+ return `Tool "${toolName}" not found. Did you mean "${closest}"? (${error})`;
595
+ }
596
+ return `Invalid tool call "${toolName}": ${error}`;
597
+ }
598
+ };
599
+
600
+ // src/tools/handlers.ts
601
+ var rateLimiter = new ActionRateLimiter(60);
602
+ var policyGate = new PolicyGate();
603
+ var stuckDetector = new StuckDetector();
604
+ var watchdog = new WatchdogTimer(5e3);
605
+ function jsonSchemaToZod(schema) {
606
+ if (!schema || typeof schema !== "object") return z.unknown();
607
+ const s = schema;
608
+ if (s.type === "string") {
609
+ if (s.enum && Array.isArray(s.enum) && s.enum.length > 0) {
610
+ return z.enum(s.enum);
611
+ }
612
+ return z.string();
613
+ }
614
+ if (s.type === "number") return z.number();
615
+ if (s.type === "boolean") return z.boolean();
616
+ if (s.type === "array") {
617
+ const itemSchema = s.items ? jsonSchemaToZod(s.items) : z.unknown();
618
+ return z.array(itemSchema);
619
+ }
620
+ if (s.type === "object" || s.properties) {
621
+ const shape = {};
622
+ const requiredKeys = new Set(s.required || []);
623
+ if (s.properties) {
624
+ for (const [key, prop] of Object.entries(s.properties)) {
625
+ let fieldSchema = jsonSchemaToZod(prop);
626
+ if (!requiredKeys.has(key)) {
627
+ fieldSchema = fieldSchema.optional();
628
+ }
629
+ shape[key] = fieldSchema;
630
+ }
631
+ }
632
+ return z.object(shape).passthrough();
633
+ }
634
+ return z.unknown();
635
+ }
636
+ function registerAllTools(server2) {
637
+ const toolNames = toolDefinitions.map((t) => t.name);
638
+ for (const def of toolDefinitions) {
639
+ const zodShape = {};
640
+ const schemaProps = def.inputSchema.properties || {};
641
+ const requiredList = new Set(def.inputSchema.required || []);
642
+ for (const [key, prop] of Object.entries(schemaProps)) {
643
+ let fieldSchema = jsonSchemaToZod(prop);
644
+ if (!requiredList.has(key)) {
645
+ fieldSchema = fieldSchema.optional();
646
+ }
647
+ zodShape[key] = fieldSchema;
648
+ }
649
+ server2.tool(def.name, def.description, zodShape, async (args) => {
650
+ try {
651
+ const project = getProjectSlug(args.project);
652
+ const isReadOnly = READ_ONLY_TOOLS.has(def.name);
653
+ const db = isReadOnly ? getReadOnlyDb(project) : getDb(project);
654
+ if (!EmergencySafety.isSafe()) {
655
+ throw new ValidationError("Emergency safety kill switch is engaged. Execution blocked.");
656
+ }
657
+ let result;
658
+ switch (def.name) {
659
+ case "load_behavior": {
660
+ if (!rateLimiter.allowAction()) {
661
+ throw new ValidationError("Action rate limit exceeded (>60 actions/sec).");
662
+ }
663
+ if (!policyGate.isAllowed(args.behavior_name)) {
664
+ throw new ValidationError(
665
+ `PolicyGate blocked loading disallowed behavior "${args.behavior_name}".`
666
+ );
667
+ }
668
+ watchdog.feed();
669
+ result = ExecutionEngine.startExecution(db, { project, ...args });
670
+ result._suggestions = [
671
+ {
672
+ tool: "get_status",
673
+ args: { execution_id: result.id, action: "current" },
674
+ reason: "Inspect live behavior execution traversal path"
675
+ },
676
+ {
677
+ tool: "get_metrics",
678
+ args: { execution_id: result.id, action: "current" },
679
+ reason: "Query runtime tick durations and telemetry"
680
+ }
681
+ ];
682
+ break;
683
+ }
684
+ case "set_parameters": {
685
+ if (args.action === "set" && args.execution_id) {
686
+ result = BlackboardEngine.setBlackboardKey(db, {
687
+ project,
688
+ execution_id: args.execution_id,
689
+ key: "parameters",
690
+ value: args.parameters
691
+ });
692
+ } else if (args.action === "get" && args.execution_id) {
693
+ result = BlackboardEngine.getBlackboard(db, {
694
+ project,
695
+ execution_id: args.execution_id
696
+ });
697
+ } else if (args.action === "reset" && args.execution_id) {
698
+ result = BlackboardEngine.setBlackboardKey(db, {
699
+ project,
700
+ execution_id: args.execution_id,
701
+ key: "parameters",
702
+ value: {}
703
+ });
704
+ } else {
705
+ throw new ValidationError(`Unsupported set_parameters action: "${args.action}".`);
706
+ }
707
+ break;
708
+ }
709
+ case "get_status": {
710
+ watchdog.feed();
711
+ if (args.execution_id) {
712
+ result = ExecutionEngine.getExecution(db, { project, id: args.execution_id });
713
+ } else {
714
+ const list = ExecutionEngine.listExecutions(db, { project, limit: 1 });
715
+ result = list[0] || { status: "idle", message: "No active execution found." };
716
+ }
717
+ if (result.active_node_path) {
718
+ const stuckCheck = stuckDetector.checkStuck(result.active_node_path);
719
+ if (stuckCheck.isStuck) {
720
+ result.stuck_detected = true;
721
+ result._suggestions = [
722
+ {
723
+ tool: "abort_behavior",
724
+ args: {
725
+ execution_id: result.id,
726
+ action: "abort",
727
+ reason: "Stuck in node path"
728
+ },
729
+ reason: "Abort stuck execution"
730
+ }
731
+ ];
732
+ }
733
+ }
734
+ break;
735
+ }
736
+ case "abort_behavior": {
737
+ const execs = ExecutionEngine.listExecutions(db, {
738
+ project,
739
+ status: "running",
740
+ limit: 1
741
+ });
742
+ const execId = args.execution_id || execs[0]?.id;
743
+ if (!execId) throw new ValidationError("No active running execution to abort.");
744
+ result = ExecutionEngine.stopExecution(db, {
745
+ project,
746
+ execution_id: execId,
747
+ status: args.action === "pause" ? "paused" : "aborted",
748
+ error_message: args.reason
749
+ });
750
+ result._suggestions = [
751
+ {
752
+ tool: "get_metrics",
753
+ args: { execution_id: execId, action: "history" },
754
+ reason: "Inspect post-abort telemetry"
755
+ }
756
+ ];
757
+ break;
758
+ }
759
+ case "register_trigger": {
760
+ const action = args.action;
761
+ if (action === "register") {
762
+ result = TriggerRegistry.registerTrigger(db, { project, ...args });
763
+ } else if (action === "list") {
764
+ result = TriggerRegistry.listTriggers(db, project);
765
+ } else {
766
+ throw new ValidationError(
767
+ `Unsupported register_trigger action: "${action}". Supported actions: register, list.`
768
+ );
769
+ }
770
+ break;
771
+ }
772
+ case "replay_recording": {
773
+ if (args.action === "capture" && args.execution_id) {
774
+ result = RecordingEngine.saveRecording(db, {
775
+ project,
776
+ execution_id: args.execution_id,
777
+ behavior_name: args.name || "behavior",
778
+ frames: args.frames || []
779
+ });
780
+ } else if (args.action === "list") {
781
+ result = RecordingEngine.listRecordings(db, project);
782
+ } else {
783
+ throw new ValidationError(
784
+ `Unsupported replay_recording action: "${args.action}". Supported actions: capture, list.`
785
+ );
786
+ }
787
+ break;
788
+ }
789
+ case "get_metrics": {
790
+ result = MetricsEngine.getMetrics(db, { project, ...args });
791
+ break;
792
+ }
793
+ case "manage_behaviors": {
794
+ const action = args.action;
795
+ if (action === "register") {
796
+ result = BehaviorRegistry.registerBehavior(db, {
797
+ project,
798
+ name: args.name,
799
+ version: args.version,
800
+ description: args.description,
801
+ tree: args.tree || JSON.parse(args.tree_json || "{}")
802
+ });
803
+ } else if (action === "get") {
804
+ result = BehaviorRegistry.getBehavior(db, {
805
+ project,
806
+ name: args.name,
807
+ version: args.version
808
+ });
809
+ } else if (action === "list") {
810
+ result = BehaviorRegistry.listBehaviors(db, project);
811
+ } else {
812
+ throw new ValidationError(
813
+ `Unsupported manage_behaviors action: "${action}". Supported actions: register, get, list.`
814
+ );
815
+ }
816
+ break;
817
+ }
818
+ case "manage_blackboard": {
819
+ const action = args.action;
820
+ if (action === "get" && args.execution_id) {
821
+ result = BlackboardEngine.getBlackboard(db, {
822
+ project,
823
+ execution_id: args.execution_id
824
+ });
825
+ } else if (action === "set" && args.execution_id && args.key) {
826
+ result = BlackboardEngine.setBlackboardKey(db, {
827
+ project,
828
+ execution_id: args.execution_id,
829
+ key: args.key,
830
+ value: args.value
831
+ });
832
+ } else {
833
+ throw new ValidationError(
834
+ `Unsupported manage_blackboard action or missing execution_id: "${action}". Supported actions: get, set.`
835
+ );
836
+ }
837
+ break;
838
+ }
839
+ case "manage_runtime_db": {
840
+ const action = args.action;
841
+ if (action === "stats") {
842
+ const behaviorsCount = db.prepare("SELECT COUNT(*) as c FROM behavior_definitions WHERE project = ?").get(project).c;
843
+ const executionsCount = db.prepare("SELECT COUNT(*) as c FROM execution_state WHERE project = ?").get(project).c;
844
+ const triggersCount = db.prepare("SELECT COUNT(*) as c FROM triggers WHERE project = ?").get(project).c;
845
+ result = { behaviorsCount, executionsCount, triggersCount, project };
846
+ } else if (action === "audit") {
847
+ result = verifyEventChain(db, project);
848
+ } else if (action === "snapshot") {
849
+ result = SnapshotEngine.saveSnapshot(db, {
850
+ project,
851
+ name: args.name || `snap_${Date.now()}`,
852
+ description: args.description
853
+ });
854
+ } else if (action === "restore") {
855
+ result = SnapshotEngine.restoreSnapshot(db, { project, name: args.name });
856
+ } else if (action === "diff") {
857
+ result = SnapshotEngine.listSnapshots(db, { project });
858
+ } else {
859
+ throw new ValidationError(
860
+ `Unsupported manage_runtime_db action: "${action}". Supported actions: stats, audit, snapshot, restore, diff.`
861
+ );
862
+ }
863
+ break;
864
+ }
865
+ default:
866
+ throw new ValidationError(`Unrecognized tool "${def.name}".`);
867
+ }
868
+ return {
869
+ content: [
870
+ {
871
+ type: "text",
872
+ text: JSON.stringify(result, null, 2)
873
+ }
874
+ ]
875
+ };
876
+ } catch (error) {
877
+ const advice = SchemaAdvisor.getAdvice(def.name, error.message, toolNames);
878
+ return {
879
+ isError: true,
880
+ content: [
881
+ {
882
+ type: "text",
883
+ text: JSON.stringify(
884
+ {
885
+ error: error.message,
886
+ code: error.code || "EXECUTION_ERROR",
887
+ advice
888
+ },
889
+ null,
890
+ 2
891
+ )
892
+ }
893
+ ]
894
+ };
895
+ }
896
+ });
897
+ }
898
+ }
899
+
900
+ // src/tools/prompts.ts
901
+ import { z as z2 } from "zod";
902
+ function registerAllPrompts(server2) {
903
+ server2.prompt(
904
+ "behavior-design",
905
+ "Design a robust, composable behavior tree with sequences, selectors, guards, and decorators",
906
+ {
907
+ goal: z2.string().describe("Target goal or behavior objective"),
908
+ environment: z2.string().optional().describe("Operating environment constraints")
909
+ },
910
+ async (args) => {
911
+ return {
912
+ messages: [
913
+ {
914
+ role: "user",
915
+ content: {
916
+ type: "text",
917
+ text: `Design a behavior tree to accomplish "${args.goal}". Constraints: "${args.environment || "Standard browser automation"}". Output valid JSON conforming to the PuterVision behavior tree schema with sequence, selector, and condition/action nodes. Register with \`manage_behaviors(register)\`.`
918
+ }
919
+ }
920
+ ]
921
+ };
922
+ }
923
+ );
924
+ server2.prompt(
925
+ "debug-stuck",
926
+ "Investigate and resolve stuck behavior execution instances and infinite node loops",
927
+ {
928
+ execution_id: z2.string().describe("Stuck execution instance ID")
929
+ },
930
+ async (args) => {
931
+ return {
932
+ messages: [
933
+ {
934
+ role: "user",
935
+ content: {
936
+ type: "text",
937
+ text: `Diagnose stuck execution "${args.execution_id}". Inspect active node path with \`get_status(current)\`, inspect blackboard state with \`manage_blackboard(get)\`, and analyze metrics via \`get_metrics(current)\`.`
938
+ }
939
+ }
940
+ ]
941
+ };
942
+ }
943
+ );
944
+ server2.prompt(
945
+ "optimize-behavior",
946
+ "Optimize behavior tree node ordering and conditions for 60Hz tick efficiency",
947
+ {
948
+ behavior_name: z2.string().describe("Behavior tree name to optimize")
949
+ },
950
+ async (args) => {
951
+ return {
952
+ messages: [
953
+ {
954
+ role: "user",
955
+ content: {
956
+ type: "text",
957
+ text: `Analyze telemetry metrics for behavior tree "${args.behavior_name}" via \`get_metrics(history)\`. Identify slow condition evaluations and reorder selector branches to maximize early exits.`
958
+ }
959
+ }
960
+ ]
961
+ };
962
+ }
963
+ );
964
+ server2.prompt(
965
+ "trigger-design",
966
+ "Create reactive interrupt triggers with priority preemption and cooldown guards",
967
+ {
968
+ behavior_name: z2.string().describe("Emergency behavior to trigger"),
969
+ emergency_condition: z2.string().describe("Trigger condition description")
970
+ },
971
+ async (args) => {
972
+ return {
973
+ messages: [
974
+ {
975
+ role: "user",
976
+ content: {
977
+ type: "text",
978
+ text: `Configure a reactive interrupt trigger for emergency behavior "${args.behavior_name}". Condition: "${args.emergency_condition}". Register with \`register_trigger(register)\` setting high priority (0.9+) and appropriate cooldown.`
979
+ }
980
+ }
981
+ ]
982
+ };
983
+ }
984
+ );
985
+ }
986
+
987
+ // src/server.ts
988
+ var server = new McpServer({
989
+ name: "io.github.putervision/behavior-mcp",
990
+ version: getVersion()
991
+ });
992
+ function getVarString(val) {
993
+ if (Array.isArray(val)) return val[0];
994
+ return val;
995
+ }
996
+ server.registerResource(
997
+ "runtime-status",
998
+ new ResourceTemplate("runtime:///{project}/status", { list: void 0 }),
999
+ {
1000
+ title: "Runtime Status Template",
1001
+ description: "Current execution status, active behavior, and tick counter",
1002
+ mimeType: "application/json"
1003
+ },
1004
+ async (uri, variables) => {
1005
+ const project = getProjectSlug(getVarString(variables.project));
1006
+ const db = getReadOnlyDb(project);
1007
+ const execs = ExecutionEngine.listExecutions(db, { project, limit: 1 });
1008
+ return {
1009
+ contents: [{ uri: uri.href, mimeType: "application/json", text: JSON.stringify(execs[0] || { status: "idle" }, null, 2) }]
1010
+ };
1011
+ }
1012
+ );
1013
+ server.registerResource(
1014
+ "runtime-active-tree",
1015
+ new ResourceTemplate("runtime:///{project}/active_tree", { list: void 0 }),
1016
+ {
1017
+ title: "Runtime Active Behavior Tree Template",
1018
+ description: "Active behavior tree node hierarchy and current active path",
1019
+ mimeType: "application/json"
1020
+ },
1021
+ async (uri, variables) => {
1022
+ const project = getProjectSlug(getVarString(variables.project));
1023
+ const db = getReadOnlyDb(project);
1024
+ const exec = ExecutionEngine.listExecutions(db, { project, status: "running", limit: 1 })[0];
1025
+ const behavior = exec ? BehaviorRegistry.getBehavior(db, { project, name: exec.behavior_name, version: exec.behavior_version }) : null;
1026
+ return {
1027
+ contents: [{ uri: uri.href, mimeType: "application/json", text: JSON.stringify({ exec, behavior }, null, 2) }]
1028
+ };
1029
+ }
1030
+ );
1031
+ server.registerResource(
1032
+ "runtime-metrics",
1033
+ new ResourceTemplate("runtime:///{project}/metrics", { list: void 0 }),
1034
+ {
1035
+ title: "Runtime Metrics Template",
1036
+ description: "Telemetry and tick performance aggregations",
1037
+ mimeType: "application/json"
1038
+ },
1039
+ async (uri, variables) => {
1040
+ const project = getProjectSlug(getVarString(variables.project));
1041
+ const db = getReadOnlyDb(project);
1042
+ const metrics = MetricsEngine.getMetrics(db, { project, limit: 50 });
1043
+ return {
1044
+ contents: [{ uri: uri.href, mimeType: "application/json", text: JSON.stringify(metrics, null, 2) }]
1045
+ };
1046
+ }
1047
+ );
1048
+ server.registerResource(
1049
+ "runtime-triggers",
1050
+ new ResourceTemplate("runtime:///{project}/triggers", { list: void 0 }),
1051
+ {
1052
+ title: "Runtime Triggers Template",
1053
+ description: "Registered reactive triggers and fire history",
1054
+ mimeType: "application/json"
1055
+ },
1056
+ async (uri, variables) => {
1057
+ const project = getProjectSlug(getVarString(variables.project));
1058
+ const db = getReadOnlyDb(project);
1059
+ const triggers = TriggerRegistry.listTriggers(db, project);
1060
+ return {
1061
+ contents: [{ uri: uri.href, mimeType: "application/json", text: JSON.stringify(triggers, null, 2) }]
1062
+ };
1063
+ }
1064
+ );
1065
+ server.registerResource(
1066
+ "runtime-recordings",
1067
+ new ResourceTemplate("runtime:///{project}/recordings", { list: void 0 }),
1068
+ {
1069
+ title: "Runtime Recordings Template",
1070
+ description: "Saved action recordings for replay",
1071
+ mimeType: "application/json"
1072
+ },
1073
+ async (uri, variables) => {
1074
+ const project = getProjectSlug(getVarString(variables.project));
1075
+ const db = getReadOnlyDb(project);
1076
+ const recordings = RecordingEngine.listRecordings(db, project);
1077
+ return {
1078
+ contents: [{ uri: uri.href, mimeType: "application/json", text: JSON.stringify(recordings, null, 2) }]
1079
+ };
1080
+ }
1081
+ );
1082
+ server.registerResource(
1083
+ "runtime-behaviors",
1084
+ new ResourceTemplate("runtime:///{project}/behaviors", { list: void 0 }),
1085
+ {
1086
+ title: "Runtime Behavior Registry Template",
1087
+ description: "Registered behavior tree definitions and tree hashes",
1088
+ mimeType: "application/json"
1089
+ },
1090
+ async (uri, variables) => {
1091
+ const project = getProjectSlug(getVarString(variables.project));
1092
+ const db = getReadOnlyDb(project);
1093
+ const behaviors = BehaviorRegistry.listBehaviors(db, project);
1094
+ return {
1095
+ contents: [{ uri: uri.href, mimeType: "application/json", text: JSON.stringify(behaviors, null, 2) }]
1096
+ };
1097
+ }
1098
+ );
1099
+ server.registerResource(
1100
+ "runtime-blackboard",
1101
+ new ResourceTemplate("runtime:///{project}/blackboard", { list: void 0 }),
1102
+ {
1103
+ title: "Runtime Blackboard State Template",
1104
+ description: "Current behavior tree blackboard variable state",
1105
+ mimeType: "application/json"
1106
+ },
1107
+ async (uri, variables) => {
1108
+ const project = getProjectSlug(getVarString(variables.project));
1109
+ const db = getReadOnlyDb(project);
1110
+ const exec = ExecutionEngine.listExecutions(db, { project, limit: 1 })[0];
1111
+ return {
1112
+ contents: [{ uri: uri.href, mimeType: "application/json", text: JSON.stringify(exec?.blackboard_json || "{}", null, 2) }]
1113
+ };
1114
+ }
1115
+ );
1116
+ server.registerResource(
1117
+ "runtime-watchdog",
1118
+ new ResourceTemplate("runtime:///{project}/watchdog", { list: void 0 }),
1119
+ {
1120
+ title: "Runtime Watchdog Health Template",
1121
+ description: "Watchdog status, stuck detection counters, and rate limit metrics",
1122
+ mimeType: "application/json"
1123
+ },
1124
+ async (uri, variables) => {
1125
+ const project = getProjectSlug(getVarString(variables.project));
1126
+ const db = getReadOnlyDb(project);
1127
+ const exec = ExecutionEngine.listExecutions(db, { project, limit: 1 })[0];
1128
+ return {
1129
+ contents: [
1130
+ {
1131
+ uri: uri.href,
1132
+ mimeType: "application/json",
1133
+ text: JSON.stringify({ project, stuck_score: exec?.stuck_score || 0, current_status: exec?.status || "idle" }, null, 2)
1134
+ }
1135
+ ]
1136
+ };
1137
+ }
1138
+ );
1139
+ server.registerResource(
1140
+ "runtime-health",
1141
+ "runtime:///health",
1142
+ {
1143
+ title: "Behavior Runtime Health",
1144
+ description: "Server health status, version, and timestamp",
1145
+ mimeType: "application/json"
1146
+ },
1147
+ async (uri) => {
1148
+ return {
1149
+ contents: [
1150
+ {
1151
+ uri: uri.href,
1152
+ mimeType: "application/json",
1153
+ text: JSON.stringify({
1154
+ status: "healthy",
1155
+ version: getVersion(),
1156
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1157
+ }, null, 2)
1158
+ }
1159
+ ]
1160
+ };
1161
+ }
1162
+ );
1163
+ registerAllTools(server);
1164
+ registerAllPrompts(server);
1165
+
1166
+ export {
1167
+ MetricsEngine,
1168
+ SnapshotEngine,
1169
+ WatchdogTimer,
1170
+ ActionRateLimiter,
1171
+ PolicyGate,
1172
+ EmergencySafety,
1173
+ StuckDetector,
1174
+ server
1175
+ };
1176
+ //# sourceMappingURL=chunk-MXYFWAJG.js.map