@defend-tech/opencode-optima 0.1.36 → 0.1.38

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
@@ -9600,6 +9600,96 @@ async function sendOpenCodeSessionEvent(client, { sessionId, agent, text, direct
9600
9600
  if (directBaseUrl) return sendOpenCodeSessionEventDirect({ baseUrl: directBaseUrl, sessionId, text, fetchImpl });
9601
9601
  throw new Error("OpenCode client does not expose session.prompt or session.promptAsync.");
9602
9602
  }
9603
+ function normalizeOpenCodeSessionMessages(result) {
9604
+ const data = result?.data ?? result;
9605
+ if (Array.isArray(data)) return [...data];
9606
+ if (Array.isArray(data?.messages)) return [...data.messages];
9607
+ if (Array.isArray(data?.items)) return [...data.items];
9608
+ return [];
9609
+ }
9610
+ async function readOpenCodeSessionMessages(client, { sessionId, limit = 20 } = {}) {
9611
+ if (typeof client?.session?.messages !== "function") return null;
9612
+ const attempts = [
9613
+ { path: { id: sessionId }, query: { limit } },
9614
+ { path: { sessionID: sessionId }, query: { limit } },
9615
+ { id: sessionId, limit },
9616
+ { sessionID: sessionId, limit }
9617
+ ];
9618
+ let firstError = null;
9619
+ for (const attempt of attempts) {
9620
+ try {
9621
+ return normalizeOpenCodeSessionMessages(await client.session.messages(attempt));
9622
+ } catch (error) {
9623
+ firstError ??= error;
9624
+ }
9625
+ }
9626
+ throw firstError;
9627
+ }
9628
+ function openCodeMessageText(message) {
9629
+ const parts = [message?.text, message?.content, message?.message, message?.body?.text, message?.data?.text];
9630
+ const partList = Array.isArray(message?.parts) ? message.parts : Array.isArray(message?.body?.parts) ? message.body.parts : [];
9631
+ for (const part of partList) parts.push(part?.text, part?.content);
9632
+ return parts.filter((value) => typeof value === "string").join("\n");
9633
+ }
9634
+ async function verifyOpenCodeSessionEventDelivery(client, { sessionId, beforeMessages = null, expectedText = "", markers = [], attempts = 3, delayMs = 25 } = {}) {
9635
+ let lastError = "message_verification_unavailable";
9636
+ for (let attempt = 0; attempt < Math.max(1, attempts); attempt += 1) {
9637
+ try {
9638
+ const afterMessages = await readOpenCodeSessionMessages(client, { sessionId, limit: 50 });
9639
+ if (!afterMessages) return { ok: true, method: "verification_unavailable", skipped: true };
9640
+ const beforeCount = Array.isArray(beforeMessages) ? beforeMessages.length : null;
9641
+ if (beforeCount !== null && afterMessages.length > beforeCount) return { ok: true, method: "message_count", beforeCount, afterCount: afterMessages.length };
9642
+ const textNeedles = [expectedText, ...markers].map((value) => String(value || "").trim()).filter(Boolean);
9643
+ const haystack = afterMessages.slice(-20).map(openCodeMessageText).join("\n");
9644
+ const matched = textNeedles.find((needle) => haystack.includes(needle));
9645
+ if (matched) return { ok: true, method: "message_text", marker: matched, beforeCount, afterCount: afterMessages.length };
9646
+ lastError = "message_not_visible";
9647
+ } catch (error) {
9648
+ lastError = error.message || "message_verification_failed";
9649
+ }
9650
+ if (attempt < attempts - 1 && delayMs > 0) await new Promise((resolve) => setTimeout(resolve, delayMs));
9651
+ }
9652
+ return { ok: false, reason: lastError };
9653
+ }
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 };
9667
+ }
9668
+ try {
9669
+ await clickupClient.postTaskComment({ taskId, comment });
9670
+ return { ok: true, comment };
9671
+ } 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 };
9674
+ }
9675
+ }
9676
+ async function deliverClickUpSessionEventWithVerification({ openCodeClient, sendSessionEvent, clickupClient, worktree, taskId, sessionId, agent, text, directory, opencodeBaseUrl, eventMarkers = [], verifySessionEventDelivery = verifyOpenCodeSessionEventDelivery } = {}) {
9677
+ const beforeMessages = await readOpenCodeSessionMessages(openCodeClient, { sessionId, limit: 50 }).catch(() => null);
9678
+ await sendSessionEvent(openCodeClient, { sessionId, agent, text, directory, opencodeBaseUrl });
9679
+ let verification = await verifySessionEventDelivery(openCodeClient, { sessionId, beforeMessages, expectedText: text, markers: eventMarkers });
9680
+ if (verification?.ok) return { ok: true, verification, fallback: false };
9681
+ const canFallbackDirect = Boolean(opencodeBaseUrl);
9682
+ if (canFallbackDirect) {
9683
+ const retryBeforeMessages = await readOpenCodeSessionMessages(openCodeClient, { sessionId, limit: 50 }).catch(() => beforeMessages);
9684
+ await sendSessionEvent(openCodeClient, { sessionId, agent, text, directory, opencodeBaseUrl, direct: true });
9685
+ verification = await verifySessionEventDelivery(openCodeClient, { sessionId, beforeMessages: retryBeforeMessages, expectedText: text, markers: eventMarkers });
9686
+ if (verification?.ok) return { ok: true, verification, fallback: true };
9687
+ }
9688
+ const reason = verification?.reason || "message_delivery_failed";
9689
+ 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 };
9692
+ }
9603
9693
  function formatClickUpWebhookPrompt({ eventType, taskId, payload, branch = "", worktree = "", deliveryEvidencePath = "" }) {
9604
9694
  const comment = clickUpCommentFromPayload(payload);
9605
9695
  const commentText = clickUpCommentText(comment).trim();
@@ -9616,6 +9706,45 @@ function formatClickUpWebhookPrompt({ eventType, taskId, payload, branch = "", w
9616
9706
  deliveryEvidencePath ? `Final merge-trackable evidence must be written under ${deliveryEvidencePath}; use .optima only as local mirror/staging.` : null
9617
9707
  ].filter((part) => part !== null).join("\n");
9618
9708
  }
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
+ }
9619
9748
  function appendClickUpWebhookLocalLog(worktree, entry) {
9620
9749
  const logPath = clickUpWebhookLogPath(worktree);
9621
9750
  fs2.mkdirSync(path2.dirname(logPath), { recursive: true });
@@ -9792,7 +9921,7 @@ async function withClickUpTaskRouteLock(taskId, operation) {
9792
9921
  if (activeClickUpTaskRoutes.get(key) === current) activeClickUpTaskRoutes.delete(key);
9793
9922
  }
9794
9923
  }
