portals-mcp 1.2.2 → 1.2.3-beta.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/dist/api.js +19 -0
- package/dist/context-assembler.js +199 -0
- package/dist/docs.js +14 -42
- package/dist/feedback-prompts.js +5 -91
- package/dist/index.js +1 -1
- package/dist/intelligence-bridge.js +30 -11
- package/dist/lookup.js +0 -4
- package/dist/resources/ai/bootstrap.json +13 -21
- package/dist/resources/guide/rules-and-conventions.md +1 -1
- package/dist/resources/guide/workflow-steps.md +15 -0
- package/dist/resources/index/intents.json +38 -28
- package/dist/resources/index/knowledge-map.json +28 -34
- package/dist/resources/logic/expression-guide.md +2 -18
- package/dist/resources/logic/js-function-reference.md +1 -1
- package/dist/resources/python/lib/__pycache__/portals_effects.cpython-314.pyc +0 -0
- package/dist/resources/python/lib/__pycache__/portals_ops.cpython-314.pyc +0 -0
- package/dist/resources/python/lib/__pycache__/portals_utils.cpython-314.pyc +0 -0
- package/dist/resources/python/tools/validate_room.py +56 -0
- package/dist/resources/recipes/board-game-pawn/implementation.md +12 -0
- package/dist/resources/recipes/cutscene-camera/implementation.md +20 -0
- package/dist/resources/recipes/dice-roll/implementation.md +12 -0
- package/dist/resources/recipes/keypad/implementation.md +12 -0
- package/dist/resources/recipes/leaderboard-scoring/implementation.md +12 -0
- package/dist/resources/recipes/logic-board/implementation.md +13 -1
- package/dist/resources/recipes/manifest.json +42 -258
- package/dist/resources/recipes/position-heartbeat/implementation.md +12 -0
- package/dist/resources/recipes/side-scroller/implementation.md +12 -0
- package/dist/resources/ref/pitfalls.json +1 -1
- package/dist/resources/ref/systems/function-effector-js.json +54 -0
- package/dist/resources/ref/systems/function-effector.json +1 -11
- package/dist/resources/ref/systems/multiplayer.json +57 -0
- package/dist/resources/ref/systems/string-variables.json +40 -0
- package/dist/resources/ref/systems/use-effector.json +73 -0
- package/dist/resources/reference/gotchas.md +121 -0
- package/dist/resources/reference/interactions.md +1 -1
- package/dist/resources/usage-rules.md +20 -49
- package/dist/resources/workflows/function-effects-reference.md +2 -18
- package/dist/resources/workflows/modular-build-workflow.md +240 -0
- package/dist/session-tracker.js +200 -0
- package/dist/tools.js +643 -765
- package/dist/ws-bridge.js +29 -6
- package/package.json +1 -1
- package/dist/resources/ai/workflow-map.json +0 -6
- package/dist/resources/guide/glb-workflow.md +0 -35
- package/dist/resources/recipes/board-game-pawn/overview.json +0 -13
- package/dist/resources/recipes/cutscene-camera/overview.json +0 -13
- package/dist/resources/recipes/dice-roll/overview.json +0 -13
- package/dist/resources/recipes/keypad/overview.json +0 -13
- package/dist/resources/recipes/leaderboard-scoring/overview.json +0 -13
- package/dist/resources/recipes/logic-board/overview.json +0 -13
- package/dist/resources/recipes/position-heartbeat/overview.json +0 -13
- package/dist/resources/recipes/side-scroller/overview.json +0 -13
- package/dist/resources/scene/composition.md +0 -153
- package/dist/resources/scene/density-targets.md +0 -130
- package/dist/resources/scene/glb-catalog-system.md +0 -364
- package/dist/resources/scene/glb-workflow.md +0 -358
- package/dist/resources/scene/modular-kits.md +0 -238
- package/dist/resources/scene/placement-strategies.md +0 -343
- package/dist/resources/templates/game-design-doc.md +0 -158
- package/dist/resources/templates/simple_demo.json +0 -143
- package/dist/resources/workflows/asset-pipeline.md +0 -31
- package/dist/resources/workflows/builder.md +0 -18
- package/dist/resources/workflows/component-template.md +0 -362
- package/dist/resources/workflows/game-designer-workflow.md +0 -191
- package/dist/resources/workflows/game-designer.md +0 -14
- package/dist/resources/workflows/game-logic-board.md +0 -235
- package/dist/resources/workflows/validate-room.md +0 -37
package/dist/api.js
CHANGED
|
@@ -192,6 +192,25 @@ export async function sendUserMessage(content, roomId) {
|
|
|
192
192
|
});
|
|
193
193
|
await parseJsonResponse(res, "Failed to send user message");
|
|
194
194
|
}
|
|
195
|
+
export async function uploadAudio(roomId, fileData, fileType) {
|
|
196
|
+
const res = await fetch(`${API_BASE}/api/v2/utils/generate-audio-upload-url`, {
|
|
197
|
+
method: "POST",
|
|
198
|
+
body: JSON.stringify({ fileType, roomId }),
|
|
199
|
+
headers: {
|
|
200
|
+
"Content-Type": "application/json",
|
|
201
|
+
"x-api-key": getAccessKey(),
|
|
202
|
+
},
|
|
203
|
+
});
|
|
204
|
+
const json = await parseJsonResponse(res, "Failed to generate audio upload URL");
|
|
205
|
+
const putRes = await fetch(json.signedUploadURL, {
|
|
206
|
+
method: "PUT",
|
|
207
|
+
headers: { "Content-Type": fileType },
|
|
208
|
+
body: fileData,
|
|
209
|
+
});
|
|
210
|
+
if (!putRes.ok)
|
|
211
|
+
throw new Error(`Failed to upload audio: ${putRes.statusText}`);
|
|
212
|
+
return { assetURL: json.assetURL, objectKey: json.objectKey };
|
|
213
|
+
}
|
|
195
214
|
export async function downloadRoomData(roomId) {
|
|
196
215
|
const res = await fetch(`${API_BASE}/api/v2/mcp/download-room-data`, {
|
|
197
216
|
method: "GET",
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context assembler — powers the get_context tool.
|
|
3
|
+
*
|
|
4
|
+
* Uses the existing intent-matching engine from context-recommend.ts
|
|
5
|
+
* to identify relevant resources, then reads them from disk and returns
|
|
6
|
+
* the content inline (instead of returning URIs for the agent to read).
|
|
7
|
+
*/
|
|
8
|
+
import { readFile } from "node:fs/promises";
|
|
9
|
+
import { existsSync } from "node:fs";
|
|
10
|
+
import { resolve } from "node:path";
|
|
11
|
+
import { recommendContextForTask } from "./context-recommend.js";
|
|
12
|
+
import { resolveDocsRoot, docsRootCandidates } from "./docs.js";
|
|
13
|
+
import { searchBundledRecipes } from "./recipe-search.js";
|
|
14
|
+
import { tokenizeQuery } from "./utils.js";
|
|
15
|
+
// Map intent IDs to pitfall categories
|
|
16
|
+
const INTENT_TO_PITFALL_CATEGORIES = {
|
|
17
|
+
"js-scripting": ["js", "javascript", "functioneffectorjs", "variables", "strings"],
|
|
18
|
+
"function-effects": ["ncalc", "functioneffector", "expressions", "variables", "quests", "tasks"],
|
|
19
|
+
"gameplay-build": ["quests", "tasks", "variables", "interactions"],
|
|
20
|
+
"scene-item": ["items", "fields", "naming"],
|
|
21
|
+
"scene-design": ["items", "placement", "coordinates"],
|
|
22
|
+
"create-room": ["room", "settings"],
|
|
23
|
+
"settings-update": ["settings"],
|
|
24
|
+
"asset-pipeline": ["assets", "glb", "upload"],
|
|
25
|
+
};
|
|
26
|
+
async function loadPitfalls() {
|
|
27
|
+
const docsRoot = resolveDocsRoot();
|
|
28
|
+
if (!docsRoot)
|
|
29
|
+
return [];
|
|
30
|
+
const pitfallsPath = resolve(docsRoot, "ref", "pitfalls.json");
|
|
31
|
+
if (!existsSync(pitfallsPath))
|
|
32
|
+
return [];
|
|
33
|
+
try {
|
|
34
|
+
const raw = JSON.parse(await readFile(pitfallsPath, "utf-8"));
|
|
35
|
+
// pitfalls.json has a "pitfalls" array
|
|
36
|
+
if (Array.isArray(raw.pitfalls))
|
|
37
|
+
return raw.pitfalls;
|
|
38
|
+
if (Array.isArray(raw))
|
|
39
|
+
return raw;
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function filterPitfalls(pitfalls, intents) {
|
|
47
|
+
const relevantCategories = new Set();
|
|
48
|
+
for (const intent of intents) {
|
|
49
|
+
const categories = INTENT_TO_PITFALL_CATEGORIES[intent];
|
|
50
|
+
if (categories) {
|
|
51
|
+
for (const cat of categories)
|
|
52
|
+
relevantCategories.add(cat);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// If no category mapping, return top pitfalls as general guidance
|
|
56
|
+
if (relevantCategories.size === 0) {
|
|
57
|
+
return pitfalls.slice(0, 5).map(pitfallToGotcha);
|
|
58
|
+
}
|
|
59
|
+
const filtered = pitfalls.filter((p) => {
|
|
60
|
+
if (!p.categories || p.categories.length === 0)
|
|
61
|
+
return true; // uncategorized = always relevant
|
|
62
|
+
return p.categories.some((c) => relevantCategories.has(c.toLowerCase()));
|
|
63
|
+
});
|
|
64
|
+
return filtered.map(pitfallToGotcha);
|
|
65
|
+
}
|
|
66
|
+
function pitfallToGotcha(p) {
|
|
67
|
+
const gotcha = { rule: p.rule, detail: p.detail };
|
|
68
|
+
if (p.wrong)
|
|
69
|
+
gotcha.wrong = p.wrong;
|
|
70
|
+
if (p.right)
|
|
71
|
+
gotcha.right = p.right;
|
|
72
|
+
return gotcha;
|
|
73
|
+
}
|
|
74
|
+
// ── Resource reading ──
|
|
75
|
+
async function readResourceContent(uri) {
|
|
76
|
+
const docsRoot = resolveDocsRoot();
|
|
77
|
+
if (!docsRoot)
|
|
78
|
+
return null;
|
|
79
|
+
// docs://ref/systems/function-effector → ref/systems/function-effector
|
|
80
|
+
const stripped = uri.replace(/^docs:\/\//, "");
|
|
81
|
+
// Try common extensions
|
|
82
|
+
const extensions = [".json", ".md", ".txt", ".py"];
|
|
83
|
+
const roots = docsRootCandidates();
|
|
84
|
+
for (const root of roots) {
|
|
85
|
+
if (!existsSync(root))
|
|
86
|
+
continue;
|
|
87
|
+
for (const ext of extensions) {
|
|
88
|
+
const fullPath = resolve(root, stripped + ext);
|
|
89
|
+
if (existsSync(fullPath)) {
|
|
90
|
+
try {
|
|
91
|
+
return await readFile(fullPath, "utf-8");
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// Try without extension (file might already have one in the URI)
|
|
99
|
+
const directPath = resolve(root, stripped);
|
|
100
|
+
if (existsSync(directPath)) {
|
|
101
|
+
try {
|
|
102
|
+
return await readFile(directPath, "utf-8");
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
// ── Main assembly ──
|
|
112
|
+
export async function assembleContext(query, options) {
|
|
113
|
+
// 1. Score intents using existing engine
|
|
114
|
+
const recommendOpts = { maxIntents: 3 };
|
|
115
|
+
if (options?.stage)
|
|
116
|
+
recommendOpts.stage = options.stage;
|
|
117
|
+
const recommendation = recommendContextForTask(query, recommendOpts);
|
|
118
|
+
const matchedIntents = recommendation.selected_intents.map((i) => i.intent);
|
|
119
|
+
// 2. Collect resource URIs to read (required + recommended, skip deep_dive)
|
|
120
|
+
const resourcesToRead = [
|
|
121
|
+
...recommendation.resource_phases.required,
|
|
122
|
+
...recommendation.resource_phases.recommended,
|
|
123
|
+
];
|
|
124
|
+
// Deduplicate URIs
|
|
125
|
+
const uniqueUris = [...new Set(resourcesToRead.map((r) => r.uri))];
|
|
126
|
+
// 3. Read resource content in parallel
|
|
127
|
+
const contentParts = [];
|
|
128
|
+
const readPromises = uniqueUris.map(async (uri) => {
|
|
129
|
+
const content = await readResourceContent(uri);
|
|
130
|
+
if (content) {
|
|
131
|
+
return { uri, content };
|
|
132
|
+
}
|
|
133
|
+
return null;
|
|
134
|
+
});
|
|
135
|
+
const results = await Promise.all(readPromises);
|
|
136
|
+
for (const result of results) {
|
|
137
|
+
if (result) {
|
|
138
|
+
contentParts.push(result.content);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
// 4. Filter pitfalls to relevant gotchas
|
|
142
|
+
const allPitfalls = await loadPitfalls();
|
|
143
|
+
const gotchas = filterPitfalls(allPitfalls, matchedIntents);
|
|
144
|
+
// 5. Search recipes
|
|
145
|
+
const tokens = tokenizeQuery(query);
|
|
146
|
+
let recipes = [];
|
|
147
|
+
try {
|
|
148
|
+
const recipeResults = await searchBundledRecipes(query, { top: 5 });
|
|
149
|
+
recipes = recipeResults.matches.map((r) => ({
|
|
150
|
+
name: r.name,
|
|
151
|
+
summary: r.summary,
|
|
152
|
+
patterns: r.patterns,
|
|
153
|
+
uri: r.resource_uri,
|
|
154
|
+
}));
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
// Non-critical
|
|
158
|
+
}
|
|
159
|
+
// 6. Build task summary
|
|
160
|
+
const taskSummary = buildTaskSummary(matchedIntents, query);
|
|
161
|
+
// 7. Collect deep-dive URIs (not read inline, available for agents that want more)
|
|
162
|
+
const deepDiveUris = recommendation.resource_phases.deep_dive.map((r) => r.uri);
|
|
163
|
+
// 8. Collect suggested tools
|
|
164
|
+
const suggestedTools = recommendation.tool_sequence.map((t) => t.tool);
|
|
165
|
+
return {
|
|
166
|
+
task_summary: taskSummary,
|
|
167
|
+
matched_intents: matchedIntents,
|
|
168
|
+
knowledge: contentParts.join("\n\n---\n\n"),
|
|
169
|
+
gotchas,
|
|
170
|
+
relevant_recipes: recipes,
|
|
171
|
+
deep_dive_uris: deepDiveUris,
|
|
172
|
+
suggested_tools: suggestedTools,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
function buildTaskSummary(intents, query) {
|
|
176
|
+
const descriptions = {
|
|
177
|
+
"setup-auth": "Setting up authentication and connection",
|
|
178
|
+
"create-room": "Creating a new room",
|
|
179
|
+
"scene-item": "Working with scene items and placement",
|
|
180
|
+
"gameplay-build": "Building gameplay mechanics",
|
|
181
|
+
"function-effects": "Implementing logic with NCalc expressions",
|
|
182
|
+
"js-scripting": "Building with JavaScript/FunctionEffectorJS",
|
|
183
|
+
"logic-board": "Creating a logic board / flowchart",
|
|
184
|
+
"local-scripting": "Using Python tools for room generation",
|
|
185
|
+
"validation-publish": "Validating and publishing",
|
|
186
|
+
"asset-pipeline": "Uploading and managing assets",
|
|
187
|
+
"settings-update": "Updating room settings",
|
|
188
|
+
"game-design-full": "Designing and building a complete game",
|
|
189
|
+
"scene-design": "Designing scene composition and layout",
|
|
190
|
+
"room-targeting": "Working with an existing room",
|
|
191
|
+
};
|
|
192
|
+
const parts = intents
|
|
193
|
+
.map((i) => descriptions[i])
|
|
194
|
+
.filter(Boolean);
|
|
195
|
+
if (parts.length === 0) {
|
|
196
|
+
return `Task: ${query}`;
|
|
197
|
+
}
|
|
198
|
+
return parts.join(" + ");
|
|
199
|
+
}
|
package/dist/docs.js
CHANGED
|
@@ -2,7 +2,6 @@ import { existsSync, readdirSync } from "node:fs";
|
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
3
|
import { dirname, resolve } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { markResourceRead } from "./context-handshake.js";
|
|
6
5
|
import { isWithinRoot } from "./utils.js";
|
|
7
6
|
const SUPPORTED_RESOURCE_EXTENSIONS = [".md", ".json", ".py", ".txt"];
|
|
8
7
|
const STATIC_DOC_RESOURCES = [
|
|
@@ -108,22 +107,6 @@ const STATIC_DOC_RESOURCES = [
|
|
|
108
107
|
mimeType: "text/markdown",
|
|
109
108
|
},
|
|
110
109
|
// ── Workflows ────────────────────────────────────────────────────
|
|
111
|
-
{
|
|
112
|
-
name: "docs_workflow_builder",
|
|
113
|
-
uri: "docs://workflows/builder",
|
|
114
|
-
title: "Builder Workflow",
|
|
115
|
-
description: "Build and iteration workflow for approved game designs.",
|
|
116
|
-
relativePath: "workflows/builder.md",
|
|
117
|
-
mimeType: "text/markdown",
|
|
118
|
-
},
|
|
119
|
-
{
|
|
120
|
-
name: "docs_workflow_game_designer",
|
|
121
|
-
uri: "docs://workflows/game-designer",
|
|
122
|
-
title: "Game Designer Workflow",
|
|
123
|
-
description: "Discovery, design doc creation, and approval flow.",
|
|
124
|
-
relativePath: "workflows/game-designer.md",
|
|
125
|
-
mimeType: "text/markdown",
|
|
126
|
-
},
|
|
127
110
|
{
|
|
128
111
|
name: "docs_workflow_quality_review",
|
|
129
112
|
uri: "docs://workflows/quality-review",
|
|
@@ -148,12 +131,10 @@ const HELP_TOPICS = [
|
|
|
148
131
|
title: "System Overview",
|
|
149
132
|
summary: "Start here for AI-native contracts and minimal context loading.",
|
|
150
133
|
steps: [
|
|
151
|
-
"
|
|
152
|
-
"
|
|
153
|
-
"Use recommend_context for task-specific resource selection.",
|
|
154
|
-
"Read context_handshake.required_resources before mutating tools.",
|
|
155
|
-
"Use context_handshake.token as contextToken on mutating tool calls.",
|
|
134
|
+
"Call get_context with a description of what you want to build — it returns inline specs, gotchas, and recipes.",
|
|
135
|
+
"Use lookup(term) for quick spec lookups on specific items, triggers, or effects.",
|
|
156
136
|
"Use docs://usage-rules when wiring read/write workflows.",
|
|
137
|
+
"Read docs://ai/contracts/tool-output-envelope to parse all tool outputs safely.",
|
|
157
138
|
],
|
|
158
139
|
resources: [
|
|
159
140
|
"docs://ai/bootstrap",
|
|
@@ -168,12 +149,11 @@ const HELP_TOPICS = [
|
|
|
168
149
|
title: "Validate Room Workflow",
|
|
169
150
|
summary: "Run validation before merge/upload and resolve structured errors.",
|
|
170
151
|
steps: [
|
|
171
|
-
"
|
|
172
|
-
"
|
|
173
|
-
"Use docs://workflows/validate-room as the quality checklist.",
|
|
152
|
+
"Call get_context with your validation task for relevant context.",
|
|
153
|
+
"Use run_python_tool with validate_room to check the room snapshot.",
|
|
174
154
|
"After set_room_data, re-read room data and verify expected counts.",
|
|
175
155
|
],
|
|
176
|
-
resources: ["docs://
|
|
156
|
+
resources: ["docs://usage-rules", "docs://reference/gotchas"],
|
|
177
157
|
aliases: ["validate", "quality", "validation"],
|
|
178
158
|
},
|
|
179
159
|
{
|
|
@@ -181,15 +161,12 @@ const HELP_TOPICS = [
|
|
|
181
161
|
title: "Design + Build Flow",
|
|
182
162
|
summary: "Use the standardized sequence to maximize quality and consistency.",
|
|
183
163
|
steps: [
|
|
184
|
-
"Call
|
|
185
|
-
"
|
|
186
|
-
"Follow docs://workflows/builder for implementation and iteration.",
|
|
187
|
-
"Use docs://recipes/manifest for known mechanics.",
|
|
164
|
+
"Call get_context with your game concept for targeted specs, gotchas, and recipe matches.",
|
|
165
|
+
"Use search_recipes to find known mechanics before building from scratch.",
|
|
188
166
|
"Use lookup tool for specific item/trigger/effect specs.",
|
|
189
167
|
],
|
|
190
168
|
resources: [
|
|
191
|
-
"docs://workflows/
|
|
192
|
-
"docs://workflows/builder",
|
|
169
|
+
"docs://workflows/builder-workflow",
|
|
193
170
|
"docs://ref/systems/function-effector",
|
|
194
171
|
"docs://recipes/manifest",
|
|
195
172
|
],
|
|
@@ -231,12 +208,11 @@ const HELP_TOPICS = [
|
|
|
231
208
|
title: "Asset Upload Workflow",
|
|
232
209
|
summary: "Upload GLB/image assets with MCP tools and keep returned URLs in context.",
|
|
233
210
|
steps: [
|
|
234
|
-
"Use
|
|
235
|
-
"Use
|
|
236
|
-
"Use upload_image or upload_images_from_folder for image assets.",
|
|
211
|
+
"Use upload_glb for model assets (supports single file or folder).",
|
|
212
|
+
"Use upload_image for image assets (supports single file or folder).",
|
|
237
213
|
"Persist returned asset URLs before applying room data updates.",
|
|
238
214
|
],
|
|
239
|
-
resources: ["docs://
|
|
215
|
+
resources: ["docs://usage-rules"],
|
|
240
216
|
aliases: ["glb", "images", "upload", "cdn"],
|
|
241
217
|
},
|
|
242
218
|
{
|
|
@@ -277,7 +253,7 @@ const HELP_TOPICS = [
|
|
|
277
253
|
"Normalize non-standard input: use sentiment_map for words, out_of_range for numbers > 5.",
|
|
278
254
|
"Branch: high (4-5) → ask what stood out, mid (3) → ask what would improve it, low (1-2) → ask what went wrong and offer to iterate.",
|
|
279
255
|
"Use canonical tags: positive (great-layout, good-mechanics, nice-visuals, good-audio, matched-vision, fun-to-play) or negative (wrong-layout, missing-mechanic, wrong-recipe, needs-polish, missing-audio, too-hard, too-easy, confusing, incomplete, performance).",
|
|
280
|
-
"Call rate_build with
|
|
256
|
+
"Call rate_build with rating, tags, optional aspects (Match, Polish, Completeness), and comment.",
|
|
281
257
|
],
|
|
282
258
|
resources: ["docs://usage-rules", "docs://guide/workflow-steps"],
|
|
283
259
|
aliases: ["rate", "rating", "review"],
|
|
@@ -287,11 +263,9 @@ const HELP_TOPICS = [
|
|
|
287
263
|
title: "Context Selection",
|
|
288
264
|
summary: "Load high-signal resources in stable order to reduce token usage.",
|
|
289
265
|
steps: [
|
|
290
|
-
"
|
|
266
|
+
"Call get_context with your task description for targeted inline knowledge.",
|
|
291
267
|
"Use the lookup tool for specific item/trigger/effect specs (~100 tokens each).",
|
|
292
|
-
"Call recommend_context for broader task-specific resource selection.",
|
|
293
268
|
"Read docs://index/knowledge-map to navigate all available content.",
|
|
294
|
-
"Load priority-2 resources from recommend_context when task details are ambiguous.",
|
|
295
269
|
"Avoid loading full snapshots unless condensed summaries are insufficient.",
|
|
296
270
|
],
|
|
297
271
|
resources: [
|
|
@@ -558,7 +532,6 @@ export function registerDocsResources(server) {
|
|
|
558
532
|
description: "Machine-readable JSON catalog of all docs:// resources exposed by this server.",
|
|
559
533
|
mimeType: "application/json",
|
|
560
534
|
}, async (uri) => {
|
|
561
|
-
markResourceRead(uri.toString());
|
|
562
535
|
return {
|
|
563
536
|
contents: [
|
|
564
537
|
{
|
|
@@ -575,7 +548,6 @@ export function registerDocsResources(server) {
|
|
|
575
548
|
description: resource.description,
|
|
576
549
|
mimeType: resource.mimeType,
|
|
577
550
|
}, async (uri) => {
|
|
578
|
-
markResourceRead(uri.toString());
|
|
579
551
|
const text = await readDocResource(resource);
|
|
580
552
|
return {
|
|
581
553
|
contents: [
|
package/dist/feedback-prompts.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
export function buildSceneRefinementPrompt(
|
|
1
|
+
export function buildSceneRefinementPrompt(sessionId, roomId, filePath, mode) {
|
|
2
2
|
if (mode === "full") {
|
|
3
3
|
return {
|
|
4
4
|
required_action: true,
|
|
5
5
|
action: "scene_refinement_loop",
|
|
6
|
-
instruction: "
|
|
6
|
+
instruction: "You MUST run the scene refinement loop. " +
|
|
7
7
|
"Call analyze_scene with the snapshot file path. Review the check results. " +
|
|
8
8
|
"For each failed check, apply the suggested corrections via apply_operations. " +
|
|
9
9
|
"Then ask the user: 'Would you like another scene review pass?' " +
|
|
10
10
|
"If yes, call analyze_scene again and repeat. " +
|
|
11
|
-
"If no (or all checks pass), proceed
|
|
12
|
-
|
|
11
|
+
"If no (or all checks pass), proceed.",
|
|
12
|
+
session_id: sessionId,
|
|
13
13
|
room_id: roomId,
|
|
14
14
|
file_path: filePath,
|
|
15
15
|
convergence: {
|
|
@@ -26,95 +26,9 @@ export function buildSceneRefinementPrompt(contextToken, roomId, filePath, mode)
|
|
|
26
26
|
"Call analyze_scene with focus.objectIds set to the IDs of modified/added items. " +
|
|
27
27
|
"Review any failed checks related to those items and apply fixes if needed. " +
|
|
28
28
|
"This is a single-pass review, not an iterative loop.",
|
|
29
|
-
|
|
29
|
+
session_id: sessionId,
|
|
30
30
|
room_id: roomId,
|
|
31
31
|
file_path: filePath,
|
|
32
32
|
submit_via: "analyze_scene",
|
|
33
33
|
};
|
|
34
34
|
}
|
|
35
|
-
export function buildPushRatingPrompt(contextToken, roomId, pushInfo) {
|
|
36
|
-
return {
|
|
37
|
-
required_action: true,
|
|
38
|
-
action: "collect_push_rating",
|
|
39
|
-
instruction: "You MUST ask the user this question and wait for their response before calling rate_push. " +
|
|
40
|
-
"Do NOT self-assess or guess — the rating must come from the user. " +
|
|
41
|
-
"If the user declines or ignores, say 'No problem!' and move on without calling rate_push.",
|
|
42
|
-
context_token: contextToken ?? null,
|
|
43
|
-
room_id: roomId,
|
|
44
|
-
phase_number: pushInfo.phaseNumber,
|
|
45
|
-
push_in_phase: pushInfo.pushCountInPhase,
|
|
46
|
-
total_pushes: pushInfo.pushCountInSession,
|
|
47
|
-
script: {
|
|
48
|
-
prompt: "Quick check — on a scale of 1-5, how did this update land? (Feel free to add any comments too)",
|
|
49
|
-
scale_labels: {
|
|
50
|
-
"1": "Poor",
|
|
51
|
-
"2": "Below expectations",
|
|
52
|
-
"3": "Decent",
|
|
53
|
-
"4": "Good",
|
|
54
|
-
"5": "Nailed it",
|
|
55
|
-
},
|
|
56
|
-
after_rating: "If they want more changes, use phase_trigger on the next push. If they're satisfied, call rate_build for the overall session rating.",
|
|
57
|
-
},
|
|
58
|
-
submit_via: "rate_push",
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
export function buildSessionRatingPrompt(contextToken, roomId) {
|
|
62
|
-
return {
|
|
63
|
-
trigger: "after_user_tests_build",
|
|
64
|
-
context_token: contextToken ?? null,
|
|
65
|
-
room_id: roomId,
|
|
66
|
-
script: {
|
|
67
|
-
opening: "Nice work testing the build! I'd love to hear how it turned out.",
|
|
68
|
-
rating_question: "On a quick scale of 1-5, how did this build land for you?",
|
|
69
|
-
scale_labels: {
|
|
70
|
-
"1": "Poor — major issues",
|
|
71
|
-
"2": "Below expectations",
|
|
72
|
-
"3": "Decent — works but needs improvement",
|
|
73
|
-
"4": "Good — close to what I wanted",
|
|
74
|
-
"5": "Excellent — nailed it",
|
|
75
|
-
},
|
|
76
|
-
followup_high: "Awesome! Anything that stood out? I'll tag the highlights so future builds keep that quality.",
|
|
77
|
-
followup_mid: "Got it. What would push this from a 3 to a 5? I can note the specific areas.",
|
|
78
|
-
followup_low: "Sorry about that. Was it the layout, the mechanics, missing features, or something else? I want to capture what needs fixing and can take another pass.",
|
|
79
|
-
closing: "Thanks for the feedback — it helps the system get smarter at building.",
|
|
80
|
-
},
|
|
81
|
-
tag_vocabulary: {
|
|
82
|
-
positive: [
|
|
83
|
-
{ tag: "great-layout", meaning: "Spatial design felt right" },
|
|
84
|
-
{ tag: "good-mechanics", meaning: "Mechanics were fun and worked" },
|
|
85
|
-
{ tag: "nice-visuals", meaning: "Lighting and visual polish were strong" },
|
|
86
|
-
{ tag: "good-audio", meaning: "Sound enhanced the experience" },
|
|
87
|
-
{ tag: "matched-vision", meaning: "Build matched what was described" },
|
|
88
|
-
{ tag: "fun-to-play", meaning: "Overall experience was enjoyable" },
|
|
89
|
-
],
|
|
90
|
-
negative: [
|
|
91
|
-
{ tag: "wrong-layout", meaning: "Items misplaced, floating, or poorly arranged" },
|
|
92
|
-
{ tag: "missing-mechanic", meaning: "A requested mechanic was absent or broken" },
|
|
93
|
-
{ tag: "wrong-recipe", meaning: "Wrong approach or pattern for a mechanic" },
|
|
94
|
-
{ tag: "needs-polish", meaning: "Functional but rough" },
|
|
95
|
-
{ tag: "missing-audio", meaning: "Sound effects or ambient audio absent" },
|
|
96
|
-
{ tag: "too-hard", meaning: "Difficulty too high" },
|
|
97
|
-
{ tag: "too-easy", meaning: "Not enough challenge" },
|
|
98
|
-
{ tag: "confusing", meaning: "Unclear what to do" },
|
|
99
|
-
{ tag: "incomplete", meaning: "Build felt unfinished" },
|
|
100
|
-
{ tag: "performance", meaning: "Lag or too many objects" },
|
|
101
|
-
],
|
|
102
|
-
},
|
|
103
|
-
normalization: {
|
|
104
|
-
out_of_range: "Map to 1-5: 0/negative→1, 6-7→4, 8-10→5. Percentages proportionally.",
|
|
105
|
-
sentiment_map: {
|
|
106
|
-
terrible: 1, awful: 1, hate: 1,
|
|
107
|
-
bad: 2, disappointing: 2, meh: 2,
|
|
108
|
-
okay: 3, fine: 3, decent: 3, alright: 3,
|
|
109
|
-
good: 4, nice: 4, solid: 4, "pretty good": 4,
|
|
110
|
-
amazing: 5, perfect: 5, "love it": 5, excellent: 5, awesome: 5, great: 5,
|
|
111
|
-
},
|
|
112
|
-
non_numeric: "Map sentiment to closest rating, confirm with user: 'Sounds like a 4/5 — does that feel right?'",
|
|
113
|
-
},
|
|
114
|
-
aspects: {
|
|
115
|
-
accuracy: "Match — did this match what you asked for?",
|
|
116
|
-
quality: "Polish — does it look and feel good?",
|
|
117
|
-
completeness: "Completeness — is anything missing?",
|
|
118
|
-
},
|
|
119
|
-
};
|
|
120
|
-
}
|
package/dist/index.js
CHANGED
|
@@ -60,7 +60,7 @@ async function main() {
|
|
|
60
60
|
}
|
|
61
61
|
// Initialize intelligence service in the background (non-blocking)
|
|
62
62
|
initIntelligence().catch(() => {
|
|
63
|
-
// Intelligence init failed —
|
|
63
|
+
// Intelligence init failed — get_context falls back to keyword matching
|
|
64
64
|
});
|
|
65
65
|
// Start WebSocket bridge for browser communication
|
|
66
66
|
startBridge();
|
|
@@ -95,7 +95,21 @@ export async function ensureIntelligence() {
|
|
|
95
95
|
})();
|
|
96
96
|
return retryInFlight;
|
|
97
97
|
}
|
|
98
|
-
export function shutdownIntelligence() {
|
|
98
|
+
export function shutdownIntelligence(activeSessionIds) {
|
|
99
|
+
// Close any open sessions before shutting down
|
|
100
|
+
if (initialized && activeSessionIds) {
|
|
101
|
+
for (const sessionId of activeSessionIds) {
|
|
102
|
+
try {
|
|
103
|
+
// endSession auto-computes avg confidence from ai_feedback
|
|
104
|
+
endSession(sessionId);
|
|
105
|
+
triggerRetrievalLearning();
|
|
106
|
+
uploadSessionTrace(sessionId);
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
// Best-effort — shutting down
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
99
113
|
initialized = false;
|
|
100
114
|
}
|
|
101
115
|
// ── Search (blocking) ──
|
|
@@ -152,7 +166,7 @@ export function markSessionCompleted(sessionId) {
|
|
|
152
166
|
export function endSession(sessionId, feedback) {
|
|
153
167
|
if (!initialized)
|
|
154
168
|
return;
|
|
155
|
-
fireAndForget(`/sessions/${sessionId}/end`, feedback);
|
|
169
|
+
fireAndForget(`/sessions/${sessionId}/end`, feedback ?? {});
|
|
156
170
|
}
|
|
157
171
|
// ── Steps (fire-and-forget) ──
|
|
158
172
|
export function recordStep(sessionId, step) {
|
|
@@ -244,17 +258,17 @@ export function recordPushToPhase(phaseId) {
|
|
|
244
258
|
return;
|
|
245
259
|
fireAndForget(`/phases/${phaseId}/push`, {});
|
|
246
260
|
}
|
|
247
|
-
// ──
|
|
248
|
-
export function
|
|
261
|
+
// ── AI feedback ──
|
|
262
|
+
export function recordAiFeedback(sessionId, feedback) {
|
|
249
263
|
if (!initialized)
|
|
250
264
|
return;
|
|
251
|
-
fireAndForget(`/sessions/${sessionId}/
|
|
265
|
+
fireAndForget(`/sessions/${sessionId}/ai-feedback`, feedback);
|
|
252
266
|
}
|
|
253
|
-
export async function
|
|
267
|
+
export async function getAiFeedback(sessionId) {
|
|
254
268
|
if (!initialized)
|
|
255
269
|
return [];
|
|
256
270
|
try {
|
|
257
|
-
return await httpGet(`/sessions/${sessionId}/
|
|
271
|
+
return await httpGet(`/sessions/${sessionId}/ai-feedback`);
|
|
258
272
|
}
|
|
259
273
|
catch {
|
|
260
274
|
return [];
|
|
@@ -293,11 +307,14 @@ export function uploadSessionTrace(sessionId) {
|
|
|
293
307
|
Promise.all([
|
|
294
308
|
getSession(sessionId),
|
|
295
309
|
getPhases(sessionId),
|
|
296
|
-
|
|
310
|
+
getAiFeedback(sessionId),
|
|
297
311
|
])
|
|
298
|
-
.then(([session, phases,
|
|
312
|
+
.then(([session, phases, aiFeedback]) => {
|
|
299
313
|
if (!session || session.rating == null)
|
|
300
314
|
return;
|
|
315
|
+
// Aggregate doc gaps and recipe requests across all feedback entries
|
|
316
|
+
const allDocGaps = aiFeedback.flatMap((f) => f.docGaps);
|
|
317
|
+
const allRecipeRequests = aiFeedback.flatMap((f) => f.recipeRequests);
|
|
301
318
|
// Build a simplified anonymized trace from available data
|
|
302
319
|
const trace = {
|
|
303
320
|
contributorId: getContributorId(),
|
|
@@ -311,8 +328,10 @@ export function uploadSessionTrace(sessionId) {
|
|
|
311
328
|
contextSignals: [],
|
|
312
329
|
toolSequence: [],
|
|
313
330
|
phaseCount: phases.length,
|
|
314
|
-
|
|
315
|
-
|
|
331
|
+
pushConfidences: aiFeedback.map((f) => f.confidence),
|
|
332
|
+
docGaps: [...new Set(allDocGaps)],
|
|
333
|
+
recipeRequests: [...new Set(allRecipeRequests)],
|
|
334
|
+
schemaVersion: 2,
|
|
316
335
|
};
|
|
317
336
|
uploadTrace(trace);
|
|
318
337
|
})
|
package/dist/lookup.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { readFile, readdir, stat } from "node:fs/promises";
|
|
2
2
|
import { join, extname, relative } from "node:path";
|
|
3
|
-
import { markResourceRead } from "./context-handshake.js";
|
|
4
3
|
const index = [];
|
|
5
4
|
let initialized = false;
|
|
6
5
|
function normalizeKey(value) {
|
|
@@ -228,7 +227,6 @@ export function performLookup(term, category, type) {
|
|
|
228
227
|
const readUris = new Set();
|
|
229
228
|
for (const entry of matches) {
|
|
230
229
|
if (!readUris.has(entry.uri)) {
|
|
231
|
-
markResourceRead(entry.uri);
|
|
232
230
|
readUris.add(entry.uri);
|
|
233
231
|
}
|
|
234
232
|
}
|
|
@@ -260,7 +258,6 @@ export function performLookup(term, category, type) {
|
|
|
260
258
|
});
|
|
261
259
|
if (exactMatches.length > 0) {
|
|
262
260
|
for (const entry of exactMatches) {
|
|
263
|
-
markResourceRead(entry.uri);
|
|
264
261
|
}
|
|
265
262
|
return {
|
|
266
263
|
match_type: "exact",
|
|
@@ -289,7 +286,6 @@ export function performLookup(term, category, type) {
|
|
|
289
286
|
})
|
|
290
287
|
.slice(0, 5);
|
|
291
288
|
for (const entry of fuzzyMatches) {
|
|
292
|
-
markResourceRead(entry.uri);
|
|
293
289
|
}
|
|
294
290
|
return {
|
|
295
291
|
match_type: "fuzzy",
|