@mestreyoda/fabrica 0.1.10 → 0.1.12
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 +190 -89
- package/dist/index.js.map +4 -4
- package/genesis/scripts/scaffold-project.sh +32 -9
- 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.12") {
|
|
111333
|
+
return "0.1.12";
|
|
111334
111334
|
}
|
|
111335
111335
|
try {
|
|
111336
111336
|
const pkgPath = path5.join(THIS_DIR, "..", "..", "package.json");
|
|
@@ -112573,7 +112573,7 @@ async function resilientLabelTransition(provider, issueId, from, to, log3) {
|
|
|
112573
112573
|
for (let i2 = 0; i2 < 2; i2++) {
|
|
112574
112574
|
try {
|
|
112575
112575
|
await provider.removeLabels(issueId, [from]);
|
|
112576
|
-
log3?.(`
|
|
112576
|
+
log3?.(`dual_state_recovery: removed ${from} from issue ${issueId} (atomic PUT should have prevented this \u2014 investigate)`);
|
|
112577
112577
|
return { success: true, dualStateResolved: true };
|
|
112578
112578
|
} catch (retryErr) {
|
|
112579
112579
|
log3?.(`Retry ${i2 + 1}/2 to remove ${from} failed: ${String(retryErr)}`);
|
|
@@ -114240,7 +114240,7 @@ var require_Policy = __commonJS({
|
|
|
114240
114240
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
114241
114241
|
exports2.handleAll = exports2.noop = exports2.Policy = void 0;
|
|
114242
114242
|
exports2.handleType = handleType;
|
|
114243
|
-
exports2.handleWhen =
|
|
114243
|
+
exports2.handleWhen = handleWhen2;
|
|
114244
114244
|
exports2.handleResultType = handleResultType;
|
|
114245
114245
|
exports2.handleWhenResult = handleWhenResult;
|
|
114246
114246
|
exports2.bulkhead = bulkhead;
|
|
@@ -114367,7 +114367,7 @@ var require_Policy = __commonJS({
|
|
|
114367
114367
|
function handleType(cls, predicate) {
|
|
114368
114368
|
return new Policy({ errorFilter: typeFilter(cls, predicate), resultFilter: never2 });
|
|
114369
114369
|
}
|
|
114370
|
-
function
|
|
114370
|
+
function handleWhen2(predicate) {
|
|
114371
114371
|
return new Policy({ errorFilter: predicate, resultFilter: never2 });
|
|
114372
114372
|
}
|
|
114373
114373
|
function handleResultType(cls, predicate) {
|
|
@@ -117174,15 +117174,23 @@ init_logger();
|
|
|
117174
117174
|
|
|
117175
117175
|
// lib/providers/resilience.ts
|
|
117176
117176
|
var import_cockatiel = __toESM(require_dist4(), 1);
|
|
117177
|
+
var GitHubRateLimitError = class extends Error {
|
|
117178
|
+
constructor(retryAfterMs) {
|
|
117179
|
+
super(`GitHub rate limit \u2014 retry after ${retryAfterMs}ms`);
|
|
117180
|
+
this.retryAfterMs = retryAfterMs;
|
|
117181
|
+
this.name = "GitHubRateLimitError";
|
|
117182
|
+
}
|
|
117183
|
+
};
|
|
117177
117184
|
var MAX_ENTRIES = 50;
|
|
117178
117185
|
var policyCache = /* @__PURE__ */ new Map();
|
|
117179
117186
|
var accessOrder = [];
|
|
117180
117187
|
function createPolicy() {
|
|
117181
|
-
const
|
|
117188
|
+
const retryableErrors = (0, import_cockatiel.handleWhen)((err) => !(err instanceof GitHubRateLimitError));
|
|
117189
|
+
const retryPolicy = (0, import_cockatiel.retry)(retryableErrors, {
|
|
117182
117190
|
maxAttempts: 3,
|
|
117183
117191
|
backoff: new import_cockatiel.ExponentialBackoff({
|
|
117184
117192
|
initialDelay: 500,
|
|
117185
|
-
maxDelay:
|
|
117193
|
+
maxDelay: 1e4
|
|
117186
117194
|
})
|
|
117187
117195
|
});
|
|
117188
117196
|
const breakerPolicy = (0, import_cockatiel.circuitBreaker)(import_cockatiel.handleAll, {
|
|
@@ -118097,6 +118105,35 @@ var GitHubProvider = class {
|
|
|
118097
118105
|
async gh(args) {
|
|
118098
118106
|
return this.ghAt(args, { cwd: this.repoPath });
|
|
118099
118107
|
}
|
|
118108
|
+
get providerKey() {
|
|
118109
|
+
return this.repoPath;
|
|
118110
|
+
}
|
|
118111
|
+
/**
|
|
118112
|
+
* Execute a `gh api` call with optional JSON body sent via stdin.
|
|
118113
|
+
* Uses withResilience (per-provider retry + circuit breaker).
|
|
118114
|
+
* Throws GitHubRateLimitError on 429 / rate limit responses.
|
|
118115
|
+
*/
|
|
118116
|
+
async ghApi(endpoint2, method, body) {
|
|
118117
|
+
const args = ["api", endpoint2, "--method", method];
|
|
118118
|
+
if (body !== void 0) {
|
|
118119
|
+
args.push("--input", "-");
|
|
118120
|
+
}
|
|
118121
|
+
return withResilience(async () => {
|
|
118122
|
+
const result = await this.runCommand(["gh", ...args], {
|
|
118123
|
+
timeoutMs: 3e4,
|
|
118124
|
+
cwd: this.repoPath,
|
|
118125
|
+
input: body !== void 0 ? JSON.stringify(body) : void 0
|
|
118126
|
+
});
|
|
118127
|
+
if (result.code != null && result.code !== 0) {
|
|
118128
|
+
const errText = result.stderr?.trim() ?? "";
|
|
118129
|
+
if (errText.includes("rate limit") || errText.includes("429")) {
|
|
118130
|
+
throw new GitHubRateLimitError(6e4);
|
|
118131
|
+
}
|
|
118132
|
+
throw new Error(errText || `gh api ${method} ${endpoint2} failed with exit code ${result.code}`);
|
|
118133
|
+
}
|
|
118134
|
+
return result.stdout.trim();
|
|
118135
|
+
}, this.providerKey);
|
|
118136
|
+
}
|
|
118100
118137
|
async git(args, opts) {
|
|
118101
118138
|
const result = await this.runCommand(["git", ...args], {
|
|
118102
118139
|
timeoutMs: opts?.timeoutMs ?? 3e4,
|
|
@@ -118152,14 +118189,28 @@ var GitHubProvider = class {
|
|
|
118152
118189
|
return Buffer.from(input).toString("base64url");
|
|
118153
118190
|
}
|
|
118154
118191
|
async githubFetch(url2, init, auth7) {
|
|
118155
|
-
|
|
118156
|
-
|
|
118157
|
-
|
|
118158
|
-
|
|
118159
|
-
|
|
118160
|
-
headers.
|
|
118161
|
-
|
|
118162
|
-
|
|
118192
|
+
return withResilience(async () => {
|
|
118193
|
+
const headers = new Headers(init.headers ?? {});
|
|
118194
|
+
headers.set("Accept", "application/vnd.github+json");
|
|
118195
|
+
headers.set("Authorization", `Bearer ${auth7.token}`);
|
|
118196
|
+
headers.set("X-GitHub-Api-Version", "2022-11-28");
|
|
118197
|
+
if (init.body && !headers.has("Content-Type")) {
|
|
118198
|
+
headers.set("Content-Type", "application/json");
|
|
118199
|
+
}
|
|
118200
|
+
const res = await fetch(url2, { ...init, headers });
|
|
118201
|
+
const remaining = res.headers.get("x-ratelimit-remaining");
|
|
118202
|
+
if (res.status === 429 || res.status === 403 && remaining === "0") {
|
|
118203
|
+
const retryAfter = res.headers.get("retry-after");
|
|
118204
|
+
const resetEpoch = res.headers.get("x-ratelimit-reset");
|
|
118205
|
+
const waitMs = retryAfter ? parseInt(retryAfter, 10) * 1e3 : resetEpoch ? Math.max(0, parseInt(resetEpoch, 10) * 1e3 - Date.now()) + 1e3 : 6e4;
|
|
118206
|
+
throw new GitHubRateLimitError(waitMs);
|
|
118207
|
+
}
|
|
118208
|
+
if (!res.ok) {
|
|
118209
|
+
const body = await res.text().catch(() => "");
|
|
118210
|
+
throw new Error(`GitHub API ${res.status}: ${body}`);
|
|
118211
|
+
}
|
|
118212
|
+
return res;
|
|
118213
|
+
}, this.providerKey);
|
|
118163
118214
|
}
|
|
118164
118215
|
async resolveInstallationAuth() {
|
|
118165
118216
|
const profile = this.resolveAuthProfile();
|
|
@@ -118557,7 +118608,7 @@ Bootstrapped by Fabrica.
|
|
|
118557
118608
|
}
|
|
118558
118609
|
async listIssuesByLabel(label) {
|
|
118559
118610
|
try {
|
|
118560
|
-
const raw = await this.gh(["issue", "list", "--label", label, "--state", "open", "--json", "number,title,body,labels,state,url"]);
|
|
118611
|
+
const raw = await this.gh(["issue", "list", "--label", label, "--state", "open", "-L", "200", "--json", "number,title,body,labels,state,url"]);
|
|
118561
118612
|
return JSON.parse(raw).map(toIssue);
|
|
118562
118613
|
} catch {
|
|
118563
118614
|
return [];
|
|
@@ -118565,7 +118616,7 @@ Bootstrapped by Fabrica.
|
|
|
118565
118616
|
}
|
|
118566
118617
|
async listIssues(opts) {
|
|
118567
118618
|
try {
|
|
118568
|
-
const args = ["issue", "list", "--state", opts?.state ?? "open", "--json", "number,title,body,labels,state,url"];
|
|
118619
|
+
const args = ["issue", "list", "--state", opts?.state ?? "open", "-L", "200", "--json", "number,title,body,labels,state,url"];
|
|
118569
118620
|
if (opts?.label) args.push("--label", opts.label);
|
|
118570
118621
|
const raw = await this.gh(args);
|
|
118571
118622
|
return JSON.parse(raw).map(toIssue);
|
|
@@ -118587,29 +118638,20 @@ Bootstrapped by Fabrica.
|
|
|
118587
118638
|
}
|
|
118588
118639
|
}
|
|
118589
118640
|
async transitionLabel(issueId, from, to) {
|
|
118590
|
-
await this.gh(["issue", "edit", String(issueId), "--add-label", to]);
|
|
118591
118641
|
const issue2 = await this.getIssue(issueId);
|
|
118592
118642
|
const stateLabels = getStateLabels(this.workflow);
|
|
118593
|
-
const
|
|
118594
|
-
|
|
118595
|
-
|
|
118596
|
-
);
|
|
118597
|
-
if (currentStateLabels.length > 0 || staleOperationalLabels.length > 0) {
|
|
118598
|
-
const args = ["issue", "edit", String(issueId)];
|
|
118599
|
-
for (const l of currentStateLabels) args.push("--remove-label", l);
|
|
118600
|
-
for (const l of staleOperationalLabels) args.push("--remove-label", l);
|
|
118601
|
-
await this.gh(args);
|
|
118602
|
-
}
|
|
118643
|
+
const desired = issue2.labels.filter(
|
|
118644
|
+
(l) => !stateLabels.includes(l) && !LEGACY_OPERATIONAL_LABELS.includes(l)
|
|
118645
|
+
).concat(to);
|
|
118646
|
+
await this.ghApi(`repos/{owner}/{repo}/issues/${issueId}/labels`, "PUT", { labels: desired });
|
|
118603
118647
|
try {
|
|
118604
118648
|
const postIssue = await this.getIssue(issueId);
|
|
118605
118649
|
const postStateLabels = postIssue.labels.filter((l) => stateLabels.includes(l));
|
|
118606
118650
|
if (postStateLabels.length !== 1 || !postStateLabels.includes(to)) {
|
|
118607
|
-
logger3.error(
|
|
118608
|
-
issueId,
|
|
118609
|
-
|
|
118610
|
-
|
|
118611
|
-
postStateLabels
|
|
118612
|
-
}, "State transition anomaly detected after GitHub issue label transition");
|
|
118651
|
+
logger3.error(
|
|
118652
|
+
{ issueId, from, to, postStateLabels },
|
|
118653
|
+
"State transition anomaly detected after atomic label PUT"
|
|
118654
|
+
);
|
|
118613
118655
|
}
|
|
118614
118656
|
} catch {
|
|
118615
118657
|
}
|
|
@@ -120056,7 +120098,11 @@ async function persistMergedArtifact(opts) {
|
|
|
120056
120098
|
},
|
|
120057
120099
|
currentPrState: PrState.MERGED,
|
|
120058
120100
|
followUpPrRequired: false
|
|
120059
|
-
}).catch(() => {
|
|
120101
|
+
}).catch((err) => {
|
|
120102
|
+
console.warn(
|
|
120103
|
+
JSON.stringify({ projectSlug, issueId, prNumber, error: String(err) }),
|
|
120104
|
+
"persistMergedArtifact failed \u2014 issue close guard may not find merge evidence"
|
|
120105
|
+
);
|
|
120060
120106
|
});
|
|
120061
120107
|
}
|
|
120062
120108
|
async function guardedCloseIssue(opts) {
|
|
@@ -124469,6 +124515,7 @@ init_audit();
|
|
|
124469
124515
|
init_audit();
|
|
124470
124516
|
init_workflow();
|
|
124471
124517
|
init_context3();
|
|
124518
|
+
init_labels();
|
|
124472
124519
|
var GRACE_PERIOD_MS = 5 * 60 * 1e3;
|
|
124473
124520
|
var DISPATCH_CONFIRMATION_TIMEOUT_MS = 2 * 60 * 1e3;
|
|
124474
124521
|
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".`;
|
|
@@ -124578,7 +124625,7 @@ async function checkWorkerHealth(opts) {
|
|
|
124578
124625
|
async function revertLabel(fix, from, to) {
|
|
124579
124626
|
if (!issueIdNum) return;
|
|
124580
124627
|
try {
|
|
124581
|
-
await provider
|
|
124628
|
+
await resilientLabelTransition(provider, issueIdNum, from, to);
|
|
124582
124629
|
fix.labelReverted = `${from} \u2192 ${to}`;
|
|
124583
124630
|
} catch {
|
|
124584
124631
|
fix.labelRevertFailed = true;
|
|
@@ -125131,7 +125178,7 @@ async function scanOrphanedLabels(opts) {
|
|
|
125131
125178
|
queueLabel,
|
|
125132
125179
|
workflow
|
|
125133
125180
|
);
|
|
125134
|
-
await provider
|
|
125181
|
+
await resilientLabelTransition(provider, issue2.iid, activeLabel, revertTarget);
|
|
125135
125182
|
fix.fixed = true;
|
|
125136
125183
|
fix.labelReverted = `${activeLabel} \u2192 ${revertTarget}`;
|
|
125137
125184
|
fix.issue.expectedLabel = revertTarget;
|
|
@@ -134220,6 +134267,23 @@ async function getLifecycleService(workspaceDir, logger6) {
|
|
|
134220
134267
|
return created;
|
|
134221
134268
|
}
|
|
134222
134269
|
|
|
134270
|
+
// lib/utils/async.ts
|
|
134271
|
+
async function raceWithTimeout(fn, timeoutMs, onTimeout) {
|
|
134272
|
+
let timer;
|
|
134273
|
+
const timeoutPromise = new Promise((resolve3) => {
|
|
134274
|
+
timer = setTimeout(() => {
|
|
134275
|
+
onTimeout();
|
|
134276
|
+
resolve3("timeout");
|
|
134277
|
+
}, timeoutMs);
|
|
134278
|
+
});
|
|
134279
|
+
try {
|
|
134280
|
+
const result = await Promise.race([fn(), timeoutPromise]);
|
|
134281
|
+
return result;
|
|
134282
|
+
} finally {
|
|
134283
|
+
clearTimeout(timer);
|
|
134284
|
+
}
|
|
134285
|
+
}
|
|
134286
|
+
|
|
134223
134287
|
// lib/services/heartbeat/tick-runner.ts
|
|
134224
134288
|
init_audit();
|
|
134225
134289
|
|
|
@@ -134509,6 +134573,7 @@ init_workflow();
|
|
|
134509
134573
|
// lib/services/heartbeat/review.ts
|
|
134510
134574
|
init_workflow();
|
|
134511
134575
|
init_audit();
|
|
134576
|
+
init_labels();
|
|
134512
134577
|
async function reviewPass(opts) {
|
|
134513
134578
|
const rc = opts.runCommand;
|
|
134514
134579
|
const { workspaceDir, projectName, workflow, provider, repoPath, gitPullTimeoutMs = 3e4, baseBranch, onMerge, onFeedback, onPrClosed } = opts;
|
|
@@ -134546,7 +134611,7 @@ async function reviewPass(opts) {
|
|
|
134546
134611
|
const targetKey2 = typeof changesTransition === "string" ? changesTransition : changesTransition.target;
|
|
134547
134612
|
const targetState2 = workflow.states[targetKey2];
|
|
134548
134613
|
if (targetState2) {
|
|
134549
|
-
await provider
|
|
134614
|
+
await resilientLabelTransition(provider, issue2.iid, state.label, targetState2.label);
|
|
134550
134615
|
await log(workspaceDir, "review_transition", {
|
|
134551
134616
|
project: projectName,
|
|
134552
134617
|
issueId: issue2.iid,
|
|
@@ -134569,7 +134634,7 @@ async function reviewPass(opts) {
|
|
|
134569
134634
|
const targetKey2 = typeof conflictTransition === "string" ? conflictTransition : conflictTransition.target;
|
|
134570
134635
|
const targetState2 = workflow.states[targetKey2];
|
|
134571
134636
|
if (targetState2) {
|
|
134572
|
-
await provider
|
|
134637
|
+
await resilientLabelTransition(provider, issue2.iid, state.label, targetState2.label);
|
|
134573
134638
|
await log(workspaceDir, "review_transition", {
|
|
134574
134639
|
project: projectName,
|
|
134575
134640
|
issueId: issue2.iid,
|
|
@@ -134601,7 +134666,7 @@ async function reviewPass(opts) {
|
|
|
134601
134666
|
const closedActions = typeof closedTransition === "object" ? closedTransition.actions : void 0;
|
|
134602
134667
|
const targetState2 = workflow.states[targetKey2];
|
|
134603
134668
|
if (targetState2) {
|
|
134604
|
-
await provider
|
|
134669
|
+
await resilientLabelTransition(provider, issue2.iid, state.label, targetState2.label);
|
|
134605
134670
|
if (closedActions) {
|
|
134606
134671
|
for (const action of closedActions) {
|
|
134607
134672
|
switch (action) {
|
|
@@ -134716,7 +134781,7 @@ async function reviewPass(opts) {
|
|
|
134716
134781
|
const failedKey = typeof failedTransition === "string" ? failedTransition : failedTransition.target;
|
|
134717
134782
|
const failedState = workflow.states[failedKey];
|
|
134718
134783
|
if (failedState) {
|
|
134719
|
-
await provider
|
|
134784
|
+
await resilientLabelTransition(provider, issue2.iid, state.label, failedState.label);
|
|
134720
134785
|
await log(workspaceDir, "review_transition", {
|
|
134721
134786
|
project: projectName,
|
|
134722
134787
|
issueId: issue2.iid,
|
|
@@ -134761,7 +134826,7 @@ async function reviewPass(opts) {
|
|
|
134761
134826
|
}
|
|
134762
134827
|
}
|
|
134763
134828
|
if (aborted2) continue;
|
|
134764
|
-
await provider
|
|
134829
|
+
await resilientLabelTransition(provider, issue2.iid, state.label, targetState.label);
|
|
134765
134830
|
await log(workspaceDir, "review_transition", {
|
|
134766
134831
|
project: projectName,
|
|
134767
134832
|
issueId: issue2.iid,
|
|
@@ -134791,6 +134856,7 @@ async function reactToFeedbackComments(provider, issueId) {
|
|
|
134791
134856
|
// lib/services/heartbeat/review-skip.ts
|
|
134792
134857
|
init_workflow();
|
|
134793
134858
|
init_audit();
|
|
134859
|
+
init_labels();
|
|
134794
134860
|
async function reviewSkipPass(opts) {
|
|
134795
134861
|
const rc = opts.runCommand;
|
|
134796
134862
|
const { workspaceDir, projectName, workflow, provider, repoPath, gitPullTimeoutMs = 3e4, onMerge } = opts;
|
|
@@ -134874,7 +134940,7 @@ async function reviewSkipPass(opts) {
|
|
|
134874
134940
|
const failedKey = typeof failedTransition === "string" ? failedTransition : failedTransition.target;
|
|
134875
134941
|
const failedState = workflow.states[failedKey];
|
|
134876
134942
|
if (failedState) {
|
|
134877
|
-
await provider
|
|
134943
|
+
await resilientLabelTransition(provider, issue2.iid, state.label, failedState.label);
|
|
134878
134944
|
transitions++;
|
|
134879
134945
|
}
|
|
134880
134946
|
}
|
|
@@ -134917,7 +134983,7 @@ async function reviewSkipPass(opts) {
|
|
|
134917
134983
|
}
|
|
134918
134984
|
}
|
|
134919
134985
|
if (aborted2) continue;
|
|
134920
|
-
await provider
|
|
134986
|
+
await resilientLabelTransition(provider, issue2.iid, state.label, targetState.label);
|
|
134921
134987
|
await log(workspaceDir, "review_skip_transition", {
|
|
134922
134988
|
project: projectName,
|
|
134923
134989
|
issueId: issue2.iid,
|
|
@@ -134934,6 +135000,7 @@ async function reviewSkipPass(opts) {
|
|
|
134934
135000
|
// lib/services/heartbeat/test-skip.ts
|
|
134935
135001
|
init_workflow();
|
|
134936
135002
|
init_audit();
|
|
135003
|
+
init_labels();
|
|
134937
135004
|
async function testSkipPass(opts) {
|
|
134938
135005
|
const { workspaceDir, projectName, workflow, provider, repoPath, gitPullTimeoutMs = 3e4, runCommand } = opts;
|
|
134939
135006
|
let transitions = 0;
|
|
@@ -134999,7 +135066,7 @@ async function testSkipPass(opts) {
|
|
|
134999
135066
|
const failedKey = typeof failedTransition === "string" ? failedTransition : failedTransition.target;
|
|
135000
135067
|
const failedState = workflow.states[failedKey];
|
|
135001
135068
|
if (failedState) {
|
|
135002
|
-
await provider
|
|
135069
|
+
await resilientLabelTransition(provider, issue2.iid, state.label, failedState.label);
|
|
135003
135070
|
transitions++;
|
|
135004
135071
|
}
|
|
135005
135072
|
}
|
|
@@ -135044,7 +135111,7 @@ async function testSkipPass(opts) {
|
|
|
135044
135111
|
}
|
|
135045
135112
|
}
|
|
135046
135113
|
if (aborted2) continue;
|
|
135047
|
-
await provider
|
|
135114
|
+
await resilientLabelTransition(provider, issue2.iid, state.label, targetState.label);
|
|
135048
135115
|
await log(workspaceDir, "test_skip_transition", {
|
|
135049
135116
|
project: projectName,
|
|
135050
135117
|
issueId: issue2.iid,
|
|
@@ -135061,6 +135128,7 @@ async function testSkipPass(opts) {
|
|
|
135061
135128
|
// lib/services/heartbeat/hold-escape.ts
|
|
135062
135129
|
init_workflow();
|
|
135063
135130
|
init_audit();
|
|
135131
|
+
init_labels();
|
|
135064
135132
|
async function holdEscapePass(opts) {
|
|
135065
135133
|
const { workspaceDir, projectName, workflow, provider } = opts;
|
|
135066
135134
|
let transitions = 0;
|
|
@@ -135094,7 +135162,7 @@ async function holdEscapePass(opts) {
|
|
|
135094
135162
|
}
|
|
135095
135163
|
await provider.closeIssue(issue2.iid);
|
|
135096
135164
|
await clearIssueRuntime(workspaceDir, project.slug, issue2.iid);
|
|
135097
|
-
await provider
|
|
135165
|
+
await resilientLabelTransition(provider, issue2.iid, state.label, terminalState.label);
|
|
135098
135166
|
await log(workspaceDir, "hold_escape_transition", {
|
|
135099
135167
|
project: projectName,
|
|
135100
135168
|
issueId: issue2.iid,
|
|
@@ -135958,21 +136026,6 @@ function registerHeartbeatService(api, pluginCtx) {
|
|
|
135958
136026
|
}
|
|
135959
136027
|
});
|
|
135960
136028
|
}
|
|
135961
|
-
async function raceWithTimeout(fn, timeoutMs, onTimeout) {
|
|
135962
|
-
let timer;
|
|
135963
|
-
const timeoutPromise = new Promise((resolve3) => {
|
|
135964
|
-
timer = setTimeout(() => {
|
|
135965
|
-
onTimeout();
|
|
135966
|
-
resolve3("timeout");
|
|
135967
|
-
}, timeoutMs);
|
|
135968
|
-
});
|
|
135969
|
-
try {
|
|
135970
|
-
const result = await Promise.race([fn(), timeoutPromise]);
|
|
135971
|
-
return result;
|
|
135972
|
-
} finally {
|
|
135973
|
-
clearTimeout(timer);
|
|
135974
|
-
}
|
|
135975
|
-
}
|
|
135976
136029
|
var DEFAULT_TICK_TIMEOUT_MS = 5e4;
|
|
135977
136030
|
var _ticksTimedOut = 0;
|
|
135978
136031
|
async function withTickMutex(fn) {
|
|
@@ -136025,7 +136078,9 @@ async function runHeartbeatTick(ctx, logger6, mode) {
|
|
|
136025
136078
|
_anyTickRunning = false;
|
|
136026
136079
|
});
|
|
136027
136080
|
} else {
|
|
136028
|
-
logger6.error("tick_mutex: tickPromise undefined in timeout handler \u2014
|
|
136081
|
+
logger6.error("tick_mutex: tickPromise undefined in timeout handler \u2014 forcing mutex release");
|
|
136082
|
+
_tickRunning[mode] = false;
|
|
136083
|
+
_anyTickRunning = false;
|
|
136029
136084
|
}
|
|
136030
136085
|
});
|
|
136031
136086
|
void raceResult;
|
|
@@ -138256,6 +138311,14 @@ async function pathExists(candidate) {
|
|
|
138256
138311
|
return false;
|
|
138257
138312
|
}
|
|
138258
138313
|
}
|
|
138314
|
+
async function isValidBinary(filePath) {
|
|
138315
|
+
try {
|
|
138316
|
+
const stat2 = await fs34.stat(filePath);
|
|
138317
|
+
return stat2.size > 0;
|
|
138318
|
+
} catch {
|
|
138319
|
+
return false;
|
|
138320
|
+
}
|
|
138321
|
+
}
|
|
138259
138322
|
function familyForStack(stack) {
|
|
138260
138323
|
if (NODE_STACKS.has(stack)) return "node";
|
|
138261
138324
|
if (PYTHON_STACKS.has(stack)) return "python";
|
|
@@ -138368,7 +138431,7 @@ function buildPythonBootstrapPrelude() {
|
|
|
138368
138431
|
|
|
138369
138432
|
# --- Shared toolchain (ruff, mypy, pip-audit) ---
|
|
138370
138433
|
TOOLCHAIN="$HOME/.openclaw/toolchains/python"
|
|
138371
|
-
if [ ! -x "$TOOLCHAIN/bin/ruff" ]; then
|
|
138434
|
+
if [ ! -x "$TOOLCHAIN/bin/ruff" ] || [ ! -s "$TOOLCHAIN/bin/ruff" ]; then
|
|
138372
138435
|
echo "[qa] Toolchain not found \u2014 provisioning..."
|
|
138373
138436
|
command -v uv >/dev/null 2>&1 || {
|
|
138374
138437
|
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
@@ -138515,7 +138578,7 @@ async function ensurePythonToolchain(runCommand, homeDir) {
|
|
|
138515
138578
|
const ruffPath = path34.join(toolchainPath, "bin", "ruff");
|
|
138516
138579
|
const fingerprintPath = path34.join(toolchainPath, TOOLCHAIN_FINGERPRINT_FILE);
|
|
138517
138580
|
const expectedFp = toolchainFingerprint();
|
|
138518
|
-
if (await
|
|
138581
|
+
if (await isValidBinary(ruffPath)) {
|
|
138519
138582
|
try {
|
|
138520
138583
|
const currentFp = (await fs34.readFile(fingerprintPath, "utf-8")).trim();
|
|
138521
138584
|
if (currentFp === expectedFp) {
|
|
@@ -139112,17 +139175,6 @@ var scaffoldStep = {
|
|
|
139112
139175
|
mode: "scaffold",
|
|
139113
139176
|
runCommand: ctx.runCommand
|
|
139114
139177
|
});
|
|
139115
|
-
if (!bootstrap.ready) {
|
|
139116
|
-
ctx.log(`Scaffold bootstrap failed: ${bootstrap.reason ?? "unknown reason"}`);
|
|
139117
|
-
return {
|
|
139118
|
-
...result.plannedPayload,
|
|
139119
|
-
step: "scaffold",
|
|
139120
|
-
scaffold: { created: false, reason: bootstrap.reason ?? "bootstrap_failed" }
|
|
139121
|
-
};
|
|
139122
|
-
}
|
|
139123
|
-
ctx.log(
|
|
139124
|
-
bootstrap.skipped ? `Scaffold bootstrap already current (${bootstrap.packageManager})` : `Scaffold bootstrap completed (${bootstrap.packageManager})`
|
|
139125
|
-
);
|
|
139126
139178
|
if (scaffold.stack && PYTHON_STACKS2.has(scaffold.stack) && payload.spec) {
|
|
139127
139179
|
try {
|
|
139128
139180
|
const contract = generateQaContract({
|
|
@@ -139137,6 +139189,17 @@ var scaffoldStep = {
|
|
|
139137
139189
|
ctx.log(`Warning: could not write qa.sh: ${err instanceof Error ? err.message : String(err)}`);
|
|
139138
139190
|
}
|
|
139139
139191
|
}
|
|
139192
|
+
if (!bootstrap.ready) {
|
|
139193
|
+
ctx.log(`Scaffold bootstrap failed: ${bootstrap.reason ?? "unknown reason"}`);
|
|
139194
|
+
return {
|
|
139195
|
+
...result.plannedPayload,
|
|
139196
|
+
step: "scaffold",
|
|
139197
|
+
scaffold: { created: false, reason: bootstrap.reason ?? "bootstrap_failed" }
|
|
139198
|
+
};
|
|
139199
|
+
}
|
|
139200
|
+
ctx.log(
|
|
139201
|
+
bootstrap.skipped ? `Scaffold bootstrap already current (${bootstrap.packageManager})` : `Scaffold bootstrap completed (${bootstrap.packageManager})`
|
|
139202
|
+
);
|
|
139140
139203
|
}
|
|
139141
139204
|
return {
|
|
139142
139205
|
...result.plannedPayload,
|
|
@@ -142147,6 +142210,7 @@ function shouldSuppressTelegramBootstrapReply(session, request2) {
|
|
|
142147
142210
|
}
|
|
142148
142211
|
|
|
142149
142212
|
// lib/dispatch/telegram-bootstrap-hook.ts
|
|
142213
|
+
var BOOTSTRAP_TIMEOUT_MS = 5 * 6e4;
|
|
142150
142214
|
var BOOTSTRAP_MESSAGES = {
|
|
142151
142215
|
ack: {
|
|
142152
142216
|
pt: "Recebi! Vou analisar e come\xE7ar a montar o projeto...",
|
|
@@ -142416,8 +142480,24 @@ async function classifyAndBootstrap(ctx, workspaceDir, conversationId, content)
|
|
|
142416
142480
|
await sendTelegramText(ctx, conversationId, buildClarificationMessage(parsed, pendingClarification, language));
|
|
142417
142481
|
return;
|
|
142418
142482
|
}
|
|
142419
|
-
|
|
142420
|
-
|
|
142483
|
+
bootstrapWithTimeout(ctx, conversationId, workspaceDir, incomingRequest, sourceRoute);
|
|
142484
|
+
}
|
|
142485
|
+
function bootstrapWithTimeout(ctx, conversationId, workspaceDir, request2, sourceRoute) {
|
|
142486
|
+
raceWithTimeout(
|
|
142487
|
+
() => continueBootstrap(ctx, conversationId, workspaceDir, request2, sourceRoute),
|
|
142488
|
+
BOOTSTRAP_TIMEOUT_MS,
|
|
142489
|
+
() => {
|
|
142490
|
+
ctx.logger.warn({ conversationId }, "Bootstrap pipeline timed out after 5 minutes");
|
|
142491
|
+
upsertTelegramBootstrapSession(workspaceDir, {
|
|
142492
|
+
conversationId,
|
|
142493
|
+
rawIdea: request2.rawIdea,
|
|
142494
|
+
status: "failed",
|
|
142495
|
+
error: "Pipeline timeout (5min)"
|
|
142496
|
+
}).catch(() => {
|
|
142497
|
+
});
|
|
142498
|
+
}
|
|
142499
|
+
).catch((err) => {
|
|
142500
|
+
logBootstrapWarning(ctx, `[telegram-bootstrap] pipeline error: ${err instanceof Error ? err.message : String(err)}`);
|
|
142421
142501
|
});
|
|
142422
142502
|
}
|
|
142423
142503
|
async function continueBootstrap(ctx, conversationId, workspaceDir, request2, sourceRoute) {
|
|
@@ -142432,6 +142512,24 @@ async function continueBootstrap(ctx, conversationId, workspaceDir, request2, so
|
|
|
142432
142512
|
}
|
|
142433
142513
|
const projectName = request2.projectName ?? void 0;
|
|
142434
142514
|
const stackHint = request2.stackHint;
|
|
142515
|
+
if (!stackHint) {
|
|
142516
|
+
const existingSession = await readTelegramBootstrapSession(workspaceDir, conversationId);
|
|
142517
|
+
const lang = existingSession?.language ?? "pt";
|
|
142518
|
+
await upsertTelegramBootstrapSession(workspaceDir, {
|
|
142519
|
+
conversationId,
|
|
142520
|
+
rawIdea: request2.rawIdea,
|
|
142521
|
+
projectName: request2.projectName ?? void 0,
|
|
142522
|
+
status: "clarifying",
|
|
142523
|
+
pendingClarification: "stack",
|
|
142524
|
+
language: lang
|
|
142525
|
+
});
|
|
142526
|
+
await sendTelegramText(ctx, conversationId, buildClarificationMessage(
|
|
142527
|
+
{ rawIdea: request2.rawIdea, projectName: request2.projectName ?? void 0, stackHint: request2.stackHint ?? void 0 },
|
|
142528
|
+
"stack",
|
|
142529
|
+
lang
|
|
142530
|
+
));
|
|
142531
|
+
return;
|
|
142532
|
+
}
|
|
142435
142533
|
const candidateSlug = inferProjectSlug(projectName ?? request2.rawIdea);
|
|
142436
142534
|
if (candidateSlug) {
|
|
142437
142535
|
const projects = await readProjects(workspaceDir).catch(() => null);
|
|
@@ -142611,8 +142709,12 @@ function registerTelegramBootstrapHook(api, ctx) {
|
|
|
142611
142709
|
api.on("before_prompt_build", async (_event, eventCtx) => {
|
|
142612
142710
|
const hookCtx = eventCtx;
|
|
142613
142711
|
if (hookCtx.channelId !== "telegram") return {};
|
|
142614
|
-
const
|
|
142615
|
-
if (
|
|
142712
|
+
const sessionKey = hookCtx.sessionKey ?? "";
|
|
142713
|
+
if (sessionKey.includes(":topic:") || sessionKey.includes(":group:")) return {};
|
|
142714
|
+
const sessionKeyParts = sessionKey.split(":");
|
|
142715
|
+
const chatId = sessionKeyParts.length >= 5 ? sessionKeyParts[sessionKeyParts.length - 1] : "";
|
|
142716
|
+
const conversationId = chatId ? `telegram:${chatId}` : "";
|
|
142717
|
+
if (!conversationId) return {};
|
|
142616
142718
|
const workspaceDir = resolveWorkspaceDir(ctx.config);
|
|
142617
142719
|
if (!workspaceDir) return {};
|
|
142618
142720
|
const session = await readTelegramBootstrapSession(workspaceDir, conversationId);
|
|
@@ -142625,11 +142727,14 @@ function registerTelegramBootstrapHook(api, ctx) {
|
|
|
142625
142727
|
].join("\n")
|
|
142626
142728
|
};
|
|
142627
142729
|
});
|
|
142628
|
-
api.on("message_sending", async (
|
|
142730
|
+
api.on("message_sending", async (event, eventCtx) => {
|
|
142629
142731
|
const hookCtx = eventCtx;
|
|
142630
142732
|
if (hookCtx.channelId !== "telegram") return;
|
|
142631
|
-
const
|
|
142632
|
-
|
|
142733
|
+
const sendEvent = event;
|
|
142734
|
+
const rawTo = String(sendEvent.to ?? "").trim();
|
|
142735
|
+
if (!rawTo || rawTo.startsWith("-")) return;
|
|
142736
|
+
const conversationId = rawTo.includes(":") ? rawTo : `telegram:${rawTo}`;
|
|
142737
|
+
if (conversationId.includes(":topic:")) return;
|
|
142633
142738
|
const workspaceDir = resolveWorkspaceDir(ctx.config);
|
|
142634
142739
|
if (!workspaceDir) return;
|
|
142635
142740
|
const session = await readTelegramBootstrapSession(workspaceDir, conversationId);
|
|
@@ -142673,11 +142778,9 @@ function registerTelegramBootstrapHook(api, ctx) {
|
|
|
142673
142778
|
repoPath: existingSession.repoPath ?? null
|
|
142674
142779
|
};
|
|
142675
142780
|
ctx.logger.info(`[telegram-bootstrap] clarification resolved: stack=${mergedRequest.stackHint}, idea="${mergedRequest.rawIdea}" (conversation: ${conversationId})`);
|
|
142676
|
-
|
|
142781
|
+
bootstrapWithTimeout(ctx, conversationId, workspaceDir, mergedRequest, existingSession.sourceRoute ?? {
|
|
142677
142782
|
channel: "telegram",
|
|
142678
142783
|
channelId: conversationId
|
|
142679
|
-
}).catch((err) => {
|
|
142680
|
-
logBootstrapWarning(ctx, `[telegram-bootstrap] unhandled pipeline error: ${err instanceof Error ? err.message : String(err)}`);
|
|
142681
142784
|
});
|
|
142682
142785
|
return;
|
|
142683
142786
|
}
|
|
@@ -142745,11 +142848,9 @@ function registerTelegramBootstrapHook(api, ctx) {
|
|
|
142745
142848
|
await sendTelegramText(ctx, conversationId, buildClarificationMessage(parsed, pendingClarification, language));
|
|
142746
142849
|
return;
|
|
142747
142850
|
}
|
|
142748
|
-
|
|
142851
|
+
bootstrapWithTimeout(ctx, conversationId, workspaceDir, incomingRequest, {
|
|
142749
142852
|
channel: "telegram",
|
|
142750
142853
|
channelId: conversationId
|
|
142751
|
-
}).catch((err) => {
|
|
142752
|
-
logBootstrapWarning(ctx, `[telegram-bootstrap] unhandled pipeline error: ${err instanceof Error ? err.message : String(err)}`);
|
|
142753
142854
|
});
|
|
142754
142855
|
});
|
|
142755
142856
|
}
|