9795
- async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, worktree = process.cwd(), clickupClient, openCodeClient, sessionExists = openCodeSessionExists, createSession = createOpenCodeSession, sendSessionEvent = sendOpenCodeSessionEvent, ensureTaskWorktree = ensureClickUpTaskWorktree, saveState = null, now = () => /* @__PURE__ */ new Date(), host = os.hostname(), commentLedgerPath = clickUpCommentLedgerPath(worktree) } = {}) {
9924
+ async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, worktree = process.cwd(), clickupClient, openCodeClient, sessionExists = openCodeSessionExists, createSession = createOpenCodeSession, sendSessionEvent = sendOpenCodeSessionEvent, ensureTaskWorktree = ensureClickUpTaskWorktree, verifySessionEventDelivery = verifyOpenCodeSessionEventDelivery, saveState = null, now = () => /* @__PURE__ */ new Date(), host = os.hostname(), commentLedgerPath = clickUpCommentLedgerPath(worktree) } = {}) {
9796
9925
  const eventType = clickUpEventType(payload);
9797
9926
  const eventKey = clickUpWebhookEventKey(payload);
9798
9927
  const remembered = rememberClickUpWebhookEvent(state, eventKey);
@@ -9882,24 +10011,26 @@ async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, w
9882
10011
  appendClickUpWebhookLocalLog(worktree, { type: "pending_session_metadata_failed", taskId, sessionId: pendingSessionId, message: error.message });
9883
10012
  throw error;
9884
10013
  }
9885
- await sendSessionEvent(openCodeClient, { sessionId: pendingSessionId, agent: config.routing.targetAgent, text: prompt, directory: taskRoute.worktree, opencodeBaseUrl: config.opencode?.baseUrl });
10014
+ const delivery = await deliverClickUpSessionEventWithVerification({ openCodeClient, sendSessionEvent, clickupClient, worktree, taskId, sessionId: pendingSessionId, agent: config.routing.targetAgent, text: prompt, directory: taskRoute.worktree, opencodeBaseUrl: config.opencode?.baseUrl, eventMarkers: [taskId, eventType], verifySessionEventDelivery });
10015
+ if (!delivery.ok) return finish({ ...delivery, eventKey, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath, evidencePath: routingMetadata.evidence_path });
9886
10016
  const nextMetadata = clearClickUpPendingSessionMetadata(setClickUpSessionMetadata(pendingMetadata, config.routing.metadataKey, pendingSessionId), config.routing.metadataKey);
