@theokit/studio 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/LICENSE +201 -0
  2. package/dist/assets/geist-400-B1wb-odA.woff2 +0 -0
  3. package/dist/assets/geist-500-fu4ekwtu.woff2 +0 -0
  4. package/dist/assets/geist-600-BvYtazuD.woff2 +0 -0
  5. package/dist/assets/geist-mono-400-BxSzyECb.woff2 +0 -0
  6. package/dist/assets/geist-mono-500-BdW0Mzrp.woff2 +0 -0
  7. package/dist/assets/geist-mono-600-C_rrDxVZ.woff2 +0 -0
  8. package/dist/assets/index-BO_6oSGr.js +4 -0
  9. package/dist/assets/main-B8fxyGhN.css +1 -0
  10. package/dist/assets/main-DSIlqqrG.js +132 -0
  11. package/dist/assets/ui_false-B1n26OIN.js +1 -0
  12. package/dist/assets/ui_false-C86dpdOS.js +1 -0
  13. package/dist/assets/ui_false-CmAnmupH.js +1 -0
  14. package/dist/assets/ui_false-D-OXQOOV.js +1 -0
  15. package/dist/assets/ui_false-D2OIiKrG.js +1 -0
  16. package/dist/assets/ui_false-DXBDqAI8.js +1 -0
  17. package/dist/assets/ui_false-bdnHF6UP.js +1 -0
  18. package/dist/index.html +12 -0
  19. package/dist/plugin/index.d.ts +27 -0
  20. package/dist/plugin/index.js +578 -0
  21. package/dist/spa/assets/geist-400-B1wb-odA.woff2 +0 -0
  22. package/dist/spa/assets/geist-500-fu4ekwtu.woff2 +0 -0
  23. package/dist/spa/assets/geist-600-BvYtazuD.woff2 +0 -0
  24. package/dist/spa/assets/geist-mono-400-BxSzyECb.woff2 +0 -0
  25. package/dist/spa/assets/geist-mono-500-BdW0Mzrp.woff2 +0 -0
  26. package/dist/spa/assets/geist-mono-600-C_rrDxVZ.woff2 +0 -0
  27. package/dist/spa/assets/index-B5gt81MK.js +4 -0
  28. package/dist/spa/assets/main-B9ECDPqJ.js +112 -0
  29. package/dist/spa/assets/main-Db-qK75F.css +1 -0
  30. package/dist/spa/assets/ui_false-B1n26OIN.js +1 -0
  31. package/dist/spa/assets/ui_false-C86dpdOS.js +1 -0
  32. package/dist/spa/assets/ui_false-CmAnmupH.js +1 -0
  33. package/dist/spa/assets/ui_false-D-OXQOOV.js +1 -0
  34. package/dist/spa/assets/ui_false-D2OIiKrG.js +1 -0
  35. package/dist/spa/assets/ui_false-DXBDqAI8.js +1 -0
  36. package/dist/spa/assets/ui_false-bdnHF6UP.js +1 -0
  37. package/dist/spa/index.html +12 -0
  38. package/package.json +61 -0
