@mestreyoda/fabrica 0.2.21 → 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 +474 -392
- 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");
|
|
@@ -118618,9 +118618,9 @@ function fallbackSpecData(type, rawIdea) {
|
|
|
118618
118618
|
default:
|
|
118619
118619
|
base.scope_v1 = deriveFeatureScopeFromRawIdea(rawIdea);
|
|
118620
118620
|
base.acceptance_criteria = [
|
|
118621
|
-
"
|
|
118622
|
-
"
|
|
118623
|
-
"
|
|
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
118624
|
];
|
|
118625
118625
|
}
|
|
118626
118626
|
return base;
|
|
@@ -139764,6 +139764,287 @@ var securityReviewStep = {
|
|
|
139764
139764
|
|
|
139765
139765
|
// lib/intake/steps/create-task.ts
|
|
139766
139766
|
init_workflow();
|
|
139767
|
+
|
|
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;
|
|
139805
|
+
}
|
|
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;
|
|
139843
|
+
}
|
|
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" };
|
|
139860
|
+
}
|
|
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
|
+
}
|
|
139869
|
+
}
|
|
139870
|
+
return { ok: true };
|
|
139871
|
+
}
|
|
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
|
|
139767
140048
|
function buildIssueBody(payload) {
|
|
139768
140049
|
const spec = payload.spec;
|
|
139769
140050
|
const sections = [];
|
|
@@ -139835,6 +140116,26 @@ var createTaskStep = {
|
|
|
139835
140116
|
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
139836
140117
|
};
|
|
139837
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
|
+
}
|
|
139838
140139
|
return {
|
|
139839
140140
|
...payload,
|
|
139840
140141
|
step: "create-task",
|
|
@@ -139870,6 +140171,26 @@ var createTaskStep = {
|
|
|
139870
140171
|
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
139871
140172
|
};
|
|
139872
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
|
+
}
|
|
139873
140194
|
return {
|
|
139874
140195
|
...payload,
|
|
139875
140196
|
step: "create-task",
|
|
@@ -139996,7 +140317,7 @@ function runTriageLogic(input, matrix) {
|
|
|
139996
140317
|
objective: input.objective ?? input.rawIdea,
|
|
139997
140318
|
scopeItems: (input.scopeText ?? "").split("\n").filter((l) => l.trim()),
|
|
139998
140319
|
acceptanceCriteria: (input.acText ?? "").split("\n").filter((l) => l.trim()),
|
|
139999
|
-
dod: input.
|
|
140320
|
+
dod: input.dodText ?? ""
|
|
140000
140321
|
});
|
|
140001
140322
|
const specQualityBlock = specQualityErrors.length > 0;
|
|
140002
140323
|
return {
|
|
@@ -140100,8 +140421,9 @@ var triageStep = {
|
|
|
140100
140421
|
rawIdea: payload.raw_idea,
|
|
140101
140422
|
acText: spec.acceptance_criteria.join("\n"),
|
|
140102
140423
|
scopeText: spec.scope_v1.join("\n"),
|
|
140424
|
+
dodText: spec.definition_of_done.join("\n"),
|
|
140103
140425
|
oosText: spec.out_of_scope.join("\n"),
|
|
140104
|
-
authSignal: payload.
|
|
140426
|
+
authSignal: /\b(login|register|jwt|oauth|auth|role-based access|rbac|permission)\b/i.test(`${payload.raw_idea} ${spec.objective}`)
|
|
140105
140427
|
}, matrix);
|
|
140106
140428
|
const repoUrl = payload.scaffold?.repo_url ?? payload.metadata?.repo_url ?? "";
|
|
140107
140429
|
const repoPath = payload.provisioning?.repo_local ?? payload.scaffold?.repo_local ?? payload.metadata?.repo_path ?? payload.project_map?.root ?? void 0;
|
|
@@ -140229,6 +140551,27 @@ var triageStep = {
|
|
|
140229
140551
|
child_issue_numbers: createdChildIssueNumbers
|
|
140230
140552
|
};
|
|
140231
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
|
+
}
|
|
140232
140575
|
return {
|
|
140233
140576
|
...payload,
|
|
140234
140577
|
step: "triage",
|
|
@@ -140576,23 +140919,23 @@ init_workflow();
|
|
|
140576
140919
|
|
|
140577
140920
|
// lib/dispatch/dispatch-dedup.ts
|
|
140578
140921
|
init_constants();
|
|
140579
|
-
import
|
|
140580
|
-
import
|
|
140581
|
-
import { createHash as
|
|
140922
|
+
import fs35 from "node:fs/promises";
|
|
140923
|
+
import path36 from "node:path";
|
|
140924
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
140582
140925
|
var DEDUP_FILE = "dispatch-dedup.ndjson";
|
|
140583
140926
|
var BUCKET_MS = 5 * 6e4;
|
|
140584
140927
|
var DEFAULT_TTL_MS2 = 30 * 6e4;
|
|
140585
140928
|
function dedupPath(workspaceDir) {
|
|
140586
|
-
return
|
|
140929
|
+
return path36.join(workspaceDir, DATA_DIR, DEDUP_FILE);
|
|
140587
140930
|
}
|
|
140588
140931
|
function computeDispatchId(projectSlug, issueId, role, level, now2 = Date.now()) {
|
|
140589
140932
|
const bucket = Math.floor(now2 / BUCKET_MS);
|
|
140590
140933
|
const input = `${projectSlug}:${issueId}:${role}:${level}:${bucket}`;
|
|
140591
|
-
return
|
|
140934
|
+
return createHash5("sha256").update(input).digest("hex").slice(0, 16);
|
|
140592
140935
|
}
|
|
140593
140936
|
async function readEntries2(filePath) {
|
|
140594
140937
|
try {
|
|
140595
|
-
const content = await
|
|
140938
|
+
const content = await fs35.readFile(filePath, "utf-8");
|
|
140596
140939
|
return content.split("\n").filter(Boolean).map((line) => {
|
|
140597
140940
|
try {
|
|
140598
140941
|
return JSON.parse(line);
|
|
@@ -140611,9 +140954,9 @@ async function isDuplicate(workspaceDir, dispatchId) {
|
|
|
140611
140954
|
}
|
|
140612
140955
|
async function recordDispatch(workspaceDir, dispatchId) {
|
|
140613
140956
|
const filePath = dedupPath(workspaceDir);
|
|
140614
|
-
await
|
|
140957
|
+
await fs35.mkdir(path36.dirname(filePath), { recursive: true });
|
|
140615
140958
|
const entry = { id: dispatchId, ts: Date.now() };
|
|
140616
|
-
await
|
|
140959
|
+
await fs35.appendFile(filePath, JSON.stringify(entry) + "\n", "utf-8");
|
|
140617
140960
|
}
|
|
140618
140961
|
async function cleanupExpired(workspaceDir, ttlMs = DEFAULT_TTL_MS2) {
|
|
140619
140962
|
const filePath = dedupPath(workspaceDir);
|
|
@@ -140623,8 +140966,8 @@ async function cleanupExpired(workspaceDir, ttlMs = DEFAULT_TTL_MS2) {
|
|
|
140623
140966
|
if (kept.length < entries.length) {
|
|
140624
140967
|
const content = kept.map((e2) => JSON.stringify(e2)).join("\n") + (kept.length > 0 ? "\n" : "");
|
|
140625
140968
|
const tmpPath = filePath + ".tmp";
|
|
140626
|
-
await
|
|
140627
|
-
await
|
|
140969
|
+
await fs35.writeFile(tmpPath, content, "utf-8");
|
|
140970
|
+
await fs35.rename(tmpPath, filePath);
|
|
140628
140971
|
}
|
|
140629
140972
|
}
|
|
140630
140973
|
|
|
@@ -140856,392 +141199,117 @@ async function projectTick(opts) {
|
|
|
140856
141199
|
if (dryRun) {
|
|
140857
141200
|
const existingSession = roleWorker.levels[effectiveLevel]?.[freeSlot]?.sessionKey;
|
|
140858
141201
|
pickups.push({
|
|
140859
|
-
project: project.name,
|
|
140860
|
-
projectSlug,
|
|
140861
|
-
issueId: issue2.iid,
|
|
140862
|
-
issueTitle: issue2.title,
|
|
140863
|
-
issueUrl: issue2.web_url,
|
|
140864
|
-
role,
|
|
140865
|
-
level: effectiveLevel,
|
|
140866
|
-
sessionAction: existingSession ? "send" : "spawn",
|
|
140867
|
-
announcement: `[DRY RUN] Would pick up #${issue2.iid}`
|
|
140868
|
-
});
|
|
140869
|
-
pickupCount++;
|
|
140870
|
-
continue;
|
|
140871
|
-
}
|
|
140872
|
-
const environment = await ensureEnvironment({
|
|
140873
|
-
workspaceDir,
|
|
140874
|
-
projectSlug,
|
|
140875
|
-
project: fresh,
|
|
140876
|
-
stack,
|
|
140877
|
-
mode: role === "tester" ? "tester" : "developer",
|
|
140878
|
-
runCommand
|
|
140879
|
-
});
|
|
140880
|
-
if (!environment.ready) {
|
|
140881
|
-
const environmentSkipReason = classifyEnvironmentGateSkip(environment.state);
|
|
140882
|
-
skipped.push({ role, reason: environmentSkipReason });
|
|
140883
|
-
await log(workspaceDir, "dispatch_blocked_environment_not_ready", {
|
|
140884
|
-
projectSlug,
|
|
140885
|
-
role,
|
|
140886
|
-
issueId: issue2.iid,
|
|
140887
|
-
reason: environmentSkipReason,
|
|
140888
|
-
environmentStatus: environment.state.status,
|
|
140889
|
-
nextProvisionRetryAt: environment.state.nextProvisionRetryAt ?? null,
|
|
140890
|
-
lastProvisionError: environment.state.lastProvisionError ?? null
|
|
140891
|
-
}).catch(() => {
|
|
140892
|
-
});
|
|
140893
|
-
continue;
|
|
140894
|
-
}
|
|
140895
|
-
}
|
|
140896
|
-
if (dryRun) {
|
|
140897
|
-
const existingSession = roleWorker.levels[effectiveLevel]?.[freeSlot]?.sessionKey;
|
|
140898
|
-
pickups.push({
|
|
140899
|
-
project: project.name,
|
|
140900
|
-
projectSlug,
|
|
140901
|
-
issueId: issue2.iid,
|
|
140902
|
-
issueTitle: issue2.title,
|
|
140903
|
-
issueUrl: issue2.web_url,
|
|
140904
|
-
role,
|
|
140905
|
-
level: effectiveLevel,
|
|
140906
|
-
sessionAction: existingSession ? "send" : "spawn",
|
|
140907
|
-
announcement: `[DRY RUN] Would pick up #${issue2.iid}`
|
|
140908
|
-
});
|
|
140909
|
-
} else {
|
|
140910
|
-
try {
|
|
140911
|
-
const dr = await dispatch({
|
|
140912
|
-
workspaceDir,
|
|
140913
|
-
agentId,
|
|
140914
|
-
project: fresh,
|
|
140915
|
-
issueId: issue2.iid,
|
|
140916
|
-
issueTitle: issue2.title,
|
|
140917
|
-
issueDescription: issue2.description ?? "",
|
|
140918
|
-
issueUrl: issue2.web_url,
|
|
140919
|
-
role,
|
|
140920
|
-
level: effectiveLevel,
|
|
140921
|
-
fromLabel: currentLabel,
|
|
140922
|
-
toLabel: targetLabel,
|
|
140923
|
-
provider,
|
|
140924
|
-
pluginConfig,
|
|
140925
|
-
sessionKey,
|
|
140926
|
-
runtime,
|
|
140927
|
-
slotIndex: freeSlot,
|
|
140928
|
-
instanceName,
|
|
140929
|
-
runCommand
|
|
140930
|
-
});
|
|
140931
|
-
pickups.push({
|
|
140932
|
-
project: project.name,
|
|
140933
|
-
projectSlug,
|
|
140934
|
-
issueId: issue2.iid,
|
|
140935
|
-
issueTitle: issue2.title,
|
|
140936
|
-
issueUrl: issue2.web_url,
|
|
140937
|
-
role,
|
|
140938
|
-
level: dr.level,
|
|
140939
|
-
sessionAction: dr.sessionAction,
|
|
140940
|
-
announcement: dr.announcement
|
|
140941
|
-
});
|
|
140942
|
-
await recordDispatch(workspaceDir, dispatchId).catch(() => {
|
|
140943
|
-
});
|
|
140944
|
-
} catch (err) {
|
|
140945
|
-
skipped.push({ role, reason: `Dispatch failed: ${err.message}` });
|
|
140946
|
-
continue;
|
|
140947
|
-
}
|
|
140948
|
-
}
|
|
140949
|
-
pickupCount++;
|
|
140950
|
-
}
|
|
140951
|
-
return { pickups, skipped };
|
|
140952
|
-
}
|
|
140953
|
-
function resolveLevelForIssue(issue2, role) {
|
|
140954
|
-
const roleLevel = detectRoleLevelFromLabels(issue2.labels);
|
|
140955
|
-
if (roleLevel?.role === role) return roleLevel.level;
|
|
140956
|
-
if (roleLevel) {
|
|
140957
|
-
const levels = getLevelsForRole(role);
|
|
140958
|
-
if (levels.includes(roleLevel.level)) return roleLevel.level;
|
|
140959
|
-
}
|
|
140960
|
-
return selectLevel(issue2.title, issue2.description ?? "", role).level;
|
|
140961
|
-
}
|
|
140962
|
-
function getFeedbackQueueLabel(workflow) {
|
|
140963
|
-
const developerQueues = getQueueLabels(workflow, "developer");
|
|
140964
|
-
return developerQueues.find((label) => isFeedbackState(workflow, label)) ?? null;
|
|
140965
|
-
}
|
|
140966
|
-
|
|
140967
|
-
// lib/dispatch/telegram-bootstrap-hook.ts
|
|
140968
|
-
init_audit();
|
|
140969
|
-
init_zod();
|
|
140970
|
-
|
|
140971
|
-
// lib/dispatch/telegram-bootstrap-session.ts
|
|
140972
|
-
init_constants();
|
|
140973
|
-
import { createHash as createHash5, randomUUID as randomUUID4 } from "node:crypto";
|
|
140974
|
-
import fs35 from "node:fs/promises";
|
|
140975
|
-
import path36 from "node:path";
|
|
140976
|
-
var SESSION_TTL_MS = 10 * 6e4;
|
|
140977
|
-
var CLASSIFYING_TTL_MS = 15e3;
|
|
140978
|
-
var RELEASED_CLASSIFY_ERRORS = /* @__PURE__ */ new Set([
|
|
140979
|
-
"classify_invalid_result",
|
|
140980
|
-
"classify_low_confidence",
|
|
140981
|
-
"classify_not_project",
|
|
140982
|
-
"classify_result_expired"
|
|
140983
|
-
]);
|
|
140984
|
-
function toCanonicalTelegramBootstrapConversationId(conversationId) {
|
|
140985
|
-
const trimmed = conversationId.trim();
|
|
140986
|
-
if (!trimmed) return trimmed;
|
|
140987
|
-
return trimmed.startsWith("telegram:") ? trimmed : `telegram:${trimmed}`;
|
|
140988
|
-
}
|
|
140989
|
-
function sessionsDir(workspaceDir) {
|
|
140990
|
-
return path36.join(workspaceDir, DATA_DIR, "bootstrap-sessions");
|
|
140991
|
-
}
|
|
140992
|
-
function sessionPath(workspaceDir, conversationId) {
|
|
140993
|
-
return path36.join(
|
|
140994
|
-
sessionsDir(workspaceDir),
|
|
140995
|
-
`${toCanonicalTelegramBootstrapConversationId(conversationId)}.json`
|
|
140996
|
-
);
|
|
140997
|
-
}
|
|
140998
|
-
function alternateSessionPath(workspaceDir, conversationId) {
|
|
140999
|
-
const canonical = toCanonicalTelegramBootstrapConversationId(conversationId);
|
|
141000
|
-
const bare = canonical.startsWith("telegram:") ? canonical.slice("telegram:".length) : canonical;
|
|
141001
|
-
if (!bare || bare === canonical) return null;
|
|
141002
|
-
return path36.join(sessionsDir(workspaceDir), `${bare}.json`);
|
|
141003
|
-
}
|
|
141004
|
-
function normalizeStoredSession(session) {
|
|
141005
|
-
const canonicalConversationId = toCanonicalTelegramBootstrapConversationId(session.conversationId);
|
|
141006
|
-
if (canonicalConversationId === session.conversationId) {
|
|
141007
|
-
return session;
|
|
141008
|
-
}
|
|
141009
|
-
return {
|
|
141010
|
-
...session,
|
|
141011
|
-
id: buildBootstrapSessionId(canonicalConversationId, session.rawIdea),
|
|
141012
|
-
conversationId: canonicalConversationId
|
|
141013
|
-
};
|
|
141014
|
-
}
|
|
141015
|
-
function stableHash(input) {
|
|
141016
|
-
return createHash5("sha256").update(input).digest("hex").slice(0, 16);
|
|
141017
|
-
}
|
|
141018
|
-
function buildBootstrapSessionId(conversationId, rawIdea) {
|
|
141019
|
-
return `tgdm-${conversationId}-${stableHash(rawIdea.trim().toLowerCase())}`;
|
|
141020
|
-
}
|
|
141021
|
-
function buildBootstrapRequestFingerprint(input) {
|
|
141022
|
-
return stableHash(JSON.stringify({
|
|
141023
|
-
rawIdea: input.rawIdea.trim().toLowerCase(),
|
|
141024
|
-
projectName: input.projectName?.trim().toLowerCase() || null,
|
|
141025
|
-
stackHint: input.stackHint?.trim().toLowerCase() || null,
|
|
141026
|
-
repoUrl: input.repoUrl?.trim().toLowerCase() || null,
|
|
141027
|
-
repoPath: input.repoPath?.trim().toLowerCase() || null
|
|
141028
|
-
}));
|
|
141029
|
-
}
|
|
141030
|
-
function buildBootstrapRequestHash(input) {
|
|
141031
|
-
return buildBootstrapRequestFingerprint(input);
|
|
141032
|
-
}
|
|
141033
|
-
function nextSuppressUntil(status) {
|
|
141034
|
-
const ttl = status === "classifying" || status === "pending_classify" ? CLASSIFYING_TTL_MS : SESSION_TTL_MS;
|
|
141035
|
-
return new Date(Date.now() + ttl).toISOString();
|
|
141036
|
-
}
|
|
141037
|
-
function isReleasedClassifyFailure(error48) {
|
|
141038
|
-
return Boolean(error48 && RELEASED_CLASSIFY_ERRORS.has(error48));
|
|
141039
|
-
}
|
|
141040
|
-
function resolveNullableField(inputValue, existingValue, fallback = null) {
|
|
141041
|
-
return inputValue !== void 0 ? inputValue : existingValue ?? fallback;
|
|
141042
|
-
}
|
|
141043
|
-
function defaultNextRetryAtForStatus(status, existingValue) {
|
|
141044
|
-
if (status === "bootstrapping" || status === "dispatching") {
|
|
141045
|
-
return existingValue ?? null;
|
|
141046
|
-
}
|
|
141047
|
-
return null;
|
|
141048
|
-
}
|
|
141049
|
-
var MONOTONIC_BOOTSTRAP_FIELDS = [
|
|
141050
|
-
"ackSentAt",
|
|
141051
|
-
"projectRegisteredAt",
|
|
141052
|
-
"topicKickoffSentAt",
|
|
141053
|
-
"projectTickedAt",
|
|
141054
|
-
"completionAckSentAt"
|
|
141055
|
-
];
|
|
141056
|
-
function shouldPersistBootstrapCheckpoint(current, next) {
|
|
141057
|
-
if (!current) return { ok: true };
|
|
141058
|
-
if (current.conversationId !== next.conversationId) return { ok: true };
|
|
141059
|
-
const currentAttemptSeq = current.attemptSeq ?? null;
|
|
141060
|
-
const nextAttemptSeq = next.attemptSeq ?? null;
|
|
141061
|
-
if (currentAttemptSeq != null && nextAttemptSeq != null && nextAttemptSeq < currentAttemptSeq) {
|
|
141062
|
-
return { ok: false, reason: "stale_regression" };
|
|
141063
|
-
}
|
|
141064
|
-
const sameAttempt = Boolean(
|
|
141065
|
-
current.attemptId && next.attemptId && current.attemptSeq != null && next.attemptSeq != null && current.attemptId === next.attemptId && current.attemptSeq === next.attemptSeq
|
|
141066
|
-
);
|
|
141067
|
-
if (!sameAttempt) return { ok: true };
|
|
141068
|
-
for (const field of MONOTONIC_BOOTSTRAP_FIELDS) {
|
|
141069
|
-
if (current[field] && !next[field]) {
|
|
141070
|
-
return { ok: false, reason: "stale_regression" };
|
|
141202
|
+
project: project.name,
|
|
141203
|
+
projectSlug,
|
|
141204
|
+
issueId: issue2.iid,
|
|
141205
|
+
issueTitle: issue2.title,
|
|
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
|
+
}
|
|
141071
141238
|
}
|
|
141072
|
-
|
|
141073
|
-
|
|
141074
|
-
|
|
141075
|
-
|
|
141076
|
-
|
|
141077
|
-
|
|
141078
|
-
|
|
141079
|
-
|
|
141080
|
-
|
|
141081
|
-
|
|
141082
|
-
|
|
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 {
|
|
141083
141253
|
try {
|
|
141084
|
-
const
|
|
141085
|
-
|
|
141086
|
-
|
|
141087
|
-
|
|
141088
|
-
|
|
141089
|
-
|
|
141090
|
-
|
|
141091
|
-
|
|
141092
|
-
|
|
141093
|
-
|
|
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;
|
|
141094
141290
|
}
|
|
141095
141291
|
}
|
|
141096
|
-
|
|
141097
|
-
} catch (error48) {
|
|
141098
|
-
if (error48?.code === "ENOENT") return null;
|
|
141099
|
-
throw error48;
|
|
141100
|
-
}
|
|
141101
|
-
}
|
|
141102
|
-
async function deleteTelegramBootstrapSession(workspaceDir, conversationId) {
|
|
141103
|
-
await fs35.unlink(sessionPath(workspaceDir, conversationId)).catch(() => {
|
|
141104
|
-
});
|
|
141105
|
-
const legacyPath = alternateSessionPath(workspaceDir, conversationId);
|
|
141106
|
-
if (legacyPath) {
|
|
141107
|
-
await fs35.unlink(legacyPath).catch(() => {
|
|
141108
|
-
});
|
|
141109
|
-
}
|
|
141110
|
-
}
|
|
141111
|
-
async function writeTelegramBootstrapSession(workspaceDir, session) {
|
|
141112
|
-
const canonicalSession = normalizeStoredSession(session);
|
|
141113
|
-
const dir = sessionsDir(workspaceDir);
|
|
141114
|
-
await fs35.mkdir(dir, { recursive: true });
|
|
141115
|
-
const file2 = sessionPath(workspaceDir, canonicalSession.conversationId);
|
|
141116
|
-
const tmp = `${file2}.${randomUUID4()}.tmp`;
|
|
141117
|
-
await fs35.writeFile(tmp, JSON.stringify(canonicalSession, null, 2) + "\n", "utf-8");
|
|
141118
|
-
await fs35.rename(tmp, file2);
|
|
141119
|
-
const legacyPath = alternateSessionPath(workspaceDir, session.conversationId);
|
|
141120
|
-
if (legacyPath) {
|
|
141121
|
-
await fs35.unlink(legacyPath).catch(() => {
|
|
141122
|
-
});
|
|
141123
|
-
}
|
|
141124
|
-
}
|
|
141125
|
-
async function upsertTelegramBootstrapSession(workspaceDir, input) {
|
|
141126
|
-
const conversationId = toCanonicalTelegramBootstrapConversationId(input.conversationId);
|
|
141127
|
-
const existing = await readTelegramBootstrapSession(workspaceDir, conversationId);
|
|
141128
|
-
const resolvedSourceRoute = resolveNullableField(input.sourceRoute, existing?.sourceRoute);
|
|
141129
|
-
const resolvedProjectRoute = resolveNullableField(input.projectRoute, existing?.projectRoute);
|
|
141130
|
-
const resolvedProjectName = resolveNullableField(input.projectName, existing?.projectName);
|
|
141131
|
-
const resolvedStackHint = resolveNullableField(input.stackHint, existing?.stackHint);
|
|
141132
|
-
const resolvedRepoUrl = resolveNullableField(input.repoUrl, existing?.repoUrl);
|
|
141133
|
-
const resolvedRepoPath = resolveNullableField(input.repoPath, existing?.repoPath);
|
|
141134
|
-
const resolvedProjectSlug = resolveNullableField(input.projectSlug, existing?.projectSlug);
|
|
141135
|
-
const resolvedIssueId = resolveNullableField(input.issueId, existing?.issueId);
|
|
141136
|
-
const resolvedMessageThreadId = resolveNullableField(input.messageThreadId, existing?.messageThreadId);
|
|
141137
|
-
const resolvedProjectChannelId = resolveNullableField(input.projectChannelId, existing?.projectChannelId);
|
|
141138
|
-
const resolvedAttemptCount = resolveNullableField(input.attemptCount, existing?.attemptCount, 0);
|
|
141139
|
-
const resolvedAttemptId = resolveNullableField(input.attemptId, existing?.attemptId);
|
|
141140
|
-
const resolvedAttemptSeq = resolveNullableField(input.attemptSeq, existing?.attemptSeq);
|
|
141141
|
-
const resolvedClassifySessionKey = resolveNullableField(input.classifySessionKey, existing?.classifySessionKey);
|
|
141142
|
-
const resolvedClassifyRunId = resolveNullableField(input.classifyRunId, existing?.classifyRunId);
|
|
141143
|
-
const resolvedClassifyStartedAt = resolveNullableField(input.classifyStartedAt, existing?.classifyStartedAt);
|
|
141144
|
-
const resolvedBootstrapStep = resolveNullableField(input.bootstrapStep, existing?.bootstrapStep);
|
|
141145
|
-
const resolvedNextRetryAt = input.nextRetryAt !== void 0 ? input.nextRetryAt : defaultNextRetryAtForStatus(input.status, existing?.nextRetryAt);
|
|
141146
|
-
const resolvedAckSentAt = resolveNullableField(input.ackSentAt, existing?.ackSentAt);
|
|
141147
|
-
const resolvedProjectRegisteredAt = resolveNullableField(input.projectRegisteredAt, existing?.projectRegisteredAt);
|
|
141148
|
-
const resolvedTopicKickoffSentAt = resolveNullableField(input.topicKickoffSentAt, existing?.topicKickoffSentAt);
|
|
141149
|
-
const resolvedProjectTickedAt = resolveNullableField(input.projectTickedAt, existing?.projectTickedAt);
|
|
141150
|
-
const resolvedCompletionAckSentAt = resolveNullableField(input.completionAckSentAt, existing?.completionAckSentAt);
|
|
141151
|
-
const resolvedError = input.error !== void 0 ? input.error : input.lastError !== void 0 ? input.lastError : existing?.error ?? existing?.lastError ?? null;
|
|
141152
|
-
const requestHash = buildBootstrapRequestHash({
|
|
141153
|
-
rawIdea: input.rawIdea,
|
|
141154
|
-
projectName: resolvedProjectName,
|
|
141155
|
-
stackHint: resolvedStackHint,
|
|
141156
|
-
repoUrl: resolvedRepoUrl,
|
|
141157
|
-
repoPath: resolvedRepoPath
|
|
141158
|
-
});
|
|
141159
|
-
const now2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
141160
|
-
const session = {
|
|
141161
|
-
id: existing?.id ?? buildBootstrapSessionId(conversationId, input.rawIdea),
|
|
141162
|
-
conversationId,
|
|
141163
|
-
sourceChannel: input.sourceChannel ?? input.sourceRoute?.channel ?? existing?.sourceChannel ?? "telegram",
|
|
141164
|
-
sourceRoute: resolvedSourceRoute,
|
|
141165
|
-
projectRoute: resolvedProjectRoute,
|
|
141166
|
-
requestHash,
|
|
141167
|
-
requestFingerprint: requestHash,
|
|
141168
|
-
lastCompletedRequestHash: input.status === "completed" ? requestHash : existing?.lastCompletedRequestHash ?? null,
|
|
141169
|
-
rawIdea: input.rawIdea,
|
|
141170
|
-
projectName: resolvedProjectName,
|
|
141171
|
-
stackHint: resolvedStackHint,
|
|
141172
|
-
repoUrl: resolvedRepoUrl,
|
|
141173
|
-
repoPath: resolvedRepoPath,
|
|
141174
|
-
projectSlug: resolvedProjectSlug,
|
|
141175
|
-
issueId: resolvedIssueId,
|
|
141176
|
-
messageThreadId: resolvedMessageThreadId,
|
|
141177
|
-
projectChannelId: resolvedProjectChannelId,
|
|
141178
|
-
language: input.language ?? existing?.language,
|
|
141179
|
-
status: input.status,
|
|
141180
|
-
attemptId: resolvedAttemptId,
|
|
141181
|
-
attemptSeq: resolvedAttemptSeq,
|
|
141182
|
-
classifySessionKey: resolvedClassifySessionKey,
|
|
141183
|
-
classifyRunId: resolvedClassifyRunId,
|
|
141184
|
-
classifyStartedAt: resolvedClassifyStartedAt,
|
|
141185
|
-
bootstrapStep: resolvedBootstrapStep,
|
|
141186
|
-
attemptCount: resolvedAttemptCount,
|
|
141187
|
-
lastError: resolvedError,
|
|
141188
|
-
nextRetryAt: resolvedNextRetryAt,
|
|
141189
|
-
ackSentAt: resolvedAckSentAt,
|
|
141190
|
-
projectRegisteredAt: resolvedProjectRegisteredAt,
|
|
141191
|
-
topicKickoffSentAt: resolvedTopicKickoffSentAt,
|
|
141192
|
-
projectTickedAt: resolvedProjectTickedAt,
|
|
141193
|
-
completionAckSentAt: resolvedCompletionAckSentAt,
|
|
141194
|
-
pendingClarification: input.pendingClarification !== void 0 ? input.pendingClarification : existing?.pendingClarification ?? null,
|
|
141195
|
-
orphanedArtifacts: input.orphanedArtifacts !== void 0 ? input.orphanedArtifacts : existing?.orphanedArtifacts ?? null,
|
|
141196
|
-
createdAt: existing?.createdAt ?? now2,
|
|
141197
|
-
updatedAt: now2,
|
|
141198
|
-
suppressUntil: nextSuppressUntil(input.status),
|
|
141199
|
-
error: resolvedError
|
|
141200
|
-
};
|
|
141201
|
-
const writeDecision = shouldPersistBootstrapCheckpoint(existing, session);
|
|
141202
|
-
if (!writeDecision.ok && existing) {
|
|
141203
|
-
return existing;
|
|
141292
|
+
pickupCount++;
|
|
141204
141293
|
}
|
|
141205
|
-
|
|
141206
|
-
return session;
|
|
141294
|
+
return { pickups, skipped };
|
|
141207
141295
|
}
|
|
141208
|
-
function
|
|
141209
|
-
|
|
141210
|
-
if (
|
|
141211
|
-
if (
|
|
141212
|
-
|
|
141213
|
-
if (
|
|
141214
|
-
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;
|
|
141215
141302
|
}
|
|
141216
|
-
|
|
141217
|
-
return buildBootstrapRequestFingerprint(request) === session.requestHash;
|
|
141218
|
-
}
|
|
141219
|
-
function isTelegramBootstrapSessionExpired(session, now2 = Date.now()) {
|
|
141220
|
-
if (!session) return false;
|
|
141221
|
-
const suppressUntil = Date.parse(session.suppressUntil);
|
|
141222
|
-
return !Number.isNaN(suppressUntil) && suppressUntil < now2;
|
|
141223
|
-
}
|
|
141224
|
-
function isRecoverableTelegramBootstrapSession(session) {
|
|
141225
|
-
return session?.status === "bootstrapping" || session?.status === "dispatching";
|
|
141226
|
-
}
|
|
141227
|
-
function isClaimableTelegramBootstrapSession(session, now2 = Date.now()) {
|
|
141228
|
-
if (!isRecoverableTelegramBootstrapSession(session)) return false;
|
|
141229
|
-
if (!session.nextRetryAt) return true;
|
|
141230
|
-
const retryAt = Date.parse(session.nextRetryAt);
|
|
141231
|
-
return Number.isNaN(retryAt) || retryAt <= now2;
|
|
141303
|
+
return selectLevel(issue2.title, issue2.description ?? "", role).level;
|
|
141232
141304
|
}
|
|
141233
|
-
function
|
|
141234
|
-
|
|
141235
|
-
|
|
141236
|
-
const currentHasAttempt = current.attemptId != null && current.attemptSeq != null;
|
|
141237
|
-
const candidateHasAttempt = candidate.attemptId != null && candidate.attemptSeq != null;
|
|
141238
|
-
if (currentHasAttempt || candidateHasAttempt) {
|
|
141239
|
-
return current.attemptId !== candidate.attemptId || current.attemptSeq !== candidate.attemptSeq;
|
|
141240
|
-
}
|
|
141241
|
-
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;
|
|
141242
141308
|
}
|
|
141243
141309
|
|
|
141244
141310
|
// lib/dispatch/telegram-bootstrap-hook.ts
|
|
141311
|
+
init_audit();
|
|
141312
|
+
init_zod();
|
|
141245
141313
|
init_constants();
|
|
141246
141314
|
var BOOTSTRAP_RETRY_DELAY_MS = 15e3;
|
|
141247
141315
|
var LAYER3_CONFIDENCE_THRESHOLD = 0.6;
|
|
@@ -143089,6 +143157,8 @@ Erro: ${result.error ?? "erro desconhecido"}`
|
|
|
143089
143157
|
const projectChannelId = result.payload.metadata.channel_id ?? telegramConfig.projectsForumChatId;
|
|
143090
143158
|
const messageThreadId = result.payload.metadata.message_thread_id;
|
|
143091
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;
|
|
143092
143162
|
if (projectChannelId && messageThreadId) {
|
|
143093
143163
|
const projectRoute = {
|
|
143094
143164
|
channel: "telegram",
|
|
@@ -143103,6 +143173,10 @@ Erro: ${result.error ?? "erro desconhecido"}`
|
|
|
143103
143173
|
bootstrapStep: "project_registered",
|
|
143104
143174
|
projectName: resolvedProjectName,
|
|
143105
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,
|
|
143106
143180
|
projectRegisteredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
143107
143181
|
projectChannelId: String(projectChannelId),
|
|
143108
143182
|
messageThreadId,
|
|
@@ -143118,6 +143192,10 @@ Erro: ${result.error ?? "erro desconhecido"}`
|
|
|
143118
143192
|
status: "dispatching",
|
|
143119
143193
|
projectName: resolvedProjectName,
|
|
143120
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,
|
|
143121
143199
|
projectRegisteredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
143122
143200
|
projectChannelId: String(projectChannelId),
|
|
143123
143201
|
messageThreadId,
|
|
@@ -143133,6 +143211,10 @@ Erro: ${result.error ?? "erro desconhecido"}`
|
|
|
143133
143211
|
...registeredSession,
|
|
143134
143212
|
projectName: resolvedProjectName,
|
|
143135
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,
|
|
143136
143218
|
projectRegisteredAt: registeredSession.projectRegisteredAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
143137
143219
|
language: currentSession?.language ?? "pt"
|
|
143138
143220
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mestreyoda/fabrica",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.22",
|
|
4
4
|
"description": "Autonomous software engineering pipeline for OpenClaw. Turns ideas into deployed code via intake, dispatch, review, test, and merge.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|