@defend-tech/opencode-optima 0.1.38 → 0.1.39

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
@@ -7932,6 +7932,7 @@ var CLICKUP_WEBHOOK_EVENTS = ["taskStatusUpdated", "taskAssigneeUpdated", "taskC
7932
7932
  var CLICKUP_WEBHOOK_TERMINAL_STATUSES = ["backlog", "done", "completed", "closed"];
7933
7933
  var CLICKUP_PM_METADATA_KEY = "optima.sessions.product_manager";
7934
7934
  var CLICKUP_PM_MENTION_NAME = "Defend Tech Product Manager";
7935
+ var CLICKUP_BLOCKER_TAG_NAME = "BLOCKER";
7935
7936
  var CLICKUP_WEBHOOK_RUNTIME_VERSION = 1;
7936
7937
  var CLICKUP_WEBHOOK_MAX_BODY_BYTES = 1024 * 1024;
7937
7938
  var CLICKUP_WEBHOOK_REQUEST_TIMEOUT_MS = 1e4;
@@ -9156,6 +9157,12 @@ function createClickUpApiClient(config, fetchImpl = globalThis.fetch) {
9156
9157
  body: JSON.stringify({ comment_text: comment })
9157
9158
  });
9158
9159
  },
9160
+ async addTaskTag({ taskId, tagName = CLICKUP_BLOCKER_TAG_NAME } = {}) {
9161
+ return request(`https://api.clickup.com/api/v2/task/${encodeURIComponent(taskId)}/tag/${encodeURIComponent(tagName)}`, {
9162
+ method: "POST",
9163
+ body: JSON.stringify({})
9164
+ });
9165
+ },
9159
9166
  async listAssignedTasks({ assigneeId, statuses = [], limit = CLICKUP_WEBHOOK_STARTUP_TASK_LIMIT } = {}) {
9160
9167
  const params = new URLSearchParams();
9161
9168
  if (assigneeId) params.append("assignees[]", assigneeId);
@@ -9651,26 +9658,18 @@ async function verifyOpenCodeSessionEventDelivery(client, { sessionId, beforeMes
9651
9658
  }
9652
9659
  return { ok: false, reason: lastError };
9653
9660
  }
9654
- function buildClickUpMessageDeliveryBlockerComment({ taskId, sessionId, reason, action } = {}) {
9655
- return [
9656
- `Optima blocker: could not deliver ClickUp task ${taskId} to OpenCode session ${sessionId}.`,
9657
- `Delivery verification failed after send${action ? ` (${action})` : ""}: ${reason || "message_delivery_failed"}.`,
9658
- "The webhook did not mark this event as successfully routed; please inspect/replay the event or route the task manually.",
9659
- clickUpNoAbandonmentRuleText()
9660
- ].join("\n");
9661
- }
9662
- async function postClickUpMessageDeliveryBlocker({ clickupClient, worktree, taskId, sessionId, reason, action } = {}) {
9663
- const comment = buildClickUpMessageDeliveryBlockerComment({ taskId, sessionId, reason, action });
9664
- if (typeof clickupClient?.postTaskComment !== "function") {
9665
- appendClickUpWebhookLocalLog(worktree, { type: "message_delivery_blocker_comment_unavailable", taskId, sessionId, reason, action });
9666
- return { ok: false, skipped: true, reason: "post_task_comment_unavailable", comment };
9661
+ async function applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason, source, tagName = CLICKUP_BLOCKER_TAG_NAME } = {}) {
9662
+ if (typeof clickupClient?.addTaskTag !== "function") {
9663
+ appendClickUpWebhookLocalLog(worktree, { type: "blocker_tag_unavailable", taskId, reason, source, tagName });
9664
+ return { ok: false, skipped: true, reason: "add_task_tag_unavailable", tagName };
9667
9665
  }
9668
9666
  try {
9669
- await clickupClient.postTaskComment({ taskId, comment });
9670
- return { ok: true, comment };
9667
+ await clickupClient.addTaskTag({ taskId, tagName });
9668
+ appendClickUpWebhookLocalLog(worktree, { type: "blocker_tag_applied", taskId, reason, source, tagName });
9669
+ return { ok: true, tagName };
9671
9670
  } catch (error) {
9672
- appendClickUpWebhookLocalLog(worktree, { type: "message_delivery_blocker_comment_failed", taskId, sessionId, reason, action, message: error.message });
9673
- return { ok: false, error: error.message, comment };
9671
+ appendClickUpWebhookLocalLog(worktree, { type: "blocker_tag_failed", taskId, reason, source, tagName, message: error.message });
9672
+ return { ok: false, error: error.message, tagName };
9674
9673
  }
9675
9674
  }
9676
9675
  async function deliverClickUpSessionEventWithVerification({ openCodeClient, sendSessionEvent, clickupClient, worktree, taskId, sessionId, agent, text, directory, opencodeBaseUrl, eventMarkers = [], verifySessionEventDelivery = verifyOpenCodeSessionEventDelivery } = {}) {
@@ -9687,8 +9686,8 @@ async function deliverClickUpSessionEventWithVerification({ openCodeClient, send
9687
9686
  }
9688
9687
  const reason = verification?.reason || "message_delivery_failed";
9689
9688
  appendClickUpWebhookLocalLog(worktree, { type: "message_delivery_failed", taskId, sessionId, reason, fallbackAttempted: canFallbackDirect });
9690
- const blocker = await postClickUpMessageDeliveryBlocker({ clickupClient, worktree, taskId, sessionId, reason, action: canFallbackDirect ? "fallback_failed" : "verification_failed" });
9691
- return { ok: false, action: "message_delivery_failed", reason, taskId, sessionId, fallbackAttempted: canFallbackDirect, blockerComment: blocker };
9689
+ const blocker = await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason, source: canFallbackDirect ? "delivery_fallback_failed" : "delivery_verification_failed" });
9690
+ return { ok: false, action: "message_delivery_failed", reason, taskId, sessionId, fallbackAttempted: canFallbackDirect, blockerTag: blocker };
9692
9691
  }
