@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.
package/dist/index.js CHANGED
@@ -8627,6 +8627,7 @@ var activeWorkflows = /* @__PURE__ */ new Map();
8627
8627
  var activeClickUpWebhookListeners = /* @__PURE__ */ new Map();
8628
8628
  var activeClickUpWebhookLifecycleRegistry = /* @__PURE__ */ new Map();
8629
8629
  var activeClickUpTaskRoutes = /* @__PURE__ */ new Map();
8630
+ var GITHUB_WEBHOOK_EVENTS = ["pull_request", "pull_request_review", "pull_request_review_comment", "issue_comment"];
8630
8631
  function isRootDirectory(candidate) {
8631
8632
  const resolved = path6.resolve(candidate);
8632
8633
  return resolved === path6.parse(resolved).root;
@@ -8819,6 +8820,70 @@ function deriveClickUpPrTarget({ isRelease = false, parentTaskId, parentBranch,
8819
8820
  }
8820
8821
  return devBranch;
8821
8822
  }
8823
+ function deriveClickUpRequiredPullRequest({ taskId, taskType = "Tarea", parentTaskId = "", subtaskId = "", branch = "", parentBranch = "", devBranch = "dev", mainBranch = "main", isRelease = false, githubRemote = "origin", prUrl = "", prNumber = "" } = {}) {
8824
+ const effectiveParent = parentTaskId || taskId;
8825
+ const isSubtask = isClickUpSubtaskRoute({ taskType, parentTaskId: effectiveParent, subtaskId, taskId });
8826
+ const sourceBranch = branch || deriveClickUpBranchName({ taskType, parentTaskId: effectiveParent, subtaskId, taskId });
8827
+ const targetBranch = deriveClickUpPrTarget({
8828
+ isRelease,
8829
+ parentBranch: isSubtask ? parentBranch || deriveClickUpBranchName({ taskType, parentTaskId: effectiveParent }) : parentBranch,
8830
+ parentTaskId: isSubtask ? effectiveParent : "",
8831
+ parentTaskType: taskType,
8832
+ taskType,
8833
+ devBranch,
8834
+ mainBranch
8835
+ });
8836
+ return {
8837
+ required: true,
8838
+ sourceBranch,
8839
+ targetBranch,
8840
+ githubRemote,
8841
+ prUrl: String(prUrl || "").trim(),
8842
+ prNumber: String(prNumber || "").trim(),
8843
+ scope: isSubtask ? "subtask_to_parent" : isRelease ? "release_to_main" : "task_to_dev",
8844
+ policy: "validation_requires_open_pull_request"
8845
+ };
8846
+ }
8847
+ function buildClickUpReviewAutomationPlan({ isSubtask = false, humanApprover = "CTO", preproductionProvider = "Vercel", preproductionEnvironment = "preproduction" } = {}) {
8848
+ return {
8849
+ github_webhooks: {
8850
+ listen_for: ["pull_request_review", "pull_request_review_comment", "issue_comment", "pull_request"],
8851
+ comment_policy: "reply_before_change_and_after_change",
8852
+ change_request_status: "in progress",
8853
+ after_fix_status: "validation",
8854
+ state_storage: "ClickUp agent_metadata.github"
8855
+ },
8856
+ review_comments: [
8857
+ "Answer GitHub comments directly in GitHub.",
8858
+ "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.",
8859
+ "Also update ClickUp with human-readable model work status; runtime/process noise stays in local logs."
8860
+ ],
8861
+ accepted_review: {
8862
+ accepted_by: humanApprover,
8863
+ trigger: "approved_pull_request_review",
8864
+ action: isSubtask ? "merge_subtask_pr_into_parent_branch" : "merge_parent_pr_into_dev",
8865
+ delete_source_branch_after_success: true,
8866
+ delete_worktree_after_success: true,
8867
+ preserve_opencode_sessions_in_agent_metadata: true
8868
+ },
8869
+ preproduction: isSubtask ? {
8870
+ required: false,
8871
+ reason: "Subtask merge stops at parent branch; parent task owns dev/preproduction."
8872
+ } : {
8873
+ required: true,
8874
+ provider: preproductionProvider,
8875
+ environment: preproductionEnvironment,
8876
+ verify_deployment_after_merge: true,
8877
+ run_smoke_regression: true,
8878
+ on_failure: "create_bug_subtasks_and_return_parent_to_in_progress",
8879
+ completion_gate: "preproduction_deployed_and_regression_passed"
8880
+ },
8881
+ completion: {
8882
+ final_clickup_comment_required: true,
8883
+ final_status: isSubtask ? "completed_or_parent_validation" : "completed"
8884
+ }
8885
+ };
8886
+ }
8822
8887
  function validateMainWorkspaceBranchSafety({ currentBranch, requiredBranch = "dev", forbiddenBranch = "main" } = {}) {
8823
8888
  const normalized = String(currentBranch ?? "").trim();
8824
8889
  if (!normalized) {
@@ -9481,6 +9546,7 @@ function buildClickUpStartTaskPayload({ taskId, taskType = "Tarea", parentTaskId
9481
9546
  const worktree = deriveClickUpWorktree({ baseWorktree, taskId, taskType, parentTaskId: effectiveParent, subtaskId });
9482
9547
  const prTarget = isSubtask ? parentBranch : "dev";
9483
9548
  const startFrom = isSubtask ? parentBranch : "dev";
9549
+ const requiredPullRequest = deriveClickUpRequiredPullRequest({ taskId, taskType, parentTaskId: effectiveParent, subtaskId, branch, parentBranch });
9484
9550
  const estimate = preEstimateClickUpWork({ complexity, filesChanged, acceptanceCriteria, unknowns });
9485
9551
  const metadata = mergeClickUpAgentMetadata(existingAgentMetadata, {
9486
9552
  task: {
@@ -9494,6 +9560,10 @@ function buildClickUpStartTaskPayload({ taskId, taskType = "Tarea", parentTaskId
9494
9560
  mirror_task_path: `.optima/tasks/${branchSafeClickUpId(taskId || subtaskId || effectiveParent)}.md`,
9495
9561
  evidence_path: `.optima/evidences/${branchSafeClickUpId(taskId || subtaskId || effectiveParent)}/SUMMARY.md`,
9496
9562
  delivery_evidence_path: deliveryEvidencePathForClickUpTask(taskId || subtaskId || effectiveParent),
9563
+ github: {
9564
+ required_pull_request: requiredPullRequest,
9565
+ review_automation: buildClickUpReviewAutomationPlan({ isSubtask })
9566
+ },
9497
9567
  ...agentMetadata
9498
9568
  }
9499
9569
  });
@@ -9516,6 +9586,10 @@ function buildClickUpStartTaskPayload({ taskId, taskType = "Tarea", parentTaskId
9516
9586
  parentBranch: parentBranch || void 0,
9517
9587
  prTarget
9518
9588
  },
9589
+ github: {
9590
+ requiredPullRequest,
9591
+ reviewAutomation: buildClickUpReviewAutomationPlan({ isSubtask })
9592
+ },
9519
9593
  mirror: {
9520
9594
  taskPath: `.optima/tasks/${branchSafeClickUpId(taskId || subtaskId || effectiveParent)}.md`,
9521
9595
  evidenceSummaryPath: `.optima/evidences/${branchSafeClickUpId(taskId || subtaskId || effectiveParent)}/SUMMARY.md`,
@@ -9526,14 +9600,15 @@ function buildClickUpStartTaskPayload({ taskId, taskType = "Tarea", parentTaskId
9526
9600
  wouldCreate: true
9527
9601
  },
9528
9602
  clickup: {
9529
- comment: `Starting task from ${startFrom}. Branch: ${branch}. Worktree: ${worktree}. Delivery evidence: ${deliveryEvidencePathForClickUpTask(taskId || subtaskId || effectiveParent)}.`,
9603
+ comment: `Starting task from ${startFrom}. Branch: ${branch}. Worktree: ${worktree}. Validation PR target: ${requiredPullRequest.targetBranch}. Delivery evidence: ${deliveryEvidencePathForClickUpTask(taskId || subtaskId || effectiveParent)}.`,
9530
9604
  description,
9531
9605
  fields: {
9532
9606
  Definition: definitionContent,
9533
9607
  "Story Points": estimate.storyPoints,
9534
9608
  agent_metadata: metadata,
9535
9609
  branch,
9536
- worktree
9610
+ worktree,
9611
+ pr_target: requiredPullRequest.targetBranch
9537
9612
  },
9538
9613
  definition_doc: {
9539
9614
  parent: normalizeClickUpDefinitionDocParent(definitionDocParent),
@@ -9543,23 +9618,29 @@ function buildClickUpStartTaskPayload({ taskId, taskType = "Tarea", parentTaskId
9543
9618
  }
9544
9619
  };
9545
9620
  }
9546
- 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 } = {}) {
9621
+ 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" } = {}) {
9547
9622
  const from = normalizeClickUpStatus(fromStatus);
9623
+ const effectiveIsSubtask = Boolean(isSubtask || isClickUpSubtaskRoute({ taskType, parentTaskId: parentTaskId || taskId, subtaskId, taskId }));
9548
9624
  let to = normalizeClickUpStatus(toStatus);
9549
9625
  if (validationFailed) to = "in progress";
9550
- if (!to && validationPassed) to = isSubtask ? "completed" : "merge";
9626
+ if (!to && validationPassed) to = effectiveIsSubtask ? "completed" : "merge";
9551
9627
  if ((from === "completed" || from === "closed") && !reopen) {
9552
9628
  return { ok: true, noop: true, dryRun: true, message: "Completed/closed task is a no-op unless reopen is explicit." };
9553
9629
  }
9554
9630
  const key = `${from}->${to}`;
9555
9631
  const rule = CLICKUP_TRANSITIONS.get(key);
9556
9632
  if (!rule) return { ok: false, dryRun: true, message: `Transition not allowed: ${key}` };
9557
- const assignsFinalApprovers = rule.assignFinalApprovers === true && !(rule.parentOnlyFinalApproval && isSubtask);
9633
+ const assignsFinalApprovers = rule.assignFinalApprovers === true && !(rule.parentOnlyFinalApproval && effectiveIsSubtask);
9558
9634
  const requiresPlanCompletionContract = from === "plan" && to === "in progress";
9635
+ const requiresValidationPullRequest = from === "in progress" && to === "validation";
9559
9636
  const definitionContent = compactMarkdownValue(definition);
9560
9637
  const description = compactMarkdownValue(planDescription);
9638
+ const canDerivePullRequest = Boolean(taskId || branch);
9639
+ const shouldAttachPullRequest = requiresValidationPullRequest || (from === "validation" || to === "merge" || from === "merge") && canDerivePullRequest;
9640
+ const requiredPullRequest = shouldAttachPullRequest && canDerivePullRequest ? deriveClickUpRequiredPullRequest({ taskId, taskType, parentTaskId, subtaskId, branch, parentBranch, devBranch, mainBranch, githubRemote, prUrl, prNumber }) : null;
9641
+ const reviewAutomation = requiredPullRequest ? buildClickUpReviewAutomationPlan({ isSubtask: effectiveIsSubtask, preproductionProvider, preproductionEnvironment }) : null;
9561
9642
  const validationErrors = [];
9562
- if (rule.parentOnlyFinalApproval && isSubtask) {
9643
+ if (rule.parentOnlyFinalApproval && effectiveIsSubtask) {
9563
9644
  validationErrors.push("Subtasks must not use the parent final-approval transition; merge validated subtasks directly into the parent branch/workspace.");
9564
9645
  }
9565
9646
  if (assignsFinalApprovers && !isRealClickUpAssigneeId(productManagerAssignee) && requireProductManagerAssignee !== false) {
@@ -9569,19 +9650,34 @@ function buildClickUpTransitionPayload({ fromStatus, toStatus, validationPassed
9569
9650
  if (!description) validationErrors.push("planDescription is required for the plan-completion ClickUp task description rewrite.");
9570
9651
  if (!definitionContent) validationErrors.push("definition is required and must contain the complete plan/Definition content at plan completion.");
9571
9652
  }
9653
+ if (requiresValidationPullRequest) {
9654
+ if (!requiredPullRequest) validationErrors.push("taskId or branch is required before moving a ClickUp task to validation.");
9655
+ if (!requiredPullRequest?.sourceBranch) validationErrors.push("branch/sourceBranch is required before moving a ClickUp task to validation.");
9656
+ if (!requiredPullRequest?.targetBranch) validationErrors.push("PR target branch is required before moving a ClickUp task to validation.");
9657
+ if (!requiredPullRequest?.prUrl && !requiredPullRequest?.prNumber) validationErrors.push("An open GitHub pull request URL or number is required before moving a ClickUp task to validation.");
9658
+ }
9572
9659
  if (validationErrors.length > 0) return clickUpPayloadValidationError(validationErrors);
9573
9660
  const finalApprovers = assignsFinalApprovers ? finalApprovalAssignees(CLICKUP_FINAL_APPROVER_ROLES, humansRegistry) : [];
9574
9661
  const explicitRemovals = (removeAssignees || []).filter(Boolean);
9575
9662
  const normalizedProductManagerAssignee = isRealClickUpAssigneeId(productManagerAssignee) ? String(productManagerAssignee).trim() : "";
9576
9663
  const removalTargets = assignsFinalApprovers ? [...new Set([normalizedProductManagerAssignee, ...explicitRemovals].filter(Boolean))] : explicitRemovals;
9577
9664
  const assigned = [...new Set([...assignees || [], ...finalApprovers].filter(Boolean))].filter((assignee) => !removalTargets.includes(assignee));
9578
- const authority = from === "validation" || to === "merge" ? determineClickUpMergeAuthority({ isSubtask, clickupStatus: to, validationPassed: validationPassed || to === "merge", humansRegistry }) : null;
9665
+ const authority = from === "validation" || to === "merge" ? determineClickUpMergeAuthority({ isSubtask: effectiveIsSubtask, clickupStatus: to, validationPassed: validationPassed || to === "merge", humansRegistry }) : null;
9579
9666
  const fields = authority ? { merge_authority: JSON.stringify(authority) } : {};
9667
+ if (requiredPullRequest) {
9668
+ fields.github_pull_request = JSON.stringify(requiredPullRequest);
9669
+ fields.github_review_automation = JSON.stringify(reviewAutomation);
9670
+ }
9580
9671
  if (definitionContent) fields.Definition = definitionContent;
9581
9672
  return {
9582
9673
  ok: true,
9583
9674
  dryRun: true,
9584
- transition: { fromStatus: from, toStatus: rule.status, validationPassed, validationFailed, isSubtask },
9675
+ transition: { fromStatus: from, toStatus: rule.status, validationPassed, validationFailed, isSubtask: effectiveIsSubtask },
9676
+ github: requiredPullRequest ? {
9677
+ requiredPullRequest,
9678
+ reviewAutomation
9679
+ } : void 0,
9680
+ vercel: reviewAutomation?.preproduction?.required ? reviewAutomation.preproduction : void 0,
9585
9681
  clickup: {
9586
9682
  status: rule.status,
9587
9683
  assignees: assigned,
@@ -9785,9 +9881,48 @@ function normalizeNonNegativeInteger(value, defaultValue) {
9785
9881
  const number = Number(value);
9786
9882
  return Number.isInteger(number) && number >= 0 ? number : defaultValue;
9787
9883
  }
9884
+ function defaultGitHubWebhookPublicUrl(clickupPublicUrl = "") {
9885
+ const raw = String(clickupPublicUrl || "").trim();
9886
+ if (!raw) return "";
9887
+ try {
9888
+ const url = new URL(raw);
9889
+ url.pathname = "/optima/github/webhook";
9890
+ url.search = "";
9891
+ url.hash = "";
9892
+ return url.toString();
9893
+ } catch {
9894
+ return raw.replace(/\/optima\/clickup\/webhook\b/, "/optima/github/webhook");
9895
+ }
9896
+ }
9897
+ function normalizeGitHubWebhookConfig(rawGithub = {}, clickupWebhook = {}) {
9898
+ const github = isPlainObject(rawGithub) ? rawGithub : {};
9899
+ const webhook = isPlainObject(github.webhook) ? github.webhook : {};
9900
+ const enabled = github.enabled === true || webhook.enabled === true;
9901
+ const events = Array.isArray(webhook.events) && webhook.events.length > 0 ? [...new Set(webhook.events.map((event) => String(event || "").trim()).filter(Boolean))] : [...GITHUB_WEBHOOK_EVENTS];
9902
+ const publicUrl = String(webhook.public_url || webhook.publicUrl || github.public_url || github.publicUrl || defaultGitHubWebhookPublicUrl(clickupWebhook.publicUrl)).trim();
9903
+ const secret = String(webhook.secret || github.secret || "").trim();
9904
+ const apiToken = String(github.api_token || github.apiToken || "").trim();
9905
+ const repository = String(github.repository || github.repo || "").trim();
9906
+ const [ownerFromRepo, nameFromRepo] = repository.includes("/") ? repository.split("/", 2) : ["", ""];
9907
+ return {
9908
+ enabled,
9909
+ apiToken,
9910
+ repository,
9911
+ owner: String(github.owner || ownerFromRepo || "").trim(),
9912
+ repo: String(github.repo_name || github.repoName || nameFromRepo || "").trim(),
9913
+ webhook: {
9914
+ enabled,
9915
+ publicUrl,
9916
+ secret,
9917
+ path: publicUrl ? new URL(publicUrl, "http://localhost").pathname : "/optima/github/webhook",
9918
+ events
9919
+ }
9920
+ };
9921
+ }
9788
9922
  function normalizeClickUpWebhookConfig(rawClickUp = null, worktree = process.cwd()) {
9789
9923
  const raw = isPlainObject(rawClickUp) ? rawClickUp : {};
9790
9924
  const webhook = isPlainObject(raw.webhook) ? raw.webhook : {};
9925
+ const github = normalizeGitHubWebhookConfig(raw.github, { publicUrl: String(webhook.public_url || webhook.publicUrl || "").trim() });
9791
9926
  const routing = isPlainObject(raw.routing) ? raw.routing : {};
9792
9927
  const opencode = isPlainObject(raw.opencode) ? raw.opencode : {};
9793
9928
  const openchamber = isPlainObject(raw.openchamber) ? raw.openchamber : {};
@@ -9840,6 +9975,7 @@ function normalizeClickUpWebhookConfig(rawClickUp = null, worktree = process.cwd
9840
9975
  },
9841
9976
  events
9842
9977
  },
9978
+ github,
9843
9979
  routing: {
9844
9980
  targetAgent: String(routing.target_agent || routing.targetAgent || "workflow_product_manager").trim(),
9845
9981
  productManagerAssigneeId: String(routing.product_manager_assignee_id || routing.productManagerAssigneeId || "").trim(),
@@ -9861,6 +9997,13 @@ function normalizeClickUpWebhookConfig(rawClickUp = null, worktree = process.cwd
9861
9997
  if (!config.webhook.bindHost) errors.push("clickup.webhook.bind_host is required");
9862
9998
  if (!Number.isInteger(config.webhook.bindPort) || config.webhook.bindPort <= 0) errors.push("clickup.webhook.bind_port must be a positive integer");
9863
9999
  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");
10000
+ if (config.github.webhook.enabled) {
10001
+ if (!config.github.webhook.publicUrl) errors.push("clickup.github.webhook.public_url is required when GitHub webhook is enabled");
10002
+ if (!config.github.webhook.secret) errors.push("clickup.github.webhook.secret is required when GitHub webhook is enabled");
10003
+ 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");
10004
+ const missingGitHubEvents = GITHUB_WEBHOOK_EVENTS.filter((event) => !config.github.webhook.events.includes(event));
10005
+ if (missingGitHubEvents.length > 0) errors.push(`clickup.github.webhook.events missing: ${missingGitHubEvents.join(", ")}`);
10006
+ }
9864
10007
  if (config.opencode.baseUrl) {
9865
10008
  try {
9866
10009
  new URL(config.opencode.baseUrl);
@@ -9898,6 +10041,8 @@ function sanitizeClickUpWebhookState(state = {}, config = null) {
9898
10041
  listener: isPlainObject(state.listener) ? state.listener : {},
9899
10042
  pendingSessions: isPlainObject(state.pendingSessions) ? state.pendingSessions : {},
9900
10043
  lastWebhookAt: state.lastWebhookAt || state.last_webhook_at || null,
10044
+ githubLastWebhookAt: state.githubLastWebhookAt || state.github_last_webhook_at || null,
10045
+ githubRecentEventKeys: Array.isArray(state.githubRecentEventKeys) ? state.githubRecentEventKeys.slice(-200) : [],
9901
10046
  recentEventKeys
9902
10047
  };
9903
10048
  }
@@ -10014,6 +10159,29 @@ function createClickUpApiClient(config, fetchImpl = globalThis.fetch) {
10014
10159
  }
10015
10160
  };
10016
10161
  }
10162
+ function createGitHubApiClient(config = {}, fetchImpl = globalThis.fetch) {
10163
+ const token = resolveSecretReference(config?.apiToken);
10164
+ const owner = String(config?.owner || "").trim();
10165
+ const repo = String(config?.repo || "").trim();
10166
+ const request = async (pathname) => {
10167
+ if (typeof fetchImpl !== "function") throw new Error("fetch is unavailable; inject a GitHub client for live PR lookup");
10168
+ if (!owner || !repo) throw new Error("GitHub repository owner/repo is not configured");
10169
+ const response = await fetchImpl(`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}${pathname}`, {
10170
+ headers: {
10171
+ Accept: "application/vnd.github+json",
10172
+ "X-GitHub-Api-Version": "2022-11-28",
10173
+ ...token ? { Authorization: `Bearer ${token}` } : {}
10174
+ }
10175
+ });
10176
+ if (!response.ok) throw new Error(`GitHub API request failed: ${response.status}`);
10177
+ return response.json();
10178
+ };
10179
+ return {
10180
+ async getPullRequest(number) {
10181
+ return request(`/pulls/${encodeURIComponent(number)}`);
10182
+ }
10183
+ };
10184
+ }
10017
10185
  function createTestClickUpApiClient(config) {
10018
10186
  const metadata = /* @__PURE__ */ new Map();
10019
10187
  return {
@@ -10244,6 +10412,19 @@ function verifyClickUpSignature(rawBody, signatureHeader, secret) {
10244
10412
  const wanted = Buffer.from(expected, "hex");
10245
10413
  return given.length === wanted.length && crypto.timingSafeEqual(given, wanted);
10246
10414
  }
10415
+ function verifyGitHubSignature(rawBody, signatureHeader, secret) {
10416
+ return verifyClickUpSignature(rawBody, signatureHeader, secret);
10417
+ }
10418
+ function gitHubWebhookEventKey({ headers = {}, event = "", payload = {} } = {}) {
10419
+ const delivery = headers["x-github-delivery"] || headers["X-GitHub-Delivery"] || payload.delivery || payload.delivery_id || "";
10420
+ const id = delivery || payload.pull_request?.id || payload.comment?.id || payload.review?.id || payload.issue?.id || "unknown";
10421
+ return ["github", event || headers["x-github-event"] || headers["X-GitHub-Event"] || "event", id].filter(Boolean).join(":");
10422
+ }
10423
+ function rememberGitHubWebhookEvent(state = {}, eventKey, limit = 200) {
10424
+ const recent = Array.isArray(state.githubRecentEventKeys) ? state.githubRecentEventKeys : [];
10425
+ if (recent.includes(eventKey)) return { duplicate: true, state };
10426
+ return { duplicate: false, state: { ...state, githubRecentEventKeys: [...recent, eventKey].slice(-limit) } };
10427
+ }
10247
10428
  async function resyncClickUpWebhookForSignatureDrift({ rawBody, signature, config, state = {}, worktree = process.cwd(), clickupClient, saveState } = {}) {
10248
10429
  const validation = clickUpWebhookValidationFromConfig(config);
10249
10430
  if (!clickupClient?.listWebhooks && !clickupClient?.createWebhook) {
@@ -10450,6 +10631,105 @@ function clickUpTaskAgentMetadata(task = {}, fieldId = "") {
10450
10631
  const field = fields.find((item) => String(item?.id || "") === fieldId || String(item?.name || "") === "agent_metadata");
10451
10632
  return field?.value || "";
10452
10633
  }
10634
+ function gitHubWebhookExpectedPath(config = {}) {
10635
+ return config?.github?.webhook?.path || "/optima/github/webhook";
10636
+ }
10637
+ function gitHubWebhookEnabled(config = {}) {
10638
+ return Boolean(config?.github?.webhook?.enabled && resolveSecretReference(config?.github?.webhook?.secret));
10639
+ }
10640
+ function clickUpTaskIdFromGitHubBranch(branch = "") {
10641
+ const tail = String(branch || "").trim().split("/").filter(Boolean).at(-1) || "";
10642
+ if (!tail) return "";
10643
+ const subtask = tail.match(/-subtask-(.+)$/);
10644
+ return branchSafeClickUpId(subtask ? subtask[1] : tail);
10645
+ }
10646
+ async function gitHubPullRequestContextFromPayload(payload = {}, event = "", githubClient = null) {
10647
+ let pr = payload.pull_request || null;
10648
+ if (!pr && event === "issue_comment" && payload.issue?.pull_request && githubClient?.getPullRequest) {
10649
+ pr = await githubClient.getPullRequest(payload.issue.number);
10650
+ }
10651
+ if (!pr) return null;
10652
+ const headRef = String(pr.head?.ref || pr.head_ref || "").trim();
10653
+ const baseRef = String(pr.base?.ref || pr.base_ref || "").trim();
10654
+ return {
10655
+ number: pr.number || payload.issue?.number || "",
10656
+ id: pr.id || "",
10657
+ url: pr.html_url || pr.url || "",
10658
+ title: pr.title || payload.issue?.title || "",
10659
+ state: pr.state || "",
10660
+ merged: pr.merged === true,
10661
+ headRef,
10662
+ baseRef,
10663
+ taskId: clickUpTaskIdFromGitHubBranch(headRef),
10664
+ repository: pr.head?.repo?.full_name || pr.base?.repo?.full_name || payload.repository?.full_name || ""
10665
+ };
10666
+ }
10667
+ function gitHubWebhookCommentBody(payload = {}) {
10668
+ return String(payload.comment?.body || payload.review?.body || payload.pull_request?.body || "").trim();
10669
+ }
10670
+ function formatGitHubWebhookPrompt({ event = "", action = "", pr = {}, payload = {}, taskId = "" } = {}) {
10671
+ const sender = payload.sender?.login || payload.comment?.user?.login || payload.review?.user?.login || "unknown";
10672
+ const reviewState = String(payload.review?.state || "").trim();
10673
+ const commentBody = gitHubWebhookCommentBody(payload);
10674
+ const lines = [
10675
+ "[GitHub Webhook Event]",
10676
+ `Event: ${event}`,
10677
+ `Action: ${action || "unknown"}`,
10678
+ `Task ID: ${taskId || pr.taskId || "unknown"}`,
10679
+ `Pull Request: #${pr.number || "unknown"} ${pr.url || ""}`.trim(),
10680
+ `PR Branch: ${pr.headRef || "unknown"} -> ${pr.baseRef || "unknown"}`,
10681
+ `Sender: ${sender}`,
10682
+ reviewState ? `Review State: ${reviewState}` : null,
10683
+ commentBody ? ["", "Comment/Review Body:", commentBody].join("\n") : null,
10684
+ "",
10685
+ "Required handling:",
10686
+ "- Answer human GitHub comments in GitHub.",
10687
+ "- 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.",
10688
+ "- 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.",
10689
+ "- Keep Optima runtime/process failures out of ClickUp comments; use local logs for process noise."
10690
+ ].filter(Boolean);
10691
+ return lines.join("\n");
10692
+ }
10693
+ function mergeGitHubWebhookMetadata(existingMetadata, { pr = {}, event = "", action = "", delivery = "" } = {}) {
10694
+ const current = normalizeAgentMetadataJson(existingMetadata);
10695
+ const next = {
10696
+ ...current,
10697
+ task: {
10698
+ ...isPlainObject(current.task) ? current.task : {},
10699
+ github: {
10700
+ ...isPlainObject(current.task?.github) ? current.task.github : {},
10701
+ pull_request: Object.fromEntries(Object.entries({
10702
+ number: pr.number || void 0,
10703
+ url: pr.url || void 0,
10704
+ source_branch: pr.headRef || void 0,
10705
+ target_branch: pr.baseRef || void 0,
10706
+ repository: pr.repository || void 0
10707
+ }).filter(([, value]) => value !== void 0 && value !== "")),
10708
+ last_webhook: {
10709
+ event,
10710
+ action,
10711
+ delivery,
10712
+ at: (/* @__PURE__ */ new Date()).toISOString()
10713
+ }
10714
+ }
10715
+ }
10716
+ };
10717
+ return JSON.stringify(sortJsonValue(next), null, 2);
10718
+ }
10719
+ function summarizeGitHubRouteForLog(result = {}, payload = {}, event = "") {
10720
+ return {
10721
+ taskId: result.taskId || null,
10722
+ event: event || null,
10723
+ eventKey: result.eventKey || null,
10724
+ ok: result.ok === true,
10725
+ action: result.action || null,
10726
+ reason: result.reason || null,
10727
+ sessionId: result.sessionId || null,
10728
+ pullRequest: result.prNumber || payload?.pull_request?.number || payload?.issue?.number || null,
10729
+ branch: result.branch || null,
10730
+ worktree: result.worktree || null
10731
+ };
10732
+ }
10453
10733
  function getNestedMetadataValue(metadata, dottedKey) {
10454
10734
  const parts = String(dottedKey || "").split(".").filter(Boolean);
10455
10735
  let cursor = metadata;
@@ -11355,9 +11635,9 @@ function buildClickUpWebhookAuditSummary({ method, url, config, handled, error,
11355
11635
  ok: Boolean(handled?.ok),
11356
11636
  reason: handled?.reason || result.reason || (error ? "handler_error" : void 0),
11357
11637
  action: result.action,
11358
- event: payload ? clickUpEventType(payload) : void 0,
11638
+ event: result.event || (payload ? clickUpEventType(payload) : void 0),
11359
11639
  task: result.taskId || clickUpTaskIdFromPayload(payload || {}),
11360
- comment: clickUpCommentFromPayload(payload || {})?.id || payload?.comment_id || payload?.commentId,
11640
+ comment: clickUpCommentFromPayload(payload || {})?.id || payload?.comment_id || payload?.commentId || payload?.comment?.id || payload?.review?.id,
11361
11641
  session: result.sessionId,
11362
11642
  base_path: config?.basePath || "",
11363
11643
  request_file: requestFile || void 0
@@ -11371,7 +11651,13 @@ function writeClickUpWebhookAuditLog({ method, url, headers = {}, rawBody = "",
11371
11651
  if (level === "error" && !failed) return;
11372
11652
  const logDir = clickUpWebhookAuditLogDir();
11373
11653
  fs5.mkdirSync(logDir, { recursive: true });
11374
- const secretValues = [resolveSecretReference(config?.apiToken), state?.secret, config?.webhook?.secret].filter(Boolean);
11654
+ const secretValues = [
11655
+ resolveSecretReference(config?.apiToken),
11656
+ resolveSecretReference(config?.github?.apiToken),
11657
+ state?.secret,
11658
+ config?.webhook?.secret,
11659
+ resolveSecretReference(config?.github?.webhook?.secret)
11660
+ ].filter(Boolean);
11375
11661
  let requestFile;
11376
11662
  if (level === "verbose") {
11377
11663
  const absoluteRequestFile = clickUpWebhookRequestFilePath(at, logDir);
@@ -11833,6 +12119,176 @@ function runClickUpWebhookRouteInBackground({ payload, config, state, worktree,
11833
12119
  });
11834
12120
  return route;
11835
12121
  }
12122
+ async function routeGitHubWebhookEventUnlocked({ payload, event = "", delivery = "", config, state = {}, worktree = process.cwd(), clickupClient, githubClient = null, openCodeClient, sendSessionEvent = sendOpenCodeSessionEvent, verifySessionEventDelivery = verifyOpenCodeSessionEventDelivery } = {}) {
12123
+ const normalizedEvent = String(event || payload?.event || "").trim();
12124
+ const action = String(payload?.action || "").trim();
12125
+ const eventKey = gitHubWebhookEventKey({ headers: { "x-github-delivery": delivery, "x-github-event": normalizedEvent }, event: normalizedEvent, payload });
12126
+ if (!config?.github?.webhook?.events?.includes(normalizedEvent)) {
12127
+ return { ok: true, action: "ignored", reason: "unsupported_event", event: normalizedEvent, eventKey };
12128
+ }
12129
+ let pr;
12130
+ try {
12131
+ pr = await gitHubPullRequestContextFromPayload(payload, normalizedEvent, githubClient);
12132
+ } catch (error) {
12133
+ appendClickUpWebhookLocalLog(worktree, { type: "github_pr_context_failed", event: normalizedEvent, delivery, message: error.message });
12134
+ return { ok: false, action: "error", reason: "pull_request_context_failed", event: normalizedEvent, eventKey, message: error.message };
12135
+ }
12136
+ if (!pr) {
12137
+ appendClickUpWebhookLocalLog(worktree, { type: "github_pr_context_unavailable", event: normalizedEvent, delivery, action });
12138
+ return { ok: true, action: "ignored", reason: "pull_request_context_unavailable", event: normalizedEvent, eventKey };
12139
+ }
12140
+ const taskId = String(pr.taskId || "").trim();
12141
+ if (!taskId) {
12142
+ appendClickUpWebhookLocalLog(worktree, { type: "github_task_id_unavailable", event: normalizedEvent, delivery, prNumber: pr.number || null, headRef: pr.headRef || null });
12143
+ return { ok: false, action: "error", reason: "task_id_unavailable", event: normalizedEvent, eventKey, prNumber: pr.number || null };
12144
+ }
12145
+ const task = payload.task || (clickupClient?.getTask ? await clickupClient.getTask(taskId) : null);
12146
+ if (!task) {
12147
+ appendClickUpWebhookLocalLog(worktree, { type: "github_clickup_task_unavailable", taskId, event: normalizedEvent, delivery, prNumber: pr.number || null });
12148
+ return { ok: false, action: "error", reason: "task_unavailable", taskId, event: normalizedEvent, eventKey, prNumber: pr.number || null };
12149
+ }
12150
+ if (isClickUpTaskTerminal(task, {}, config.routing?.ignoredStatuses)) {
12151
+ return { ok: true, action: "ignored", reason: "terminal_status", taskId, event: normalizedEvent, eventKey, prNumber: pr.number || null };
12152
+ }
12153
+ const existingMetadata = clickUpTaskAgentMetadata(task, config.routing.metadataFieldId);
12154
+ const metadata = normalizeAgentMetadataJson(existingMetadata);
12155
+ const rawSessionId = getNestedMetadataValue(metadata, config.routing.metadataKey) || getNestedMetadataValue(metadata, "optima.sessions.product_manager");
12156
+ const sessionId = typeof rawSessionId === "string" && rawSessionId.startsWith("ses_") ? rawSessionId : "";
12157
+ const directory = String(
12158
+ getNestedMetadataValue(metadata, "task.worktree") || getNestedMetadataValue(metadata, "task.subtask_worktree") || ""
12159
+ ).trim();
12160
+ const branch = String(
12161
+ getNestedMetadataValue(metadata, "task.branch") || getNestedMetadataValue(metadata, "task.subtask_branch") || pr.headRef || ""
12162
+ ).trim();
12163
+ if (!sessionId) {
12164
+ appendClickUpWebhookLocalLog(worktree, { type: "github_session_missing", taskId, event: normalizedEvent, delivery, prNumber: pr.number || null, branch: branch || null });
12165
+ return { ok: false, action: "error", reason: "session_missing", taskId, event: normalizedEvent, eventKey, prNumber: pr.number || null, branch: branch || null };
12166
+ }
12167
+ if (!directory) {
12168
+ appendClickUpWebhookLocalLog(worktree, { type: "github_worktree_missing", taskId, event: normalizedEvent, delivery, sessionId, prNumber: pr.number || null });
12169
+ return { ok: false, action: "error", reason: "worktree_missing", taskId, event: normalizedEvent, eventKey, sessionId, prNumber: pr.number || null, branch: branch || null };
12170
+ }
12171
+ const prompt = formatGitHubWebhookPrompt({ event: normalizedEvent, action, pr, payload, taskId });
12172
+ const nextMetadata = mergeGitHubWebhookMetadata(existingMetadata, { pr, event: normalizedEvent, action, delivery });
12173
+ if (typeof clickupClient?.updateTaskMetadata === "function") {
12174
+ await clickupClient.updateTaskMetadata({ taskId, fieldId: config.routing.metadataFieldId, value: nextMetadata });
12175
+ }
12176
+ const deliveryResult = await deliverClickUpSessionEventWithVerification({
12177
+ openCodeClient,
12178
+ sendSessionEvent,
12179
+ clickupClient,
12180
+ worktree,
12181
+ taskId,
12182
+ sessionId,
12183
+ agent: config.routing.targetAgent,
12184
+ text: prompt,
12185
+ directory,
12186
+ opencodeBaseUrl: config.opencode?.baseUrl,
12187
+ directPrompt: config.opencode?.promptDelivery === "http",
12188
+ directDelivery: "steer",
12189
+ acceptPromptAdmission: config.opencode?.acceptPromptAdmission === true,
12190
+ eventMarkers: [taskId, String(pr.number || ""), normalizedEvent].filter(Boolean),
12191
+ verifySessionEventDelivery,
12192
+ applyBlockerOnFailure: false
12193
+ });
12194
+ if (!deliveryResult.ok) {
12195
+ appendClickUpWebhookLocalLog(worktree, { type: "github_message_delivery_failed", taskId, event: normalizedEvent, delivery, sessionId, prNumber: pr.number || null, reason: deliveryResult.reason || "message_delivery_failed" });
12196
+ return { ...deliveryResult, eventKey, taskId, event: normalizedEvent, prNumber: pr.number || null, branch: branch || pr.headRef || null, worktree: directory };
12197
+ }
12198
+ return {
12199
+ ok: true,
12200
+ action: "github_event_delivered",
12201
+ taskId,
12202
+ sessionId,
12203
+ event: normalizedEvent,
12204
+ eventKey,
12205
+ prNumber: pr.number || null,
12206
+ branch: branch || pr.headRef || null,
12207
+ worktree: directory,
12208
+ deliveryVerification: deliveryResult.verification,
12209
+ deliveryAdmission: deliveryResult.admissionVerification,
12210
+ deliveryFallback: deliveryResult.fallback
12211
+ };
12212
+ }
12213
+ async function routeGitHubWebhookEvent(options = {}) {
12214
+ let taskId = "";
12215
+ try {
12216
+ const pr = await gitHubPullRequestContextFromPayload(options.payload || {}, options.event || "", options.githubClient || null);
12217
+ taskId = pr?.taskId || "";
12218
+ } catch {
12219
+ taskId = "";
12220
+ }
12221
+ return withClickUpTaskRouteLock(taskId, () => routeGitHubWebhookEventUnlocked(options));
12222
+ }
12223
+ function runGitHubWebhookRouteInBackground({ payload, event, delivery, config, state, worktree, clickupClient, githubClient, openCodeClient, sendSessionEvent, verifySessionEventDelivery } = {}) {
12224
+ const eventKey = gitHubWebhookEventKey({ headers: { "x-github-delivery": delivery, "x-github-event": event }, event, payload });
12225
+ appendClickUpWebhookLocalLog(worktree, { type: "github_webhook_route_started", event, eventKey, async: true, delivery: delivery || null });
12226
+ const route = Promise.resolve().then(() => routeGitHubWebhookEvent({ payload, event, delivery, config, state, worktree, clickupClient, githubClient, openCodeClient, sendSessionEvent, verifySessionEventDelivery })).then((result) => {
12227
+ appendClickUpWebhookLocalLog(worktree, { type: "github_webhook_route_finished", async: true, ...summarizeGitHubRouteForLog(result, payload, event) });
12228
+ return result;
12229
+ }).catch((error) => {
12230
+ appendClickUpWebhookLocalLog(worktree, { type: "github_webhook_route_failed", event, eventKey, async: true, delivery: delivery || null, message: error.message });
12231
+ return { ok: false, action: "error", reason: "background_route_failed", message: error.message };
12232
+ });
12233
+ route.catch(() => {
12234
+ });
12235
+ return route;
12236
+ }
12237
+ 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 } = {}) {
12238
+ let payload = null;
12239
+ let handled = null;
12240
+ let authenticatedWebhook = false;
12241
+ let receivedAt = null;
12242
+ let latestPersistedState = state;
12243
+ const persistState = (nextState) => {
12244
+ latestPersistedState = nextState;
12245
+ if (saveState) saveState(nextState);
12246
+ };
12247
+ const finish = (result) => {
12248
+ handled = result;
12249
+ receivedAt ??= now();
12250
+ const auditState = authenticatedWebhook ? { ...latestPersistedState, githubLastWebhookAt: receivedAt.toISOString() } : latestPersistedState;
12251
+ if (saveState && authenticatedWebhook) persistState(auditState);
12252
+ writeClickUpWebhookAuditLog({ method, url, headers, rawBody, config, state: auditState, handled, payload, at: receivedAt });
12253
+ return result;
12254
+ };
12255
+ try {
12256
+ if (method !== "POST") return finish({ ok: false, status: 405, reason: "method_not_allowed" });
12257
+ if (url !== null) {
12258
+ const requestPath = new URL(String(url), config?.github?.webhook?.publicUrl || config?.webhook?.publicUrl || "http://localhost").pathname;
12259
+ if (requestPath !== gitHubWebhookExpectedPath(config)) return finish({ ok: false, status: 404, reason: "wrong_path" });
12260
+ }
12261
+ if (!gitHubWebhookEnabled(config)) return finish({ ok: false, status: 404, reason: "github_webhook_disabled" });
12262
+ const maxBodyBytes = Number(config?.webhook?.maxBodyBytes || CLICKUP_WEBHOOK_MAX_BODY_BYTES);
12263
+ const bodyBytes = Buffer.isBuffer(rawBody) ? rawBody.length : Buffer.byteLength(String(rawBody || ""));
12264
+ if (bodyBytes > maxBodyBytes) return finish({ ok: false, status: 413, reason: "body_too_large" });
12265
+ const signature = headers["x-hub-signature-256"] || headers["X-Hub-Signature-256"];
12266
+ const secret = resolveSecretReference(config.github.webhook.secret);
12267
+ if (!verifyGitHubSignature(rawBody, signature, secret)) return finish({ ok: false, status: 401, reason: "invalid_signature" });
12268
+ authenticatedWebhook = true;
12269
+ receivedAt = now();
12270
+ try {
12271
+ payload = JSON.parse(Buffer.isBuffer(rawBody) ? rawBody.toString("utf8") : String(rawBody));
12272
+ } catch {
12273
+ return finish({ ok: false, status: 400, reason: "invalid_json" });
12274
+ }
12275
+ const event = String(headers["x-github-event"] || headers["X-GitHub-Event"] || "").trim();
12276
+ const delivery = String(headers["x-github-delivery"] || headers["X-GitHub-Delivery"] || "").trim();
12277
+ const eventKey = gitHubWebhookEventKey({ headers, event, payload });
12278
+ const remembered = rememberGitHubWebhookEvent({ ...state, githubLastWebhookAt: receivedAt.toISOString() }, eventKey);
12279
+ persistState(remembered.state);
12280
+ if (remembered.duplicate) return finish({ ok: true, status: 200, reason: "duplicate_event", result: { action: "ignored", reason: "duplicate", event, eventKey } });
12281
+ if (asyncRouting) {
12282
+ runGitHubWebhookRouteInBackground({ payload, event, delivery, config, state: remembered.state, worktree, clickupClient, githubClient, openCodeClient, sendSessionEvent, verifySessionEventDelivery });
12283
+ return finish({ ok: true, status: 200, reason: "accepted", result: { action: "accepted", event, eventKey } });
12284
+ }
12285
+ const result = await routeGitHubWebhookEvent({ payload, event, delivery, config, state: remembered.state, worktree, clickupClient, githubClient, openCodeClient, sendSessionEvent, verifySessionEventDelivery });
12286
+ return finish({ ok: result.ok, status: result.ok ? 200 : 422, result });
12287
+ } catch (error) {
12288
+ writeClickUpWebhookAuditLog({ method, url, headers, rawBody, config, state, handled, error, payload, at: now() });
12289
+ throw error;
12290
+ }
12291
+ }
11836
12292
  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 } = {}) {
11837
12293
  let payload = null;
11838
12294
  let handled = null;
@@ -11895,13 +12351,17 @@ function clickUpListenerFingerprint({ config, state, worktree, clickupClient, op
11895
12351
  teamId: config?.teamId || "",
11896
12352
  publicUrl: config?.webhook?.publicUrl || "",
11897
12353
  path: clickUpWebhookExpectedPath(config),
12354
+ githubPublicUrl: config?.github?.webhook?.publicUrl || "",
12355
+ githubPath: gitHubWebhookExpectedPath(config),
12356
+ githubEnabled: gitHubWebhookEnabled(config),
12357
+ githubEvents: config?.github?.webhook?.events || [],
11898
12358
  location: config?.webhook?.location || {},
11899
12359
  webhookId: state?.webhookId || "",
11900
12360
  secret: state?.secret || "",
11901
12361
  events: config?.webhook?.events || []
11902
12362
  });
11903
12363
  }
11904
- function startClickUpWebhookListener({ config, state, worktree, clickupClient, openCodeClient, listenerRegistry = activeClickUpWebhookListeners } = {}) {
12364
+ function startClickUpWebhookListener({ config, state, worktree, clickupClient, githubClient = null, openCodeClient, listenerRegistry = activeClickUpWebhookListeners } = {}) {
11905
12365
  if (!isClickUpWebhookStateActive(state, config)) return { active: false, ready: false, reason: "inactive_state" };
11906
12366
  const key = clickUpListenerKey(config);
11907
12367
  const fingerprint = clickUpListenerFingerprint({ config, state, worktree, clickupClient, openCodeClient });
@@ -11920,7 +12380,9 @@ function startClickUpWebhookListener({ config, state, worktree, clickupClient, o
11920
12380
  return;
11921
12381
  }
11922
12382
  const requestPath = new URL(req.url || "/", config.webhook.publicUrl).pathname;
11923
- if (requestPath !== clickUpWebhookExpectedPath(config)) {
12383
+ const isClickUpPath = requestPath === clickUpWebhookExpectedPath(config);
12384
+ const isGitHubPath = gitHubWebhookEnabled(config) && requestPath === gitHubWebhookExpectedPath(config);
12385
+ if (!isClickUpPath && !isGitHubPath) {
11924
12386
  res.writeHead(404, { "Content-Type": "application/json" });
11925
12387
  res.end(JSON.stringify({ ok: false, reason: "wrong_path" }));
11926
12388
  return;
@@ -11947,7 +12409,7 @@ function startClickUpWebhookListener({ config, state, worktree, clickupClient, o
11947
12409
  if (overflow || res.writableEnded) return;
11948
12410
  const latestState = readClickUpWebhookState(worktree, config);
11949
12411
  try {
11950
- const handled = await handleClickUpWebhookRequest({
12412
+ const common = {
11951
12413
  method: req.method,
11952
12414
  url: req.url,
11953
12415
  headers: req.headers,
@@ -11959,7 +12421,8 @@ function startClickUpWebhookListener({ config, state, worktree, clickupClient, o
11959
12421
  openCodeClient,
11960
12422
  saveState: (nextState) => writeClickUpWebhookState(worktree, nextState, config),
11961
12423
  asyncRouting: true
11962
- });
12424
+ };
12425
+ const handled = isGitHubPath ? await handleGitHubWebhookRequest({ ...common, githubClient }) : await handleClickUpWebhookRequest(common);
11963
12426
  res.writeHead(handled.status, { "Content-Type": "application/json" });
11964
12427
  res.end(JSON.stringify({ ok: handled.ok, reason: handled.reason, result: handled.result?.action }));
11965
12428
  } catch (error) {
@@ -13077,6 +13540,7 @@ async function OptimaPlugin(input = {}, pluginOptions = {}) {
13077
13540
  const operatingTeamMode = getOperatingTeamMode(repoCfg);
13078
13541
  const clickUpWebhookValidation = normalizeClickUpWebhookConfig(pickConfiguredClickUp(input, repoCfg, resolvedPluginOptions), worktree);
13079
13542
  const runtimeClickUpClient = input.clickupClient || (clickUpWebhookValidation.config?.test === true ? createTestClickUpApiClient(clickUpWebhookValidation.config) : createClickUpApiClient(clickUpWebhookValidation.config, input.fetch));
13543
+ const runtimeGitHubClient = input.githubClient || (clickUpWebhookValidation.config?.github?.webhook?.enabled === true ? createGitHubApiClient(clickUpWebhookValidation.config.github, input.fetch) : null);
13080
13544
  let clickUpWebhookRuntime = { active: false, valid: false, reason: "not_configured" };
13081
13545
  if (clickUpWebhookValidation.complete) {
13082
13546
  try {
@@ -13101,6 +13565,7 @@ async function OptimaPlugin(input = {}, pluginOptions = {}) {
13101
13565
  state: listenerState,
13102
13566
  worktree,
13103
13567
  clickupClient: lifecycleClickUpClient,
13568
+ githubClient: runtimeGitHubClient,
13104
13569
  openCodeClient: input.client,
13105
13570
  listenerRegistry
13106
13571
  });
@@ -13776,7 +14241,7 @@ Follow-up: use optima_prompt_workflow with session_id '${sessionId}' to check in
13776
14241
  }
13777
14242
  };
13778
14243
  }
13779
- 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 };
14244
+ 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 };
13780
14245
  export {
13781
14246
  OptimaPlugin as default
13782
14247
  };