@mestreyoda/fabrica 0.1.8 → 0.1.9

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/index.js CHANGED
@@ -111329,8 +111329,8 @@ import fsSync from "node:fs";
111329
111329
  import path5 from "node:path";
111330
111330
  import { fileURLToPath as fileURLToPath3 } from "node:url";
111331
111331
  function getCurrentVersion() {
111332
- if ("0.1.8") {
111333
- return "0.1.8";
111332
+ if ("0.1.9") {
111333
+ return "0.1.9";
111334
111334
  }
111335
111335
  try {
111336
111336
  const pkgPath = path5.join(THIS_DIR, "..", "..", "package.json");
@@ -142032,6 +142032,9 @@ function registerAttachmentHook(api, ctx) {
142032
142032
 
142033
142033
  // lib/dispatch/telegram-bootstrap-hook.ts
142034
142034
  import { homedir as homedir3 } from "node:os";
142035
+ init_runtime_paths();
142036
+ init_extract_json();
142037
+ init_zod();
142035
142038
 
142036
142039
  // lib/dispatch/telegram-bootstrap-session.ts
142037
142040
  init_migrate_layout();
@@ -142070,7 +142073,7 @@ async function readTelegramBootstrapSession(workspaceDir, conversationId) {
142070
142073
  try {
142071
142074
  const raw = await fs38.readFile(sessionPath(workspaceDir, conversationId), "utf-8");
142072
142075
  const session = JSON.parse(raw);
142073
- if (session.status === "clarifying" && Date.parse(session.suppressUntil) < Date.now()) {
142076
+ if ((session.status === "clarifying" || session.status === "classifying") && Date.parse(session.suppressUntil) < Date.now()) {
142074
142077
  await fs38.unlink(sessionPath(workspaceDir, conversationId)).catch(() => {
142075
142078
  });
142076
142079
  return null;
@@ -142081,6 +142084,10 @@ async function readTelegramBootstrapSession(workspaceDir, conversationId) {
142081
142084
  throw error48;
142082
142085
  }
142083
142086
  }
142087
+ async function deleteTelegramBootstrapSession(workspaceDir, conversationId) {
142088
+ await fs38.unlink(sessionPath(workspaceDir, conversationId)).catch(() => {
142089
+ });
142090
+ }
142084
142091
  async function writeTelegramBootstrapSession(workspaceDir, session) {
142085
142092
  const dir = sessionsDir(workspaceDir);
142086
142093
  await fs38.mkdir(dir, { recursive: true });
@@ -142184,6 +142191,56 @@ function parseBootstrapRequest(text) {
142184
142191
  stackHint
142185
142192
  };
142186
142193
  }
142194
+ var MAX_CLASSIFY_LENGTH = 500;
142195
+ function isAmbiguousCandidate(text) {
142196
+ const lower2 = text.toLowerCase();
142197
+ if (lower2.length <= 20 || lower2.length > MAX_CLASSIFY_LENGTH) return false;
142198
+ const softwareCue = /\b(projeto|project|cli|api|app|aplicativo|servi[cç]o|library|biblioteca|repo|reposit[oó]rio|tool|ferramenta|sistema|system|bot|script|programa|program)\b/.test(lower2);
142199
+ return softwareCue;
142200
+ }
142201
+ var DmIntentSchema = external_exports.object({
142202
+ intent: external_exports.enum(["create_project", "other"]),
142203
+ confidence: external_exports.number().min(0).max(1),
142204
+ stackHint: external_exports.string().nullable().optional(),
142205
+ projectSlug: external_exports.string().nullable().optional()
142206
+ });
142207
+ var CLASSIFY_PROMPT_TEMPLATE = `Classify this Telegram DM. Is the user asking to create/build a new software project, or is it something else (question, greeting, status check)?
142208
+
142209
+ Message: "$CONTENT"
142210
+
142211
+ Return ONLY valid JSON:
142212
+ {"intent": "create_project" | "other", "confidence": 0.0-1.0, "stackHint": "<detected stack or null>", "projectSlug": "<suggested slug or null>"}
142213
+
142214
+ Examples:
142215
+ - "Cria uma CLI Python que valida CPF" \u2192 {"intent":"create_project","confidence":0.95,"stackHint":"python-cli","projectSlug":"validador-cpf-cli"}
142216
+ - "Build me a REST API for tasks" \u2192 {"intent":"create_project","confidence":0.9,"stackHint":"fastapi","projectSlug":"task-api"}
142217
+ - "How's the project going?" \u2192 {"intent":"other","confidence":0.95,"stackHint":null,"projectSlug":null}
142218
+ - "Oi, tudo bem?" \u2192 {"intent":"other","confidence":0.99,"stackHint":null,"projectSlug":null}
142219
+ - "Me faz um app que converte temperaturas" \u2192 {"intent":"create_project","confidence":0.9,"stackHint":null,"projectSlug":"conversor-temperaturas"}`;
142220
+ async function classifyDmIntent(ctx, content, workspaceDir) {
142221
+ try {
142222
+ const truncated = content.slice(0, MAX_CLASSIFY_LENGTH);
142223
+ const prompt = CLASSIFY_PROMPT_TEMPLATE.replace("$CONTENT", truncated.replace(/"/g, '\\"'));
142224
+ const cliPath = resolveOpenClawCli({ homeDir: homedir3(), workspaceDir });
142225
+ const sessionId = `dm-classify-${Date.now()}`;
142226
+ const result = await ctx.runCommand(
142227
+ [cliPath, "agent", "--local", "-m", prompt, "--session-id", sessionId, "--json"],
142228
+ { timeoutMs: 15e3 }
142229
+ );
142230
+ const stdout = result.stdout ?? "";
142231
+ if (!stdout.trim()) return null;
142232
+ const parsed = extractJsonFromStdout(stdout);
142233
+ if (!parsed) return null;
142234
+ const text = parsed?.payloads?.[0]?.text;
142235
+ const jsonStr = text ? text.replace(/^```(json)?/gm, "").replace(/```$/gm, "").trim() : JSON.stringify(parsed);
142236
+ const intentData = JSON.parse(jsonStr);
142237
+ const validated = DmIntentSchema.safeParse(intentData);
142238
+ if (!validated.success) return null;
142239
+ return validated.data;
142240
+ } catch {
142241
+ return null;
142242
+ }
142243
+ }
142187
142244
  function isBootstrapCandidate(text) {
142188
142245
  const lower2 = text.toLowerCase();
142189
142246
  if (/^\s*(project name|nome do projeto|repository url|repo url|stack)\s*:/im.test(text)) return true;
@@ -142274,6 +142331,67 @@ function logBootstrapWarning(ctx, message) {
142274
142331
  ctx.logger.info(message);
142275
142332
  }
142276
142333
  }
142334
+ async function classifyAndBootstrap(ctx, workspaceDir, conversationId, content) {
142335
+ const classification = await classifyDmIntent(ctx, content, workspaceDir);
142336
+ if (!classification || classification.intent !== "create_project" || classification.confidence < 0.7) {
142337
+ if (!classification) {
142338
+ logBootstrapWarning(ctx, `[telegram-bootstrap] LLM classify failed, falling back (conversation: ${conversationId})`);
142339
+ }
142340
+ await deleteTelegramBootstrapSession(workspaceDir, conversationId);
142341
+ return;
142342
+ }
142343
+ await sendTelegramText(ctx, conversationId, "Recebi! Vou analisar e come\xE7ar a montar o projeto...");
142344
+ const parsed = parseBootstrapRequest(content);
142345
+ if (classification.stackHint && !parsed.stackHint) {
142346
+ parsed.stackHint = classification.stackHint;
142347
+ }
142348
+ if (classification.projectSlug && !parsed.projectName) {
142349
+ parsed.projectName = classification.projectSlug;
142350
+ }
142351
+ const incomingRequest = {
142352
+ rawIdea: parsed.rawIdea,
142353
+ projectName: parsed.projectName ?? null,
142354
+ stackHint: parsed.stackHint ?? null,
142355
+ repoUrl: parsed.repoUrl ?? null,
142356
+ repoPath: parsed.repoPath ?? null
142357
+ };
142358
+ const incomingRequestHash = buildBootstrapRequestHash(incomingRequest);
142359
+ const sessionForHash = await readTelegramBootstrapSession(workspaceDir, conversationId);
142360
+ if (sessionForHash?.requestHash === incomingRequestHash) {
142361
+ if (sessionForHash.status === "completed") {
142362
+ ctx.logger.info(`[telegram-bootstrap] duplicate completed DM ignored (LLM path) for conversation ${conversationId}`);
142363
+ return;
142364
+ }
142365
+ const isExpiredReceived = sessionForHash.status === "received" && Date.parse(sessionForHash.suppressUntil) < Date.now();
142366
+ if (sessionForHash.status !== "failed" && sessionForHash.status !== "classifying" && !isExpiredReceived) {
142367
+ ctx.logger.info(`[telegram-bootstrap] duplicate in-flight DM ignored (LLM path) for conversation ${conversationId}`);
142368
+ return;
142369
+ }
142370
+ }
142371
+ const sourceRoute = { channel: "telegram", channelId: conversationId };
142372
+ const session = await upsertTelegramBootstrapSession(workspaceDir, {
142373
+ conversationId,
142374
+ ...incomingRequest,
142375
+ sourceRoute,
142376
+ sourceChannel: "telegram",
142377
+ status: "received"
142378
+ });
142379
+ if (!parsed.stackHint) {
142380
+ const pendingClarification = !parsed.projectName ? "stack_and_name" : "stack";
142381
+ await upsertTelegramBootstrapSession(workspaceDir, {
142382
+ conversationId,
142383
+ ...incomingRequest,
142384
+ sourceRoute: session.sourceRoute,
142385
+ status: "clarifying",
142386
+ pendingClarification
142387
+ });
142388
+ await sendTelegramText(ctx, conversationId, buildClarificationMessage(parsed, pendingClarification));
142389
+ return;
142390
+ }
142391
+ continueBootstrap(ctx, conversationId, workspaceDir, incomingRequest, sourceRoute).catch((err) => {
142392
+ logBootstrapWarning(ctx, `[telegram-bootstrap] unhandled pipeline error (LLM path): ${err instanceof Error ? err.message : String(err)}`);
142393
+ });
142394
+ }
142277
142395
  async function continueBootstrap(ctx, conversationId, workspaceDir, request2, sourceRoute) {
142278
142396
  const telegramConfig = readFabricaTelegramConfig(ctx.pluginConfig);
142279
142397
  if (!telegramConfig.projectsForumChatId) {
@@ -142518,7 +142636,20 @@ function registerTelegramBootstrapHook(api, ctx) {
142518
142636
  });
142519
142637
  return;
142520
142638
  }
142521
- if (!isBootstrapCandidate(content)) return;
142639
+ if (!isBootstrapCandidate(content)) {
142640
+ if (isAmbiguousCandidate(content)) {
142641
+ await upsertTelegramBootstrapSession(workspaceDir, {
142642
+ conversationId,
142643
+ rawIdea: content,
142644
+ sourceRoute: { channel: "telegram", channelId: conversationId },
142645
+ status: "classifying"
142646
+ });
142647
+ classifyAndBootstrap(ctx, workspaceDir, conversationId, content).catch((err) => {
142648
+ logBootstrapWarning(ctx, `[telegram-bootstrap] LLM classify error: ${err instanceof Error ? err.message : String(err)}`);
142649
+ });
142650
+ }
142651
+ return;
142652
+ }
142522
142653
  const parsed = parseBootstrapRequest(content);
142523
142654
  const incomingRequest = {
142524
142655
  rawIdea: parsed.rawIdea,
@@ -142543,6 +142674,7 @@ function registerTelegramBootstrapHook(api, ctx) {
142543
142674
  ctx.logger.info(`[telegram-bootstrap] stale received session (expired) \u2014 restarting pipeline for conversation ${conversationId}`);
142544
142675
  }
142545
142676
  }
142677
+ await sendTelegramText(ctx, conversationId, "Recebi! Vou analisar e come\xE7ar a montar o projeto...");
142546
142678
  const session = await upsertTelegramBootstrapSession(workspaceDir, {
142547
142679
  conversationId,
142548
142680
  ...incomingRequest,