@mestreyoda/fabrica 0.1.7 → 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 +141 -8
- package/dist/index.js.map +3 -3
- package/package.json +1 -1
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.
|
|
111333
|
-
return "0.1.
|
|
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");
|
|
@@ -138808,8 +138808,8 @@ async function ensurePythonEnvironment(repoPath, stack, runCommand) {
|
|
|
138808
138808
|
skipped: false,
|
|
138809
138809
|
stack,
|
|
138810
138810
|
family: "python",
|
|
138811
|
-
toolchain: "
|
|
138812
|
-
packageManager: "
|
|
138811
|
+
toolchain: "uv",
|
|
138812
|
+
packageManager: "uv",
|
|
138813
138813
|
lockfile: null,
|
|
138814
138814
|
environmentPath: null,
|
|
138815
138815
|
commandsRun,
|
|
@@ -139086,6 +139086,7 @@ fi
|
|
|
139086
139086
|
}
|
|
139087
139087
|
|
|
139088
139088
|
// lib/intake/steps/scaffold.ts
|
|
139089
|
+
var PYTHON_STACKS2 = /* @__PURE__ */ new Set(["python-cli", "fastapi", "flask", "django"]);
|
|
139089
139090
|
var scaffoldStep = {
|
|
139090
139091
|
name: "scaffold",
|
|
139091
139092
|
shouldRun: (payload) => payload.impact?.is_greenfield === true && !payload.dry_run,
|
|
@@ -139122,7 +139123,7 @@ var scaffoldStep = {
|
|
|
139122
139123
|
ctx.log(
|
|
139123
139124
|
bootstrap.skipped ? `Scaffold bootstrap already current (${bootstrap.packageManager})` : `Scaffold bootstrap completed (${bootstrap.packageManager})`
|
|
139124
139125
|
);
|
|
139125
|
-
if (payload.spec) {
|
|
139126
|
+
if (scaffold.stack && PYTHON_STACKS2.has(scaffold.stack) && payload.spec) {
|
|
139126
139127
|
try {
|
|
139127
139128
|
const contract = generateQaContract({
|
|
139128
139129
|
spec: payload.spec,
|
|
@@ -142031,6 +142032,9 @@ function registerAttachmentHook(api, ctx) {
|
|
|
142031
142032
|
|
|
142032
142033
|
// lib/dispatch/telegram-bootstrap-hook.ts
|
|
142033
142034
|
import { homedir as homedir3 } from "node:os";
|
|
142035
|
+
init_runtime_paths();
|
|
142036
|
+
init_extract_json();
|
|
142037
|
+
init_zod();
|
|
142034
142038
|
|
|
142035
142039
|
// lib/dispatch/telegram-bootstrap-session.ts
|
|
142036
142040
|
init_migrate_layout();
|
|
@@ -142069,7 +142073,7 @@ async function readTelegramBootstrapSession(workspaceDir, conversationId) {
|
|
|
142069
142073
|
try {
|
|
142070
142074
|
const raw = await fs38.readFile(sessionPath(workspaceDir, conversationId), "utf-8");
|
|
142071
142075
|
const session = JSON.parse(raw);
|
|
142072
|
-
if (session.status === "clarifying" && Date.parse(session.suppressUntil) < Date.now()) {
|
|
142076
|
+
if ((session.status === "clarifying" || session.status === "classifying") && Date.parse(session.suppressUntil) < Date.now()) {
|
|
142073
142077
|
await fs38.unlink(sessionPath(workspaceDir, conversationId)).catch(() => {
|
|
142074
142078
|
});
|
|
142075
142079
|
return null;
|
|
@@ -142080,6 +142084,10 @@ async function readTelegramBootstrapSession(workspaceDir, conversationId) {
|
|
|
142080
142084
|
throw error48;
|
|
142081
142085
|
}
|
|
142082
142086
|
}
|
|
142087
|
+
async function deleteTelegramBootstrapSession(workspaceDir, conversationId) {
|
|
142088
|
+
await fs38.unlink(sessionPath(workspaceDir, conversationId)).catch(() => {
|
|
142089
|
+
});
|
|
142090
|
+
}
|
|
142083
142091
|
async function writeTelegramBootstrapSession(workspaceDir, session) {
|
|
142084
142092
|
const dir = sessionsDir(workspaceDir);
|
|
142085
142093
|
await fs38.mkdir(dir, { recursive: true });
|
|
@@ -142183,10 +142191,60 @@ function parseBootstrapRequest(text) {
|
|
|
142183
142191
|
stackHint
|
|
142184
142192
|
};
|
|
142185
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
|
+
}
|
|
142186
142244
|
function isBootstrapCandidate(text) {
|
|
142187
142245
|
const lower2 = text.toLowerCase();
|
|
142188
142246
|
if (/^\s*(project name|nome do projeto|repository url|repo url|stack)\s*:/im.test(text)) return true;
|
|
142189
|
-
const createCue = /\b(crie|criar|create|register|registre|novo projeto|new project)\b/.test(lower2);
|
|
142247
|
+
const createCue = /\b(crie|cria|criar|create|register|registre|construa|desenvolva|novo projeto|new project)\b/.test(lower2);
|
|
142190
142248
|
const softwareCue = /\b(projeto|project|cli|api|app|aplicativo|servi[cç]o|library|biblioteca|repo|reposit[oó]rio)\b/.test(lower2);
|
|
142191
142249
|
return createCue && softwareCue;
|
|
142192
142250
|
}
|
|
@@ -142273,6 +142331,67 @@ function logBootstrapWarning(ctx, message) {
|
|
|
142273
142331
|
ctx.logger.info(message);
|
|
142274
142332
|
}
|
|
142275
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
|
+
}
|
|
142276
142395
|
async function continueBootstrap(ctx, conversationId, workspaceDir, request2, sourceRoute) {
|
|
142277
142396
|
const telegramConfig = readFabricaTelegramConfig(ctx.pluginConfig);
|
|
142278
142397
|
if (!telegramConfig.projectsForumChatId) {
|
|
@@ -142517,7 +142636,20 @@ function registerTelegramBootstrapHook(api, ctx) {
|
|
|
142517
142636
|
});
|
|
142518
142637
|
return;
|
|
142519
142638
|
}
|
|
142520
|
-
if (!isBootstrapCandidate(content))
|
|
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
|
+
}
|
|
142521
142653
|
const parsed = parseBootstrapRequest(content);
|
|
142522
142654
|
const incomingRequest = {
|
|
142523
142655
|
rawIdea: parsed.rawIdea,
|
|
@@ -142542,6 +142674,7 @@ function registerTelegramBootstrapHook(api, ctx) {
|
|
|
142542
142674
|
ctx.logger.info(`[telegram-bootstrap] stale received session (expired) \u2014 restarting pipeline for conversation ${conversationId}`);
|
|
142543
142675
|
}
|
|
142544
142676
|
}
|
|
142677
|
+
await sendTelegramText(ctx, conversationId, "Recebi! Vou analisar e come\xE7ar a montar o projeto...");
|
|
142545
142678
|
const session = await upsertTelegramBootstrapSession(workspaceDir, {
|
|
142546
142679
|
conversationId,
|
|
142547
142680
|
...incomingRequest,
|