@defend-tech/opencode-optima 0.1.12 → 0.1.14

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(),
@@ -9188,10 +9207,26 @@ async function createOpenCodeSession(client, { title, directory } = {}) {
9188
9207
  const result = await client.session.create({ query: { directory }, body: { title } });
9189
9208
  return result?.data?.id || result?.id;
9190
9209
  }
9210
+ async function callOpenCodePromptWithPathFallback(method, sessionId, payload) {
9211
+ try {
9212
+ return await method({ ...payload, path: { sessionID: sessionId } });
9213
+ } catch (error) {
9214
+ try {
9215
+ return await method({ ...payload, path: { id: sessionId } });
9216
+ } catch {
9217
+ throw error;
9218
+ }
9219
+ }
9220
+ }
9191
9221
  async function sendOpenCodeSessionEvent(client, { sessionId, agent, text, directory } = {}) {
9192
- const payload = { path: { id: sessionId }, query: { directory }, body: { agent, parts: [{ type: "text", text }] } };
9193
- if (typeof client?.session?.promptAsync === "function") return client.session.promptAsync(payload);
9194
- return client.session.prompt(payload);
9222
+ const payload = { query: { directory }, body: { agent, parts: [{ type: "text", text }] } };
9223
+ if (typeof client?.session?.prompt === "function") {
9224
+ return callOpenCodePromptWithPathFallback(client.session.prompt.bind(client.session), sessionId, payload);
9225
+ }
9226
+ if (typeof client?.session?.promptAsync === "function") {
9227
+ return callOpenCodePromptWithPathFallback(client.session.promptAsync.bind(client.session), sessionId, payload);
9228
+ }
9229
+ throw new Error("OpenCode client does not expose session.prompt or session.promptAsync.");
9195
9230
  }
