@defend-tech/opencode-optima 0.1.19 → 0.1.21
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 +76 -27
- package/dist/sanitize_cli.js +76 -27
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -8831,10 +8831,21 @@ function resolveOptimaPluginOptions(rawInput = {}, pluginOptions = {}) {
|
|
|
8831
8831
|
function pickConfiguredClickUp(rawInput = {}, repoCfg = {}, pluginOptions = {}) {
|
|
8832
8832
|
return pluginOptions?.clickup || pluginOptions?.experimental?.optima?.clickup || rawInput?.options?.clickup || rawInput?.options?.experimental?.optima?.clickup || rawInput?.experimental?.optima?.clickup || rawInput?.config?.experimental?.optima?.clickup || rawInput?.opencode?.experimental?.optima?.clickup || repoCfg?.experimental?.optima?.clickup || repoCfg?.clickup || null;
|
|
8833
8833
|
}
|
|
8834
|
+
function normalizeOpenCodeBaseUrl(value, defaultValue = "http://127.0.0.1:3001") {
|
|
8835
|
+
const candidate = String(value || defaultValue || "").trim();
|
|
8836
|
+
if (!candidate) return "";
|
|
8837
|
+
try {
|
|
8838
|
+
const url = new URL(candidate);
|
|
8839
|
+
return url.toString().replace(/\/+$/, "");
|
|
8840
|
+
} catch {
|
|
8841
|
+
return candidate.replace(/\/+$/, "");
|
|
8842
|
+
}
|
|
8843
|
+
}
|
|
8834
8844
|
function normalizeClickUpWebhookConfig(rawClickUp = null, worktree = process.cwd()) {
|
|
8835
8845
|
const raw = isPlainObject(rawClickUp) ? rawClickUp : {};
|
|
8836
8846
|
const webhook = isPlainObject(raw.webhook) ? raw.webhook : {};
|
|
8837
8847
|
const routing = isPlainObject(raw.routing) ? raw.routing : {};
|
|
8848
|
+
const opencode = isPlainObject(raw.opencode) ? raw.opencode : {};
|
|
8838
8849
|
const location = isPlainObject(webhook.location) ? webhook.location : {};
|
|
8839
8850
|
const events = Array.isArray(webhook.events) && webhook.events.length > 0 ? [...new Set(webhook.events.map((event) => String(event || "").trim()).filter(Boolean))] : [...CLICKUP_WEBHOOK_EVENTS];
|
|
8840
8851
|
const ignoredStatuses = Array.isArray(routing.ignored_statuses) && routing.ignored_statuses.length > 0 ? routing.ignored_statuses : CLICKUP_WEBHOOK_TERMINAL_STATUSES;
|
|
@@ -8844,6 +8855,9 @@ function normalizeClickUpWebhookConfig(rawClickUp = null, worktree = process.cwd
|
|
|
8844
8855
|
teamId: String(raw.team_id || raw.teamId || "").trim(),
|
|
8845
8856
|
apiToken: String(raw.api_token || raw.apiToken || "").trim(),
|
|
8846
8857
|
log: normalizeClickUpWebhookLogLevel(raw.log),
|
|
8858
|
+
opencode: {
|
|
8859
|
+
baseUrl: normalizeOpenCodeBaseUrl(opencode.base_url || opencode.baseUrl || raw.opencode_base_url || raw.opencodeBaseUrl)
|
|
8860
|
+
},
|
|
8847
8861
|
webhook: {
|
|
8848
8862
|
publicUrl: String(webhook.public_url || webhook.publicUrl || "").trim(),
|
|
8849
8863
|
bindHost: String(webhook.bind_host || webhook.bindHost || "127.0.0.1").trim(),
|
|
@@ -8877,6 +8891,13 @@ function normalizeClickUpWebhookConfig(rawClickUp = null, worktree = process.cwd
|
|
|
8877
8891
|
if (!config.webhook.bindHost) errors.push("clickup.webhook.bind_host is required");
|
|
8878
8892
|
if (!Number.isInteger(config.webhook.bindPort) || config.webhook.bindPort <= 0) errors.push("clickup.webhook.bind_port must be a positive integer");
|
|
8879
8893
|
if (!config.webhook.location.list_id && !config.webhook.location.folder_id && !config.webhook.location.space_id) errors.push("one clickup.webhook.location id is required");
|
|
8894
|
+
if (config.opencode.baseUrl) {
|
|
8895
|
+
try {
|
|
8896
|
+
new URL(config.opencode.baseUrl);
|
|
8897
|
+
} catch {
|
|
8898
|
+
errors.push("clickup.opencode.base_url must be a valid URL");
|
|
8899
|
+
}
|
|
8900
|
+
}
|
|
8880
8901
|
const missingEvents = CLICKUP_WEBHOOK_EVENTS.filter((event) => !config.webhook.events.includes(event));
|
|
8881
8902
|
if (missingEvents.length > 0) errors.push(`clickup.webhook.events missing: ${missingEvents.join(", ")}`);
|
|
8882
8903
|
if (config.routing.targetAgent !== "workflow_product_manager") errors.push("clickup.routing.target_agent must be workflow_product_manager");
|
|
@@ -9234,8 +9255,10 @@ async function openCodeSessionExists(client, sessionId) {
|
|
|
9234
9255
|
}
|
|
9235
9256
|
return false;
|
|
9236
9257
|
}
|
|
9237
|
-
async function createOpenCodeSession(client, { title, directory } = {}) {
|
|
9238
|
-
const
|
|
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 });
|
|
9239
9262
|
return result?.data?.id || result?.id;
|
|
9240
9263
|
}
|
|
9241
9264
|
function assertOpenCodePromptAccepted(result) {
|
|
@@ -9246,33 +9269,47 @@ function assertOpenCodePromptAccepted(result) {
|
|
|
9246
9269
|
}
|
|
9247
9270
|
return result;
|
|
9248
9271
|
}
|
|
9249
|
-
async function
|
|
9250
|
-
|
|
9251
|
-
|
|
9252
|
-
|
|
9253
|
-
|
|
9254
|
-
|
|
9255
|
-
|
|
9256
|
-
|
|
9257
|
-
|
|
9272
|
+
async function sendOpenCodeSessionEventDirect({ baseUrl, sessionId, text, fetchImpl = globalThis.fetch } = {}) {
|
|
9273
|
+
if (typeof fetchImpl !== "function") throw new Error("OpenCode direct prompt delivery requires fetch.");
|
|
9274
|
+
const root = normalizeOpenCodeBaseUrl(baseUrl, "");
|
|
9275
|
+
if (!root) throw new Error("OpenCode direct prompt delivery requires a base URL.");
|
|
9276
|
+
const url = `${root}/api/session/${encodeURIComponent(sessionId)}/prompt`;
|
|
9277
|
+
const response = await fetchImpl(url, {
|
|
9278
|
+
method: "POST",
|
|
9279
|
+
headers: { "content-type": "application/json" },
|
|
9280
|
+
body: JSON.stringify({ prompt: { text }, delivery: "queue", resume: true })
|
|
9281
|
+
});
|
|
9282
|
+
const raw = await response.text();
|
|
9283
|
+
let data = null;
|
|
9284
|
+
if (raw) {
|
|
9258
9285
|
try {
|
|
9259
|
-
|
|
9286
|
+
data = JSON.parse(raw);
|
|
9260
9287
|
} catch (error) {
|
|
9261
|
-
|
|
9288
|
+
if (response.ok) throw new Error(`OpenCode prompt response was not JSON: ${error.message}`);
|
|
9262
9289
|
}
|
|
9263
9290
|
}
|
|
9264
|
-
|
|
9291
|
+
if (!response.ok) {
|
|
9292
|
+
const message = data?.error?.message || data?.message || raw || `OpenCode prompt request failed with status ${response.status}.`;
|
|
9293
|
+
throw new Error(message);
|
|
9294
|
+
}
|
|
9295
|
+
if (!data?.data?.id) throw new Error("OpenCode prompt response did not include data.id.");
|
|
9296
|
+
return { ok: true, method: "http", status: response.status, data: data.data, response: data };
|
|
9265
9297
|
}
|
|
9266
|
-
async function sendOpenCodeSessionEvent(client, { sessionId, agent, text, directory } = {}) {
|
|
9298
|
+
async function sendOpenCodeSessionEvent(client, { sessionId, agent, text, directory, opencodeBaseUrl, baseUrl, fetchImpl, direct = false } = {}) {
|
|
9299
|
+
const directBaseUrl = opencodeBaseUrl || baseUrl;
|
|
9267
9300
|
const parts = [{ type: "text", text }];
|
|
9268
|
-
const
|
|
9269
|
-
|
|
9270
|
-
|
|
9271
|
-
|
|
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));
|
|
9272
9308
|
}
|
|
9273
|
-
if (typeof client?.session?.
|
|
9274
|
-
return
|
|
9309
|
+
if (!direct && typeof client?.session?.prompt === "function") {
|
|
9310
|
+
return assertOpenCodePromptAccepted(await client.session.prompt(structuredPayload));
|
|
9275
9311
|
}
|
|
9312
|
+
if (directBaseUrl) return sendOpenCodeSessionEventDirect({ baseUrl: directBaseUrl, sessionId, text, fetchImpl });
|
|
9276
9313
|
throw new Error("OpenCode client does not expose session.prompt or session.promptAsync.");
|
|
9277
9314
|
}
|
|
9278
9315
|
function formatClickUpWebhookPrompt({ eventType, taskId, payload }) {
|
|
@@ -9501,7 +9538,7 @@ async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, w
|
|
|
9501
9538
|
if (!existingSessionId) {
|
|
9502
9539
|
let pendingSessionId = getNestedMetadataValue(metadata, clickUpPendingSessionKey(config.routing.metadataKey)) || state.pendingSessions?.[taskId];
|
|
9503
9540
|
if (!pendingSessionId) {
|
|
9504
|
-
pendingSessionId = await createSession(openCodeClient, { title: `ClickUp PM: ${taskId}`, directory: config.basePath });
|
|
9541
|
+
pendingSessionId = await createSession(openCodeClient, { title: `ClickUp PM: ${taskId}`, directory: config.basePath, agent: config.routing.targetAgent });
|
|
9505
9542
|
if (!String(pendingSessionId || "").startsWith("ses_")) return { ok: false, action: "error", reason: "session_create_failed", taskId };
|
|
9506
9543
|
if (saveState) {
|
|
9507
9544
|
saveState({
|
|
@@ -9517,7 +9554,7 @@ async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, w
|
|
|
9517
9554
|
appendClickUpWebhookLocalLog(worktree, { type: "pending_session_metadata_failed", taskId, sessionId: pendingSessionId, message: error.message });
|
|
9518
9555
|
throw error;
|
|
9519
9556
|
}
|
|
9520
|
-
await sendSessionEvent(openCodeClient, { sessionId: pendingSessionId, agent: config.routing.targetAgent, text: prompt, directory: config.basePath });
|
|
9557
|
+
await sendSessionEvent(openCodeClient, { sessionId: pendingSessionId, agent: config.routing.targetAgent, text: prompt, directory: config.basePath, opencodeBaseUrl: config.opencode?.baseUrl });
|
|
9521
9558
|
const nextMetadata = clearClickUpPendingSessionMetadata(setClickUpSessionMetadata(pendingMetadata, config.routing.metadataKey, pendingSessionId), config.routing.metadataKey);
|
|
9522
9559
|
await clickupClient.updateTaskMetadata({ taskId, fieldId: config.routing.metadataFieldId, value: nextMetadata });
|
|
9523
9560
|
const { [taskId]: _completedPending, ...remainingPending } = stateToPersist.pendingSessions || {};
|
|
@@ -9526,7 +9563,7 @@ async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, w
|
|
|
9526
9563
|
}
|
|
9527
9564
|
const sessionId = String(existingSessionId);
|
|
9528
9565
|
if (await sessionExists(openCodeClient, sessionId)) {
|
|
9529
|
-
await sendSessionEvent(openCodeClient, { sessionId, agent: config.routing.targetAgent, text: prompt, directory: config.basePath });
|
|
9566
|
+
await sendSessionEvent(openCodeClient, { sessionId, agent: config.routing.targetAgent, text: prompt, directory: config.basePath, opencodeBaseUrl: config.opencode?.baseUrl });
|
|
9530
9567
|
return finish({ ok: true, action: "sent_to_existing_session", taskId, sessionId, eventKey });
|
|
9531
9568
|
}
|
|
9532
9569
|
const at = now().toISOString();
|
|
@@ -10555,9 +10592,21 @@ function getModePromptFragment(agentId, operatingTeamMode, worktree) {
|
|
|
10555
10592
|
if (!fragmentPath) return "";
|
|
10556
10593
|
return readResolvedFile(fragmentPath, worktree, { preferCompactPromptDocs: true });
|
|
10557
10594
|
}
|
|
10595
|
+
function isSameOrNestedPath(candidate, root) {
|
|
10596
|
+
if (typeof candidate !== "string" || typeof root !== "string" || !candidate.trim() || !root.trim()) return false;
|
|
10597
|
+
const resolvedCandidate = path2.resolve(candidate);
|
|
10598
|
+
const resolvedRoot = path2.resolve(root);
|
|
10599
|
+
const relative = path2.relative(resolvedRoot, resolvedCandidate);
|
|
10600
|
+
return relative === "" || !!relative && !relative.startsWith("..") && !path2.isAbsolute(relative);
|
|
10601
|
+
}
|
|
10602
|
+
function shouldRegisterWorkflowProductManager(options = {}, worktree = process.cwd()) {
|
|
10603
|
+
if (options.clickUpWebhookActive === true) return true;
|
|
10604
|
+
const validation = options.clickUpWebhookValidation;
|
|
10605
|
+
return validation?.complete === true && isSameOrNestedPath(worktree, validation.config?.basePath);
|
|
10606
|
+
}
|
|
10558
10607
|
function buildOptimaAgents(repoCfg, operatingTeamMode, worktree, debugDir, options = {}) {
|
|
10559
10608
|
const optimaActive = repoCfg && repoCfg.enabled === true;
|
|
10560
|
-
const clickUpWebhookActive = options
|
|
10609
|
+
const clickUpWebhookActive = shouldRegisterWorkflowProductManager(options, worktree);
|
|
10561
10610
|
const repoAgentDefinitions = repoAgentsDir(worktree);
|
|
10562
10611
|
const repoAgentAdditions = repoAgentAdditionsDir(worktree);
|
|
10563
10612
|
const legacyAgentsDir = legacyRepoAgentsDir(worktree);
|
|
@@ -11259,7 +11308,7 @@ Follow-up: use optima_prompt_workflow with session_id '${sessionId}' to check in
|
|
|
11259
11308
|
},
|
|
11260
11309
|
async config(cfg) {
|
|
11261
11310
|
cfg.agent ??= {};
|
|
11262
|
-
const ourAgents = buildOptimaAgents(repoCfg, operatingTeamMode, worktree, debugDir, { clickUpWebhookActive });
|
|
11311
|
+
const ourAgents = buildOptimaAgents(repoCfg, operatingTeamMode, worktree, debugDir, { clickUpWebhookActive, clickUpWebhookValidation });
|
|
11263
11312
|
const builtInAgents = ["build", "plan", "general", "explore"];
|
|
11264
11313
|
const preserveExistingAgents = repoCfg.features?.preserve_existing_agents ?? DEFAULT_PRESERVE_EXISTING_AGENTS;
|
|
11265
11314
|
const allToDisable = preserveExistingAgents ? /* @__PURE__ */ new Set() : /* @__PURE__ */ new Set([...builtInAgents, ...Object.keys(cfg.agent)]);
|
|
@@ -11277,7 +11326,7 @@ Follow-up: use optima_prompt_workflow with session_id '${sessionId}' to check in
|
|
|
11277
11326
|
}
|
|
11278
11327
|
};
|
|
11279
11328
|
}
|
|
11280
|
-
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 };
|
|
11329
|
+
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, normalizeOpenCodeBaseUrl, readClickUpWebhookState, resolveOptimaPluginOptions, resolveSecretReference, routeClickUpWebhookEvent, sendOpenCodeSessionEvent, sendOpenCodeSessionEventDirect, 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 };
|
|
11281
11330
|
export {
|
|
11282
11331
|
OptimaPlugin as default
|
|
11283
11332
|
};
|
package/dist/sanitize_cli.js
CHANGED
|
@@ -8838,10 +8838,21 @@ function resolveOptimaPluginOptions(rawInput = {}, pluginOptions = {}) {
|
|
|
8838
8838
|
function pickConfiguredClickUp(rawInput = {}, repoCfg = {}, pluginOptions = {}) {
|
|
8839
8839
|
return pluginOptions?.clickup || pluginOptions?.experimental?.optima?.clickup || rawInput?.options?.clickup || rawInput?.options?.experimental?.optima?.clickup || rawInput?.experimental?.optima?.clickup || rawInput?.config?.experimental?.optima?.clickup || rawInput?.opencode?.experimental?.optima?.clickup || repoCfg?.experimental?.optima?.clickup || repoCfg?.clickup || null;
|
|
8840
8840
|
}
|
|
8841
|
+
function normalizeOpenCodeBaseUrl(value, defaultValue = "http://127.0.0.1:3001") {
|
|
8842
|
+
const candidate = String(value || defaultValue || "").trim();
|
|
8843
|
+
if (!candidate) return "";
|
|
8844
|
+
try {
|
|
8845
|
+
const url = new URL(candidate);
|
|
8846
|
+
return url.toString().replace(/\/+$/, "");
|
|
8847
|
+
} catch {
|
|
8848
|
+
return candidate.replace(/\/+$/, "");
|
|
8849
|
+
}
|
|
8850
|
+
}
|
|
8841
8851
|
function normalizeClickUpWebhookConfig(rawClickUp = null, worktree = process.cwd()) {
|
|
8842
8852
|
const raw = isPlainObject(rawClickUp) ? rawClickUp : {};
|
|
8843
8853
|
const webhook = isPlainObject(raw.webhook) ? raw.webhook : {};
|
|
8844
8854
|
const routing = isPlainObject(raw.routing) ? raw.routing : {};
|
|
8855
|
+
const opencode = isPlainObject(raw.opencode) ? raw.opencode : {};
|
|
8845
8856
|
const location = isPlainObject(webhook.location) ? webhook.location : {};
|
|
8846
8857
|
const events = Array.isArray(webhook.events) && webhook.events.length > 0 ? [...new Set(webhook.events.map((event) => String(event || "").trim()).filter(Boolean))] : [...CLICKUP_WEBHOOK_EVENTS];
|
|
8847
8858
|
const ignoredStatuses = Array.isArray(routing.ignored_statuses) && routing.ignored_statuses.length > 0 ? routing.ignored_statuses : CLICKUP_WEBHOOK_TERMINAL_STATUSES;
|
|
@@ -8851,6 +8862,9 @@ function normalizeClickUpWebhookConfig(rawClickUp = null, worktree = process.cwd
|
|
|
8851
8862
|
teamId: String(raw.team_id || raw.teamId || "").trim(),
|
|
8852
8863
|
apiToken: String(raw.api_token || raw.apiToken || "").trim(),
|
|
8853
8864
|
log: normalizeClickUpWebhookLogLevel(raw.log),
|
|
8865
|
+
opencode: {
|
|
8866
|
+
baseUrl: normalizeOpenCodeBaseUrl(opencode.base_url || opencode.baseUrl || raw.opencode_base_url || raw.opencodeBaseUrl)
|
|
8867
|
+
},
|
|
8854
8868
|
webhook: {
|
|
8855
8869
|
publicUrl: String(webhook.public_url || webhook.publicUrl || "").trim(),
|
|
8856
8870
|
bindHost: String(webhook.bind_host || webhook.bindHost || "127.0.0.1").trim(),
|
|
@@ -8884,6 +8898,13 @@ function normalizeClickUpWebhookConfig(rawClickUp = null, worktree = process.cwd
|
|
|
8884
8898
|
if (!config.webhook.bindHost) errors.push("clickup.webhook.bind_host is required");
|
|
8885
8899
|
if (!Number.isInteger(config.webhook.bindPort) || config.webhook.bindPort <= 0) errors.push("clickup.webhook.bind_port must be a positive integer");
|
|
8886
8900
|
if (!config.webhook.location.list_id && !config.webhook.location.folder_id && !config.webhook.location.space_id) errors.push("one clickup.webhook.location id is required");
|
|
8901
|
+
if (config.opencode.baseUrl) {
|
|
8902
|
+
try {
|
|
8903
|
+
new URL(config.opencode.baseUrl);
|
|
8904
|
+
} catch {
|
|
8905
|
+
errors.push("clickup.opencode.base_url must be a valid URL");
|
|
8906
|
+
}
|
|
8907
|
+
}
|
|
8887
8908
|
const missingEvents = CLICKUP_WEBHOOK_EVENTS.filter((event) => !config.webhook.events.includes(event));
|
|
8888
8909
|
if (missingEvents.length > 0) errors.push(`clickup.webhook.events missing: ${missingEvents.join(", ")}`);
|
|
8889
8910
|
if (config.routing.targetAgent !== "workflow_product_manager") errors.push("clickup.routing.target_agent must be workflow_product_manager");
|
|
@@ -9241,8 +9262,10 @@ async function openCodeSessionExists(client, sessionId) {
|
|
|
9241
9262
|
}
|
|
9242
9263
|
return false;
|
|
9243
9264
|
}
|
|
9244
|
-
async function createOpenCodeSession(client, { title, directory } = {}) {
|
|
9245
|
-
const
|
|
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 });
|
|
9246
9269
|
return result?.data?.id || result?.id;
|
|
9247
9270
|
}
|
|
9248
9271
|
function assertOpenCodePromptAccepted(result) {
|
|
@@ -9253,33 +9276,47 @@ function assertOpenCodePromptAccepted(result) {
|
|
|
9253
9276
|
}
|
|
9254
9277
|
return result;
|
|
9255
9278
|
}
|
|
9256
|
-
async function
|
|
9257
|
-
|
|
9258
|
-
|
|
9259
|
-
|
|
9260
|
-
|
|
9261
|
-
|
|
9262
|
-
|
|
9263
|
-
|
|
9264
|
-
|
|
9279
|
+
async function sendOpenCodeSessionEventDirect({ baseUrl, sessionId, text, fetchImpl = globalThis.fetch } = {}) {
|
|
9280
|
+
if (typeof fetchImpl !== "function") throw new Error("OpenCode direct prompt delivery requires fetch.");
|
|
9281
|
+
const root = normalizeOpenCodeBaseUrl(baseUrl, "");
|
|
9282
|
+
if (!root) throw new Error("OpenCode direct prompt delivery requires a base URL.");
|
|
9283
|
+
const url = `${root}/api/session/${encodeURIComponent(sessionId)}/prompt`;
|
|
9284
|
+
const response = await fetchImpl(url, {
|
|
9285
|
+
method: "POST",
|
|
9286
|
+
headers: { "content-type": "application/json" },
|
|
9287
|
+
body: JSON.stringify({ prompt: { text }, delivery: "queue", resume: true })
|
|
9288
|
+
});
|
|
9289
|
+
const raw = await response.text();
|
|
9290
|
+
let data = null;
|
|
9291
|
+
if (raw) {
|
|
9265
9292
|
try {
|
|
9266
|
-
|
|
9293
|
+
data = JSON.parse(raw);
|
|
9267
9294
|
} catch (error) {
|
|
9268
|
-
|
|
9295
|
+
if (response.ok) throw new Error(`OpenCode prompt response was not JSON: ${error.message}`);
|
|
9269
9296
|
}
|
|
9270
9297
|
}
|
|
9271
|
-
|
|
9298
|
+
if (!response.ok) {
|
|
9299
|
+
const message = data?.error?.message || data?.message || raw || `OpenCode prompt request failed with status ${response.status}.`;
|
|
9300
|
+
throw new Error(message);
|
|
9301
|
+
}
|
|
9302
|
+
if (!data?.data?.id) throw new Error("OpenCode prompt response did not include data.id.");
|
|
9303
|
+
return { ok: true, method: "http", status: response.status, data: data.data, response: data };
|
|
9272
9304
|
}
|
|
9273
|
-
async function sendOpenCodeSessionEvent(client, { sessionId, agent, text, directory } = {}) {
|
|
9305
|
+
async function sendOpenCodeSessionEvent(client, { sessionId, agent, text, directory, opencodeBaseUrl, baseUrl, fetchImpl, direct = false } = {}) {
|
|
9306
|
+
const directBaseUrl = opencodeBaseUrl || baseUrl;
|
|
9274
9307
|
const parts = [{ type: "text", text }];
|
|
9275
|
-
const
|
|
9276
|
-
|
|
9277
|
-
|
|
9278
|
-
|
|
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));
|
|
9279
9315
|
}
|
|
9280
|
-
if (typeof client?.session?.
|
|
9281
|
-
return
|
|
9316
|
+
if (!direct && typeof client?.session?.prompt === "function") {
|
|
9317
|
+
return assertOpenCodePromptAccepted(await client.session.prompt(structuredPayload));
|
|
9282
9318
|
}
|
|
9319
|
+
if (directBaseUrl) return sendOpenCodeSessionEventDirect({ baseUrl: directBaseUrl, sessionId, text, fetchImpl });
|
|
9283
9320
|
throw new Error("OpenCode client does not expose session.prompt or session.promptAsync.");
|
|
9284
9321
|
}
|
|
9285
9322
|
function formatClickUpWebhookPrompt({ eventType, taskId, payload }) {
|
|
@@ -9508,7 +9545,7 @@ async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, w
|
|
|
9508
9545
|
if (!existingSessionId) {
|
|
9509
9546
|
let pendingSessionId = getNestedMetadataValue(metadata, clickUpPendingSessionKey(config.routing.metadataKey)) || state.pendingSessions?.[taskId];
|
|
9510
9547
|
if (!pendingSessionId) {
|
|
9511
|
-
pendingSessionId = await createSession(openCodeClient, { title: `ClickUp PM: ${taskId}`, directory: config.basePath });
|
|
9548
|
+
pendingSessionId = await createSession(openCodeClient, { title: `ClickUp PM: ${taskId}`, directory: config.basePath, agent: config.routing.targetAgent });
|
|
9512
9549
|
if (!String(pendingSessionId || "").startsWith("ses_")) return { ok: false, action: "error", reason: "session_create_failed", taskId };
|
|
9513
9550
|
if (saveState) {
|
|
9514
9551
|
saveState({
|
|
@@ -9524,7 +9561,7 @@ async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, w
|
|
|
9524
9561
|
appendClickUpWebhookLocalLog(worktree, { type: "pending_session_metadata_failed", taskId, sessionId: pendingSessionId, message: error.message });
|
|
9525
9562
|
throw error;
|
|
9526
9563
|
}
|
|
9527
|
-
await sendSessionEvent(openCodeClient, { sessionId: pendingSessionId, agent: config.routing.targetAgent, text: prompt, directory: config.basePath });
|
|
9564
|
+
await sendSessionEvent(openCodeClient, { sessionId: pendingSessionId, agent: config.routing.targetAgent, text: prompt, directory: config.basePath, opencodeBaseUrl: config.opencode?.baseUrl });
|
|
9528
9565
|
const nextMetadata = clearClickUpPendingSessionMetadata(setClickUpSessionMetadata(pendingMetadata, config.routing.metadataKey, pendingSessionId), config.routing.metadataKey);
|
|
9529
9566
|
await clickupClient.updateTaskMetadata({ taskId, fieldId: config.routing.metadataFieldId, value: nextMetadata });
|
|
9530
9567
|
const { [taskId]: _completedPending, ...remainingPending } = stateToPersist.pendingSessions || {};
|
|
@@ -9533,7 +9570,7 @@ async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, w
|
|
|
9533
9570
|
}
|
|
9534
9571
|
const sessionId = String(existingSessionId);
|
|
9535
9572
|
if (await sessionExists(openCodeClient, sessionId)) {
|
|
9536
|
-
await sendSessionEvent(openCodeClient, { sessionId, agent: config.routing.targetAgent, text: prompt, directory: config.basePath });
|
|
9573
|
+
await sendSessionEvent(openCodeClient, { sessionId, agent: config.routing.targetAgent, text: prompt, directory: config.basePath, opencodeBaseUrl: config.opencode?.baseUrl });
|
|
9537
9574
|
return finish({ ok: true, action: "sent_to_existing_session", taskId, sessionId, eventKey });
|
|
9538
9575
|
}
|
|
9539
9576
|
const at = now().toISOString();
|
|
@@ -10562,9 +10599,21 @@ function getModePromptFragment(agentId, operatingTeamMode, worktree) {
|
|
|
10562
10599
|
if (!fragmentPath) return "";
|
|
10563
10600
|
return readResolvedFile(fragmentPath, worktree, { preferCompactPromptDocs: true });
|
|
10564
10601
|
}
|
|
10602
|
+
function isSameOrNestedPath(candidate, root) {
|
|
10603
|
+
if (typeof candidate !== "string" || typeof root !== "string" || !candidate.trim() || !root.trim()) return false;
|
|
10604
|
+
const resolvedCandidate = path2.resolve(candidate);
|
|
10605
|
+
const resolvedRoot = path2.resolve(root);
|
|
10606
|
+
const relative = path2.relative(resolvedRoot, resolvedCandidate);
|
|
10607
|
+
return relative === "" || !!relative && !relative.startsWith("..") && !path2.isAbsolute(relative);
|
|
10608
|
+
}
|
|
10609
|
+
function shouldRegisterWorkflowProductManager(options = {}, worktree = process.cwd()) {
|
|
10610
|
+
if (options.clickUpWebhookActive === true) return true;
|
|
10611
|
+
const validation = options.clickUpWebhookValidation;
|
|
10612
|
+
return validation?.complete === true && isSameOrNestedPath(worktree, validation.config?.basePath);
|
|
10613
|
+
}
|
|
10565
10614
|
function buildOptimaAgents(repoCfg, operatingTeamMode, worktree, debugDir, options = {}) {
|
|
10566
10615
|
const optimaActive = repoCfg && repoCfg.enabled === true;
|
|
10567
|
-
const clickUpWebhookActive = options
|
|
10616
|
+
const clickUpWebhookActive = shouldRegisterWorkflowProductManager(options, worktree);
|
|
10568
10617
|
const repoAgentDefinitions = repoAgentsDir(worktree);
|
|
10569
10618
|
const repoAgentAdditions = repoAgentAdditionsDir(worktree);
|
|
10570
10619
|
const legacyAgentsDir = legacyRepoAgentsDir(worktree);
|
|
@@ -11266,7 +11315,7 @@ Follow-up: use optima_prompt_workflow with session_id '${sessionId}' to check in
|
|
|
11266
11315
|
},
|
|
11267
11316
|
async config(cfg) {
|
|
11268
11317
|
cfg.agent ??= {};
|
|
11269
|
-
const ourAgents = buildOptimaAgents(repoCfg, operatingTeamMode, worktree, debugDir, { clickUpWebhookActive });
|
|
11318
|
+
const ourAgents = buildOptimaAgents(repoCfg, operatingTeamMode, worktree, debugDir, { clickUpWebhookActive, clickUpWebhookValidation });
|
|
11270
11319
|
const builtInAgents = ["build", "plan", "general", "explore"];
|
|
11271
11320
|
const preserveExistingAgents = repoCfg.features?.preserve_existing_agents ?? DEFAULT_PRESERVE_EXISTING_AGENTS;
|
|
11272
11321
|
const allToDisable = preserveExistingAgents ? /* @__PURE__ */ new Set() : /* @__PURE__ */ new Set([...builtInAgents, ...Object.keys(cfg.agent)]);
|
|
@@ -11284,7 +11333,7 @@ Follow-up: use optima_prompt_workflow with session_id '${sessionId}' to check in
|
|
|
11284
11333
|
}
|
|
11285
11334
|
};
|
|
11286
11335
|
}
|
|
11287
|
-
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 };
|
|
11336
|
+
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, normalizeOpenCodeBaseUrl, readClickUpWebhookState, resolveOptimaPluginOptions, resolveSecretReference, routeClickUpWebhookEvent, sendOpenCodeSessionEvent, sendOpenCodeSessionEventDirect, 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 };
|
|
11288
11337
|
|
|
11289
11338
|
// src/sanitize_cli.js
|
|
11290
11339
|
var { migrateLegacyOptimaLayout: migrateLegacyOptimaLayout2 } = OptimaPlugin.__internals;
|