@defend-tech/opencode-optima 0.1.35 → 0.1.36

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
@@ -7935,6 +7935,8 @@ var CLICKUP_PM_MENTION_NAME = "Defend Tech Product Manager";
7935
7935
  var CLICKUP_WEBHOOK_RUNTIME_VERSION = 1;
7936
7936
  var CLICKUP_WEBHOOK_MAX_BODY_BYTES = 1024 * 1024;
7937
7937
  var CLICKUP_WEBHOOK_REQUEST_TIMEOUT_MS = 1e4;
7938
+ var CLICKUP_WEBHOOK_STARTUP_TASK_LIMIT = 50;
7939
+ var CLICKUP_WEBHOOK_STARTUP_COMMENT_LIMIT = 100;
7938
7940
  var CLICKUP_WEBHOOK_LOG_LEVELS = /* @__PURE__ */ new Set(["error", "info", "verbose"]);
7939
7941
  var CLICKUP_WEBHOOK_REDACTED = "[REDACTED]";
7940
7942
  var DISCUSSION_BACKFILL_FETCH_LIMIT = 100;
@@ -9053,6 +9055,7 @@ function sanitizeClickUpWebhookState(state = {}, config = null) {
9053
9055
  lastValidatedAt: state.lastValidatedAt || null,
9054
9056
  listener: isPlainObject(state.listener) ? state.listener : {},
9055
9057
  pendingSessions: isPlainObject(state.pendingSessions) ? state.pendingSessions : {},
9058
+ lastWebhookAt: state.lastWebhookAt || state.last_webhook_at || null,
9056
9059
  recentEventKeys
9057
9060
  };
9058
9061
  }
@@ -9135,6 +9138,9 @@ function createClickUpApiClient(config, fetchImpl = globalThis.fetch) {
9135
9138
  async deleteWebhook(webhookId) {
9136
9139
  return request(`https://api.clickup.com/api/v2/webhook/${encodeURIComponent(webhookId)}`, { method: "DELETE" });
9137
9140
  },
9141
+ async getAuthorizedUser() {
9142
+ return request("https://api.clickup.com/api/v2/user");
9143
+ },
9138
9144
  async getTask(taskId) {
9139
9145
  return request(`https://api.clickup.com/api/v2/task/${encodeURIComponent(taskId)}`);
9140
9146
  },
@@ -9149,6 +9155,20 @@ function createClickUpApiClient(config, fetchImpl = globalThis.fetch) {
9149
9155
  method: "POST",
9150
9156
  body: JSON.stringify({ comment_text: comment })
9151
9157
  });
9158
+ },
9159
+ async listAssignedTasks({ assigneeId, statuses = [], limit = CLICKUP_WEBHOOK_STARTUP_TASK_LIMIT } = {}) {
9160
+ const params = new URLSearchParams();
9161
+ if (assigneeId) params.append("assignees[]", assigneeId);
9162
+ for (const status of statuses) if (status) params.append("statuses[]", status);
9163
+ if (limit) params.append("limit", String(limit));
9164
+ return request(`https://api.clickup.com/api/v2/team/${encodeURIComponent(config.teamId)}/task?${params.toString()}`);
9165
+ },
9166
+ async getTaskComments({ taskId, start, limit = CLICKUP_WEBHOOK_STARTUP_COMMENT_LIMIT } = {}) {
9167
+ const params = new URLSearchParams();
9168
+ if (start) params.set("start", String(start));
9169
+ if (limit) params.set("limit", String(limit));
9170
+ const suffix = params.toString() ? `?${params.toString()}` : "";
9171
+ return request(`https://api.clickup.com/api/v2/task/${encodeURIComponent(taskId)}/comment${suffix}`);
9152
9172
  }
9153
9173
  };
9154
9174
  }
