@mestreyoda/fabrica 0.1.12 → 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 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.12") {
111333
- return "0.1.12";
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");
@@ -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 tickPromise;
136067
- const wrappedTickFn = () => {
136068
- tickPromise = tickFn();
136069
- return tickPromise;
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
- if (tickPromise) {
136076
- tickPromise.finally(() => {
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
- return createHash4("sha256").update(PYTHON_TOOLCHAIN_PACKAGES.join(",")).digest("hex");
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();