@mestreyoda/fabrica 0.2.20 → 0.2.22
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 +833 -472
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -113905,8 +113905,8 @@ import fsSync from "node:fs";
|
|
|
113905
113905
|
import path5 from "node:path";
|
|
113906
113906
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
113907
113907
|
function getCurrentVersion() {
|
|
113908
|
-
if ("0.2.
|
|
113909
|
-
return "0.2.
|
|
113908
|
+
if ("0.2.22") {
|
|
113909
|
+
return "0.2.22";
|
|
113910
113910
|
}
|
|
113911
113911
|
try {
|
|
113912
113912
|
const pkgPath = path5.join(THIS_DIR, "..", "..", "package.json");
|
|
@@ -114883,7 +114883,7 @@ function resolve2(config2, sourceLayers, trace2) {
|
|
|
114883
114883
|
dispatchMs: config2.timeouts?.dispatchMs ?? 6e5,
|
|
114884
114884
|
staleWorkerHours: config2.timeouts?.staleWorkerHours ?? 2,
|
|
114885
114885
|
sessionContextBudget: config2.timeouts?.sessionContextBudget ?? 0.6,
|
|
114886
|
-
stallTimeoutMinutes: config2.timeouts?.stallTimeoutMinutes ??
|
|
114886
|
+
stallTimeoutMinutes: config2.timeouts?.stallTimeoutMinutes ?? 25,
|
|
114887
114887
|
sessionConfirmAttempts: config2.timeouts?.sessionConfirmAttempts ?? 5,
|
|
114888
114888
|
sessionConfirmDelayMs: config2.timeouts?.sessionConfirmDelayMs ?? 250,
|
|
114889
114889
|
sessionLabelMaxLength: config2.timeouts?.sessionLabelMaxLength ?? 64,
|
|
@@ -118561,6 +118561,34 @@ var conduct_interview_exports = {};
|
|
|
118561
118561
|
__export(conduct_interview_exports, {
|
|
118562
118562
|
conductInterviewStep: () => conductInterviewStep
|
|
118563
118563
|
});
|
|
118564
|
+
function deriveFeatureScopeFromRawIdea(rawIdea) {
|
|
118565
|
+
const text = rawIdea.toLowerCase();
|
|
118566
|
+
const scope = [];
|
|
118567
|
+
const add = (item) => {
|
|
118568
|
+
if (!scope.includes(item)) scope.push(item);
|
|
118569
|
+
};
|
|
118570
|
+
if (/\b(auth|oauth|jwt|login|register|session|role-based access|rbac|permissions?)\b/i.test(text)) {
|
|
118571
|
+
add("Implement authentication, authorization, and role-aware access rules");
|
|
118572
|
+
}
|
|
118573
|
+
if (/\b(incident|project|task|owner|assign|timeline|status|update)s?\b/i.test(text)) {
|
|
118574
|
+
add("Implement the core domain workflows and CRUD endpoints for the main entities");
|
|
118575
|
+
}
|
|
118576
|
+
if (/\b(alert|alerts|notif|notification|notifications|reminder|reminders|escalation|escalations)\b/i.test(text)) {
|
|
118577
|
+
add("Implement notifications, reminders, and escalation flows for key events");
|
|
118578
|
+
}
|
|
118579
|
+
if (/\b(background process|background worker|worker|queue|job|celery|bull|sidekiq|scheduler)\b/i.test(text)) {
|
|
118580
|
+
add("Implement the background processing pipeline required for asynchronous work");
|
|
118581
|
+
}
|
|
118582
|
+
if (/\b(admin view|admin panel|admin console|dashboard|audit history|audit log|activity history)\b/i.test(text)) {
|
|
118583
|
+
add("Implement administrative visibility and audit/history capabilities for operators");
|
|
118584
|
+
}
|
|
118585
|
+
if (scope.length < 3) {
|
|
118586
|
+
add("Implement the primary user workflow described in the request");
|
|
118587
|
+
add("Expose the key interactions needed to operate the system end to end");
|
|
118588
|
+
add("Add validation and persistence rules for the main business flow");
|
|
118589
|
+
}
|
|
118590
|
+
return scope.slice(0, 5);
|
|
118591
|
+
}
|
|
118564
118592
|
function fallbackSpecData(type, rawIdea) {
|
|
118565
118593
|
const title = rawIdea.slice(0, 120);
|
|
118566
118594
|
const base = {
|
|
@@ -118588,7 +118616,12 @@ function fallbackSpecData(type, rawIdea) {
|
|
|
118588
118616
|
base.acceptance_criteria = ["Infrastructure change applied and verified"];
|
|
118589
118617
|
break;
|
|
118590
118618
|
default:
|
|
118591
|
-
base.
|
|
118619
|
+
base.scope_v1 = deriveFeatureScopeFromRawIdea(rawIdea);
|
|
118620
|
+
base.acceptance_criteria = [
|
|
118621
|
+
"Allows operators to complete the primary workflow end to end as requested",
|
|
118622
|
+
"Validates and enforces the role, permission, or delivery constraints described in the request",
|
|
118623
|
+
"Processes the asynchronous or background behavior required for the main operational path"
|
|
118624
|
+
];
|
|
118592
118625
|
}
|
|
118593
118626
|
return base;
|
|
118594
118627
|
}
|
|
@@ -124987,6 +125020,20 @@ function buildMessage(event) {
|
|
|
124987
125020
|
}
|
|
124988
125021
|
return msg;
|
|
124989
125022
|
}
|
|
125023
|
+
case "workerProgress": {
|
|
125024
|
+
const worker = formatWorkerString(event.role, {
|
|
125025
|
+
name: event.name,
|
|
125026
|
+
level: event.level
|
|
125027
|
+
});
|
|
125028
|
+
let msg = `\u23F3 ${worker} still working on #${event.issueId}: ${event.issueTitle}`;
|
|
125029
|
+
if (event.summary) msg += `
|
|
125030
|
+
${event.summary}`;
|
|
125031
|
+
if (event.minutesActive != null) msg += `
|
|
125032
|
+
\u{1F552} Active for ${event.minutesActive} min`;
|
|
125033
|
+
msg += `
|
|
125034
|
+
\u{1F4CB} [Issue #${event.issueId}](${event.issueUrl})`;
|
|
125035
|
+
return msg;
|
|
125036
|
+
}
|
|
124990
125037
|
case "workerComplete": {
|
|
124991
125038
|
const icons = {
|
|
124992
125039
|
done: "\u2705",
|
|
@@ -126741,6 +126788,10 @@ function createTaskCreateTool(ctx) {
|
|
|
126741
126788
|
pickup: {
|
|
126742
126789
|
type: "boolean",
|
|
126743
126790
|
description: "If true, immediately pick up this issue for DEV after creation. Defaults to false."
|
|
126791
|
+
},
|
|
126792
|
+
parentIssueId: {
|
|
126793
|
+
type: "number",
|
|
126794
|
+
description: "Optional parent/epic issue ID when creating a child task as part of a decomposition plan."
|
|
126744
126795
|
}
|
|
126745
126796
|
}
|
|
126746
126797
|
},
|
|
@@ -126749,12 +126800,17 @@ function createTaskCreateTool(ctx) {
|
|
|
126749
126800
|
const description = params.description ?? "";
|
|
126750
126801
|
const assignees = params.assignees ?? [];
|
|
126751
126802
|
const pickup = params.pickup ?? false;
|
|
126803
|
+
const parentIssueId = Number.isFinite(params.parentIssueId) ? Number(params.parentIssueId) : null;
|
|
126752
126804
|
const workspaceDir = requireWorkspaceDir(toolCtx);
|
|
126753
126805
|
const { project, route } = await resolveProjectFromContext(workspaceDir, toolCtx, params.channelId);
|
|
126754
126806
|
const resolvedConfig = await loadConfig(workspaceDir, project.slug);
|
|
126755
126807
|
const label = resolvedConfig.workflow.states[resolvedConfig.workflow.initial]?.label ?? "Planning";
|
|
126756
126808
|
const { provider, type: providerType } = await resolveProvider(project, ctx.runCommand);
|
|
126757
|
-
|
|
126809
|
+
let body = description;
|
|
126810
|
+
if (parentIssueId) {
|
|
126811
|
+
body = `${description}${description.trim().length > 0 ? "\n\n" : ""}Parent issue: #${parentIssueId}`;
|
|
126812
|
+
}
|
|
126813
|
+
const issue2 = await provider.createIssue(title, body, label, assignees);
|
|
126758
126814
|
provider.reactToIssue(issue2.iid, "eyes").catch(() => {
|
|
126759
126815
|
});
|
|
126760
126816
|
const primaryChannel = findPrimaryChannel(project);
|
|
@@ -126768,13 +126824,31 @@ function createTaskCreateTool(ctx) {
|
|
|
126768
126824
|
);
|
|
126769
126825
|
autoAssignOwnerLabel(workspaceDir, provider, issue2.iid, project).catch(() => {
|
|
126770
126826
|
});
|
|
126827
|
+
if (parentIssueId) {
|
|
126828
|
+
await updateIssueRuntime(workspaceDir, project.slug, issue2.iid, {
|
|
126829
|
+
parentIssueId
|
|
126830
|
+
}).catch(() => {
|
|
126831
|
+
});
|
|
126832
|
+
const parentRuntime = project.issueRuntime?.[String(parentIssueId)] ?? {};
|
|
126833
|
+
const previousChildren = Array.isArray(parentRuntime.childIssueIds) ? parentRuntime.childIssueIds : [];
|
|
126834
|
+
await updateIssueRuntime(workspaceDir, project.slug, parentIssueId, {
|
|
126835
|
+
childIssueIds: [.../* @__PURE__ */ new Set([...previousChildren, issue2.iid])]
|
|
126836
|
+
}).catch(() => {
|
|
126837
|
+
});
|
|
126838
|
+
provider.addComment(
|
|
126839
|
+
parentIssueId,
|
|
126840
|
+
`\u{1F4CC} Child task created: [#${issue2.iid}: ${issue2.title}](${issue2.web_url})`
|
|
126841
|
+
).catch(() => {
|
|
126842
|
+
});
|
|
126843
|
+
}
|
|
126771
126844
|
await log(workspaceDir, "task_create", {
|
|
126772
126845
|
project: project.name,
|
|
126773
126846
|
issueId: issue2.iid,
|
|
126774
126847
|
title,
|
|
126775
126848
|
label,
|
|
126776
126849
|
provider: providerType,
|
|
126777
|
-
pickup
|
|
126850
|
+
pickup,
|
|
126851
|
+
parentIssueId
|
|
126778
126852
|
});
|
|
126779
126853
|
const hasBody = description && description.trim().length > 0;
|
|
126780
126854
|
let announcement = `\u{1F4CB} Created #${issue2.iid}: "${title}" (${label})`;
|
|
@@ -128835,6 +128909,7 @@ async function dispatchTask(opts) {
|
|
|
128835
128909
|
inconclusiveCompletionAt: null,
|
|
128836
128910
|
inconclusiveCompletionReason: null,
|
|
128837
128911
|
sessionCompletedAt: null,
|
|
128912
|
+
progressNotifiedAt: null,
|
|
128838
128913
|
lastSessionKey: sessionKey
|
|
128839
128914
|
}).catch((err) => {
|
|
128840
128915
|
log(workspaceDir, "dispatch_warning", { step: "record_dispatch_requested", issue: issueId, err: String(err) }).catch(() => {
|
|
@@ -131332,6 +131407,7 @@ async function checkWorkerHealth(opts) {
|
|
|
131332
131407
|
completionRecoveryWindowMs = COMPLETION_RECOVERY_WINDOW_MS,
|
|
131333
131408
|
executionContractRecoveryWindowMs = EXECUTION_CONTRACT_RECOVERY_WINDOW_MS,
|
|
131334
131409
|
runCommand,
|
|
131410
|
+
stallTimeoutMinutes = 25,
|
|
131335
131411
|
notificationConfig
|
|
131336
131412
|
} = opts;
|
|
131337
131413
|
const fixes = [];
|
|
@@ -131831,7 +131907,120 @@ async function checkWorkerHealth(opts) {
|
|
|
131831
131907
|
continue;
|
|
131832
131908
|
}
|
|
131833
131909
|
if (slot.active && slot.startTime && sessionKey && sessions && sessionAlive) {
|
|
131834
|
-
const
|
|
131910
|
+
const startedAtMs = new Date(slot.startTime).getTime();
|
|
131911
|
+
const minutesActive = (Date.now() - startedAtMs) / 6e4;
|
|
131912
|
+
const hours = minutesActive / 60;
|
|
131913
|
+
let hasReviewableArtifact = Boolean(
|
|
131914
|
+
issueRuntime?.currentPrNumber || issueRuntime?.currentPrUrl || issueRuntime?.artifactOfRecord?.prNumber
|
|
131915
|
+
);
|
|
131916
|
+
if (!hasReviewableArtifact && issueIdNum) {
|
|
131917
|
+
try {
|
|
131918
|
+
const prStatus = await provider.getPrStatus(issueIdNum);
|
|
131919
|
+
hasReviewableArtifact = Boolean(
|
|
131920
|
+
prStatus.url && prStatus.state !== PrState.MERGED && prStatus.state !== PrState.CLOSED && prStatus.currentIssueMatch !== false
|
|
131921
|
+
);
|
|
131922
|
+
} catch {
|
|
131923
|
+
}
|
|
131924
|
+
}
|
|
131925
|
+
if (role === "developer" && issue2 && issueIdNum && !hasReviewableArtifact && minutesActive >= Math.max(5, Math.floor(stallTimeoutMinutes / 2)) && !issueRuntime?.progressNotifiedAt) {
|
|
131926
|
+
const channel = project.channels?.[0];
|
|
131927
|
+
await notify(
|
|
131928
|
+
{
|
|
131929
|
+
type: "workerProgress",
|
|
131930
|
+
project: project.name,
|
|
131931
|
+
issueId: issueIdNum,
|
|
131932
|
+
issueUrl: issue2.web_url,
|
|
131933
|
+
issueTitle: issue2.title,
|
|
131934
|
+
role,
|
|
131935
|
+
level,
|
|
131936
|
+
minutesActive: Math.round(minutesActive),
|
|
131937
|
+
summary: "Still iterating on implementation/QA without a PR yet.",
|
|
131938
|
+
dispatchCycleId: slot.dispatchCycleId ?? issueRuntime?.lastDispatchCycleId ?? null,
|
|
131939
|
+
dispatchRunId: slot.dispatchRunId ?? issueRuntime?.dispatchRunId ?? null
|
|
131940
|
+
},
|
|
131941
|
+
{
|
|
131942
|
+
workspaceDir,
|
|
131943
|
+
config: notificationConfig,
|
|
131944
|
+
target: channel ? {
|
|
131945
|
+
channelId: channel.channelId,
|
|
131946
|
+
channel: channel.channel,
|
|
131947
|
+
accountId: channel.accountId,
|
|
131948
|
+
messageThreadId: channel.messageThreadId
|
|
131949
|
+
} : void 0,
|
|
131950
|
+
runCommand
|
|
131951
|
+
}
|
|
131952
|
+
).catch(() => {
|
|
131953
|
+
});
|
|
131954
|
+
await updateIssueRuntime(workspaceDir, projectSlug, issueIdNum, {
|
|
131955
|
+
progressNotifiedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
131956
|
+
lastSessionKey: sessionKey
|
|
131957
|
+
}).catch(() => {
|
|
131958
|
+
});
|
|
131959
|
+
}
|
|
131960
|
+
if (role === "developer" && issue2 && issueIdNum && !hasReviewableArtifact && minutesActive >= stallTimeoutMinutes) {
|
|
131961
|
+
const fix = {
|
|
131962
|
+
issue: {
|
|
131963
|
+
type: "completion_recovery_exhausted",
|
|
131964
|
+
severity: "critical",
|
|
131965
|
+
project: project.name,
|
|
131966
|
+
projectSlug,
|
|
131967
|
+
role,
|
|
131968
|
+
level,
|
|
131969
|
+
sessionKey,
|
|
131970
|
+
issueId: slot.issueId,
|
|
131971
|
+
slotIndex,
|
|
131972
|
+
message: `${role.toUpperCase()} ${level}[${slotIndex}] active for ${Math.round(minutesActive)}m without a PR or terminal result`
|
|
131973
|
+
},
|
|
131974
|
+
fixed: false
|
|
131975
|
+
};
|
|
131976
|
+
if (autoFix) {
|
|
131977
|
+
const channel = project.channels?.[0];
|
|
131978
|
+
await notify(
|
|
131979
|
+
{
|
|
131980
|
+
type: "workerRecoveryExhausted",
|
|
131981
|
+
project: project.name,
|
|
131982
|
+
issueId: issueIdNum,
|
|
131983
|
+
issueUrl: issue2.web_url,
|
|
131984
|
+
issueTitle: issue2.title,
|
|
131985
|
+
role,
|
|
131986
|
+
detail: `No PR or canonical completion after ${Math.round(minutesActive)} minutes of active work. Re-queueing for a fresh attempt.`,
|
|
131987
|
+
nextState: slotQueueLabel,
|
|
131988
|
+
dispatchCycleId: slot.dispatchCycleId ?? issueRuntime?.lastDispatchCycleId ?? null,
|
|
131989
|
+
dispatchRunId: slot.dispatchRunId ?? issueRuntime?.dispatchRunId ?? null
|
|
131990
|
+
},
|
|
131991
|
+
{
|
|
131992
|
+
workspaceDir,
|
|
131993
|
+
config: notificationConfig,
|
|
131994
|
+
target: channel ? {
|
|
131995
|
+
channelId: channel.channelId,
|
|
131996
|
+
channel: channel.channel,
|
|
131997
|
+
accountId: channel.accountId,
|
|
131998
|
+
messageThreadId: channel.messageThreadId
|
|
131999
|
+
} : void 0,
|
|
132000
|
+
runCommand
|
|
132001
|
+
}
|
|
132002
|
+
).catch(() => {
|
|
132003
|
+
});
|
|
132004
|
+
await revertLabel(fix, expectedLabel, slotQueueLabel);
|
|
132005
|
+
if (!fix.labelRevertFailed) {
|
|
132006
|
+
await deactivateSlot();
|
|
132007
|
+
await updateIssueRuntime(workspaceDir, projectSlug, issueIdNum, {
|
|
132008
|
+
inconclusiveCompletionAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
132009
|
+
inconclusiveCompletionReason: "stalled_without_artifact",
|
|
132010
|
+
progressNotifiedAt: null
|
|
132011
|
+
}).catch(() => {
|
|
132012
|
+
});
|
|
132013
|
+
fix.fixed = true;
|
|
132014
|
+
await auditHealthFixApplied(workspaceDir, fix, {
|
|
132015
|
+
action: "requeue_issue",
|
|
132016
|
+
fromLabel: expectedLabel,
|
|
132017
|
+
toLabel: slotQueueLabel
|
|
132018
|
+
});
|
|
132019
|
+
}
|
|
132020
|
+
}
|
|
132021
|
+
fixes.push(fix);
|
|
132022
|
+
continue;
|
|
132023
|
+
}
|
|
131835
132024
|
if (hours > staleWorkerHours) {
|
|
131836
132025
|
const fix = {
|
|
131837
132026
|
issue: {
|
|
@@ -139575,69 +139764,350 @@ var securityReviewStep = {
|
|
|
139575
139764
|
|
|
139576
139765
|
// lib/intake/steps/create-task.ts
|
|
139577
139766
|
init_workflow();
|
|
139578
|
-
function buildIssueBody(payload) {
|
|
139579
|
-
const spec = payload.spec;
|
|
139580
|
-
const sections = [];
|
|
139581
|
-
sections.push(`## Objetivo
|
|
139582
|
-
|
|
139583
|
-
${spec.objective}`);
|
|
139584
|
-
sections.push(`## Escopo V1
|
|
139585
|
-
|
|
139586
|
-
${spec.scope_v1.map((s2) => `- ${s2}`).join("\n")}`);
|
|
139587
|
-
if (spec.out_of_scope.length > 0) {
|
|
139588
|
-
sections.push(`## Fora de escopo
|
|
139589
139767
|
|
|
139590
|
-
|
|
139768
|
+
// lib/dispatch/telegram-bootstrap-session.ts
|
|
139769
|
+
init_constants();
|
|
139770
|
+
import { createHash as createHash4, randomUUID as randomUUID4 } from "node:crypto";
|
|
139771
|
+
import fs34 from "node:fs/promises";
|
|
139772
|
+
import path35 from "node:path";
|
|
139773
|
+
var SESSION_TTL_MS = 10 * 6e4;
|
|
139774
|
+
var CLASSIFYING_TTL_MS = 15e3;
|
|
139775
|
+
var RELEASED_CLASSIFY_ERRORS = /* @__PURE__ */ new Set([
|
|
139776
|
+
"classify_invalid_result",
|
|
139777
|
+
"classify_low_confidence",
|
|
139778
|
+
"classify_not_project",
|
|
139779
|
+
"classify_result_expired"
|
|
139780
|
+
]);
|
|
139781
|
+
function toCanonicalTelegramBootstrapConversationId(conversationId) {
|
|
139782
|
+
const trimmed = conversationId.trim();
|
|
139783
|
+
if (!trimmed) return trimmed;
|
|
139784
|
+
return trimmed.startsWith("telegram:") ? trimmed : `telegram:${trimmed}`;
|
|
139785
|
+
}
|
|
139786
|
+
function sessionsDir(workspaceDir) {
|
|
139787
|
+
return path35.join(workspaceDir, DATA_DIR, "bootstrap-sessions");
|
|
139788
|
+
}
|
|
139789
|
+
function sessionPath(workspaceDir, conversationId) {
|
|
139790
|
+
return path35.join(
|
|
139791
|
+
sessionsDir(workspaceDir),
|
|
139792
|
+
`${toCanonicalTelegramBootstrapConversationId(conversationId)}.json`
|
|
139793
|
+
);
|
|
139794
|
+
}
|
|
139795
|
+
function alternateSessionPath(workspaceDir, conversationId) {
|
|
139796
|
+
const canonical = toCanonicalTelegramBootstrapConversationId(conversationId);
|
|
139797
|
+
const bare = canonical.startsWith("telegram:") ? canonical.slice("telegram:".length) : canonical;
|
|
139798
|
+
if (!bare || bare === canonical) return null;
|
|
139799
|
+
return path35.join(sessionsDir(workspaceDir), `${bare}.json`);
|
|
139800
|
+
}
|
|
139801
|
+
function normalizeStoredSession(session) {
|
|
139802
|
+
const canonicalConversationId = toCanonicalTelegramBootstrapConversationId(session.conversationId);
|
|
139803
|
+
if (canonicalConversationId === session.conversationId) {
|
|
139804
|
+
return session;
|
|
139591
139805
|
}
|
|
139592
|
-
|
|
139593
|
-
|
|
139594
|
-
|
|
139595
|
-
|
|
139596
|
-
|
|
139597
|
-
|
|
139598
|
-
|
|
139599
|
-
|
|
139600
|
-
|
|
139601
|
-
|
|
139806
|
+
return {
|
|
139807
|
+
...session,
|
|
139808
|
+
id: buildBootstrapSessionId(canonicalConversationId, session.rawIdea),
|
|
139809
|
+
conversationId: canonicalConversationId
|
|
139810
|
+
};
|
|
139811
|
+
}
|
|
139812
|
+
function stableHash(input) {
|
|
139813
|
+
return createHash4("sha256").update(input).digest("hex").slice(0, 16);
|
|
139814
|
+
}
|
|
139815
|
+
function buildBootstrapSessionId(conversationId, rawIdea) {
|
|
139816
|
+
return `tgdm-${conversationId}-${stableHash(rawIdea.trim().toLowerCase())}`;
|
|
139817
|
+
}
|
|
139818
|
+
function buildBootstrapRequestFingerprint(input) {
|
|
139819
|
+
return stableHash(JSON.stringify({
|
|
139820
|
+
rawIdea: input.rawIdea.trim().toLowerCase(),
|
|
139821
|
+
projectName: input.projectName?.trim().toLowerCase() || null,
|
|
139822
|
+
stackHint: input.stackHint?.trim().toLowerCase() || null,
|
|
139823
|
+
repoUrl: input.repoUrl?.trim().toLowerCase() || null,
|
|
139824
|
+
repoPath: input.repoPath?.trim().toLowerCase() || null
|
|
139825
|
+
}));
|
|
139826
|
+
}
|
|
139827
|
+
function buildBootstrapRequestHash(input) {
|
|
139828
|
+
return buildBootstrapRequestFingerprint(input);
|
|
139829
|
+
}
|
|
139830
|
+
function nextSuppressUntil(status) {
|
|
139831
|
+
const ttl = status === "classifying" || status === "pending_classify" ? CLASSIFYING_TTL_MS : SESSION_TTL_MS;
|
|
139832
|
+
return new Date(Date.now() + ttl).toISOString();
|
|
139833
|
+
}
|
|
139834
|
+
function isReleasedClassifyFailure(error48) {
|
|
139835
|
+
return Boolean(error48 && RELEASED_CLASSIFY_ERRORS.has(error48));
|
|
139836
|
+
}
|
|
139837
|
+
function resolveNullableField(inputValue, existingValue, fallback = null) {
|
|
139838
|
+
return inputValue !== void 0 ? inputValue : existingValue ?? fallback;
|
|
139839
|
+
}
|
|
139840
|
+
function defaultNextRetryAtForStatus(status, existingValue) {
|
|
139841
|
+
if (status === "bootstrapping" || status === "dispatching") {
|
|
139842
|
+
return existingValue ?? null;
|
|
139602
139843
|
}
|
|
139603
|
-
|
|
139604
|
-
|
|
139605
|
-
|
|
139606
|
-
|
|
139844
|
+
return null;
|
|
139845
|
+
}
|
|
139846
|
+
var MONOTONIC_BOOTSTRAP_FIELDS = [
|
|
139847
|
+
"ackSentAt",
|
|
139848
|
+
"projectRegisteredAt",
|
|
139849
|
+
"topicKickoffSentAt",
|
|
139850
|
+
"projectTickedAt",
|
|
139851
|
+
"completionAckSentAt"
|
|
139852
|
+
];
|
|
139853
|
+
function shouldPersistBootstrapCheckpoint(current, next) {
|
|
139854
|
+
if (!current) return { ok: true };
|
|
139855
|
+
if (current.conversationId !== next.conversationId) return { ok: true };
|
|
139856
|
+
const currentAttemptSeq = current.attemptSeq ?? null;
|
|
139857
|
+
const nextAttemptSeq = next.attemptSeq ?? null;
|
|
139858
|
+
if (currentAttemptSeq != null && nextAttemptSeq != null && nextAttemptSeq < currentAttemptSeq) {
|
|
139859
|
+
return { ok: false, reason: "stale_regression" };
|
|
139607
139860
|
}
|
|
139608
|
-
|
|
139609
|
-
|
|
139610
|
-
|
|
139611
|
-
|
|
139861
|
+
const sameAttempt = Boolean(
|
|
139862
|
+
current.attemptId && next.attemptId && current.attemptSeq != null && next.attemptSeq != null && current.attemptId === next.attemptId && current.attemptSeq === next.attemptSeq
|
|
139863
|
+
);
|
|
139864
|
+
if (!sameAttempt) return { ok: true };
|
|
139865
|
+
for (const field of MONOTONIC_BOOTSTRAP_FIELDS) {
|
|
139866
|
+
if (current[field] && !next[field]) {
|
|
139867
|
+
return { ok: false, reason: "stale_regression" };
|
|
139868
|
+
}
|
|
139612
139869
|
}
|
|
139613
|
-
|
|
139614
|
-
_Generated by Genesis Pipeline (session: ${payload.session_id})_`);
|
|
139615
|
-
return sections.join("\n\n");
|
|
139870
|
+
return { ok: true };
|
|
139616
139871
|
}
|
|
139617
|
-
|
|
139618
|
-
|
|
139619
|
-
|
|
139620
|
-
|
|
139621
|
-
|
|
139622
|
-
|
|
139623
|
-
|
|
139624
|
-
|
|
139625
|
-
|
|
139626
|
-
|
|
139627
|
-
|
|
139628
|
-
|
|
139629
|
-
|
|
139630
|
-
|
|
139631
|
-
|
|
139632
|
-
|
|
139633
|
-
|
|
139634
|
-
|
|
139635
|
-
|
|
139636
|
-
|
|
139637
|
-
|
|
139638
|
-
|
|
139639
|
-
|
|
139640
|
-
|
|
139872
|
+
async function readTelegramBootstrapSession(workspaceDir, conversationId) {
|
|
139873
|
+
const canonicalConversationId = toCanonicalTelegramBootstrapConversationId(conversationId);
|
|
139874
|
+
const paths = [
|
|
139875
|
+
sessionPath(workspaceDir, canonicalConversationId),
|
|
139876
|
+
alternateSessionPath(workspaceDir, conversationId)
|
|
139877
|
+
].filter((entry) => Boolean(entry));
|
|
139878
|
+
try {
|
|
139879
|
+
for (const candidatePath of paths) {
|
|
139880
|
+
try {
|
|
139881
|
+
const raw = await fs34.readFile(candidatePath, "utf-8");
|
|
139882
|
+
const session = normalizeStoredSession(JSON.parse(raw));
|
|
139883
|
+
if (session.status === "failed" && isReleasedClassifyFailure(session.error)) {
|
|
139884
|
+
await deleteTelegramBootstrapSession(workspaceDir, canonicalConversationId);
|
|
139885
|
+
return null;
|
|
139886
|
+
}
|
|
139887
|
+
return session;
|
|
139888
|
+
} catch (error48) {
|
|
139889
|
+
if (error48?.code === "ENOENT") continue;
|
|
139890
|
+
throw error48;
|
|
139891
|
+
}
|
|
139892
|
+
}
|
|
139893
|
+
return null;
|
|
139894
|
+
} catch (error48) {
|
|
139895
|
+
if (error48?.code === "ENOENT") return null;
|
|
139896
|
+
throw error48;
|
|
139897
|
+
}
|
|
139898
|
+
}
|
|
139899
|
+
async function deleteTelegramBootstrapSession(workspaceDir, conversationId) {
|
|
139900
|
+
await fs34.unlink(sessionPath(workspaceDir, conversationId)).catch(() => {
|
|
139901
|
+
});
|
|
139902
|
+
const legacyPath = alternateSessionPath(workspaceDir, conversationId);
|
|
139903
|
+
if (legacyPath) {
|
|
139904
|
+
await fs34.unlink(legacyPath).catch(() => {
|
|
139905
|
+
});
|
|
139906
|
+
}
|
|
139907
|
+
}
|
|
139908
|
+
async function writeTelegramBootstrapSession(workspaceDir, session) {
|
|
139909
|
+
const canonicalSession = normalizeStoredSession(session);
|
|
139910
|
+
const dir = sessionsDir(workspaceDir);
|
|
139911
|
+
await fs34.mkdir(dir, { recursive: true });
|
|
139912
|
+
const file2 = sessionPath(workspaceDir, canonicalSession.conversationId);
|
|
139913
|
+
const tmp = `${file2}.${randomUUID4()}.tmp`;
|
|
139914
|
+
await fs34.writeFile(tmp, JSON.stringify(canonicalSession, null, 2) + "\n", "utf-8");
|
|
139915
|
+
await fs34.rename(tmp, file2);
|
|
139916
|
+
const legacyPath = alternateSessionPath(workspaceDir, session.conversationId);
|
|
139917
|
+
if (legacyPath) {
|
|
139918
|
+
await fs34.unlink(legacyPath).catch(() => {
|
|
139919
|
+
});
|
|
139920
|
+
}
|
|
139921
|
+
}
|
|
139922
|
+
async function upsertTelegramBootstrapSession(workspaceDir, input) {
|
|
139923
|
+
const conversationId = toCanonicalTelegramBootstrapConversationId(input.conversationId);
|
|
139924
|
+
const existing = await readTelegramBootstrapSession(workspaceDir, conversationId);
|
|
139925
|
+
const resolvedSourceRoute = resolveNullableField(input.sourceRoute, existing?.sourceRoute);
|
|
139926
|
+
const resolvedProjectRoute = resolveNullableField(input.projectRoute, existing?.projectRoute);
|
|
139927
|
+
const resolvedProjectName = resolveNullableField(input.projectName, existing?.projectName);
|
|
139928
|
+
const resolvedStackHint = resolveNullableField(input.stackHint, existing?.stackHint);
|
|
139929
|
+
const resolvedRepoUrl = resolveNullableField(input.repoUrl, existing?.repoUrl);
|
|
139930
|
+
const resolvedRepoPath = resolveNullableField(input.repoPath, existing?.repoPath);
|
|
139931
|
+
const resolvedProjectSlug = resolveNullableField(input.projectSlug, existing?.projectSlug);
|
|
139932
|
+
const resolvedIssueId = resolveNullableField(input.issueId, existing?.issueId);
|
|
139933
|
+
const resolvedIssueUrl = resolveNullableField(input.issueUrl, existing?.issueUrl);
|
|
139934
|
+
const resolvedTriageReadyForDispatch = resolveNullableField(input.triageReadyForDispatch, existing?.triageReadyForDispatch);
|
|
139935
|
+
const resolvedTriageErrors = input.triageErrors !== void 0 ? input.triageErrors : existing?.triageErrors ?? null;
|
|
139936
|
+
const resolvedMessageThreadId = resolveNullableField(input.messageThreadId, existing?.messageThreadId);
|
|
139937
|
+
const resolvedProjectChannelId = resolveNullableField(input.projectChannelId, existing?.projectChannelId);
|
|
139938
|
+
const resolvedAttemptCount = resolveNullableField(input.attemptCount, existing?.attemptCount, 0);
|
|
139939
|
+
const resolvedAttemptId = resolveNullableField(input.attemptId, existing?.attemptId);
|
|
139940
|
+
const resolvedAttemptSeq = resolveNullableField(input.attemptSeq, existing?.attemptSeq);
|
|
139941
|
+
const resolvedClassifySessionKey = resolveNullableField(input.classifySessionKey, existing?.classifySessionKey);
|
|
139942
|
+
const resolvedClassifyRunId = resolveNullableField(input.classifyRunId, existing?.classifyRunId);
|
|
139943
|
+
const resolvedClassifyStartedAt = resolveNullableField(input.classifyStartedAt, existing?.classifyStartedAt);
|
|
139944
|
+
const resolvedBootstrapStep = resolveNullableField(input.bootstrapStep, existing?.bootstrapStep);
|
|
139945
|
+
const resolvedNextRetryAt = input.nextRetryAt !== void 0 ? input.nextRetryAt : defaultNextRetryAtForStatus(input.status, existing?.nextRetryAt);
|
|
139946
|
+
const resolvedAckSentAt = resolveNullableField(input.ackSentAt, existing?.ackSentAt);
|
|
139947
|
+
const resolvedProjectRegisteredAt = resolveNullableField(input.projectRegisteredAt, existing?.projectRegisteredAt);
|
|
139948
|
+
const resolvedTopicKickoffSentAt = resolveNullableField(input.topicKickoffSentAt, existing?.topicKickoffSentAt);
|
|
139949
|
+
const resolvedProjectTickedAt = resolveNullableField(input.projectTickedAt, existing?.projectTickedAt);
|
|
139950
|
+
const resolvedCompletionAckSentAt = resolveNullableField(input.completionAckSentAt, existing?.completionAckSentAt);
|
|
139951
|
+
const resolvedError = input.error !== void 0 ? input.error : input.lastError !== void 0 ? input.lastError : existing?.error ?? existing?.lastError ?? null;
|
|
139952
|
+
const requestHash = buildBootstrapRequestHash({
|
|
139953
|
+
rawIdea: input.rawIdea,
|
|
139954
|
+
projectName: resolvedProjectName,
|
|
139955
|
+
stackHint: resolvedStackHint,
|
|
139956
|
+
repoUrl: resolvedRepoUrl,
|
|
139957
|
+
repoPath: resolvedRepoPath
|
|
139958
|
+
});
|
|
139959
|
+
const now2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
139960
|
+
const session = {
|
|
139961
|
+
id: existing?.id ?? buildBootstrapSessionId(conversationId, input.rawIdea),
|
|
139962
|
+
conversationId,
|
|
139963
|
+
sourceChannel: input.sourceChannel ?? input.sourceRoute?.channel ?? existing?.sourceChannel ?? "telegram",
|
|
139964
|
+
sourceRoute: resolvedSourceRoute,
|
|
139965
|
+
projectRoute: resolvedProjectRoute,
|
|
139966
|
+
requestHash,
|
|
139967
|
+
requestFingerprint: requestHash,
|
|
139968
|
+
lastCompletedRequestHash: input.status === "completed" ? requestHash : existing?.lastCompletedRequestHash ?? null,
|
|
139969
|
+
rawIdea: input.rawIdea,
|
|
139970
|
+
projectName: resolvedProjectName,
|
|
139971
|
+
stackHint: resolvedStackHint,
|
|
139972
|
+
repoUrl: resolvedRepoUrl,
|
|
139973
|
+
repoPath: resolvedRepoPath,
|
|
139974
|
+
projectSlug: resolvedProjectSlug,
|
|
139975
|
+
issueId: resolvedIssueId,
|
|
139976
|
+
issueUrl: resolvedIssueUrl,
|
|
139977
|
+
triageReadyForDispatch: resolvedTriageReadyForDispatch,
|
|
139978
|
+
triageErrors: resolvedTriageErrors,
|
|
139979
|
+
messageThreadId: resolvedMessageThreadId,
|
|
139980
|
+
projectChannelId: resolvedProjectChannelId,
|
|
139981
|
+
language: input.language ?? existing?.language,
|
|
139982
|
+
status: input.status,
|
|
139983
|
+
attemptId: resolvedAttemptId,
|
|
139984
|
+
attemptSeq: resolvedAttemptSeq,
|
|
139985
|
+
classifySessionKey: resolvedClassifySessionKey,
|
|
139986
|
+
classifyRunId: resolvedClassifyRunId,
|
|
139987
|
+
classifyStartedAt: resolvedClassifyStartedAt,
|
|
139988
|
+
bootstrapStep: resolvedBootstrapStep,
|
|
139989
|
+
attemptCount: resolvedAttemptCount,
|
|
139990
|
+
lastError: resolvedError,
|
|
139991
|
+
nextRetryAt: resolvedNextRetryAt,
|
|
139992
|
+
ackSentAt: resolvedAckSentAt,
|
|
139993
|
+
projectRegisteredAt: resolvedProjectRegisteredAt,
|
|
139994
|
+
topicKickoffSentAt: resolvedTopicKickoffSentAt,
|
|
139995
|
+
projectTickedAt: resolvedProjectTickedAt,
|
|
139996
|
+
completionAckSentAt: resolvedCompletionAckSentAt,
|
|
139997
|
+
pendingClarification: input.pendingClarification !== void 0 ? input.pendingClarification : existing?.pendingClarification ?? null,
|
|
139998
|
+
orphanedArtifacts: input.orphanedArtifacts !== void 0 ? input.orphanedArtifacts : existing?.orphanedArtifacts ?? null,
|
|
139999
|
+
createdAt: existing?.createdAt ?? now2,
|
|
140000
|
+
updatedAt: now2,
|
|
140001
|
+
suppressUntil: nextSuppressUntil(input.status),
|
|
140002
|
+
error: resolvedError
|
|
140003
|
+
};
|
|
140004
|
+
const writeDecision = shouldPersistBootstrapCheckpoint(existing, session);
|
|
140005
|
+
if (!writeDecision.ok && existing) {
|
|
140006
|
+
return existing;
|
|
140007
|
+
}
|
|
140008
|
+
await writeTelegramBootstrapSession(workspaceDir, session);
|
|
140009
|
+
return session;
|
|
140010
|
+
}
|
|
140011
|
+
function shouldSuppressTelegramBootstrapReply(session, request) {
|
|
140012
|
+
if (!session) return false;
|
|
140013
|
+
if (isTelegramBootstrapSessionExpired(session)) return false;
|
|
140014
|
+
if (session.status === "completed" || session.status === "failed") {
|
|
140015
|
+
if (session.status === "failed" && isReleasedClassifyFailure(session.error)) return false;
|
|
140016
|
+
if (!request) return false;
|
|
140017
|
+
return buildBootstrapRequestFingerprint(request) === session.requestHash;
|
|
140018
|
+
}
|
|
140019
|
+
if (!request) return true;
|
|
140020
|
+
return buildBootstrapRequestFingerprint(request) === session.requestHash;
|
|
140021
|
+
}
|
|
140022
|
+
function isTelegramBootstrapSessionExpired(session, now2 = Date.now()) {
|
|
140023
|
+
if (!session) return false;
|
|
140024
|
+
const suppressUntil = Date.parse(session.suppressUntil);
|
|
140025
|
+
return !Number.isNaN(suppressUntil) && suppressUntil < now2;
|
|
140026
|
+
}
|
|
140027
|
+
function isRecoverableTelegramBootstrapSession(session) {
|
|
140028
|
+
return session?.status === "bootstrapping" || session?.status === "dispatching";
|
|
140029
|
+
}
|
|
140030
|
+
function isClaimableTelegramBootstrapSession(session, now2 = Date.now()) {
|
|
140031
|
+
if (!isRecoverableTelegramBootstrapSession(session)) return false;
|
|
140032
|
+
if (!session.nextRetryAt) return true;
|
|
140033
|
+
const retryAt = Date.parse(session.nextRetryAt);
|
|
140034
|
+
return Number.isNaN(retryAt) || retryAt <= now2;
|
|
140035
|
+
}
|
|
140036
|
+
function isSupersededTelegramBootstrapAttempt(current, candidate) {
|
|
140037
|
+
if (!current || !candidate) return false;
|
|
140038
|
+
if (current.conversationId !== candidate.conversationId) return true;
|
|
140039
|
+
const currentHasAttempt = current.attemptId != null && current.attemptSeq != null;
|
|
140040
|
+
const candidateHasAttempt = candidate.attemptId != null && candidate.attemptSeq != null;
|
|
140041
|
+
if (currentHasAttempt || candidateHasAttempt) {
|
|
140042
|
+
return current.attemptId !== candidate.attemptId || current.attemptSeq !== candidate.attemptSeq;
|
|
140043
|
+
}
|
|
140044
|
+
return current.requestHash !== candidate.requestHash || current.status !== candidate.status || current.updatedAt !== candidate.updatedAt;
|
|
140045
|
+
}
|
|
140046
|
+
|
|
140047
|
+
// lib/intake/steps/create-task.ts
|
|
140048
|
+
function buildIssueBody(payload) {
|
|
140049
|
+
const spec = payload.spec;
|
|
140050
|
+
const sections = [];
|
|
140051
|
+
sections.push(`## Objetivo
|
|
140052
|
+
|
|
140053
|
+
${spec.objective}`);
|
|
140054
|
+
sections.push(`## Escopo V1
|
|
140055
|
+
|
|
140056
|
+
${spec.scope_v1.map((s2) => `- ${s2}`).join("\n")}`);
|
|
140057
|
+
if (spec.out_of_scope.length > 0) {
|
|
140058
|
+
sections.push(`## Fora de escopo
|
|
140059
|
+
|
|
140060
|
+
${spec.out_of_scope.map((s2) => `- ${s2}`).join("\n")}`);
|
|
140061
|
+
}
|
|
140062
|
+
sections.push(`## Acceptance Criteria
|
|
140063
|
+
|
|
140064
|
+
${spec.acceptance_criteria.map((s2) => `- [ ] ${s2}`).join("\n")}`);
|
|
140065
|
+
sections.push(`## Definition of Done
|
|
140066
|
+
|
|
140067
|
+
${spec.definition_of_done.map((s2) => `- [ ] ${s2}`).join("\n")}`);
|
|
140068
|
+
if (spec.constraints && spec.constraints !== "None specified") {
|
|
140069
|
+
sections.push(`## Constraints
|
|
140070
|
+
|
|
140071
|
+
${spec.constraints}`);
|
|
140072
|
+
}
|
|
140073
|
+
if (spec.risks.length > 0) {
|
|
140074
|
+
sections.push(`## Risks
|
|
140075
|
+
|
|
140076
|
+
${spec.risks.map((s2) => `- ${s2}`).join("\n")}`);
|
|
140077
|
+
}
|
|
140078
|
+
if (payload.qa_contract) {
|
|
140079
|
+
sections.push(`## QA Contract
|
|
140080
|
+
|
|
140081
|
+
QA contract generated with ${payload.qa_contract.gates.length} gates. Canonical QA Evidence must be posted in the PR body.`);
|
|
140082
|
+
}
|
|
140083
|
+
sections.push(`---
|
|
140084
|
+
_Generated by Genesis Pipeline (session: ${payload.session_id})_`);
|
|
140085
|
+
return sections.join("\n\n");
|
|
140086
|
+
}
|
|
140087
|
+
var createTaskStep = {
|
|
140088
|
+
name: "create-task",
|
|
140089
|
+
shouldRun: (payload) => !!payload.spec && !payload.dry_run && payload.metadata?.project_registered === true && Boolean(
|
|
140090
|
+
payload.provisioning?.repo_local || payload.provisioning?.repo_url || payload.scaffold?.repo_local || payload.scaffold?.repo_url || payload.metadata?.repo_path || payload.metadata?.repo_url
|
|
140091
|
+
),
|
|
140092
|
+
async execute(payload, ctx) {
|
|
140093
|
+
if (payload.metadata?.project_registered !== true) {
|
|
140094
|
+
throw new Error("Project must be registered successfully before issue creation");
|
|
140095
|
+
}
|
|
140096
|
+
const spec = payload.spec;
|
|
140097
|
+
ctx.log(`Creating issue: "${spec.title}"`);
|
|
140098
|
+
const repoUrl = payload.provisioning?.repo_url ?? payload.scaffold?.repo_url ?? payload.metadata?.repo_url;
|
|
140099
|
+
const repoPath = payload.provisioning?.repo_local ?? payload.scaffold?.repo_local ?? payload.metadata?.repo_path ?? payload.project_map?.root ?? void 0;
|
|
140100
|
+
const projectSlug = payload.metadata?.project_slug ?? payload.scaffold?.project_slug ?? payload.project_map?.project_slug;
|
|
140101
|
+
const resolvedConfig = await loadConfig(ctx.workspaceDir, projectSlug ?? void 0);
|
|
140102
|
+
const initialLabel = resolvedConfig.workflow.states[resolvedConfig.workflow.initial]?.label ?? DEFAULT_WORKFLOW.states[DEFAULT_WORKFLOW.initial]?.label ?? "Planning";
|
|
140103
|
+
const initialLabelColor = getLabelColors(resolvedConfig.workflow)[initialLabel] ?? getLabelColors(DEFAULT_WORKFLOW)[initialLabel] ?? "#95a5a6";
|
|
140104
|
+
const body = buildIssueBody(payload);
|
|
140105
|
+
const title = spec.title;
|
|
140106
|
+
if (ctx.createIssueProvider && repoPath) {
|
|
140107
|
+
const { provider } = await ctx.createIssueProvider({
|
|
140108
|
+
repoPath,
|
|
140109
|
+
projectSlug
|
|
140110
|
+
});
|
|
139641
140111
|
await provider.ensureLabel(initialLabel, initialLabelColor);
|
|
139642
140112
|
const created = await provider.createIssue(title, body, initialLabel);
|
|
139643
140113
|
const issue3 = {
|
|
@@ -139646,6 +140116,26 @@ var createTaskStep = {
|
|
|
139646
140116
|
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
139647
140117
|
};
|
|
139648
140118
|
ctx.log(`Issue created via provider: #${issue3.number} \u2014 ${issue3.url}`);
|
|
140119
|
+
const bootstrapConversationId2 = payload.metadata?.source === "telegram-dm-bootstrap" ? payload.metadata?.channel_id : null;
|
|
140120
|
+
if (bootstrapConversationId2) {
|
|
140121
|
+
await upsertTelegramBootstrapSession(ctx.workspaceDir, {
|
|
140122
|
+
conversationId: String(bootstrapConversationId2),
|
|
140123
|
+
rawIdea: payload.raw_idea,
|
|
140124
|
+
projectName: payload.metadata?.project_name ?? null,
|
|
140125
|
+
stackHint: payload.metadata?.stack_hint ?? null,
|
|
140126
|
+
repoUrl: repoUrl ?? null,
|
|
140127
|
+
repoPath: repoPath ?? null,
|
|
140128
|
+
status: "dispatching",
|
|
140129
|
+
bootstrapStep: "project_registered",
|
|
140130
|
+
projectSlug: projectSlug ?? null,
|
|
140131
|
+
issueId: issue3.number,
|
|
140132
|
+
issueUrl: issue3.url,
|
|
140133
|
+
projectChannelId: payload.metadata?.channel_id ?? null,
|
|
140134
|
+
messageThreadId: payload.metadata?.message_thread_id ?? null,
|
|
140135
|
+
projectRegisteredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
140136
|
+
}).catch(() => {
|
|
140137
|
+
});
|
|
140138
|
+
}
|
|
139649
140139
|
return {
|
|
139650
140140
|
...payload,
|
|
139651
140141
|
step: "create-task",
|
|
@@ -139681,6 +140171,26 @@ var createTaskStep = {
|
|
|
139681
140171
|
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
139682
140172
|
};
|
|
139683
140173
|
ctx.log(`Issue created via compatibility fallback: #${issueNumber} \u2014 ${issueUrl}`);
|
|
140174
|
+
const bootstrapConversationId = payload.metadata?.source === "telegram-dm-bootstrap" ? payload.metadata?.channel_id : null;
|
|
140175
|
+
if (bootstrapConversationId) {
|
|
140176
|
+
await upsertTelegramBootstrapSession(ctx.workspaceDir, {
|
|
140177
|
+
conversationId: String(bootstrapConversationId),
|
|
140178
|
+
rawIdea: payload.raw_idea,
|
|
140179
|
+
projectName: payload.metadata?.project_name ?? null,
|
|
140180
|
+
stackHint: payload.metadata?.stack_hint ?? null,
|
|
140181
|
+
repoUrl: repoUrl ?? null,
|
|
140182
|
+
repoPath: repoPath ?? null,
|
|
140183
|
+
status: "dispatching",
|
|
140184
|
+
bootstrapStep: "project_registered",
|
|
140185
|
+
projectSlug: projectSlug ?? null,
|
|
140186
|
+
issueId: issue2.number,
|
|
140187
|
+
issueUrl: issue2.url,
|
|
140188
|
+
projectChannelId: payload.metadata?.channel_id ?? null,
|
|
140189
|
+
messageThreadId: payload.metadata?.message_thread_id ?? null,
|
|
140190
|
+
projectRegisteredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
140191
|
+
}).catch(() => {
|
|
140192
|
+
});
|
|
140193
|
+
}
|
|
139684
140194
|
return {
|
|
139685
140195
|
...payload,
|
|
139686
140196
|
step: "create-task",
|
|
@@ -139694,14 +140204,14 @@ function detectRawIdeaComplexity(rawIdea) {
|
|
|
139694
140204
|
const text = rawIdea.toLowerCase();
|
|
139695
140205
|
const signals = [];
|
|
139696
140206
|
const subsystemPatterns = [
|
|
139697
|
-
[/\b(worker|background.?job|queue|celery|bull|sidekiq|task.?runner)\b/i, "background-worker"],
|
|
140207
|
+
[/\b(worker|background.?job|background process|background service|background worker|queue|celery|bull|sidekiq|task.?runner|scheduler|job processor)\b/i, "background-worker"],
|
|
139698
140208
|
[/\b(websocket|server.?sent|sse|real.?time|realtime|socket\.io|push.?notif)\b/i, "realtime"],
|
|
139699
|
-
[/\b(auth|oauth|jwt|login|register|session|user.?account|signup)\b/i, "auth"],
|
|
139700
|
-
[/\b(notif|alert
|
|
139701
|
-
[/\b(database|banco|db|postgres|mysql|mongodb|redis|sqlite|orm)\b/i, "database"],
|
|
140209
|
+
[/\b(auth|oauth|jwt|login|register|session|user.?account|signup|role-based access|rbac|permission[s]?)\b/i, "auth"],
|
|
140210
|
+
[/\b(notif|notification[s]?|alert[s]?|email|sms|webhook|subscription|subscribe|reminder[s]?|escalation[s]?)\b/i, "notifications"],
|
|
140211
|
+
[/\b(database|banco|db|postgres|mysql|mongodb|redis|sqlite|orm|audit history|audit log|activity history)\b/i, "database"],
|
|
139702
140212
|
[/\b(api\s+rest|rest\s+api|endpoint|rota|route|graphql|grpc)\b/i, "api-layer"],
|
|
139703
140213
|
[/\b(docker|kubernetes|k8s|deploy|ci|cd|pipeline)\b/i, "infra"],
|
|
139704
|
-
[/\b(dashboard|frontend|interface|ui|tela|p[aá]gina)\b/i, "frontend"]
|
|
140214
|
+
[/\b(dashboard|frontend|interface|ui|tela|p[aá]gina|admin view|admin panel|admin console)\b/i, "frontend"]
|
|
139705
140215
|
];
|
|
139706
140216
|
for (const [regex, label] of subsystemPatterns) {
|
|
139707
140217
|
if (regex.test(text)) signals.push(label);
|
|
@@ -139807,7 +140317,7 @@ function runTriageLogic(input, matrix) {
|
|
|
139807
140317
|
objective: input.objective ?? input.rawIdea,
|
|
139808
140318
|
scopeItems: (input.scopeText ?? "").split("\n").filter((l) => l.trim()),
|
|
139809
140319
|
acceptanceCriteria: (input.acText ?? "").split("\n").filter((l) => l.trim()),
|
|
139810
|
-
dod: input.
|
|
140320
|
+
dod: input.dodText ?? ""
|
|
139811
140321
|
});
|
|
139812
140322
|
const specQualityBlock = specQualityErrors.length > 0;
|
|
139813
140323
|
return {
|
|
@@ -139858,6 +140368,38 @@ function loadMatrix() {
|
|
|
139858
140368
|
cachedMatrix = _require3("../configs/triage-matrix.json");
|
|
139859
140369
|
return cachedMatrix;
|
|
139860
140370
|
}
|
|
140371
|
+
function buildChildDrafts(payload, issueNumber, effort) {
|
|
140372
|
+
const spec = payload.spec;
|
|
140373
|
+
const scopeItems = spec.scope_v1.filter((item) => item.trim().length > 0);
|
|
140374
|
+
const maxChildren = effort === "xlarge" ? 4 : 3;
|
|
140375
|
+
const chunkSize = 2;
|
|
140376
|
+
const drafts = [];
|
|
140377
|
+
for (let i2 = 0; i2 < scopeItems.length && drafts.length < maxChildren; i2 += chunkSize) {
|
|
140378
|
+
const slice = scopeItems.slice(i2, i2 + chunkSize);
|
|
140379
|
+
if (slice.length === 0) continue;
|
|
140380
|
+
const childIndex = drafts.length + 1;
|
|
140381
|
+
drafts.push({
|
|
140382
|
+
title: `${spec.title} \u2014 Part ${childIndex}`,
|
|
140383
|
+
description: [
|
|
140384
|
+
`## Objective`,
|
|
140385
|
+
spec.objective,
|
|
140386
|
+
"",
|
|
140387
|
+
`## Parent Issue`,
|
|
140388
|
+
`Parent issue: #${issueNumber}`,
|
|
140389
|
+
"",
|
|
140390
|
+
`## This Part`,
|
|
140391
|
+
...slice.map((item) => `- ${item}`),
|
|
140392
|
+
"",
|
|
140393
|
+
`## Acceptance Criteria`,
|
|
140394
|
+
...spec.acceptance_criteria.map((item) => `- ${item}`),
|
|
140395
|
+
"",
|
|
140396
|
+
`## Definition of Done`,
|
|
140397
|
+
...spec.definition_of_done.map((item) => `- ${item}`)
|
|
140398
|
+
].join("\n")
|
|
140399
|
+
});
|
|
140400
|
+
}
|
|
140401
|
+
return drafts;
|
|
140402
|
+
}
|
|
139861
140403
|
var triageStep = {
|
|
139862
140404
|
name: "triage",
|
|
139863
140405
|
shouldRun: (payload) => !!payload.issues?.length && !payload.dry_run,
|
|
@@ -139877,10 +140419,11 @@ var triageStep = {
|
|
|
139877
140419
|
totalRisks: (impact?.risk_areas?.length ?? 0) + (payload.security?.spec_security_notes?.length ?? 0),
|
|
139878
140420
|
objective: spec.objective,
|
|
139879
140421
|
rawIdea: payload.raw_idea,
|
|
139880
|
-
acText: spec.acceptance_criteria.join("
|
|
139881
|
-
scopeText: spec.scope_v1.join("
|
|
139882
|
-
|
|
139883
|
-
|
|
140422
|
+
acText: spec.acceptance_criteria.join("\n"),
|
|
140423
|
+
scopeText: spec.scope_v1.join("\n"),
|
|
140424
|
+
dodText: spec.definition_of_done.join("\n"),
|
|
140425
|
+
oosText: spec.out_of_scope.join("\n"),
|
|
140426
|
+
authSignal: /\b(login|register|jwt|oauth|auth|role-based access|rbac|permission)\b/i.test(`${payload.raw_idea} ${spec.objective}`)
|
|
139884
140427
|
}, matrix);
|
|
139885
140428
|
const repoUrl = payload.scaffold?.repo_url ?? payload.metadata?.repo_url ?? "";
|
|
139886
140429
|
const repoPath = payload.provisioning?.repo_local ?? payload.scaffold?.repo_local ?? payload.metadata?.repo_path ?? payload.project_map?.root ?? void 0;
|
|
@@ -139889,8 +140432,12 @@ var triageStep = {
|
|
|
139889
140432
|
const initialLabel = resolvedConfig?.workflow.states[resolvedConfig.workflow.initial]?.label ?? "Planning";
|
|
139890
140433
|
const allLabels = [decision.priorityLabel, decision.effortLabel];
|
|
139891
140434
|
if (decision.typeLabel) allLabels.push(decision.typeLabel);
|
|
139892
|
-
if (!decision.readyForDispatch) allLabels.push("needs-human");
|
|
140435
|
+
if (!decision.readyForDispatch || decision.specQualityBlock) allLabels.push("needs-human");
|
|
139893
140436
|
const uniqueLabels = Array.from(new Set(allLabels.filter(Boolean)));
|
|
140437
|
+
const shouldDecompose = decision.readyForDispatch && !decision.specQualityBlock && ["large", "xlarge"].includes(decision.effort);
|
|
140438
|
+
const decompositionDrafts = shouldDecompose ? buildChildDrafts(payload, issue2.number, decision.effort) : [];
|
|
140439
|
+
const canDecompose = decompositionDrafts.length >= 2;
|
|
140440
|
+
let createdChildIssueNumbers = [];
|
|
139894
140441
|
if (ctx.createIssueProvider && repoPath) {
|
|
139895
140442
|
try {
|
|
139896
140443
|
const { provider } = await ctx.createIssueProvider({
|
|
@@ -139900,7 +140447,36 @@ var triageStep = {
|
|
|
139900
140447
|
for (const label of uniqueLabels) {
|
|
139901
140448
|
await provider.addLabel(issue2.number, label);
|
|
139902
140449
|
}
|
|
139903
|
-
if (decision.
|
|
140450
|
+
if (decision.specQualityBlock) {
|
|
140451
|
+
await provider.addComment(issue2.number, [
|
|
140452
|
+
"\u{1F6AB} Spec quality gate blocked automatic dispatch.",
|
|
140453
|
+
"",
|
|
140454
|
+
"The request needs a stronger objective, more concrete scope items, and verifiable acceptance criteria before creating execution tasks."
|
|
140455
|
+
].join("\n"));
|
|
140456
|
+
} else if (canDecompose) {
|
|
140457
|
+
await provider.addLabel(issue2.number, "decomposition:parent");
|
|
140458
|
+
const childIssues = [];
|
|
140459
|
+
for (const draft of decompositionDrafts) {
|
|
140460
|
+
const child = await provider.createIssue(draft.title, draft.description, initialLabel);
|
|
140461
|
+
childIssues.push({ iid: child.iid, title: child.title, web_url: child.web_url });
|
|
140462
|
+
createdChildIssueNumbers.push(child.iid);
|
|
140463
|
+
for (const label of uniqueLabels) {
|
|
140464
|
+
await provider.addLabel(child.iid, label);
|
|
140465
|
+
}
|
|
140466
|
+
await provider.addLabel(child.iid, "decomposition:child");
|
|
140467
|
+
}
|
|
140468
|
+
await provider.addComment(issue2.number, [
|
|
140469
|
+
"## Decomposition Plan",
|
|
140470
|
+
...childIssues.map((child) => `- [ ] #${child.iid} ${child.title}`)
|
|
140471
|
+
].join("\n"));
|
|
140472
|
+
} else if (shouldDecompose) {
|
|
140473
|
+
await provider.addLabel(issue2.number, "needs-human");
|
|
140474
|
+
await provider.addComment(issue2.number, [
|
|
140475
|
+
"\u26A0\uFE0F Automatic decomposition was requested, but the generated spec did not yield at least two independently scoped child tasks.",
|
|
140476
|
+
"",
|
|
140477
|
+
"Refine the scope/acceptance criteria or split the plan manually before dispatch."
|
|
140478
|
+
].join("\n"));
|
|
140479
|
+
} else if (decision.readyForDispatch) {
|
|
139904
140480
|
await provider.transitionLabel(issue2.number, initialLabel, decision.targetState);
|
|
139905
140481
|
const dispatchLabels = [];
|
|
139906
140482
|
if (decision.targetState === "To Do" && decision.dispatchLabel) {
|
|
@@ -139965,10 +140541,37 @@ var triageStep = {
|
|
|
139965
140541
|
project_channel_id: null,
|
|
139966
140542
|
labels_applied: uniqueLabels,
|
|
139967
140543
|
issue_number: issue2.number,
|
|
139968
|
-
ready_for_dispatch: decision.readyForDispatch && decision.errors.length === 0,
|
|
139969
|
-
errors:
|
|
140544
|
+
ready_for_dispatch: decision.readyForDispatch && decision.errors.length === 0 && !decision.specQualityBlock && !canDecompose,
|
|
140545
|
+
errors: [
|
|
140546
|
+
...decision.errors,
|
|
140547
|
+
...decision.specQualityBlock ? ["spec_quality_block"] : [],
|
|
140548
|
+
...shouldDecompose && !canDecompose ? ["decomposition_needs_human"] : []
|
|
140549
|
+
],
|
|
140550
|
+
decomposition_mode: canDecompose ? "parent_child" : "none",
|
|
140551
|
+
child_issue_numbers: createdChildIssueNumbers
|
|
139970
140552
|
};
|
|
139971
140553
|
ctx.log(`Triage: ${triage.priority}, effort=${triage.effort}, ready=${triage.ready_for_dispatch}`);
|
|
140554
|
+
const bootstrapConversationId = payload.metadata?.source === "telegram-dm-bootstrap" ? payload.metadata?.channel_id : null;
|
|
140555
|
+
if (bootstrapConversationId) {
|
|
140556
|
+
await upsertTelegramBootstrapSession(ctx.workspaceDir, {
|
|
140557
|
+
conversationId: String(bootstrapConversationId),
|
|
140558
|
+
rawIdea: payload.raw_idea,
|
|
140559
|
+
projectName: payload.metadata?.project_name ?? null,
|
|
140560
|
+
stackHint: payload.metadata?.stack_hint ?? null,
|
|
140561
|
+
repoUrl: payload.provisioning?.repo_url ?? payload.scaffold?.repo_url ?? payload.metadata?.repo_url ?? null,
|
|
140562
|
+
repoPath: payload.provisioning?.repo_local ?? payload.scaffold?.repo_local ?? payload.metadata?.repo_path ?? null,
|
|
140563
|
+
status: triage.ready_for_dispatch ? "dispatching" : "completed",
|
|
140564
|
+
bootstrapStep: triage.ready_for_dispatch ? "project_ticked" : "completed",
|
|
140565
|
+
projectSlug: payload.metadata?.project_slug ?? payload.scaffold?.project_slug ?? null,
|
|
140566
|
+
issueId: issue2.number,
|
|
140567
|
+
issueUrl: issue2.url,
|
|
140568
|
+
projectChannelId: triage.project_channel_id ?? payload.metadata?.channel_id ?? null,
|
|
140569
|
+
messageThreadId: payload.metadata?.message_thread_id ?? null,
|
|
140570
|
+
triageReadyForDispatch: triage.ready_for_dispatch,
|
|
140571
|
+
triageErrors: triage.errors
|
|
140572
|
+
}).catch(() => {
|
|
140573
|
+
});
|
|
140574
|
+
}
|
|
139972
140575
|
return {
|
|
139973
140576
|
...payload,
|
|
139974
140577
|
step: "triage",
|
|
@@ -140316,23 +140919,23 @@ init_workflow();
|
|
|
140316
140919
|
|
|
140317
140920
|
// lib/dispatch/dispatch-dedup.ts
|
|
140318
140921
|
init_constants();
|
|
140319
|
-
import
|
|
140320
|
-
import
|
|
140321
|
-
import { createHash as
|
|
140922
|
+
import fs35 from "node:fs/promises";
|
|
140923
|
+
import path36 from "node:path";
|
|
140924
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
140322
140925
|
var DEDUP_FILE = "dispatch-dedup.ndjson";
|
|
140323
140926
|
var BUCKET_MS = 5 * 6e4;
|
|
140324
140927
|
var DEFAULT_TTL_MS2 = 30 * 6e4;
|
|
140325
140928
|
function dedupPath(workspaceDir) {
|
|
140326
|
-
return
|
|
140929
|
+
return path36.join(workspaceDir, DATA_DIR, DEDUP_FILE);
|
|
140327
140930
|
}
|
|
140328
140931
|
function computeDispatchId(projectSlug, issueId, role, level, now2 = Date.now()) {
|
|
140329
140932
|
const bucket = Math.floor(now2 / BUCKET_MS);
|
|
140330
140933
|
const input = `${projectSlug}:${issueId}:${role}:${level}:${bucket}`;
|
|
140331
|
-
return
|
|
140934
|
+
return createHash5("sha256").update(input).digest("hex").slice(0, 16);
|
|
140332
140935
|
}
|
|
140333
140936
|
async function readEntries2(filePath) {
|
|
140334
140937
|
try {
|
|
140335
|
-
const content = await
|
|
140938
|
+
const content = await fs35.readFile(filePath, "utf-8");
|
|
140336
140939
|
return content.split("\n").filter(Boolean).map((line) => {
|
|
140337
140940
|
try {
|
|
140338
140941
|
return JSON.parse(line);
|
|
@@ -140351,9 +140954,9 @@ async function isDuplicate(workspaceDir, dispatchId) {
|
|
|
140351
140954
|
}
|
|
140352
140955
|
async function recordDispatch(workspaceDir, dispatchId) {
|
|
140353
140956
|
const filePath = dedupPath(workspaceDir);
|
|
140354
|
-
await
|
|
140957
|
+
await fs35.mkdir(path36.dirname(filePath), { recursive: true });
|
|
140355
140958
|
const entry = { id: dispatchId, ts: Date.now() };
|
|
140356
|
-
await
|
|
140959
|
+
await fs35.appendFile(filePath, JSON.stringify(entry) + "\n", "utf-8");
|
|
140357
140960
|
}
|
|
140358
140961
|
async function cleanupExpired(workspaceDir, ttlMs = DEFAULT_TTL_MS2) {
|
|
140359
140962
|
const filePath = dedupPath(workspaceDir);
|
|
@@ -140363,8 +140966,8 @@ async function cleanupExpired(workspaceDir, ttlMs = DEFAULT_TTL_MS2) {
|
|
|
140363
140966
|
if (kept.length < entries.length) {
|
|
140364
140967
|
const content = kept.map((e2) => JSON.stringify(e2)).join("\n") + (kept.length > 0 ? "\n" : "");
|
|
140365
140968
|
const tmpPath = filePath + ".tmp";
|
|
140366
|
-
await
|
|
140367
|
-
await
|
|
140969
|
+
await fs35.writeFile(tmpPath, content, "utf-8");
|
|
140970
|
+
await fs35.rename(tmpPath, filePath);
|
|
140368
140971
|
}
|
|
140369
140972
|
}
|
|
140370
140973
|
|
|
@@ -140600,388 +141203,113 @@ async function projectTick(opts) {
|
|
|
140600
141203
|
projectSlug,
|
|
140601
141204
|
issueId: issue2.iid,
|
|
140602
141205
|
issueTitle: issue2.title,
|
|
140603
|
-
issueUrl: issue2.web_url,
|
|
140604
|
-
role,
|
|
140605
|
-
level: effectiveLevel,
|
|
140606
|
-
sessionAction: existingSession ? "send" : "spawn",
|
|
140607
|
-
announcement: `[DRY RUN] Would pick up #${issue2.iid}`
|
|
140608
|
-
});
|
|
140609
|
-
pickupCount++;
|
|
140610
|
-
continue;
|
|
140611
|
-
}
|
|
140612
|
-
const environment = await ensureEnvironment({
|
|
140613
|
-
workspaceDir,
|
|
140614
|
-
projectSlug,
|
|
140615
|
-
project: fresh,
|
|
140616
|
-
stack,
|
|
140617
|
-
mode: role === "tester" ? "tester" : "developer",
|
|
140618
|
-
runCommand
|
|
140619
|
-
});
|
|
140620
|
-
if (!environment.ready) {
|
|
140621
|
-
const environmentSkipReason = classifyEnvironmentGateSkip(environment.state);
|
|
140622
|
-
skipped.push({ role, reason: environmentSkipReason });
|
|
140623
|
-
await log(workspaceDir, "dispatch_blocked_environment_not_ready", {
|
|
140624
|
-
projectSlug,
|
|
140625
|
-
role,
|
|
140626
|
-
issueId: issue2.iid,
|
|
140627
|
-
reason: environmentSkipReason,
|
|
140628
|
-
environmentStatus: environment.state.status,
|
|
140629
|
-
nextProvisionRetryAt: environment.state.nextProvisionRetryAt ?? null,
|
|
140630
|
-
lastProvisionError: environment.state.lastProvisionError ?? null
|
|
140631
|
-
}).catch(() => {
|
|
140632
|
-
});
|
|
140633
|
-
continue;
|
|
140634
|
-
}
|
|
140635
|
-
}
|
|
140636
|
-
if (dryRun) {
|
|
140637
|
-
const existingSession = roleWorker.levels[effectiveLevel]?.[freeSlot]?.sessionKey;
|
|
140638
|
-
pickups.push({
|
|
140639
|
-
project: project.name,
|
|
140640
|
-
projectSlug,
|
|
140641
|
-
issueId: issue2.iid,
|
|
140642
|
-
issueTitle: issue2.title,
|
|
140643
|
-
issueUrl: issue2.web_url,
|
|
140644
|
-
role,
|
|
140645
|
-
level: effectiveLevel,
|
|
140646
|
-
sessionAction: existingSession ? "send" : "spawn",
|
|
140647
|
-
announcement: `[DRY RUN] Would pick up #${issue2.iid}`
|
|
140648
|
-
});
|
|
140649
|
-
} else {
|
|
140650
|
-
try {
|
|
140651
|
-
const dr = await dispatch({
|
|
140652
|
-
workspaceDir,
|
|
140653
|
-
agentId,
|
|
140654
|
-
project: fresh,
|
|
140655
|
-
issueId: issue2.iid,
|
|
140656
|
-
issueTitle: issue2.title,
|
|
140657
|
-
issueDescription: issue2.description ?? "",
|
|
140658
|
-
issueUrl: issue2.web_url,
|
|
140659
|
-
role,
|
|
140660
|
-
level: effectiveLevel,
|
|
140661
|
-
fromLabel: currentLabel,
|
|
140662
|
-
toLabel: targetLabel,
|
|
140663
|
-
provider,
|
|
140664
|
-
pluginConfig,
|
|
140665
|
-
sessionKey,
|
|
140666
|
-
runtime,
|
|
140667
|
-
slotIndex: freeSlot,
|
|
140668
|
-
instanceName,
|
|
140669
|
-
runCommand
|
|
140670
|
-
});
|
|
140671
|
-
pickups.push({
|
|
140672
|
-
project: project.name,
|
|
140673
|
-
projectSlug,
|
|
140674
|
-
issueId: issue2.iid,
|
|
140675
|
-
issueTitle: issue2.title,
|
|
140676
|
-
issueUrl: issue2.web_url,
|
|
140677
|
-
role,
|
|
140678
|
-
level: dr.level,
|
|
140679
|
-
sessionAction: dr.sessionAction,
|
|
140680
|
-
announcement: dr.announcement
|
|
140681
|
-
});
|
|
140682
|
-
await recordDispatch(workspaceDir, dispatchId).catch(() => {
|
|
140683
|
-
});
|
|
140684
|
-
} catch (err) {
|
|
140685
|
-
skipped.push({ role, reason: `Dispatch failed: ${err.message}` });
|
|
140686
|
-
continue;
|
|
140687
|
-
}
|
|
140688
|
-
}
|
|
140689
|
-
pickupCount++;
|
|
140690
|
-
}
|
|
140691
|
-
return { pickups, skipped };
|
|
140692
|
-
}
|
|
140693
|
-
function resolveLevelForIssue(issue2, role) {
|
|
140694
|
-
const roleLevel = detectRoleLevelFromLabels(issue2.labels);
|
|
140695
|
-
if (roleLevel?.role === role) return roleLevel.level;
|
|
140696
|
-
if (roleLevel) {
|
|
140697
|
-
const levels = getLevelsForRole(role);
|
|
140698
|
-
if (levels.includes(roleLevel.level)) return roleLevel.level;
|
|
140699
|
-
}
|
|
140700
|
-
return selectLevel(issue2.title, issue2.description ?? "", role).level;
|
|
140701
|
-
}
|
|
140702
|
-
function getFeedbackQueueLabel(workflow) {
|
|
140703
|
-
const developerQueues = getQueueLabels(workflow, "developer");
|
|
140704
|
-
return developerQueues.find((label) => isFeedbackState(workflow, label)) ?? null;
|
|
140705
|
-
}
|
|
140706
|
-
|
|
140707
|
-
// lib/dispatch/telegram-bootstrap-hook.ts
|
|
140708
|
-
init_audit();
|
|
140709
|
-
init_zod();
|
|
140710
|
-
|
|
140711
|
-
// lib/dispatch/telegram-bootstrap-session.ts
|
|
140712
|
-
init_constants();
|
|
140713
|
-
import { createHash as createHash5, randomUUID as randomUUID4 } from "node:crypto";
|
|
140714
|
-
import fs35 from "node:fs/promises";
|
|
140715
|
-
import path36 from "node:path";
|
|
140716
|
-
var SESSION_TTL_MS = 10 * 6e4;
|
|
140717
|
-
var CLASSIFYING_TTL_MS = 15e3;
|
|
140718
|
-
var RELEASED_CLASSIFY_ERRORS = /* @__PURE__ */ new Set([
|
|
140719
|
-
"classify_invalid_result",
|
|
140720
|
-
"classify_low_confidence",
|
|
140721
|
-
"classify_not_project",
|
|
140722
|
-
"classify_result_expired"
|
|
140723
|
-
]);
|
|
140724
|
-
function toCanonicalTelegramBootstrapConversationId(conversationId) {
|
|
140725
|
-
const trimmed = conversationId.trim();
|
|
140726
|
-
if (!trimmed) return trimmed;
|
|
140727
|
-
return trimmed.startsWith("telegram:") ? trimmed : `telegram:${trimmed}`;
|
|
140728
|
-
}
|
|
140729
|
-
function sessionsDir(workspaceDir) {
|
|
140730
|
-
return path36.join(workspaceDir, DATA_DIR, "bootstrap-sessions");
|
|
140731
|
-
}
|
|
140732
|
-
function sessionPath(workspaceDir, conversationId) {
|
|
140733
|
-
return path36.join(
|
|
140734
|
-
sessionsDir(workspaceDir),
|
|
140735
|
-
`${toCanonicalTelegramBootstrapConversationId(conversationId)}.json`
|
|
140736
|
-
);
|
|
140737
|
-
}
|
|
140738
|
-
function alternateSessionPath(workspaceDir, conversationId) {
|
|
140739
|
-
const canonical = toCanonicalTelegramBootstrapConversationId(conversationId);
|
|
140740
|
-
const bare = canonical.startsWith("telegram:") ? canonical.slice("telegram:".length) : canonical;
|
|
140741
|
-
if (!bare || bare === canonical) return null;
|
|
140742
|
-
return path36.join(sessionsDir(workspaceDir), `${bare}.json`);
|
|
140743
|
-
}
|
|
140744
|
-
function normalizeStoredSession(session) {
|
|
140745
|
-
const canonicalConversationId = toCanonicalTelegramBootstrapConversationId(session.conversationId);
|
|
140746
|
-
if (canonicalConversationId === session.conversationId) {
|
|
140747
|
-
return session;
|
|
140748
|
-
}
|
|
140749
|
-
return {
|
|
140750
|
-
...session,
|
|
140751
|
-
id: buildBootstrapSessionId(canonicalConversationId, session.rawIdea),
|
|
140752
|
-
conversationId: canonicalConversationId
|
|
140753
|
-
};
|
|
140754
|
-
}
|
|
140755
|
-
function stableHash(input) {
|
|
140756
|
-
return createHash5("sha256").update(input).digest("hex").slice(0, 16);
|
|
140757
|
-
}
|
|
140758
|
-
function buildBootstrapSessionId(conversationId, rawIdea) {
|
|
140759
|
-
return `tgdm-${conversationId}-${stableHash(rawIdea.trim().toLowerCase())}`;
|
|
140760
|
-
}
|
|
140761
|
-
function buildBootstrapRequestFingerprint(input) {
|
|
140762
|
-
return stableHash(JSON.stringify({
|
|
140763
|
-
rawIdea: input.rawIdea.trim().toLowerCase(),
|
|
140764
|
-
projectName: input.projectName?.trim().toLowerCase() || null,
|
|
140765
|
-
stackHint: input.stackHint?.trim().toLowerCase() || null,
|
|
140766
|
-
repoUrl: input.repoUrl?.trim().toLowerCase() || null,
|
|
140767
|
-
repoPath: input.repoPath?.trim().toLowerCase() || null
|
|
140768
|
-
}));
|
|
140769
|
-
}
|
|
140770
|
-
function buildBootstrapRequestHash(input) {
|
|
140771
|
-
return buildBootstrapRequestFingerprint(input);
|
|
140772
|
-
}
|
|
140773
|
-
function nextSuppressUntil(status) {
|
|
140774
|
-
const ttl = status === "classifying" || status === "pending_classify" ? CLASSIFYING_TTL_MS : SESSION_TTL_MS;
|
|
140775
|
-
return new Date(Date.now() + ttl).toISOString();
|
|
140776
|
-
}
|
|
140777
|
-
function isReleasedClassifyFailure(error48) {
|
|
140778
|
-
return Boolean(error48 && RELEASED_CLASSIFY_ERRORS.has(error48));
|
|
140779
|
-
}
|
|
140780
|
-
function resolveNullableField(inputValue, existingValue, fallback = null) {
|
|
140781
|
-
return inputValue !== void 0 ? inputValue : existingValue ?? fallback;
|
|
140782
|
-
}
|
|
140783
|
-
function defaultNextRetryAtForStatus(status, existingValue) {
|
|
140784
|
-
if (status === "bootstrapping" || status === "dispatching") {
|
|
140785
|
-
return existingValue ?? null;
|
|
140786
|
-
}
|
|
140787
|
-
return null;
|
|
140788
|
-
}
|
|
140789
|
-
var MONOTONIC_BOOTSTRAP_FIELDS = [
|
|
140790
|
-
"ackSentAt",
|
|
140791
|
-
"projectRegisteredAt",
|
|
140792
|
-
"topicKickoffSentAt",
|
|
140793
|
-
"projectTickedAt",
|
|
140794
|
-
"completionAckSentAt"
|
|
140795
|
-
];
|
|
140796
|
-
function shouldPersistBootstrapCheckpoint(current, next) {
|
|
140797
|
-
if (!current) return { ok: true };
|
|
140798
|
-
if (current.conversationId !== next.conversationId) return { ok: true };
|
|
140799
|
-
const currentAttemptSeq = current.attemptSeq ?? null;
|
|
140800
|
-
const nextAttemptSeq = next.attemptSeq ?? null;
|
|
140801
|
-
if (currentAttemptSeq != null && nextAttemptSeq != null && nextAttemptSeq < currentAttemptSeq) {
|
|
140802
|
-
return { ok: false, reason: "stale_regression" };
|
|
140803
|
-
}
|
|
140804
|
-
const sameAttempt = Boolean(
|
|
140805
|
-
current.attemptId && next.attemptId && current.attemptSeq != null && next.attemptSeq != null && current.attemptId === next.attemptId && current.attemptSeq === next.attemptSeq
|
|
140806
|
-
);
|
|
140807
|
-
if (!sameAttempt) return { ok: true };
|
|
140808
|
-
for (const field of MONOTONIC_BOOTSTRAP_FIELDS) {
|
|
140809
|
-
if (current[field] && !next[field]) {
|
|
140810
|
-
return { ok: false, reason: "stale_regression" };
|
|
141206
|
+
issueUrl: issue2.web_url,
|
|
141207
|
+
role,
|
|
141208
|
+
level: effectiveLevel,
|
|
141209
|
+
sessionAction: existingSession ? "send" : "spawn",
|
|
141210
|
+
announcement: `[DRY RUN] Would pick up #${issue2.iid}`
|
|
141211
|
+
});
|
|
141212
|
+
pickupCount++;
|
|
141213
|
+
continue;
|
|
141214
|
+
}
|
|
141215
|
+
const environment = await ensureEnvironment({
|
|
141216
|
+
workspaceDir,
|
|
141217
|
+
projectSlug,
|
|
141218
|
+
project: fresh,
|
|
141219
|
+
stack,
|
|
141220
|
+
mode: role === "tester" ? "tester" : "developer",
|
|
141221
|
+
runCommand
|
|
141222
|
+
});
|
|
141223
|
+
if (!environment.ready) {
|
|
141224
|
+
const environmentSkipReason = classifyEnvironmentGateSkip(environment.state);
|
|
141225
|
+
skipped.push({ role, reason: environmentSkipReason });
|
|
141226
|
+
await log(workspaceDir, "dispatch_blocked_environment_not_ready", {
|
|
141227
|
+
projectSlug,
|
|
141228
|
+
role,
|
|
141229
|
+
issueId: issue2.iid,
|
|
141230
|
+
reason: environmentSkipReason,
|
|
141231
|
+
environmentStatus: environment.state.status,
|
|
141232
|
+
nextProvisionRetryAt: environment.state.nextProvisionRetryAt ?? null,
|
|
141233
|
+
lastProvisionError: environment.state.lastProvisionError ?? null
|
|
141234
|
+
}).catch(() => {
|
|
141235
|
+
});
|
|
141236
|
+
continue;
|
|
141237
|
+
}
|
|
140811
141238
|
}
|
|
140812
|
-
|
|
140813
|
-
|
|
140814
|
-
|
|
140815
|
-
|
|
140816
|
-
|
|
140817
|
-
|
|
140818
|
-
|
|
140819
|
-
|
|
140820
|
-
|
|
140821
|
-
|
|
140822
|
-
|
|
141239
|
+
if (dryRun) {
|
|
141240
|
+
const existingSession = roleWorker.levels[effectiveLevel]?.[freeSlot]?.sessionKey;
|
|
141241
|
+
pickups.push({
|
|
141242
|
+
project: project.name,
|
|
141243
|
+
projectSlug,
|
|
141244
|
+
issueId: issue2.iid,
|
|
141245
|
+
issueTitle: issue2.title,
|
|
141246
|
+
issueUrl: issue2.web_url,
|
|
141247
|
+
role,
|
|
141248
|
+
level: effectiveLevel,
|
|
141249
|
+
sessionAction: existingSession ? "send" : "spawn",
|
|
141250
|
+
announcement: `[DRY RUN] Would pick up #${issue2.iid}`
|
|
141251
|
+
});
|
|
141252
|
+
} else {
|
|
140823
141253
|
try {
|
|
140824
|
-
const
|
|
140825
|
-
|
|
140826
|
-
|
|
140827
|
-
|
|
140828
|
-
|
|
140829
|
-
|
|
140830
|
-
|
|
140831
|
-
|
|
140832
|
-
|
|
140833
|
-
|
|
141254
|
+
const dr = await dispatch({
|
|
141255
|
+
workspaceDir,
|
|
141256
|
+
agentId,
|
|
141257
|
+
project: fresh,
|
|
141258
|
+
issueId: issue2.iid,
|
|
141259
|
+
issueTitle: issue2.title,
|
|
141260
|
+
issueDescription: issue2.description ?? "",
|
|
141261
|
+
issueUrl: issue2.web_url,
|
|
141262
|
+
role,
|
|
141263
|
+
level: effectiveLevel,
|
|
141264
|
+
fromLabel: currentLabel,
|
|
141265
|
+
toLabel: targetLabel,
|
|
141266
|
+
provider,
|
|
141267
|
+
pluginConfig,
|
|
141268
|
+
sessionKey,
|
|
141269
|
+
runtime,
|
|
141270
|
+
slotIndex: freeSlot,
|
|
141271
|
+
instanceName,
|
|
141272
|
+
runCommand
|
|
141273
|
+
});
|
|
141274
|
+
pickups.push({
|
|
141275
|
+
project: project.name,
|
|
141276
|
+
projectSlug,
|
|
141277
|
+
issueId: issue2.iid,
|
|
141278
|
+
issueTitle: issue2.title,
|
|
141279
|
+
issueUrl: issue2.web_url,
|
|
141280
|
+
role,
|
|
141281
|
+
level: dr.level,
|
|
141282
|
+
sessionAction: dr.sessionAction,
|
|
141283
|
+
announcement: dr.announcement
|
|
141284
|
+
});
|
|
141285
|
+
await recordDispatch(workspaceDir, dispatchId).catch(() => {
|
|
141286
|
+
});
|
|
141287
|
+
} catch (err) {
|
|
141288
|
+
skipped.push({ role, reason: `Dispatch failed: ${err.message}` });
|
|
141289
|
+
continue;
|
|
140834
141290
|
}
|
|
140835
141291
|
}
|
|
140836
|
-
|
|
140837
|
-
} catch (error48) {
|
|
140838
|
-
if (error48?.code === "ENOENT") return null;
|
|
140839
|
-
throw error48;
|
|
140840
|
-
}
|
|
140841
|
-
}
|
|
140842
|
-
async function deleteTelegramBootstrapSession(workspaceDir, conversationId) {
|
|
140843
|
-
await fs35.unlink(sessionPath(workspaceDir, conversationId)).catch(() => {
|
|
140844
|
-
});
|
|
140845
|
-
const legacyPath = alternateSessionPath(workspaceDir, conversationId);
|
|
140846
|
-
if (legacyPath) {
|
|
140847
|
-
await fs35.unlink(legacyPath).catch(() => {
|
|
140848
|
-
});
|
|
140849
|
-
}
|
|
140850
|
-
}
|
|
140851
|
-
async function writeTelegramBootstrapSession(workspaceDir, session) {
|
|
140852
|
-
const canonicalSession = normalizeStoredSession(session);
|
|
140853
|
-
const dir = sessionsDir(workspaceDir);
|
|
140854
|
-
await fs35.mkdir(dir, { recursive: true });
|
|
140855
|
-
const file2 = sessionPath(workspaceDir, canonicalSession.conversationId);
|
|
140856
|
-
const tmp = `${file2}.${randomUUID4()}.tmp`;
|
|
140857
|
-
await fs35.writeFile(tmp, JSON.stringify(canonicalSession, null, 2) + "\n", "utf-8");
|
|
140858
|
-
await fs35.rename(tmp, file2);
|
|
140859
|
-
const legacyPath = alternateSessionPath(workspaceDir, session.conversationId);
|
|
140860
|
-
if (legacyPath) {
|
|
140861
|
-
await fs35.unlink(legacyPath).catch(() => {
|
|
140862
|
-
});
|
|
140863
|
-
}
|
|
140864
|
-
}
|
|
140865
|
-
async function upsertTelegramBootstrapSession(workspaceDir, input) {
|
|
140866
|
-
const conversationId = toCanonicalTelegramBootstrapConversationId(input.conversationId);
|
|
140867
|
-
const existing = await readTelegramBootstrapSession(workspaceDir, conversationId);
|
|
140868
|
-
const resolvedSourceRoute = resolveNullableField(input.sourceRoute, existing?.sourceRoute);
|
|
140869
|
-
const resolvedProjectRoute = resolveNullableField(input.projectRoute, existing?.projectRoute);
|
|
140870
|
-
const resolvedProjectName = resolveNullableField(input.projectName, existing?.projectName);
|
|
140871
|
-
const resolvedStackHint = resolveNullableField(input.stackHint, existing?.stackHint);
|
|
140872
|
-
const resolvedRepoUrl = resolveNullableField(input.repoUrl, existing?.repoUrl);
|
|
140873
|
-
const resolvedRepoPath = resolveNullableField(input.repoPath, existing?.repoPath);
|
|
140874
|
-
const resolvedProjectSlug = resolveNullableField(input.projectSlug, existing?.projectSlug);
|
|
140875
|
-
const resolvedIssueId = resolveNullableField(input.issueId, existing?.issueId);
|
|
140876
|
-
const resolvedMessageThreadId = resolveNullableField(input.messageThreadId, existing?.messageThreadId);
|
|
140877
|
-
const resolvedProjectChannelId = resolveNullableField(input.projectChannelId, existing?.projectChannelId);
|
|
140878
|
-
const resolvedAttemptCount = resolveNullableField(input.attemptCount, existing?.attemptCount, 0);
|
|
140879
|
-
const resolvedAttemptId = resolveNullableField(input.attemptId, existing?.attemptId);
|
|
140880
|
-
const resolvedAttemptSeq = resolveNullableField(input.attemptSeq, existing?.attemptSeq);
|
|
140881
|
-
const resolvedClassifySessionKey = resolveNullableField(input.classifySessionKey, existing?.classifySessionKey);
|
|
140882
|
-
const resolvedClassifyRunId = resolveNullableField(input.classifyRunId, existing?.classifyRunId);
|
|
140883
|
-
const resolvedClassifyStartedAt = resolveNullableField(input.classifyStartedAt, existing?.classifyStartedAt);
|
|
140884
|
-
const resolvedBootstrapStep = resolveNullableField(input.bootstrapStep, existing?.bootstrapStep);
|
|
140885
|
-
const resolvedNextRetryAt = input.nextRetryAt !== void 0 ? input.nextRetryAt : defaultNextRetryAtForStatus(input.status, existing?.nextRetryAt);
|
|
140886
|
-
const resolvedAckSentAt = resolveNullableField(input.ackSentAt, existing?.ackSentAt);
|
|
140887
|
-
const resolvedProjectRegisteredAt = resolveNullableField(input.projectRegisteredAt, existing?.projectRegisteredAt);
|
|
140888
|
-
const resolvedTopicKickoffSentAt = resolveNullableField(input.topicKickoffSentAt, existing?.topicKickoffSentAt);
|
|
140889
|
-
const resolvedProjectTickedAt = resolveNullableField(input.projectTickedAt, existing?.projectTickedAt);
|
|
140890
|
-
const resolvedCompletionAckSentAt = resolveNullableField(input.completionAckSentAt, existing?.completionAckSentAt);
|
|
140891
|
-
const resolvedError = input.error !== void 0 ? input.error : input.lastError !== void 0 ? input.lastError : existing?.error ?? existing?.lastError ?? null;
|
|
140892
|
-
const requestHash = buildBootstrapRequestHash({
|
|
140893
|
-
rawIdea: input.rawIdea,
|
|
140894
|
-
projectName: resolvedProjectName,
|
|
140895
|
-
stackHint: resolvedStackHint,
|
|
140896
|
-
repoUrl: resolvedRepoUrl,
|
|
140897
|
-
repoPath: resolvedRepoPath
|
|
140898
|
-
});
|
|
140899
|
-
const now2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
140900
|
-
const session = {
|
|
140901
|
-
id: existing?.id ?? buildBootstrapSessionId(conversationId, input.rawIdea),
|
|
140902
|
-
conversationId,
|
|
140903
|
-
sourceChannel: input.sourceChannel ?? input.sourceRoute?.channel ?? existing?.sourceChannel ?? "telegram",
|
|
140904
|
-
sourceRoute: resolvedSourceRoute,
|
|
140905
|
-
projectRoute: resolvedProjectRoute,
|
|
140906
|
-
requestHash,
|
|
140907
|
-
requestFingerprint: requestHash,
|
|
140908
|
-
lastCompletedRequestHash: input.status === "completed" ? requestHash : existing?.lastCompletedRequestHash ?? null,
|
|
140909
|
-
rawIdea: input.rawIdea,
|
|
140910
|
-
projectName: resolvedProjectName,
|
|
140911
|
-
stackHint: resolvedStackHint,
|
|
140912
|
-
repoUrl: resolvedRepoUrl,
|
|
140913
|
-
repoPath: resolvedRepoPath,
|
|
140914
|
-
projectSlug: resolvedProjectSlug,
|
|
140915
|
-
issueId: resolvedIssueId,
|
|
140916
|
-
messageThreadId: resolvedMessageThreadId,
|
|
140917
|
-
projectChannelId: resolvedProjectChannelId,
|
|
140918
|
-
language: input.language ?? existing?.language,
|
|
140919
|
-
status: input.status,
|
|
140920
|
-
attemptId: resolvedAttemptId,
|
|
140921
|
-
attemptSeq: resolvedAttemptSeq,
|
|
140922
|
-
classifySessionKey: resolvedClassifySessionKey,
|
|
140923
|
-
classifyRunId: resolvedClassifyRunId,
|
|
140924
|
-
classifyStartedAt: resolvedClassifyStartedAt,
|
|
140925
|
-
bootstrapStep: resolvedBootstrapStep,
|
|
140926
|
-
attemptCount: resolvedAttemptCount,
|
|
140927
|
-
lastError: resolvedError,
|
|
140928
|
-
nextRetryAt: resolvedNextRetryAt,
|
|
140929
|
-
ackSentAt: resolvedAckSentAt,
|
|
140930
|
-
projectRegisteredAt: resolvedProjectRegisteredAt,
|
|
140931
|
-
topicKickoffSentAt: resolvedTopicKickoffSentAt,
|
|
140932
|
-
projectTickedAt: resolvedProjectTickedAt,
|
|
140933
|
-
completionAckSentAt: resolvedCompletionAckSentAt,
|
|
140934
|
-
pendingClarification: input.pendingClarification !== void 0 ? input.pendingClarification : existing?.pendingClarification ?? null,
|
|
140935
|
-
orphanedArtifacts: input.orphanedArtifacts !== void 0 ? input.orphanedArtifacts : existing?.orphanedArtifacts ?? null,
|
|
140936
|
-
createdAt: existing?.createdAt ?? now2,
|
|
140937
|
-
updatedAt: now2,
|
|
140938
|
-
suppressUntil: nextSuppressUntil(input.status),
|
|
140939
|
-
error: resolvedError
|
|
140940
|
-
};
|
|
140941
|
-
const writeDecision = shouldPersistBootstrapCheckpoint(existing, session);
|
|
140942
|
-
if (!writeDecision.ok && existing) {
|
|
140943
|
-
return existing;
|
|
141292
|
+
pickupCount++;
|
|
140944
141293
|
}
|
|
140945
|
-
|
|
140946
|
-
return session;
|
|
141294
|
+
return { pickups, skipped };
|
|
140947
141295
|
}
|
|
140948
|
-
function
|
|
140949
|
-
|
|
140950
|
-
if (
|
|
140951
|
-
if (
|
|
140952
|
-
|
|
140953
|
-
if (
|
|
140954
|
-
return buildBootstrapRequestFingerprint(request) === session.requestHash;
|
|
141296
|
+
function resolveLevelForIssue(issue2, role) {
|
|
141297
|
+
const roleLevel = detectRoleLevelFromLabels(issue2.labels);
|
|
141298
|
+
if (roleLevel?.role === role) return roleLevel.level;
|
|
141299
|
+
if (roleLevel) {
|
|
141300
|
+
const levels = getLevelsForRole(role);
|
|
141301
|
+
if (levels.includes(roleLevel.level)) return roleLevel.level;
|
|
140955
141302
|
}
|
|
140956
|
-
|
|
140957
|
-
return buildBootstrapRequestFingerprint(request) === session.requestHash;
|
|
140958
|
-
}
|
|
140959
|
-
function isTelegramBootstrapSessionExpired(session, now2 = Date.now()) {
|
|
140960
|
-
if (!session) return false;
|
|
140961
|
-
const suppressUntil = Date.parse(session.suppressUntil);
|
|
140962
|
-
return !Number.isNaN(suppressUntil) && suppressUntil < now2;
|
|
140963
|
-
}
|
|
140964
|
-
function isRecoverableTelegramBootstrapSession(session) {
|
|
140965
|
-
return session?.status === "bootstrapping" || session?.status === "dispatching";
|
|
140966
|
-
}
|
|
140967
|
-
function isClaimableTelegramBootstrapSession(session, now2 = Date.now()) {
|
|
140968
|
-
if (!isRecoverableTelegramBootstrapSession(session)) return false;
|
|
140969
|
-
if (!session.nextRetryAt) return true;
|
|
140970
|
-
const retryAt = Date.parse(session.nextRetryAt);
|
|
140971
|
-
return Number.isNaN(retryAt) || retryAt <= now2;
|
|
141303
|
+
return selectLevel(issue2.title, issue2.description ?? "", role).level;
|
|
140972
141304
|
}
|
|
140973
|
-
function
|
|
140974
|
-
|
|
140975
|
-
|
|
140976
|
-
const currentHasAttempt = current.attemptId != null && current.attemptSeq != null;
|
|
140977
|
-
const candidateHasAttempt = candidate.attemptId != null && candidate.attemptSeq != null;
|
|
140978
|
-
if (currentHasAttempt || candidateHasAttempt) {
|
|
140979
|
-
return current.attemptId !== candidate.attemptId || current.attemptSeq !== candidate.attemptSeq;
|
|
140980
|
-
}
|
|
140981
|
-
return current.requestHash !== candidate.requestHash || current.status !== candidate.status || current.updatedAt !== candidate.updatedAt;
|
|
141305
|
+
function getFeedbackQueueLabel(workflow) {
|
|
141306
|
+
const developerQueues = getQueueLabels(workflow, "developer");
|
|
141307
|
+
return developerQueues.find((label) => isFeedbackState(workflow, label)) ?? null;
|
|
140982
141308
|
}
|
|
140983
141309
|
|
|
140984
141310
|
// lib/dispatch/telegram-bootstrap-hook.ts
|
|
141311
|
+
init_audit();
|
|
141312
|
+
init_zod();
|
|
140985
141313
|
init_constants();
|
|
140986
141314
|
var BOOTSTRAP_RETRY_DELAY_MS = 15e3;
|
|
140987
141315
|
var LAYER3_CONFIDENCE_THRESHOLD = 0.6;
|
|
@@ -141099,6 +141427,11 @@ function inferProjectSlug(text) {
|
|
|
141099
141427
|
const slug = cleaned.toLowerCase().normalize("NFKD").replace(/[^\w\s-]/g, "").trim().replace(/\s+/g, "-").replace(/-+/g, "-").slice(0, 64);
|
|
141100
141428
|
return slug || void 0;
|
|
141101
141429
|
}
|
|
141430
|
+
function normalizeProjectNameCandidate(value) {
|
|
141431
|
+
const normalized = normalizeText3(value);
|
|
141432
|
+
if (!normalized) return void 0;
|
|
141433
|
+
return inferProjectSlug(normalized) ?? void 0;
|
|
141434
|
+
}
|
|
141102
141435
|
function normalizeText3(value) {
|
|
141103
141436
|
const trimmed = value?.trim();
|
|
141104
141437
|
return trimmed ? trimmed : void 0;
|
|
@@ -141122,7 +141455,7 @@ function detectStackHint(text) {
|
|
|
141122
141455
|
}
|
|
141123
141456
|
function parseField(text, labels) {
|
|
141124
141457
|
for (const label of labels) {
|
|
141125
|
-
const regex = new RegExp(
|
|
141458
|
+
const regex = new RegExp(`(?:^|[\\n.,;!?]\\s*)${label}\\s*:\\s*(.+)$`, "im");
|
|
141126
141459
|
const match = text.match(regex);
|
|
141127
141460
|
if (match?.[1]) return normalizeText3(match[1]);
|
|
141128
141461
|
}
|
|
@@ -141134,12 +141467,12 @@ function parseIdeaBlock(text) {
|
|
|
141134
141467
|
}
|
|
141135
141468
|
function parseExplicitProjectName(text) {
|
|
141136
141469
|
const match = text.match(/\b(?:called|named|chamado)\s+[`"'“”‘’]?([a-z0-9][a-z0-9-]{1,63})[`"'“”‘’]?(?=$|[\s.,!?;:])/i);
|
|
141137
|
-
return match?.[1]?.toLowerCase();
|
|
141470
|
+
return normalizeProjectNameCandidate(match?.[1]?.toLowerCase());
|
|
141138
141471
|
}
|
|
141139
141472
|
function parseBootstrapRequest(text) {
|
|
141140
141473
|
const repoUrl = parseField(text, ["repository url", "repo url", "reposit[o\xF3]rio url", "github repo"]);
|
|
141141
141474
|
const rawIdea = parseIdeaBlock(text) ?? text.trim();
|
|
141142
|
-
const projectName = parseField(text, ["project name", "nome do projeto", "repo name", "repository name"]) ?? parseExplicitProjectName(text);
|
|
141475
|
+
const projectName = normalizeProjectNameCandidate(parseField(text, ["project name", "nome do projeto", "repo name", "repository name"])) ?? parseExplicitProjectName(text);
|
|
141143
141476
|
const repoPath = parseField(text, ["local repository path", "repo path", "caminho local", "local path"]);
|
|
141144
141477
|
const explicitStack = parseField(text, ["stack", "framework", "linguagem"]);
|
|
141145
141478
|
const stackHint = (explicitStack ? normalizeStackHint(explicitStack) : "") || detectStackHint(text);
|
|
@@ -141318,25 +141651,26 @@ function parseClarificationResponse(text, session) {
|
|
|
141318
141651
|
if (autoPatterns.test(normalizeUserResponse(text))) {
|
|
141319
141652
|
return { recognized: true, projectName: inferProjectSlug(session.rawIdea) ?? `project-${Date.now()}`, stackHint: session.stackHint ?? void 0 };
|
|
141320
141653
|
}
|
|
141321
|
-
const nameField = parseField(text, ["project name", "nome do projeto", "nome", "name"]);
|
|
141654
|
+
const nameField = normalizeProjectNameCandidate(parseField(text, ["project name", "nome do projeto", "nome", "name"]));
|
|
141322
141655
|
if (nameField) {
|
|
141323
141656
|
return { recognized: true, projectName: nameField, stackHint: session.stackHint ?? void 0 };
|
|
141324
141657
|
}
|
|
141325
|
-
|
|
141326
|
-
|
|
141658
|
+
const normalizedInlineName = trimmed.length <= 64 ? normalizeProjectNameCandidate(trimmed) : void 0;
|
|
141659
|
+
if (normalizedInlineName) {
|
|
141660
|
+
return { recognized: true, projectName: normalizedInlineName, stackHint: session.stackHint ?? void 0 };
|
|
141327
141661
|
}
|
|
141328
141662
|
return { recognized: false };
|
|
141329
141663
|
}
|
|
141664
|
+
const projectNameFromField = normalizeProjectNameCandidate(parseField(text, ["project name", "nome do projeto", "nome", "name"]));
|
|
141665
|
+
const nameItMatch = !projectNameFromField ? text.match(/(?:name|call|chamar?)\s+(?:it\s+)?([\w-]{2,64})/i) : null;
|
|
141666
|
+
const inlineName = projectNameFromField ?? normalizeProjectNameCandidate(nameItMatch?.[1]);
|
|
141330
141667
|
const stackField = parseField(text, ["stack", "framework", "linguagem", "language"]);
|
|
141331
141668
|
if (stackField) {
|
|
141332
141669
|
const normalizedStack = normalizeStackHint(stackField) || detectStackHint(stackField) || stackField;
|
|
141333
|
-
return { recognized: true, stackHint: normalizedStack };
|
|
141670
|
+
return { recognized: true, stackHint: normalizedStack, projectName: inlineName };
|
|
141334
141671
|
}
|
|
141335
141672
|
const detectedStack = detectStackHint(text);
|
|
141336
141673
|
if (detectedStack) {
|
|
141337
|
-
const projectNameFromField = parseField(text, ["project name", "nome do projeto", "nome", "name"]);
|
|
141338
|
-
const nameItMatch = !projectNameFromField ? text.match(/(?:name|call|chamar?)\s+(?:it\s+)?([\w-]{2,64})/i) : null;
|
|
141339
|
-
const inlineName = projectNameFromField ?? (nameItMatch ? nameItMatch[1].toLowerCase().replace(/[^a-z0-9-]/g, "").replace(/-+/g, "-") || void 0 : void 0);
|
|
141340
141674
|
return { recognized: true, stackHint: detectedStack, projectName: inlineName };
|
|
141341
141675
|
}
|
|
141342
141676
|
const lower2 = normalizeUserResponse(text);
|
|
@@ -141569,7 +141903,7 @@ async function handleTelegramBootstrapDmMessage(ctx, rawConversationId, content)
|
|
|
141569
141903
|
conversationId,
|
|
141570
141904
|
rawIdea: content,
|
|
141571
141905
|
stackHint: parsed.stackHint ?? void 0,
|
|
141572
|
-
projectName: parsed.projectSlug ?? void 0,
|
|
141906
|
+
projectName: parsed.projectName ?? parsed.projectSlug ?? void 0,
|
|
141573
141907
|
status: "clarifying",
|
|
141574
141908
|
pendingClarification: "scope",
|
|
141575
141909
|
language
|
|
@@ -142823,6 +143157,8 @@ Erro: ${result.error ?? "erro desconhecido"}`
|
|
|
142823
143157
|
const projectChannelId = result.payload.metadata.channel_id ?? telegramConfig.projectsForumChatId;
|
|
142824
143158
|
const messageThreadId = result.payload.metadata.message_thread_id;
|
|
142825
143159
|
const projectSlug = result.payload.metadata.project_slug ?? result.payload.scaffold?.project_slug ?? null;
|
|
143160
|
+
const createdIssue = result.payload.issues?.[0] ?? null;
|
|
143161
|
+
const triage = result.payload.triage ?? null;
|
|
142826
143162
|
if (projectChannelId && messageThreadId) {
|
|
142827
143163
|
const projectRoute = {
|
|
142828
143164
|
channel: "telegram",
|
|
@@ -142837,6 +143173,10 @@ Erro: ${result.error ?? "erro desconhecido"}`
|
|
|
142837
143173
|
bootstrapStep: "project_registered",
|
|
142838
143174
|
projectName: resolvedProjectName,
|
|
142839
143175
|
projectSlug: projectSlug ?? void 0,
|
|
143176
|
+
issueId: createdIssue?.number ?? null,
|
|
143177
|
+
issueUrl: createdIssue?.url ?? null,
|
|
143178
|
+
triageReadyForDispatch: triage?.ready_for_dispatch ?? null,
|
|
143179
|
+
triageErrors: triage?.errors ?? null,
|
|
142840
143180
|
projectRegisteredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
142841
143181
|
projectChannelId: String(projectChannelId),
|
|
142842
143182
|
messageThreadId,
|
|
@@ -142852,6 +143192,10 @@ Erro: ${result.error ?? "erro desconhecido"}`
|
|
|
142852
143192
|
status: "dispatching",
|
|
142853
143193
|
projectName: resolvedProjectName,
|
|
142854
143194
|
projectSlug: projectSlug ?? void 0,
|
|
143195
|
+
issueId: createdIssue?.number ?? null,
|
|
143196
|
+
issueUrl: createdIssue?.url ?? null,
|
|
143197
|
+
triageReadyForDispatch: triage?.ready_for_dispatch ?? null,
|
|
143198
|
+
triageErrors: triage?.errors ?? null,
|
|
142855
143199
|
projectRegisteredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
142856
143200
|
projectChannelId: String(projectChannelId),
|
|
142857
143201
|
messageThreadId,
|
|
@@ -142867,6 +143211,10 @@ Erro: ${result.error ?? "erro desconhecido"}`
|
|
|
142867
143211
|
...registeredSession,
|
|
142868
143212
|
projectName: resolvedProjectName,
|
|
142869
143213
|
projectSlug: projectSlug ?? registeredSession.projectSlug ?? void 0,
|
|
143214
|
+
issueId: createdIssue?.number ?? registeredSession.issueId ?? null,
|
|
143215
|
+
issueUrl: createdIssue?.url ?? registeredSession.issueUrl ?? null,
|
|
143216
|
+
triageReadyForDispatch: triage?.ready_for_dispatch ?? registeredSession.triageReadyForDispatch ?? null,
|
|
143217
|
+
triageErrors: triage?.errors ?? registeredSession.triageErrors ?? null,
|
|
142870
143218
|
projectRegisteredAt: registeredSession.projectRegisteredAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
142871
143219
|
language: currentSession?.language ?? "pt"
|
|
142872
143220
|
});
|
|
@@ -143955,6 +144303,7 @@ async function performHealthPass(workspaceDir, projectSlug, project, sessions, p
|
|
|
143955
144303
|
workflow: resolvedConfig?.workflow,
|
|
143956
144304
|
dispatchConfirmTimeoutMs: resolvedConfig?.timeouts?.dispatchConfirmTimeoutMs,
|
|
143957
144305
|
healthGracePeriodMs: resolvedConfig?.timeouts?.healthGracePeriodMs,
|
|
144306
|
+
stallTimeoutMinutes,
|
|
143958
144307
|
runCommand,
|
|
143959
144308
|
notificationConfig: notifyConfig
|
|
143960
144309
|
});
|
|
@@ -146332,8 +146681,18 @@ function stringifyAnswerRecord(record2) {
|
|
|
146332
146681
|
}
|
|
146333
146682
|
return normalized;
|
|
146334
146683
|
}
|
|
146335
|
-
function
|
|
146684
|
+
function extractExplicitProjectName(text) {
|
|
146685
|
+
if (!text) return null;
|
|
146686
|
+
const fieldMatch = text.match(/(?:^|[\n.,;!?]\s*)(?:project name|repo name|repository name|nome do projeto)\s*:\s*([a-z0-9][a-z0-9-]{1,63})\b/i);
|
|
146687
|
+
if (fieldMatch?.[1]) return fieldMatch[1].trim().toLowerCase();
|
|
146688
|
+
const inlineMatch = text.match(/\b(?:called|named|chamado)\s+[`"'“”‘’]?([a-z0-9][a-z0-9-]{1,63})[`"'“”‘’]?(?=$|[\s.,!?;:])/i);
|
|
146689
|
+
if (inlineMatch?.[1]) return inlineMatch[1].trim().toLowerCase();
|
|
146690
|
+
return null;
|
|
146691
|
+
}
|
|
146692
|
+
function deriveProjectName(repoUrl, projectName, freeText) {
|
|
146336
146693
|
if (projectName) return projectName;
|
|
146694
|
+
const parsedFromText = extractExplicitProjectName(freeText ?? null);
|
|
146695
|
+
if (parsedFromText) return parsedFromText;
|
|
146337
146696
|
if (!repoUrl) return null;
|
|
146338
146697
|
const sanitized = repoUrl.replace(/\/+$/, "");
|
|
146339
146698
|
const lastSegment = sanitized.split("/").pop();
|
|
@@ -146423,9 +146782,11 @@ function normalizeGenesisRequest(params, existingPayload) {
|
|
|
146423
146782
|
throw new Error('phase is required and must be "discover" or "commit"');
|
|
146424
146783
|
}
|
|
146425
146784
|
const repoUrl = normalizeOptionalString(params.repo_url) ?? existingPayload?.metadata.repo_url ?? null;
|
|
146785
|
+
const freeTextProjectSource = normalizeOptionalString(params.idea) ?? normalizeOptionalString(params.command) ?? existingPayload?.raw_idea ?? null;
|
|
146426
146786
|
const projectName = deriveProjectName(
|
|
146427
146787
|
repoUrl,
|
|
146428
|
-
normalizeOptionalString(params.project_name) ?? existingPayload?.metadata.project_name ?? null
|
|
146788
|
+
normalizeOptionalString(params.project_name) ?? existingPayload?.metadata.project_name ?? null,
|
|
146789
|
+
freeTextProjectSource
|
|
146429
146790
|
);
|
|
146430
146791
|
const answersJson = {
|
|
146431
146792
|
...existingPayload?.metadata.answers_json ?? {},
|