@mestreyoda/fabrica 0.2.1 → 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 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.1") {
111328
- return "0.2.1";
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");
@@ -124578,103 +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
- import { execFileSync } from "child_process";
124583
- async function diagnoseStall(input) {
124584
- const { owner, repo, issueId, dispatchAttemptCount } = input;
124585
- if ((dispatchAttemptCount ?? 0) >= 2) {
124586
- const hasArtifacts = await checkForArtifacts(owner, repo, issueId);
124587
- if (!hasArtifacts) {
124588
- return { action: "needs_human_review", reason: "stall", evidence: `${dispatchAttemptCount} attempts, zero artifacts` };
124589
- }
124590
- }
124591
- const pr = await findPrForIssue(owner, repo, issueId);
124592
- if (pr) {
124593
- const qaStatus = await checkPrQaStatus(owner, repo, pr.number);
124594
- if (qaStatus === "pass") {
124595
- return { action: "transition_to_review", reason: "stall", evidence: `PR #${pr.number} exists, QA passing`, prNumber: pr.number };
124596
- }
124597
- return { action: "redispatch_same_level", reason: "stall", evidence: `PR #${pr.number} exists, QA ${qaStatus}`, prNumber: pr.number };
124598
- }
124599
- const hasCommits = await checkForBranchCommits(owner, repo, issueId);
124600
- if (hasCommits) {
124601
- return { action: "nudge_open_pr", reason: "stall", evidence: "Branch has commits but no PR" };
124602
- }
124603
- const sessionAge = Date.now() - input.sessionUpdatedAt;
124604
- const isSessionDead = sessionAge > 30 * 6e4;
124605
- if (isSessionDead) {
124606
- return { action: "log_infra", reason: "infra", evidence: "Session dead, zero artifacts" };
124607
- }
124608
- return { action: "escalate_level", reason: "complexity", evidence: "Active session without commits" };
124609
- }
124610
- function ghSync(args) {
124611
- return execFileSync("gh", args, { encoding: "utf-8", timeout: 15e3 });
124612
- }
124613
- async function findPrForIssue(owner, repo, issueId) {
124614
- if (!owner || !repo) return null;
124615
- try {
124616
- const stdout = ghSync([
124617
- "pr",
124618
- "list",
124619
- "--repo",
124620
- `${owner}/${repo}`,
124621
- "--search",
124622
- `${issueId}`,
124623
- "--json",
124624
- "number",
124625
- "--limit",
124626
- "1"
124627
- ]);
124628
- const prs = JSON.parse(stdout);
124629
- return prs.length > 0 ? prs[0] : null;
124630
- } catch {
124631
- return null;
124632
- }
124633
- }
124634
- async function checkPrQaStatus(owner, repo, prNumber) {
124635
- if (!owner || !repo) return "pending";
124636
- try {
124637
- const stdout = ghSync([
124638
- "pr",
124639
- "view",
124640
- String(prNumber),
124641
- "--repo",
124642
- `${owner}/${repo}`,
124643
- "--json",
124644
- "statusCheckRollup"
124645
- ]);
124646
- const data = JSON.parse(stdout);
124647
- const checks = data.statusCheckRollup ?? [];
124648
- if (checks.length === 0) return "pass";
124649
- if (checks.every((c) => c.conclusion === "SUCCESS")) return "pass";
124650
- if (checks.some((c) => c.conclusion === "FAILURE")) return "fail";
124651
- return "pending";
124652
- } catch {
124653
- return "pass";
124654
- }
124655
- }
124656
- async function checkForBranchCommits(owner, repo, issueId) {
124657
- if (!owner || !repo) return false;
124658
- try {
124659
- const stdout = ghSync([
124660
- "api",
124661
- `repos/${owner}/${repo}/commits`,
124662
- "--jq",
124663
- ".[0].sha",
124664
- "-f",
124665
- `sha=issue-${issueId}`
124666
- ]);
124667
- return stdout.trim().length > 0;
124668
- } catch {
124669
- return false;
124670
- }
124671
- }
124672
- async function checkForArtifacts(owner, repo, issueId) {
124673
- const pr = await findPrForIssue(owner, repo, issueId);
124674
- if (pr) return true;
124675
- return checkForBranchCommits(owner, repo, issueId);
124676
- }
124677
-
124678
124581
  // lib/services/heartbeat/health.ts