9196
9231
  function formatClickUpWebhookPrompt({ eventType, taskId, payload }) {
9197
9232
  const comment = clickUpCommentFromPayload(payload);
@@ -9212,6 +9247,74 @@ function appendClickUpWebhookLocalLog(worktree, entry) {
9212
9247
  fs2.appendFileSync(logPath, `${JSON.stringify(safeEntry)}
9213
9248
  `, "utf8");
9214
9249
  }
9250
+ function redactClickUpWebhookAuditValue(value, secretValues = []) {
9251
+ if (typeof value === "string") {
9252
+ return secretValues.reduce((next, secret) => secret ? next.split(secret).join(CLICKUP_WEBHOOK_REDACTED) : next, value);
9253
+ }
9254
+ if (Array.isArray(value)) return value.map((item) => redactClickUpWebhookAuditValue(item, secretValues));
9255
+ if (!isPlainObject(value)) return value;
9256
+ return Object.fromEntries(Object.entries(value).map(([key, entryValue]) => {
9257
+ const normalizedKey = normalizeLooseToken(key);
9258
+ const secretKey = normalizedKey.includes("signature") || normalizedKey.includes("authorization") || normalizedKey.includes("cookie") || normalizedKey.includes("token") || normalizedKey.includes("secret") || normalizedKey.includes("api-key") || normalizedKey.includes("apikey");
9259
+ return [key, secretKey ? CLICKUP_WEBHOOK_REDACTED : redactClickUpWebhookAuditValue(entryValue, secretValues)];
9260
+ }));
9261
+ }
9262
+ function buildClickUpWebhookAuditSummary({ method, url, config, handled, error, payload, requestFile, at = /* @__PURE__ */ new Date() } = {}) {
9263
+ const result = handled?.result || {};
9264
+ const summary = {
9265
+ at: at.toISOString(),
9266
+ method,
9267
+ path: url === null ? null : new URL(String(url || "/"), config?.webhook?.publicUrl || "http://localhost").pathname,
9268
+ status: handled?.status || 500,
9269
+ ok: Boolean(handled?.ok),
9270
+ reason: handled?.reason || result.reason || (error ? "handler_error" : void 0),
9271
+ action: result.action,
9272
+ event: payload ? clickUpEventType(payload) : void 0,
9273
+ task: result.taskId || clickUpTaskIdFromPayload(payload || {}),
9274
+ comment: clickUpCommentFromPayload(payload || {})?.id || payload?.comment_id || payload?.commentId,
9275
+ session: result.sessionId,
9276
+ base_path: config?.basePath || "",
9277
+ request_file: requestFile || void 0
9278
+ };
9279
+ return Object.fromEntries(Object.entries(summary).filter(([, value]) => value !== void 0 && value !== ""));
9280
+ }
9281
+ function writeClickUpWebhookAuditLog({ method, url, headers = {}, rawBody = "", config, state = {}, handled = null, error = null, payload = null, at = /* @__PURE__ */ new Date() } = {}) {
9282
+ try {
9283
+ const level = normalizeClickUpWebhookLogLevel(config?.log);
9284
+ const failed = Boolean(error || !handled?.ok || (handled?.status || 500) >= 400);
9285
+ if (level === "error" && !failed) return;
9286
+ const logDir = clickUpWebhookAuditLogDir();
9287
+ fs2.mkdirSync(logDir, { recursive: true });
9288
+ const secretValues = [resolveSecretReference(config?.apiToken), state?.secret, config?.webhook?.secret].filter(Boolean);
9289
+ let requestFile;
9290
+ if (level === "verbose") {
9291
+ const absoluteRequestFile = clickUpWebhookRequestFilePath(at, logDir);
9292
+ fs2.mkdirSync(path2.dirname(absoluteRequestFile), { recursive: true });
9293
+ requestFile = path2.relative(logDir, absoluteRequestFile).split(path2.sep).join("/");
9294
+ const parsedBody = payload || (() => {
9295
+ try {
9296
+ return JSON.parse(Buffer.isBuffer(rawBody) ? rawBody.toString("utf8") : String(rawBody || ""));
9297
+ } catch {
9298
+ return void 0;
9299
+ }
9300
+ })();
9301
+ const requestArtifact = redactClickUpWebhookAuditValue({
9302
+ at: at.toISOString(),
9303
+ method,
9304
+ url,
9305
+ path: url === null ? null : new URL(String(url || "/"), config?.webhook?.publicUrl || "http://localhost").pathname,
9306
+ headers,
9307
+ body: parsedBody === void 0 ? Buffer.isBuffer(rawBody) ? rawBody.toString("utf8") : String(rawBody || "") : parsedBody
9308
+ }, secretValues);
9309
+ fs2.writeFileSync(absoluteRequestFile, `${JSON.stringify(requestArtifact, null, 2)}
9310
+ `, "utf8");
9311
+ }
9312
+ const summary = redactClickUpWebhookAuditValue(buildClickUpWebhookAuditSummary({ method, url, config, handled, error, payload, requestFile, at }), secretValues);
9313
+ fs2.appendFileSync(clickUpWebhookAuditLogPath(at, logDir), `${JSON.stringify(summary)}
9314
+ `, "utf8");
9315
+ } catch {
9316
+ }
9317
+ }
9215
9318
  async function withClickUpTaskRouteLock(taskId, operation) {
9216
9319
  const key = String(taskId || "");
9217
9320
  if (!key) return operation();
@@ -9280,9 +9383,9 @@ async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, w
9280
9383
  appendClickUpWebhookLocalLog(worktree, { type: "pending_session_metadata_failed", taskId, sessionId: pendingSessionId, message: error.message });
9281
9384
  throw error;
9282
9385
  }
9386
+ await sendSessionEvent(openCodeClient, { sessionId: pendingSessionId, agent: config.routing.targetAgent, text: prompt, directory: config.basePath });
9283
9387
  const nextMetadata = clearClickUpPendingSessionMetadata(setClickUpSessionMetadata(pendingMetadata, config.routing.metadataKey, pendingSessionId), config.routing.metadataKey);
9284
9388
  await clickupClient.updateTaskMetadata({ taskId, fieldId: config.routing.metadataFieldId, value: nextMetadata });
9285
- await sendSessionEvent(openCodeClient, { sessionId: pendingSessionId, agent: config.routing.targetAgent, text: prompt, directory: config.basePath });
9286
9389
  const { [taskId]: _completedPending, ...remainingPending } = stateToPersist.pendingSessions || {};
9287
9390
  stateToPersist = { ...stateToPersist, pendingSessions: remainingPending };
9288
9391
  return finish({ ok: true, action: "created_session", taskId, sessionId: pendingSessionId, eventKey });
@@ -9309,25 +9412,36 @@ function clickUpWebhookExpectedPath(config) {
9309
9412
  return "/";
9310
9413
  }
9311
9414
  }
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;
9415
+ async function handleClickUpWebhookRequest({ method = "POST", url = null, headers = {}, rawBody = "", config, state, worktree, clickupClient, openCodeClient, saveState, now = () => /* @__PURE__ */ new Date() } = {}) {
9416
+ let payload = null;
9417
+ let handled = null;
9418
+ const finish = (result) => {
9419
+ handled = result;
9420
+ writeClickUpWebhookAuditLog({ method, url, headers, rawBody, config, state, handled, payload, at: now() });
9421
+ return result;
9422
+ };
9324
9423
  try {
9325
- payload = JSON.parse(Buffer.isBuffer(rawBody) ? rawBody.toString("utf8") : String(rawBody));
9326
- } catch {
9327
- return { ok: false, status: 400, reason: "invalid_json" };
9424
+ if (method !== "POST") return finish({ ok: false, status: 405, reason: "method_not_allowed" });
9425
+ if (url !== null) {
9426
+ const requestPath = new URL(String(url), config?.webhook?.publicUrl || "http://localhost").pathname;
9427
+ if (requestPath !== clickUpWebhookExpectedPath(config)) return finish({ ok: false, status: 404, reason: "wrong_path" });
9428
+ }
9429
+ const maxBodyBytes = Number(config?.webhook?.maxBodyBytes || CLICKUP_WEBHOOK_MAX_BODY_BYTES);
9430
+ const bodyBytes = Buffer.isBuffer(rawBody) ? rawBody.length : Buffer.byteLength(String(rawBody || ""));
9431
+ if (bodyBytes > maxBodyBytes) return finish({ ok: false, status: 413, reason: "body_too_large" });
9432
+ const signature = headers["x-signature"] || headers["X-Signature"];
9433
+ if (!verifyClickUpSignature(rawBody, signature, state?.secret)) return finish({ ok: false, status: 401, reason: "invalid_signature" });
9434
+ try {
9435
+ payload = JSON.parse(Buffer.isBuffer(rawBody) ? rawBody.toString("utf8") : String(rawBody));
9436
+ } catch {
9437
+ return finish({ ok: false, status: 400, reason: "invalid_json" });
9438
+ }
9439
+ const result = await routeClickUpWebhookEvent({ payload, config, state, worktree, clickupClient, openCodeClient, saveState });
9440
+ return finish({ ok: result.ok, status: result.ok ? 200 : 422, result });
9441
+ } catch (error) {
9442
+ writeClickUpWebhookAuditLog({ method, url, headers, rawBody, config, state, handled, error, payload, at: now() });
9443
+ throw error;
9328
9444
  }
9329
- const result = await routeClickUpWebhookEvent({ payload, config, state, worktree, clickupClient, openCodeClient, saveState });
9330
- return { ok: result.ok, status: result.ok ? 200 : 422, result };
9331
9445
  }