9693
9692
  function formatClickUpWebhookPrompt({ eventType, taskId, payload, branch = "", worktree = "", deliveryEvidencePath = "" }) {
9694
9693
  const comment = clickUpCommentFromPayload(payload);
@@ -9706,45 +9705,6 @@ function formatClickUpWebhookPrompt({ eventType, taskId, payload, branch = "", w
9706
9705
  deliveryEvidencePath ? `Final merge-trackable evidence must be written under ${deliveryEvidencePath}; use .optima only as local mirror/staging.` : null
9707
9706
  ].filter((part) => part !== null).join("\n");
9708
9707
  }
9709
- function clickUpNoAbandonmentRuleText() {
9710
- return "No-abandonment rule: PM must keep working, or before stopping must post a ClickUp comment, hand off, remove PM as assignee, and assign the next owner.";
9711
- }
9712
- function buildClickUpStartupResumeComment({ taskId, result = {} } = {}) {
9713
- const contextParts = [
9714
- result.branch ? `- Branch: ${result.branch}` : null,
9715
- result.worktree ? `- Worktree: ${result.worktree}` : null,
9716
- result.deliveryEvidencePath ? `- Delivery evidence path: ${result.deliveryEvidencePath}` : null,
9717
- result.evidencePath ? `- Local evidence path: ${result.evidencePath}` : null
9718
- ].filter(Boolean);
9719
- return [
9720
- `Startup reconciliation resumed ClickUp task ${taskId}.`,
9721
- `Route result: ${result.action || "unknown"}${result.ok === false ? " (failed)" : ""}.`,
9722
- result.sessionId ? `Session id: ${result.sessionId}.` : "Session id: unavailable.",
9723
- contextParts.length ? contextParts.join("\n") : "Worktree/branch/delivery evidence path: unavailable.",
9724
- clickUpNoAbandonmentRuleText()
9725
- ].join("\n");
9726
- }
9727
- function buildClickUpStartupBlockerComment({ taskId, error } = {}) {
9728
- const summary = error?.message || String(error || "unknown error");
9729
- return [
9730
- `Startup reconciliation blocker for ClickUp task ${taskId}: ${summary}`,
9731
- "Optima skipped this task for this startup pass and continued with the next PM-assigned task.",
9732
- clickUpNoAbandonmentRuleText()
9733
- ].join("\n");
9734
- }
9735
- async function postClickUpStartupComment({ clickupClient, worktree, taskId, comment, type } = {}) {
9736
- if (typeof clickupClient?.postTaskComment !== "function") {
9737
- appendClickUpWebhookLocalLog(worktree, { type: "startup_reconciliation_comment_unavailable", taskId, commentType: type });
9738
- return { ok: false, skipped: true, reason: "post_task_comment_unavailable" };
9739
- }
9740
- try {
9741
- await clickupClient.postTaskComment({ taskId, comment });
9742
- return { ok: true };
9743
- } catch (error) {
9744
- appendClickUpWebhookLocalLog(worktree, { type: "startup_reconciliation_comment_failed", taskId, commentType: type, message: error.message });
9745
- return { ok: false, error: error.message };
9746
- }
9747
- }
9748
9708
  function appendClickUpWebhookLocalLog(worktree, entry) {
9749
9709
  const logPath = clickUpWebhookLogPath(worktree);
9750
9710
  fs2.mkdirSync(path2.dirname(logPath), { recursive: true });
@@ -9980,8 +9940,8 @@ async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, w
9980
9940
  } catch (error) {
9981
9941
  const message = `Optima webhook could not create or reuse a task worktree for ${taskId}: ${error.message}`;
9982
9942
  appendClickUpWebhookLocalLog(worktree, { type: "task_worktree_failed", taskId, message });
9983
- if (typeof clickupClient?.postTaskComment === "function") await clickupClient.postTaskComment({ taskId, comment: message });
9984
- return { ok: false, action: "error", reason: "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 };
9985
9945
  }
9986
9946
  const deliveryEvidencePath = deliveryEvidencePathForClickUpTask(taskId);
9987
9947
  const routingMetadata = {
@@ -10027,10 +9987,9 @@ async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, w
10027
9987
  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 });
10028
9988
  }
