@mestreyoda/fabrica 0.1.13 → 0.1.15

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.13") {
111333
- return "0.1.13";
111332
+ if ("0.1.15") {
111333
+ return "0.1.15";
111334
111334
  }
111335
111335
  try {
111336
111336
  const pkgPath = path5.join(THIS_DIR, "..", "..", "package.json");
@@ -142208,8 +142208,6 @@ function registerAttachmentHook(api, ctx) {
142208
142208
 
142209
142209
  // lib/dispatch/telegram-bootstrap-hook.ts
142210
142210
  import { homedir as homedir3 } from "node:os";
142211
- init_runtime_paths();
142212
- init_extract_json();
142213
142211
  init_zod();
142214
142212
 
142215
142213
  // lib/dispatch/telegram-bootstrap-session.ts
@@ -142244,14 +142242,14 @@ function buildBootstrapRequestHash(input) {
142244
142242
  return buildBootstrapRequestFingerprint(input);
142245
142243
  }
142246
142244
  function nextSuppressUntil(status) {
142247
- const ttl = status === "classifying" ? CLASSIFYING_TTL_MS : SESSION_TTL_MS;
142245
+ const ttl = status === "classifying" || status === "pending_classify" ? CLASSIFYING_TTL_MS : SESSION_TTL_MS;
142248
142246
  return new Date(Date.now() + ttl).toISOString();
142249
142247
  }
142250
142248
  async function readTelegramBootstrapSession(workspaceDir, conversationId) {
142251
142249
  try {
142252
142250
  const raw = await fs38.readFile(sessionPath(workspaceDir, conversationId), "utf-8");
142253
142251
  const session = JSON.parse(raw);
142254
- if ((session.status === "clarifying" || session.status === "classifying") && Date.parse(session.suppressUntil) < Date.now()) {
142252
+ if ((session.status === "clarifying" || session.status === "classifying" || session.status === "pending_classify") && Date.parse(session.suppressUntil) < Date.now()) {
142255
142253
  await fs38.unlink(sessionPath(workspaceDir, conversationId)).catch(() => {
142256
142254
  });
142257
142255
  return null;
@@ -142324,6 +142322,7 @@ function shouldSuppressTelegramBootstrapReply(session, request2) {
142324
142322
 
142325
142323
  // lib/dispatch/telegram-bootstrap-hook.ts
142326
142324
  var BOOTSTRAP_TIMEOUT_MS = 5 * 6e4;
142325
+ var LAYER3_CONFIDENCE_THRESHOLD = 0.6;
142327
142326
  var BOOTSTRAP_MESSAGES = {
142328
142327
  ack: {
142329
142328
  pt: "Recebi! Vou analisar e come\xE7ar a montar o projeto...",
@@ -142341,6 +142340,10 @@ var BOOTSTRAP_MESSAGES = {
142341
142340
  pt: "N\xE3o consegui identificar a stack. Pode me dizer qual linguagem/framework voc\xEA quer usar? Ex: Python, Node.js, Go, Java...",
142342
142341
  en: "Couldn't identify the stack. Can you tell me which language/framework you'd like to use? e.g., Python, Node.js, Go, Java..."
142343
142342
  },
142343
+ clarifyName: {
142344
+ pt: "Como voc\xEA quer chamar o projeto? Se preferir, posso escolher um nome.",
142345
+ en: "What do you want to name the project? If you prefer, I can pick one."
142346
+ },
142344
142347
  registered: {
142345
142348
  pt: (name, link) => `Projeto "${name}" registrado.
142346
142349
  Vou continuar o fluxo em ${link}`,
@@ -142349,13 +142352,18 @@ I'll continue the flow at ${link}`
142349
142352
  }
142350
142353
  };
142351
142354
  function inferProjectSlug(text) {
142352
- const slug = text.toLowerCase().normalize("NFKD").replace(/[^\w\s-]/g, "").trim().replace(/\s+/g, "-").replace(/-+/g, "-").slice(0, 64);
142355
+ let cleaned = text.replace(/^(create|build|crie|cria|criar|fazer?|quero|i need|i want)\s+(uma|um|me\s+a?|an|a)?\s*/i, "").replace(/\s+(that|which|que|para|for|pra)\s+.*/i, "").trim();
142356
+ if (!cleaned) cleaned = text;
142357
+ const slug = cleaned.toLowerCase().normalize("NFKD").replace(/[^\w\s-]/g, "").trim().replace(/\s+/g, "-").replace(/-+/g, "-").slice(0, 64);
142353
142358
  return slug || void 0;
142354
142359
  }
142355
142360
  function normalizeText3(value) {
142356
142361
  const trimmed = value?.trim();
142357
142362
  return trimmed ? trimmed : void 0;
142358
142363
  }
142364
+ function normalizeUserResponse(text) {
142365
+ return text.trim().replace(/[.,!?;:\u2026]+$/, "").toLowerCase();
142366
+ }
142359
142367
  function detectStackHint(text) {
142360
142368
  const lower2 = text.toLowerCase();
142361
142369
  if (/\b(nextjs|next\.js)\b/.test(lower2)) return "nextjs";
@@ -142422,26 +142430,31 @@ Examples:
142422
142430
  - "How's the project going?" \u2192 {"intent":"other","confidence":0.95,"stackHint":null,"projectSlug":null,"language":"en"}
142423
142431
  - "Oi, tudo bem?" \u2192 {"intent":"other","confidence":0.99,"stackHint":null,"projectSlug":null,"language":"pt"}
142424
142432
  - "Me faz um app que converte temperaturas" \u2192 {"intent":"create_project","confidence":0.9,"stackHint":null,"projectSlug":"conversor-temperaturas","language":"pt"}`;
142425
- async function classifyDmIntent(ctx, content, workspaceDir) {
142433
+ async function classifyDmIntent(ctx, content, _workspaceDir) {
142426
142434
  try {
142435
+ const runtime = ctx.runtime;
142436
+ if (!runtime?.subagent?.run) return null;
142427
142437
  const truncated = content.slice(0, MAX_CLASSIFY_LENGTH);
142428
142438
  const prompt = CLASSIFY_PROMPT_TEMPLATE.replace("$CONTENT", truncated.replace(/"/g, '\\"'));
142429
- const cliPath = resolveOpenClawCli({ homeDir: homedir3(), workspaceDir });
142430
- const sessionId = `dm-classify-${Date.now()}`;
142431
- const result = await ctx.runCommand(
142432
- [cliPath, "agent", "--local", "-m", prompt, "--session-id", sessionId, "--json"],
142433
- { timeoutMs: 15e3 }
142434
- );
142435
- const stdout = result.stdout ?? "";
142436
- if (!stdout.trim()) return null;
142437
- const parsed = extractJsonFromStdout(stdout);
142438
- if (!parsed) return null;
142439
- const text = parsed?.payloads?.[0]?.text;
142440
- const jsonStr = text ? text.replace(/^```(json)?/gm, "").replace(/```$/gm, "").trim() : JSON.stringify(parsed);
142439
+ const sessionKey = `dm-classify-${Date.now()}`;
142440
+ const { runId } = await runtime.subagent.run({
142441
+ sessionKey,
142442
+ message: prompt,
142443
+ extraSystemPrompt: "You are a JSON classifier. Return ONLY valid JSON, no markdown fences.",
142444
+ lane: "subagent",
142445
+ deliver: false,
142446
+ idempotencyKey: sessionKey
142447
+ });
142448
+ const waitResult = await runtime.subagent.waitForRun({ runId, timeoutMs: 15e3 });
142449
+ if (waitResult.status !== "ok") return null;
142450
+ const messages = await runtime.subagent.getSessionMessages({ sessionKey });
142451
+ const lastAssistant = messages?.filter((m2) => m2.role === "assistant").pop();
142452
+ const text = lastAssistant?.content ?? "";
142453
+ if (!text.trim()) return null;
142454
+ const jsonStr = text.replace(/^```(json)?/gm, "").replace(/```$/gm, "").trim();
142441
142455
  const intentData = JSON.parse(jsonStr);
142442
142456
  const validated = DmIntentSchema.safeParse(intentData);
142443
- if (!validated.success) return null;
142444
- return validated.data;
142457
+ return validated.success ? validated.data : null;
142445
142458
  } catch {
142446
142459
  return null;
142447
142460
  }
@@ -142454,6 +142467,21 @@ function isBootstrapCandidate(text) {
142454
142467
  return createCue && softwareCue;
142455
142468
  }
142456
142469
  function parseClarificationResponse(text, session) {
142470
+ if (session.pendingClarification === "name") {
142471
+ const trimmed = text.trim();
142472
+ const autoPatterns = /^(escolha|pick one|tanto faz|you choose|pode escolher|auto|skip)$/i;
142473
+ if (autoPatterns.test(normalizeUserResponse(text))) {
142474
+ return { recognized: true, projectName: inferProjectSlug(session.rawIdea) ?? `project-${Date.now()}`, stackHint: session.stackHint ?? void 0 };
142475
+ }
142476
+ const nameField = parseField(text, ["project name", "nome do projeto", "nome", "name"]);
142477
+ if (nameField) {
142478
+ return { recognized: true, projectName: nameField, stackHint: session.stackHint ?? void 0 };
142479
+ }
142480
+ if (trimmed.length > 0 && trimmed.length <= 64) {
142481
+ return { recognized: true, projectName: trimmed, stackHint: session.stackHint ?? void 0 };
142482
+ }
142483
+ return { recognized: false };
142484
+ }
142457
142485
  const stackField = parseField(text, ["stack", "framework", "linguagem", "language"]);
142458
142486
  if (stackField) {
142459
142487
  return { recognized: true, stackHint: detectStackHint(stackField) ?? stackField };
@@ -142462,7 +142490,7 @@ function parseClarificationResponse(text, session) {
142462
142490
  if (detectedStack) {
142463
142491
  return { recognized: true, stackHint: detectedStack };
142464
142492
  }
142465
- const lower2 = text.toLowerCase().trim();
142493
+ const lower2 = normalizeUserResponse(text);
142466
142494
  const bareStackMap = {
142467
142495
  python: "python-cli",
142468
142496
  py: "python-cli",
@@ -142489,16 +142517,14 @@ function parseClarificationResponse(text, session) {
142489
142517
  return { recognized: false };
142490
142518
  }
142491
142519
  function buildClarificationMessage(parsed, pendingClarification, language = "pt") {
142520
+ if (pendingClarification === "name") {
142521
+ return BOOTSTRAP_MESSAGES.clarifyName[language];
142522
+ }
142492
142523
  if (pendingClarification === "stack_and_name" || !parsed.stackHint && !parsed.projectName) {
142493
142524
  return BOOTSTRAP_MESSAGES.clarifyBoth[language];
142494
142525
  }
142495
142526
  return BOOTSTRAP_MESSAGES.clarifyStack[language];
142496
142527
  }
142497
- function buildFollowUpClarification(session) {
142498
- const lang = session.language ?? "pt";
142499
- if (!session.stackHint) return BOOTSTRAP_MESSAGES.clarifyStackFollowUp[lang];
142500
- return lang === "en" ? "Can you give me more details about what you want to build?" : "Pode me dar mais detalhes sobre o que voc\xEA quer construir?";
142501
- }
142502
142528
  function buildTopicDeepLink(chatId, topicId) {
142503
142529
  const stripped = chatId.replace(/^-100/, "");
142504
142530
  return `https://t.me/c/${stripped}/${topicId}`;
@@ -142534,8 +142560,14 @@ function logBootstrapWarning(ctx, message) {
142534
142560
  }
142535
142561
  }
142536
142562
  async function classifyAndBootstrap(ctx, workspaceDir, conversationId, content) {
142563
+ await upsertTelegramBootstrapSession(workspaceDir, {
142564
+ conversationId,
142565
+ rawIdea: content,
142566
+ sourceRoute: { channel: "telegram", channelId: conversationId },
142567
+ status: "classifying"
142568
+ });
142537
142569
  const classification = await classifyDmIntent(ctx, content, workspaceDir);
142538
- if (!classification || classification.intent !== "create_project" || classification.confidence < 0.7) {
142570
+ if (!classification || classification.intent !== "create_project" || classification.confidence < LAYER3_CONFIDENCE_THRESHOLD) {
142539
142571
  if (!classification) {
142540
142572
  logBootstrapWarning(ctx, `[telegram-bootstrap] LLM classify failed, falling back (conversation: ${conversationId})`);
142541
142573
  }
@@ -142623,7 +142655,6 @@ async function continueBootstrap(ctx, conversationId, workspaceDir, request2, so
142623
142655
  );
142624
142656
  return;
142625
142657
  }
142626
- const projectName = request2.projectName ?? void 0;
142627
142658
  const stackHint = request2.stackHint;
142628
142659
  if (!stackHint) {
142629
142660
  const existingSession = await readTelegramBootstrapSession(workspaceDir, conversationId);
@@ -142643,7 +142674,38 @@ async function continueBootstrap(ctx, conversationId, workspaceDir, request2, so
142643
142674
  ));
142644
142675
  return;
142645
142676
  }
142646
- const candidateSlug = inferProjectSlug(projectName ?? request2.rawIdea);
142677
+ if (!request2.projectName) {
142678
+ const inferredSlug = inferProjectSlug(request2.rawIdea);
142679
+ if (inferredSlug) {
142680
+ request2.projectName = inferredSlug;
142681
+ } else {
142682
+ const existingSession = await readTelegramBootstrapSession(workspaceDir, conversationId);
142683
+ if (existingSession?.pendingClarification === "name") {
142684
+ request2.projectName = `project-${Date.now()}`;
142685
+ } else {
142686
+ const lang = existingSession?.language ?? "pt";
142687
+ await upsertTelegramBootstrapSession(workspaceDir, {
142688
+ conversationId,
142689
+ rawIdea: request2.rawIdea,
142690
+ stackHint: request2.stackHint ?? void 0,
142691
+ status: "clarifying",
142692
+ pendingClarification: "name",
142693
+ language: lang
142694
+ });
142695
+ await sendTelegramText(
142696
+ ctx,
142697
+ conversationId,
142698
+ buildClarificationMessage(
142699
+ { rawIdea: request2.rawIdea, projectName: void 0, stackHint: request2.stackHint ?? void 0 },
142700
+ "name",
142701
+ lang
142702
+ )
142703
+ );
142704
+ return;
142705
+ }
142706
+ }
142707
+ }
142708
+ const candidateSlug = inferProjectSlug(request2.projectName ?? request2.rawIdea);
142647
142709
  if (candidateSlug) {
142648
142710
  const projects = await readProjects(workspaceDir).catch(() => null);
142649
142711
  if (projects?.projects?.[candidateSlug]) {
@@ -142692,7 +142754,7 @@ async function continueBootstrap(ctx, conversationId, workspaceDir, request2, so
142692
142754
  metadata: {
142693
142755
  source: "telegram-dm-bootstrap",
142694
142756
  factory_change: false,
142695
- project_name: projectName ?? null,
142757
+ project_name: request2.projectName ?? null,
142696
142758
  repo_url: request2.repoUrl ?? null,
142697
142759
  repo_path: request2.repoPath ?? null,
142698
142760
  stack_hint: stackHint,
@@ -142749,7 +142811,7 @@ Erro: ${result.error ?? "erro desconhecido"}`
142749
142811
  );
142750
142812
  return;
142751
142813
  }
142752
- const resolvedProjectName = result.payload.metadata.project_name ?? result.payload.metadata.project_slug ?? projectName ?? "projeto";
142814
+ const resolvedProjectName = result.payload.metadata.project_name ?? result.payload.metadata.project_slug ?? request2.projectName ?? "projeto";
142753
142815
  const projectChannelId = result.payload.metadata.channel_id ?? telegramConfig.projectsForumChatId;
142754
142816
  const messageThreadId = result.payload.metadata.message_thread_id;
142755
142817
  const projectSlug = result.payload.metadata.project_slug ?? result.payload.scaffold?.project_slug ?? null;
@@ -142872,7 +142934,7 @@ function registerTelegramBootstrapHook(api, ctx) {
142872
142934
  }
142873
142935
  const existingSession = await readTelegramBootstrapSession(workspaceDir, conversationId);
142874
142936
  const sessionIsExpired = existingSession != null && Date.parse(existingSession.suppressUntil) < Date.now();
142875
- if (existingSession && !sessionIsExpired && existingSession.status === "classifying") {
142937
+ if (existingSession && !sessionIsExpired && (existingSession.status === "classifying" || existingSession.status === "pending_classify")) {
142876
142938
  ctx.logger.info(`[telegram-bootstrap] LLM classification in progress for ${conversationId}, ignoring concurrent message`);
142877
142939
  return;
142878
142940
  }
@@ -142880,7 +142942,11 @@ function registerTelegramBootstrapHook(api, ctx) {
142880
142942
  const clarResult = parseClarificationResponse(content, existingSession);
142881
142943
  if (!clarResult.recognized) {
142882
142944
  ctx.logger.info(`[telegram-bootstrap] clarification response not recognized, re-asking (conversation: ${conversationId})`);
142883
- await sendTelegramText(ctx, conversationId, buildFollowUpClarification(existingSession));
142945
+ await sendTelegramText(ctx, conversationId, buildClarificationMessage(
142946
+ { rawIdea: existingSession.rawIdea, stackHint: existingSession.stackHint ?? void 0, projectName: existingSession.projectName ?? void 0 },
142947
+ existingSession.pendingClarification ?? void 0,
142948
+ existingSession.language ?? "pt"
142949
+ ));
142884
142950
  return;
142885
142951
  }
142886
142952
  const mergedRequest = {
@@ -142903,7 +142969,7 @@ function registerTelegramBootstrapHook(api, ctx) {
142903
142969
  conversationId,
142904
142970
  rawIdea: content,
142905
142971
  sourceRoute: { channel: "telegram", channelId: conversationId },
142906
- status: "classifying"
142972
+ status: "pending_classify"
142907
142973
  });
142908
142974
  classifyAndBootstrap(ctx, workspaceDir, conversationId, content).catch((err) => {
142909
142975
  logBootstrapWarning(ctx, `[telegram-bootstrap] LLM classify error: ${err instanceof Error ? err.message : String(err)}`);
@@ -142912,22 +142978,22 @@ function registerTelegramBootstrapHook(api, ctx) {
142912
142978
  return;
142913
142979
  }
142914
142980
  const parsed = parseBootstrapRequest(content);
142915
- const incomingRequest = {
142981
+ const preClassifyRequest = {
142916
142982
  rawIdea: parsed.rawIdea,
142917
142983
  projectName: parsed.projectName ?? null,
142918
142984
  stackHint: parsed.stackHint ?? null,
142919
142985
  repoUrl: parsed.repoUrl ?? null,
142920
142986
  repoPath: parsed.repoPath ?? null
142921
142987
  };
142922
- const incomingRequestHash = buildBootstrapRequestHash(incomingRequest);
142923
- const sessionForHash = await readTelegramBootstrapSession(workspaceDir, conversationId);
142924
- if (sessionForHash?.requestHash === incomingRequestHash) {
142925
- if (sessionForHash.status === "completed") {
142988
+ const preClassifyHash = buildBootstrapRequestHash(preClassifyRequest);
142989
+ const sessionForHashPreClassify = await readTelegramBootstrapSession(workspaceDir, conversationId);
142990
+ if (sessionForHashPreClassify?.requestHash === preClassifyHash) {
142991
+ if (sessionForHashPreClassify.status === "completed") {
142926
142992
  ctx.logger.info(`[telegram-bootstrap] duplicate completed DM ignored for conversation ${conversationId}`);
142927
142993
  return;
142928
142994
  }
142929
- const isExpiredReceived = sessionForHash.status === "received" && Date.parse(sessionForHash.suppressUntil) < Date.now();
142930
- if (sessionForHash.status !== "failed" && !isExpiredReceived) {
142995
+ const isExpiredReceived = sessionForHashPreClassify.status === "received" && Date.parse(sessionForHashPreClassify.suppressUntil) < Date.now();
142996
+ if (sessionForHashPreClassify.status !== "failed" && !isExpiredReceived) {
142931
142997
  ctx.logger.info(`[telegram-bootstrap] duplicate in-flight DM ignored for conversation ${conversationId}`);
142932
142998
  return;
142933
142999
  }
@@ -142935,6 +143001,25 @@ function registerTelegramBootstrapHook(api, ctx) {
142935
143001
  ctx.logger.info(`[telegram-bootstrap] stale received session (expired) \u2014 restarting pipeline for conversation ${conversationId}`);
142936
143002
  }
142937
143003
  }
143004
+ if (!parsed.projectName && ctx.runtime?.subagent?.run) {
143005
+ await upsertTelegramBootstrapSession(workspaceDir, {
143006
+ conversationId,
143007
+ rawIdea: parsed.rawIdea,
143008
+ sourceRoute: { channel: "telegram", channelId: conversationId },
143009
+ status: "pending_classify"
143010
+ });
143011
+ const classification = await classifyDmIntent(ctx, content, workspaceDir);
143012
+ if (classification?.projectSlug) {
143013
+ parsed.projectName = classification.projectSlug;
143014
+ }
143015
+ }
143016
+ const incomingRequest = {
143017
+ rawIdea: parsed.rawIdea,
143018
+ projectName: parsed.projectName ?? null,
143019
+ stackHint: parsed.stackHint ?? null,
143020
+ repoUrl: parsed.repoUrl ?? null,
143021
+ repoPath: parsed.repoPath ?? null
143022
+ };
142938
143023
  const language = /\b(cria|crie|criar|construa|desenvolva|registre|novo projeto)\b/i.test(content) ? "pt" : "en";
142939
143024
  await sendTelegramText(ctx, conversationId, BOOTSTRAP_MESSAGES.ack[language]);
142940
143025
  const session = await upsertTelegramBootstrapSession(workspaceDir, {