@@ -9378,6 +9398,31 @@ function recordClickUpCommentVersionProcessed({ ledgerPath, key, taskId, eventTy
9378
9398
  function clickUpTaskIdFromPayload(payload = {}) {
9379
9399
  return String(payload.task_id || payload.taskId || payload.task?.id || payload.task?.task_id || "").trim();
9380
9400
  }
9401
+ function clickUpTaskListItems(response = {}) {
9402
+ if (Array.isArray(response)) return response;
9403
+ if (Array.isArray(response.tasks)) return response.tasks;
9404
+ if (Array.isArray(response.data)) return response.data;
9405
+ if (Array.isArray(response?.data?.tasks)) return response.data.tasks;
9406
+ return [];
9407
+ }
9408
+ function clickUpCommentListItems(response = {}) {
9409
+ if (Array.isArray(response)) return response;
9410
+ if (Array.isArray(response.comments)) return response.comments;
9411
+ if (Array.isArray(response.data)) return response.data;
9412
+ if (Array.isArray(response?.data?.comments)) return response.data.comments;
9413
+ return [];
9414
+ }
9415
+ function clickUpTimestampMs(value) {
9416
+ if (value === null || value === void 0 || value === "") return 0;
9417
+ if (value instanceof Date) return value.getTime();
9418
+ const numeric = Number(value);
9419
+ if (Number.isFinite(numeric) && numeric > 0) return numeric < 1e10 ? numeric * 1e3 : numeric;
9420
+ const parsed = Date.parse(String(value));
9421
+ return Number.isFinite(parsed) ? parsed : 0;
9422
+ }
9423
+ function clickUpCommentUpdatedMs(comment = {}) {
9424
+ return clickUpTimestampMs(comment.date_updated || comment.dateUpdated || comment.updated_at || comment.updatedAt || comment.date || comment.date_created || comment.created_at);
9425
+ }
9381
9426
  function clickUpTaskName(task = {}) {
9382
9427
  return String(task?.name || "").trim();
9383
9428
  }
@@ -9860,6 +9905,94 @@ async function routeClickUpWebhookEvent(options = {}) {
9860
9905
  const taskId = clickUpTaskIdFromPayload(options.payload || {});
9861
9906
  return withClickUpTaskRouteLock(taskId, () => routeClickUpWebhookEventUnlocked(options));
9862
9907
  }
9908
+ async function reconcileClickUpStartup({ config, state = {}, worktree = process.cwd(), clickupClient, openCodeClient, sessionExists = openCodeSessionExists, createSession = createOpenCodeSession, sendSessionEvent = sendOpenCodeSessionEvent, saveState = null, now = () => /* @__PURE__ */ new Date(), limit = CLICKUP_WEBHOOK_STARTUP_TASK_LIMIT } = {}) {
9909
+ if (!config || !clickupClient?.listAssignedTasks) return { ok: true, skipped: true, reason: "clickup_task_listing_unavailable", assigned: 0, comments: 0 };
9910
+ let authorizedUserId = "";
9911
+ if (clickupClient?.getAuthorizedUser) {
9912
+ try {
9913
+ const userResponse = await clickupClient.getAuthorizedUser();
9914
+ authorizedUserId = String(userResponse?.user?.id || userResponse?.id || "").trim();
9915
+ } catch (error) {
9916
+ appendClickUpWebhookLocalLog(worktree, { type: "startup_reconciliation_user_lookup_failed", message: error.message });
9917
+ }
9918
+ }
9919
+ let mutableState = state;
9920
+ const persistState = (nextState) => {
9921
+ mutableState = nextState;
9922
+ if (saveState) saveState(nextState);
9923
+ };
9924
+ const lastWebhookMs = clickUpTimestampMs(mutableState.lastWebhookAt);
9925
+ const ignored = new Set((config.routing?.ignoredStatuses || CLICKUP_WEBHOOK_TERMINAL_STATUSES).map(normalizeClickUpStatus));
9926
+ const routed = { assigned: 0, comments: 0, ignored: 0, errors: 0 };
9927
+ clickUpWebhookLifecycleLog(worktree, { type: "startup_reconciliation_started", lastWebhookAt: state.lastWebhookAt || null });
9928
+ const listed = await clickupClient.listAssignedTasks({ assigneeId: config.routing.productManagerAssigneeId, limit });
9929
+ const tasks = clickUpTaskListItems(listed).slice(0, limit);
9930
+ for (const task of tasks) {
9931
+ const taskId = String(task?.id || task?.task_id || task?.taskId || "").trim();
9932
+ if (!taskId) {
9933
+ routed.ignored += 1;
9934
+ continue;
9935
+ }
9936
+ if (ignored.has(normalizeClickUpStatus(clickUpTaskStatus(task)))) {
9937
+ routed.ignored += 1;
9938
+ continue;
9939
+ }
9940
+ if (!isClickUpTaskAssignedToProductManager(task, config.routing.productManagerAssigneeId)) {
9941
+ routed.ignored += 1;
9942
+ continue;
9943
+ }
9944
+ try {
9945
+ const result = await routeClickUpWebhookEvent({
9946
+ payload: { webhook_id: mutableState.webhookId || "startup", event: "taskAssigneeUpdated", task_id: taskId, task, startup_reconciliation: true },
9947
+ config,
9948
+ state: mutableState,
9949
+ worktree,
9950
+ clickupClient,
9951
+ openCodeClient,
9952
+ sessionExists,
9953
+ createSession,
9954
+ sendSessionEvent,
9955
+ saveState: persistState,
9956
+ now
9957
+ });
9958
+ if (result?.ok && result.action !== "ignored") routed.assigned += 1;
9959
+ } catch (error) {
9960
+ routed.errors += 1;
9961
+ appendClickUpWebhookLocalLog(worktree, { type: "startup_reconciliation_task_failed", taskId, message: error.message });
9962
+ }
9963
+ if (!lastWebhookMs || !clickupClient?.getTaskComments) continue;
9964
+ try {
9965
+ const commentsResponse = await clickupClient.getTaskComments({ taskId, start: lastWebhookMs, limit: CLICKUP_WEBHOOK_STARTUP_COMMENT_LIMIT });
9966
+ for (const comment of clickUpCommentListItems(commentsResponse).slice(0, CLICKUP_WEBHOOK_STARTUP_COMMENT_LIMIT)) {
9967
+ const commentMs = clickUpCommentUpdatedMs(comment);
9968
+ if (!commentMs || commentMs <= lastWebhookMs) continue;
9969
+ const authorId = clickUpCommentAuthorId(comment);
9970
+ if (authorId && (authorId === config.routing.ignoredCommentAuthorId || authorId === authorizedUserId)) continue;
9971
+ if (!clickUpCommentMentionsProductManager(comment, config.routing)) continue;
9972
+ const event = comment.date_updated || comment.dateUpdated || comment.updated_at || comment.updatedAt ? "taskCommentUpdated" : "taskCommentPosted";
9973
+ const result = await routeClickUpWebhookEvent({
9974
+ payload: { webhook_id: mutableState.webhookId || "startup", event, task_id: taskId, task, comment, history_item: { id: `startup-${taskId}-${comment.id || commentMs}` }, startup_reconciliation: true },
9975
+ config,
9976
+ state: mutableState,
9977
+ worktree,
9978
+ clickupClient,
9979
+ openCodeClient,
9980
+ sessionExists,
9981
+ createSession,
9982
+ sendSessionEvent,
9983
+ saveState: persistState,
9984
+ now
9985
+ });
9986
+ if (result?.ok && result.action !== "ignored") routed.comments += 1;
9987
+ }
9988
+ } catch (error) {
9989
+ routed.errors += 1;
9990
+ appendClickUpWebhookLocalLog(worktree, { type: "startup_reconciliation_comments_failed", taskId, message: error.message });
9991
+ }
9992
+ }
9993
+ clickUpWebhookLifecycleLog(worktree, { type: "startup_reconciliation_finished", ...routed });
9994
+ return { ok: routed.errors === 0, ...routed };
9995
+ }
9863
9996
  function clickUpWebhookExpectedPath(config) {
9864
9997
  try {
9865
9998
  return new URL(config?.webhook?.publicUrl).pathname || "/";
@@ -9870,9 +10003,14 @@ function clickUpWebhookExpectedPath(config) {
9870
10003
  async function handleClickUpWebhookRequest({ method = "POST", url = null, headers = {}, rawBody = "", config, state, worktree, clickupClient, openCodeClient, saveState, now = () => /* @__PURE__ */ new Date() } = {}) {
9871
10004
  let payload = null;
9872
10005
  let handled = null;
10006
+ let authenticatedWebhook = false;
10007
+ let receivedAt = null;
9873
10008
  const finish = (result) => {
9874
10009
  handled = result;
9875
- writeClickUpWebhookAuditLog({ method, url, headers, rawBody, config, state, handled, payload, at: now() });
10010
+ receivedAt ??= now();
10011
+ const auditState = authenticatedWebhook ? { ...state, lastWebhookAt: receivedAt.toISOString() } : state;
10012
+ if (saveState && authenticatedWebhook) saveState(auditState);
10013
+ writeClickUpWebhookAuditLog({ method, url, headers, rawBody, config, state: auditState, handled, payload, at: receivedAt });
9876
10014
  return result;
9877
10015
  };
9878
10016
  try {
@@ -9886,12 +10024,15 @@ async function handleClickUpWebhookRequest({ method = "POST", url = null, header
9886
10024
  if (bodyBytes > maxBodyBytes) return finish({ ok: false, status: 413, reason: "body_too_large" });
9887
10025
  const signature = headers["x-signature"] || headers["X-Signature"];
9888
10026
  if (!verifyClickUpSignature(rawBody, signature, state?.secret)) return finish({ ok: false, status: 401, reason: "invalid_signature" });
10027
+ authenticatedWebhook = true;
10028
+ receivedAt = now();
9889
10029
  try {
9890
10030
  payload = JSON.parse(Buffer.isBuffer(rawBody) ? rawBody.toString("utf8") : String(rawBody));
9891
10031
  } catch {
9892
10032
  return finish({ ok: false, status: 400, reason: "invalid_json" });
9893
10033
  }
9894
- const result = await routeClickUpWebhookEvent({ payload, config, state, worktree, clickupClient, openCodeClient, saveState });
10034
+ const receivedState = { ...state, lastWebhookAt: receivedAt.toISOString() };
10035
+ const result = await routeClickUpWebhookEvent({ payload, config, state: receivedState, worktree, clickupClient, openCodeClient, saveState });
9895
10036
  return finish({ ok: result.ok, status: result.ok ? 200 : 422, result });
9896
10037
  } catch (error) {
9897
10038
  writeClickUpWebhookAuditLog({ method, url, headers, rawBody, config, state, handled, error, payload, at: now() });
@@ -11045,6 +11186,18 @@ async function OptimaPlugin(input = {}, pluginOptions = {}) {
11045
11186
  listener: { bindHost: clickUpWebhookValidation.config.webhook.bindHost, bindPort: clickUpWebhookValidation.config.webhook.bindPort, startedAt: (/* @__PURE__ */ new Date()).toISOString() }
11046
11187
  }, clickUpWebhookValidation.config);
11047
11188
  registerClickUpWebhookLifecycle({ config: clickUpWebhookValidation.config, state: activeState, worktree, clickupClient: lifecycleClickUpClient, listener: readyListener, listenerRegistry });
11189
+ try {
11190
+ await reconcileClickUpStartup({
11191
+ config: clickUpWebhookValidation.config,
11192
+ state: activeState,
11193
+ worktree,
11194
+ clickupClient: lifecycleClickUpClient,
11195
+ openCodeClient: input.client,
11196
+ saveState: (nextState) => writeClickUpWebhookState(worktree, nextState, clickUpWebhookValidation.config)
11197
+ });
11198
+ } catch (error) {
11199
+ appendClickUpWebhookLocalLog(worktree, { type: "startup_reconciliation_failed", message: error.message });
11200
+ }
11048
11201
  } else {
11049
11202
  clickUpWebhookLifecycleLog(worktree, { type: "remote_webhook_preserved", reason: readyListener.reason || "listener_unavailable", webhookId: listenerState.webhookId });
11050
11203
  markClickUpWebhookInactive(worktree, listenerState, clickUpWebhookValidation.config);
@@ -11619,7 +11772,7 @@ Follow-up: use optima_prompt_workflow with session_id '${sessionId}' to check in
11619
11772
  }
11620
11773
  };
11621
11774
  }
11622
- 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, 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 };
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 };
11623
11776
  export {
11624
11777
  OptimaPlugin as default
11625
11778
  };
@@ -7942,6 +7942,8 @@ var CLICKUP_PM_MENTION_NAME = "Defend Tech Product Manager";
7942
7942
  var CLICKUP_WEBHOOK_RUNTIME_VERSION = 1;
7943
7943
  var CLICKUP_WEBHOOK_MAX_BODY_BYTES = 1024 * 1024;
7944
7944
  var CLICKUP_WEBHOOK_REQUEST_TIMEOUT_MS = 1e4;
7945
+ var CLICKUP_WEBHOOK_STARTUP_TASK_LIMIT = 50;
7946
+ var CLICKUP_WEBHOOK_STARTUP_COMMENT_LIMIT = 100;
7945
7947
  var CLICKUP_WEBHOOK_LOG_LEVELS = /* @__PURE__ */ new Set(["error", "info", "verbose"]);
7946
7948
  var CLICKUP_WEBHOOK_REDACTED = "[REDACTED]";
7947
7949
  var DISCUSSION_BACKFILL_FETCH_LIMIT = 100;
@@ -9060,6 +9062,7 @@ function sanitizeClickUpWebhookState(state = {}, config = null) {
9060
9062
  lastValidatedAt: state.lastValidatedAt || null,
9061
9063
  listener: isPlainObject(state.listener) ? state.listener : {},
9062
9064
  pendingSessions: isPlainObject(state.pendingSessions) ? state.pendingSessions : {},
9065
+ lastWebhookAt: state.lastWebhookAt || state.last_webhook_at || null,
9063
9066
  recentEventKeys
9064
9067
  };
9065
9068
  }
@@ -9142,6 +9145,9 @@ function createClickUpApiClient(config, fetchImpl = globalThis.fetch) {
9142
9145
  async deleteWebhook(webhookId) {
9143
9146
  return request(`https://api.clickup.com/api/v2/webhook/${encodeURIComponent(webhookId)}`, { method: "DELETE" });
9144
9147
  },
9148
+ async getAuthorizedUser() {
9149
+ return request("https://api.clickup.com/api/v2/user");
9150
+ },
9145
9151
  async getTask(taskId) {
9146
9152
  return request(`https://api.clickup.com/api/v2/task/${encodeURIComponent(taskId)}`);
9147
9153
  },
@@ -9156,6 +9162,20 @@ function createClickUpApiClient(config, fetchImpl = globalThis.fetch) {
9156
9162
  method: "POST",
9157
9163
  body: JSON.stringify({ comment_text: comment })
9158
9164
  });
9165
+ },
9166
+ async listAssignedTasks({ assigneeId, statuses = [], limit = CLICKUP_WEBHOOK_STARTUP_TASK_LIMIT } = {}) {
9167
+ const params = new URLSearchParams();
9168
+ if (assigneeId) params.append("assignees[]", assigneeId);
9169
+ for (const status of statuses) if (status) params.append("statuses[]", status);
9170
+ if (limit) params.append("limit", String(limit));
9171
+ return request(`https://api.clickup.com/api/v2/team/${encodeURIComponent(config.teamId)}/task?${params.toString()}`);
9172
+ },
9173
+ async getTaskComments({ taskId, start, limit = CLICKUP_WEBHOOK_STARTUP_COMMENT_LIMIT } = {}) {
9174
+ const params = new URLSearchParams();
9175
+ if (start) params.set("start", String(start));
9176
+ if (limit) params.set("limit", String(limit));
9177
+ const suffix = params.toString() ? `?${params.toString()}` : "";
9178
+ return request(`https://api.clickup.com/api/v2/task/${encodeURIComponent(taskId)}/comment${suffix}`);
9159
9179
  }
9160
9180
  };