10029
9989
  const at = now().toISOString();
10030
- const incidentComment = `Optima webhook could not route this ClickUp event because OpenCode session ${sessionId} is missing on host ${host} at ${at}. No replacement session was created automatically.`;
10031
9990
  appendClickUpWebhookLocalLog(worktree, { type: "missing_session", taskId, sessionId, host, at });
10032
- await clickupClient.postTaskComment({ taskId, comment: incidentComment });
10033
- return finish({ ok: true, action: "missing_session_reported", taskId, sessionId, eventKey, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath, evidencePath: routingMetadata.evidence_path });
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 });
10034
9993
  }
10035
9994
  async function routeClickUpWebhookEvent(options = {}) {
10036
9995
  const taskId = clickUpTaskIdFromPayload(options.payload || {});
@@ -10088,24 +10047,14 @@ async function reconcileClickUpStartup({ config, state = {}, worktree = process.
10088
10047
  });
10089
10048
  if (result?.ok && result.action !== "ignored") {
10090
10049
  routed.assigned += 1;
10091
- await postClickUpStartupComment({
10092
- clickupClient,
10093
- worktree,
10094
- taskId,
10095
- type: "resume",
10096
- comment: buildClickUpStartupResumeComment({ taskId, result })
10097
- });
10050
+ } else if (result?.ok === false) {
10051
+ routed.errors += 1;
10052
+ if (!result.blockerTag) await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason: result.reason || "startup_reconciliation_route_failed", source: "startup_reconciliation_task" });
10098
10053
  }
10099
10054
  } catch (error) {
10100
10055
  routed.errors += 1;
10101
10056
  appendClickUpWebhookLocalLog(worktree, { type: "startup_reconciliation_task_failed", taskId, message: error.message });
10102
- await postClickUpStartupComment({
10103
- clickupClient,
10104
- worktree,
10105
- taskId,
10106
- type: "blocker",
10107
- comment: buildClickUpStartupBlockerComment({ taskId, error })
10108
- });
10057
+ await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason: "startup_reconciliation_task_failed", source: "startup_reconciliation_task" });
10109
10058
  }
10110
10059
  if (!lastWebhookMs || !clickupClient?.getTaskComments) continue;
10111
10060
  try {
@@ -10135,6 +10084,7 @@ async function reconcileClickUpStartup({ config, state = {}, worktree = process.
10135
10084
  } catch (error) {
10136
10085
  routed.errors += 1;
10137
10086
  appendClickUpWebhookLocalLog(worktree, { type: "startup_reconciliation_comments_failed", taskId, message: error.message });
10087
+ await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason: "startup_reconciliation_comments_failed", source: "startup_reconciliation_comments" });
10138
10088
  }
10139
10089
  }
10140
10090
  clickUpWebhookLifecycleLog(worktree, { type: "startup_reconciliation_finished", ...routed });
