@defend-tech/opencode-optima 0.1.48 → 0.1.50

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
@@ -7938,6 +7938,7 @@ var CLICKUP_WEBHOOK_MAX_BODY_BYTES = 1024 * 1024;
7938
7938
  var CLICKUP_WEBHOOK_REQUEST_TIMEOUT_MS = 1e4;
7939
7939
  var CLICKUP_WEBHOOK_STARTUP_TASK_LIMIT = 50;
7940
7940
  var CLICKUP_WEBHOOK_STARTUP_COMMENT_LIMIT = 100;
7941
+ var CLICKUP_WEBHOOK_STARTUP_RECONCILIATION_DELAY_MS = 3e4;
7941
7942
  var CLICKUP_WEBHOOK_LOG_LEVELS = /* @__PURE__ */ new Set(["error", "info", "verbose"]);
7942
7943
  var CLICKUP_WEBHOOK_REDACTED = "[REDACTED]";
7943
7944
  var DISCUSSION_BACKFILL_FETCH_LIMIT = 100;
@@ -8163,6 +8164,7 @@ function hasActionableClickUpRoute(result = {}) {
8163
8164
  if (!result.sessionId) return false;
8164
8165
  if (result.action === "message_delivery_failed" || result.action === "error") return false;
8165
8166
  if (result.deliveryVerification?.ok === false) return false;
8167
+ if (result.deliveryVerification?.method === "prompt_admission") return false;
8166
8168
  return true;
8167
8169
  }
8168
8170
  function determineClickUpMergeAuthority({ isSubtask = false, clickupStatus = "", validationPassed = false, mergeFailed = false, finalApprovalRoles = CLICKUP_FINAL_APPROVER_ROLES, humansRegistry } = {}) {
@@ -9012,6 +9014,11 @@ function normalizeOpenCodeBaseUrl(value, defaultValue = "http://127.0.0.1:3001")
9012
9014
  return candidate.replace(/\/+$/, "");
9013
9015
  }
9014
9016
  }