9161
9181
  }
@@ -9385,6 +9405,31 @@ function recordClickUpCommentVersionProcessed({ ledgerPath, key, taskId, eventTy
9385
9405
  function clickUpTaskIdFromPayload(payload = {}) {
9386
9406
  return String(payload.task_id || payload.taskId || payload.task?.id || payload.task?.task_id || "").trim();
9387
9407
  }
9408
+ function clickUpTaskListItems(response = {}) {
9409
+ if (Array.isArray(response)) return response;
9410
+ if (Array.isArray(response.tasks)) return response.tasks;
9411
+ if (Array.isArray(response.data)) return response.data;
9412
+ if (Array.isArray(response?.data?.tasks)) return response.data.tasks;
9413
+ return [];
9414
+ }
9415
+ function clickUpCommentListItems(response = {}) {
9416
+ if (Array.isArray(response)) return response;
9417
+ if (Array.isArray(response.comments)) return response.comments;
9418
+ if (Array.isArray(response.data)) return response.data;
9419
+ if (Array.isArray(response?.data?.comments)) return response.data.comments;
9420
+ return [];
9421
+ }
9422
+ function clickUpTimestampMs(value) {
9423
+ if (value === null || value === void 0 || value === "") return 0;
9424
+ if (value instanceof Date) return value.getTime();
9425
+ const numeric = Number(value);
9426
+ if (Number.isFinite(numeric) && numeric > 0) return numeric < 1e10 ? numeric * 1e3 : numeric;
9427
+ const parsed = Date.parse(String(value));
9428
+ return Number.isFinite(parsed) ? parsed : 0;
9429
+ }
9430
+ function clickUpCommentUpdatedMs(comment = {}) {
9431
+ return clickUpTimestampMs(comment.date_updated || comment.dateUpdated || comment.updated_at || comment.updatedAt || comment.date || comment.date_created || comment.created_at);
9432
+ }
9388
9433
  function clickUpTaskName(task = {}) {
9389
9434
  return String(task?.name || "").trim();
9390
9435
  }
@@ -9867,6 +9912,94 @@ async function routeClickUpWebhookEvent(options = {}) {
9867
9912
  const taskId = clickUpTaskIdFromPayload(options.payload || {});
9868
9913
  return withClickUpTaskRouteLock(taskId, () => routeClickUpWebhookEventUnlocked(options));
9869
9914
  }
9915
+ async function reconcileClickUpStartup({ config, state = {}, worktree = process.cwd(), clickupClient, openCodeClient, sessionExists = openCodeSessionExists, createSession = createOpenCodeSession, sendSessionEvent = sendOpenCodeSessionEvent, saveState = null, now = () => /* @__PURE__ */ new Date(), limit = CLICKUP_WEBHOOK_STARTUP_TASK_LIMIT } = {}) {
9916
+ if (!config || !clickupClient?.listAssignedTasks) return { ok: true, skipped: true, reason: "clickup_task_listing_unavailable", assigned: 0, comments: 0 };
9917
+ let authorizedUserId = "";
9918
+ if (clickupClient?.getAuthorizedUser) {
9919
+ try {
9920
+ const userResponse = await clickupClient.getAuthorizedUser();
9921
+ authorizedUserId = String(userResponse?.user?.id || userResponse?.id || "").trim();
9922
+ } catch (error) {
9923
+ appendClickUpWebhookLocalLog(worktree, { type: "startup_reconciliation_user_lookup_failed", message: error.message });
9924
+ }
9925
+ }
9926
+ let mutableState = state;
9927
+ const persistState = (nextState) => {
9928
+ mutableState = nextState;
9929
+ if (saveState) saveState(nextState);
9930
+ };
9931
+ const lastWebhookMs = clickUpTimestampMs(mutableState.lastWebhookAt);
9932
+ const ignored = new Set((config.routing?.ignoredStatuses || CLICKUP_WEBHOOK_TERMINAL_STATUSES).map(normalizeClickUpStatus));
9933
+ const routed = { assigned: 0, comments: 0, ignored: 0, errors: 0 };
9934
+ clickUpWebhookLifecycleLog(worktree, { type: "startup_reconciliation_started", lastWebhookAt: state.lastWebhookAt || null });
9935
+ const listed = await clickupClient.listAssignedTasks({ assigneeId: config.routing.productManagerAssigneeId, limit });
9936
+ const tasks = clickUpTaskListItems(listed).slice(0, limit);
9937
+ for (const task of tasks) {
9938
+ const taskId = String(task?.id || task?.task_id || task?.taskId || "").trim();
9939
+ if (!taskId) {
9940
+ routed.ignored += 1;
9941
+ continue;
9942
+ }
9943
+ if (ignored.has(normalizeClickUpStatus(clickUpTaskStatus(task)))) {
9944
+ routed.ignored += 1;
9945
+ continue;
9946
+ }
9947
+ if (!isClickUpTaskAssignedToProductManager(task, config.routing.productManagerAssigneeId)) {
9948
+ routed.ignored += 1;
9949
+ continue;
9950
+ }
9951
+ try {
9952
+ const result = await routeClickUpWebhookEvent({
9953
+ payload: { webhook_id: mutableState.webhookId || "startup", event: "taskAssigneeUpdated", task_id: taskId, task, startup_reconciliation: true },
9954
+ config,
9955
+ state: mutableState,
9956
+ worktree,
9957
+ clickupClient,
9958
+ openCodeClient,
9959
+ sessionExists,
9960
+ createSession,
9961
+ sendSessionEvent,
9962
+ saveState: persistState,
9963
+ now
9964
+ });
9965
+ if (result?.ok && result.action !== "ignored") routed.assigned += 1;
9966
+ } catch (error) {
9967
+ routed.errors += 1;
9968
+ appendClickUpWebhookLocalLog(worktree, { type: "startup_reconciliation_task_failed", taskId, message: error.message });
9969
+ }
9970
+ if (!lastWebhookMs || !clickupClient?.getTaskComments) continue;
9971
+ try {
9972
+ const commentsResponse = await clickupClient.getTaskComments({ taskId, start: lastWebhookMs, limit: CLICKUP_WEBHOOK_STARTUP_COMMENT_LIMIT });
9973
+ for (const comment of clickUpCommentListItems(commentsResponse).slice(0, CLICKUP_WEBHOOK_STARTUP_COMMENT_LIMIT)) {
9974
+ const commentMs = clickUpCommentUpdatedMs(comment);
9975
+ if (!commentMs || commentMs <= lastWebhookMs) continue;
9976
+ const authorId = clickUpCommentAuthorId(comment);
9977
+ if (authorId && (authorId === config.routing.ignoredCommentAuthorId || authorId === authorizedUserId)) continue;
9978
+ if (!clickUpCommentMentionsProductManager(comment, config.routing)) continue;
9979
+ const event = comment.date_updated || comment.dateUpdated || comment.updated_at || comment.updatedAt ? "taskCommentUpdated" : "taskCommentPosted";
9980
+ const result = await routeClickUpWebhookEvent({
9981
+ payload: { webhook_id: mutableState.webhookId || "startup", event, task_id: taskId, task, comment, history_item: { id: `startup-${taskId}-${comment.id || commentMs}` }, startup_reconciliation: true },
9982
+ config,
9983
+ state: mutableState,
9984
+ worktree,
9985
+ clickupClient,
9986
+ openCodeClient,
9987
+ sessionExists,
9988
+ createSession,
9989
+ sendSessionEvent,
9990
+ saveState: persistState,
9991
+ now
9992
+ });
9993
+ if (result?.ok && result.action !== "ignored") routed.comments += 1;
9994
+ }
9995
+ } catch (error) {
9996
+ routed.errors += 1;
9997
+ appendClickUpWebhookLocalLog(worktree, { type: "startup_reconciliation_comments_failed", taskId, message: error.message });
9998
+ }
9999
+ }
10000
+ clickUpWebhookLifecycleLog(worktree, { type: "startup_reconciliation_finished", ...routed });
10001
+ return { ok: routed.errors === 0, ...routed };
10002
+ }
9870
10003
  function clickUpWebhookExpectedPath(config) {
9871
10004
  try {
9872
10005
  return new URL(config?.webhook?.publicUrl).pathname || "/";
@@ -9877,9 +10010,14 @@ function clickUpWebhookExpectedPath(config) {
9877
10010
  async function handleClickUpWebhookRequest({ method = "POST", url = null, headers = {}, rawBody = "", config, state, worktree, clickupClient, openCodeClient, saveState, now = () => /* @__PURE__ */ new Date() } = {}) {
9878
10011
  let payload = null;
9879
10012
  let handled = null;
10013
+ let authenticatedWebhook = false;
10014
+ let receivedAt = null;
9880
10015
  const finish = (result) => {
9881
10016
  handled = result;
9882
- writeClickUpWebhookAuditLog({ method, url, headers, rawBody, config, state, handled, payload, at: now() });
10017
+ receivedAt ??= now();
10018
+ const auditState = authenticatedWebhook ? { ...state, lastWebhookAt: receivedAt.toISOString() } : state;
10019
+ if (saveState && authenticatedWebhook) saveState(auditState);
10020
+ writeClickUpWebhookAuditLog({ method, url, headers, rawBody, config, state: auditState, handled, payload, at: receivedAt });
9883
10021
  return result;
9884
10022
  };
9885
10023
  try {
@@ -9893,12 +10031,15 @@ async function handleClickUpWebhookRequest({ method = "POST", url = null, header
9893
10031
  if (bodyBytes > maxBodyBytes) return finish({ ok: false, status: 413, reason: "body_too_large" });
9894
10032
  const signature = headers["x-signature"] || headers["X-Signature"];
9895
10033
  if (!verifyClickUpSignature(rawBody, signature, state?.secret)) return finish({ ok: false, status: 401, reason: "invalid_signature" });
10034
+ authenticatedWebhook = true;
10035
+ receivedAt = now();
9896
10036
  try {
9897
10037
  payload = JSON.parse(Buffer.isBuffer(rawBody) ? rawBody.toString("utf8") : String(rawBody));
9898
10038
  } catch {
9899
10039
  return finish({ ok: false, status: 400, reason: "invalid_json" });
9900
10040
  }
9901
- const result = await routeClickUpWebhookEvent({ payload, config, state, worktree, clickupClient, openCodeClient, saveState });
10041
+ const receivedState = { ...state, lastWebhookAt: receivedAt.toISOString() };
10042
+ const result = await routeClickUpWebhookEvent({ payload, config, state: receivedState, worktree, clickupClient, openCodeClient, saveState });
9902
10043
  return finish({ ok: result.ok, status: result.ok ? 200 : 422, result });
9903
10044
  } catch (error) {
9904
10045
  writeClickUpWebhookAuditLog({ method, url, headers, rawBody, config, state, handled, error, payload, at: now() });
@@ -11052,6 +11193,18 @@ async function OptimaPlugin(input = {}, pluginOptions = {}) {
11052
11193
  listener: { bindHost: clickUpWebhookValidation.config.webhook.bindHost, bindPort: clickUpWebhookValidation.config.webhook.bindPort, startedAt: (/* @__PURE__ */ new Date()).toISOString() }
11053
11194
  }, clickUpWebhookValidation.config);
11054
11195
  registerClickUpWebhookLifecycle({ config: clickUpWebhookValidation.config, state: activeState, worktree, clickupClient: lifecycleClickUpClient, listener: readyListener, listenerRegistry });
11196
+ try {
11197
+ await reconcileClickUpStartup({
11198
+ config: clickUpWebhookValidation.config,
11199
+ state: activeState,
11200
+ worktree,
11201
+ clickupClient: lifecycleClickUpClient,
11202
+ openCodeClient: input.client,
11203
+ saveState: (nextState) => writeClickUpWebhookState(worktree, nextState, clickUpWebhookValidation.config)
11204
+ });
11205
+ } catch (error) {
11206
+ appendClickUpWebhookLocalLog(worktree, { type: "startup_reconciliation_failed", message: error.message });
11207
+ }
11055
11208
  } else {
11056
11209
  clickUpWebhookLifecycleLog(worktree, { type: "remote_webhook_preserved", reason: readyListener.reason || "listener_unavailable", webhookId: listenerState.webhookId });
11057
11210
  markClickUpWebhookInactive(worktree, listenerState, clickUpWebhookValidation.config);
@@ -11626,7 +11779,7 @@ Follow-up: use optima_prompt_workflow with session_id '${sessionId}' to check in
11626
11779
  }
11627
11780
  };
11628
11781
  }
11629
- 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, 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 };
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 };
11630
11783
 
11631
11784
  // src/sanitize_cli.js
11632
11785
  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.35",
3
+ "version": "0.1.36",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+ssh://git@github.com/defend-tech/opencode-optima.git"