@defend-tech/opencode-optima 0.1.62 → 0.1.64

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.
@@ -33,7 +33,7 @@ You are Workflow_Product_Manager, Optima's ClickUp-first delivery orchestrator.
33
33
 
34
34
  - Register only when Optima opt-in ClickUp webhook mode is configured and active/valid.
35
35
  - Webhook wakeup requires signed `X-Signature` HMAC SHA-256 verification, duplicate suppression, PM assignment/non-terminal status checks, and comment mention gating for `@Defend Tech Product Manager`.
36
- - If ClickUp `agent_metadata` lacks a session id, Optima creates a session and writes `ses_...`; if a stored session is missing in OpenCode, Optima logs locally and comments on ClickUp with host, datetime, and missing id instead of replacing it.
36
+ - Webhook routing is successful only after the worktree is OpenChamber-visible, an OpenCode `workflow_product_manager` session exists in that exact worktree directory, and prompt delivery/admission is verified. Worktree visibility alone is a launch failure, not successful routing.
37
37
  - Delivery task types: `Tarea`, `Bug`, `Doc`, `PoC`. Ignore unless converted/linked to delivery: `Idea`, legacy `Backlog`, `Hito`, `Nota de reunión`, `Respuesta del formulario`. Treat `Backlog` task type as `Idea`, not `backlog` status.
38
38
  - Branch-safe slugs: `Tarea` -> `tarea`, `Bug` -> `bug`, `Doc` -> `doc`, `PoC` -> `poc`. Unknown task type: pause and ask PMA/PO for clarification.
39
39
 
package/dist/index.js CHANGED
@@ -8621,13 +8621,6 @@ var activeClickUpWebhookLifecycleRegistry = /* @__PURE__ */ new Map();
8621
8621
  var CLICKUP_WEBHOOK_CLEANUP_TIMEOUT_MS = 8e3;
8622
8622
  var CLICKUP_WEBHOOK_SIGNAL_STATE = Symbol.for("opencode-optima.clickup-webhook.signal-state");
8623
8623
  var activeClickUpTaskRoutes = /* @__PURE__ */ new Map();