9887
10017
  await clickupClient.updateTaskMetadata({ taskId, fieldId: config.routing.metadataFieldId, value: nextMetadata });
9888
10018
  const { [taskId]: _completedPending, ...remainingPending } = stateToPersist.pendingSessions || {};
9889
10019
  stateToPersist = { ...stateToPersist, pendingSessions: remainingPending };
9890
- return finish({ ok: true, action: "created_session", taskId, sessionId: pendingSessionId, eventKey });
10020
+ 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, deliveryFallback: delivery.fallback });
9891
10021
  }
9892
10022
  const sessionId = String(existingSessionId);
9893
10023
  if (await sessionExists(openCodeClient, sessionId)) {
9894
10024
  if (typeof clickupClient?.updateTaskMetadata === "function") await clickupClient.updateTaskMetadata({ taskId, fieldId: config.routing.metadataFieldId, value: metadataWithRouting });
9895
- await sendSessionEvent(openCodeClient, { sessionId, agent: config.routing.targetAgent, text: prompt, directory: taskRoute.worktree, opencodeBaseUrl: config.opencode?.baseUrl });
9896
- return finish({ ok: true, action: "sent_to_existing_session", taskId, sessionId, eventKey });
10025
+ const delivery = await deliverClickUpSessionEventWithVerification({ openCodeClient, sendSessionEvent, clickupClient, worktree, taskId, sessionId, agent: config.routing.targetAgent, text: prompt, directory: taskRoute.worktree, opencodeBaseUrl: config.opencode?.baseUrl, eventMarkers: [taskId, eventType], verifySessionEventDelivery });
10026
+ if (!delivery.ok) return finish({ ...delivery, eventKey, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath, evidencePath: routingMetadata.evidence_path });
10027
+ 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 });
9897
10028
  }
9898
10029
  const at = now().toISOString();
