@defend-tech/opencode-optima 0.1.50 → 0.1.52

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
@@ -9719,7 +9719,13 @@ async function readOpenCodeJsonResponse(response, endpointName) {
9719
9719
  return { raw };
9720
9720
  }
9721
9721
  }
9722
- async function sendOpenCodeSessionEventDirect({ baseUrl, sessionId, text, agent, fetchImpl = globalThis.fetch, legacyOnly = false } = {}) {
9722
+ function appendDirectoryQuery(url, directory) {
9723
+ const value = String(directory || "").trim();
9724
+ if (!value) return url;
9725
+ const separator = url.includes("?") ? "&" : "?";
9726
+ return `${url}${separator}directory=${encodeURIComponent(value)}`;
9727
+ }
9728
+ async function sendOpenCodeSessionEventDirect({ baseUrl, sessionId, text, agent, directory, fetchImpl = globalThis.fetch, legacyOnly = false } = {}) {
9723
9729
  if (typeof fetchImpl !== "function") throw new Error("OpenCode direct prompt delivery requires fetch.");
9724
9730
  const root = normalizeOpenCodeBaseUrl(baseUrl, "");
9725
9731
  if (!root) throw new Error("OpenCode direct prompt delivery requires a base URL.");
@@ -9727,7 +9733,7 @@ async function sendOpenCodeSessionEventDirect({ baseUrl, sessionId, text, agent,
9727
9733
  const attempts = [
9728
9734
  legacyOnly ? null : {
9729
9735
  name: "v2 prompt",
9730
- url: `${root}/api/session/${encodedSession}/prompt`,
9736
+ url: appendDirectoryQuery(`${root}/api/session/${encodedSession}/prompt`, directory),
9731
9737
  body: { prompt: { text }, delivery: "queue", resume: true },
9732
9738
  accept: (response, data) => {
9733
9739
  if (!response.ok) return null;
@@ -9737,7 +9743,7 @@ async function sendOpenCodeSessionEventDirect({ baseUrl, sessionId, text, agent,
9737
9743
  },
9738
9744
  {
9739
9745
  name: "legacy async prompt",
9740
- url: `${root}/session/${encodedSession}/prompt_async`,
9746
+ url: appendDirectoryQuery(`${root}/session/${encodedSession}/prompt_async`, directory),
9741
9747
  body: { agent, parts: [{ type: "text", text }] },
9742
9748
  accept: (response, data) => {
9743
9749
  if (response.status !== 204 && !response.ok) return null;
@@ -9761,7 +9767,17 @@ async function sendOpenCodeSessionEventDirect({ baseUrl, sessionId, text, agent,
9761
9767
  }
9762
9768
  throw firstError || new Error("OpenCode direct prompt delivery failed.");
9763
9769
  }
9764
- async function callOpenCodePromptWithFallbacks(method, sessionId, flatPayload, structuredPayload) {
9770
+ function tagOpenCodePromptResult(result, deliveryMethod) {
9771
+ if (result && typeof result === "object") {
9772
+ try {
9773
+ Object.defineProperty(result, "__optimaPromptDelivery", { value: deliveryMethod, enumerable: false, configurable: true });
9774
+ } catch {
9775
+ return { result, __optimaPromptDelivery: deliveryMethod };
9776
+ }
9777
+ }
9778
+ return result;
9779
+ }
9780
+ async function callOpenCodePromptWithFallbacks(method, sessionId, flatPayload, structuredPayload, deliveryMethod) {
9765
9781
  const attempts = [
9766
9782
  { ...structuredPayload, path: { id: sessionId } },
9767
9783
  { ...structuredPayload, path: { sessionID: sessionId } },
@@ -9771,14 +9787,14 @@ async function callOpenCodePromptWithFallbacks(method, sessionId, flatPayload, s
9771
9787
  let firstError = null;
9772
9788
  for (const attempt of attempts) {
9773
9789
  try {
9774
- return assertOpenCodePromptAccepted(await method(attempt));
9790
+ return tagOpenCodePromptResult(assertOpenCodePromptAccepted(await method(attempt)), deliveryMethod);
9775
9791
  } catch (error) {
9776
9792
  firstError ??= error;
9777
9793
  }
9778
9794
  }
9779
9795
  throw firstError;
9780
9796
  }
9781
- async function sendOpenCodeSessionEvent(client, { sessionId, agent, text, directory, opencodeBaseUrl, baseUrl, fetchImpl, direct = false, legacyOnly = false } = {}) {
9797
+ async function sendOpenCodeSessionEvent(client, { sessionId, agent, text, directory, opencodeBaseUrl, baseUrl, fetchImpl, direct = false, legacyOnly = false, allowDirectFallback = true } = {}) {
9782
9798
  const directBaseUrl = opencodeBaseUrl || baseUrl;
9783
9799
  const parts = [{ type: "text", text }];
9784
9800
  const flatPayload = { directory, agent, parts };
@@ -9788,21 +9804,21 @@ async function sendOpenCodeSessionEvent(client, { sessionId, agent, text, direct
9788
9804
  body: { agent, parts }
9789
9805
  };
9790
9806
  let firstError = null;
9791
- if (!direct && typeof client?.session?.promptAsync === "function") {
9807
+ if (!direct && typeof client?.session?.prompt === "function") {
9792
9808
  try {
9793
- return await callOpenCodePromptWithFallbacks(client.session.promptAsync.bind(client.session), sessionId, flatPayload, structuredPayload);
9809
+ return await callOpenCodePromptWithFallbacks(client.session.prompt.bind(client.session), sessionId, flatPayload, structuredPayload, "prompt");
9794
9810
  } catch (error) {
9795
9811
  firstError ??= error;
9796
9812
  }
9797
9813
  }
9798
- if (!direct && typeof client?.session?.prompt === "function") {
9814
+ if (!direct && typeof client?.session?.promptAsync === "function") {
9799
9815
  try {
9800
- return await callOpenCodePromptWithFallbacks(client.session.prompt.bind(client.session), sessionId, flatPayload, structuredPayload);
9816
+ return await callOpenCodePromptWithFallbacks(client.session.promptAsync.bind(client.session), sessionId, flatPayload, structuredPayload, "prompt_async");
9801
9817
  } catch (error) {
9802
9818
  firstError ??= error;
9803
9819
  }
9804
9820
  }
9805
- if (directBaseUrl) return sendOpenCodeSessionEventDirect({ baseUrl: directBaseUrl, sessionId, text, agent, fetchImpl, legacyOnly });
9821
+ if (allowDirectFallback && directBaseUrl) return sendOpenCodeSessionEventDirect({ baseUrl: directBaseUrl, sessionId, text, agent, directory, fetchImpl, legacyOnly });
9806
9822
  if (firstError) throw firstError;
9807
9823
  throw new Error("OpenCode client does not expose session.prompt or session.promptAsync.");
9808
9824
  }
@@ -9837,6 +9853,11 @@ function openCodeMessageText(message) {
9837
9853
  for (const part of partList) parts.push(part?.text, part?.content);
9838
9854
  return parts.filter((value) => typeof value === "string").join("\n");
9839
9855
  }
9856
+ function openCodeMessageStableKey(message, index = 0) {
9857
+ const id = message?.id || message?.messageID || message?.messageId || message?.data?.id || null;
9858
+ if (id) return `id:${id}`;
9859
+ return `idx:${index}:text:${openCodeMessageText(message)}`;
9860
+ }
9840
9861
  async function verifyOpenCodeSessionEventDelivery(client, { sessionId, beforeMessages = null, expectedText = "", markers = [], attempts = 8, delayMs = 250 } = {}) {
9841
9862
  let lastError = "message_verification_unavailable";
9842
9863
  for (let attempt = 0; attempt < Math.max(1, attempts); attempt += 1) {
@@ -9844,13 +9865,15 @@ async function verifyOpenCodeSessionEventDelivery(client, { sessionId, beforeMes
9844
9865
  const afterMessages = await readOpenCodeSessionMessages(client, { sessionId, limit: 50 });
9845
9866
  if (!afterMessages) return { ok: false, reason: "message_verification_unavailable" };
9846
9867
  const beforeCount = Array.isArray(beforeMessages) ? beforeMessages.length : null;
9847
- const lastMessage = afterMessages.at(-1) || null;
9868
+ const beforeKeys = Array.isArray(beforeMessages) ? new Set(beforeMessages.map(openCodeMessageStableKey)) : null;
9869
+ const newMessages = beforeKeys ? afterMessages.filter((message, index) => !beforeKeys.has(openCodeMessageStableKey(message, index))) : afterMessages;
9870
+ const lastMessage = newMessages.at(-1) || afterMessages.at(-1) || null;
9848
9871
  const lastMessageId = lastMessage?.id || lastMessage?.messageID || lastMessage?.messageId || null;
9849
9872
  if (beforeCount !== null && afterMessages.length > beforeCount) return { ok: true, method: "message_count", beforeCount, afterCount: afterMessages.length, lastMessageId };
9850
9873
  const textNeedles = [expectedText, ...markers].map((value) => String(value || "").trim()).filter(Boolean);
9851
- const haystack = afterMessages.slice(-20).map(openCodeMessageText).join("\n");
9874
+ const haystack = newMessages.slice(-20).map(openCodeMessageText).join("\n");
9852
9875
  const matched = textNeedles.find((needle) => haystack.includes(needle));
9853
- if (matched) return { ok: true, method: "message_text", marker: matched, beforeCount, afterCount: afterMessages.length, lastMessageId };
9876
+ if (matched && (!beforeKeys || newMessages.length > 0)) return { ok: true, method: "message_text", marker: matched, beforeCount, afterCount: afterMessages.length, lastMessageId };
9854
9877
  lastError = "message_not_visible";
9855
9878
  } catch (error) {
9856
9879
  lastError = error.message || "message_verification_failed";
@@ -9873,46 +9896,44 @@ async function applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason,
9873
9896
  return { ok: false, error: error.message, tagName };
9874
9897
  }
9875
9898
  }
9899
+ function openCodeBlockingPromptVerification(result, sessionId) {
9900
+ if (result?.__optimaPromptDelivery !== "prompt") return null;
9901
+ const response = result?.response ?? result;
9902
+ const data = response?.data ?? result?.data ?? null;
9903
+ const parts = normalizePromptResponseParts(result);
9904
+ const messageId = data?.id ?? response?.id ?? result?.id ?? data?.messageID ?? response?.messageID ?? result?.messageID;
9905
+ const deliveredSessionId = response?.sessionID ?? response?.sessionId ?? data?.sessionID ?? data?.sessionId ?? result?.sessionID ?? result?.sessionId;
9906
+ if (deliveredSessionId && String(deliveredSessionId) !== String(sessionId)) {
9907
+ throw new Error(`OpenCode blocking prompt targeted foreign session ${deliveredSessionId}.`);
9908
+ }
9909
+ if (parts.length > 0 || messageId) return { ok: true, method: parts.length > 0 ? "blocking_prompt_parts" : "blocking_prompt_message", messageId: messageId ? String(messageId) : null, sessionId: deliveredSessionId ? String(deliveredSessionId) : String(sessionId), parts: parts.length };
9910
+ return null;
9911
+ }
9876
9912
  async function deliverClickUpSessionEventWithVerification({ openCodeClient, sendSessionEvent, clickupClient, worktree, taskId, sessionId, agent, text, directory, opencodeBaseUrl, eventMarkers = [], verifySessionEventDelivery = verifyOpenCodeSessionEventDelivery, applyBlockerOnFailure = true } = {}) {
9877
9913
  const beforeMessages = await readOpenCodeSessionMessages(openCodeClient, { sessionId, limit: 50 }).catch(() => null);
9878
- const sendResult = await sendSessionEvent(openCodeClient, { sessionId, agent, text, directory, opencodeBaseUrl });
9914
+ const sendResult = await sendSessionEvent(openCodeClient, { sessionId, agent, text, directory, opencodeBaseUrl, allowDirectFallback: false });
9915
+ let blockingPromptVerification = null;
9879
9916
  let admissionVerification = null;
9880
9917
  try {
9881
- admissionVerification = openCodePromptAdmissionVerification(sendResult, sessionId);
9918
+ blockingPromptVerification = openCodeBlockingPromptVerification(sendResult, sessionId);
9919
+ if (!blockingPromptVerification) admissionVerification = openCodePromptAdmissionVerification(sendResult, sessionId);
9882
9920
  } catch (error) {
9883
9921
  appendClickUpWebhookLocalLog(worktree, { type: "message_delivery_failed", taskId, sessionId, reason: error.message, fallbackAttempted: false });
9884
9922
  if (!applyBlockerOnFailure) return { ok: false, action: "message_delivery_failed", reason: error.message, taskId, sessionId, fallbackAttempted: false };
9885
9923
  const blocker2 = await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason: error.message, source: "delivery_admission_failed" });
9886
9924
  return { ok: false, action: "message_delivery_failed", reason: error.message, taskId, sessionId, fallbackAttempted: false, blockerTag: blocker2 };
9887
9925
  }
9926
+ if (blockingPromptVerification) return { ok: true, verification: blockingPromptVerification, admissionVerification: null, fallback: false };
9888
9927
  let verification = await verifySessionEventDelivery(openCodeClient, { sessionId, beforeMessages, expectedText: text, markers: eventMarkers });
9889
9928
  if (verification?.ok) return { ok: true, verification, admissionVerification, fallback: false };
9890
- if (verification?.reason === "message_verification_unavailable" && !admissionVerification) {
9891
- return { ok: true, verification: { ok: true, method: "legacy_prompt_accepted", skipped: true }, fallback: false };
9892
- }
9893
9929
  if (admissionVerification) {
9894
9930
  appendClickUpWebhookLocalLog(worktree, { type: "message_delivery_admitted_but_invisible", taskId, sessionId, admission: admissionVerification, reason: verification?.reason || "message_not_visible" });
9895
9931
  }
9896
- const canFallbackDirect = Boolean(opencodeBaseUrl);
9897
- if (canFallbackDirect) {
9898
- const retryBeforeMessages = await readOpenCodeSessionMessages(openCodeClient, { sessionId, limit: 50 }).catch(() => beforeMessages);
9899
- const retrySendResult = await sendSessionEvent(openCodeClient, { sessionId, agent, text, directory, opencodeBaseUrl, direct: true, legacyOnly: Boolean(admissionVerification) });
9900
- try {
9901
- admissionVerification = openCodePromptAdmissionVerification(retrySendResult, sessionId);
9902
- } catch (error) {
9903
- verification = { ok: false, reason: error.message };
9904
- }
9905
- verification = await verifySessionEventDelivery(openCodeClient, { sessionId, beforeMessages: retryBeforeMessages, expectedText: text, markers: eventMarkers });
9906
- if (verification?.ok) return { ok: true, verification, admissionVerification, fallback: true };
9907
- if (verification?.reason === "message_verification_unavailable" && !admissionVerification) {
9908
- return { ok: true, verification: { ok: true, method: "legacy_prompt_accepted", skipped: true }, fallback: true };
9909
- }
9910
- }
9911
- const reason = verification?.reason || "message_delivery_failed";
9912
- appendClickUpWebhookLocalLog(worktree, { type: "message_delivery_failed", taskId, sessionId, reason, fallbackAttempted: canFallbackDirect });
9913
- if (!applyBlockerOnFailure) return { ok: false, action: "message_delivery_failed", reason, taskId, sessionId, fallbackAttempted: canFallbackDirect };
9914
- const blocker = await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason, source: canFallbackDirect ? "delivery_fallback_failed" : "delivery_verification_failed" });
9915
- return { ok: false, action: "message_delivery_failed", reason, taskId, sessionId, fallbackAttempted: canFallbackDirect, blockerTag: blocker };
9932
+ const reason = verification?.reason || (admissionVerification ? "prompt_admission_not_delivered" : "message_delivery_failed");
9933
+ appendClickUpWebhookLocalLog(worktree, { type: "message_delivery_failed", taskId, sessionId, reason, fallbackAttempted: false, httpFallbackDisabled: Boolean(opencodeBaseUrl) });
9934
+ if (!applyBlockerOnFailure) return { ok: false, action: "message_delivery_failed", reason, taskId, sessionId, fallbackAttempted: false };
9935
+ const blocker = await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason, source: "delivery_verification_failed" });
9936
+ return { ok: false, action: "message_delivery_failed", reason, taskId, sessionId, fallbackAttempted: false, blockerTag: blocker };
9916
9937
  }
9917
9938
  async function recoverClickUpPmSession({ openCodeClient, sendSessionEvent, clickupClient, worktree, taskId, staleSessionId, sessionTitle, taskRoute, metadataWithRouting, config, prompt, eventMarkers = [], deliveryEvidencePath, evidencePath, eventKey, createSession, verifySessionEventDelivery } = {}) {
9918
9939
  appendClickUpWebhookLocalLog(worktree, { type: "pm_session_recovery_started", taskId, staleSessionId, worktree: taskRoute?.worktree });
@@ -9726,7 +9726,13 @@ async function readOpenCodeJsonResponse(response, endpointName) {
9726
9726
  return { raw };
9727
9727
  }
9728
9728
  }
9729
- async function sendOpenCodeSessionEventDirect({ baseUrl, sessionId, text, agent, fetchImpl = globalThis.fetch, legacyOnly = false } = {}) {
9729
+ function appendDirectoryQuery(url, directory) {
9730
+ const value = String(directory || "").trim();
9731
+ if (!value) return url;
9732
+ const separator = url.includes("?") ? "&" : "?";
9733
+ return `${url}${separator}directory=${encodeURIComponent(value)}`;
9734
+ }
9735
+ async function sendOpenCodeSessionEventDirect({ baseUrl, sessionId, text, agent, directory, fetchImpl = globalThis.fetch, legacyOnly = false } = {}) {
9730
9736
  if (typeof fetchImpl !== "function") throw new Error("OpenCode direct prompt delivery requires fetch.");
9731
9737
  const root = normalizeOpenCodeBaseUrl(baseUrl, "");
9732
9738
  if (!root) throw new Error("OpenCode direct prompt delivery requires a base URL.");
@@ -9734,7 +9740,7 @@ async function sendOpenCodeSessionEventDirect({ baseUrl, sessionId, text, agent,
9734
9740
  const attempts = [
9735
9741
  legacyOnly ? null : {
9736
9742
  name: "v2 prompt",
9737
- url: `${root}/api/session/${encodedSession}/prompt`,
9743
+ url: appendDirectoryQuery(`${root}/api/session/${encodedSession}/prompt`, directory),
9738
9744
  body: { prompt: { text }, delivery: "queue", resume: true },
9739
9745
  accept: (response, data) => {
9740
9746
  if (!response.ok) return null;
@@ -9744,7 +9750,7 @@ async function sendOpenCodeSessionEventDirect({ baseUrl, sessionId, text, agent,
9744
9750
  },
9745
9751
  {
9746
9752
  name: "legacy async prompt",
9747
- url: `${root}/session/${encodedSession}/prompt_async`,
9753
+ url: appendDirectoryQuery(`${root}/session/${encodedSession}/prompt_async`, directory),
9748
9754
  body: { agent, parts: [{ type: "text", text }] },
9749
9755
  accept: (response, data) => {
9750
9756
  if (response.status !== 204 && !response.ok) return null;
@@ -9768,7 +9774,17 @@ async function sendOpenCodeSessionEventDirect({ baseUrl, sessionId, text, agent,
9768
9774
  }
9769
9775
  throw firstError || new Error("OpenCode direct prompt delivery failed.");
9770
9776
  }
9771
- async function callOpenCodePromptWithFallbacks(method, sessionId, flatPayload, structuredPayload) {
9777
+ function tagOpenCodePromptResult(result, deliveryMethod) {
9778
+ if (result && typeof result === "object") {
9779
+ try {
9780
+ Object.defineProperty(result, "__optimaPromptDelivery", { value: deliveryMethod, enumerable: false, configurable: true });
9781
+ } catch {
9782
+ return { result, __optimaPromptDelivery: deliveryMethod };
9783
+ }
9784
+ }
9785
+ return result;
9786
+ }
9787
+ async function callOpenCodePromptWithFallbacks(method, sessionId, flatPayload, structuredPayload, deliveryMethod) {
9772
9788
  const attempts = [
9773
9789
  { ...structuredPayload, path: { id: sessionId } },
9774
9790
  { ...structuredPayload, path: { sessionID: sessionId } },
@@ -9778,14 +9794,14 @@ async function callOpenCodePromptWithFallbacks(method, sessionId, flatPayload, s
9778
9794
  let firstError = null;
9779
9795
  for (const attempt of attempts) {
9780
9796
  try {
9781
- return assertOpenCodePromptAccepted(await method(attempt));
9797
+ return tagOpenCodePromptResult(assertOpenCodePromptAccepted(await method(attempt)), deliveryMethod);
9782
9798
  } catch (error) {
9783
9799
  firstError ??= error;
9784
9800
  }
9785
9801
  }
9786
9802
  throw firstError;
9787
9803
  }
9788
- async function sendOpenCodeSessionEvent(client, { sessionId, agent, text, directory, opencodeBaseUrl, baseUrl, fetchImpl, direct = false, legacyOnly = false } = {}) {
9804
+ async function sendOpenCodeSessionEvent(client, { sessionId, agent, text, directory, opencodeBaseUrl, baseUrl, fetchImpl, direct = false, legacyOnly = false, allowDirectFallback = true } = {}) {
9789
9805
  const directBaseUrl = opencodeBaseUrl || baseUrl;
9790
9806
  const parts = [{ type: "text", text }];
9791
9807
  const flatPayload = { directory, agent, parts };
@@ -9795,21 +9811,21 @@ async function sendOpenCodeSessionEvent(client, { sessionId, agent, text, direct
9795
9811
  body: { agent, parts }
9796
9812
  };
9797
9813
  let firstError = null;
9798
- if (!direct && typeof client?.session?.promptAsync === "function") {
9814
+ if (!direct && typeof client?.session?.prompt === "function") {
9799
9815
  try {
9800
- return await callOpenCodePromptWithFallbacks(client.session.promptAsync.bind(client.session), sessionId, flatPayload, structuredPayload);
9816
+ return await callOpenCodePromptWithFallbacks(client.session.prompt.bind(client.session), sessionId, flatPayload, structuredPayload, "prompt");
9801
9817
  } catch (error) {
9802
9818
  firstError ??= error;
9803
9819
  }
9804
9820
  }
9805
- if (!direct && typeof client?.session?.prompt === "function") {
9821
+ if (!direct && typeof client?.session?.promptAsync === "function") {
9806
9822
  try {
9807
- return await callOpenCodePromptWithFallbacks(client.session.prompt.bind(client.session), sessionId, flatPayload, structuredPayload);
9823
+ return await callOpenCodePromptWithFallbacks(client.session.promptAsync.bind(client.session), sessionId, flatPayload, structuredPayload, "prompt_async");
9808
9824
  } catch (error) {
9809
9825
  firstError ??= error;
9810
9826
  }
9811
9827
  }
9812
- if (directBaseUrl) return sendOpenCodeSessionEventDirect({ baseUrl: directBaseUrl, sessionId, text, agent, fetchImpl, legacyOnly });
9828
+ if (allowDirectFallback && directBaseUrl) return sendOpenCodeSessionEventDirect({ baseUrl: directBaseUrl, sessionId, text, agent, directory, fetchImpl, legacyOnly });
9813
9829
  if (firstError) throw firstError;
9814
9830
  throw new Error("OpenCode client does not expose session.prompt or session.promptAsync.");
9815
9831
  }
@@ -9844,6 +9860,11 @@ function openCodeMessageText(message) {
9844
9860
  for (const part of partList) parts.push(part?.text, part?.content);
9845
9861
  return parts.filter((value) => typeof value === "string").join("\n");
9846
9862
  }
9863
+ function openCodeMessageStableKey(message, index = 0) {
9864
+ const id = message?.id || message?.messageID || message?.messageId || message?.data?.id || null;
9865
+ if (id) return `id:${id}`;
9866
+ return `idx:${index}:text:${openCodeMessageText(message)}`;
9867
+ }
9847
9868
  async function verifyOpenCodeSessionEventDelivery(client, { sessionId, beforeMessages = null, expectedText = "", markers = [], attempts = 8, delayMs = 250 } = {}) {
9848
9869
  let lastError = "message_verification_unavailable";
9849
9870
  for (let attempt = 0; attempt < Math.max(1, attempts); attempt += 1) {
@@ -9851,13 +9872,15 @@ async function verifyOpenCodeSessionEventDelivery(client, { sessionId, beforeMes
9851
9872
  const afterMessages = await readOpenCodeSessionMessages(client, { sessionId, limit: 50 });
9852
9873
  if (!afterMessages) return { ok: false, reason: "message_verification_unavailable" };
9853
9874
  const beforeCount = Array.isArray(beforeMessages) ? beforeMessages.length : null;
9854
- const lastMessage = afterMessages.at(-1) || null;
9875
+ const beforeKeys = Array.isArray(beforeMessages) ? new Set(beforeMessages.map(openCodeMessageStableKey)) : null;
9876
+ const newMessages = beforeKeys ? afterMessages.filter((message, index) => !beforeKeys.has(openCodeMessageStableKey(message, index))) : afterMessages;
9877
+ const lastMessage = newMessages.at(-1) || afterMessages.at(-1) || null;
9855
9878
  const lastMessageId = lastMessage?.id || lastMessage?.messageID || lastMessage?.messageId || null;
9856
9879
  if (beforeCount !== null && afterMessages.length > beforeCount) return { ok: true, method: "message_count", beforeCount, afterCount: afterMessages.length, lastMessageId };
9857
9880
  const textNeedles = [expectedText, ...markers].map((value) => String(value || "").trim()).filter(Boolean);
9858
- const haystack = afterMessages.slice(-20).map(openCodeMessageText).join("\n");
9881
+ const haystack = newMessages.slice(-20).map(openCodeMessageText).join("\n");
9859
9882
  const matched = textNeedles.find((needle) => haystack.includes(needle));
9860
- if (matched) return { ok: true, method: "message_text", marker: matched, beforeCount, afterCount: afterMessages.length, lastMessageId };
9883
+ if (matched && (!beforeKeys || newMessages.length > 0)) return { ok: true, method: "message_text", marker: matched, beforeCount, afterCount: afterMessages.length, lastMessageId };
9861
9884
  lastError = "message_not_visible";
9862
9885
  } catch (error) {
9863
9886
  lastError = error.message || "message_verification_failed";
@@ -9880,46 +9903,44 @@ async function applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason,
9880
9903
  return { ok: false, error: error.message, tagName };
9881
9904
  }
9882
9905
  }
9906
+ function openCodeBlockingPromptVerification(result, sessionId) {
9907
+ if (result?.__optimaPromptDelivery !== "prompt") return null;
9908
+ const response = result?.response ?? result;
9909
+ const data = response?.data ?? result?.data ?? null;
9910
+ const parts = normalizePromptResponseParts(result);
9911
+ const messageId = data?.id ?? response?.id ?? result?.id ?? data?.messageID ?? response?.messageID ?? result?.messageID;
9912
+ const deliveredSessionId = response?.sessionID ?? response?.sessionId ?? data?.sessionID ?? data?.sessionId ?? result?.sessionID ?? result?.sessionId;
9913
+ if (deliveredSessionId && String(deliveredSessionId) !== String(sessionId)) {
9914
+ throw new Error(`OpenCode blocking prompt targeted foreign session ${deliveredSessionId}.`);
9915
+ }
9916
+ if (parts.length > 0 || messageId) return { ok: true, method: parts.length > 0 ? "blocking_prompt_parts" : "blocking_prompt_message", messageId: messageId ? String(messageId) : null, sessionId: deliveredSessionId ? String(deliveredSessionId) : String(sessionId), parts: parts.length };
9917
+ return null;
9918
+ }
9883
9919
  async function deliverClickUpSessionEventWithVerification({ openCodeClient, sendSessionEvent, clickupClient, worktree, taskId, sessionId, agent, text, directory, opencodeBaseUrl, eventMarkers = [], verifySessionEventDelivery = verifyOpenCodeSessionEventDelivery, applyBlockerOnFailure = true } = {}) {
9884
9920
  const beforeMessages = await readOpenCodeSessionMessages(openCodeClient, { sessionId, limit: 50 }).catch(() => null);
9885
- const sendResult = await sendSessionEvent(openCodeClient, { sessionId, agent, text, directory, opencodeBaseUrl });
9921
+ const sendResult = await sendSessionEvent(openCodeClient, { sessionId, agent, text, directory, opencodeBaseUrl, allowDirectFallback: false });
9922
+ let blockingPromptVerification = null;
9886
9923
  let admissionVerification = null;
9887
9924
  try {
9888
- admissionVerification = openCodePromptAdmissionVerification(sendResult, sessionId);
9925
+ blockingPromptVerification = openCodeBlockingPromptVerification(sendResult, sessionId);
9926
+ if (!blockingPromptVerification) admissionVerification = openCodePromptAdmissionVerification(sendResult, sessionId);
9889
9927
  } catch (error) {
9890
9928
  appendClickUpWebhookLocalLog(worktree, { type: "message_delivery_failed", taskId, sessionId, reason: error.message, fallbackAttempted: false });
9891
9929
  if (!applyBlockerOnFailure) return { ok: false, action: "message_delivery_failed", reason: error.message, taskId, sessionId, fallbackAttempted: false };
9892
9930
  const blocker2 = await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason: error.message, source: "delivery_admission_failed" });
9893
9931
  return { ok: false, action: "message_delivery_failed", reason: error.message, taskId, sessionId, fallbackAttempted: false, blockerTag: blocker2 };
9894
9932
  }
9933
+ if (blockingPromptVerification) return { ok: true, verification: blockingPromptVerification, admissionVerification: null, fallback: false };
9895
9934
  let verification = await verifySessionEventDelivery(openCodeClient, { sessionId, beforeMessages, expectedText: text, markers: eventMarkers });
9896
9935
  if (verification?.ok) return { ok: true, verification, admissionVerification, fallback: false };
9897
- if (verification?.reason === "message_verification_unavailable" && !admissionVerification) {
9898
- return { ok: true, verification: { ok: true, method: "legacy_prompt_accepted", skipped: true }, fallback: false };
9899
- }
9900
9936
  if (admissionVerification) {
9901
9937
  appendClickUpWebhookLocalLog(worktree, { type: "message_delivery_admitted_but_invisible", taskId, sessionId, admission: admissionVerification, reason: verification?.reason || "message_not_visible" });
9902
9938
  }
9903
- const canFallbackDirect = Boolean(opencodeBaseUrl);
9904
- if (canFallbackDirect) {
9905
- const retryBeforeMessages = await readOpenCodeSessionMessages(openCodeClient, { sessionId, limit: 50 }).catch(() => beforeMessages);
9906
- const retrySendResult = await sendSessionEvent(openCodeClient, { sessionId, agent, text, directory, opencodeBaseUrl, direct: true, legacyOnly: Boolean(admissionVerification) });
9907
- try {
9908
- admissionVerification = openCodePromptAdmissionVerification(retrySendResult, sessionId);
9909
- } catch (error) {
9910
- verification = { ok: false, reason: error.message };
9911
- }
9912
- verification = await verifySessionEventDelivery(openCodeClient, { sessionId, beforeMessages: retryBeforeMessages, expectedText: text, markers: eventMarkers });
9913
- if (verification?.ok) return { ok: true, verification, admissionVerification, fallback: true };
9914
- if (verification?.reason === "message_verification_unavailable" && !admissionVerification) {
9915
- return { ok: true, verification: { ok: true, method: "legacy_prompt_accepted", skipped: true }, fallback: true };
9916
- }
9917
- }
9918
- const reason = verification?.reason || "message_delivery_failed";
9919
- appendClickUpWebhookLocalLog(worktree, { type: "message_delivery_failed", taskId, sessionId, reason, fallbackAttempted: canFallbackDirect });
9920
- if (!applyBlockerOnFailure) return { ok: false, action: "message_delivery_failed", reason, taskId, sessionId, fallbackAttempted: canFallbackDirect };
9921
- const blocker = await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason, source: canFallbackDirect ? "delivery_fallback_failed" : "delivery_verification_failed" });
9922
- return { ok: false, action: "message_delivery_failed", reason, taskId, sessionId, fallbackAttempted: canFallbackDirect, blockerTag: blocker };
9939
+ const reason = verification?.reason || (admissionVerification ? "prompt_admission_not_delivered" : "message_delivery_failed");
9940
+ appendClickUpWebhookLocalLog(worktree, { type: "message_delivery_failed", taskId, sessionId, reason, fallbackAttempted: false, httpFallbackDisabled: Boolean(opencodeBaseUrl) });
9941
+ if (!applyBlockerOnFailure) return { ok: false, action: "message_delivery_failed", reason, taskId, sessionId, fallbackAttempted: false };
9942
+ const blocker = await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason, source: "delivery_verification_failed" });
9943
+ return { ok: false, action: "message_delivery_failed", reason, taskId, sessionId, fallbackAttempted: false, blockerTag: blocker };
9923
9944
  }
9924
9945
  async function recoverClickUpPmSession({ openCodeClient, sendSessionEvent, clickupClient, worktree, taskId, staleSessionId, sessionTitle, taskRoute, metadataWithRouting, config, prompt, eventMarkers = [], deliveryEvidencePath, evidencePath, eventKey, createSession, verifySessionEventDelivery } = {}) {
9925
9946
  appendClickUpWebhookLocalLog(worktree, { type: "pm_session_recovery_started", taskId, staleSessionId, worktree: taskRoute?.worktree });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defend-tech/opencode-optima",
3
- "version": "0.1.50",
3
+ "version": "0.1.52",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+ssh://git@github.com/defend-tech/opencode-optima.git"