8624
- var objectIdentityMap = /* @__PURE__ */ new WeakMap();
8625
- var objectIdentitySequence = 0;
8626
- function objectIdentity(value) {
8627
- if (!value || typeof value !== "object" && typeof value !== "function") return String(value ?? "");
8628
- if (!objectIdentityMap.has(value)) objectIdentityMap.set(value, `obj_${++objectIdentitySequence}`);
8629
- return objectIdentityMap.get(value);
8630
- }
8631
8624
  function isRootDirectory(candidate) {
8632
8625
  const resolved = path5.resolve(candidate);
8633
8626
  return resolved === path5.parse(resolved).root;
@@ -8753,7 +8746,6 @@ function hasActionableClickUpRoute(result = {}) {
8753
8746
  if (!result.sessionId) return false;
8754
8747
  if (result.action === "message_delivery_failed" || result.action === "error") return false;
8755
8748
  if (result.deliveryVerification?.ok === false) return false;
8756
- if (result.deliveryVerification?.method === "prompt_admission") return false;
8757
8749
  return true;
8758
8750
  }
8759
8751
  function determineClickUpMergeAuthority({ isSubtask = false, clickupStatus = "", validationPassed = false, mergeFailed = false, finalApprovalRoles = CLICKUP_FINAL_APPROVER_ROLES, humansRegistry } = {}) {
@@ -9350,11 +9342,6 @@ async function syncOpenChamberWorktreeVisibility({ openchamberBaseUrl, opencodeB
9350
9342
  }
9351
9343
  return visibility;
9352
9344
  }
9353
- function assertOpenChamberClickUpWorktreePath({ baseWorktree, worktreePath } = {}) {
9354
- if (!isClickUpDerivedWorktreeSibling(worktreePath, baseWorktree)) {
9355
- throw new Error(`OpenChamber worktree path is outside the configured ClickUp sibling scope: ${worktreePath}`);
9356
- }
9357
- }
9358
9345
  async function createOpenChamberClickUpWorktree({ openchamberBaseUrl, baseUrl, baseWorktree, branch, worktreePath, startPoint, branchExists = false, fetchImpl = globalThis.fetch } = {}) {
9359
9346
  const effectiveOpenChamberBaseUrl = openchamberBaseUrl || baseUrl;
9360
9347
  const worktreeName = clickUpOpenChamberWorktreeName(branch);
@@ -9377,7 +9364,6 @@ async function createOpenChamberClickUpWorktree({ openchamberBaseUrl, baseUrl, b
9377
9364
  return { created, worktree: path5.resolve(createdDirectory), branch: createdBranch, verified };
9378
9365
  }
9379
9366
  async function registerOpenChamberClickUpWorktree({ openchamberBaseUrl, opencodeBaseUrl, baseUrl, baseWorktree, branch, worktreePath, fetchImpl = globalThis.fetch, source = "reuse" } = {}) {
9380
- assertOpenChamberClickUpWorktreePath({ baseWorktree, worktreePath });
9381
9367
  const visibility = await syncOpenChamberWorktreeVisibility({ openchamberBaseUrl: openchamberBaseUrl || baseUrl, opencodeBaseUrl: opencodeBaseUrl || baseUrl, baseWorktree, worktreePath, branch, fetchImpl });
9382
9368
  return { branch, worktree: path5.resolve(worktreePath), reused: true, provider: "openchamber", openChamber: { source, visibility } };
9383
9369
  }
@@ -9431,30 +9417,6 @@ async function ensureClickUpTaskWorktreeForWebhook({ opencodeBaseUrl = "", openc
9431
9417
  } catch (error) {
9432
9418
  const message = `OpenChamber worktree provisioning failed for ${options.taskId || "unknown task"}; raw git fallback is disabled to avoid invisible worktrees. ${error.message}`;
9433
9419
  appendClickUpWebhookLocalLog(webhookWorktree, { type: "openchamber_worktree_failed", taskId: options.taskId || null, message });
9434
- if (typeof clickupClient?.postTaskComment === "function") {
9435
- const taskId = options.taskId || "unknown task";
9436
- const ledgerPath = clickUpCommentLedgerPath(webhookWorktree);
9437
- const comment = `${message}
9438
-
9439
- Optima did not run raw git worktree fallback. Please verify clickup.openchamber.base_url and OpenCode/OpenChamber endpoint configuration.`;
9440
- let dedupe = { post: true, key: clickUpWorktreeFailureCommentKey({ taskId, message }) };
9441
- try {
9442
- dedupe = shouldPostClickUpWorktreeFailureComment({ ledgerPath, taskId, message });
9443
- } catch (ledgerError) {
9444
- appendClickUpWebhookLocalLog(webhookWorktree, { type: "openchamber_worktree_comment_dedupe_failed", taskId, message: ledgerError.message });
9445
- }
9446
- if (dedupe.post) {
9447
- try {
9448
- await clickupClient.postTaskComment({ taskId: options.taskId, comment });
9449
- recordClickUpWorktreeFailureComment({ ledgerPath, taskId, message, posted: true });
9450
- } catch (commentError) {
9451
- appendClickUpWebhookLocalLog(webhookWorktree, { type: "openchamber_worktree_blocker_comment_failed", taskId: options.taskId || null, message: commentError.message });
9452
- }
9453
- } else {
9454
- appendClickUpWebhookLocalLog(webhookWorktree, { type: "openchamber_worktree_blocker_comment_deduped", taskId, key: dedupe.key, reason: dedupe.reason });
9455
- recordClickUpWorktreeFailureComment({ ledgerPath, taskId, message, posted: false, skippedReason: dedupe.reason });
9456
- }
9457
- }
9458
9420
  throw new Error(message);
9459
9421
  }
9460
9422
  }
@@ -10400,35 +10362,6 @@ function recordClickUpCommentVersionProcessed({ ledgerPath, key, taskId, eventTy
10400
10362
  recordedAt: at.toISOString()
10401
10363
  });
10402
10364
  }
10403
- function clickUpWorktreeFailureCommentKey({ taskId, message } = {}) {
10404
- const hash = crypto.createHash("sha256").update(String(message || "")).digest("hex").slice(0, 16);
10405
- return [String(taskId || "unknown").trim() || "unknown", "worktree_failure", hash].join(":");
10406
- }
10407
- function shouldPostClickUpWorktreeFailureComment({ ledgerPath, taskId, message, now = /* @__PURE__ */ new Date(), windowMs = CLICKUP_WORKTREE_FAILURE_COMMENT_DEDUPE_MS } = {}) {
10408
- const key = clickUpWorktreeFailureCommentKey({ taskId, message });
10409
- const nowMs = now instanceof Date ? now.getTime() : new Date(now).getTime();
10410
- for (const entry of readClickUpCommentLedgerEntries(ledgerPath).reverse()) {
10411
- if (entry?.key !== key) continue;
10412
- const postedAtMs = new Date(entry.postedAt || entry.recordedAt || entry.at || 0).getTime();
10413
- if (Number.isFinite(postedAtMs) && Number.isFinite(nowMs) && nowMs - postedAtMs <= windowMs) {
10414
- return { post: false, key, reason: "recent_duplicate" };
10415
- }
10416
- if (entry?.message === message) return { post: false, key, reason: "latest_equivalent" };
10417
- }
10418
- return { post: true, key };
10419
- }
10420
- function recordClickUpWorktreeFailureComment({ ledgerPath, taskId, message, posted = true, skippedReason = null, at = /* @__PURE__ */ new Date() } = {}) {
10421
- appendClickUpCommentLedgerEntry(ledgerPath, {
10422
- key: clickUpWorktreeFailureCommentKey({ taskId, message }),
10423
- taskId,
10424
- eventType: "worktree_failure",
10425
- action: posted ? "worktree_failure_comment_posted" : "worktree_failure_comment_skipped",
10426
- message,
10427
- posted,
10428
- skippedReason,
10429
- postedAt: at.toISOString()
10430
- });
10431
- }
10432
10365
  function clickUpTaskIdFromPayload(payload = {}) {
10433
10366
  return String(payload.task_id || payload.taskId || payload.task?.id || payload.task?.task_id || "").trim();
10434
10367
  }
@@ -10562,12 +10495,16 @@ function clearClickUpPendingSessionMetadata(metadata, metadataKey) {
10562
10495
  delete cursor[parts[parts.length - 1]];
10563
10496
  return JSON.stringify(sortJsonValue(root), null, 2);
10564
10497
  }
10565
- async function openCodeSessionExists(client, sessionId) {
10498
+ function withOptionalDirectoryQuery(payload, directory) {
10499
+ if (!directory) return payload;
10500
+ return { ...payload, query: { ...payload.query || {}, directory } };
10501
+ }
10502
+ async function openCodeSessionExists(client, sessionId, { directory = "" } = {}) {
10566
10503
  const getAttempts = [
10567
- { path: { id: sessionId } },
10568
- { path: { sessionID: sessionId } },
10569
- { sessionID: sessionId },
10570
- { id: sessionId }
10504
+ withOptionalDirectoryQuery({ path: { id: sessionId } }, directory),
10505
+ withOptionalDirectoryQuery({ path: { sessionID: sessionId } }, directory),
10506
+ directory ? { sessionID: sessionId, directory } : { sessionID: sessionId },
10507
+ directory ? { id: sessionId, directory } : { id: sessionId }
10571
10508
  ];
10572
10509
  if (typeof client?.session?.get === "function") {
10573
10510
  for (const attempt of getAttempts) {
@@ -10579,10 +10516,10 @@ async function openCodeSessionExists(client, sessionId) {
10579
10516
  }
10580
10517
  }
10581
10518
  const messageAttempts = [
10582
- { path: { id: sessionId }, query: { limit: 1 } },
10583
- { path: { sessionID: sessionId }, query: { limit: 1 } },
10584
- { sessionID: sessionId, limit: 1 },
10585
- { id: sessionId, limit: 1 }
10519
+ { path: { id: sessionId }, query: { limit: 1, ...directory ? { directory } : {} } },
10520
+ { path: { sessionID: sessionId }, query: { limit: 1, ...directory ? { directory } : {} } },
10521
+ directory ? { sessionID: sessionId, directory, limit: 1 } : { sessionID: sessionId, limit: 1 },
10522
+ directory ? { id: sessionId, directory, limit: 1 } : { id: sessionId, limit: 1 }
10586
10523
  ];
10587
10524
  if (typeof client?.session?.messages === "function") {
10588
10525
  for (const attempt of messageAttempts) {
@@ -10986,6 +10923,11 @@ async function verifyOpenCodeSessionEventDelivery(client, { sessionId, directory
10986
10923
  }
10987
10924
  return { ok: false, reason: lastError };
10988
10925
  }
10926
+ async function postClickUpLaunchFailureComment({ clickupClient, worktree, taskId, reason, source } = {}) {
10927
+ void clickupClient;
10928
+ appendClickUpWebhookLocalLog(worktree, { type: "launch_failure_comment_skipped", taskId, reason, source, policy: "local_log_only" });
10929
+ return { ok: false, skipped: true, reason: "local_log_only" };
10930
+ }
10989
10931
  async function applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason, source, tagName = CLICKUP_BLOCKER_TAG_NAME } = {}) {
10990
10932
  if (typeof clickupClient?.addTaskTag !== "function") {
10991
10933
  appendClickUpWebhookLocalLog(worktree, { type: "blocker_tag_unavailable", taskId, reason, source, tagName });
@@ -11015,7 +10957,17 @@ function openCodeBlockingPromptVerification(result, sessionId) {
11015
10957
  }
11016
10958
  async function deliverClickUpSessionEventWithVerification({ openCodeClient, sendSessionEvent, clickupClient, worktree, taskId, sessionId, agent, text, directory, opencodeBaseUrl, directPrompt = false, acceptPromptAdmission = false, eventMarkers = [], verifySessionEventDelivery = verifyOpenCodeSessionEventDelivery, applyBlockerOnFailure = true } = {}) {
11017
10959
  const beforeMessages = await readOpenCodeSessionMessages(openCodeClient, { sessionId, directory, limit: 50 }).catch(() => null);
11018
- const sendResult = await sendSessionEvent(openCodeClient, { sessionId, agent, text, directory, opencodeBaseUrl, direct: directPrompt, allowDirectFallback: directPrompt });
10960
+ let sendResult;
10961
+ try {
10962
+ sendResult = await sendSessionEvent(openCodeClient, { sessionId, agent, text, directory, opencodeBaseUrl, direct: directPrompt, allowDirectFallback: directPrompt });
10963
+ } catch (error) {
10964
+ const reason2 = error.message || "message_delivery_failed";
10965
+ appendClickUpWebhookLocalLog(worktree, { type: "message_delivery_failed", taskId, sessionId, reason: reason2, fallbackAttempted: false });
10966
+ if (!applyBlockerOnFailure) return { ok: false, action: "message_delivery_failed", reason: reason2, taskId, sessionId, fallbackAttempted: false };
10967
+ const blocker2 = await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason: reason2, source: "delivery_send_failed" });
10968
+ const launchFailureComment2 = await postClickUpLaunchFailureComment({ clickupClient, worktree, taskId, reason: reason2, source: "delivery_send_failed" });
10969
+ return { ok: false, action: "message_delivery_failed", reason: reason2, taskId, sessionId, fallbackAttempted: false, blockerTag: blocker2, launchFailureComment: launchFailureComment2 };
10970
+ }
11019
10971
  let blockingPromptVerification = null;
11020
10972
  let admissionVerification = null;
11021
10973
  try {
@@ -11025,7 +10977,8 @@ async function deliverClickUpSessionEventWithVerification({ openCodeClient, send
11025
10977
  appendClickUpWebhookLocalLog(worktree, { type: "message_delivery_failed", taskId, sessionId, reason: error.message, fallbackAttempted: false });
11026
10978
  if (!applyBlockerOnFailure) return { ok: false, action: "message_delivery_failed", reason: error.message, taskId, sessionId, fallbackAttempted: false };
11027
10979
  const blocker2 = await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason: error.message, source: "delivery_admission_failed" });
11028
- return { ok: false, action: "message_delivery_failed", reason: error.message, taskId, sessionId, fallbackAttempted: false, blockerTag: blocker2 };
10980
+ const launchFailureComment2 = await postClickUpLaunchFailureComment({ clickupClient, worktree, taskId, reason: error.message, source: "delivery_admission_failed" });
10981
+ return { ok: false, action: "message_delivery_failed", reason: error.message, taskId, sessionId, fallbackAttempted: false, blockerTag: blocker2, launchFailureComment: launchFailureComment2 };
11029
10982
  }
11030
10983
  if (blockingPromptVerification) return { ok: true, verification: blockingPromptVerification, admissionVerification: null, fallback: false };
11031
10984
  if (admissionVerification && acceptPromptAdmission) {
@@ -11040,7 +10993,8 @@ async function deliverClickUpSessionEventWithVerification({ openCodeClient, send
11040
10993
  appendClickUpWebhookLocalLog(worktree, { type: "message_delivery_failed", taskId, sessionId, reason, fallbackAttempted: false, httpFallbackDisabled: Boolean(opencodeBaseUrl) });
11041
10994
  if (!applyBlockerOnFailure) return { ok: false, action: "message_delivery_failed", reason, taskId, sessionId, fallbackAttempted: false };
11042
10995
  const blocker = await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason, source: "delivery_verification_failed" });
11043
- return { ok: false, action: "message_delivery_failed", reason, taskId, sessionId, fallbackAttempted: false, blockerTag: blocker };
10996
+ const launchFailureComment = await postClickUpLaunchFailureComment({ clickupClient, worktree, taskId, reason, source: "delivery_verification_failed" });
10997
+ return { ok: false, action: "message_delivery_failed", reason, taskId, sessionId, fallbackAttempted: false, blockerTag: blocker, launchFailureComment };
11044
10998
  }
11045
10999
  async function recoverClickUpPmSession({ openCodeClient, sendSessionEvent, clickupClient, worktree, taskId, staleSessionId, sessionTitle, taskRoute, metadataWithRouting, config, prompt, eventMarkers = [], deliveryEvidencePath, evidencePath, eventKey, createSession, verifySessionEventDelivery } = {}) {
11046
11000
  appendClickUpWebhookLocalLog(worktree, { type: "pm_session_recovery_started", taskId, staleSessionId, worktree: taskRoute?.worktree });
@@ -11050,12 +11004,14 @@ async function recoverClickUpPmSession({ openCodeClient, sendSessionEvent, click
11050
11004
  } catch (error) {
11051
11005
  appendClickUpWebhookLocalLog(worktree, { type: "pm_session_recovery_create_failed", taskId, staleSessionId, message: error.message });
11052
11006
  const blockerTag = await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason: "replacement_session_create_failed", source: "pm_session_recovery" });
11053
- 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 };
11007
+ const launchFailureComment = await postClickUpLaunchFailureComment({ clickupClient, worktree, taskId, reason: "replacement_session_create_failed", source: "pm_session_recovery" });
11008
+ return { ok: false, action: "message_delivery_failed", reason: "replacement_session_create_failed", taskId, sessionId: staleSessionId, replacementAttempted: true, blockerTag, launchFailureComment, eventKey, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath, evidencePath };
11054
11009
  }
11055
11010
  if (!String(replacementSessionId || "").startsWith("ses_")) {
11056
11011
  appendClickUpWebhookLocalLog(worktree, { type: "pm_session_recovery_create_invalid", taskId, staleSessionId, replacementSessionId });
11057
11012
  const blockerTag = await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason: "replacement_session_create_failed", source: "pm_session_recovery" });
11058
- 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 };
11013
+ const launchFailureComment = await postClickUpLaunchFailureComment({ clickupClient, worktree, taskId, reason: "replacement_session_create_failed", source: "pm_session_recovery" });
11014
+ return { ok: false, action: "message_delivery_failed", reason: "replacement_session_create_failed", taskId, sessionId: staleSessionId, replacementAttempted: true, blockerTag, launchFailureComment, eventKey, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath, evidencePath };
11059
11015
  }
11060
11016
  const replacementDelivery = await deliverClickUpSessionEventWithVerification({
11061
11017
  openCodeClient,
@@ -11077,7 +11033,8 @@ async function recoverClickUpPmSession({ openCodeClient, sendSessionEvent, click
11077
11033
  if (!replacementDelivery.ok) {
11078
11034
  appendClickUpWebhookLocalLog(worktree, { type: "pm_session_recovery_delivery_failed", taskId, staleSessionId, replacementSessionId, reason: replacementDelivery.reason });
11079
11035
  const blockerTag = await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason: replacementDelivery.reason || "replacement_delivery_failed", source: "pm_session_recovery" });
11080
- return { ...replacementDelivery, sessionId: staleSessionId, replacementSessionId, replacementAttempted: true, blockerTag, eventKey, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath, evidencePath };
11036
+ const launchFailureComment = replacementDelivery.launchFailureComment || await postClickUpLaunchFailureComment({ clickupClient, worktree, taskId, reason: replacementDelivery.reason || "replacement_delivery_failed", source: "pm_session_recovery" });
11037
+ return { ...replacementDelivery, sessionId: staleSessionId, replacementSessionId, replacementAttempted: true, blockerTag, launchFailureComment, eventKey, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath, evidencePath };
11081
11038
  }
11082
11039
  const replacementMetadata = clearClickUpPendingSessionMetadata(setClickUpSessionMetadata(metadataWithRouting, config.routing.metadataKey, replacementSessionId), config.routing.metadataKey);
11083
11040
  await clickupClient.updateTaskMetadata({ taskId, fieldId: config.routing.metadataFieldId, value: replacementMetadata });
@@ -11361,8 +11318,26 @@ async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, w
11361
11318
  if (!existingSessionId) {
11362
11319
  let pendingSessionId = getNestedMetadataValue(metadata, clickUpPendingSessionKey(config.routing.metadataKey)) || state.pendingSessions?.[taskId];
11363
11320
  if (!pendingSessionId) {
11364
- pendingSessionId = await createSession(openCodeClient, { title: sessionTitle, directory: taskRoute.worktree, agent: config.routing.targetAgent });
11365
- if (!String(pendingSessionId || "").startsWith("ses_")) return { ok: false, action: "error", reason: "session_create_failed", taskId };
11321
+ try {
11322
+ pendingSessionId = await createSession(openCodeClient, { title: sessionTitle, directory: taskRoute.worktree, agent: config.routing.targetAgent });
11323
+ } catch (error) {
11324
+ appendClickUpWebhookLocalLog(worktree, { type: "pm_session_create_failed", taskId, worktree: taskRoute.worktree, message: error.message });
11325
+ const blockerTag = await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason: "session_create_failed", source: "route_create_session" });
11326
+ const launchFailureComment = await postClickUpLaunchFailureComment({ clickupClient, worktree, taskId, reason: "session_create_failed", source: "route_create_session" });
11327
+ return finish({ ok: false, action: "error", reason: "session_create_failed", taskId, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath, evidencePath: routingMetadata.evidence_path, blockerTag, launchFailureComment });
11328
+ }
11329
+ if (!String(pendingSessionId || "").startsWith("ses_")) {
11330
+ appendClickUpWebhookLocalLog(worktree, { type: "pm_session_create_invalid", taskId, worktree: taskRoute.worktree, sessionId: pendingSessionId || null });
11331
+ const blockerTag = await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason: "session_create_failed", source: "route_create_session" });
11332
+ const launchFailureComment = await postClickUpLaunchFailureComment({ clickupClient, worktree, taskId, reason: "session_create_failed", source: "route_create_session" });
11333
+ return finish({ ok: false, action: "error", reason: "session_create_failed", taskId, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath, evidencePath: routingMetadata.evidence_path, blockerTag, launchFailureComment });
11334
+ }
11335
+ if (!await sessionExists(openCodeClient, pendingSessionId, { directory: taskRoute.worktree })) {
11336
+ appendClickUpWebhookLocalLog(worktree, { type: "pm_session_create_directory_unverified", taskId, worktree: taskRoute.worktree, sessionId: pendingSessionId });
11337
+ const blockerTag = await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason: "session_directory_unverified", source: "route_create_session" });
11338
+ const launchFailureComment = await postClickUpLaunchFailureComment({ clickupClient, worktree, taskId, reason: "session_directory_unverified", source: "route_create_session" });
11339
+ return finish({ ok: false, action: "error", reason: "session_directory_unverified", taskId, sessionId: pendingSessionId, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath, evidencePath: routingMetadata.evidence_path, blockerTag, launchFailureComment });
11340
+ }
11366
11341
  if (saveState) {
11367
11342
  saveState({
11368
11343
  ...state,
@@ -11378,7 +11353,20 @@ async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, w
11378
11353
  throw error;
11379
11354
  }
11380
11355
  const delivery = await deliverClickUpSessionEventWithVerification({ openCodeClient, sendSessionEvent, clickupClient, worktree, taskId, sessionId: pendingSessionId, agent: config.routing.targetAgent, text: prompt, directory: taskRoute.worktree, opencodeBaseUrl: config.opencode?.baseUrl, directPrompt: config.opencode?.promptDelivery === "http", acceptPromptAdmission: config.opencode?.acceptPromptAdmission === true, eventMarkers: [taskId, eventType], verifySessionEventDelivery });
11381
- if (!delivery.ok) return finish({ ...delivery, eventKey, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath, evidencePath: routingMetadata.evidence_path });
11356
+ if (!delivery.ok) {
11357
+ const failedMetadata = clearClickUpPendingSessionMetadata(metadataWithRouting, config.routing.metadataKey);
11358
+ let pendingCleared = false;
11359
+ try {
11360
+ await clickupClient.updateTaskMetadata({ taskId, fieldId: config.routing.metadataFieldId, value: failedMetadata });
11361
+ pendingCleared = true;
11362
+ } catch (error) {
11363
+ appendClickUpWebhookLocalLog(worktree, { type: "pending_session_clear_failed", taskId, sessionId: pendingSessionId, message: error.message });
11364
+ }
11365
+ const { [taskId]: _failedPending, ...remainingPending2 } = stateToPersist.pendingSessions || {};
11366
+ stateToPersist = { ...stateToPersist, pendingSessions: remainingPending2 };
11367
+ appendClickUpWebhookLocalLog(worktree, { type: "pending_session_delivery_failed", taskId, sessionId: pendingSessionId, reason: delivery.reason || "message_delivery_failed", pendingCleared });
11368
+ return finish({ ...delivery, eventKey, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath, evidencePath: routingMetadata.evidence_path, pendingCleared });
11369
+ }
11382
11370
  const nextMetadata = clearClickUpPendingSessionMetadata(setClickUpSessionMetadata(pendingMetadata, config.routing.metadataKey, pendingSessionId), config.routing.metadataKey);
11383
11371
  await clickupClient.updateTaskMetadata({ taskId, fieldId: config.routing.metadataFieldId, value: nextMetadata });
11384
11372
  const { [taskId]: _completedPending, ...remainingPending } = stateToPersist.pendingSessions || {};
@@ -11386,7 +11374,7 @@ async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, w
11386
11374
  return finish({ ok: true, action: "created_session", taskId, sessionId: pendingSessionId, eventKey, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath, evidencePath: routingMetadata.evidence_path, deliveryVerification: delivery.verification, deliveryAdmission: delivery.admissionVerification, deliveryFallback: delivery.fallback, deliveryAttempts: delivery.fallback ? 2 : 1 });
11387
11375
  }
11388
11376
  const sessionId = String(existingSessionId);
11389
- if (await sessionExists(openCodeClient, sessionId)) {
11377
+ if (await sessionExists(openCodeClient, sessionId, { directory: taskRoute.worktree })) {
11390
11378
  if (typeof clickupClient?.updateTaskMetadata === "function") await clickupClient.updateTaskMetadata({ taskId, fieldId: config.routing.metadataFieldId, value: metadataWithRouting });
11391
11379
  const delivery = await deliverClickUpSessionEventWithVerification({ openCodeClient, sendSessionEvent, clickupClient, worktree, taskId, sessionId, agent: config.routing.targetAgent, text: prompt, directory: taskRoute.worktree, opencodeBaseUrl: config.opencode?.baseUrl, directPrompt: config.opencode?.promptDelivery === "http", acceptPromptAdmission: config.opencode?.acceptPromptAdmission === true, eventMarkers: [taskId, eventType], verifySessionEventDelivery, applyBlockerOnFailure: false });
11392
11380
  if (!delivery.ok) {
@@ -11514,6 +11502,7 @@ async function reconcileClickUpStartup({ config, state = {}, worktree = process.
11514
11502
  routed.undelivered += 1;
11515
11503
  appendClickUpWebhookLocalLog(worktree, { type: "startup_reconciliation_task_undelivered", taskId, action: result?.action || "error", sessionId: routeSummary.sessionId, reason: result.reason || "startup_reconciliation_route_failed" });
11516
11504
  if (!result.blockerTag) await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason: result.reason || "startup_reconciliation_route_failed", source: "startup_reconciliation_task" });
11505
+ if (!result.launchFailureComment) await postClickUpLaunchFailureComment({ clickupClient, worktree, taskId, reason: result.reason || "startup_reconciliation_route_failed", source: "startup_reconciliation_task" });
11517
11506
  } else {
11518
11507
  routed.undelivered += 1;
11519
11508
  appendClickUpWebhookLocalLog(worktree, { type: "startup_reconciliation_task_undelivered", taskId, action: routeSummary.action, sessionId: routeSummary.sessionId, reason: "route_not_actionable" });
@@ -11621,14 +11610,13 @@ function clickUpListenerKey(config) {
11621
11610
  }
11622
11611
  function clickUpListenerFingerprint({ config, state, worktree, clickupClient, openCodeClient } = {}) {
11623
11612
  return JSON.stringify({
11613
+ teamId: config?.teamId || "",
11624
11614
  publicUrl: config?.webhook?.publicUrl || "",
11625
11615
  path: clickUpWebhookExpectedPath(config),
11626
- worktree: path5.resolve(worktree || process.cwd()),
11616
+ location: config?.webhook?.location || {},
11627
11617
  webhookId: state?.webhookId || "",
11628
11618
  secret: state?.secret || "",
11629
- events: config?.webhook?.events || [],
11630
- clickupClient: objectIdentity(clickupClient),
11631
- openCodeClient: objectIdentity(openCodeClient)
11619
+ events: config?.webhook?.events || []
11632
11620
  });
11633
11621
  }
11634
11622
  function startClickUpWebhookListener({ config, state, worktree, clickupClient, openCodeClient, listenerRegistry = activeClickUpWebhookListeners } = {}) {
@@ -8628,13 +8628,6 @@ var activeClickUpWebhookLifecycleRegistry = /* @__PURE__ */ new Map();
8628
8628
  var CLICKUP_WEBHOOK_CLEANUP_TIMEOUT_MS = 8e3;
8629
8629
  var CLICKUP_WEBHOOK_SIGNAL_STATE = Symbol.for("opencode-optima.clickup-webhook.signal-state");
8630
8630
  var activeClickUpTaskRoutes = /* @__PURE__ */ new Map();
8631
- var objectIdentityMap = /* @__PURE__ */ new WeakMap();
8632
- var objectIdentitySequence = 0;
8633
- function objectIdentity(value) {
8634
- if (!value || typeof value !== "object" && typeof value !== "function") return String(value ?? "");
8635
- if (!objectIdentityMap.has(value)) objectIdentityMap.set(value, `obj_${++objectIdentitySequence}`);
8636
- return objectIdentityMap.get(value);
8637
- }
8638
8631
  function isRootDirectory(candidate) {
8639
8632
  const resolved = path5.resolve(candidate);
8640
8633
  return resolved === path5.parse(resolved).root;
@@ -8760,7 +8753,6 @@ function hasActionableClickUpRoute(result = {}) {
8760
8753
  if (!result.sessionId) return false;
8761
8754
  if (result.action === "message_delivery_failed" || result.action === "error") return false;
8762
8755
  if (result.deliveryVerification?.ok === false) return false;
8763
- if (result.deliveryVerification?.method === "prompt_admission") return false;
8764
8756
  return true;
8765
8757
  }
8766
8758
  function determineClickUpMergeAuthority({ isSubtask = false, clickupStatus = "", validationPassed = false, mergeFailed = false, finalApprovalRoles = CLICKUP_FINAL_APPROVER_ROLES, humansRegistry } = {}) {
@@ -9357,11 +9349,6 @@ async function syncOpenChamberWorktreeVisibility({ openchamberBaseUrl, opencodeB
9357
9349
  }
9358
9350
  return visibility;
9359
9351
  }
9360
- function assertOpenChamberClickUpWorktreePath({ baseWorktree, worktreePath } = {}) {
9361
- if (!isClickUpDerivedWorktreeSibling(worktreePath, baseWorktree)) {
9362
- throw new Error(`OpenChamber worktree path is outside the configured ClickUp sibling scope: ${worktreePath}`);
9363
- }
9364
- }
9365
9352
  async function createOpenChamberClickUpWorktree({ openchamberBaseUrl, baseUrl, baseWorktree, branch, worktreePath, startPoint, branchExists = false, fetchImpl = globalThis.fetch } = {}) {
9366
9353
  const effectiveOpenChamberBaseUrl = openchamberBaseUrl || baseUrl;
9367
9354
  const worktreeName = clickUpOpenChamberWorktreeName(branch);
@@ -9384,7 +9371,6 @@ async function createOpenChamberClickUpWorktree({ openchamberBaseUrl, baseUrl, b
9384
9371
  return { created, worktree: path5.resolve(createdDirectory), branch: createdBranch, verified };
9385
9372
  }
9386
9373
  async function registerOpenChamberClickUpWorktree({ openchamberBaseUrl, opencodeBaseUrl, baseUrl, baseWorktree, branch, worktreePath, fetchImpl = globalThis.fetch, source = "reuse" } = {}) {
9387
- assertOpenChamberClickUpWorktreePath({ baseWorktree, worktreePath });
9388
9374
  const visibility = await syncOpenChamberWorktreeVisibility({ openchamberBaseUrl: openchamberBaseUrl || baseUrl, opencodeBaseUrl: opencodeBaseUrl || baseUrl, baseWorktree, worktreePath, branch, fetchImpl });
9389
9375
  return { branch, worktree: path5.resolve(worktreePath), reused: true, provider: "openchamber", openChamber: { source, visibility } };
9390
9376
  }
@@ -9438,30 +9424,6 @@ async function ensureClickUpTaskWorktreeForWebhook({ opencodeBaseUrl = "", openc
9438
9424
  } catch (error) {
9439
9425
  const message = `OpenChamber worktree provisioning failed for ${options.taskId || "unknown task"}; raw git fallback is disabled to avoid invisible worktrees. ${error.message}`;
9440
9426
  appendClickUpWebhookLocalLog(webhookWorktree, { type: "openchamber_worktree_failed", taskId: options.taskId || null, message });
9441
- if (typeof clickupClient?.postTaskComment === "function") {
9442
- const taskId = options.taskId || "unknown task";
9443
- const ledgerPath = clickUpCommentLedgerPath(webhookWorktree);
9444
- const comment = `${message}
9445
-
9446
- Optima did not run raw git worktree fallback. Please verify clickup.openchamber.base_url and OpenCode/OpenChamber endpoint configuration.`;
9447
- let dedupe = { post: true, key: clickUpWorktreeFailureCommentKey({ taskId, message }) };
9448
- try {
9449
- dedupe = shouldPostClickUpWorktreeFailureComment({ ledgerPath, taskId, message });
9450
- } catch (ledgerError) {
9451
- appendClickUpWebhookLocalLog(webhookWorktree, { type: "openchamber_worktree_comment_dedupe_failed", taskId, message: ledgerError.message });
9452
- }
9453
- if (dedupe.post) {
9454
- try {
9455
- await clickupClient.postTaskComment({ taskId: options.taskId, comment });
9456
- recordClickUpWorktreeFailureComment({ ledgerPath, taskId, message, posted: true });
9457
- } catch (commentError) {
9458
- appendClickUpWebhookLocalLog(webhookWorktree, { type: "openchamber_worktree_blocker_comment_failed", taskId: options.taskId || null, message: commentError.message });
9459
- }
9460
- } else {
9461
- appendClickUpWebhookLocalLog(webhookWorktree, { type: "openchamber_worktree_blocker_comment_deduped", taskId, key: dedupe.key, reason: dedupe.reason });
9462
- recordClickUpWorktreeFailureComment({ ledgerPath, taskId, message, posted: false, skippedReason: dedupe.reason });
9463
- }
9464
- }
9465
9427
  throw new Error(message);
9466
9428
  }
9467
9429
  }
@@ -10407,35 +10369,6 @@ function recordClickUpCommentVersionProcessed({ ledgerPath, key, taskId, eventTy
10407
10369
  recordedAt: at.toISOString()
10408
10370
  });
10409
10371
  }
10410
- function clickUpWorktreeFailureCommentKey({ taskId, message } = {}) {
10411
- const hash = crypto.createHash("sha256").update(String(message || "")).digest("hex").slice(0, 16);
10412
- return [String(taskId || "unknown").trim() || "unknown", "worktree_failure", hash].join(":");
10413
- }
10414
- function shouldPostClickUpWorktreeFailureComment({ ledgerPath, taskId, message, now = /* @__PURE__ */ new Date(), windowMs = CLICKUP_WORKTREE_FAILURE_COMMENT_DEDUPE_MS } = {}) {
10415
- const key = clickUpWorktreeFailureCommentKey({ taskId, message });
10416
- const nowMs = now instanceof Date ? now.getTime() : new Date(now).getTime();
10417
- for (const entry of readClickUpCommentLedgerEntries(ledgerPath).reverse()) {
10418
- if (entry?.key !== key) continue;
10419
- const postedAtMs = new Date(entry.postedAt || entry.recordedAt || entry.at || 0).getTime();
10420
- if (Number.isFinite(postedAtMs) && Number.isFinite(nowMs) && nowMs - postedAtMs <= windowMs) {
10421
- return { post: false, key, reason: "recent_duplicate" };
10422
- }
10423
- if (entry?.message === message) return { post: false, key, reason: "latest_equivalent" };
10424
- }
10425
- return { post: true, key };
10426
- }
10427
- function recordClickUpWorktreeFailureComment({ ledgerPath, taskId, message, posted = true, skippedReason = null, at = /* @__PURE__ */ new Date() } = {}) {
10428
- appendClickUpCommentLedgerEntry(ledgerPath, {
10429
- key: clickUpWorktreeFailureCommentKey({ taskId, message }),
10430
- taskId,
10431
- eventType: "worktree_failure",
10432
- action: posted ? "worktree_failure_comment_posted" : "worktree_failure_comment_skipped",
10433
- message,
10434
- posted,
10435
- skippedReason,
10436
- postedAt: at.toISOString()
10437
- });
10438
- }
10439
10372
  function clickUpTaskIdFromPayload(payload = {}) {
10440
10373
  return String(payload.task_id || payload.taskId || payload.task?.id || payload.task?.task_id || "").trim();
10441
10374
  }
@@ -10569,12 +10502,16 @@ function clearClickUpPendingSessionMetadata(metadata, metadataKey) {
10569
10502
  delete cursor[parts[parts.length - 1]];
10570
10503
  return JSON.stringify(sortJsonValue(root), null, 2);
10571
10504
  }
10572
- async function openCodeSessionExists(client, sessionId) {
10505
+ function withOptionalDirectoryQuery(payload, directory) {
10506
+ if (!directory) return payload;
10507
+ return { ...payload, query: { ...payload.query || {}, directory } };
10508
+ }
10509
+ async function openCodeSessionExists(client, sessionId, { directory = "" } = {}) {
10573
10510
  const getAttempts = [
10574
- { path: { id: sessionId } },
10575
- { path: { sessionID: sessionId } },
10576
- { sessionID: sessionId },
10577
- { id: sessionId }
10511
+ withOptionalDirectoryQuery({ path: { id: sessionId } }, directory),
10512
+ withOptionalDirectoryQuery({ path: { sessionID: sessionId } }, directory),
10513
+ directory ? { sessionID: sessionId, directory } : { sessionID: sessionId },
10514
+ directory ? { id: sessionId, directory } : { id: sessionId }
10578
10515
  ];
10579
10516
  if (typeof client?.session?.get === "function") {
10580
10517
  for (const attempt of getAttempts) {
@@ -10586,10 +10523,10 @@ async function openCodeSessionExists(client, sessionId) {
10586
10523
  }
10587
10524
  }
10588
10525
  const messageAttempts = [
10589
- { path: { id: sessionId }, query: { limit: 1 } },
10590
- { path: { sessionID: sessionId }, query: { limit: 1 } },
10591
- { sessionID: sessionId, limit: 1 },
10592
- { id: sessionId, limit: 1 }
10526
+ { path: { id: sessionId }, query: { limit: 1, ...directory ? { directory } : {} } },
10527
+ { path: { sessionID: sessionId }, query: { limit: 1, ...directory ? { directory } : {} } },
10528
+ directory ? { sessionID: sessionId, directory, limit: 1 } : { sessionID: sessionId, limit: 1 },
10529
+ directory ? { id: sessionId, directory, limit: 1 } : { id: sessionId, limit: 1 }
10593
10530
  ];
10594
10531
  if (typeof client?.session?.messages === "function") {
10595
10532
  for (const attempt of messageAttempts) {
@@ -10993,6 +10930,11 @@ async function verifyOpenCodeSessionEventDelivery(client, { sessionId, directory
10993
10930
  }
10994
10931
  return { ok: false, reason: lastError };
10995
10932
  }
10933
+ async function postClickUpLaunchFailureComment({ clickupClient, worktree, taskId, reason, source } = {}) {
10934
+ void clickupClient;
10935
+ appendClickUpWebhookLocalLog(worktree, { type: "launch_failure_comment_skipped", taskId, reason, source, policy: "local_log_only" });
10936
+ return { ok: false, skipped: true, reason: "local_log_only" };
10937
+ }
10996
10938
  async function applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason, source, tagName = CLICKUP_BLOCKER_TAG_NAME } = {}) {
10997
10939
  if (typeof clickupClient?.addTaskTag !== "function") {
10998
10940
  appendClickUpWebhookLocalLog(worktree, { type: "blocker_tag_unavailable", taskId, reason, source, tagName });
@@ -11022,7 +10964,17 @@ function openCodeBlockingPromptVerification(result, sessionId) {
11022
10964
  }
11023
10965
  async function deliverClickUpSessionEventWithVerification({ openCodeClient, sendSessionEvent, clickupClient, worktree, taskId, sessionId, agent, text, directory, opencodeBaseUrl, directPrompt = false, acceptPromptAdmission = false, eventMarkers = [], verifySessionEventDelivery = verifyOpenCodeSessionEventDelivery, applyBlockerOnFailure = true } = {}) {
11024
10966
  const beforeMessages = await readOpenCodeSessionMessages(openCodeClient, { sessionId, directory, limit: 50 }).catch(() => null);
11025
- const sendResult = await sendSessionEvent(openCodeClient, { sessionId, agent, text, directory, opencodeBaseUrl, direct: directPrompt, allowDirectFallback: directPrompt });
10967
+ let sendResult;
10968
+ try {
10969
+ sendResult = await sendSessionEvent(openCodeClient, { sessionId, agent, text, directory, opencodeBaseUrl, direct: directPrompt, allowDirectFallback: directPrompt });
10970
+ } catch (error) {
10971
+ const reason2 = error.message || "message_delivery_failed";
10972
+ appendClickUpWebhookLocalLog(worktree, { type: "message_delivery_failed", taskId, sessionId, reason: reason2, fallbackAttempted: false });
10973
+ if (!applyBlockerOnFailure) return { ok: false, action: "message_delivery_failed", reason: reason2, taskId, sessionId, fallbackAttempted: false };
10974
+ const blocker2 = await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason: reason2, source: "delivery_send_failed" });
10975
+ const launchFailureComment2 = await postClickUpLaunchFailureComment({ clickupClient, worktree, taskId, reason: reason2, source: "delivery_send_failed" });
10976
+ return { ok: false, action: "message_delivery_failed", reason: reason2, taskId, sessionId, fallbackAttempted: false, blockerTag: blocker2, launchFailureComment: launchFailureComment2 };
10977
+ }
11026
10978
  let blockingPromptVerification = null;
11027
10979
  let admissionVerification = null;
11028
10980
  try {
@@ -11032,7 +10984,8 @@ async function deliverClickUpSessionEventWithVerification({ openCodeClient, send
11032
10984
  appendClickUpWebhookLocalLog(worktree, { type: "message_delivery_failed", taskId, sessionId, reason: error.message, fallbackAttempted: false });
11033
10985
  if (!applyBlockerOnFailure) return { ok: false, action: "message_delivery_failed", reason: error.message, taskId, sessionId, fallbackAttempted: false };
11034
10986
  const blocker2 = await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason: error.message, source: "delivery_admission_failed" });
11035
- return { ok: false, action: "message_delivery_failed", reason: error.message, taskId, sessionId, fallbackAttempted: false, blockerTag: blocker2 };
10987
+ const launchFailureComment2 = await postClickUpLaunchFailureComment({ clickupClient, worktree, taskId, reason: error.message, source: "delivery_admission_failed" });
10988
+ return { ok: false, action: "message_delivery_failed", reason: error.message, taskId, sessionId, fallbackAttempted: false, blockerTag: blocker2, launchFailureComment: launchFailureComment2 };
11036
10989
  }
11037
10990
  if (blockingPromptVerification) return { ok: true, verification: blockingPromptVerification, admissionVerification: null, fallback: false };
11038
10991
  if (admissionVerification && acceptPromptAdmission) {
@@ -11047,7 +11000,8 @@ async function deliverClickUpSessionEventWithVerification({ openCodeClient, send
11047
11000
  appendClickUpWebhookLocalLog(worktree, { type: "message_delivery_failed", taskId, sessionId, reason, fallbackAttempted: false, httpFallbackDisabled: Boolean(opencodeBaseUrl) });
11048
11001
  if (!applyBlockerOnFailure) return { ok: false, action: "message_delivery_failed", reason, taskId, sessionId, fallbackAttempted: false };
11049
11002
  const blocker = await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason, source: "delivery_verification_failed" });
11050
- return { ok: false, action: "message_delivery_failed", reason, taskId, sessionId, fallbackAttempted: false, blockerTag: blocker };
11003
+ const launchFailureComment = await postClickUpLaunchFailureComment({ clickupClient, worktree, taskId, reason, source: "delivery_verification_failed" });
11004
+ return { ok: false, action: "message_delivery_failed", reason, taskId, sessionId, fallbackAttempted: false, blockerTag: blocker, launchFailureComment };
11051
11005
  }
11052
11006
  async function recoverClickUpPmSession({ openCodeClient, sendSessionEvent, clickupClient, worktree, taskId, staleSessionId, sessionTitle, taskRoute, metadataWithRouting, config, prompt, eventMarkers = [], deliveryEvidencePath, evidencePath, eventKey, createSession, verifySessionEventDelivery } = {}) {
11053
11007
  appendClickUpWebhookLocalLog(worktree, { type: "pm_session_recovery_started", taskId, staleSessionId, worktree: taskRoute?.worktree });
@@ -11057,12 +11011,14 @@ async function recoverClickUpPmSession({ openCodeClient, sendSessionEvent, click
11057
11011
  } catch (error) {
11058
11012
  appendClickUpWebhookLocalLog(worktree, { type: "pm_session_recovery_create_failed", taskId, staleSessionId, message: error.message });
11059
11013
  const blockerTag = await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason: "replacement_session_create_failed", source: "pm_session_recovery" });
11060
- 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 };
11014
+ const launchFailureComment = await postClickUpLaunchFailureComment({ clickupClient, worktree, taskId, reason: "replacement_session_create_failed", source: "pm_session_recovery" });
11015
+ return { ok: false, action: "message_delivery_failed", reason: "replacement_session_create_failed", taskId, sessionId: staleSessionId, replacementAttempted: true, blockerTag, launchFailureComment, eventKey, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath, evidencePath };
11061
11016
  }
11062
11017
  if (!String(replacementSessionId || "").startsWith("ses_")) {
11063
11018
  appendClickUpWebhookLocalLog(worktree, { type: "pm_session_recovery_create_invalid", taskId, staleSessionId, replacementSessionId });
11064
11019
  const blockerTag = await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason: "replacement_session_create_failed", source: "pm_session_recovery" });
11065
- 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 };
11020
+ const launchFailureComment = await postClickUpLaunchFailureComment({ clickupClient, worktree, taskId, reason: "replacement_session_create_failed", source: "pm_session_recovery" });
11021
+ return { ok: false, action: "message_delivery_failed", reason: "replacement_session_create_failed", taskId, sessionId: staleSessionId, replacementAttempted: true, blockerTag, launchFailureComment, eventKey, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath, evidencePath };
11066
11022
  }
11067
11023
  const replacementDelivery = await deliverClickUpSessionEventWithVerification({
11068
11024
  openCodeClient,
@@ -11084,7 +11040,8 @@ async function recoverClickUpPmSession({ openCodeClient, sendSessionEvent, click
11084
11040
  if (!replacementDelivery.ok) {
11085
11041
  appendClickUpWebhookLocalLog(worktree, { type: "pm_session_recovery_delivery_failed", taskId, staleSessionId, replacementSessionId, reason: replacementDelivery.reason });
11086
11042
  const blockerTag = await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason: replacementDelivery.reason || "replacement_delivery_failed", source: "pm_session_recovery" });
11087
- return { ...replacementDelivery, sessionId: staleSessionId, replacementSessionId, replacementAttempted: true, blockerTag, eventKey, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath, evidencePath };
11043
+ const launchFailureComment = replacementDelivery.launchFailureComment || await postClickUpLaunchFailureComment({ clickupClient, worktree, taskId, reason: replacementDelivery.reason || "replacement_delivery_failed", source: "pm_session_recovery" });
11044
+ return { ...replacementDelivery, sessionId: staleSessionId, replacementSessionId, replacementAttempted: true, blockerTag, launchFailureComment, eventKey, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath, evidencePath };
11088
11045
  }
11089
11046
  const replacementMetadata = clearClickUpPendingSessionMetadata(setClickUpSessionMetadata(metadataWithRouting, config.routing.metadataKey, replacementSessionId), config.routing.metadataKey);
11090
11047
  await clickupClient.updateTaskMetadata({ taskId, fieldId: config.routing.metadataFieldId, value: replacementMetadata });
@@ -11368,8 +11325,26 @@ async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, w
11368
11325
  if (!existingSessionId) {
11369
11326
  let pendingSessionId = getNestedMetadataValue(metadata, clickUpPendingSessionKey(config.routing.metadataKey)) || state.pendingSessions?.[taskId];
11370
11327
  if (!pendingSessionId) {
11371
- pendingSessionId = await createSession(openCodeClient, { title: sessionTitle, directory: taskRoute.worktree, agent: config.routing.targetAgent });
11372
- if (!String(pendingSessionId || "").startsWith("ses_")) return { ok: false, action: "error", reason: "session_create_failed", taskId };
11328
+ try {
11329
+ pendingSessionId = await createSession(openCodeClient, { title: sessionTitle, directory: taskRoute.worktree, agent: config.routing.targetAgent });
11330
+ } catch (error) {
11331
+ appendClickUpWebhookLocalLog(worktree, { type: "pm_session_create_failed", taskId, worktree: taskRoute.worktree, message: error.message });
11332
+ const blockerTag = await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason: "session_create_failed", source: "route_create_session" });
11333
+ const launchFailureComment = await postClickUpLaunchFailureComment({ clickupClient, worktree, taskId, reason: "session_create_failed", source: "route_create_session" });
11334
+ return finish({ ok: false, action: "error", reason: "session_create_failed", taskId, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath, evidencePath: routingMetadata.evidence_path, blockerTag, launchFailureComment });
11335
+ }
11336
+ if (!String(pendingSessionId || "").startsWith("ses_")) {
11337
+ appendClickUpWebhookLocalLog(worktree, { type: "pm_session_create_invalid", taskId, worktree: taskRoute.worktree, sessionId: pendingSessionId || null });
11338
+ const blockerTag = await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason: "session_create_failed", source: "route_create_session" });
11339
+ const launchFailureComment = await postClickUpLaunchFailureComment({ clickupClient, worktree, taskId, reason: "session_create_failed", source: "route_create_session" });
11340
+ return finish({ ok: false, action: "error", reason: "session_create_failed", taskId, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath, evidencePath: routingMetadata.evidence_path, blockerTag, launchFailureComment });
11341
+ }
11342
+ if (!await sessionExists(openCodeClient, pendingSessionId, { directory: taskRoute.worktree })) {
11343
+ appendClickUpWebhookLocalLog(worktree, { type: "pm_session_create_directory_unverified", taskId, worktree: taskRoute.worktree, sessionId: pendingSessionId });
11344
+ const blockerTag = await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason: "session_directory_unverified", source: "route_create_session" });
11345
+ const launchFailureComment = await postClickUpLaunchFailureComment({ clickupClient, worktree, taskId, reason: "session_directory_unverified", source: "route_create_session" });
11346
+ return finish({ ok: false, action: "error", reason: "session_directory_unverified", taskId, sessionId: pendingSessionId, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath, evidencePath: routingMetadata.evidence_path, blockerTag, launchFailureComment });
11347
+ }
11373
11348
  if (saveState) {
11374
11349
  saveState({
11375
11350
  ...state,
@@ -11385,7 +11360,20 @@ async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, w
11385
11360
  throw error;
11386
11361
  }
11387
11362
  const delivery = await deliverClickUpSessionEventWithVerification({ openCodeClient, sendSessionEvent, clickupClient, worktree, taskId, sessionId: pendingSessionId, agent: config.routing.targetAgent, text: prompt, directory: taskRoute.worktree, opencodeBaseUrl: config.opencode?.baseUrl, directPrompt: config.opencode?.promptDelivery === "http", acceptPromptAdmission: config.opencode?.acceptPromptAdmission === true, eventMarkers: [taskId, eventType], verifySessionEventDelivery });
11388
- if (!delivery.ok) return finish({ ...delivery, eventKey, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath, evidencePath: routingMetadata.evidence_path });
11363
+ if (!delivery.ok) {
11364
+ const failedMetadata = clearClickUpPendingSessionMetadata(metadataWithRouting, config.routing.metadataKey);
11365
+ let pendingCleared = false;
11366
+ try {
11367
+ await clickupClient.updateTaskMetadata({ taskId, fieldId: config.routing.metadataFieldId, value: failedMetadata });
11368
+ pendingCleared = true;
11369
+ } catch (error) {
11370
+ appendClickUpWebhookLocalLog(worktree, { type: "pending_session_clear_failed", taskId, sessionId: pendingSessionId, message: error.message });
11371
+ }
11372
+ const { [taskId]: _failedPending, ...remainingPending2 } = stateToPersist.pendingSessions || {};
11373
+ stateToPersist = { ...stateToPersist, pendingSessions: remainingPending2 };
11374
+ appendClickUpWebhookLocalLog(worktree, { type: "pending_session_delivery_failed", taskId, sessionId: pendingSessionId, reason: delivery.reason || "message_delivery_failed", pendingCleared });
11375
+ return finish({ ...delivery, eventKey, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath, evidencePath: routingMetadata.evidence_path, pendingCleared });
11376
+ }
11389
11377
  const nextMetadata = clearClickUpPendingSessionMetadata(setClickUpSessionMetadata(pendingMetadata, config.routing.metadataKey, pendingSessionId), config.routing.metadataKey);
11390
11378
  await clickupClient.updateTaskMetadata({ taskId, fieldId: config.routing.metadataFieldId, value: nextMetadata });
11391
11379
  const { [taskId]: _completedPending, ...remainingPending } = stateToPersist.pendingSessions || {};
@@ -11393,7 +11381,7 @@ async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, w
11393
11381
  return finish({ ok: true, action: "created_session", taskId, sessionId: pendingSessionId, eventKey, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath, evidencePath: routingMetadata.evidence_path, deliveryVerification: delivery.verification, deliveryAdmission: delivery.admissionVerification, deliveryFallback: delivery.fallback, deliveryAttempts: delivery.fallback ? 2 : 1 });
11394
11382
  }
11395
11383
  const sessionId = String(existingSessionId);
11396
- if (await sessionExists(openCodeClient, sessionId)) {
11384
+ if (await sessionExists(openCodeClient, sessionId, { directory: taskRoute.worktree })) {
11397
11385
  if (typeof clickupClient?.updateTaskMetadata === "function") await clickupClient.updateTaskMetadata({ taskId, fieldId: config.routing.metadataFieldId, value: metadataWithRouting });
11398
11386
  const delivery = await deliverClickUpSessionEventWithVerification({ openCodeClient, sendSessionEvent, clickupClient, worktree, taskId, sessionId, agent: config.routing.targetAgent, text: prompt, directory: taskRoute.worktree, opencodeBaseUrl: config.opencode?.baseUrl, directPrompt: config.opencode?.promptDelivery === "http", acceptPromptAdmission: config.opencode?.acceptPromptAdmission === true, eventMarkers: [taskId, eventType], verifySessionEventDelivery, applyBlockerOnFailure: false });
11399
11387
  if (!delivery.ok) {
@@ -11521,6 +11509,7 @@ async function reconcileClickUpStartup({ config, state = {}, worktree = process.
11521
11509
  routed.undelivered += 1;
11522
11510
  appendClickUpWebhookLocalLog(worktree, { type: "startup_reconciliation_task_undelivered", taskId, action: result?.action || "error", sessionId: routeSummary.sessionId, reason: result.reason || "startup_reconciliation_route_failed" });
11523
11511
  if (!result.blockerTag) await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason: result.reason || "startup_reconciliation_route_failed", source: "startup_reconciliation_task" });
11512
+ if (!result.launchFailureComment) await postClickUpLaunchFailureComment({ clickupClient, worktree, taskId, reason: result.reason || "startup_reconciliation_route_failed", source: "startup_reconciliation_task" });
11524
11513
  } else {
11525
11514
  routed.undelivered += 1;
11526
11515
  appendClickUpWebhookLocalLog(worktree, { type: "startup_reconciliation_task_undelivered", taskId, action: routeSummary.action, sessionId: routeSummary.sessionId, reason: "route_not_actionable" });
@@ -11628,14 +11617,13 @@ function clickUpListenerKey(config) {
11628
11617
  }
11629
11618
  function clickUpListenerFingerprint({ config, state, worktree, clickupClient, openCodeClient } = {}) {
11630
11619
  return JSON.stringify({
11620
+ teamId: config?.teamId || "",
11631
11621
  publicUrl: config?.webhook?.publicUrl || "",
11632
11622
  path: clickUpWebhookExpectedPath(config),
11633
- worktree: path5.resolve(worktree || process.cwd()),
11623
+ location: config?.webhook?.location || {},
11634
11624
  webhookId: state?.webhookId || "",
11635
11625
  secret: state?.secret || "",
11636
- events: config?.webhook?.events || [],
11637
- clickupClient: objectIdentity(clickupClient),
11638
- openCodeClient: objectIdentity(openCodeClient)
11626
+ events: config?.webhook?.events || []
11639
11627
  });
11640
11628
  }
11641
11629
  function startClickUpWebhookListener({ config, state, worktree, clickupClient, openCodeClient, listenerRegistry = activeClickUpWebhookListeners } = {}) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defend-tech/opencode-optima",
3
- "version": "0.1.62",
3
+ "version": "0.1.64",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+ssh://git@github.com/defend-tech/opencode-optima.git"