9899
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.`;
9900
10031
  appendClickUpWebhookLocalLog(worktree, { type: "missing_session", taskId, sessionId, host, at });
9901
10032
  await clickupClient.postTaskComment({ taskId, comment: incidentComment });
9902
- return finish({ ok: true, action: "missing_session_reported", taskId, sessionId, eventKey });
10033
+ return finish({ ok: true, action: "missing_session_reported", taskId, sessionId, eventKey, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath, evidencePath: routingMetadata.evidence_path });
9903
10034
  }
9904
10035
  async function routeClickUpWebhookEvent(options = {}) {
9905
10036
  const taskId = clickUpTaskIdFromPayload(options.payload || {});
@@ -9955,10 +10086,26 @@ async function reconcileClickUpStartup({ config, state = {}, worktree = process.
9955
10086
  saveState: persistState,
9956
10087
  now
9957
10088
  });
9958
- if (result?.ok && result.action !== "ignored") routed.assigned += 1;
10089
+ if (result?.ok && result.action !== "ignored") {
10090
+ routed.assigned += 1;
10091
+ await postClickUpStartupComment({
10092
+ clickupClient,
10093
+ worktree,
10094
+ taskId,
10095
+ type: "resume",
10096
+ comment: buildClickUpStartupResumeComment({ taskId, result })
10097
+ });
10098
+ }
9959
10099
  } catch (error) {
9960
10100
  routed.errors += 1;
9961
10101
  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
+ });
9962
10109
  }
9963
10110
  if (!lastWebhookMs || !clickupClient?.getTaskComments) continue;
9964
10111
  try {
@@ -11772,7 +11919,7 @@ Follow-up: use optima_prompt_workflow with session_id '${sessionId}' to check in
11772
11919
  }
11773
11920
  };
11774
11921
  }
11775
- OptimaPlugin.__internals = { BUNDLE_AGENTS_DIR, BUNDLE_ASSETS_DIR, CLICKUP_DEFINITION_DOC_PARENT, activeClickUpWebhookLifecycleRegistry, cleanupManagedClickUpWebhook, deleteClickUpWebhookBestEffort, BUNDLE_POLICIES_DIR, buildClickUpApplyPayloadResult, buildClickUpCreateSubtasksPayload, buildClickUpStartTaskPayload, buildClickUpSummaryPayload, buildClickUpTransitionPayload, buildOptimaAgents, clickUpCommentLedgerKey, clickUpCommentMentionsProductManager, clickUpWebhookAuditLogDir, clickUpWebhookAuditLogPath, createClickUpApiClient, deliveryEvidencePathForClickUpTask, ensureClickUpTaskWorktree, ensureClickUpWebhookSubscription, handleClickUpWebhookRequest, isClickUpWebhookStateActive, normalizeClickUpWebhookConfig, normalizeClickUpWebhookLogLevel, normalizeOpenCodeBaseUrl, readClickUpCommentLedger, readClickUpWebhookState, reconcileClickUpStartup, recordClickUpCommentVersionProcessed, resolveOptimaPluginOptions, resolveSecretReference, routeClickUpWebhookEvent, sendOpenCodeSessionEvent, sendOpenCodeSessionEventDirect, stableClickUpCommentVersionMarker, startClickUpWebhookListener, validateClickUpWebhookState, verifyClickUpSignature, writeClickUpWebhookState, decideClickUpStatusAction, deriveClickUpBranchName, deriveClickUpPendingSubtaskBranch, deriveClickUpPrTarget, deriveClickUpWorktree, determineClickUpMergeAuthority, ensureOptimaGitignoreRules, explicitSafeInputWorktree, finalApprovalAssignees, formatValidationResult, isIgnoredClickUpTaskType, isOptimaPluginPackageWorktree, isSafeWritableDirectory, loadHumansRegistry, mergeClickUpAgentMetadata, mergeClickUpSessionMetadata, migrateLegacyOptimaLayout, normalizeAgentMetadataJson, normalizeClickUpStatus, normalizeClickUpTaskType, normalizePromptResponseParts, normalizeWorkflowTaskPath, parseClickUpSubtasksMarkdown, parseHumansRegistry, parseMarkdownArtifact, parseMarkdownSections, preEstimateClickUpWork, readMarkdownArtifact, resolveHumanRoles, resolveSafeWorktree, safeWorktreeOrFailure, stripRawLogSections, validateMainWorkspaceBranchSafety, workflowFinalMessageFromPromptResponse };
11922
+ OptimaPlugin.__internals = { BUNDLE_AGENTS_DIR, BUNDLE_ASSETS_DIR, CLICKUP_DEFINITION_DOC_PARENT, activeClickUpWebhookLifecycleRegistry, cleanupManagedClickUpWebhook, deleteClickUpWebhookBestEffort, BUNDLE_POLICIES_DIR, buildClickUpApplyPayloadResult, buildClickUpCreateSubtasksPayload, buildClickUpStartTaskPayload, buildClickUpSummaryPayload, buildClickUpTransitionPayload, buildOptimaAgents, clickUpCommentLedgerKey, clickUpCommentMentionsProductManager, clickUpWebhookAuditLogDir, clickUpWebhookAuditLogPath, createClickUpApiClient, deliveryEvidencePathForClickUpTask, ensureClickUpTaskWorktree, ensureClickUpWebhookSubscription, handleClickUpWebhookRequest, isClickUpWebhookStateActive, normalizeClickUpWebhookConfig, normalizeClickUpWebhookLogLevel, normalizeOpenCodeBaseUrl, readClickUpCommentLedger, readClickUpWebhookState, reconcileClickUpStartup, recordClickUpCommentVersionProcessed, resolveOptimaPluginOptions, resolveSecretReference, routeClickUpWebhookEvent, sendOpenCodeSessionEvent, sendOpenCodeSessionEventDirect, verifyOpenCodeSessionEventDelivery, stableClickUpCommentVersionMarker, startClickUpWebhookListener, validateClickUpWebhookState, verifyClickUpSignature, writeClickUpWebhookState, decideClickUpStatusAction, deriveClickUpBranchName, deriveClickUpPendingSubtaskBranch, deriveClickUpPrTarget, deriveClickUpWorktree, determineClickUpMergeAuthority, ensureOptimaGitignoreRules, explicitSafeInputWorktree, finalApprovalAssignees, formatValidationResult, isIgnoredClickUpTaskType, isOptimaPluginPackageWorktree, isSafeWritableDirectory, loadHumansRegistry, mergeClickUpAgentMetadata, mergeClickUpSessionMetadata, migrateLegacyOptimaLayout, normalizeAgentMetadataJson, normalizeClickUpStatus, normalizeClickUpTaskType, normalizePromptResponseParts, normalizeWorkflowTaskPath, parseClickUpSubtasksMarkdown, parseHumansRegistry, parseMarkdownArtifact, parseMarkdownSections, preEstimateClickUpWork, readMarkdownArtifact, resolveHumanRoles, resolveSafeWorktree, safeWorktreeOrFailure, stripRawLogSections, validateMainWorkspaceBranchSafety, workflowFinalMessageFromPromptResponse };
11776
11923
  export {
11777
11924
  OptimaPlugin as default
11778
11925
  };
@@ -9607,6 +9607,96 @@ async function sendOpenCodeSessionEvent(client, { sessionId, agent, text, direct
9607
9607
  if (directBaseUrl) return sendOpenCodeSessionEventDirect({ baseUrl: directBaseUrl, sessionId, text, fetchImpl });
9608
9608
  throw new Error("OpenCode client does not expose session.prompt or session.promptAsync.");
9609
9609
  }
9610
+ function normalizeOpenCodeSessionMessages(result) {
9611
+ const data = result?.data ?? result;
9612
+ if (Array.isArray(data)) return [...data];
9613
+ if (Array.isArray(data?.messages)) return [...data.messages];
9614
+ if (Array.isArray(data?.items)) return [...data.items];
9615
+ return [];
9616
+ }
9617
+ async function readOpenCodeSessionMessages(client, { sessionId, limit = 20 } = {}) {
9618
+ if (typeof client?.session?.messages !== "function") return null;
9619
+ const attempts = [
9620
+ { path: { id: sessionId }, query: { limit } },
9621
+ { path: { sessionID: sessionId }, query: { limit } },
9622
+ { id: sessionId, limit },
9623
+ { sessionID: sessionId, limit }
9624
+ ];
9625
+ let firstError = null;
9626
+ for (const attempt of attempts) {
9627
+ try {
9628
+ return normalizeOpenCodeSessionMessages(await client.session.messages(attempt));
9629
+ } catch (error) {
9630
+ firstError ??= error;
9631
+ }
9632
+ }
9633
+ throw firstError;
9634
+ }
9635
+ function openCodeMessageText(message) {
9636
+ const parts = [message?.text, message?.content, message?.message, message?.body?.text, message?.data?.text];
9637
+ const partList = Array.isArray(message?.parts) ? message.parts : Array.isArray(message?.body?.parts) ? message.body.parts : [];
9638
+ for (const part of partList) parts.push(part?.text, part?.content);
9639
+ return parts.filter((value) => typeof value === "string").join("\n");
9640
+ }
9641
+ async function verifyOpenCodeSessionEventDelivery(client, { sessionId, beforeMessages = null, expectedText = "", markers = [], attempts = 3, delayMs = 25 } = {}) {
9642
+ let lastError = "message_verification_unavailable";
9643
+ for (let attempt = 0; attempt < Math.max(1, attempts); attempt += 1) {
9644
+ try {
9645
+ const afterMessages = await readOpenCodeSessionMessages(client, { sessionId, limit: 50 });
9646
+ if (!afterMessages) return { ok: true, method: "verification_unavailable", skipped: true };
9647
+ const beforeCount = Array.isArray(beforeMessages) ? beforeMessages.length : null;
9648
+ if (beforeCount !== null && afterMessages.length > beforeCount) return { ok: true, method: "message_count", beforeCount, afterCount: afterMessages.length };
9649
+ const textNeedles = [expectedText, ...markers].map((value) => String(value || "").trim()).filter(Boolean);
9650
+ const haystack = afterMessages.slice(-20).map(openCodeMessageText).join("\n");
9651
+ const matched = textNeedles.find((needle) => haystack.includes(needle));
9652
+ if (matched) return { ok: true, method: "message_text", marker: matched, beforeCount, afterCount: afterMessages.length };
9653
+ lastError = "message_not_visible";
9654
+ } catch (error) {
9655
+ lastError = error.message || "message_verification_failed";
9656
+ }
9657
+ if (attempt < attempts - 1 && delayMs > 0) await new Promise((resolve) => setTimeout(resolve, delayMs));
9658
+ }
9659
+ return { ok: false, reason: lastError };
9660
+ }
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 };
9674
+ }
9675
+ try {
9676
+ await clickupClient.postTaskComment({ taskId, comment });
9677
+ return { ok: true, comment };
9678
+ } 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 };
9681
+ }
9682
+ }
9683
+ async function deliverClickUpSessionEventWithVerification({ openCodeClient, sendSessionEvent, clickupClient, worktree, taskId, sessionId, agent, text, directory, opencodeBaseUrl, eventMarkers = [], verifySessionEventDelivery = verifyOpenCodeSessionEventDelivery } = {}) {
9684
+ const beforeMessages = await readOpenCodeSessionMessages(openCodeClient, { sessionId, limit: 50 }).catch(() => null);
9685
+ await sendSessionEvent(openCodeClient, { sessionId, agent, text, directory, opencodeBaseUrl });
9686
+ let verification = await verifySessionEventDelivery(openCodeClient, { sessionId, beforeMessages, expectedText: text, markers: eventMarkers });
9687
+ if (verification?.ok) return { ok: true, verification, fallback: false };
9688
+ const canFallbackDirect = Boolean(opencodeBaseUrl);
9689
+ if (canFallbackDirect) {
9690
+ const retryBeforeMessages = await readOpenCodeSessionMessages(openCodeClient, { sessionId, limit: 50 }).catch(() => beforeMessages);
9691
+ await sendSessionEvent(openCodeClient, { sessionId, agent, text, directory, opencodeBaseUrl, direct: true });
9692
+ verification = await verifySessionEventDelivery(openCodeClient, { sessionId, beforeMessages: retryBeforeMessages, expectedText: text, markers: eventMarkers });
9693
+ if (verification?.ok) return { ok: true, verification, fallback: true };
9694
+ }
9695
+ const reason = verification?.reason || "message_delivery_failed";
9696
+ 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 };
9699
+ }
9610
9700
  function formatClickUpWebhookPrompt({ eventType, taskId, payload, branch = "", worktree = "", deliveryEvidencePath = "" }) {
9611
9701
  const comment = clickUpCommentFromPayload(payload);
9612
9702
  const commentText = clickUpCommentText(comment).trim();
@@ -9623,6 +9713,45 @@ function formatClickUpWebhookPrompt({ eventType, taskId, payload, branch = "", w
9623
9713
  deliveryEvidencePath ? `Final merge-trackable evidence must be written under ${deliveryEvidencePath}; use .optima only as local mirror/staging.` : null
9624
9714
  ].filter((part) => part !== null).join("\n");
9625
9715
  }
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
+ }
9626
9755
  function appendClickUpWebhookLocalLog(worktree, entry) {
9627
9756
  const logPath = clickUpWebhookLogPath(worktree);
9628
9757
  fs2.mkdirSync(path2.dirname(logPath), { recursive: true });
@@ -9799,7 +9928,7 @@ async function withClickUpTaskRouteLock(taskId, operation) {
9799
9928
  if (activeClickUpTaskRoutes.get(key) === current) activeClickUpTaskRoutes.delete(key);
9800
9929
  }
9801
9930
  }
9802
- async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, worktree = process.cwd(), clickupClient, openCodeClient, sessionExists = openCodeSessionExists, createSession = createOpenCodeSession, sendSessionEvent = sendOpenCodeSessionEvent, ensureTaskWorktree = ensureClickUpTaskWorktree, saveState = null, now = () => /* @__PURE__ */ new Date(), host = os.hostname(), commentLedgerPath = clickUpCommentLedgerPath(worktree) } = {}) {
9931
+ async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, worktree = process.cwd(), clickupClient, openCodeClient, sessionExists = openCodeSessionExists, createSession = createOpenCodeSession, sendSessionEvent = sendOpenCodeSessionEvent, ensureTaskWorktree = ensureClickUpTaskWorktree, verifySessionEventDelivery = verifyOpenCodeSessionEventDelivery, saveState = null, now = () => /* @__PURE__ */ new Date(), host = os.hostname(), commentLedgerPath = clickUpCommentLedgerPath(worktree) } = {}) {
9803
9932
  const eventType = clickUpEventType(payload);
9804
9933
  const eventKey = clickUpWebhookEventKey(payload);
9805
9934
  const remembered = rememberClickUpWebhookEvent(state, eventKey);
@@ -9889,24 +10018,26 @@ async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, w
9889
10018
  appendClickUpWebhookLocalLog(worktree, { type: "pending_session_metadata_failed", taskId, sessionId: pendingSessionId, message: error.message });
9890
10019
  throw error;
9891
10020
  }
9892
- await sendSessionEvent(openCodeClient, { sessionId: pendingSessionId, agent: config.routing.targetAgent, text: prompt, directory: taskRoute.worktree, opencodeBaseUrl: config.opencode?.baseUrl });
10021
+ const delivery = await deliverClickUpSessionEventWithVerification({ openCodeClient, sendSessionEvent, clickupClient, worktree, taskId, sessionId: pendingSessionId, agent: config.routing.targetAgent, text: prompt, directory: taskRoute.worktree, opencodeBaseUrl: config.opencode?.baseUrl, eventMarkers: [taskId, eventType], verifySessionEventDelivery });
10022
+ if (!delivery.ok) return finish({ ...delivery, eventKey, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath, evidencePath: routingMetadata.evidence_path });
9893
10023
  const nextMetadata = clearClickUpPendingSessionMetadata(setClickUpSessionMetadata(pendingMetadata, config.routing.metadataKey, pendingSessionId), config.routing.metadataKey);
9894
10024
  await clickupClient.updateTaskMetadata({ taskId, fieldId: config.routing.metadataFieldId, value: nextMetadata });
9895
10025
  const { [taskId]: _completedPending, ...remainingPending } = stateToPersist.pendingSessions || {};
9896
10026
  stateToPersist = { ...stateToPersist, pendingSessions: remainingPending };
9897
- return finish({ ok: true, action: "created_session", taskId, sessionId: pendingSessionId, eventKey });
10027
+ 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, deliveryFallback: delivery.fallback });
9898
10028
  }
9899
10029
  const sessionId = String(existingSessionId);
9900
10030
  if (await sessionExists(openCodeClient, sessionId)) {
9901
10031
  if (typeof clickupClient?.updateTaskMetadata === "function") await clickupClient.updateTaskMetadata({ taskId, fieldId: config.routing.metadataFieldId, value: metadataWithRouting });
9902
- await sendSessionEvent(openCodeClient, { sessionId, agent: config.routing.targetAgent, text: prompt, directory: taskRoute.worktree, opencodeBaseUrl: config.opencode?.baseUrl });
9903
- return finish({ ok: true, action: "sent_to_existing_session", taskId, sessionId, eventKey });
10032
+ const delivery = await deliverClickUpSessionEventWithVerification({ openCodeClient, sendSessionEvent, clickupClient, worktree, taskId, sessionId, agent: config.routing.targetAgent, text: prompt, directory: taskRoute.worktree, opencodeBaseUrl: config.opencode?.baseUrl, eventMarkers: [taskId, eventType], verifySessionEventDelivery });
10033
+ if (!delivery.ok) return finish({ ...delivery, eventKey, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath, evidencePath: routingMetadata.evidence_path });
10034
+ 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 });
9904
10035
  }
9905
10036
  const at = now().toISOString();
9906
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.`;
9907
10038
  appendClickUpWebhookLocalLog(worktree, { type: "missing_session", taskId, sessionId, host, at });
