@defend-tech/opencode-optima 0.1.72 → 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;
|
|
@@ -9880,9 +9881,48 @@ function normalizeNonNegativeInteger(value, defaultValue) {
|
|
|
9880
9881
|
const number = Number(value);
|
|
9881
9882
|
return Number.isInteger(number) && number >= 0 ? number : defaultValue;
|
|
9882
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
|
+
}
|
|
9883
9922
|
function normalizeClickUpWebhookConfig(rawClickUp = null, worktree = process.cwd()) {
|
|
9884
9923
|
const raw = isPlainObject(rawClickUp) ? rawClickUp : {};
|
|
9885
9924
|
const webhook = isPlainObject(raw.webhook) ? raw.webhook : {};
|
|
9925
|
+
const github = normalizeGitHubWebhookConfig(raw.github, { publicUrl: String(webhook.public_url || webhook.publicUrl || "").trim() });
|
|
9886
9926
|
const routing = isPlainObject(raw.routing) ? raw.routing : {};
|
|
9887
9927
|
const opencode = isPlainObject(raw.opencode) ? raw.opencode : {};
|
|
9888
9928
|
const openchamber = isPlainObject(raw.openchamber) ? raw.openchamber : {};
|
|
@@ -9935,6 +9975,7 @@ function normalizeClickUpWebhookConfig(rawClickUp = null, worktree = process.cwd
|
|
|
9935
9975
|
},
|
|
9936
9976
|
events
|
|
9937
9977
|
},
|
|
9978
|
+
github,
|
|
9938
9979
|
routing: {
|
|
9939
9980
|
targetAgent: String(routing.target_agent || routing.targetAgent || "workflow_product_manager").trim(),
|
|
9940
9981
|
productManagerAssigneeId: String(routing.product_manager_assignee_id || routing.productManagerAssigneeId || "").trim(),
|
|
@@ -9956,6 +9997,13 @@ function normalizeClickUpWebhookConfig(rawClickUp = null, worktree = process.cwd
|
|
|
9956
9997
|
if (!config.webhook.bindHost) errors.push("clickup.webhook.bind_host is required");
|
|
9957
9998
|
if (!Number.isInteger(config.webhook.bindPort) || config.webhook.bindPort <= 0) errors.push("clickup.webhook.bind_port must be a positive integer");
|
|
9958
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
|
+
}
|
|
9959
10007
|
if (config.opencode.baseUrl) {
|
|
9960
10008
|
try {
|
|
9961
10009
|
new URL(config.opencode.baseUrl);
|
|
@@ -9993,6 +10041,8 @@ function sanitizeClickUpWebhookState(state = {}, config = null) {
|
|
|
9993
10041
|
listener: isPlainObject(state.listener) ? state.listener : {},
|
|
9994
10042
|
pendingSessions: isPlainObject(state.pendingSessions) ? state.pendingSessions : {},
|
|
9995
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) : [],
|
|
9996
10046
|
recentEventKeys
|
|
9997
10047
|
};
|
|
9998
10048
|
}
|
|
@@ -10109,6 +10159,29 @@ function createClickUpApiClient(config, fetchImpl = globalThis.fetch) {
|
|
|
10109
10159
|
}
|
|
10110
10160
|
};
|
|
10111
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
|
+
}
|
|
10112
10185
|
function createTestClickUpApiClient(config) {
|
|
10113
10186
|
const metadata = /* @__PURE__ */ new Map();
|
|
10114
10187
|
return {
|
|
@@ -10339,6 +10412,19 @@ function verifyClickUpSignature(rawBody, signatureHeader, secret) {
|
|
|
10339
10412
|
const wanted = Buffer.from(expected, "hex");
|
|
10340
10413
|
return given.length === wanted.length && crypto.timingSafeEqual(given, wanted);
|
|
10341
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
|
+
}
|
|
10342
10428
|
async function resyncClickUpWebhookForSignatureDrift({ rawBody, signature, config, state = {}, worktree = process.cwd(), clickupClient, saveState } = {}) {
|
|
10343
10429
|
const validation = clickUpWebhookValidationFromConfig(config);
|
|
10344
10430
|
if (!clickupClient?.listWebhooks && !clickupClient?.createWebhook) {
|
|
@@ -10545,6 +10631,105 @@ function clickUpTaskAgentMetadata(task = {}, fieldId = "") {
|
|
|
10545
10631
|
const field = fields.find((item) => String(item?.id || "") === fieldId || String(item?.name || "") === "agent_metadata");
|
|
10546
10632
|
return field?.value || "";
|
|
10547
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
|
+
}
|
|
10548
10733
|
function getNestedMetadataValue(metadata, dottedKey) {
|
|
10549
10734
|
const parts = String(dottedKey || "").split(".").filter(Boolean);
|
|
10550
10735
|
let cursor = metadata;
|
|
@@ -11450,9 +11635,9 @@ function buildClickUpWebhookAuditSummary({ method, url, config, handled, error,
|
|
|
11450
11635
|
ok: Boolean(handled?.ok),
|
|
11451
11636
|
reason: handled?.reason || result.reason || (error ? "handler_error" : void 0),
|
|
11452
11637
|
action: result.action,
|
|
11453
|
-
event: payload ? clickUpEventType(payload) : void 0,
|
|
11638
|
+
event: result.event || (payload ? clickUpEventType(payload) : void 0),
|
|
11454
11639
|
task: result.taskId || clickUpTaskIdFromPayload(payload || {}),
|
|
11455
|
-
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,
|
|
11456
11641
|
session: result.sessionId,
|
|
11457
11642
|
base_path: config?.basePath || "",
|
|
11458
11643
|
request_file: requestFile || void 0
|
|
@@ -11466,7 +11651,13 @@ function writeClickUpWebhookAuditLog({ method, url, headers = {}, rawBody = "",
|
|
|
11466
11651
|
if (level === "error" && !failed) return;
|
|
11467
11652
|
const logDir = clickUpWebhookAuditLogDir();
|
|
11468
11653
|
fs5.mkdirSync(logDir, { recursive: true });
|
|
11469
|
-
const secretValues = [
|
|
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);
|
|
11470
11661
|
let requestFile;
|
|
11471
11662
|
if (level === "verbose") {
|
|
11472
11663
|
const absoluteRequestFile = clickUpWebhookRequestFilePath(at, logDir);
|
|
@@ -11928,6 +12119,176 @@ function runClickUpWebhookRouteInBackground({ payload, config, state, worktree,
|
|
|
11928
12119
|
});
|
|
11929
12120
|
return route;
|
|
11930
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
|
+
}
|
|
11931
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 } = {}) {
|
|
11932
12293
|
let payload = null;
|
|
11933
12294
|
let handled = null;
|
|
@@ -11990,13 +12351,17 @@ function clickUpListenerFingerprint({ config, state, worktree, clickupClient, op
|
|
|
11990
12351
|
teamId: config?.teamId || "",
|
|
11991
12352
|
publicUrl: config?.webhook?.publicUrl || "",
|
|
11992
12353
|
path: clickUpWebhookExpectedPath(config),
|
|
12354
|
+
githubPublicUrl: config?.github?.webhook?.publicUrl || "",
|
|
12355
|
+
githubPath: gitHubWebhookExpectedPath(config),
|
|
12356
|
+
githubEnabled: gitHubWebhookEnabled(config),
|
|
12357
|
+
githubEvents: config?.github?.webhook?.events || [],
|
|
11993
12358
|
location: config?.webhook?.location || {},
|
|
11994
12359
|
webhookId: state?.webhookId || "",
|
|
11995
12360
|
secret: state?.secret || "",
|
|
11996
12361
|
events: config?.webhook?.events || []
|
|
11997
12362
|
});
|
|
11998
12363
|
}
|
|
11999
|
-
function startClickUpWebhookListener({ config, state, worktree, clickupClient, openCodeClient, listenerRegistry = activeClickUpWebhookListeners } = {}) {
|
|
12364
|
+
function startClickUpWebhookListener({ config, state, worktree, clickupClient, githubClient = null, openCodeClient, listenerRegistry = activeClickUpWebhookListeners } = {}) {
|
|
12000
12365
|
if (!isClickUpWebhookStateActive(state, config)) return { active: false, ready: false, reason: "inactive_state" };
|
|
12001
12366
|
const key = clickUpListenerKey(config);
|
|
12002
12367
|
const fingerprint = clickUpListenerFingerprint({ config, state, worktree, clickupClient, openCodeClient });
|
|
@@ -12015,7 +12380,9 @@ function startClickUpWebhookListener({ config, state, worktree, clickupClient, o
|
|
|
12015
12380
|
return;
|
|
12016
12381
|
}
|
|
12017
12382
|
const requestPath = new URL(req.url || "/", config.webhook.publicUrl).pathname;
|
|
12018
|
-
|
|
12383
|
+
const isClickUpPath = requestPath === clickUpWebhookExpectedPath(config);
|
|
12384
|
+
const isGitHubPath = gitHubWebhookEnabled(config) && requestPath === gitHubWebhookExpectedPath(config);
|
|
12385
|
+
if (!isClickUpPath && !isGitHubPath) {
|
|
12019
12386
|
res.writeHead(404, { "Content-Type": "application/json" });
|
|
12020
12387
|
res.end(JSON.stringify({ ok: false, reason: "wrong_path" }));
|
|
12021
12388
|
return;
|
|
@@ -12042,7 +12409,7 @@ function startClickUpWebhookListener({ config, state, worktree, clickupClient, o
|
|
|
12042
12409
|
if (overflow || res.writableEnded) return;
|
|
12043
12410
|
const latestState = readClickUpWebhookState(worktree, config);
|
|
12044
12411
|
try {
|
|
12045
|
-
const
|
|
12412
|
+
const common = {
|
|
12046
12413
|
method: req.method,
|
|
12047
12414
|
url: req.url,
|
|
12048
12415
|
headers: req.headers,
|
|
@@ -12054,7 +12421,8 @@ function startClickUpWebhookListener({ config, state, worktree, clickupClient, o
|
|
|
12054
12421
|
openCodeClient,
|
|
12055
12422
|
saveState: (nextState) => writeClickUpWebhookState(worktree, nextState, config),
|
|
12056
12423
|
asyncRouting: true
|
|
12057
|
-
}
|
|
12424
|
+
};
|
|
12425
|
+
const handled = isGitHubPath ? await handleGitHubWebhookRequest({ ...common, githubClient }) : await handleClickUpWebhookRequest(common);
|
|
12058
12426
|
res.writeHead(handled.status, { "Content-Type": "application/json" });
|
|
12059
12427
|
res.end(JSON.stringify({ ok: handled.ok, reason: handled.reason, result: handled.result?.action }));
|
|
12060
12428
|
} catch (error) {
|
|
@@ -13172,6 +13540,7 @@ async function OptimaPlugin(input = {}, pluginOptions = {}) {
|
|
|
13172
13540
|
const operatingTeamMode = getOperatingTeamMode(repoCfg);
|
|
13173
13541
|
const clickUpWebhookValidation = normalizeClickUpWebhookConfig(pickConfiguredClickUp(input, repoCfg, resolvedPluginOptions), worktree);
|
|
13174
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);
|
|
13175
13544
|
let clickUpWebhookRuntime = { active: false, valid: false, reason: "not_configured" };
|
|
13176
13545
|
if (clickUpWebhookValidation.complete) {
|
|
13177
13546
|
try {
|
|
@@ -13196,6 +13565,7 @@ async function OptimaPlugin(input = {}, pluginOptions = {}) {
|
|
|
13196
13565
|
state: listenerState,
|
|
13197
13566
|
worktree,
|
|
13198
13567
|
clickupClient: lifecycleClickUpClient,
|
|
13568
|
+
githubClient: runtimeGitHubClient,
|
|
13199
13569
|
openCodeClient: input.client,
|
|
13200
13570
|
listenerRegistry
|
|
13201
13571
|
});
|
|
@@ -13871,7 +14241,7 @@ Follow-up: use optima_prompt_workflow with session_id '${sessionId}' to check in
|
|
|
13871
14241
|
}
|
|
13872
14242
|
};
|
|
13873
14243
|
}
|
|
13874
|
-
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, 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, 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 };
|
|
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 };
|
|
13875
14245
|
export {
|
|
13876
14246
|
OptimaPlugin as default
|
|
13877
14247
|
};
|
package/dist/sanitize_cli.js
CHANGED
|
@@ -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;
|
|
@@ -9887,9 +9888,48 @@ function normalizeNonNegativeInteger(value, defaultValue) {
|
|
|
9887
9888
|
const number = Number(value);
|
|
9888
9889
|
return Number.isInteger(number) && number >= 0 ? number : defaultValue;
|
|
9889
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
|
+
}
|
|
9890
9929
|
function normalizeClickUpWebhookConfig(rawClickUp = null, worktree = process.cwd()) {
|
|
9891
9930
|
const raw = isPlainObject(rawClickUp) ? rawClickUp : {};
|
|
9892
9931
|
const webhook = isPlainObject(raw.webhook) ? raw.webhook : {};
|
|
9932
|
+
const github = normalizeGitHubWebhookConfig(raw.github, { publicUrl: String(webhook.public_url || webhook.publicUrl || "").trim() });
|
|
9893
9933
|
const routing = isPlainObject(raw.routing) ? raw.routing : {};
|
|
9894
9934
|
const opencode = isPlainObject(raw.opencode) ? raw.opencode : {};
|
|
9895
9935
|
const openchamber = isPlainObject(raw.openchamber) ? raw.openchamber : {};
|
|
@@ -9942,6 +9982,7 @@ function normalizeClickUpWebhookConfig(rawClickUp = null, worktree = process.cwd
|
|
|
9942
9982
|
},
|
|
9943
9983
|
events
|
|
9944
9984
|
},
|
|
9985
|
+
github,
|
|
9945
9986
|
routing: {
|
|
9946
9987
|
targetAgent: String(routing.target_agent || routing.targetAgent || "workflow_product_manager").trim(),
|
|
9947
9988
|
productManagerAssigneeId: String(routing.product_manager_assignee_id || routing.productManagerAssigneeId || "").trim(),
|
|
@@ -9963,6 +10004,13 @@ function normalizeClickUpWebhookConfig(rawClickUp = null, worktree = process.cwd
|
|
|
9963
10004
|
if (!config.webhook.bindHost) errors.push("clickup.webhook.bind_host is required");
|
|
9964
10005
|
if (!Number.isInteger(config.webhook.bindPort) || config.webhook.bindPort <= 0) errors.push("clickup.webhook.bind_port must be a positive integer");
|
|
9965
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
|
+
}
|
|
9966
10014
|
if (config.opencode.baseUrl) {
|
|
9967
10015
|
try {
|
|
9968
10016
|
new URL(config.opencode.baseUrl);
|
|
@@ -10000,6 +10048,8 @@ function sanitizeClickUpWebhookState(state = {}, config = null) {
|
|
|
10000
10048
|
listener: isPlainObject(state.listener) ? state.listener : {},
|
|
10001
10049
|
pendingSessions: isPlainObject(state.pendingSessions) ? state.pendingSessions : {},
|
|
10002
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) : [],
|
|
10003
10053
|
recentEventKeys
|
|
10004
10054
|
};
|
|
10005
10055
|
}
|
|
@@ -10116,6 +10166,29 @@ function createClickUpApiClient(config, fetchImpl = globalThis.fetch) {
|
|
|
10116
10166
|
}
|
|
10117
10167
|
};
|
|
10118
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
|
+
}
|
|
10119
10192
|
function createTestClickUpApiClient(config) {
|
|
10120
10193
|
const metadata = /* @__PURE__ */ new Map();
|
|
10121
10194
|
return {
|
|
@@ -10346,6 +10419,19 @@ function verifyClickUpSignature(rawBody, signatureHeader, secret) {
|
|
|
10346
10419
|
const wanted = Buffer.from(expected, "hex");
|
|
10347
10420
|
return given.length === wanted.length && crypto.timingSafeEqual(given, wanted);
|
|
10348
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
|
+
}
|
|
10349
10435
|
async function resyncClickUpWebhookForSignatureDrift({ rawBody, signature, config, state = {}, worktree = process.cwd(), clickupClient, saveState } = {}) {
|
|
10350
10436
|
const validation = clickUpWebhookValidationFromConfig(config);
|
|
10351
10437
|
if (!clickupClient?.listWebhooks && !clickupClient?.createWebhook) {
|
|
@@ -10552,6 +10638,105 @@ function clickUpTaskAgentMetadata(task = {}, fieldId = "") {
|
|
|
10552
10638
|
const field = fields.find((item) => String(item?.id || "") === fieldId || String(item?.name || "") === "agent_metadata");
|
|
10553
10639
|
return field?.value || "";
|
|
10554
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
|
+
}
|
|
10555
10740
|
function getNestedMetadataValue(metadata, dottedKey) {
|
|
10556
10741
|
const parts = String(dottedKey || "").split(".").filter(Boolean);
|
|
10557
10742
|
let cursor = metadata;
|
|
@@ -11457,9 +11642,9 @@ function buildClickUpWebhookAuditSummary({ method, url, config, handled, error,
|
|
|
11457
11642
|
ok: Boolean(handled?.ok),
|
|
11458
11643
|
reason: handled?.reason || result.reason || (error ? "handler_error" : void 0),
|
|
11459
11644
|
action: result.action,
|
|
11460
|
-
event: payload ? clickUpEventType(payload) : void 0,
|
|
11645
|
+
event: result.event || (payload ? clickUpEventType(payload) : void 0),
|
|
11461
11646
|
task: result.taskId || clickUpTaskIdFromPayload(payload || {}),
|
|
11462
|
-
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,
|
|
11463
11648
|
session: result.sessionId,
|
|
11464
11649
|
base_path: config?.basePath || "",
|
|
11465
11650
|
request_file: requestFile || void 0
|
|
@@ -11473,7 +11658,13 @@ function writeClickUpWebhookAuditLog({ method, url, headers = {}, rawBody = "",
|
|
|
11473
11658
|
if (level === "error" && !failed) return;
|
|
11474
11659
|
const logDir = clickUpWebhookAuditLogDir();
|
|
11475
11660
|
fs5.mkdirSync(logDir, { recursive: true });
|
|
11476
|
-
const secretValues = [
|
|
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);
|
|
11477
11668
|
let requestFile;
|
|
11478
11669
|
if (level === "verbose") {
|
|
11479
11670
|
const absoluteRequestFile = clickUpWebhookRequestFilePath(at, logDir);
|
|
@@ -11935,6 +12126,176 @@ function runClickUpWebhookRouteInBackground({ payload, config, state, worktree,
|
|
|
11935
12126
|
});
|
|
11936
12127
|
return route;
|
|
11937
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
|
+
}
|
|
11938
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 } = {}) {
|
|
11939
12300
|
let payload = null;
|
|
11940
12301
|
let handled = null;
|
|
@@ -11997,13 +12358,17 @@ function clickUpListenerFingerprint({ config, state, worktree, clickupClient, op
|
|
|
11997
12358
|
teamId: config?.teamId || "",
|
|
11998
12359
|
publicUrl: config?.webhook?.publicUrl || "",
|
|
11999
12360
|
path: clickUpWebhookExpectedPath(config),
|
|
12361
|
+
githubPublicUrl: config?.github?.webhook?.publicUrl || "",
|
|
12362
|
+
githubPath: gitHubWebhookExpectedPath(config),
|
|
12363
|
+
githubEnabled: gitHubWebhookEnabled(config),
|
|
12364
|
+
githubEvents: config?.github?.webhook?.events || [],
|
|
12000
12365
|
location: config?.webhook?.location || {},
|
|
12001
12366
|
webhookId: state?.webhookId || "",
|
|
12002
12367
|
secret: state?.secret || "",
|
|
12003
12368
|
events: config?.webhook?.events || []
|
|
12004
12369
|
});
|
|
12005
12370
|
}
|
|
12006
|
-
function startClickUpWebhookListener({ config, state, worktree, clickupClient, openCodeClient, listenerRegistry = activeClickUpWebhookListeners } = {}) {
|
|
12371
|
+
function startClickUpWebhookListener({ config, state, worktree, clickupClient, githubClient = null, openCodeClient, listenerRegistry = activeClickUpWebhookListeners } = {}) {
|
|
12007
12372
|
if (!isClickUpWebhookStateActive(state, config)) return { active: false, ready: false, reason: "inactive_state" };
|
|
12008
12373
|
const key = clickUpListenerKey(config);
|
|
12009
12374
|
const fingerprint = clickUpListenerFingerprint({ config, state, worktree, clickupClient, openCodeClient });
|
|
@@ -12022,7 +12387,9 @@ function startClickUpWebhookListener({ config, state, worktree, clickupClient, o
|
|
|
12022
12387
|
return;
|
|
12023
12388
|
}
|
|
12024
12389
|
const requestPath = new URL(req.url || "/", config.webhook.publicUrl).pathname;
|
|
12025
|
-
|
|
12390
|
+
const isClickUpPath = requestPath === clickUpWebhookExpectedPath(config);
|
|
12391
|
+
const isGitHubPath = gitHubWebhookEnabled(config) && requestPath === gitHubWebhookExpectedPath(config);
|
|
12392
|
+
if (!isClickUpPath && !isGitHubPath) {
|
|
12026
12393
|
res.writeHead(404, { "Content-Type": "application/json" });
|
|
12027
12394
|
res.end(JSON.stringify({ ok: false, reason: "wrong_path" }));
|
|
12028
12395
|
return;
|
|
@@ -12049,7 +12416,7 @@ function startClickUpWebhookListener({ config, state, worktree, clickupClient, o
|
|
|
12049
12416
|
if (overflow || res.writableEnded) return;
|
|
12050
12417
|
const latestState = readClickUpWebhookState(worktree, config);
|
|
12051
12418
|
try {
|
|
12052
|
-
const
|
|
12419
|
+
const common = {
|
|
12053
12420
|
method: req.method,
|
|
12054
12421
|
url: req.url,
|
|
12055
12422
|
headers: req.headers,
|
|
@@ -12061,7 +12428,8 @@ function startClickUpWebhookListener({ config, state, worktree, clickupClient, o
|
|
|
12061
12428
|
openCodeClient,
|
|
12062
12429
|
saveState: (nextState) => writeClickUpWebhookState(worktree, nextState, config),
|
|
12063
12430
|
asyncRouting: true
|
|
12064
|
-
}
|
|
12431
|
+
};
|
|
12432
|
+
const handled = isGitHubPath ? await handleGitHubWebhookRequest({ ...common, githubClient }) : await handleClickUpWebhookRequest(common);
|
|
12065
12433
|
res.writeHead(handled.status, { "Content-Type": "application/json" });
|
|
12066
12434
|
res.end(JSON.stringify({ ok: handled.ok, reason: handled.reason, result: handled.result?.action }));
|
|
12067
12435
|
} catch (error) {
|
|
@@ -13179,6 +13547,7 @@ async function OptimaPlugin(input = {}, pluginOptions = {}) {
|
|
|
13179
13547
|
const operatingTeamMode = getOperatingTeamMode(repoCfg);
|
|
13180
13548
|
const clickUpWebhookValidation = normalizeClickUpWebhookConfig(pickConfiguredClickUp(input, repoCfg, resolvedPluginOptions), worktree);
|
|
13181
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);
|
|
13182
13551
|
let clickUpWebhookRuntime = { active: false, valid: false, reason: "not_configured" };
|
|
13183
13552
|
if (clickUpWebhookValidation.complete) {
|
|
13184
13553
|
try {
|
|
@@ -13203,6 +13572,7 @@ async function OptimaPlugin(input = {}, pluginOptions = {}) {
|
|
|
13203
13572
|
state: listenerState,
|
|
13204
13573
|
worktree,
|
|
13205
13574
|
clickupClient: lifecycleClickUpClient,
|
|
13575
|
+
githubClient: runtimeGitHubClient,
|
|
13206
13576
|
openCodeClient: input.client,
|
|
13207
13577
|
listenerRegistry
|
|
13208
13578
|
});
|
|
@@ -13878,7 +14248,7 @@ Follow-up: use optima_prompt_workflow with session_id '${sessionId}' to check in
|
|
|
13878
14248
|
}
|
|
13879
14249
|
};
|
|
13880
14250
|
}
|
|
13881
|
-
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, 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, 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 };
|
|
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 };
|
|
13882
14252
|
|
|
13883
14253
|
// src/sanitize_cli.js
|
|
13884
14254
|
var { migrateLegacyOptimaLayout: migrateLegacyOptimaLayout2 } = OptimaPlugin.__internals;
|
|
@@ -26,8 +26,9 @@
|
|
|
26
26
|
- Human approval assignment is prohibited except for the strict allowlist: parent `plan` with clear questions already posted in ClickUp comments; `in progress` blocked by missing credentials, permissions, external tools, or access; or parent `validation` with a functional preview URL such as `https://<taskid>-preview.defend.tech`. Do not assign `CTO`/`PO` for generic handoff, routine validation, cleanup, subtask planning/validation, or partial-phase stops.
|
|
27
27
|
- Store ClickUp `agent_metadata` JSON with session IDs per agent/type/task/subtask; keep `Definition` as the plan contract and final Documentation as delivered behavior docs.
|
|
28
28
|
- `workflow_product_manager` is registered only when explicit ClickUp webhook mode is configured and the local webhook subscription state is active/valid.
|
|
29
|
-
- Webhook mode is opt-in: Optima validates signed `X-Signature` HMAC SHA-256 requests, routes status/assignee events only for Product Manager-assigned non-terminal tasks, routes comments only when they mention `@Defend Tech Product Manager`, stores new `ses_...` ids in ClickUp `agent_metadata
|
|
30
|
-
- The
|
|
29
|
+
- Webhook mode is opt-in: Optima validates signed `X-Signature` HMAC SHA-256 ClickUp requests, routes status/assignee events only for Product Manager-assigned non-terminal tasks, routes comments only when they mention `@Defend Tech Product Manager`, and stores new `ses_...` ids in ClickUp `agent_metadata`. Runtime/process failures stay in local logs and must not create ClickUp comments or tags.
|
|
30
|
+
- The same gated in-process listener can also accept signed GitHub `X-Hub-Signature-256` PR/review/comment events at `/optima/github/webhook` when `clickup.github.webhook` is enabled. GitHub events resolve the ClickUp task from the PR source branch, update only `agent_metadata.task.github`, and steer the existing `workflow_product_manager` session; they do not create replacement sessions or ClickUp runtime comments.
|
|
31
|
+
- Deployments still need a stable public URL/tunnel and local `.optima/.config/runtime/` state containing the ClickUp webhook id/secret. When a ClickUp client exposes webhook validation, Optima validates remote state; otherwise local-only validation is limited to ignored runtime state with matching URL/events and present id/secret.
|
|
31
32
|
|
|
32
33
|
## Shared Worktree And Git
|
|
33
34
|
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
- Git rules: principal workspace stays on `dev`, never `main`; parent branches use `<type>/<parent-id>`; subtask branches use non-nested `<type>/<parent-id>-subtask-<subtask-id>` and pending subtasks use `<type>/<parent-id>-pending-<title-slug>`; parent task pulls remote once at start; subtasks start from and PR to the parent local branch, bootstrapping the parent from `dev`/`origin/dev` first when missing; PoC branches stay `poc/<clickup-task-id>`; parents PR to `dev`, releases PR `dev` -> `main`; failed/conflicted subtask or parent merges return the affected item to `in progress` for the coding owner; no direct `main` pushes.
|
|
20
20
|
- Store `agent_metadata` session JSON; `Definition` is the plan contract, final Documentation is delivered behavior docs.
|
|
21
21
|
- `workflow_product_manager` is registered only when opt-in ClickUp webhook mode is complete and active/valid.
|
|
22
|
-
- Webhook mode validates `X-Signature` HMAC SHA-256, routes only PM-assigned non-terminal status/assignee events, routes comments only on `@Defend Tech Product Manager`, writes new `ses_...` ids to `agent_metadata`, and
|
|
23
|
-
- The listener is gated in-process local runtime; production needs a stable public URL/tunnel and ignored `.optima/.config/runtime/` webhook state.
|
|
22
|
+
- Webhook mode validates ClickUp `X-Signature` HMAC SHA-256, routes only PM-assigned non-terminal status/assignee events, routes comments only on `@Defend Tech Product Manager`, writes new `ses_...` ids to `agent_metadata`, and keeps runtime/process failures in local logs, never ClickUp comments/tags.
|
|
23
|
+
- The listener is gated in-process local runtime; production needs a stable public URL/tunnel and ignored `.optima/.config/runtime/` webhook state. It can also accept GitHub `X-Hub-Signature-256` PR/review/comment webhooks at `/optima/github/webhook`; these update only `agent_metadata.task.github` and steer the existing WPM session resolved from the PR source branch.
|
|
24
24
|
- Communication: questions, blockers, reviews, dependencies, and escalation go through PMA. Tech Lead reviews architecture/code/PR/standards/repo skills; Validator/QA verifies tests/regression/coverage/evidence/docs.
|
|
25
25
|
- Reopen same-scope discrepancies by reactivating the same task file, adding `Reopen History`, and reusing existing Task/Workflow Runner session IDs when possible.
|
|
26
26
|
- Blockers move to `.optima/tasks/blocked/` with a clear blocker report and PO-facing resolution need.
|