@mestreyoda/fabrica 0.1.11 → 0.1.13
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 +308 -101
- 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
|
@@ -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.13") {
|
|
111333
|
+
return "0.1.13";
|
|
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
|
}
|
|
@@ -119810,6 +119852,22 @@ PR merged \u2014 issue closed automatically (was stuck in ${event.fromState})`;
|
|
|
119810
119852
|
\u{1F3C1} Done \u2014 no human action needed.`;
|
|
119811
119853
|
return msg;
|
|
119812
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
|
+
}
|
|
119813
119871
|
}
|
|
119814
119872
|
}
|
|
119815
119873
|
async function sendMessage(target, message, channel, workspaceDir, runtime, accountId, runCommand, messageThreadId, auditMeta) {
|
|
@@ -120056,7 +120114,11 @@ async function persistMergedArtifact(opts) {
|
|
|
120056
120114
|
},
|
|
120057
120115
|
currentPrState: PrState.MERGED,
|
|
120058
120116
|
followUpPrRequired: false
|
|
120059
|
-
}).catch(() => {
|
|
120117
|
+
}).catch((err) => {
|
|
120118
|
+
console.warn(
|
|
120119
|
+
JSON.stringify({ projectSlug, issueId, prNumber, error: String(err) }),
|
|
120120
|
+
"persistMergedArtifact failed \u2014 issue close guard may not find merge evidence"
|
|
120121
|
+
);
|
|
120060
120122
|
});
|
|
120061
120123
|
}
|
|
120062
120124
|
async function guardedCloseIssue(opts) {
|
|
@@ -120543,10 +120605,10 @@ init_audit();
|
|
|
120543
120605
|
init_migrate_layout();
|
|
120544
120606
|
init_roles();
|
|
120545
120607
|
init_workflow();
|
|
120608
|
+
init_labels();
|
|
120546
120609
|
|
|
120547
120610
|
// lib/tools/tasks/public-output-sanitizer.ts
|
|
120548
120611
|
var SECRET_PATTERNS = [
|
|
120549
|
-
/\b[A-Za-z_][A-Za-z0-9_]*=([^\s]+)/g,
|
|
120550
120612
|
/\b(?:ghp_|gho_|github_pat_|sk-|xoxb-|xoxp-|AIza|AKIA|glpat-)[A-Za-z0-9._-]*/g,
|
|
120551
120613
|
/\b(?:token|secret|api[_-]?key|password|passwd|authorization|bearer)\b\s*[:=]\s*[^\s]+/gi
|
|
120552
120614
|
];
|
|
@@ -120871,6 +120933,7 @@ function matchesReviewArtifact(comment, artifactId, artifactType) {
|
|
|
120871
120933
|
}
|
|
120872
120934
|
return comment.state === "COMMENTED" && !comment.path;
|
|
120873
120935
|
}
|
|
120936
|
+
var INFRA_FAIL_CIRCUIT_BREAKER_THRESHOLD = 2;
|
|
120874
120937
|
function createWorkFinishTool(ctx) {
|
|
120875
120938
|
return (toolCtx) => ({
|
|
120876
120939
|
name: "work_finish",
|
|
@@ -120882,7 +120945,7 @@ function createWorkFinishTool(ctx) {
|
|
|
120882
120945
|
properties: {
|
|
120883
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." },
|
|
120884
120947
|
role: { type: "string", enum: getAllRoleIds(), description: "Worker role" },
|
|
120885
|
-
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." },
|
|
120886
120949
|
summary: { type: "string", description: "Brief summary" },
|
|
120887
120950
|
prUrl: { type: "string", description: "PR/MR URL (auto-detected if omitted)" },
|
|
120888
120951
|
createdTasks: {
|
|
@@ -120964,6 +121027,79 @@ function createWorkFinishTool(ctx) {
|
|
|
120964
121027
|
})),
|
|
120965
121028
|
keyTransitions: resolvedConfig.workflowMeta.keyTransitions
|
|
120966
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
|
+
}
|
|
120967
121103
|
if (!getRule(role, result, workflow))
|
|
120968
121104
|
throw new Error(`Invalid completion: ${role}:${result}`);
|
|
120969
121105
|
const repoPath = resolveRepoPath(project.repo);
|
|
@@ -121082,6 +121218,9 @@ function createWorkFinishTool(ctx) {
|
|
|
121082
121218
|
details: { role, result }
|
|
121083
121219
|
}).catch(() => {
|
|
121084
121220
|
});
|
|
121221
|
+
if (role === "tester" && issueRuntime?.infraFailCount) {
|
|
121222
|
+
await updateIssueRuntime(workspaceDir, project.slug, issueId, { infraFailCount: 0 });
|
|
121223
|
+
}
|
|
121085
121224
|
return jsonResult2({
|
|
121086
121225
|
success: true,
|
|
121087
121226
|
project: project.name,
|
|
@@ -124469,6 +124608,7 @@ init_audit();
|
|
|
124469
124608
|
init_audit();
|
|
124470
124609
|
init_workflow();
|
|
124471
124610
|
init_context3();
|
|
124611
|
+
init_labels();
|
|
124472
124612
|
var GRACE_PERIOD_MS = 5 * 60 * 1e3;
|
|
124473
124613
|
var DISPATCH_CONFIRMATION_TIMEOUT_MS = 2 * 60 * 1e3;
|
|
124474
124614
|
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 +124718,7 @@ async function checkWorkerHealth(opts) {
|
|
|
124578
124718
|
async function revertLabel(fix, from, to) {
|
|
124579
124719
|
if (!issueIdNum) return;
|
|
124580
124720
|
try {
|
|
124581
|
-
await provider
|
|
124721
|
+
await resilientLabelTransition(provider, issueIdNum, from, to);
|
|
124582
124722
|
fix.labelReverted = `${from} \u2192 ${to}`;
|
|
124583
124723
|
} catch {
|
|
124584
124724
|
fix.labelRevertFailed = true;
|
|
@@ -125131,7 +125271,7 @@ async function scanOrphanedLabels(opts) {
|
|
|
125131
125271
|
queueLabel,
|
|
125132
125272
|
workflow
|
|
125133
125273
|
);
|
|
125134
|
-
await provider
|
|
125274
|
+
await resilientLabelTransition(provider, issue2.iid, activeLabel, revertTarget);
|
|
125135
125275
|
fix.fixed = true;
|
|
125136
125276
|
fix.labelReverted = `${activeLabel} \u2192 ${revertTarget}`;
|
|
125137
125277
|
fix.issue.expectedLabel = revertTarget;
|
|
@@ -134220,6 +134360,23 @@ async function getLifecycleService(workspaceDir, logger6) {
|
|
|
134220
134360
|
return created;
|
|
134221
134361
|
}
|
|
134222
134362
|
|
|
134363
|
+
// lib/utils/async.ts
|
|
134364
|
+
async function raceWithTimeout(fn, timeoutMs, onTimeout) {
|
|
134365
|
+
let timer;
|
|
134366
|
+
const timeoutPromise = new Promise((resolve3) => {
|
|
134367
|
+
timer = setTimeout(() => {
|
|
134368
|
+
onTimeout();
|
|
134369
|
+
resolve3("timeout");
|
|
134370
|
+
}, timeoutMs);
|
|
134371
|
+
});
|
|
134372
|
+
try {
|
|
134373
|
+
const result = await Promise.race([fn(), timeoutPromise]);
|
|
134374
|
+
return result;
|
|
134375
|
+
} finally {
|
|
134376
|
+
clearTimeout(timer);
|
|
134377
|
+
}
|
|
134378
|
+
}
|
|
134379
|
+
|
|
134223
134380
|
// lib/services/heartbeat/tick-runner.ts
|
|
134224
134381
|
init_audit();
|
|
134225
134382
|
|
|
@@ -134509,6 +134666,7 @@ init_workflow();
|
|
|
134509
134666
|
// lib/services/heartbeat/review.ts
|
|
134510
134667
|
init_workflow();
|
|
134511
134668
|
init_audit();
|
|
134669
|
+
init_labels();
|
|
134512
134670
|
async function reviewPass(opts) {
|
|
134513
134671
|
const rc = opts.runCommand;
|
|
134514
134672
|
const { workspaceDir, projectName, workflow, provider, repoPath, gitPullTimeoutMs = 3e4, baseBranch, onMerge, onFeedback, onPrClosed } = opts;
|
|
@@ -134546,7 +134704,7 @@ async function reviewPass(opts) {
|
|
|
134546
134704
|
const targetKey2 = typeof changesTransition === "string" ? changesTransition : changesTransition.target;
|
|
134547
134705
|
const targetState2 = workflow.states[targetKey2];
|
|
134548
134706
|
if (targetState2) {
|
|
134549
|
-
await provider
|
|
134707
|
+
await resilientLabelTransition(provider, issue2.iid, state.label, targetState2.label);
|
|
134550
134708
|
await log(workspaceDir, "review_transition", {
|
|
134551
134709
|
project: projectName,
|
|
134552
134710
|
issueId: issue2.iid,
|
|
@@ -134569,7 +134727,7 @@ async function reviewPass(opts) {
|
|
|
134569
134727
|
const targetKey2 = typeof conflictTransition === "string" ? conflictTransition : conflictTransition.target;
|
|
134570
134728
|
const targetState2 = workflow.states[targetKey2];
|
|
134571
134729
|
if (targetState2) {
|
|
134572
|
-
await provider
|
|
134730
|
+
await resilientLabelTransition(provider, issue2.iid, state.label, targetState2.label);
|
|
134573
134731
|
await log(workspaceDir, "review_transition", {
|
|
134574
134732
|
project: projectName,
|
|
134575
134733
|
issueId: issue2.iid,
|
|
@@ -134601,7 +134759,7 @@ async function reviewPass(opts) {
|
|
|
134601
134759
|
const closedActions = typeof closedTransition === "object" ? closedTransition.actions : void 0;
|
|
134602
134760
|
const targetState2 = workflow.states[targetKey2];
|
|
134603
134761
|
if (targetState2) {
|
|
134604
|
-
await provider
|
|
134762
|
+
await resilientLabelTransition(provider, issue2.iid, state.label, targetState2.label);
|
|
134605
134763
|
if (closedActions) {
|
|
134606
134764
|
for (const action of closedActions) {
|
|
134607
134765
|
switch (action) {
|
|
@@ -134716,7 +134874,7 @@ async function reviewPass(opts) {
|
|
|
134716
134874
|
const failedKey = typeof failedTransition === "string" ? failedTransition : failedTransition.target;
|
|
134717
134875
|
const failedState = workflow.states[failedKey];
|
|
134718
134876
|
if (failedState) {
|
|
134719
|
-
await provider
|
|
134877
|
+
await resilientLabelTransition(provider, issue2.iid, state.label, failedState.label);
|
|
134720
134878
|
await log(workspaceDir, "review_transition", {
|
|
134721
134879
|
project: projectName,
|
|
134722
134880
|
issueId: issue2.iid,
|
|
@@ -134761,7 +134919,7 @@ async function reviewPass(opts) {
|
|
|
134761
134919
|
}
|
|
134762
134920
|
}
|
|
134763
134921
|
if (aborted2) continue;
|
|
134764
|
-
await provider
|
|
134922
|
+
await resilientLabelTransition(provider, issue2.iid, state.label, targetState.label);
|
|
134765
134923
|
await log(workspaceDir, "review_transition", {
|
|
134766
134924
|
project: projectName,
|
|
134767
134925
|
issueId: issue2.iid,
|
|
@@ -134791,6 +134949,7 @@ async function reactToFeedbackComments(provider, issueId) {
|
|
|
134791
134949
|
// lib/services/heartbeat/review-skip.ts
|
|
134792
134950
|
init_workflow();
|
|
134793
134951
|
init_audit();
|
|
134952
|
+
init_labels();
|
|
134794
134953
|
async function reviewSkipPass(opts) {
|
|
134795
134954
|
const rc = opts.runCommand;
|
|
134796
134955
|
const { workspaceDir, projectName, workflow, provider, repoPath, gitPullTimeoutMs = 3e4, onMerge } = opts;
|
|
@@ -134874,7 +135033,7 @@ async function reviewSkipPass(opts) {
|
|
|
134874
135033
|
const failedKey = typeof failedTransition === "string" ? failedTransition : failedTransition.target;
|
|
134875
135034
|
const failedState = workflow.states[failedKey];
|
|
134876
135035
|
if (failedState) {
|
|
134877
|
-
await provider
|
|
135036
|
+
await resilientLabelTransition(provider, issue2.iid, state.label, failedState.label);
|
|
134878
135037
|
transitions++;
|
|
134879
135038
|
}
|
|
134880
135039
|
}
|
|
@@ -134917,7 +135076,7 @@ async function reviewSkipPass(opts) {
|
|
|
134917
135076
|
}
|
|
134918
135077
|
}
|
|
134919
135078
|
if (aborted2) continue;
|
|
134920
|
-
await provider
|
|
135079
|
+
await resilientLabelTransition(provider, issue2.iid, state.label, targetState.label);
|
|
134921
135080
|
await log(workspaceDir, "review_skip_transition", {
|
|
134922
135081
|
project: projectName,
|
|
134923
135082
|
issueId: issue2.iid,
|
|
@@ -134934,6 +135093,7 @@ async function reviewSkipPass(opts) {
|
|
|
134934
135093
|
// lib/services/heartbeat/test-skip.ts
|
|
134935
135094
|
init_workflow();
|
|
134936
135095
|
init_audit();
|
|
135096
|
+
init_labels();
|
|
134937
135097
|
async function testSkipPass(opts) {
|
|
134938
135098
|
const { workspaceDir, projectName, workflow, provider, repoPath, gitPullTimeoutMs = 3e4, runCommand } = opts;
|
|
134939
135099
|
let transitions = 0;
|
|
@@ -134999,7 +135159,7 @@ async function testSkipPass(opts) {
|
|
|
134999
135159
|
const failedKey = typeof failedTransition === "string" ? failedTransition : failedTransition.target;
|
|
135000
135160
|
const failedState = workflow.states[failedKey];
|
|
135001
135161
|
if (failedState) {
|
|
135002
|
-
await provider
|
|
135162
|
+
await resilientLabelTransition(provider, issue2.iid, state.label, failedState.label);
|
|
135003
135163
|
transitions++;
|
|
135004
135164
|
}
|
|
135005
135165
|
}
|
|
@@ -135044,7 +135204,7 @@ async function testSkipPass(opts) {
|
|
|
135044
135204
|
}
|
|
135045
135205
|
}
|
|
135046
135206
|
if (aborted2) continue;
|
|
135047
|
-
await provider
|
|
135207
|
+
await resilientLabelTransition(provider, issue2.iid, state.label, targetState.label);
|
|
135048
135208
|
await log(workspaceDir, "test_skip_transition", {
|
|
135049
135209
|
project: projectName,
|
|
135050
135210
|
issueId: issue2.iid,
|
|
@@ -135061,6 +135221,7 @@ async function testSkipPass(opts) {
|
|
|
135061
135221
|
// lib/services/heartbeat/hold-escape.ts
|
|
135062
135222
|
init_workflow();
|
|
135063
135223
|
init_audit();
|
|
135224
|
+
init_labels();
|
|
135064
135225
|
async function holdEscapePass(opts) {
|
|
135065
135226
|
const { workspaceDir, projectName, workflow, provider } = opts;
|
|
135066
135227
|
let transitions = 0;
|
|
@@ -135094,7 +135255,7 @@ async function holdEscapePass(opts) {
|
|
|
135094
135255
|
}
|
|
135095
135256
|
await provider.closeIssue(issue2.iid);
|
|
135096
135257
|
await clearIssueRuntime(workspaceDir, project.slug, issue2.iid);
|
|
135097
|
-
await provider
|
|
135258
|
+
await resilientLabelTransition(provider, issue2.iid, state.label, terminalState.label);
|
|
135098
135259
|
await log(workspaceDir, "hold_escape_transition", {
|
|
135099
135260
|
project: projectName,
|
|
135100
135261
|
issueId: issue2.iid,
|
|
@@ -135958,21 +136119,6 @@ function registerHeartbeatService(api, pluginCtx) {
|
|
|
135958
136119
|
}
|
|
135959
136120
|
});
|
|
135960
136121
|
}
|
|
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
136122
|
var DEFAULT_TICK_TIMEOUT_MS = 5e4;
|
|
135977
136123
|
var _ticksTimedOut = 0;
|
|
135978
136124
|
async function withTickMutex(fn) {
|
|
@@ -136010,23 +136156,39 @@ async function runHeartbeatTick(ctx, logger6, mode) {
|
|
|
136010
136156
|
})
|
|
136011
136157
|
);
|
|
136012
136158
|
const tickFn = lifecycle ? () => lifecycle.track(mode === "repair" ? "recovery" : "heartbeat", {}, run) : run;
|
|
136013
|
-
let
|
|
136014
|
-
|
|
136015
|
-
|
|
136016
|
-
|
|
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
|
+
}
|
|
136017
136176
|
};
|
|
136177
|
+
const HARD_TICK_TIMEOUT_MS = 5 * 6e4;
|
|
136018
136178
|
const raceResult = await raceWithTimeout(wrappedTickFn, DEFAULT_TICK_TIMEOUT_MS, () => {
|
|
136019
136179
|
_ticksTimedOut++;
|
|
136020
136180
|
timedOut = true;
|
|
136021
136181
|
logger6.warn(`work_heartbeat ${mode} tick timed out after ${DEFAULT_TICK_TIMEOUT_MS}ms (total timeouts: ${_ticksTimedOut})`);
|
|
136022
|
-
|
|
136023
|
-
|
|
136024
|
-
|
|
136025
|
-
|
|
136026
|
-
|
|
136027
|
-
|
|
136028
|
-
|
|
136029
|
-
|
|
136182
|
+
const hardTimeout = setTimeout(() => {
|
|
136183
|
+
logger6.error("tick_mutex: hard timeout \u2014 forcing mutex release");
|
|
136184
|
+
_tickRunning[mode] = false;
|
|
136185
|
+
_anyTickRunning = false;
|
|
136186
|
+
}, HARD_TICK_TIMEOUT_MS);
|
|
136187
|
+
tickPromise.finally(() => {
|
|
136188
|
+
clearTimeout(hardTimeout);
|
|
136189
|
+
_tickRunning[mode] = false;
|
|
136190
|
+
_anyTickRunning = false;
|
|
136191
|
+
});
|
|
136030
136192
|
});
|
|
136031
136193
|
void raceResult;
|
|
136032
136194
|
} catch (err) {
|
|
@@ -138256,6 +138418,14 @@ async function pathExists(candidate) {
|
|
|
138256
138418
|
return false;
|
|
138257
138419
|
}
|
|
138258
138420
|
}
|
|
138421
|
+
async function isValidBinary(filePath) {
|
|
138422
|
+
try {
|
|
138423
|
+
const stat2 = await fs34.stat(filePath);
|
|
138424
|
+
return stat2.size > 0;
|
|
138425
|
+
} catch {
|
|
138426
|
+
return false;
|
|
138427
|
+
}
|
|
138428
|
+
}
|
|
138259
138429
|
function familyForStack(stack) {
|
|
138260
138430
|
if (NODE_STACKS.has(stack)) return "node";
|
|
138261
138431
|
if (PYTHON_STACKS.has(stack)) return "python";
|
|
@@ -138368,7 +138538,7 @@ function buildPythonBootstrapPrelude() {
|
|
|
138368
138538
|
|
|
138369
138539
|
# --- Shared toolchain (ruff, mypy, pip-audit) ---
|
|
138370
138540
|
TOOLCHAIN="$HOME/.openclaw/toolchains/python"
|
|
138371
|
-
if [ ! -x "$TOOLCHAIN/bin/ruff" ]; then
|
|
138541
|
+
if [ ! -x "$TOOLCHAIN/bin/ruff" ] || [ ! -s "$TOOLCHAIN/bin/ruff" ]; then
|
|
138372
138542
|
echo "[qa] Toolchain not found \u2014 provisioning..."
|
|
138373
138543
|
command -v uv >/dev/null 2>&1 || {
|
|
138374
138544
|
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
@@ -138506,16 +138676,22 @@ Install manually: curl -LsSf ${UV_INSTALL_URL} | sh`
|
|
|
138506
138676
|
var PYTHON_TOOLCHAIN_PACKAGES = ["ruff", "mypy", "pip-audit"];
|
|
138507
138677
|
var TOOLCHAIN_DIR = ".openclaw/toolchains/python";
|
|
138508
138678
|
var TOOLCHAIN_FINGERPRINT_FILE = "toolchain.sha256";
|
|
138509
|
-
function toolchainFingerprint() {
|
|
138510
|
-
|
|
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");
|
|
138511
138687
|
}
|
|
138512
138688
|
async function ensurePythonToolchain(runCommand, homeDir) {
|
|
138513
138689
|
const home = homeDir ?? process.env.HOME ?? "/tmp";
|
|
138514
138690
|
const toolchainPath = path34.join(home, TOOLCHAIN_DIR);
|
|
138515
138691
|
const ruffPath = path34.join(toolchainPath, "bin", "ruff");
|
|
138516
138692
|
const fingerprintPath = path34.join(toolchainPath, TOOLCHAIN_FINGERPRINT_FILE);
|
|
138517
|
-
const expectedFp = toolchainFingerprint();
|
|
138518
|
-
if (await
|
|
138693
|
+
const expectedFp = await toolchainFingerprint(runCommand);
|
|
138694
|
+
if (await isValidBinary(ruffPath)) {
|
|
138519
138695
|
try {
|
|
138520
138696
|
const currentFp = (await fs34.readFile(fingerprintPath, "utf-8")).trim();
|
|
138521
138697
|
if (currentFp === expectedFp) {
|
|
@@ -139112,17 +139288,6 @@ var scaffoldStep = {
|
|
|
139112
139288
|
mode: "scaffold",
|
|
139113
139289
|
runCommand: ctx.runCommand
|
|
139114
139290
|
});
|
|
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
139291
|
if (scaffold.stack && PYTHON_STACKS2.has(scaffold.stack) && payload.spec) {
|
|
139127
139292
|
try {
|
|
139128
139293
|
const contract = generateQaContract({
|
|
@@ -139137,6 +139302,17 @@ var scaffoldStep = {
|
|
|
139137
139302
|
ctx.log(`Warning: could not write qa.sh: ${err instanceof Error ? err.message : String(err)}`);
|
|
139138
139303
|
}
|
|
139139
139304
|
}
|
|
139305
|
+
if (!bootstrap.ready) {
|
|
139306
|
+
ctx.log(`Scaffold bootstrap failed: ${bootstrap.reason ?? "unknown reason"}`);
|
|
139307
|
+
return {
|
|
139308
|
+
...result.plannedPayload,
|
|
139309
|
+
step: "scaffold",
|
|
139310
|
+
scaffold: { created: false, reason: bootstrap.reason ?? "bootstrap_failed" }
|
|
139311
|
+
};
|
|
139312
|
+
}
|
|
139313
|
+
ctx.log(
|
|
139314
|
+
bootstrap.skipped ? `Scaffold bootstrap already current (${bootstrap.packageManager})` : `Scaffold bootstrap completed (${bootstrap.packageManager})`
|
|
139315
|
+
);
|
|
139140
139316
|
}
|
|
139141
139317
|
return {
|
|
139142
139318
|
...result.plannedPayload,
|
|
@@ -142147,6 +142323,7 @@ function shouldSuppressTelegramBootstrapReply(session, request2) {
|
|
|
142147
142323
|
}
|
|
142148
142324
|
|
|
142149
142325
|
// lib/dispatch/telegram-bootstrap-hook.ts
|
|
142326
|
+
var BOOTSTRAP_TIMEOUT_MS = 5 * 6e4;
|
|
142150
142327
|
var BOOTSTRAP_MESSAGES = {
|
|
142151
142328
|
ack: {
|
|
142152
142329
|
pt: "Recebi! Vou analisar e come\xE7ar a montar o projeto...",
|
|
@@ -142416,8 +142593,24 @@ async function classifyAndBootstrap(ctx, workspaceDir, conversationId, content)
|
|
|
142416
142593
|
await sendTelegramText(ctx, conversationId, buildClarificationMessage(parsed, pendingClarification, language));
|
|
142417
142594
|
return;
|
|
142418
142595
|
}
|
|
142419
|
-
|
|
142420
|
-
|
|
142596
|
+
bootstrapWithTimeout(ctx, conversationId, workspaceDir, incomingRequest, sourceRoute);
|
|
142597
|
+
}
|
|
142598
|
+
function bootstrapWithTimeout(ctx, conversationId, workspaceDir, request2, sourceRoute) {
|
|
142599
|
+
raceWithTimeout(
|
|
142600
|
+
() => continueBootstrap(ctx, conversationId, workspaceDir, request2, sourceRoute),
|
|
142601
|
+
BOOTSTRAP_TIMEOUT_MS,
|
|
142602
|
+
() => {
|
|
142603
|
+
ctx.logger.warn({ conversationId }, "Bootstrap pipeline timed out after 5 minutes");
|
|
142604
|
+
upsertTelegramBootstrapSession(workspaceDir, {
|
|
142605
|
+
conversationId,
|
|
142606
|
+
rawIdea: request2.rawIdea,
|
|
142607
|
+
status: "failed",
|
|
142608
|
+
error: "Pipeline timeout (5min)"
|
|
142609
|
+
}).catch(() => {
|
|
142610
|
+
});
|
|
142611
|
+
}
|
|
142612
|
+
).catch((err) => {
|
|
142613
|
+
logBootstrapWarning(ctx, `[telegram-bootstrap] pipeline error: ${err instanceof Error ? err.message : String(err)}`);
|
|
142421
142614
|
});
|
|
142422
142615
|
}
|
|
142423
142616
|
async function continueBootstrap(ctx, conversationId, workspaceDir, request2, sourceRoute) {
|
|
@@ -142432,6 +142625,24 @@ async function continueBootstrap(ctx, conversationId, workspaceDir, request2, so
|
|
|
142432
142625
|
}
|
|
142433
142626
|
const projectName = request2.projectName ?? void 0;
|
|
142434
142627
|
const stackHint = request2.stackHint;
|
|
142628
|
+
if (!stackHint) {
|
|
142629
|
+
const existingSession = await readTelegramBootstrapSession(workspaceDir, conversationId);
|
|
142630
|
+
const lang = existingSession?.language ?? "pt";
|
|
142631
|
+
await upsertTelegramBootstrapSession(workspaceDir, {
|
|
142632
|
+
conversationId,
|
|
142633
|
+
rawIdea: request2.rawIdea,
|
|
142634
|
+
projectName: request2.projectName ?? void 0,
|
|
142635
|
+
status: "clarifying",
|
|
142636
|
+
pendingClarification: "stack",
|
|
142637
|
+
language: lang
|
|
142638
|
+
});
|
|
142639
|
+
await sendTelegramText(ctx, conversationId, buildClarificationMessage(
|
|
142640
|
+
{ rawIdea: request2.rawIdea, projectName: request2.projectName ?? void 0, stackHint: request2.stackHint ?? void 0 },
|
|
142641
|
+
"stack",
|
|
142642
|
+
lang
|
|
142643
|
+
));
|
|
142644
|
+
return;
|
|
142645
|
+
}
|
|
142435
142646
|
const candidateSlug = inferProjectSlug(projectName ?? request2.rawIdea);
|
|
142436
142647
|
if (candidateSlug) {
|
|
142437
142648
|
const projects = await readProjects(workspaceDir).catch(() => null);
|
|
@@ -142680,11 +142891,9 @@ function registerTelegramBootstrapHook(api, ctx) {
|
|
|
142680
142891
|
repoPath: existingSession.repoPath ?? null
|
|
142681
142892
|
};
|
|
142682
142893
|
ctx.logger.info(`[telegram-bootstrap] clarification resolved: stack=${mergedRequest.stackHint}, idea="${mergedRequest.rawIdea}" (conversation: ${conversationId})`);
|
|
142683
|
-
|
|
142894
|
+
bootstrapWithTimeout(ctx, conversationId, workspaceDir, mergedRequest, existingSession.sourceRoute ?? {
|
|
142684
142895
|
channel: "telegram",
|
|
142685
142896
|
channelId: conversationId
|
|
142686
|
-
}).catch((err) => {
|
|
142687
|
-
logBootstrapWarning(ctx, `[telegram-bootstrap] unhandled pipeline error: ${err instanceof Error ? err.message : String(err)}`);
|
|
142688
142897
|
});
|
|
142689
142898
|
return;
|
|
142690
142899
|
}
|
|
@@ -142752,11 +142961,9 @@ function registerTelegramBootstrapHook(api, ctx) {
|
|
|
142752
142961
|
await sendTelegramText(ctx, conversationId, buildClarificationMessage(parsed, pendingClarification, language));
|
|
142753
142962
|
return;
|
|
142754
142963
|
}
|
|
142755
|
-
|
|
142964
|
+
bootstrapWithTimeout(ctx, conversationId, workspaceDir, incomingRequest, {
|
|
142756
142965
|
channel: "telegram",
|
|
142757
142966
|
channelId: conversationId
|
|
142758
|
-
}).catch((err) => {
|
|
142759
|
-
logBootstrapWarning(ctx, `[telegram-bootstrap] unhandled pipeline error: ${err instanceof Error ? err.message : String(err)}`);
|
|
142760
142967
|
});
|
|
142761
142968
|
});
|
|
142762
142969
|
}
|