9908
10039
  await clickupClient.postTaskComment({ taskId, comment: incidentComment });
9909
- return finish({ ok: true, action: "missing_session_reported", taskId, sessionId, eventKey });
10040
+ return finish({ ok: true, action: "missing_session_reported", taskId, sessionId, eventKey, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath, evidencePath: routingMetadata.evidence_path });
9910
10041
  }
9911
10042
  async function routeClickUpWebhookEvent(options = {}) {
9912
10043
  const taskId = clickUpTaskIdFromPayload(options.payload || {});
@@ -9962,10 +10093,26 @@ async function reconcileClickUpStartup({ config, state = {}, worktree = process.
9962
10093
  saveState: persistState,
9963
10094
  now
9964
10095
  });
9965
- if (result?.ok && result.action !== "ignored") routed.assigned += 1;
10096
+ if (result?.ok && result.action !== "ignored") {
10097
+ routed.assigned += 1;
10098
+ await postClickUpStartupComment({
10099
+ clickupClient,
10100
+ worktree,
10101
+ taskId,
10102
+ type: "resume",
10103
+ comment: buildClickUpStartupResumeComment({ taskId, result })
10104
+ });
10105
+ }
9966
10106
  } catch (error) {
9967
10107
  routed.errors += 1;
9968
10108
  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
+ });
9969
10116
  }
