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