@defend-tech/opencode-optima 0.1.15 → 0.1.17

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
@@ -8017,6 +8017,9 @@ var REPO_LOCAL_POLICIES_README = [
8017
8017
  ].join("\n");
8018
8018
  var activeWorkflows = /* @__PURE__ */ new Map();
8019
8019
  var activeClickUpWebhookListeners = /* @__PURE__ */ new Map();
8020
+ var activeClickUpWebhookLifecycleRegistry = /* @__PURE__ */ new Map();
8021
+ var CLICKUP_WEBHOOK_CLEANUP_TIMEOUT_MS = 8e3;
8022
+ var CLICKUP_WEBHOOK_SIGNAL_STATE = Symbol.for("opencode-optima.clickup-webhook.signal-state");
8020
8023
  var activeClickUpTaskRoutes = /* @__PURE__ */ new Map();
8021
8024
  var objectIdentityMap = /* @__PURE__ */ new WeakMap();
8022
8025
  var objectIdentitySequence = 0;
@@ -9041,8 +9044,13 @@ async function ensureClickUpWebhookSubscription({ validation, worktree, clickupC
9041
9044
  const existingValidation = await validateClickUpWebhookState(existing, config, clickupClient);
9042
9045
  if (existingValidation.valid) {
9043
9046
  const state = writeClickUpWebhookState(worktree, { ...existingValidation.state, recentEventKeys: existing.recentEventKeys || [] }, config);
9047
+ clickUpWebhookLifecycleLog(worktree, { type: "remote_webhook_reused", webhookId: state.webhookId, mode: existingValidation.mode });
9044
9048
  return { active: true, valid: true, mode: existingValidation.mode, limitation: existingValidation.limitation, state };
9045
9049
  }
9050
+ if (existing.webhookId && clickupClient?.deleteWebhook) {
9051
+ await deleteClickUpWebhookBestEffort({ webhookId: existing.webhookId, clickupClient, worktree, reason: existingValidation.reason || "startup_self_heal" });
9052
+ markClickUpWebhookInactive(worktree, existing, config);
9053
+ }
9046
9054
  if (!clickupClient?.createWebhook) {
9047
9055
  return { active: false, valid: false, reason: "clickup_client_unavailable", state: existing };
9048
9056
  }
@@ -9062,15 +9070,12 @@ async function ensureClickUpWebhookSubscription({ validation, worktree, clickupC
9062
9070
  }
9063
9071
  }
9064
9072
  if (!createdValidation.valid) {
9065
- if (stateForValidation.webhookId && clickupClient?.deleteWebhook) {
9066
- try {
9067
- await clickupClient.deleteWebhook(stateForValidation.webhookId);
9068
- } catch {
9069
- }
9070
- }
9073
+ await deleteClickUpWebhookBestEffort({ webhookId: stateForValidation.webhookId, clickupClient, worktree, reason: "created_state_invalid" });
9074
+ clickUpWebhookLifecycleLog(worktree, { type: "remote_webhook_create_invalid", webhookId: stateForValidation.webhookId, reason: createdValidation.reason });
9071
9075
  return { active: false, valid: false, reason: "created_state_invalid", validation: createdValidation, state: stateForValidation };
9072
9076
  }
9073
9077
  const next = writeClickUpWebhookState(worktree, createdValidation.state || stateForValidation, config);
9078
+ clickUpWebhookLifecycleLog(worktree, { type: "remote_webhook_created", webhookId: next.webhookId, mode: createdValidation.mode || "created" });
9074
9079
  const mode = createdValidation.mode === "created_pending_remote_validation" ? "created_pending_remote_validation" : clickupClient?.listWebhooks ? "created_remote_validated" : "created";
9075
9080
  return { active: true, valid: true, mode, limitation: createdValidation.limitation, state: next };
9076
9081
  }
@@ -9253,6 +9258,93 @@ function appendClickUpWebhookLocalLog(worktree, entry) {
9253
9258
  fs2.appendFileSync(logPath, `${JSON.stringify(safeEntry)}
9254
9259
  `, "utf8");
9255
9260
  }
9261
+ function clickUpWebhookLifecycleLog(worktree, entry) {
9262
+ try {
9263
+ appendClickUpWebhookLocalLog(worktree, { scope: "lifecycle", ...entry });
9264
+ } catch {
9265
+ }
9266
+ }
9267
+ function promiseWithTimeout(promise, timeoutMs, timeoutValue) {
9268
+ let timeout;
9269
+ return Promise.race([
9270
+ Promise.resolve(promise),
9271
+ new Promise((resolve) => {
9272
+ timeout = setTimeout(() => resolve(timeoutValue), Math.max(1, Number(timeoutMs) || CLICKUP_WEBHOOK_CLEANUP_TIMEOUT_MS));
9273
+ })
9274
+ ]).finally(() => clearTimeout(timeout));
9275
+ }
9276
+ function closeClickUpWebhookServer(server) {
9277
+ if (!server || typeof server.close !== "function") return Promise.resolve({ ok: true, skipped: true });
9278
+ return new Promise((resolve) => {
9279
+ try {
9280
+ server.close((error) => resolve(error ? { ok: false, error: error.message } : { ok: true }));
9281
+ } catch (error) {
9282
+ resolve({ ok: false, error: error.message });
9283
+ }
9284
+ });
9285
+ }
9286
+ function managedClickUpWebhookKey({ worktree, state, config } = {}) {
9287
+ return [path2.resolve(worktree || process.cwd()), state?.webhookId || "", config?.webhook?.publicUrl || ""].join("|");
9288
+ }
9289
+ async function deleteClickUpWebhookBestEffort({ webhookId, clickupClient, worktree, reason = "cleanup" } = {}) {
9290
+ const id = String(webhookId || "").trim();
9291
+ if (!id || !clickupClient?.deleteWebhook) return { ok: false, skipped: true, reason: "delete_unavailable" };
9292
+ try {
9293
+ await clickupClient.deleteWebhook(id);
9294
+ clickUpWebhookLifecycleLog(worktree, { type: "remote_webhook_deleted", reason, webhookId: id });
9295
+ return { ok: true };
9296
+ } catch (error) {
9297
+ clickUpWebhookLifecycleLog(worktree, { type: "remote_webhook_delete_failed", reason, webhookId: id, message: error.message });
9298
+ return { ok: false, error: error.message };
9299
+ }
9300
+ }
9301
+ function markClickUpWebhookInactive(worktree, state = {}, config = null) {
9302
+ if (!worktree || !state?.webhookId) return null;
9303
+ return writeClickUpWebhookState(worktree, { ...state, active: false, listener: {}, lastValidatedAt: (/* @__PURE__ */ new Date()).toISOString() }, config);
9304
+ }
9305
+ async function cleanupManagedClickUpWebhook(entry = {}, { timeoutMs = CLICKUP_WEBHOOK_CLEANUP_TIMEOUT_MS, reason = "shutdown" } = {}) {
9306
+ if (!entry || entry.cleaning) return { ok: true, skipped: true, reason: "already_cleaning" };
9307
+ entry.cleaning = true;
9308
+ const cleanup = (async () => {
9309
+ clickUpWebhookLifecycleLog(entry.worktree, { type: "cleanup_started", reason, key: entry.key, webhookId: entry.state?.webhookId });
9310
+ const closeResult = await closeClickUpWebhookServer(entry.listener?.server);
9311
+ if (entry.listenerRegistry && entry.listener?.key) entry.listenerRegistry.delete(entry.listener.key);
9312
+ const deleteResult = await deleteClickUpWebhookBestEffort({ webhookId: entry.state?.webhookId, clickupClient: entry.clickupClient, worktree: entry.worktree, reason });
9313
+ if (deleteResult.ok) markClickUpWebhookInactive(entry.worktree, entry.state, entry.config);
9314
+ activeClickUpWebhookLifecycleRegistry.delete(entry.key);
9315
+ clickUpWebhookLifecycleLog(entry.worktree, { type: "cleanup_finished", reason, close_ok: closeResult.ok, delete_ok: deleteResult.ok, delete_reason: deleteResult.reason });
9316
+ return { ok: closeResult.ok !== false && deleteResult.ok !== false, closeResult, deleteResult };
9317
+ })();
9318
+ const result = await promiseWithTimeout(cleanup, timeoutMs, { ok: false, timeout: true, reason: "cleanup_timeout" });
9319
+ if (result?.timeout) clickUpWebhookLifecycleLog(entry.worktree, { type: "cleanup_timeout", reason, webhookId: entry.state?.webhookId });
9320
+ return result;
9321
+ }
9322
+ function registerClickUpWebhookLifecycle({ config, state, worktree, clickupClient, listener, listenerRegistry = activeClickUpWebhookListeners } = {}) {
9323
+ if (!isClickUpWebhookStateActive(state, config)) return null;
9324
+ const key = managedClickUpWebhookKey({ worktree, state, config });
9325
+ const entry = { key, config, state, worktree, clickupClient, listener, listenerRegistry, registeredAt: (/* @__PURE__ */ new Date()).toISOString() };
9326
+ activeClickUpWebhookLifecycleRegistry.set(key, entry);
9327
+ installClickUpWebhookSignalHandlers();
9328
+ clickUpWebhookLifecycleLog(worktree, { type: "lifecycle_registered", key, webhookId: state.webhookId });
9329
+ return entry;
9330
+ }
9331
+ function installClickUpWebhookSignalHandlers() {
9332
+ const globalState = globalThis[CLICKUP_WEBHOOK_SIGNAL_STATE] || { installed: false, running: false };
9333
+ if (globalState.installed) return;
9334
+ const handler = (signal) => {
9335
+ if (globalState.running) return;
9336
+ globalState.running = true;
9337
+ const timeoutMs = CLICKUP_WEBHOOK_CLEANUP_TIMEOUT_MS;
9338
+ const cleanup = Promise.allSettled([...activeClickUpWebhookLifecycleRegistry.values()].map((entry) => cleanupManagedClickUpWebhook(entry, { timeoutMs, reason: signal })));
9339
+ promiseWithTimeout(cleanup, timeoutMs + 500, { timeout: true }).finally(() => {
9340
+ process.exit(signal === "SIGINT" ? 130 : 143);
9341
+ });
9342
+ };
9343
+ process.once("SIGTERM", handler);
9344
+ process.once("SIGINT", handler);
9345
+ globalState.installed = true;
9346
+ globalThis[CLICKUP_WEBHOOK_SIGNAL_STATE] = globalState;
9347
+ }
9256
9348
  function redactClickUpWebhookAuditValue(value, secretValues = []) {
9257
9349
  if (typeof value === "string") {
9258
9350
  return secretValues.reduce((next, secret) => secret ? next.split(secret).join(CLICKUP_WEBHOOK_REDACTED) : next, value);
@@ -10566,25 +10658,35 @@ async function OptimaPlugin(input = {}, pluginOptions = {}) {
10566
10658
  let clickUpWebhookActive = clickUpWebhookRuntime.active === true && clickUpWebhookRuntime.valid === true;
10567
10659
  if (clickUpWebhookActive && input.startClickUpWebhookListener !== false) {
10568
10660
  try {
10661
+ const lifecycleClickUpClient = input.clickupClient || createClickUpApiClient(clickUpWebhookValidation.config, input.fetch);
10662
+ const listenerRegistry = input.clickUpWebhookListenerRegistry || activeClickUpWebhookListeners;
10663
+ const listenerState = clickUpWebhookRuntime.state || readClickUpWebhookState(worktree, clickUpWebhookValidation.config);
10569
10664
  const listener = startClickUpWebhookListener({
10570
10665
  config: clickUpWebhookValidation.config,
10571
- state: clickUpWebhookRuntime.state || readClickUpWebhookState(worktree, clickUpWebhookValidation.config),
10666
+ state: listenerState,
10572
10667
  worktree,
10573
- clickupClient: input.clickupClient || createClickUpApiClient(clickUpWebhookValidation.config, input.fetch),
10668
+ clickupClient: lifecycleClickUpClient,
10574
10669
  openCodeClient: input.client,
10575
- listenerRegistry: input.clickUpWebhookListenerRegistry || activeClickUpWebhookListeners
10670
+ listenerRegistry
10576
10671
  });
10577
10672
  const readyListener = listener.readyPromise ? await listener.readyPromise : listener;
10578
10673
  if (readyListener.active && readyListener.ready !== false) {
10579
- writeClickUpWebhookState(worktree, {
10580
- ...clickUpWebhookRuntime.state || readClickUpWebhookState(worktree, clickUpWebhookValidation.config),
10674
+ const activeState = writeClickUpWebhookState(worktree, {
10675
+ ...listenerState,
10581
10676
  listener: { bindHost: clickUpWebhookValidation.config.webhook.bindHost, bindPort: clickUpWebhookValidation.config.webhook.bindPort, startedAt: (/* @__PURE__ */ new Date()).toISOString() }
10582
10677
  }, clickUpWebhookValidation.config);
10678
+ registerClickUpWebhookLifecycle({ config: clickUpWebhookValidation.config, state: activeState, worktree, clickupClient: lifecycleClickUpClient, listener: readyListener, listenerRegistry });
10583
10679
  } else {
10680
+ await deleteClickUpWebhookBestEffort({ webhookId: listenerState.webhookId, clickupClient: lifecycleClickUpClient, worktree, reason: readyListener.reason || "listener_unavailable" });
10681
+ markClickUpWebhookInactive(worktree, listenerState, clickUpWebhookValidation.config);
10584
10682
  clickUpWebhookActive = false;
10585
10683
  clickUpWebhookRuntime = { ...clickUpWebhookRuntime, active: false, valid: false, reason: readyListener.reason || "listener_unavailable" };
10586
10684
  }
10587
10685
  } catch (error) {
10686
+ const failureState = clickUpWebhookRuntime.state || readClickUpWebhookState(worktree, clickUpWebhookValidation.config);
10687
+ const failureClient = input.clickupClient || createClickUpApiClient(clickUpWebhookValidation.config, input.fetch);
10688
+ await deleteClickUpWebhookBestEffort({ webhookId: failureState.webhookId, clickupClient: failureClient, worktree, reason: "listener_failed" });
10689
+ markClickUpWebhookInactive(worktree, failureState, clickUpWebhookValidation.config);
10588
10690
  clickUpWebhookActive = false;
10589
10691
  clickUpWebhookRuntime = { ...clickUpWebhookRuntime, active: false, valid: false, reason: "listener_failed", error: error.message };
10590
10692
  appendClickUpWebhookLocalLog(worktree, { type: "listener_failed", message: error.message });
@@ -11139,7 +11241,7 @@ Follow-up: use optima_prompt_workflow with session_id '${sessionId}' to check in
11139
11241
  }
11140
11242
  };
11141
11243
  }
11142
- 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 };
11244
+ OptimaPlugin.__internals = { BUNDLE_AGENTS_DIR, activeClickUpWebhookLifecycleRegistry, cleanupManagedClickUpWebhook, deleteClickUpWebhookBestEffort, BUNDLE_POLICIES_DIR, 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 };
11143
11245
  export {
11144
11246
  OptimaPlugin as default
11145
11247
  };
@@ -8024,6 +8024,9 @@ var REPO_LOCAL_POLICIES_README = [
8024
8024
  ].join("\n");
8025
8025
  var activeWorkflows = /* @__PURE__ */ new Map();
8026
8026
  var activeClickUpWebhookListeners = /* @__PURE__ */ new Map();
8027
+ var activeClickUpWebhookLifecycleRegistry = /* @__PURE__ */ new Map();
8028
+ var CLICKUP_WEBHOOK_CLEANUP_TIMEOUT_MS = 8e3;
8029
+ var CLICKUP_WEBHOOK_SIGNAL_STATE = Symbol.for("opencode-optima.clickup-webhook.signal-state");
8027
8030
  var activeClickUpTaskRoutes = /* @__PURE__ */ new Map();
8028
8031
  var objectIdentityMap = /* @__PURE__ */ new WeakMap();
8029
8032
  var objectIdentitySequence = 0;
@@ -9048,8 +9051,13 @@ async function ensureClickUpWebhookSubscription({ validation, worktree, clickupC
9048
9051
  const existingValidation = await validateClickUpWebhookState(existing, config, clickupClient);
9049
9052
  if (existingValidation.valid) {
9050
9053
  const state = writeClickUpWebhookState(worktree, { ...existingValidation.state, recentEventKeys: existing.recentEventKeys || [] }, config);
9054
+ clickUpWebhookLifecycleLog(worktree, { type: "remote_webhook_reused", webhookId: state.webhookId, mode: existingValidation.mode });
9051
9055
  return { active: true, valid: true, mode: existingValidation.mode, limitation: existingValidation.limitation, state };
9052
9056
  }
9057
+ if (existing.webhookId && clickupClient?.deleteWebhook) {
9058
+ await deleteClickUpWebhookBestEffort({ webhookId: existing.webhookId, clickupClient, worktree, reason: existingValidation.reason || "startup_self_heal" });
9059
+ markClickUpWebhookInactive(worktree, existing, config);
9060
+ }
9053
9061
  if (!clickupClient?.createWebhook) {
9054
9062
  return { active: false, valid: false, reason: "clickup_client_unavailable", state: existing };
9055
9063
  }
@@ -9069,15 +9077,12 @@ async function ensureClickUpWebhookSubscription({ validation, worktree, clickupC
9069
9077
  }
9070
9078
  }
9071
9079
  if (!createdValidation.valid) {
9072
- if (stateForValidation.webhookId && clickupClient?.deleteWebhook) {
9073
- try {
9074
- await clickupClient.deleteWebhook(stateForValidation.webhookId);
9075
- } catch {
9076
- }
9077
- }
9080
+ await deleteClickUpWebhookBestEffort({ webhookId: stateForValidation.webhookId, clickupClient, worktree, reason: "created_state_invalid" });
9081
+ clickUpWebhookLifecycleLog(worktree, { type: "remote_webhook_create_invalid", webhookId: stateForValidation.webhookId, reason: createdValidation.reason });
9078
9082
  return { active: false, valid: false, reason: "created_state_invalid", validation: createdValidation, state: stateForValidation };
9079
9083
  }
9080
9084
  const next = writeClickUpWebhookState(worktree, createdValidation.state || stateForValidation, config);
9085
+ clickUpWebhookLifecycleLog(worktree, { type: "remote_webhook_created", webhookId: next.webhookId, mode: createdValidation.mode || "created" });
9081
9086
  const mode = createdValidation.mode === "created_pending_remote_validation" ? "created_pending_remote_validation" : clickupClient?.listWebhooks ? "created_remote_validated" : "created";
9082
9087
  return { active: true, valid: true, mode, limitation: createdValidation.limitation, state: next };
9083
9088
  }
@@ -9260,6 +9265,93 @@ function appendClickUpWebhookLocalLog(worktree, entry) {
9260
9265
  fs2.appendFileSync(logPath, `${JSON.stringify(safeEntry)}
9261
9266
  `, "utf8");
9262
9267
  }
9268
+ function clickUpWebhookLifecycleLog(worktree, entry) {
9269
+ try {
9270
+ appendClickUpWebhookLocalLog(worktree, { scope: "lifecycle", ...entry });
9271
+ } catch {
9272
+ }
9273
+ }
9274
+ function promiseWithTimeout(promise, timeoutMs, timeoutValue) {
9275
+ let timeout;
9276
+ return Promise.race([
9277
+ Promise.resolve(promise),
9278
+ new Promise((resolve) => {
9279
+ timeout = setTimeout(() => resolve(timeoutValue), Math.max(1, Number(timeoutMs) || CLICKUP_WEBHOOK_CLEANUP_TIMEOUT_MS));
9280
+ })
9281
+ ]).finally(() => clearTimeout(timeout));
9282
+ }
9283
+ function closeClickUpWebhookServer(server) {
9284
+ if (!server || typeof server.close !== "function") return Promise.resolve({ ok: true, skipped: true });
9285
+ return new Promise((resolve) => {
9286
+ try {
9287
+ server.close((error) => resolve(error ? { ok: false, error: error.message } : { ok: true }));
9288
+ } catch (error) {
9289
+ resolve({ ok: false, error: error.message });
9290
+ }
9291
+ });
9292
+ }
9293
+ function managedClickUpWebhookKey({ worktree, state, config } = {}) {
9294
+ return [path2.resolve(worktree || process.cwd()), state?.webhookId || "", config?.webhook?.publicUrl || ""].join("|");
9295
+ }
9296
+ async function deleteClickUpWebhookBestEffort({ webhookId, clickupClient, worktree, reason = "cleanup" } = {}) {
9297
+ const id = String(webhookId || "").trim();
9298
+ if (!id || !clickupClient?.deleteWebhook) return { ok: false, skipped: true, reason: "delete_unavailable" };
9299
+ try {
9300
+ await clickupClient.deleteWebhook(id);
9301
+ clickUpWebhookLifecycleLog(worktree, { type: "remote_webhook_deleted", reason, webhookId: id });
9302
+ return { ok: true };
9303
+ } catch (error) {
9304
+ clickUpWebhookLifecycleLog(worktree, { type: "remote_webhook_delete_failed", reason, webhookId: id, message: error.message });
9305
+ return { ok: false, error: error.message };
9306
+ }
9307
+ }
9308
+ function markClickUpWebhookInactive(worktree, state = {}, config = null) {
9309
+ if (!worktree || !state?.webhookId) return null;
9310
+ return writeClickUpWebhookState(worktree, { ...state, active: false, listener: {}, lastValidatedAt: (/* @__PURE__ */ new Date()).toISOString() }, config);
9311
+ }
9312
+ async function cleanupManagedClickUpWebhook(entry = {}, { timeoutMs = CLICKUP_WEBHOOK_CLEANUP_TIMEOUT_MS, reason = "shutdown" } = {}) {
9313
+ if (!entry || entry.cleaning) return { ok: true, skipped: true, reason: "already_cleaning" };
9314
+ entry.cleaning = true;
9315
+ const cleanup = (async () => {
9316
+ clickUpWebhookLifecycleLog(entry.worktree, { type: "cleanup_started", reason, key: entry.key, webhookId: entry.state?.webhookId });
9317
+ const closeResult = await closeClickUpWebhookServer(entry.listener?.server);
9318
+ if (entry.listenerRegistry && entry.listener?.key) entry.listenerRegistry.delete(entry.listener.key);
9319
+ const deleteResult = await deleteClickUpWebhookBestEffort({ webhookId: entry.state?.webhookId, clickupClient: entry.clickupClient, worktree: entry.worktree, reason });
9320
+ if (deleteResult.ok) markClickUpWebhookInactive(entry.worktree, entry.state, entry.config);
9321
+ activeClickUpWebhookLifecycleRegistry.delete(entry.key);
9322
+ clickUpWebhookLifecycleLog(entry.worktree, { type: "cleanup_finished", reason, close_ok: closeResult.ok, delete_ok: deleteResult.ok, delete_reason: deleteResult.reason });
9323
+ return { ok: closeResult.ok !== false && deleteResult.ok !== false, closeResult, deleteResult };
9324
+ })();
9325
+ const result = await promiseWithTimeout(cleanup, timeoutMs, { ok: false, timeout: true, reason: "cleanup_timeout" });
9326
+ if (result?.timeout) clickUpWebhookLifecycleLog(entry.worktree, { type: "cleanup_timeout", reason, webhookId: entry.state?.webhookId });
9327
+ return result;
9328
+ }
9329
+ function registerClickUpWebhookLifecycle({ config, state, worktree, clickupClient, listener, listenerRegistry = activeClickUpWebhookListeners } = {}) {
9330
+ if (!isClickUpWebhookStateActive(state, config)) return null;
9331
+ const key = managedClickUpWebhookKey({ worktree, state, config });
9332
+ const entry = { key, config, state, worktree, clickupClient, listener, listenerRegistry, registeredAt: (/* @__PURE__ */ new Date()).toISOString() };
9333
+ activeClickUpWebhookLifecycleRegistry.set(key, entry);
9334
+ installClickUpWebhookSignalHandlers();
9335
+ clickUpWebhookLifecycleLog(worktree, { type: "lifecycle_registered", key, webhookId: state.webhookId });
9336
+ return entry;
9337
+ }
9338
+ function installClickUpWebhookSignalHandlers() {
9339
+ const globalState = globalThis[CLICKUP_WEBHOOK_SIGNAL_STATE] || { installed: false, running: false };
9340
+ if (globalState.installed) return;
9341
+ const handler = (signal) => {
9342
+ if (globalState.running) return;
9343
+ globalState.running = true;
9344
+ const timeoutMs = CLICKUP_WEBHOOK_CLEANUP_TIMEOUT_MS;
9345
+ const cleanup = Promise.allSettled([...activeClickUpWebhookLifecycleRegistry.values()].map((entry) => cleanupManagedClickUpWebhook(entry, { timeoutMs, reason: signal })));
9346
+ promiseWithTimeout(cleanup, timeoutMs + 500, { timeout: true }).finally(() => {
9347
+ process.exit(signal === "SIGINT" ? 130 : 143);
9348
+ });
9349
+ };
9350
+ process.once("SIGTERM", handler);
9351
+ process.once("SIGINT", handler);
9352
+ globalState.installed = true;
9353
+ globalThis[CLICKUP_WEBHOOK_SIGNAL_STATE] = globalState;
9354
+ }
9263
9355
  function redactClickUpWebhookAuditValue(value, secretValues = []) {
9264
9356
  if (typeof value === "string") {
9265
9357
  return secretValues.reduce((next, secret) => secret ? next.split(secret).join(CLICKUP_WEBHOOK_REDACTED) : next, value);
@@ -10573,25 +10665,35 @@ async function OptimaPlugin(input = {}, pluginOptions = {}) {
10573
10665
  let clickUpWebhookActive = clickUpWebhookRuntime.active === true && clickUpWebhookRuntime.valid === true;
10574
10666
  if (clickUpWebhookActive && input.startClickUpWebhookListener !== false) {
10575
10667
  try {
10668
+ const lifecycleClickUpClient = input.clickupClient || createClickUpApiClient(clickUpWebhookValidation.config, input.fetch);
10669
+ const listenerRegistry = input.clickUpWebhookListenerRegistry || activeClickUpWebhookListeners;
10670
+ const listenerState = clickUpWebhookRuntime.state || readClickUpWebhookState(worktree, clickUpWebhookValidation.config);
10576
10671
  const listener = startClickUpWebhookListener({
10577
10672
  config: clickUpWebhookValidation.config,
10578
- state: clickUpWebhookRuntime.state || readClickUpWebhookState(worktree, clickUpWebhookValidation.config),
10673
+ state: listenerState,
10579
10674
  worktree,
10580
- clickupClient: input.clickupClient || createClickUpApiClient(clickUpWebhookValidation.config, input.fetch),
10675
+ clickupClient: lifecycleClickUpClient,
10581
10676
  openCodeClient: input.client,
10582
- listenerRegistry: input.clickUpWebhookListenerRegistry || activeClickUpWebhookListeners
10677
+ listenerRegistry
10583
10678
  });
10584
10679
  const readyListener = listener.readyPromise ? await listener.readyPromise : listener;
10585
10680
  if (readyListener.active && readyListener.ready !== false) {
10586
- writeClickUpWebhookState(worktree, {
10587
- ...clickUpWebhookRuntime.state || readClickUpWebhookState(worktree, clickUpWebhookValidation.config),
10681
+ const activeState = writeClickUpWebhookState(worktree, {
10682
+ ...listenerState,
10588
10683
  listener: { bindHost: clickUpWebhookValidation.config.webhook.bindHost, bindPort: clickUpWebhookValidation.config.webhook.bindPort, startedAt: (/* @__PURE__ */ new Date()).toISOString() }
10589
10684
  }, clickUpWebhookValidation.config);
10685
+ registerClickUpWebhookLifecycle({ config: clickUpWebhookValidation.config, state: activeState, worktree, clickupClient: lifecycleClickUpClient, listener: readyListener, listenerRegistry });
10590
10686
  } else {
10687
+ await deleteClickUpWebhookBestEffort({ webhookId: listenerState.webhookId, clickupClient: lifecycleClickUpClient, worktree, reason: readyListener.reason || "listener_unavailable" });
10688
+ markClickUpWebhookInactive(worktree, listenerState, clickUpWebhookValidation.config);
10591
10689
  clickUpWebhookActive = false;
10592
10690
  clickUpWebhookRuntime = { ...clickUpWebhookRuntime, active: false, valid: false, reason: readyListener.reason || "listener_unavailable" };
10593
10691
  }
10594
10692
  } catch (error) {
10693
+ const failureState = clickUpWebhookRuntime.state || readClickUpWebhookState(worktree, clickUpWebhookValidation.config);
10694
+ const failureClient = input.clickupClient || createClickUpApiClient(clickUpWebhookValidation.config, input.fetch);
10695
+ await deleteClickUpWebhookBestEffort({ webhookId: failureState.webhookId, clickupClient: failureClient, worktree, reason: "listener_failed" });
10696
+ markClickUpWebhookInactive(worktree, failureState, clickUpWebhookValidation.config);
10595
10697
  clickUpWebhookActive = false;
10596
10698
  clickUpWebhookRuntime = { ...clickUpWebhookRuntime, active: false, valid: false, reason: "listener_failed", error: error.message };
10597
10699
  appendClickUpWebhookLocalLog(worktree, { type: "listener_failed", message: error.message });
@@ -11146,7 +11248,7 @@ Follow-up: use optima_prompt_workflow with session_id '${sessionId}' to check in
11146
11248
  }
11147
11249
  };
11148
11250
  }
11149
- 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 };
11251
+ OptimaPlugin.__internals = { BUNDLE_AGENTS_DIR, activeClickUpWebhookLifecycleRegistry, cleanupManagedClickUpWebhook, deleteClickUpWebhookBestEffort, BUNDLE_POLICIES_DIR, 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 };
11150
11252
 
11151
11253
  // src/sanitize_cli.js
11152
11254
  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.15",
3
+ "version": "0.1.17",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+ssh://git@github.com/defend-tech/opencode-optima.git"