9970
10117
  if (!lastWebhookMs || !clickupClient?.getTaskComments) continue;
9971
10118
  try {
@@ -11779,7 +11926,7 @@ Follow-up: use optima_prompt_workflow with session_id '${sessionId}' to check in
11779
11926
  }
11780
11927
  };
11781
11928
  }
11782
- OptimaPlugin.__internals = { BUNDLE_AGENTS_DIR, BUNDLE_ASSETS_DIR, CLICKUP_DEFINITION_DOC_PARENT, activeClickUpWebhookLifecycleRegistry, cleanupManagedClickUpWebhook, deleteClickUpWebhookBestEffort, BUNDLE_POLICIES_DIR, buildClickUpApplyPayloadResult, buildClickUpCreateSubtasksPayload, buildClickUpStartTaskPayload, buildClickUpSummaryPayload, buildClickUpTransitionPayload, buildOptimaAgents, clickUpCommentLedgerKey, clickUpCommentMentionsProductManager, clickUpWebhookAuditLogDir, clickUpWebhookAuditLogPath, createClickUpApiClient, deliveryEvidencePathForClickUpTask, ensureClickUpTaskWorktree, ensureClickUpWebhookSubscription, handleClickUpWebhookRequest, isClickUpWebhookStateActive, normalizeClickUpWebhookConfig, normalizeClickUpWebhookLogLevel, normalizeOpenCodeBaseUrl, readClickUpCommentLedger, readClickUpWebhookState, reconcileClickUpStartup, recordClickUpCommentVersionProcessed, resolveOptimaPluginOptions, resolveSecretReference, routeClickUpWebhookEvent, sendOpenCodeSessionEvent, sendOpenCodeSessionEventDirect, stableClickUpCommentVersionMarker, startClickUpWebhookListener, validateClickUpWebhookState, verifyClickUpSignature, writeClickUpWebhookState, decideClickUpStatusAction, deriveClickUpBranchName, deriveClickUpPendingSubtaskBranch, deriveClickUpPrTarget, deriveClickUpWorktree, determineClickUpMergeAuthority, ensureOptimaGitignoreRules, explicitSafeInputWorktree, finalApprovalAssignees, formatValidationResult, isIgnoredClickUpTaskType, isOptimaPluginPackageWorktree, isSafeWritableDirectory, loadHumansRegistry, mergeClickUpAgentMetadata, mergeClickUpSessionMetadata, migrateLegacyOptimaLayout, normalizeAgentMetadataJson, normalizeClickUpStatus, normalizeClickUpTaskType, normalizePromptResponseParts, normalizeWorkflowTaskPath, parseClickUpSubtasksMarkdown, parseHumansRegistry, parseMarkdownArtifact, parseMarkdownSections, preEstimateClickUpWork, readMarkdownArtifact, resolveHumanRoles, resolveSafeWorktree, safeWorktreeOrFailure, stripRawLogSections, validateMainWorkspaceBranchSafety, workflowFinalMessageFromPromptResponse };
11929
+ OptimaPlugin.__internals = { BUNDLE_AGENTS_DIR, BUNDLE_ASSETS_DIR, CLICKUP_DEFINITION_DOC_PARENT, activeClickUpWebhookLifecycleRegistry, cleanupManagedClickUpWebhook, deleteClickUpWebhookBestEffort, BUNDLE_POLICIES_DIR, buildClickUpApplyPayloadResult, buildClickUpCreateSubtasksPayload, buildClickUpStartTaskPayload, buildClickUpSummaryPayload, buildClickUpTransitionPayload, buildOptimaAgents, clickUpCommentLedgerKey, clickUpCommentMentionsProductManager, clickUpWebhookAuditLogDir, clickUpWebhookAuditLogPath, createClickUpApiClient, deliveryEvidencePathForClickUpTask, ensureClickUpTaskWorktree, ensureClickUpWebhookSubscription, handleClickUpWebhookRequest, isClickUpWebhookStateActive, normalizeClickUpWebhookConfig, normalizeClickUpWebhookLogLevel, normalizeOpenCodeBaseUrl, readClickUpCommentLedger, readClickUpWebhookState, reconcileClickUpStartup, recordClickUpCommentVersionProcessed, resolveOptimaPluginOptions, resolveSecretReference, routeClickUpWebhookEvent, sendOpenCodeSessionEvent, sendOpenCodeSessionEventDirect, verifyOpenCodeSessionEventDelivery, stableClickUpCommentVersionMarker, startClickUpWebhookListener, validateClickUpWebhookState, verifyClickUpSignature, writeClickUpWebhookState, decideClickUpStatusAction, deriveClickUpBranchName, deriveClickUpPendingSubtaskBranch, deriveClickUpPrTarget, deriveClickUpWorktree, determineClickUpMergeAuthority, ensureOptimaGitignoreRules, explicitSafeInputWorktree, finalApprovalAssignees, formatValidationResult, isIgnoredClickUpTaskType, isOptimaPluginPackageWorktree, isSafeWritableDirectory, loadHumansRegistry, mergeClickUpAgentMetadata, mergeClickUpSessionMetadata, migrateLegacyOptimaLayout, normalizeAgentMetadataJson, normalizeClickUpStatus, normalizeClickUpTaskType, normalizePromptResponseParts, normalizeWorkflowTaskPath, parseClickUpSubtasksMarkdown, parseHumansRegistry, parseMarkdownArtifact, parseMarkdownSections, preEstimateClickUpWork, readMarkdownArtifact, resolveHumanRoles, resolveSafeWorktree, safeWorktreeOrFailure, stripRawLogSections, validateMainWorkspaceBranchSafety, workflowFinalMessageFromPromptResponse };
11783
11930
 
11784
11931
  // src/sanitize_cli.js
11785
11932
  var { migrateLegacyOptimaLayout: migrateLegacyOptimaLayout2 } = OptimaPlugin.__internals;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defend-tech/opencode-optima",
3
- "version": "0.1.36",
3
+ "version": "0.1.38",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+ssh://git@github.com/defend-tech/opencode-optima.git"