@defend-tech/opencode-optima 0.1.71 → 0.1.73

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.
@@ -8634,6 +8634,7 @@ var activeWorkflows = /* @__PURE__ */ new Map();
8634
8634
  var activeClickUpWebhookListeners = /* @__PURE__ */ new Map();
8635
8635
  var activeClickUpWebhookLifecycleRegistry = /* @__PURE__ */ new Map();
8636
8636
  var activeClickUpTaskRoutes = /* @__PURE__ */ new Map();
8637
+ var GITHUB_WEBHOOK_EVENTS = ["pull_request", "pull_request_review", "pull_request_review_comment", "issue_comment"];
8637
8638
  function isRootDirectory(candidate) {
8638
8639
  const resolved = path6.resolve(candidate);
8639
8640
  return resolved === path6.parse(resolved).root;
@@ -8826,6 +8827,70 @@ function deriveClickUpPrTarget({ isRelease = false, parentTaskId, parentBranch,
8826
8827
  }
8827
8828
  return devBranch;
8828
8829
  }
8830
+ function deriveClickUpRequiredPullRequest({ taskId, taskType = "Tarea", parentTaskId = "", subtaskId = "", branch = "", parentBranch = "", devBranch = "dev", mainBranch = "main", isRelease = false, githubRemote = "origin", prUrl = "", prNumber = "" } = {}) {
8831
+ const effectiveParent = parentTaskId || taskId;
8832
+ const isSubtask = isClickUpSubtaskRoute({ taskType, parentTaskId: effectiveParent, subtaskId, taskId });
8833
+ const sourceBranch = branch || deriveClickUpBranchName({ taskType, parentTaskId: effectiveParent, subtaskId, taskId });
8834
+ const targetBranch = deriveClickUpPrTarget({
8835
+ isRelease,
8836
+ parentBranch: isSubtask ? parentBranch || deriveClickUpBranchName({ taskType, parentTaskId: effectiveParent }) : parentBranch,
8837
+ parentTaskId: isSubtask ? effectiveParent : "",
8838
+ parentTaskType: taskType,
8839
+ taskType,
8840
+ devBranch,
8841
+ mainBranch
8842
+ });
8843
+ return {
8844
+ required: true,
8845
+ sourceBranch,
8846
+ targetBranch,
8847
+ githubRemote,
8848
+ prUrl: String(prUrl || "").trim(),
8849
+ prNumber: String(prNumber || "").trim(),
8850
+ scope: isSubtask ? "subtask_to_parent" : isRelease ? "release_to_main" : "task_to_dev",
8851
+ policy: "validation_requires_open_pull_request"
8852
+ };
8853
+ }
8854
+ function buildClickUpReviewAutomationPlan({ isSubtask = false, humanApprover = "CTO", preproductionProvider = "Vercel", preproductionEnvironment = "preproduction" } = {}) {
8855
+ return {
8856
+ github_webhooks: {
8857
+ listen_for: ["pull_request_review", "pull_request_review_comment", "issue_comment", "pull_request"],
8858
+ comment_policy: "reply_before_change_and_after_change",
8859
+ change_request_status: "in progress",
8860
+ after_fix_status: "validation",
8861
+ state_storage: "ClickUp agent_metadata.github"
8862
+ },
8863
+ review_comments: [
8864
+ "Answer GitHub comments directly in GitHub.",
8865
+ "If a comment implies a code/doc/test change, first reply with the intended action, move the ClickUp task/subtask to in progress, implement, push the same branch, move it back to validation, update the PR, then reply again with what changed.",
8866
+ "Also update ClickUp with human-readable model work status; runtime/process noise stays in local logs."
8867
+ ],
8868
+ accepted_review: {
8869
+ accepted_by: humanApprover,
8870
+ trigger: "approved_pull_request_review",
8871
+ action: isSubtask ? "merge_subtask_pr_into_parent_branch" : "merge_parent_pr_into_dev",
8872
+ delete_source_branch_after_success: true,
8873
+ delete_worktree_after_success: true,
8874
+ preserve_opencode_sessions_in_agent_metadata: true
8875
+ },
8876
+ preproduction: isSubtask ? {
8877
+ required: false,
8878
+ reason: "Subtask merge stops at parent branch; parent task owns dev/preproduction."
8879
+ } : {
8880
+ required: true,
8881
+ provider: preproductionProvider,
8882
+ environment: preproductionEnvironment,
8883
+ verify_deployment_after_merge: true,
8884
+ run_smoke_regression: true,
8885
+ on_failure: "create_bug_subtasks_and_return_parent_to_in_progress",
8886
+ completion_gate: "preproduction_deployed_and_regression_passed"
8887
+ },
8888
+ completion: {
8889
+ final_clickup_comment_required: true,
8890
+ final_status: isSubtask ? "completed_or_parent_validation" : "completed"
8891
+ }
8892
+ };
8893
+ }
8829
8894
  function validateMainWorkspaceBranchSafety({ currentBranch, requiredBranch = "dev", forbiddenBranch = "main" } = {}) {
8830
8895
  const normalized = String(currentBranch ?? "").trim();
8831
8896
  if (!normalized) {
@@ -9488,6 +9553,7 @@ function buildClickUpStartTaskPayload({ taskId, taskType = "Tarea", parentTaskId
9488
9553
  const worktree = deriveClickUpWorktree({ baseWorktree, taskId, taskType, parentTaskId: effectiveParent, subtaskId });
9489
9554
  const prTarget = isSubtask ? parentBranch : "dev";
9490
9555
  const startFrom = isSubtask ? parentBranch : "dev";
9556
+ const requiredPullRequest = deriveClickUpRequiredPullRequest({ taskId, taskType, parentTaskId: effectiveParent, subtaskId, branch, parentBranch });
9491
9557
  const estimate = preEstimateClickUpWork({ complexity, filesChanged, acceptanceCriteria, unknowns });
9492
9558
  const metadata = mergeClickUpAgentMetadata(existingAgentMetadata, {
9493
9559
  task: {
@@ -9501,6 +9567,10 @@ function buildClickUpStartTaskPayload({ taskId, taskType = "Tarea", parentTaskId
9501
9567
  mirror_task_path: `.optima/tasks/${branchSafeClickUpId(taskId || subtaskId || effectiveParent)}.md`,
9502
9568
  evidence_path: `.optima/evidences/${branchSafeClickUpId(taskId || subtaskId || effectiveParent)}/SUMMARY.md`,
9503
9569
  delivery_evidence_path: deliveryEvidencePathForClickUpTask(taskId || subtaskId || effectiveParent),
9570
+ github: {
9571
+ required_pull_request: requiredPullRequest,
9572
+ review_automation: buildClickUpReviewAutomationPlan({ isSubtask })
9573
+ },
9504
9574
  ...agentMetadata
9505
9575
  }
9506
9576
  });
@@ -9523,6 +9593,10 @@ function buildClickUpStartTaskPayload({ taskId, taskType = "Tarea", parentTaskId
9523
9593
  parentBranch: parentBranch || void 0,
9524
9594
  prTarget
9525
9595
  },
9596
+ github: {
9597
+ requiredPullRequest,
9598
+ reviewAutomation: buildClickUpReviewAutomationPlan({ isSubtask })
9599
+ },
9526
9600
  mirror: {
9527
9601
  taskPath: `.optima/tasks/${branchSafeClickUpId(taskId || subtaskId || effectiveParent)}.md`,
9528
9602
  evidenceSummaryPath: `.optima/evidences/${branchSafeClickUpId(taskId || subtaskId || effectiveParent)}/SUMMARY.md`,
@@ -9533,14 +9607,15 @@ function buildClickUpStartTaskPayload({ taskId, taskType = "Tarea", parentTaskId
9533
9607
  wouldCreate: true
9534
9608
  },
9535
9609
  clickup: {
9536
- comment: `Starting task from ${startFrom}. Branch: ${branch}. Worktree: ${worktree}. Delivery evidence: ${deliveryEvidencePathForClickUpTask(taskId || subtaskId || effectiveParent)}.`,
9610
+ comment: `Starting task from ${startFrom}. Branch: ${branch}. Worktree: ${worktree}. Validation PR target: ${requiredPullRequest.targetBranch}. Delivery evidence: ${deliveryEvidencePathForClickUpTask(taskId || subtaskId || effectiveParent)}.`,
9537
9611
  description,
9538
9612
  fields: {
9539
9613
  Definition: definitionContent,
9540
9614
  "Story Points": estimate.storyPoints,
9541
9615
  agent_metadata: metadata,
9542
9616
  branch,
9543
- worktree
9617
+ worktree,
9618
+ pr_target: requiredPullRequest.targetBranch
9544
9619
  },
9545
9620
  definition_doc: {
9546
9621
  parent: normalizeClickUpDefinitionDocParent(definitionDocParent),
@@ -9550,23 +9625,29 @@ function buildClickUpStartTaskPayload({ taskId, taskType = "Tarea", parentTaskId
9550
9625
  }
9551
9626
  };
9552
9627
  }
9553
- function buildClickUpTransitionPayload({ fromStatus, toStatus, validationPassed = false, validationFailed = false, isSubtask = false, reopen = false, assignees = [], removeAssignees = [], productManagerAssignee = "", requireProductManagerAssignee = true, planDescription = "", definition = "", definitionDocParent = CLICKUP_DEFINITION_DOC_PARENT, humansRegistry } = {}) {
9628
+ function buildClickUpTransitionPayload({ fromStatus, toStatus, validationPassed = false, validationFailed = false, isSubtask = false, reopen = false, assignees = [], removeAssignees = [], productManagerAssignee = "", requireProductManagerAssignee = true, planDescription = "", definition = "", definitionDocParent = CLICKUP_DEFINITION_DOC_PARENT, humansRegistry, taskId = "", taskType = "Tarea", parentTaskId = "", subtaskId = "", branch = "", parentBranch = "", prUrl = "", prNumber = "", githubRemote = "origin", devBranch = "dev", mainBranch = "main", preproductionProvider = "Vercel", preproductionEnvironment = "preproduction" } = {}) {
9554
9629
  const from = normalizeClickUpStatus(fromStatus);
9630
+ const effectiveIsSubtask = Boolean(isSubtask || isClickUpSubtaskRoute({ taskType, parentTaskId: parentTaskId || taskId, subtaskId, taskId }));
9555
9631
  let to = normalizeClickUpStatus(toStatus);
9556
9632
  if (validationFailed) to = "in progress";
9557
- if (!to && validationPassed) to = isSubtask ? "completed" : "merge";
9633
+ if (!to && validationPassed) to = effectiveIsSubtask ? "completed" : "merge";
9558
9634
  if ((from === "completed" || from === "closed") && !reopen) {
9559
9635
  return { ok: true, noop: true, dryRun: true, message: "Completed/closed task is a no-op unless reopen is explicit." };
9560
9636
  }
9561
9637
  const key = `${from}->${to}`;
9562
9638
  const rule = CLICKUP_TRANSITIONS.get(key);
9563
9639
  if (!rule) return { ok: false, dryRun: true, message: `Transition not allowed: ${key}` };
9564
- const assignsFinalApprovers = rule.assignFinalApprovers === true && !(rule.parentOnlyFinalApproval && isSubtask);
9640
+ const assignsFinalApprovers = rule.assignFinalApprovers === true && !(rule.parentOnlyFinalApproval && effectiveIsSubtask);
9565
9641
  const requiresPlanCompletionContract = from === "plan" && to === "in progress";
9642
+ const requiresValidationPullRequest = from === "in progress" && to === "validation";
9566
9643
  const definitionContent = compactMarkdownValue(definition);
9567
9644
  const description = compactMarkdownValue(planDescription);
9645
+ const canDerivePullRequest = Boolean(taskId || branch);
9646
+ const shouldAttachPullRequest = requiresValidationPullRequest || (from === "validation" || to === "merge" || from === "merge") && canDerivePullRequest;
9647
+ const requiredPullRequest = shouldAttachPullRequest && canDerivePullRequest ? deriveClickUpRequiredPullRequest({ taskId, taskType, parentTaskId, subtaskId, branch, parentBranch, devBranch, mainBranch, githubRemote, prUrl, prNumber }) : null;
9648
+ const reviewAutomation = requiredPullRequest ? buildClickUpReviewAutomationPlan({ isSubtask: effectiveIsSubtask, preproductionProvider, preproductionEnvironment }) : null;
9568
9649
  const validationErrors = [];
9569
- if (rule.parentOnlyFinalApproval && isSubtask) {
9650
+ if (rule.parentOnlyFinalApproval && effectiveIsSubtask) {
9570
9651
  validationErrors.push("Subtasks must not use the parent final-approval transition; merge validated subtasks directly into the parent branch/workspace.");
9571
9652
  }
9572
9653
  if (assignsFinalApprovers && !isRealClickUpAssigneeId(productManagerAssignee) && requireProductManagerAssignee !== false) {
@@ -9576,19 +9657,34 @@ function buildClickUpTransitionPayload({ fromStatus, toStatus, validationPassed
9576
9657
  if (!description) validationErrors.push("planDescription is required for the plan-completion ClickUp task description rewrite.");
9577
9658
  if (!definitionContent) validationErrors.push("definition is required and must contain the complete plan/Definition content at plan completion.");
9578
9659
  }
9660
+ if (requiresValidationPullRequest) {
9661
+ if (!requiredPullRequest) validationErrors.push("taskId or branch is required before moving a ClickUp task to validation.");
9662
+ if (!requiredPullRequest?.sourceBranch) validationErrors.push("branch/sourceBranch is required before moving a ClickUp task to validation.");
9663
+ if (!requiredPullRequest?.targetBranch) validationErrors.push("PR target branch is required before moving a ClickUp task to validation.");
9664
+ if (!requiredPullRequest?.prUrl && !requiredPullRequest?.prNumber) validationErrors.push("An open GitHub pull request URL or number is required before moving a ClickUp task to validation.");
9665
+ }
9579
9666
  if (validationErrors.length > 0) return clickUpPayloadValidationError(validationErrors);
9580
9667
  const finalApprovers = assignsFinalApprovers ? finalApprovalAssignees(CLICKUP_FINAL_APPROVER_ROLES, humansRegistry) : [];
9581
9668
  const explicitRemovals = (removeAssignees || []).filter(Boolean);
9582
9669
  const normalizedProductManagerAssignee = isRealClickUpAssigneeId(productManagerAssignee) ? String(productManagerAssignee).trim() : "";
9583
9670
  const removalTargets = assignsFinalApprovers ? [...new Set([normalizedProductManagerAssignee, ...explicitRemovals].filter(Boolean))] : explicitRemovals;
9584
9671
  const assigned = [...new Set([...assignees || [], ...finalApprovers].filter(Boolean))].filter((assignee) => !removalTargets.includes(assignee));
9585
- const authority = from === "validation" || to === "merge" ? determineClickUpMergeAuthority({ isSubtask, clickupStatus: to, validationPassed: validationPassed || to === "merge", humansRegistry }) : null;
9672
+ const authority = from === "validation" || to === "merge" ? determineClickUpMergeAuthority({ isSubtask: effectiveIsSubtask, clickupStatus: to, validationPassed: validationPassed || to === "merge", humansRegistry }) : null;
9586
9673
  const fields = authority ? { merge_authority: JSON.stringify(authority) } : {};
9674
+ if (requiredPullRequest) {
9675
+ fields.github_pull_request = JSON.stringify(requiredPullRequest);
9676
+ fields.github_review_automation = JSON.stringify(reviewAutomation);
9677
+ }
9587
9678
  if (definitionContent) fields.Definition = definitionContent;
9588
9679
  return {
9589
9680
  ok: true,
9590
9681
  dryRun: true,
9591
- transition: { fromStatus: from, toStatus: rule.status, validationPassed, validationFailed, isSubtask },
9682
+ transition: { fromStatus: from, toStatus: rule.status, validationPassed, validationFailed, isSubtask: effectiveIsSubtask },
9683
+ github: requiredPullRequest ? {
9684
+ requiredPullRequest,
9685
+ reviewAutomation
9686
+ } : void 0,
9687
+ vercel: reviewAutomation?.preproduction?.required ? reviewAutomation.preproduction : void 0,
9592
9688
  clickup: {
9593
9689
  status: rule.status,
9594
9690
  assignees: assigned,
@@ -9792,9 +9888,48 @@ function normalizeNonNegativeInteger(value, defaultValue) {
9792
9888
  const number = Number(value);
9793
9889
  return Number.isInteger(number) && number >= 0 ? number : defaultValue;
9794
9890
  }
9891
+ function defaultGitHubWebhookPublicUrl(clickupPublicUrl = "") {
9892
+ const raw = String(clickupPublicUrl || "").trim();
9893
+ if (!raw) return "";
9894
+ try {
9895
+ const url = new URL(raw);
9896
+ url.pathname = "/optima/github/webhook";
9897
+ url.search = "";
9898
+ url.hash = "";
9899
+ return url.toString();
9900
+ } catch {
9901
+ return raw.replace(/\/optima\/clickup\/webhook\b/, "/optima/github/webhook");
9902
+ }
9903
+ }
9904
+ function normalizeGitHubWebhookConfig(rawGithub = {}, clickupWebhook = {}) {
9905
+ const github = isPlainObject(rawGithub) ? rawGithub : {};
9906
+ const webhook = isPlainObject(github.webhook) ? github.webhook : {};
9907
+ const enabled = github.enabled === true || webhook.enabled === true;
9908
+ const events = Array.isArray(webhook.events) && webhook.events.length > 0 ? [...new Set(webhook.events.map((event) => String(event || "").trim()).filter(Boolean))] : [...GITHUB_WEBHOOK_EVENTS];
9909
+ const publicUrl = String(webhook.public_url || webhook.publicUrl || github.public_url || github.publicUrl || defaultGitHubWebhookPublicUrl(clickupWebhook.publicUrl)).trim();
9910
+ const secret = String(webhook.secret || github.secret || "").trim();
9911
+ const apiToken = String(github.api_token || github.apiToken || "").trim();
9912
+ const repository = String(github.repository || github.repo || "").trim();
9913
+ const [ownerFromRepo, nameFromRepo] = repository.includes("/") ? repository.split("/", 2) : ["", ""];
9914
+ return {
9915
+ enabled,
9916
+ apiToken,
9917
+ repository,
9918
+ owner: String(github.owner || ownerFromRepo || "").trim(),
9919
+ repo: String(github.repo_name || github.repoName || nameFromRepo || "").trim(),
9920
+ webhook: {
9921
+ enabled,
9922
+ publicUrl,
9923
+ secret,
9924
+ path: publicUrl ? new URL(publicUrl, "http://localhost").pathname : "/optima/github/webhook",
9925
+ events
9926
+ }
9927
+ };
9928
+ }
9795
9929
  function normalizeClickUpWebhookConfig(rawClickUp = null, worktree = process.cwd()) {
9796
9930
  const raw = isPlainObject(rawClickUp) ? rawClickUp : {};
9797
9931
  const webhook = isPlainObject(raw.webhook) ? raw.webhook : {};
9932
+ const github = normalizeGitHubWebhookConfig(raw.github, { publicUrl: String(webhook.public_url || webhook.publicUrl || "").trim() });
9798
9933
  const routing = isPlainObject(raw.routing) ? raw.routing : {};
9799
9934
  const opencode = isPlainObject(raw.opencode) ? raw.opencode : {};
9800
9935
  const openchamber = isPlainObject(raw.openchamber) ? raw.openchamber : {};
@@ -9847,6 +9982,7 @@ function normalizeClickUpWebhookConfig(rawClickUp = null, worktree = process.cwd
9847
9982
  },
9848
9983
  events
9849
9984
  },
9985
+ github,
9850
9986
  routing: {
9851
9987
  targetAgent: String(routing.target_agent || routing.targetAgent || "workflow_product_manager").trim(),
9852
9988
  productManagerAssigneeId: String(routing.product_manager_assignee_id || routing.productManagerAssigneeId || "").trim(),
@@ -9868,6 +10004,13 @@ function normalizeClickUpWebhookConfig(rawClickUp = null, worktree = process.cwd
9868
10004
  if (!config.webhook.bindHost) errors.push("clickup.webhook.bind_host is required");
9869
10005
  if (!Number.isInteger(config.webhook.bindPort) || config.webhook.bindPort <= 0) errors.push("clickup.webhook.bind_port must be a positive integer");
9870
10006
  if (!config.webhook.location.list_id && !config.webhook.location.folder_id && !config.webhook.location.space_id) errors.push("one clickup.webhook.location id is required");
10007
+ if (config.github.webhook.enabled) {
10008
+ if (!config.github.webhook.publicUrl) errors.push("clickup.github.webhook.public_url is required when GitHub webhook is enabled");
10009
+ if (!config.github.webhook.secret) errors.push("clickup.github.webhook.secret is required when GitHub webhook is enabled");
10010
+ if (!config.github.webhook.path || config.github.webhook.path === clickUpWebhookExpectedPath(config)) errors.push("clickup.github.webhook.path must be distinct from the ClickUp webhook path");
10011
+ const missingGitHubEvents = GITHUB_WEBHOOK_EVENTS.filter((event) => !config.github.webhook.events.includes(event));
10012
+ if (missingGitHubEvents.length > 0) errors.push(`clickup.github.webhook.events missing: ${missingGitHubEvents.join(", ")}`);
10013
+ }
9871
10014
  if (config.opencode.baseUrl) {
9872
10015
  try {
9873
10016
  new URL(config.opencode.baseUrl);
@@ -9905,6 +10048,8 @@ function sanitizeClickUpWebhookState(state = {}, config = null) {
9905
10048
  listener: isPlainObject(state.listener) ? state.listener : {},
9906
10049
  pendingSessions: isPlainObject(state.pendingSessions) ? state.pendingSessions : {},
9907
10050
  lastWebhookAt: state.lastWebhookAt || state.last_webhook_at || null,
10051
+ githubLastWebhookAt: state.githubLastWebhookAt || state.github_last_webhook_at || null,
10052
+ githubRecentEventKeys: Array.isArray(state.githubRecentEventKeys) ? state.githubRecentEventKeys.slice(-200) : [],
9908
10053
  recentEventKeys
9909
10054
  };
9910
10055
  }
@@ -10021,6 +10166,29 @@ function createClickUpApiClient(config, fetchImpl = globalThis.fetch) {
10021
10166
  }
10022
10167
  };
10023
10168
  }
10169
+ function createGitHubApiClient(config = {}, fetchImpl = globalThis.fetch) {
10170
+ const token = resolveSecretReference(config?.apiToken);
10171
+ const owner = String(config?.owner || "").trim();
10172
+ const repo = String(config?.repo || "").trim();
10173
+ const request = async (pathname) => {
10174
+ if (typeof fetchImpl !== "function") throw new Error("fetch is unavailable; inject a GitHub client for live PR lookup");
10175
+ if (!owner || !repo) throw new Error("GitHub repository owner/repo is not configured");
10176
+ const response = await fetchImpl(`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}${pathname}`, {
10177
+ headers: {
10178
+ Accept: "application/vnd.github+json",
10179
+ "X-GitHub-Api-Version": "2022-11-28",
10180
+ ...token ? { Authorization: `Bearer ${token}` } : {}
10181
+ }
10182
+ });
10183
+ if (!response.ok) throw new Error(`GitHub API request failed: ${response.status}`);
10184
+ return response.json();
10185
+ };
10186
+ return {
10187
+ async getPullRequest(number) {
10188
+ return request(`/pulls/${encodeURIComponent(number)}`);
10189
+ }
10190
+ };
10191
+ }
10024
10192
  function createTestClickUpApiClient(config) {
10025
10193
  const metadata = /* @__PURE__ */ new Map();
10026
10194
  return {
@@ -10251,6 +10419,19 @@ function verifyClickUpSignature(rawBody, signatureHeader, secret) {
10251
10419
  const wanted = Buffer.from(expected, "hex");
10252
10420
  return given.length === wanted.length && crypto.timingSafeEqual(given, wanted);
10253
10421
  }
10422
+ function verifyGitHubSignature(rawBody, signatureHeader, secret) {
10423
+ return verifyClickUpSignature(rawBody, signatureHeader, secret);
10424
+ }
10425
+ function gitHubWebhookEventKey({ headers = {}, event = "", payload = {} } = {}) {
10426
+ const delivery = headers["x-github-delivery"] || headers["X-GitHub-Delivery"] || payload.delivery || payload.delivery_id || "";
10427
+ const id = delivery || payload.pull_request?.id || payload.comment?.id || payload.review?.id || payload.issue?.id || "unknown";
10428
+ return ["github", event || headers["x-github-event"] || headers["X-GitHub-Event"] || "event", id].filter(Boolean).join(":");
10429
+ }
10430
+ function rememberGitHubWebhookEvent(state = {}, eventKey, limit = 200) {
10431
+ const recent = Array.isArray(state.githubRecentEventKeys) ? state.githubRecentEventKeys : [];
10432
+ if (recent.includes(eventKey)) return { duplicate: true, state };
10433
+ return { duplicate: false, state: { ...state, githubRecentEventKeys: [...recent, eventKey].slice(-limit) } };
10434
+ }
10254
10435
  async function resyncClickUpWebhookForSignatureDrift({ rawBody, signature, config, state = {}, worktree = process.cwd(), clickupClient, saveState } = {}) {
10255
10436
  const validation = clickUpWebhookValidationFromConfig(config);
10256
10437
  if (!clickupClient?.listWebhooks && !clickupClient?.createWebhook) {
@@ -10457,6 +10638,105 @@ function clickUpTaskAgentMetadata(task = {}, fieldId = "") {
10457
10638
  const field = fields.find((item) => String(item?.id || "") === fieldId || String(item?.name || "") === "agent_metadata");
10458
10639
  return field?.value || "";
10459
10640
  }
10641
+ function gitHubWebhookExpectedPath(config = {}) {
10642
+ return config?.github?.webhook?.path || "/optima/github/webhook";
10643
+ }
10644
+ function gitHubWebhookEnabled(config = {}) {
10645
+ return Boolean(config?.github?.webhook?.enabled && resolveSecretReference(config?.github?.webhook?.secret));
10646
+ }
10647
+ function clickUpTaskIdFromGitHubBranch(branch = "") {
10648
+ const tail = String(branch || "").trim().split("/").filter(Boolean).at(-1) || "";
10649
+ if (!tail) return "";
10650
+ const subtask = tail.match(/-subtask-(.+)$/);
10651
+ return branchSafeClickUpId(subtask ? subtask[1] : tail);
10652
+ }
10653
+ async function gitHubPullRequestContextFromPayload(payload = {}, event = "", githubClient = null) {
10654
+ let pr = payload.pull_request || null;
10655
+ if (!pr && event === "issue_comment" && payload.issue?.pull_request && githubClient?.getPullRequest) {
10656
+ pr = await githubClient.getPullRequest(payload.issue.number);
10657
+ }
10658
+ if (!pr) return null;
10659
+ const headRef = String(pr.head?.ref || pr.head_ref || "").trim();
10660
+ const baseRef = String(pr.base?.ref || pr.base_ref || "").trim();
10661
+ return {
10662
+ number: pr.number || payload.issue?.number || "",
10663
+ id: pr.id || "",
10664
+ url: pr.html_url || pr.url || "",
10665
+ title: pr.title || payload.issue?.title || "",
10666
+ state: pr.state || "",
10667
+ merged: pr.merged === true,
10668
+ headRef,
10669
+ baseRef,
10670
+ taskId: clickUpTaskIdFromGitHubBranch(headRef),
10671
+ repository: pr.head?.repo?.full_name || pr.base?.repo?.full_name || payload.repository?.full_name || ""
10672
+ };
10673
+ }
10674
+ function gitHubWebhookCommentBody(payload = {}) {
10675
+ return String(payload.comment?.body || payload.review?.body || payload.pull_request?.body || "").trim();
10676
+ }
10677
+ function formatGitHubWebhookPrompt({ event = "", action = "", pr = {}, payload = {}, taskId = "" } = {}) {
10678
+ const sender = payload.sender?.login || payload.comment?.user?.login || payload.review?.user?.login || "unknown";
10679
+ const reviewState = String(payload.review?.state || "").trim();
10680
+ const commentBody = gitHubWebhookCommentBody(payload);
10681
+ const lines = [
10682
+ "[GitHub Webhook Event]",
10683
+ `Event: ${event}`,
10684
+ `Action: ${action || "unknown"}`,
10685
+ `Task ID: ${taskId || pr.taskId || "unknown"}`,
10686
+ `Pull Request: #${pr.number || "unknown"} ${pr.url || ""}`.trim(),
10687
+ `PR Branch: ${pr.headRef || "unknown"} -> ${pr.baseRef || "unknown"}`,
10688
+ `Sender: ${sender}`,
10689
+ reviewState ? `Review State: ${reviewState}` : null,
10690
+ commentBody ? ["", "Comment/Review Body:", commentBody].join("\n") : null,
10691
+ "",
10692
+ "Required handling:",
10693
+ "- Answer human GitHub comments in GitHub.",
10694
+ "- If the comment/review implies a change, reply first with what you will do, move the ClickUp task/subtask to in progress, implement/fix in the same branch, push, return ClickUp to validation, update the PR, and reply again with what changed.",
10695
+ "- If this is an approved parent PR by the configured final approver/CTO, continue the merge-to-dev, Vercel preproduction verification, smoke regression, cleanup, and ClickUp completion workflow.",
10696
+ "- Keep Optima runtime/process failures out of ClickUp comments; use local logs for process noise."
10697
+ ].filter(Boolean);
10698
+ return lines.join("\n");
10699
+ }
10700
+ function mergeGitHubWebhookMetadata(existingMetadata, { pr = {}, event = "", action = "", delivery = "" } = {}) {
10701
+ const current = normalizeAgentMetadataJson(existingMetadata);
10702
+ const next = {
10703
+ ...current,
10704
+ task: {
10705
+ ...isPlainObject(current.task) ? current.task : {},
10706
+ github: {
10707
+ ...isPlainObject(current.task?.github) ? current.task.github : {},
10708
+ pull_request: Object.fromEntries(Object.entries({
10709
+ number: pr.number || void 0,
10710
+ url: pr.url || void 0,
10711
+ source_branch: pr.headRef || void 0,
10712
+ target_branch: pr.baseRef || void 0,
10713
+ repository: pr.repository || void 0
10714
+ }).filter(([, value]) => value !== void 0 && value !== "")),
10715
+ last_webhook: {
10716
+ event,
10717
+ action,
10718
+ delivery,
10719
+ at: (/* @__PURE__ */ new Date()).toISOString()
10720
+ }
10721
+ }
10722
+ }
10723
+ };
10724
+ return JSON.stringify(sortJsonValue(next), null, 2);
10725
+ }
10726
+ function summarizeGitHubRouteForLog(result = {}, payload = {}, event = "") {
10727
+ return {
10728
+ taskId: result.taskId || null,
10729
+ event: event || null,
10730
+ eventKey: result.eventKey || null,
10731
+ ok: result.ok === true,
10732
+ action: result.action || null,
10733
+ reason: result.reason || null,
10734
+ sessionId: result.sessionId || null,
10735
+ pullRequest: result.prNumber || payload?.pull_request?.number || payload?.issue?.number || null,
10736
+ branch: result.branch || null,
10737
+ worktree: result.worktree || null
10738
+ };
10739
+ }
10460
10740
  function getNestedMetadataValue(metadata, dottedKey) {
10461
10741
  const parts = String(dottedKey || "").split(".").filter(Boolean);
10462
10742
  let cursor = metadata;
@@ -11362,9 +11642,9 @@ function buildClickUpWebhookAuditSummary({ method, url, config, handled, error,
11362
11642
  ok: Boolean(handled?.ok),
11363
11643
  reason: handled?.reason || result.reason || (error ? "handler_error" : void 0),
11364
11644
  action: result.action,
11365
- event: payload ? clickUpEventType(payload) : void 0,
11645
+ event: result.event || (payload ? clickUpEventType(payload) : void 0),
11366
11646
  task: result.taskId || clickUpTaskIdFromPayload(payload || {}),
11367
- comment: clickUpCommentFromPayload(payload || {})?.id || payload?.comment_id || payload?.commentId,
11647
+ comment: clickUpCommentFromPayload(payload || {})?.id || payload?.comment_id || payload?.commentId || payload?.comment?.id || payload?.review?.id,
11368
11648
  session: result.sessionId,
11369
11649
  base_path: config?.basePath || "",
11370
11650
  request_file: requestFile || void 0
@@ -11378,7 +11658,13 @@ function writeClickUpWebhookAuditLog({ method, url, headers = {}, rawBody = "",
11378
11658
  if (level === "error" && !failed) return;
11379
11659
  const logDir = clickUpWebhookAuditLogDir();
11380
11660
  fs5.mkdirSync(logDir, { recursive: true });
11381
- const secretValues = [resolveSecretReference(config?.apiToken), state?.secret, config?.webhook?.secret].filter(Boolean);
11661
+ const secretValues = [
11662
+ resolveSecretReference(config?.apiToken),
11663
+ resolveSecretReference(config?.github?.apiToken),
11664
+ state?.secret,
11665
+ config?.webhook?.secret,
11666
+ resolveSecretReference(config?.github?.webhook?.secret)
11667
+ ].filter(Boolean);
11382
11668
  let requestFile;
11383
11669
  if (level === "verbose") {
11384
11670
  const absoluteRequestFile = clickUpWebhookRequestFilePath(at, logDir);
@@ -11840,6 +12126,176 @@ function runClickUpWebhookRouteInBackground({ payload, config, state, worktree,
11840
12126
  });
11841
12127
  return route;
11842
12128
  }
12129
+ async function routeGitHubWebhookEventUnlocked({ payload, event = "", delivery = "", config, state = {}, worktree = process.cwd(), clickupClient, githubClient = null, openCodeClient, sendSessionEvent = sendOpenCodeSessionEvent, verifySessionEventDelivery = verifyOpenCodeSessionEventDelivery } = {}) {
12130
+ const normalizedEvent = String(event || payload?.event || "").trim();
12131
+ const action = String(payload?.action || "").trim();
12132
+ const eventKey = gitHubWebhookEventKey({ headers: { "x-github-delivery": delivery, "x-github-event": normalizedEvent }, event: normalizedEvent, payload });
12133
+ if (!config?.github?.webhook?.events?.includes(normalizedEvent)) {
12134
+ return { ok: true, action: "ignored", reason: "unsupported_event", event: normalizedEvent, eventKey };
12135
+ }
12136
+ let pr;
12137
+ try {
12138
+ pr = await gitHubPullRequestContextFromPayload(payload, normalizedEvent, githubClient);
12139
+ } catch (error) {
12140
+ appendClickUpWebhookLocalLog(worktree, { type: "github_pr_context_failed", event: normalizedEvent, delivery, message: error.message });
12141
+ return { ok: false, action: "error", reason: "pull_request_context_failed", event: normalizedEvent, eventKey, message: error.message };
12142
+ }
12143
+ if (!pr) {
12144
+ appendClickUpWebhookLocalLog(worktree, { type: "github_pr_context_unavailable", event: normalizedEvent, delivery, action });
12145
+ return { ok: true, action: "ignored", reason: "pull_request_context_unavailable", event: normalizedEvent, eventKey };
12146
+ }
12147
+ const taskId = String(pr.taskId || "").trim();
12148
+ if (!taskId) {
12149
+ appendClickUpWebhookLocalLog(worktree, { type: "github_task_id_unavailable", event: normalizedEvent, delivery, prNumber: pr.number || null, headRef: pr.headRef || null });
12150
+ return { ok: false, action: "error", reason: "task_id_unavailable", event: normalizedEvent, eventKey, prNumber: pr.number || null };
12151
+ }
12152
+ const task = payload.task || (clickupClient?.getTask ? await clickupClient.getTask(taskId) : null);
12153
+ if (!task) {
12154
+ appendClickUpWebhookLocalLog(worktree, { type: "github_clickup_task_unavailable", taskId, event: normalizedEvent, delivery, prNumber: pr.number || null });
12155
+ return { ok: false, action: "error", reason: "task_unavailable", taskId, event: normalizedEvent, eventKey, prNumber: pr.number || null };
12156
+ }
12157
+ if (isClickUpTaskTerminal(task, {}, config.routing?.ignoredStatuses)) {
12158
+ return { ok: true, action: "ignored", reason: "terminal_status", taskId, event: normalizedEvent, eventKey, prNumber: pr.number || null };
12159
+ }
12160
+ const existingMetadata = clickUpTaskAgentMetadata(task, config.routing.metadataFieldId);
12161
+ const metadata = normalizeAgentMetadataJson(existingMetadata);
12162
+ const rawSessionId = getNestedMetadataValue(metadata, config.routing.metadataKey) || getNestedMetadataValue(metadata, "optima.sessions.product_manager");
12163
+ const sessionId = typeof rawSessionId === "string" && rawSessionId.startsWith("ses_") ? rawSessionId : "";
12164
+ const directory = String(
12165
+ getNestedMetadataValue(metadata, "task.worktree") || getNestedMetadataValue(metadata, "task.subtask_worktree") || ""
12166
+ ).trim();
12167
+ const branch = String(
12168
+ getNestedMetadataValue(metadata, "task.branch") || getNestedMetadataValue(metadata, "task.subtask_branch") || pr.headRef || ""
12169
+ ).trim();
12170
+ if (!sessionId) {
12171
+ appendClickUpWebhookLocalLog(worktree, { type: "github_session_missing", taskId, event: normalizedEvent, delivery, prNumber: pr.number || null, branch: branch || null });
12172
+ return { ok: false, action: "error", reason: "session_missing", taskId, event: normalizedEvent, eventKey, prNumber: pr.number || null, branch: branch || null };
12173
+ }
12174
+ if (!directory) {
12175
+ appendClickUpWebhookLocalLog(worktree, { type: "github_worktree_missing", taskId, event: normalizedEvent, delivery, sessionId, prNumber: pr.number || null });
12176
+ return { ok: false, action: "error", reason: "worktree_missing", taskId, event: normalizedEvent, eventKey, sessionId, prNumber: pr.number || null, branch: branch || null };
12177
+ }
12178
+ const prompt = formatGitHubWebhookPrompt({ event: normalizedEvent, action, pr, payload, taskId });
12179
+ const nextMetadata = mergeGitHubWebhookMetadata(existingMetadata, { pr, event: normalizedEvent, action, delivery });
12180
+ if (typeof clickupClient?.updateTaskMetadata === "function") {
12181
+ await clickupClient.updateTaskMetadata({ taskId, fieldId: config.routing.metadataFieldId, value: nextMetadata });
12182
+ }
12183
+ const deliveryResult = await deliverClickUpSessionEventWithVerification({
12184
+ openCodeClient,
12185
+ sendSessionEvent,
12186
+ clickupClient,
12187
+ worktree,
12188
+ taskId,
12189
+ sessionId,
12190
+ agent: config.routing.targetAgent,
12191
+ text: prompt,
12192
+ directory,
12193
+ opencodeBaseUrl: config.opencode?.baseUrl,
12194
+ directPrompt: config.opencode?.promptDelivery === "http",
12195
+ directDelivery: "steer",
12196
+ acceptPromptAdmission: config.opencode?.acceptPromptAdmission === true,
12197
+ eventMarkers: [taskId, String(pr.number || ""), normalizedEvent].filter(Boolean),
12198
+ verifySessionEventDelivery,
12199
+ applyBlockerOnFailure: false
12200
+ });
12201
+ if (!deliveryResult.ok) {
12202
+ appendClickUpWebhookLocalLog(worktree, { type: "github_message_delivery_failed", taskId, event: normalizedEvent, delivery, sessionId, prNumber: pr.number || null, reason: deliveryResult.reason || "message_delivery_failed" });
12203
+ return { ...deliveryResult, eventKey, taskId, event: normalizedEvent, prNumber: pr.number || null, branch: branch || pr.headRef || null, worktree: directory };
12204
+ }
12205
+ return {
12206
+ ok: true,
12207
+ action: "github_event_delivered",
12208
+ taskId,
12209
+ sessionId,
12210
+ event: normalizedEvent,
12211
+ eventKey,
12212
+ prNumber: pr.number || null,
12213
+ branch: branch || pr.headRef || null,
12214
+ worktree: directory,
12215
+ deliveryVerification: deliveryResult.verification,
12216
+ deliveryAdmission: deliveryResult.admissionVerification,
12217
+ deliveryFallback: deliveryResult.fallback
12218
+ };
12219
+ }
12220
+ async function routeGitHubWebhookEvent(options = {}) {
12221
+ let taskId = "";
12222
+ try {
12223
+ const pr = await gitHubPullRequestContextFromPayload(options.payload || {}, options.event || "", options.githubClient || null);
12224
+ taskId = pr?.taskId || "";
12225
+ } catch {
12226
+ taskId = "";
12227
+ }
12228
+ return withClickUpTaskRouteLock(taskId, () => routeGitHubWebhookEventUnlocked(options));
12229
+ }
12230
+ function runGitHubWebhookRouteInBackground({ payload, event, delivery, config, state, worktree, clickupClient, githubClient, openCodeClient, sendSessionEvent, verifySessionEventDelivery } = {}) {
12231
+ const eventKey = gitHubWebhookEventKey({ headers: { "x-github-delivery": delivery, "x-github-event": event }, event, payload });
12232
+ appendClickUpWebhookLocalLog(worktree, { type: "github_webhook_route_started", event, eventKey, async: true, delivery: delivery || null });
12233
+ const route = Promise.resolve().then(() => routeGitHubWebhookEvent({ payload, event, delivery, config, state, worktree, clickupClient, githubClient, openCodeClient, sendSessionEvent, verifySessionEventDelivery })).then((result) => {
12234
+ appendClickUpWebhookLocalLog(worktree, { type: "github_webhook_route_finished", async: true, ...summarizeGitHubRouteForLog(result, payload, event) });
12235
+ return result;
12236
+ }).catch((error) => {
12237
+ appendClickUpWebhookLocalLog(worktree, { type: "github_webhook_route_failed", event, eventKey, async: true, delivery: delivery || null, message: error.message });
12238
+ return { ok: false, action: "error", reason: "background_route_failed", message: error.message };
12239
+ });
12240
+ route.catch(() => {
12241
+ });
12242
+ return route;
12243
+ }
12244
+ async function handleGitHubWebhookRequest({ method = "POST", url = null, headers = {}, rawBody = "", config, state, worktree, clickupClient, githubClient = null, openCodeClient, saveState, now = () => /* @__PURE__ */ new Date(), sendSessionEvent, verifySessionEventDelivery, asyncRouting = false } = {}) {
12245
+ let payload = null;
12246
+ let handled = null;
12247
+ let authenticatedWebhook = false;
12248
+ let receivedAt = null;
12249
+ let latestPersistedState = state;
12250
+ const persistState = (nextState) => {
12251
+ latestPersistedState = nextState;
12252
+ if (saveState) saveState(nextState);
12253
+ };
12254
+ const finish = (result) => {
12255
+ handled = result;
12256
+ receivedAt ??= now();
12257
+ const auditState = authenticatedWebhook ? { ...latestPersistedState, githubLastWebhookAt: receivedAt.toISOString() } : latestPersistedState;
12258
+ if (saveState && authenticatedWebhook) persistState(auditState);
12259
+ writeClickUpWebhookAuditLog({ method, url, headers, rawBody, config, state: auditState, handled, payload, at: receivedAt });
12260
+ return result;
12261
+ };
12262
+ try {
12263
+ if (method !== "POST") return finish({ ok: false, status: 405, reason: "method_not_allowed" });
12264
+ if (url !== null) {
12265
+ const requestPath = new URL(String(url), config?.github?.webhook?.publicUrl || config?.webhook?.publicUrl || "http://localhost").pathname;
12266
+ if (requestPath !== gitHubWebhookExpectedPath(config)) return finish({ ok: false, status: 404, reason: "wrong_path" });
12267
+ }
12268
+ if (!gitHubWebhookEnabled(config)) return finish({ ok: false, status: 404, reason: "github_webhook_disabled" });
12269
+ const maxBodyBytes = Number(config?.webhook?.maxBodyBytes || CLICKUP_WEBHOOK_MAX_BODY_BYTES);
12270
+ const bodyBytes = Buffer.isBuffer(rawBody) ? rawBody.length : Buffer.byteLength(String(rawBody || ""));
12271
+ if (bodyBytes > maxBodyBytes) return finish({ ok: false, status: 413, reason: "body_too_large" });
12272
+ const signature = headers["x-hub-signature-256"] || headers["X-Hub-Signature-256"];
12273
+ const secret = resolveSecretReference(config.github.webhook.secret);
12274
+ if (!verifyGitHubSignature(rawBody, signature, secret)) return finish({ ok: false, status: 401, reason: "invalid_signature" });
12275
+ authenticatedWebhook = true;
12276
+ receivedAt = now();
12277
+ try {
12278
+ payload = JSON.parse(Buffer.isBuffer(rawBody) ? rawBody.toString("utf8") : String(rawBody));
12279
+ } catch {
12280
+ return finish({ ok: false, status: 400, reason: "invalid_json" });
12281
+ }
12282
+ const event = String(headers["x-github-event"] || headers["X-GitHub-Event"] || "").trim();
12283
+ const delivery = String(headers["x-github-delivery"] || headers["X-GitHub-Delivery"] || "").trim();
12284
+ const eventKey = gitHubWebhookEventKey({ headers, event, payload });
12285
+ const remembered = rememberGitHubWebhookEvent({ ...state, githubLastWebhookAt: receivedAt.toISOString() }, eventKey);
12286
+ persistState(remembered.state);
12287
+ if (remembered.duplicate) return finish({ ok: true, status: 200, reason: "duplicate_event", result: { action: "ignored", reason: "duplicate", event, eventKey } });
12288
+ if (asyncRouting) {
12289
+ runGitHubWebhookRouteInBackground({ payload, event, delivery, config, state: remembered.state, worktree, clickupClient, githubClient, openCodeClient, sendSessionEvent, verifySessionEventDelivery });
12290
+ return finish({ ok: true, status: 200, reason: "accepted", result: { action: "accepted", event, eventKey } });
12291
+ }
12292
+ const result = await routeGitHubWebhookEvent({ payload, event, delivery, config, state: remembered.state, worktree, clickupClient, githubClient, openCodeClient, sendSessionEvent, verifySessionEventDelivery });
12293
+ return finish({ ok: result.ok, status: result.ok ? 200 : 422, result });
12294
+ } catch (error) {
12295
+ writeClickUpWebhookAuditLog({ method, url, headers, rawBody, config, state, handled, error, payload, at: now() });
12296
+ throw error;
12297
+ }
12298
+ }
11843
12299
  async function handleClickUpWebhookRequest({ method = "POST", url = null, headers = {}, rawBody = "", config, state, worktree, clickupClient, openCodeClient, saveState, now = () => /* @__PURE__ */ new Date(), sessionExists, createSession, sendSessionEvent, ensureTaskWorktree, verifySessionEventDelivery, asyncRouting = false } = {}) {
11844
12300
  let payload = null;
11845
12301
  let handled = null;
@@ -11902,13 +12358,17 @@ function clickUpListenerFingerprint({ config, state, worktree, clickupClient, op
11902
12358
  teamId: config?.teamId || "",
11903
12359
  publicUrl: config?.webhook?.publicUrl || "",
11904
12360
  path: clickUpWebhookExpectedPath(config),
12361
+ githubPublicUrl: config?.github?.webhook?.publicUrl || "",
12362
+ githubPath: gitHubWebhookExpectedPath(config),
12363
+ githubEnabled: gitHubWebhookEnabled(config),
12364
+ githubEvents: config?.github?.webhook?.events || [],
11905
12365
  location: config?.webhook?.location || {},
11906
12366
  webhookId: state?.webhookId || "",
11907
12367
  secret: state?.secret || "",
11908
12368
  events: config?.webhook?.events || []
11909
12369
  });
11910
12370
  }
11911
- function startClickUpWebhookListener({ config, state, worktree, clickupClient, openCodeClient, listenerRegistry = activeClickUpWebhookListeners } = {}) {
12371
+ function startClickUpWebhookListener({ config, state, worktree, clickupClient, githubClient = null, openCodeClient, listenerRegistry = activeClickUpWebhookListeners } = {}) {
11912
12372
  if (!isClickUpWebhookStateActive(state, config)) return { active: false, ready: false, reason: "inactive_state" };
11913
12373
  const key = clickUpListenerKey(config);
11914
12374
  const fingerprint = clickUpListenerFingerprint({ config, state, worktree, clickupClient, openCodeClient });
@@ -11927,7 +12387,9 @@ function startClickUpWebhookListener({ config, state, worktree, clickupClient, o
11927
12387
  return;
11928
12388
  }
11929
12389
  const requestPath = new URL(req.url || "/", config.webhook.publicUrl).pathname;
11930
- if (requestPath !== clickUpWebhookExpectedPath(config)) {
12390
+ const isClickUpPath = requestPath === clickUpWebhookExpectedPath(config);
12391
+ const isGitHubPath = gitHubWebhookEnabled(config) && requestPath === gitHubWebhookExpectedPath(config);
12392
+ if (!isClickUpPath && !isGitHubPath) {
11931
12393
  res.writeHead(404, { "Content-Type": "application/json" });
11932
12394
  res.end(JSON.stringify({ ok: false, reason: "wrong_path" }));
11933
12395
  return;
@@ -11954,7 +12416,7 @@ function startClickUpWebhookListener({ config, state, worktree, clickupClient, o
11954
12416
  if (overflow || res.writableEnded) return;
11955
12417
  const latestState = readClickUpWebhookState(worktree, config);
11956
12418
  try {
11957
- const handled = await handleClickUpWebhookRequest({
12419
+ const common = {
11958
12420
  method: req.method,
11959
12421
  url: req.url,
11960
12422
  headers: req.headers,
@@ -11966,7 +12428,8 @@ function startClickUpWebhookListener({ config, state, worktree, clickupClient, o
11966
12428
  openCodeClient,
11967
12429
  saveState: (nextState) => writeClickUpWebhookState(worktree, nextState, config),
11968
12430
  asyncRouting: true
11969
- });
12431
+ };
12432
+ const handled = isGitHubPath ? await handleGitHubWebhookRequest({ ...common, githubClient }) : await handleClickUpWebhookRequest(common);
11970
12433
  res.writeHead(handled.status, { "Content-Type": "application/json" });
11971
12434
  res.end(JSON.stringify({ ok: handled.ok, reason: handled.reason, result: handled.result?.action }));
11972
12435
  } catch (error) {
@@ -13084,6 +13547,7 @@ async function OptimaPlugin(input = {}, pluginOptions = {}) {
13084
13547
  const operatingTeamMode = getOperatingTeamMode(repoCfg);
13085
13548
  const clickUpWebhookValidation = normalizeClickUpWebhookConfig(pickConfiguredClickUp(input, repoCfg, resolvedPluginOptions), worktree);
13086
13549
  const runtimeClickUpClient = input.clickupClient || (clickUpWebhookValidation.config?.test === true ? createTestClickUpApiClient(clickUpWebhookValidation.config) : createClickUpApiClient(clickUpWebhookValidation.config, input.fetch));
13550
+ const runtimeGitHubClient = input.githubClient || (clickUpWebhookValidation.config?.github?.webhook?.enabled === true ? createGitHubApiClient(clickUpWebhookValidation.config.github, input.fetch) : null);
13087
13551
  let clickUpWebhookRuntime = { active: false, valid: false, reason: "not_configured" };
13088
13552
  if (clickUpWebhookValidation.complete) {
13089
13553
  try {
@@ -13108,6 +13572,7 @@ async function OptimaPlugin(input = {}, pluginOptions = {}) {
13108
13572
  state: listenerState,
13109
13573
  worktree,
13110
13574
  clickupClient: lifecycleClickUpClient,
13575
+ githubClient: runtimeGitHubClient,
13111
13576
  openCodeClient: input.client,
13112
13577
  listenerRegistry
13113
13578
  });
@@ -13783,7 +14248,7 @@ Follow-up: use optima_prompt_workflow with session_id '${sessionId}' to check in
13783
14248
  }
13784
14249
  };
13785
14250
  }
13786
- 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, compactPromptPath, createClickUpApiClient, createTestClickUpApiClient, createOpenCodeSession, createOpenCodeSessionControl, deliveryEvidencePathForClickUpTask, ensureClickUpTaskWorktree, ensureClickUpTaskWorktreeForWebhook, ensureClickUpTaskWorktreeOpenChamber, ensureClickUpWebhookSubscription, handleClickUpWebhookRequest, inspectOpenCodeSessionActivity, isClickUpDerivedWorktreeSibling, isClickUpSubtaskRoute, isClickUpWebhookStateActive, isSameOrNestedPath, normalizeClickUpWebhookConfig, normalizeClickUpWebhookLogLevel, normalizeOpenCodeBaseUrl, openCodeSessionExists, promptOpenCodeSessionControl, probeOpenCodeSessionControl, readClickUpCommentLedger, readClickUpWebhookState, readOpenCodeSessionControl, readOpenCodeSessionMessages, reconcileClickUpStartup, registerOpenChamberClickUpWorktree, scheduleClickUpAssignmentWatchdog, scheduleClickUpStartupReconciliation, syncOpenChamberWorktreeVisibility, waitForOpenCodeReadiness, recordClickUpCommentVersionProcessed, resyncClickUpWebhookForSignatureDrift, resolveIncludeFile, resolveIncludes, resolveOptimaPluginOptions, resolveSecretReference, routeClickUpWebhookEvent, sendOpenCodeSessionEvent, sendOpenCodeSessionEventDirect, summarizeOpenCodeMessages, verifyOpenCodeSessionEventDelivery, stableClickUpCommentVersionMarker, startClickUpWebhookListener, validateClickUpWebhookState, verifyClickUpSignature, writeClickUpWebhookState, decideClickUpStatusAction, deriveClickUpBranchName, deriveClickUpPendingSubtaskBranch, deriveClickUpPrTarget, deriveClickUpWorktree, determineClickUpMergeAuthority, ensureOptimaGitignoreRules, explicitSafeInputWorktree, finalApprovalAssignees, formatRepairResult, formatValidationResult, isIgnoredClickUpTaskType, isOptimaPluginPackageWorktree, isSafeWritableDirectory, loadHumansRegistry, mergeClickUpAgentMetadata, mergeClickUpSessionMetadata, migrateLegacyOptimaLayout, normalizeAgentMetadataJson, normalizeClickUpStatus, normalizeClickUpTaskType, normalizePromptResponseParts, normalizeWorkflowTaskPath, optimaRepairDependencies, parseClickUpSubtasksMarkdown, parseHumansRegistry, parseMarkdownArtifact, parseMarkdownSections, planOptimaRepair, preEstimateClickUpWork, readMarkdownArtifact, resolveHumanRoles, resolveSafeWorktree, safeWorktreeOrFailure, stripRawLogSections, validateMainWorkspaceBranchSafety, workflowFinalMessageFromPromptResponse };
14251
+ OptimaPlugin.__internals = { BUNDLE_AGENTS_DIR, BUNDLE_ASSETS_DIR, CLICKUP_DEFINITION_DOC_PARENT, activeClickUpWebhookLifecycleRegistry, cleanupManagedClickUpWebhook, deleteClickUpWebhookBestEffort, BUNDLE_POLICIES_DIR, buildClickUpApplyPayloadResult, buildClickUpCreateSubtasksPayload, buildClickUpReviewAutomationPlan, buildClickUpStartTaskPayload, buildClickUpSummaryPayload, buildClickUpTransitionPayload, buildOptimaAgents, clickUpCommentLedgerKey, clickUpCommentMentionsProductManager, clickUpWebhookAuditLogDir, clickUpWebhookAuditLogPath, compactPromptPath, createClickUpApiClient, createGitHubApiClient, createTestClickUpApiClient, createOpenCodeSession, createOpenCodeSessionControl, deliveryEvidencePathForClickUpTask, ensureClickUpTaskWorktree, ensureClickUpTaskWorktreeForWebhook, ensureClickUpTaskWorktreeOpenChamber, ensureClickUpWebhookSubscription, gitHubPullRequestContextFromPayload, gitHubWebhookExpectedPath, handleClickUpWebhookRequest, handleGitHubWebhookRequest, inspectOpenCodeSessionActivity, isClickUpDerivedWorktreeSibling, isClickUpSubtaskRoute, isClickUpWebhookStateActive, isSameOrNestedPath, normalizeClickUpWebhookConfig, normalizeClickUpWebhookLogLevel, normalizeOpenCodeBaseUrl, openCodeSessionExists, promptOpenCodeSessionControl, probeOpenCodeSessionControl, readClickUpCommentLedger, readClickUpWebhookState, readOpenCodeSessionControl, readOpenCodeSessionMessages, reconcileClickUpStartup, registerOpenChamberClickUpWorktree, routeGitHubWebhookEvent, scheduleClickUpAssignmentWatchdog, scheduleClickUpStartupReconciliation, syncOpenChamberWorktreeVisibility, waitForOpenCodeReadiness, recordClickUpCommentVersionProcessed, resyncClickUpWebhookForSignatureDrift, resolveIncludeFile, resolveIncludes, resolveOptimaPluginOptions, resolveSecretReference, routeClickUpWebhookEvent, sendOpenCodeSessionEvent, sendOpenCodeSessionEventDirect, summarizeOpenCodeMessages, verifyOpenCodeSessionEventDelivery, stableClickUpCommentVersionMarker, startClickUpWebhookListener, validateClickUpWebhookState, verifyClickUpSignature, verifyGitHubSignature, writeClickUpWebhookState, decideClickUpStatusAction, deriveClickUpBranchName, deriveClickUpPendingSubtaskBranch, deriveClickUpPrTarget, deriveClickUpRequiredPullRequest, deriveClickUpWorktree, determineClickUpMergeAuthority, ensureOptimaGitignoreRules, explicitSafeInputWorktree, finalApprovalAssignees, formatRepairResult, formatValidationResult, isIgnoredClickUpTaskType, isOptimaPluginPackageWorktree, isSafeWritableDirectory, loadHumansRegistry, mergeClickUpAgentMetadata, mergeClickUpSessionMetadata, migrateLegacyOptimaLayout, normalizeAgentMetadataJson, normalizeClickUpStatus, normalizeClickUpTaskType, normalizePromptResponseParts, normalizeWorkflowTaskPath, optimaRepairDependencies, parseClickUpSubtasksMarkdown, parseHumansRegistry, parseMarkdownArtifact, parseMarkdownSections, planOptimaRepair, preEstimateClickUpWork, readMarkdownArtifact, resolveHumanRoles, resolveSafeWorktree, safeWorktreeOrFailure, stripRawLogSections, validateMainWorkspaceBranchSafety, workflowFinalMessageFromPromptResponse };
13787
14252
 
13788
14253
  // src/sanitize_cli.js
13789
14254
  var { migrateLegacyOptimaLayout: migrateLegacyOptimaLayout2 } = OptimaPlugin.__internals;