@@ -7939,6 +7939,7 @@ var CLICKUP_WEBHOOK_EVENTS = ["taskStatusUpdated", "taskAssigneeUpdated", "taskC
7939
7939
  var CLICKUP_WEBHOOK_TERMINAL_STATUSES = ["backlog", "done", "completed", "closed"];
7940
7940
  var CLICKUP_PM_METADATA_KEY = "optima.sessions.product_manager";
7941
7941
  var CLICKUP_PM_MENTION_NAME = "Defend Tech Product Manager";
7942
+ var CLICKUP_BLOCKER_TAG_NAME = "BLOCKER";
7942
7943
  var CLICKUP_WEBHOOK_RUNTIME_VERSION = 1;
7943
7944
  var CLICKUP_WEBHOOK_MAX_BODY_BYTES = 1024 * 1024;
7944
7945
  var CLICKUP_WEBHOOK_REQUEST_TIMEOUT_MS = 1e4;
@@ -9163,6 +9164,12 @@ function createClickUpApiClient(config, fetchImpl = globalThis.fetch) {
9163
9164
  body: JSON.stringify({ comment_text: comment })
9164
9165
  });
9165
9166
  },
9167
+ async addTaskTag({ taskId, tagName = CLICKUP_BLOCKER_TAG_NAME } = {}) {
9168
+ return request(`https://api.clickup.com/api/v2/task/${encodeURIComponent(taskId)}/tag/${encodeURIComponent(tagName)}`, {
9169
+ method: "POST",
9170
+ body: JSON.stringify({})
9171
+ });
9172
+ },
9166
9173
  async listAssignedTasks({ assigneeId, statuses = [], limit = CLICKUP_WEBHOOK_STARTUP_TASK_LIMIT } = {}) {
9167
9174
  const params = new URLSearchParams();
9168
9175
  if (assigneeId) params.append("assignees[]", assigneeId);
@@ -9658,26 +9665,18 @@ async function verifyOpenCodeSessionEventDelivery(client, { sessionId, beforeMes
9658
9665
  }
9659
9666
  return { ok: false, reason: lastError };
9660
9667
  }
9661
- function buildClickUpMessageDeliveryBlockerComment({ taskId, sessionId, reason, action } = {}) {
9662
- return [
9663
- `Optima blocker: could not deliver ClickUp task ${taskId} to OpenCode session ${sessionId}.`,
9664
- `Delivery verification failed after send${action ? ` (${action})` : ""}: ${reason || "message_delivery_failed"}.`,
9665
- "The webhook did not mark this event as successfully routed; please inspect/replay the event or route the task manually.",
9666
- clickUpNoAbandonmentRuleText()
9667
- ].join("\n");
9668
- }
9669
- async function postClickUpMessageDeliveryBlocker({ clickupClient, worktree, taskId, sessionId, reason, action } = {}) {
9670
- const comment = buildClickUpMessageDeliveryBlockerComment({ taskId, sessionId, reason, action });
9671
- if (typeof clickupClient?.postTaskComment !== "function") {
9672
- appendClickUpWebhookLocalLog(worktree, { type: "message_delivery_blocker_comment_unavailable", taskId, sessionId, reason, action });
9673
- return { ok: false, skipped: true, reason: "post_task_comment_unavailable", comment };
9668
+ async function applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason, source, tagName = CLICKUP_BLOCKER_TAG_NAME } = {}) {
9669
+ if (typeof clickupClient?.addTaskTag !== "function") {
9670
+ appendClickUpWebhookLocalLog(worktree, { type: "blocker_tag_unavailable", taskId, reason, source, tagName });
9671
+ return { ok: false, skipped: true, reason: "add_task_tag_unavailable", tagName };
9674
9672
  }
9675
9673
  try {
9676
- await clickupClient.postTaskComment({ taskId, comment });
9677
- return { ok: true, comment };
9674
+ await clickupClient.addTaskTag({ taskId, tagName });
9675
+ appendClickUpWebhookLocalLog(worktree, { type: "blocker_tag_applied", taskId, reason, source, tagName });
9676
+ return { ok: true, tagName };
9678
9677
  } catch (error) {
9679
- appendClickUpWebhookLocalLog(worktree, { type: "message_delivery_blocker_comment_failed", taskId, sessionId, reason, action, message: error.message });
9680
- return { ok: false, error: error.message, comment };
9678
+ appendClickUpWebhookLocalLog(worktree, { type: "blocker_tag_failed", taskId, reason, source, tagName, message: error.message });
9679
+ return { ok: false, error: error.message, tagName };
9681
9680
  }
9682
9681
  }
9683
9682
  async function deliverClickUpSessionEventWithVerification({ openCodeClient, sendSessionEvent, clickupClient, worktree, taskId, sessionId, agent, text, directory, opencodeBaseUrl, eventMarkers = [], verifySessionEventDelivery = verifyOpenCodeSessionEventDelivery } = {}) {
@@ -9694,8 +9693,8 @@ async function deliverClickUpSessionEventWithVerification({ openCodeClient, send
9694
9693
  }
9695
9694
  const reason = verification?.reason || "message_delivery_failed";
9696
9695
  appendClickUpWebhookLocalLog(worktree, { type: "message_delivery_failed", taskId, sessionId, reason, fallbackAttempted: canFallbackDirect });
9697
- const blocker = await postClickUpMessageDeliveryBlocker({ clickupClient, worktree, taskId, sessionId, reason, action: canFallbackDirect ? "fallback_failed" : "verification_failed" });
9698
- return { ok: false, action: "message_delivery_failed", reason, taskId, sessionId, fallbackAttempted: canFallbackDirect, blockerComment: blocker };
9696
+ const blocker = await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason, source: canFallbackDirect ? "delivery_fallback_failed" : "delivery_verification_failed" });
9697
+ return { ok: false, action: "message_delivery_failed", reason, taskId, sessionId, fallbackAttempted: canFallbackDirect, blockerTag: blocker };
9699
9698
  }
