@defend-tech/opencode-optima 0.1.20 → 0.1.22

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
@@ -9255,8 +9255,10 @@ async function openCodeSessionExists(client, sessionId) {
9255
9255
  }
9256
9256
  return false;
9257
9257
  }
9258
- async function createOpenCodeSession(client, { title, directory } = {}) {
9259
- const result = await client.session.create({ query: { directory }, body: { title } });
9258
+ async function createOpenCodeSession(client, { title, directory, agent } = {}) {
9259
+ const body = { title };
9260
+ if (agent) body.agent = agent;
9261
+ const result = await client.session.create({ query: { directory }, body });
9260
9262
  return result?.data?.id || result?.id;
9261
9263
  }
9262
9264
  function assertOpenCodePromptAccepted(result) {
@@ -9293,35 +9295,21 @@ async function sendOpenCodeSessionEventDirect({ baseUrl, sessionId, text, fetchI
9293
9295
  if (!data?.data?.id) throw new Error("OpenCode prompt response did not include data.id.");
9294
9296
  return { ok: true, method: "http", status: response.status, data: data.data, response: data };
9295
9297
  }
9296
- async function callOpenCodePromptWithFallbacks(method, sessionId, flatPayload, structuredPayload) {
9297
- const attempts = [
9298
- { id: sessionId, ...flatPayload },
9299
- { sessionID: sessionId, ...flatPayload },
9300
- { ...structuredPayload, path: { sessionID: sessionId } },
9301
- { ...structuredPayload, path: { id: sessionId } }
9302
- ];
9303
- let firstError = null;
9304
- for (const attempt of attempts) {
9305
- try {
9306
- return assertOpenCodePromptAccepted(await method(attempt));
9307
- } catch (error) {
9308
- firstError ??= error;
9309
- }
9310
- }
9311
- throw firstError;
9312
- }
9313
- async function sendOpenCodeSessionEvent(client, { sessionId, agent, text, directory, opencodeBaseUrl, baseUrl, fetchImpl } = {}) {
9298
+ async function sendOpenCodeSessionEvent(client, { sessionId, agent, text, directory, opencodeBaseUrl, baseUrl, fetchImpl, direct = false } = {}) {
9314
9299
  const directBaseUrl = opencodeBaseUrl || baseUrl;
9315
- if (directBaseUrl) return sendOpenCodeSessionEventDirect({ baseUrl: directBaseUrl, sessionId, text, fetchImpl });
9316
9300
  const parts = [{ type: "text", text }];
9317
- const flatPayload = { directory, agent, parts };
9318
- const structuredPayload = { query: { directory }, body: { agent, parts } };
9319
- if (typeof client?.session?.prompt === "function") {
9320
- return callOpenCodePromptWithFallbacks(client.session.prompt.bind(client.session), sessionId, flatPayload, structuredPayload);
9301
+ const structuredPayload = {
9302
+ path: { id: sessionId },
9303
+ query: { directory },
9304
+ body: { agent, parts }
9305
+ };
9306
+ if (!direct && typeof client?.session?.promptAsync === "function") {
9307
+ return assertOpenCodePromptAccepted(await client.session.promptAsync(structuredPayload));
9321
9308
  }
9322
- if (typeof client?.session?.promptAsync === "function") {
9323
- return callOpenCodePromptWithFallbacks(client.session.promptAsync.bind(client.session), sessionId, flatPayload, structuredPayload);
9309
+ if (!direct && typeof client?.session?.prompt === "function") {
9310
+ return assertOpenCodePromptAccepted(await client.session.prompt(structuredPayload));
9324
9311
  }
9312
+ if (directBaseUrl) return sendOpenCodeSessionEventDirect({ baseUrl: directBaseUrl, sessionId, text, fetchImpl });
9325
9313
  throw new Error("OpenCode client does not expose session.prompt or session.promptAsync.");
9326
9314
  }
9327
9315
  function formatClickUpWebhookPrompt({ eventType, taskId, payload }) {
@@ -9394,11 +9382,12 @@ async function cleanupManagedClickUpWebhook(entry = {}, { timeoutMs = CLICKUP_WE
9394
9382
  clickUpWebhookLifecycleLog(entry.worktree, { type: "cleanup_started", reason, key: entry.key, webhookId: entry.state?.webhookId });
9395
9383
  const closeResult = await closeClickUpWebhookServer(entry.listener?.server);
9396
9384
  if (entry.listenerRegistry && entry.listener?.key) entry.listenerRegistry.delete(entry.listener.key);
9397
- const deleteResult = await deleteClickUpWebhookBestEffort({ webhookId: entry.state?.webhookId, clickupClient: entry.clickupClient, worktree: entry.worktree, reason });
9398
- if (deleteResult.ok) markClickUpWebhookInactive(entry.worktree, entry.state, entry.config);
9385
+ const preservedResult = { ok: true, skipped: true, reason: "remote_webhook_preserved" };
9386
+ clickUpWebhookLifecycleLog(entry.worktree, { type: "remote_webhook_preserved", reason, webhookId: entry.state?.webhookId });
9387
+ markClickUpWebhookInactive(entry.worktree, entry.state, entry.config);
9399
9388
  activeClickUpWebhookLifecycleRegistry.delete(entry.key);
9400
- clickUpWebhookLifecycleLog(entry.worktree, { type: "cleanup_finished", reason, close_ok: closeResult.ok, delete_ok: deleteResult.ok, delete_reason: deleteResult.reason });
9401
- return { ok: closeResult.ok !== false && deleteResult.ok !== false, closeResult, deleteResult };
9389
+ clickUpWebhookLifecycleLog(entry.worktree, { type: "cleanup_finished", reason, close_ok: closeResult.ok, remote_webhook: "preserved" });
9390
+ return { ok: closeResult.ok !== false, closeResult, deleteResult: preservedResult, preserveResult: preservedResult };
9402
9391
  })();
9403
9392
  const result = await promiseWithTimeout(cleanup, timeoutMs, { ok: false, timeout: true, reason: "cleanup_timeout" });
9404
9393
  if (result?.timeout) clickUpWebhookLifecycleLog(entry.worktree, { type: "cleanup_timeout", reason, webhookId: entry.state?.webhookId });
@@ -9550,7 +9539,7 @@ async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, w
9550
9539
  if (!existingSessionId) {
9551
9540
  let pendingSessionId = getNestedMetadataValue(metadata, clickUpPendingSessionKey(config.routing.metadataKey)) || state.pendingSessions?.[taskId];
9552
9541
  if (!pendingSessionId) {
9553
- pendingSessionId = await createSession(openCodeClient, { title: `ClickUp PM: ${taskId}`, directory: config.basePath });
9542
+ pendingSessionId = await createSession(openCodeClient, { title: `ClickUp PM: ${taskId}`, directory: config.basePath, agent: config.routing.targetAgent });
9554
9543
  if (!String(pendingSessionId || "").startsWith("ses_")) return { ok: false, action: "error", reason: "session_create_failed", taskId };
9555
9544
  if (saveState) {
9556
9545
  saveState({
@@ -10604,9 +10593,21 @@ function getModePromptFragment(agentId, operatingTeamMode, worktree) {
10604
10593
  if (!fragmentPath) return "";
10605
10594
  return readResolvedFile(fragmentPath, worktree, { preferCompactPromptDocs: true });
10606
10595
  }
10596
+ function isSameOrNestedPath(candidate, root) {
10597
+ if (typeof candidate !== "string" || typeof root !== "string" || !candidate.trim() || !root.trim()) return false;
10598
+ const resolvedCandidate = path2.resolve(candidate);
10599
+ const resolvedRoot = path2.resolve(root);
10600
+ const relative = path2.relative(resolvedRoot, resolvedCandidate);
10601
+ return relative === "" || !!relative && !relative.startsWith("..") && !path2.isAbsolute(relative);
10602
+ }
10603
+ function shouldRegisterWorkflowProductManager(options = {}, worktree = process.cwd()) {
10604
+ if (options.clickUpWebhookActive === true) return true;
10605
+ const validation = options.clickUpWebhookValidation;
10606
+ return validation?.complete === true && isSameOrNestedPath(worktree, validation.config?.basePath);
10607
+ }
10607
10608
  function buildOptimaAgents(repoCfg, operatingTeamMode, worktree, debugDir, options = {}) {
10608
10609
  const optimaActive = repoCfg && repoCfg.enabled === true;
10609
- const clickUpWebhookActive = options.clickUpWebhookActive === true;
10610
+ const clickUpWebhookActive = shouldRegisterWorkflowProductManager(options, worktree);
10610
10611
  const repoAgentDefinitions = repoAgentsDir(worktree);
10611
10612
  const repoAgentAdditions = repoAgentAdditionsDir(worktree);
10612
10613
  const legacyAgentsDir = legacyRepoAgentsDir(worktree);
@@ -10762,15 +10763,14 @@ async function OptimaPlugin(input = {}, pluginOptions = {}) {
10762
10763
  }, clickUpWebhookValidation.config);
10763
10764
  registerClickUpWebhookLifecycle({ config: clickUpWebhookValidation.config, state: activeState, worktree, clickupClient: lifecycleClickUpClient, listener: readyListener, listenerRegistry });
10764
10765
  } else {
10765
- await deleteClickUpWebhookBestEffort({ webhookId: listenerState.webhookId, clickupClient: lifecycleClickUpClient, worktree, reason: readyListener.reason || "listener_unavailable" });
10766
+ clickUpWebhookLifecycleLog(worktree, { type: "remote_webhook_preserved", reason: readyListener.reason || "listener_unavailable", webhookId: listenerState.webhookId });
10766
10767
  markClickUpWebhookInactive(worktree, listenerState, clickUpWebhookValidation.config);
10767
10768
  clickUpWebhookActive = false;
10768
10769
  clickUpWebhookRuntime = { ...clickUpWebhookRuntime, active: false, valid: false, reason: readyListener.reason || "listener_unavailable" };
10769
10770
  }
10770
10771
  } catch (error) {
10771
10772
  const failureState = clickUpWebhookRuntime.state || readClickUpWebhookState(worktree, clickUpWebhookValidation.config);
10772
- const failureClient = input.clickupClient || createClickUpApiClient(clickUpWebhookValidation.config, input.fetch);
10773
- await deleteClickUpWebhookBestEffort({ webhookId: failureState.webhookId, clickupClient: failureClient, worktree, reason: "listener_failed" });
10773
+ clickUpWebhookLifecycleLog(worktree, { type: "remote_webhook_preserved", reason: "listener_failed", webhookId: failureState.webhookId });
10774
10774
  markClickUpWebhookInactive(worktree, failureState, clickUpWebhookValidation.config);
10775
10775
  clickUpWebhookActive = false;
10776
10776
  clickUpWebhookRuntime = { ...clickUpWebhookRuntime, active: false, valid: false, reason: "listener_failed", error: error.message };
@@ -11308,7 +11308,7 @@ Follow-up: use optima_prompt_workflow with session_id '${sessionId}' to check in
11308
11308
  },
11309
11309
  async config(cfg) {
11310
11310
  cfg.agent ??= {};
11311
- const ourAgents = buildOptimaAgents(repoCfg, operatingTeamMode, worktree, debugDir, { clickUpWebhookActive });
11311
+ const ourAgents = buildOptimaAgents(repoCfg, operatingTeamMode, worktree, debugDir, { clickUpWebhookActive, clickUpWebhookValidation });
11312
11312
  const builtInAgents = ["build", "plan", "general", "explore"];
11313
11313
  const preserveExistingAgents = repoCfg.features?.preserve_existing_agents ?? DEFAULT_PRESERVE_EXISTING_AGENTS;
11314
11314
  const allToDisable = preserveExistingAgents ? /* @__PURE__ */ new Set() : /* @__PURE__ */ new Set([...builtInAgents, ...Object.keys(cfg.agent)]);
@@ -9262,8 +9262,10 @@ async function openCodeSessionExists(client, sessionId) {
9262
9262
  }
9263
9263
  return false;
9264
9264
  }
9265
- async function createOpenCodeSession(client, { title, directory } = {}) {
9266
- const result = await client.session.create({ query: { directory }, body: { title } });
9265
+ async function createOpenCodeSession(client, { title, directory, agent } = {}) {
9266
+ const body = { title };
9267
+ if (agent) body.agent = agent;
9268
+ const result = await client.session.create({ query: { directory }, body });
9267
9269
  return result?.data?.id || result?.id;
9268
9270
  }
9269
9271
  function assertOpenCodePromptAccepted(result) {
@@ -9300,35 +9302,21 @@ async function sendOpenCodeSessionEventDirect({ baseUrl, sessionId, text, fetchI
9300
9302
  if (!data?.data?.id) throw new Error("OpenCode prompt response did not include data.id.");
9301
9303
  return { ok: true, method: "http", status: response.status, data: data.data, response: data };
9302
9304
  }
9303
- async function callOpenCodePromptWithFallbacks(method, sessionId, flatPayload, structuredPayload) {
9304
- const attempts = [
9305
- { id: sessionId, ...flatPayload },
9306
- { sessionID: sessionId, ...flatPayload },
9307
- { ...structuredPayload, path: { sessionID: sessionId } },
9308
- { ...structuredPayload, path: { id: sessionId } }
9309
- ];
9310
- let firstError = null;
9311
- for (const attempt of attempts) {
9312
- try {
9313
- return assertOpenCodePromptAccepted(await method(attempt));
9314
- } catch (error) {
9315
- firstError ??= error;
9316
- }
9317
- }
9318
- throw firstError;
9319
- }
9320
- async function sendOpenCodeSessionEvent(client, { sessionId, agent, text, directory, opencodeBaseUrl, baseUrl, fetchImpl } = {}) {
9305
+ async function sendOpenCodeSessionEvent(client, { sessionId, agent, text, directory, opencodeBaseUrl, baseUrl, fetchImpl, direct = false } = {}) {
9321
9306
  const directBaseUrl = opencodeBaseUrl || baseUrl;
9322
- if (directBaseUrl) return sendOpenCodeSessionEventDirect({ baseUrl: directBaseUrl, sessionId, text, fetchImpl });
9323
9307
  const parts = [{ type: "text", text }];
9324
- const flatPayload = { directory, agent, parts };
9325
- const structuredPayload = { query: { directory }, body: { agent, parts } };
9326
- if (typeof client?.session?.prompt === "function") {
9327
- return callOpenCodePromptWithFallbacks(client.session.prompt.bind(client.session), sessionId, flatPayload, structuredPayload);
9308
+ const structuredPayload = {
9309
+ path: { id: sessionId },
9310
+ query: { directory },
9311
+ body: { agent, parts }
9312
+ };
9313
+ if (!direct && typeof client?.session?.promptAsync === "function") {
9314
+ return assertOpenCodePromptAccepted(await client.session.promptAsync(structuredPayload));
9328
9315
  }
9329
- if (typeof client?.session?.promptAsync === "function") {
9330
- return callOpenCodePromptWithFallbacks(client.session.promptAsync.bind(client.session), sessionId, flatPayload, structuredPayload);
9316
+ if (!direct && typeof client?.session?.prompt === "function") {
9317
+ return assertOpenCodePromptAccepted(await client.session.prompt(structuredPayload));
9331
9318
  }
9319
+ if (directBaseUrl) return sendOpenCodeSessionEventDirect({ baseUrl: directBaseUrl, sessionId, text, fetchImpl });
9332
9320
  throw new Error("OpenCode client does not expose session.prompt or session.promptAsync.");
9333
9321
  }
9334
9322
  function formatClickUpWebhookPrompt({ eventType, taskId, payload }) {
@@ -9401,11 +9389,12 @@ async function cleanupManagedClickUpWebhook(entry = {}, { timeoutMs = CLICKUP_WE
9401
9389
  clickUpWebhookLifecycleLog(entry.worktree, { type: "cleanup_started", reason, key: entry.key, webhookId: entry.state?.webhookId });
9402
9390
  const closeResult = await closeClickUpWebhookServer(entry.listener?.server);
9403
9391
  if (entry.listenerRegistry && entry.listener?.key) entry.listenerRegistry.delete(entry.listener.key);
9404
- const deleteResult = await deleteClickUpWebhookBestEffort({ webhookId: entry.state?.webhookId, clickupClient: entry.clickupClient, worktree: entry.worktree, reason });
9405
- if (deleteResult.ok) markClickUpWebhookInactive(entry.worktree, entry.state, entry.config);
9392
+ const preservedResult = { ok: true, skipped: true, reason: "remote_webhook_preserved" };
9393
+ clickUpWebhookLifecycleLog(entry.worktree, { type: "remote_webhook_preserved", reason, webhookId: entry.state?.webhookId });
9394
+ markClickUpWebhookInactive(entry.worktree, entry.state, entry.config);
9406
9395
  activeClickUpWebhookLifecycleRegistry.delete(entry.key);
9407
- clickUpWebhookLifecycleLog(entry.worktree, { type: "cleanup_finished", reason, close_ok: closeResult.ok, delete_ok: deleteResult.ok, delete_reason: deleteResult.reason });
9408
- return { ok: closeResult.ok !== false && deleteResult.ok !== false, closeResult, deleteResult };
9396
+ clickUpWebhookLifecycleLog(entry.worktree, { type: "cleanup_finished", reason, close_ok: closeResult.ok, remote_webhook: "preserved" });
9397
+ return { ok: closeResult.ok !== false, closeResult, deleteResult: preservedResult, preserveResult: preservedResult };
9409
9398
  })();
9410
9399
  const result = await promiseWithTimeout(cleanup, timeoutMs, { ok: false, timeout: true, reason: "cleanup_timeout" });
9411
9400
  if (result?.timeout) clickUpWebhookLifecycleLog(entry.worktree, { type: "cleanup_timeout", reason, webhookId: entry.state?.webhookId });
@@ -9557,7 +9546,7 @@ async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, w
9557
9546
  if (!existingSessionId) {
9558
9547
  let pendingSessionId = getNestedMetadataValue(metadata, clickUpPendingSessionKey(config.routing.metadataKey)) || state.pendingSessions?.[taskId];
9559
9548
  if (!pendingSessionId) {
9560
- pendingSessionId = await createSession(openCodeClient, { title: `ClickUp PM: ${taskId}`, directory: config.basePath });
9549
+ pendingSessionId = await createSession(openCodeClient, { title: `ClickUp PM: ${taskId}`, directory: config.basePath, agent: config.routing.targetAgent });
9561
9550
  if (!String(pendingSessionId || "").startsWith("ses_")) return { ok: false, action: "error", reason: "session_create_failed", taskId };
9562
9551
  if (saveState) {
9563
9552
  saveState({
@@ -10611,9 +10600,21 @@ function getModePromptFragment(agentId, operatingTeamMode, worktree) {
10611
10600
  if (!fragmentPath) return "";
10612
10601
  return readResolvedFile(fragmentPath, worktree, { preferCompactPromptDocs: true });
10613
10602
  }
10603
+ function isSameOrNestedPath(candidate, root) {
10604
+ if (typeof candidate !== "string" || typeof root !== "string" || !candidate.trim() || !root.trim()) return false;
10605
+ const resolvedCandidate = path2.resolve(candidate);
10606
+ const resolvedRoot = path2.resolve(root);
10607
+ const relative = path2.relative(resolvedRoot, resolvedCandidate);
10608
+ return relative === "" || !!relative && !relative.startsWith("..") && !path2.isAbsolute(relative);
10609
+ }
10610
+ function shouldRegisterWorkflowProductManager(options = {}, worktree = process.cwd()) {
10611
+ if (options.clickUpWebhookActive === true) return true;
10612
+ const validation = options.clickUpWebhookValidation;
10613
+ return validation?.complete === true && isSameOrNestedPath(worktree, validation.config?.basePath);
10614
+ }
10614
10615
  function buildOptimaAgents(repoCfg, operatingTeamMode, worktree, debugDir, options = {}) {
10615
10616
  const optimaActive = repoCfg && repoCfg.enabled === true;
10616
- const clickUpWebhookActive = options.clickUpWebhookActive === true;
10617
+ const clickUpWebhookActive = shouldRegisterWorkflowProductManager(options, worktree);
10617
10618
  const repoAgentDefinitions = repoAgentsDir(worktree);
10618
10619
  const repoAgentAdditions = repoAgentAdditionsDir(worktree);
10619
10620
  const legacyAgentsDir = legacyRepoAgentsDir(worktree);
@@ -10769,15 +10770,14 @@ async function OptimaPlugin(input = {}, pluginOptions = {}) {
10769
10770
  }, clickUpWebhookValidation.config);
10770
10771
  registerClickUpWebhookLifecycle({ config: clickUpWebhookValidation.config, state: activeState, worktree, clickupClient: lifecycleClickUpClient, listener: readyListener, listenerRegistry });
10771
10772
  } else {
10772
- await deleteClickUpWebhookBestEffort({ webhookId: listenerState.webhookId, clickupClient: lifecycleClickUpClient, worktree, reason: readyListener.reason || "listener_unavailable" });
10773
+ clickUpWebhookLifecycleLog(worktree, { type: "remote_webhook_preserved", reason: readyListener.reason || "listener_unavailable", webhookId: listenerState.webhookId });
10773
10774
  markClickUpWebhookInactive(worktree, listenerState, clickUpWebhookValidation.config);
10774
10775
  clickUpWebhookActive = false;
10775
10776
  clickUpWebhookRuntime = { ...clickUpWebhookRuntime, active: false, valid: false, reason: readyListener.reason || "listener_unavailable" };
10776
10777
  }
10777
10778
  } catch (error) {
10778
10779
  const failureState = clickUpWebhookRuntime.state || readClickUpWebhookState(worktree, clickUpWebhookValidation.config);
10779
- const failureClient = input.clickupClient || createClickUpApiClient(clickUpWebhookValidation.config, input.fetch);
10780
- await deleteClickUpWebhookBestEffort({ webhookId: failureState.webhookId, clickupClient: failureClient, worktree, reason: "listener_failed" });
10780
+ clickUpWebhookLifecycleLog(worktree, { type: "remote_webhook_preserved", reason: "listener_failed", webhookId: failureState.webhookId });
10781
10781
  markClickUpWebhookInactive(worktree, failureState, clickUpWebhookValidation.config);
10782
10782
  clickUpWebhookActive = false;
10783
10783
  clickUpWebhookRuntime = { ...clickUpWebhookRuntime, active: false, valid: false, reason: "listener_failed", error: error.message };
@@ -11315,7 +11315,7 @@ Follow-up: use optima_prompt_workflow with session_id '${sessionId}' to check in
11315
11315
  },
11316
11316
  async config(cfg) {
11317
11317
  cfg.agent ??= {};
11318
- const ourAgents = buildOptimaAgents(repoCfg, operatingTeamMode, worktree, debugDir, { clickUpWebhookActive });
11318
+ const ourAgents = buildOptimaAgents(repoCfg, operatingTeamMode, worktree, debugDir, { clickUpWebhookActive, clickUpWebhookValidation });
11319
11319
  const builtInAgents = ["build", "plan", "general", "explore"];
11320
11320
  const preserveExistingAgents = repoCfg.features?.preserve_existing_agents ?? DEFAULT_PRESERVE_EXISTING_AGENTS;
11321
11321
  const allToDisable = preserveExistingAgents ? /* @__PURE__ */ new Set() : /* @__PURE__ */ new Set([...builtInAgents, ...Object.keys(cfg.agent)]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defend-tech/opencode-optima",
3
- "version": "0.1.20",
3
+ "version": "0.1.22",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+ssh://git@github.com/defend-tech/opencode-optima.git"