@@ -0,0 +1 @@
1
+ throw new Error('Could not resolve "mdast-util-to-hast" imported by "@theokit/ui".');
@@ -0,0 +1 @@
1
+ const o={};throw new Error('Could not resolve "mdast-util-from-markdown" imported by "@theokit/ui".');export{o as default};
@@ -0,0 +1 @@
1
+ throw new Error('Could not resolve "hast-util-sanitize" imported by "@theokit/ui".');
@@ -0,0 +1 @@
1
+ throw new Error('Could not resolve "hast-util-to-jsx-runtime" imported by "@theokit/ui".');
@@ -0,0 +1 @@
1
+ const o={};throw new Error('Could not resolve "shiki" imported by "@theokit/ui".');export{o as default};
@@ -0,0 +1 @@
1
+ const t={};throw new Error('Could not resolve "mdast-util-gfm" imported by "@theokit/ui".');export{t as default};
@@ -0,0 +1 @@
1
+ const o={};throw new Error('Could not resolve "micromark-extension-gfm" imported by "@theokit/ui".');export{o as default};
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>TheoKit Studio</title>
7
+ <script type="module" crossorigin src="/assets/index-BO_6oSGr.js"></script>
8
+ </head>
9
+ <body>
10
+ <div id="root"></div>
11
+ </body>
12
+ </html>
@@ -0,0 +1,27 @@
1
+ import { Plugin } from 'vite';
2
+ import { compileAgentModule } from '@theokit/agents/bridge';
3
+
4
+ /** Input entregue à fábrica de stream (default: streamAgentUIMessages do bridge). */
5
+ interface RunStreamInput {
6
+ message: string;
7
+ sessionId: string;
8
+ signal: AbortSignal;
9
+ cwd?: string;
10
+ }
11
+ type RunStreamFactory = (compiled: ReturnType<typeof compileAgentModule>, apiKey: string, input: RunStreamInput) => AsyncIterable<unknown>;
12
+
13
+ /**
14
+ * `@theokit/studio/plugin` — monta a reflection API (`/_studio/api/*`) e (T2.2) a SPA
15
+ * (`/_studio`) no dev server Vite do host, como connect middleware registrado em
16
+ * `configureServer` (mesmo padrão dos middlewares do theokit dev — plano M1, ADR D1).
17
+ */
18
+ interface StudioPluginOptions {
19
+ /** diretório dos agents relativo ao project root (convenção theokit: "agents"). */
20
+ agentsDir?: string;
21
+ /** seam de e2e/teste (DIP): substitui streamAgentUIMessages no run endpoint. */
22
+ streamFactory?: RunStreamFactory;
23
+ }
24
+ /** Plugin Vite: registra o middleware do Studio no dev server do host. */
25
+ declare function theokitStudio(options?: StudioPluginOptions): Plugin;
26
+
27
+ export { type StudioPluginOptions, theokitStudio };
@@ -0,0 +1,578 @@
1
+ // plugin/index.ts
2
+ import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
3
+ import { dirname as dirname2, join as join4 } from "path";
4
+ import { fileURLToPath as fileURLToPath2 } from "url";
5
+
6
+ // plugin/http.ts
7
+ function sendErrorEnvelope(res, status, code, message) {
8
+ if (res.writableEnded || res.destroyed) return;
9
+ const payload = JSON.stringify({ error: { code, message } });
10
+ if (res.headersSent) {
11
+ res.end(payload);
12
+ return;
13
+ }
14
+ res.writeHead(status, { "Content-Type": "application/json" });
15
+ res.end(payload);
16
+ }
17
+ function sendJson(res, status, body) {
18
+ if (res.writableEnded || res.destroyed) return;
19
+ if (res.headersSent) {
20
+ console.warn("theokit-studio: sendJson called on an already-committed response \u2014 ending it");
21
+ res.end();
22
+ return;
23
+ }
24
+ res.writeHead(status, { "Content-Type": "application/json" });
25
+ res.end(JSON.stringify(body));
26
+ }
27
+
28
+ // plugin/reflection-api.ts
29
+ import { join as join2, relative as relative2 } from "path";
30
+ import { compileAgentModule } from "@theokit/agents/bridge";
31
+
32
+ // plugin/agent-scan.ts
33
+ import { existsSync, readdirSync, statSync } from "fs";
34
+ import { extname, join, relative } from "path";
35
+ var AGENT_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx"]);
36
+ var TEST_FILE = /\.(test|spec)$/;
37
+ var AGENT_SUBFOLDERS = /* @__PURE__ */ new Set([
38
+ "tools",
39
+ "skills",
40
+ "prompts",
41
+ "lib",
42
+ "hooks",
43
+ "channels",
44
+ "connections",
45
+ "subagents",
46
+ "schedules",
47
+ "sandbox",
48
+ "workflows",
49
+ "evals",
50
+ "memory"
51
+ ]);
52
+ function walk(dir, visit) {
53
+ let entries;
54
+ try {
55
+ entries = readdirSync(dir, { withFileTypes: true });
56
+ } catch (error) {
57
+ const code = error.code ?? "UNKNOWN";
58
+ console.warn(`theokit-studio: skipping unreadable agents directory ${dir} (${code})`);
59
+ return;
60
+ }
61
+ for (const entry of entries) {
62
+ const abs = join(dir, entry.name);
63
+ if (entry.isDirectory()) {
64
+ walk(abs, visit);
65
+ } else if (AGENT_EXTENSIONS.has(extname(entry.name))) {
66
+ visit(abs);
67
+ }
68
+ }
69
+ }
70
+ function scanStudioAgents(projectRoot, agentsDirName = "agents") {
71
+ const agentsDir = join(projectRoot, agentsDirName);
72
+ if (!existsSync(agentsDir) || !statSync(agentsDir).isDirectory()) {
73
+ return [];
74
+ }
75
+ const results = [];
76
+ walk(agentsDir, (absPath) => {
77
+ let rel = relative(agentsDir, absPath).replace(/\\/g, "/");
78
+ rel = rel.slice(0, -extname(rel).length);
79
+ if (TEST_FILE.test(rel)) return;
80
+ const dirs = rel.split("/").slice(0, -1);
81
+ if (dirs.some((segment) => AGENT_SUBFOLDERS.has(segment))) return;
82
+ if (rel.endsWith("/index")) rel = rel.slice(0, -6);
83
+ if (rel === "index" || rel === "") return;
84
+ results.push({ filePath: absPath, name: rel });
85
+ });
86
+ results.sort((a, b) => a.name.localeCompare(b.name));
87
+ return results;
88
+ }
89
+
90
+ // plugin/reflection-api.ts
91
+ var DEFAULT_LOAD_TIMEOUT_MS = 1e4;
92
+ async function loadWithTimeout(load, filePath, timeoutMs) {
93
+ let timer;
94
+ try {
95
+ return await Promise.race([
96
+ load(filePath),
97
+ new Promise((_, reject) => {
98
+ timer = setTimeout(() => reject(new Error(`load timeout after ${timeoutMs}ms`)), timeoutMs);
99
+ })
100
+ ]);
101
+ } finally {
102
+ clearTimeout(timer);
103
+ }
104
+ }
105
+ function toReflectionAgent(node, projectRoot, compiled) {
106
+ return {
107
+ name: node.name,
108
+ filePath: relative2(projectRoot, node.filePath).replace(/\\/g, "/"),
109
+ model: compiled.model,
110
+ tools: compiled.tools.map((t) => ({ name: t.name, description: t.description })),
111
+ subagents: Object.keys(compiled.agents),
112
+ skills: compiled.skills ? { enabled: compiled.skills.enabled, autoInject: compiled.skills.autoInject } : void 0
113
+ };
114
+ }
115
+ async function listReflectionAgents(deps) {
116
+ const nodes = scanStudioAgents(deps.projectRoot, deps.agentsDir);
117
+ if (nodes.length === 0) {
118
+ return {
119
+ items: [],
120
+ hint: `no agents found \u2014 create ${deps.agentsDir ?? "agents"}/<name>.ts in the project root`
121
+ };
122
+ }
123
+ const timeoutMs = deps.loadTimeoutMs ?? DEFAULT_LOAD_TIMEOUT_MS;
124
+ const items = [];
125
+ for (const node of nodes) {
126
+ const relPath = relative2(deps.projectRoot, node.filePath).replace(/\\/g, "/");
127
+ try {
128
+ const mod = await loadWithTimeout(deps.load, node.filePath, timeoutMs);
129
+ items.push(toReflectionAgent(node, deps.projectRoot, compileAgentModule(mod, relPath)));
130
+ } catch (error) {
131
+ items.push({
132
+ name: node.name,
133
+ filePath: relPath,
134
+ error: error instanceof Error ? error.message : String(error)
135
+ });
136
+ }
137
+ }
138
+ return { items };
139
+ }
140
+ var WORKFLOW_NOTE = "subagent declarado no agent (enumera\xE7\xE3o de inst\xE2ncias de workflow \xE9 gap do SDK \u2014 theokit-sdk#123)";
141
+ function aggregateReflection(agents) {
142
+ const byName = /* @__PURE__ */ new Map();
143
+ const workflows = [];
144
+ for (const agent of agents) {
145
+ if (agent.error) continue;
146
+ for (const tool of agent.tools ?? []) {
147
+ const seen = byName.get(tool.name);
148
+ if (seen) {
149
+ seen.usedBy += 1;
150
+ } else {
151
+ byName.set(tool.name, { description: tool.description, usedBy: 1 });
152
+ }
153
+ }
154
+ for (const sub of agent.subagents ?? []) {
155
+ workflows.push({
156
+ id: `${agent.name}/${sub}`,
157
+ name: sub,
158
+ agent: agent.name,
159
+ source: "subagent",
160
+ note: WORKFLOW_NOTE
161
+ });
162
+ }
163
+ }
164
+ const tools = [...byName.entries()].map(([name, v]) => ({ name, description: v.description, usedBy: v.usedBy })).sort((a, b) => a.name.localeCompare(b.name));
165
+ workflows.sort((a, b) => a.id.localeCompare(b.id));
166
+ return { tools, workflows };
167
+ }
168
+ async function listReflectionSkills(deps) {
169
+ const skillsDir = join2(deps.projectRoot, ".theokit/skills");
170
+ try {
171
+ if (deps.discover) {
172
+ return { items: await deps.discover(skillsDir) };
173
+ }
174
+ const { discoverSkills } = await import("@theokit/sdk/skills");
175
+ const invalid = [];
176
+ const skills = await discoverSkills(skillsDir, {
177
+ // Skill malformada é pulada — mas nunca em silêncio (error-handling.md § 2).
178
+ onInvalidSkill: (info) => invalid.push(`${info.name}: ${info.code} \u2014 ${info.message}`)
179
+ });
180
+ return {
181
+ items: skills.map((s) => ({
182
+ name: s.name,
183
+ description: s.description,
184
+ category: s.category
185
+ })),
186
+ invalid: invalid.length > 0 ? invalid : void 0
187
+ };
188
+ } catch (error) {
189
+ return {
190
+ items: [],
191
+ degraded: error instanceof Error ? error.message : String(error)
192
+ };
193
+ }
194
+ }
195
+
196
+ // plugin/run-endpoint.ts
197
+ import { randomUUID } from "crypto";
198
+ import { relative as relative3 } from "path";
199
+ import { compileAgentModule as compileAgentModule2, streamAgentUIMessages } from "@theokit/agents/bridge";
200
+ var RUN_PREFIX = "/_studio/api/agents/";
201
+ var RUN_SUFFIX = "/run";
202
+ var PROVIDER_ENV_PRIORITY = [
203
+ "OPENROUTER_API_KEY",
204
+ "OPENAI_API_KEY",
205
+ "ANTHROPIC_API_KEY"
206
+ ];
207
+ function matchRunPath(pathname) {
208
+ if (!pathname.startsWith(RUN_PREFIX) || !pathname.endsWith(RUN_SUFFIX)) return null;
209
+ const raw = pathname.slice(RUN_PREFIX.length, -RUN_SUFFIX.length);
210
+ if (raw.length === 0) return null;
211
+ try {
212
+ return { kind: "match", name: decodeURIComponent(raw) };
213
+ } catch {
214
+ return { kind: "malformed" };
215
+ }
216
+ }
217
+ function isSameOrigin(req) {
218
+ const origin = req.headers.origin;
219
+ if (origin === void 0) return true;
220
+ if (origin === "null") return false;
221
+ try {
222
+ return new URL(origin).host === req.headers.host;
223
+ } catch {
224
+ return false;
225
+ }
226
+ }
227
+ function resolveApiKey(env) {
228
+ for (const key of PROVIDER_ENV_PRIORITY) {
229
+ const value = env[key];
230
+ if (value !== void 0 && value.length > 0) return value;
231
+ }
232
+ return void 0;
233
+ }
234
+ async function readBody(req) {
235
+ const chunks = [];
236
+ for await (const chunk of req) {
237
+ chunks.push(Buffer.from(chunk));
238
+ }
239
+ return Buffer.concat(chunks).toString("utf8");
240
+ }
241
+ function parseRunBody(raw) {
242
+ let parsed;
243
+ try {
244
+ parsed = JSON.parse(raw);
245
+ } catch {
246
+ return null;
247
+ }
248
+ if (typeof parsed !== "object" || parsed === null) return null;
249
+ const message = parsed.message;
250
+ if (typeof message !== "string" || message.trim().length === 0) return null;
251
+ const sessionId = parsed.sessionId;
252
+ return {
253
+ message,
254
+ // sessionId do cliente preservado (continuidade multi-turn — conversationStorage
255
+ // do SDK é keyed por sessionId; paridade com parseAgentRequestBody do theokit).
256
+ sessionId: typeof sessionId === "string" && sessionId.length > 0 ? sessionId : randomUUID()
257
+ };
258
+ }
259
+ var NDJSON_HEADERS = {
260
+ "Content-Type": "application/x-ndjson",
261
+ "Transfer-Encoding": "chunked",
262
+ "Cache-Control": "no-store"
263
+ };
264
+ async function resolveRunRequest(pathname, req, deps) {
265
+ const match = matchRunPath(pathname);
266
+ if (match === null) {
267
+ return refuse(404, "NOT_FOUND", `no studio api route matches ${pathname}`);
268
+ }
269
+ if (match.kind === "malformed") {
270
+ return refuse(400, "BAD_REQUEST", "malformed percent-encoding in agent name");
271
+ }
272
+ const name = match.name;
273
+ if (req.method !== "POST") {
274
+ return refuse(405, "METHOD_NOT_ALLOWED", `use POST for ${pathname}`);
275
+ }
276
+ if (!isSameOrigin(req)) {
277
+ return refuse(403, "ORIGIN_FORBIDDEN", "cross-origin agent runs are not allowed");
278
+ }
279
+ const body = parseRunBody(await readBody(req));
280
+ if (body === null) {
281
+ return refuse(
282
+ 400,
283
+ "BAD_REQUEST",
284
+ "request body must be JSON with a non-empty `message` string"
285
+ );
286
+ }
287
+ const node = scanStudioAgents(deps.projectRoot, deps.agentsDir).find((n) => n.name === name);
288
+ if (!node) {
289
+ return refuse(404, "AGENT_NOT_FOUND", `no agent named "${name}" in the project`);
290
+ }
291
+ const apiKey = resolveApiKey(deps.env ?? process.env);
292
+ if (apiKey === void 0) {
293
+ return refuse(
294
+ 424,
295
+ "PROVIDER_KEY_MISSING",
296
+ `no provider API key found \u2014 set one of ${PROVIDER_ENV_PRIORITY.join(", ")} in the dev server environment`
297
+ );
298
+ }
299
+ try {
300
+ const mod = await deps.load(node.filePath);
301
+ const compiled = compileAgentModule2(mod, relative3(deps.projectRoot, node.filePath));
302
+ return { kind: "ready", body, apiKey, compiled };
303
+ } catch (error) {
304
+ return refuse(422, "AGENT_INVALID", error instanceof Error ? error.message : String(error));
305
+ }
306
+ }
307
+ function refuse(status, code, message) {
308
+ return { kind: "refused", status, code, message };
309
+ }
310
+ async function handleAgentRun(pathname, req, res, deps) {
311
+ const resolved = await resolveRunRequest(pathname, req, deps);
312
+ if (resolved.kind === "refused") {
313
+ sendErrorEnvelope(res, resolved.status, resolved.code, resolved.message);
314
+ return;
315
+ }
316
+ const { body, apiKey, compiled } = resolved;
317
+ const controller = new AbortController();
318
+ req.on("close", () => {
319
+ if (!res.writableEnded) controller.abort();
320
+ });
321
+ const writeLine = (line) => {
322
+ if (res.writableEnded || controller.signal.aborted) return;
323
+ res.write(`${JSON.stringify(line)}
324
+ `);
325
+ };
326
+ res.writeHead(200, NDJSON_HEADERS);
327
+ const streamFactory = deps.streamFactory ?? ((c, k, input) => (
328
+ // cwd fica fora: StreamAgentOptions do 0.39.0 publicado ainda não o aceita
329
+ // (chegou no worktree 0.40); RunStreamInput o carrega para factories injetadas.
330
+ streamAgentUIMessages(c, k, {
331
+ message: input.message,
332
+ sessionId: input.sessionId,
333
+ signal: input.signal
334
+ })
335
+ ));
336
+ try {
337
+ const stream = streamFactory(compiled, apiKey, {
338
+ message: body.message,
339
+ sessionId: body.sessionId,
340
+ signal: controller.signal,
341
+ cwd: deps.projectRoot
342
+ });
343
+ for await (const chunk of stream) {
344
+ if (controller.signal.aborted) break;
345
+ writeLine({ kind: "message", chunk });
346
+ }
347
+ if (!controller.signal.aborted) {
348
+ writeLine({ kind: "done" });
349
+ }
350
+ } catch (error) {
351
+ writeLine({
352
+ kind: "error",
353
+ error: {
354
+ code: "RUN_FAILED",
355
+ message: error instanceof Error ? error.message : String(error)
356
+ }
357
+ });
358
+ } finally {
359
+ if (!res.writableEnded) res.end();
360
+ }
361
+ }
362
+
363
+ // plugin/static-serve.ts
364
+ import { existsSync as existsSync2, readFileSync, statSync as statSync2 } from "fs";
365
+ import { dirname, extname as extname2, join as join3, normalize, sep } from "path";
366
+ import { fileURLToPath } from "url";
367
+ var STUDIO_PREFIX = "/_studio";
368
+ var CONTENT_TYPES = {
369
+ ".html": "text/html; charset=utf-8",
370
+ ".js": "text/javascript; charset=utf-8",
371
+ ".css": "text/css; charset=utf-8",
372
+ ".svg": "image/svg+xml",
373
+ ".png": "image/png",
374
+ ".ico": "image/x-icon",
375
+ ".json": "application/json",
376
+ ".map": "application/json",
377
+ ".woff2": "font/woff2"
378
+ };
379
+ function resolveSpaDir(opts) {
380
+ const override = opts.env.THEOKIT_STUDIO_DIST;
381
+ if (override !== void 0 && override.length > 0) {
382
+ return normalize(join3(override, "."));
383
+ }
384
+ const here = dirname(fileURLToPath(opts.moduleUrl ?? import.meta.url));
385
+ for (const rel of ["../spa", "../dist/spa"]) {
386
+ const candidate = join3(here, rel);
387
+ if (existsSync2(candidate)) return candidate;
388
+ }
389
+ return join3(here, "../spa");
390
+ }
391
+ function safeJoin(spaDir, relPath) {
392
+ let decoded;
393
+ try {
394
+ decoded = decodeURIComponent(relPath);
395
+ } catch {
396
+ return { kind: "bad-request" };
397
+ }
398
+ if (decoded.includes("\0")) return { kind: "bad-request" };
399
+ const resolved = normalize(join3(spaDir, decoded));
400
+ if (!resolved.startsWith(spaDir + sep)) return { kind: "forbidden" };
401
+ return { kind: "ok", path: resolved };
402
+ }
403
+ function serveIndexWithConfig(res, opts) {
404
+ const indexPath = join3(opts.spaDir, "index.html");
405
+ const html = readFileSync(indexPath, "utf8");
406
+ const json = JSON.stringify(opts.config).replace(/</g, "\\u003c");
407
+ const script = `<script>window.__STUDIO_CONFIG__=${json}</script>`;
408
+ let injected;
409
+ if (html.includes("</head>")) {
410
+ injected = html.replace("</head>", `${script}</head>`);
411
+ } else {
412
+ console.warn("TheoKit Studio: index.html without </head> \u2014 prepending config script");
413
+ injected = script + html;
414
+ }
415
+ if (res.writableEnded || res.destroyed) return;
416
+ res.writeHead(200, {
417
+ "Content-Type": "text/html; charset=utf-8",
418
+ // Config muda por boot do dev server — index injetado NUNCA cacheado.
419
+ "Cache-Control": "no-store"
420
+ });
421
+ res.end(injected);
422
+ }
423
+ async function serveStudio(pathname, res, opts) {
424
+ if (!existsSync2(join3(opts.spaDir, "index.html"))) {
425
+ sendErrorEnvelope(
426
+ res,
427
+ 503,
428
+ "STUDIO_ASSETS_MISSING",
429
+ `studio SPA dist not found at ${opts.spaDir} \u2014 run the @theokit/studio build (pnpm --filter @theokit/studio build) or set THEOKIT_STUDIO_DIST`
430
+ );
431
+ return;
432
+ }
433
+ const rel = pathname.slice(STUDIO_PREFIX.length).replace(/^\/+/, "");
434
+ const ext = extname2(rel);
435
+ const isKnownAsset = rel.length > 0 && ext in CONTENT_TYPES && ext !== ".html";
436
+ if (!isKnownAsset) {
437
+ const check = safeJoin(opts.spaDir, rel.length === 0 ? "index.html" : rel);
438
+ if (check.kind === "bad-request") {
439
+ sendErrorEnvelope(res, 400, "BAD_REQUEST", "malformed path");
440
+ return;
441
+ }
442
+ if (check.kind === "forbidden") {
443
+ sendErrorEnvelope(res, 403, "FORBIDDEN", "path escapes the studio root");
444
+ return;
445
+ }
446
+ serveIndexWithConfig(res, opts);
447
+ return;
448
+ }
449
+ const safe = safeJoin(opts.spaDir, rel);
450
+ if (safe.kind === "bad-request") {
451
+ sendErrorEnvelope(res, 400, "BAD_REQUEST", "malformed asset path");
452
+ return;
453
+ }
454
+ if (safe.kind === "forbidden") {
455
+ sendErrorEnvelope(res, 403, "FORBIDDEN", "asset path escapes the studio root");
456
+ return;
457
+ }
458
+ if (!existsSync2(safe.path) || !statSync2(safe.path).isFile()) {
459
+ sendErrorEnvelope(res, 404, "NOT_FOUND", `no studio asset at ${pathname}`);
460
+ return;
461
+ }
462
+ let content;
463
+ try {
464
+ content = readFileSync(safe.path);
465
+ } catch (error) {
466
+ sendErrorEnvelope(
467
+ res,
468
+ 500,
469
+ "ASSET_READ_FAILED",
470
+ `could not read studio asset at ${pathname}: ${error instanceof Error ? error.message : String(error)}`
471
+ );
472
+ return;
473
+ }
474
+ if (res.writableEnded || res.destroyed) return;
475
+ res.writeHead(200, { "Content-Type": CONTENT_TYPES[ext] ?? "application/octet-stream" });
476
+ res.end(content);
477
+ }
478
+
479
+ // plugin/index.ts
480
+ var STUDIO_PREFIX2 = "/_studio";
481
+ var RESERVED_API_NAMESPACES = ["/_studio/api", "/_studio/svc"];
482
+ function resolveStudioVersion() {
483
+ const here = dirname2(fileURLToPath2(import.meta.url));
484
+ for (const rel of ["../package.json", "../../package.json"]) {
485
+ const candidate = join4(here, rel);
486
+ if (existsSync3(candidate)) {
487
+ try {
488
+ const parsed = JSON.parse(readFileSync2(candidate, "utf8"));
489
+ if (typeof parsed.version === "string") return parsed.version;
490
+ } catch {
491
+ }
492
+ }
493
+ }
494
+ return "unknown";
495
+ }
496
+ function isStudioPath(pathname) {
497
+ return pathname === STUDIO_PREFIX2 || pathname.startsWith(`${STUDIO_PREFIX2}/`);
498
+ }
499
+ function loadAgents(ctx) {
500
+ return listReflectionAgents({
501
+ projectRoot: ctx.server.config.root,
502
+ agentsDir: ctx.options.agentsDir,
503
+ // Hot-reload grátis: o Vite invalida o module graph — por isso nunca cacheamos.
504
+ load: (filePath) => ctx.server.ssrLoadModule(filePath)
505
+ });
506
+ }
507
+ function isReservedApiNamespace(pathname) {
508
+ return RESERVED_API_NAMESPACES.some(
509
+ (prefix) => pathname === prefix || pathname.startsWith(`${prefix}/`)
510
+ );
511
+ }
512
+ async function handleStudioRequest(pathname, req, res, ctx) {
513
+ if (pathname === "/_studio/api/health") {
514
+ sendJson(res, 200, { ok: true, studio: resolveStudioVersion() });
515
+ return;
516
+ }
517
+ if (pathname === "/_studio/api/agents") {
518
+ sendJson(res, 200, await loadAgents(ctx));
519
+ return;
520
+ }
521
+ if (pathname === "/_studio/api/tools" || pathname === "/_studio/api/workflows") {
522
+ const { tools, workflows } = aggregateReflection((await loadAgents(ctx)).items);
523
+ sendJson(res, 200, pathname.endsWith("tools") ? { items: tools } : { items: workflows });
524
+ return;
525
+ }
526
+ if (pathname === "/_studio/api/skills") {
527
+ sendJson(res, 200, await listReflectionSkills({ projectRoot: ctx.server.config.root }));
528
+ return;
529
+ }
530
+ if (matchRunPath(pathname) !== null) {
531
+ await handleAgentRun(pathname, req, res, {
532
+ projectRoot: ctx.server.config.root,
533
+ agentsDir: ctx.options.agentsDir,
534
+ load: (filePath) => ctx.server.ssrLoadModule(filePath),
535
+ streamFactory: ctx.options.streamFactory
536
+ });
537
+ return;
538
+ }
539
+ if (isReservedApiNamespace(pathname)) {
540
+ sendErrorEnvelope(res, 404, "NOT_FOUND", `no studio api route matches ${pathname}`);
541
+ return;
542
+ }
543
+ await serveStudio(pathname, res, {
544
+ spaDir: ctx.spaDir,
545
+ config: { mode: "live", basePath: STUDIO_PREFIX2 }
546
+ });
547
+ }
548
+ function theokitStudio(options = {}) {
549
+ return {
550
+ name: "theokit-studio",
551
+ configureServer(server) {
552
+ const ctx = {
553
+ server,
554
+ options,
555
+ spaDir: resolveSpaDir({ env: process.env })
556
+ };
557
+ const middleware = (req, res, next) => {
558
+ const pathname = new URL(req.url ?? "/", "http://local").pathname;
559
+ if (!isStudioPath(pathname)) {
560
+ next();
561
+ return;
562
+ }
563
+ handleStudioRequest(pathname, req, res, ctx).catch((error) => {
564
+ sendErrorEnvelope(
565
+ res,
566
+ 500,
567
+ "INTERNAL",
568
+ error instanceof Error ? error.message : String(error)
569
+ );
570
+ });
571
+ };
572
+ server.middlewares.use(middleware);
573
+ }
574
+ };
575
+ }
576
+ export {
577
+ theokitStudio
578
+ };
@@ -0,0 +1,4 @@
1
+ const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./main-B9ECDPqJ.js","./main-Db-qK75F.css"])))=>i.map(i=>d[i]);
2
+ (function(){const o=document.createElement("link").relList;if(o&&o.supports&&o.supports("modulepreload"))return;for(const t of document.querySelectorAll('link[rel="modulepreload"]'))s(t);new MutationObserver(t=>{for(const r of t)if(r.type==="childList")for(const i of r.addedNodes)i.tagName==="LINK"&&i.rel==="modulepreload"&&s(i)}).observe(document,{childList:!0,subtree:!0});function n(t){const r={};return t.integrity&&(r.integrity=t.integrity),t.referrerPolicy&&(r.referrerPolicy=t.referrerPolicy),t.crossOrigin==="use-credentials"?r.credentials="include":t.crossOrigin==="anonymous"?r.credentials="omit":r.credentials="same-origin",r}function s(t){if(t.ep)return;t.ep=!0;const r=n(t);fetch(t.href,r)}})();const v="modulepreload",S=function(e,o){return new URL(e,o).href},h={},_=function(o,n,s){let t=Promise.resolve();if(n&&n.length>0){let y=function(l){return Promise.all(l.map(d=>Promise.resolve(d).then(u=>({status:"fulfilled",value:u}),u=>({status:"rejected",reason:u}))))};const i=document.getElementsByTagName("link"),c=document.querySelector("meta[property=csp-nonce]"),p=c?.nonce||c?.getAttribute("nonce");t=y(n.map(l=>{if(l=S(l,s),l in h)return;h[l]=!0;const d=l.endsWith(".css"),u=d?'[rel="stylesheet"]':"";if(s)for(let f=i.length-1;f>=0;f--){const m=i[f];if(m.href===l&&(!d||m.rel==="stylesheet"))return}else if(document.querySelector(`link[href="${l}"]${u}`))return;const a=document.createElement("link");if(a.rel=d?"stylesheet":v,d||(a.as="script"),a.crossOrigin="",a.href=l,p&&a.setAttribute("nonce",p),document.head.appendChild(a),d)return new Promise((f,m)=>{a.addEventListener("load",f),a.addEventListener("error",()=>m(new Error(`Unable to preload CSS for ${l}`)))})}))}function r(i){const c=new Event("vite:preloadError",{cancelable:!0});if(c.payload=i,window.dispatchEvent(c),!c.defaultPrevented)throw i}return t.then(i=>{for(const c of i||[])c.status==="rejected"&&r(c.reason);return o().catch(r)})};function E(e,o={}){const n=o.mode??"production",s=document.getElementById("root")??document.body,t=document.createElement("main");t.setAttribute("role","alert"),t.style.cssText="font-family: monospace; padding: 2rem; color: #f66;";const r=document.createElement("h1");r.textContent="TheoKit Studio failed to start",t.appendChild(r);const i=document.createElement("pre");i.style.cssText="white-space: pre-wrap; color: #ccc;",n==="development"||n==="test"?i.textContent=e instanceof Error?`${e.message}
3
+
4
+ ${e.stack??""}`:String(e):i.textContent="Run Studio in development mode to view detailed diagnostics.",t.appendChild(i),s.replaceChildren(t)}const g=new Set(["default","empty"]),O=new Set(["fixtures","live"]);function b(e,o){if(e===void 0)return;if(typeof e!="string"||e.length===0){o.push(`basePath must be a non-empty string — ignoring ${JSON.stringify(e)}`);return}const n=e.startsWith("/")?e:`/${e}`;return n.length>1?n.replace(/\/+$/,""):n}function w(e){if(e==null)return{scenario:"default"};if(typeof e!="object")return console.warn("TheoKit Studio: malformed window.__STUDIO_CONFIG__ — falling back to default fixtures",e),{scenario:"default"};const o=e,n=[];let s="default";o.scenario!==void 0&&(typeof o.scenario=="string"&&g.has(o.scenario)?s=o.scenario:n.push(`invalid scenario ${JSON.stringify(o.scenario)} (expected ${[...g].map(c=>`"${c}"`).join(" | ")})`));let t;o.mode!==void 0&&(typeof o.mode=="string"&&O.has(o.mode)?t=o.mode:n.push(`invalid mode ${JSON.stringify(o.mode)}`));const r=b(o.basePath,n);n.length>0&&console.warn(`TheoKit Studio: window.__STUDIO_CONFIG__ partially malformed (${n.join("; ")}) — invalid fields fell back to defaults`,e);const i={scenario:s};return t!==void 0&&(i.mode=t),r!==void 0&&(i.basePath=r),i}async function P(){try{const{mount:e}=await _(async()=>{const{mount:s}=await import("./main-B9ECDPqJ.js");return{mount:s}},__vite__mapDeps([0,1]),import.meta.url),o=w(window.__STUDIO_CONFIG__),n=document.getElementById("root");if(!n)throw new Error("TheoKit Studio: #root element not found in the host document");e(n,o)}catch(e){throw E(e,{mode:"production"}),e}}P().catch(()=>{});export{_};