@mestreyoda/fabrica 0.2.0 → 0.2.1
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 +123 -37
- package/dist/index.js.map +2 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -111324,8 +111324,8 @@ import fsSync from "node:fs";
|
|
|
111324
111324
|
import path5 from "node:path";
|
|
111325
111325
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
111326
111326
|
function getCurrentVersion() {
|
|
111327
|
-
if ("0.2.
|
|
111328
|
-
return "0.2.
|
|
111327
|
+
if ("0.2.1") {
|
|
111328
|
+
return "0.2.1";
|
|
111329
111329
|
}
|
|
111330
111330
|
try {
|
|
111331
111331
|
const pkgPath = path5.join(THIS_DIR, "..", "..", "package.json");
|
|
@@ -112302,7 +112302,7 @@ function resolve2(config2, sourceLayers, trace2) {
|
|
|
112302
112302
|
dispatchMs: config2.timeouts?.dispatchMs ?? 6e5,
|
|
112303
112303
|
staleWorkerHours: config2.timeouts?.staleWorkerHours ?? 2,
|
|
112304
112304
|
sessionContextBudget: config2.timeouts?.sessionContextBudget ?? 0.6,
|
|
112305
|
-
stallTimeoutMinutes: config2.timeouts?.stallTimeoutMinutes ??
|
|
112305
|
+
stallTimeoutMinutes: config2.timeouts?.stallTimeoutMinutes ?? 5,
|
|
112306
112306
|
sessionConfirmAttempts: config2.timeouts?.sessionConfirmAttempts ?? 5,
|
|
112307
112307
|
sessionConfirmDelayMs: config2.timeouts?.sessionConfirmDelayMs ?? 250,
|
|
112308
112308
|
sessionLabelMaxLength: config2.timeouts?.sessionLabelMaxLength ?? 64,
|
|
@@ -124579,6 +124579,7 @@ import { jsonResult as jsonResult15 } from "openclaw/plugin-sdk";
|
|
|
124579
124579
|
init_audit();
|
|
124580
124580
|
|
|
124581
124581
|
// lib/services/heartbeat/diagnostic.ts
|
|
124582
|
+
import { execFileSync } from "child_process";
|
|
124582
124583
|
async function diagnoseStall(input) {
|
|
124583
124584
|
const { owner, repo, issueId, dispatchAttemptCount } = input;
|
|
124584
124585
|
if ((dispatchAttemptCount ?? 0) >= 2) {
|
|
@@ -124591,9 +124592,9 @@ async function diagnoseStall(input) {
|
|
|
124591
124592
|
if (pr) {
|
|
124592
124593
|
const qaStatus = await checkPrQaStatus(owner, repo, pr.number);
|
|
124593
124594
|
if (qaStatus === "pass") {
|
|
124594
|
-
return { action: "transition_to_review", reason: "stall", evidence: `PR #${pr.number} exists, QA passing
|
|
124595
|
+
return { action: "transition_to_review", reason: "stall", evidence: `PR #${pr.number} exists, QA passing`, prNumber: pr.number };
|
|
124595
124596
|
}
|
|
124596
|
-
return { action: "redispatch_same_level", reason: "stall", evidence: `PR #${pr.number} exists, QA ${qaStatus}
|
|
124597
|
+
return { action: "redispatch_same_level", reason: "stall", evidence: `PR #${pr.number} exists, QA ${qaStatus}`, prNumber: pr.number };
|
|
124597
124598
|
}
|
|
124598
124599
|
const hasCommits = await checkForBranchCommits(owner, repo, issueId);
|
|
124599
124600
|
if (hasCommits) {
|
|
@@ -124606,10 +124607,13 @@ async function diagnoseStall(input) {
|
|
|
124606
124607
|
}
|
|
124607
124608
|
return { action: "escalate_level", reason: "complexity", evidence: "Active session without commits" };
|
|
124608
124609
|
}
|
|
124610
|
+
function ghSync(args) {
|
|
124611
|
+
return execFileSync("gh", args, { encoding: "utf-8", timeout: 15e3 });
|
|
124612
|
+
}
|
|
124609
124613
|
async function findPrForIssue(owner, repo, issueId) {
|
|
124614
|
+
if (!owner || !repo) return null;
|
|
124610
124615
|
try {
|
|
124611
|
-
const
|
|
124612
|
-
const { stdout } = await execa("gh", [
|
|
124616
|
+
const stdout = ghSync([
|
|
124613
124617
|
"pr",
|
|
124614
124618
|
"list",
|
|
124615
124619
|
"--repo",
|
|
@@ -124628,29 +124632,31 @@ async function findPrForIssue(owner, repo, issueId) {
|
|
|
124628
124632
|
}
|
|
124629
124633
|
}
|
|
124630
124634
|
async function checkPrQaStatus(owner, repo, prNumber) {
|
|
124635
|
+
if (!owner || !repo) return "pending";
|
|
124631
124636
|
try {
|
|
124632
|
-
const
|
|
124633
|
-
const { stdout } = await execa("gh", [
|
|
124637
|
+
const stdout = ghSync([
|
|
124634
124638
|
"pr",
|
|
124635
|
-
"
|
|
124639
|
+
"view",
|
|
124636
124640
|
String(prNumber),
|
|
124637
124641
|
"--repo",
|
|
124638
124642
|
`${owner}/${repo}`,
|
|
124639
124643
|
"--json",
|
|
124640
|
-
"
|
|
124644
|
+
"statusCheckRollup"
|
|
124641
124645
|
]);
|
|
124642
|
-
const
|
|
124643
|
-
|
|
124644
|
-
if (checks.
|
|
124646
|
+
const data = JSON.parse(stdout);
|
|
124647
|
+
const checks = data.statusCheckRollup ?? [];
|
|
124648
|
+
if (checks.length === 0) return "pass";
|
|
124649
|
+
if (checks.every((c) => c.conclusion === "SUCCESS")) return "pass";
|
|
124650
|
+
if (checks.some((c) => c.conclusion === "FAILURE")) return "fail";
|
|
124645
124651
|
return "pending";
|
|
124646
124652
|
} catch {
|
|
124647
|
-
return "
|
|
124653
|
+
return "pass";
|
|
124648
124654
|
}
|
|
124649
124655
|
}
|
|
124650
124656
|
async function checkForBranchCommits(owner, repo, issueId) {
|
|
124657
|
+
if (!owner || !repo) return false;
|
|
124651
124658
|
try {
|
|
124652
|
-
const
|
|
124653
|
-
const { stdout } = await execa("gh", [
|
|
124659
|
+
const stdout = ghSync([
|
|
124654
124660
|
"api",
|
|
124655
124661
|
`repos/${owner}/${repo}/commits`,
|
|
124656
124662
|
"--jq",
|
|
@@ -124678,6 +124684,7 @@ var GRACE_PERIOD_MS = 5 * 60 * 1e3;
|
|
|
124678
124684
|
var DISPATCH_CONFIRMATION_TIMEOUT_MS = 2 * 60 * 1e3;
|
|
124679
124685
|
var NUDGE_MESSAGE = `You appear to have stalled. Continue working on your current task. If you are blocked or unable to proceed, call work_finish with result "blocked".`;
|
|
124680
124686
|
var MAX_STALL_NUDGES = 3;
|
|
124687
|
+
var MAX_DISPATCH_ATTEMPTS = 5;
|
|
124681
124688
|
async function auditHealthFixApplied(workspaceDir, fix, details) {
|
|
124682
124689
|
if (!fix.fixed) return;
|
|
124683
124690
|
await withCorrelationContext({
|
|
@@ -125045,30 +125052,84 @@ async function checkWorkerHealth(opts) {
|
|
|
125045
125052
|
}
|
|
125046
125053
|
if (slot.active && sessionKey && sessions && !withinGracePeriod && isSessionAlive(sessionKey, sessions)) {
|
|
125047
125054
|
const session = sessions.get(sessionKey);
|
|
125048
|
-
const stallThresholdMs = (opts.stallTimeoutMinutes ??
|
|
125055
|
+
const stallThresholdMs = (opts.stallTimeoutMinutes ?? 5) * 6e4;
|
|
125049
125056
|
if (session.updatedAt == null) continue;
|
|
125050
125057
|
const sessionIdleMs = Date.now() - session.updatedAt;
|
|
125051
125058
|
if (sessionIdleMs > stallThresholdMs) {
|
|
125052
125059
|
const idleMinutes = Math.round(sessionIdleMs / 6e4);
|
|
125060
|
+
const repoRemote = project.repoRemote ?? "";
|
|
125061
|
+
const remoteMatch = repoRemote.match(/github\.com\/([^/]+)\/([^/.]+)/);
|
|
125062
|
+
const diagnostic = await diagnoseStall({
|
|
125063
|
+
projectSlug,
|
|
125064
|
+
owner: remoteMatch?.[1] ?? "",
|
|
125065
|
+
repo: remoteMatch?.[2] ?? "",
|
|
125066
|
+
issueId: issueIdNum ?? 0,
|
|
125067
|
+
sessionKey,
|
|
125068
|
+
slotStartTime: slot.startTime ? new Date(slot.startTime).getTime() : Date.now() - sessionIdleMs,
|
|
125069
|
+
sessionUpdatedAt: session.updatedAt,
|
|
125070
|
+
dispatchAttemptCount: issueRuntime?.dispatchAttemptCount ?? 0
|
|
125071
|
+
});
|
|
125072
|
+
const evidenceActions = ["transition_to_review", "nudge_open_pr"];
|
|
125073
|
+
const hasActionableEvidence = evidenceActions.includes(diagnostic.action);
|
|
125053
125074
|
const modelUnresponsiveMs = stallThresholdMs * MAX_STALL_NUDGES;
|
|
125054
125075
|
const slotAgeMs = slot.startTime ? Date.now() - new Date(slot.startTime).getTime() : sessionIdleMs;
|
|
125055
|
-
|
|
125056
|
-
|
|
125076
|
+
const isModelUnresponsive = slotAgeMs > modelUnresponsiveMs;
|
|
125077
|
+
const currentAttempts = issueRuntime?.dispatchAttemptCount ?? 0;
|
|
125078
|
+
if (currentAttempts >= MAX_DISPATCH_ATTEMPTS && !hasActionableEvidence) {
|
|
125079
|
+
const fix2 = {
|
|
125080
|
+
issue: {
|
|
125081
|
+
type: "diagnostic_needs_human_review",
|
|
125082
|
+
severity: "critical",
|
|
125083
|
+
project: project.name,
|
|
125084
|
+
projectSlug,
|
|
125085
|
+
role,
|
|
125086
|
+
level,
|
|
125087
|
+
sessionKey,
|
|
125088
|
+
issueId: slot.issueId,
|
|
125089
|
+
slotIndex,
|
|
125090
|
+
message: `${role.toUpperCase()} ${level}[${slotIndex}] circuit breaker: ${currentAttempts} dispatch attempts without completion \u2014 moving to Refining`
|
|
125091
|
+
},
|
|
125092
|
+
fixed: false
|
|
125093
|
+
};
|
|
125094
|
+
if (autoFix) {
|
|
125095
|
+
await revertLabel(fix2, expectedLabel, "Refining");
|
|
125096
|
+
await deactivateSlot();
|
|
125097
|
+
fix2.fixed = true;
|
|
125098
|
+
}
|
|
125099
|
+
await log(workspaceDir, "dispatch_circuit_breaker", {
|
|
125100
|
+
project: project.name,
|
|
125057
125101
|
projectSlug,
|
|
125058
|
-
|
|
125059
|
-
|
|
125060
|
-
issueId: issueIdNum ?? 0,
|
|
125102
|
+
role,
|
|
125103
|
+
level,
|
|
125061
125104
|
sessionKey,
|
|
125062
|
-
|
|
125063
|
-
|
|
125064
|
-
dispatchAttemptCount:
|
|
125105
|
+
issueId: slot.issueId,
|
|
125106
|
+
slotIndex,
|
|
125107
|
+
dispatchAttemptCount: currentAttempts,
|
|
125108
|
+
diagnostic: diagnostic.action,
|
|
125109
|
+
evidence: diagnostic.evidence
|
|
125110
|
+
}).catch(() => {
|
|
125065
125111
|
});
|
|
125112
|
+
fixes.push(fix2);
|
|
125113
|
+
continue;
|
|
125114
|
+
}
|
|
125115
|
+
if (hasActionableEvidence || isModelUnresponsive) {
|
|
125066
125116
|
if (issueIdNum) {
|
|
125067
|
-
|
|
125117
|
+
const runtimeUpdate = {
|
|
125068
125118
|
lastDiagnosticResult: diagnostic.evidence,
|
|
125069
125119
|
lastFailureReason: diagnostic.reason,
|
|
125070
125120
|
dispatchAttemptCount: (issueRuntime?.dispatchAttemptCount ?? 0) + 1
|
|
125071
|
-
}
|
|
125121
|
+
};
|
|
125122
|
+
if (diagnostic.prNumber) {
|
|
125123
|
+
const prOwner = remoteMatch?.[1] ?? "";
|
|
125124
|
+
const prRepo = remoteMatch?.[2] ?? "";
|
|
125125
|
+
runtimeUpdate.currentPrNumber = diagnostic.prNumber;
|
|
125126
|
+
runtimeUpdate.currentPrUrl = `https://github.com/${prOwner}/${prRepo}/pull/${diagnostic.prNumber}`;
|
|
125127
|
+
runtimeUpdate.currentPrState = "open";
|
|
125128
|
+
runtimeUpdate.bindingSource = "diagnostic";
|
|
125129
|
+
runtimeUpdate.bindingConfidence = "high";
|
|
125130
|
+
runtimeUpdate.boundAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
125131
|
+
}
|
|
125132
|
+
await updateIssueRuntime(workspaceDir, projectSlug, String(issueIdNum), runtimeUpdate).catch(() => {
|
|
125072
125133
|
});
|
|
125073
125134
|
}
|
|
125074
125135
|
const diagnosticType = `diagnostic_${diagnostic.action}`;
|
|
@@ -125089,13 +125150,19 @@ async function checkWorkerHealth(opts) {
|
|
|
125089
125150
|
};
|
|
125090
125151
|
if (autoFix) {
|
|
125091
125152
|
switch (diagnostic.action) {
|
|
125092
|
-
case "transition_to_review":
|
|
125093
|
-
|
|
125153
|
+
case "transition_to_review": {
|
|
125154
|
+
const targetLabel = role === "tester" ? "Done" : role === "reviewer" ? "To Test" : "To Review";
|
|
125155
|
+
await revertLabel(fix2, expectedLabel, targetLabel);
|
|
125094
125156
|
if (!fix2.labelRevertFailed) {
|
|
125095
125157
|
await deactivateSlot();
|
|
125158
|
+
if (role === "tester" && issueIdNum) {
|
|
125159
|
+
await provider.closeIssue(issueIdNum).catch(() => {
|
|
125160
|
+
});
|
|
125161
|
+
}
|
|
125096
125162
|
fix2.fixed = true;
|
|
125097
125163
|
}
|
|
125098
125164
|
break;
|
|
125165
|
+
}
|
|
125099
125166
|
case "redispatch_same_level":
|
|
125100
125167
|
case "nudge_open_pr":
|
|
125101
125168
|
case "retry_infra":
|
|
@@ -125113,10 +125180,12 @@ async function checkWorkerHealth(opts) {
|
|
|
125113
125180
|
}
|
|
125114
125181
|
break;
|
|
125115
125182
|
case "needs_human_review":
|
|
125183
|
+
await revertLabel(fix2, expectedLabel, "Refining");
|
|
125116
125184
|
await deactivateSlot();
|
|
125117
125185
|
fix2.fixed = true;
|
|
125118
125186
|
break;
|
|
125119
125187
|
case "log_infra":
|
|
125188
|
+
await revertLabel(fix2, expectedLabel, "Refining");
|
|
125120
125189
|
await deactivateSlot();
|
|
125121
125190
|
fix2.fixed = true;
|
|
125122
125191
|
break;
|
|
@@ -125134,6 +125203,7 @@ async function checkWorkerHealth(opts) {
|
|
|
125134
125203
|
diagnostic: diagnostic.action,
|
|
125135
125204
|
reason: diagnostic.reason,
|
|
125136
125205
|
evidence: diagnostic.evidence,
|
|
125206
|
+
fastPath: hasActionableEvidence,
|
|
125137
125207
|
dispatchAttemptCount: (issueRuntime?.dispatchAttemptCount ?? 0) + 1
|
|
125138
125208
|
}).catch(() => {
|
|
125139
125209
|
});
|
|
@@ -125151,7 +125221,7 @@ async function checkWorkerHealth(opts) {
|
|
|
125151
125221
|
sessionKey,
|
|
125152
125222
|
issueId: slot.issueId,
|
|
125153
125223
|
slotIndex,
|
|
125154
|
-
message: `${role.toUpperCase()} ${level}[${slotIndex}] session idle ${idleMinutes}m \u2014 sending nudge`
|
|
125224
|
+
message: `${role.toUpperCase()} ${level}[${slotIndex}] session idle ${idleMinutes}m \u2014 no deliverables yet, sending nudge`
|
|
125155
125225
|
},
|
|
125156
125226
|
fixed: false
|
|
125157
125227
|
};
|
|
@@ -138727,16 +138797,32 @@ async function classifyDmIntent(ctx, content, _workspaceDir) {
|
|
|
138727
138797
|
idempotencyKey: sessionKey
|
|
138728
138798
|
});
|
|
138729
138799
|
const waitResult = await runtime.subagent.waitForRun({ runId, timeoutMs: 15e3 });
|
|
138730
|
-
if (waitResult.status !== "ok")
|
|
138731
|
-
|
|
138732
|
-
|
|
138733
|
-
|
|
138734
|
-
|
|
138800
|
+
if (waitResult.status !== "ok") {
|
|
138801
|
+
ctx.logger.warn(`[telegram-bootstrap] classify waitForRun status=${waitResult.status} (expected ok)`);
|
|
138802
|
+
return null;
|
|
138803
|
+
}
|
|
138804
|
+
const messagesResult = await runtime.subagent.getSessionMessages({ sessionKey });
|
|
138805
|
+
const messages = Array.isArray(messagesResult) ? messagesResult : Array.isArray(messagesResult?.messages) ? messagesResult.messages : [];
|
|
138806
|
+
const lastAssistant = messages.filter((m2) => m2.role === "assistant").pop();
|
|
138807
|
+
if (!lastAssistant) {
|
|
138808
|
+
ctx.logger.warn(`[telegram-bootstrap] classify: no assistant message found (messages=${messages.length}, resultType=${typeof messagesResult})`);
|
|
138809
|
+
return null;
|
|
138810
|
+
}
|
|
138811
|
+
const rawContent = lastAssistant.content;
|
|
138812
|
+
const text = typeof rawContent === "string" ? rawContent : Array.isArray(rawContent) ? rawContent.find((b) => b.type === "text")?.text ?? "" : "";
|
|
138813
|
+
if (!text.trim()) {
|
|
138814
|
+
ctx.logger.warn(`[telegram-bootstrap] classify: empty text from assistant (contentType=${typeof rawContent}, isArray=${Array.isArray(rawContent)})`);
|
|
138815
|
+
return null;
|
|
138816
|
+
}
|
|
138735
138817
|
const jsonStr = text.replace(/^```(json)?/gm, "").replace(/```$/gm, "").trim();
|
|
138736
138818
|
const intentData = JSON.parse(jsonStr);
|
|
138737
138819
|
const validated = DmIntentSchema.safeParse(intentData);
|
|
138820
|
+
if (!validated.success) {
|
|
138821
|
+
ctx.logger.warn(`[telegram-bootstrap] classify: schema validation failed: ${validated.error?.message}`);
|
|
138822
|
+
}
|
|
138738
138823
|
return validated.success ? validated.data : null;
|
|
138739
|
-
} catch {
|
|
138824
|
+
} catch (err) {
|
|
138825
|
+
ctx.logger.warn(`[telegram-bootstrap] classify exception: ${err.message ?? err}`);
|
|
138740
138826
|
return null;
|
|
138741
138827
|
}
|
|
138742
138828
|
}
|