@mestreyoda/fabrica 0.2.0 → 0.2.2
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 +120 -271
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -111324,8 +111324,8 @@ import fsSync from "node:fs";
|
|
|
111324
111324
|
import path5 from "node:path";
|
|
111325
111325
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
111326
111326
|
function getCurrentVersion() {
|
|
111327
|
-
if ("0.2.
|
|
111328
|
-
return "0.2.
|
|
111327
|
+
if ("0.2.2") {
|
|
111328
|
+
return "0.2.2";
|
|
111329
111329
|
}
|
|
111330
111330
|
try {
|
|
111331
111331
|
const pkgPath = path5.join(THIS_DIR, "..", "..", "package.json");
|
|
@@ -112302,7 +112302,7 @@ function resolve2(config2, sourceLayers, trace2) {
|
|
|
112302
112302
|
dispatchMs: config2.timeouts?.dispatchMs ?? 6e5,
|
|
112303
112303
|
staleWorkerHours: config2.timeouts?.staleWorkerHours ?? 2,
|
|
112304
112304
|
sessionContextBudget: config2.timeouts?.sessionContextBudget ?? 0.6,
|
|
112305
|
-
stallTimeoutMinutes: config2.timeouts?.stallTimeoutMinutes ??
|
|
112305
|
+
stallTimeoutMinutes: config2.timeouts?.stallTimeoutMinutes ?? 5,
|
|
112306
112306
|
sessionConfirmAttempts: config2.timeouts?.sessionConfirmAttempts ?? 5,
|
|
112307
112307
|
sessionConfirmDelayMs: config2.timeouts?.sessionConfirmDelayMs ?? 250,
|
|
112308
112308
|
sessionLabelMaxLength: config2.timeouts?.sessionLabelMaxLength ?? 64,
|
|
@@ -124578,97 +124578,6 @@ function createProjectRegisterTool(ctx) {
|
|
|
124578
124578
|
import { jsonResult as jsonResult15 } from "openclaw/plugin-sdk";
|
|
124579
124579
|
init_audit();
|
|
124580
124580
|
|
|
124581
|
-
// lib/services/heartbeat/diagnostic.ts
|
|
124582
|
-
async function diagnoseStall(input) {
|
|
124583
|
-
const { owner, repo, issueId, dispatchAttemptCount } = input;
|
|
124584
|
-
if ((dispatchAttemptCount ?? 0) >= 2) {
|
|
124585
|
-
const hasArtifacts = await checkForArtifacts(owner, repo, issueId);
|
|
124586
|
-
if (!hasArtifacts) {
|
|
124587
|
-
return { action: "needs_human_review", reason: "stall", evidence: `${dispatchAttemptCount} attempts, zero artifacts` };
|
|
124588
|
-
}
|
|
124589
|
-
}
|
|
124590
|
-
const pr = await findPrForIssue(owner, repo, issueId);
|
|
124591
|
-
if (pr) {
|
|
124592
|
-
const qaStatus = await checkPrQaStatus(owner, repo, pr.number);
|
|
124593
|
-
if (qaStatus === "pass") {
|
|
124594
|
-
return { action: "transition_to_review", reason: "stall", evidence: `PR #${pr.number} exists, QA passing` };
|
|
124595
|
-
}
|
|
124596
|
-
return { action: "redispatch_same_level", reason: "stall", evidence: `PR #${pr.number} exists, QA ${qaStatus}` };
|
|
124597
|
-
}
|
|
124598
|
-
const hasCommits = await checkForBranchCommits(owner, repo, issueId);
|
|
124599
|
-
if (hasCommits) {
|
|
124600
|
-
return { action: "nudge_open_pr", reason: "stall", evidence: "Branch has commits but no PR" };
|
|
124601
|
-
}
|
|
124602
|
-
const sessionAge = Date.now() - input.sessionUpdatedAt;
|
|
124603
|
-
const isSessionDead = sessionAge > 30 * 6e4;
|
|
124604
|
-
if (isSessionDead) {
|
|
124605
|
-
return { action: "log_infra", reason: "infra", evidence: "Session dead, zero artifacts" };
|
|
124606
|
-
}
|
|
124607
|
-
return { action: "escalate_level", reason: "complexity", evidence: "Active session without commits" };
|
|
124608
|
-
}
|
|
124609
|
-
async function findPrForIssue(owner, repo, issueId) {
|
|
124610
|
-
try {
|
|
124611
|
-
const { execa } = await import("execa");
|
|
124612
|
-
const { stdout } = await execa("gh", [
|
|
124613
|
-
"pr",
|
|
124614
|
-
"list",
|
|
124615
|
-
"--repo",
|
|
124616
|
-
`${owner}/${repo}`,
|
|
124617
|
-
"--search",
|
|
124618
|
-
`${issueId}`,
|
|
124619
|
-
"--json",
|
|
124620
|
-
"number",
|
|
124621
|
-
"--limit",
|
|
124622
|
-
"1"
|
|
124623
|
-
]);
|
|
124624
|
-
const prs = JSON.parse(stdout);
|
|
124625
|
-
return prs.length > 0 ? prs[0] : null;
|
|
124626
|
-
} catch {
|
|
124627
|
-
return null;
|
|
124628
|
-
}
|
|
124629
|
-
}
|
|
124630
|
-
async function checkPrQaStatus(owner, repo, prNumber) {
|
|
124631
|
-
try {
|
|
124632
|
-
const { execa } = await import("execa");
|
|
124633
|
-
const { stdout } = await execa("gh", [
|
|
124634
|
-
"pr",
|
|
124635
|
-
"checks",
|
|
124636
|
-
String(prNumber),
|
|
124637
|
-
"--repo",
|
|
124638
|
-
`${owner}/${repo}`,
|
|
124639
|
-
"--json",
|
|
124640
|
-
"state"
|
|
124641
|
-
]);
|
|
124642
|
-
const checks = JSON.parse(stdout);
|
|
124643
|
-
if (checks.every((c) => c.state === "SUCCESS")) return "pass";
|
|
124644
|
-
if (checks.some((c) => c.state === "FAILURE")) return "fail";
|
|
124645
|
-
return "pending";
|
|
124646
|
-
} catch {
|
|
124647
|
-
return "pending";
|
|
124648
|
-
}
|
|
124649
|
-
}
|
|
124650
|
-
async function checkForBranchCommits(owner, repo, issueId) {
|
|
124651
|
-
try {
|
|
124652
|
-
const { execa } = await import("execa");
|
|
124653
|
-
const { stdout } = await execa("gh", [
|
|
124654
|
-
"api",
|
|
124655
|
-
`repos/${owner}/${repo}/commits`,
|
|
124656
|
-
"--jq",
|
|
124657
|
-
".[0].sha",
|
|
124658
|
-
"-f",
|
|
124659
|
-
`sha=issue-${issueId}`
|
|
124660
|
-
]);
|
|
124661
|
-
return stdout.trim().length > 0;
|
|
124662
|
-
} catch {
|
|
124663
|
-
return false;
|
|
124664
|
-
}
|
|
124665
|
-
}
|
|
124666
|
-
async function checkForArtifacts(owner, repo, issueId) {
|
|
124667
|
-
const pr = await findPrForIssue(owner, repo, issueId);
|
|
124668
|
-
if (pr) return true;
|
|
124669
|
-
return checkForBranchCommits(owner, repo, issueId);
|
|
124670
|
-
}
|
|
124671
|
-
|
|
124672
124581
|
// lib/services/heartbeat/health.ts
|
|
124673
124582
|
init_audit();
|
|
124674
124583
|
init_workflow();
|
|
@@ -124676,8 +124585,6 @@ init_context3();
|
|
|
124676
124585
|
init_labels();
|
|
124677
124586
|
var GRACE_PERIOD_MS = 5 * 60 * 1e3;
|
|
124678
124587
|
var DISPATCH_CONFIRMATION_TIMEOUT_MS = 2 * 60 * 1e3;
|
|
124679
|
-
var NUDGE_MESSAGE = `You appear to have stalled. Continue working on your current task. If you are blocked or unable to proceed, call work_finish with result "blocked".`;
|
|
124680
|
-
var MAX_STALL_NUDGES = 3;
|
|
124681
124588
|
async function auditHealthFixApplied(workspaceDir, fix, details) {
|
|
124682
124589
|
if (!fix.fixed) return;
|
|
124683
124590
|
await withCorrelationContext({
|
|
@@ -124709,8 +124616,7 @@ async function auditHealthFixApplied(workspaceDir, fix, details) {
|
|
|
124709
124616
|
toLabel: details.toLabel ?? fix.issue.expectedLabel ?? null,
|
|
124710
124617
|
idleMinutes: details.idleMinutes ?? null,
|
|
124711
124618
|
deliveryState: details.deliveryState ?? null,
|
|
124712
|
-
labelReverted: fix.labelReverted ?? null
|
|
124713
|
-
nudgeSent: fix.nudgeSent ?? false
|
|
124619
|
+
labelReverted: fix.labelReverted ?? null
|
|
124714
124620
|
}).catch(() => {
|
|
124715
124621
|
});
|
|
124716
124622
|
}));
|
|
@@ -125043,155 +124949,6 @@ async function checkWorkerHealth(opts) {
|
|
|
125043
124949
|
fixes.push(fix);
|
|
125044
124950
|
continue;
|
|
125045
124951
|
}
|
|
125046
|
-
if (slot.active && sessionKey && sessions && !withinGracePeriod && isSessionAlive(sessionKey, sessions)) {
|
|
125047
|
-
const session = sessions.get(sessionKey);
|
|
125048
|
-
const stallThresholdMs = (opts.stallTimeoutMinutes ?? 15) * 6e4;
|
|
125049
|
-
if (session.updatedAt == null) continue;
|
|
125050
|
-
const sessionIdleMs = Date.now() - session.updatedAt;
|
|
125051
|
-
if (sessionIdleMs > stallThresholdMs) {
|
|
125052
|
-
const idleMinutes = Math.round(sessionIdleMs / 6e4);
|
|
125053
|
-
const modelUnresponsiveMs = stallThresholdMs * MAX_STALL_NUDGES;
|
|
125054
|
-
const slotAgeMs = slot.startTime ? Date.now() - new Date(slot.startTime).getTime() : sessionIdleMs;
|
|
125055
|
-
if (slotAgeMs > modelUnresponsiveMs) {
|
|
125056
|
-
const diagnostic = await diagnoseStall({
|
|
125057
|
-
projectSlug,
|
|
125058
|
-
owner: project.remote?.owner ?? "",
|
|
125059
|
-
repo: project.remote?.repo ?? "",
|
|
125060
|
-
issueId: issueIdNum ?? 0,
|
|
125061
|
-
sessionKey,
|
|
125062
|
-
slotStartTime: slot.startTime ? new Date(slot.startTime).getTime() : Date.now() - sessionIdleMs,
|
|
125063
|
-
sessionUpdatedAt: session.updatedAt,
|
|
125064
|
-
dispatchAttemptCount: issueRuntime?.dispatchAttemptCount ?? 0
|
|
125065
|
-
});
|
|
125066
|
-
if (issueIdNum) {
|
|
125067
|
-
await updateIssueRuntime(workspaceDir, projectSlug, String(issueIdNum), {
|
|
125068
|
-
lastDiagnosticResult: diagnostic.evidence,
|
|
125069
|
-
lastFailureReason: diagnostic.reason,
|
|
125070
|
-
dispatchAttemptCount: (issueRuntime?.dispatchAttemptCount ?? 0) + 1
|
|
125071
|
-
}).catch(() => {
|
|
125072
|
-
});
|
|
125073
|
-
}
|
|
125074
|
-
const diagnosticType = `diagnostic_${diagnostic.action}`;
|
|
125075
|
-
const fix2 = {
|
|
125076
|
-
issue: {
|
|
125077
|
-
type: diagnosticType,
|
|
125078
|
-
severity: diagnostic.action === "needs_human_review" ? "critical" : "warning",
|
|
125079
|
-
project: project.name,
|
|
125080
|
-
projectSlug,
|
|
125081
|
-
role,
|
|
125082
|
-
level,
|
|
125083
|
-
sessionKey,
|
|
125084
|
-
issueId: slot.issueId,
|
|
125085
|
-
slotIndex,
|
|
125086
|
-
message: `${role.toUpperCase()} ${level}[${slotIndex}] stall diagnosed: ${diagnostic.action} \u2014 ${diagnostic.evidence}`
|
|
125087
|
-
},
|
|
125088
|
-
fixed: false
|
|
125089
|
-
};
|
|
125090
|
-
if (autoFix) {
|
|
125091
|
-
switch (diagnostic.action) {
|
|
125092
|
-
case "transition_to_review":
|
|
125093
|
-
await revertLabel(fix2, expectedLabel, "To Review");
|
|
125094
|
-
if (!fix2.labelRevertFailed) {
|
|
125095
|
-
await deactivateSlot();
|
|
125096
|
-
fix2.fixed = true;
|
|
125097
|
-
}
|
|
125098
|
-
break;
|
|
125099
|
-
case "redispatch_same_level":
|
|
125100
|
-
case "nudge_open_pr":
|
|
125101
|
-
case "retry_infra":
|
|
125102
|
-
await revertLabel(fix2, expectedLabel, slotQueueLabel);
|
|
125103
|
-
if (!fix2.labelRevertFailed) {
|
|
125104
|
-
await deactivateSlot();
|
|
125105
|
-
fix2.fixed = true;
|
|
125106
|
-
}
|
|
125107
|
-
break;
|
|
125108
|
-
case "escalate_level":
|
|
125109
|
-
await revertLabel(fix2, expectedLabel, slotQueueLabel);
|
|
125110
|
-
if (!fix2.labelRevertFailed) {
|
|
125111
|
-
await deactivateSlot();
|
|
125112
|
-
fix2.fixed = true;
|
|
125113
|
-
}
|
|
125114
|
-
break;
|
|
125115
|
-
case "needs_human_review":
|
|
125116
|
-
await deactivateSlot();
|
|
125117
|
-
fix2.fixed = true;
|
|
125118
|
-
break;
|
|
125119
|
-
case "log_infra":
|
|
125120
|
-
await deactivateSlot();
|
|
125121
|
-
fix2.fixed = true;
|
|
125122
|
-
break;
|
|
125123
|
-
}
|
|
125124
|
-
}
|
|
125125
|
-
await log(workspaceDir, "stall_diagnostic", {
|
|
125126
|
-
project: project.name,
|
|
125127
|
-
projectSlug,
|
|
125128
|
-
role,
|
|
125129
|
-
level,
|
|
125130
|
-
sessionKey,
|
|
125131
|
-
issueId: slot.issueId,
|
|
125132
|
-
slotIndex,
|
|
125133
|
-
idleMinutes,
|
|
125134
|
-
diagnostic: diagnostic.action,
|
|
125135
|
-
reason: diagnostic.reason,
|
|
125136
|
-
evidence: diagnostic.evidence,
|
|
125137
|
-
dispatchAttemptCount: (issueRuntime?.dispatchAttemptCount ?? 0) + 1
|
|
125138
|
-
}).catch(() => {
|
|
125139
|
-
});
|
|
125140
|
-
fixes.push(fix2);
|
|
125141
|
-
continue;
|
|
125142
|
-
}
|
|
125143
|
-
const fix = {
|
|
125144
|
-
issue: {
|
|
125145
|
-
type: "session_stalled",
|
|
125146
|
-
severity: "critical",
|
|
125147
|
-
project: project.name,
|
|
125148
|
-
projectSlug,
|
|
125149
|
-
role,
|
|
125150
|
-
level,
|
|
125151
|
-
sessionKey,
|
|
125152
|
-
issueId: slot.issueId,
|
|
125153
|
-
slotIndex,
|
|
125154
|
-
message: `${role.toUpperCase()} ${level}[${slotIndex}] session idle ${idleMinutes}m \u2014 sending nudge`
|
|
125155
|
-
},
|
|
125156
|
-
fixed: false
|
|
125157
|
-
};
|
|
125158
|
-
if (autoFix) {
|
|
125159
|
-
sendToAgent(sessionKey, NUDGE_MESSAGE, {
|
|
125160
|
-
agentId: opts.agentId,
|
|
125161
|
-
projectName: project.name,
|
|
125162
|
-
issueId: issueIdNum,
|
|
125163
|
-
role,
|
|
125164
|
-
level,
|
|
125165
|
-
slotIndex,
|
|
125166
|
-
workspaceDir,
|
|
125167
|
-
runCommand: opts.runCommand,
|
|
125168
|
-
runtime: opts.runtime
|
|
125169
|
-
});
|
|
125170
|
-
fix.nudgeSent = true;
|
|
125171
|
-
fix.fixed = true;
|
|
125172
|
-
await auditHealthFixApplied(workspaceDir, fix, {
|
|
125173
|
-
action: "nudge_session",
|
|
125174
|
-
idleMinutes,
|
|
125175
|
-
deliveryState
|
|
125176
|
-
});
|
|
125177
|
-
}
|
|
125178
|
-
await log(workspaceDir, "session_stalled", {
|
|
125179
|
-
project: project.name,
|
|
125180
|
-
projectSlug,
|
|
125181
|
-
role,
|
|
125182
|
-
level,
|
|
125183
|
-
sessionKey,
|
|
125184
|
-
issueId: slot.issueId,
|
|
125185
|
-
slotIndex,
|
|
125186
|
-
idleMinutes,
|
|
125187
|
-
deliveryState,
|
|
125188
|
-
action: "nudge"
|
|
125189
|
-
}).catch(() => {
|
|
125190
|
-
});
|
|
125191
|
-
fixes.push(fix);
|
|
125192
|
-
continue;
|
|
125193
|
-
}
|
|
125194
|
-
}
|
|
125195
124952
|
if (slot.active && slot.startTime && sessionKey && sessions && isSessionAlive(sessionKey, sessions)) {
|
|
125196
124953
|
const hours = (Date.now() - new Date(slot.startTime).getTime()) / 36e5;
|
|
125197
124954
|
if (hours > staleWorkerHours) {
|
|
@@ -125225,6 +124982,32 @@ async function checkWorkerHealth(opts) {
|
|
|
125225
124982
|
fixes.push(fix);
|
|
125226
124983
|
}
|
|
125227
124984
|
}
|
|
124985
|
+
if (slot.active && issueIdNum && issue2 && currentLabel === expectedLabel && autoFix) {
|
|
124986
|
+
try {
|
|
124987
|
+
const prStatus = await provider.getPrStatus(issueIdNum);
|
|
124988
|
+
if (prStatus.url && prStatus.state !== PrState.MERGED && prStatus.state !== PrState.CLOSED && prStatus.state !== PrState.CHANGES_REQUESTED && prStatus.state !== PrState.HAS_COMMENTS && prStatus.currentIssueMatch !== false) {
|
|
124989
|
+
const rule = getCompletionRule(workflow, role, "done");
|
|
124990
|
+
if (rule && rule.to !== expectedLabel) {
|
|
124991
|
+
await resilientLabelTransition(provider, issueIdNum, expectedLabel, rule.to);
|
|
124992
|
+
await deactivateSlot();
|
|
124993
|
+
await log(workspaceDir, "health_transition_to_review", {
|
|
124994
|
+
project: project.name,
|
|
124995
|
+
projectSlug,
|
|
124996
|
+
role,
|
|
124997
|
+
level,
|
|
124998
|
+
issueId: slot.issueId,
|
|
124999
|
+
sessionKey,
|
|
125000
|
+
slotIndex,
|
|
125001
|
+
fromLabel: expectedLabel,
|
|
125002
|
+
toLabel: rule.to,
|
|
125003
|
+
prUrl: prStatus.url
|
|
125004
|
+
}).catch(() => {
|
|
125005
|
+
});
|
|
125006
|
+
}
|
|
125007
|
+
}
|
|
125008
|
+
} catch {
|
|
125009
|
+
}
|
|
125010
|
+
}
|
|
125228
125011
|
if (!slot.active && issue2 && currentLabel === expectedLabel) {
|
|
125229
125012
|
const fix = {
|
|
125230
125013
|
issue: {
|
|
@@ -125504,8 +125287,7 @@ function createHealthTool(ctx) {
|
|
|
125504
125287
|
role,
|
|
125505
125288
|
sessions,
|
|
125506
125289
|
autoFix: fix,
|
|
125507
|
-
provider
|
|
125508
|
-
runCommand: ctx.runCommand
|
|
125290
|
+
provider
|
|
125509
125291
|
});
|
|
125510
125292
|
issues.push(...healthFixes.map((f3) => ({ ...f3, project: project.name, role })));
|
|
125511
125293
|
const orphanFixes = await scanOrphanedLabels({
|
|
@@ -131644,10 +131426,6 @@ async function performHealthPass(workspaceDir, projectSlug, project, sessions, p
|
|
|
131644
131426
|
autoFix: true,
|
|
131645
131427
|
provider,
|
|
131646
131428
|
staleWorkerHours,
|
|
131647
|
-
stallTimeoutMinutes,
|
|
131648
|
-
runCommand,
|
|
131649
|
-
runtime,
|
|
131650
|
-
agentId,
|
|
131651
131429
|
workflow: resolvedConfig?.workflow,
|
|
131652
131430
|
dispatchConfirmTimeoutMs: resolvedConfig?.timeouts?.dispatchConfirmTimeoutMs
|
|
131653
131431
|
});
|
|
@@ -138727,16 +138505,32 @@ async function classifyDmIntent(ctx, content, _workspaceDir) {
|
|
|
138727
138505
|
idempotencyKey: sessionKey
|
|
138728
138506
|
});
|
|
138729
138507
|
const waitResult = await runtime.subagent.waitForRun({ runId, timeoutMs: 15e3 });
|
|
138730
|
-
if (waitResult.status !== "ok")
|
|
138731
|
-
|
|
138732
|
-
|
|
138733
|
-
|
|
138734
|
-
|
|
138508
|
+
if (waitResult.status !== "ok") {
|
|
138509
|
+
ctx.logger.warn(`[telegram-bootstrap] classify waitForRun status=${waitResult.status} (expected ok)`);
|
|
138510
|
+
return null;
|
|
138511
|
+
}
|
|
138512
|
+
const messagesResult = await runtime.subagent.getSessionMessages({ sessionKey });
|
|
138513
|
+
const messages = Array.isArray(messagesResult) ? messagesResult : Array.isArray(messagesResult?.messages) ? messagesResult.messages : [];
|
|
138514
|
+
const lastAssistant = messages.filter((m2) => m2.role === "assistant").pop();
|
|
138515
|
+
if (!lastAssistant) {
|
|
138516
|
+
ctx.logger.warn(`[telegram-bootstrap] classify: no assistant message found (messages=${messages.length}, resultType=${typeof messagesResult})`);
|
|
138517
|
+
return null;
|
|
138518
|
+
}
|
|
138519
|
+
const rawContent = lastAssistant.content;
|
|
138520
|
+
const text = typeof rawContent === "string" ? rawContent : Array.isArray(rawContent) ? rawContent.find((b) => b.type === "text")?.text ?? "" : "";
|
|
138521
|
+
if (!text.trim()) {
|
|
138522
|
+
ctx.logger.warn(`[telegram-bootstrap] classify: empty text from assistant (contentType=${typeof rawContent}, isArray=${Array.isArray(rawContent)})`);
|
|
138523
|
+
return null;
|
|
138524
|
+
}
|
|
138735
138525
|
const jsonStr = text.replace(/^```(json)?/gm, "").replace(/```$/gm, "").trim();
|
|
138736
138526
|
const intentData = JSON.parse(jsonStr);
|
|
138737
138527
|
const validated = DmIntentSchema.safeParse(intentData);
|
|
138528
|
+
if (!validated.success) {
|
|
138529
|
+
ctx.logger.warn(`[telegram-bootstrap] classify: schema validation failed: ${validated.error?.message}`);
|
|
138530
|
+
}
|
|
138738
138531
|
return validated.success ? validated.data : null;
|
|
138739
|
-
} catch {
|
|
138532
|
+
} catch (err) {
|
|
138533
|
+
ctx.logger.warn(`[telegram-bootstrap] classify exception: ${err.message ?? err}`);
|
|
138740
138534
|
return null;
|
|
138741
138535
|
}
|
|
138742
138536
|
}
|
|
@@ -138816,14 +138610,16 @@ function buildTopicDeepLink(chatId, topicId) {
|
|
|
138816
138610
|
function buildDmAck(projectName, topicLink, language = "pt") {
|
|
138817
138611
|
return BOOTSTRAP_MESSAGES.registered[language](projectName, topicLink);
|
|
138818
138612
|
}
|
|
138819
|
-
function buildTopicKickoff(projectName, idea) {
|
|
138820
|
-
|
|
138821
|
-
|
|
138822
|
-
|
|
138823
|
-
|
|
138824
|
-
|
|
138825
|
-
|
|
138826
|
-
|
|
138613
|
+
function buildTopicKickoff(projectName, idea, language = "pt") {
|
|
138614
|
+
const header = language === "en" ? `\u{1F9F1} Project automatically registered by Fabrica.
|
|
138615
|
+
Project: ${projectName}
|
|
138616
|
+
|
|
138617
|
+
Original request summary:` : `\u{1F9F1} Projeto registrado automaticamente pela Fabrica.
|
|
138618
|
+
Projeto: ${projectName}
|
|
138619
|
+
|
|
138620
|
+
Resumo do pedido inicial:`;
|
|
138621
|
+
return `${header}
|
|
138622
|
+
${idea}`;
|
|
138827
138623
|
}
|
|
138828
138624
|
async function sendTelegramText(ctx, target, message, opts) {
|
|
138829
138625
|
const sendOpts = {
|
|
@@ -139113,7 +138909,8 @@ Erro: ${result.error ?? "erro desconhecido"}`
|
|
|
139113
138909
|
accountId: telegramConfig.projectsForumAccountId ?? void 0
|
|
139114
138910
|
}
|
|
139115
138911
|
});
|
|
139116
|
-
|
|
138912
|
+
const sessionLang = currentSession?.language ?? "pt";
|
|
138913
|
+
await sendTelegramText(ctx, projectChannelId, buildTopicKickoff(resolvedProjectName, request.rawIdea, sessionLang), {
|
|
139117
138914
|
accountId: telegramConfig.projectsForumAccountId,
|
|
139118
138915
|
messageThreadId
|
|
139119
138916
|
});
|
|
@@ -139132,7 +138929,6 @@ Erro: ${result.error ?? "erro desconhecido"}`
|
|
|
139132
138929
|
logBootstrapWarning(ctx, `[telegram-bootstrap] immediate projectTick failed: ${error48 instanceof Error ? error48.message : String(error48)}`);
|
|
139133
138930
|
});
|
|
139134
138931
|
}
|
|
139135
|
-
const sessionLang = currentSession?.language ?? "pt";
|
|
139136
138932
|
await sendTelegramText(ctx, conversationId, buildDmAck(resolvedProjectName, buildTopicDeepLink(String(projectChannelId), messageThreadId), sessionLang));
|
|
139137
138933
|
await upsertTelegramBootstrapSession(workspaceDir, {
|
|
139138
138934
|
conversationId,
|
|
@@ -139652,6 +139448,36 @@ function registerGatewayLifecycleHook(api, ctx) {
|
|
|
139652
139448
|
|
|
139653
139449
|
// lib/dispatch/subagent-lifecycle-hook.ts
|
|
139654
139450
|
init_audit();
|
|
139451
|
+
|
|
139452
|
+
// lib/dispatch/reactive-dispatch-hook.ts
|
|
139453
|
+
var COMPLETION_TOOLS = /* @__PURE__ */ new Set(["work_finish", "review_submit"]);
|
|
139454
|
+
var spawnTimes = /* @__PURE__ */ new Map();
|
|
139455
|
+
function getSpawnTime(sessionKey) {
|
|
139456
|
+
return spawnTimes.get(sessionKey);
|
|
139457
|
+
}
|
|
139458
|
+
function clearSpawnTime(sessionKey) {
|
|
139459
|
+
spawnTimes.delete(sessionKey);
|
|
139460
|
+
}
|
|
139461
|
+
function registerReactiveDispatchHooks(api, ctx) {
|
|
139462
|
+
api.on("after_tool_call", async (event, _eventCtx) => {
|
|
139463
|
+
if (!COMPLETION_TOOLS.has(event.toolName)) return;
|
|
139464
|
+
ctx.runtime?.system.requestHeartbeatNow({ reason: "work_finish", coalesceMs: 2e3 });
|
|
139465
|
+
});
|
|
139466
|
+
api.on("agent_end", async (_event, eventCtx) => {
|
|
139467
|
+
const sessionKey = eventCtx.sessionKey;
|
|
139468
|
+
if (!sessionKey) return;
|
|
139469
|
+
const parsed = parseFabricaSessionKey(sessionKey);
|
|
139470
|
+
if (!parsed) return;
|
|
139471
|
+
ctx.runtime?.system.requestHeartbeatNow({ reason: "agent_end", coalesceMs: 2e3 });
|
|
139472
|
+
});
|
|
139473
|
+
api.on("subagent_spawned", async (event, _eventCtx) => {
|
|
139474
|
+
const sessionKey = event.childSessionKey;
|
|
139475
|
+
if (!sessionKey) return;
|
|
139476
|
+
spawnTimes.set(sessionKey, Date.now());
|
|
139477
|
+
});
|
|
139478
|
+
}
|
|
139479
|
+
|
|
139480
|
+
// lib/dispatch/subagent-lifecycle-hook.ts
|
|
139655
139481
|
function registerSubagentLifecycleHook(api, ctx) {
|
|
139656
139482
|
const workspaceDir = resolveWorkspaceDir(ctx.config);
|
|
139657
139483
|
if (!workspaceDir) return;
|
|
@@ -139661,11 +139487,15 @@ function registerSubagentLifecycleHook(api, ctx) {
|
|
|
139661
139487
|
const parsed = parseFabricaSessionKey(sessionKey);
|
|
139662
139488
|
if (!parsed) return;
|
|
139663
139489
|
const { projectName, role } = parsed;
|
|
139490
|
+
const spawnTime = getSpawnTime(sessionKey);
|
|
139491
|
+
clearSpawnTime(sessionKey);
|
|
139492
|
+
const durationMs = spawnTime != null ? Date.now() - spawnTime : void 0;
|
|
139664
139493
|
await log(workspaceDir, "subagent_ended", {
|
|
139665
139494
|
sessionKey,
|
|
139666
139495
|
project: projectName,
|
|
139667
139496
|
role,
|
|
139668
|
-
outcome: event.outcome ?? "unknown"
|
|
139497
|
+
outcome: event.outcome ?? "unknown",
|
|
139498
|
+
...durationMs != null ? { durationMs } : {}
|
|
139669
139499
|
}).catch(() => {
|
|
139670
139500
|
});
|
|
139671
139501
|
ctx.logger.info(
|
|
@@ -139729,6 +139559,23 @@ function registerModelResolveHook(api, ctx) {
|
|
|
139729
139559
|
});
|
|
139730
139560
|
}
|
|
139731
139561
|
|
|
139562
|
+
// lib/dispatch/worker-context-hook.ts
|
|
139563
|
+
var WORK_FINISH_CONTEXT = `## Task Completion
|
|
139564
|
+
|
|
139565
|
+
When you have finished your task, you MUST call the \`work_finish\` tool to signal completion.
|
|
139566
|
+
Do NOT rely on your session ending automatically \u2014 you must explicitly call \`work_finish\`.
|
|
139567
|
+
This is required for the pipeline to advance to the next stage.
|
|
139568
|
+
`;
|
|
139569
|
+
function registerWorkerContextHook(api, _ctx) {
|
|
139570
|
+
api.on("before_agent_start", async (_event, eventCtx) => {
|
|
139571
|
+
const sessionKey = eventCtx.sessionKey;
|
|
139572
|
+
if (!sessionKey) return;
|
|
139573
|
+
const parsed = parseFabricaSessionKey(sessionKey);
|
|
139574
|
+
if (!parsed) return;
|
|
139575
|
+
return { prependSystemContext: WORK_FINISH_CONTEXT };
|
|
139576
|
+
});
|
|
139577
|
+
}
|
|
139578
|
+
|
|
139732
139579
|
// index.ts
|
|
139733
139580
|
var plugin = {
|
|
139734
139581
|
id: "fabrica",
|
|
@@ -139869,8 +139716,10 @@ var plugin = {
|
|
|
139869
139716
|
registerGatewayLifecycleHook(api, ctx);
|
|
139870
139717
|
registerSubagentLifecycleHook(api, ctx);
|
|
139871
139718
|
registerModelResolveHook(api, ctx);
|
|
139719
|
+
registerWorkerContextHook(api, ctx);
|
|
139720
|
+
registerReactiveDispatchHooks(api, ctx);
|
|
139872
139721
|
ctx.logger.info(
|
|
139873
|
-
"Fabrica plugin registered (25 tools, 1 CLI command group, 1 service,
|
|
139722
|
+
"Fabrica plugin registered (25 tools, 1 CLI command group, 1 service, 9 hooks total: bootstrap, telegram-dm bootstrap, attachment, gateway lifecycle, subagent lifecycle, model-resolve, worker-context, reactive-dispatch, optional GitHub webhook route)"
|
|
139874
139723
|
);
|
|
139875
139724
|
}
|
|
139876
139725
|
};
|