9700
9699
  function formatClickUpWebhookPrompt({ eventType, taskId, payload, branch = "", worktree = "", deliveryEvidencePath = "" }) {
9701
9700
  const comment = clickUpCommentFromPayload(payload);
@@ -9713,45 +9712,6 @@ function formatClickUpWebhookPrompt({ eventType, taskId, payload, branch = "", w
9713
9712
  deliveryEvidencePath ? `Final merge-trackable evidence must be written under ${deliveryEvidencePath}; use .optima only as local mirror/staging.` : null
9714
9713
  ].filter((part) => part !== null).join("\n");
9715
9714
  }
9716
- function clickUpNoAbandonmentRuleText() {
9717
- return "No-abandonment rule: PM must keep working, or before stopping must post a ClickUp comment, hand off, remove PM as assignee, and assign the next owner.";
9718
- }
9719
- function buildClickUpStartupResumeComment({ taskId, result = {} } = {}) {
9720
- const contextParts = [
9721
- result.branch ? `- Branch: ${result.branch}` : null,
9722
- result.worktree ? `- Worktree: ${result.worktree}` : null,
9723
- result.deliveryEvidencePath ? `- Delivery evidence path: ${result.deliveryEvidencePath}` : null,
9724
- result.evidencePath ? `- Local evidence path: ${result.evidencePath}` : null
9725
- ].filter(Boolean);
9726
- return [
9727
- `Startup reconciliation resumed ClickUp task ${taskId}.`,
9728
- `Route result: ${result.action || "unknown"}${result.ok === false ? " (failed)" : ""}.`,
9729
- result.sessionId ? `Session id: ${result.sessionId}.` : "Session id: unavailable.",
9730
- contextParts.length ? contextParts.join("\n") : "Worktree/branch/delivery evidence path: unavailable.",
9731
- clickUpNoAbandonmentRuleText()
9732
- ].join("\n");
9733
- }
9734
- function buildClickUpStartupBlockerComment({ taskId, error } = {}) {
9735
- const summary = error?.message || String(error || "unknown error");
9736
- return [
9737
- `Startup reconciliation blocker for ClickUp task ${taskId}: ${summary}`,
9738
- "Optima skipped this task for this startup pass and continued with the next PM-assigned task.",
9739
- clickUpNoAbandonmentRuleText()
9740
- ].join("\n");
9741
- }
9742
- async function postClickUpStartupComment({ clickupClient, worktree, taskId, comment, type } = {}) {
9743
- if (typeof clickupClient?.postTaskComment !== "function") {
9744
- appendClickUpWebhookLocalLog(worktree, { type: "startup_reconciliation_comment_unavailable", taskId, commentType: type });
9745
- return { ok: false, skipped: true, reason: "post_task_comment_unavailable" };
9746
- }
9747
- try {
9748
- await clickupClient.postTaskComment({ taskId, comment });
9749
- return { ok: true };
9750
- } catch (error) {
9751
- appendClickUpWebhookLocalLog(worktree, { type: "startup_reconciliation_comment_failed", taskId, commentType: type, message: error.message });
9752
- return { ok: false, error: error.message };
9753
- }
9754
- }
9755
9715
  function appendClickUpWebhookLocalLog(worktree, entry) {
9756
9716
  const logPath = clickUpWebhookLogPath(worktree);
9757
9717
  fs2.mkdirSync(path2.dirname(logPath), { recursive: true });
@@ -9987,8 +9947,8 @@ async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, w
9987
9947
  } catch (error) {
9988
9948
  const message = `Optima webhook could not create or reuse a task worktree for ${taskId}: ${error.message}`;
9989
9949
  appendClickUpWebhookLocalLog(worktree, { type: "task_worktree_failed", taskId, message });
9990
- if (typeof clickupClient?.postTaskComment === "function") await clickupClient.postTaskComment({ taskId, comment: message });
9991
- return { ok: false, action: "error", reason: "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 };
9992
9952
  }
9993
9953
  const deliveryEvidencePath = deliveryEvidencePathForClickUpTask(taskId);
9994
9954
  const routingMetadata = {
@@ -10034,10 +9994,9 @@ async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, w
10034
9994
  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 });
10035
9995
  }
