@mestreyoda/fabrica 0.2.2 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +216 -71
- package/dist/index.js.map +4 -4
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -110821,7 +110821,7 @@ var init_registry = __esm({
|
|
|
110821
110821
|
});
|
|
110822
110822
|
|
|
110823
110823
|
// lib/config/merge.ts
|
|
110824
|
-
function mergeConfig(base, overlay) {
|
|
110824
|
+
function mergeConfig(base, overlay, traceOpts) {
|
|
110825
110825
|
const merged = {};
|
|
110826
110826
|
if (base.roles || overlay.roles) {
|
|
110827
110827
|
merged.roles = { ...base.roles };
|
|
@@ -110872,6 +110872,44 @@ function mergeConfig(base, overlay) {
|
|
|
110872
110872
|
} : void 0
|
|
110873
110873
|
};
|
|
110874
110874
|
}
|
|
110875
|
+
if (traceOpts) {
|
|
110876
|
+
const { baseLabel, overlayLabel } = traceOpts;
|
|
110877
|
+
const trace2 = {};
|
|
110878
|
+
if (merged.workflow) {
|
|
110879
|
+
for (const key of ["initial", "reviewPolicy", "testPolicy", "roleExecution", "maxWorkersPerLevel"]) {
|
|
110880
|
+
if (merged.workflow[key] !== void 0) {
|
|
110881
|
+
const fromOverlay = overlay.workflow?.[key] !== void 0;
|
|
110882
|
+
trace2[`workflow.${key}`] = fromOverlay ? overlayLabel : baseLabel;
|
|
110883
|
+
}
|
|
110884
|
+
}
|
|
110885
|
+
}
|
|
110886
|
+
if (merged.timeouts) {
|
|
110887
|
+
for (const [key, value] of Object.entries(merged.timeouts)) {
|
|
110888
|
+
if (value !== void 0) {
|
|
110889
|
+
const fromOverlay = overlay.timeouts?.[key] !== void 0;
|
|
110890
|
+
trace2[`timeouts.${key}`] = fromOverlay ? overlayLabel : baseLabel;
|
|
110891
|
+
}
|
|
110892
|
+
}
|
|
110893
|
+
}
|
|
110894
|
+
if (merged.roles) {
|
|
110895
|
+
for (const [roleId, roleValue] of Object.entries(merged.roles)) {
|
|
110896
|
+
if (roleValue === false) {
|
|
110897
|
+
trace2[`roles.${roleId}`] = overlay.roles?.[roleId] === false ? overlayLabel : baseLabel;
|
|
110898
|
+
continue;
|
|
110899
|
+
}
|
|
110900
|
+
if (typeof roleValue === "object") {
|
|
110901
|
+
for (const key of ["defaultLevel", "levels", "completionResults"]) {
|
|
110902
|
+
if (roleValue[key] !== void 0) {
|
|
110903
|
+
const overlayRole = overlay.roles?.[roleId];
|
|
110904
|
+
const fromOverlay = typeof overlayRole === "object" && overlayRole?.[key] !== void 0;
|
|
110905
|
+
trace2[`roles.${roleId}.${key}`] = fromOverlay ? overlayLabel : baseLabel;
|
|
110906
|
+
}
|
|
110907
|
+
}
|
|
110908
|
+
}
|
|
110909
|
+
}
|
|
110910
|
+
}
|
|
110911
|
+
return Object.assign(merged, { _trace: trace2 });
|
|
110912
|
+
}
|
|
110875
110913
|
return merged;
|
|
110876
110914
|
}
|
|
110877
110915
|
function mergeWorkflowStates(base, overlay) {
|
|
@@ -110917,42 +110955,10 @@ function mergeRoleOverride(base, overlay) {
|
|
|
110917
110955
|
};
|
|
110918
110956
|
}
|
|
110919
110957
|
function mergeConfigWithTrace(base, overlay, baseLabel, overlayLabel) {
|
|
110920
|
-
const
|
|
110921
|
-
const trace2 = {};
|
|
110922
|
-
|
|
110923
|
-
|
|
110924
|
-
if (merged.workflow[key] !== void 0) {
|
|
110925
|
-
const fromOverlay = overlay.workflow?.[key] !== void 0;
|
|
110926
|
-
trace2[`workflow.${key}`] = fromOverlay ? overlayLabel : baseLabel;
|
|
110927
|
-
}
|
|
110928
|
-
}
|
|
110929
|
-
}
|
|
110930
|
-
if (merged.timeouts) {
|
|
110931
|
-
for (const [key, value] of Object.entries(merged.timeouts)) {
|
|
110932
|
-
if (value !== void 0) {
|
|
110933
|
-
const fromOverlay = overlay.timeouts?.[key] !== void 0;
|
|
110934
|
-
trace2[`timeouts.${key}`] = fromOverlay ? overlayLabel : baseLabel;
|
|
110935
|
-
}
|
|
110936
|
-
}
|
|
110937
|
-
}
|
|
110938
|
-
if (merged.roles) {
|
|
110939
|
-
for (const [roleId, roleValue] of Object.entries(merged.roles)) {
|
|
110940
|
-
if (roleValue === false) {
|
|
110941
|
-
trace2[`roles.${roleId}`] = overlay.roles?.[roleId] === false ? overlayLabel : baseLabel;
|
|
110942
|
-
continue;
|
|
110943
|
-
}
|
|
110944
|
-
if (typeof roleValue === "object") {
|
|
110945
|
-
for (const key of ["defaultLevel", "levels", "completionResults"]) {
|
|
110946
|
-
if (roleValue[key] !== void 0) {
|
|
110947
|
-
const overlayRole = overlay.roles?.[roleId];
|
|
110948
|
-
const fromOverlay = typeof overlayRole === "object" && overlayRole?.[key] !== void 0;
|
|
110949
|
-
trace2[`roles.${roleId}.${key}`] = fromOverlay ? overlayLabel : baseLabel;
|
|
110950
|
-
}
|
|
110951
|
-
}
|
|
110952
|
-
}
|
|
110953
|
-
}
|
|
110954
|
-
}
|
|
110955
|
-
return { merged, trace: trace2 };
|
|
110958
|
+
const result = mergeConfig(base, overlay, { baseLabel, overlayLabel });
|
|
110959
|
+
const trace2 = result._trace ?? {};
|
|
110960
|
+
delete result._trace;
|
|
110961
|
+
return { merged: result, trace: trace2 };
|
|
110956
110962
|
}
|
|
110957
110963
|
var init_merge = __esm({
|
|
110958
110964
|
"lib/config/merge.ts"() {
|
|
@@ -111324,8 +111330,8 @@ import fsSync from "node:fs";
|
|
|
111324
111330
|
import path5 from "node:path";
|
|
111325
111331
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
111326
111332
|
function getCurrentVersion() {
|
|
111327
|
-
if ("0.2.
|
|
111328
|
-
return "0.2.
|
|
111333
|
+
if ("0.2.6") {
|
|
111334
|
+
return "0.2.6";
|
|
111329
111335
|
}
|
|
111330
111336
|
try {
|
|
111331
111337
|
const pkgPath = path5.join(THIS_DIR, "..", "..", "package.json");
|
|
@@ -117132,6 +117138,8 @@ var GitHubRateLimitError = class extends Error {
|
|
|
117132
117138
|
this.name = "GitHubRateLimitError";
|
|
117133
117139
|
}
|
|
117134
117140
|
};
|
|
117141
|
+
var MAX_RATE_LIMIT_RETRIES = 2;
|
|
117142
|
+
var JITTER_MAX_MS = 5e3;
|
|
117135
117143
|
var MAX_ENTRIES = 50;
|
|
117136
117144
|
var policyCache = /* @__PURE__ */ new Map();
|
|
117137
117145
|
var accessOrder = [];
|
|
@@ -117167,9 +117175,27 @@ function getProviderPolicy(providerKey) {
|
|
|
117167
117175
|
accessOrder.push(providerKey);
|
|
117168
117176
|
return policy;
|
|
117169
117177
|
}
|
|
117170
|
-
function withResilience(fn, providerKey) {
|
|
117178
|
+
async function withResilience(fn, providerKey, opts) {
|
|
117171
117179
|
const policy = providerKey ? getProviderPolicy(providerKey) : getProviderPolicy("__global__");
|
|
117172
|
-
|
|
117180
|
+
const jitterMax = opts?.jitterMaxMs ?? JITTER_MAX_MS;
|
|
117181
|
+
const withRateLimitRetry = async () => {
|
|
117182
|
+
let lastError;
|
|
117183
|
+
for (let attempt = 0; attempt <= MAX_RATE_LIMIT_RETRIES; attempt++) {
|
|
117184
|
+
try {
|
|
117185
|
+
return await fn();
|
|
117186
|
+
} catch (err) {
|
|
117187
|
+
if (err instanceof GitHubRateLimitError && attempt < MAX_RATE_LIMIT_RETRIES) {
|
|
117188
|
+
lastError = err;
|
|
117189
|
+
const jitter = jitterMax > 0 ? Math.floor(Math.random() * jitterMax) : 0;
|
|
117190
|
+
await new Promise((resolve3) => setTimeout(resolve3, err.retryAfterMs + jitter));
|
|
117191
|
+
continue;
|
|
117192
|
+
}
|
|
117193
|
+
throw err;
|
|
117194
|
+
}
|
|
117195
|
+
}
|
|
117196
|
+
throw lastError;
|
|
117197
|
+
};
|
|
117198
|
+
return policy.execute(withRateLimitRetry);
|
|
117173
117199
|
}
|
|
117174
117200
|
var providerPolicy = getProviderPolicy("__legacy_global__");
|
|
117175
117201
|
|
|
@@ -118078,7 +118104,9 @@ var GitHubProvider = class {
|
|
|
118078
118104
|
if (result.code != null && result.code !== 0) {
|
|
118079
118105
|
const errText = result.stderr?.trim() ?? "";
|
|
118080
118106
|
if (errText.includes("rate limit") || errText.includes("429")) {
|
|
118081
|
-
|
|
118107
|
+
const retryMatch = errText.match(/retry after (\d+)/i);
|
|
118108
|
+
const retryMs = retryMatch ? parseInt(retryMatch[1], 10) * 1e3 : 6e4;
|
|
118109
|
+
throw new GitHubRateLimitError(retryMs);
|
|
118082
118110
|
}
|
|
118083
118111
|
throw new Error(errText || `gh api ${method} ${endpoint} failed with exit code ${result.code}`);
|
|
118084
118112
|
}
|
|
@@ -122967,7 +122995,8 @@ ${roleInstructions}`;
|
|
|
122967
122995
|
return effortPrefix ?? roleInstructions ?? "";
|
|
122968
122996
|
}
|
|
122969
122997
|
function sendToAgent(sessionKey, taskMessage, opts) {
|
|
122970
|
-
const
|
|
122998
|
+
const epoch = opts.dispatchEpoch ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
122999
|
+
const idempotencyKey = `fabrica-${opts.projectName}-${opts.issueId}-${opts.role}-${opts.level ?? "unknown"}-${opts.slotIndex ?? 0}-${opts.fromLabel ?? "unknown"}-${sessionKey}-${epoch}`;
|
|
122971
123000
|
if (opts.runtime?.subagent?.run) {
|
|
122972
123001
|
opts.runtime.subagent.run({
|
|
122973
123002
|
sessionKey,
|
|
@@ -123388,6 +123417,7 @@ async function dispatchTask(opts) {
|
|
|
123388
123417
|
error: err.message ?? String(err)
|
|
123389
123418
|
}).catch((auditErr) => console.error("[fabrica] silent-catch:", auditErr.message));
|
|
123390
123419
|
});
|
|
123420
|
+
const dispatchEpoch = (/* @__PURE__ */ new Date()).toISOString();
|
|
123391
123421
|
sendToAgent(sessionKey, taskMessage, {
|
|
123392
123422
|
agentId,
|
|
123393
123423
|
projectName: project.name,
|
|
@@ -123405,7 +123435,8 @@ async function dispatchTask(opts) {
|
|
|
123405
123435
|
roleInstructions.trim() || void 0
|
|
123406
123436
|
) || void 0,
|
|
123407
123437
|
runCommand: rc,
|
|
123408
|
-
runtime
|
|
123438
|
+
runtime,
|
|
123439
|
+
dispatchEpoch
|
|
123409
123440
|
});
|
|
123410
123441
|
await recordIssueLifecycle({
|
|
123411
123442
|
workspaceDir,
|
|
@@ -130457,7 +130488,10 @@ async function cleanupExpired(workspaceDir, ttlMs = DEFAULT_TTL_MS2) {
|
|
|
130457
130488
|
const cutoff = Date.now() - ttlMs;
|
|
130458
130489
|
const kept = entries.filter((e2) => e2.ts >= cutoff);
|
|
130459
130490
|
if (kept.length < entries.length) {
|
|
130460
|
-
|
|
130491
|
+
const content = kept.map((e2) => JSON.stringify(e2)).join("\n") + (kept.length > 0 ? "\n" : "");
|
|
130492
|
+
const tmpPath = filePath + ".tmp";
|
|
130493
|
+
await fs28.writeFile(tmpPath, content, "utf-8");
|
|
130494
|
+
await fs28.rename(tmpPath, filePath);
|
|
130461
130495
|
}
|
|
130462
130496
|
}
|
|
130463
130497
|
|
|
@@ -130543,30 +130577,52 @@ async function projectTick(opts) {
|
|
|
130543
130577
|
}
|
|
130544
130578
|
if (role === "reviewer" || role === "tester") {
|
|
130545
130579
|
const issueRuntime = getIssueRuntime(fresh, issue2.iid);
|
|
130546
|
-
|
|
130580
|
+
let prSelector = getCanonicalPrSelector(fresh, issue2.iid);
|
|
130547
130581
|
if (!prSelector?.prNumber) {
|
|
130548
|
-
const
|
|
130549
|
-
|
|
130550
|
-
|
|
130551
|
-
|
|
130552
|
-
await
|
|
130553
|
-
|
|
130554
|
-
|
|
130555
|
-
|
|
130556
|
-
|
|
130557
|
-
from: currentLabel,
|
|
130558
|
-
to: feedbackLabel,
|
|
130559
|
-
prState: issueRuntime?.currentPrState ?? null,
|
|
130560
|
-
prUrl: issueRuntime?.currentPrUrl ?? null,
|
|
130561
|
-
prNumber: null,
|
|
130562
|
-
currentIssueMatch: null,
|
|
130563
|
-
reason: "missing_canonical_pr"
|
|
130582
|
+
const fallbackStatus = await provider.getPrStatus(issue2.iid).catch(() => null);
|
|
130583
|
+
const hasFallbackPr = !!fallbackStatus?.url && !!fallbackStatus.number && fallbackStatus.state !== PrState.MERGED && fallbackStatus.state !== PrState.CLOSED && fallbackStatus.currentIssueMatch !== false;
|
|
130584
|
+
if (hasFallbackPr && fallbackStatus?.number) {
|
|
130585
|
+
if (!dryRun) {
|
|
130586
|
+
await updateIssueRuntime(workspaceDir, projectSlug, issue2.iid, {
|
|
130587
|
+
currentPrNumber: fallbackStatus.number,
|
|
130588
|
+
currentPrUrl: fallbackStatus.url,
|
|
130589
|
+
currentPrState: fallbackStatus.state ?? null
|
|
130590
|
+
}).catch(() => {
|
|
130564
130591
|
});
|
|
130565
|
-
} catch {
|
|
130566
130592
|
}
|
|
130593
|
+
const runtimeKey = String(issue2.iid);
|
|
130594
|
+
fresh.issueRuntime = fresh.issueRuntime ?? {};
|
|
130595
|
+
fresh.issueRuntime[runtimeKey] = {
|
|
130596
|
+
...fresh.issueRuntime[runtimeKey] ?? {},
|
|
130597
|
+
currentPrNumber: fallbackStatus.number,
|
|
130598
|
+
currentPrUrl: fallbackStatus.url ?? null,
|
|
130599
|
+
currentPrState: fallbackStatus.state ?? null
|
|
130600
|
+
};
|
|
130601
|
+
prSelector = { prNumber: fallbackStatus.number };
|
|
130602
|
+
} else {
|
|
130603
|
+
const feedbackLabel = getFeedbackQueueLabel(workflow);
|
|
130604
|
+
if (!dryRun && feedbackLabel && feedbackLabel !== currentLabel) {
|
|
130605
|
+
try {
|
|
130606
|
+
await provider.transitionLabel(issue2.iid, currentLabel, feedbackLabel);
|
|
130607
|
+
await log(workspaceDir, "queue_pr_guard", {
|
|
130608
|
+
project: project.name,
|
|
130609
|
+
projectSlug,
|
|
130610
|
+
issueId: issue2.iid,
|
|
130611
|
+
role,
|
|
130612
|
+
from: currentLabel,
|
|
130613
|
+
to: feedbackLabel,
|
|
130614
|
+
prState: issueRuntime?.currentPrState ?? null,
|
|
130615
|
+
prUrl: issueRuntime?.currentPrUrl ?? null,
|
|
130616
|
+
prNumber: null,
|
|
130617
|
+
currentIssueMatch: null,
|
|
130618
|
+
reason: "missing_canonical_pr"
|
|
130619
|
+
});
|
|
130620
|
+
} catch {
|
|
130621
|
+
}
|
|
130622
|
+
}
|
|
130623
|
+
skipped.push({ role, reason: "No canonical bound PR for review/test cycle" });
|
|
130624
|
+
continue;
|
|
130567
130625
|
}
|
|
130568
|
-
skipped.push({ role, reason: "No canonical bound PR for review/test cycle" });
|
|
130569
|
-
continue;
|
|
130570
130626
|
}
|
|
130571
130627
|
const prStatus = await provider.getPrStatus(issue2.iid, prSelector);
|
|
130572
130628
|
const hasReviewablePr = !!prStatus.url && prStatus.state !== PrState.MERGED && prStatus.state !== PrState.CLOSED && prStatus.currentIssueMatch !== false;
|
|
@@ -132184,23 +132240,46 @@ async function checkProjectActive(workspaceDir, slug) {
|
|
|
132184
132240
|
);
|
|
132185
132241
|
}
|
|
132186
132242
|
|
|
132243
|
+
// lib/services/heartbeat/wake-bridge.ts
|
|
132244
|
+
var _wakeCallback = null;
|
|
132245
|
+
function setPluginWakeHandler(cb) {
|
|
132246
|
+
_wakeCallback = cb;
|
|
132247
|
+
}
|
|
132248
|
+
async function wakeHeartbeat(reason) {
|
|
132249
|
+
await _wakeCallback?.(reason);
|
|
132250
|
+
}
|
|
132251
|
+
|
|
132187
132252
|
// lib/services/heartbeat/index.ts
|
|
132188
132253
|
function registerHeartbeatService(api, pluginCtx) {
|
|
132189
132254
|
let sharedIntervalId = null;
|
|
132190
|
-
let
|
|
132255
|
+
let _pendingWake = false;
|
|
132191
132256
|
api.registerService({
|
|
132192
132257
|
id: "fabrica-heartbeat",
|
|
132193
132258
|
start: async (svcCtx) => {
|
|
132194
132259
|
const { intervalSeconds } = resolveHeartbeatConfig(pluginCtx.pluginConfig);
|
|
132195
132260
|
const intervalMs = intervalSeconds * 1e3;
|
|
132196
|
-
setTimeout(() => runHeartbeatTick(pluginCtx, svcCtx.logger, "
|
|
132261
|
+
setTimeout(() => runHeartbeatTick(pluginCtx, svcCtx.logger, "full"), 2e3);
|
|
132197
132262
|
sharedIntervalId = setInterval(() => {
|
|
132198
|
-
|
|
132199
|
-
|
|
132200
|
-
|
|
132263
|
+
runHeartbeatTick(pluginCtx, svcCtx.logger, "full").finally(() => {
|
|
132264
|
+
if (_pendingWake) {
|
|
132265
|
+
_pendingWake = false;
|
|
132266
|
+
svcCtx.logger.info("heartbeat_wake: running deferred full tick");
|
|
132267
|
+
runHeartbeatTick(pluginCtx, svcCtx.logger, "full");
|
|
132268
|
+
}
|
|
132269
|
+
});
|
|
132201
132270
|
}, intervalMs);
|
|
132271
|
+
setPluginWakeHandler(async (reason) => {
|
|
132272
|
+
if (_anyTickRunning) {
|
|
132273
|
+
_pendingWake = true;
|
|
132274
|
+
svcCtx.logger.info(`heartbeat_wake: deferred (tick-in-progress), reason=${reason}`);
|
|
132275
|
+
return;
|
|
132276
|
+
}
|
|
132277
|
+
svcCtx.logger.info(`heartbeat_wake: running full tick, reason=${reason}`);
|
|
132278
|
+
await runHeartbeatTick(pluginCtx, svcCtx.logger, "full");
|
|
132279
|
+
});
|
|
132202
132280
|
},
|
|
132203
132281
|
stop: async (svcCtx) => {
|
|
132282
|
+
setPluginWakeHandler(null);
|
|
132204
132283
|
if (sharedIntervalId) {
|
|
132205
132284
|
clearInterval(sharedIntervalId);
|
|
132206
132285
|
sharedIntervalId = null;
|
|
@@ -139086,7 +139165,7 @@ function registerTelegramBootstrapHook(api, ctx) {
|
|
|
139086
139165
|
ctx.logger.info(`[telegram-bootstrap] stale received session (expired) \u2014 restarting pipeline for conversation ${conversationId}`);
|
|
139087
139166
|
}
|
|
139088
139167
|
}
|
|
139089
|
-
if (!parsed.projectName && ctx.runtime?.subagent?.run) {
|
|
139168
|
+
if (!parsed.projectName && ctx.runtime?.subagent?.run != null) {
|
|
139090
139169
|
await upsertTelegramBootstrapSession(workspaceDir, {
|
|
139091
139170
|
conversationId,
|
|
139092
139171
|
rawIdea: parsed.rawIdea,
|
|
@@ -139462,6 +139541,8 @@ function registerReactiveDispatchHooks(api, ctx) {
|
|
|
139462
139541
|
api.on("after_tool_call", async (event, _eventCtx) => {
|
|
139463
139542
|
if (!COMPLETION_TOOLS.has(event.toolName)) return;
|
|
139464
139543
|
ctx.runtime?.system.requestHeartbeatNow({ reason: "work_finish", coalesceMs: 2e3 });
|
|
139544
|
+
wakeHeartbeat("work_finish").catch(() => {
|
|
139545
|
+
});
|
|
139465
139546
|
});
|
|
139466
139547
|
api.on("agent_end", async (_event, eventCtx) => {
|
|
139467
139548
|
const sessionKey = eventCtx.sessionKey;
|
|
@@ -139469,6 +139550,8 @@ function registerReactiveDispatchHooks(api, ctx) {
|
|
|
139469
139550
|
const parsed = parseFabricaSessionKey(sessionKey);
|
|
139470
139551
|
if (!parsed) return;
|
|
139471
139552
|
ctx.runtime?.system.requestHeartbeatNow({ reason: "agent_end", coalesceMs: 2e3 });
|
|
139553
|
+
wakeHeartbeat("agent_end").catch(() => {
|
|
139554
|
+
});
|
|
139472
139555
|
});
|
|
139473
139556
|
api.on("subagent_spawned", async (event, _eventCtx) => {
|
|
139474
139557
|
const sessionKey = event.childSessionKey;
|
|
@@ -139478,6 +139561,7 @@ function registerReactiveDispatchHooks(api, ctx) {
|
|
|
139478
139561
|
}
|
|
139479
139562
|
|
|
139480
139563
|
// lib/dispatch/subagent-lifecycle-hook.ts
|
|
139564
|
+
init_workflow();
|
|
139481
139565
|
function registerSubagentLifecycleHook(api, ctx) {
|
|
139482
139566
|
const workspaceDir = resolveWorkspaceDir(ctx.config);
|
|
139483
139567
|
if (!workspaceDir) return;
|
|
@@ -139501,6 +139585,67 @@ function registerSubagentLifecycleHook(api, ctx) {
|
|
|
139501
139585
|
ctx.logger.info(
|
|
139502
139586
|
`subagent_ended: worker ${role} in "${projectName}" ended with outcome=${event.outcome ?? "unknown"} (session=${sessionKey})`
|
|
139503
139587
|
);
|
|
139588
|
+
try {
|
|
139589
|
+
const projects = await readProjects(workspaceDir);
|
|
139590
|
+
const projectEntry = Object.entries(projects.projects).find(
|
|
139591
|
+
([, p]) => p.name === projectName
|
|
139592
|
+
);
|
|
139593
|
+
if (!projectEntry) return;
|
|
139594
|
+
const [projectSlug, project] = projectEntry;
|
|
139595
|
+
const roleWorker = project.workers[role];
|
|
139596
|
+
if (!roleWorker) return;
|
|
139597
|
+
let foundLevel;
|
|
139598
|
+
let foundSlotIndex;
|
|
139599
|
+
let foundSlot;
|
|
139600
|
+
for (const [level, slots] of Object.entries(roleWorker.levels)) {
|
|
139601
|
+
for (let i2 = 0; i2 < slots.length; i2++) {
|
|
139602
|
+
if (slots[i2].sessionKey === sessionKey && slots[i2].active) {
|
|
139603
|
+
foundLevel = level;
|
|
139604
|
+
foundSlotIndex = i2;
|
|
139605
|
+
foundSlot = slots[i2];
|
|
139606
|
+
break;
|
|
139607
|
+
}
|
|
139608
|
+
}
|
|
139609
|
+
if (foundSlot) break;
|
|
139610
|
+
}
|
|
139611
|
+
if (!foundSlot || foundLevel == null || foundSlotIndex == null) return;
|
|
139612
|
+
const issueId = foundSlot.issueId;
|
|
139613
|
+
await deactivateWorker(workspaceDir, projectSlug, role, {
|
|
139614
|
+
level: foundLevel,
|
|
139615
|
+
slotIndex: foundSlotIndex
|
|
139616
|
+
});
|
|
139617
|
+
if (issueId) {
|
|
139618
|
+
try {
|
|
139619
|
+
const config2 = await loadConfig(workspaceDir, projectSlug);
|
|
139620
|
+
const activeLabel = getActiveLabel(config2.workflow, role);
|
|
139621
|
+
const revertLabel = getRevertLabel(config2.workflow, role);
|
|
139622
|
+
const { provider } = await createProvider({
|
|
139623
|
+
repo: project.repo,
|
|
139624
|
+
provider: project.provider,
|
|
139625
|
+
runCommand: ctx.runCommand
|
|
139626
|
+
});
|
|
139627
|
+
const issue2 = await provider.getIssue(Number(issueId));
|
|
139628
|
+
if (issue2.labels.includes(activeLabel)) {
|
|
139629
|
+
await provider.transitionLabel(Number(issueId), activeLabel, revertLabel);
|
|
139630
|
+
}
|
|
139631
|
+
} catch {
|
|
139632
|
+
}
|
|
139633
|
+
}
|
|
139634
|
+
await log(workspaceDir, "subagent_ended_slot_cleanup", {
|
|
139635
|
+
sessionKey,
|
|
139636
|
+
project: projectName,
|
|
139637
|
+
role,
|
|
139638
|
+
level: foundLevel,
|
|
139639
|
+
slotIndex: foundSlotIndex,
|
|
139640
|
+
issueId,
|
|
139641
|
+
outcome: event.outcome ?? "unknown"
|
|
139642
|
+
}).catch(() => {
|
|
139643
|
+
});
|
|
139644
|
+
wakeHeartbeat("subagent_ended").catch(() => {
|
|
139645
|
+
});
|
|
139646
|
+
} catch (err) {
|
|
139647
|
+
ctx.logger.warn(`subagent_ended_slot_cleanup failed: ${err.message}`);
|
|
139648
|
+
}
|
|
139504
139649
|
});
|
|
139505
139650
|
}
|
|
139506
139651
|
|