9017
+ function normalizeNonNegativeInteger(value, defaultValue) {
9018
+ if (value === void 0 || value === null || value === "") return defaultValue;
9019
+ const number = Number(value);
9020
+ return Number.isInteger(number) && number >= 0 ? number : defaultValue;
9021
+ }
9015
9022
  function normalizeClickUpWebhookConfig(rawClickUp = null, worktree = process.cwd()) {
9016
9023
  const raw = isPlainObject(rawClickUp) ? rawClickUp : {};
9017
9024
  const webhook = isPlainObject(raw.webhook) ? raw.webhook : {};
@@ -9027,7 +9034,11 @@ function normalizeClickUpWebhookConfig(rawClickUp = null, worktree = process.cwd
9027
9034
  apiToken: String(raw.api_token || raw.apiToken || "").trim(),
9028
9035
  log: normalizeClickUpWebhookLogLevel(raw.log),
9029
9036
  opencode: {
9030
- baseUrl: normalizeOpenCodeBaseUrl(opencode.base_url || opencode.baseUrl || raw.opencode_base_url || raw.opencodeBaseUrl)
9037
+ baseUrl: normalizeOpenCodeBaseUrl(opencode.base_url || opencode.baseUrl || raw.opencode_base_url || raw.opencodeBaseUrl),
9038
+ startupReconciliationDelayMs: normalizeNonNegativeInteger(
9039
+ opencode.startup_reconciliation_delay_ms ?? opencode.startupReconciliationDelayMs ?? raw.startup_reconciliation_delay_ms ?? raw.startupReconciliationDelayMs,
9040
+ CLICKUP_WEBHOOK_STARTUP_RECONCILIATION_DELAY_MS
9041
+ )
9031
9042
  },
9032
9043
  webhook: {
9033
9044
  publicUrl: String(webhook.public_url || webhook.publicUrl || "").trim(),
@@ -9655,6 +9666,22 @@ async function createOpenCodeSession(client, { title, directory, agent } = {}) {
9655
9666
  }
9656
9667
  throw firstError || new Error("OpenCode session create failed.");
9657
9668
  }
9669
+ async function waitForOpenCodeReadiness(client, { worktree = process.cwd(), attempts = 10, delayMs = 500, now = () => /* @__PURE__ */ new Date() } = {}) {
9670
+ if (typeof client?.session?.create !== "function") return { ok: true, skipped: true, reason: "session_create_probe_unavailable" };
9671
+ let lastError = "opencode_not_ready";
9672
+ const maxAttempts = Math.max(1, Number(attempts) || 1);
9673
+ for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
9674
+ try {
9675
+ const sessionId = await createOpenCodeSession(client, { title: `Optima startup readiness probe ${now().toISOString()}`, directory: worktree });
9676
+ if (await openCodeSessionExists(client, sessionId)) return { ok: true, method: "session_create_probe", sessionId, attempts: attempt };
9677
+ lastError = "readiness_probe_session_not_visible";
9678
+ } catch (error) {
9679
+ lastError = error.message || "opencode_not_ready";
9680
+ }
9681
+ if (attempt < maxAttempts && delayMs > 0) await new Promise((resolve) => setTimeout(resolve, delayMs));
9682
+ }
9683
+ return { ok: false, reason: lastError, attempts: maxAttempts };
9684
+ }
9658
9685
  function assertOpenCodePromptAccepted(result) {
9659
9686
  const status = Number(result?.status || result?.response?.status || result?.error?.status || 0);
9660
9687
  if (status >= 400 || result?.ok === false || result?.error) {
@@ -9692,13 +9719,13 @@ async function readOpenCodeJsonResponse(response, endpointName) {
9692
9719
  return { raw };
9693
9720
  }
9694
9721
  }
9695
- async function sendOpenCodeSessionEventDirect({ baseUrl, sessionId, text, agent, fetchImpl = globalThis.fetch } = {}) {
9722
+ async function sendOpenCodeSessionEventDirect({ baseUrl, sessionId, text, agent, fetchImpl = globalThis.fetch, legacyOnly = false } = {}) {
9696
9723
  if (typeof fetchImpl !== "function") throw new Error("OpenCode direct prompt delivery requires fetch.");
9697
9724
  const root = normalizeOpenCodeBaseUrl(baseUrl, "");
9698
9725
  if (!root) throw new Error("OpenCode direct prompt delivery requires a base URL.");
9699
9726
  const encodedSession = encodeURIComponent(sessionId);
9700
9727
  const attempts = [
9701
- {
9728
+ legacyOnly ? null : {
9702
9729
  name: "v2 prompt",
9703
9730
  url: `${root}/api/session/${encodedSession}/prompt`,
9704
9731
  body: { prompt: { text }, delivery: "queue", resume: true },
@@ -9717,7 +9744,7 @@ async function sendOpenCodeSessionEventDirect({ baseUrl, sessionId, text, agent,
9717
9744
  return { ok: true, method: "http", endpoint: "/session/{sessionID}/prompt_async", status: response.status, data: data?.data || null, response: data };
9718
9745
  }
9719
9746
  }
9720
- ];
9747
+ ].filter(Boolean);
9721
9748
  let firstError = null;
9722
9749
  for (const attempt of attempts) {
9723
9750
  const response = await fetchImpl(attempt.url, {
@@ -9751,7 +9778,7 @@ async function callOpenCodePromptWithFallbacks(method, sessionId, flatPayload, s
9751
9778
  }
9752
9779
  throw firstError;
9753
9780
  }
9754
- async function sendOpenCodeSessionEvent(client, { sessionId, agent, text, directory, opencodeBaseUrl, baseUrl, fetchImpl, direct = false } = {}) {
9781
+ async function sendOpenCodeSessionEvent(client, { sessionId, agent, text, directory, opencodeBaseUrl, baseUrl, fetchImpl, direct = false, legacyOnly = false } = {}) {
9755
9782
  const directBaseUrl = opencodeBaseUrl || baseUrl;
9756
9783
  const parts = [{ type: "text", text }];
9757
9784
  const flatPayload = { directory, agent, parts };
@@ -9775,7 +9802,7 @@ async function sendOpenCodeSessionEvent(client, { sessionId, agent, text, direct
9775
9802
  firstError ??= error;
9776
9803
  }
9777
9804
  }
9778
- if (directBaseUrl) return sendOpenCodeSessionEventDirect({ baseUrl: directBaseUrl, sessionId, text, agent, fetchImpl });
9805
+ if (directBaseUrl) return sendOpenCodeSessionEventDirect({ baseUrl: directBaseUrl, sessionId, text, agent, fetchImpl, legacyOnly });
9779
9806
  if (firstError) throw firstError;
9780
9807
  throw new Error("OpenCode client does not expose session.prompt or session.promptAsync.");
9781
9808
  }
@@ -9810,18 +9837,20 @@ function openCodeMessageText(message) {
9810
9837
  for (const part of partList) parts.push(part?.text, part?.content);
9811
9838
  return parts.filter((value) => typeof value === "string").join("\n");
9812
9839
  }
9813
- async function verifyOpenCodeSessionEventDelivery(client, { sessionId, beforeMessages = null, expectedText = "", markers = [], attempts = 3, delayMs = 25 } = {}) {
9840
+ async function verifyOpenCodeSessionEventDelivery(client, { sessionId, beforeMessages = null, expectedText = "", markers = [], attempts = 8, delayMs = 250 } = {}) {
9814
9841
  let lastError = "message_verification_unavailable";
9815
9842
  for (let attempt = 0; attempt < Math.max(1, attempts); attempt += 1) {
9816
9843
  try {
9817
9844
  const afterMessages = await readOpenCodeSessionMessages(client, { sessionId, limit: 50 });
9818
- if (!afterMessages) return { ok: true, method: "verification_unavailable", skipped: true };
9845
+ if (!afterMessages) return { ok: false, reason: "message_verification_unavailable" };
9819
9846
  const beforeCount = Array.isArray(beforeMessages) ? beforeMessages.length : null;
9820
- if (beforeCount !== null && afterMessages.length > beforeCount) return { ok: true, method: "message_count", beforeCount, afterCount: afterMessages.length };
9847
+ const lastMessage = afterMessages.at(-1) || null;
9848
+ const lastMessageId = lastMessage?.id || lastMessage?.messageID || lastMessage?.messageId || null;
9849
+ if (beforeCount !== null && afterMessages.length > beforeCount) return { ok: true, method: "message_count", beforeCount, afterCount: afterMessages.length, lastMessageId };
9821
9850
  const textNeedles = [expectedText, ...markers].map((value) => String(value || "").trim()).filter(Boolean);
9822
9851
  const haystack = afterMessages.slice(-20).map(openCodeMessageText).join("\n");
9823
9852
  const matched = textNeedles.find((needle) => haystack.includes(needle));
9824
- if (matched) return { ok: true, method: "message_text", marker: matched, beforeCount, afterCount: afterMessages.length };
9853
+ if (matched) return { ok: true, method: "message_text", marker: matched, beforeCount, afterCount: afterMessages.length, lastMessageId };
9825
9854
  lastError = "message_not_visible";
9826
9855
  } catch (error) {
9827
9856
  lastError = error.message || "message_verification_failed";
@@ -9856,21 +9885,28 @@ async function deliverClickUpSessionEventWithVerification({ openCodeClient, send
9856
9885
  const blocker2 = await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason: error.message, source: "delivery_admission_failed" });
9857
9886
  return { ok: false, action: "message_delivery_failed", reason: error.message, taskId, sessionId, fallbackAttempted: false, blockerTag: blocker2 };
9858
9887
  }
9859
- if (admissionVerification) return { ok: true, verification: admissionVerification, fallback: false };
9860
9888
  let verification = await verifySessionEventDelivery(openCodeClient, { sessionId, beforeMessages, expectedText: text, markers: eventMarkers });
9861
- if (verification?.ok) return { ok: true, verification, fallback: false };
9889
+ 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
+ if (admissionVerification) {
9894
+ appendClickUpWebhookLocalLog(worktree, { type: "message_delivery_admitted_but_invisible", taskId, sessionId, admission: admissionVerification, reason: verification?.reason || "message_not_visible" });
9895
+ }
9862
9896
  const canFallbackDirect = Boolean(opencodeBaseUrl);
9863
9897
  if (canFallbackDirect) {
9864
9898
  const retryBeforeMessages = await readOpenCodeSessionMessages(openCodeClient, { sessionId, limit: 50 }).catch(() => beforeMessages);
9865
- const retrySendResult = await sendSessionEvent(openCodeClient, { sessionId, agent, text, directory, opencodeBaseUrl, direct: true });
9899
+ const retrySendResult = await sendSessionEvent(openCodeClient, { sessionId, agent, text, directory, opencodeBaseUrl, direct: true, legacyOnly: Boolean(admissionVerification) });
9866
9900
  try {
9867
9901
  admissionVerification = openCodePromptAdmissionVerification(retrySendResult, sessionId);
9868
9902
  } catch (error) {
9869
9903
  verification = { ok: false, reason: error.message };
9870
9904
  }
9871
- if (admissionVerification) return { ok: true, verification: admissionVerification, fallback: true };
9872
9905
  verification = await verifySessionEventDelivery(openCodeClient, { sessionId, beforeMessages: retryBeforeMessages, expectedText: text, markers: eventMarkers });
9873
- if (verification?.ok) return { ok: true, verification, fallback: true };
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
+ }
9874
9910
  }
9875
9911
  const reason = verification?.reason || "message_delivery_failed";
9876
9912
  appendClickUpWebhookLocalLog(worktree, { type: "message_delivery_failed", taskId, sessionId, reason, fallbackAttempted: canFallbackDirect });
@@ -9916,7 +9952,7 @@ async function recoverClickUpPmSession({ openCodeClient, sendSessionEvent, click
9916
9952
  const replacementMetadata = clearClickUpPendingSessionMetadata(setClickUpSessionMetadata(metadataWithRouting, config.routing.metadataKey, replacementSessionId), config.routing.metadataKey);
9917
9953
  await clickupClient.updateTaskMetadata({ taskId, fieldId: config.routing.metadataFieldId, value: replacementMetadata });
9918
9954
  appendClickUpWebhookLocalLog(worktree, { type: "pm_session_recovery_succeeded", taskId, staleSessionId, replacementSessionId });
9919
- return { ok: true, action: "sent_to_replacement_session", taskId, sessionId: replacementSessionId, staleSessionId, replacementAttempted: true, eventKey, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath, evidencePath, deliveryVerification: replacementDelivery.verification, deliveryFallback: replacementDelivery.fallback };
9955
+ return { ok: true, action: "sent_to_replacement_session", taskId, sessionId: replacementSessionId, staleSessionId, replacementAttempted: true, eventKey, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath, evidencePath, deliveryVerification: replacementDelivery.verification, deliveryAdmission: replacementDelivery.admissionVerification, deliveryFallback: replacementDelivery.fallback, deliveryAttempts: replacementDelivery.fallback ? 2 : 1 };
9920
9956
  }
9921
9957
  function formatClickUpWebhookPrompt({ eventType, taskId, payload, branch = "", worktree = "", deliveryEvidencePath = "", humanRoleContext = [] }) {
9922
9958
  const comment = clickUpCommentFromPayload(payload);
@@ -10212,7 +10248,7 @@ async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, w
10212
10248
  await clickupClient.updateTaskMetadata({ taskId, fieldId: config.routing.metadataFieldId, value: nextMetadata });
10213
10249
  const { [taskId]: _completedPending, ...remainingPending } = stateToPersist.pendingSessions || {};
10214
10250
  stateToPersist = { ...stateToPersist, pendingSessions: remainingPending };
10215
- 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 });
10251
+ 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 });
10216
10252
  }
10217
10253
  const sessionId = String(existingSessionId);
10218
10254
  if (await sessionExists(openCodeClient, sessionId)) {
@@ -10222,7 +10258,7 @@ async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, w
10222
10258
  const recovery2 = await recoverClickUpPmSession({ openCodeClient, sendSessionEvent, clickupClient, worktree, taskId, staleSessionId: sessionId, sessionTitle, taskRoute, metadataWithRouting, config, prompt, eventMarkers: [taskId, eventType], deliveryEvidencePath, evidencePath: routingMetadata.evidence_path, eventKey, createSession, verifySessionEventDelivery });
10223
10259
  return finish(recovery2);
10224
10260
  }
10225
- 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 });
10261
+ 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, deliveryAdmission: delivery.admissionVerification, deliveryFallback: delivery.fallback, deliveryAttempts: delivery.fallback ? 2 : 1 });
10226
10262
  }
10227
10263
  const at = now().toISOString();
10228
10264
  appendClickUpWebhookLocalLog(worktree, { type: "missing_session", taskId, sessionId, host, at });
@@ -10233,8 +10269,44 @@ async function routeClickUpWebhookEvent(options = {}) {
10233
10269
  const taskId = clickUpTaskIdFromPayload(options.payload || {});
10234
10270
  return withClickUpTaskRouteLock(taskId, () => routeClickUpWebhookEventUnlocked(options));
10235
10271
  }
10236
- async function reconcileClickUpStartup({ config, state = {}, worktree = process.cwd(), clickupClient, openCodeClient, sessionExists = openCodeSessionExists, createSession = createOpenCodeSession, sendSessionEvent = sendOpenCodeSessionEvent, verifySessionEventDelivery = verifyOpenCodeSessionEventDelivery, saveState = null, now = () => /* @__PURE__ */ new Date(), limit = CLICKUP_WEBHOOK_STARTUP_TASK_LIMIT } = {}) {
10237
- if (!config || !clickupClient?.listAssignedTasks) return { ok: true, skipped: true, reason: "clickup_task_listing_unavailable", assigned: 0, comments: 0 };
10272
+ function defaultStartupReconciliationScheduler(run, delayMs) {
10273
+ return setTimeout(run, delayMs);
10274
+ }
10275
+ function scheduleClickUpStartupReconciliation({ config, state = {}, worktree = process.cwd(), clickupClient, openCodeClient, scheduler = defaultStartupReconciliationScheduler, delayMs = config?.opencode?.startupReconciliationDelayMs ?? CLICKUP_WEBHOOK_STARTUP_RECONCILIATION_DELAY_MS, saveState = null, reconcile = reconcileClickUpStartup } = {}) {
10276
+ const normalizedDelayMs = normalizeNonNegativeInteger(delayMs, CLICKUP_WEBHOOK_STARTUP_RECONCILIATION_DELAY_MS);
10277
+ clickUpWebhookLifecycleLog(worktree, { type: "startup_reconciliation_scheduled", delayMs: normalizedDelayMs, webhookId: state?.webhookId || null });
10278
+ const run = () => {
10279
+ Promise.resolve().then(() => reconcile({
10280
+ config,
10281
+ state,
10282
+ worktree,
10283
+ clickupClient,
10284
+ openCodeClient,
10285
+ saveState
10286
+ })).catch((error) => {
10287
+ clickUpWebhookLifecycleLog(worktree, { type: "startup_reconciliation_failed", message: error.message });
10288
+ appendClickUpWebhookLocalLog(worktree, { type: "startup_reconciliation_failed", message: error.message });
10289
+ });
10290
+ };
10291
+ const timer = scheduler(run, normalizedDelayMs);
10292
+ timer?.unref?.();
10293
+ return { scheduled: true, delayMs: normalizedDelayMs, timer };
10294
+ }
10295
+ async function reconcileClickUpStartup({ config, state = {}, worktree = process.cwd(), clickupClient, openCodeClient, sessionExists = openCodeSessionExists, createSession = createOpenCodeSession, sendSessionEvent = sendOpenCodeSessionEvent, verifySessionEventDelivery = verifyOpenCodeSessionEventDelivery, waitForReadiness = waitForOpenCodeReadiness, saveState = null, now = () => /* @__PURE__ */ new Date(), limit = CLICKUP_WEBHOOK_STARTUP_TASK_LIMIT } = {}) {
10296
+ clickUpWebhookLifecycleLog(worktree, { type: "startup_reconciliation_started", lastWebhookAt: state.lastWebhookAt || null });
10297
+ if (!config || !clickupClient?.listAssignedTasks) {
10298
+ const skipped = { ok: true, skipped: true, reason: "clickup_task_listing_unavailable", assigned: 0, comments: 0 };
10299
+ clickUpWebhookLifecycleLog(worktree, { type: "startup_reconciliation_finished", ...skipped });
10300
+ return skipped;
10301
+ }
10302
+ const readiness = await waitForReadiness(openCodeClient, { worktree, now });
10303
+ appendClickUpWebhookLocalLog(worktree, { type: readiness.ok ? "startup_reconciliation_readiness_ready" : "startup_reconciliation_readiness_failed", ...readiness });
10304
+ if (!readiness.ok) {
10305
+ const failed = { ok: false, skipped: true, reason: "opencode_not_ready", readiness, assigned: 0, comments: 0, ignored: 0, errors: 1, undelivered: 1, tasks: [], validation: { undelivered: 1, emptyPromptSessions: [] } };
10306
+ clickUpWebhookLifecycleLog(worktree, { type: "startup_reconciliation_failed", reason: failed.reason, readiness });
10307
+ clickUpWebhookLifecycleLog(worktree, { type: "startup_reconciliation_finished", ...failed });
10308
+ return failed;
10309
+ }
10238
10310
  let authorizedUserId = "";
10239
10311
  if (clickupClient?.getAuthorizedUser) {
10240
10312
  try {
@@ -10252,7 +10324,6 @@ async function reconcileClickUpStartup({ config, state = {}, worktree = process.
10252
10324
  const lastWebhookMs = clickUpTimestampMs(mutableState.lastWebhookAt);
10253
10325
  const ignored = new Set((config.routing?.ignoredStatuses || CLICKUP_WEBHOOK_TERMINAL_STATUSES).map(normalizeClickUpStatus));
10254
10326
  const routed = { assigned: 0, comments: 0, ignored: 0, errors: 0, undelivered: 0, tasks: [] };
10255
- clickUpWebhookLifecycleLog(worktree, { type: "startup_reconciliation_started", lastWebhookAt: state.lastWebhookAt || null });
10256
10327
  const listed = await clickupClient.listAssignedTasks({ assigneeId: config.routing.productManagerAssigneeId, limit });
10257
10328
  const tasks = clickUpTaskListItems(listed).slice(0, limit);
10258
10329
  for (const task of tasks) {
@@ -10292,7 +10363,10 @@ async function reconcileClickUpStartup({ config, state = {}, worktree = process.
10292
10363
  branch: result?.branch || null,
10293
10364
  worktree: result?.worktree || null,
10294
10365
  verification: result?.deliveryVerification?.method || result?.deliveryVerification?.reason || null,
10295
- delivered: hasActionableClickUpRoute(result)
10366
+ delivered: hasActionableClickUpRoute(result),
10367
+ attempts: result?.deliveryAttempts || null,
10368
+ finalMessageCount: result?.deliveryVerification?.afterCount ?? null,
10369
+ finalMessageId: result?.deliveryVerification?.lastMessageId || null
10296
10370
  };
10297
10371
  routed.tasks.push(routeSummary);
10298
10372
  appendClickUpWebhookLocalLog(worktree, { type: "startup_reconciliation_task_routed", ...routeSummary });
@@ -11567,18 +11641,16 @@ async function OptimaPlugin(input = {}, pluginOptions = {}) {
11567
11641
  listener: { bindHost: clickUpWebhookValidation.config.webhook.bindHost, bindPort: clickUpWebhookValidation.config.webhook.bindPort, startedAt: (/* @__PURE__ */ new Date()).toISOString() }
11568
11642
  }, clickUpWebhookValidation.config);
11569
11643
  registerClickUpWebhookLifecycle({ config: clickUpWebhookValidation.config, state: activeState, worktree, clickupClient: lifecycleClickUpClient, listener: readyListener, listenerRegistry });
11570
- try {
11571
- await reconcileClickUpStartup({
11572
- config: clickUpWebhookValidation.config,
11573
- state: activeState,
11574
- worktree,
11575
- clickupClient: lifecycleClickUpClient,
11576
- openCodeClient: input.client,
11577
- saveState: (nextState) => writeClickUpWebhookState(worktree, nextState, clickUpWebhookValidation.config)
11578
- });
11579
- } catch (error) {
11580
- appendClickUpWebhookLocalLog(worktree, { type: "startup_reconciliation_failed", message: error.message });
11581
- }
11644
+ scheduleClickUpStartupReconciliation({
11645
+ config: clickUpWebhookValidation.config,
11646
+ state: activeState,
11647
+ worktree,
11648
+ clickupClient: lifecycleClickUpClient,
11649
+ openCodeClient: input.client,
11650
+ scheduler: input.startupReconciliationScheduler,
11651
+ delayMs: input.startupReconciliationDelayMs,
11652
+ saveState: (nextState) => writeClickUpWebhookState(worktree, nextState, clickUpWebhookValidation.config)
11653
+ });
11582
11654
  } else {
11583
11655
  clickUpWebhookLifecycleLog(worktree, { type: "remote_webhook_preserved", reason: readyListener.reason || "listener_unavailable", webhookId: listenerState.webhookId });
11584
11656
  markClickUpWebhookInactive(worktree, listenerState, clickUpWebhookValidation.config);
@@ -12153,7 +12225,7 @@ Follow-up: use optima_prompt_workflow with session_id '${sessionId}' to check in
12153
12225
  }
12154
12226
  };
12155
12227
  }
12156
- 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, createOpenCodeSession, deliveryEvidencePathForClickUpTask, ensureClickUpTaskWorktree, ensureClickUpWebhookSubscription, handleClickUpWebhookRequest, isClickUpWebhookStateActive, normalizeClickUpWebhookConfig, normalizeClickUpWebhookLogLevel, normalizeOpenCodeBaseUrl, openCodeSessionExists, readClickUpCommentLedger, readClickUpWebhookState, readOpenCodeSessionMessages, reconcileClickUpStartup, recordClickUpCommentVersionProcessed, resyncClickUpWebhookForSignatureDrift, 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 };
12228
+ 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, createOpenCodeSession, deliveryEvidencePathForClickUpTask, ensureClickUpTaskWorktree, ensureClickUpWebhookSubscription, handleClickUpWebhookRequest, isClickUpWebhookStateActive, normalizeClickUpWebhookConfig, normalizeClickUpWebhookLogLevel, normalizeOpenCodeBaseUrl, openCodeSessionExists, readClickUpCommentLedger, readClickUpWebhookState, readOpenCodeSessionMessages, reconcileClickUpStartup, scheduleClickUpStartupReconciliation, waitForOpenCodeReadiness, recordClickUpCommentVersionProcessed, resyncClickUpWebhookForSignatureDrift, 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 };
12157
12229
  export {
12158
12230
  OptimaPlugin as default
12159
12231
  };
@@ -7945,6 +7945,7 @@ var CLICKUP_WEBHOOK_MAX_BODY_BYTES = 1024 * 1024;
7945
7945
  var CLICKUP_WEBHOOK_REQUEST_TIMEOUT_MS = 1e4;
7946
7946
  var CLICKUP_WEBHOOK_STARTUP_TASK_LIMIT = 50;
7947
7947
  var CLICKUP_WEBHOOK_STARTUP_COMMENT_LIMIT = 100;
7948
+ var CLICKUP_WEBHOOK_STARTUP_RECONCILIATION_DELAY_MS = 3e4;
7948
7949
  var CLICKUP_WEBHOOK_LOG_LEVELS = /* @__PURE__ */ new Set(["error", "info", "verbose"]);
7949
7950
  var CLICKUP_WEBHOOK_REDACTED = "[REDACTED]";
7950
7951
  var DISCUSSION_BACKFILL_FETCH_LIMIT = 100;
@@ -8170,6 +8171,7 @@ function hasActionableClickUpRoute(result = {}) {
8170
8171
  if (!result.sessionId) return false;
8171
8172
  if (result.action === "message_delivery_failed" || result.action === "error") return false;
8172
8173
  if (result.deliveryVerification?.ok === false) return false;
8174
+ if (result.deliveryVerification?.method === "prompt_admission") return false;
8173
8175
  return true;
8174
8176
  }
8175
8177
  function determineClickUpMergeAuthority({ isSubtask = false, clickupStatus = "", validationPassed = false, mergeFailed = false, finalApprovalRoles = CLICKUP_FINAL_APPROVER_ROLES, humansRegistry } = {}) {
@@ -9019,6 +9021,11 @@ function normalizeOpenCodeBaseUrl(value, defaultValue = "http://127.0.0.1:3001")
9019
9021
  return candidate.replace(/\/+$/, "");
9020
9022
  }
9021
9023
  }
9024
+ function normalizeNonNegativeInteger(value, defaultValue) {
9025
+ if (value === void 0 || value === null || value === "") return defaultValue;
9026
+ const number = Number(value);
9027
+ return Number.isInteger(number) && number >= 0 ? number : defaultValue;
9028
+ }
9022
9029
  function normalizeClickUpWebhookConfig(rawClickUp = null, worktree = process.cwd()) {
9023
9030
  const raw = isPlainObject(rawClickUp) ? rawClickUp : {};
9024
9031
  const webhook = isPlainObject(raw.webhook) ? raw.webhook : {};
@@ -9034,7 +9041,11 @@ function normalizeClickUpWebhookConfig(rawClickUp = null, worktree = process.cwd
9034
9041
  apiToken: String(raw.api_token || raw.apiToken || "").trim(),
9035
9042
  log: normalizeClickUpWebhookLogLevel(raw.log),
9036
9043
  opencode: {
9037
- baseUrl: normalizeOpenCodeBaseUrl(opencode.base_url || opencode.baseUrl || raw.opencode_base_url || raw.opencodeBaseUrl)
9044
+ baseUrl: normalizeOpenCodeBaseUrl(opencode.base_url || opencode.baseUrl || raw.opencode_base_url || raw.opencodeBaseUrl),
9045
+ startupReconciliationDelayMs: normalizeNonNegativeInteger(
9046
+ opencode.startup_reconciliation_delay_ms ?? opencode.startupReconciliationDelayMs ?? raw.startup_reconciliation_delay_ms ?? raw.startupReconciliationDelayMs,
9047
+ CLICKUP_WEBHOOK_STARTUP_RECONCILIATION_DELAY_MS
9048
+ )
9038
9049
  },
9039
9050
  webhook: {
9040
9051
  publicUrl: String(webhook.public_url || webhook.publicUrl || "").trim(),
@@ -9662,6 +9673,22 @@ async function createOpenCodeSession(client, { title, directory, agent } = {}) {
9662
9673
  }
9663
9674
  throw firstError || new Error("OpenCode session create failed.");
9664
9675
  }
9676
+ async function waitForOpenCodeReadiness(client, { worktree = process.cwd(), attempts = 10, delayMs = 500, now = () => /* @__PURE__ */ new Date() } = {}) {
9677
+ if (typeof client?.session?.create !== "function") return { ok: true, skipped: true, reason: "session_create_probe_unavailable" };
9678
+ let lastError = "opencode_not_ready";
9679
+ const maxAttempts = Math.max(1, Number(attempts) || 1);
9680
+ for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
9681
+ try {
9682
+ const sessionId = await createOpenCodeSession(client, { title: `Optima startup readiness probe ${now().toISOString()}`, directory: worktree });
9683
+ if (await openCodeSessionExists(client, sessionId)) return { ok: true, method: "session_create_probe", sessionId, attempts: attempt };
9684
+ lastError = "readiness_probe_session_not_visible";
9685
+ } catch (error) {
9686
+ lastError = error.message || "opencode_not_ready";
9687
+ }
9688
+ if (attempt < maxAttempts && delayMs > 0) await new Promise((resolve) => setTimeout(resolve, delayMs));
9689
+ }
9690
+ return { ok: false, reason: lastError, attempts: maxAttempts };
9691
+ }
9665
9692
  function assertOpenCodePromptAccepted(result) {
9666
9693
  const status = Number(result?.status || result?.response?.status || result?.error?.status || 0);
9667
9694
  if (status >= 400 || result?.ok === false || result?.error) {
@@ -9699,13 +9726,13 @@ async function readOpenCodeJsonResponse(response, endpointName) {
9699
9726
  return { raw };
9700
9727
  }
9701
9728
  }
9702
- async function sendOpenCodeSessionEventDirect({ baseUrl, sessionId, text, agent, fetchImpl = globalThis.fetch } = {}) {
9729
+ async function sendOpenCodeSessionEventDirect({ baseUrl, sessionId, text, agent, fetchImpl = globalThis.fetch, legacyOnly = false } = {}) {
9703
9730
  if (typeof fetchImpl !== "function") throw new Error("OpenCode direct prompt delivery requires fetch.");
9704
9731
  const root = normalizeOpenCodeBaseUrl(baseUrl, "");
9705
9732
  if (!root) throw new Error("OpenCode direct prompt delivery requires a base URL.");
9706
9733
  const encodedSession = encodeURIComponent(sessionId);
9707
9734
  const attempts = [
9708
- {
9735
+ legacyOnly ? null : {
9709
9736
  name: "v2 prompt",
9710
9737
  url: `${root}/api/session/${encodedSession}/prompt`,
9711
9738
  body: { prompt: { text }, delivery: "queue", resume: true },
@@ -9724,7 +9751,7 @@ async function sendOpenCodeSessionEventDirect({ baseUrl, sessionId, text, agent,
9724
9751
  return { ok: true, method: "http", endpoint: "/session/{sessionID}/prompt_async", status: response.status, data: data?.data || null, response: data };
9725
9752
  }
9726
9753
  }
9727
- ];
9754
+ ].filter(Boolean);
9728
9755
  let firstError = null;
9729
9756
  for (const attempt of attempts) {
9730
9757
  const response = await fetchImpl(attempt.url, {
@@ -9758,7 +9785,7 @@ async function callOpenCodePromptWithFallbacks(method, sessionId, flatPayload, s
9758
9785
  }
9759
9786
  throw firstError;
9760
9787
  }
9761
- async function sendOpenCodeSessionEvent(client, { sessionId, agent, text, directory, opencodeBaseUrl, baseUrl, fetchImpl, direct = false } = {}) {
9788
+ async function sendOpenCodeSessionEvent(client, { sessionId, agent, text, directory, opencodeBaseUrl, baseUrl, fetchImpl, direct = false, legacyOnly = false } = {}) {
9762
9789
  const directBaseUrl = opencodeBaseUrl || baseUrl;
9763
9790
  const parts = [{ type: "text", text }];
9764
9791
  const flatPayload = { directory, agent, parts };
@@ -9782,7 +9809,7 @@ async function sendOpenCodeSessionEvent(client, { sessionId, agent, text, direct
9782
9809
  firstError ??= error;
9783
9810
  }
9784
9811
  }
9785
- if (directBaseUrl) return sendOpenCodeSessionEventDirect({ baseUrl: directBaseUrl, sessionId, text, agent, fetchImpl });
9812
+ if (directBaseUrl) return sendOpenCodeSessionEventDirect({ baseUrl: directBaseUrl, sessionId, text, agent, fetchImpl, legacyOnly });
9786
9813
  if (firstError) throw firstError;
9787
9814
  throw new Error("OpenCode client does not expose session.prompt or session.promptAsync.");
9788
9815
  }
@@ -9817,18 +9844,20 @@ function openCodeMessageText(message) {
9817
9844
  for (const part of partList) parts.push(part?.text, part?.content);
9818
9845
  return parts.filter((value) => typeof value === "string").join("\n");
9819
9846
  }
9820
- async function verifyOpenCodeSessionEventDelivery(client, { sessionId, beforeMessages = null, expectedText = "", markers = [], attempts = 3, delayMs = 25 } = {}) {
9847
+ async function verifyOpenCodeSessionEventDelivery(client, { sessionId, beforeMessages = null, expectedText = "", markers = [], attempts = 8, delayMs = 250 } = {}) {
9821
9848
  let lastError = "message_verification_unavailable";
9822
9849
  for (let attempt = 0; attempt < Math.max(1, attempts); attempt += 1) {
9823
9850
  try {
9824
9851
  const afterMessages = await readOpenCodeSessionMessages(client, { sessionId, limit: 50 });
9825
- if (!afterMessages) return { ok: true, method: "verification_unavailable", skipped: true };
9852
+ if (!afterMessages) return { ok: false, reason: "message_verification_unavailable" };
9826
9853
  const beforeCount = Array.isArray(beforeMessages) ? beforeMessages.length : null;
9827
- if (beforeCount !== null && afterMessages.length > beforeCount) return { ok: true, method: "message_count", beforeCount, afterCount: afterMessages.length };
9854
+ const lastMessage = afterMessages.at(-1) || null;
9855
+ const lastMessageId = lastMessage?.id || lastMessage?.messageID || lastMessage?.messageId || null;
9856
+ if (beforeCount !== null && afterMessages.length > beforeCount) return { ok: true, method: "message_count", beforeCount, afterCount: afterMessages.length, lastMessageId };
9828
9857
  const textNeedles = [expectedText, ...markers].map((value) => String(value || "").trim()).filter(Boolean);
9829
9858
  const haystack = afterMessages.slice(-20).map(openCodeMessageText).join("\n");
9830
9859
  const matched = textNeedles.find((needle) => haystack.includes(needle));
9831
- if (matched) return { ok: true, method: "message_text", marker: matched, beforeCount, afterCount: afterMessages.length };
9860
+ if (matched) return { ok: true, method: "message_text", marker: matched, beforeCount, afterCount: afterMessages.length, lastMessageId };
9832
9861
  lastError = "message_not_visible";
9833
9862
  } catch (error) {
9834
9863
  lastError = error.message || "message_verification_failed";
@@ -9863,21 +9892,28 @@ async function deliverClickUpSessionEventWithVerification({ openCodeClient, send
9863
9892
  const blocker2 = await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason: error.message, source: "delivery_admission_failed" });
9864
9893
  return { ok: false, action: "message_delivery_failed", reason: error.message, taskId, sessionId, fallbackAttempted: false, blockerTag: blocker2 };
9865
9894
  }
9866
- if (admissionVerification) return { ok: true, verification: admissionVerification, fallback: false };
9867
9895
  let verification = await verifySessionEventDelivery(openCodeClient, { sessionId, beforeMessages, expectedText: text, markers: eventMarkers });
9868
- if (verification?.ok) return { ok: true, verification, fallback: false };
9896
+ 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
+ if (admissionVerification) {
9901
+ appendClickUpWebhookLocalLog(worktree, { type: "message_delivery_admitted_but_invisible", taskId, sessionId, admission: admissionVerification, reason: verification?.reason || "message_not_visible" });
9902
+ }
9869
9903
  const canFallbackDirect = Boolean(opencodeBaseUrl);
9870
9904
  if (canFallbackDirect) {
9871
9905
  const retryBeforeMessages = await readOpenCodeSessionMessages(openCodeClient, { sessionId, limit: 50 }).catch(() => beforeMessages);
9872
- const retrySendResult = await sendSessionEvent(openCodeClient, { sessionId, agent, text, directory, opencodeBaseUrl, direct: true });
9906
+ const retrySendResult = await sendSessionEvent(openCodeClient, { sessionId, agent, text, directory, opencodeBaseUrl, direct: true, legacyOnly: Boolean(admissionVerification) });
9873
9907
  try {
9874
9908
  admissionVerification = openCodePromptAdmissionVerification(retrySendResult, sessionId);
9875
9909
  } catch (error) {
9876
9910
  verification = { ok: false, reason: error.message };
9877
9911
  }
9878
- if (admissionVerification) return { ok: true, verification: admissionVerification, fallback: true };
9879
9912
  verification = await verifySessionEventDelivery(openCodeClient, { sessionId, beforeMessages: retryBeforeMessages, expectedText: text, markers: eventMarkers });
9880
- if (verification?.ok) return { ok: true, verification, fallback: true };
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
+ }
9881
9917
  }
9882
9918
  const reason = verification?.reason || "message_delivery_failed";
9883
9919
  appendClickUpWebhookLocalLog(worktree, { type: "message_delivery_failed", taskId, sessionId, reason, fallbackAttempted: canFallbackDirect });
@@ -9923,7 +9959,7 @@ async function recoverClickUpPmSession({ openCodeClient, sendSessionEvent, click
9923
9959
  const replacementMetadata = clearClickUpPendingSessionMetadata(setClickUpSessionMetadata(metadataWithRouting, config.routing.metadataKey, replacementSessionId), config.routing.metadataKey);
9924
9960
  await clickupClient.updateTaskMetadata({ taskId, fieldId: config.routing.metadataFieldId, value: replacementMetadata });
9925
9961
  appendClickUpWebhookLocalLog(worktree, { type: "pm_session_recovery_succeeded", taskId, staleSessionId, replacementSessionId });
9926
- return { ok: true, action: "sent_to_replacement_session", taskId, sessionId: replacementSessionId, staleSessionId, replacementAttempted: true, eventKey, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath, evidencePath, deliveryVerification: replacementDelivery.verification, deliveryFallback: replacementDelivery.fallback };
9962
+ return { ok: true, action: "sent_to_replacement_session", taskId, sessionId: replacementSessionId, staleSessionId, replacementAttempted: true, eventKey, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath, evidencePath, deliveryVerification: replacementDelivery.verification, deliveryAdmission: replacementDelivery.admissionVerification, deliveryFallback: replacementDelivery.fallback, deliveryAttempts: replacementDelivery.fallback ? 2 : 1 };
9927
9963
  }
9928
9964
  function formatClickUpWebhookPrompt({ eventType, taskId, payload, branch = "", worktree = "", deliveryEvidencePath = "", humanRoleContext = [] }) {
9929
9965
  const comment = clickUpCommentFromPayload(payload);
@@ -10219,7 +10255,7 @@ async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, w
10219
10255
  await clickupClient.updateTaskMetadata({ taskId, fieldId: config.routing.metadataFieldId, value: nextMetadata });
10220
10256
  const { [taskId]: _completedPending, ...remainingPending } = stateToPersist.pendingSessions || {};
10221
10257
  stateToPersist = { ...stateToPersist, pendingSessions: remainingPending };
10222
- 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 });
10258
+ 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 });
10223
10259
  }
10224
10260
  const sessionId = String(existingSessionId);
10225
10261
  if (await sessionExists(openCodeClient, sessionId)) {
@@ -10229,7 +10265,7 @@ async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, w
10229
10265
  const recovery2 = await recoverClickUpPmSession({ openCodeClient, sendSessionEvent, clickupClient, worktree, taskId, staleSessionId: sessionId, sessionTitle, taskRoute, metadataWithRouting, config, prompt, eventMarkers: [taskId, eventType], deliveryEvidencePath, evidencePath: routingMetadata.evidence_path, eventKey, createSession, verifySessionEventDelivery });
10230
10266
  return finish(recovery2);
10231
10267
  }
10232
- 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 });
10268
+ 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, deliveryAdmission: delivery.admissionVerification, deliveryFallback: delivery.fallback, deliveryAttempts: delivery.fallback ? 2 : 1 });
10233
10269
  }
10234
10270
  const at = now().toISOString();
10235
10271
  appendClickUpWebhookLocalLog(worktree, { type: "missing_session", taskId, sessionId, host, at });
@@ -10240,8 +10276,44 @@ async function routeClickUpWebhookEvent(options = {}) {
10240
10276
  const taskId = clickUpTaskIdFromPayload(options.payload || {});
10241
10277
  return withClickUpTaskRouteLock(taskId, () => routeClickUpWebhookEventUnlocked(options));
10242
10278
  }
10243
- async function reconcileClickUpStartup({ config, state = {}, worktree = process.cwd(), clickupClient, openCodeClient, sessionExists = openCodeSessionExists, createSession = createOpenCodeSession, sendSessionEvent = sendOpenCodeSessionEvent, verifySessionEventDelivery = verifyOpenCodeSessionEventDelivery, saveState = null, now = () => /* @__PURE__ */ new Date(), limit = CLICKUP_WEBHOOK_STARTUP_TASK_LIMIT } = {}) {
10244
- if (!config || !clickupClient?.listAssignedTasks) return { ok: true, skipped: true, reason: "clickup_task_listing_unavailable", assigned: 0, comments: 0 };
10279
+ function defaultStartupReconciliationScheduler(run, delayMs) {
10280
+ return setTimeout(run, delayMs);
10281
+ }
10282
+ function scheduleClickUpStartupReconciliation({ config, state = {}, worktree = process.cwd(), clickupClient, openCodeClient, scheduler = defaultStartupReconciliationScheduler, delayMs = config?.opencode?.startupReconciliationDelayMs ?? CLICKUP_WEBHOOK_STARTUP_RECONCILIATION_DELAY_MS, saveState = null, reconcile = reconcileClickUpStartup } = {}) {
10283
+ const normalizedDelayMs = normalizeNonNegativeInteger(delayMs, CLICKUP_WEBHOOK_STARTUP_RECONCILIATION_DELAY_MS);
10284
+ clickUpWebhookLifecycleLog(worktree, { type: "startup_reconciliation_scheduled", delayMs: normalizedDelayMs, webhookId: state?.webhookId || null });
10285
+ const run = () => {
10286
+ Promise.resolve().then(() => reconcile({
10287
+ config,
10288
+ state,
10289
+ worktree,
10290
+ clickupClient,
10291
+ openCodeClient,
10292
+ saveState
10293
+ })).catch((error) => {
10294
+ clickUpWebhookLifecycleLog(worktree, { type: "startup_reconciliation_failed", message: error.message });
10295
+ appendClickUpWebhookLocalLog(worktree, { type: "startup_reconciliation_failed", message: error.message });
10296
+ });
10297
+ };
10298
+ const timer = scheduler(run, normalizedDelayMs);
10299
+ timer?.unref?.();
10300
+ return { scheduled: true, delayMs: normalizedDelayMs, timer };
10301
+ }
10302
+ async function reconcileClickUpStartup({ config, state = {}, worktree = process.cwd(), clickupClient, openCodeClient, sessionExists = openCodeSessionExists, createSession = createOpenCodeSession, sendSessionEvent = sendOpenCodeSessionEvent, verifySessionEventDelivery = verifyOpenCodeSessionEventDelivery, waitForReadiness = waitForOpenCodeReadiness, saveState = null, now = () => /* @__PURE__ */ new Date(), limit = CLICKUP_WEBHOOK_STARTUP_TASK_LIMIT } = {}) {
10303
+ clickUpWebhookLifecycleLog(worktree, { type: "startup_reconciliation_started", lastWebhookAt: state.lastWebhookAt || null });
10304
+ if (!config || !clickupClient?.listAssignedTasks) {
10305
+ const skipped = { ok: true, skipped: true, reason: "clickup_task_listing_unavailable", assigned: 0, comments: 0 };
10306
+ clickUpWebhookLifecycleLog(worktree, { type: "startup_reconciliation_finished", ...skipped });
10307
+ return skipped;
10308
+ }
10309
+ const readiness = await waitForReadiness(openCodeClient, { worktree, now });
10310
+ appendClickUpWebhookLocalLog(worktree, { type: readiness.ok ? "startup_reconciliation_readiness_ready" : "startup_reconciliation_readiness_failed", ...readiness });
10311
+ if (!readiness.ok) {
10312
+ const failed = { ok: false, skipped: true, reason: "opencode_not_ready", readiness, assigned: 0, comments: 0, ignored: 0, errors: 1, undelivered: 1, tasks: [], validation: { undelivered: 1, emptyPromptSessions: [] } };
10313
+ clickUpWebhookLifecycleLog(worktree, { type: "startup_reconciliation_failed", reason: failed.reason, readiness });
10314
+ clickUpWebhookLifecycleLog(worktree, { type: "startup_reconciliation_finished", ...failed });
10315
+ return failed;
10316
+ }
10245
10317
  let authorizedUserId = "";
10246
10318
  if (clickupClient?.getAuthorizedUser) {
10247
10319
  try {
@@ -10259,7 +10331,6 @@ async function reconcileClickUpStartup({ config, state = {}, worktree = process.
10259
10331
  const lastWebhookMs = clickUpTimestampMs(mutableState.lastWebhookAt);
10260
10332
  const ignored = new Set((config.routing?.ignoredStatuses || CLICKUP_WEBHOOK_TERMINAL_STATUSES).map(normalizeClickUpStatus));
10261
10333
  const routed = { assigned: 0, comments: 0, ignored: 0, errors: 0, undelivered: 0, tasks: [] };
10262
- clickUpWebhookLifecycleLog(worktree, { type: "startup_reconciliation_started", lastWebhookAt: state.lastWebhookAt || null });
10263
10334
  const listed = await clickupClient.listAssignedTasks({ assigneeId: config.routing.productManagerAssigneeId, limit });
10264
10335
  const tasks = clickUpTaskListItems(listed).slice(0, limit);
10265
10336
  for (const task of tasks) {
@@ -10299,7 +10370,10 @@ async function reconcileClickUpStartup({ config, state = {}, worktree = process.
10299
10370
  branch: result?.branch || null,
10300
10371
  worktree: result?.worktree || null,
10301
10372
  verification: result?.deliveryVerification?.method || result?.deliveryVerification?.reason || null,
10302
- delivered: hasActionableClickUpRoute(result)
10373
+ delivered: hasActionableClickUpRoute(result),
10374
+ attempts: result?.deliveryAttempts || null,
10375
+ finalMessageCount: result?.deliveryVerification?.afterCount ?? null,
10376
+ finalMessageId: result?.deliveryVerification?.lastMessageId || null
10303
10377
  };
10304
10378
  routed.tasks.push(routeSummary);
10305
10379
  appendClickUpWebhookLocalLog(worktree, { type: "startup_reconciliation_task_routed", ...routeSummary });
@@ -11574,18 +11648,16 @@ async function OptimaPlugin(input = {}, pluginOptions = {}) {
11574
11648
  listener: { bindHost: clickUpWebhookValidation.config.webhook.bindHost, bindPort: clickUpWebhookValidation.config.webhook.bindPort, startedAt: (/* @__PURE__ */ new Date()).toISOString() }
11575
11649
  }, clickUpWebhookValidation.config);
11576
11650
  registerClickUpWebhookLifecycle({ config: clickUpWebhookValidation.config, state: activeState, worktree, clickupClient: lifecycleClickUpClient, listener: readyListener, listenerRegistry });
11577
- try {
11578
- await reconcileClickUpStartup({
11579
- config: clickUpWebhookValidation.config,
11580
- state: activeState,
11581
- worktree,
11582
- clickupClient: lifecycleClickUpClient,
11583
- openCodeClient: input.client,
11584
- saveState: (nextState) => writeClickUpWebhookState(worktree, nextState, clickUpWebhookValidation.config)
11585
- });
11586
- } catch (error) {
11587
- appendClickUpWebhookLocalLog(worktree, { type: "startup_reconciliation_failed", message: error.message });
11588
- }
11651
+ scheduleClickUpStartupReconciliation({
11652
+ config: clickUpWebhookValidation.config,
11653
+ state: activeState,
11654
+ worktree,
11655
+ clickupClient: lifecycleClickUpClient,
11656
+ openCodeClient: input.client,
11657
+ scheduler: input.startupReconciliationScheduler,
11658
+ delayMs: input.startupReconciliationDelayMs,
11659
+ saveState: (nextState) => writeClickUpWebhookState(worktree, nextState, clickUpWebhookValidation.config)
11660
+ });
11589
11661
  } else {
11590
11662
  clickUpWebhookLifecycleLog(worktree, { type: "remote_webhook_preserved", reason: readyListener.reason || "listener_unavailable", webhookId: listenerState.webhookId });
11591
11663
  markClickUpWebhookInactive(worktree, listenerState, clickUpWebhookValidation.config);
@@ -12160,7 +12232,7 @@ Follow-up: use optima_prompt_workflow with session_id '${sessionId}' to check in
12160
12232
  }
12161
12233
  };
12162
12234
  }
12163
- 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, createOpenCodeSession, deliveryEvidencePathForClickUpTask, ensureClickUpTaskWorktree, ensureClickUpWebhookSubscription, handleClickUpWebhookRequest, isClickUpWebhookStateActive, normalizeClickUpWebhookConfig, normalizeClickUpWebhookLogLevel, normalizeOpenCodeBaseUrl, openCodeSessionExists, readClickUpCommentLedger, readClickUpWebhookState, readOpenCodeSessionMessages, reconcileClickUpStartup, recordClickUpCommentVersionProcessed, resyncClickUpWebhookForSignatureDrift, 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 };
12235
+ 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, createOpenCodeSession, deliveryEvidencePathForClickUpTask, ensureClickUpTaskWorktree, ensureClickUpWebhookSubscription, handleClickUpWebhookRequest, isClickUpWebhookStateActive, normalizeClickUpWebhookConfig, normalizeClickUpWebhookLogLevel, normalizeOpenCodeBaseUrl, openCodeSessionExists, readClickUpCommentLedger, readClickUpWebhookState, readOpenCodeSessionMessages, reconcileClickUpStartup, scheduleClickUpStartupReconciliation, waitForOpenCodeReadiness, recordClickUpCommentVersionProcessed, resyncClickUpWebhookForSignatureDrift, 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 };
12164
12236
 
12165
12237
  // src/sanitize_cli.js
12166
12238
  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.48",
3
+ "version": "0.1.50",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+ssh://git@github.com/defend-tech/opencode-optima.git"