10036
9996
  const at = now().toISOString();
10037
- const incidentComment = `Optima webhook could not route this ClickUp event because OpenCode session ${sessionId} is missing on host ${host} at ${at}. No replacement session was created automatically.`;
10038
9997
  appendClickUpWebhookLocalLog(worktree, { type: "missing_session", taskId, sessionId, host, at });
10039
- await clickupClient.postTaskComment({ taskId, comment: incidentComment });
10040
- return finish({ ok: true, action: "missing_session_reported", taskId, sessionId, eventKey, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath, evidencePath: routingMetadata.evidence_path });
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 });
10041
10000
  }
10042
10001
  async function routeClickUpWebhookEvent(options = {}) {
10043
10002
  const taskId = clickUpTaskIdFromPayload(options.payload || {});
@@ -10095,24 +10054,14 @@ async function reconcileClickUpStartup({ config, state = {}, worktree = process.
10095
10054
  });
10096
10055
  if (result?.ok && result.action !== "ignored") {
10097
10056
  routed.assigned += 1;
10098
- await postClickUpStartupComment({
10099
- clickupClient,
10100
- worktree,
10101
- taskId,
10102
- type: "resume",
10103
- comment: buildClickUpStartupResumeComment({ taskId, result })
10104
- });
10057
+ } else if (result?.ok === false) {
10058
+ routed.errors += 1;
10059
+ if (!result.blockerTag) await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason: result.reason || "startup_reconciliation_route_failed", source: "startup_reconciliation_task" });
10105
10060
  }
10106
10061
  } catch (error) {
10107
10062
  routed.errors += 1;
10108
10063
  appendClickUpWebhookLocalLog(worktree, { type: "startup_reconciliation_task_failed", taskId, message: error.message });
10109
- await postClickUpStartupComment({
10110
- clickupClient,
10111
- worktree,
10112
- taskId,
10113
- type: "blocker",
10114
- comment: buildClickUpStartupBlockerComment({ taskId, error })
10115
- });
10064
+ await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason: "startup_reconciliation_task_failed", source: "startup_reconciliation_task" });
10116
10065
  }
10117
10066
  if (!lastWebhookMs || !clickupClient?.getTaskComments) continue;
10118
10067
  try {
@@ -10142,6 +10091,7 @@ async function reconcileClickUpStartup({ config, state = {}, worktree = process.
10142
10091
  } catch (error) {
10143
10092
  routed.errors += 1;
10144
10093
  appendClickUpWebhookLocalLog(worktree, { type: "startup_reconciliation_comments_failed", taskId, message: error.message });
10094
+ await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason: "startup_reconciliation_comments_failed", source: "startup_reconciliation_comments" });
10145
10095
  }
10146
10096
  }
10147
10097
  clickUpWebhookLifecycleLog(worktree, { type: "startup_reconciliation_finished", ...routed });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defend-tech/opencode-optima",
3
- "version": "0.1.38",
3
+ "version": "0.1.39",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+ssh://git@github.com/defend-tech/opencode-optima.git"