@mestreyoda/fabrica 0.1.12 → 0.1.14
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 +216 -42
- package/dist/index.js.map +3 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -110768,7 +110768,7 @@ var init_registry = __esm({
|
|
|
110768
110768
|
senior: "\u{1F9E0}"
|
|
110769
110769
|
},
|
|
110770
110770
|
fallbackEmoji: "\u{1F50D}",
|
|
110771
|
-
completionResults: ["pass", "fail", "refine", "blocked"],
|
|
110771
|
+
completionResults: ["pass", "fail", "fail_infra", "refine", "blocked"],
|
|
110772
110772
|
sessionKeyPattern: "tester",
|
|
110773
110773
|
notifications: { onStart: true, onComplete: true }
|
|
110774
110774
|
},
|
|
@@ -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.14") {
|
|
111333
|
+
return "0.1.14";
|
|
111334
111334
|
}
|
|
111335
111335
|
try {
|
|
111336
111336
|
const pkgPath = path5.join(THIS_DIR, "..", "..", "package.json");
|
|
@@ -119852,6 +119852,22 @@ PR merged \u2014 issue closed automatically (was stuck in ${event.fromState})`;
|
|
|
119852
119852
|
\u{1F3C1} Done \u2014 no human action needed.`;
|
|
119853
119853
|
return msg;
|
|
119854
119854
|
}
|
|
119855
|
+
case "infraFailure": {
|
|
119856
|
+
const icon = event.infraFailCount >= 2 ? "\u{1F6A8}" : "\u26A0\uFE0F";
|
|
119857
|
+
let msg = `${icon} Infrastructure failure on #${event.issueId} (attempt ${event.infraFailCount})`;
|
|
119858
|
+
msg += `
|
|
119859
|
+
${event.summary}`;
|
|
119860
|
+
msg += `
|
|
119861
|
+
\u{1F4CB} [Issue #${event.issueId}](${event.issueUrl})`;
|
|
119862
|
+
if (event.infraFailCount >= 2) {
|
|
119863
|
+
msg += `
|
|
119864
|
+
\u2192 Circuit breaker tripped \u2014 moved to Refining (operator intervention required)`;
|
|
119865
|
+
} else {
|
|
119866
|
+
msg += `
|
|
119867
|
+
\u2192 Returned to To Test queue \u2014 will retry after toolchain is fixed`;
|
|
119868
|
+
}
|
|
119869
|
+
return msg;
|
|
119870
|
+
}
|
|
119855
119871
|
}
|
|
119856
119872
|
}
|
|
119857
119873
|
async function sendMessage(target, message, channel, workspaceDir, runtime, accountId, runCommand, messageThreadId, auditMeta) {
|
|
@@ -120589,10 +120605,10 @@ init_audit();
|
|
|
120589
120605
|
init_migrate_layout();
|
|
120590
120606
|
init_roles();
|
|
120591
120607
|
init_workflow();
|
|
120608
|
+
init_labels();
|
|
120592
120609
|
|
|
120593
120610
|
// lib/tools/tasks/public-output-sanitizer.ts
|
|
120594
120611
|
var SECRET_PATTERNS = [
|
|
120595
|
-
/\b[A-Za-z_][A-Za-z0-9_]*=([^\s]+)/g,
|
|
120596
120612
|
/\b(?:ghp_|gho_|github_pat_|sk-|xoxb-|xoxp-|AIza|AKIA|glpat-)[A-Za-z0-9._-]*/g,
|
|
120597
120613
|
/\b(?:token|secret|api[_-]?key|password|passwd|authorization|bearer)\b\s*[:=]\s*[^\s]+/gi
|
|
120598
120614
|
];
|
|
@@ -120917,6 +120933,7 @@ function matchesReviewArtifact(comment, artifactId, artifactType) {
|
|
|
120917
120933
|
}
|
|
120918
120934
|
return comment.state === "COMMENTED" && !comment.path;
|
|
120919
120935
|
}
|
|
120936
|
+
var INFRA_FAIL_CIRCUIT_BREAKER_THRESHOLD = 2;
|
|
120920
120937
|
function createWorkFinishTool(ctx) {
|
|
120921
120938
|
return (toolCtx) => ({
|
|
120922
120939
|
name: "work_finish",
|
|
@@ -120928,7 +120945,7 @@ function createWorkFinishTool(ctx) {
|
|
|
120928
120945
|
properties: {
|
|
120929
120946
|
channelId: { type: "string", description: "YOUR chat/group ID \u2014 the numeric ID of the chat you are in right now (e.g. '-1003844794417'). Do NOT guess; use the ID of the conversation this message came from." },
|
|
120930
120947
|
role: { type: "string", enum: getAllRoleIds(), description: "Worker role" },
|
|
120931
|
-
result: { type: "string", enum: ["done", "pass", "fail", "refine", "blocked", "approve", "reject"], description: "Completion result" },
|
|
120948
|
+
result: { type: "string", enum: ["done", "pass", "fail", "fail_infra", "refine", "blocked", "approve", "reject"], description: "Completion result. Use fail_infra (tester only) when the test toolchain is missing or broken \u2014 this keeps the issue in the test queue instead of routing it to the developer." },
|
|
120932
120949
|
summary: { type: "string", description: "Brief summary" },
|
|
120933
120950
|
prUrl: { type: "string", description: "PR/MR URL (auto-detected if omitted)" },
|
|
120934
120951
|
createdTasks: {
|
|
@@ -121010,6 +121027,79 @@ function createWorkFinishTool(ctx) {
|
|
|
121010
121027
|
})),
|
|
121011
121028
|
keyTransitions: resolvedConfig.workflowMeta.keyTransitions
|
|
121012
121029
|
});
|
|
121030
|
+
if (role === "tester" && result === "fail_infra") {
|
|
121031
|
+
const currentInfraFails = (issueRuntime?.infraFailCount ?? 0) + 1;
|
|
121032
|
+
await updateIssueRuntime(workspaceDir, project.slug, issueId, {
|
|
121033
|
+
infraFailCount: currentInfraFails
|
|
121034
|
+
});
|
|
121035
|
+
await log(workspaceDir, "infra_failure", {
|
|
121036
|
+
project: project.name,
|
|
121037
|
+
issue: issueId,
|
|
121038
|
+
role,
|
|
121039
|
+
result,
|
|
121040
|
+
summary: summary ?? null,
|
|
121041
|
+
infraFailCount: currentInfraFails
|
|
121042
|
+
});
|
|
121043
|
+
const notifyConfig = getNotificationConfig(ctx.pluginConfig);
|
|
121044
|
+
const target = resolveNotifyChannel([], project.channels);
|
|
121045
|
+
const issueUrl = `https://github.com/${project.repo}/issues/${issueId}`;
|
|
121046
|
+
await notify(
|
|
121047
|
+
{
|
|
121048
|
+
type: "infraFailure",
|
|
121049
|
+
project: project.name,
|
|
121050
|
+
issueId,
|
|
121051
|
+
issueUrl,
|
|
121052
|
+
summary: summary ?? "Infrastructure failure during testing",
|
|
121053
|
+
infraFailCount: currentInfraFails
|
|
121054
|
+
},
|
|
121055
|
+
{
|
|
121056
|
+
workspaceDir,
|
|
121057
|
+
config: notifyConfig,
|
|
121058
|
+
channelId: target?.channelId,
|
|
121059
|
+
channel: target?.channel ?? "telegram",
|
|
121060
|
+
runtime: ctx.runtime,
|
|
121061
|
+
accountId: target?.accountId,
|
|
121062
|
+
messageThreadId: target?.messageThreadId,
|
|
121063
|
+
runCommand: ctx.runCommand
|
|
121064
|
+
}
|
|
121065
|
+
).catch((err) => {
|
|
121066
|
+
getRootLogger().warn(`infra_failure notification failed: ${err}`);
|
|
121067
|
+
});
|
|
121068
|
+
if (currentInfraFails >= INFRA_FAIL_CIRCUIT_BREAKER_THRESHOLD) {
|
|
121069
|
+
await log(workspaceDir, "infra_failure_circuit_breaker", {
|
|
121070
|
+
project: project.name,
|
|
121071
|
+
issue: issueId,
|
|
121072
|
+
infraFailCount: currentInfraFails
|
|
121073
|
+
});
|
|
121074
|
+
await resilientLabelTransition(provider, issueId, "Testing", "Refining");
|
|
121075
|
+
} else {
|
|
121076
|
+
await resilientLabelTransition(provider, issueId, "Testing", "To Test");
|
|
121077
|
+
}
|
|
121078
|
+
await deactivateWorker(workspaceDir, project.slug, "tester", {
|
|
121079
|
+
level: slotLevel,
|
|
121080
|
+
slotIndex,
|
|
121081
|
+
issueId: String(issueId)
|
|
121082
|
+
});
|
|
121083
|
+
await recordIssueLifecycle({
|
|
121084
|
+
workspaceDir,
|
|
121085
|
+
slug: project.slug,
|
|
121086
|
+
issueId,
|
|
121087
|
+
stage: "session_completed",
|
|
121088
|
+
sessionKey: toolCtx.sessionKey ?? null,
|
|
121089
|
+
details: { role, result, infraFailCount: currentInfraFails }
|
|
121090
|
+
}).catch(() => {
|
|
121091
|
+
});
|
|
121092
|
+
return jsonResult2({
|
|
121093
|
+
success: true,
|
|
121094
|
+
project: project.name,
|
|
121095
|
+
projectSlug: project.slug,
|
|
121096
|
+
issueId,
|
|
121097
|
+
role,
|
|
121098
|
+
result,
|
|
121099
|
+
infraFailCount: currentInfraFails,
|
|
121100
|
+
circuitBroken: currentInfraFails >= INFRA_FAIL_CIRCUIT_BREAKER_THRESHOLD
|
|
121101
|
+
});
|
|
121102
|
+
}
|
|
121013
121103
|
if (!getRule(role, result, workflow))
|
|
121014
121104
|
throw new Error(`Invalid completion: ${role}:${result}`);
|
|
121015
121105
|
const repoPath = resolveRepoPath(project.repo);
|
|
@@ -121128,6 +121218,9 @@ function createWorkFinishTool(ctx) {
|
|
|
121128
121218
|
details: { role, result }
|
|
121129
121219
|
}).catch(() => {
|
|
121130
121220
|
});
|
|
121221
|
+
if (role === "tester" && issueRuntime?.infraFailCount) {
|
|
121222
|
+
await updateIssueRuntime(workspaceDir, project.slug, issueId, { infraFailCount: 0 });
|
|
121223
|
+
}
|
|
121131
121224
|
return jsonResult2({
|
|
121132
121225
|
success: true,
|
|
121133
121226
|
project: project.name,
|
|
@@ -136063,25 +136156,39 @@ async function runHeartbeatTick(ctx, logger6, mode) {
|
|
|
136063
136156
|
})
|
|
136064
136157
|
);
|
|
136065
136158
|
const tickFn = lifecycle ? () => lifecycle.track(mode === "repair" ? "recovery" : "heartbeat", {}, run) : run;
|
|
136066
|
-
let
|
|
136067
|
-
|
|
136068
|
-
|
|
136069
|
-
|
|
136159
|
+
let resolveTick;
|
|
136160
|
+
let rejectTick;
|
|
136161
|
+
const tickPromise = new Promise((res, rej) => {
|
|
136162
|
+
resolveTick = res;
|
|
136163
|
+
rejectTick = rej;
|
|
136164
|
+
});
|
|
136165
|
+
tickPromise.catch(() => {
|
|
136166
|
+
});
|
|
136167
|
+
const wrappedTickFn = async () => {
|
|
136168
|
+
try {
|
|
136169
|
+
const result = await tickFn();
|
|
136170
|
+
resolveTick(result);
|
|
136171
|
+
return result;
|
|
136172
|
+
} catch (err) {
|
|
136173
|
+
rejectTick(err);
|
|
136174
|
+
throw err;
|
|
136175
|
+
}
|
|
136070
136176
|
};
|
|
136177
|
+
const HARD_TICK_TIMEOUT_MS = 5 * 6e4;
|
|
136071
136178
|
const raceResult = await raceWithTimeout(wrappedTickFn, DEFAULT_TICK_TIMEOUT_MS, () => {
|
|
136072
136179
|
_ticksTimedOut++;
|
|
136073
136180
|
timedOut = true;
|
|
136074
136181
|
logger6.warn(`work_heartbeat ${mode} tick timed out after ${DEFAULT_TICK_TIMEOUT_MS}ms (total timeouts: ${_ticksTimedOut})`);
|
|
136075
|
-
|
|
136076
|
-
|
|
136077
|
-
_tickRunning[mode] = false;
|
|
136078
|
-
_anyTickRunning = false;
|
|
136079
|
-
});
|
|
136080
|
-
} else {
|
|
136081
|
-
logger6.error("tick_mutex: tickPromise undefined in timeout handler \u2014 forcing mutex release");
|
|
136182
|
+
const hardTimeout = setTimeout(() => {
|
|
136183
|
+
logger6.error("tick_mutex: hard timeout \u2014 forcing mutex release");
|
|
136082
136184
|
_tickRunning[mode] = false;
|
|
136083
136185
|
_anyTickRunning = false;
|
|
136084
|
-
}
|
|
136186
|
+
}, HARD_TICK_TIMEOUT_MS);
|
|
136187
|
+
tickPromise.finally(() => {
|
|
136188
|
+
clearTimeout(hardTimeout);
|
|
136189
|
+
_tickRunning[mode] = false;
|
|
136190
|
+
_anyTickRunning = false;
|
|
136191
|
+
});
|
|
136085
136192
|
});
|
|
136086
136193
|
void raceResult;
|
|
136087
136194
|
} catch (err) {
|
|
@@ -138569,15 +138676,21 @@ Install manually: curl -LsSf ${UV_INSTALL_URL} | sh`
|
|
|
138569
138676
|
var PYTHON_TOOLCHAIN_PACKAGES = ["ruff", "mypy", "pip-audit"];
|
|
138570
138677
|
var TOOLCHAIN_DIR = ".openclaw/toolchains/python";
|
|
138571
138678
|
var TOOLCHAIN_FINGERPRINT_FILE = "toolchain.sha256";
|
|
138572
|
-
function toolchainFingerprint() {
|
|
138573
|
-
|
|
138679
|
+
async function toolchainFingerprint(runCommand) {
|
|
138680
|
+
let pythonVersion = "unknown";
|
|
138681
|
+
try {
|
|
138682
|
+
const result = await runCommand("python3", ["--version"], { timeout: 5e3 });
|
|
138683
|
+
if (result.exitCode === 0) pythonVersion = result.stdout.trim();
|
|
138684
|
+
} catch {
|
|
138685
|
+
}
|
|
138686
|
+
return createHash4("sha256").update(PYTHON_TOOLCHAIN_PACKAGES.join(",") + ":" + pythonVersion).digest("hex");
|
|
138574
138687
|
}
|
|
138575
138688
|
async function ensurePythonToolchain(runCommand, homeDir) {
|
|
138576
138689
|
const home = homeDir ?? process.env.HOME ?? "/tmp";
|
|
138577
138690
|
const toolchainPath = path34.join(home, TOOLCHAIN_DIR);
|
|
138578
138691
|
const ruffPath = path34.join(toolchainPath, "bin", "ruff");
|
|
138579
138692
|
const fingerprintPath = path34.join(toolchainPath, TOOLCHAIN_FINGERPRINT_FILE);
|
|
138580
|
-
const expectedFp = toolchainFingerprint();
|
|
138693
|
+
const expectedFp = await toolchainFingerprint(runCommand);
|
|
138581
138694
|
if (await isValidBinary(ruffPath)) {
|
|
138582
138695
|
try {
|
|
138583
138696
|
const currentFp = (await fs34.readFile(fingerprintPath, "utf-8")).trim();
|
|
@@ -142095,8 +142208,6 @@ function registerAttachmentHook(api, ctx) {
|
|
|
142095
142208
|
|
|
142096
142209
|
// lib/dispatch/telegram-bootstrap-hook.ts
|
|
142097
142210
|
import { homedir as homedir3 } from "node:os";
|
|
142098
|
-
init_runtime_paths();
|
|
142099
|
-
init_extract_json();
|
|
142100
142211
|
init_zod();
|
|
142101
142212
|
|
|
142102
142213
|
// lib/dispatch/telegram-bootstrap-session.ts
|
|
@@ -142131,14 +142242,14 @@ function buildBootstrapRequestHash(input) {
|
|
|
142131
142242
|
return buildBootstrapRequestFingerprint(input);
|
|
142132
142243
|
}
|
|
142133
142244
|
function nextSuppressUntil(status) {
|
|
142134
|
-
const ttl = status === "classifying" ? CLASSIFYING_TTL_MS : SESSION_TTL_MS;
|
|
142245
|
+
const ttl = status === "classifying" || status === "pending_classify" ? CLASSIFYING_TTL_MS : SESSION_TTL_MS;
|
|
142135
142246
|
return new Date(Date.now() + ttl).toISOString();
|
|
142136
142247
|
}
|
|
142137
142248
|
async function readTelegramBootstrapSession(workspaceDir, conversationId) {
|
|
142138
142249
|
try {
|
|
142139
142250
|
const raw = await fs38.readFile(sessionPath(workspaceDir, conversationId), "utf-8");
|
|
142140
142251
|
const session = JSON.parse(raw);
|
|
142141
|
-
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()) {
|
|
142142
142253
|
await fs38.unlink(sessionPath(workspaceDir, conversationId)).catch(() => {
|
|
142143
142254
|
});
|
|
142144
142255
|
return null;
|
|
@@ -142228,6 +142339,10 @@ var BOOTSTRAP_MESSAGES = {
|
|
|
142228
142339
|
pt: "N\xE3o consegui identificar a stack. Pode me dizer qual linguagem/framework voc\xEA quer usar? Ex: Python, Node.js, Go, Java...",
|
|
142229
142340
|
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..."
|
|
142230
142341
|
},
|
|
142342
|
+
clarifyName: {
|
|
142343
|
+
pt: "Como voc\xEA quer chamar o projeto? Se preferir, posso escolher um nome.",
|
|
142344
|
+
en: "What do you want to name the project? If you prefer, I can pick one."
|
|
142345
|
+
},
|
|
142231
142346
|
registered: {
|
|
142232
142347
|
pt: (name, link) => `Projeto "${name}" registrado.
|
|
142233
142348
|
Vou continuar o fluxo em ${link}`,
|
|
@@ -142236,7 +142351,9 @@ I'll continue the flow at ${link}`
|
|
|
142236
142351
|
}
|
|
142237
142352
|
};
|
|
142238
142353
|
function inferProjectSlug(text) {
|
|
142239
|
-
|
|
142354
|
+
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();
|
|
142355
|
+
if (!cleaned) cleaned = text;
|
|
142356
|
+
const slug = cleaned.toLowerCase().normalize("NFKD").replace(/[^\w\s-]/g, "").trim().replace(/\s+/g, "-").replace(/-+/g, "-").slice(0, 64);
|
|
142240
142357
|
return slug || void 0;
|
|
142241
142358
|
}
|
|
142242
142359
|
function normalizeText3(value) {
|
|
@@ -142309,26 +142426,31 @@ Examples:
|
|
|
142309
142426
|
- "How's the project going?" \u2192 {"intent":"other","confidence":0.95,"stackHint":null,"projectSlug":null,"language":"en"}
|
|
142310
142427
|
- "Oi, tudo bem?" \u2192 {"intent":"other","confidence":0.99,"stackHint":null,"projectSlug":null,"language":"pt"}
|
|
142311
142428
|
- "Me faz um app que converte temperaturas" \u2192 {"intent":"create_project","confidence":0.9,"stackHint":null,"projectSlug":"conversor-temperaturas","language":"pt"}`;
|
|
142312
|
-
async function classifyDmIntent(ctx, content,
|
|
142429
|
+
async function classifyDmIntent(ctx, content, _workspaceDir) {
|
|
142313
142430
|
try {
|
|
142431
|
+
const runtime = ctx.runtime;
|
|
142432
|
+
if (!runtime?.subagent?.run) return null;
|
|
142314
142433
|
const truncated = content.slice(0, MAX_CLASSIFY_LENGTH);
|
|
142315
142434
|
const prompt = CLASSIFY_PROMPT_TEMPLATE.replace("$CONTENT", truncated.replace(/"/g, '\\"'));
|
|
142316
|
-
const
|
|
142317
|
-
const
|
|
142318
|
-
|
|
142319
|
-
|
|
142320
|
-
|
|
142321
|
-
|
|
142322
|
-
|
|
142323
|
-
|
|
142324
|
-
|
|
142325
|
-
|
|
142326
|
-
|
|
142327
|
-
const
|
|
142435
|
+
const sessionKey = `dm-classify-${Date.now()}`;
|
|
142436
|
+
const { runId } = await runtime.subagent.run({
|
|
142437
|
+
sessionKey,
|
|
142438
|
+
message: prompt,
|
|
142439
|
+
extraSystemPrompt: "You are a JSON classifier. Return ONLY valid JSON, no markdown fences.",
|
|
142440
|
+
lane: "subagent",
|
|
142441
|
+
deliver: false,
|
|
142442
|
+
idempotencyKey: sessionKey
|
|
142443
|
+
});
|
|
142444
|
+
const waitResult = await runtime.subagent.waitForRun({ runId, timeoutMs: 15e3 });
|
|
142445
|
+
if (waitResult.status !== "ok") return null;
|
|
142446
|
+
const messages = await runtime.subagent.getSessionMessages({ sessionKey });
|
|
142447
|
+
const lastAssistant = messages?.filter((m2) => m2.role === "assistant").pop();
|
|
142448
|
+
const text = lastAssistant?.content ?? "";
|
|
142449
|
+
if (!text.trim()) return null;
|
|
142450
|
+
const jsonStr = text.replace(/^```(json)?/gm, "").replace(/```$/gm, "").trim();
|
|
142328
142451
|
const intentData = JSON.parse(jsonStr);
|
|
142329
142452
|
const validated = DmIntentSchema.safeParse(intentData);
|
|
142330
|
-
|
|
142331
|
-
return validated.data;
|
|
142453
|
+
return validated.success ? validated.data : null;
|
|
142332
142454
|
} catch {
|
|
142333
142455
|
return null;
|
|
142334
142456
|
}
|
|
@@ -142341,6 +142463,21 @@ function isBootstrapCandidate(text) {
|
|
|
142341
142463
|
return createCue && softwareCue;
|
|
142342
142464
|
}
|
|
142343
142465
|
function parseClarificationResponse(text, session) {
|
|
142466
|
+
if (session.pendingClarification === "name") {
|
|
142467
|
+
const trimmed = text.trim();
|
|
142468
|
+
const autoPatterns = /^(escolha|pick one|tanto faz|you choose|pode escolher|auto|skip)$/i;
|
|
142469
|
+
if (autoPatterns.test(trimmed)) {
|
|
142470
|
+
return { recognized: true, projectName: inferProjectSlug(session.rawIdea) ?? void 0, stackHint: session.stackHint ?? void 0 };
|
|
142471
|
+
}
|
|
142472
|
+
const nameField = parseField(text, ["project name", "nome do projeto", "nome", "name"]);
|
|
142473
|
+
if (nameField) {
|
|
142474
|
+
return { recognized: true, projectName: nameField, stackHint: session.stackHint ?? void 0 };
|
|
142475
|
+
}
|
|
142476
|
+
if (trimmed.length > 0 && trimmed.length <= 64) {
|
|
142477
|
+
return { recognized: true, projectName: trimmed, stackHint: session.stackHint ?? void 0 };
|
|
142478
|
+
}
|
|
142479
|
+
return { recognized: false };
|
|
142480
|
+
}
|
|
142344
142481
|
const stackField = parseField(text, ["stack", "framework", "linguagem", "language"]);
|
|
142345
142482
|
if (stackField) {
|
|
142346
142483
|
return { recognized: true, stackHint: detectStackHint(stackField) ?? stackField };
|
|
@@ -142376,6 +142513,9 @@ function parseClarificationResponse(text, session) {
|
|
|
142376
142513
|
return { recognized: false };
|
|
142377
142514
|
}
|
|
142378
142515
|
function buildClarificationMessage(parsed, pendingClarification, language = "pt") {
|
|
142516
|
+
if (pendingClarification === "name") {
|
|
142517
|
+
return BOOTSTRAP_MESSAGES.clarifyName[language];
|
|
142518
|
+
}
|
|
142379
142519
|
if (pendingClarification === "stack_and_name" || !parsed.stackHint && !parsed.projectName) {
|
|
142380
142520
|
return BOOTSTRAP_MESSAGES.clarifyBoth[language];
|
|
142381
142521
|
}
|
|
@@ -142421,6 +142561,12 @@ function logBootstrapWarning(ctx, message) {
|
|
|
142421
142561
|
}
|
|
142422
142562
|
}
|
|
142423
142563
|
async function classifyAndBootstrap(ctx, workspaceDir, conversationId, content) {
|
|
142564
|
+
await upsertTelegramBootstrapSession(workspaceDir, {
|
|
142565
|
+
conversationId,
|
|
142566
|
+
rawIdea: content,
|
|
142567
|
+
sourceRoute: { channel: "telegram", channelId: conversationId },
|
|
142568
|
+
status: "classifying"
|
|
142569
|
+
});
|
|
142424
142570
|
const classification = await classifyDmIntent(ctx, content, workspaceDir);
|
|
142425
142571
|
if (!classification || classification.intent !== "create_project" || classification.confidence < 0.7) {
|
|
142426
142572
|
if (!classification) {
|
|
@@ -142530,6 +142676,28 @@ async function continueBootstrap(ctx, conversationId, workspaceDir, request2, so
|
|
|
142530
142676
|
));
|
|
142531
142677
|
return;
|
|
142532
142678
|
}
|
|
142679
|
+
if (!projectName) {
|
|
142680
|
+
const existingSession = await readTelegramBootstrapSession(workspaceDir, conversationId);
|
|
142681
|
+
const lang = existingSession?.language ?? "pt";
|
|
142682
|
+
await upsertTelegramBootstrapSession(workspaceDir, {
|
|
142683
|
+
conversationId,
|
|
142684
|
+
rawIdea: request2.rawIdea,
|
|
142685
|
+
stackHint: request2.stackHint ?? void 0,
|
|
142686
|
+
status: "clarifying",
|
|
142687
|
+
pendingClarification: "name",
|
|
142688
|
+
language: lang
|
|
142689
|
+
});
|
|
142690
|
+
await sendTelegramText(
|
|
142691
|
+
ctx,
|
|
142692
|
+
conversationId,
|
|
142693
|
+
buildClarificationMessage(
|
|
142694
|
+
{ rawIdea: request2.rawIdea, projectName: void 0, stackHint: request2.stackHint ?? void 0 },
|
|
142695
|
+
"name",
|
|
142696
|
+
lang
|
|
142697
|
+
)
|
|
142698
|
+
);
|
|
142699
|
+
return;
|
|
142700
|
+
}
|
|
142533
142701
|
const candidateSlug = inferProjectSlug(projectName ?? request2.rawIdea);
|
|
142534
142702
|
if (candidateSlug) {
|
|
142535
142703
|
const projects = await readProjects(workspaceDir).catch(() => null);
|
|
@@ -142759,7 +142927,7 @@ function registerTelegramBootstrapHook(api, ctx) {
|
|
|
142759
142927
|
}
|
|
142760
142928
|
const existingSession = await readTelegramBootstrapSession(workspaceDir, conversationId);
|
|
142761
142929
|
const sessionIsExpired = existingSession != null && Date.parse(existingSession.suppressUntil) < Date.now();
|
|
142762
|
-
if (existingSession && !sessionIsExpired && existingSession.status === "classifying") {
|
|
142930
|
+
if (existingSession && !sessionIsExpired && (existingSession.status === "classifying" || existingSession.status === "pending_classify")) {
|
|
142763
142931
|
ctx.logger.info(`[telegram-bootstrap] LLM classification in progress for ${conversationId}, ignoring concurrent message`);
|
|
142764
142932
|
return;
|
|
142765
142933
|
}
|
|
@@ -142790,7 +142958,7 @@ function registerTelegramBootstrapHook(api, ctx) {
|
|
|
142790
142958
|
conversationId,
|
|
142791
142959
|
rawIdea: content,
|
|
142792
142960
|
sourceRoute: { channel: "telegram", channelId: conversationId },
|
|
142793
|
-
status: "
|
|
142961
|
+
status: "pending_classify"
|
|
142794
142962
|
});
|
|
142795
142963
|
classifyAndBootstrap(ctx, workspaceDir, conversationId, content).catch((err) => {
|
|
142796
142964
|
logBootstrapWarning(ctx, `[telegram-bootstrap] LLM classify error: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -142799,6 +142967,12 @@ function registerTelegramBootstrapHook(api, ctx) {
|
|
|
142799
142967
|
return;
|
|
142800
142968
|
}
|
|
142801
142969
|
const parsed = parseBootstrapRequest(content);
|
|
142970
|
+
if (!parsed.projectName && ctx.runtime?.subagent?.run) {
|
|
142971
|
+
const classification = await classifyDmIntent(ctx, content, workspaceDir);
|
|
142972
|
+
if (classification?.projectSlug) {
|
|
142973
|
+
parsed.projectName = classification.projectSlug;
|
|
142974
|
+
}
|
|
142975
|
+
}
|
|
142802
142976
|
const incomingRequest = {
|
|
142803
142977
|
rawIdea: parsed.rawIdea,
|
|
142804
142978
|
projectName: parsed.projectName ?? null,
|