@defend-tech/opencode-optima 0.1.34 → 0.1.35

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
@@ -9194,7 +9194,7 @@ async function findReusableClickUpWebhook(config, clickupClient = null) {
9194
9194
  }
9195
9195
  return null;
9196
9196
  }
9197
- async function validateClickUpWebhookState(state, config, clickupClient = null) {
9197
+ async function validateClickUpWebhookState(state, config, clickupClient = null, { allowRemoteUnhealthyLocalRecovery = false } = {}) {
9198
9198
  if (!isClickUpWebhookStateActive(state, config)) return { valid: false, reason: "state_incomplete" };
9199
9199
  if (clickupClient?.listWebhooks) {
9200
9200
  const listed = await clickupClient.listWebhooks({ teamId: config.teamId });
@@ -9202,10 +9202,22 @@ async function validateClickUpWebhookState(state, config, clickupClient = null)
9202
9202
  if (!match) return { valid: false, reason: "remote_state_missing" };
9203
9203
  const remote = normalizeClickUpWebhookApiResponse(match, config);
9204
9204
  const remoteWithLocalSecret = { ...remote, secret: state.secret };
9205
- if (!isClickUpWebhookStateActive(remoteWithLocalSecret, config) || remote.webhookId !== state.webhookId) {
9206
- return { valid: false, reason: "remote_state_mismatch", remote };
9205
+ if (isClickUpWebhookStateActive(remoteWithLocalSecret, config) && remote.webhookId === state.webhookId) {
9206
+ return { valid: true, mode: "remote", state: { ...remoteWithLocalSecret, recentEventKeys: state.recentEventKeys || [], lastValidatedAt: (/* @__PURE__ */ new Date()).toISOString() } };
9207
9207
  }
9208
- return { valid: true, mode: "remote", state: { ...remoteWithLocalSecret, recentEventKeys: state.recentEventKeys || [], lastValidatedAt: (/* @__PURE__ */ new Date()).toISOString() } };
9208
+ const localStateValidation = await validateClickUpWebhookState(state, config, null);
9209
+ if (allowRemoteUnhealthyLocalRecovery && localStateValidation.valid && remote.webhookId === state.webhookId) {
9210
+ const remoteConfigMatches = remote.publicUrl === config.webhook.publicUrl && [...new Set(config.webhook.events || [])].every((event) => new Set(remote.events || []).has(event));
9211
+ if (remoteConfigMatches) {
9212
+ return {
9213
+ ...localStateValidation,
9214
+ mode: "local_state_remote_unhealthy",
9215
+ limitation: "ClickUp remote webhook is present but unhealthy; Optima starts the local listener from matching local id/secret/config so delivery can recover.",
9216
+ remote
9217
+ };
9218
+ }
9219
+ }
9220
+ return { valid: false, reason: "remote_state_mismatch", remote };
9209
9221
  }
9210
9222
  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." };
9211
9223
  }
@@ -9215,7 +9227,7 @@ async function ensureClickUpWebhookSubscription({ validation, worktree, clickupC
9215
9227
  }
9216
9228
  const { config } = validation;
9217
9229
  const existing = readClickUpWebhookState(worktree, config);
9218
- const existingValidation = await validateClickUpWebhookState(existing, config, clickupClient);
9230
+ const existingValidation = await validateClickUpWebhookState(existing, config, clickupClient, { allowRemoteUnhealthyLocalRecovery: true });
9219
9231
  if (existingValidation.valid) {
9220
9232
  const state = writeClickUpWebhookState(worktree, { ...existingValidation.state, recentEventKeys: existing.recentEventKeys || [] }, config);
9221
9233
  clickUpWebhookLifecycleLog(worktree, { type: "remote_webhook_reused", webhookId: state.webhookId, mode: existingValidation.mode });
@@ -9758,12 +9770,18 @@ async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, w
9758
9770
  if (!taskId) return { ok: false, action: "error", reason: "missing_task_id" };
9759
9771
  let task = payload.task || (clickupClient?.getTask ? await clickupClient.getTask(taskId) : null);
9760
9772
  if (!task) return { ok: false, action: "error", reason: "task_unavailable", taskId };
9773
+ if (clickupClient?.getTask) {
9774
+ const latestTask = await clickupClient.getTask(taskId);
9775
+ if (latestTask) task = latestTask;
9776
+ }
9761
9777
  const isCommentEvent = eventType === "taskCommentPosted" || eventType === "taskCommentUpdated";
9762
9778
  if (isCommentEvent) {
9763
9779
  const comment = clickUpCommentFromPayload(payload);
9764
9780
  const authorId = clickUpCommentAuthorId(comment);
9765
9781
  if (authorId && authorId === config.routing.ignoredCommentAuthorId) return finish({ ok: true, action: "ignored", reason: "self_authored_comment", taskId });
9766
- if (!clickUpCommentMentionsProductManager(comment, config.routing)) return finish({ ok: true, action: "ignored", reason: "missing_product_manager_mention", taskId });
9782
+ const mentionsProductManager = clickUpCommentMentionsProductManager(comment, config.routing);
9783
+ const assignedToProductManager = isClickUpTaskAssignedToProductManager(task, config.routing.productManagerAssigneeId);
9784
+ if (!mentionsProductManager && !assignedToProductManager) return finish({ ok: true, action: "ignored", reason: "missing_product_manager_mention_or_assignment", taskId });
9767
9785
  commentLedgerKey = clickUpCommentLedgerKey({ taskId, eventType, payload });
9768
9786
  try {
9769
9787
  if (isClickUpCommentVersionProcessed({ ledgerPath: commentLedgerPath, key: commentLedgerKey, worktree })) {
@@ -9775,10 +9793,6 @@ async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, w
9775
9793
  }
9776
9794
  if (!isCommentEvent && !isClickUpTaskAssignedToProductManager(task, config.routing.productManagerAssigneeId)) return finish({ ok: true, action: "ignored", reason: "not_assigned_to_product_manager", taskId });
9777
9795
  if (isClickUpTaskTerminal(task, payload, config.routing.ignoredStatuses)) return finish({ ok: true, action: "ignored", reason: "terminal_status", taskId });
9778
- if (clickupClient?.getTask) {
9779
- const latestTask = await clickupClient.getTask(taskId);
9780
- if (latestTask) task = latestTask;
9781
- }
9782
9796
  const sessionTitle = formatClickUpSessionTitle({ taskId, payloadTask: payload.task, task });
9783
9797
  const existingMetadata = clickUpTaskAgentMetadata(task, config.routing.metadataFieldId);
9784
9798
  const metadata = normalizeAgentMetadataJson(existingMetadata);
@@ -9201,7 +9201,7 @@ async function findReusableClickUpWebhook(config, clickupClient = null) {
9201
9201
  }
9202
9202
  return null;
9203
9203
  }
9204
- async function validateClickUpWebhookState(state, config, clickupClient = null) {
9204
+ async function validateClickUpWebhookState(state, config, clickupClient = null, { allowRemoteUnhealthyLocalRecovery = false } = {}) {
9205
9205
  if (!isClickUpWebhookStateActive(state, config)) return { valid: false, reason: "state_incomplete" };
9206
9206
  if (clickupClient?.listWebhooks) {
9207
9207
  const listed = await clickupClient.listWebhooks({ teamId: config.teamId });
@@ -9209,10 +9209,22 @@ async function validateClickUpWebhookState(state, config, clickupClient = null)
9209
9209
  if (!match) return { valid: false, reason: "remote_state_missing" };
9210
9210
  const remote = normalizeClickUpWebhookApiResponse(match, config);
9211
9211
  const remoteWithLocalSecret = { ...remote, secret: state.secret };
9212
- if (!isClickUpWebhookStateActive(remoteWithLocalSecret, config) || remote.webhookId !== state.webhookId) {
9213
- return { valid: false, reason: "remote_state_mismatch", remote };
9212
+ if (isClickUpWebhookStateActive(remoteWithLocalSecret, config) && remote.webhookId === state.webhookId) {
9213
+ return { valid: true, mode: "remote", state: { ...remoteWithLocalSecret, recentEventKeys: state.recentEventKeys || [], lastValidatedAt: (/* @__PURE__ */ new Date()).toISOString() } };
9214
9214
  }
9215
- return { valid: true, mode: "remote", state: { ...remoteWithLocalSecret, recentEventKeys: state.recentEventKeys || [], lastValidatedAt: (/* @__PURE__ */ new Date()).toISOString() } };
9215
+ const localStateValidation = await validateClickUpWebhookState(state, config, null);
9216
+ if (allowRemoteUnhealthyLocalRecovery && localStateValidation.valid && remote.webhookId === state.webhookId) {
9217
+ const remoteConfigMatches = remote.publicUrl === config.webhook.publicUrl && [...new Set(config.webhook.events || [])].every((event) => new Set(remote.events || []).has(event));
9218
+ if (remoteConfigMatches) {
9219
+ return {
9220
+ ...localStateValidation,
9221
+ mode: "local_state_remote_unhealthy",
9222
+ limitation: "ClickUp remote webhook is present but unhealthy; Optima starts the local listener from matching local id/secret/config so delivery can recover.",
9223
+ remote
9224
+ };
9225
+ }
9226
+ }
9227
+ return { valid: false, reason: "remote_state_mismatch", remote };
9216
9228
  }
9217
9229
  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." };
9218
9230
  }
@@ -9222,7 +9234,7 @@ async function ensureClickUpWebhookSubscription({ validation, worktree, clickupC
9222
9234
  }
9223
9235
  const { config } = validation;
9224
9236
  const existing = readClickUpWebhookState(worktree, config);
9225
- const existingValidation = await validateClickUpWebhookState(existing, config, clickupClient);
9237
+ const existingValidation = await validateClickUpWebhookState(existing, config, clickupClient, { allowRemoteUnhealthyLocalRecovery: true });
9226
9238
  if (existingValidation.valid) {
9227
9239
  const state = writeClickUpWebhookState(worktree, { ...existingValidation.state, recentEventKeys: existing.recentEventKeys || [] }, config);
9228
9240
  clickUpWebhookLifecycleLog(worktree, { type: "remote_webhook_reused", webhookId: state.webhookId, mode: existingValidation.mode });
@@ -9765,12 +9777,18 @@ async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, w
9765
9777
  if (!taskId) return { ok: false, action: "error", reason: "missing_task_id" };
9766
9778
  let task = payload.task || (clickupClient?.getTask ? await clickupClient.getTask(taskId) : null);
9767
9779
  if (!task) return { ok: false, action: "error", reason: "task_unavailable", taskId };
9780
+ if (clickupClient?.getTask) {
9781
+ const latestTask = await clickupClient.getTask(taskId);
9782
+ if (latestTask) task = latestTask;
9783
+ }
9768
9784
  const isCommentEvent = eventType === "taskCommentPosted" || eventType === "taskCommentUpdated";
9769
9785
  if (isCommentEvent) {
9770
9786
  const comment = clickUpCommentFromPayload(payload);
9771
9787
  const authorId = clickUpCommentAuthorId(comment);
9772
9788
  if (authorId && authorId === config.routing.ignoredCommentAuthorId) return finish({ ok: true, action: "ignored", reason: "self_authored_comment", taskId });
9773
- if (!clickUpCommentMentionsProductManager(comment, config.routing)) return finish({ ok: true, action: "ignored", reason: "missing_product_manager_mention", taskId });
9789
+ const mentionsProductManager = clickUpCommentMentionsProductManager(comment, config.routing);
9790
+ const assignedToProductManager = isClickUpTaskAssignedToProductManager(task, config.routing.productManagerAssigneeId);
9791
+ if (!mentionsProductManager && !assignedToProductManager) return finish({ ok: true, action: "ignored", reason: "missing_product_manager_mention_or_assignment", taskId });
9774
9792
  commentLedgerKey = clickUpCommentLedgerKey({ taskId, eventType, payload });
9775
9793
  try {
9776
9794
  if (isClickUpCommentVersionProcessed({ ledgerPath: commentLedgerPath, key: commentLedgerKey, worktree })) {
@@ -9782,10 +9800,6 @@ async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, w
9782
9800
  }
9783
9801
  if (!isCommentEvent && !isClickUpTaskAssignedToProductManager(task, config.routing.productManagerAssigneeId)) return finish({ ok: true, action: "ignored", reason: "not_assigned_to_product_manager", taskId });
9784
9802
  if (isClickUpTaskTerminal(task, payload, config.routing.ignoredStatuses)) return finish({ ok: true, action: "ignored", reason: "terminal_status", taskId });
9785
- if (clickupClient?.getTask) {
9786
- const latestTask = await clickupClient.getTask(taskId);
9787
- if (latestTask) task = latestTask;
9788
- }
9789
9803
  const sessionTitle = formatClickUpSessionTitle({ taskId, payloadTask: payload.task, task });
9790
9804
  const existingMetadata = clickUpTaskAgentMetadata(task, config.routing.metadataFieldId);
9791
9805
  const metadata = normalizeAgentMetadataJson(existingMetadata);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defend-tech/opencode-optima",
3
- "version": "0.1.34",
3
+ "version": "0.1.35",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+ssh://git@github.com/defend-tech/opencode-optima.git"