9332
9446
  function clickUpListenerKey(config) {
9333
9447
  return `${config?.webhook?.bindHost || "127.0.0.1"}:${config?.webhook?.bindPort || 0}`;
@@ -11019,7 +11133,7 @@ Follow-up: use optima_prompt_workflow with session_id '${sessionId}' to check in
11019
11133
  }
11020
11134
  };
11021
11135
  }
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 };
11136
+ OptimaPlugin.__internals = { buildClickUpApplyPayloadResult, buildClickUpCreateSubtasksPayload, buildClickUpStartTaskPayload, buildClickUpSummaryPayload, buildClickUpTransitionPayload, buildOptimaAgents, clickUpCommentMentionsProductManager, clickUpWebhookAuditLogDir, clickUpWebhookAuditLogPath, createClickUpApiClient, ensureClickUpWebhookSubscription, handleClickUpWebhookRequest, isClickUpWebhookStateActive, normalizeClickUpWebhookConfig, normalizeClickUpWebhookLogLevel, readClickUpWebhookState, resolveOptimaPluginOptions, resolveSecretReference, routeClickUpWebhookEvent, sendOpenCodeSessionEvent, 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
11137
  export {
11024
11138
  OptimaPlugin as default
11025
11139
  };
@@ -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(),
@@ -9195,10 +9214,26 @@ async function createOpenCodeSession(client, { title, directory } = {}) {
9195
9214
  const result = await client.session.create({ query: { directory }, body: { title } });
9196
9215
  return result?.data?.id || result?.id;
9197
9216
  }
9217
+ async function callOpenCodePromptWithPathFallback(method, sessionId, payload) {
9218
+ try {
9219
+ return await method({ ...payload, path: { sessionID: sessionId } });
9220
+ } catch (error) {
9221
+ try {
9222
+ return await method({ ...payload, path: { id: sessionId } });
9223
+ } catch {
9224
+ throw error;
9225
+ }
9226
+ }
9227
+ }
9198
9228
  async function sendOpenCodeSessionEvent(client, { sessionId, agent, text, directory } = {}) {
9199
- const payload = { path: { id: sessionId }, query: { directory }, body: { agent, parts: [{ type: "text", text }] } };
9200
- if (typeof client?.session?.promptAsync === "function") return client.session.promptAsync(payload);
9201
- return client.session.prompt(payload);
9229
+ const payload = { query: { directory }, body: { agent, parts: [{ type: "text", text }] } };
9230
+ if (typeof client?.session?.prompt === "function") {
9231
+ return callOpenCodePromptWithPathFallback(client.session.prompt.bind(client.session), sessionId, payload);
9232
+ }
9233
+ if (typeof client?.session?.promptAsync === "function") {
9234
+ return callOpenCodePromptWithPathFallback(client.session.promptAsync.bind(client.session), sessionId, payload);
9235
+ }
9236
+ throw new Error("OpenCode client does not expose session.prompt or session.promptAsync.");
9202
9237
  }
9203
9238
  function formatClickUpWebhookPrompt({ eventType, taskId, payload }) {
9204
9239
  const comment = clickUpCommentFromPayload(payload);
@@ -9219,6 +9254,74 @@ function appendClickUpWebhookLocalLog(worktree, entry) {
9219
9254
  fs2.appendFileSync(logPath, `${JSON.stringify(safeEntry)}
9220
9255
  `, "utf8");
9221
9256
  }
9257
+ function redactClickUpWebhookAuditValue(value, secretValues = []) {
9258
+ if (typeof value === "string") {
9259
+ return secretValues.reduce((next, secret) => secret ? next.split(secret).join(CLICKUP_WEBHOOK_REDACTED) : next, value);
9260
+ }
9261
+ if (Array.isArray(value)) return value.map((item) => redactClickUpWebhookAuditValue(item, secretValues));
9262
+ if (!isPlainObject(value)) return value;
9263
+ return Object.fromEntries(Object.entries(value).map(([key, entryValue]) => {
9264
+ const normalizedKey = normalizeLooseToken(key);
9265
+ const secretKey = normalizedKey.includes("signature") || normalizedKey.includes("authorization") || normalizedKey.includes("cookie") || normalizedKey.includes("token") || normalizedKey.includes("secret") || normalizedKey.includes("api-key") || normalizedKey.includes("apikey");
9266
+ return [key, secretKey ? CLICKUP_WEBHOOK_REDACTED : redactClickUpWebhookAuditValue(entryValue, secretValues)];
9267
+ }));
9268
+ }
9269
+ function buildClickUpWebhookAuditSummary({ method, url, config, handled, error, payload, requestFile, at = /* @__PURE__ */ new Date() } = {}) {
9270
+ const result = handled?.result || {};
9271
+ const summary = {
9272
+ at: at.toISOString(),
9273
+ method,
9274
+ path: url === null ? null : new URL(String(url || "/"), config?.webhook?.publicUrl || "http://localhost").pathname,
9275
+ status: handled?.status || 500,
9276
+ ok: Boolean(handled?.ok),
9277
+ reason: handled?.reason || result.reason || (error ? "handler_error" : void 0),
9278
+ action: result.action,
9279
+ event: payload ? clickUpEventType(payload) : void 0,
9280
+ task: result.taskId || clickUpTaskIdFromPayload(payload || {}),
9281
+ comment: clickUpCommentFromPayload(payload || {})?.id || payload?.comment_id || payload?.commentId,
9282
+ session: result.sessionId,
9283
+ base_path: config?.basePath || "",
9284
+ request_file: requestFile || void 0
9285
+ };
9286
+ return Object.fromEntries(Object.entries(summary).filter(([, value]) => value !== void 0 && value !== ""));
9287
+ }
9288
+ function writeClickUpWebhookAuditLog({ method, url, headers = {}, rawBody = "", config, state = {}, handled = null, error = null, payload = null, at = /* @__PURE__ */ new Date() } = {}) {
9289
+ try {
9290
+ const level = normalizeClickUpWebhookLogLevel(config?.log);
9291
+ const failed = Boolean(error || !handled?.ok || (handled?.status || 500) >= 400);
9292
+ if (level === "error" && !failed) return;
9293
+ const logDir = clickUpWebhookAuditLogDir();
9294
+ fs2.mkdirSync(logDir, { recursive: true });
9295
+ const secretValues = [resolveSecretReference(config?.apiToken), state?.secret, config?.webhook?.secret].filter(Boolean);
9296
+ let requestFile;
9297
+ if (level === "verbose") {
9298
+ const absoluteRequestFile = clickUpWebhookRequestFilePath(at, logDir);
9299
+ fs2.mkdirSync(path2.dirname(absoluteRequestFile), { recursive: true });
9300
+ requestFile = path2.relative(logDir, absoluteRequestFile).split(path2.sep).join("/");
9301
+ const parsedBody = payload || (() => {
9302
+ try {
9303
+ return JSON.parse(Buffer.isBuffer(rawBody) ? rawBody.toString("utf8") : String(rawBody || ""));
9304
+ } catch {
9305
+ return void 0;
9306
+ }
9307
+ })();
9308
+ const requestArtifact = redactClickUpWebhookAuditValue({
9309
+ at: at.toISOString(),
9310
+ method,
9311
+ url,
9312
+ path: url === null ? null : new URL(String(url || "/"), config?.webhook?.publicUrl || "http://localhost").pathname,
9313
+ headers,
9314
+ body: parsedBody === void 0 ? Buffer.isBuffer(rawBody) ? rawBody.toString("utf8") : String(rawBody || "") : parsedBody
9315
+ }, secretValues);
9316
+ fs2.writeFileSync(absoluteRequestFile, `${JSON.stringify(requestArtifact, null, 2)}
9317
+ `, "utf8");
9318
+ }
9319
+ const summary = redactClickUpWebhookAuditValue(buildClickUpWebhookAuditSummary({ method, url, config, handled, error, payload, requestFile, at }), secretValues);
9320
+ fs2.appendFileSync(clickUpWebhookAuditLogPath(at, logDir), `${JSON.stringify(summary)}
9321
+ `, "utf8");
9322
+ } catch {
9323
+ }
9324
+ }
9222
9325
  async function withClickUpTaskRouteLock(taskId, operation) {
9223
9326
  const key = String(taskId || "");
9224
9327
  if (!key) return operation();
@@ -9287,9 +9390,9 @@ async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, w
9287
9390
  appendClickUpWebhookLocalLog(worktree, { type: "pending_session_metadata_failed", taskId, sessionId: pendingSessionId, message: error.message });
9288
9391
  throw error;
9289
9392
  }
9393
+ await sendSessionEvent(openCodeClient, { sessionId: pendingSessionId, agent: config.routing.targetAgent, text: prompt, directory: config.basePath });
9290
9394
  const nextMetadata = clearClickUpPendingSessionMetadata(setClickUpSessionMetadata(pendingMetadata, config.routing.metadataKey, pendingSessionId), config.routing.metadataKey);
9291
9395
  await clickupClient.updateTaskMetadata({ taskId, fieldId: config.routing.metadataFieldId, value: nextMetadata });
9292
- await sendSessionEvent(openCodeClient, { sessionId: pendingSessionId, agent: config.routing.targetAgent, text: prompt, directory: config.basePath });
9293
9396
  const { [taskId]: _completedPending, ...remainingPending } = stateToPersist.pendingSessions || {};
9294
9397
  stateToPersist = { ...stateToPersist, pendingSessions: remainingPending };
9295
9398
  return finish({ ok: true, action: "created_session", taskId, sessionId: pendingSessionId, eventKey });
@@ -9316,25 +9419,36 @@ function clickUpWebhookExpectedPath(config) {
9316
9419
  return "/";
9317
9420
  }
9318
9421
  }
9319
- async function handleClickUpWebhookRequest({ method = "POST", url = null, headers = {}, rawBody = "", config, state, worktree, clickupClient, openCodeClient, saveState } = {}) {
9320
- if (method !== "POST") return { ok: false, status: 405, reason: "method_not_allowed" };
9321
- if (url !== null) {
9322
- const requestPath = new URL(String(url), config?.webhook?.publicUrl || "http://localhost").pathname;
9323
- if (requestPath !== clickUpWebhookExpectedPath(config)) return { ok: false, status: 404, reason: "wrong_path" };
9324
- }
9325
- const maxBodyBytes = Number(config?.webhook?.maxBodyBytes || CLICKUP_WEBHOOK_MAX_BODY_BYTES);
9326
- const bodyBytes = Buffer.isBuffer(rawBody) ? rawBody.length : Buffer.byteLength(String(rawBody || ""));
9327
- if (bodyBytes > maxBodyBytes) return { ok: false, status: 413, reason: "body_too_large" };
9328
- const signature = headers["x-signature"] || headers["X-Signature"];
9329
- if (!verifyClickUpSignature(rawBody, signature, state?.secret)) return { ok: false, status: 401, reason: "invalid_signature" };
9330
- let payload;
9422
+ async function handleClickUpWebhookRequest({ method = "POST", url = null, headers = {}, rawBody = "", config, state, worktree, clickupClient, openCodeClient, saveState, now = () => /* @__PURE__ */ new Date() } = {}) {
9423
+ let payload = null;
9424
+ let handled = null;
9425
+ const finish = (result) => {
9426
+ handled = result;
9427
+ writeClickUpWebhookAuditLog({ method, url, headers, rawBody, config, state, handled, payload, at: now() });
9428
+ return result;
9429
+ };
9331
9430
  try {
9332
- payload = JSON.parse(Buffer.isBuffer(rawBody) ? rawBody.toString("utf8") : String(rawBody));
9333
- } catch {
9334
- return { ok: false, status: 400, reason: "invalid_json" };
9431
+ if (method !== "POST") return finish({ ok: false, status: 405, reason: "method_not_allowed" });
9432
+ if (url !== null) {
9433
+ const requestPath = new URL(String(url), config?.webhook?.publicUrl || "http://localhost").pathname;
9434
+ if (requestPath !== clickUpWebhookExpectedPath(config)) return finish({ ok: false, status: 404, reason: "wrong_path" });
9435
+ }
9436
+ const maxBodyBytes = Number(config?.webhook?.maxBodyBytes || CLICKUP_WEBHOOK_MAX_BODY_BYTES);
9437
+ const bodyBytes = Buffer.isBuffer(rawBody) ? rawBody.length : Buffer.byteLength(String(rawBody || ""));
9438
+ if (bodyBytes > maxBodyBytes) return finish({ ok: false, status: 413, reason: "body_too_large" });
9439
+ const signature = headers["x-signature"] || headers["X-Signature"];
9440
+ if (!verifyClickUpSignature(rawBody, signature, state?.secret)) return finish({ ok: false, status: 401, reason: "invalid_signature" });
9441
+ try {
9442
+ payload = JSON.parse(Buffer.isBuffer(rawBody) ? rawBody.toString("utf8") : String(rawBody));
9443
+ } catch {
9444
+ return finish({ ok: false, status: 400, reason: "invalid_json" });
9445
+ }
9446
+ const result = await routeClickUpWebhookEvent({ payload, config, state, worktree, clickupClient, openCodeClient, saveState });
9447
+ return finish({ ok: result.ok, status: result.ok ? 200 : 422, result });
9448
+ } catch (error) {
9449
+ writeClickUpWebhookAuditLog({ method, url, headers, rawBody, config, state, handled, error, payload, at: now() });
9450
+ throw error;
9335
9451
  }
9336
- const result = await routeClickUpWebhookEvent({ payload, config, state, worktree, clickupClient, openCodeClient, saveState });
9337
- return { ok: result.ok, status: result.ok ? 200 : 422, result };
9338
9452
  }
9339
9453
  function clickUpListenerKey(config) {
9340
9454
  return `${config?.webhook?.bindHost || "127.0.0.1"}:${config?.webhook?.bindPort || 0}`;
@@ -11026,7 +11140,7 @@ Follow-up: use optima_prompt_workflow with session_id '${sessionId}' to check in
11026
11140
  }
11027
11141
  };
11028
11142
  }
11029
- 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 };
11143
+ OptimaPlugin.__internals = { buildClickUpApplyPayloadResult, buildClickUpCreateSubtasksPayload, buildClickUpStartTaskPayload, buildClickUpSummaryPayload, buildClickUpTransitionPayload, buildOptimaAgents, clickUpCommentMentionsProductManager, clickUpWebhookAuditLogDir, clickUpWebhookAuditLogPath, createClickUpApiClient, ensureClickUpWebhookSubscription, handleClickUpWebhookRequest, isClickUpWebhookStateActive, normalizeClickUpWebhookConfig, normalizeClickUpWebhookLogLevel, readClickUpWebhookState, resolveOptimaPluginOptions, resolveSecretReference, routeClickUpWebhookEvent, sendOpenCodeSessionEvent, 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 };
11030
11144
 
11031
11145
  // src/sanitize_cli.js
11032
11146
  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.12",
3
+ "version": "0.1.14",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+ssh://git@github.com/defend-tech/opencode-optima.git"