124679
124582
  init_audit();
124680
124583
  init_workflow();
@@ -124682,9 +124585,6 @@ init_context3();
124682
124585
  init_labels();
124683
124586
  var GRACE_PERIOD_MS = 5 * 60 * 1e3;
124684
124587
  var DISPATCH_CONFIRMATION_TIMEOUT_MS = 2 * 60 * 1e3;
124685
- 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".`;
124686
- var MAX_STALL_NUDGES = 3;
124687
- var MAX_DISPATCH_ATTEMPTS = 5;
124688
124588
  async function auditHealthFixApplied(workspaceDir, fix, details) {
124689
124589
  if (!fix.fixed) return;
124690
124590
  await withCorrelationContext({
@@ -124716,8 +124616,7 @@ async function auditHealthFixApplied(workspaceDir, fix, details) {
124716
124616
  toLabel: details.toLabel ?? fix.issue.expectedLabel ?? null,
124717
124617
  idleMinutes: details.idleMinutes ?? null,
124718
124618
  deliveryState: details.deliveryState ?? null,
124719
- labelReverted: fix.labelReverted ?? null,
124720
- nudgeSent: fix.nudgeSent ?? false
124619
+ labelReverted: fix.labelReverted ?? null
124721
124620
  }).catch(() => {
124722
124621
  });
124723
124622
  }));
@@ -125050,218 +124949,6 @@ async function checkWorkerHealth(opts) {
125050
124949
  fixes.push(fix);
125051
124950
  continue;
125052
124951
  }
