@defend-tech/opencode-optima 0.1.59 → 0.1.61
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/assets/agents/workflow_product_manager.md +3 -1
- package/dist/index.js +295 -9
- package/dist/sanitize_cli.js +295 -9
- package/package.json +1 -1
|
@@ -51,7 +51,9 @@ You are Workflow_Product_Manager, Optima's ClickUp-first delivery orchestrator.
|
|
|
51
51
|
|
|
52
52
|
- Principal workspace stays on `dev`; never use `main` for delivery and never push directly to `main`.
|
|
53
53
|
- Do not implement, plan, or write ClickUp task mirrors in the principal workspace. Use task-specific worktrees/branches.
|
|
54
|
-
- To plan a ClickUp task, first create or reuse that task's branch/worktree with `optima_clickup_start_task
|
|
54
|
+
- To plan a ClickUp task, first create or reuse that task's branch/worktree with `optima_clickup_start_task`; webhook-created worktrees must be provisioned or registered through the configured OpenChamber Git API (`clickup.openchamber.base_url` `/api/git/worktrees`) before local `.optima` mirror writes.
|
|
55
|
+
- Use `clickup.openchamber.base_url` only for OpenChamber Git worktree API calls; use `clickup.opencode.base_url` for OpenCode workspace/project sync, visibility checks, and session/prompt delivery.
|
|
56
|
+
- If OpenChamber cannot create or verify the required branch/worktree (especially subtask start-from-parent semantics), fail closed with a ClickUp blocker comment instead of silently using raw `git worktree add`.
|
|
55
57
|
- Do not update the principal `dev` workspace `.optima/tasks/current.md` for ClickUp task planning.
|
|
56
58
|
- Unrelated active tasks in `.optima/tasks/current.md` must not block planning; move to/create the correct task worktree instead.
|
|
57
59
|
- Parent setup pulls remote once; after parent branch creation, subtasks can trust the parent local branch without continuous remote polling.
|
package/dist/index.js
CHANGED
|
@@ -8527,6 +8527,7 @@ var CLICKUP_WEBHOOK_REQUEST_TIMEOUT_MS = 1e4;
|
|
|
8527
8527
|
var CLICKUP_WEBHOOK_STARTUP_TASK_LIMIT = 50;
|
|
8528
8528
|
var CLICKUP_WEBHOOK_STARTUP_COMMENT_LIMIT = 100;
|
|
8529
8529
|
var CLICKUP_WEBHOOK_STARTUP_RECONCILIATION_DELAY_MS = 3e4;
|
|
8530
|
+
var CLICKUP_WORKTREE_FAILURE_COMMENT_DEDUPE_MS = 10 * 60 * 1e3;
|
|
8530
8531
|
var CLICKUP_WEBHOOK_LOG_LEVELS = /* @__PURE__ */ new Set(["error", "info", "verbose"]);
|
|
8531
8532
|
var CLICKUP_WEBHOOK_REDACTED = "[REDACTED]";
|
|
8532
8533
|
var DISCUSSION_BACKFILL_FETCH_LIMIT = 100;
|
|
@@ -9224,6 +9225,239 @@ function addClickUpWorktreeForBranch({ baseWorktree, branch, worktreePath, start
|
|
|
9224
9225
|
runGitFn(baseWorktree, ["worktree", "add", "-b", branch, worktreePath, startPoint]);
|
|
9225
9226
|
}
|
|
9226
9227
|
}
|
|
9228
|
+
function clickUpOpenChamberWorktreeName(branch = "") {
|
|
9229
|
+
return String(branch || "").trim().replace(/\//g, "-");
|
|
9230
|
+
}
|
|
9231
|
+
function openChamberUrl(baseUrl, pathname, query = {}) {
|
|
9232
|
+
const url = new URL(pathname, normalizeOpenCodeBaseUrl(baseUrl));
|
|
9233
|
+
for (const [key, value] of Object.entries(query)) {
|
|
9234
|
+
if (value !== void 0 && value !== null && String(value).trim()) url.searchParams.set(key, String(value));
|
|
9235
|
+
}
|
|
9236
|
+
return url.toString();
|
|
9237
|
+
}
|
|
9238
|
+
async function readOpenChamberJson(response, endpoint, serviceName = "OpenChamber") {
|
|
9239
|
+
const text = await response.text();
|
|
9240
|
+
let data = null;
|
|
9241
|
+
if (text.trim()) {
|
|
9242
|
+
try {
|
|
9243
|
+
data = JSON.parse(text);
|
|
9244
|
+
} catch {
|
|
9245
|
+
throw new Error(`${serviceName} ${endpoint} returned non-JSON response.`);
|
|
9246
|
+
}
|
|
9247
|
+
}
|
|
9248
|
+
if (!response.ok) {
|
|
9249
|
+
const message = data?.error?.message || data?.message || data?.data?.message || text.slice(0, 200) || `HTTP ${response.status}`;
|
|
9250
|
+
throw new Error(`${serviceName} ${endpoint} failed: ${response.status} ${message}`);
|
|
9251
|
+
}
|
|
9252
|
+
return data;
|
|
9253
|
+
}
|
|
9254
|
+
async function requestServiceJson({ baseUrl, serviceName = "OpenChamber", endpoint, method = "GET", directory, body, fetchImpl = globalThis.fetch } = {}) {
|
|
9255
|
+
if (typeof fetchImpl !== "function") throw new Error(`${serviceName} API calls require fetch.`);
|
|
9256
|
+
const response = await fetchImpl(openChamberUrl(baseUrl, endpoint, { directory }), {
|
|
9257
|
+
method,
|
|
9258
|
+
headers: body ? { "content-type": "application/json" } : void 0,
|
|
9259
|
+
body: body ? JSON.stringify(body) : void 0
|
|
9260
|
+
});
|
|
9261
|
+
return readOpenChamberJson(response, endpoint, serviceName);
|
|
9262
|
+
}
|
|
9263
|
+
async function requestOpenChamberJson(options = {}) {
|
|
9264
|
+
return requestServiceJson({ ...options, serviceName: "OpenChamber" });
|
|
9265
|
+
}
|
|
9266
|
+
async function requestOpenCodeJson(options = {}) {
|
|
9267
|
+
return requestServiceJson({ ...options, serviceName: "OpenCode" });
|
|
9268
|
+
}
|
|
9269
|
+
function normalizeOpenChamberCollection(value) {
|
|
9270
|
+
if (Array.isArray(value)) return value;
|
|
9271
|
+
if (!isPlainObject(value)) return [];
|
|
9272
|
+
for (const key of ["worktrees", "items", "data", "result", "results", "directories", "sandboxes"]) {
|
|
9273
|
+
const nested = value[key];
|
|
9274
|
+
if (Array.isArray(nested)) return nested;
|
|
9275
|
+
if (isPlainObject(nested)) {
|
|
9276
|
+
const normalized = normalizeOpenChamberCollection(nested);
|
|
9277
|
+
if (normalized.length > 0) return normalized;
|
|
9278
|
+
}
|
|
9279
|
+
}
|
|
9280
|
+
return [];
|
|
9281
|
+
}
|
|
9282
|
+
function openChamberEntryDirectory(entry) {
|
|
9283
|
+
if (typeof entry === "string") return entry;
|
|
9284
|
+
return String(entry?.directory || entry?.path || entry?.worktreePath || entry?.worktree_path || entry?.worktree?.path || entry?.worktree?.directory || "");
|
|
9285
|
+
}
|
|
9286
|
+
function openChamberEntryBranch(entry) {
|
|
9287
|
+
if (typeof entry === "string") return "";
|
|
9288
|
+
return String(entry?.branch || entry?.branchName || entry?.branch_name || entry?.worktree?.branch || "").replace(/^refs\/heads\//, "");
|
|
9289
|
+
}
|
|
9290
|
+
function openChamberListIncludesDirectory(list, directory) {
|
|
9291
|
+
const resolved = path5.resolve(directory);
|
|
9292
|
+
return normalizeOpenChamberCollection(list).some((entry) => {
|
|
9293
|
+
const entryDirectory = openChamberEntryDirectory(entry);
|
|
9294
|
+
return entryDirectory && path5.resolve(entryDirectory) === resolved;
|
|
9295
|
+
});
|
|
9296
|
+
}
|
|
9297
|
+
function openChamberListIncludesBranch(list, directory, branch) {
|
|
9298
|
+
const resolved = path5.resolve(directory);
|
|
9299
|
+
return normalizeOpenChamberCollection(list).some((entry) => {
|
|
9300
|
+
const entryDirectory = openChamberEntryDirectory(entry);
|
|
9301
|
+
if (!entryDirectory || path5.resolve(entryDirectory) !== resolved) return false;
|
|
9302
|
+
const entryBranch = openChamberEntryBranch(entry);
|
|
9303
|
+
return !entryBranch || entryBranch === branch;
|
|
9304
|
+
});
|
|
9305
|
+
}
|
|
9306
|
+
async function findOpenChamberProject({ opencodeBaseUrl, baseWorktree, fetchImpl = globalThis.fetch } = {}) {
|
|
9307
|
+
const projects = await requestOpenCodeJson({ baseUrl: opencodeBaseUrl, endpoint: "/project", directory: baseWorktree, fetchImpl });
|
|
9308
|
+
if (!Array.isArray(projects)) return null;
|
|
9309
|
+
const resolvedBase = path5.resolve(baseWorktree);
|
|
9310
|
+
return projects.find((project) => path5.resolve(String(project?.worktree || project?.path || "")) === resolvedBase) || null;
|
|
9311
|
+
}
|
|
9312
|
+
async function refreshOpenChamberProjectCopy({ opencodeBaseUrl, projectId, fetchImpl = globalThis.fetch } = {}) {
|
|
9313
|
+
if (!projectId) return { refreshed: false, reason: "project_id_unavailable" };
|
|
9314
|
+
await requestOpenCodeJson({ baseUrl: opencodeBaseUrl, endpoint: `/experimental/project/${encodeURIComponent(projectId)}/copy/refresh`, method: "POST", fetchImpl });
|
|
9315
|
+
return { refreshed: true, projectId };
|
|
9316
|
+
}
|
|
9317
|
+
async function listOpenChamberGitWorktrees({ openchamberBaseUrl, baseUrl, baseWorktree, fetchImpl = globalThis.fetch } = {}) {
|
|
9318
|
+
return requestOpenChamberJson({ baseUrl: openchamberBaseUrl || baseUrl, endpoint: "/api/git/worktrees", directory: baseWorktree, fetchImpl });
|
|
9319
|
+
}
|
|
9320
|
+
async function verifyOpenChamberGitWorktree({ openchamberBaseUrl, baseUrl, baseWorktree, worktreePath, branch, fetchImpl = globalThis.fetch } = {}) {
|
|
9321
|
+
const worktrees = await listOpenChamberGitWorktrees({ openchamberBaseUrl: openchamberBaseUrl || baseUrl, baseWorktree, fetchImpl });
|
|
9322
|
+
const verified = openChamberListIncludesBranch(worktrees, worktreePath, branch);
|
|
9323
|
+
if (!verified) {
|
|
9324
|
+
throw new Error(`OpenChamber Git worktree verification failed for ${worktreePath} on ${branch}.`);
|
|
9325
|
+
}
|
|
9326
|
+
return { worktree: true, branch: true };
|
|
9327
|
+
}
|
|
9328
|
+
async function syncOpenChamberWorktreeVisibility({ openchamberBaseUrl, opencodeBaseUrl, baseUrl, baseWorktree, worktreePath, branch, fetchImpl = globalThis.fetch } = {}) {
|
|
9329
|
+
const effectiveOpenChamberBaseUrl = openchamberBaseUrl || baseUrl;
|
|
9330
|
+
const effectiveOpenCodeBaseUrl = opencodeBaseUrl || baseUrl;
|
|
9331
|
+
const gitWorktree = await verifyOpenChamberGitWorktree({ openchamberBaseUrl: effectiveOpenChamberBaseUrl, baseWorktree, worktreePath, branch, fetchImpl });
|
|
9332
|
+
await requestOpenCodeJson({ baseUrl: effectiveOpenCodeBaseUrl, endpoint: "/experimental/workspace/sync-list", method: "POST", directory: baseWorktree, fetchImpl });
|
|
9333
|
+
const project = await findOpenChamberProject({ opencodeBaseUrl: effectiveOpenCodeBaseUrl, baseWorktree, fetchImpl });
|
|
9334
|
+
if (!project?.id) throw new Error("OpenCode project was not found after workspace sync; refusing to treat worktree as visible.");
|
|
9335
|
+
await refreshOpenChamberProjectCopy({ opencodeBaseUrl: effectiveOpenCodeBaseUrl, projectId: project.id, fetchImpl });
|
|
9336
|
+
const [worktrees, workspaces, directories] = await Promise.all([
|
|
9337
|
+
listOpenChamberGitWorktrees({ openchamberBaseUrl: effectiveOpenChamberBaseUrl, baseWorktree, fetchImpl }),
|
|
9338
|
+
requestOpenCodeJson({ baseUrl: effectiveOpenCodeBaseUrl, endpoint: "/experimental/workspace", directory: baseWorktree, fetchImpl }),
|
|
9339
|
+
requestOpenCodeJson({ baseUrl: effectiveOpenCodeBaseUrl, endpoint: `/project/${encodeURIComponent(project.id)}/directories`, directory: baseWorktree, fetchImpl })
|
|
9340
|
+
]);
|
|
9341
|
+
const visibility = {
|
|
9342
|
+
worktree: openChamberListIncludesBranch(worktrees, worktreePath, branch),
|
|
9343
|
+
workspace: openChamberListIncludesBranch(workspaces, worktreePath, branch),
|
|
9344
|
+
projectDirectory: openChamberListIncludesDirectory(directories, worktreePath),
|
|
9345
|
+
gitWorktree,
|
|
9346
|
+
projectId: project.id
|
|
9347
|
+
};
|
|
9348
|
+
if (!visibility.worktree || !visibility.workspace || !visibility.projectDirectory) {
|
|
9349
|
+
throw new Error(`OpenCode visibility verification failed for ${worktreePath}: ${JSON.stringify(visibility)}`);
|
|
9350
|
+
}
|
|
9351
|
+
return visibility;
|
|
9352
|
+
}
|
|
9353
|
+
function assertOpenChamberClickUpWorktreePath({ baseWorktree, worktreePath } = {}) {
|
|
9354
|
+
if (!isClickUpDerivedWorktreeSibling(worktreePath, baseWorktree)) {
|
|
9355
|
+
throw new Error(`OpenChamber worktree path is outside the configured ClickUp sibling scope: ${worktreePath}`);
|
|
9356
|
+
}
|
|
9357
|
+
}
|
|
9358
|
+
async function createOpenChamberClickUpWorktree({ openchamberBaseUrl, baseUrl, baseWorktree, branch, worktreePath, startPoint, branchExists = false, fetchImpl = globalThis.fetch } = {}) {
|
|
9359
|
+
const effectiveOpenChamberBaseUrl = openchamberBaseUrl || baseUrl;
|
|
9360
|
+
const worktreeName = clickUpOpenChamberWorktreeName(branch);
|
|
9361
|
+
const body = { name: worktreeName, mode: branchExists ? "existing" : "new", worktreeName, branchName: branch, startRef: branchExists ? branch : startPoint };
|
|
9362
|
+
let created;
|
|
9363
|
+
try {
|
|
9364
|
+
created = await requestOpenChamberJson({ baseUrl: effectiveOpenChamberBaseUrl, endpoint: "/api/git/worktrees", method: "POST", directory: baseWorktree, body, fetchImpl });
|
|
9365
|
+
} catch (error) {
|
|
9366
|
+
throw new Error(`OpenChamber could not create ${branch} from ${branchExists ? branch : startPoint}; API may not support required branch/startRef semantics. Fail-closed: ${error.message}`);
|
|
9367
|
+
}
|
|
9368
|
+
const createdDirectory = openChamberEntryDirectory(created);
|
|
9369
|
+
const createdBranch = openChamberEntryBranch(created);
|
|
9370
|
+
if (!createdDirectory || !path5.isAbsolute(createdDirectory)) {
|
|
9371
|
+
throw new Error(`OpenChamber did not return an absolute worktree path for ${branch}.`);
|
|
9372
|
+
}
|
|
9373
|
+
if (createdBranch !== branch) {
|
|
9374
|
+
throw new Error(`OpenChamber created unexpected branch ${createdBranch || "<unknown>"}; expected ${branch}.`);
|
|
9375
|
+
}
|
|
9376
|
+
const verified = await verifyOpenChamberGitWorktree({ openchamberBaseUrl: effectiveOpenChamberBaseUrl, baseWorktree, worktreePath: createdDirectory, branch, fetchImpl });
|
|
9377
|
+
return { created, worktree: path5.resolve(createdDirectory), branch: createdBranch, verified };
|
|
9378
|
+
}
|
|
9379
|
+
async function registerOpenChamberClickUpWorktree({ openchamberBaseUrl, opencodeBaseUrl, baseUrl, baseWorktree, branch, worktreePath, fetchImpl = globalThis.fetch, source = "reuse" } = {}) {
|
|
9380
|
+
assertOpenChamberClickUpWorktreePath({ baseWorktree, worktreePath });
|
|
9381
|
+
const visibility = await syncOpenChamberWorktreeVisibility({ openchamberBaseUrl: openchamberBaseUrl || baseUrl, opencodeBaseUrl: opencodeBaseUrl || baseUrl, baseWorktree, worktreePath, branch, fetchImpl });
|
|
9382
|
+
return { branch, worktree: path5.resolve(worktreePath), reused: true, provider: "openchamber", openChamber: { source, visibility } };
|
|
9383
|
+
}
|
|
9384
|
+
async function ensureClickUpTaskWorktreeOpenChamber({ baseWorktree = "", taskId, taskType = "Tarea", parentTaskId = "", subtaskId = "", existingMetadata = {}, runGitFn = runGit, openchamberBaseUrl = "", opencodeBaseUrl = "", baseUrl = "", fetchImpl = globalThis.fetch, log = null } = {}) {
|
|
9385
|
+
const effectiveOpenChamberBaseUrl = openchamberBaseUrl || baseUrl;
|
|
9386
|
+
const effectiveOpenCodeBaseUrl = opencodeBaseUrl || baseUrl;
|
|
9387
|
+
const effectiveParent = parentTaskId || taskId;
|
|
9388
|
+
const isSubtask = isClickUpSubtaskRoute({ taskType, parentTaskId: effectiveParent, subtaskId, taskId });
|
|
9389
|
+
const parentBranch = isSubtask ? deriveClickUpBranchName({ taskType, parentTaskId: effectiveParent }) : "";
|
|
9390
|
+
const branch = deriveClickUpBranchName({ taskType, parentTaskId: effectiveParent, subtaskId, taskId });
|
|
9391
|
+
const prTarget = parentBranch || "dev";
|
|
9392
|
+
const existing = safeExistingClickUpWorktree({ metadata: existingMetadata, branch });
|
|
9393
|
+
if (existing) {
|
|
9394
|
+
const registered = await registerOpenChamberClickUpWorktree({ openchamberBaseUrl: effectiveOpenChamberBaseUrl, opencodeBaseUrl: effectiveOpenCodeBaseUrl, baseWorktree, branch, worktreePath: existing.worktree, fetchImpl, source: "metadata_reuse" });
|
|
9395
|
+
log?.({ type: "openchamber_worktree_registered", taskId, branch, worktree: registered.worktree, source: "metadata_reuse", visibility: registered.openChamber.visibility });
|
|
9396
|
+
return { ...registered, parentBranch: parentBranch || void 0, prTarget };
|
|
9397
|
+
}
|
|
9398
|
+
const worktreePath = deriveClickUpWorktree({ baseWorktree, taskId, taskType, parentTaskId: effectiveParent, subtaskId });
|
|
9399
|
+
if (fs5.existsSync(worktreePath)) {
|
|
9400
|
+
const registered = await registerOpenChamberClickUpWorktree({ openchamberBaseUrl: effectiveOpenChamberBaseUrl, opencodeBaseUrl: effectiveOpenCodeBaseUrl, baseWorktree, branch, worktreePath, fetchImpl, source: "existing_directory" });
|
|
9401
|
+
log?.({ type: "openchamber_worktree_registered", taskId, branch, worktree: registered.worktree, source: "existing_directory", visibility: registered.openChamber.visibility });
|
|
9402
|
+
return { ...registered, parentBranch: parentBranch || void 0, prTarget };
|
|
9403
|
+
}
|
|
9404
|
+
let parentBootstrap = null;
|
|
9405
|
+
if (isSubtask) {
|
|
9406
|
+
const parentWorktree = deriveClickUpWorktree({ baseWorktree, taskId: effectiveParent, taskType, parentTaskId: effectiveParent });
|
|
9407
|
+
if (fs5.existsSync(parentWorktree)) {
|
|
9408
|
+
const registeredParent = await registerOpenChamberClickUpWorktree({ openchamberBaseUrl: effectiveOpenChamberBaseUrl, opencodeBaseUrl: effectiveOpenCodeBaseUrl, baseWorktree, branch: parentBranch, worktreePath: parentWorktree, fetchImpl, source: "parent_existing_directory" });
|
|
9409
|
+
parentBootstrap = { branch: parentBranch, worktree: registeredParent.worktree, reused: true, provider: "openchamber", visibility: registeredParent.openChamber.visibility };
|
|
9410
|
+
} else {
|
|
9411
|
+
const parentStartPoint = resolveClickUpDevStartPoint(baseWorktree, runGitFn);
|
|
9412
|
+
const parentBranchExists = clickUpGitRefExists(baseWorktree, parentBranch, runGitFn);
|
|
9413
|
+
const createdParent = await createOpenChamberClickUpWorktree({ openchamberBaseUrl: effectiveOpenChamberBaseUrl, baseWorktree, branch: parentBranch, worktreePath: parentWorktree, startPoint: parentStartPoint, branchExists: parentBranchExists, fetchImpl });
|
|
9414
|
+
const parentVisibility = await syncOpenChamberWorktreeVisibility({ openchamberBaseUrl: effectiveOpenChamberBaseUrl, opencodeBaseUrl: effectiveOpenCodeBaseUrl, baseWorktree, worktreePath: createdParent.worktree, branch: parentBranch, fetchImpl });
|
|
9415
|
+
parentBootstrap = { branch: parentBranch, worktree: createdParent.worktree, startPoint: parentBranchExists ? parentBranch : parentStartPoint, reused: parentBranchExists, provider: "openchamber", visibility: parentVisibility };
|
|
9416
|
+
log?.({ type: "openchamber_worktree_created", taskId: effectiveParent, branch: parentBranch, worktree: parentBootstrap.worktree, startPoint: parentBootstrap.startPoint, visibility: parentVisibility });
|
|
9417
|
+
}
|
|
9418
|
+
}
|
|
9419
|
+
const startPoint = isSubtask ? parentBranch : resolveClickUpDevStartPoint(baseWorktree, runGitFn);
|
|
9420
|
+
const branchExists = clickUpGitRefExists(baseWorktree, branch, runGitFn);
|
|
9421
|
+
const created = await createOpenChamberClickUpWorktree({ openchamberBaseUrl: effectiveOpenChamberBaseUrl, baseWorktree, branch, worktreePath, startPoint, branchExists, fetchImpl });
|
|
9422
|
+
const visibility = await syncOpenChamberWorktreeVisibility({ openchamberBaseUrl: effectiveOpenChamberBaseUrl, opencodeBaseUrl: effectiveOpenCodeBaseUrl, baseWorktree, worktreePath: created.worktree, branch, fetchImpl });
|
|
9423
|
+
log?.({ type: "openchamber_worktree_created", taskId, branch, worktree: created.worktree, startPoint: branchExists ? branch : startPoint, visibility });
|
|
9424
|
+
return { branch, worktree: created.worktree, reused: false, startPoint, parentBranch: parentBranch || void 0, prTarget, parentBootstrap: parentBootstrap || void 0, provider: "openchamber", openChamber: { source: "created", visibility } };
|
|
9425
|
+
}
|
|
9426
|
+
async function ensureClickUpTaskWorktreeForWebhook({ opencodeBaseUrl = "", opencodeBaseUrlConfigured = false, openchamberBaseUrl = "", openchamberBaseUrlConfigured = false, clickupClient = null, webhookWorktree = process.cwd(), fetchImpl = globalThis.fetch, ...options } = {}) {
|
|
9427
|
+
void opencodeBaseUrlConfigured;
|
|
9428
|
+
if (!openchamberBaseUrlConfigured || !openchamberBaseUrl) return ensureClickUpTaskWorktree(options);
|
|
9429
|
+
try {
|
|
9430
|
+
return await ensureClickUpTaskWorktreeOpenChamber({ ...options, openchamberBaseUrl, opencodeBaseUrl, fetchImpl, log: (entry) => appendClickUpWebhookLocalLog(webhookWorktree, entry) });
|
|
9431
|
+
} catch (error) {
|
|
9432
|
+
const message = `OpenChamber worktree provisioning failed for ${options.taskId || "unknown task"}; raw git fallback is disabled to avoid invisible worktrees. ${error.message}`;
|
|
9433
|
+
appendClickUpWebhookLocalLog(webhookWorktree, { type: "openchamber_worktree_failed", taskId: options.taskId || null, message });
|
|
9434
|
+
if (typeof clickupClient?.postTaskComment === "function") {
|
|
9435
|
+
const taskId = options.taskId || "unknown task";
|
|
9436
|
+
const ledgerPath = clickUpCommentLedgerPath(webhookWorktree);
|
|
9437
|
+
const comment = `${message}
|
|
9438
|
+
|
|
9439
|
+
Optima did not run raw git worktree fallback. Please verify clickup.openchamber.base_url and OpenCode/OpenChamber endpoint configuration.`;
|
|
9440
|
+
let dedupe = { post: true, key: clickUpWorktreeFailureCommentKey({ taskId, message }) };
|
|
9441
|
+
try {
|
|
9442
|
+
dedupe = shouldPostClickUpWorktreeFailureComment({ ledgerPath, taskId, message });
|
|
9443
|
+
} catch (ledgerError) {
|
|
9444
|
+
appendClickUpWebhookLocalLog(webhookWorktree, { type: "openchamber_worktree_comment_dedupe_failed", taskId, message: ledgerError.message });
|
|
9445
|
+
}
|
|
9446
|
+
if (dedupe.post) {
|
|
9447
|
+
try {
|
|
9448
|
+
await clickupClient.postTaskComment({ taskId: options.taskId, comment });
|
|
9449
|
+
recordClickUpWorktreeFailureComment({ ledgerPath, taskId, message, posted: true });
|
|
9450
|
+
} catch (commentError) {
|
|
9451
|
+
appendClickUpWebhookLocalLog(webhookWorktree, { type: "openchamber_worktree_blocker_comment_failed", taskId: options.taskId || null, message: commentError.message });
|
|
9452
|
+
}
|
|
9453
|
+
} else {
|
|
9454
|
+
appendClickUpWebhookLocalLog(webhookWorktree, { type: "openchamber_worktree_blocker_comment_deduped", taskId, key: dedupe.key, reason: dedupe.reason });
|
|
9455
|
+
recordClickUpWorktreeFailureComment({ ledgerPath, taskId, message, posted: false, skippedReason: dedupe.reason });
|
|
9456
|
+
}
|
|
9457
|
+
}
|
|
9458
|
+
throw new Error(message);
|
|
9459
|
+
}
|
|
9460
|
+
}
|
|
9227
9461
|
function ensureClickUpTaskWorktree({ baseWorktree = "", taskId, taskType = "Tarea", parentTaskId = "", subtaskId = "", existingMetadata = {}, runGitFn = runGit, allowNonGitFallback = false } = {}) {
|
|
9228
9462
|
const effectiveParent = parentTaskId || taskId;
|
|
9229
9463
|
const isSubtask = isClickUpSubtaskRoute({ taskType, parentTaskId: effectiveParent, subtaskId, taskId });
|
|
@@ -9585,6 +9819,11 @@ function normalizeClickUpWebhookConfig(rawClickUp = null, worktree = process.cwd
|
|
|
9585
9819
|
const webhook = isPlainObject(raw.webhook) ? raw.webhook : {};
|
|
9586
9820
|
const routing = isPlainObject(raw.routing) ? raw.routing : {};
|
|
9587
9821
|
const opencode = isPlainObject(raw.opencode) ? raw.opencode : {};
|
|
9822
|
+
const openchamber = isPlainObject(raw.openchamber) ? raw.openchamber : {};
|
|
9823
|
+
const rawOpenCodeBaseUrl = opencode.base_url ?? opencode.baseUrl ?? raw.opencode_base_url ?? raw.opencodeBaseUrl;
|
|
9824
|
+
const rawOpenChamberBaseUrl = openchamber.base_url ?? openchamber.baseUrl ?? raw.openchamber_base_url ?? raw.openchamberBaseUrl;
|
|
9825
|
+
const openCodeBaseUrlConfigured = rawOpenCodeBaseUrl !== void 0 && rawOpenCodeBaseUrl !== null && String(rawOpenCodeBaseUrl).trim() !== "";
|
|
9826
|
+
const openChamberBaseUrlConfigured = rawOpenChamberBaseUrl !== void 0 && rawOpenChamberBaseUrl !== null && String(rawOpenChamberBaseUrl).trim() !== "";
|
|
9588
9827
|
const location = isPlainObject(webhook.location) ? webhook.location : {};
|
|
9589
9828
|
const events = Array.isArray(webhook.events) && webhook.events.length > 0 ? [...new Set(webhook.events.map((event) => String(event || "").trim()).filter(Boolean))] : [...CLICKUP_WEBHOOK_EVENTS];
|
|
9590
9829
|
const ignoredStatuses = Array.isArray(routing.ignored_statuses) && routing.ignored_statuses.length > 0 ? routing.ignored_statuses : CLICKUP_WEBHOOK_TERMINAL_STATUSES;
|
|
@@ -9596,7 +9835,8 @@ function normalizeClickUpWebhookConfig(rawClickUp = null, worktree = process.cwd
|
|
|
9596
9835
|
apiToken: String(raw.api_token || raw.apiToken || "").trim(),
|
|
9597
9836
|
log: normalizeClickUpWebhookLogLevel(raw.log),
|
|
9598
9837
|
opencode: {
|
|
9599
|
-
baseUrl: normalizeOpenCodeBaseUrl(
|
|
9838
|
+
baseUrl: normalizeOpenCodeBaseUrl(rawOpenCodeBaseUrl),
|
|
9839
|
+
baseUrlConfigured: openCodeBaseUrlConfigured,
|
|
9600
9840
|
promptDelivery: ["http", "direct"].includes(String(opencode.prompt_delivery || opencode.promptDelivery || raw.prompt_delivery || raw.promptDelivery || "").trim().toLowerCase()) ? "http" : "sdk",
|
|
9601
9841
|
acceptPromptAdmission: opencode.accept_prompt_admission === true || opencode.acceptPromptAdmission === true || raw.accept_prompt_admission === true || raw.acceptPromptAdmission === true,
|
|
9602
9842
|
startupReconciliationDelayMs: normalizeNonNegativeInteger(
|
|
@@ -9604,6 +9844,10 @@ function normalizeClickUpWebhookConfig(rawClickUp = null, worktree = process.cwd
|
|
|
9604
9844
|
CLICKUP_WEBHOOK_STARTUP_RECONCILIATION_DELAY_MS
|
|
9605
9845
|
)
|
|
9606
9846
|
},
|
|
9847
|
+
openchamber: {
|
|
9848
|
+
baseUrl: normalizeOpenCodeBaseUrl(rawOpenChamberBaseUrl, ""),
|
|
9849
|
+
baseUrlConfigured: openChamberBaseUrlConfigured
|
|
9850
|
+
},
|
|
9607
9851
|
webhook: {
|
|
9608
9852
|
publicUrl: String(webhook.public_url || webhook.publicUrl || "").trim(),
|
|
9609
9853
|
bindHost: String(webhook.bind_host || webhook.bindHost || "127.0.0.1").trim(),
|
|
@@ -9645,6 +9889,13 @@ function normalizeClickUpWebhookConfig(rawClickUp = null, worktree = process.cwd
|
|
|
9645
9889
|
errors.push("clickup.opencode.base_url must be a valid URL");
|
|
9646
9890
|
}
|
|
9647
9891
|
}
|
|
9892
|
+
if (config.openchamber.baseUrl) {
|
|
9893
|
+
try {
|
|
9894
|
+
new URL(config.openchamber.baseUrl);
|
|
9895
|
+
} catch {
|
|
9896
|
+
errors.push("clickup.openchamber.base_url must be a valid URL");
|
|
9897
|
+
}
|
|
9898
|
+
}
|
|
9648
9899
|
const missingEvents = CLICKUP_WEBHOOK_EVENTS.filter((event) => !config.webhook.events.includes(event));
|
|
9649
9900
|
if (missingEvents.length > 0) errors.push(`clickup.webhook.events missing: ${missingEvents.join(", ")}`);
|
|
9650
9901
|
if (config.routing.targetAgent !== "workflow_product_manager") errors.push("clickup.routing.target_agent must be workflow_product_manager");
|
|
@@ -10001,23 +10252,29 @@ function rememberClickUpWebhookEvent(state = {}, eventKey, limit = 200) {
|
|
|
10001
10252
|
if (recent.includes(eventKey)) return { duplicate: true, state };
|
|
10002
10253
|
return { duplicate: false, state: { ...state, recentEventKeys: [...recent, eventKey].slice(-limit) } };
|
|
10003
10254
|
}
|
|
10004
|
-
function
|
|
10005
|
-
|
|
10006
|
-
if (!ledgerPath || !fs5.existsSync(ledgerPath)) return processed;
|
|
10255
|
+
function readClickUpCommentLedgerEntries(ledgerPath) {
|
|
10256
|
+
if (!ledgerPath || !fs5.existsSync(ledgerPath)) return [];
|
|
10007
10257
|
try {
|
|
10008
10258
|
const raw = fs5.readFileSync(ledgerPath, "utf8");
|
|
10259
|
+
const entries = [];
|
|
10009
10260
|
for (const [index, line] of raw.split(/\r?\n/).entries()) {
|
|
10010
10261
|
if (!line.trim()) continue;
|
|
10011
10262
|
try {
|
|
10012
|
-
|
|
10013
|
-
if (entry?.key) processed.add(String(entry.key));
|
|
10263
|
+
entries.push(JSON.parse(line));
|
|
10014
10264
|
} catch (error) {
|
|
10015
10265
|
throw new Error(`malformed ledger row ${index + 1}: ${error.message}`);
|
|
10016
10266
|
}
|
|
10017
10267
|
}
|
|
10268
|
+
return entries;
|
|
10018
10269
|
} catch (error) {
|
|
10019
10270
|
throw new Error(`ClickUp comment ledger unavailable: ${error.message}`);
|
|
10020
10271
|
}
|
|
10272
|
+
}
|
|
10273
|
+
function readClickUpCommentLedger(ledgerPath) {
|
|
10274
|
+
const processed = /* @__PURE__ */ new Set();
|
|
10275
|
+
for (const entry of readClickUpCommentLedgerEntries(ledgerPath)) {
|
|
10276
|
+
if (entry?.key) processed.add(String(entry.key));
|
|
10277
|
+
}
|
|
10021
10278
|
return processed;
|
|
10022
10279
|
}
|
|
10023
10280
|
function appendClickUpCommentLedgerEntry(ledgerPath, entry = {}) {
|
|
@@ -10083,6 +10340,35 @@ function recordClickUpCommentVersionProcessed({ ledgerPath, key, taskId, eventTy
|
|
|
10083
10340
|
recordedAt: at.toISOString()
|
|
10084
10341
|
});
|
|
10085
10342
|
}
|
|
10343
|
+
function clickUpWorktreeFailureCommentKey({ taskId, message } = {}) {
|
|
10344
|
+
const hash = crypto.createHash("sha256").update(String(message || "")).digest("hex").slice(0, 16);
|
|
10345
|
+
return [String(taskId || "unknown").trim() || "unknown", "worktree_failure", hash].join(":");
|
|
10346
|
+
}
|
|
10347
|
+
function shouldPostClickUpWorktreeFailureComment({ ledgerPath, taskId, message, now = /* @__PURE__ */ new Date(), windowMs = CLICKUP_WORKTREE_FAILURE_COMMENT_DEDUPE_MS } = {}) {
|
|
10348
|
+
const key = clickUpWorktreeFailureCommentKey({ taskId, message });
|
|
10349
|
+
const nowMs = now instanceof Date ? now.getTime() : new Date(now).getTime();
|
|
10350
|
+
for (const entry of readClickUpCommentLedgerEntries(ledgerPath).reverse()) {
|
|
10351
|
+
if (entry?.key !== key) continue;
|
|
10352
|
+
const postedAtMs = new Date(entry.postedAt || entry.recordedAt || entry.at || 0).getTime();
|
|
10353
|
+
if (Number.isFinite(postedAtMs) && Number.isFinite(nowMs) && nowMs - postedAtMs <= windowMs) {
|
|
10354
|
+
return { post: false, key, reason: "recent_duplicate" };
|
|
10355
|
+
}
|
|
10356
|
+
if (entry?.message === message) return { post: false, key, reason: "latest_equivalent" };
|
|
10357
|
+
}
|
|
10358
|
+
return { post: true, key };
|
|
10359
|
+
}
|
|
10360
|
+
function recordClickUpWorktreeFailureComment({ ledgerPath, taskId, message, posted = true, skippedReason = null, at = /* @__PURE__ */ new Date() } = {}) {
|
|
10361
|
+
appendClickUpCommentLedgerEntry(ledgerPath, {
|
|
10362
|
+
key: clickUpWorktreeFailureCommentKey({ taskId, message }),
|
|
10363
|
+
taskId,
|
|
10364
|
+
eventType: "worktree_failure",
|
|
10365
|
+
action: posted ? "worktree_failure_comment_posted" : "worktree_failure_comment_skipped",
|
|
10366
|
+
message,
|
|
10367
|
+
posted,
|
|
10368
|
+
skippedReason,
|
|
10369
|
+
postedAt: at.toISOString()
|
|
10370
|
+
});
|
|
10371
|
+
}
|
|
10086
10372
|
function clickUpTaskIdFromPayload(payload = {}) {
|
|
10087
10373
|
return String(payload.task_id || payload.taskId || payload.task?.id || payload.task?.task_id || "").trim();
|
|
10088
10374
|
}
|
|
@@ -10934,7 +11220,7 @@ async function withClickUpTaskRouteLock(taskId, operation) {
|
|
|
10934
11220
|
if (activeClickUpTaskRoutes.get(key) === current) activeClickUpTaskRoutes.delete(key);
|
|
10935
11221
|
}
|
|
10936
11222
|
}
|
|
10937
|
-
async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, worktree = process.cwd(), clickupClient, openCodeClient, sessionExists = openCodeSessionExists, createSession = createOpenCodeSession, sendSessionEvent = sendOpenCodeSessionEvent, ensureTaskWorktree =
|
|
11223
|
+
async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, worktree = process.cwd(), clickupClient, openCodeClient, sessionExists = openCodeSessionExists, createSession = createOpenCodeSession, sendSessionEvent = sendOpenCodeSessionEvent, ensureTaskWorktree = ensureClickUpTaskWorktreeForWebhook, verifySessionEventDelivery = verifyOpenCodeSessionEventDelivery, saveState = null, now = () => /* @__PURE__ */ new Date(), host = os.hostname(), commentLedgerPath = clickUpCommentLedgerPath(worktree) } = {}) {
|
|
10938
11224
|
const eventType = clickUpEventType(payload);
|
|
10939
11225
|
const eventKey = clickUpWebhookEventKey(payload);
|
|
10940
11226
|
const isStartupAssignmentReconciliation = payload?.startup_reconciliation === true && eventType === "taskAssigneeUpdated";
|
|
@@ -10990,7 +11276,7 @@ async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, w
|
|
|
10990
11276
|
const subtaskId = parentTaskId && parentTaskId !== taskId ? taskId : "";
|
|
10991
11277
|
let taskRoute;
|
|
10992
11278
|
try {
|
|
10993
|
-
taskRoute = ensureTaskWorktree({ baseWorktree: config.basePath, taskId, taskType, parentTaskId, subtaskId, existingMetadata: metadata, allowNonGitFallback: process.env.NODE_ENV === "test" || config.test === true });
|
|
11279
|
+
taskRoute = await ensureTaskWorktree({ baseWorktree: config.basePath, taskId, taskType, parentTaskId, subtaskId, existingMetadata: metadata, allowNonGitFallback: process.env.NODE_ENV === "test" || config.test === true, opencodeBaseUrl: config.opencode?.baseUrl, opencodeBaseUrlConfigured: config.opencode?.baseUrlConfigured === true, openchamberBaseUrl: config.openchamber?.baseUrl, openchamberBaseUrlConfigured: config.openchamber?.baseUrlConfigured === true, clickupClient, webhookWorktree: worktree });
|
|
10994
11280
|
} catch (error) {
|
|
10995
11281
|
const message = `Optima webhook could not create or reuse a task worktree for ${taskId}: ${error.message}`;
|
|
10996
11282
|
appendClickUpWebhookLocalLog(worktree, { type: "task_worktree_failed", taskId, message });
|
|
@@ -13149,7 +13435,7 @@ Follow-up: use optima_prompt_workflow with session_id '${sessionId}' to check in
|
|
|
13149
13435
|
}
|
|
13150
13436
|
};
|
|
13151
13437
|
}
|
|
13152
|
-
OptimaPlugin.__internals = { BUNDLE_AGENTS_DIR, BUNDLE_ASSETS_DIR, CLICKUP_DEFINITION_DOC_PARENT, activeClickUpWebhookLifecycleRegistry, cleanupManagedClickUpWebhook, deleteClickUpWebhookBestEffort, BUNDLE_POLICIES_DIR, buildClickUpApplyPayloadResult, buildClickUpCreateSubtasksPayload, buildClickUpStartTaskPayload, buildClickUpSummaryPayload, buildClickUpTransitionPayload, buildOptimaAgents, clickUpCommentLedgerKey, clickUpCommentMentionsProductManager, clickUpWebhookAuditLogDir, clickUpWebhookAuditLogPath, compactPromptPath, createClickUpApiClient, createTestClickUpApiClient, createOpenCodeSession, createOpenCodeSessionControl, deliveryEvidencePathForClickUpTask, ensureClickUpTaskWorktree, ensureClickUpWebhookSubscription, handleClickUpWebhookRequest, isClickUpDerivedWorktreeSibling, isClickUpSubtaskRoute, isClickUpWebhookStateActive, isSameOrNestedPath, normalizeClickUpWebhookConfig, normalizeClickUpWebhookLogLevel, normalizeOpenCodeBaseUrl, openCodeSessionExists, promptOpenCodeSessionControl, probeOpenCodeSessionControl, readClickUpCommentLedger, readClickUpWebhookState, readOpenCodeSessionControl, readOpenCodeSessionMessages, reconcileClickUpStartup, scheduleClickUpStartupReconciliation, waitForOpenCodeReadiness, recordClickUpCommentVersionProcessed, resyncClickUpWebhookForSignatureDrift, resolveIncludeFile, resolveIncludes, resolveOptimaPluginOptions, resolveSecretReference, routeClickUpWebhookEvent, sendOpenCodeSessionEvent, sendOpenCodeSessionEventDirect, summarizeOpenCodeMessages, verifyOpenCodeSessionEventDelivery, stableClickUpCommentVersionMarker, startClickUpWebhookListener, validateClickUpWebhookState, verifyClickUpSignature, writeClickUpWebhookState, decideClickUpStatusAction, deriveClickUpBranchName, deriveClickUpPendingSubtaskBranch, deriveClickUpPrTarget, deriveClickUpWorktree, determineClickUpMergeAuthority, ensureOptimaGitignoreRules, explicitSafeInputWorktree, finalApprovalAssignees, formatRepairResult, formatValidationResult, isIgnoredClickUpTaskType, isOptimaPluginPackageWorktree, isSafeWritableDirectory, loadHumansRegistry, mergeClickUpAgentMetadata, mergeClickUpSessionMetadata, migrateLegacyOptimaLayout, normalizeAgentMetadataJson, normalizeClickUpStatus, normalizeClickUpTaskType, normalizePromptResponseParts, normalizeWorkflowTaskPath, optimaRepairDependencies, parseClickUpSubtasksMarkdown, parseHumansRegistry, parseMarkdownArtifact, parseMarkdownSections, planOptimaRepair, preEstimateClickUpWork, readMarkdownArtifact, resolveHumanRoles, resolveSafeWorktree, safeWorktreeOrFailure, stripRawLogSections, validateMainWorkspaceBranchSafety, workflowFinalMessageFromPromptResponse };
|
|
13438
|
+
OptimaPlugin.__internals = { BUNDLE_AGENTS_DIR, BUNDLE_ASSETS_DIR, CLICKUP_DEFINITION_DOC_PARENT, activeClickUpWebhookLifecycleRegistry, cleanupManagedClickUpWebhook, deleteClickUpWebhookBestEffort, BUNDLE_POLICIES_DIR, buildClickUpApplyPayloadResult, buildClickUpCreateSubtasksPayload, buildClickUpStartTaskPayload, buildClickUpSummaryPayload, buildClickUpTransitionPayload, buildOptimaAgents, clickUpCommentLedgerKey, clickUpCommentMentionsProductManager, clickUpWebhookAuditLogDir, clickUpWebhookAuditLogPath, compactPromptPath, createClickUpApiClient, createTestClickUpApiClient, createOpenCodeSession, createOpenCodeSessionControl, deliveryEvidencePathForClickUpTask, ensureClickUpTaskWorktree, ensureClickUpTaskWorktreeForWebhook, ensureClickUpTaskWorktreeOpenChamber, ensureClickUpWebhookSubscription, handleClickUpWebhookRequest, isClickUpDerivedWorktreeSibling, isClickUpSubtaskRoute, isClickUpWebhookStateActive, isSameOrNestedPath, normalizeClickUpWebhookConfig, normalizeClickUpWebhookLogLevel, normalizeOpenCodeBaseUrl, openCodeSessionExists, promptOpenCodeSessionControl, probeOpenCodeSessionControl, readClickUpCommentLedger, readClickUpWebhookState, readOpenCodeSessionControl, readOpenCodeSessionMessages, reconcileClickUpStartup, registerOpenChamberClickUpWorktree, scheduleClickUpStartupReconciliation, syncOpenChamberWorktreeVisibility, waitForOpenCodeReadiness, recordClickUpCommentVersionProcessed, resyncClickUpWebhookForSignatureDrift, resolveIncludeFile, resolveIncludes, resolveOptimaPluginOptions, resolveSecretReference, routeClickUpWebhookEvent, sendOpenCodeSessionEvent, sendOpenCodeSessionEventDirect, summarizeOpenCodeMessages, verifyOpenCodeSessionEventDelivery, stableClickUpCommentVersionMarker, startClickUpWebhookListener, validateClickUpWebhookState, verifyClickUpSignature, writeClickUpWebhookState, decideClickUpStatusAction, deriveClickUpBranchName, deriveClickUpPendingSubtaskBranch, deriveClickUpPrTarget, deriveClickUpWorktree, determineClickUpMergeAuthority, ensureOptimaGitignoreRules, explicitSafeInputWorktree, finalApprovalAssignees, formatRepairResult, formatValidationResult, isIgnoredClickUpTaskType, isOptimaPluginPackageWorktree, isSafeWritableDirectory, loadHumansRegistry, mergeClickUpAgentMetadata, mergeClickUpSessionMetadata, migrateLegacyOptimaLayout, normalizeAgentMetadataJson, normalizeClickUpStatus, normalizeClickUpTaskType, normalizePromptResponseParts, normalizeWorkflowTaskPath, optimaRepairDependencies, parseClickUpSubtasksMarkdown, parseHumansRegistry, parseMarkdownArtifact, parseMarkdownSections, planOptimaRepair, preEstimateClickUpWork, readMarkdownArtifact, resolveHumanRoles, resolveSafeWorktree, safeWorktreeOrFailure, stripRawLogSections, validateMainWorkspaceBranchSafety, workflowFinalMessageFromPromptResponse };
|
|
13153
13439
|
export {
|
|
13154
13440
|
OptimaPlugin as default
|
|
13155
13441
|
};
|
package/dist/sanitize_cli.js
CHANGED
|
@@ -8534,6 +8534,7 @@ var CLICKUP_WEBHOOK_REQUEST_TIMEOUT_MS = 1e4;
|
|
|
8534
8534
|
var CLICKUP_WEBHOOK_STARTUP_TASK_LIMIT = 50;
|
|
8535
8535
|
var CLICKUP_WEBHOOK_STARTUP_COMMENT_LIMIT = 100;
|
|
8536
8536
|
var CLICKUP_WEBHOOK_STARTUP_RECONCILIATION_DELAY_MS = 3e4;
|
|
8537
|
+
var CLICKUP_WORKTREE_FAILURE_COMMENT_DEDUPE_MS = 10 * 60 * 1e3;
|
|
8537
8538
|
var CLICKUP_WEBHOOK_LOG_LEVELS = /* @__PURE__ */ new Set(["error", "info", "verbose"]);
|
|
8538
8539
|
var CLICKUP_WEBHOOK_REDACTED = "[REDACTED]";
|
|
8539
8540
|
var DISCUSSION_BACKFILL_FETCH_LIMIT = 100;
|
|
@@ -9231,6 +9232,239 @@ function addClickUpWorktreeForBranch({ baseWorktree, branch, worktreePath, start
|
|
|
9231
9232
|
runGitFn(baseWorktree, ["worktree", "add", "-b", branch, worktreePath, startPoint]);
|
|
9232
9233
|
}
|
|
9233
9234
|
}
|
|
9235
|
+
function clickUpOpenChamberWorktreeName(branch = "") {
|
|
9236
|
+
return String(branch || "").trim().replace(/\//g, "-");
|
|
9237
|
+
}
|
|
9238
|
+
function openChamberUrl(baseUrl, pathname, query = {}) {
|
|
9239
|
+
const url = new URL(pathname, normalizeOpenCodeBaseUrl(baseUrl));
|
|
9240
|
+
for (const [key, value] of Object.entries(query)) {
|
|
9241
|
+
if (value !== void 0 && value !== null && String(value).trim()) url.searchParams.set(key, String(value));
|
|
9242
|
+
}
|
|
9243
|
+
return url.toString();
|
|
9244
|
+
}
|
|
9245
|
+
async function readOpenChamberJson(response, endpoint, serviceName = "OpenChamber") {
|
|
9246
|
+
const text = await response.text();
|
|
9247
|
+
let data = null;
|
|
9248
|
+
if (text.trim()) {
|
|
9249
|
+
try {
|
|
9250
|
+
data = JSON.parse(text);
|
|
9251
|
+
} catch {
|
|
9252
|
+
throw new Error(`${serviceName} ${endpoint} returned non-JSON response.`);
|
|
9253
|
+
}
|
|
9254
|
+
}
|
|
9255
|
+
if (!response.ok) {
|
|
9256
|
+
const message = data?.error?.message || data?.message || data?.data?.message || text.slice(0, 200) || `HTTP ${response.status}`;
|
|
9257
|
+
throw new Error(`${serviceName} ${endpoint} failed: ${response.status} ${message}`);
|
|
9258
|
+
}
|
|
9259
|
+
return data;
|
|
9260
|
+
}
|
|
9261
|
+
async function requestServiceJson({ baseUrl, serviceName = "OpenChamber", endpoint, method = "GET", directory, body, fetchImpl = globalThis.fetch } = {}) {
|
|
9262
|
+
if (typeof fetchImpl !== "function") throw new Error(`${serviceName} API calls require fetch.`);
|
|
9263
|
+
const response = await fetchImpl(openChamberUrl(baseUrl, endpoint, { directory }), {
|
|
9264
|
+
method,
|
|
9265
|
+
headers: body ? { "content-type": "application/json" } : void 0,
|
|
9266
|
+
body: body ? JSON.stringify(body) : void 0
|
|
9267
|
+
});
|
|
9268
|
+
return readOpenChamberJson(response, endpoint, serviceName);
|
|
9269
|
+
}
|
|
9270
|
+
async function requestOpenChamberJson(options = {}) {
|
|
9271
|
+
return requestServiceJson({ ...options, serviceName: "OpenChamber" });
|
|
9272
|
+
}
|
|
9273
|
+
async function requestOpenCodeJson(options = {}) {
|
|
9274
|
+
return requestServiceJson({ ...options, serviceName: "OpenCode" });
|
|
9275
|
+
}
|
|
9276
|
+
function normalizeOpenChamberCollection(value) {
|
|
9277
|
+
if (Array.isArray(value)) return value;
|
|
9278
|
+
if (!isPlainObject(value)) return [];
|
|
9279
|
+
for (const key of ["worktrees", "items", "data", "result", "results", "directories", "sandboxes"]) {
|
|
9280
|
+
const nested = value[key];
|
|
9281
|
+
if (Array.isArray(nested)) return nested;
|
|
9282
|
+
if (isPlainObject(nested)) {
|
|
9283
|
+
const normalized = normalizeOpenChamberCollection(nested);
|
|
9284
|
+
if (normalized.length > 0) return normalized;
|
|
9285
|
+
}
|
|
9286
|
+
}
|
|
9287
|
+
return [];
|
|
9288
|
+
}
|
|
9289
|
+
function openChamberEntryDirectory(entry) {
|
|
9290
|
+
if (typeof entry === "string") return entry;
|
|
9291
|
+
return String(entry?.directory || entry?.path || entry?.worktreePath || entry?.worktree_path || entry?.worktree?.path || entry?.worktree?.directory || "");
|
|
9292
|
+
}
|
|
9293
|
+
function openChamberEntryBranch(entry) {
|
|
9294
|
+
if (typeof entry === "string") return "";
|
|
9295
|
+
return String(entry?.branch || entry?.branchName || entry?.branch_name || entry?.worktree?.branch || "").replace(/^refs\/heads\//, "");
|
|
9296
|
+
}
|
|
9297
|
+
function openChamberListIncludesDirectory(list, directory) {
|
|
9298
|
+
const resolved = path5.resolve(directory);
|
|
9299
|
+
return normalizeOpenChamberCollection(list).some((entry) => {
|
|
9300
|
+
const entryDirectory = openChamberEntryDirectory(entry);
|
|
9301
|
+
return entryDirectory && path5.resolve(entryDirectory) === resolved;
|
|
9302
|
+
});
|
|
9303
|
+
}
|
|
9304
|
+
function openChamberListIncludesBranch(list, directory, branch) {
|
|
9305
|
+
const resolved = path5.resolve(directory);
|
|
9306
|
+
return normalizeOpenChamberCollection(list).some((entry) => {
|
|
9307
|
+
const entryDirectory = openChamberEntryDirectory(entry);
|
|
9308
|
+
if (!entryDirectory || path5.resolve(entryDirectory) !== resolved) return false;
|
|
9309
|
+
const entryBranch = openChamberEntryBranch(entry);
|
|
9310
|
+
return !entryBranch || entryBranch === branch;
|
|
9311
|
+
});
|
|
9312
|
+
}
|
|
9313
|
+
async function findOpenChamberProject({ opencodeBaseUrl, baseWorktree, fetchImpl = globalThis.fetch } = {}) {
|
|
9314
|
+
const projects = await requestOpenCodeJson({ baseUrl: opencodeBaseUrl, endpoint: "/project", directory: baseWorktree, fetchImpl });
|
|
9315
|
+
if (!Array.isArray(projects)) return null;
|
|
9316
|
+
const resolvedBase = path5.resolve(baseWorktree);
|
|
9317
|
+
return projects.find((project) => path5.resolve(String(project?.worktree || project?.path || "")) === resolvedBase) || null;
|
|
9318
|
+
}
|
|
9319
|
+
async function refreshOpenChamberProjectCopy({ opencodeBaseUrl, projectId, fetchImpl = globalThis.fetch } = {}) {
|
|
9320
|
+
if (!projectId) return { refreshed: false, reason: "project_id_unavailable" };
|
|
9321
|
+
await requestOpenCodeJson({ baseUrl: opencodeBaseUrl, endpoint: `/experimental/project/${encodeURIComponent(projectId)}/copy/refresh`, method: "POST", fetchImpl });
|
|
9322
|
+
return { refreshed: true, projectId };
|
|
9323
|
+
}
|
|
9324
|
+
async function listOpenChamberGitWorktrees({ openchamberBaseUrl, baseUrl, baseWorktree, fetchImpl = globalThis.fetch } = {}) {
|
|
9325
|
+
return requestOpenChamberJson({ baseUrl: openchamberBaseUrl || baseUrl, endpoint: "/api/git/worktrees", directory: baseWorktree, fetchImpl });
|
|
9326
|
+
}
|
|
9327
|
+
async function verifyOpenChamberGitWorktree({ openchamberBaseUrl, baseUrl, baseWorktree, worktreePath, branch, fetchImpl = globalThis.fetch } = {}) {
|
|
9328
|
+
const worktrees = await listOpenChamberGitWorktrees({ openchamberBaseUrl: openchamberBaseUrl || baseUrl, baseWorktree, fetchImpl });
|
|
9329
|
+
const verified = openChamberListIncludesBranch(worktrees, worktreePath, branch);
|
|
9330
|
+
if (!verified) {
|
|
9331
|
+
throw new Error(`OpenChamber Git worktree verification failed for ${worktreePath} on ${branch}.`);
|
|
9332
|
+
}
|
|
9333
|
+
return { worktree: true, branch: true };
|
|
9334
|
+
}
|
|
9335
|
+
async function syncOpenChamberWorktreeVisibility({ openchamberBaseUrl, opencodeBaseUrl, baseUrl, baseWorktree, worktreePath, branch, fetchImpl = globalThis.fetch } = {}) {
|
|
9336
|
+
const effectiveOpenChamberBaseUrl = openchamberBaseUrl || baseUrl;
|
|
9337
|
+
const effectiveOpenCodeBaseUrl = opencodeBaseUrl || baseUrl;
|
|
9338
|
+
const gitWorktree = await verifyOpenChamberGitWorktree({ openchamberBaseUrl: effectiveOpenChamberBaseUrl, baseWorktree, worktreePath, branch, fetchImpl });
|
|
9339
|
+
await requestOpenCodeJson({ baseUrl: effectiveOpenCodeBaseUrl, endpoint: "/experimental/workspace/sync-list", method: "POST", directory: baseWorktree, fetchImpl });
|
|
9340
|
+
const project = await findOpenChamberProject({ opencodeBaseUrl: effectiveOpenCodeBaseUrl, baseWorktree, fetchImpl });
|
|
9341
|
+
if (!project?.id) throw new Error("OpenCode project was not found after workspace sync; refusing to treat worktree as visible.");
|
|
9342
|
+
await refreshOpenChamberProjectCopy({ opencodeBaseUrl: effectiveOpenCodeBaseUrl, projectId: project.id, fetchImpl });
|
|
9343
|
+
const [worktrees, workspaces, directories] = await Promise.all([
|
|
9344
|
+
listOpenChamberGitWorktrees({ openchamberBaseUrl: effectiveOpenChamberBaseUrl, baseWorktree, fetchImpl }),
|
|
9345
|
+
requestOpenCodeJson({ baseUrl: effectiveOpenCodeBaseUrl, endpoint: "/experimental/workspace", directory: baseWorktree, fetchImpl }),
|
|
9346
|
+
requestOpenCodeJson({ baseUrl: effectiveOpenCodeBaseUrl, endpoint: `/project/${encodeURIComponent(project.id)}/directories`, directory: baseWorktree, fetchImpl })
|
|
9347
|
+
]);
|
|
9348
|
+
const visibility = {
|
|
9349
|
+
worktree: openChamberListIncludesBranch(worktrees, worktreePath, branch),
|
|
9350
|
+
workspace: openChamberListIncludesBranch(workspaces, worktreePath, branch),
|
|
9351
|
+
projectDirectory: openChamberListIncludesDirectory(directories, worktreePath),
|
|
9352
|
+
gitWorktree,
|
|
9353
|
+
projectId: project.id
|
|
9354
|
+
};
|
|
9355
|
+
if (!visibility.worktree || !visibility.workspace || !visibility.projectDirectory) {
|
|
9356
|
+
throw new Error(`OpenCode visibility verification failed for ${worktreePath}: ${JSON.stringify(visibility)}`);
|
|
9357
|
+
}
|
|
9358
|
+
return visibility;
|
|
9359
|
+
}
|
|
9360
|
+
function assertOpenChamberClickUpWorktreePath({ baseWorktree, worktreePath } = {}) {
|
|
9361
|
+
if (!isClickUpDerivedWorktreeSibling(worktreePath, baseWorktree)) {
|
|
9362
|
+
throw new Error(`OpenChamber worktree path is outside the configured ClickUp sibling scope: ${worktreePath}`);
|
|
9363
|
+
}
|
|
9364
|
+
}
|
|
9365
|
+
async function createOpenChamberClickUpWorktree({ openchamberBaseUrl, baseUrl, baseWorktree, branch, worktreePath, startPoint, branchExists = false, fetchImpl = globalThis.fetch } = {}) {
|
|
9366
|
+
const effectiveOpenChamberBaseUrl = openchamberBaseUrl || baseUrl;
|
|
9367
|
+
const worktreeName = clickUpOpenChamberWorktreeName(branch);
|
|
9368
|
+
const body = { name: worktreeName, mode: branchExists ? "existing" : "new", worktreeName, branchName: branch, startRef: branchExists ? branch : startPoint };
|
|
9369
|
+
let created;
|
|
9370
|
+
try {
|
|
9371
|
+
created = await requestOpenChamberJson({ baseUrl: effectiveOpenChamberBaseUrl, endpoint: "/api/git/worktrees", method: "POST", directory: baseWorktree, body, fetchImpl });
|
|
9372
|
+
} catch (error) {
|
|
9373
|
+
throw new Error(`OpenChamber could not create ${branch} from ${branchExists ? branch : startPoint}; API may not support required branch/startRef semantics. Fail-closed: ${error.message}`);
|
|
9374
|
+
}
|
|
9375
|
+
const createdDirectory = openChamberEntryDirectory(created);
|
|
9376
|
+
const createdBranch = openChamberEntryBranch(created);
|
|
9377
|
+
if (!createdDirectory || !path5.isAbsolute(createdDirectory)) {
|
|
9378
|
+
throw new Error(`OpenChamber did not return an absolute worktree path for ${branch}.`);
|
|
9379
|
+
}
|
|
9380
|
+
if (createdBranch !== branch) {
|
|
9381
|
+
throw new Error(`OpenChamber created unexpected branch ${createdBranch || "<unknown>"}; expected ${branch}.`);
|
|
9382
|
+
}
|
|
9383
|
+
const verified = await verifyOpenChamberGitWorktree({ openchamberBaseUrl: effectiveOpenChamberBaseUrl, baseWorktree, worktreePath: createdDirectory, branch, fetchImpl });
|
|
9384
|
+
return { created, worktree: path5.resolve(createdDirectory), branch: createdBranch, verified };
|
|
9385
|
+
}
|
|
9386
|
+
async function registerOpenChamberClickUpWorktree({ openchamberBaseUrl, opencodeBaseUrl, baseUrl, baseWorktree, branch, worktreePath, fetchImpl = globalThis.fetch, source = "reuse" } = {}) {
|
|
9387
|
+
assertOpenChamberClickUpWorktreePath({ baseWorktree, worktreePath });
|
|
9388
|
+
const visibility = await syncOpenChamberWorktreeVisibility({ openchamberBaseUrl: openchamberBaseUrl || baseUrl, opencodeBaseUrl: opencodeBaseUrl || baseUrl, baseWorktree, worktreePath, branch, fetchImpl });
|
|
9389
|
+
return { branch, worktree: path5.resolve(worktreePath), reused: true, provider: "openchamber", openChamber: { source, visibility } };
|
|
9390
|
+
}
|
|
9391
|
+
async function ensureClickUpTaskWorktreeOpenChamber({ baseWorktree = "", taskId, taskType = "Tarea", parentTaskId = "", subtaskId = "", existingMetadata = {}, runGitFn = runGit, openchamberBaseUrl = "", opencodeBaseUrl = "", baseUrl = "", fetchImpl = globalThis.fetch, log = null } = {}) {
|
|
9392
|
+
const effectiveOpenChamberBaseUrl = openchamberBaseUrl || baseUrl;
|
|
9393
|
+
const effectiveOpenCodeBaseUrl = opencodeBaseUrl || baseUrl;
|
|
9394
|
+
const effectiveParent = parentTaskId || taskId;
|
|
9395
|
+
const isSubtask = isClickUpSubtaskRoute({ taskType, parentTaskId: effectiveParent, subtaskId, taskId });
|
|
9396
|
+
const parentBranch = isSubtask ? deriveClickUpBranchName({ taskType, parentTaskId: effectiveParent }) : "";
|
|
9397
|
+
const branch = deriveClickUpBranchName({ taskType, parentTaskId: effectiveParent, subtaskId, taskId });
|
|
9398
|
+
const prTarget = parentBranch || "dev";
|
|
9399
|
+
const existing = safeExistingClickUpWorktree({ metadata: existingMetadata, branch });
|
|
9400
|
+
if (existing) {
|
|
9401
|
+
const registered = await registerOpenChamberClickUpWorktree({ openchamberBaseUrl: effectiveOpenChamberBaseUrl, opencodeBaseUrl: effectiveOpenCodeBaseUrl, baseWorktree, branch, worktreePath: existing.worktree, fetchImpl, source: "metadata_reuse" });
|
|
9402
|
+
log?.({ type: "openchamber_worktree_registered", taskId, branch, worktree: registered.worktree, source: "metadata_reuse", visibility: registered.openChamber.visibility });
|
|
9403
|
+
return { ...registered, parentBranch: parentBranch || void 0, prTarget };
|
|
9404
|
+
}
|
|
9405
|
+
const worktreePath = deriveClickUpWorktree({ baseWorktree, taskId, taskType, parentTaskId: effectiveParent, subtaskId });
|
|
9406
|
+
if (fs5.existsSync(worktreePath)) {
|
|
9407
|
+
const registered = await registerOpenChamberClickUpWorktree({ openchamberBaseUrl: effectiveOpenChamberBaseUrl, opencodeBaseUrl: effectiveOpenCodeBaseUrl, baseWorktree, branch, worktreePath, fetchImpl, source: "existing_directory" });
|
|
9408
|
+
log?.({ type: "openchamber_worktree_registered", taskId, branch, worktree: registered.worktree, source: "existing_directory", visibility: registered.openChamber.visibility });
|
|
9409
|
+
return { ...registered, parentBranch: parentBranch || void 0, prTarget };
|
|
9410
|
+
}
|
|
9411
|
+
let parentBootstrap = null;
|
|
9412
|
+
if (isSubtask) {
|
|
9413
|
+
const parentWorktree = deriveClickUpWorktree({ baseWorktree, taskId: effectiveParent, taskType, parentTaskId: effectiveParent });
|
|
9414
|
+
if (fs5.existsSync(parentWorktree)) {
|
|
9415
|
+
const registeredParent = await registerOpenChamberClickUpWorktree({ openchamberBaseUrl: effectiveOpenChamberBaseUrl, opencodeBaseUrl: effectiveOpenCodeBaseUrl, baseWorktree, branch: parentBranch, worktreePath: parentWorktree, fetchImpl, source: "parent_existing_directory" });
|
|
9416
|
+
parentBootstrap = { branch: parentBranch, worktree: registeredParent.worktree, reused: true, provider: "openchamber", visibility: registeredParent.openChamber.visibility };
|
|
9417
|
+
} else {
|
|
9418
|
+
const parentStartPoint = resolveClickUpDevStartPoint(baseWorktree, runGitFn);
|
|
9419
|
+
const parentBranchExists = clickUpGitRefExists(baseWorktree, parentBranch, runGitFn);
|
|
9420
|
+
const createdParent = await createOpenChamberClickUpWorktree({ openchamberBaseUrl: effectiveOpenChamberBaseUrl, baseWorktree, branch: parentBranch, worktreePath: parentWorktree, startPoint: parentStartPoint, branchExists: parentBranchExists, fetchImpl });
|
|
9421
|
+
const parentVisibility = await syncOpenChamberWorktreeVisibility({ openchamberBaseUrl: effectiveOpenChamberBaseUrl, opencodeBaseUrl: effectiveOpenCodeBaseUrl, baseWorktree, worktreePath: createdParent.worktree, branch: parentBranch, fetchImpl });
|
|
9422
|
+
parentBootstrap = { branch: parentBranch, worktree: createdParent.worktree, startPoint: parentBranchExists ? parentBranch : parentStartPoint, reused: parentBranchExists, provider: "openchamber", visibility: parentVisibility };
|
|
9423
|
+
log?.({ type: "openchamber_worktree_created", taskId: effectiveParent, branch: parentBranch, worktree: parentBootstrap.worktree, startPoint: parentBootstrap.startPoint, visibility: parentVisibility });
|
|
9424
|
+
}
|
|
9425
|
+
}
|
|
9426
|
+
const startPoint = isSubtask ? parentBranch : resolveClickUpDevStartPoint(baseWorktree, runGitFn);
|
|
9427
|
+
const branchExists = clickUpGitRefExists(baseWorktree, branch, runGitFn);
|
|
9428
|
+
const created = await createOpenChamberClickUpWorktree({ openchamberBaseUrl: effectiveOpenChamberBaseUrl, baseWorktree, branch, worktreePath, startPoint, branchExists, fetchImpl });
|
|
9429
|
+
const visibility = await syncOpenChamberWorktreeVisibility({ openchamberBaseUrl: effectiveOpenChamberBaseUrl, opencodeBaseUrl: effectiveOpenCodeBaseUrl, baseWorktree, worktreePath: created.worktree, branch, fetchImpl });
|
|
9430
|
+
log?.({ type: "openchamber_worktree_created", taskId, branch, worktree: created.worktree, startPoint: branchExists ? branch : startPoint, visibility });
|
|
9431
|
+
return { branch, worktree: created.worktree, reused: false, startPoint, parentBranch: parentBranch || void 0, prTarget, parentBootstrap: parentBootstrap || void 0, provider: "openchamber", openChamber: { source: "created", visibility } };
|
|
9432
|
+
}
|
|
9433
|
+
async function ensureClickUpTaskWorktreeForWebhook({ opencodeBaseUrl = "", opencodeBaseUrlConfigured = false, openchamberBaseUrl = "", openchamberBaseUrlConfigured = false, clickupClient = null, webhookWorktree = process.cwd(), fetchImpl = globalThis.fetch, ...options } = {}) {
|
|
9434
|
+
void opencodeBaseUrlConfigured;
|
|
9435
|
+
if (!openchamberBaseUrlConfigured || !openchamberBaseUrl) return ensureClickUpTaskWorktree(options);
|
|
9436
|
+
try {
|
|
9437
|
+
return await ensureClickUpTaskWorktreeOpenChamber({ ...options, openchamberBaseUrl, opencodeBaseUrl, fetchImpl, log: (entry) => appendClickUpWebhookLocalLog(webhookWorktree, entry) });
|
|
9438
|
+
} catch (error) {
|
|
9439
|
+
const message = `OpenChamber worktree provisioning failed for ${options.taskId || "unknown task"}; raw git fallback is disabled to avoid invisible worktrees. ${error.message}`;
|
|
9440
|
+
appendClickUpWebhookLocalLog(webhookWorktree, { type: "openchamber_worktree_failed", taskId: options.taskId || null, message });
|
|
9441
|
+
if (typeof clickupClient?.postTaskComment === "function") {
|
|
9442
|
+
const taskId = options.taskId || "unknown task";
|
|
9443
|
+
const ledgerPath = clickUpCommentLedgerPath(webhookWorktree);
|
|
9444
|
+
const comment = `${message}
|
|
9445
|
+
|
|
9446
|
+
Optima did not run raw git worktree fallback. Please verify clickup.openchamber.base_url and OpenCode/OpenChamber endpoint configuration.`;
|
|
9447
|
+
let dedupe = { post: true, key: clickUpWorktreeFailureCommentKey({ taskId, message }) };
|
|
9448
|
+
try {
|
|
9449
|
+
dedupe = shouldPostClickUpWorktreeFailureComment({ ledgerPath, taskId, message });
|
|
9450
|
+
} catch (ledgerError) {
|
|
9451
|
+
appendClickUpWebhookLocalLog(webhookWorktree, { type: "openchamber_worktree_comment_dedupe_failed", taskId, message: ledgerError.message });
|
|
9452
|
+
}
|
|
9453
|
+
if (dedupe.post) {
|
|
9454
|
+
try {
|
|
9455
|
+
await clickupClient.postTaskComment({ taskId: options.taskId, comment });
|
|
9456
|
+
recordClickUpWorktreeFailureComment({ ledgerPath, taskId, message, posted: true });
|
|
9457
|
+
} catch (commentError) {
|
|
9458
|
+
appendClickUpWebhookLocalLog(webhookWorktree, { type: "openchamber_worktree_blocker_comment_failed", taskId: options.taskId || null, message: commentError.message });
|
|
9459
|
+
}
|
|
9460
|
+
} else {
|
|
9461
|
+
appendClickUpWebhookLocalLog(webhookWorktree, { type: "openchamber_worktree_blocker_comment_deduped", taskId, key: dedupe.key, reason: dedupe.reason });
|
|
9462
|
+
recordClickUpWorktreeFailureComment({ ledgerPath, taskId, message, posted: false, skippedReason: dedupe.reason });
|
|
9463
|
+
}
|
|
9464
|
+
}
|
|
9465
|
+
throw new Error(message);
|
|
9466
|
+
}
|
|
9467
|
+
}
|
|
9234
9468
|
function ensureClickUpTaskWorktree({ baseWorktree = "", taskId, taskType = "Tarea", parentTaskId = "", subtaskId = "", existingMetadata = {}, runGitFn = runGit, allowNonGitFallback = false } = {}) {
|
|
9235
9469
|
const effectiveParent = parentTaskId || taskId;
|
|
9236
9470
|
const isSubtask = isClickUpSubtaskRoute({ taskType, parentTaskId: effectiveParent, subtaskId, taskId });
|
|
@@ -9592,6 +9826,11 @@ function normalizeClickUpWebhookConfig(rawClickUp = null, worktree = process.cwd
|
|
|
9592
9826
|
const webhook = isPlainObject(raw.webhook) ? raw.webhook : {};
|
|
9593
9827
|
const routing = isPlainObject(raw.routing) ? raw.routing : {};
|
|
9594
9828
|
const opencode = isPlainObject(raw.opencode) ? raw.opencode : {};
|
|
9829
|
+
const openchamber = isPlainObject(raw.openchamber) ? raw.openchamber : {};
|
|
9830
|
+
const rawOpenCodeBaseUrl = opencode.base_url ?? opencode.baseUrl ?? raw.opencode_base_url ?? raw.opencodeBaseUrl;
|
|
9831
|
+
const rawOpenChamberBaseUrl = openchamber.base_url ?? openchamber.baseUrl ?? raw.openchamber_base_url ?? raw.openchamberBaseUrl;
|
|
9832
|
+
const openCodeBaseUrlConfigured = rawOpenCodeBaseUrl !== void 0 && rawOpenCodeBaseUrl !== null && String(rawOpenCodeBaseUrl).trim() !== "";
|
|
9833
|
+
const openChamberBaseUrlConfigured = rawOpenChamberBaseUrl !== void 0 && rawOpenChamberBaseUrl !== null && String(rawOpenChamberBaseUrl).trim() !== "";
|
|
9595
9834
|
const location = isPlainObject(webhook.location) ? webhook.location : {};
|
|
9596
9835
|
const events = Array.isArray(webhook.events) && webhook.events.length > 0 ? [...new Set(webhook.events.map((event) => String(event || "").trim()).filter(Boolean))] : [...CLICKUP_WEBHOOK_EVENTS];
|
|
9597
9836
|
const ignoredStatuses = Array.isArray(routing.ignored_statuses) && routing.ignored_statuses.length > 0 ? routing.ignored_statuses : CLICKUP_WEBHOOK_TERMINAL_STATUSES;
|
|
@@ -9603,7 +9842,8 @@ function normalizeClickUpWebhookConfig(rawClickUp = null, worktree = process.cwd
|
|
|
9603
9842
|
apiToken: String(raw.api_token || raw.apiToken || "").trim(),
|
|
9604
9843
|
log: normalizeClickUpWebhookLogLevel(raw.log),
|
|
9605
9844
|
opencode: {
|
|
9606
|
-
baseUrl: normalizeOpenCodeBaseUrl(
|
|
9845
|
+
baseUrl: normalizeOpenCodeBaseUrl(rawOpenCodeBaseUrl),
|
|
9846
|
+
baseUrlConfigured: openCodeBaseUrlConfigured,
|
|
9607
9847
|
promptDelivery: ["http", "direct"].includes(String(opencode.prompt_delivery || opencode.promptDelivery || raw.prompt_delivery || raw.promptDelivery || "").trim().toLowerCase()) ? "http" : "sdk",
|
|
9608
9848
|
acceptPromptAdmission: opencode.accept_prompt_admission === true || opencode.acceptPromptAdmission === true || raw.accept_prompt_admission === true || raw.acceptPromptAdmission === true,
|
|
9609
9849
|
startupReconciliationDelayMs: normalizeNonNegativeInteger(
|
|
@@ -9611,6 +9851,10 @@ function normalizeClickUpWebhookConfig(rawClickUp = null, worktree = process.cwd
|
|
|
9611
9851
|
CLICKUP_WEBHOOK_STARTUP_RECONCILIATION_DELAY_MS
|
|
9612
9852
|
)
|
|
9613
9853
|
},
|
|
9854
|
+
openchamber: {
|
|
9855
|
+
baseUrl: normalizeOpenCodeBaseUrl(rawOpenChamberBaseUrl, ""),
|
|
9856
|
+
baseUrlConfigured: openChamberBaseUrlConfigured
|
|
9857
|
+
},
|
|
9614
9858
|
webhook: {
|
|
9615
9859
|
publicUrl: String(webhook.public_url || webhook.publicUrl || "").trim(),
|
|
9616
9860
|
bindHost: String(webhook.bind_host || webhook.bindHost || "127.0.0.1").trim(),
|
|
@@ -9652,6 +9896,13 @@ function normalizeClickUpWebhookConfig(rawClickUp = null, worktree = process.cwd
|
|
|
9652
9896
|
errors.push("clickup.opencode.base_url must be a valid URL");
|
|
9653
9897
|
}
|
|
9654
9898
|
}
|
|
9899
|
+
if (config.openchamber.baseUrl) {
|
|
9900
|
+
try {
|
|
9901
|
+
new URL(config.openchamber.baseUrl);
|
|
9902
|
+
} catch {
|
|
9903
|
+
errors.push("clickup.openchamber.base_url must be a valid URL");
|
|
9904
|
+
}
|
|
9905
|
+
}
|
|
9655
9906
|
const missingEvents = CLICKUP_WEBHOOK_EVENTS.filter((event) => !config.webhook.events.includes(event));
|
|
9656
9907
|
if (missingEvents.length > 0) errors.push(`clickup.webhook.events missing: ${missingEvents.join(", ")}`);
|
|
9657
9908
|
if (config.routing.targetAgent !== "workflow_product_manager") errors.push("clickup.routing.target_agent must be workflow_product_manager");
|
|
@@ -10008,23 +10259,29 @@ function rememberClickUpWebhookEvent(state = {}, eventKey, limit = 200) {
|
|
|
10008
10259
|
if (recent.includes(eventKey)) return { duplicate: true, state };
|
|
10009
10260
|
return { duplicate: false, state: { ...state, recentEventKeys: [...recent, eventKey].slice(-limit) } };
|
|
10010
10261
|
}
|
|
10011
|
-
function
|
|
10012
|
-
|
|
10013
|
-
if (!ledgerPath || !fs5.existsSync(ledgerPath)) return processed;
|
|
10262
|
+
function readClickUpCommentLedgerEntries(ledgerPath) {
|
|
10263
|
+
if (!ledgerPath || !fs5.existsSync(ledgerPath)) return [];
|
|
10014
10264
|
try {
|
|
10015
10265
|
const raw = fs5.readFileSync(ledgerPath, "utf8");
|
|
10266
|
+
const entries = [];
|
|
10016
10267
|
for (const [index, line] of raw.split(/\r?\n/).entries()) {
|
|
10017
10268
|
if (!line.trim()) continue;
|
|
10018
10269
|
try {
|
|
10019
|
-
|
|
10020
|
-
if (entry?.key) processed.add(String(entry.key));
|
|
10270
|
+
entries.push(JSON.parse(line));
|
|
10021
10271
|
} catch (error) {
|
|
10022
10272
|
throw new Error(`malformed ledger row ${index + 1}: ${error.message}`);
|
|
10023
10273
|
}
|
|
10024
10274
|
}
|
|
10275
|
+
return entries;
|
|
10025
10276
|
} catch (error) {
|
|
10026
10277
|
throw new Error(`ClickUp comment ledger unavailable: ${error.message}`);
|
|
10027
10278
|
}
|
|
10279
|
+
}
|
|
10280
|
+
function readClickUpCommentLedger(ledgerPath) {
|
|
10281
|
+
const processed = /* @__PURE__ */ new Set();
|
|
10282
|
+
for (const entry of readClickUpCommentLedgerEntries(ledgerPath)) {
|
|
10283
|
+
if (entry?.key) processed.add(String(entry.key));
|
|
10284
|
+
}
|
|
10028
10285
|
return processed;
|
|
10029
10286
|
}
|
|
10030
10287
|
function appendClickUpCommentLedgerEntry(ledgerPath, entry = {}) {
|
|
@@ -10090,6 +10347,35 @@ function recordClickUpCommentVersionProcessed({ ledgerPath, key, taskId, eventTy
|
|
|
10090
10347
|
recordedAt: at.toISOString()
|
|
10091
10348
|
});
|
|
10092
10349
|
}
|
|
10350
|
+
function clickUpWorktreeFailureCommentKey({ taskId, message } = {}) {
|
|
10351
|
+
const hash = crypto.createHash("sha256").update(String(message || "")).digest("hex").slice(0, 16);
|
|
10352
|
+
return [String(taskId || "unknown").trim() || "unknown", "worktree_failure", hash].join(":");
|
|
10353
|
+
}
|
|
10354
|
+
function shouldPostClickUpWorktreeFailureComment({ ledgerPath, taskId, message, now = /* @__PURE__ */ new Date(), windowMs = CLICKUP_WORKTREE_FAILURE_COMMENT_DEDUPE_MS } = {}) {
|
|
10355
|
+
const key = clickUpWorktreeFailureCommentKey({ taskId, message });
|
|
10356
|
+
const nowMs = now instanceof Date ? now.getTime() : new Date(now).getTime();
|
|
10357
|
+
for (const entry of readClickUpCommentLedgerEntries(ledgerPath).reverse()) {
|
|
10358
|
+
if (entry?.key !== key) continue;
|
|
10359
|
+
const postedAtMs = new Date(entry.postedAt || entry.recordedAt || entry.at || 0).getTime();
|
|
10360
|
+
if (Number.isFinite(postedAtMs) && Number.isFinite(nowMs) && nowMs - postedAtMs <= windowMs) {
|
|
10361
|
+
return { post: false, key, reason: "recent_duplicate" };
|
|
10362
|
+
}
|
|
10363
|
+
if (entry?.message === message) return { post: false, key, reason: "latest_equivalent" };
|
|
10364
|
+
}
|
|
10365
|
+
return { post: true, key };
|
|
10366
|
+
}
|
|
10367
|
+
function recordClickUpWorktreeFailureComment({ ledgerPath, taskId, message, posted = true, skippedReason = null, at = /* @__PURE__ */ new Date() } = {}) {
|
|
10368
|
+
appendClickUpCommentLedgerEntry(ledgerPath, {
|
|
10369
|
+
key: clickUpWorktreeFailureCommentKey({ taskId, message }),
|
|
10370
|
+
taskId,
|
|
10371
|
+
eventType: "worktree_failure",
|
|
10372
|
+
action: posted ? "worktree_failure_comment_posted" : "worktree_failure_comment_skipped",
|
|
10373
|
+
message,
|
|
10374
|
+
posted,
|
|
10375
|
+
skippedReason,
|
|
10376
|
+
postedAt: at.toISOString()
|
|
10377
|
+
});
|
|
10378
|
+
}
|
|
10093
10379
|
function clickUpTaskIdFromPayload(payload = {}) {
|
|
10094
10380
|
return String(payload.task_id || payload.taskId || payload.task?.id || payload.task?.task_id || "").trim();
|
|
10095
10381
|
}
|
|
@@ -10941,7 +11227,7 @@ async function withClickUpTaskRouteLock(taskId, operation) {
|
|
|
10941
11227
|
if (activeClickUpTaskRoutes.get(key) === current) activeClickUpTaskRoutes.delete(key);
|
|
10942
11228
|
}
|
|
10943
11229
|
}
|
|
10944
|
-
async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, worktree = process.cwd(), clickupClient, openCodeClient, sessionExists = openCodeSessionExists, createSession = createOpenCodeSession, sendSessionEvent = sendOpenCodeSessionEvent, ensureTaskWorktree =
|
|
11230
|
+
async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, worktree = process.cwd(), clickupClient, openCodeClient, sessionExists = openCodeSessionExists, createSession = createOpenCodeSession, sendSessionEvent = sendOpenCodeSessionEvent, ensureTaskWorktree = ensureClickUpTaskWorktreeForWebhook, verifySessionEventDelivery = verifyOpenCodeSessionEventDelivery, saveState = null, now = () => /* @__PURE__ */ new Date(), host = os.hostname(), commentLedgerPath = clickUpCommentLedgerPath(worktree) } = {}) {
|
|
10945
11231
|
const eventType = clickUpEventType(payload);
|
|
10946
11232
|
const eventKey = clickUpWebhookEventKey(payload);
|
|
10947
11233
|
const isStartupAssignmentReconciliation = payload?.startup_reconciliation === true && eventType === "taskAssigneeUpdated";
|
|
@@ -10997,7 +11283,7 @@ async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, w
|
|
|
10997
11283
|
const subtaskId = parentTaskId && parentTaskId !== taskId ? taskId : "";
|
|
10998
11284
|
let taskRoute;
|
|
10999
11285
|
try {
|
|
11000
|
-
taskRoute = ensureTaskWorktree({ baseWorktree: config.basePath, taskId, taskType, parentTaskId, subtaskId, existingMetadata: metadata, allowNonGitFallback: process.env.NODE_ENV === "test" || config.test === true });
|
|
11286
|
+
taskRoute = await ensureTaskWorktree({ baseWorktree: config.basePath, taskId, taskType, parentTaskId, subtaskId, existingMetadata: metadata, allowNonGitFallback: process.env.NODE_ENV === "test" || config.test === true, opencodeBaseUrl: config.opencode?.baseUrl, opencodeBaseUrlConfigured: config.opencode?.baseUrlConfigured === true, openchamberBaseUrl: config.openchamber?.baseUrl, openchamberBaseUrlConfigured: config.openchamber?.baseUrlConfigured === true, clickupClient, webhookWorktree: worktree });
|
|
11001
11287
|
} catch (error) {
|
|
11002
11288
|
const message = `Optima webhook could not create or reuse a task worktree for ${taskId}: ${error.message}`;
|
|
11003
11289
|
appendClickUpWebhookLocalLog(worktree, { type: "task_worktree_failed", taskId, message });
|
|
@@ -13156,7 +13442,7 @@ Follow-up: use optima_prompt_workflow with session_id '${sessionId}' to check in
|
|
|
13156
13442
|
}
|
|
13157
13443
|
};
|
|
13158
13444
|
}
|
|
13159
|
-
OptimaPlugin.__internals = { BUNDLE_AGENTS_DIR, BUNDLE_ASSETS_DIR, CLICKUP_DEFINITION_DOC_PARENT, activeClickUpWebhookLifecycleRegistry, cleanupManagedClickUpWebhook, deleteClickUpWebhookBestEffort, BUNDLE_POLICIES_DIR, buildClickUpApplyPayloadResult, buildClickUpCreateSubtasksPayload, buildClickUpStartTaskPayload, buildClickUpSummaryPayload, buildClickUpTransitionPayload, buildOptimaAgents, clickUpCommentLedgerKey, clickUpCommentMentionsProductManager, clickUpWebhookAuditLogDir, clickUpWebhookAuditLogPath, compactPromptPath, createClickUpApiClient, createTestClickUpApiClient, createOpenCodeSession, createOpenCodeSessionControl, deliveryEvidencePathForClickUpTask, ensureClickUpTaskWorktree, ensureClickUpWebhookSubscription, handleClickUpWebhookRequest, isClickUpDerivedWorktreeSibling, isClickUpSubtaskRoute, isClickUpWebhookStateActive, isSameOrNestedPath, normalizeClickUpWebhookConfig, normalizeClickUpWebhookLogLevel, normalizeOpenCodeBaseUrl, openCodeSessionExists, promptOpenCodeSessionControl, probeOpenCodeSessionControl, readClickUpCommentLedger, readClickUpWebhookState, readOpenCodeSessionControl, readOpenCodeSessionMessages, reconcileClickUpStartup, scheduleClickUpStartupReconciliation, waitForOpenCodeReadiness, recordClickUpCommentVersionProcessed, resyncClickUpWebhookForSignatureDrift, resolveIncludeFile, resolveIncludes, resolveOptimaPluginOptions, resolveSecretReference, routeClickUpWebhookEvent, sendOpenCodeSessionEvent, sendOpenCodeSessionEventDirect, summarizeOpenCodeMessages, verifyOpenCodeSessionEventDelivery, stableClickUpCommentVersionMarker, startClickUpWebhookListener, validateClickUpWebhookState, verifyClickUpSignature, writeClickUpWebhookState, decideClickUpStatusAction, deriveClickUpBranchName, deriveClickUpPendingSubtaskBranch, deriveClickUpPrTarget, deriveClickUpWorktree, determineClickUpMergeAuthority, ensureOptimaGitignoreRules, explicitSafeInputWorktree, finalApprovalAssignees, formatRepairResult, formatValidationResult, isIgnoredClickUpTaskType, isOptimaPluginPackageWorktree, isSafeWritableDirectory, loadHumansRegistry, mergeClickUpAgentMetadata, mergeClickUpSessionMetadata, migrateLegacyOptimaLayout, normalizeAgentMetadataJson, normalizeClickUpStatus, normalizeClickUpTaskType, normalizePromptResponseParts, normalizeWorkflowTaskPath, optimaRepairDependencies, parseClickUpSubtasksMarkdown, parseHumansRegistry, parseMarkdownArtifact, parseMarkdownSections, planOptimaRepair, preEstimateClickUpWork, readMarkdownArtifact, resolveHumanRoles, resolveSafeWorktree, safeWorktreeOrFailure, stripRawLogSections, validateMainWorkspaceBranchSafety, workflowFinalMessageFromPromptResponse };
|
|
13445
|
+
OptimaPlugin.__internals = { BUNDLE_AGENTS_DIR, BUNDLE_ASSETS_DIR, CLICKUP_DEFINITION_DOC_PARENT, activeClickUpWebhookLifecycleRegistry, cleanupManagedClickUpWebhook, deleteClickUpWebhookBestEffort, BUNDLE_POLICIES_DIR, buildClickUpApplyPayloadResult, buildClickUpCreateSubtasksPayload, buildClickUpStartTaskPayload, buildClickUpSummaryPayload, buildClickUpTransitionPayload, buildOptimaAgents, clickUpCommentLedgerKey, clickUpCommentMentionsProductManager, clickUpWebhookAuditLogDir, clickUpWebhookAuditLogPath, compactPromptPath, createClickUpApiClient, createTestClickUpApiClient, createOpenCodeSession, createOpenCodeSessionControl, deliveryEvidencePathForClickUpTask, ensureClickUpTaskWorktree, ensureClickUpTaskWorktreeForWebhook, ensureClickUpTaskWorktreeOpenChamber, ensureClickUpWebhookSubscription, handleClickUpWebhookRequest, isClickUpDerivedWorktreeSibling, isClickUpSubtaskRoute, isClickUpWebhookStateActive, isSameOrNestedPath, normalizeClickUpWebhookConfig, normalizeClickUpWebhookLogLevel, normalizeOpenCodeBaseUrl, openCodeSessionExists, promptOpenCodeSessionControl, probeOpenCodeSessionControl, readClickUpCommentLedger, readClickUpWebhookState, readOpenCodeSessionControl, readOpenCodeSessionMessages, reconcileClickUpStartup, registerOpenChamberClickUpWorktree, scheduleClickUpStartupReconciliation, syncOpenChamberWorktreeVisibility, waitForOpenCodeReadiness, recordClickUpCommentVersionProcessed, resyncClickUpWebhookForSignatureDrift, resolveIncludeFile, resolveIncludes, resolveOptimaPluginOptions, resolveSecretReference, routeClickUpWebhookEvent, sendOpenCodeSessionEvent, sendOpenCodeSessionEventDirect, summarizeOpenCodeMessages, verifyOpenCodeSessionEventDelivery, stableClickUpCommentVersionMarker, startClickUpWebhookListener, validateClickUpWebhookState, verifyClickUpSignature, writeClickUpWebhookState, decideClickUpStatusAction, deriveClickUpBranchName, deriveClickUpPendingSubtaskBranch, deriveClickUpPrTarget, deriveClickUpWorktree, determineClickUpMergeAuthority, ensureOptimaGitignoreRules, explicitSafeInputWorktree, finalApprovalAssignees, formatRepairResult, formatValidationResult, isIgnoredClickUpTaskType, isOptimaPluginPackageWorktree, isSafeWritableDirectory, loadHumansRegistry, mergeClickUpAgentMetadata, mergeClickUpSessionMetadata, migrateLegacyOptimaLayout, normalizeAgentMetadataJson, normalizeClickUpStatus, normalizeClickUpTaskType, normalizePromptResponseParts, normalizeWorkflowTaskPath, optimaRepairDependencies, parseClickUpSubtasksMarkdown, parseHumansRegistry, parseMarkdownArtifact, parseMarkdownSections, planOptimaRepair, preEstimateClickUpWork, readMarkdownArtifact, resolveHumanRoles, resolveSafeWorktree, safeWorktreeOrFailure, stripRawLogSections, validateMainWorkspaceBranchSafety, workflowFinalMessageFromPromptResponse };
|
|
13160
13446
|
|
|
13161
13447
|
// src/sanitize_cli.js
|
|
13162
13448
|
var { migrateLegacyOptimaLayout: migrateLegacyOptimaLayout2 } = OptimaPlugin.__internals;
|