@defend-tech/opencode-optima 0.1.11 → 0.1.13

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
@@ -7929,6 +7929,8 @@ var CLICKUP_PM_MENTION_NAME = "Defend Tech Product Manager";
7929
7929
  var CLICKUP_WEBHOOK_RUNTIME_VERSION = 1;
7930
7930
  var CLICKUP_WEBHOOK_MAX_BODY_BYTES = 1024 * 1024;
7931
7931
  var CLICKUP_WEBHOOK_REQUEST_TIMEOUT_MS = 1e4;
7932
+ var CLICKUP_WEBHOOK_LOG_LEVELS = /* @__PURE__ */ new Set(["error", "info", "verbose"]);
7933
+ var CLICKUP_WEBHOOK_REDACTED = "[REDACTED]";
7932
7934
  var DISCUSSION_BACKFILL_FETCH_LIMIT = 100;
7933
7935
  var DEFAULT_PRESERVE_EXISTING_AGENTS = true;
7934
7936
  var SAFE_WORKTREE_FAILURE = "FAIL: Optima needs a writable project directory. Open/add a project in OpenChamber first. Refusing to use '/'.";
@@ -8793,6 +8795,22 @@ function clickUpWebhookStatePath(worktree) {
8793
8795
  function clickUpWebhookLogPath(worktree) {
8794
8796
  return path2.join(optimaRuntimeDir(worktree), "clickup-webhook.log.jsonl");
8795
8797
  }
8798
+ function normalizeClickUpWebhookLogLevel(value) {
8799
+ const level = String(value || "info").trim().toLowerCase();
8800
+ return CLICKUP_WEBHOOK_LOG_LEVELS.has(level) ? level : "info";
8801
+ }
8802
+ function clickUpWebhookAuditLogDir() {
8803
+ const dataHome = process.env.XDG_DATA_HOME && path2.isAbsolute(process.env.XDG_DATA_HOME) ? process.env.XDG_DATA_HOME : path2.join(os.homedir(), ".local", "share", "opencode");
8804
+ return path2.join(dataHome, "opencode-optima");
8805
+ }
8806
+ function clickUpWebhookAuditLogPath(at = /* @__PURE__ */ new Date(), logDir = clickUpWebhookAuditLogDir()) {
8807
+ return path2.join(logDir, `clickup-webhook-${at.toISOString().slice(0, 10)}.jsonl`);
8808
+ }
8809
+ function clickUpWebhookRequestFilePath(at = /* @__PURE__ */ new Date(), logDir = clickUpWebhookAuditLogDir()) {
8810
+ const stamp = at.toISOString().replace(/:/g, "-").replace(/\./g, "-");
8811
+ const shortId = crypto.randomBytes(4).toString("hex");
8812
+ return path2.join(logDir, "requests", `clickup-webhook-${stamp}-${shortId}.json`);
8813
+ }
8796
8814
  function findOptimaPluginTupleOptions(pluginEntries = []) {
8797
8815
  if (!Array.isArray(pluginEntries)) return null;
8798
8816
  for (const entry of pluginEntries) {
@@ -8822,6 +8840,7 @@ function normalizeClickUpWebhookConfig(rawClickUp = null, worktree = process.cwd
8822
8840
  basePath: String(raw.base_path || raw.basePath || worktree || "").trim(),
8823
8841
  teamId: String(raw.team_id || raw.teamId || "").trim(),
8824
8842
  apiToken: String(raw.api_token || raw.apiToken || "").trim(),
8843
+ log: normalizeClickUpWebhookLogLevel(raw.log),
8825
8844
  webhook: {
8826
8845
  publicUrl: String(webhook.public_url || webhook.publicUrl || "").trim(),
8827
8846
  bindHost: String(webhook.bind_host || webhook.bindHost || "127.0.0.1").trim(),
@@ -9035,7 +9054,13 @@ async function ensureClickUpWebhookSubscription({ validation, worktree, clickupC
9035
9054
  });
9036
9055
  const createdState = normalizeClickUpWebhookApiResponse(created, config);
9037
9056
  const stateForValidation = { ...createdState, recentEventKeys: existing.recentEventKeys || [] };
9038
- const createdValidation = await validateClickUpWebhookState(stateForValidation, config, clickupClient?.listWebhooks ? clickupClient : null);
9057
+ let createdValidation = await validateClickUpWebhookState(stateForValidation, config, clickupClient?.listWebhooks ? clickupClient : null);
9058
+ if (!createdValidation.valid && createdValidation.reason === "remote_state_missing") {
9059
+ const localCreatedValidation = await validateClickUpWebhookState(stateForValidation, config, null);
9060
+ if (localCreatedValidation.valid) {
9061
+ createdValidation = { ...localCreatedValidation, mode: "created_pending_remote_validation", limitation: "ClickUp webhook created from API response; remote list validation had not propagated yet." };
9062
+ }
9063
+ }
9039
9064
  if (!createdValidation.valid) {
9040
9065
  if (stateForValidation.webhookId && clickupClient?.deleteWebhook) {
9041
9066
  try {
@@ -9046,7 +9071,8 @@ async function ensureClickUpWebhookSubscription({ validation, worktree, clickupC
9046
9071
  return { active: false, valid: false, reason: "created_state_invalid", validation: createdValidation, state: stateForValidation };
9047
9072
  }
9048
9073
  const next = writeClickUpWebhookState(worktree, createdValidation.state || stateForValidation, config);
9049
- return { active: true, valid: true, mode: clickupClient?.listWebhooks ? "created_remote_validated" : "created", state: next };
9074
+ const mode = createdValidation.mode === "created_pending_remote_validation" ? "created_pending_remote_validation" : clickupClient?.listWebhooks ? "created_remote_validated" : "created";
9075
+ return { active: true, valid: true, mode, limitation: createdValidation.limitation, state: next };
9050
9076
  }
9051
9077
  function verifyClickUpSignature(rawBody, signatureHeader, secret) {
9052
9078
  const signature = String(signatureHeader || "").replace(/^sha256=/i, "").trim();
@@ -9205,6 +9231,74 @@ function appendClickUpWebhookLocalLog(worktree, entry) {
9205
9231
  fs2.appendFileSync(logPath, `${JSON.stringify(safeEntry)}
9206
9232
  `, "utf8");
9207
9233
  }
9234
+ function redactClickUpWebhookAuditValue(value, secretValues = []) {
9235
+ if (typeof value === "string") {
9236
+ return secretValues.reduce((next, secret) => secret ? next.split(secret).join(CLICKUP_WEBHOOK_REDACTED) : next, value);
9237
+ }
9238
+ if (Array.isArray(value)) return value.map((item) => redactClickUpWebhookAuditValue(item, secretValues));
9239
+ if (!isPlainObject(value)) return value;
9240
+ return Object.fromEntries(Object.entries(value).map(([key, entryValue]) => {
9241
+ const normalizedKey = normalizeLooseToken(key);
9242
+ const secretKey = normalizedKey.includes("signature") || normalizedKey.includes("authorization") || normalizedKey.includes("cookie") || normalizedKey.includes("token") || normalizedKey.includes("secret") || normalizedKey.includes("api-key") || normalizedKey.includes("apikey");
9243
+ return [key, secretKey ? CLICKUP_WEBHOOK_REDACTED : redactClickUpWebhookAuditValue(entryValue, secretValues)];
9244
+ }));
9245
+ }
9246
+ function buildClickUpWebhookAuditSummary({ method, url, config, handled, error, payload, requestFile, at = /* @__PURE__ */ new Date() } = {}) {
9247
+ const result = handled?.result || {};
9248
+ const summary = {
9249
+ at: at.toISOString(),
9250
+ method,
9251
+ path: url === null ? null : new URL(String(url || "/"), config?.webhook?.publicUrl || "http://localhost").pathname,
9252
+ status: handled?.status || 500,
9253
+ ok: Boolean(handled?.ok),
9254
+ reason: handled?.reason || result.reason || (error ? "handler_error" : void 0),
9255
+ action: result.action,
9256
+ event: payload ? clickUpEventType(payload) : void 0,
9257
+ task: result.taskId || clickUpTaskIdFromPayload(payload || {}),
9258
+ comment: clickUpCommentFromPayload(payload || {})?.id || payload?.comment_id || payload?.commentId,
9259
+ session: result.sessionId,
9260
+ base_path: config?.basePath || "",
9261
+ request_file: requestFile || void 0
9262
+ };
9263
+ return Object.fromEntries(Object.entries(summary).filter(([, value]) => value !== void 0 && value !== ""));
9264
+ }
9265
+ function writeClickUpWebhookAuditLog({ method, url, headers = {}, rawBody = "", config, state = {}, handled = null, error = null, payload = null, at = /* @__PURE__ */ new Date() } = {}) {
9266
+ try {
9267
+ const level = normalizeClickUpWebhookLogLevel(config?.log);
9268
+ const failed = Boolean(error || !handled?.ok || (handled?.status || 500) >= 400);
9269
+ if (level === "error" && !failed) return;
9270
+ const logDir = clickUpWebhookAuditLogDir();
9271
+ fs2.mkdirSync(logDir, { recursive: true });
9272
+ const secretValues = [resolveSecretReference(config?.apiToken), state?.secret, config?.webhook?.secret].filter(Boolean);
9273
+ let requestFile;
9274
+ if (level === "verbose") {
9275
+ const absoluteRequestFile = clickUpWebhookRequestFilePath(at, logDir);
9276
+ fs2.mkdirSync(path2.dirname(absoluteRequestFile), { recursive: true });
9277
+ requestFile = path2.relative(logDir, absoluteRequestFile).split(path2.sep).join("/");
9278
+ const parsedBody = payload || (() => {
9279
+ try {
9280
+ return JSON.parse(Buffer.isBuffer(rawBody) ? rawBody.toString("utf8") : String(rawBody || ""));
9281
+ } catch {
9282
+ return void 0;
9283
+ }
9284
+ })();
9285
+ const requestArtifact = redactClickUpWebhookAuditValue({
9286
+ at: at.toISOString(),
9287
+ method,
9288
+ url,
9289
+ path: url === null ? null : new URL(String(url || "/"), config?.webhook?.publicUrl || "http://localhost").pathname,
9290
+ headers,
9291
+ body: parsedBody === void 0 ? Buffer.isBuffer(rawBody) ? rawBody.toString("utf8") : String(rawBody || "") : parsedBody
9292
+ }, secretValues);
9293
+ fs2.writeFileSync(absoluteRequestFile, `${JSON.stringify(requestArtifact, null, 2)}
9294
+ `, "utf8");
9295
+ }
9296
+ const summary = redactClickUpWebhookAuditValue(buildClickUpWebhookAuditSummary({ method, url, config, handled, error, payload, requestFile, at }), secretValues);
9297
+ fs2.appendFileSync(clickUpWebhookAuditLogPath(at, logDir), `${JSON.stringify(summary)}
9298
+ `, "utf8");
9299
+ } catch {
9300
+ }
9301
+ }
9208
9302
  async function withClickUpTaskRouteLock(taskId, operation) {
9209
9303
  const key = String(taskId || "");
9210
9304
  if (!key) return operation();
@@ -9302,25 +9396,36 @@ function clickUpWebhookExpectedPath(config) {
9302
9396
  return "/";
9303
9397
  }
9304
9398
  }
9305
- async function handleClickUpWebhookRequest({ method = "POST", url = null, headers = {}, rawBody = "", config, state, worktree, clickupClient, openCodeClient, saveState } = {}) {
9306
- if (method !== "POST") return { ok: false, status: 405, reason: "method_not_allowed" };
9307
- if (url !== null) {
9308
- const requestPath = new URL(String(url), config?.webhook?.publicUrl || "http://localhost").pathname;
9309
- if (requestPath !== clickUpWebhookExpectedPath(config)) return { ok: false, status: 404, reason: "wrong_path" };
9310
- }
9311
- const maxBodyBytes = Number(config?.webhook?.maxBodyBytes || CLICKUP_WEBHOOK_MAX_BODY_BYTES);
9312
- const bodyBytes = Buffer.isBuffer(rawBody) ? rawBody.length : Buffer.byteLength(String(rawBody || ""));
9313
- if (bodyBytes > maxBodyBytes) return { ok: false, status: 413, reason: "body_too_large" };
9314
- const signature = headers["x-signature"] || headers["X-Signature"];
9315
- if (!verifyClickUpSignature(rawBody, signature, state?.secret)) return { ok: false, status: 401, reason: "invalid_signature" };
9316
- let payload;
9399
+ async function handleClickUpWebhookRequest({ method = "POST", url = null, headers = {}, rawBody = "", config, state, worktree, clickupClient, openCodeClient, saveState, now = () => /* @__PURE__ */ new Date() } = {}) {
9400
+ let payload = null;
9401
+ let handled = null;
9402
+ const finish = (result) => {
9403
+ handled = result;
9404
+ writeClickUpWebhookAuditLog({ method, url, headers, rawBody, config, state, handled, payload, at: now() });
9405
+ return result;
9406
+ };
9317
9407
  try {
9318
- payload = JSON.parse(Buffer.isBuffer(rawBody) ? rawBody.toString("utf8") : String(rawBody));
9319
- } catch {
9320
- return { ok: false, status: 400, reason: "invalid_json" };
9408
+ if (method !== "POST") return finish({ ok: false, status: 405, reason: "method_not_allowed" });
9409
+ if (url !== null) {
9410
+ const requestPath = new URL(String(url), config?.webhook?.publicUrl || "http://localhost").pathname;
9411
+ if (requestPath !== clickUpWebhookExpectedPath(config)) return finish({ ok: false, status: 404, reason: "wrong_path" });
9412
+ }
9413
+ const maxBodyBytes = Number(config?.webhook?.maxBodyBytes || CLICKUP_WEBHOOK_MAX_BODY_BYTES);
9414
+ const bodyBytes = Buffer.isBuffer(rawBody) ? rawBody.length : Buffer.byteLength(String(rawBody || ""));
9415
+ if (bodyBytes > maxBodyBytes) return finish({ ok: false, status: 413, reason: "body_too_large" });
9416
+ const signature = headers["x-signature"] || headers["X-Signature"];
9417
+ if (!verifyClickUpSignature(rawBody, signature, state?.secret)) return finish({ ok: false, status: 401, reason: "invalid_signature" });
9418
+ try {
9419
+ payload = JSON.parse(Buffer.isBuffer(rawBody) ? rawBody.toString("utf8") : String(rawBody));
9420
+ } catch {
9421
+ return finish({ ok: false, status: 400, reason: "invalid_json" });
9422
+ }
9423
+ const result = await routeClickUpWebhookEvent({ payload, config, state, worktree, clickupClient, openCodeClient, saveState });
9424
+ return finish({ ok: result.ok, status: result.ok ? 200 : 422, result });
9425
+ } catch (error) {
9426
+ writeClickUpWebhookAuditLog({ method, url, headers, rawBody, config, state, handled, error, payload, at: now() });
9427
+ throw error;
9321
9428
  }
9322
- const result = await routeClickUpWebhookEvent({ payload, config, state, worktree, clickupClient, openCodeClient, saveState });
9323
- return { ok: result.ok, status: result.ok ? 200 : 422, result };
9324
9429
  }
9325
9430
  function clickUpListenerKey(config) {
9326
9431
  return `${config?.webhook?.bindHost || "127.0.0.1"}:${config?.webhook?.bindPort || 0}`;
@@ -11012,7 +11117,7 @@ Follow-up: use optima_prompt_workflow with session_id '${sessionId}' to check in
11012
11117
  }
11013
11118
  };
11014
11119
  }
11015
- OptimaPlugin.__internals = { buildClickUpApplyPayloadResult, buildClickUpCreateSubtasksPayload, buildClickUpStartTaskPayload, buildClickUpSummaryPayload, buildClickUpTransitionPayload, buildOptimaAgents, clickUpCommentMentionsProductManager, createClickUpApiClient, ensureClickUpWebhookSubscription, handleClickUpWebhookRequest, isClickUpWebhookStateActive, normalizeClickUpWebhookConfig, readClickUpWebhookState, resolveOptimaPluginOptions, resolveSecretReference, routeClickUpWebhookEvent, 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 };
11120
+ OptimaPlugin.__internals = { buildClickUpApplyPayloadResult, buildClickUpCreateSubtasksPayload, buildClickUpStartTaskPayload, buildClickUpSummaryPayload, buildClickUpTransitionPayload, buildOptimaAgents, clickUpCommentMentionsProductManager, clickUpWebhookAuditLogDir, clickUpWebhookAuditLogPath, createClickUpApiClient, ensureClickUpWebhookSubscription, handleClickUpWebhookRequest, isClickUpWebhookStateActive, normalizeClickUpWebhookConfig, normalizeClickUpWebhookLogLevel, readClickUpWebhookState, resolveOptimaPluginOptions, resolveSecretReference, routeClickUpWebhookEvent, 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 };
11016
11121
  export {
11017
11122
  OptimaPlugin as default
11018
11123
  };
@@ -7936,6 +7936,8 @@ var CLICKUP_PM_MENTION_NAME = "Defend Tech Product Manager";
7936
7936
  var CLICKUP_WEBHOOK_RUNTIME_VERSION = 1;
7937
7937
  var CLICKUP_WEBHOOK_MAX_BODY_BYTES = 1024 * 1024;
7938
7938
  var CLICKUP_WEBHOOK_REQUEST_TIMEOUT_MS = 1e4;
7939
+ var CLICKUP_WEBHOOK_LOG_LEVELS = /* @__PURE__ */ new Set(["error", "info", "verbose"]);
7940
+ var CLICKUP_WEBHOOK_REDACTED = "[REDACTED]";
7939
7941
  var DISCUSSION_BACKFILL_FETCH_LIMIT = 100;
7940
7942
  var DEFAULT_PRESERVE_EXISTING_AGENTS = true;
7941
7943
  var SAFE_WORKTREE_FAILURE = "FAIL: Optima needs a writable project directory. Open/add a project in OpenChamber first. Refusing to use '/'.";
@@ -8800,6 +8802,22 @@ function clickUpWebhookStatePath(worktree) {
8800
8802
  function clickUpWebhookLogPath(worktree) {
8801
8803
  return path2.join(optimaRuntimeDir(worktree), "clickup-webhook.log.jsonl");
8802
8804
  }
8805
+ function normalizeClickUpWebhookLogLevel(value) {
8806
+ const level = String(value || "info").trim().toLowerCase();
8807
+ return CLICKUP_WEBHOOK_LOG_LEVELS.has(level) ? level : "info";
8808
+ }
8809
+ function clickUpWebhookAuditLogDir() {
8810
+ const dataHome = process.env.XDG_DATA_HOME && path2.isAbsolute(process.env.XDG_DATA_HOME) ? process.env.XDG_DATA_HOME : path2.join(os.homedir(), ".local", "share", "opencode");
8811
+ return path2.join(dataHome, "opencode-optima");
8812
+ }
8813
+ function clickUpWebhookAuditLogPath(at = /* @__PURE__ */ new Date(), logDir = clickUpWebhookAuditLogDir()) {
8814
+ return path2.join(logDir, `clickup-webhook-${at.toISOString().slice(0, 10)}.jsonl`);
8815
+ }
8816
+ function clickUpWebhookRequestFilePath(at = /* @__PURE__ */ new Date(), logDir = clickUpWebhookAuditLogDir()) {
8817
+ const stamp = at.toISOString().replace(/:/g, "-").replace(/\./g, "-");
8818
+ const shortId = crypto.randomBytes(4).toString("hex");
8819
+ return path2.join(logDir, "requests", `clickup-webhook-${stamp}-${shortId}.json`);
8820
+ }
8803
8821
  function findOptimaPluginTupleOptions(pluginEntries = []) {
8804
8822
  if (!Array.isArray(pluginEntries)) return null;
8805
8823
  for (const entry of pluginEntries) {
@@ -8829,6 +8847,7 @@ function normalizeClickUpWebhookConfig(rawClickUp = null, worktree = process.cwd
8829
8847
  basePath: String(raw.base_path || raw.basePath || worktree || "").trim(),
8830
8848
  teamId: String(raw.team_id || raw.teamId || "").trim(),
8831
8849
  apiToken: String(raw.api_token || raw.apiToken || "").trim(),
8850
+ log: normalizeClickUpWebhookLogLevel(raw.log),
8832
8851
  webhook: {
8833
8852
  publicUrl: String(webhook.public_url || webhook.publicUrl || "").trim(),
8834
8853
  bindHost: String(webhook.bind_host || webhook.bindHost || "127.0.0.1").trim(),
@@ -9042,7 +9061,13 @@ async function ensureClickUpWebhookSubscription({ validation, worktree, clickupC
9042
9061
  });
9043
9062
  const createdState = normalizeClickUpWebhookApiResponse(created, config);
9044
9063
  const stateForValidation = { ...createdState, recentEventKeys: existing.recentEventKeys || [] };
9045
- const createdValidation = await validateClickUpWebhookState(stateForValidation, config, clickupClient?.listWebhooks ? clickupClient : null);
9064
+ let createdValidation = await validateClickUpWebhookState(stateForValidation, config, clickupClient?.listWebhooks ? clickupClient : null);
9065
+ if (!createdValidation.valid && createdValidation.reason === "remote_state_missing") {
9066
+ const localCreatedValidation = await validateClickUpWebhookState(stateForValidation, config, null);
9067
+ if (localCreatedValidation.valid) {
9068
+ createdValidation = { ...localCreatedValidation, mode: "created_pending_remote_validation", limitation: "ClickUp webhook created from API response; remote list validation had not propagated yet." };
9069
+ }
9070
+ }
9046
9071
  if (!createdValidation.valid) {
9047
9072
  if (stateForValidation.webhookId && clickupClient?.deleteWebhook) {
9048
9073
  try {
@@ -9053,7 +9078,8 @@ async function ensureClickUpWebhookSubscription({ validation, worktree, clickupC
9053
9078
  return { active: false, valid: false, reason: "created_state_invalid", validation: createdValidation, state: stateForValidation };
9054
9079
  }
9055
9080
  const next = writeClickUpWebhookState(worktree, createdValidation.state || stateForValidation, config);
9056
- return { active: true, valid: true, mode: clickupClient?.listWebhooks ? "created_remote_validated" : "created", state: next };
9081
+ const mode = createdValidation.mode === "created_pending_remote_validation" ? "created_pending_remote_validation" : clickupClient?.listWebhooks ? "created_remote_validated" : "created";
9082
+ return { active: true, valid: true, mode, limitation: createdValidation.limitation, state: next };
9057
9083
  }
9058
9084
  function verifyClickUpSignature(rawBody, signatureHeader, secret) {
9059
9085
  const signature = String(signatureHeader || "").replace(/^sha256=/i, "").trim();
@@ -9212,6 +9238,74 @@ function appendClickUpWebhookLocalLog(worktree, entry) {
9212
9238
  fs2.appendFileSync(logPath, `${JSON.stringify(safeEntry)}
9213
9239
  `, "utf8");
9214
9240
  }
9241
+ function redactClickUpWebhookAuditValue(value, secretValues = []) {
9242
+ if (typeof value === "string") {
9243
+ return secretValues.reduce((next, secret) => secret ? next.split(secret).join(CLICKUP_WEBHOOK_REDACTED) : next, value);
9244
+ }
9245
+ if (Array.isArray(value)) return value.map((item) => redactClickUpWebhookAuditValue(item, secretValues));
9246
+ if (!isPlainObject(value)) return value;
9247
+ return Object.fromEntries(Object.entries(value).map(([key, entryValue]) => {
9248
+ const normalizedKey = normalizeLooseToken(key);
9249
+ const secretKey = normalizedKey.includes("signature") || normalizedKey.includes("authorization") || normalizedKey.includes("cookie") || normalizedKey.includes("token") || normalizedKey.includes("secret") || normalizedKey.includes("api-key") || normalizedKey.includes("apikey");
9250
+ return [key, secretKey ? CLICKUP_WEBHOOK_REDACTED : redactClickUpWebhookAuditValue(entryValue, secretValues)];
9251
+ }));
9252
+ }
9253
+ function buildClickUpWebhookAuditSummary({ method, url, config, handled, error, payload, requestFile, at = /* @__PURE__ */ new Date() } = {}) {
9254
+ const result = handled?.result || {};
9255
+ const summary = {
9256
+ at: at.toISOString(),
9257
+ method,
9258
+ path: url === null ? null : new URL(String(url || "/"), config?.webhook?.publicUrl || "http://localhost").pathname,
9259
+ status: handled?.status || 500,
9260
+ ok: Boolean(handled?.ok),
9261
+ reason: handled?.reason || result.reason || (error ? "handler_error" : void 0),
9262
+ action: result.action,
9263
+ event: payload ? clickUpEventType(payload) : void 0,
9264
+ task: result.taskId || clickUpTaskIdFromPayload(payload || {}),
9265
+ comment: clickUpCommentFromPayload(payload || {})?.id || payload?.comment_id || payload?.commentId,
9266
+ session: result.sessionId,
9267
+ base_path: config?.basePath || "",
9268
+ request_file: requestFile || void 0
9269
+ };
9270
+ return Object.fromEntries(Object.entries(summary).filter(([, value]) => value !== void 0 && value !== ""));
9271
+ }
9272
+ function writeClickUpWebhookAuditLog({ method, url, headers = {}, rawBody = "", config, state = {}, handled = null, error = null, payload = null, at = /* @__PURE__ */ new Date() } = {}) {
9273
+ try {
9274
+ const level = normalizeClickUpWebhookLogLevel(config?.log);
9275
+ const failed = Boolean(error || !handled?.ok || (handled?.status || 500) >= 400);
9276
+ if (level === "error" && !failed) return;
9277
+ const logDir = clickUpWebhookAuditLogDir();
9278
+ fs2.mkdirSync(logDir, { recursive: true });
9279
+ const secretValues = [resolveSecretReference(config?.apiToken), state?.secret, config?.webhook?.secret].filter(Boolean);
9280
+ let requestFile;
9281
+ if (level === "verbose") {
9282
+ const absoluteRequestFile = clickUpWebhookRequestFilePath(at, logDir);
9283
+ fs2.mkdirSync(path2.dirname(absoluteRequestFile), { recursive: true });
9284
+ requestFile = path2.relative(logDir, absoluteRequestFile).split(path2.sep).join("/");
9285
+ const parsedBody = payload || (() => {
9286
+ try {
9287
+ return JSON.parse(Buffer.isBuffer(rawBody) ? rawBody.toString("utf8") : String(rawBody || ""));
9288
+ } catch {
9289
+ return void 0;
9290
+ }
9291
+ })();
9292
+ const requestArtifact = redactClickUpWebhookAuditValue({
9293
+ at: at.toISOString(),
9294
+ method,
9295
+ url,
9296
+ path: url === null ? null : new URL(String(url || "/"), config?.webhook?.publicUrl || "http://localhost").pathname,
9297
+ headers,
9298
+ body: parsedBody === void 0 ? Buffer.isBuffer(rawBody) ? rawBody.toString("utf8") : String(rawBody || "") : parsedBody
9299
+ }, secretValues);
9300
+ fs2.writeFileSync(absoluteRequestFile, `${JSON.stringify(requestArtifact, null, 2)}
9301
+ `, "utf8");
9302
+ }
9303
+ const summary = redactClickUpWebhookAuditValue(buildClickUpWebhookAuditSummary({ method, url, config, handled, error, payload, requestFile, at }), secretValues);
9304
+ fs2.appendFileSync(clickUpWebhookAuditLogPath(at, logDir), `${JSON.stringify(summary)}
9305
+ `, "utf8");
9306
+ } catch {
9307
+ }
9308
+ }
9215
9309
  async function withClickUpTaskRouteLock(taskId, operation) {
9216
9310
  const key = String(taskId || "");
9217
9311
  if (!key) return operation();
@@ -9309,25 +9403,36 @@ function clickUpWebhookExpectedPath(config) {
9309
9403
  return "/";
9310
9404
  }
9311
9405
  }
9312
- async function handleClickUpWebhookRequest({ method = "POST", url = null, headers = {}, rawBody = "", config, state, worktree, clickupClient, openCodeClient, saveState } = {}) {
9313
- if (method !== "POST") return { ok: false, status: 405, reason: "method_not_allowed" };
9314
- if (url !== null) {
9315
- const requestPath = new URL(String(url), config?.webhook?.publicUrl || "http://localhost").pathname;
9316
- if (requestPath !== clickUpWebhookExpectedPath(config)) return { ok: false, status: 404, reason: "wrong_path" };
9317
- }
9318
- const maxBodyBytes = Number(config?.webhook?.maxBodyBytes || CLICKUP_WEBHOOK_MAX_BODY_BYTES);
9319
- const bodyBytes = Buffer.isBuffer(rawBody) ? rawBody.length : Buffer.byteLength(String(rawBody || ""));
9320
- if (bodyBytes > maxBodyBytes) return { ok: false, status: 413, reason: "body_too_large" };
9321
- const signature = headers["x-signature"] || headers["X-Signature"];
9322
- if (!verifyClickUpSignature(rawBody, signature, state?.secret)) return { ok: false, status: 401, reason: "invalid_signature" };
9323
- let payload;
9406
+ async function handleClickUpWebhookRequest({ method = "POST", url = null, headers = {}, rawBody = "", config, state, worktree, clickupClient, openCodeClient, saveState, now = () => /* @__PURE__ */ new Date() } = {}) {
9407
+ let payload = null;
9408
+ let handled = null;
9409
+ const finish = (result) => {
9410
+ handled = result;
9411
+ writeClickUpWebhookAuditLog({ method, url, headers, rawBody, config, state, handled, payload, at: now() });
9412
+ return result;
9413
+ };
9324
9414
  try {
9325
- payload = JSON.parse(Buffer.isBuffer(rawBody) ? rawBody.toString("utf8") : String(rawBody));
9326
- } catch {
9327
- return { ok: false, status: 400, reason: "invalid_json" };
9415
+ if (method !== "POST") return finish({ ok: false, status: 405, reason: "method_not_allowed" });
9416
+ if (url !== null) {
9417
+ const requestPath = new URL(String(url), config?.webhook?.publicUrl || "http://localhost").pathname;
9418
+ if (requestPath !== clickUpWebhookExpectedPath(config)) return finish({ ok: false, status: 404, reason: "wrong_path" });
9419
+ }
9420
+ const maxBodyBytes = Number(config?.webhook?.maxBodyBytes || CLICKUP_WEBHOOK_MAX_BODY_BYTES);
9421
+ const bodyBytes = Buffer.isBuffer(rawBody) ? rawBody.length : Buffer.byteLength(String(rawBody || ""));
9422
+ if (bodyBytes > maxBodyBytes) return finish({ ok: false, status: 413, reason: "body_too_large" });
9423
+ const signature = headers["x-signature"] || headers["X-Signature"];
9424
+ if (!verifyClickUpSignature(rawBody, signature, state?.secret)) return finish({ ok: false, status: 401, reason: "invalid_signature" });
9425
+ try {
9426
+ payload = JSON.parse(Buffer.isBuffer(rawBody) ? rawBody.toString("utf8") : String(rawBody));
9427
+ } catch {
9428
+ return finish({ ok: false, status: 400, reason: "invalid_json" });
9429
+ }
9430
+ const result = await routeClickUpWebhookEvent({ payload, config, state, worktree, clickupClient, openCodeClient, saveState });
9431
+ return finish({ ok: result.ok, status: result.ok ? 200 : 422, result });
9432
+ } catch (error) {
9433
+ writeClickUpWebhookAuditLog({ method, url, headers, rawBody, config, state, handled, error, payload, at: now() });
9434
+ throw error;
9328
9435
  }
9329
- const result = await routeClickUpWebhookEvent({ payload, config, state, worktree, clickupClient, openCodeClient, saveState });
9330
- return { ok: result.ok, status: result.ok ? 200 : 422, result };
9331
9436
  }
9332
9437
  function clickUpListenerKey(config) {
9333
9438
  return `${config?.webhook?.bindHost || "127.0.0.1"}:${config?.webhook?.bindPort || 0}`;
@@ -11019,7 +11124,7 @@ Follow-up: use optima_prompt_workflow with session_id '${sessionId}' to check in
11019
11124
  }
11020
11125
  };
11021
11126
  }
11022
- OptimaPlugin.__internals = { buildClickUpApplyPayloadResult, buildClickUpCreateSubtasksPayload, buildClickUpStartTaskPayload, buildClickUpSummaryPayload, buildClickUpTransitionPayload, buildOptimaAgents, clickUpCommentMentionsProductManager, createClickUpApiClient, ensureClickUpWebhookSubscription, handleClickUpWebhookRequest, isClickUpWebhookStateActive, normalizeClickUpWebhookConfig, readClickUpWebhookState, resolveOptimaPluginOptions, resolveSecretReference, routeClickUpWebhookEvent, 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 };
11127
+ OptimaPlugin.__internals = { buildClickUpApplyPayloadResult, buildClickUpCreateSubtasksPayload, buildClickUpStartTaskPayload, buildClickUpSummaryPayload, buildClickUpTransitionPayload, buildOptimaAgents, clickUpCommentMentionsProductManager, clickUpWebhookAuditLogDir, clickUpWebhookAuditLogPath, createClickUpApiClient, ensureClickUpWebhookSubscription, handleClickUpWebhookRequest, isClickUpWebhookStateActive, normalizeClickUpWebhookConfig, normalizeClickUpWebhookLogLevel, readClickUpWebhookState, resolveOptimaPluginOptions, resolveSecretReference, routeClickUpWebhookEvent, 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 };
11023
11128
 
11024
11129
  // src/sanitize_cli.js
11025
11130
  var { migrateLegacyOptimaLayout: migrateLegacyOptimaLayout2 } = OptimaPlugin.__internals;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defend-tech/opencode-optima",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+ssh://git@github.com/defend-tech/opencode-optima.git"