125053
- if (slot.active && sessionKey && sessions && !withinGracePeriod && isSessionAlive(sessionKey, sessions)) {
125054
- const session = sessions.get(sessionKey);
125055
- const stallThresholdMs = (opts.stallTimeoutMinutes ?? 5) * 6e4;
125056
- if (session.updatedAt == null) continue;
125057
- const sessionIdleMs = Date.now() - session.updatedAt;
125058
- if (sessionIdleMs > stallThresholdMs) {
125059
- const idleMinutes = Math.round(sessionIdleMs / 6e4);
125060
- const repoRemote = project.repoRemote ?? "";
125061
- const remoteMatch = repoRemote.match(/github\.com\/([^/]+)\/([^/.]+)/);
125062
- const diagnostic = await diagnoseStall({
125063
- projectSlug,
125064
- owner: remoteMatch?.[1] ?? "",
125065
- repo: remoteMatch?.[2] ?? "",
125066
- issueId: issueIdNum ?? 0,
125067
- sessionKey,
125068
- slotStartTime: slot.startTime ? new Date(slot.startTime).getTime() : Date.now() - sessionIdleMs,
125069
- sessionUpdatedAt: session.updatedAt,
125070
- dispatchAttemptCount: issueRuntime?.dispatchAttemptCount ?? 0
125071
- });
125072
- const evidenceActions = ["transition_to_review", "nudge_open_pr"];
125073
- const hasActionableEvidence = evidenceActions.includes(diagnostic.action);
125074
- const modelUnresponsiveMs = stallThresholdMs * MAX_STALL_NUDGES;
125075
- const slotAgeMs = slot.startTime ? Date.now() - new Date(slot.startTime).getTime() : sessionIdleMs;
125076
- const isModelUnresponsive = slotAgeMs > modelUnresponsiveMs;
125077
- const currentAttempts = issueRuntime?.dispatchAttemptCount ?? 0;
125078
- if (currentAttempts >= MAX_DISPATCH_ATTEMPTS && !hasActionableEvidence) {
125079
- const fix2 = {
125080
- issue: {
125081
- type: "diagnostic_needs_human_review",
125082
- severity: "critical",
125083
- project: project.name,
125084
- projectSlug,
125085
- role,
125086
- level,
125087
- sessionKey,
125088
- issueId: slot.issueId,
125089
- slotIndex,
125090
- message: `${role.toUpperCase()} ${level}[${slotIndex}] circuit breaker: ${currentAttempts} dispatch attempts without completion \u2014 moving to Refining`
125091
- },
125092
- fixed: false
125093
- };
125094
- if (autoFix) {
125095
- await revertLabel(fix2, expectedLabel, "Refining");
125096
- await deactivateSlot();
125097
- fix2.fixed = true;
125098
- }
125099
- await log(workspaceDir, "dispatch_circuit_breaker", {
125100
- project: project.name,
125101
- projectSlug,
125102
- role,
125103
- level,
125104
- sessionKey,
125105
- issueId: slot.issueId,
125106
- slotIndex,
125107
- dispatchAttemptCount: currentAttempts,
125108
- diagnostic: diagnostic.action,
125109
- evidence: diagnostic.evidence
125110
- }).catch(() => {
125111
- });
125112
- fixes.push(fix2);
125113
- continue;
125114
- }
125115
- if (hasActionableEvidence || isModelUnresponsive) {
125116
- if (issueIdNum) {
125117
- const runtimeUpdate = {
125118
- lastDiagnosticResult: diagnostic.evidence,
125119
- lastFailureReason: diagnostic.reason,
125120
- dispatchAttemptCount: (issueRuntime?.dispatchAttemptCount ?? 0) + 1
125121
- };
125122
- if (diagnostic.prNumber) {
125123
- const prOwner = remoteMatch?.[1] ?? "";
125124
- const prRepo = remoteMatch?.[2] ?? "";
125125
- runtimeUpdate.currentPrNumber = diagnostic.prNumber;
125126
- runtimeUpdate.currentPrUrl = `https://github.com/${prOwner}/${prRepo}/pull/${diagnostic.prNumber}`;
125127
- runtimeUpdate.currentPrState = "open";
125128
- runtimeUpdate.bindingSource = "diagnostic";
125129
- runtimeUpdate.bindingConfidence = "high";
125130
- runtimeUpdate.boundAt = (/* @__PURE__ */ new Date()).toISOString();
125131
- }
125132
- await updateIssueRuntime(workspaceDir, projectSlug, String(issueIdNum), runtimeUpdate).catch(() => {
125133
- });
125134
- }
125135
- const diagnosticType = `diagnostic_${diagnostic.action}`;
125136
- const fix2 = {
125137
- issue: {
125138
- type: diagnosticType,
125139
- severity: diagnostic.action === "needs_human_review" ? "critical" : "warning",
125140
- project: project.name,
125141
- projectSlug,
125142
- role,
125143
- level,
125144
- sessionKey,
125145
- issueId: slot.issueId,
125146
- slotIndex,
125147
- message: `${role.toUpperCase()} ${level}[${slotIndex}] stall diagnosed: ${diagnostic.action} \u2014 ${diagnostic.evidence}`
125148
- },
125149
- fixed: false
125150
- };
125151
- if (autoFix) {
125152
- switch (diagnostic.action) {
125153
- case "transition_to_review": {
125154
- const targetLabel = role === "tester" ? "Done" : role === "reviewer" ? "To Test" : "To Review";
125155
- await revertLabel(fix2, expectedLabel, targetLabel);
125156
- if (!fix2.labelRevertFailed) {
125157
- await deactivateSlot();
125158
- if (role === "tester" && issueIdNum) {
125159
- await provider.closeIssue(issueIdNum).catch(() => {
125160
- });
125161
- }
125162
- fix2.fixed = true;
125163
- }
125164
- break;
125165
- }
125166
- case "redispatch_same_level":
125167
- case "nudge_open_pr":
125168
- case "retry_infra":
125169
- await revertLabel(fix2, expectedLabel, slotQueueLabel);
125170
- if (!fix2.labelRevertFailed) {
125171
- await deactivateSlot();
125172
- fix2.fixed = true;
125173
- }
125174
- break;
125175
- case "escalate_level":
125176
- await revertLabel(fix2, expectedLabel, slotQueueLabel);
125177
- if (!fix2.labelRevertFailed) {
125178
- await deactivateSlot();
125179
- fix2.fixed = true;
125180
- }
125181
- break;
125182
- case "needs_human_review":
125183
- await revertLabel(fix2, expectedLabel, "Refining");
125184
- await deactivateSlot();
125185
- fix2.fixed = true;
125186
- break;
125187
- case "log_infra":
125188
- await revertLabel(fix2, expectedLabel, "Refining");
125189
- await deactivateSlot();
125190
- fix2.fixed = true;
125191
- break;
125192
- }
125193
- }
125194
- await log(workspaceDir, "stall_diagnostic", {
125195
- project: project.name,
125196
- projectSlug,
125197
- role,
125198
- level,
125199
- sessionKey,
125200
- issueId: slot.issueId,
125201
- slotIndex,
125202
- idleMinutes,
125203
- diagnostic: diagnostic.action,
125204
- reason: diagnostic.reason,
125205
- evidence: diagnostic.evidence,
125206
- fastPath: hasActionableEvidence,
125207
- dispatchAttemptCount: (issueRuntime?.dispatchAttemptCount ?? 0) + 1
125208
- }).catch(() => {
125209
- });
125210
- fixes.push(fix2);
125211
- continue;
125212
- }
125213
- const fix = {
125214
- issue: {
125215
- type: "session_stalled",
125216
- severity: "critical",
125217
- project: project.name,
125218
- projectSlug,
125219
- role,
125220
- level,
125221
- sessionKey,
125222
- issueId: slot.issueId,
125223
- slotIndex,
125224
- message: `${role.toUpperCase()} ${level}[${slotIndex}] session idle ${idleMinutes}m \u2014 no deliverables yet, sending nudge`
125225
- },
125226
- fixed: false
125227
- };
125228
- if (autoFix) {
125229
- sendToAgent(sessionKey, NUDGE_MESSAGE, {
125230
- agentId: opts.agentId,
125231
- projectName: project.name,
125232
- issueId: issueIdNum,
125233
- role,
125234
- level,
125235
- slotIndex,
125236
- workspaceDir,
125237
- runCommand: opts.runCommand,
125238
- runtime: opts.runtime
125239
- });
125240
- fix.nudgeSent = true;
125241
- fix.fixed = true;
125242
- await auditHealthFixApplied(workspaceDir, fix, {
125243
- action: "nudge_session",
125244
- idleMinutes,
125245
- deliveryState
125246
- });
125247
- }
125248
- await log(workspaceDir, "session_stalled", {
125249
- project: project.name,
125250
- projectSlug,
125251
- role,
125252
- level,
125253
- sessionKey,
125254
- issueId: slot.issueId,
125255
- slotIndex,
125256
- idleMinutes,
125257
- deliveryState,
125258
- action: "nudge"
125259
- }).catch(() => {
125260
- });
125261
- fixes.push(fix);
125262
- continue;
125263
- }
125264
- }
125265
124952
  if (slot.active && slot.startTime && sessionKey && sessions && isSessionAlive(sessionKey, sessions)) {
125266
124953
  const hours = (Date.now() - new Date(slot.startTime).getTime()) / 36e5;
125267
124954
  if (hours > staleWorkerHours) {
@@ -125295,6 +124982,32 @@ async function checkWorkerHealth(opts) {
125295
124982
  fixes.push(fix);
125296
124983
  }
125297
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
+ }
125298
125011
  if (!slot.active && issue2 && currentLabel === expectedLabel) {
125299
125012
  const fix = {
125300
125013
  issue: {
@@ -125574,8 +125287,7 @@ function createHealthTool(ctx) {
125574
125287
  role,
125575
125288
  sessions,
125576
125289
  autoFix: fix,
125577
- provider,
125578
- runCommand: ctx.runCommand
125290
+ provider
125579
125291
  });
125580
125292
  issues.push(...healthFixes.map((f3) => ({ ...f3, project: project.name, role })));
125581
125293
  const orphanFixes = await scanOrphanedLabels({
@@ -131714,10 +131426,6 @@ async function performHealthPass(workspaceDir, projectSlug, project, sessions, p
131714
131426
  autoFix: true,
131715
131427
  provider,
131716
131428
  staleWorkerHours,
131717
- stallTimeoutMinutes,
131718
- runCommand,
131719
- runtime,
131720
- agentId,
131721
131429
  workflow: resolvedConfig?.workflow,
131722
131430
  dispatchConfirmTimeoutMs: resolvedConfig?.timeouts?.dispatchConfirmTimeoutMs
131723
131431
  });
@@ -138902,14 +138610,16 @@ function buildTopicDeepLink(chatId, topicId) {
138902
138610
  function buildDmAck(projectName, topicLink, language = "pt") {
138903
138611
  return BOOTSTRAP_MESSAGES.registered[language](projectName, topicLink);
138904
138612
  }
138905
- function buildTopicKickoff(projectName, idea) {
138906
- return [
138907
- `\u{1F9F1} Projeto registrado automaticamente pela Fabrica.`,
138908
- `Projeto: ${projectName}`,
138909
- "",
138910
- "Resumo do pedido inicial:",
138911
- idea
138912
- ].join("\n");
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}`;
138913
138623
  }
138914
138624
  async function sendTelegramText(ctx, target, message, opts) {
138915
138625
  const sendOpts = {
@@ -139199,7 +138909,8 @@ Erro: ${result.error ?? "erro desconhecido"}`
139199
138909
  accountId: telegramConfig.projectsForumAccountId ?? void 0
139200
138910
  }
139201
138911
  });
139202
- await sendTelegramText(ctx, projectChannelId, buildTopicKickoff(resolvedProjectName, request.rawIdea), {
138912
+ const sessionLang = currentSession?.language ?? "pt";
138913
+ await sendTelegramText(ctx, projectChannelId, buildTopicKickoff(resolvedProjectName, request.rawIdea, sessionLang), {
139203
138914
  accountId: telegramConfig.projectsForumAccountId,
139204
138915
  messageThreadId
139205
138916
  });
@@ -139218,7 +138929,6 @@ Erro: ${result.error ?? "erro desconhecido"}`
139218
138929
  logBootstrapWarning(ctx, `[telegram-bootstrap] immediate projectTick failed: ${error48 instanceof Error ? error48.message : String(error48)}`);
139219
138930
  });
139220
138931
  }
139221
- const sessionLang = currentSession?.language ?? "pt";
139222
138932
  await sendTelegramText(ctx, conversationId, buildDmAck(resolvedProjectName, buildTopicDeepLink(String(projectChannelId), messageThreadId), sessionLang));
139223
138933
  await upsertTelegramBootstrapSession(workspaceDir, {
139224
138934
  conversationId,
@@ -139738,6 +139448,36 @@ function registerGatewayLifecycleHook(api, ctx) {
139738
139448
 
139739
139449
  // lib/dispatch/subagent-lifecycle-hook.ts
139740
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
139741
139481
  function registerSubagentLifecycleHook(api, ctx) {
139742
139482
  const workspaceDir = resolveWorkspaceDir(ctx.config);
139743
139483
  if (!workspaceDir) return;
@@ -139747,11 +139487,15 @@ function registerSubagentLifecycleHook(api, ctx) {
139747
139487
  const parsed = parseFabricaSessionKey(sessionKey);
139748
139488
  if (!parsed) return;
139749
139489
  const { projectName, role } = parsed;
139490
+ const spawnTime = getSpawnTime(sessionKey);
139491
+ clearSpawnTime(sessionKey);
139492
+ const durationMs = spawnTime != null ? Date.now() - spawnTime : void 0;
139750
139493
  await log(workspaceDir, "subagent_ended", {
139751
139494
  sessionKey,
139752
139495
  project: projectName,
139753
139496
  role,
139754
- outcome: event.outcome ?? "unknown"
139497
+ outcome: event.outcome ?? "unknown",
139498
+ ...durationMs != null ? { durationMs } : {}
139755
139499
  }).catch(() => {
139756
139500
  });
139757
139501
  ctx.logger.info(
@@ -139815,6 +139559,23 @@ function registerModelResolveHook(api, ctx) {
139815
139559
  });
139816
139560
  }
139817
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
+
139818
139579
  // index.ts
139819
139580
  var plugin = {
139820
139581
  id: "fabrica",
@@ -139955,8 +139716,10 @@ var plugin = {
139955
139716
  registerGatewayLifecycleHook(api, ctx);
139956
139717
  registerSubagentLifecycleHook(api, ctx);
139957
139718
  registerModelResolveHook(api, ctx);
139719
+ registerWorkerContextHook(api, ctx);
139720
+ registerReactiveDispatchHooks(api, ctx);
139958
139721
  ctx.logger.info(
139959
- "Fabrica plugin registered (25 tools, 1 CLI command group, 1 service, 7 hooks total: bootstrap, telegram-dm bootstrap, attachment, gateway lifecycle, subagent lifecycle, model-resolve, optional GitHub webhook route)"
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)"
139960
139723
  );
139961
139724
  }
139962
139725
  };