@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 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
- async function ensureClickUpWebhookSubscription({ validation, worktree, clickupClient = null } = {}) {
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
- async function sendOpenCodeSessionEventDirect({ baseUrl, sessionId, text, fetchImpl = globalThis.fetch } = {}) {
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 url = `${root}/api/session/${encodeURIComponent(sessionId)}/prompt`;
9572
- const response = await fetchImpl(url, {
9573
- method: "POST",
9574
- headers: { "content-type": "application/json" },
9575
- body: JSON.stringify({ prompt: { text }, delivery: "queue", resume: true })
9576
- });
9577
- const raw = await response.text();
9578
- let data = null;
9579
- if (raw) {
9580
- try {
9581
- data = JSON.parse(raw);
9582
- } catch (error) {
9583
- if (response.ok) throw new Error(`OpenCode prompt response was not JSON: ${error.message}`);
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
- if (!response.ok) {
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 blockerTag2 = await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason: "task_worktree_failed", source: "route_worktree" });
9944
- return { ok: false, action: "error", reason: "task_worktree_failed", taskId, message, blockerTag: blockerTag2 };
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) return finish({ ...delivery, eventKey, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath, evidencePath: routingMetadata.evidence_path });
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 blockerTag = await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason: "missing_session", source: "route_existing_session" });
9992
- return finish({ ok: true, action: "missing_session_reported", taskId, sessionId, eventKey, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath, evidencePath: routingMetadata.evidence_path, blockerTag });
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 ? { ...state, lastWebhookAt: receivedAt.toISOString() } : state;
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, state?.secret)) return finish({ ok: false, status: 401, reason: "invalid_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 = { ...state, lastWebhookAt: receivedAt.toISOString() };
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
  };
@@ -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
- async function ensureClickUpWebhookSubscription({ validation, worktree, clickupClient = null } = {}) {
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
- async function sendOpenCodeSessionEventDirect({ baseUrl, sessionId, text, fetchImpl = globalThis.fetch } = {}) {
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 url = `${root}/api/session/${encodeURIComponent(sessionId)}/prompt`;
9579
- const response = await fetchImpl(url, {
9580
- method: "POST",
9581
- headers: { "content-type": "application/json" },
9582
- body: JSON.stringify({ prompt: { text }, delivery: "queue", resume: true })
9583
- });
9584
- const raw = await response.text();
9585
- let data = null;
9586
- if (raw) {
9587
- try {
9588
- data = JSON.parse(raw);
9589
- } catch (error) {
9590
- if (response.ok) throw new Error(`OpenCode prompt response was not JSON: ${error.message}`);
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
- if (!response.ok) {
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 blockerTag2 = await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason: "task_worktree_failed", source: "route_worktree" });
9951
- return { ok: false, action: "error", reason: "task_worktree_failed", taskId, message, blockerTag: blockerTag2 };
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) return finish({ ...delivery, eventKey, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath, evidencePath: routingMetadata.evidence_path });
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 blockerTag = await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason: "missing_session", source: "route_existing_session" });
9999
- return finish({ ok: true, action: "missing_session_reported", taskId, sessionId, eventKey, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath, evidencePath: routingMetadata.evidence_path, blockerTag });
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 ? { ...state, lastWebhookAt: receivedAt.toISOString() } : state;
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, state?.secret)) return finish({ ok: false, status: 401, reason: "invalid_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 = { ...state, lastWebhookAt: receivedAt.toISOString() };
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defend-tech/opencode-optima",
3
- "version": "0.1.40",
3
+ "version": "0.1.43",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+ssh://git@github.com/defend-tech/opencode-optima.git"