@defend-tech/opencode-optima 0.1.40 → 0.1.43
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 +146 -36
- package/dist/sanitize_cli.js +146 -36
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -9248,12 +9248,16 @@ async function validateClickUpWebhookState(state, config, clickupClient = null,
|
|
|
9248
9248
|
}
|
|
9249
9249
|
return { valid: true, mode: "local_state", state: { ...state, lastValidatedAt: (/* @__PURE__ */ new Date()).toISOString() }, limitation: "ClickUp API validation unavailable; Optima trusts ignored local runtime state only after id/secret/config checks." };
|
|
9250
9250
|
}
|
|
9251
|
-
|
|
9251
|
+
function clickUpWebhookValidationFromConfig(config = null) {
|
|
9252
|
+
if (!config) return { complete: false, ok: false, errors: ["clickup_config_unavailable"] };
|
|
9253
|
+
return { complete: true, ok: true, enabled: true, config, errors: [] };
|
|
9254
|
+
}
|
|
9255
|
+
async function ensureClickUpWebhookSubscription({ validation, worktree, clickupClient = null, existingState = null } = {}) {
|
|
9252
9256
|
if (!validation?.complete) {
|
|
9253
9257
|
return { active: false, valid: false, reason: "config_incomplete", errors: validation?.errors || [] };
|
|
9254
9258
|
}
|
|
9255
9259
|
const { config } = validation;
|
|
9256
|
-
const existing = readClickUpWebhookState(worktree, config);
|
|
9260
|
+
const existing = existingState ? sanitizeClickUpWebhookState(existingState, config) : readClickUpWebhookState(worktree, config);
|
|
9257
9261
|
const existingValidation = await validateClickUpWebhookState(existing, config, clickupClient, { allowRemoteUnhealthyLocalRecovery: true });
|
|
9258
9262
|
if (existingValidation.valid) {
|
|
9259
9263
|
const state = writeClickUpWebhookState(worktree, { ...existingValidation.state, recentEventKeys: existing.recentEventKeys || [] }, config);
|
|
@@ -9306,6 +9310,30 @@ function verifyClickUpSignature(rawBody, signatureHeader, secret) {
|
|
|
9306
9310
|
const wanted = Buffer.from(expected, "hex");
|
|
9307
9311
|
return given.length === wanted.length && crypto.timingSafeEqual(given, wanted);
|
|
9308
9312
|
}
|
|
9313
|
+
async function resyncClickUpWebhookForSignatureDrift({ rawBody, signature, config, state = {}, worktree = process.cwd(), clickupClient, saveState } = {}) {
|
|
9314
|
+
const validation = clickUpWebhookValidationFromConfig(config);
|
|
9315
|
+
if (!clickupClient?.listWebhooks && !clickupClient?.createWebhook) {
|
|
9316
|
+
appendClickUpWebhookLocalLog(worktree, { type: "signature_resync_skipped", reason: "clickup_client_unavailable", webhookId: state?.webhookId || null });
|
|
9317
|
+
return { ok: false, reason: "clickup_client_unavailable", state };
|
|
9318
|
+
}
|
|
9319
|
+
try {
|
|
9320
|
+
const resync = await ensureClickUpWebhookSubscription({ validation, worktree, clickupClient, existingState: state });
|
|
9321
|
+
if (!resync?.valid || !resync?.state?.secret) {
|
|
9322
|
+
appendClickUpWebhookLocalLog(worktree, { type: "signature_resync_failed", reason: resync?.reason || "resync_failed", webhookId: state?.webhookId || null });
|
|
9323
|
+
return { ok: false, reason: resync?.reason || "resync_failed", resync, state };
|
|
9324
|
+
}
|
|
9325
|
+
if (!verifyClickUpSignature(rawBody, signature, resync.state.secret)) {
|
|
9326
|
+
appendClickUpWebhookLocalLog(worktree, { type: "signature_resync_unverified", reason: "invalid_signature_after_resync", oldWebhookId: state?.webhookId || null, webhookId: resync.state.webhookId || null, mode: resync.mode || null });
|
|
9327
|
+
return { ok: false, reason: "invalid_signature_after_resync", resync, state: resync.state };
|
|
9328
|
+
}
|
|
9329
|
+
if (saveState) saveState(resync.state);
|
|
9330
|
+
appendClickUpWebhookLocalLog(worktree, { type: "signature_resync_verified", oldWebhookId: state?.webhookId || null, webhookId: resync.state.webhookId || null, mode: resync.mode || null });
|
|
9331
|
+
return { ok: true, state: resync.state, resync };
|
|
9332
|
+
} catch (error) {
|
|
9333
|
+
appendClickUpWebhookLocalLog(worktree, { type: "signature_resync_error", webhookId: state?.webhookId || null, message: error.message });
|
|
9334
|
+
return { ok: false, reason: "resync_error", error: error.message, state };
|
|
9335
|
+
}
|
|
9336
|
+
}
|
|
9309
9337
|
function clickUpWebhookEventKey(payload = {}) {
|
|
9310
9338
|
const history = Array.isArray(payload.history_items) ? payload.history_items[0] : payload.history_item;
|
|
9311
9339
|
const comment = payload.comment || history?.comment || {};
|
|
@@ -9564,31 +9592,64 @@ function assertOpenCodePromptAccepted(result) {
|
|
|
9564
9592
|
}
|
|
9565
9593
|
return result;
|
|
9566
9594
|
}
|
|
9567
|
-
|
|
9595
|
+
function responseLooksLikeHtml(contentType = "", raw = "") {
|
|
9596
|
+
return String(contentType || "").toLowerCase().includes("text/html") || /^\s*<!doctype\s+html\b/i.test(raw) || /^\s*<html\b/i.test(raw);
|
|
9597
|
+
}
|
|
9598
|
+
async function readOpenCodeJsonResponse(response, endpointName) {
|
|
9599
|
+
const contentType = response.headers?.get?.("content-type") || "";
|
|
9600
|
+
const raw = await response.text();
|
|
9601
|
+
if (responseLooksLikeHtml(contentType, raw)) {
|
|
9602
|
+
throw new Error(`OpenCode ${endpointName} returned the HTML app shell instead of an API response.`);
|
|
9603
|
+
}
|
|
9604
|
+
if (!raw) return null;
|
|
9605
|
+
try {
|
|
9606
|
+
return JSON.parse(raw);
|
|
9607
|
+
} catch (error) {
|
|
9608
|
+
if (response.ok) throw new Error(`OpenCode ${endpointName} response was not JSON: ${error.message}`);
|
|
9609
|
+
return { raw };
|
|
9610
|
+
}
|
|
9611
|
+
}
|
|
9612
|
+
async function sendOpenCodeSessionEventDirect({ baseUrl, sessionId, text, agent, fetchImpl = globalThis.fetch } = {}) {
|
|
9568
9613
|
if (typeof fetchImpl !== "function") throw new Error("OpenCode direct prompt delivery requires fetch.");
|
|
9569
9614
|
const root = normalizeOpenCodeBaseUrl(baseUrl, "");
|
|
9570
9615
|
if (!root) throw new Error("OpenCode direct prompt delivery requires a base URL.");
|
|
9571
|
-
const
|
|
9572
|
-
const
|
|
9573
|
-
|
|
9574
|
-
|
|
9575
|
-
|
|
9576
|
-
|
|
9577
|
-
|
|
9578
|
-
|
|
9579
|
-
|
|
9580
|
-
|
|
9581
|
-
|
|
9582
|
-
}
|
|
9583
|
-
|
|
9616
|
+
const encodedSession = encodeURIComponent(sessionId);
|
|
9617
|
+
const attempts = [
|
|
9618
|
+
{
|
|
9619
|
+
name: "v2 prompt",
|
|
9620
|
+
url: `${root}/api/session/${encodedSession}/prompt`,
|
|
9621
|
+
body: { prompt: { text }, delivery: "queue", resume: true },
|
|
9622
|
+
accept: (response, data) => {
|
|
9623
|
+
if (!response.ok) return null;
|
|
9624
|
+
if (!data?.data?.id) throw new Error("OpenCode v2 prompt response did not include data.id.");
|
|
9625
|
+
return { ok: true, method: "http", endpoint: "/api/session/{sessionID}/prompt", status: response.status, data: data.data, response: data };
|
|
9626
|
+
}
|
|
9627
|
+
},
|
|
9628
|
+
{
|
|
9629
|
+
name: "legacy async prompt",
|
|
9630
|
+
url: `${root}/session/${encodedSession}/prompt_async`,
|
|
9631
|
+
body: { agent, parts: [{ type: "text", text }] },
|
|
9632
|
+
accept: (response, data) => {
|
|
9633
|
+
if (response.status !== 204 && !response.ok) return null;
|
|
9634
|
+
return { ok: true, method: "http", endpoint: "/session/{sessionID}/prompt_async", status: response.status, data: data?.data || null, response: data };
|
|
9635
|
+
}
|
|
9584
9636
|
}
|
|
9637
|
+
];
|
|
9638
|
+
let firstError = null;
|
|
9639
|
+
for (const attempt of attempts) {
|
|
9640
|
+
const response = await fetchImpl(attempt.url, {
|
|
9641
|
+
method: "POST",
|
|
9642
|
+
headers: { "content-type": "application/json" },
|
|
9643
|
+
body: JSON.stringify(attempt.body)
|
|
9644
|
+
});
|
|
9645
|
+
const data = await readOpenCodeJsonResponse(response, attempt.name);
|
|
9646
|
+
const accepted = attempt.accept(response, data);
|
|
9647
|
+
if (accepted) return accepted;
|
|
9648
|
+
const message = data?.error?.message || data?.message || data?.raw || `OpenCode ${attempt.name} request failed with status ${response.status}.`;
|
|
9649
|
+
firstError ??= new Error(message);
|
|
9650
|
+
if (![404, 405].includes(Number(response.status))) break;
|
|
9585
9651
|
}
|
|
9586
|
-
|
|
9587
|
-
const message = data?.error?.message || data?.message || raw || `OpenCode prompt request failed with status ${response.status}.`;
|
|
9588
|
-
throw new Error(message);
|
|
9589
|
-
}
|
|
9590
|
-
if (!data?.data?.id) throw new Error("OpenCode prompt response did not include data.id.");
|
|
9591
|
-
return { ok: true, method: "http", status: response.status, data: data.data, response: data };
|
|
9652
|
+
throw firstError || new Error("OpenCode direct prompt delivery failed.");
|
|
9592
9653
|
}
|
|
9593
9654
|
async function sendOpenCodeSessionEvent(client, { sessionId, agent, text, directory, opencodeBaseUrl, baseUrl, fetchImpl, direct = false } = {}) {
|
|
9594
9655
|
const directBaseUrl = opencodeBaseUrl || baseUrl;
|
|
@@ -9604,7 +9665,7 @@ async function sendOpenCodeSessionEvent(client, { sessionId, agent, text, direct
|
|
|
9604
9665
|
if (!direct && typeof client?.session?.prompt === "function") {
|
|
9605
9666
|
return assertOpenCodePromptAccepted(await client.session.prompt(structuredPayload));
|
|
9606
9667
|
}
|
|
9607
|
-
if (directBaseUrl) return sendOpenCodeSessionEventDirect({ baseUrl: directBaseUrl, sessionId, text, fetchImpl });
|
|
9668
|
+
if (directBaseUrl) return sendOpenCodeSessionEventDirect({ baseUrl: directBaseUrl, sessionId, text, agent, fetchImpl });
|
|
9608
9669
|
throw new Error("OpenCode client does not expose session.prompt or session.promptAsync.");
|
|
9609
9670
|
}
|
|
9610
9671
|
function normalizeOpenCodeSessionMessages(result) {
|
|
@@ -9672,7 +9733,7 @@ async function applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason,
|
|
|
9672
9733
|
return { ok: false, error: error.message, tagName };
|
|
9673
9734
|
}
|
|
9674
9735
|
}
|
|
9675
|
-
async function deliverClickUpSessionEventWithVerification({ openCodeClient, sendSessionEvent, clickupClient, worktree, taskId, sessionId, agent, text, directory, opencodeBaseUrl, eventMarkers = [], verifySessionEventDelivery = verifyOpenCodeSessionEventDelivery } = {}) {
|
|
9736
|
+
async function deliverClickUpSessionEventWithVerification({ openCodeClient, sendSessionEvent, clickupClient, worktree, taskId, sessionId, agent, text, directory, opencodeBaseUrl, eventMarkers = [], verifySessionEventDelivery = verifyOpenCodeSessionEventDelivery, applyBlockerOnFailure = true } = {}) {
|
|
9676
9737
|
const beforeMessages = await readOpenCodeSessionMessages(openCodeClient, { sessionId, limit: 50 }).catch(() => null);
|
|
9677
9738
|
await sendSessionEvent(openCodeClient, { sessionId, agent, text, directory, opencodeBaseUrl });
|
|
9678
9739
|
let verification = await verifySessionEventDelivery(openCodeClient, { sessionId, beforeMessages, expectedText: text, markers: eventMarkers });
|
|
@@ -9686,9 +9747,50 @@ async function deliverClickUpSessionEventWithVerification({ openCodeClient, send
|
|
|
9686
9747
|
}
|
|
9687
9748
|
const reason = verification?.reason || "message_delivery_failed";
|
|
9688
9749
|
appendClickUpWebhookLocalLog(worktree, { type: "message_delivery_failed", taskId, sessionId, reason, fallbackAttempted: canFallbackDirect });
|
|
9750
|
+
if (!applyBlockerOnFailure) return { ok: false, action: "message_delivery_failed", reason, taskId, sessionId, fallbackAttempted: canFallbackDirect };
|
|
9689
9751
|
const blocker = await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason, source: canFallbackDirect ? "delivery_fallback_failed" : "delivery_verification_failed" });
|
|
9690
9752
|
return { ok: false, action: "message_delivery_failed", reason, taskId, sessionId, fallbackAttempted: canFallbackDirect, blockerTag: blocker };
|
|
9691
9753
|
}
|
|
9754
|
+
async function recoverClickUpPmSession({ openCodeClient, sendSessionEvent, clickupClient, worktree, taskId, staleSessionId, sessionTitle, taskRoute, metadataWithRouting, config, prompt, eventMarkers = [], deliveryEvidencePath, evidencePath, eventKey, createSession, verifySessionEventDelivery } = {}) {
|
|
9755
|
+
appendClickUpWebhookLocalLog(worktree, { type: "pm_session_recovery_started", taskId, staleSessionId, worktree: taskRoute?.worktree });
|
|
9756
|
+
let replacementSessionId = "";
|
|
9757
|
+
try {
|
|
9758
|
+
replacementSessionId = await createSession(openCodeClient, { title: `${sessionTitle} (recovery)`, directory: taskRoute.worktree, agent: config.routing.targetAgent });
|
|
9759
|
+
} catch (error) {
|
|
9760
|
+
appendClickUpWebhookLocalLog(worktree, { type: "pm_session_recovery_create_failed", taskId, staleSessionId, message: error.message });
|
|
9761
|
+
const blockerTag = await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason: "replacement_session_create_failed", source: "pm_session_recovery" });
|
|
9762
|
+
return { ok: false, action: "message_delivery_failed", reason: "replacement_session_create_failed", taskId, sessionId: staleSessionId, replacementAttempted: true, blockerTag, eventKey, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath, evidencePath };
|
|
9763
|
+
}
|
|
9764
|
+
if (!String(replacementSessionId || "").startsWith("ses_")) {
|
|
9765
|
+
appendClickUpWebhookLocalLog(worktree, { type: "pm_session_recovery_create_invalid", taskId, staleSessionId, replacementSessionId });
|
|
9766
|
+
const blockerTag = await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason: "replacement_session_create_failed", source: "pm_session_recovery" });
|
|
9767
|
+
return { ok: false, action: "message_delivery_failed", reason: "replacement_session_create_failed", taskId, sessionId: staleSessionId, replacementAttempted: true, blockerTag, eventKey, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath, evidencePath };
|
|
9768
|
+
}
|
|
9769
|
+
const replacementDelivery = await deliverClickUpSessionEventWithVerification({
|
|
9770
|
+
openCodeClient,
|
|
9771
|
+
sendSessionEvent,
|
|
9772
|
+
clickupClient,
|
|
9773
|
+
worktree,
|
|
9774
|
+
taskId,
|
|
9775
|
+
sessionId: replacementSessionId,
|
|
9776
|
+
agent: config.routing.targetAgent,
|
|
9777
|
+
text: prompt,
|
|
9778
|
+
directory: taskRoute.worktree,
|
|
9779
|
+
opencodeBaseUrl: config.opencode?.baseUrl,
|
|
9780
|
+
eventMarkers,
|
|
9781
|
+
verifySessionEventDelivery,
|
|
9782
|
+
applyBlockerOnFailure: false
|
|
9783
|
+
});
|
|
9784
|
+
if (!replacementDelivery.ok) {
|
|
9785
|
+
appendClickUpWebhookLocalLog(worktree, { type: "pm_session_recovery_delivery_failed", taskId, staleSessionId, replacementSessionId, reason: replacementDelivery.reason });
|
|
9786
|
+
const blockerTag = await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason: replacementDelivery.reason || "replacement_delivery_failed", source: "pm_session_recovery" });
|
|
9787
|
+
return { ...replacementDelivery, sessionId: staleSessionId, replacementSessionId, replacementAttempted: true, blockerTag, eventKey, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath, evidencePath };
|
|
9788
|
+
}
|
|
9789
|
+
const replacementMetadata = clearClickUpPendingSessionMetadata(setClickUpSessionMetadata(metadataWithRouting, config.routing.metadataKey, replacementSessionId), config.routing.metadataKey);
|
|
9790
|
+
await clickupClient.updateTaskMetadata({ taskId, fieldId: config.routing.metadataFieldId, value: replacementMetadata });
|
|
9791
|
+
appendClickUpWebhookLocalLog(worktree, { type: "pm_session_recovery_succeeded", taskId, staleSessionId, replacementSessionId });
|
|
9792
|
+
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 };
|
|
9793
|
+
}
|
|
9692
9794
|
function formatClickUpWebhookPrompt({ eventType, taskId, payload, branch = "", worktree = "", deliveryEvidencePath = "" }) {
|
|
9693
9795
|
const comment = clickUpCommentFromPayload(payload);
|
|
9694
9796
|
const commentText = clickUpCommentText(comment).trim();
|
|
@@ -9940,8 +10042,8 @@ async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, w
|
|
|
9940
10042
|
} catch (error) {
|
|
9941
10043
|
const message = `Optima webhook could not create or reuse a task worktree for ${taskId}: ${error.message}`;
|
|
9942
10044
|
appendClickUpWebhookLocalLog(worktree, { type: "task_worktree_failed", taskId, message });
|
|
9943
|
-
const
|
|
9944
|
-
return { ok: false, action: "error", reason: "task_worktree_failed", taskId, message, blockerTag
|
|
10045
|
+
const blockerTag = await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason: "task_worktree_failed", source: "route_worktree" });
|
|
10046
|
+
return { ok: false, action: "error", reason: "task_worktree_failed", taskId, message, blockerTag };
|
|
9945
10047
|
}
|
|
9946
10048
|
const deliveryEvidencePath = deliveryEvidencePathForClickUpTask(taskId);
|
|
9947
10049
|
const routingMetadata = {
|
|
@@ -9982,14 +10084,17 @@ async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, w
|
|
|
9982
10084
|
const sessionId = String(existingSessionId);
|
|
9983
10085
|
if (await sessionExists(openCodeClient, sessionId)) {
|
|
9984
10086
|
if (typeof clickupClient?.updateTaskMetadata === "function") await clickupClient.updateTaskMetadata({ taskId, fieldId: config.routing.metadataFieldId, value: metadataWithRouting });
|
|
9985
|
-
const delivery = await deliverClickUpSessionEventWithVerification({ openCodeClient, sendSessionEvent, clickupClient, worktree, taskId, sessionId, agent: config.routing.targetAgent, text: prompt, directory: taskRoute.worktree, opencodeBaseUrl: config.opencode?.baseUrl, eventMarkers: [taskId, eventType], verifySessionEventDelivery });
|
|
9986
|
-
if (!delivery.ok)
|
|
10087
|
+
const delivery = await deliverClickUpSessionEventWithVerification({ openCodeClient, sendSessionEvent, clickupClient, worktree, taskId, sessionId, agent: config.routing.targetAgent, text: prompt, directory: taskRoute.worktree, opencodeBaseUrl: config.opencode?.baseUrl, eventMarkers: [taskId, eventType], verifySessionEventDelivery, applyBlockerOnFailure: false });
|
|
10088
|
+
if (!delivery.ok) {
|
|
10089
|
+
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 });
|
|
10090
|
+
return finish(recovery2);
|
|
10091
|
+
}
|
|
9987
10092
|
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 });
|
|
9988
10093
|
}
|
|
9989
10094
|
const at = now().toISOString();
|
|
9990
10095
|
appendClickUpWebhookLocalLog(worktree, { type: "missing_session", taskId, sessionId, host, at });
|
|
9991
|
-
const
|
|
9992
|
-
return finish(
|
|
10096
|
+
const recovery = 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 });
|
|
10097
|
+
return finish(recovery);
|
|
9993
10098
|
}
|
|
9994
10099
|
async function routeClickUpWebhookEvent(options = {}) {
|
|
9995
10100
|
const taskId = clickUpTaskIdFromPayload(options.payload || {});
|
|
@@ -10097,15 +10202,16 @@ function clickUpWebhookExpectedPath(config) {
|
|
|
10097
10202
|
return "/";
|
|
10098
10203
|
}
|
|
10099
10204
|
}
|
|
10100
|
-
async function handleClickUpWebhookRequest({ method = "POST", url = null, headers = {}, rawBody = "", config, state, worktree, clickupClient, openCodeClient, saveState, now = () => /* @__PURE__ */ new Date() } = {}) {
|
|
10205
|
+
async function handleClickUpWebhookRequest({ method = "POST", url = null, headers = {}, rawBody = "", config, state, worktree, clickupClient, openCodeClient, saveState, now = () => /* @__PURE__ */ new Date(), sessionExists, createSession, sendSessionEvent, ensureTaskWorktree, verifySessionEventDelivery } = {}) {
|
|
10101
10206
|
let payload = null;
|
|
10102
10207
|
let handled = null;
|
|
10103
10208
|
let authenticatedWebhook = false;
|
|
10104
10209
|
let receivedAt = null;
|
|
10210
|
+
let activeState = state;
|
|
10105
10211
|
const finish = (result) => {
|
|
10106
10212
|
handled = result;
|
|
10107
10213
|
receivedAt ??= now();
|
|
10108
|
-
const auditState = authenticatedWebhook ? { ...
|
|
10214
|
+
const auditState = authenticatedWebhook ? { ...activeState, lastWebhookAt: receivedAt.toISOString() } : activeState;
|
|
10109
10215
|
if (saveState && authenticatedWebhook) saveState(auditState);
|
|
10110
10216
|
writeClickUpWebhookAuditLog({ method, url, headers, rawBody, config, state: auditState, handled, payload, at: receivedAt });
|
|
10111
10217
|
return result;
|
|
@@ -10120,7 +10226,11 @@ async function handleClickUpWebhookRequest({ method = "POST", url = null, header
|
|
|
10120
10226
|
const bodyBytes = Buffer.isBuffer(rawBody) ? rawBody.length : Buffer.byteLength(String(rawBody || ""));
|
|
10121
10227
|
if (bodyBytes > maxBodyBytes) return finish({ ok: false, status: 413, reason: "body_too_large" });
|
|
10122
10228
|
const signature = headers["x-signature"] || headers["X-Signature"];
|
|
10123
|
-
if (!verifyClickUpSignature(rawBody, signature,
|
|
10229
|
+
if (!verifyClickUpSignature(rawBody, signature, activeState?.secret)) {
|
|
10230
|
+
const resync = await resyncClickUpWebhookForSignatureDrift({ rawBody, signature, config, state: activeState, worktree, clickupClient, saveState });
|
|
10231
|
+
if (!resync.ok) return finish({ ok: false, status: 401, reason: "invalid_signature", resync: { attempted: true, reason: resync.reason } });
|
|
10232
|
+
activeState = resync.state;
|
|
10233
|
+
}
|
|
10124
10234
|
authenticatedWebhook = true;
|
|
10125
10235
|
receivedAt = now();
|
|
10126
10236
|
try {
|
|
@@ -10128,8 +10238,8 @@ async function handleClickUpWebhookRequest({ method = "POST", url = null, header
|
|
|
10128
10238
|
} catch {
|
|
10129
10239
|
return finish({ ok: false, status: 400, reason: "invalid_json" });
|
|
10130
10240
|
}
|
|
10131
|
-
const receivedState = { ...
|
|
10132
|
-
const result = await routeClickUpWebhookEvent({ payload, config, state: receivedState, worktree, clickupClient, openCodeClient, saveState });
|
|
10241
|
+
const receivedState = { ...activeState, lastWebhookAt: receivedAt.toISOString() };
|
|
10242
|
+
const result = await routeClickUpWebhookEvent({ payload, config, state: receivedState, worktree, clickupClient, openCodeClient, saveState, sessionExists, createSession, sendSessionEvent, ensureTaskWorktree, verifySessionEventDelivery });
|
|
10133
10243
|
return finish({ ok: result.ok, status: result.ok ? 200 : 422, result });
|
|
10134
10244
|
} catch (error) {
|
|
10135
10245
|
writeClickUpWebhookAuditLog({ method, url, headers, rawBody, config, state, handled, error, payload, at: now() });
|
|
@@ -11869,7 +11979,7 @@ Follow-up: use optima_prompt_workflow with session_id '${sessionId}' to check in
|
|
|
11869
11979
|
}
|
|
11870
11980
|
};
|
|
11871
11981
|
}
|
|
11872
|
-
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, deliveryEvidencePathForClickUpTask, ensureClickUpTaskWorktree, ensureClickUpWebhookSubscription, handleClickUpWebhookRequest, isClickUpWebhookStateActive, normalizeClickUpWebhookConfig, normalizeClickUpWebhookLogLevel, normalizeOpenCodeBaseUrl, readClickUpCommentLedger, readClickUpWebhookState, reconcileClickUpStartup, recordClickUpCommentVersionProcessed, 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 };
|
|
11982
|
+
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, deliveryEvidencePathForClickUpTask, ensureClickUpTaskWorktree, ensureClickUpWebhookSubscription, handleClickUpWebhookRequest, isClickUpWebhookStateActive, normalizeClickUpWebhookConfig, normalizeClickUpWebhookLogLevel, normalizeOpenCodeBaseUrl, readClickUpCommentLedger, readClickUpWebhookState, 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 };
|
|
11873
11983
|
export {
|
|
11874
11984
|
OptimaPlugin as default
|
|
11875
11985
|
};
|
package/dist/sanitize_cli.js
CHANGED
|
@@ -9255,12 +9255,16 @@ async function validateClickUpWebhookState(state, config, clickupClient = null,
|
|
|
9255
9255
|
}
|
|
9256
9256
|
return { valid: true, mode: "local_state", state: { ...state, lastValidatedAt: (/* @__PURE__ */ new Date()).toISOString() }, limitation: "ClickUp API validation unavailable; Optima trusts ignored local runtime state only after id/secret/config checks." };
|
|
9257
9257
|
}
|
|
9258
|
-
|
|
9258
|
+
function clickUpWebhookValidationFromConfig(config = null) {
|
|
9259
|
+
if (!config) return { complete: false, ok: false, errors: ["clickup_config_unavailable"] };
|
|
9260
|
+
return { complete: true, ok: true, enabled: true, config, errors: [] };
|
|
9261
|
+
}
|
|
9262
|
+
async function ensureClickUpWebhookSubscription({ validation, worktree, clickupClient = null, existingState = null } = {}) {
|
|
9259
9263
|
if (!validation?.complete) {
|
|
9260
9264
|
return { active: false, valid: false, reason: "config_incomplete", errors: validation?.errors || [] };
|
|
9261
9265
|
}
|
|
9262
9266
|
const { config } = validation;
|
|
9263
|
-
const existing = readClickUpWebhookState(worktree, config);
|
|
9267
|
+
const existing = existingState ? sanitizeClickUpWebhookState(existingState, config) : readClickUpWebhookState(worktree, config);
|
|
9264
9268
|
const existingValidation = await validateClickUpWebhookState(existing, config, clickupClient, { allowRemoteUnhealthyLocalRecovery: true });
|
|
9265
9269
|
if (existingValidation.valid) {
|
|
9266
9270
|
const state = writeClickUpWebhookState(worktree, { ...existingValidation.state, recentEventKeys: existing.recentEventKeys || [] }, config);
|
|
@@ -9313,6 +9317,30 @@ function verifyClickUpSignature(rawBody, signatureHeader, secret) {
|
|
|
9313
9317
|
const wanted = Buffer.from(expected, "hex");
|
|
9314
9318
|
return given.length === wanted.length && crypto.timingSafeEqual(given, wanted);
|
|
9315
9319
|
}
|
|
9320
|
+
async function resyncClickUpWebhookForSignatureDrift({ rawBody, signature, config, state = {}, worktree = process.cwd(), clickupClient, saveState } = {}) {
|
|
9321
|
+
const validation = clickUpWebhookValidationFromConfig(config);
|
|
9322
|
+
if (!clickupClient?.listWebhooks && !clickupClient?.createWebhook) {
|
|
9323
|
+
appendClickUpWebhookLocalLog(worktree, { type: "signature_resync_skipped", reason: "clickup_client_unavailable", webhookId: state?.webhookId || null });
|
|
9324
|
+
return { ok: false, reason: "clickup_client_unavailable", state };
|
|
9325
|
+
}
|
|
9326
|
+
try {
|
|
9327
|
+
const resync = await ensureClickUpWebhookSubscription({ validation, worktree, clickupClient, existingState: state });
|
|
9328
|
+
if (!resync?.valid || !resync?.state?.secret) {
|
|
9329
|
+
appendClickUpWebhookLocalLog(worktree, { type: "signature_resync_failed", reason: resync?.reason || "resync_failed", webhookId: state?.webhookId || null });
|
|
9330
|
+
return { ok: false, reason: resync?.reason || "resync_failed", resync, state };
|
|
9331
|
+
}
|
|
9332
|
+
if (!verifyClickUpSignature(rawBody, signature, resync.state.secret)) {
|
|
9333
|
+
appendClickUpWebhookLocalLog(worktree, { type: "signature_resync_unverified", reason: "invalid_signature_after_resync", oldWebhookId: state?.webhookId || null, webhookId: resync.state.webhookId || null, mode: resync.mode || null });
|
|
9334
|
+
return { ok: false, reason: "invalid_signature_after_resync", resync, state: resync.state };
|
|
9335
|
+
}
|
|
9336
|
+
if (saveState) saveState(resync.state);
|
|
9337
|
+
appendClickUpWebhookLocalLog(worktree, { type: "signature_resync_verified", oldWebhookId: state?.webhookId || null, webhookId: resync.state.webhookId || null, mode: resync.mode || null });
|
|
9338
|
+
return { ok: true, state: resync.state, resync };
|
|
9339
|
+
} catch (error) {
|
|
9340
|
+
appendClickUpWebhookLocalLog(worktree, { type: "signature_resync_error", webhookId: state?.webhookId || null, message: error.message });
|
|
9341
|
+
return { ok: false, reason: "resync_error", error: error.message, state };
|
|
9342
|
+
}
|
|
9343
|
+
}
|
|
9316
9344
|
function clickUpWebhookEventKey(payload = {}) {
|
|
9317
9345
|
const history = Array.isArray(payload.history_items) ? payload.history_items[0] : payload.history_item;
|
|
9318
9346
|
const comment = payload.comment || history?.comment || {};
|
|
@@ -9571,31 +9599,64 @@ function assertOpenCodePromptAccepted(result) {
|
|
|
9571
9599
|
}
|
|
9572
9600
|
return result;
|
|
9573
9601
|
}
|
|
9574
|
-
|
|
9602
|
+
function responseLooksLikeHtml(contentType = "", raw = "") {
|
|
9603
|
+
return String(contentType || "").toLowerCase().includes("text/html") || /^\s*<!doctype\s+html\b/i.test(raw) || /^\s*<html\b/i.test(raw);
|
|
9604
|
+
}
|
|
9605
|
+
async function readOpenCodeJsonResponse(response, endpointName) {
|
|
9606
|
+
const contentType = response.headers?.get?.("content-type") || "";
|
|
9607
|
+
const raw = await response.text();
|
|
9608
|
+
if (responseLooksLikeHtml(contentType, raw)) {
|
|
9609
|
+
throw new Error(`OpenCode ${endpointName} returned the HTML app shell instead of an API response.`);
|
|
9610
|
+
}
|
|
9611
|
+
if (!raw) return null;
|
|
9612
|
+
try {
|
|
9613
|
+
return JSON.parse(raw);
|
|
9614
|
+
} catch (error) {
|
|
9615
|
+
if (response.ok) throw new Error(`OpenCode ${endpointName} response was not JSON: ${error.message}`);
|
|
9616
|
+
return { raw };
|
|
9617
|
+
}
|
|
9618
|
+
}
|
|
9619
|
+
async function sendOpenCodeSessionEventDirect({ baseUrl, sessionId, text, agent, fetchImpl = globalThis.fetch } = {}) {
|
|
9575
9620
|
if (typeof fetchImpl !== "function") throw new Error("OpenCode direct prompt delivery requires fetch.");
|
|
9576
9621
|
const root = normalizeOpenCodeBaseUrl(baseUrl, "");
|
|
9577
9622
|
if (!root) throw new Error("OpenCode direct prompt delivery requires a base URL.");
|
|
9578
|
-
const
|
|
9579
|
-
const
|
|
9580
|
-
|
|
9581
|
-
|
|
9582
|
-
|
|
9583
|
-
|
|
9584
|
-
|
|
9585
|
-
|
|
9586
|
-
|
|
9587
|
-
|
|
9588
|
-
|
|
9589
|
-
}
|
|
9590
|
-
|
|
9623
|
+
const encodedSession = encodeURIComponent(sessionId);
|
|
9624
|
+
const attempts = [
|
|
9625
|
+
{
|
|
9626
|
+
name: "v2 prompt",
|
|
9627
|
+
url: `${root}/api/session/${encodedSession}/prompt`,
|
|
9628
|
+
body: { prompt: { text }, delivery: "queue", resume: true },
|
|
9629
|
+
accept: (response, data) => {
|
|
9630
|
+
if (!response.ok) return null;
|
|
9631
|
+
if (!data?.data?.id) throw new Error("OpenCode v2 prompt response did not include data.id.");
|
|
9632
|
+
return { ok: true, method: "http", endpoint: "/api/session/{sessionID}/prompt", status: response.status, data: data.data, response: data };
|
|
9633
|
+
}
|
|
9634
|
+
},
|
|
9635
|
+
{
|
|
9636
|
+
name: "legacy async prompt",
|
|
9637
|
+
url: `${root}/session/${encodedSession}/prompt_async`,
|
|
9638
|
+
body: { agent, parts: [{ type: "text", text }] },
|
|
9639
|
+
accept: (response, data) => {
|
|
9640
|
+
if (response.status !== 204 && !response.ok) return null;
|
|
9641
|
+
return { ok: true, method: "http", endpoint: "/session/{sessionID}/prompt_async", status: response.status, data: data?.data || null, response: data };
|
|
9642
|
+
}
|
|
9591
9643
|
}
|
|
9644
|
+
];
|
|
9645
|
+
let firstError = null;
|
|
9646
|
+
for (const attempt of attempts) {
|
|
9647
|
+
const response = await fetchImpl(attempt.url, {
|
|
9648
|
+
method: "POST",
|
|
9649
|
+
headers: { "content-type": "application/json" },
|
|
9650
|
+
body: JSON.stringify(attempt.body)
|
|
9651
|
+
});
|
|
9652
|
+
const data = await readOpenCodeJsonResponse(response, attempt.name);
|
|
9653
|
+
const accepted = attempt.accept(response, data);
|
|
9654
|
+
if (accepted) return accepted;
|
|
9655
|
+
const message = data?.error?.message || data?.message || data?.raw || `OpenCode ${attempt.name} request failed with status ${response.status}.`;
|
|
9656
|
+
firstError ??= new Error(message);
|
|
9657
|
+
if (![404, 405].includes(Number(response.status))) break;
|
|
9592
9658
|
}
|
|
9593
|
-
|
|
9594
|
-
const message = data?.error?.message || data?.message || raw || `OpenCode prompt request failed with status ${response.status}.`;
|
|
9595
|
-
throw new Error(message);
|
|
9596
|
-
}
|
|
9597
|
-
if (!data?.data?.id) throw new Error("OpenCode prompt response did not include data.id.");
|
|
9598
|
-
return { ok: true, method: "http", status: response.status, data: data.data, response: data };
|
|
9659
|
+
throw firstError || new Error("OpenCode direct prompt delivery failed.");
|
|
9599
9660
|
}
|
|
9600
9661
|
async function sendOpenCodeSessionEvent(client, { sessionId, agent, text, directory, opencodeBaseUrl, baseUrl, fetchImpl, direct = false } = {}) {
|
|
9601
9662
|
const directBaseUrl = opencodeBaseUrl || baseUrl;
|
|
@@ -9611,7 +9672,7 @@ async function sendOpenCodeSessionEvent(client, { sessionId, agent, text, direct
|
|
|
9611
9672
|
if (!direct && typeof client?.session?.prompt === "function") {
|
|
9612
9673
|
return assertOpenCodePromptAccepted(await client.session.prompt(structuredPayload));
|
|
9613
9674
|
}
|
|
9614
|
-
if (directBaseUrl) return sendOpenCodeSessionEventDirect({ baseUrl: directBaseUrl, sessionId, text, fetchImpl });
|
|
9675
|
+
if (directBaseUrl) return sendOpenCodeSessionEventDirect({ baseUrl: directBaseUrl, sessionId, text, agent, fetchImpl });
|
|
9615
9676
|
throw new Error("OpenCode client does not expose session.prompt or session.promptAsync.");
|
|
9616
9677
|
}
|
|
9617
9678
|
function normalizeOpenCodeSessionMessages(result) {
|
|
@@ -9679,7 +9740,7 @@ async function applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason,
|
|
|
9679
9740
|
return { ok: false, error: error.message, tagName };
|
|
9680
9741
|
}
|
|
9681
9742
|
}
|
|
9682
|
-
async function deliverClickUpSessionEventWithVerification({ openCodeClient, sendSessionEvent, clickupClient, worktree, taskId, sessionId, agent, text, directory, opencodeBaseUrl, eventMarkers = [], verifySessionEventDelivery = verifyOpenCodeSessionEventDelivery } = {}) {
|
|
9743
|
+
async function deliverClickUpSessionEventWithVerification({ openCodeClient, sendSessionEvent, clickupClient, worktree, taskId, sessionId, agent, text, directory, opencodeBaseUrl, eventMarkers = [], verifySessionEventDelivery = verifyOpenCodeSessionEventDelivery, applyBlockerOnFailure = true } = {}) {
|
|
9683
9744
|
const beforeMessages = await readOpenCodeSessionMessages(openCodeClient, { sessionId, limit: 50 }).catch(() => null);
|
|
9684
9745
|
await sendSessionEvent(openCodeClient, { sessionId, agent, text, directory, opencodeBaseUrl });
|
|
9685
9746
|
let verification = await verifySessionEventDelivery(openCodeClient, { sessionId, beforeMessages, expectedText: text, markers: eventMarkers });
|
|
@@ -9693,9 +9754,50 @@ async function deliverClickUpSessionEventWithVerification({ openCodeClient, send
|
|
|
9693
9754
|
}
|
|
9694
9755
|
const reason = verification?.reason || "message_delivery_failed";
|
|
9695
9756
|
appendClickUpWebhookLocalLog(worktree, { type: "message_delivery_failed", taskId, sessionId, reason, fallbackAttempted: canFallbackDirect });
|
|
9757
|
+
if (!applyBlockerOnFailure) return { ok: false, action: "message_delivery_failed", reason, taskId, sessionId, fallbackAttempted: canFallbackDirect };
|
|
9696
9758
|
const blocker = await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason, source: canFallbackDirect ? "delivery_fallback_failed" : "delivery_verification_failed" });
|
|
9697
9759
|
return { ok: false, action: "message_delivery_failed", reason, taskId, sessionId, fallbackAttempted: canFallbackDirect, blockerTag: blocker };
|
|
9698
9760
|
}
|
|
9761
|
+
async function recoverClickUpPmSession({ openCodeClient, sendSessionEvent, clickupClient, worktree, taskId, staleSessionId, sessionTitle, taskRoute, metadataWithRouting, config, prompt, eventMarkers = [], deliveryEvidencePath, evidencePath, eventKey, createSession, verifySessionEventDelivery } = {}) {
|
|
9762
|
+
appendClickUpWebhookLocalLog(worktree, { type: "pm_session_recovery_started", taskId, staleSessionId, worktree: taskRoute?.worktree });
|
|
9763
|
+
let replacementSessionId = "";
|
|
9764
|
+
try {
|
|
9765
|
+
replacementSessionId = await createSession(openCodeClient, { title: `${sessionTitle} (recovery)`, directory: taskRoute.worktree, agent: config.routing.targetAgent });
|
|
9766
|
+
} catch (error) {
|
|
9767
|
+
appendClickUpWebhookLocalLog(worktree, { type: "pm_session_recovery_create_failed", taskId, staleSessionId, message: error.message });
|
|
9768
|
+
const blockerTag = await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason: "replacement_session_create_failed", source: "pm_session_recovery" });
|
|
9769
|
+
return { ok: false, action: "message_delivery_failed", reason: "replacement_session_create_failed", taskId, sessionId: staleSessionId, replacementAttempted: true, blockerTag, eventKey, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath, evidencePath };
|
|
9770
|
+
}
|
|
9771
|
+
if (!String(replacementSessionId || "").startsWith("ses_")) {
|
|
9772
|
+
appendClickUpWebhookLocalLog(worktree, { type: "pm_session_recovery_create_invalid", taskId, staleSessionId, replacementSessionId });
|
|
9773
|
+
const blockerTag = await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason: "replacement_session_create_failed", source: "pm_session_recovery" });
|
|
9774
|
+
return { ok: false, action: "message_delivery_failed", reason: "replacement_session_create_failed", taskId, sessionId: staleSessionId, replacementAttempted: true, blockerTag, eventKey, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath, evidencePath };
|
|
9775
|
+
}
|
|
9776
|
+
const replacementDelivery = await deliverClickUpSessionEventWithVerification({
|
|
9777
|
+
openCodeClient,
|
|
9778
|
+
sendSessionEvent,
|
|
9779
|
+
clickupClient,
|
|
9780
|
+
worktree,
|
|
9781
|
+
taskId,
|
|
9782
|
+
sessionId: replacementSessionId,
|
|
9783
|
+
agent: config.routing.targetAgent,
|
|
9784
|
+
text: prompt,
|
|
9785
|
+
directory: taskRoute.worktree,
|
|
9786
|
+
opencodeBaseUrl: config.opencode?.baseUrl,
|
|
9787
|
+
eventMarkers,
|
|
9788
|
+
verifySessionEventDelivery,
|
|
9789
|
+
applyBlockerOnFailure: false
|
|
9790
|
+
});
|
|
9791
|
+
if (!replacementDelivery.ok) {
|
|
9792
|
+
appendClickUpWebhookLocalLog(worktree, { type: "pm_session_recovery_delivery_failed", taskId, staleSessionId, replacementSessionId, reason: replacementDelivery.reason });
|
|
9793
|
+
const blockerTag = await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason: replacementDelivery.reason || "replacement_delivery_failed", source: "pm_session_recovery" });
|
|
9794
|
+
return { ...replacementDelivery, sessionId: staleSessionId, replacementSessionId, replacementAttempted: true, blockerTag, eventKey, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath, evidencePath };
|
|
9795
|
+
}
|
|
9796
|
+
const replacementMetadata = clearClickUpPendingSessionMetadata(setClickUpSessionMetadata(metadataWithRouting, config.routing.metadataKey, replacementSessionId), config.routing.metadataKey);
|
|
9797
|
+
await clickupClient.updateTaskMetadata({ taskId, fieldId: config.routing.metadataFieldId, value: replacementMetadata });
|
|
9798
|
+
appendClickUpWebhookLocalLog(worktree, { type: "pm_session_recovery_succeeded", taskId, staleSessionId, replacementSessionId });
|
|
9799
|
+
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 };
|
|
9800
|
+
}
|
|
9699
9801
|
function formatClickUpWebhookPrompt({ eventType, taskId, payload, branch = "", worktree = "", deliveryEvidencePath = "" }) {
|
|
9700
9802
|
const comment = clickUpCommentFromPayload(payload);
|
|
9701
9803
|
const commentText = clickUpCommentText(comment).trim();
|
|
@@ -9947,8 +10049,8 @@ async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, w
|
|
|
9947
10049
|
} catch (error) {
|
|
9948
10050
|
const message = `Optima webhook could not create or reuse a task worktree for ${taskId}: ${error.message}`;
|
|
9949
10051
|
appendClickUpWebhookLocalLog(worktree, { type: "task_worktree_failed", taskId, message });
|
|
9950
|
-
const
|
|
9951
|
-
return { ok: false, action: "error", reason: "task_worktree_failed", taskId, message, blockerTag
|
|
10052
|
+
const blockerTag = await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason: "task_worktree_failed", source: "route_worktree" });
|
|
10053
|
+
return { ok: false, action: "error", reason: "task_worktree_failed", taskId, message, blockerTag };
|
|
9952
10054
|
}
|
|
9953
10055
|
const deliveryEvidencePath = deliveryEvidencePathForClickUpTask(taskId);
|
|
9954
10056
|
const routingMetadata = {
|
|
@@ -9989,14 +10091,17 @@ async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, w
|
|
|
9989
10091
|
const sessionId = String(existingSessionId);
|
|
9990
10092
|
if (await sessionExists(openCodeClient, sessionId)) {
|
|
9991
10093
|
if (typeof clickupClient?.updateTaskMetadata === "function") await clickupClient.updateTaskMetadata({ taskId, fieldId: config.routing.metadataFieldId, value: metadataWithRouting });
|
|
9992
|
-
const delivery = await deliverClickUpSessionEventWithVerification({ openCodeClient, sendSessionEvent, clickupClient, worktree, taskId, sessionId, agent: config.routing.targetAgent, text: prompt, directory: taskRoute.worktree, opencodeBaseUrl: config.opencode?.baseUrl, eventMarkers: [taskId, eventType], verifySessionEventDelivery });
|
|
9993
|
-
if (!delivery.ok)
|
|
10094
|
+
const delivery = await deliverClickUpSessionEventWithVerification({ openCodeClient, sendSessionEvent, clickupClient, worktree, taskId, sessionId, agent: config.routing.targetAgent, text: prompt, directory: taskRoute.worktree, opencodeBaseUrl: config.opencode?.baseUrl, eventMarkers: [taskId, eventType], verifySessionEventDelivery, applyBlockerOnFailure: false });
|
|
10095
|
+
if (!delivery.ok) {
|
|
10096
|
+
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 });
|
|
10097
|
+
return finish(recovery2);
|
|
10098
|
+
}
|
|
9994
10099
|
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 });
|
|
9995
10100
|
}
|
|
9996
10101
|
const at = now().toISOString();
|
|
9997
10102
|
appendClickUpWebhookLocalLog(worktree, { type: "missing_session", taskId, sessionId, host, at });
|
|
9998
|
-
const
|
|
9999
|
-
return finish(
|
|
10103
|
+
const recovery = 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 });
|
|
10104
|
+
return finish(recovery);
|
|
10000
10105
|
}
|
|
10001
10106
|
async function routeClickUpWebhookEvent(options = {}) {
|
|
10002
10107
|
const taskId = clickUpTaskIdFromPayload(options.payload || {});
|
|
@@ -10104,15 +10209,16 @@ function clickUpWebhookExpectedPath(config) {
|
|
|
10104
10209
|
return "/";
|
|
10105
10210
|
}
|
|
10106
10211
|
}
|
|
10107
|
-
async function handleClickUpWebhookRequest({ method = "POST", url = null, headers = {}, rawBody = "", config, state, worktree, clickupClient, openCodeClient, saveState, now = () => /* @__PURE__ */ new Date() } = {}) {
|
|
10212
|
+
async function handleClickUpWebhookRequest({ method = "POST", url = null, headers = {}, rawBody = "", config, state, worktree, clickupClient, openCodeClient, saveState, now = () => /* @__PURE__ */ new Date(), sessionExists, createSession, sendSessionEvent, ensureTaskWorktree, verifySessionEventDelivery } = {}) {
|
|
10108
10213
|
let payload = null;
|
|
10109
10214
|
let handled = null;
|
|
10110
10215
|
let authenticatedWebhook = false;
|
|
10111
10216
|
let receivedAt = null;
|
|
10217
|
+
let activeState = state;
|
|
10112
10218
|
const finish = (result) => {
|
|
10113
10219
|
handled = result;
|
|
10114
10220
|
receivedAt ??= now();
|
|
10115
|
-
const auditState = authenticatedWebhook ? { ...
|
|
10221
|
+
const auditState = authenticatedWebhook ? { ...activeState, lastWebhookAt: receivedAt.toISOString() } : activeState;
|
|
10116
10222
|
if (saveState && authenticatedWebhook) saveState(auditState);
|
|
10117
10223
|
writeClickUpWebhookAuditLog({ method, url, headers, rawBody, config, state: auditState, handled, payload, at: receivedAt });
|
|
10118
10224
|
return result;
|
|
@@ -10127,7 +10233,11 @@ async function handleClickUpWebhookRequest({ method = "POST", url = null, header
|
|
|
10127
10233
|
const bodyBytes = Buffer.isBuffer(rawBody) ? rawBody.length : Buffer.byteLength(String(rawBody || ""));
|
|
10128
10234
|
if (bodyBytes > maxBodyBytes) return finish({ ok: false, status: 413, reason: "body_too_large" });
|
|
10129
10235
|
const signature = headers["x-signature"] || headers["X-Signature"];
|
|
10130
|
-
if (!verifyClickUpSignature(rawBody, signature,
|
|
10236
|
+
if (!verifyClickUpSignature(rawBody, signature, activeState?.secret)) {
|
|
10237
|
+
const resync = await resyncClickUpWebhookForSignatureDrift({ rawBody, signature, config, state: activeState, worktree, clickupClient, saveState });
|
|
10238
|
+
if (!resync.ok) return finish({ ok: false, status: 401, reason: "invalid_signature", resync: { attempted: true, reason: resync.reason } });
|
|
10239
|
+
activeState = resync.state;
|
|
10240
|
+
}
|
|
10131
10241
|
authenticatedWebhook = true;
|
|
10132
10242
|
receivedAt = now();
|
|
10133
10243
|
try {
|
|
@@ -10135,8 +10245,8 @@ async function handleClickUpWebhookRequest({ method = "POST", url = null, header
|
|
|
10135
10245
|
} catch {
|
|
10136
10246
|
return finish({ ok: false, status: 400, reason: "invalid_json" });
|
|
10137
10247
|
}
|
|
10138
|
-
const receivedState = { ...
|
|
10139
|
-
const result = await routeClickUpWebhookEvent({ payload, config, state: receivedState, worktree, clickupClient, openCodeClient, saveState });
|
|
10248
|
+
const receivedState = { ...activeState, lastWebhookAt: receivedAt.toISOString() };
|
|
10249
|
+
const result = await routeClickUpWebhookEvent({ payload, config, state: receivedState, worktree, clickupClient, openCodeClient, saveState, sessionExists, createSession, sendSessionEvent, ensureTaskWorktree, verifySessionEventDelivery });
|
|
10140
10250
|
return finish({ ok: result.ok, status: result.ok ? 200 : 422, result });
|
|
10141
10251
|
} catch (error) {
|
|
10142
10252
|
writeClickUpWebhookAuditLog({ method, url, headers, rawBody, config, state, handled, error, payload, at: now() });
|
|
@@ -11876,7 +11986,7 @@ Follow-up: use optima_prompt_workflow with session_id '${sessionId}' to check in
|
|
|
11876
11986
|
}
|
|
11877
11987
|
};
|
|
11878
11988
|
}
|
|
11879
|
-
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, deliveryEvidencePathForClickUpTask, ensureClickUpTaskWorktree, ensureClickUpWebhookSubscription, handleClickUpWebhookRequest, isClickUpWebhookStateActive, normalizeClickUpWebhookConfig, normalizeClickUpWebhookLogLevel, normalizeOpenCodeBaseUrl, readClickUpCommentLedger, readClickUpWebhookState, reconcileClickUpStartup, recordClickUpCommentVersionProcessed, 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 };
|
|
11989
|
+
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, deliveryEvidencePathForClickUpTask, ensureClickUpTaskWorktree, ensureClickUpWebhookSubscription, handleClickUpWebhookRequest, isClickUpWebhookStateActive, normalizeClickUpWebhookConfig, normalizeClickUpWebhookLogLevel, normalizeOpenCodeBaseUrl, readClickUpCommentLedger, readClickUpWebhookState, 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 };
|
|
11880
11990
|
|
|
11881
11991
|
// src/sanitize_cli.js
|
|
11882
11992
|
var { migrateLegacyOptimaLayout: migrateLegacyOptimaLayout2 } = OptimaPlugin.__internals;
|