@defend-tech/opencode-optima 0.1.48 → 0.1.50
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 +107 -35
- package/dist/sanitize_cli.js +107 -35
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7938,6 +7938,7 @@ var CLICKUP_WEBHOOK_MAX_BODY_BYTES = 1024 * 1024;
|
|
|
7938
7938
|
var CLICKUP_WEBHOOK_REQUEST_TIMEOUT_MS = 1e4;
|
|
7939
7939
|
var CLICKUP_WEBHOOK_STARTUP_TASK_LIMIT = 50;
|
|
7940
7940
|
var CLICKUP_WEBHOOK_STARTUP_COMMENT_LIMIT = 100;
|
|
7941
|
+
var CLICKUP_WEBHOOK_STARTUP_RECONCILIATION_DELAY_MS = 3e4;
|
|
7941
7942
|
var CLICKUP_WEBHOOK_LOG_LEVELS = /* @__PURE__ */ new Set(["error", "info", "verbose"]);
|
|
7942
7943
|
var CLICKUP_WEBHOOK_REDACTED = "[REDACTED]";
|
|
7943
7944
|
var DISCUSSION_BACKFILL_FETCH_LIMIT = 100;
|
|
@@ -8163,6 +8164,7 @@ function hasActionableClickUpRoute(result = {}) {
|
|
|
8163
8164
|
if (!result.sessionId) return false;
|
|
8164
8165
|
if (result.action === "message_delivery_failed" || result.action === "error") return false;
|
|
8165
8166
|
if (result.deliveryVerification?.ok === false) return false;
|
|
8167
|
+
if (result.deliveryVerification?.method === "prompt_admission") return false;
|
|
8166
8168
|
return true;
|
|
8167
8169
|
}
|
|
8168
8170
|
function determineClickUpMergeAuthority({ isSubtask = false, clickupStatus = "", validationPassed = false, mergeFailed = false, finalApprovalRoles = CLICKUP_FINAL_APPROVER_ROLES, humansRegistry } = {}) {
|
|
@@ -9012,6 +9014,11 @@ function normalizeOpenCodeBaseUrl(value, defaultValue = "http://127.0.0.1:3001")
|
|
|
9012
9014
|
return candidate.replace(/\/+$/, "");
|
|
9013
9015
|
}
|
|
9014
9016
|
}
|
|
9017
|
+
function normalizeNonNegativeInteger(value, defaultValue) {
|
|
9018
|
+
if (value === void 0 || value === null || value === "") return defaultValue;
|
|
9019
|
+
const number = Number(value);
|
|
9020
|
+
return Number.isInteger(number) && number >= 0 ? number : defaultValue;
|
|
9021
|
+
}
|
|
9015
9022
|
function normalizeClickUpWebhookConfig(rawClickUp = null, worktree = process.cwd()) {
|
|
9016
9023
|
const raw = isPlainObject(rawClickUp) ? rawClickUp : {};
|
|
9017
9024
|
const webhook = isPlainObject(raw.webhook) ? raw.webhook : {};
|
|
@@ -9027,7 +9034,11 @@ function normalizeClickUpWebhookConfig(rawClickUp = null, worktree = process.cwd
|
|
|
9027
9034
|
apiToken: String(raw.api_token || raw.apiToken || "").trim(),
|
|
9028
9035
|
log: normalizeClickUpWebhookLogLevel(raw.log),
|
|
9029
9036
|
opencode: {
|
|
9030
|
-
baseUrl: normalizeOpenCodeBaseUrl(opencode.base_url || opencode.baseUrl || raw.opencode_base_url || raw.opencodeBaseUrl)
|
|
9037
|
+
baseUrl: normalizeOpenCodeBaseUrl(opencode.base_url || opencode.baseUrl || raw.opencode_base_url || raw.opencodeBaseUrl),
|
|
9038
|
+
startupReconciliationDelayMs: normalizeNonNegativeInteger(
|
|
9039
|
+
opencode.startup_reconciliation_delay_ms ?? opencode.startupReconciliationDelayMs ?? raw.startup_reconciliation_delay_ms ?? raw.startupReconciliationDelayMs,
|
|
9040
|
+
CLICKUP_WEBHOOK_STARTUP_RECONCILIATION_DELAY_MS
|
|
9041
|
+
)
|
|
9031
9042
|
},
|
|
9032
9043
|
webhook: {
|
|
9033
9044
|
publicUrl: String(webhook.public_url || webhook.publicUrl || "").trim(),
|
|
@@ -9655,6 +9666,22 @@ async function createOpenCodeSession(client, { title, directory, agent } = {}) {
|
|
|
9655
9666
|
}
|
|
9656
9667
|
throw firstError || new Error("OpenCode session create failed.");
|
|
9657
9668
|
}
|
|
9669
|
+
async function waitForOpenCodeReadiness(client, { worktree = process.cwd(), attempts = 10, delayMs = 500, now = () => /* @__PURE__ */ new Date() } = {}) {
|
|
9670
|
+
if (typeof client?.session?.create !== "function") return { ok: true, skipped: true, reason: "session_create_probe_unavailable" };
|
|
9671
|
+
let lastError = "opencode_not_ready";
|
|
9672
|
+
const maxAttempts = Math.max(1, Number(attempts) || 1);
|
|
9673
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
9674
|
+
try {
|
|
9675
|
+
const sessionId = await createOpenCodeSession(client, { title: `Optima startup readiness probe ${now().toISOString()}`, directory: worktree });
|
|
9676
|
+
if (await openCodeSessionExists(client, sessionId)) return { ok: true, method: "session_create_probe", sessionId, attempts: attempt };
|
|
9677
|
+
lastError = "readiness_probe_session_not_visible";
|
|
9678
|
+
} catch (error) {
|
|
9679
|
+
lastError = error.message || "opencode_not_ready";
|
|
9680
|
+
}
|
|
9681
|
+
if (attempt < maxAttempts && delayMs > 0) await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
9682
|
+
}
|
|
9683
|
+
return { ok: false, reason: lastError, attempts: maxAttempts };
|
|
9684
|
+
}
|
|
9658
9685
|
function assertOpenCodePromptAccepted(result) {
|
|
9659
9686
|
const status = Number(result?.status || result?.response?.status || result?.error?.status || 0);
|
|
9660
9687
|
if (status >= 400 || result?.ok === false || result?.error) {
|
|
@@ -9692,13 +9719,13 @@ async function readOpenCodeJsonResponse(response, endpointName) {
|
|
|
9692
9719
|
return { raw };
|
|
9693
9720
|
}
|
|
9694
9721
|
}
|
|
9695
|
-
async function sendOpenCodeSessionEventDirect({ baseUrl, sessionId, text, agent, fetchImpl = globalThis.fetch } = {}) {
|
|
9722
|
+
async function sendOpenCodeSessionEventDirect({ baseUrl, sessionId, text, agent, fetchImpl = globalThis.fetch, legacyOnly = false } = {}) {
|
|
9696
9723
|
if (typeof fetchImpl !== "function") throw new Error("OpenCode direct prompt delivery requires fetch.");
|
|
9697
9724
|
const root = normalizeOpenCodeBaseUrl(baseUrl, "");
|
|
9698
9725
|
if (!root) throw new Error("OpenCode direct prompt delivery requires a base URL.");
|
|
9699
9726
|
const encodedSession = encodeURIComponent(sessionId);
|
|
9700
9727
|
const attempts = [
|
|
9701
|
-
{
|
|
9728
|
+
legacyOnly ? null : {
|
|
9702
9729
|
name: "v2 prompt",
|
|
9703
9730
|
url: `${root}/api/session/${encodedSession}/prompt`,
|
|
9704
9731
|
body: { prompt: { text }, delivery: "queue", resume: true },
|
|
@@ -9717,7 +9744,7 @@ async function sendOpenCodeSessionEventDirect({ baseUrl, sessionId, text, agent,
|
|
|
9717
9744
|
return { ok: true, method: "http", endpoint: "/session/{sessionID}/prompt_async", status: response.status, data: data?.data || null, response: data };
|
|
9718
9745
|
}
|
|
9719
9746
|
}
|
|
9720
|
-
];
|
|
9747
|
+
].filter(Boolean);
|
|
9721
9748
|
let firstError = null;
|
|
9722
9749
|
for (const attempt of attempts) {
|
|
9723
9750
|
const response = await fetchImpl(attempt.url, {
|
|
@@ -9751,7 +9778,7 @@ async function callOpenCodePromptWithFallbacks(method, sessionId, flatPayload, s
|
|
|
9751
9778
|
}
|
|
9752
9779
|
throw firstError;
|
|
9753
9780
|
}
|
|
9754
|
-
async function sendOpenCodeSessionEvent(client, { sessionId, agent, text, directory, opencodeBaseUrl, baseUrl, fetchImpl, direct = false } = {}) {
|
|
9781
|
+
async function sendOpenCodeSessionEvent(client, { sessionId, agent, text, directory, opencodeBaseUrl, baseUrl, fetchImpl, direct = false, legacyOnly = false } = {}) {
|
|
9755
9782
|
const directBaseUrl = opencodeBaseUrl || baseUrl;
|
|
9756
9783
|
const parts = [{ type: "text", text }];
|
|
9757
9784
|
const flatPayload = { directory, agent, parts };
|
|
@@ -9775,7 +9802,7 @@ async function sendOpenCodeSessionEvent(client, { sessionId, agent, text, direct
|
|
|
9775
9802
|
firstError ??= error;
|
|
9776
9803
|
}
|
|
9777
9804
|
}
|
|
9778
|
-
if (directBaseUrl) return sendOpenCodeSessionEventDirect({ baseUrl: directBaseUrl, sessionId, text, agent, fetchImpl });
|
|
9805
|
+
if (directBaseUrl) return sendOpenCodeSessionEventDirect({ baseUrl: directBaseUrl, sessionId, text, agent, fetchImpl, legacyOnly });
|
|
9779
9806
|
if (firstError) throw firstError;
|
|
9780
9807
|
throw new Error("OpenCode client does not expose session.prompt or session.promptAsync.");
|
|
9781
9808
|
}
|
|
@@ -9810,18 +9837,20 @@ function openCodeMessageText(message) {
|
|
|
9810
9837
|
for (const part of partList) parts.push(part?.text, part?.content);
|
|
9811
9838
|
return parts.filter((value) => typeof value === "string").join("\n");
|
|
9812
9839
|
}
|
|
9813
|
-
async function verifyOpenCodeSessionEventDelivery(client, { sessionId, beforeMessages = null, expectedText = "", markers = [], attempts =
|
|
9840
|
+
async function verifyOpenCodeSessionEventDelivery(client, { sessionId, beforeMessages = null, expectedText = "", markers = [], attempts = 8, delayMs = 250 } = {}) {
|
|
9814
9841
|
let lastError = "message_verification_unavailable";
|
|
9815
9842
|
for (let attempt = 0; attempt < Math.max(1, attempts); attempt += 1) {
|
|
9816
9843
|
try {
|
|
9817
9844
|
const afterMessages = await readOpenCodeSessionMessages(client, { sessionId, limit: 50 });
|
|
9818
|
-
if (!afterMessages) return { ok:
|
|
9845
|
+
if (!afterMessages) return { ok: false, reason: "message_verification_unavailable" };
|
|
9819
9846
|
const beforeCount = Array.isArray(beforeMessages) ? beforeMessages.length : null;
|
|
9820
|
-
|
|
9847
|
+
const lastMessage = afterMessages.at(-1) || null;
|
|
9848
|
+
const lastMessageId = lastMessage?.id || lastMessage?.messageID || lastMessage?.messageId || null;
|
|
9849
|
+
if (beforeCount !== null && afterMessages.length > beforeCount) return { ok: true, method: "message_count", beforeCount, afterCount: afterMessages.length, lastMessageId };
|
|
9821
9850
|
const textNeedles = [expectedText, ...markers].map((value) => String(value || "").trim()).filter(Boolean);
|
|
9822
9851
|
const haystack = afterMessages.slice(-20).map(openCodeMessageText).join("\n");
|
|
9823
9852
|
const matched = textNeedles.find((needle) => haystack.includes(needle));
|
|
9824
|
-
if (matched) return { ok: true, method: "message_text", marker: matched, beforeCount, afterCount: afterMessages.length };
|
|
9853
|
+
if (matched) return { ok: true, method: "message_text", marker: matched, beforeCount, afterCount: afterMessages.length, lastMessageId };
|
|
9825
9854
|
lastError = "message_not_visible";
|
|
9826
9855
|
} catch (error) {
|
|
9827
9856
|
lastError = error.message || "message_verification_failed";
|
|
@@ -9856,21 +9885,28 @@ async function deliverClickUpSessionEventWithVerification({ openCodeClient, send
|
|
|
9856
9885
|
const blocker2 = await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason: error.message, source: "delivery_admission_failed" });
|
|
9857
9886
|
return { ok: false, action: "message_delivery_failed", reason: error.message, taskId, sessionId, fallbackAttempted: false, blockerTag: blocker2 };
|
|
9858
9887
|
}
|
|
9859
|
-
if (admissionVerification) return { ok: true, verification: admissionVerification, fallback: false };
|
|
9860
9888
|
let verification = await verifySessionEventDelivery(openCodeClient, { sessionId, beforeMessages, expectedText: text, markers: eventMarkers });
|
|
9861
|
-
if (verification?.ok) return { ok: true, verification, fallback: false };
|
|
9889
|
+
if (verification?.ok) return { ok: true, verification, admissionVerification, fallback: false };
|
|
9890
|
+
if (verification?.reason === "message_verification_unavailable" && !admissionVerification) {
|
|
9891
|
+
return { ok: true, verification: { ok: true, method: "legacy_prompt_accepted", skipped: true }, fallback: false };
|
|
9892
|
+
}
|
|
9893
|
+
if (admissionVerification) {
|
|
9894
|
+
appendClickUpWebhookLocalLog(worktree, { type: "message_delivery_admitted_but_invisible", taskId, sessionId, admission: admissionVerification, reason: verification?.reason || "message_not_visible" });
|
|
9895
|
+
}
|
|
9862
9896
|
const canFallbackDirect = Boolean(opencodeBaseUrl);
|
|
9863
9897
|
if (canFallbackDirect) {
|
|
9864
9898
|
const retryBeforeMessages = await readOpenCodeSessionMessages(openCodeClient, { sessionId, limit: 50 }).catch(() => beforeMessages);
|
|
9865
|
-
const retrySendResult = await sendSessionEvent(openCodeClient, { sessionId, agent, text, directory, opencodeBaseUrl, direct: true });
|
|
9899
|
+
const retrySendResult = await sendSessionEvent(openCodeClient, { sessionId, agent, text, directory, opencodeBaseUrl, direct: true, legacyOnly: Boolean(admissionVerification) });
|
|
9866
9900
|
try {
|
|
9867
9901
|
admissionVerification = openCodePromptAdmissionVerification(retrySendResult, sessionId);
|
|
9868
9902
|
} catch (error) {
|
|
9869
9903
|
verification = { ok: false, reason: error.message };
|
|
9870
9904
|
}
|
|
9871
|
-
if (admissionVerification) return { ok: true, verification: admissionVerification, fallback: true };
|
|
9872
9905
|
verification = await verifySessionEventDelivery(openCodeClient, { sessionId, beforeMessages: retryBeforeMessages, expectedText: text, markers: eventMarkers });
|
|
9873
|
-
if (verification?.ok) return { ok: true, verification, fallback: true };
|
|
9906
|
+
if (verification?.ok) return { ok: true, verification, admissionVerification, fallback: true };
|
|
9907
|
+
if (verification?.reason === "message_verification_unavailable" && !admissionVerification) {
|
|
9908
|
+
return { ok: true, verification: { ok: true, method: "legacy_prompt_accepted", skipped: true }, fallback: true };
|
|
9909
|
+
}
|
|
9874
9910
|
}
|
|
9875
9911
|
const reason = verification?.reason || "message_delivery_failed";
|
|
9876
9912
|
appendClickUpWebhookLocalLog(worktree, { type: "message_delivery_failed", taskId, sessionId, reason, fallbackAttempted: canFallbackDirect });
|
|
@@ -9916,7 +9952,7 @@ async function recoverClickUpPmSession({ openCodeClient, sendSessionEvent, click
|
|
|
9916
9952
|
const replacementMetadata = clearClickUpPendingSessionMetadata(setClickUpSessionMetadata(metadataWithRouting, config.routing.metadataKey, replacementSessionId), config.routing.metadataKey);
|
|
9917
9953
|
await clickupClient.updateTaskMetadata({ taskId, fieldId: config.routing.metadataFieldId, value: replacementMetadata });
|
|
9918
9954
|
appendClickUpWebhookLocalLog(worktree, { type: "pm_session_recovery_succeeded", taskId, staleSessionId, replacementSessionId });
|
|
9919
|
-
return { ok: true, action: "sent_to_replacement_session", taskId, sessionId: replacementSessionId, staleSessionId, replacementAttempted: true, eventKey, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath, evidencePath, deliveryVerification: replacementDelivery.verification, deliveryFallback: replacementDelivery.fallback };
|
|
9955
|
+
return { ok: true, action: "sent_to_replacement_session", taskId, sessionId: replacementSessionId, staleSessionId, replacementAttempted: true, eventKey, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath, evidencePath, deliveryVerification: replacementDelivery.verification, deliveryAdmission: replacementDelivery.admissionVerification, deliveryFallback: replacementDelivery.fallback, deliveryAttempts: replacementDelivery.fallback ? 2 : 1 };
|
|
9920
9956
|
}
|
|
9921
9957
|
function formatClickUpWebhookPrompt({ eventType, taskId, payload, branch = "", worktree = "", deliveryEvidencePath = "", humanRoleContext = [] }) {
|
|
9922
9958
|
const comment = clickUpCommentFromPayload(payload);
|
|
@@ -10212,7 +10248,7 @@ async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, w
|
|
|
10212
10248
|
await clickupClient.updateTaskMetadata({ taskId, fieldId: config.routing.metadataFieldId, value: nextMetadata });
|
|
10213
10249
|
const { [taskId]: _completedPending, ...remainingPending } = stateToPersist.pendingSessions || {};
|
|
10214
10250
|
stateToPersist = { ...stateToPersist, pendingSessions: remainingPending };
|
|
10215
|
-
return finish({ ok: true, action: "created_session", taskId, sessionId: pendingSessionId, eventKey, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath, evidencePath: routingMetadata.evidence_path, deliveryVerification: delivery.verification, deliveryFallback: delivery.fallback });
|
|
10251
|
+
return finish({ ok: true, action: "created_session", taskId, sessionId: pendingSessionId, eventKey, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath, evidencePath: routingMetadata.evidence_path, deliveryVerification: delivery.verification, deliveryAdmission: delivery.admissionVerification, deliveryFallback: delivery.fallback, deliveryAttempts: delivery.fallback ? 2 : 1 });
|
|
10216
10252
|
}
|
|
10217
10253
|
const sessionId = String(existingSessionId);
|
|
10218
10254
|
if (await sessionExists(openCodeClient, sessionId)) {
|
|
@@ -10222,7 +10258,7 @@ async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, w
|
|
|
10222
10258
|
const recovery2 = await recoverClickUpPmSession({ openCodeClient, sendSessionEvent, clickupClient, worktree, taskId, staleSessionId: sessionId, sessionTitle, taskRoute, metadataWithRouting, config, prompt, eventMarkers: [taskId, eventType], deliveryEvidencePath, evidencePath: routingMetadata.evidence_path, eventKey, createSession, verifySessionEventDelivery });
|
|
10223
10259
|
return finish(recovery2);
|
|
10224
10260
|
}
|
|
10225
|
-
return finish({ ok: true, action: "sent_to_existing_session", taskId, sessionId, eventKey, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath, evidencePath: routingMetadata.evidence_path, deliveryVerification: delivery.verification, deliveryFallback: delivery.fallback });
|
|
10261
|
+
return finish({ ok: true, action: "sent_to_existing_session", taskId, sessionId, eventKey, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath, evidencePath: routingMetadata.evidence_path, deliveryVerification: delivery.verification, deliveryAdmission: delivery.admissionVerification, deliveryFallback: delivery.fallback, deliveryAttempts: delivery.fallback ? 2 : 1 });
|
|
10226
10262
|
}
|
|
10227
10263
|
const at = now().toISOString();
|
|
10228
10264
|
appendClickUpWebhookLocalLog(worktree, { type: "missing_session", taskId, sessionId, host, at });
|
|
@@ -10233,8 +10269,44 @@ async function routeClickUpWebhookEvent(options = {}) {
|
|
|
10233
10269
|
const taskId = clickUpTaskIdFromPayload(options.payload || {});
|
|
10234
10270
|
return withClickUpTaskRouteLock(taskId, () => routeClickUpWebhookEventUnlocked(options));
|
|
10235
10271
|
}
|
|
10236
|
-
|
|
10237
|
-
|
|
10272
|
+
function defaultStartupReconciliationScheduler(run, delayMs) {
|
|
10273
|
+
return setTimeout(run, delayMs);
|
|
10274
|
+
}
|
|
10275
|
+
function scheduleClickUpStartupReconciliation({ config, state = {}, worktree = process.cwd(), clickupClient, openCodeClient, scheduler = defaultStartupReconciliationScheduler, delayMs = config?.opencode?.startupReconciliationDelayMs ?? CLICKUP_WEBHOOK_STARTUP_RECONCILIATION_DELAY_MS, saveState = null, reconcile = reconcileClickUpStartup } = {}) {
|
|
10276
|
+
const normalizedDelayMs = normalizeNonNegativeInteger(delayMs, CLICKUP_WEBHOOK_STARTUP_RECONCILIATION_DELAY_MS);
|
|
10277
|
+
clickUpWebhookLifecycleLog(worktree, { type: "startup_reconciliation_scheduled", delayMs: normalizedDelayMs, webhookId: state?.webhookId || null });
|
|
10278
|
+
const run = () => {
|
|
10279
|
+
Promise.resolve().then(() => reconcile({
|
|
10280
|
+
config,
|
|
10281
|
+
state,
|
|
10282
|
+
worktree,
|
|
10283
|
+
clickupClient,
|
|
10284
|
+
openCodeClient,
|
|
10285
|
+
saveState
|
|
10286
|
+
})).catch((error) => {
|
|
10287
|
+
clickUpWebhookLifecycleLog(worktree, { type: "startup_reconciliation_failed", message: error.message });
|
|
10288
|
+
appendClickUpWebhookLocalLog(worktree, { type: "startup_reconciliation_failed", message: error.message });
|
|
10289
|
+
});
|
|
10290
|
+
};
|
|
10291
|
+
const timer = scheduler(run, normalizedDelayMs);
|
|
10292
|
+
timer?.unref?.();
|
|
10293
|
+
return { scheduled: true, delayMs: normalizedDelayMs, timer };
|
|
10294
|
+
}
|
|
10295
|
+
async function reconcileClickUpStartup({ config, state = {}, worktree = process.cwd(), clickupClient, openCodeClient, sessionExists = openCodeSessionExists, createSession = createOpenCodeSession, sendSessionEvent = sendOpenCodeSessionEvent, verifySessionEventDelivery = verifyOpenCodeSessionEventDelivery, waitForReadiness = waitForOpenCodeReadiness, saveState = null, now = () => /* @__PURE__ */ new Date(), limit = CLICKUP_WEBHOOK_STARTUP_TASK_LIMIT } = {}) {
|
|
10296
|
+
clickUpWebhookLifecycleLog(worktree, { type: "startup_reconciliation_started", lastWebhookAt: state.lastWebhookAt || null });
|
|
10297
|
+
if (!config || !clickupClient?.listAssignedTasks) {
|
|
10298
|
+
const skipped = { ok: true, skipped: true, reason: "clickup_task_listing_unavailable", assigned: 0, comments: 0 };
|
|
10299
|
+
clickUpWebhookLifecycleLog(worktree, { type: "startup_reconciliation_finished", ...skipped });
|
|
10300
|
+
return skipped;
|
|
10301
|
+
}
|
|
10302
|
+
const readiness = await waitForReadiness(openCodeClient, { worktree, now });
|
|
10303
|
+
appendClickUpWebhookLocalLog(worktree, { type: readiness.ok ? "startup_reconciliation_readiness_ready" : "startup_reconciliation_readiness_failed", ...readiness });
|
|
10304
|
+
if (!readiness.ok) {
|
|
10305
|
+
const failed = { ok: false, skipped: true, reason: "opencode_not_ready", readiness, assigned: 0, comments: 0, ignored: 0, errors: 1, undelivered: 1, tasks: [], validation: { undelivered: 1, emptyPromptSessions: [] } };
|
|
10306
|
+
clickUpWebhookLifecycleLog(worktree, { type: "startup_reconciliation_failed", reason: failed.reason, readiness });
|
|
10307
|
+
clickUpWebhookLifecycleLog(worktree, { type: "startup_reconciliation_finished", ...failed });
|
|
10308
|
+
return failed;
|
|
10309
|
+
}
|
|
10238
10310
|
let authorizedUserId = "";
|
|
10239
10311
|
if (clickupClient?.getAuthorizedUser) {
|
|
10240
10312
|
try {
|
|
@@ -10252,7 +10324,6 @@ async function reconcileClickUpStartup({ config, state = {}, worktree = process.
|
|
|
10252
10324
|
const lastWebhookMs = clickUpTimestampMs(mutableState.lastWebhookAt);
|
|
10253
10325
|
const ignored = new Set((config.routing?.ignoredStatuses || CLICKUP_WEBHOOK_TERMINAL_STATUSES).map(normalizeClickUpStatus));
|
|
10254
10326
|
const routed = { assigned: 0, comments: 0, ignored: 0, errors: 0, undelivered: 0, tasks: [] };
|
|
10255
|
-
clickUpWebhookLifecycleLog(worktree, { type: "startup_reconciliation_started", lastWebhookAt: state.lastWebhookAt || null });
|
|
10256
10327
|
const listed = await clickupClient.listAssignedTasks({ assigneeId: config.routing.productManagerAssigneeId, limit });
|
|
10257
10328
|
const tasks = clickUpTaskListItems(listed).slice(0, limit);
|
|
10258
10329
|
for (const task of tasks) {
|
|
@@ -10292,7 +10363,10 @@ async function reconcileClickUpStartup({ config, state = {}, worktree = process.
|
|
|
10292
10363
|
branch: result?.branch || null,
|
|
10293
10364
|
worktree: result?.worktree || null,
|
|
10294
10365
|
verification: result?.deliveryVerification?.method || result?.deliveryVerification?.reason || null,
|
|
10295
|
-
delivered: hasActionableClickUpRoute(result)
|
|
10366
|
+
delivered: hasActionableClickUpRoute(result),
|
|
10367
|
+
attempts: result?.deliveryAttempts || null,
|
|
10368
|
+
finalMessageCount: result?.deliveryVerification?.afterCount ?? null,
|
|
10369
|
+
finalMessageId: result?.deliveryVerification?.lastMessageId || null
|
|
10296
10370
|
};
|
|
10297
10371
|
routed.tasks.push(routeSummary);
|
|
10298
10372
|
appendClickUpWebhookLocalLog(worktree, { type: "startup_reconciliation_task_routed", ...routeSummary });
|
|
@@ -11567,18 +11641,16 @@ async function OptimaPlugin(input = {}, pluginOptions = {}) {
|
|
|
11567
11641
|
listener: { bindHost: clickUpWebhookValidation.config.webhook.bindHost, bindPort: clickUpWebhookValidation.config.webhook.bindPort, startedAt: (/* @__PURE__ */ new Date()).toISOString() }
|
|
11568
11642
|
}, clickUpWebhookValidation.config);
|
|
11569
11643
|
registerClickUpWebhookLifecycle({ config: clickUpWebhookValidation.config, state: activeState, worktree, clickupClient: lifecycleClickUpClient, listener: readyListener, listenerRegistry });
|
|
11570
|
-
|
|
11571
|
-
|
|
11572
|
-
|
|
11573
|
-
|
|
11574
|
-
|
|
11575
|
-
|
|
11576
|
-
|
|
11577
|
-
|
|
11578
|
-
|
|
11579
|
-
}
|
|
11580
|
-
appendClickUpWebhookLocalLog(worktree, { type: "startup_reconciliation_failed", message: error.message });
|
|
11581
|
-
}
|
|
11644
|
+
scheduleClickUpStartupReconciliation({
|
|
11645
|
+
config: clickUpWebhookValidation.config,
|
|
11646
|
+
state: activeState,
|
|
11647
|
+
worktree,
|
|
11648
|
+
clickupClient: lifecycleClickUpClient,
|
|
11649
|
+
openCodeClient: input.client,
|
|
11650
|
+
scheduler: input.startupReconciliationScheduler,
|
|
11651
|
+
delayMs: input.startupReconciliationDelayMs,
|
|
11652
|
+
saveState: (nextState) => writeClickUpWebhookState(worktree, nextState, clickUpWebhookValidation.config)
|
|
11653
|
+
});
|
|
11582
11654
|
} else {
|
|
11583
11655
|
clickUpWebhookLifecycleLog(worktree, { type: "remote_webhook_preserved", reason: readyListener.reason || "listener_unavailable", webhookId: listenerState.webhookId });
|
|
11584
11656
|
markClickUpWebhookInactive(worktree, listenerState, clickUpWebhookValidation.config);
|
|
@@ -12153,7 +12225,7 @@ Follow-up: use optima_prompt_workflow with session_id '${sessionId}' to check in
|
|
|
12153
12225
|
}
|
|
12154
12226
|
};
|
|
12155
12227
|
}
|
|
12156
|
-
OptimaPlugin.__internals = { BUNDLE_AGENTS_DIR, BUNDLE_ASSETS_DIR, CLICKUP_DEFINITION_DOC_PARENT, activeClickUpWebhookLifecycleRegistry, cleanupManagedClickUpWebhook, deleteClickUpWebhookBestEffort, BUNDLE_POLICIES_DIR, buildClickUpApplyPayloadResult, buildClickUpCreateSubtasksPayload, buildClickUpStartTaskPayload, buildClickUpSummaryPayload, buildClickUpTransitionPayload, buildOptimaAgents, clickUpCommentLedgerKey, clickUpCommentMentionsProductManager, clickUpWebhookAuditLogDir, clickUpWebhookAuditLogPath, createClickUpApiClient, createOpenCodeSession, deliveryEvidencePathForClickUpTask, ensureClickUpTaskWorktree, ensureClickUpWebhookSubscription, handleClickUpWebhookRequest, isClickUpWebhookStateActive, normalizeClickUpWebhookConfig, normalizeClickUpWebhookLogLevel, normalizeOpenCodeBaseUrl, openCodeSessionExists, readClickUpCommentLedger, readClickUpWebhookState, readOpenCodeSessionMessages, reconcileClickUpStartup, recordClickUpCommentVersionProcessed, resyncClickUpWebhookForSignatureDrift, resolveOptimaPluginOptions, resolveSecretReference, routeClickUpWebhookEvent, sendOpenCodeSessionEvent, sendOpenCodeSessionEventDirect, verifyOpenCodeSessionEventDelivery, stableClickUpCommentVersionMarker, startClickUpWebhookListener, validateClickUpWebhookState, verifyClickUpSignature, writeClickUpWebhookState, decideClickUpStatusAction, deriveClickUpBranchName, deriveClickUpPendingSubtaskBranch, deriveClickUpPrTarget, deriveClickUpWorktree, determineClickUpMergeAuthority, ensureOptimaGitignoreRules, explicitSafeInputWorktree, finalApprovalAssignees, formatValidationResult, isIgnoredClickUpTaskType, isOptimaPluginPackageWorktree, isSafeWritableDirectory, loadHumansRegistry, mergeClickUpAgentMetadata, mergeClickUpSessionMetadata, migrateLegacyOptimaLayout, normalizeAgentMetadataJson, normalizeClickUpStatus, normalizeClickUpTaskType, normalizePromptResponseParts, normalizeWorkflowTaskPath, parseClickUpSubtasksMarkdown, parseHumansRegistry, parseMarkdownArtifact, parseMarkdownSections, preEstimateClickUpWork, readMarkdownArtifact, resolveHumanRoles, resolveSafeWorktree, safeWorktreeOrFailure, stripRawLogSections, validateMainWorkspaceBranchSafety, workflowFinalMessageFromPromptResponse };
|
|
12228
|
+
OptimaPlugin.__internals = { BUNDLE_AGENTS_DIR, BUNDLE_ASSETS_DIR, CLICKUP_DEFINITION_DOC_PARENT, activeClickUpWebhookLifecycleRegistry, cleanupManagedClickUpWebhook, deleteClickUpWebhookBestEffort, BUNDLE_POLICIES_DIR, buildClickUpApplyPayloadResult, buildClickUpCreateSubtasksPayload, buildClickUpStartTaskPayload, buildClickUpSummaryPayload, buildClickUpTransitionPayload, buildOptimaAgents, clickUpCommentLedgerKey, clickUpCommentMentionsProductManager, clickUpWebhookAuditLogDir, clickUpWebhookAuditLogPath, createClickUpApiClient, createOpenCodeSession, deliveryEvidencePathForClickUpTask, ensureClickUpTaskWorktree, ensureClickUpWebhookSubscription, handleClickUpWebhookRequest, isClickUpWebhookStateActive, normalizeClickUpWebhookConfig, normalizeClickUpWebhookLogLevel, normalizeOpenCodeBaseUrl, openCodeSessionExists, readClickUpCommentLedger, readClickUpWebhookState, readOpenCodeSessionMessages, reconcileClickUpStartup, scheduleClickUpStartupReconciliation, waitForOpenCodeReadiness, recordClickUpCommentVersionProcessed, resyncClickUpWebhookForSignatureDrift, resolveOptimaPluginOptions, resolveSecretReference, routeClickUpWebhookEvent, sendOpenCodeSessionEvent, sendOpenCodeSessionEventDirect, verifyOpenCodeSessionEventDelivery, stableClickUpCommentVersionMarker, startClickUpWebhookListener, validateClickUpWebhookState, verifyClickUpSignature, writeClickUpWebhookState, decideClickUpStatusAction, deriveClickUpBranchName, deriveClickUpPendingSubtaskBranch, deriveClickUpPrTarget, deriveClickUpWorktree, determineClickUpMergeAuthority, ensureOptimaGitignoreRules, explicitSafeInputWorktree, finalApprovalAssignees, formatValidationResult, isIgnoredClickUpTaskType, isOptimaPluginPackageWorktree, isSafeWritableDirectory, loadHumansRegistry, mergeClickUpAgentMetadata, mergeClickUpSessionMetadata, migrateLegacyOptimaLayout, normalizeAgentMetadataJson, normalizeClickUpStatus, normalizeClickUpTaskType, normalizePromptResponseParts, normalizeWorkflowTaskPath, parseClickUpSubtasksMarkdown, parseHumansRegistry, parseMarkdownArtifact, parseMarkdownSections, preEstimateClickUpWork, readMarkdownArtifact, resolveHumanRoles, resolveSafeWorktree, safeWorktreeOrFailure, stripRawLogSections, validateMainWorkspaceBranchSafety, workflowFinalMessageFromPromptResponse };
|
|
12157
12229
|
export {
|
|
12158
12230
|
OptimaPlugin as default
|
|
12159
12231
|
};
|
package/dist/sanitize_cli.js
CHANGED
|
@@ -7945,6 +7945,7 @@ var CLICKUP_WEBHOOK_MAX_BODY_BYTES = 1024 * 1024;
|
|
|
7945
7945
|
var CLICKUP_WEBHOOK_REQUEST_TIMEOUT_MS = 1e4;
|
|
7946
7946
|
var CLICKUP_WEBHOOK_STARTUP_TASK_LIMIT = 50;
|
|
7947
7947
|
var CLICKUP_WEBHOOK_STARTUP_COMMENT_LIMIT = 100;
|
|
7948
|
+
var CLICKUP_WEBHOOK_STARTUP_RECONCILIATION_DELAY_MS = 3e4;
|
|
7948
7949
|
var CLICKUP_WEBHOOK_LOG_LEVELS = /* @__PURE__ */ new Set(["error", "info", "verbose"]);
|
|
7949
7950
|
var CLICKUP_WEBHOOK_REDACTED = "[REDACTED]";
|
|
7950
7951
|
var DISCUSSION_BACKFILL_FETCH_LIMIT = 100;
|
|
@@ -8170,6 +8171,7 @@ function hasActionableClickUpRoute(result = {}) {
|
|
|
8170
8171
|
if (!result.sessionId) return false;
|
|
8171
8172
|
if (result.action === "message_delivery_failed" || result.action === "error") return false;
|
|
8172
8173
|
if (result.deliveryVerification?.ok === false) return false;
|
|
8174
|
+
if (result.deliveryVerification?.method === "prompt_admission") return false;
|
|
8173
8175
|
return true;
|
|
8174
8176
|
}
|
|
8175
8177
|
function determineClickUpMergeAuthority({ isSubtask = false, clickupStatus = "", validationPassed = false, mergeFailed = false, finalApprovalRoles = CLICKUP_FINAL_APPROVER_ROLES, humansRegistry } = {}) {
|
|
@@ -9019,6 +9021,11 @@ function normalizeOpenCodeBaseUrl(value, defaultValue = "http://127.0.0.1:3001")
|
|
|
9019
9021
|
return candidate.replace(/\/+$/, "");
|
|
9020
9022
|
}
|
|
9021
9023
|
}
|
|
9024
|
+
function normalizeNonNegativeInteger(value, defaultValue) {
|
|
9025
|
+
if (value === void 0 || value === null || value === "") return defaultValue;
|
|
9026
|
+
const number = Number(value);
|
|
9027
|
+
return Number.isInteger(number) && number >= 0 ? number : defaultValue;
|
|
9028
|
+
}
|
|
9022
9029
|
function normalizeClickUpWebhookConfig(rawClickUp = null, worktree = process.cwd()) {
|
|
9023
9030
|
const raw = isPlainObject(rawClickUp) ? rawClickUp : {};
|
|
9024
9031
|
const webhook = isPlainObject(raw.webhook) ? raw.webhook : {};
|
|
@@ -9034,7 +9041,11 @@ function normalizeClickUpWebhookConfig(rawClickUp = null, worktree = process.cwd
|
|
|
9034
9041
|
apiToken: String(raw.api_token || raw.apiToken || "").trim(),
|
|
9035
9042
|
log: normalizeClickUpWebhookLogLevel(raw.log),
|
|
9036
9043
|
opencode: {
|
|
9037
|
-
baseUrl: normalizeOpenCodeBaseUrl(opencode.base_url || opencode.baseUrl || raw.opencode_base_url || raw.opencodeBaseUrl)
|
|
9044
|
+
baseUrl: normalizeOpenCodeBaseUrl(opencode.base_url || opencode.baseUrl || raw.opencode_base_url || raw.opencodeBaseUrl),
|
|
9045
|
+
startupReconciliationDelayMs: normalizeNonNegativeInteger(
|
|
9046
|
+
opencode.startup_reconciliation_delay_ms ?? opencode.startupReconciliationDelayMs ?? raw.startup_reconciliation_delay_ms ?? raw.startupReconciliationDelayMs,
|
|
9047
|
+
CLICKUP_WEBHOOK_STARTUP_RECONCILIATION_DELAY_MS
|
|
9048
|
+
)
|
|
9038
9049
|
},
|
|
9039
9050
|
webhook: {
|
|
9040
9051
|
publicUrl: String(webhook.public_url || webhook.publicUrl || "").trim(),
|
|
@@ -9662,6 +9673,22 @@ async function createOpenCodeSession(client, { title, directory, agent } = {}) {
|
|
|
9662
9673
|
}
|
|
9663
9674
|
throw firstError || new Error("OpenCode session create failed.");
|
|
9664
9675
|
}
|
|
9676
|
+
async function waitForOpenCodeReadiness(client, { worktree = process.cwd(), attempts = 10, delayMs = 500, now = () => /* @__PURE__ */ new Date() } = {}) {
|
|
9677
|
+
if (typeof client?.session?.create !== "function") return { ok: true, skipped: true, reason: "session_create_probe_unavailable" };
|
|
9678
|
+
let lastError = "opencode_not_ready";
|
|
9679
|
+
const maxAttempts = Math.max(1, Number(attempts) || 1);
|
|
9680
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
9681
|
+
try {
|
|
9682
|
+
const sessionId = await createOpenCodeSession(client, { title: `Optima startup readiness probe ${now().toISOString()}`, directory: worktree });
|
|
9683
|
+
if (await openCodeSessionExists(client, sessionId)) return { ok: true, method: "session_create_probe", sessionId, attempts: attempt };
|
|
9684
|
+
lastError = "readiness_probe_session_not_visible";
|
|
9685
|
+
} catch (error) {
|
|
9686
|
+
lastError = error.message || "opencode_not_ready";
|
|
9687
|
+
}
|
|
9688
|
+
if (attempt < maxAttempts && delayMs > 0) await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
9689
|
+
}
|
|
9690
|
+
return { ok: false, reason: lastError, attempts: maxAttempts };
|
|
9691
|
+
}
|
|
9665
9692
|
function assertOpenCodePromptAccepted(result) {
|
|
9666
9693
|
const status = Number(result?.status || result?.response?.status || result?.error?.status || 0);
|
|
9667
9694
|
if (status >= 400 || result?.ok === false || result?.error) {
|
|
@@ -9699,13 +9726,13 @@ async function readOpenCodeJsonResponse(response, endpointName) {
|
|
|
9699
9726
|
return { raw };
|
|
9700
9727
|
}
|
|
9701
9728
|
}
|
|
9702
|
-
async function sendOpenCodeSessionEventDirect({ baseUrl, sessionId, text, agent, fetchImpl = globalThis.fetch } = {}) {
|
|
9729
|
+
async function sendOpenCodeSessionEventDirect({ baseUrl, sessionId, text, agent, fetchImpl = globalThis.fetch, legacyOnly = false } = {}) {
|
|
9703
9730
|
if (typeof fetchImpl !== "function") throw new Error("OpenCode direct prompt delivery requires fetch.");
|
|
9704
9731
|
const root = normalizeOpenCodeBaseUrl(baseUrl, "");
|
|
9705
9732
|
if (!root) throw new Error("OpenCode direct prompt delivery requires a base URL.");
|
|
9706
9733
|
const encodedSession = encodeURIComponent(sessionId);
|
|
9707
9734
|
const attempts = [
|
|
9708
|
-
{
|
|
9735
|
+
legacyOnly ? null : {
|
|
9709
9736
|
name: "v2 prompt",
|
|
9710
9737
|
url: `${root}/api/session/${encodedSession}/prompt`,
|
|
9711
9738
|
body: { prompt: { text }, delivery: "queue", resume: true },
|
|
@@ -9724,7 +9751,7 @@ async function sendOpenCodeSessionEventDirect({ baseUrl, sessionId, text, agent,
|
|
|
9724
9751
|
return { ok: true, method: "http", endpoint: "/session/{sessionID}/prompt_async", status: response.status, data: data?.data || null, response: data };
|
|
9725
9752
|
}
|
|
9726
9753
|
}
|
|
9727
|
-
];
|
|
9754
|
+
].filter(Boolean);
|
|
9728
9755
|
let firstError = null;
|
|
9729
9756
|
for (const attempt of attempts) {
|
|
9730
9757
|
const response = await fetchImpl(attempt.url, {
|
|
@@ -9758,7 +9785,7 @@ async function callOpenCodePromptWithFallbacks(method, sessionId, flatPayload, s
|
|
|
9758
9785
|
}
|
|
9759
9786
|
throw firstError;
|
|
9760
9787
|
}
|
|
9761
|
-
async function sendOpenCodeSessionEvent(client, { sessionId, agent, text, directory, opencodeBaseUrl, baseUrl, fetchImpl, direct = false } = {}) {
|
|
9788
|
+
async function sendOpenCodeSessionEvent(client, { sessionId, agent, text, directory, opencodeBaseUrl, baseUrl, fetchImpl, direct = false, legacyOnly = false } = {}) {
|
|
9762
9789
|
const directBaseUrl = opencodeBaseUrl || baseUrl;
|
|
9763
9790
|
const parts = [{ type: "text", text }];
|
|
9764
9791
|
const flatPayload = { directory, agent, parts };
|
|
@@ -9782,7 +9809,7 @@ async function sendOpenCodeSessionEvent(client, { sessionId, agent, text, direct
|
|
|
9782
9809
|
firstError ??= error;
|
|
9783
9810
|
}
|
|
9784
9811
|
}
|
|
9785
|
-
if (directBaseUrl) return sendOpenCodeSessionEventDirect({ baseUrl: directBaseUrl, sessionId, text, agent, fetchImpl });
|
|
9812
|
+
if (directBaseUrl) return sendOpenCodeSessionEventDirect({ baseUrl: directBaseUrl, sessionId, text, agent, fetchImpl, legacyOnly });
|
|
9786
9813
|
if (firstError) throw firstError;
|
|
9787
9814
|
throw new Error("OpenCode client does not expose session.prompt or session.promptAsync.");
|
|
9788
9815
|
}
|
|
@@ -9817,18 +9844,20 @@ function openCodeMessageText(message) {
|
|
|
9817
9844
|
for (const part of partList) parts.push(part?.text, part?.content);
|
|
9818
9845
|
return parts.filter((value) => typeof value === "string").join("\n");
|
|
9819
9846
|
}
|
|
9820
|
-
async function verifyOpenCodeSessionEventDelivery(client, { sessionId, beforeMessages = null, expectedText = "", markers = [], attempts =
|
|
9847
|
+
async function verifyOpenCodeSessionEventDelivery(client, { sessionId, beforeMessages = null, expectedText = "", markers = [], attempts = 8, delayMs = 250 } = {}) {
|
|
9821
9848
|
let lastError = "message_verification_unavailable";
|
|
9822
9849
|
for (let attempt = 0; attempt < Math.max(1, attempts); attempt += 1) {
|
|
9823
9850
|
try {
|
|
9824
9851
|
const afterMessages = await readOpenCodeSessionMessages(client, { sessionId, limit: 50 });
|
|
9825
|
-
if (!afterMessages) return { ok:
|
|
9852
|
+
if (!afterMessages) return { ok: false, reason: "message_verification_unavailable" };
|
|
9826
9853
|
const beforeCount = Array.isArray(beforeMessages) ? beforeMessages.length : null;
|
|
9827
|
-
|
|
9854
|
+
const lastMessage = afterMessages.at(-1) || null;
|
|
9855
|
+
const lastMessageId = lastMessage?.id || lastMessage?.messageID || lastMessage?.messageId || null;
|
|
9856
|
+
if (beforeCount !== null && afterMessages.length > beforeCount) return { ok: true, method: "message_count", beforeCount, afterCount: afterMessages.length, lastMessageId };
|
|
9828
9857
|
const textNeedles = [expectedText, ...markers].map((value) => String(value || "").trim()).filter(Boolean);
|
|
9829
9858
|
const haystack = afterMessages.slice(-20).map(openCodeMessageText).join("\n");
|
|
9830
9859
|
const matched = textNeedles.find((needle) => haystack.includes(needle));
|
|
9831
|
-
if (matched) return { ok: true, method: "message_text", marker: matched, beforeCount, afterCount: afterMessages.length };
|
|
9860
|
+
if (matched) return { ok: true, method: "message_text", marker: matched, beforeCount, afterCount: afterMessages.length, lastMessageId };
|
|
9832
9861
|
lastError = "message_not_visible";
|
|
9833
9862
|
} catch (error) {
|
|
9834
9863
|
lastError = error.message || "message_verification_failed";
|
|
@@ -9863,21 +9892,28 @@ async function deliverClickUpSessionEventWithVerification({ openCodeClient, send
|
|
|
9863
9892
|
const blocker2 = await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason: error.message, source: "delivery_admission_failed" });
|
|
9864
9893
|
return { ok: false, action: "message_delivery_failed", reason: error.message, taskId, sessionId, fallbackAttempted: false, blockerTag: blocker2 };
|
|
9865
9894
|
}
|
|
9866
|
-
if (admissionVerification) return { ok: true, verification: admissionVerification, fallback: false };
|
|
9867
9895
|
let verification = await verifySessionEventDelivery(openCodeClient, { sessionId, beforeMessages, expectedText: text, markers: eventMarkers });
|
|
9868
|
-
if (verification?.ok) return { ok: true, verification, fallback: false };
|
|
9896
|
+
if (verification?.ok) return { ok: true, verification, admissionVerification, fallback: false };
|
|
9897
|
+
if (verification?.reason === "message_verification_unavailable" && !admissionVerification) {
|
|
9898
|
+
return { ok: true, verification: { ok: true, method: "legacy_prompt_accepted", skipped: true }, fallback: false };
|
|
9899
|
+
}
|
|
9900
|
+
if (admissionVerification) {
|
|
9901
|
+
appendClickUpWebhookLocalLog(worktree, { type: "message_delivery_admitted_but_invisible", taskId, sessionId, admission: admissionVerification, reason: verification?.reason || "message_not_visible" });
|
|
9902
|
+
}
|
|
9869
9903
|
const canFallbackDirect = Boolean(opencodeBaseUrl);
|
|
9870
9904
|
if (canFallbackDirect) {
|
|
9871
9905
|
const retryBeforeMessages = await readOpenCodeSessionMessages(openCodeClient, { sessionId, limit: 50 }).catch(() => beforeMessages);
|
|
9872
|
-
const retrySendResult = await sendSessionEvent(openCodeClient, { sessionId, agent, text, directory, opencodeBaseUrl, direct: true });
|
|
9906
|
+
const retrySendResult = await sendSessionEvent(openCodeClient, { sessionId, agent, text, directory, opencodeBaseUrl, direct: true, legacyOnly: Boolean(admissionVerification) });
|
|
9873
9907
|
try {
|
|
9874
9908
|
admissionVerification = openCodePromptAdmissionVerification(retrySendResult, sessionId);
|
|
9875
9909
|
} catch (error) {
|
|
9876
9910
|
verification = { ok: false, reason: error.message };
|
|
9877
9911
|
}
|
|
9878
|
-
if (admissionVerification) return { ok: true, verification: admissionVerification, fallback: true };
|
|
9879
9912
|
verification = await verifySessionEventDelivery(openCodeClient, { sessionId, beforeMessages: retryBeforeMessages, expectedText: text, markers: eventMarkers });
|
|
9880
|
-
if (verification?.ok) return { ok: true, verification, fallback: true };
|
|
9913
|
+
if (verification?.ok) return { ok: true, verification, admissionVerification, fallback: true };
|
|
9914
|
+
if (verification?.reason === "message_verification_unavailable" && !admissionVerification) {
|
|
9915
|
+
return { ok: true, verification: { ok: true, method: "legacy_prompt_accepted", skipped: true }, fallback: true };
|
|
9916
|
+
}
|
|
9881
9917
|
}
|
|
9882
9918
|
const reason = verification?.reason || "message_delivery_failed";
|
|
9883
9919
|
appendClickUpWebhookLocalLog(worktree, { type: "message_delivery_failed", taskId, sessionId, reason, fallbackAttempted: canFallbackDirect });
|
|
@@ -9923,7 +9959,7 @@ async function recoverClickUpPmSession({ openCodeClient, sendSessionEvent, click
|
|
|
9923
9959
|
const replacementMetadata = clearClickUpPendingSessionMetadata(setClickUpSessionMetadata(metadataWithRouting, config.routing.metadataKey, replacementSessionId), config.routing.metadataKey);
|
|
9924
9960
|
await clickupClient.updateTaskMetadata({ taskId, fieldId: config.routing.metadataFieldId, value: replacementMetadata });
|
|
9925
9961
|
appendClickUpWebhookLocalLog(worktree, { type: "pm_session_recovery_succeeded", taskId, staleSessionId, replacementSessionId });
|
|
9926
|
-
return { ok: true, action: "sent_to_replacement_session", taskId, sessionId: replacementSessionId, staleSessionId, replacementAttempted: true, eventKey, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath, evidencePath, deliveryVerification: replacementDelivery.verification, deliveryFallback: replacementDelivery.fallback };
|
|
9962
|
+
return { ok: true, action: "sent_to_replacement_session", taskId, sessionId: replacementSessionId, staleSessionId, replacementAttempted: true, eventKey, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath, evidencePath, deliveryVerification: replacementDelivery.verification, deliveryAdmission: replacementDelivery.admissionVerification, deliveryFallback: replacementDelivery.fallback, deliveryAttempts: replacementDelivery.fallback ? 2 : 1 };
|
|
9927
9963
|
}
|
|
9928
9964
|
function formatClickUpWebhookPrompt({ eventType, taskId, payload, branch = "", worktree = "", deliveryEvidencePath = "", humanRoleContext = [] }) {
|
|
9929
9965
|
const comment = clickUpCommentFromPayload(payload);
|
|
@@ -10219,7 +10255,7 @@ async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, w
|
|
|
10219
10255
|
await clickupClient.updateTaskMetadata({ taskId, fieldId: config.routing.metadataFieldId, value: nextMetadata });
|
|
10220
10256
|
const { [taskId]: _completedPending, ...remainingPending } = stateToPersist.pendingSessions || {};
|
|
10221
10257
|
stateToPersist = { ...stateToPersist, pendingSessions: remainingPending };
|
|
10222
|
-
return finish({ ok: true, action: "created_session", taskId, sessionId: pendingSessionId, eventKey, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath, evidencePath: routingMetadata.evidence_path, deliveryVerification: delivery.verification, deliveryFallback: delivery.fallback });
|
|
10258
|
+
return finish({ ok: true, action: "created_session", taskId, sessionId: pendingSessionId, eventKey, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath, evidencePath: routingMetadata.evidence_path, deliveryVerification: delivery.verification, deliveryAdmission: delivery.admissionVerification, deliveryFallback: delivery.fallback, deliveryAttempts: delivery.fallback ? 2 : 1 });
|
|
10223
10259
|
}
|
|
10224
10260
|
const sessionId = String(existingSessionId);
|
|
10225
10261
|
if (await sessionExists(openCodeClient, sessionId)) {
|
|
@@ -10229,7 +10265,7 @@ async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, w
|
|
|
10229
10265
|
const recovery2 = await recoverClickUpPmSession({ openCodeClient, sendSessionEvent, clickupClient, worktree, taskId, staleSessionId: sessionId, sessionTitle, taskRoute, metadataWithRouting, config, prompt, eventMarkers: [taskId, eventType], deliveryEvidencePath, evidencePath: routingMetadata.evidence_path, eventKey, createSession, verifySessionEventDelivery });
|
|
10230
10266
|
return finish(recovery2);
|
|
10231
10267
|
}
|
|
10232
|
-
return finish({ ok: true, action: "sent_to_existing_session", taskId, sessionId, eventKey, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath, evidencePath: routingMetadata.evidence_path, deliveryVerification: delivery.verification, deliveryFallback: delivery.fallback });
|
|
10268
|
+
return finish({ ok: true, action: "sent_to_existing_session", taskId, sessionId, eventKey, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath, evidencePath: routingMetadata.evidence_path, deliveryVerification: delivery.verification, deliveryAdmission: delivery.admissionVerification, deliveryFallback: delivery.fallback, deliveryAttempts: delivery.fallback ? 2 : 1 });
|
|
10233
10269
|
}
|
|
10234
10270
|
const at = now().toISOString();
|
|
10235
10271
|
appendClickUpWebhookLocalLog(worktree, { type: "missing_session", taskId, sessionId, host, at });
|
|
@@ -10240,8 +10276,44 @@ async function routeClickUpWebhookEvent(options = {}) {
|
|
|
10240
10276
|
const taskId = clickUpTaskIdFromPayload(options.payload || {});
|
|
10241
10277
|
return withClickUpTaskRouteLock(taskId, () => routeClickUpWebhookEventUnlocked(options));
|
|
10242
10278
|
}
|
|
10243
|
-
|
|
10244
|
-
|
|
10279
|
+
function defaultStartupReconciliationScheduler(run, delayMs) {
|
|
10280
|
+
return setTimeout(run, delayMs);
|
|
10281
|
+
}
|
|
10282
|
+
function scheduleClickUpStartupReconciliation({ config, state = {}, worktree = process.cwd(), clickupClient, openCodeClient, scheduler = defaultStartupReconciliationScheduler, delayMs = config?.opencode?.startupReconciliationDelayMs ?? CLICKUP_WEBHOOK_STARTUP_RECONCILIATION_DELAY_MS, saveState = null, reconcile = reconcileClickUpStartup } = {}) {
|
|
10283
|
+
const normalizedDelayMs = normalizeNonNegativeInteger(delayMs, CLICKUP_WEBHOOK_STARTUP_RECONCILIATION_DELAY_MS);
|
|
10284
|
+
clickUpWebhookLifecycleLog(worktree, { type: "startup_reconciliation_scheduled", delayMs: normalizedDelayMs, webhookId: state?.webhookId || null });
|
|
10285
|
+
const run = () => {
|
|
10286
|
+
Promise.resolve().then(() => reconcile({
|
|
10287
|
+
config,
|
|
10288
|
+
state,
|
|
10289
|
+
worktree,
|
|
10290
|
+
clickupClient,
|
|
10291
|
+
openCodeClient,
|
|
10292
|
+
saveState
|
|
10293
|
+
})).catch((error) => {
|
|
10294
|
+
clickUpWebhookLifecycleLog(worktree, { type: "startup_reconciliation_failed", message: error.message });
|
|
10295
|
+
appendClickUpWebhookLocalLog(worktree, { type: "startup_reconciliation_failed", message: error.message });
|
|
10296
|
+
});
|
|
10297
|
+
};
|
|
10298
|
+
const timer = scheduler(run, normalizedDelayMs);
|
|
10299
|
+
timer?.unref?.();
|
|
10300
|
+
return { scheduled: true, delayMs: normalizedDelayMs, timer };
|
|
10301
|
+
}
|
|
10302
|
+
async function reconcileClickUpStartup({ config, state = {}, worktree = process.cwd(), clickupClient, openCodeClient, sessionExists = openCodeSessionExists, createSession = createOpenCodeSession, sendSessionEvent = sendOpenCodeSessionEvent, verifySessionEventDelivery = verifyOpenCodeSessionEventDelivery, waitForReadiness = waitForOpenCodeReadiness, saveState = null, now = () => /* @__PURE__ */ new Date(), limit = CLICKUP_WEBHOOK_STARTUP_TASK_LIMIT } = {}) {
|
|
10303
|
+
clickUpWebhookLifecycleLog(worktree, { type: "startup_reconciliation_started", lastWebhookAt: state.lastWebhookAt || null });
|
|
10304
|
+
if (!config || !clickupClient?.listAssignedTasks) {
|
|
10305
|
+
const skipped = { ok: true, skipped: true, reason: "clickup_task_listing_unavailable", assigned: 0, comments: 0 };
|
|
10306
|
+
clickUpWebhookLifecycleLog(worktree, { type: "startup_reconciliation_finished", ...skipped });
|
|
10307
|
+
return skipped;
|
|
10308
|
+
}
|
|
10309
|
+
const readiness = await waitForReadiness(openCodeClient, { worktree, now });
|
|
10310
|
+
appendClickUpWebhookLocalLog(worktree, { type: readiness.ok ? "startup_reconciliation_readiness_ready" : "startup_reconciliation_readiness_failed", ...readiness });
|
|
10311
|
+
if (!readiness.ok) {
|
|
10312
|
+
const failed = { ok: false, skipped: true, reason: "opencode_not_ready", readiness, assigned: 0, comments: 0, ignored: 0, errors: 1, undelivered: 1, tasks: [], validation: { undelivered: 1, emptyPromptSessions: [] } };
|
|
10313
|
+
clickUpWebhookLifecycleLog(worktree, { type: "startup_reconciliation_failed", reason: failed.reason, readiness });
|
|
10314
|
+
clickUpWebhookLifecycleLog(worktree, { type: "startup_reconciliation_finished", ...failed });
|
|
10315
|
+
return failed;
|
|
10316
|
+
}
|
|
10245
10317
|
let authorizedUserId = "";
|
|
10246
10318
|
if (clickupClient?.getAuthorizedUser) {
|
|
10247
10319
|
try {
|
|
@@ -10259,7 +10331,6 @@ async function reconcileClickUpStartup({ config, state = {}, worktree = process.
|
|
|
10259
10331
|
const lastWebhookMs = clickUpTimestampMs(mutableState.lastWebhookAt);
|
|
10260
10332
|
const ignored = new Set((config.routing?.ignoredStatuses || CLICKUP_WEBHOOK_TERMINAL_STATUSES).map(normalizeClickUpStatus));
|
|
10261
10333
|
const routed = { assigned: 0, comments: 0, ignored: 0, errors: 0, undelivered: 0, tasks: [] };
|
|
10262
|
-
clickUpWebhookLifecycleLog(worktree, { type: "startup_reconciliation_started", lastWebhookAt: state.lastWebhookAt || null });
|
|
10263
10334
|
const listed = await clickupClient.listAssignedTasks({ assigneeId: config.routing.productManagerAssigneeId, limit });
|
|
10264
10335
|
const tasks = clickUpTaskListItems(listed).slice(0, limit);
|
|
10265
10336
|
for (const task of tasks) {
|
|
@@ -10299,7 +10370,10 @@ async function reconcileClickUpStartup({ config, state = {}, worktree = process.
|
|
|
10299
10370
|
branch: result?.branch || null,
|
|
10300
10371
|
worktree: result?.worktree || null,
|
|
10301
10372
|
verification: result?.deliveryVerification?.method || result?.deliveryVerification?.reason || null,
|
|
10302
|
-
delivered: hasActionableClickUpRoute(result)
|
|
10373
|
+
delivered: hasActionableClickUpRoute(result),
|
|
10374
|
+
attempts: result?.deliveryAttempts || null,
|
|
10375
|
+
finalMessageCount: result?.deliveryVerification?.afterCount ?? null,
|
|
10376
|
+
finalMessageId: result?.deliveryVerification?.lastMessageId || null
|
|
10303
10377
|
};
|
|
10304
10378
|
routed.tasks.push(routeSummary);
|
|
10305
10379
|
appendClickUpWebhookLocalLog(worktree, { type: "startup_reconciliation_task_routed", ...routeSummary });
|
|
@@ -11574,18 +11648,16 @@ async function OptimaPlugin(input = {}, pluginOptions = {}) {
|
|
|
11574
11648
|
listener: { bindHost: clickUpWebhookValidation.config.webhook.bindHost, bindPort: clickUpWebhookValidation.config.webhook.bindPort, startedAt: (/* @__PURE__ */ new Date()).toISOString() }
|
|
11575
11649
|
}, clickUpWebhookValidation.config);
|
|
11576
11650
|
registerClickUpWebhookLifecycle({ config: clickUpWebhookValidation.config, state: activeState, worktree, clickupClient: lifecycleClickUpClient, listener: readyListener, listenerRegistry });
|
|
11577
|
-
|
|
11578
|
-
|
|
11579
|
-
|
|
11580
|
-
|
|
11581
|
-
|
|
11582
|
-
|
|
11583
|
-
|
|
11584
|
-
|
|
11585
|
-
|
|
11586
|
-
}
|
|
11587
|
-
appendClickUpWebhookLocalLog(worktree, { type: "startup_reconciliation_failed", message: error.message });
|
|
11588
|
-
}
|
|
11651
|
+
scheduleClickUpStartupReconciliation({
|
|
11652
|
+
config: clickUpWebhookValidation.config,
|
|
11653
|
+
state: activeState,
|
|
11654
|
+
worktree,
|
|
11655
|
+
clickupClient: lifecycleClickUpClient,
|
|
11656
|
+
openCodeClient: input.client,
|
|
11657
|
+
scheduler: input.startupReconciliationScheduler,
|
|
11658
|
+
delayMs: input.startupReconciliationDelayMs,
|
|
11659
|
+
saveState: (nextState) => writeClickUpWebhookState(worktree, nextState, clickUpWebhookValidation.config)
|
|
11660
|
+
});
|
|
11589
11661
|
} else {
|
|
11590
11662
|
clickUpWebhookLifecycleLog(worktree, { type: "remote_webhook_preserved", reason: readyListener.reason || "listener_unavailable", webhookId: listenerState.webhookId });
|
|
11591
11663
|
markClickUpWebhookInactive(worktree, listenerState, clickUpWebhookValidation.config);
|
|
@@ -12160,7 +12232,7 @@ Follow-up: use optima_prompt_workflow with session_id '${sessionId}' to check in
|
|
|
12160
12232
|
}
|
|
12161
12233
|
};
|
|
12162
12234
|
}
|
|
12163
|
-
OptimaPlugin.__internals = { BUNDLE_AGENTS_DIR, BUNDLE_ASSETS_DIR, CLICKUP_DEFINITION_DOC_PARENT, activeClickUpWebhookLifecycleRegistry, cleanupManagedClickUpWebhook, deleteClickUpWebhookBestEffort, BUNDLE_POLICIES_DIR, buildClickUpApplyPayloadResult, buildClickUpCreateSubtasksPayload, buildClickUpStartTaskPayload, buildClickUpSummaryPayload, buildClickUpTransitionPayload, buildOptimaAgents, clickUpCommentLedgerKey, clickUpCommentMentionsProductManager, clickUpWebhookAuditLogDir, clickUpWebhookAuditLogPath, createClickUpApiClient, createOpenCodeSession, deliveryEvidencePathForClickUpTask, ensureClickUpTaskWorktree, ensureClickUpWebhookSubscription, handleClickUpWebhookRequest, isClickUpWebhookStateActive, normalizeClickUpWebhookConfig, normalizeClickUpWebhookLogLevel, normalizeOpenCodeBaseUrl, openCodeSessionExists, readClickUpCommentLedger, readClickUpWebhookState, readOpenCodeSessionMessages, reconcileClickUpStartup, recordClickUpCommentVersionProcessed, resyncClickUpWebhookForSignatureDrift, resolveOptimaPluginOptions, resolveSecretReference, routeClickUpWebhookEvent, sendOpenCodeSessionEvent, sendOpenCodeSessionEventDirect, verifyOpenCodeSessionEventDelivery, stableClickUpCommentVersionMarker, startClickUpWebhookListener, validateClickUpWebhookState, verifyClickUpSignature, writeClickUpWebhookState, decideClickUpStatusAction, deriveClickUpBranchName, deriveClickUpPendingSubtaskBranch, deriveClickUpPrTarget, deriveClickUpWorktree, determineClickUpMergeAuthority, ensureOptimaGitignoreRules, explicitSafeInputWorktree, finalApprovalAssignees, formatValidationResult, isIgnoredClickUpTaskType, isOptimaPluginPackageWorktree, isSafeWritableDirectory, loadHumansRegistry, mergeClickUpAgentMetadata, mergeClickUpSessionMetadata, migrateLegacyOptimaLayout, normalizeAgentMetadataJson, normalizeClickUpStatus, normalizeClickUpTaskType, normalizePromptResponseParts, normalizeWorkflowTaskPath, parseClickUpSubtasksMarkdown, parseHumansRegistry, parseMarkdownArtifact, parseMarkdownSections, preEstimateClickUpWork, readMarkdownArtifact, resolveHumanRoles, resolveSafeWorktree, safeWorktreeOrFailure, stripRawLogSections, validateMainWorkspaceBranchSafety, workflowFinalMessageFromPromptResponse };
|
|
12235
|
+
OptimaPlugin.__internals = { BUNDLE_AGENTS_DIR, BUNDLE_ASSETS_DIR, CLICKUP_DEFINITION_DOC_PARENT, activeClickUpWebhookLifecycleRegistry, cleanupManagedClickUpWebhook, deleteClickUpWebhookBestEffort, BUNDLE_POLICIES_DIR, buildClickUpApplyPayloadResult, buildClickUpCreateSubtasksPayload, buildClickUpStartTaskPayload, buildClickUpSummaryPayload, buildClickUpTransitionPayload, buildOptimaAgents, clickUpCommentLedgerKey, clickUpCommentMentionsProductManager, clickUpWebhookAuditLogDir, clickUpWebhookAuditLogPath, createClickUpApiClient, createOpenCodeSession, deliveryEvidencePathForClickUpTask, ensureClickUpTaskWorktree, ensureClickUpWebhookSubscription, handleClickUpWebhookRequest, isClickUpWebhookStateActive, normalizeClickUpWebhookConfig, normalizeClickUpWebhookLogLevel, normalizeOpenCodeBaseUrl, openCodeSessionExists, readClickUpCommentLedger, readClickUpWebhookState, readOpenCodeSessionMessages, reconcileClickUpStartup, scheduleClickUpStartupReconciliation, waitForOpenCodeReadiness, recordClickUpCommentVersionProcessed, resyncClickUpWebhookForSignatureDrift, resolveOptimaPluginOptions, resolveSecretReference, routeClickUpWebhookEvent, sendOpenCodeSessionEvent, sendOpenCodeSessionEventDirect, verifyOpenCodeSessionEventDelivery, stableClickUpCommentVersionMarker, startClickUpWebhookListener, validateClickUpWebhookState, verifyClickUpSignature, writeClickUpWebhookState, decideClickUpStatusAction, deriveClickUpBranchName, deriveClickUpPendingSubtaskBranch, deriveClickUpPrTarget, deriveClickUpWorktree, determineClickUpMergeAuthority, ensureOptimaGitignoreRules, explicitSafeInputWorktree, finalApprovalAssignees, formatValidationResult, isIgnoredClickUpTaskType, isOptimaPluginPackageWorktree, isSafeWritableDirectory, loadHumansRegistry, mergeClickUpAgentMetadata, mergeClickUpSessionMetadata, migrateLegacyOptimaLayout, normalizeAgentMetadataJson, normalizeClickUpStatus, normalizeClickUpTaskType, normalizePromptResponseParts, normalizeWorkflowTaskPath, parseClickUpSubtasksMarkdown, parseHumansRegistry, parseMarkdownArtifact, parseMarkdownSections, preEstimateClickUpWork, readMarkdownArtifact, resolveHumanRoles, resolveSafeWorktree, safeWorktreeOrFailure, stripRawLogSections, validateMainWorkspaceBranchSafety, workflowFinalMessageFromPromptResponse };
|
|
12164
12236
|
|
|
12165
12237
|
// src/sanitize_cli.js
|
|
12166
12238
|
var { migrateLegacyOptimaLayout: migrateLegacyOptimaLayout2 } = OptimaPlugin.__internals;
|