@almadar/agent 3.2.0 → 3.5.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/agent/index.js +4 -2
- package/dist/agent/index.js.map +1 -1
- package/dist/agent/skill-agent.d.ts +5 -0
- package/dist/evals/utils/orbital-validate.d.ts +33 -0
- package/dist/gates/_debug.d.ts +12 -0
- package/dist/gates/agent-single-pass.d.ts +1 -0
- package/dist/gates/agentic/run.d.ts +13 -0
- package/dist/gates/agentic/tools.d.ts +301 -0
- package/dist/gates/behavior-extract.d.ts +26 -0
- package/dist/gates/gate0-application.d.ts +13 -0
- package/dist/gates/gate05-behavior-match.d.ts +26 -0
- package/dist/gates/gate1-orbital.d.ts +17 -0
- package/dist/gates/gate2-trait.d.ts +17 -0
- package/dist/gates/gate3-transition.d.ts +16 -0
- package/dist/gates/gate4-render-ui.d.ts +17 -0
- package/dist/gates/index.d.ts +23 -0
- package/dist/gates/index.js +2034 -0
- package/dist/gates/index.js.map +1 -0
- package/dist/gates/merge.d.ts +26 -0
- package/dist/gates/orchestrator.d.ts +18 -0
- package/dist/gates/prompts.d.ts +33 -0
- package/dist/gates/scoring.d.ts +43 -0
- package/dist/gates/types.d.ts +147 -0
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/tools/combine-schemas.d.ts +1 -1
- package/dist/tools/domain-orbital.d.ts +4 -4
- package/dist/tools/execute.d.ts +1 -1
- package/dist/tools/finish-task.d.ts +1 -1
- package/dist/tools/generate-schema.d.ts +1 -1
- package/dist/tools/github.d.ts +16 -16
- package/dist/tools/index.d.ts +12 -12
- package/dist/tools/orbital-batch-subagent.d.ts +1 -1
- package/dist/tools/orbital-subagent.d.ts +1 -1
- package/dist/tools/orchestrated-fixing.d.ts +2 -2
- package/dist/tools/orchestrated-generation.d.ts +2 -2
- package/dist/tools/schema-chunking.d.ts +6 -6
- package/dist/tools/trait-subagent.d.ts +1 -1
- package/dist/tools/validate-schema.d.ts +1 -1
- package/package.json +8 -3
|
@@ -0,0 +1,2034 @@
|
|
|
1
|
+
import fs4 from 'fs';
|
|
2
|
+
import path4 from 'path';
|
|
3
|
+
import { extractJsonFromText, createDeepSeekClient, createOpenRouterClient } from '@almadar/llm';
|
|
4
|
+
import { getAllBehaviors } from '@almadar/std';
|
|
5
|
+
import { execSync } from 'child_process';
|
|
6
|
+
import { getDecompositionCompact, getBindingsCompact, getBindingContextRules, getOrbRenderUIGuide } from '@almadar/skills';
|
|
7
|
+
import { tool } from '@langchain/core/tools';
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
|
|
10
|
+
var __defProp = Object.defineProperty;
|
|
11
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
12
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
13
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
14
|
+
}) : x)(function(x) {
|
|
15
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
16
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
17
|
+
});
|
|
18
|
+
var __esm = (fn, res) => function __init() {
|
|
19
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
20
|
+
};
|
|
21
|
+
var __export = (target, all) => {
|
|
22
|
+
for (var name in all)
|
|
23
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// src/gates/gate05-behavior-match.ts
|
|
27
|
+
var gate05_behavior_match_exports = {};
|
|
28
|
+
__export(gate05_behavior_match_exports, {
|
|
29
|
+
loadGoldenOrbByName: () => loadGoldenOrb,
|
|
30
|
+
runGate05: () => runGate05
|
|
31
|
+
});
|
|
32
|
+
function getBehaviorCatalog() {
|
|
33
|
+
if (cachedCatalog) return cachedCatalog;
|
|
34
|
+
const all = getAllBehaviors();
|
|
35
|
+
const lines = all.map((b) => {
|
|
36
|
+
const name = String(b.name || "");
|
|
37
|
+
const desc = String(b.description || "");
|
|
38
|
+
const states = Array.isArray(b.states) ? b.states.length : 0;
|
|
39
|
+
const events = Array.isArray(b.events) ? b.events.length : 0;
|
|
40
|
+
return `- ${name}: ${desc} (${states} states, ${events} events)`;
|
|
41
|
+
});
|
|
42
|
+
cachedCatalog = lines.join("\n");
|
|
43
|
+
return cachedCatalog;
|
|
44
|
+
}
|
|
45
|
+
function findExportsDir() {
|
|
46
|
+
const monorepoPath = path4.resolve(
|
|
47
|
+
path4.dirname(new URL(import.meta.url).pathname),
|
|
48
|
+
"../../../almadar-std/behaviors/exports"
|
|
49
|
+
);
|
|
50
|
+
if (fs4.existsSync(monorepoPath)) return monorepoPath;
|
|
51
|
+
try {
|
|
52
|
+
const stdPkg = path4.dirname(__require.resolve("@almadar/std/package.json"));
|
|
53
|
+
const nmPath = path4.join(stdPkg, "behaviors", "exports");
|
|
54
|
+
if (fs4.existsSync(nmPath)) return nmPath;
|
|
55
|
+
} catch {
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
function loadGoldenOrb(behaviorName) {
|
|
60
|
+
const exportsDir = findExportsDir();
|
|
61
|
+
if (!exportsDir) {
|
|
62
|
+
console.warn("[Gate 0.5] Cannot find behaviors/exports/ directory");
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
const orbPath = path4.join(exportsDir, `${behaviorName}.orb`);
|
|
66
|
+
if (!fs4.existsSync(orbPath)) {
|
|
67
|
+
console.warn(`[Gate 0.5] .orb file not found: ${orbPath}`);
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
const content = fs4.readFileSync(orbPath, "utf-8");
|
|
71
|
+
return JSON.parse(content);
|
|
72
|
+
}
|
|
73
|
+
function buildMatchSystemPrompt() {
|
|
74
|
+
return `You are a behavior matching assistant. Given an application description and its orbital structure, select the most relevant golden behavior from the catalog below.
|
|
75
|
+
|
|
76
|
+
The catalog contains 107 standard behaviors. Each behavior is a proven, production-quality .orb program that handles a specific interaction pattern (CRUD lists, forms, search, cart, booking, etc.).
|
|
77
|
+
|
|
78
|
+
Your task: pick the ONE behavior that best matches the application's primary interaction pattern. If no behavior is a close match, respond with "none".
|
|
79
|
+
|
|
80
|
+
Respond with ONLY valid JSON: { "match": "std-behavior-name" } or { "match": "none" }
|
|
81
|
+
No markdown, no explanation.`;
|
|
82
|
+
}
|
|
83
|
+
function buildMatchUserPrompt(prompt, orb) {
|
|
84
|
+
const orbitalNames = orb.orbitals.map((o) => {
|
|
85
|
+
const name = o.name;
|
|
86
|
+
return name;
|
|
87
|
+
});
|
|
88
|
+
return `Application: ${prompt}
|
|
89
|
+
|
|
90
|
+
Orbitals produced by Gate 0: ${orbitalNames.join(", ")}
|
|
91
|
+
|
|
92
|
+
Behavior catalog:
|
|
93
|
+
${getBehaviorCatalog()}`;
|
|
94
|
+
}
|
|
95
|
+
async function runGate05(client, prompt, orb, opts = {}) {
|
|
96
|
+
const systemPrompt = buildMatchSystemPrompt();
|
|
97
|
+
const userPrompt = buildMatchUserPrompt(prompt, orb);
|
|
98
|
+
const raw = await client.callRaw({
|
|
99
|
+
systemPrompt,
|
|
100
|
+
userPrompt,
|
|
101
|
+
maxTokens: opts.maxTokens ?? 256
|
|
102
|
+
});
|
|
103
|
+
const parsed = extractJsonFromText(raw);
|
|
104
|
+
if (!parsed) {
|
|
105
|
+
console.warn("[Gate 0.5] Failed to parse match response, falling back to pure mode");
|
|
106
|
+
return { matchedName: null, goldenOrb: null };
|
|
107
|
+
}
|
|
108
|
+
const obj = JSON.parse(parsed);
|
|
109
|
+
const matchName = String(obj.match || "none");
|
|
110
|
+
if (matchName === "none") {
|
|
111
|
+
console.log("[Gate 0.5] No behavior match found, using pure mode");
|
|
112
|
+
return { matchedName: null, goldenOrb: null };
|
|
113
|
+
}
|
|
114
|
+
const goldenOrb = loadGoldenOrb(matchName);
|
|
115
|
+
if (!goldenOrb) {
|
|
116
|
+
console.warn(`[Gate 0.5] Matched "${matchName}" but could not load .orb file`);
|
|
117
|
+
return { matchedName: matchName, goldenOrb: null };
|
|
118
|
+
}
|
|
119
|
+
console.log(`[Gate 0.5] Matched behavior: ${matchName}`);
|
|
120
|
+
return { matchedName: matchName, goldenOrb };
|
|
121
|
+
}
|
|
122
|
+
var cachedCatalog;
|
|
123
|
+
var init_gate05_behavior_match = __esm({
|
|
124
|
+
"src/gates/gate05-behavior-match.ts"() {
|
|
125
|
+
cachedCatalog = null;
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
var ORBITAL_BINARY = "/home/osamah/bin/orbital";
|
|
129
|
+
function validateWithOrbitalCLI(schema, workDir) {
|
|
130
|
+
if (!fs4.existsSync(ORBITAL_BINARY)) {
|
|
131
|
+
return {
|
|
132
|
+
valid: false,
|
|
133
|
+
errors: [{ code: "CLI_NOT_FOUND", path: "", message: `orbital binary not found at ${ORBITAL_BINARY}` }]
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
const schemaStr = typeof schema === "string" ? schema : JSON.stringify(schema, null, 2);
|
|
137
|
+
const tempPath = path4.join(workDir, `_eval_validate_${Date.now()}.orb`);
|
|
138
|
+
try {
|
|
139
|
+
fs4.writeFileSync(tempPath, schemaStr);
|
|
140
|
+
const output = execSync(`${ORBITAL_BINARY} validate --json "${tempPath}"`, {
|
|
141
|
+
encoding: "utf-8",
|
|
142
|
+
timeout: 3e4,
|
|
143
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
144
|
+
});
|
|
145
|
+
const result = JSON.parse(extractJson(output));
|
|
146
|
+
return result;
|
|
147
|
+
} catch (error) {
|
|
148
|
+
const err = error;
|
|
149
|
+
if (err.stdout) {
|
|
150
|
+
try {
|
|
151
|
+
return JSON.parse(extractJson(err.stdout));
|
|
152
|
+
} catch {
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
valid: false,
|
|
157
|
+
errors: [{
|
|
158
|
+
code: "CLI_ERROR",
|
|
159
|
+
path: "",
|
|
160
|
+
message: err.stderr?.trim() || err.message || "Unknown CLI error"
|
|
161
|
+
}]
|
|
162
|
+
};
|
|
163
|
+
} finally {
|
|
164
|
+
try {
|
|
165
|
+
fs4.unlinkSync(tempPath);
|
|
166
|
+
} catch {
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
function extractJson(raw) {
|
|
171
|
+
const objects = [];
|
|
172
|
+
let i = 0;
|
|
173
|
+
while (i < raw.length) {
|
|
174
|
+
if (raw[i] === "{") {
|
|
175
|
+
let depth = 0;
|
|
176
|
+
let inString = false;
|
|
177
|
+
let escape = false;
|
|
178
|
+
const start = i;
|
|
179
|
+
for (; i < raw.length; i++) {
|
|
180
|
+
const ch = raw[i];
|
|
181
|
+
if (escape) {
|
|
182
|
+
escape = false;
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
if (ch === "\\" && inString) {
|
|
186
|
+
escape = true;
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if (ch === '"') {
|
|
190
|
+
inString = !inString;
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
if (inString) continue;
|
|
194
|
+
if (ch === "{") depth++;
|
|
195
|
+
if (ch === "}") {
|
|
196
|
+
depth--;
|
|
197
|
+
if (depth === 0) {
|
|
198
|
+
objects.push(raw.slice(start, i + 1));
|
|
199
|
+
i++;
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
} else {
|
|
205
|
+
i++;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
for (const obj of objects) {
|
|
209
|
+
if (obj.includes('"valid"')) return obj;
|
|
210
|
+
}
|
|
211
|
+
if (objects.length > 0) return objects[objects.length - 1];
|
|
212
|
+
return raw.trim();
|
|
213
|
+
}
|
|
214
|
+
function buildGate0SystemPrompt() {
|
|
215
|
+
const decomposition = getDecompositionCompact();
|
|
216
|
+
return `You are an application architect. Given a natural language description, produce a shell .orb JSON schema.
|
|
217
|
+
|
|
218
|
+
${decomposition}
|
|
219
|
+
|
|
220
|
+
Your output must be a JSON object with:
|
|
221
|
+
- name: PascalCase application name
|
|
222
|
+
- version: "1.0.0"
|
|
223
|
+
- orbitals: array of orbital shells
|
|
224
|
+
|
|
225
|
+
Each orbital shell:
|
|
226
|
+
- name: PascalCase ending in "Orbital" (e.g., "TaskOrbital")
|
|
227
|
+
- entity: { name: PascalCase singular noun, fields: [{ name: "id", type: "string", required: true }] }
|
|
228
|
+
- traits: [{ name: PascalCase, linkedEntity: entityName, category: "interaction", stateMachine: { states: [{ name: "idle", isInitial: true }], events: [], transitions: [] } }]
|
|
229
|
+
- pages: [{ name: PascalCase, path: "/lowercase", traits: [{ ref: "TraitName" }] }]
|
|
230
|
+
|
|
231
|
+
Rules:
|
|
232
|
+
- ONE orbital per entity type
|
|
233
|
+
- Every orbital must have at least one trait
|
|
234
|
+
- Keep it minimal: only the orbitals truly needed
|
|
235
|
+
- Do NOT fill in entity fields (Gate 1 does that)
|
|
236
|
+
- Do NOT fill in state machines (Gate 2 does that)
|
|
237
|
+
|
|
238
|
+
Respond with ONLY valid JSON. No markdown, no explanation.`;
|
|
239
|
+
}
|
|
240
|
+
function buildGate0UserPrompt(prompt, guidedData) {
|
|
241
|
+
let userPrompt = `Decompose this application into an .orb schema:
|
|
242
|
+
|
|
243
|
+
${prompt}`;
|
|
244
|
+
if (guidedData) {
|
|
245
|
+
userPrompt += `
|
|
246
|
+
|
|
247
|
+
Reference structure (use as guide):
|
|
248
|
+
- ${guidedData.orbitalCount} orbital(s)
|
|
249
|
+
- Entities: ${guidedData.entityNames.join(", ")}
|
|
250
|
+
- Traits: ${guidedData.traitNames.join(", ")}`;
|
|
251
|
+
}
|
|
252
|
+
return userPrompt;
|
|
253
|
+
}
|
|
254
|
+
function buildGate1SystemPrompt() {
|
|
255
|
+
return `You are an entity designer. Given an orbital shell, enrich it with full entity fields, persistence, and page details. Do NOT modify the state machine (Gate 2 fills that).
|
|
256
|
+
|
|
257
|
+
Your output must be a JSON object with:
|
|
258
|
+
- name: keep unchanged
|
|
259
|
+
- entity:
|
|
260
|
+
- name: keep unchanged
|
|
261
|
+
- persistence: "persistent" (survives sessions, needs collection), "runtime" (transient), or "singleton" (app-wide config)
|
|
262
|
+
- collection: snake_case plural (only for persistent entities, e.g., "cart_items")
|
|
263
|
+
- fields: array of { name, type, required?, default? }
|
|
264
|
+
- First field MUST be { name: "id", type: "string", required: true }
|
|
265
|
+
- Valid types: "string", "number", "boolean", "object", "array"
|
|
266
|
+
- Include ALL fields the entity needs (status, timestamps, amounts, references, etc.)
|
|
267
|
+
- traits: keep unchanged (shell state machine, Gate 2 fills it)
|
|
268
|
+
- pages: [{ name: PascalCase, path: "/lowercase", isInitial?, traits: [{ ref: "TraitName" }] }]
|
|
269
|
+
- EVERY page MUST have a traits array binding it to the orbital's trait(s)
|
|
270
|
+
- Without traits, the page renders blank (runtime doesn't know which trait to activate)
|
|
271
|
+
|
|
272
|
+
Rules:
|
|
273
|
+
- Think about what fields this entity genuinely needs for its domain
|
|
274
|
+
- Include status fields (e.g., "status": "string") for entities with lifecycle states
|
|
275
|
+
- Include amount/count fields (e.g., "total": "number") for entities with calculations
|
|
276
|
+
- Include timestamp fields (e.g., "createdAt": "string") for auditable entities
|
|
277
|
+
- Do NOT touch the stateMachine. Return it exactly as received.
|
|
278
|
+
|
|
279
|
+
Respond with ONLY valid JSON. No markdown, no explanation.`;
|
|
280
|
+
}
|
|
281
|
+
function buildGate1UserPrompt(orbital, allOrbitals, guidedOrbital) {
|
|
282
|
+
let userPrompt = `Enrich this orbital with full entity fields and pages:
|
|
283
|
+
${JSON.stringify(orbital, null, 2)}
|
|
284
|
+
|
|
285
|
+
Other orbitals in this application (for cross-orbital context):
|
|
286
|
+
${JSON.stringify(allOrbitals.filter((o) => o.name !== orbital.name), null, 2)}`;
|
|
287
|
+
if (guidedOrbital) {
|
|
288
|
+
userPrompt += `
|
|
289
|
+
|
|
290
|
+
Reference (use as guide):
|
|
291
|
+
${JSON.stringify(guidedOrbital, null, 2)}`;
|
|
292
|
+
}
|
|
293
|
+
return userPrompt;
|
|
294
|
+
}
|
|
295
|
+
function buildGate2SystemPrompt() {
|
|
296
|
+
return `You are a state machine designer. Given a trait and its entity, design the state machine topology and assign UI slot presentations per state.
|
|
297
|
+
|
|
298
|
+
Your output must be a JSON object with:
|
|
299
|
+
- name: keep unchanged
|
|
300
|
+
- linkedEntity: keep unchanged
|
|
301
|
+
- category: "interaction"
|
|
302
|
+
- stateMachine:
|
|
303
|
+
- states: [{ name: camelCase, isInitial?: true }]
|
|
304
|
+
- events: [{ key: "UPPER_SNAKE_CASE", name: "Human Label", payloadSchema?: [{ name, type }] }]
|
|
305
|
+
- transitions: [{ from, event, to, effects: [] }]
|
|
306
|
+
- ui: { "stateName": { "presentation": "inline" | "modal" }, ... }
|
|
307
|
+
|
|
308
|
+
State Machine Rules:
|
|
309
|
+
- Exactly ONE state must have isInitial: true (typically "idle" or "browsing")
|
|
310
|
+
- Every state must be reachable from the initial state
|
|
311
|
+
- Every event must be used in at least one transition
|
|
312
|
+
- The initial state MUST have an INIT self-loop transition (idle->INIT->idle or browsing->INIT->browsing)
|
|
313
|
+
- Leave effects as empty arrays (Gate 3 fills these)
|
|
314
|
+
|
|
315
|
+
UI Slot Assignment Rules:
|
|
316
|
+
- Assign "inline" to primary view states (idle, browsing, listing, viewing)
|
|
317
|
+
- Assign "modal" to overlay action states (creating, editing, deleting, confirming)
|
|
318
|
+
- Every state MUST have an assignment in the ui object
|
|
319
|
+
- Every "modal" state MUST have BOTH a CLOSE and CANCEL transition back to an "inline" state
|
|
320
|
+
|
|
321
|
+
Typical CRUD state machine pattern:
|
|
322
|
+
- idle/browsing (inline): main list view, INIT self-loop
|
|
323
|
+
- creating (modal): opened by CREATE event, closed by SAVE/CANCEL/CLOSE
|
|
324
|
+
- editing (modal): opened by EDIT event, closed by SAVE/CANCEL/CLOSE
|
|
325
|
+
- deleting (modal): opened by DELETE event, closed by CONFIRM_DELETE/CANCEL/CLOSE
|
|
326
|
+
|
|
327
|
+
Respond with ONLY valid JSON. No markdown, no explanation.`;
|
|
328
|
+
}
|
|
329
|
+
function buildGate2UserPrompt(trait, entity, orbitalDescription, guidedTrait) {
|
|
330
|
+
let userPrompt = `Design the state machine for trait:
|
|
331
|
+
${JSON.stringify(trait, null, 2)}
|
|
332
|
+
|
|
333
|
+
Entity it manages:
|
|
334
|
+
${JSON.stringify(entity, null, 2)}
|
|
335
|
+
|
|
336
|
+
Orbital context: ${orbitalDescription}`;
|
|
337
|
+
if (guidedTrait) {
|
|
338
|
+
userPrompt += `
|
|
339
|
+
|
|
340
|
+
Reference (use as guide):
|
|
341
|
+
${JSON.stringify(guidedTrait, null, 2)}`;
|
|
342
|
+
}
|
|
343
|
+
return userPrompt;
|
|
344
|
+
}
|
|
345
|
+
function buildGate3SystemPrompt() {
|
|
346
|
+
const bindingsCompact = getBindingsCompact();
|
|
347
|
+
const bindingContextRules = getBindingContextRules();
|
|
348
|
+
return `You are a transition effect designer. Given a transition, add guards and data effects.
|
|
349
|
+
|
|
350
|
+
${bindingsCompact}
|
|
351
|
+
|
|
352
|
+
${bindingContextRules}
|
|
353
|
+
|
|
354
|
+
Your output must be a JSON object with:
|
|
355
|
+
- from: keep unchanged
|
|
356
|
+
- event: keep unchanged
|
|
357
|
+
- to: keep unchanged
|
|
358
|
+
- guard: s-expression array or null
|
|
359
|
+
Example: ["and", ["not", ["eq", "@entity.status", "locked"]], ["gt", "@entity.count", 0]]
|
|
360
|
+
- effects: array of effect tuples
|
|
361
|
+
|
|
362
|
+
Effect types (use ONLY these four):
|
|
363
|
+
- ["set", "@entity.fieldName", value] - Set entity field. Value: "@payload.field", "@now", or literal.
|
|
364
|
+
- ["fetch", "EntityName"] - Load entity data. Use in INIT transitions.
|
|
365
|
+
- ["persist", "create"|"update"|"delete", "EntityName", "@payload.data"] - CRUD entity records.
|
|
366
|
+
- ["notify", "message", "success"|"error"|"info"] - Show a notification.
|
|
367
|
+
|
|
368
|
+
BANNED effects: call-service, emit, log, navigate.
|
|
369
|
+
|
|
370
|
+
Rules:
|
|
371
|
+
- Guards are optional. Only add when the transition genuinely needs a precondition.
|
|
372
|
+
- INIT transitions: include ["fetch", "EntityName"]
|
|
373
|
+
- SAVE transitions (from creating): include ["persist", "create", "EntityName", "@payload.data"]
|
|
374
|
+
- SAVE transitions (from editing): include ["persist", "update", "EntityName", "@payload.data"]
|
|
375
|
+
- CONFIRM_DELETE transitions: include ["persist", "delete", "EntityName", "@payload.data"]
|
|
376
|
+
- set target MUST start with @entity (never @payload)
|
|
377
|
+
- Do NOT include render-ui effects (Gate 4 handles those)
|
|
378
|
+
|
|
379
|
+
Respond with ONLY valid JSON. No markdown, no explanation.`;
|
|
380
|
+
}
|
|
381
|
+
function buildGate3UserPrompt(transition, entity, sm, guidedTransition) {
|
|
382
|
+
let userPrompt = `Design effects for transition:
|
|
383
|
+
${transition.from} --[${transition.event}]--> ${transition.to}
|
|
384
|
+
|
|
385
|
+
Entity: ${JSON.stringify(entity, null, 2)}
|
|
386
|
+
All states: ${sm.states.map((s) => s.name).join(", ")}
|
|
387
|
+
All events: ${JSON.stringify(sm.events, null, 2)}`;
|
|
388
|
+
if (guidedTransition) {
|
|
389
|
+
userPrompt += `
|
|
390
|
+
|
|
391
|
+
Reference (use as guide):
|
|
392
|
+
${JSON.stringify(guidedTransition, null, 2)}`;
|
|
393
|
+
}
|
|
394
|
+
return userPrompt;
|
|
395
|
+
}
|
|
396
|
+
function buildGate4SystemPrompt() {
|
|
397
|
+
const orbGuide = getOrbRenderUIGuide();
|
|
398
|
+
return `You are a UI designer. Given a transition with its effects, produce the render-ui pattern tree.
|
|
399
|
+
|
|
400
|
+
${orbGuide}
|
|
401
|
+
|
|
402
|
+
Your output must be a JSON object with:
|
|
403
|
+
- renderEffects: [{ slot, tree }]
|
|
404
|
+
- slot: "main" or "modal" (determined by the state's slot assignment shown in the user prompt)
|
|
405
|
+
- tree: pattern composition object, or null (to clear a slot)
|
|
406
|
+
|
|
407
|
+
Slot Rules:
|
|
408
|
+
- Use the slot shown in the user prompt for the target state (main or modal)
|
|
409
|
+
- When exiting a modal state (the user prompt will say so), you MUST include:
|
|
410
|
+
1. { "slot": "modal", "tree": null } to clear the modal
|
|
411
|
+
2. { "slot": "main", "tree": { ... } } to render the main view
|
|
412
|
+
|
|
413
|
+
Binding Rules:
|
|
414
|
+
- Bindings are ONLY paths: @entity.name, @entity.price (NO expressions like @entity.x <= 0)
|
|
415
|
+
- Each prop value must contain at most ONE binding. NEVER concatenate: "@entity.price @entity.currency" is INVALID
|
|
416
|
+
- For combined display, use separate typography elements or static labels
|
|
417
|
+
- button event props must match state machine event keys
|
|
418
|
+
|
|
419
|
+
Pattern Rules:
|
|
420
|
+
- Use ONLY props that exist on the pattern. Do NOT invent props.
|
|
421
|
+
- data-list: entity, itemActions, emptyState (NOT emptyIcon, emptyTitle)
|
|
422
|
+
- button: label, variant, event, icon, size (NOT disabled)
|
|
423
|
+
- card: children (NOT padding, bg, border, use box for those)
|
|
424
|
+
|
|
425
|
+
Respond with ONLY valid JSON. No markdown, no explanation.`;
|
|
426
|
+
}
|
|
427
|
+
function buildGate4UserPrompt(transition, entity, targetStateName, sm, slotContext, guidedRenderEffects) {
|
|
428
|
+
const targetSlot = slotContext.stateSlots[targetStateName] || "main";
|
|
429
|
+
const fromSlot = slotContext.stateSlots[transition.from] || "main";
|
|
430
|
+
let userPrompt = `Design the UI for state "${targetStateName}" (renders to "${targetSlot}" slot):
|
|
431
|
+
|
|
432
|
+
Transition: ${transition.from} (${fromSlot}) --[${transition.event}]--> ${transition.to} (${targetSlot})
|
|
433
|
+
Current effects: ${JSON.stringify(transition.effects || [], null, 2)}
|
|
434
|
+
Entity: ${JSON.stringify(entity, null, 2)}
|
|
435
|
+
All states with slots: ${Object.entries(slotContext.stateSlots).map(([s, slot]) => `${s}(${slot})`).join(", ")}`;
|
|
436
|
+
if (slotContext.exitsModal) {
|
|
437
|
+
userPrompt += `
|
|
438
|
+
|
|
439
|
+
IMPORTANT: This transition exits a modal state. You MUST include:
|
|
440
|
+
1. { "slot": "modal", "tree": null } to clear the modal overlay
|
|
441
|
+
2. { "slot": "main", "tree": { ... } } to render the main view`;
|
|
442
|
+
}
|
|
443
|
+
if (guidedRenderEffects) {
|
|
444
|
+
userPrompt += `
|
|
445
|
+
|
|
446
|
+
Reference render-ui (use as guide):
|
|
447
|
+
${JSON.stringify(guidedRenderEffects, null, 2)}`;
|
|
448
|
+
}
|
|
449
|
+
return userPrompt;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// src/gates/gate0-application.ts
|
|
453
|
+
var DEFAULT_THEME = {
|
|
454
|
+
name: "default",
|
|
455
|
+
tokens: {
|
|
456
|
+
colors: {
|
|
457
|
+
primary: "#6366f1",
|
|
458
|
+
"primary-hover": "#4f46e5",
|
|
459
|
+
"primary-foreground": "#ffffff",
|
|
460
|
+
accent: "#ec4899",
|
|
461
|
+
"accent-foreground": "#ffffff",
|
|
462
|
+
success: "#22c55e",
|
|
463
|
+
warning: "#f59e0b",
|
|
464
|
+
error: "#ef4444"
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
};
|
|
468
|
+
function normalizeOrbital(raw) {
|
|
469
|
+
const name = String(raw.name || "Orbital");
|
|
470
|
+
const entityRaw = raw.entity || {};
|
|
471
|
+
const entityName = String(entityRaw.name || name.replace(/Orbital$/i, ""));
|
|
472
|
+
const fields = Array.isArray(entityRaw.fields) ? entityRaw.fields : [];
|
|
473
|
+
const hasId = fields.some((f) => f.name === "id");
|
|
474
|
+
const entity = {
|
|
475
|
+
name: entityName,
|
|
476
|
+
fields: hasId ? fields.map((f) => ({
|
|
477
|
+
name: String(f.name),
|
|
478
|
+
type: String(f.type || "string"),
|
|
479
|
+
...f.required ? { required: true } : {}
|
|
480
|
+
})) : [
|
|
481
|
+
{ name: "id", type: "string", required: true },
|
|
482
|
+
...fields.map((f) => ({
|
|
483
|
+
name: String(f.name),
|
|
484
|
+
type: String(f.type || "string"),
|
|
485
|
+
...f.required ? { required: true } : {}
|
|
486
|
+
}))
|
|
487
|
+
]
|
|
488
|
+
};
|
|
489
|
+
const traitsRaw = Array.isArray(raw.traits) ? raw.traits : [];
|
|
490
|
+
const traits = traitsRaw.length > 0 ? traitsRaw.map((t) => ({
|
|
491
|
+
name: String(t.name || `${entityName}Control`),
|
|
492
|
+
linkedEntity: String(t.linkedEntity || entityName),
|
|
493
|
+
category: "interaction",
|
|
494
|
+
stateMachine: {
|
|
495
|
+
states: [{ name: "idle", isInitial: true }],
|
|
496
|
+
events: [],
|
|
497
|
+
transitions: []
|
|
498
|
+
}
|
|
499
|
+
})) : [{
|
|
500
|
+
name: `${entityName}Control`,
|
|
501
|
+
linkedEntity: entityName,
|
|
502
|
+
category: "interaction",
|
|
503
|
+
stateMachine: {
|
|
504
|
+
states: [{ name: "idle", isInitial: true }],
|
|
505
|
+
events: [],
|
|
506
|
+
transitions: []
|
|
507
|
+
}
|
|
508
|
+
}];
|
|
509
|
+
const traitNames = traits.map((t) => t.name);
|
|
510
|
+
const pagesRaw = Array.isArray(raw.pages) ? raw.pages : [];
|
|
511
|
+
const defaultTraits = traitNames.map((n) => ({ ref: n }));
|
|
512
|
+
const pages = pagesRaw.length > 0 ? pagesRaw.map((p) => ({
|
|
513
|
+
name: String(p.name || name.replace(/Orbital$/i, "")),
|
|
514
|
+
path: String(p.path || p.route || `/${name.toLowerCase().replace(/orbital$/i, "")}`),
|
|
515
|
+
...p.isInitial ? { isInitial: true } : {},
|
|
516
|
+
traits: Array.isArray(p.traits) ? p.traits : defaultTraits
|
|
517
|
+
})) : [{
|
|
518
|
+
name: name.replace(/Orbital$/i, ""),
|
|
519
|
+
path: `/${name.toLowerCase().replace(/orbital$/i, "")}`,
|
|
520
|
+
traits: defaultTraits
|
|
521
|
+
}];
|
|
522
|
+
return { name, entity, traits, pages };
|
|
523
|
+
}
|
|
524
|
+
function parseGate0Response(raw) {
|
|
525
|
+
const parsed = extractJsonFromText(raw);
|
|
526
|
+
if (!parsed) {
|
|
527
|
+
throw new Error(`Gate 0: failed to parse JSON from response: ${raw.slice(0, 200)}`);
|
|
528
|
+
}
|
|
529
|
+
const obj = JSON.parse(parsed);
|
|
530
|
+
const appName = String(obj.name || obj.appName || "App");
|
|
531
|
+
const version = String(obj.version || "1.0.0");
|
|
532
|
+
const orbitalsRaw = Array.isArray(obj.orbitals) ? obj.orbitals : [];
|
|
533
|
+
return {
|
|
534
|
+
name: appName,
|
|
535
|
+
version,
|
|
536
|
+
design: { theme: DEFAULT_THEME },
|
|
537
|
+
orbitals: orbitalsRaw.map((o) => normalizeOrbital(o))
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
async function runGate0(client, prompt, opts = {}, behaviorData) {
|
|
541
|
+
const systemPrompt = buildGate0SystemPrompt();
|
|
542
|
+
const userPrompt = buildGate0UserPrompt(
|
|
543
|
+
prompt,
|
|
544
|
+
behaviorData?.appLevel
|
|
545
|
+
);
|
|
546
|
+
const raw = await client.callRaw({
|
|
547
|
+
systemPrompt,
|
|
548
|
+
userPrompt,
|
|
549
|
+
maxTokens: opts.maxTokens ?? 4096
|
|
550
|
+
});
|
|
551
|
+
return parseGate0Response(raw);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// src/gates/orchestrator.ts
|
|
555
|
+
init_gate05_behavior_match();
|
|
556
|
+
|
|
557
|
+
// src/gates/merge.ts
|
|
558
|
+
function deepCloneOrb(orb) {
|
|
559
|
+
return structuredClone(orb);
|
|
560
|
+
}
|
|
561
|
+
function replaceOrbital(orb, name, enriched) {
|
|
562
|
+
const clone = deepCloneOrb(orb);
|
|
563
|
+
const idx = clone.orbitals.findIndex(
|
|
564
|
+
(o) => o.name === name
|
|
565
|
+
);
|
|
566
|
+
if (idx >= 0) {
|
|
567
|
+
clone.orbitals[idx] = enriched;
|
|
568
|
+
}
|
|
569
|
+
return clone;
|
|
570
|
+
}
|
|
571
|
+
function replaceTrait(orb, orbName, traitName, enriched) {
|
|
572
|
+
const clone = deepCloneOrb(orb);
|
|
573
|
+
const orbital = clone.orbitals.find(
|
|
574
|
+
(o) => o.name === orbName
|
|
575
|
+
);
|
|
576
|
+
if (!orbital) return clone;
|
|
577
|
+
const traits = orbital.traits;
|
|
578
|
+
const idx = traits.findIndex((t) => {
|
|
579
|
+
if (typeof t === "string") return t === traitName;
|
|
580
|
+
if ("ref" in t) return t.ref === traitName;
|
|
581
|
+
return t.name === traitName;
|
|
582
|
+
});
|
|
583
|
+
if (idx >= 0) {
|
|
584
|
+
traits[idx] = enriched;
|
|
585
|
+
}
|
|
586
|
+
return clone;
|
|
587
|
+
}
|
|
588
|
+
function replaceTransition(orb, orbName, traitName, from, event, enriched) {
|
|
589
|
+
const clone = deepCloneOrb(orb);
|
|
590
|
+
const orbital = clone.orbitals.find(
|
|
591
|
+
(o) => o.name === orbName
|
|
592
|
+
);
|
|
593
|
+
if (!orbital) return clone;
|
|
594
|
+
const trait = orbital.traits.find((t) => {
|
|
595
|
+
if (typeof t === "string") return false;
|
|
596
|
+
if ("ref" in t) return false;
|
|
597
|
+
return t.name === traitName;
|
|
598
|
+
});
|
|
599
|
+
if (!trait?.stateMachine) return clone;
|
|
600
|
+
const idx = trait.stateMachine.transitions.findIndex(
|
|
601
|
+
(t) => t.from === from && t.event === event
|
|
602
|
+
);
|
|
603
|
+
if (idx >= 0) {
|
|
604
|
+
trait.stateMachine.transitions[idx] = enriched;
|
|
605
|
+
}
|
|
606
|
+
return clone;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// src/gates/gate1-orbital.ts
|
|
610
|
+
function parseGate1Response(raw, original) {
|
|
611
|
+
const parsed = extractJsonFromText(raw);
|
|
612
|
+
if (!parsed) {
|
|
613
|
+
throw new Error(`Gate 1: failed to parse JSON from response: ${raw.slice(0, 200)}`);
|
|
614
|
+
}
|
|
615
|
+
const obj = JSON.parse(parsed);
|
|
616
|
+
const entityRaw = obj.entity || {};
|
|
617
|
+
const entityName = String(
|
|
618
|
+
entityRaw.name || original.entity.name
|
|
619
|
+
);
|
|
620
|
+
const fieldsRaw = Array.isArray(entityRaw.fields) ? entityRaw.fields : [];
|
|
621
|
+
const entity = {
|
|
622
|
+
name: entityName,
|
|
623
|
+
persistence: entityRaw.persistence || "runtime",
|
|
624
|
+
...entityRaw.collection ? { collection: String(entityRaw.collection) } : {},
|
|
625
|
+
fields: fieldsRaw.length > 0 ? fieldsRaw.map((f) => ({
|
|
626
|
+
name: String(f.name || ""),
|
|
627
|
+
type: f.type || "string",
|
|
628
|
+
...f.required ? { required: true } : {},
|
|
629
|
+
...f.default !== void 0 ? { default: f.default } : {}
|
|
630
|
+
})) : [{ name: "id", type: "string", required: true }]
|
|
631
|
+
};
|
|
632
|
+
const traitsRaw = Array.isArray(obj.traits) ? obj.traits : [];
|
|
633
|
+
const traits = traitsRaw.map((t) => ({
|
|
634
|
+
name: String(t.name || ""),
|
|
635
|
+
linkedEntity: String(t.linkedEntity || entityName),
|
|
636
|
+
category: "interaction",
|
|
637
|
+
...Array.isArray(t.emits) && t.emits.length > 0 ? { emits: t.emits.map((e) => typeof e === "string" ? { event: e } : e) } : {},
|
|
638
|
+
...Array.isArray(t.listens) && t.listens.length > 0 ? { listens: t.listens.map((l) => typeof l === "string" ? { event: l, triggers: l } : l) } : {},
|
|
639
|
+
stateMachine: {
|
|
640
|
+
states: [{ name: "idle", isInitial: true }],
|
|
641
|
+
events: [],
|
|
642
|
+
transitions: []
|
|
643
|
+
}
|
|
644
|
+
}));
|
|
645
|
+
const traitNames = traits.map((t) => t.name);
|
|
646
|
+
const defaultTraitRefs = traitNames.map((n) => ({ ref: n }));
|
|
647
|
+
const pagesRaw = Array.isArray(obj.pages) ? obj.pages : [];
|
|
648
|
+
const pages = pagesRaw.map((p) => ({
|
|
649
|
+
name: String(p.name || p.label || ""),
|
|
650
|
+
path: String(p.path || p.route || "/"),
|
|
651
|
+
...p.isInitial ? { isInitial: true } : {},
|
|
652
|
+
traits: Array.isArray(p.traits) ? p.traits : defaultTraitRefs
|
|
653
|
+
}));
|
|
654
|
+
return {
|
|
655
|
+
name: String(obj.name || original.name),
|
|
656
|
+
entity,
|
|
657
|
+
traits,
|
|
658
|
+
pages
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
async function runGate1(client, orb, opts = {}, behaviorData) {
|
|
662
|
+
const systemPrompt = buildGate1SystemPrompt();
|
|
663
|
+
const allOrbitals = orb.orbitals;
|
|
664
|
+
let result = orb;
|
|
665
|
+
for (const orbital of allOrbitals) {
|
|
666
|
+
const guidedOrbital = opts.goldenOrb?.orbitals.find(
|
|
667
|
+
(o) => o.name === orbital.name
|
|
668
|
+
);
|
|
669
|
+
const userPrompt = buildGate1UserPrompt(
|
|
670
|
+
orbital,
|
|
671
|
+
allOrbitals,
|
|
672
|
+
guidedOrbital
|
|
673
|
+
);
|
|
674
|
+
const raw = await client.callRaw({
|
|
675
|
+
systemPrompt,
|
|
676
|
+
userPrompt,
|
|
677
|
+
maxTokens: opts.maxTokens ?? 4096
|
|
678
|
+
});
|
|
679
|
+
const enriched = parseGate1Response(raw, orbital);
|
|
680
|
+
result = replaceOrbital(result, orbital.name, enriched);
|
|
681
|
+
}
|
|
682
|
+
return result;
|
|
683
|
+
}
|
|
684
|
+
function parseGate2Response(raw, original) {
|
|
685
|
+
const parsed = extractJsonFromText(raw);
|
|
686
|
+
if (!parsed) {
|
|
687
|
+
console.warn(`Gate 2: no JSON found, returning trait as-is`);
|
|
688
|
+
return original;
|
|
689
|
+
}
|
|
690
|
+
let obj;
|
|
691
|
+
try {
|
|
692
|
+
obj = JSON.parse(parsed);
|
|
693
|
+
} catch {
|
|
694
|
+
console.warn(`Gate 2: malformed JSON, returning trait as-is`);
|
|
695
|
+
return original;
|
|
696
|
+
}
|
|
697
|
+
const statesRaw = Array.isArray(obj.states) ? obj.states : [];
|
|
698
|
+
const eventsRaw = Array.isArray(obj.events) ? obj.events : [];
|
|
699
|
+
const transitionsRaw = Array.isArray(obj.transitions) ? obj.transitions : [];
|
|
700
|
+
const smRaw = obj.stateMachine;
|
|
701
|
+
const states = (smRaw && Array.isArray(smRaw.states) ? smRaw.states : statesRaw).map((s) => ({
|
|
702
|
+
name: String(s.name || ""),
|
|
703
|
+
...s.isInitial ? { isInitial: true } : {}
|
|
704
|
+
}));
|
|
705
|
+
const events = (smRaw && Array.isArray(smRaw.events) ? smRaw.events : eventsRaw).map((e) => ({
|
|
706
|
+
key: String(e.key || ""),
|
|
707
|
+
name: String(e.name || e.key || ""),
|
|
708
|
+
...Array.isArray(e.payloadSchema) ? { payloadSchema: e.payloadSchema } : {}
|
|
709
|
+
}));
|
|
710
|
+
const transitions = (smRaw && Array.isArray(smRaw.transitions) ? smRaw.transitions : transitionsRaw).map((t) => ({
|
|
711
|
+
from: String(t.from || ""),
|
|
712
|
+
event: String(t.event || ""),
|
|
713
|
+
to: String(t.to || ""),
|
|
714
|
+
effects: []
|
|
715
|
+
}));
|
|
716
|
+
const stateMachine = { states, events, transitions };
|
|
717
|
+
const validPresentations = /* @__PURE__ */ new Set(["modal", "drawer", "popover", "inline", "confirm-dialog"]);
|
|
718
|
+
const uiRaw = obj.ui || smRaw?.ui;
|
|
719
|
+
const ui = uiRaw ? Object.fromEntries(
|
|
720
|
+
Object.entries(uiRaw).map(([stateName, binding]) => {
|
|
721
|
+
const pres = String(binding.presentation || "inline");
|
|
722
|
+
return [
|
|
723
|
+
stateName,
|
|
724
|
+
{
|
|
725
|
+
presentation: validPresentations.has(pres) ? pres : "inline",
|
|
726
|
+
content: binding.content || {}
|
|
727
|
+
}
|
|
728
|
+
];
|
|
729
|
+
})
|
|
730
|
+
) : void 0;
|
|
731
|
+
return {
|
|
732
|
+
...original,
|
|
733
|
+
name: String(obj.name || original.name),
|
|
734
|
+
stateMachine,
|
|
735
|
+
...ui ? { ui } : {}
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
async function runGate2(client, orb, opts = {}, behaviorData) {
|
|
739
|
+
const systemPrompt = buildGate2SystemPrompt();
|
|
740
|
+
let result = orb;
|
|
741
|
+
for (const orbital of orb.orbitals) {
|
|
742
|
+
const entity = orbital.entity;
|
|
743
|
+
for (const traitRef of orbital.traits) {
|
|
744
|
+
if (typeof traitRef === "string" || "ref" in traitRef) continue;
|
|
745
|
+
const trait = traitRef;
|
|
746
|
+
const guidedTrait = findGuidedTrait(opts.goldenOrb, trait.name);
|
|
747
|
+
const userPrompt = buildGate2UserPrompt(
|
|
748
|
+
trait,
|
|
749
|
+
entity,
|
|
750
|
+
orbital.description || "",
|
|
751
|
+
guidedTrait
|
|
752
|
+
);
|
|
753
|
+
const raw = await client.callRaw({
|
|
754
|
+
systemPrompt,
|
|
755
|
+
userPrompt,
|
|
756
|
+
maxTokens: opts.maxTokens ?? 4096
|
|
757
|
+
});
|
|
758
|
+
const enriched = parseGate2Response(raw, trait);
|
|
759
|
+
result = replaceTrait(result, orbital.name, trait.name, enriched);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
return result;
|
|
763
|
+
}
|
|
764
|
+
function findGuidedTrait(goldenOrb, traitName) {
|
|
765
|
+
if (!goldenOrb) return void 0;
|
|
766
|
+
for (const orbital of goldenOrb.orbitals) {
|
|
767
|
+
for (const t of orbital.traits) {
|
|
768
|
+
if (typeof t === "string" || "ref" in t) continue;
|
|
769
|
+
if (t.name === traitName) return t;
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
return void 0;
|
|
773
|
+
}
|
|
774
|
+
function parseGate3Response(raw, original) {
|
|
775
|
+
const parsed = extractJsonFromText(raw);
|
|
776
|
+
if (!parsed) {
|
|
777
|
+
console.warn(`Gate 3: no JSON found, returning transition as-is`);
|
|
778
|
+
return original;
|
|
779
|
+
}
|
|
780
|
+
let obj;
|
|
781
|
+
try {
|
|
782
|
+
obj = JSON.parse(parsed);
|
|
783
|
+
} catch {
|
|
784
|
+
console.warn(`Gate 3: malformed JSON, returning transition as-is`);
|
|
785
|
+
return original;
|
|
786
|
+
}
|
|
787
|
+
const guard = Array.isArray(obj.guard) ? obj.guard : void 0;
|
|
788
|
+
const effectsRaw = Array.isArray(obj.effects) ? obj.effects : [];
|
|
789
|
+
const effects = effectsRaw.filter(
|
|
790
|
+
(e) => Array.isArray(e) && e.length >= 2
|
|
791
|
+
);
|
|
792
|
+
return {
|
|
793
|
+
from: String(obj.from || original.from),
|
|
794
|
+
event: String(obj.event || original.event),
|
|
795
|
+
to: String(obj.to || original.to),
|
|
796
|
+
...guard ? { guard } : {},
|
|
797
|
+
effects
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
async function runGate3(client, orb, opts = {}, _behaviorData) {
|
|
801
|
+
const systemPrompt = buildGate3SystemPrompt();
|
|
802
|
+
let result = orb;
|
|
803
|
+
for (const orbital of orb.orbitals) {
|
|
804
|
+
const entity = orbital.entity;
|
|
805
|
+
for (const traitRef of orbital.traits) {
|
|
806
|
+
if (typeof traitRef === "string" || "ref" in traitRef) continue;
|
|
807
|
+
const trait = traitRef;
|
|
808
|
+
if (!trait.stateMachine) continue;
|
|
809
|
+
for (const transition of trait.stateMachine.transitions) {
|
|
810
|
+
const guidedTransition = findGuidedTransition(
|
|
811
|
+
opts.goldenOrb,
|
|
812
|
+
trait.name,
|
|
813
|
+
transition.from,
|
|
814
|
+
transition.event
|
|
815
|
+
);
|
|
816
|
+
const userPrompt = buildGate3UserPrompt(
|
|
817
|
+
transition,
|
|
818
|
+
entity,
|
|
819
|
+
trait.stateMachine,
|
|
820
|
+
guidedTransition
|
|
821
|
+
);
|
|
822
|
+
const raw = await client.callRaw({
|
|
823
|
+
systemPrompt,
|
|
824
|
+
userPrompt,
|
|
825
|
+
maxTokens: opts.maxTokens ?? 4096
|
|
826
|
+
});
|
|
827
|
+
const enriched = parseGate3Response(raw, transition);
|
|
828
|
+
result = replaceTransition(
|
|
829
|
+
result,
|
|
830
|
+
orbital.name,
|
|
831
|
+
trait.name,
|
|
832
|
+
transition.from,
|
|
833
|
+
transition.event,
|
|
834
|
+
enriched
|
|
835
|
+
);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
return result;
|
|
840
|
+
}
|
|
841
|
+
function findGuidedTransition(goldenOrb, traitName, from, event) {
|
|
842
|
+
if (!goldenOrb) return void 0;
|
|
843
|
+
for (const orbital of goldenOrb.orbitals) {
|
|
844
|
+
for (const t of orbital.traits) {
|
|
845
|
+
if (typeof t === "string" || "ref" in t) continue;
|
|
846
|
+
if (t.name !== traitName) continue;
|
|
847
|
+
const sm = t.stateMachine;
|
|
848
|
+
if (!sm) continue;
|
|
849
|
+
const found = sm.transitions.find(
|
|
850
|
+
(tr) => tr.from === from && tr.event === event
|
|
851
|
+
);
|
|
852
|
+
if (found) return found;
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
return void 0;
|
|
856
|
+
}
|
|
857
|
+
function buildSlotContext(sm, traitUi) {
|
|
858
|
+
const slots = {};
|
|
859
|
+
for (const state of sm.states) {
|
|
860
|
+
const binding = traitUi?.[state.name];
|
|
861
|
+
slots[state.name] = binding?.presentation === "modal" ? "modal" : "main";
|
|
862
|
+
}
|
|
863
|
+
return slots;
|
|
864
|
+
}
|
|
865
|
+
function parseGate4Response(raw) {
|
|
866
|
+
const parsed = extractJsonFromText(raw);
|
|
867
|
+
if (!parsed) {
|
|
868
|
+
console.warn("Gate 4: no JSON found in response, using fallback render-ui");
|
|
869
|
+
return [{ slot: "main", tree: { type: "typography", variant: "body", content: "Loading..." } }];
|
|
870
|
+
}
|
|
871
|
+
let obj;
|
|
872
|
+
try {
|
|
873
|
+
obj = JSON.parse(parsed);
|
|
874
|
+
} catch {
|
|
875
|
+
console.warn(`Gate 4: malformed JSON, using fallback render-ui: ${parsed.slice(0, 100)}`);
|
|
876
|
+
return [{ slot: "main", tree: { type: "typography", variant: "body", content: "Loading..." } }];
|
|
877
|
+
}
|
|
878
|
+
const renderEffectsRaw = Array.isArray(obj.renderEffects) ? obj.renderEffects : [];
|
|
879
|
+
return renderEffectsRaw.map((r) => ({
|
|
880
|
+
slot: String(r.slot || "main"),
|
|
881
|
+
tree: r.tree === null ? null : r.tree || { type: "typography", variant: "body", content: "Empty" }
|
|
882
|
+
}));
|
|
883
|
+
}
|
|
884
|
+
async function runGate4(client, orb, opts = {}, _behaviorData) {
|
|
885
|
+
const systemPrompt = buildGate4SystemPrompt();
|
|
886
|
+
let result = orb;
|
|
887
|
+
for (const orbital of orb.orbitals) {
|
|
888
|
+
const entity = orbital.entity;
|
|
889
|
+
for (const traitRef of orbital.traits) {
|
|
890
|
+
if (typeof traitRef === "string" || "ref" in traitRef) continue;
|
|
891
|
+
const trait = traitRef;
|
|
892
|
+
if (!trait.stateMachine) continue;
|
|
893
|
+
const traitUi = trait.ui;
|
|
894
|
+
const stateSlots = buildSlotContext(trait.stateMachine, traitUi);
|
|
895
|
+
for (const transition of trait.stateMachine.transitions) {
|
|
896
|
+
const fromSlot = stateSlots[transition.from] || "main";
|
|
897
|
+
const toSlot = stateSlots[transition.to] || "main";
|
|
898
|
+
const exitsModal = fromSlot === "modal" && toSlot === "main";
|
|
899
|
+
const slotContext = { stateSlots, exitsModal };
|
|
900
|
+
const guidedRenderEffects = findGuidedRenderEffects(
|
|
901
|
+
opts.goldenOrb,
|
|
902
|
+
trait.name,
|
|
903
|
+
transition.from,
|
|
904
|
+
transition.event
|
|
905
|
+
);
|
|
906
|
+
const userPrompt = buildGate4UserPrompt(
|
|
907
|
+
transition,
|
|
908
|
+
entity,
|
|
909
|
+
transition.to,
|
|
910
|
+
trait.stateMachine,
|
|
911
|
+
slotContext,
|
|
912
|
+
guidedRenderEffects
|
|
913
|
+
);
|
|
914
|
+
const raw = await client.callRaw({
|
|
915
|
+
systemPrompt,
|
|
916
|
+
userPrompt,
|
|
917
|
+
maxTokens: (opts.maxTokens ?? 4096) * 2
|
|
918
|
+
});
|
|
919
|
+
const renderEffects = parseGate4Response(raw);
|
|
920
|
+
const existingEffects = [...transition.effects || []];
|
|
921
|
+
for (const re of renderEffects) {
|
|
922
|
+
existingEffects.push(["render-ui", re.slot, re.tree]);
|
|
923
|
+
}
|
|
924
|
+
if (exitsModal) {
|
|
925
|
+
const hasModalNull = existingEffects.some(
|
|
926
|
+
(e) => Array.isArray(e) && e[0] === "render-ui" && e[1] === "modal" && e[2] === null
|
|
927
|
+
);
|
|
928
|
+
if (!hasModalNull) {
|
|
929
|
+
existingEffects.unshift(["render-ui", "modal", null]);
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
const enriched = {
|
|
933
|
+
...transition,
|
|
934
|
+
effects: existingEffects
|
|
935
|
+
};
|
|
936
|
+
result = replaceTransition(
|
|
937
|
+
result,
|
|
938
|
+
orbital.name,
|
|
939
|
+
trait.name,
|
|
940
|
+
transition.from,
|
|
941
|
+
transition.event,
|
|
942
|
+
enriched
|
|
943
|
+
);
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
return result;
|
|
948
|
+
}
|
|
949
|
+
function findGuidedRenderEffects(goldenOrb, traitName, from, event) {
|
|
950
|
+
if (!goldenOrb) return void 0;
|
|
951
|
+
for (const orbital of goldenOrb.orbitals) {
|
|
952
|
+
for (const t of orbital.traits) {
|
|
953
|
+
if (typeof t === "string" || "ref" in t) continue;
|
|
954
|
+
if (t.name !== traitName) continue;
|
|
955
|
+
const sm = t.stateMachine;
|
|
956
|
+
if (!sm) continue;
|
|
957
|
+
const tr = sm.transitions.find(
|
|
958
|
+
(tr2) => tr2.from === from && tr2.event === event
|
|
959
|
+
);
|
|
960
|
+
if (!tr?.effects) continue;
|
|
961
|
+
const renderEffects = [];
|
|
962
|
+
for (const eff of tr.effects) {
|
|
963
|
+
if (Array.isArray(eff) && eff[0] === "render-ui") {
|
|
964
|
+
renderEffects.push({
|
|
965
|
+
slot: String(eff[1] || "main"),
|
|
966
|
+
tree: eff[2] || {}
|
|
967
|
+
});
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
if (renderEffects.length > 0) return renderEffects;
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
return void 0;
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
// src/gates/orchestrator.ts
|
|
977
|
+
function createClientForProvider(config) {
|
|
978
|
+
const opts = {
|
|
979
|
+
...config.model ? { model: config.model } : {},
|
|
980
|
+
...config.temperature !== void 0 ? { temperature: config.temperature } : {}
|
|
981
|
+
};
|
|
982
|
+
switch (config.provider) {
|
|
983
|
+
case "deepseek":
|
|
984
|
+
return createDeepSeekClient(opts);
|
|
985
|
+
case "zhipu":
|
|
986
|
+
return createOpenRouterClient({ model: "z-ai/glm-4.7", ...opts });
|
|
987
|
+
default: {
|
|
988
|
+
const _exhaustive = config.provider;
|
|
989
|
+
throw new Error(`Unknown provider: ${_exhaustive}`);
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
function timeMs() {
|
|
994
|
+
return performance.now();
|
|
995
|
+
}
|
|
996
|
+
async function runGatePipeline(prompt, config, behaviorData, goldenOrb) {
|
|
997
|
+
const client = createClientForProvider(config);
|
|
998
|
+
const maxTokens = config.maxTokens || 4096;
|
|
999
|
+
const pipelineStart = timeMs();
|
|
1000
|
+
const opts = {
|
|
1001
|
+
maxTokens,
|
|
1002
|
+
goldenOrb,
|
|
1003
|
+
workDir: config.workDir
|
|
1004
|
+
};
|
|
1005
|
+
const g0Start = timeMs();
|
|
1006
|
+
const orbAfterGate0 = await runGate0(client, prompt, opts, behaviorData);
|
|
1007
|
+
const g0Ms = timeMs() - g0Start;
|
|
1008
|
+
console.log(`[Gate 0] ${orbAfterGate0.name}: ${orbAfterGate0.orbitals.length} orbital(s) (${g0Ms.toFixed(0)}ms)`);
|
|
1009
|
+
maybeValidate(config, orbAfterGate0, 0);
|
|
1010
|
+
if (config.mode === "guided" && !goldenOrb) {
|
|
1011
|
+
const g05Start = timeMs();
|
|
1012
|
+
const matchResult = await runGate05(client, prompt, orbAfterGate0, opts);
|
|
1013
|
+
const g05Ms = timeMs() - g05Start;
|
|
1014
|
+
if (matchResult.goldenOrb) {
|
|
1015
|
+
opts.goldenOrb = matchResult.goldenOrb;
|
|
1016
|
+
console.log(`[Gate 0.5] Matched: ${matchResult.matchedName} (${g05Ms.toFixed(0)}ms)`);
|
|
1017
|
+
} else {
|
|
1018
|
+
console.log(`[Gate 0.5] No match, continuing in pure mode (${g05Ms.toFixed(0)}ms)`);
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
const g1Start = timeMs();
|
|
1022
|
+
const orbAfterGate1 = await runGate1(client, orbAfterGate0, opts);
|
|
1023
|
+
const g1Ms = timeMs() - g1Start;
|
|
1024
|
+
console.log(`[Gate 1] Enriched ${orbAfterGate1.orbitals.length} orbital(s) (${g1Ms.toFixed(0)}ms)`);
|
|
1025
|
+
maybeValidate(config, orbAfterGate1, 1);
|
|
1026
|
+
const g2Start = timeMs();
|
|
1027
|
+
const orbAfterGate2 = await runGate2(client, orbAfterGate1, opts);
|
|
1028
|
+
const g2Ms = timeMs() - g2Start;
|
|
1029
|
+
console.log(`[Gate 2] State machines filled (${g2Ms.toFixed(0)}ms)`);
|
|
1030
|
+
maybeValidate(config, orbAfterGate2, 2);
|
|
1031
|
+
const g3Start = timeMs();
|
|
1032
|
+
const orbAfterGate3 = await runGate3(client, orbAfterGate2, opts);
|
|
1033
|
+
const g3Ms = timeMs() - g3Start;
|
|
1034
|
+
console.log(`[Gate 3] Guards + effects filled (${g3Ms.toFixed(0)}ms)`);
|
|
1035
|
+
maybeValidate(config, orbAfterGate3, 3);
|
|
1036
|
+
const g4Start = timeMs();
|
|
1037
|
+
const orbFinal = await runGate4(client, orbAfterGate3, opts);
|
|
1038
|
+
const g4Ms = timeMs() - g4Start;
|
|
1039
|
+
console.log(`[Gate 4] Render-ui appended (${g4Ms.toFixed(0)}ms)`);
|
|
1040
|
+
fs4.mkdirSync(config.workDir, { recursive: true });
|
|
1041
|
+
const validation = validateWithOrbitalCLI(orbFinal, config.workDir);
|
|
1042
|
+
console.log(`[Validate] valid=${validation.valid}, errors=${(validation.errors || []).length}`);
|
|
1043
|
+
let verify;
|
|
1044
|
+
if (config.verify) {
|
|
1045
|
+
verify = await runVerify(orbFinal, config);
|
|
1046
|
+
}
|
|
1047
|
+
const totalMs = timeMs() - pipelineStart;
|
|
1048
|
+
console.log(`[Pipeline] Total: ${(totalMs / 1e3).toFixed(1)}s`);
|
|
1049
|
+
const timings = {
|
|
1050
|
+
gate0Ms: g0Ms,
|
|
1051
|
+
gate1Ms: [g1Ms],
|
|
1052
|
+
gate2Ms: [g2Ms],
|
|
1053
|
+
gate3Ms: [g3Ms],
|
|
1054
|
+
gate4Ms: [g4Ms],
|
|
1055
|
+
totalMs
|
|
1056
|
+
};
|
|
1057
|
+
return {
|
|
1058
|
+
config,
|
|
1059
|
+
orbAfterGate0,
|
|
1060
|
+
orbAfterGate1,
|
|
1061
|
+
orbAfterGate2,
|
|
1062
|
+
orbAfterGate3,
|
|
1063
|
+
orbFinal,
|
|
1064
|
+
validation: {
|
|
1065
|
+
valid: validation.valid ?? false,
|
|
1066
|
+
errors: validation.errors ?? []
|
|
1067
|
+
},
|
|
1068
|
+
verify,
|
|
1069
|
+
timings
|
|
1070
|
+
};
|
|
1071
|
+
}
|
|
1072
|
+
async function runVerify(orb, config) {
|
|
1073
|
+
const tmpDir = path4.join(config.workDir, "gate-verify");
|
|
1074
|
+
fs4.mkdirSync(tmpDir, { recursive: true });
|
|
1075
|
+
const orbPath = path4.join(tmpDir, `${orb.name || "generated"}.orb`);
|
|
1076
|
+
fs4.writeFileSync(orbPath, JSON.stringify(orb, null, 2));
|
|
1077
|
+
const verifyApiPath = path4.resolve(
|
|
1078
|
+
path4.dirname(new URL(import.meta.url).pathname),
|
|
1079
|
+
"../../../../tools/orbital-verify-unified/src/api.js"
|
|
1080
|
+
);
|
|
1081
|
+
const { verifyApp } = await import(verifyApiPath);
|
|
1082
|
+
console.log(`[Verify] Starting orbital-verify on ${orbPath}`);
|
|
1083
|
+
const result = await verifyApp({
|
|
1084
|
+
orbPath,
|
|
1085
|
+
headed: config.verifyHeaded ?? false,
|
|
1086
|
+
screenshots: config.verifyScreenshots ?? false
|
|
1087
|
+
});
|
|
1088
|
+
console.log(
|
|
1089
|
+
`[Verify] ${result.totalPassed}/${result.totalChecks} passed, ${result.totalFailed} failed` + (result.coverage ? `, coverage=${(result.coverage.ratio * 100).toFixed(0)}%` : "")
|
|
1090
|
+
);
|
|
1091
|
+
return {
|
|
1092
|
+
totalPassed: result.totalPassed,
|
|
1093
|
+
totalFailed: result.totalFailed,
|
|
1094
|
+
totalChecks: result.totalChecks,
|
|
1095
|
+
coverage: result.coverage ? {
|
|
1096
|
+
ratio: result.coverage.ratio,
|
|
1097
|
+
coveredItems: result.coverage.coveredItems,
|
|
1098
|
+
totalItems: result.coverage.totalItems
|
|
1099
|
+
} : void 0,
|
|
1100
|
+
phases: result.phases.map((p) => ({
|
|
1101
|
+
phase: p.phase,
|
|
1102
|
+
name: p.name,
|
|
1103
|
+
passed: p.passed,
|
|
1104
|
+
failed: p.failed,
|
|
1105
|
+
checks: p.checks
|
|
1106
|
+
}))
|
|
1107
|
+
};
|
|
1108
|
+
}
|
|
1109
|
+
function maybeValidate(config, orb, gate) {
|
|
1110
|
+
if (!config.validateAfterEachGate) return;
|
|
1111
|
+
const result = validateWithOrbitalCLI(orb, config.workDir);
|
|
1112
|
+
if (!result.valid) {
|
|
1113
|
+
console.warn(
|
|
1114
|
+
`[Gate ${gate} validate] ${result.errors.length} error(s): ` + result.errors.slice(0, 3).map((e) => e.message).join("; ")
|
|
1115
|
+
);
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
// src/gates/index.ts
|
|
1120
|
+
init_gate05_behavior_match();
|
|
1121
|
+
|
|
1122
|
+
// src/gates/behavior-extract.ts
|
|
1123
|
+
function asArray(val) {
|
|
1124
|
+
if (!val) return [];
|
|
1125
|
+
return Array.isArray(val) ? val : [val];
|
|
1126
|
+
}
|
|
1127
|
+
function getOrbitals(behavior) {
|
|
1128
|
+
return asArray(behavior.orbitals);
|
|
1129
|
+
}
|
|
1130
|
+
function getTraitsFromOrbital(orbital) {
|
|
1131
|
+
return asArray(orbital.traits);
|
|
1132
|
+
}
|
|
1133
|
+
function getStateMachine(trait) {
|
|
1134
|
+
return trait.stateMachine || null;
|
|
1135
|
+
}
|
|
1136
|
+
function getStates(sm) {
|
|
1137
|
+
const states = asArray(sm.states);
|
|
1138
|
+
return states.map((s) => ({ name: String(s.name || "") }));
|
|
1139
|
+
}
|
|
1140
|
+
function getEvents(sm) {
|
|
1141
|
+
const events = asArray(sm.events);
|
|
1142
|
+
return events.map((e) => ({ key: String(e.key || "") }));
|
|
1143
|
+
}
|
|
1144
|
+
function getTransitions(sm) {
|
|
1145
|
+
const transitions = asArray(sm.transitions);
|
|
1146
|
+
return transitions.map((t) => ({
|
|
1147
|
+
from: String(t.from || ""),
|
|
1148
|
+
event: String(t.event || ""),
|
|
1149
|
+
to: String(t.to || ""),
|
|
1150
|
+
guard: t.guard,
|
|
1151
|
+
effects: asArray(t.effects)
|
|
1152
|
+
}));
|
|
1153
|
+
}
|
|
1154
|
+
function getPagesFromOrbital(orbital) {
|
|
1155
|
+
return asArray(orbital.pages);
|
|
1156
|
+
}
|
|
1157
|
+
function isRenderUiEffect(effect) {
|
|
1158
|
+
return Array.isArray(effect) && effect[0] === "render-ui";
|
|
1159
|
+
}
|
|
1160
|
+
function extractRenderEffects(effects) {
|
|
1161
|
+
const results = [];
|
|
1162
|
+
for (const eff of effects) {
|
|
1163
|
+
if (isRenderUiEffect(eff)) {
|
|
1164
|
+
const arr = eff;
|
|
1165
|
+
const slot = String(arr[1] || "main");
|
|
1166
|
+
const tree = arr[2] || {};
|
|
1167
|
+
results.push({ slot, tree });
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
return results;
|
|
1171
|
+
}
|
|
1172
|
+
function extractNonRenderEffects(effects) {
|
|
1173
|
+
return effects.filter((eff) => !isRenderUiEffect(eff));
|
|
1174
|
+
}
|
|
1175
|
+
function extractBehaviorGateData(behavior) {
|
|
1176
|
+
const orbitals = getOrbitals(behavior);
|
|
1177
|
+
const entityNames = [];
|
|
1178
|
+
const traitNames = [];
|
|
1179
|
+
for (const orbital of orbitals) {
|
|
1180
|
+
const entity = orbital.entity;
|
|
1181
|
+
if (entity?.name) entityNames.push(String(entity.name));
|
|
1182
|
+
for (const trait of getTraitsFromOrbital(orbital)) {
|
|
1183
|
+
if (trait.name) traitNames.push(String(trait.name));
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
const appLevel = {
|
|
1187
|
+
orbitalCount: orbitals.length,
|
|
1188
|
+
entityNames,
|
|
1189
|
+
traitNames
|
|
1190
|
+
};
|
|
1191
|
+
const orbitalLevels = orbitals.map((orbital) => {
|
|
1192
|
+
const entity = orbital.entity || {};
|
|
1193
|
+
const traits = getTraitsFromOrbital(orbital);
|
|
1194
|
+
const pages = getPagesFromOrbital(orbital);
|
|
1195
|
+
return {
|
|
1196
|
+
orbitalName: String(orbital.name || ""),
|
|
1197
|
+
entity,
|
|
1198
|
+
traitNames: traits.map((t) => String(t.name || "")),
|
|
1199
|
+
pages
|
|
1200
|
+
};
|
|
1201
|
+
});
|
|
1202
|
+
const traitLevels = [];
|
|
1203
|
+
for (const orbital of orbitals) {
|
|
1204
|
+
for (const trait of getTraitsFromOrbital(orbital)) {
|
|
1205
|
+
const sm = getStateMachine(trait);
|
|
1206
|
+
if (!sm) continue;
|
|
1207
|
+
const states = getStates(sm);
|
|
1208
|
+
const events = getEvents(sm);
|
|
1209
|
+
const transitions = getTransitions(sm);
|
|
1210
|
+
traitLevels.push({
|
|
1211
|
+
traitName: String(trait.name || ""),
|
|
1212
|
+
states: states.map((s) => s.name),
|
|
1213
|
+
events: events.map((e) => e.key),
|
|
1214
|
+
transitionEdges: transitions.map((t) => ({
|
|
1215
|
+
from: t.from,
|
|
1216
|
+
event: t.event,
|
|
1217
|
+
to: t.to
|
|
1218
|
+
}))
|
|
1219
|
+
});
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
const transitionLevels = [];
|
|
1223
|
+
for (const orbital of orbitals) {
|
|
1224
|
+
for (const trait of getTraitsFromOrbital(orbital)) {
|
|
1225
|
+
const sm = getStateMachine(trait);
|
|
1226
|
+
if (!sm) continue;
|
|
1227
|
+
for (const transition of getTransitions(sm)) {
|
|
1228
|
+
const effects = transition.effects || [];
|
|
1229
|
+
transitionLevels.push({
|
|
1230
|
+
from: transition.from,
|
|
1231
|
+
event: transition.event,
|
|
1232
|
+
to: transition.to,
|
|
1233
|
+
guard: transition.guard || null,
|
|
1234
|
+
nonRenderEffects: extractNonRenderEffects(effects)
|
|
1235
|
+
});
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
const renderUiLevels = [];
|
|
1240
|
+
for (const orbital of orbitals) {
|
|
1241
|
+
for (const trait of getTraitsFromOrbital(orbital)) {
|
|
1242
|
+
const sm = getStateMachine(trait);
|
|
1243
|
+
if (!sm) continue;
|
|
1244
|
+
for (const transition of getTransitions(sm)) {
|
|
1245
|
+
const effects = transition.effects || [];
|
|
1246
|
+
const renderEffects = extractRenderEffects(effects);
|
|
1247
|
+
if (renderEffects.length > 0) {
|
|
1248
|
+
renderUiLevels.push({
|
|
1249
|
+
from: transition.from,
|
|
1250
|
+
event: transition.event,
|
|
1251
|
+
to: transition.to,
|
|
1252
|
+
renderEffects
|
|
1253
|
+
});
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
return {
|
|
1259
|
+
appLevel,
|
|
1260
|
+
orbitalLevels,
|
|
1261
|
+
traitLevels,
|
|
1262
|
+
transitionLevels,
|
|
1263
|
+
renderUiLevels
|
|
1264
|
+
};
|
|
1265
|
+
}
|
|
1266
|
+
function getTraitGateData(data, traitName) {
|
|
1267
|
+
return data.traitLevels.find((t) => t.traitName === traitName);
|
|
1268
|
+
}
|
|
1269
|
+
function getTransitionGateData(data, from, event) {
|
|
1270
|
+
return {
|
|
1271
|
+
transition: data.transitionLevels.find((t) => t.from === from && t.event === event),
|
|
1272
|
+
renderUi: data.renderUiLevels.find((t) => t.from === from && t.event === event)
|
|
1273
|
+
};
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
// src/gates/scoring.ts
|
|
1277
|
+
function jaccard(a, b) {
|
|
1278
|
+
if (a.size === 0 && b.size === 0) return 1;
|
|
1279
|
+
const intersection = new Set([...a].filter((x) => b.has(x)));
|
|
1280
|
+
const union = /* @__PURE__ */ new Set([...a, ...b]);
|
|
1281
|
+
return intersection.size / union.size;
|
|
1282
|
+
}
|
|
1283
|
+
function symmetricDifferenceNormalized(a, b) {
|
|
1284
|
+
if (a.size === 0 && b.size === 0) return 1;
|
|
1285
|
+
const symDiff = /* @__PURE__ */ new Set([
|
|
1286
|
+
...[...a].filter((x) => !b.has(x)),
|
|
1287
|
+
...[...b].filter((x) => !a.has(x))
|
|
1288
|
+
]);
|
|
1289
|
+
const union = /* @__PURE__ */ new Set([...a, ...b]);
|
|
1290
|
+
return 1 - symDiff.size / union.size;
|
|
1291
|
+
}
|
|
1292
|
+
function coverageRatio(produced, golden) {
|
|
1293
|
+
if (golden.size === 0) return 1;
|
|
1294
|
+
const covered = [...golden].filter((x) => produced.has(x));
|
|
1295
|
+
return covered.length / golden.size;
|
|
1296
|
+
}
|
|
1297
|
+
function scoreGate0(orb, golden) {
|
|
1298
|
+
const orbitals = orb.orbitals;
|
|
1299
|
+
const countDiff = Math.abs(orbitals.length - golden.appLevel.orbitalCount);
|
|
1300
|
+
const countScore = countDiff === 0 ? 1 : countDiff === 1 ? 0.5 : 0;
|
|
1301
|
+
const producedEntities = new Set(
|
|
1302
|
+
orbitals.map((o) => String(o.entity.name).toLowerCase())
|
|
1303
|
+
);
|
|
1304
|
+
const goldenEntities = new Set(golden.appLevel.entityNames.map((n) => n.toLowerCase()));
|
|
1305
|
+
const entityJaccard = jaccard(producedEntities, goldenEntities);
|
|
1306
|
+
const score = countScore * 0.4 + entityJaccard * 0.6;
|
|
1307
|
+
return {
|
|
1308
|
+
gate: 0,
|
|
1309
|
+
score,
|
|
1310
|
+
details: { countScore, entityJaccard }
|
|
1311
|
+
};
|
|
1312
|
+
}
|
|
1313
|
+
function scoreGate1(orb, golden) {
|
|
1314
|
+
const orbitals = orb.orbitals;
|
|
1315
|
+
if (orbitals.length === 0) {
|
|
1316
|
+
return { gate: 1, score: 0, details: { fieldJaccard: 0, persistenceMatch: 0 } };
|
|
1317
|
+
}
|
|
1318
|
+
const goldenOrbital = golden.orbitalLevels[0];
|
|
1319
|
+
if (!goldenOrbital) {
|
|
1320
|
+
return { gate: 1, score: 0, details: { fieldJaccard: 0, persistenceMatch: 0 } };
|
|
1321
|
+
}
|
|
1322
|
+
const entity = orbitals[0].entity;
|
|
1323
|
+
const producedFields = new Set(entity.fields.map((f) => f.name.toLowerCase()));
|
|
1324
|
+
const goldenEntity = goldenOrbital.entity;
|
|
1325
|
+
const goldenFields = new Set(
|
|
1326
|
+
Array.isArray(goldenEntity.fields) ? goldenEntity.fields.map((f) => f.name.toLowerCase()) : []
|
|
1327
|
+
);
|
|
1328
|
+
const fieldJaccard = jaccard(producedFields, goldenFields);
|
|
1329
|
+
const goldenPersistence = String(goldenEntity.persistence || "runtime");
|
|
1330
|
+
const persistenceMatch = entity.persistence === goldenPersistence ? 1 : 0;
|
|
1331
|
+
const score = fieldJaccard * 0.7 + persistenceMatch * 0.3;
|
|
1332
|
+
return {
|
|
1333
|
+
gate: 1,
|
|
1334
|
+
score,
|
|
1335
|
+
details: { fieldJaccard, persistenceMatch }
|
|
1336
|
+
};
|
|
1337
|
+
}
|
|
1338
|
+
function scoreGate2(orb, golden) {
|
|
1339
|
+
const traits = extractAllTraits(orb);
|
|
1340
|
+
if (traits.length === 0 || golden.traitLevels.length === 0) {
|
|
1341
|
+
return { gate: 2, score: 0, details: { stateSymDiff: 0, eventJaccard: 0 } };
|
|
1342
|
+
}
|
|
1343
|
+
const trait = traits.find((t) => t.stateMachine) || traits[0];
|
|
1344
|
+
const goldenTrait = golden.traitLevels.find(
|
|
1345
|
+
(t) => t.traitName.toLowerCase() === trait.name.toLowerCase()
|
|
1346
|
+
) || golden.traitLevels[0];
|
|
1347
|
+
if (!goldenTrait || !trait.stateMachine) {
|
|
1348
|
+
return { gate: 2, score: 0, details: { stateSymDiff: 0, eventJaccard: 0 } };
|
|
1349
|
+
}
|
|
1350
|
+
const producedStates = new Set(trait.stateMachine.states.map((s) => s.name.toLowerCase()));
|
|
1351
|
+
const goldenStates = new Set(goldenTrait.states.map((s) => s.toLowerCase()));
|
|
1352
|
+
const stateSymDiff = symmetricDifferenceNormalized(producedStates, goldenStates);
|
|
1353
|
+
const producedEvents = new Set(trait.stateMachine.events.map((e) => e.key));
|
|
1354
|
+
const goldenEvents = new Set(goldenTrait.events);
|
|
1355
|
+
const eventJaccard = jaccard(producedEvents, goldenEvents);
|
|
1356
|
+
const score = stateSymDiff * 0.5 + eventJaccard * 0.5;
|
|
1357
|
+
return {
|
|
1358
|
+
gate: 2,
|
|
1359
|
+
score,
|
|
1360
|
+
details: { stateSymDiff, eventJaccard }
|
|
1361
|
+
};
|
|
1362
|
+
}
|
|
1363
|
+
function scoreGate3(orb, golden) {
|
|
1364
|
+
const transitions = extractAllTransitions(orb);
|
|
1365
|
+
if (transitions.length === 0) {
|
|
1366
|
+
return { gate: 3, score: 0.5, details: { effectCoverage: 0.5, guardMatch: 0.5 } };
|
|
1367
|
+
}
|
|
1368
|
+
let totalEffectCoverage = 0;
|
|
1369
|
+
let totalGuardMatch = 0;
|
|
1370
|
+
let count = 0;
|
|
1371
|
+
for (const transition of transitions) {
|
|
1372
|
+
const goldenTransition = golden.transitionLevels.find(
|
|
1373
|
+
(t) => t.from === transition.from && t.event === transition.event
|
|
1374
|
+
);
|
|
1375
|
+
if (!goldenTransition) {
|
|
1376
|
+
totalEffectCoverage += 0.5;
|
|
1377
|
+
totalGuardMatch += 0.5;
|
|
1378
|
+
count++;
|
|
1379
|
+
continue;
|
|
1380
|
+
}
|
|
1381
|
+
const producedTypes = new Set(
|
|
1382
|
+
(transition.effects || []).filter((e) => Array.isArray(e)).map((e) => String(e[0])).filter((t) => t !== "render-ui")
|
|
1383
|
+
);
|
|
1384
|
+
const goldenTypes = new Set(
|
|
1385
|
+
goldenTransition.nonRenderEffects.filter((e) => Array.isArray(e)).map((e) => String(e[0]))
|
|
1386
|
+
);
|
|
1387
|
+
totalEffectCoverage += coverageRatio(producedTypes, goldenTypes);
|
|
1388
|
+
const hasGuard = !!transition.guard;
|
|
1389
|
+
const goldenHasGuard = !!goldenTransition.guard;
|
|
1390
|
+
totalGuardMatch += hasGuard === goldenHasGuard ? 1 : 0;
|
|
1391
|
+
count++;
|
|
1392
|
+
}
|
|
1393
|
+
const effectCoverage = count > 0 ? totalEffectCoverage / count : 0.5;
|
|
1394
|
+
const guardMatch = count > 0 ? totalGuardMatch / count : 0.5;
|
|
1395
|
+
const score = effectCoverage * 0.7 + guardMatch * 0.3;
|
|
1396
|
+
return {
|
|
1397
|
+
gate: 3,
|
|
1398
|
+
score,
|
|
1399
|
+
details: { effectCoverage, guardMatch }
|
|
1400
|
+
};
|
|
1401
|
+
}
|
|
1402
|
+
function scoreGate4(orb, golden) {
|
|
1403
|
+
const transitions = extractAllTransitions(orb);
|
|
1404
|
+
if (transitions.length === 0 || golden.renderUiLevels.length === 0) {
|
|
1405
|
+
return { gate: 4, score: 0.5, details: { typeRecall: 0.5, nodeRatio: 0.5 } };
|
|
1406
|
+
}
|
|
1407
|
+
let totalTypeRecall = 0;
|
|
1408
|
+
let totalNodeRatio = 0;
|
|
1409
|
+
let count = 0;
|
|
1410
|
+
for (const transition of transitions) {
|
|
1411
|
+
const goldenRender = golden.renderUiLevels.find(
|
|
1412
|
+
(r) => r.from === transition.from && r.event === transition.event
|
|
1413
|
+
);
|
|
1414
|
+
if (!goldenRender) continue;
|
|
1415
|
+
const renderEffects = (transition.effects || []).filter((e) => Array.isArray(e) && e[0] === "render-ui").map((e) => {
|
|
1416
|
+
const arr = e;
|
|
1417
|
+
return { slot: String(arr[1]), tree: arr[2] || {} };
|
|
1418
|
+
});
|
|
1419
|
+
const producedTypes = /* @__PURE__ */ new Set();
|
|
1420
|
+
let producedNodes = 0;
|
|
1421
|
+
for (const re of renderEffects) {
|
|
1422
|
+
for (const t of collectTypes(re.tree)) producedTypes.add(t);
|
|
1423
|
+
producedNodes += countNodes(re.tree);
|
|
1424
|
+
}
|
|
1425
|
+
const goldenTypes = /* @__PURE__ */ new Set();
|
|
1426
|
+
let goldenNodes = 0;
|
|
1427
|
+
for (const re of goldenRender.renderEffects) {
|
|
1428
|
+
for (const t of collectTypes(re.tree)) goldenTypes.add(t);
|
|
1429
|
+
goldenNodes += countNodes(re.tree);
|
|
1430
|
+
}
|
|
1431
|
+
totalTypeRecall += coverageRatio(producedTypes, goldenTypes);
|
|
1432
|
+
totalNodeRatio += goldenNodes > 0 ? Math.min(producedNodes / goldenNodes, goldenNodes / Math.max(producedNodes, 1)) : producedNodes === 0 ? 1 : 0;
|
|
1433
|
+
count++;
|
|
1434
|
+
}
|
|
1435
|
+
const typeRecall = count > 0 ? totalTypeRecall / count : 0.5;
|
|
1436
|
+
const nodeRatio = count > 0 ? totalNodeRatio / count : 0.5;
|
|
1437
|
+
const score = typeRecall * 0.6 + nodeRatio * 0.4;
|
|
1438
|
+
return {
|
|
1439
|
+
gate: 4,
|
|
1440
|
+
score,
|
|
1441
|
+
details: { typeRecall, nodeRatio }
|
|
1442
|
+
};
|
|
1443
|
+
}
|
|
1444
|
+
var GATE_WEIGHTS = [0.1, 0.15, 0.25, 0.2, 0.3];
|
|
1445
|
+
function scoreOverall(gateScores, provider, mode, behaviorName) {
|
|
1446
|
+
let overall = 0;
|
|
1447
|
+
for (const gs of gateScores) {
|
|
1448
|
+
const weight = GATE_WEIGHTS[gs.gate] ?? 0;
|
|
1449
|
+
overall += gs.score * weight;
|
|
1450
|
+
}
|
|
1451
|
+
return {
|
|
1452
|
+
overall,
|
|
1453
|
+
gates: gateScores,
|
|
1454
|
+
provider,
|
|
1455
|
+
mode,
|
|
1456
|
+
behaviorName
|
|
1457
|
+
};
|
|
1458
|
+
}
|
|
1459
|
+
function extractAllTraits(orb) {
|
|
1460
|
+
const traits = [];
|
|
1461
|
+
for (const orbital of orb.orbitals) {
|
|
1462
|
+
for (const t of orbital.traits) {
|
|
1463
|
+
if (typeof t === "string" || "ref" in t) continue;
|
|
1464
|
+
traits.push(t);
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
return traits;
|
|
1468
|
+
}
|
|
1469
|
+
function extractAllTransitions(orb) {
|
|
1470
|
+
const transitions = [];
|
|
1471
|
+
for (const trait of extractAllTraits(orb)) {
|
|
1472
|
+
if (!trait.stateMachine) continue;
|
|
1473
|
+
transitions.push(...trait.stateMachine.transitions);
|
|
1474
|
+
}
|
|
1475
|
+
return transitions;
|
|
1476
|
+
}
|
|
1477
|
+
function collectTypes(tree) {
|
|
1478
|
+
const types = /* @__PURE__ */ new Set();
|
|
1479
|
+
if (tree.type) types.add(String(tree.type));
|
|
1480
|
+
const children = tree.children;
|
|
1481
|
+
if (Array.isArray(children)) {
|
|
1482
|
+
for (const child of children) {
|
|
1483
|
+
for (const t of collectTypes(child)) types.add(t);
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
return types;
|
|
1487
|
+
}
|
|
1488
|
+
function countNodes(tree) {
|
|
1489
|
+
let count = 1;
|
|
1490
|
+
const children = tree.children;
|
|
1491
|
+
if (Array.isArray(children)) {
|
|
1492
|
+
for (const child of children) count += countNodes(child);
|
|
1493
|
+
}
|
|
1494
|
+
return count;
|
|
1495
|
+
}
|
|
1496
|
+
init_gate05_behavior_match();
|
|
1497
|
+
function createDecomposeAppTool() {
|
|
1498
|
+
return tool(
|
|
1499
|
+
async (input) => {
|
|
1500
|
+
const client = createDeepSeekClient();
|
|
1501
|
+
const orb = await runGate0(client, input.prompt);
|
|
1502
|
+
return {
|
|
1503
|
+
appName: orb.name,
|
|
1504
|
+
orbitals: orb.orbitals.map((o) => ({
|
|
1505
|
+
name: o.name,
|
|
1506
|
+
entityName: o.entity.name,
|
|
1507
|
+
traitNames: o.traits.filter((t) => typeof t !== "string" && !("ref" in t)).map((t) => t.name),
|
|
1508
|
+
pageCount: o.pages?.length ?? 0
|
|
1509
|
+
})),
|
|
1510
|
+
shellOrb: orb
|
|
1511
|
+
};
|
|
1512
|
+
},
|
|
1513
|
+
{
|
|
1514
|
+
name: "decompose_app",
|
|
1515
|
+
description: "Decompose a natural language prompt into an orbital plan. Returns the app name, orbital list with entity names, and a shell .orb schema.",
|
|
1516
|
+
schema: z.object({
|
|
1517
|
+
prompt: z.string().describe("Natural language application description")
|
|
1518
|
+
})
|
|
1519
|
+
}
|
|
1520
|
+
);
|
|
1521
|
+
}
|
|
1522
|
+
function createMatchBehaviorTool() {
|
|
1523
|
+
return tool(
|
|
1524
|
+
async (input) => {
|
|
1525
|
+
const client = createDeepSeekClient();
|
|
1526
|
+
const shellOrb = {
|
|
1527
|
+
orbitals: [{
|
|
1528
|
+
name: input.orbitalName,
|
|
1529
|
+
entity: { name: input.entityName, fields: [{ name: "id", type: "string" }] },
|
|
1530
|
+
traits: [],
|
|
1531
|
+
pages: []
|
|
1532
|
+
}]
|
|
1533
|
+
};
|
|
1534
|
+
const result = await runGate05(client, input.description, shellOrb);
|
|
1535
|
+
return {
|
|
1536
|
+
matched: result.matchedName !== null,
|
|
1537
|
+
behaviorName: result.matchedName,
|
|
1538
|
+
hasGoldenOrb: result.goldenOrb !== null
|
|
1539
|
+
};
|
|
1540
|
+
},
|
|
1541
|
+
{
|
|
1542
|
+
name: "match_behavior",
|
|
1543
|
+
description: "Find the best matching golden behavior from 107 standard behaviors. Returns the match name and whether a golden .orb reference was loaded.",
|
|
1544
|
+
schema: z.object({
|
|
1545
|
+
description: z.string().describe("Description of the orbital to match"),
|
|
1546
|
+
orbitalName: z.string().describe("Name of the orbital"),
|
|
1547
|
+
entityName: z.string().describe("Name of the entity")
|
|
1548
|
+
})
|
|
1549
|
+
}
|
|
1550
|
+
);
|
|
1551
|
+
}
|
|
1552
|
+
function createBuildOrbitalTool(workDir) {
|
|
1553
|
+
return tool(
|
|
1554
|
+
async (input) => {
|
|
1555
|
+
const client = createDeepSeekClient();
|
|
1556
|
+
const orbitalsDir = path4.join(workDir, ".orbitals");
|
|
1557
|
+
fs4.mkdirSync(orbitalsDir, { recursive: true });
|
|
1558
|
+
let shellOrb;
|
|
1559
|
+
try {
|
|
1560
|
+
shellOrb = JSON.parse(input.shellOrb);
|
|
1561
|
+
} catch {
|
|
1562
|
+
return { success: false, error: "Invalid shellOrb JSON" };
|
|
1563
|
+
}
|
|
1564
|
+
let goldenOrb;
|
|
1565
|
+
if (input.goldenBehavior) {
|
|
1566
|
+
const { loadGoldenOrbByName } = await Promise.resolve().then(() => (init_gate05_behavior_match(), gate05_behavior_match_exports));
|
|
1567
|
+
goldenOrb = loadGoldenOrbByName(input.goldenBehavior) ?? void 0;
|
|
1568
|
+
}
|
|
1569
|
+
const opts = {
|
|
1570
|
+
goldenOrb
|
|
1571
|
+
};
|
|
1572
|
+
input.feedback;
|
|
1573
|
+
try {
|
|
1574
|
+
console.log(`[build_orbital] Gate 1: enriching entity for ${input.orbitalName}`);
|
|
1575
|
+
let orb = await runGate1(client, shellOrb, opts);
|
|
1576
|
+
console.log(`[build_orbital] Gate 2: filling state machines`);
|
|
1577
|
+
orb = await runGate2(client, orb, opts);
|
|
1578
|
+
console.log(`[build_orbital] Gate 3: adding effects`);
|
|
1579
|
+
orb = await runGate3(client, orb, opts);
|
|
1580
|
+
console.log(`[build_orbital] Gate 4: adding render-ui`);
|
|
1581
|
+
orb = await runGate4(client, orb, opts);
|
|
1582
|
+
for (const orbital of orb.orbitals) {
|
|
1583
|
+
const safeName = orbital.name.replace(/[^a-zA-Z0-9]/g, "_").toLowerCase();
|
|
1584
|
+
const orbitalPath = path4.join(orbitalsDir, `${safeName}.json`);
|
|
1585
|
+
fs4.writeFileSync(orbitalPath, JSON.stringify(orbital, null, 2));
|
|
1586
|
+
console.log(`[build_orbital] Written: ${orbitalPath}`);
|
|
1587
|
+
}
|
|
1588
|
+
fs4.mkdirSync(workDir, { recursive: true });
|
|
1589
|
+
const validation = validateWithOrbitalCLI(orb, workDir);
|
|
1590
|
+
return {
|
|
1591
|
+
success: true,
|
|
1592
|
+
orbitalName: input.orbitalName,
|
|
1593
|
+
orbitalsWritten: orb.orbitals.length,
|
|
1594
|
+
validation: {
|
|
1595
|
+
valid: validation.valid,
|
|
1596
|
+
errorCount: validation.errors?.length ?? 0,
|
|
1597
|
+
errors: (validation.errors ?? []).slice(0, 5).map((e) => e.message)
|
|
1598
|
+
}
|
|
1599
|
+
};
|
|
1600
|
+
} catch (error) {
|
|
1601
|
+
return {
|
|
1602
|
+
success: false,
|
|
1603
|
+
orbitalName: input.orbitalName,
|
|
1604
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1605
|
+
};
|
|
1606
|
+
}
|
|
1607
|
+
},
|
|
1608
|
+
{
|
|
1609
|
+
name: "build_orbital",
|
|
1610
|
+
description: "Build one orbital by running the gate pipeline (entity -> state machine -> effects -> render-ui). Writes the result to .orbitals/ for finish_task to collect. Optionally takes a golden behavior name for guided mode and feedback for retry.",
|
|
1611
|
+
schema: z.object({
|
|
1612
|
+
orbitalName: z.string().describe("Name of the orbital to build"),
|
|
1613
|
+
shellOrb: z.string().describe("JSON string of the shell OrbitalSchema from decompose_app"),
|
|
1614
|
+
goldenBehavior: z.string().optional().describe('Golden behavior name from match_behavior (e.g. "std-cart")'),
|
|
1615
|
+
feedback: z.string().optional().describe("Feedback from a failed verify_app to guide the rebuild")
|
|
1616
|
+
})
|
|
1617
|
+
}
|
|
1618
|
+
);
|
|
1619
|
+
}
|
|
1620
|
+
function createVerifyAppTool(workDir) {
|
|
1621
|
+
return tool(
|
|
1622
|
+
async (input) => {
|
|
1623
|
+
const orbPath = input.orbPath || path4.join(workDir, "schema.orb");
|
|
1624
|
+
if (!fs4.existsSync(orbPath)) {
|
|
1625
|
+
return { success: false, error: `Schema file not found: ${orbPath}` };
|
|
1626
|
+
}
|
|
1627
|
+
try {
|
|
1628
|
+
const verifyApiPath = path4.resolve(
|
|
1629
|
+
path4.dirname(new URL(import.meta.url).pathname),
|
|
1630
|
+
"../../../../../tools/orbital-verify-unified/src/api.js"
|
|
1631
|
+
);
|
|
1632
|
+
const { verifyApp } = await import(verifyApiPath);
|
|
1633
|
+
const result = await verifyApp({
|
|
1634
|
+
orbPath,
|
|
1635
|
+
headed: true,
|
|
1636
|
+
screenshots: true
|
|
1637
|
+
});
|
|
1638
|
+
const failures = [];
|
|
1639
|
+
for (const phase of result.phases) {
|
|
1640
|
+
for (const check of phase.checks) {
|
|
1641
|
+
if (!check.passed) {
|
|
1642
|
+
failures.push(`[Phase ${phase.phase} ${phase.name}] ${check.label}: ${check.detail || "failed"}`);
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
return {
|
|
1647
|
+
success: result.totalFailed === 0,
|
|
1648
|
+
passed: result.totalPassed,
|
|
1649
|
+
failed: result.totalFailed,
|
|
1650
|
+
total: result.totalChecks,
|
|
1651
|
+
coverage: result.coverage?.ratio ?? null,
|
|
1652
|
+
failures: failures.slice(0, 10)
|
|
1653
|
+
};
|
|
1654
|
+
} catch (error) {
|
|
1655
|
+
return {
|
|
1656
|
+
success: false,
|
|
1657
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1658
|
+
passed: 0,
|
|
1659
|
+
failed: 1,
|
|
1660
|
+
total: 1,
|
|
1661
|
+
coverage: null,
|
|
1662
|
+
failures: [error instanceof Error ? error.message : String(error)]
|
|
1663
|
+
};
|
|
1664
|
+
}
|
|
1665
|
+
},
|
|
1666
|
+
{
|
|
1667
|
+
name: "verify_app",
|
|
1668
|
+
description: "Run orbital-verify on a compiled .orb schema. Compiles the app, runs server tests, browser tests, and captures screenshots. Returns pass/fail counts and failure details.",
|
|
1669
|
+
schema: z.object({
|
|
1670
|
+
orbPath: z.string().optional().describe("Path to .orb file. Defaults to {workDir}/schema.orb")
|
|
1671
|
+
})
|
|
1672
|
+
}
|
|
1673
|
+
);
|
|
1674
|
+
}
|
|
1675
|
+
var BEHAVIOR_FUNCTIONS = {};
|
|
1676
|
+
async function loadBehaviorFunctions() {
|
|
1677
|
+
if (Object.keys(BEHAVIOR_FUNCTIONS).length > 0) return;
|
|
1678
|
+
try {
|
|
1679
|
+
const fns = await import('@almadar/std/behaviors/functions/index.js');
|
|
1680
|
+
const mapping = {
|
|
1681
|
+
// Molecules: CRUD/Data
|
|
1682
|
+
"std-list": "stdList",
|
|
1683
|
+
"std-cart": "stdCart",
|
|
1684
|
+
"std-detail": "stdDetail",
|
|
1685
|
+
"std-inventory": "stdInventory",
|
|
1686
|
+
"std-messaging": "stdMessaging",
|
|
1687
|
+
"std-geospatial": "stdGeospatial",
|
|
1688
|
+
// Atoms: UI
|
|
1689
|
+
"std-browse": "stdBrowse",
|
|
1690
|
+
"std-modal": "stdModal",
|
|
1691
|
+
"std-confirmation": "stdConfirmation",
|
|
1692
|
+
"std-search": "stdSearch",
|
|
1693
|
+
"std-filter": "stdFilter",
|
|
1694
|
+
"std-sort": "stdSort",
|
|
1695
|
+
"std-pagination": "stdPagination",
|
|
1696
|
+
"std-drawer": "stdDrawer",
|
|
1697
|
+
"std-notification": "stdNotification",
|
|
1698
|
+
"std-timer": "stdTimer",
|
|
1699
|
+
"std-tabs": "stdTabs",
|
|
1700
|
+
"std-loading": "stdLoading",
|
|
1701
|
+
"std-selection": "stdSelection",
|
|
1702
|
+
"std-undo": "stdUndo",
|
|
1703
|
+
"std-input": "stdInput",
|
|
1704
|
+
// Atoms: Domain
|
|
1705
|
+
"std-wizard": "stdWizard",
|
|
1706
|
+
"std-display": "stdDisplay",
|
|
1707
|
+
"std-async": "stdAsync",
|
|
1708
|
+
"std-combat": "stdCombat",
|
|
1709
|
+
"std-gameflow": "stdGameflow",
|
|
1710
|
+
"std-movement": "stdMovement",
|
|
1711
|
+
"std-quest": "stdQuest",
|
|
1712
|
+
"std-overworld": "stdOverworld",
|
|
1713
|
+
"std-circuit-breaker": "stdCircuitBreaker",
|
|
1714
|
+
"std-cache-aside": "stdCacheAside",
|
|
1715
|
+
"std-score": "stdScore",
|
|
1716
|
+
"std-collision": "stdCollision",
|
|
1717
|
+
"std-physics2d": "stdPhysics2d",
|
|
1718
|
+
"std-rate-limiter": "stdRateLimiter",
|
|
1719
|
+
// Atoms: Game UI
|
|
1720
|
+
"std-game-hud": "stdGameHud",
|
|
1721
|
+
"std-score-board": "stdScoreBoard",
|
|
1722
|
+
"std-game-menu": "stdGameMenu",
|
|
1723
|
+
"std-game-over-screen": "stdGameOverScreen",
|
|
1724
|
+
"std-dialogue-box": "stdDialogueBox",
|
|
1725
|
+
"std-inventory-panel": "stdInventoryPanel",
|
|
1726
|
+
"std-combat-log": "stdCombatLog",
|
|
1727
|
+
"std-sprite": "stdSprite",
|
|
1728
|
+
"std-game-audio": "stdGameAudio",
|
|
1729
|
+
// Atoms: Game Canvas
|
|
1730
|
+
"std-isometric-canvas": "stdIsometricCanvas",
|
|
1731
|
+
"std-platformer-canvas": "stdPlatformerCanvas",
|
|
1732
|
+
"std-simulation-canvas": "stdSimulationCanvas",
|
|
1733
|
+
"std-game-canvas-2d": "stdGameCanvas2d",
|
|
1734
|
+
"std-game-canvas-3d": "stdGameCanvas3d",
|
|
1735
|
+
// Molecules: Game
|
|
1736
|
+
"std-turn-based-battle": "stdTurnBasedBattle",
|
|
1737
|
+
"std-platformer-game": "stdPlatformerGame",
|
|
1738
|
+
"std-puzzle-game": "stdPuzzleGame",
|
|
1739
|
+
// Molecules: Educational
|
|
1740
|
+
"std-builder-game": "stdBuilderGame",
|
|
1741
|
+
"std-classifier-game": "stdClassifierGame",
|
|
1742
|
+
"std-sequencer-game": "stdSequencerGame",
|
|
1743
|
+
"std-debugger-game": "stdDebuggerGame",
|
|
1744
|
+
"std-negotiator-game": "stdNegotiatorGame",
|
|
1745
|
+
"std-simulator-game": "stdSimulatorGame",
|
|
1746
|
+
"std-event-handler-game": "stdEventHandlerGame"
|
|
1747
|
+
};
|
|
1748
|
+
for (const [behaviorName, fnName] of Object.entries(mapping)) {
|
|
1749
|
+
if (typeof fns[fnName] === "function") {
|
|
1750
|
+
BEHAVIOR_FUNCTIONS[behaviorName] = fns[fnName];
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
} catch {
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
function createUseBehaviorTool(workDir) {
|
|
1757
|
+
return tool(
|
|
1758
|
+
async (input) => {
|
|
1759
|
+
await loadBehaviorFunctions();
|
|
1760
|
+
const newEntityName = input.entityName;
|
|
1761
|
+
const newFields = JSON.parse(input.fieldsJson);
|
|
1762
|
+
let finalOrbital;
|
|
1763
|
+
const behaviorFn = BEHAVIOR_FUNCTIONS[input.behaviorName];
|
|
1764
|
+
if (behaviorFn) {
|
|
1765
|
+
console.log(`[use_behavior] Calling ${input.behaviorName} as pure function`);
|
|
1766
|
+
const orbital = behaviorFn({
|
|
1767
|
+
entityName: newEntityName,
|
|
1768
|
+
fields: newFields,
|
|
1769
|
+
persistence: input.persistence ?? "runtime",
|
|
1770
|
+
pageTitle: input.title,
|
|
1771
|
+
createButtonLabel: input.createButtonLabel,
|
|
1772
|
+
pageName: input.pageName,
|
|
1773
|
+
pagePath: input.pagePath,
|
|
1774
|
+
isInitial: input.isInitialPage
|
|
1775
|
+
});
|
|
1776
|
+
finalOrbital = orbital;
|
|
1777
|
+
} else {
|
|
1778
|
+
console.log(`[use_behavior] Falling back to substitution for ${input.behaviorName}`);
|
|
1779
|
+
const { getBehavior } = await import('@almadar/std');
|
|
1780
|
+
const behavior = getBehavior(input.behaviorName);
|
|
1781
|
+
if (!behavior) {
|
|
1782
|
+
return { success: false, error: `Behavior "${input.behaviorName}" not found` };
|
|
1783
|
+
}
|
|
1784
|
+
const cloned = structuredClone(behavior);
|
|
1785
|
+
const orbitalData = cloned.orbitals?.[0];
|
|
1786
|
+
const goldenTrait = orbitalData ? (orbitalData.traits ?? [])[0] : cloned;
|
|
1787
|
+
const goldenEntity = orbitalData?.entity;
|
|
1788
|
+
if (!goldenTrait?.stateMachine) {
|
|
1789
|
+
return { success: false, error: `Behavior "${input.behaviorName}" has no state machine` };
|
|
1790
|
+
}
|
|
1791
|
+
if (!newFields.some((f) => f.name === "id")) {
|
|
1792
|
+
newFields.unshift({ name: "id", type: "string", required: true });
|
|
1793
|
+
}
|
|
1794
|
+
const oldEntityName = String(goldenEntity?.name ?? goldenTrait.linkedEntity ?? "Item");
|
|
1795
|
+
const traitName = `${newEntityName}Management`;
|
|
1796
|
+
const orbital = {
|
|
1797
|
+
name: `${newEntityName}Orbital`,
|
|
1798
|
+
entity: {
|
|
1799
|
+
name: newEntityName,
|
|
1800
|
+
persistence: input.persistence ?? "runtime",
|
|
1801
|
+
...input.persistence === "persistent" ? { collection: newEntityName.toLowerCase() + "s" } : {},
|
|
1802
|
+
fields: newFields
|
|
1803
|
+
},
|
|
1804
|
+
traits: [{
|
|
1805
|
+
...goldenTrait,
|
|
1806
|
+
name: traitName,
|
|
1807
|
+
linkedEntity: newEntityName,
|
|
1808
|
+
category: String(goldenTrait.category ?? "interaction")
|
|
1809
|
+
}],
|
|
1810
|
+
pages: [{
|
|
1811
|
+
name: input.pageName ?? `${newEntityName}Page`,
|
|
1812
|
+
path: input.pagePath ?? `/${newEntityName.toLowerCase()}s`,
|
|
1813
|
+
...input.isInitialPage ? { isInitial: true } : {},
|
|
1814
|
+
traits: [{ ref: traitName }]
|
|
1815
|
+
}]
|
|
1816
|
+
};
|
|
1817
|
+
const orbStr = JSON.stringify(orbital);
|
|
1818
|
+
const substituted = orbStr.replace(new RegExp(`"entity":\\s*"${oldEntityName}"`, "g"), `"entity": "${newEntityName}"`).replace(new RegExp(`@entity\\.${oldEntityName}\\.`, "g"), `@entity.`).replace(new RegExp(`"${oldEntityName}"`, "g"), `"${newEntityName}"`);
|
|
1819
|
+
try {
|
|
1820
|
+
finalOrbital = JSON.parse(substituted);
|
|
1821
|
+
} catch {
|
|
1822
|
+
finalOrbital = orbital;
|
|
1823
|
+
}
|
|
1824
|
+
if (input.title) {
|
|
1825
|
+
substituteInRenderUi(finalOrbital, "typography", "content", oldEntityName, input.title, true);
|
|
1826
|
+
}
|
|
1827
|
+
if (input.createButtonLabel) {
|
|
1828
|
+
substituteInRenderUi(finalOrbital, "button", "label", null, input.createButtonLabel, false);
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
const orbitalsDir = path4.join(workDir, ".orbitals");
|
|
1832
|
+
fs4.mkdirSync(orbitalsDir, { recursive: true });
|
|
1833
|
+
const safeName = newEntityName.replace(/[^a-zA-Z0-9]/g, "_").toLowerCase();
|
|
1834
|
+
const orbitalPath = path4.join(orbitalsDir, `${safeName}.json`);
|
|
1835
|
+
fs4.writeFileSync(orbitalPath, JSON.stringify(finalOrbital, null, 2));
|
|
1836
|
+
const schema = { name: newEntityName + "App", version: "1.0.0", orbitals: [finalOrbital] };
|
|
1837
|
+
fs4.mkdirSync(workDir, { recursive: true });
|
|
1838
|
+
const validation = validateWithOrbitalCLI(schema, workDir);
|
|
1839
|
+
return {
|
|
1840
|
+
success: true,
|
|
1841
|
+
behaviorName: input.behaviorName,
|
|
1842
|
+
entityName: newEntityName,
|
|
1843
|
+
orbitalPath,
|
|
1844
|
+
usedFunction: !!behaviorFn,
|
|
1845
|
+
validation: {
|
|
1846
|
+
valid: validation.valid ?? false,
|
|
1847
|
+
errorCount: (validation.errors ?? []).length,
|
|
1848
|
+
errors: (validation.errors ?? []).slice(0, 5).map((e) => e.message)
|
|
1849
|
+
}
|
|
1850
|
+
};
|
|
1851
|
+
},
|
|
1852
|
+
{
|
|
1853
|
+
name: "use_behavior",
|
|
1854
|
+
description: "Instantiate a standard behavior with custom entity fields and labels. Zero LLM calls. Calls pure behavior function when available, falls back to substitution otherwise.",
|
|
1855
|
+
schema: z.object({
|
|
1856
|
+
behaviorName: z.string().describe('Standard behavior name (e.g., "std-list", "std-cart")'),
|
|
1857
|
+
entityName: z.string().describe('New entity name in PascalCase (e.g., "Product", "Task")'),
|
|
1858
|
+
fieldsJson: z.string().describe("JSON array of entity fields: [{ name, type, required?, default? }]"),
|
|
1859
|
+
persistence: z.string().optional().describe('"persistent", "runtime", or "singleton"'),
|
|
1860
|
+
title: z.string().optional().describe("Page title override"),
|
|
1861
|
+
createButtonLabel: z.string().optional().describe("Create button label override"),
|
|
1862
|
+
pageName: z.string().optional().describe("Page component name"),
|
|
1863
|
+
pagePath: z.string().optional().describe('Route path (e.g., "/products")'),
|
|
1864
|
+
isInitialPage: z.boolean().optional().describe("Whether this is the initial/home page")
|
|
1865
|
+
})
|
|
1866
|
+
}
|
|
1867
|
+
);
|
|
1868
|
+
}
|
|
1869
|
+
function substituteInRenderUi(orbital, patternType, propName, oldValue, newValue, firstOnly) {
|
|
1870
|
+
const traits = orbital.traits || [];
|
|
1871
|
+
for (const trait of traits) {
|
|
1872
|
+
const sm = trait.stateMachine;
|
|
1873
|
+
if (!sm) continue;
|
|
1874
|
+
const transitions = sm.transitions || [];
|
|
1875
|
+
for (const tr of transitions) {
|
|
1876
|
+
if (tr.event !== "INIT") continue;
|
|
1877
|
+
const effects = tr.effects || [];
|
|
1878
|
+
for (const eff of effects) {
|
|
1879
|
+
if (!Array.isArray(eff) || eff[0] !== "render-ui") continue;
|
|
1880
|
+
const tree = eff[2];
|
|
1881
|
+
if (!tree) continue;
|
|
1882
|
+
walkAndSubstitute(tree, patternType, propName, oldValue, newValue, firstOnly, () => {
|
|
1883
|
+
});
|
|
1884
|
+
}
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
function walkAndSubstitute(node, patternType, propName, oldValue, newValue, firstOnly, onFound) {
|
|
1889
|
+
if (node.type === patternType) {
|
|
1890
|
+
const current = node[propName];
|
|
1891
|
+
if (typeof current === "string") {
|
|
1892
|
+
if (oldValue === null || current.toLowerCase().includes(oldValue.toLowerCase())) {
|
|
1893
|
+
if (!firstOnly || !onFound) {
|
|
1894
|
+
node[propName] = newValue;
|
|
1895
|
+
onFound();
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1900
|
+
const children = node.children;
|
|
1901
|
+
if (Array.isArray(children)) {
|
|
1902
|
+
for (const child of children) {
|
|
1903
|
+
if (child && typeof child === "object") {
|
|
1904
|
+
walkAndSubstitute(child, patternType, propName, oldValue, newValue, firstOnly, onFound);
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
}
|
|
1908
|
+
}
|
|
1909
|
+
function createConnectTool(workDir) {
|
|
1910
|
+
return tool(
|
|
1911
|
+
async (input) => {
|
|
1912
|
+
const emitterPath = path4.join(workDir, ".orbitals", input.emitterFile);
|
|
1913
|
+
const listenerPath = path4.join(workDir, ".orbitals", input.listenerFile);
|
|
1914
|
+
if (!fs4.existsSync(emitterPath)) return { success: false, error: `Emitter not found: ${emitterPath}` };
|
|
1915
|
+
if (!fs4.existsSync(listenerPath)) return { success: false, error: `Listener not found: ${listenerPath}` };
|
|
1916
|
+
const emitter = JSON.parse(fs4.readFileSync(emitterPath, "utf-8"));
|
|
1917
|
+
const listener = JSON.parse(fs4.readFileSync(listenerPath, "utf-8"));
|
|
1918
|
+
const emitterTraits = emitter.traits || [];
|
|
1919
|
+
const listenerTraits = listener.traits || [];
|
|
1920
|
+
if (emitterTraits.length === 0 || listenerTraits.length === 0) {
|
|
1921
|
+
return { success: false, error: "Both orbitals must have at least one trait" };
|
|
1922
|
+
}
|
|
1923
|
+
const emitterTrait = emitterTraits[0];
|
|
1924
|
+
const listenerTrait = listenerTraits[0];
|
|
1925
|
+
const emits = emitterTrait.emits || [];
|
|
1926
|
+
emits.push({
|
|
1927
|
+
event: input.eventName,
|
|
1928
|
+
scope: "external",
|
|
1929
|
+
...input.payloadJson ? { payload: JSON.parse(input.payloadJson) } : {}
|
|
1930
|
+
});
|
|
1931
|
+
emitterTrait.emits = emits;
|
|
1932
|
+
const listens = listenerTrait.listens || [];
|
|
1933
|
+
listens.push({
|
|
1934
|
+
event: input.eventName,
|
|
1935
|
+
triggers: input.triggersEvent || "INIT",
|
|
1936
|
+
scope: "external"
|
|
1937
|
+
});
|
|
1938
|
+
listenerTrait.listens = listens;
|
|
1939
|
+
fs4.writeFileSync(emitterPath, JSON.stringify(emitter, null, 2));
|
|
1940
|
+
fs4.writeFileSync(listenerPath, JSON.stringify(listener, null, 2));
|
|
1941
|
+
return {
|
|
1942
|
+
success: true,
|
|
1943
|
+
event: input.eventName,
|
|
1944
|
+
emitter: String(emitterTrait.name),
|
|
1945
|
+
listener: String(listenerTrait.name)
|
|
1946
|
+
};
|
|
1947
|
+
},
|
|
1948
|
+
{
|
|
1949
|
+
name: "connect_behaviors",
|
|
1950
|
+
description: "Wire a cross-orbital event between two behaviors. Adds emits to the emitter trait and listens to the listener trait.",
|
|
1951
|
+
schema: z.object({
|
|
1952
|
+
emitterFile: z.string().describe('Emitter orbital filename in .orbitals/ (e.g., "product.json")'),
|
|
1953
|
+
listenerFile: z.string().describe('Listener orbital filename in .orbitals/ (e.g., "cart_item.json")'),
|
|
1954
|
+
eventName: z.string().describe('Event name in UPPER_SNAKE_CASE (e.g., "ADD_TO_CART")'),
|
|
1955
|
+
triggersEvent: z.string().optional().describe('Event to trigger on listener (defaults to "INIT")'),
|
|
1956
|
+
payloadJson: z.string().optional().describe("JSON array of payload fields: [{ name, type }]")
|
|
1957
|
+
})
|
|
1958
|
+
}
|
|
1959
|
+
);
|
|
1960
|
+
}
|
|
1961
|
+
function createComposeTool(workDir) {
|
|
1962
|
+
return tool(
|
|
1963
|
+
async (input) => {
|
|
1964
|
+
const orbitalsDir = path4.join(workDir, ".orbitals");
|
|
1965
|
+
const orbitals = [];
|
|
1966
|
+
const orbitalFiles = input.orbitalFiles ? JSON.parse(input.orbitalFiles) : fs4.readdirSync(orbitalsDir).filter((f) => f.endsWith(".json"));
|
|
1967
|
+
for (const file of orbitalFiles) {
|
|
1968
|
+
const filePath = path4.join(orbitalsDir, file);
|
|
1969
|
+
if (!fs4.existsSync(filePath)) continue;
|
|
1970
|
+
orbitals.push(JSON.parse(fs4.readFileSync(filePath, "utf-8")));
|
|
1971
|
+
}
|
|
1972
|
+
if (orbitals.length === 0) {
|
|
1973
|
+
return { success: false, error: "No orbitals found in .orbitals/" };
|
|
1974
|
+
}
|
|
1975
|
+
let pages;
|
|
1976
|
+
if (input.pagesJson) {
|
|
1977
|
+
pages = JSON.parse(input.pagesJson);
|
|
1978
|
+
} else {
|
|
1979
|
+
pages = orbitals.map((o, i) => {
|
|
1980
|
+
const oName = String(o.name || `Orbital${i}`);
|
|
1981
|
+
const entityName = oName.replace(/Orbital$/i, "");
|
|
1982
|
+
const traits = o.traits || [];
|
|
1983
|
+
return {
|
|
1984
|
+
name: `${entityName}Page`,
|
|
1985
|
+
path: `/${entityName.toLowerCase()}s`,
|
|
1986
|
+
...i === 0 ? { isInitial: true } : {},
|
|
1987
|
+
traits: traits.filter((t) => typeof t !== "string" && !("ref" in t)).map((t) => ({ ref: String(t.name) }))
|
|
1988
|
+
};
|
|
1989
|
+
});
|
|
1990
|
+
}
|
|
1991
|
+
for (const orbital of orbitals) {
|
|
1992
|
+
String(orbital.name || "");
|
|
1993
|
+
const orbitalTraitNames = (orbital.traits || []).filter((t) => typeof t !== "string" && !("ref" in t)).map((t) => String(t.name));
|
|
1994
|
+
orbital.pages = pages.filter((p) => {
|
|
1995
|
+
const pageTraits = p.traits || [];
|
|
1996
|
+
return pageTraits.some((pt) => orbitalTraitNames.includes(pt.ref));
|
|
1997
|
+
});
|
|
1998
|
+
}
|
|
1999
|
+
const schema = {
|
|
2000
|
+
name: input.appName || "Application",
|
|
2001
|
+
version: "1.0.0",
|
|
2002
|
+
orbitals
|
|
2003
|
+
};
|
|
2004
|
+
const schemaPath = path4.join(workDir, "schema.orb");
|
|
2005
|
+
fs4.writeFileSync(schemaPath, JSON.stringify(schema, null, 2));
|
|
2006
|
+
const validation = validateWithOrbitalCLI(schema, workDir);
|
|
2007
|
+
return {
|
|
2008
|
+
success: true,
|
|
2009
|
+
appName: schema.name,
|
|
2010
|
+
orbitalCount: orbitals.length,
|
|
2011
|
+
pageCount: pages.length,
|
|
2012
|
+
schemaPath,
|
|
2013
|
+
validation: {
|
|
2014
|
+
valid: validation.valid ?? false,
|
|
2015
|
+
errorCount: (validation.errors || []).length,
|
|
2016
|
+
errors: (validation.errors || []).slice(0, 5).map((e) => e.message)
|
|
2017
|
+
}
|
|
2018
|
+
};
|
|
2019
|
+
},
|
|
2020
|
+
{
|
|
2021
|
+
name: "compose_app",
|
|
2022
|
+
description: "Compose multiple orbitals from .orbitals/ into a final multi-page application schema. Writes schema.orb and validates.",
|
|
2023
|
+
schema: z.object({
|
|
2024
|
+
appName: z.string().optional().describe("Application name"),
|
|
2025
|
+
orbitalFiles: z.string().optional().describe("JSON array of orbital filenames. Defaults to all .json files in .orbitals/"),
|
|
2026
|
+
pagesJson: z.string().optional().describe("JSON array of page definitions: [{ name, path, isInitial?, traits: [{ ref }] }]. Auto-generated if omitted.")
|
|
2027
|
+
})
|
|
2028
|
+
}
|
|
2029
|
+
);
|
|
2030
|
+
}
|
|
2031
|
+
|
|
2032
|
+
export { createBuildOrbitalTool, createComposeTool, createConnectTool, createDecomposeAppTool, createMatchBehaviorTool, createUseBehaviorTool, createVerifyAppTool, deepCloneOrb, extractBehaviorGateData, getTraitGateData, getTransitionGateData, replaceOrbital, replaceTrait, replaceTransition, runGate0, runGate05, runGate1, runGate2, runGate3, runGate4, runGatePipeline, scoreGate0, scoreGate1, scoreGate2, scoreGate3, scoreGate4, scoreOverall };
|
|
2033
|
+
//# sourceMappingURL=index.js.map
|
|
2034
|
+
//# sourceMappingURL=index.js.map
|